aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRoy Ben Shabat <roy.mail.net@gmail.com>2025-09-12 17:39:45 +0300
committerRoy Ben Shabat <roy.mail.net@gmail.com>2025-09-12 17:39:45 +0300
commit7eb361c1201381c6ad88efa0ebed2c6595b45d13 (patch)
tree005c5e210d9352d3b26cbb8ab1f80139279b1898
parent8e15f292e2950cac71282923adc357f2abf8b306 (diff)
downloadTango-7eb361c1201381c6ad88efa0ebed2c6595b45d13.tar.gz
Tango-7eb361c1201381c6ad88efa0ebed2c6595b45d13.zip
Fixed FSE Gateway service with production slot cookie.
Implemented FSE dynamic csv job upload. extra inks. Implemented PPC dynamic csv job read. extra inks.
-rw-r--r--Software/Visual_Studio/FSE/Tango.FSE.BL/Services/GatewayService.cs1
-rw-r--r--Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml21
-rw-r--r--Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml.cs10
-rw-r--r--Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadViewVM.cs25
-rw-r--r--Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs35
-rw-r--r--Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/Models/JobModel.cs4
-rw-r--r--Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/ViewModels/JobsViewVM.cs14
-rw-r--r--Software/Visual_Studio/Tango.BL/Entities/Configuration.cs5
-rw-r--r--Software/Visual_Studio/Tango.BL/Helpers/SegmentsCsvHelper.cs509
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvDynamicReader.cs310
-rw-r--r--Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs20
-rw-r--r--Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj1
-rw-r--r--Software/Visual_Studio/Web/Tango.MachineService/Properties/AssemblyInfo.cs2
13 files changed, 488 insertions, 469 deletions
diff --git a/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/GatewayService.cs b/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/GatewayService.cs
index f7c603fe2..3d315f45d 100644
--- a/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/GatewayService.cs
+++ b/Software/Visual_Studio/FSE/Tango.FSE.BL/Services/GatewayService.cs
@@ -43,6 +43,7 @@ namespace Tango.FSE.BL.Services
using (HttpClient http = new HttpClient())
{
+ http.DefaultRequestHeaders.TryAddWithoutValidation("Cookie", "x-ms-routing-name=self");
GatewayClient client = new GatewayClient(ConfigurationManager.AppSettings.Get("GatewayUrl"), http);
var list = client.GetEnvironments(new EnvironmentsRequest()).Environments.OrderBy(x => x.DisplayIndex).ToList();
diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml b/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml
index c806be854..f159cadc9 100644
--- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml
+++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml
@@ -7,7 +7,7 @@
xmlns:local="clr-namespace:Tango.FSE.UI.Dialogs"
xmlns:mahapps="http://metro.mahapps.com/winfx/xaml/controls"
mc:Ignorable="d"
- Width="400" Height="500" d:DataContext="{d:DesignInstance Type=local:JobUploadViewVM, IsDesignTimeCreatable=False}" Background="{StaticResource FSE_PrimaryBackgroundLightBrush}" Foreground="{StaticResource FSE_PrimaryForegroundBrush}">
+ MinWidth="400" Width="Auto" Height="500" d:DataContext="{d:DesignInstance Type=local:JobUploadViewVM, IsDesignTimeCreatable=False}" Background="{StaticResource FSE_PrimaryBackgroundLightBrush}" Foreground="{StaticResource FSE_PrimaryForegroundBrush}">
<DockPanel Margin="10">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
@@ -36,7 +36,22 @@
<mahapps:NumericUpDown Margin="0 5 0 0" Padding="5 0 0 0" Minimum="1" Maximum="1000000" HasDecimals="False" Style="{StaticResource FSE_NumericUpDown_Flat_Dark}" HideUpDownButtons="True" Value="{Binding Length}"></mahapps:NumericUpDown>
</StackPanel>
- <UniformGrid Columns="4" Height="100">
+ <ItemsControl ItemsSource="{Binding Volumes}" Height="100">
+ <ItemsControl.ItemsPanel>
+ <ItemsPanelTemplate>
+ <UniformGrid Columns="{Binding Volumes.Count}" />
+ </ItemsPanelTemplate>
+ </ItemsControl.ItemsPanel>
+ <ItemsControl.ItemTemplate>
+ <DataTemplate>
+ <StackPanel>
+ <TextBlock HorizontalAlignment="Center" Foreground="{Binding LiquidType.LiquidTypeBrush}" Text="{Binding LiquidType.ShortName}"></TextBlock>
+ <mahapps:NumericUpDown Loaded="NumericUpDown_Loaded" PreviewKeyDown="Num_PreviewKeyDown" Minimum="0" Maximum="200" Margin="0 5 0 0" BorderBrush="{Binding LiquidType.LiquidTypeBrush}" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20" Height="60" HasDecimals="True" Style="{StaticResource FSE_NumericUpDown_Flat_Dark}" HideUpDownButtons="True" Value="{Binding Volume}"></mahapps:NumericUpDown>
+ </StackPanel>
+ </DataTemplate>
+ </ItemsControl.ItemTemplate>
+ </ItemsControl>
+ <!--<UniformGrid Columns="4" Height="100">
<StackPanel>
<TextBlock HorizontalAlignment="Center" Foreground="Cyan">C</TextBlock>
<mahapps:NumericUpDown x:Name="numC" PreviewKeyDown="Num_PreviewKeyDown" Minimum="0" Maximum="200" Margin="0 5 0 0" BorderBrush="Cyan" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20" Height="60" HasDecimals="True" Style="{StaticResource FSE_NumericUpDown_Flat_Dark}" HideUpDownButtons="True" Value="{Binding C}"></mahapps:NumericUpDown>
@@ -56,7 +71,7 @@
<TextBlock HorizontalAlignment="Center" Foreground="Black">K</TextBlock>
<mahapps:NumericUpDown x:Name="numK" PreviewKeyDown="Num_PreviewKeyDown" Minimum="0" Maximum="200" Margin="0 5 0 0" BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="20" Height="60" HasDecimals="True" Style="{StaticResource FSE_NumericUpDown_Flat_Dark}" HideUpDownButtons="True" Value="{Binding K}"></mahapps:NumericUpDown>
</StackPanel>
- </UniformGrid>
+ </UniformGrid>-->
</DockPanel>
</Grid>
</DockPanel>
diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml.cs
index 98d1d8878..f0dcffa65 100644
--- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml.cs
+++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadView.xaml.cs
@@ -28,10 +28,6 @@ namespace Tango.FSE.UI.Dialogs
{
InitializeComponent();
nums = new List<NumericUpDown>();
- nums.Add(numC);
- nums.Add(numM);
- nums.Add(numY);
- nums.Add(numK);
}
private void Num_PreviewKeyDown(object sender, KeyEventArgs e)
@@ -70,5 +66,11 @@ namespace Tango.FSE.UI.Dialogs
}
}
}
+
+ private void NumericUpDown_Loaded(object sender, RoutedEventArgs e)
+ {
+ NumericUpDown num = sender as NumericUpDown;
+ nums.Add(num);
+ }
}
}
diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadViewVM.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadViewVM.cs
index f12e4edf1..896175400 100644
--- a/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadViewVM.cs
+++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/Dialogs/JobUploadViewVM.cs
@@ -4,15 +4,23 @@ using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Tango.BL.Entities;
using Tango.Core.DI;
using Tango.FSE.Common;
using Tango.FSE.Common.Build;
+using Tango.FSE.Common.Connection;
using Tango.PPC.Shared.Statistics;
namespace Tango.FSE.UI.Dialogs
{
public class JobUploadViewVM : FSEDialogViewVM
{
+ public class LiquidVolume
+ {
+ public LiquidType LiquidType { get; set; }
+ public double Volume { get; set; }
+ }
+
private String _name;
public String Name
{
@@ -24,21 +32,18 @@ namespace Tango.FSE.UI.Dialogs
public ThreadFilterData SelectedRML { get; set; }
- public int Length { get; set; }
-
- public double C { get; set; }
-
- public double M { get; set; }
+ public List<LiquidVolume> Volumes { get; set; }
- public double Y { get; set; }
-
- public double K { get; set; }
+ public int Length { get; set; }
- public JobUploadViewVM()
+ public JobUploadViewVM(IMachineProvider machineProvider)
{
Rmls = new List<ThreadFilterData>();
- TangoIOC.Default.Inject(this);
+ Volumes = machineProvider.Machine.Configuration.NoneEmptyIdsPacks.OrderBy(x => x.LiquidType.PreferredIndex).Where(x => x.LiquidType.AvailableForStandardUser).Select(x => new LiquidVolume()
+ {
+ LiquidType = x.LiquidType
+ }).ToList();
OKText = "UPLOAD";
CancelText = "CANCEL";
diff --git a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs
index 33297d103..c7b52df81 100644
--- a/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs
+++ b/Software/Visual_Studio/FSE/Tango.FSE.UI/ViewModels/LayoutViewVM.cs
@@ -387,35 +387,34 @@ namespace Tango.FSE.UI.ViewModels
var lastSelectedRml = data.Rmls.FirstOrDefault(x => x.Guid == Settings.LastJobUploadThreadGuid);
if (lastSelectedRml == null) lastSelectedRml = data.Rmls.FirstOrDefault();
- var vm = await NotificationProvider.ShowDialog(new JobUploadViewVM() { Rmls = data.Rmls, SelectedRML = lastSelectedRml });
+ var vm = await NotificationProvider.ShowDialog(new JobUploadViewVM(MachineProvider) { Rmls = data.Rmls, SelectedRML = lastSelectedRml });
if (vm.DialogResult)
{
- TemporaryFile tmpFile = TemporaryManager.CreateFile(".csv");
+ TemporaryFile tmpFile = TemporaryManager.CreateImaginaryFile(".csv");
try
{
Settings.LastJobUploadThreadGuid = vm.SelectedRML.Guid;
Settings.Save();
- SegmentCsvModel model = new SegmentCsvModel();
- List<string> columnNames = model.GetType().GetProperties().Select(x => x.Name).ToList();
- CsvFile<SegmentCsvModel> csvFile = new CsvFile<SegmentCsvModel>(new CsvDestination(tmpFile), new CsvDefinition()
- {
- Columns = columnNames
- });
+ CsvDynamicWriter csv = new CsvDynamicWriter();
+
+ int cIndex = 0;
- model.Index = "1";
- model.ThreadName = vm.SelectedRML.Name;
- model.ColorSpace = ColorSpaces.Volume.ToDescription();
- model.Length = vm.Length.ToString();
- model.Cyan1 = vm.C.ToString();
- model.Magenta1 = vm.M.ToString();
- model.Yellow1 = vm.Y.ToString();
- model.Black1 = vm.K.ToString();
+ csv.Write(1, "Index", 0, cIndex++);
+ csv.Write(vm.SelectedRML.Name, "ThreadName", "", cIndex++);
+ csv.Write(vm.Length, "Length", 0, cIndex++);
+ csv.Write(ColorSpaces.Volume, "ColorSpace", ColorSpaces.Volume, cIndex++);
+ foreach (var liquidVolume in vm.Volumes)
+ {
+ if (liquidVolume.Volume > 0)
+ {
+ csv.Write(liquidVolume.Volume, liquidVolume.LiquidType.DisplayName, 0, cIndex);
+ }
+ }
- csvFile.Append(model);
- csvFile.Dispose();
+ csv.Save(tmpFile);
}
catch (Exception ex)
{
diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/Models/JobModel.cs b/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/Models/JobModel.cs
index 283c051e8..1b5562a59 100644
--- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/Models/JobModel.cs
+++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/Models/JobModel.cs
@@ -394,12 +394,12 @@ namespace Tango.PPC.Jobs.Models
[JsonIgnore]
public int NumberSpools
{
- get { return _numberSpools; }
+ get { return Math.Max(_numberSpools, 1); }
set
{
if (_numberSpools != value)
{
- _numberSpools = value;
+ _numberSpools = Math.Max(value, 1);
RaisePropertyChangedAuto();
OnNumberOfSpoolsChanged();
OnUpdateLengthhWeight();
diff --git a/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/ViewModels/JobsViewVM.cs b/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/ViewModels/JobsViewVM.cs
index 67a4cf972..db75b5e34 100644
--- a/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/ViewModels/JobsViewVM.cs
+++ b/Software/Visual_Studio/PPC/Modules/Tango.PPC.JobsV2/ViewModels/JobsViewVM.cs
@@ -960,6 +960,7 @@ namespace Tango.PPC.Jobs.ViewModels
job.Name = vm.Name;
job.NumberOfHeads = 1;
job.NumberOfUnits = 1;
+ job.NumberOfSpools = BuildProvider.MachineType == MachineTypes.Eureka ? 4 : 1;
job.SampleUnitsOrMeters = 1;
job.CreationDate = DateTime.UtcNow;
job.JobStatus = JobStatuses.Draft;
@@ -1045,6 +1046,7 @@ namespace Tango.PPC.Jobs.ViewModels
job.NumberOfHeads = 1;
job.NumberOfUnits = 1;
job.SampleUnitsOrMeters = 1;
+ job.NumberOfSpools = BuildProvider.MachineType == MachineTypes.Eureka ? 4 : 1;
job.CreationDate = DateTime.UtcNow;
job.JobStatus = JobStatuses.Draft;
job.JobType = JobTypes.Knitting;
@@ -1056,18 +1058,6 @@ namespace Tango.PPC.Jobs.ViewModels
job.WindingMethodGuid = Adapter.WindingMethods.FirstOrDefault().Guid;
job.SpoolTypeGuid = Settings.SpoolTypeGuid != null ? Settings.SpoolTypeGuid : Adapter.SpoolTypes.FirstOrDefault().Guid;
- if (BuildProvider.IsEureka)
- {
- job.EnableInterSegment = false;
- job.InterSegmentLength = 0;
-
- if (BuildProvider.MachineType == MachineTypes.Eureka)
- {
- job.NumberOfSpools = 4;
- }
- job.NumberOfUnits = 1;
- }
-
foreach (var segment in segments)
{
diff --git a/Software/Visual_Studio/Tango.BL/Entities/Configuration.cs b/Software/Visual_Studio/Tango.BL/Entities/Configuration.cs
index 62c20bcce..ae48b52c9 100644
--- a/Software/Visual_Studio/Tango.BL/Entities/Configuration.cs
+++ b/Software/Visual_Studio/Tango.BL/Entities/Configuration.cs
@@ -32,6 +32,11 @@ namespace Tango.BL.Entities
get { return IdsPacks.OrderBy(x => x.PackIndex).ToList(); }
}
+ public IdsPack GetIdsPackByColorMatch(String name)
+ {
+ return NoneEmptyIdsPacks.Where(x => x.LiquidType.AvailableForStandardUser).FirstOrDefault(x => x.LiquidType.Type.ToString().ToLower().Contains(name.ToLower()));
+ }
+
/// <summary>
/// Gets a collection of this configuration IDS packs where packs are not 'empty' and are supported by the specified <see cref="Rml"/>.
/// </summary>
diff --git a/Software/Visual_Studio/Tango.BL/Helpers/SegmentsCsvHelper.cs b/Software/Visual_Studio/Tango.BL/Helpers/SegmentsCsvHelper.cs
index 28c227a8d..ebc466bb7 100644
--- a/Software/Visual_Studio/Tango.BL/Helpers/SegmentsCsvHelper.cs
+++ b/Software/Visual_Studio/Tango.BL/Helpers/SegmentsCsvHelper.cs
@@ -27,114 +27,11 @@ namespace Tango.BL.Helpers
}
}
- public class SegmentCsvModel
- {
- [CsvOrder(0)]
- public String Index { get; set; }
-
- [CsvOrder(1)]
- public String ThreadName { get; set; }
-
- [CsvOrder(2)]
- public String ColorSpace { get; set; }
-
- [CsvOrder(3)]
- public String Length { get; set; }
-
- [CsvOrder(4)]
- public String CatalogName1 { get; set; }
-
- [CsvOrder(5)]
- public String CatalogItem1 { get; set; }
-
- [CsvOrder(6)]
- public String Red1 { get; set; }
- [CsvOrder(7)]
- public String Green1 { get; set; }
- [CsvOrder(8)]
- public String Blue1 { get; set; }
-
- [CsvOrder(9)]
- public String L1 { get; set; }
- [CsvOrder(10)]
- public String A1 { get; set; }
- [CsvOrder(11)]
- public String B1 { get; set; }
-
-
-
- [CsvOrder(12)]
- public String Cyan1 { get; set; }
- [CsvOrder(13)]
- public String Magenta1 { get; set; }
- [CsvOrder(14)]
- public String Yellow1 { get; set; }
- [CsvOrder(15)]
- public String Black1 { get; set; }
-
- [CsvOrder(16)]
- public String CatalogName2 { get; set; }
- [CsvOrder(17)]
- public String CatalogItem2 { get; set; }
-
- [CsvOrder(18)]
- public String Red2 { get; set; }
- [CsvOrder(19)]
- public String Green2 { get; set; }
- [CsvOrder(20)]
- public String Blue2 { get; set; }
-
- [CsvOrder(21)]
- public String L2 { get; set; }
- [CsvOrder(22)]
- public String A2 { get; set; }
- [CsvOrder(23)]
- public String B2 { get; set; }
-
-
- [CsvOrder(24)]
- public String Cyan2 { get; set; }
- [CsvOrder(25)]
- public String Magenta2 { get; set; }
- [CsvOrder(26)]
- public String Yellow2 { get; set; }
- [CsvOrder(27)]
- public String Black2 { get; set; }
-
- public bool IsRgb2NullOrEqual()
- {
- if (String.IsNullOrWhiteSpace(Red2) || String.IsNullOrWhiteSpace(Green2) || String.IsNullOrWhiteSpace(Blue2)) return true;
- if (Red1 == Red2 && Green1 == Green2 && Blue1 == Blue2) return true;
- return false;
- }
-
- internal bool IsVolumeNullOrEqual()
- {
- if (String.IsNullOrWhiteSpace(Cyan2) || String.IsNullOrWhiteSpace(Magenta2) || String.IsNullOrWhiteSpace(Yellow2) || String.IsNullOrWhiteSpace(Black2)) return true;
- if (Cyan1 == Cyan2 && Magenta1 == Magenta2 && Yellow1 == Yellow2 && Black1 == Black2) return true;
- return false;
- }
-
- internal bool IsCatalogNameNullOrEqual()
- {
- if (String.IsNullOrWhiteSpace(CatalogName2) || String.IsNullOrEmpty(CatalogItem2)) return true;
- if (CatalogName1 == CatalogName2 && CatalogItem1 == CatalogItem2) return true;
- return false;
- }
-
- internal bool IsLab2NullOrEqual()
- {
- if (String.IsNullOrWhiteSpace(L2) || String.IsNullOrWhiteSpace(A2) || String.IsNullOrWhiteSpace(B2)) return true;
- if (L1 == L2 && A1 == A2 && B1 == B2) return true;
- return false;
- }
- }
-
public static Task<SegmentsCsvResult> FromFile(String filePath, Machine machine, ObservablesContext context)
{
return Task.Factory.StartNew(() =>
{
- List<SegmentCsvModel> rows = CsvFile.Read<SegmentCsvModel>(new CsvSource(filePath)).ToList();
+ var csv = new CsvDynamicReader(filePath);
List<Segment> segments = new List<Segment>();
@@ -148,9 +45,20 @@ namespace Tango.BL.Helpers
String threadName = null;
- if (rows.Count > 0)
+ if (csv.Rows.Count > 0)
{
- threadName = rows.First().ThreadName;
+ if (csv.Rows.First().Exists("ThreadName"))
+ {
+ threadName = csv.Rows.First().Read("ThreadName", String.Empty);
+ }
+ else if (csv.Rows.First().Exists("Thread"))
+ {
+ threadName = csv.Rows.First().Read("Thread", String.Empty);
+ }
+ else if (csv.Rows.First().Exists("RML"))
+ {
+ threadName = csv.Rows.First().Read("RML", String.Empty);
+ }
}
Rml rml = null;
@@ -169,200 +77,69 @@ namespace Tango.BL.Helpers
}
}
- var idsPacks = machine.Configuration.NoneEmptyIdsPacks.ToList();
-
- var cyanIdsPack = idsPacks.FirstOrDefault(x => x.LiquidType.Type == LiquidTypes.Cyan);
- var magentaIdsPack = idsPacks.FirstOrDefault(x => x.LiquidType.Type == LiquidTypes.Magenta);
- var yellowIdsPack = idsPacks.FirstOrDefault(x => x.LiquidType.Type == LiquidTypes.Yellow);
- var blackIdsPack = idsPacks.FirstOrDefault(x => x.LiquidType.Type == LiquidTypes.Black);
-
int lineCount = 0;
- foreach (var row in rows)
+ foreach (var row in csv.Rows)
{
lineCount++;
try
{
+ //Name
Segment segment = new Segment();
segment.Name = "Standard Segment";
- double length;
- if (!double.TryParse(row.Length, out length))
+ //Length and Index
+ segment.Length = row.Read("Length", 100);
+ segment.SegmentIndex = row.Read("Index", lineCount);
+
+ //Color Space
+ ColorSpaces space = row.Read("ColorSpace", ColorSpaces.Volume);
+ ColorSpace colorSpace = colorSpaces.SingleOrDefault(x => x.Space == space);
+
+ BrushStop stop = new BrushStop();
+ stop.StopIndex = 1;
+ stop.ColorSpace = colorSpace;
+ stop.OffsetPercent = 0;
+ stop.Segment = segment;
+ segment.BrushStops.Add(stop);
+
+ //RGB
+ if (space == ColorSpaces.RGB)
{
- continue; //Ignore none indexed rows. Maybe there are many empty rows at the end to the document...
+ stop.Red = row.Read("RGB R", 0);
+ stop.Green = row.Read("RGB G", 0);
+ stop.Blue = row.Read("RGB B", 0);
}
- int segmentIndex;
- if (!int.TryParse(row.Index, out segmentIndex))
+ //LAB
+ if (space == ColorSpaces.LAB)
{
- throw new InvalidOperationException($"Value Index '{row.Index}' should be a number on line'{lineCount}'.");
+ stop.L = row.Read("L", 0);
+ stop.A = row.Read("A", 0);
+ stop.B = row.Read("B", 0);
}
- segment.Length = length;
- segment.SegmentIndex = segmentIndex;
-
- ColorSpace colorSpace = colorSpaces.SingleOrDefault(x => x.Name == row.ColorSpace);
-
- if (colorSpace == null) throw new InvalidOperationException($"Color space '{row.ColorSpace}' not found on line '{lineCount}'.");
-
- BrushStop stop1 = new BrushStop();
- stop1.StopIndex = 1;
- stop1.ColorSpace = colorSpace;
- stop1.OffsetPercent = 0;
- stop1.Segment = segment;
- segment.BrushStops.Add(stop1);
-
- if (colorSpace.Space == ColorSpaces.RGB)
+ //Volumes
+ if (space == ColorSpaces.Volume)
{
- int red1;
- if (!int.TryParse(row.Red1, out red1))
- {
- throw new InvalidOperationException($"Value Red1 '{row.Red1}' should be a number on line'{lineCount}'.");
- }
- stop1.Red = red1;
- if (stop1.Red < 0 || stop1.Red > 255) throw new InvalidOperationException($"Value red1 '{row.Red1}' is out of range on line '{lineCount}'.");
-
- int green1;
- if (!int.TryParse(row.Green1, out green1))
- {
- throw new InvalidOperationException($"Value Green1 '{row.Green1}' should be a number on line'{lineCount}'.");
- }
- stop1.Green = green1;
- if (stop1.Green < 0 || stop1.Green > 255) throw new InvalidOperationException($"Value green1 '{row.Green1}' is out of range on line '{lineCount}'.");
-
- int blue1;
- if (!int.TryParse(row.Blue1, out blue1))
- {
- throw new InvalidOperationException($"Value Blue1 '{row.Blue1}' should be a number on line'{lineCount}'.");
- }
- stop1.Blue = blue1;
- if (stop1.Blue < 0 || stop1.Blue > 255) throw new InvalidOperationException($"Value blue1 '{row.Blue1}' is out of range on line '{lineCount}'.");
-
- if (!row.IsRgb2NullOrEqual())
+ foreach (var idsPack in machine.Configuration.NoneEmptyIdsPacks.Where(x => x.LiquidType.AvailableForStandardUser))
{
- BrushStop stop2 = new BrushStop();
- stop2.StopIndex = 2;
- stop2.ColorSpace = stop1.ColorSpace;
- stop2.OffsetPercent = 100;
-
- int red2;
- if (!int.TryParse(row.Red2, out red2))
- {
- throw new InvalidOperationException($"Value Red2 '{row.Red2}' should be a number on line'{lineCount}'.");
- }
- stop2.Red = red2;
- if (stop2.Red < 0 || stop2.Red > 255) throw new InvalidOperationException($"Value red2 '{row.Red2}' is out of range on line '{lineCount}'.");
-
- int green2;
- if (!int.TryParse(row.Green2, out green2))
- {
- throw new InvalidOperationException($"Value Green2 '{row.Green2}' should be a number on line'{lineCount}'.");
- }
- stop2.Green = green2;
- if (stop2.Green < 0 || stop2.Green > 255) throw new InvalidOperationException($"Value green2 '{row.Green2}' is out of range on line '{lineCount}'.");
-
- int blue2;
- if (!int.TryParse(row.Blue2, out blue2))
- {
- throw new InvalidOperationException($"Value Blue2 '{row.Blue2}' should be a number on line'{lineCount}'.");
- }
- stop2.Blue = blue2;
- if (stop2.Blue < 0 || stop2.Blue > 255) throw new InvalidOperationException($"Value blue2 '{row.Blue2}' is out of range on line '{lineCount}'.");
-
- segment.BrushStops.Add(stop2);
- stop2.Segment = segment;
+ var volume = row.Read(idsPack.LiquidType.DisplayName, 0);
+ stop.SetVolume(idsPack.PackIndex, volume);
}
}
- else if (colorSpace.Space == ColorSpaces.Volume)
- {
- double cyan1;
- if (!double.TryParse(row.Cyan1, out cyan1))
- {
- throw new InvalidOperationException($"Value Cyan1 '{row.Cyan1}' should be a number on line'{lineCount}.'!");
- }
- if (cyan1 < 0 || cyan1 > MAX_VOLUME) throw new InvalidOperationException($"Value Cyan1 '{row.Cyan1}' is out of range on line '{lineCount}.'!");
- stop1.SetVolume(cyanIdsPack.PackIndex, cyan1);
- double magenta1;
- if (!double.TryParse(row.Magenta1, out magenta1))
- {
- throw new InvalidOperationException($"Value Magenta1 '{row.Magenta1}' should be a number on line'{lineCount}.'!");
- }
- if (magenta1 < 0 || magenta1 > MAX_VOLUME) throw new InvalidOperationException($"Value Magenta1 '{row.Magenta1}' is out of range on line '{lineCount}.'!");
-
- stop1.SetVolume(magentaIdsPack.PackIndex, magenta1);
-
- double yellow1 = double.Parse(row.Yellow1);
- if (!double.TryParse(row.Yellow1, out yellow1))
- {
- throw new InvalidOperationException($"Value Yellow1 '{row.Yellow1}' should be a number on line'{lineCount}.'!");
- }
- if (yellow1 < 0 || yellow1 > MAX_VOLUME) throw new InvalidOperationException($"Value Yellow1 '{row.Yellow1}' is out of range on line '{lineCount}.'!");
-
- stop1.SetVolume(yellowIdsPack.PackIndex, yellow1);
-
- double black1 = double.Parse(row.Black1);
- if (!double.TryParse(row.Black1, out black1))
- {
- throw new InvalidOperationException($"Value Black1 '{row.Black1}' should be a number on line'{lineCount}.'!");
- }
- if (black1 < 0 || black1 > MAX_VOLUME) throw new InvalidOperationException($"Value Black1 '{row.Black1}' is out of range on line '{lineCount}.'!");
-
- stop1.SetVolume(blackIdsPack.PackIndex, black1);
-
- if (!row.IsVolumeNullOrEqual())
- {
- BrushStop stop2 = new BrushStop();
- stop2.StopIndex = 2;
- stop2.ColorSpace = stop1.ColorSpace;
- stop2.OffsetPercent = 100;
-
- double cyan2;
- if (!double.TryParse(row.Cyan2, out cyan2))
- {
- throw new InvalidOperationException($"Value Cyan2 '{row.Cyan2}' should be a number on line'{lineCount}.'!");
- }
- if (cyan2 < 0 || cyan2 > MAX_VOLUME) throw new InvalidOperationException($"Value Cyan2 '{row.Cyan2}' is out of range on line '{lineCount}.'!");
- stop2.SetVolume(cyanIdsPack.PackIndex, cyan2);
-
- double magenta2;
- if (!double.TryParse(row.Magenta2, out magenta2))
- {
- throw new InvalidOperationException($"Value Magenta2 '{row.Magenta2}' should be a number on line'{lineCount}.'!");
- }
- if (magenta2 < 0 || magenta2 > MAX_VOLUME) throw new InvalidOperationException($"Value Magenta2 '{row.Magenta2}' is out of range on line '{lineCount}.'!");
-
- stop2.SetVolume(magentaIdsPack.PackIndex, magenta2);
-
- double yellow2 = double.Parse(row.Yellow2);
- if (!double.TryParse(row.Yellow2, out yellow2))
- {
- throw new InvalidOperationException($"Value Yellow2 '{row.Yellow2}' should be a number on line'{lineCount}.'!");
- }
- if (yellow2 < 0 || yellow2 > MAX_VOLUME) throw new InvalidOperationException($"Value Yellow2 '{row.Yellow2}' is out of range on line '{lineCount}.'!");
-
- stop2.SetVolume(yellowIdsPack.PackIndex, yellow2);
-
- double black2 = double.Parse(row.Black2);
- if (!double.TryParse(row.Black2, out black2))
- {
- throw new InvalidOperationException($"Value Black2 '{row.Black2}' should be a number on line'{lineCount}.'!");
- }
- if (black2 < 0 || black2 > MAX_VOLUME) throw new InvalidOperationException($"Value Black2 '{row.Black2}' is out of range on line '{lineCount}.'!");
-
- stop2.SetVolume(blackIdsPack.PackIndex, black2);
-
- segment.BrushStops.Add(stop2);
- stop2.Segment = segment;
- }
- }
- else if (colorSpace.Space == ColorSpaces.Catalog)
+ //Catalog
+ if (space == ColorSpaces.Catalog)
{
- var catalog = catalogs.FirstOrDefault(x => x.Name == row.CatalogName1);
+ var catalogName = row.Read("Catalog", String.Empty);
+ var catalogItemName = row.Read("Catalog Item", String.Empty);
+
+ var catalog = catalogs.FirstOrDefault(x => x.Name == catalogName);
if (catalog == null)
{
- throw new InvalidOperationException($"Catalog '{row.CatalogName1}' not found on line '{lineCount}'.");
+ throw new InvalidOperationException($"Catalog '{catalogName}' not found on line '{lineCount}'.");
}
if (machine.SiteGuid != null)
@@ -373,93 +150,17 @@ namespace Tango.BL.Helpers
}
}
- var item = catalog.ColorCatalogsGroups.SelectMany(x => x.ColorCatalogsItems).FirstOrDefault(x => x.Name == row.CatalogItem1);
+ var item = catalog.ColorCatalogsGroups.SelectMany(x => x.ColorCatalogsItems).FirstOrDefault(x => x.Name == catalogItemName);
if (item == null)
{
- throw new InvalidOperationException($"Catalog item '{row.CatalogItem1}' not found on catalog '{catalog.Name}' on line '{lineCount}'.");
+ throw new InvalidOperationException($"Catalog item '{catalogItemName}' not found on catalog '{catalog.Name}' on line '{lineCount}'.");
}
- stop1.ColorCatalog = catalog;
- stop1.ColorCatalogsItem = item;
- stop1.Color = item.Color;
- if (!row.IsCatalogNameNullOrEqual())
- {
- var catalog2 = catalogs.FirstOrDefault(x => x.Name == row.CatalogName2);
- if (catalog2 == null)
- {
- throw new InvalidOperationException($"Catalog '{row.CatalogName2}' not found on line '{lineCount}'.");
- }
-
- if (machine.SiteGuid != null)
- {
- if (!context.SitesCatalogs.Any(x => x.SiteGuid == machine.SiteGuid && x.ColorCatalogGuid == catalog2.Guid))
- {
- throw new InvalidOperationException($"Catalog '{catalog2.Name}' is not assigned for this machine's site.");
- }
- }
-
- var item2 = catalog2.ColorCatalogsGroups.SelectMany(x => x.ColorCatalogsItems).FirstOrDefault(x => x.Name == row.CatalogItem2);
- if (item2 == null)
- {
- throw new InvalidOperationException($"Catalog item '{row.CatalogItem2}' not found on catalog '{catalog2.Name}' on line '{lineCount}'.");
- }
- BrushStop stop2 = new BrushStop();
- stop2.StopIndex = 2;
- stop2.ColorSpace = stop1.ColorSpace;
- stop2.ColorCatalog = catalog2;
- stop2.ColorCatalogsItem = item2;
- stop2.Color = item2.Color;
- stop2.OffsetPercent = 100;
- segment.BrushStops.Add(stop2);
- stop2.Segment = segment;
- }
+ stop.ColorCatalog = catalog;
+ stop.ColorCatalogsItem = item;
+ stop.Color = item.Color;
}
- else if (colorSpace.Space == ColorSpaces.LAB)
- {
- double number;
- if (!double.TryParse(row.L1, out number))
- throw new InvalidOperationException($"Value L1 '{row.L1}' should be a number on line'{lineCount}'.");
- stop1.L = number;
- if (stop1.L < 0 || stop1.L > 100) throw new InvalidOperationException($"Value L1 '{row.L1}' is out of range on line '{lineCount}'.");
-
- if (!double.TryParse(row.A1, out number))
- throw new InvalidOperationException($"Value A1 '{row.A1}' should be a number on line'{lineCount}'.");
- stop1.A = number;
- if (stop1.A < -128 || stop1.A > 128) throw new InvalidOperationException($"Value A1 '{row.A1}' is out of range on line '{lineCount}'.");
-
- if (!double.TryParse(row.B1, out number))
- throw new InvalidOperationException($"Value B1 '{row.B1}' should be a number on line'{lineCount}'.");
- stop1.B = number;
- if (stop1.B < -128 || stop1.B > 128) throw new InvalidOperationException($"Value B1 '{row.B1}' is out of range on line '{lineCount}'.");
- if (!row.IsLab2NullOrEqual())
- {
- BrushStop stop2 = new BrushStop();
- stop2.StopIndex = 2;
- stop2.ColorSpace = stop1.ColorSpace;
- stop2.OffsetPercent = 100;
- double l2;
- if (!double.TryParse(row.L2, out l2))
- throw new InvalidOperationException($"Value L2 '{row.L2}' should be a number on line'{lineCount}'.");
- stop2.L = l2;
- if (stop2.L < 0 || stop2.L > 100) throw new InvalidOperationException($"Value L2 '{row.L2}' is out of range on line '{lineCount}'.");
-
- double a2;
- if (!double.TryParse(row.A2, out a2))
- throw new InvalidOperationException($"Value A2 '{row.A2}' should be a number on line'{lineCount}'.");
- stop2.A = a2;
- if (stop2.A < -128 || stop2.A > 128) throw new InvalidOperationException($"Value A2 '{row.A2}' is out of range on line '{lineCount}'.");
-
- double b2;
- if (!double.TryParse(row.B2, out b2))
- throw new InvalidOperationException($"Value B2 '{row.B2}' should be a number on line'{lineCount}'.");
- stop2.B = b2;
- if (stop2.B < -128 || stop2.B > 128) throw new InvalidOperationException($"Value B2 '{row.B2}' is out of range on line '{lineCount}'.");
-
- segment.BrushStops.Add(stop2);
- stop2.Segment = segment;
- }
- }
segments.Add(segment);
}
catch (Exception ex)
@@ -480,79 +181,49 @@ namespace Tango.BL.Helpers
{
await Task.Factory.StartNew(() =>
{
- SegmentCsvModel model = new SegmentCsvModel();
- List<string> columnNames = model.GetType().GetProperties().Select(x => x.Name).ToList();
- CsvFile<SegmentCsvModel> csvFile = new CsvFile<SegmentCsvModel>(new CsvDestination(filePath), new CsvDefinition()
- {
- Columns = columnNames
- });
+ CsvDynamicWriter csv = new CsvDynamicWriter();
+
+ int cIndex = 0;
+
foreach (var segment in segments)
{
- SegmentCsvModel csvmodel = new SegmentCsvModel();
- csvmodel.Index = segment.SegmentIndex.ToString();
- csvmodel.Length = segment.Length.ToString();
- if (segment.BrushStops.Count > 2) throw new InvalidOperationException($"Cannot save more than two brush stops!");
+ csv.Write(segment.SegmentIndex, "Index", 0, cIndex++);
+ csv.Write(segment.Length, "Length", 0, cIndex++);
+
//save first brush stop
- if (segment.BrushStops.Count > 0)
+ var stop = segment.BrushStops[0];
+
+ csv.Write(stop.ColorSpace.Space, "ColorSpace", ColorSpaces.Volume, cIndex++);
+
+ if (stop.BrushColorSpace == ColorSpaces.RGB)
{
- var brushStop1 = segment.BrushStops[0];
- csvmodel.ColorSpace = brushStop1.ColorSpace.Name;
- if (csvmodel.ColorSpace == ColorSpaces.RGB.ToDescription())
- {
- csvmodel.Red1 = brushStop1.Red.ToString();
- csvmodel.Green1 = brushStop1.Green.ToString();
- csvmodel.Blue1 = brushStop1.Blue.ToString();
- }
- else if (csvmodel.ColorSpace == ColorSpaces.Volume.ToDescription())
- {
- csvmodel.Cyan1 = brushStop1.GetVolume(LiquidTypes.Cyan).ToString();
- csvmodel.Magenta1 = brushStop1.GetVolume(LiquidTypes.Magenta).ToString();
- csvmodel.Yellow1 = brushStop1.GetVolume(LiquidTypes.Yellow).ToString();
- csvmodel.Black1 = brushStop1.GetVolume(LiquidTypes.Black).ToString();
- }
- else if (csvmodel.ColorSpace == ColorSpaces.Catalog.ToDescription())
- {
- csvmodel.CatalogName1 = brushStop1.ColorCatalog.Name;
- csvmodel.CatalogItem1 = brushStop1.ColorCatalogsItem.Name;
- }
- else if (csvmodel.ColorSpace == ColorSpaces.LAB.ToDescription())
- {
- csvmodel.L1 = brushStop1.L.ToString();
- csvmodel.A1 = brushStop1.A.ToString();
- csvmodel.B1 = brushStop1.B.ToString();
- }
+ csv.Write(stop.Red, "RGB R", 0, cIndex++);
+ csv.Write(stop.Green, "RGB G", 0, cIndex++);
+ csv.Write(stop.Blue, "RGB B", 0, cIndex++);
}
- if (segment.BrushStops.Count > 1)
+ else if (stop.BrushColorSpace == ColorSpaces.LAB)
{
- var brushStop2 = segment.BrushStops[1];
- if (csvmodel.ColorSpace == ColorSpaces.RGB.ToDescription())
- {
- csvmodel.Red2 = brushStop2.Red.ToString();
- csvmodel.Green2 = brushStop2.Green.ToString();
- csvmodel.Blue2 = brushStop2.Blue.ToString();
- }
- else if (csvmodel.ColorSpace == ColorSpaces.Volume.ToDescription())
- {
- csvmodel.Cyan2 = brushStop2.GetVolume(LiquidTypes.Cyan).ToString();
- csvmodel.Magenta2 = brushStop2.GetVolume(LiquidTypes.Magenta).ToString();
- csvmodel.Yellow2 = brushStop2.GetVolume(LiquidTypes.Yellow).ToString();
- csvmodel.Black2 = brushStop2.GetVolume(LiquidTypes.Black).ToString();
- }
- else if (csvmodel.ColorSpace == ColorSpaces.Catalog.ToDescription())
- {
- csvmodel.CatalogName2 = brushStop2.ColorCatalog.Name;
- csvmodel.CatalogItem2 = brushStop2.ColorCatalogsItem.Name;
- }
- else if (csvmodel.ColorSpace == ColorSpaces.LAB.ToDescription())
+ csv.Write(stop.L, "L", 0, cIndex++);
+ csv.Write(stop.A, "A", 0, cIndex++);
+ csv.Write(stop.B, "B", 0, cIndex++);
+ }
+ else if (stop.BrushColorSpace == ColorSpaces.Catalog)
+ {
+ csv.Write(stop.ColorCatalog.Name, "Catalog", String.Empty, cIndex++);
+ csv.Write(stop.ColorCatalogsItem.Name, "Catalog Item", String.Empty, cIndex++);
+ }
+ else
+ {
+ foreach (var liquidVolume in stop.LiquidVolumesOrderedPigmentedForStandardUser)
{
- csvmodel.L2 = brushStop2.L.ToString();
- csvmodel.A2 = brushStop2.A.ToString();
- csvmodel.B2 = brushStop2.B.ToString();
+ csv.Write(liquidVolume.Volume, liquidVolume.IdsPack.LiquidType.DisplayName, 0, cIndex++);
}
}
- csvFile.Append(csvmodel);
+ csv.Next();
}
+
+ csv.Save(filePath);
});
}
}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvDynamicReader.cs b/Software/Visual_Studio/Tango.CSV/CsvDynamicReader.cs
new file mode 100644
index 000000000..33ba859eb
--- /dev/null
+++ b/Software/Visual_Studio/Tango.CSV/CsvDynamicReader.cs
@@ -0,0 +1,310 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+
+namespace Tango.CSV
+{
+ /// <summary>
+ /// CSV reader that supports unknown schemas and typed access with defaults.
+ /// - Case-insensitive column lookup
+ /// - Robust CSV parsing (RFC-4180 style: quotes, commas, escaped quotes)
+ /// - Typed Read with defaults: string, int, double, bool, and enums
+ /// </summary>
+ public sealed class CsvDynamicReader
+ {
+ private readonly Dictionary<string, int> _colIndex;
+ private readonly List<Row> _rows;
+
+ public IReadOnlyList<Row> Rows => _rows;
+
+ public char Delimiter { get; }
+
+ public CsvDynamicReader(string path, char delimiter = ',')
+ {
+ if (path == null) throw new ArgumentNullException(nameof(path));
+ if (!File.Exists(path)) throw new FileNotFoundException("CSV file not found.", path);
+
+ Delimiter = delimiter;
+ _colIndex = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
+ _rows = new List<Row>();
+
+ using (var sr = new StreamReader(path))
+ {
+ // Read to first non-empty line for headers
+ string headerLine;
+ do
+ {
+ headerLine = sr.ReadLine();
+ if (headerLine == null)
+ throw new InvalidDataException("CSV file has no header row.");
+ } while (string.IsNullOrWhiteSpace(headerLine));
+
+ var headers = ParseCsvLine(headerLine, Delimiter);
+ for (int i = 0; i < headers.Count; i++)
+ {
+ var clean = CleanHeader(headers[i]);
+ if (!_colIndex.ContainsKey(clean))
+ _colIndex.Add(clean, i);
+ // If duplicate header name appears, first one wins.
+ }
+
+ // Read all rows
+ string line;
+ while ((line = sr.ReadLine()) != null)
+ {
+ if (line.Length == 0) continue; // skip empty
+ var fields = ParseCsvLine(line, Delimiter).ToArray();
+ _rows.Add(new Row(this, fields));
+ }
+ }
+ }
+
+ internal bool TryGetIndex(string columnName, out int index)
+ {
+ if (columnName == null) { index = -1; return false; }
+ return _colIndex.TryGetValue(columnName.Trim(), out index);
+ }
+
+ private static string CleanHeader(string s)
+ {
+ if (string.IsNullOrEmpty(s)) return string.Empty;
+ // Trim quotes if header was quoted
+ var t = s.Trim();
+ if (t.Length >= 2 && t[0] == '"' && t[t.Length - 1] == '"')
+ {
+ t = t.Substring(1, t.Length - 2).Replace("\"\"", "\"");
+ }
+ // Remove BOM if present
+ t = t.Trim('\uFEFF').Trim();
+ return t;
+ }
+
+ /// <summary>
+ /// RFC-4180-ish CSV line parser: supports quoted fields, commas, escaped quotes ("").
+ /// </summary>
+ private static List<string> ParseCsvLine(string line, char delimiter)
+ {
+ var result = new List<string>();
+ if (line == null)
+ {
+ result.Add(string.Empty);
+ return result;
+ }
+
+ var sb = new System.Text.StringBuilder(line.Length);
+ bool inQuotes = false;
+
+ for (int i = 0; i < line.Length; i++)
+ {
+ var c = line[i];
+
+ if (inQuotes)
+ {
+ if (c == '"')
+ {
+ // Escaped quote?
+ if (i + 1 < line.Length && line[i + 1] == '"')
+ {
+ sb.Append('"');
+ i++; // skip next
+ }
+ else
+ {
+ inQuotes = false;
+ }
+ }
+ else
+ {
+ sb.Append(c);
+ }
+ }
+ else
+ {
+ if (c == '"')
+ {
+ inQuotes = true;
+ }
+ else if (c == delimiter)
+ {
+ result.Add(sb.ToString());
+ sb.Clear();
+ }
+ else
+ {
+ sb.Append(c);
+ }
+ }
+ }
+
+ result.Add(sb.ToString());
+ return result;
+ }
+
+ // ------- Row --------
+
+ public sealed class Row
+ {
+ private readonly CsvDynamicReader _reader;
+ private readonly string[] _values;
+
+ internal Row(CsvDynamicReader reader, string[] values)
+ {
+ _reader = reader;
+ _values = values ?? new string[0];
+ }
+
+ public bool Exists(string columnName)
+ {
+ int idx;
+ return _reader.TryGetIndex(columnName, out idx);
+ }
+
+ /// <summary>
+ /// Typed read with a default fallback. Works for string, int, double, bool, and enums.
+ /// Usage: var v = row.Read("Col", 0); var s = row.Read("Col","def"); var b = row.Read("Flag", false);
+ /// </summary>
+ public T Read<T>(string columnName, T defaultValue)
+ {
+ int idx;
+ if (!_reader.TryGetIndex(columnName, out idx))
+ return defaultValue;
+
+ var raw = (idx >= 0 && idx < _values.Length) ? _values[idx] : null;
+ return ConvertValue(raw, defaultValue);
+ }
+
+ // -------- Conversion helpers --------
+
+ private static T ConvertValue<T>(string raw, T defaultValue)
+ {
+ if (typeof(T) == typeof(string))
+ {
+ // For strings return raw as-is (trim optional)
+ object s = raw ?? (object)defaultValue ?? string.Empty;
+ return (T)s;
+ }
+
+ if (string.IsNullOrWhiteSpace(raw))
+ return defaultValue;
+
+ var trimmed = raw.Trim();
+
+ // int
+ if (typeof(T) == typeof(int))
+ {
+ int v;
+ if (int.TryParse(trimmed, NumberStyles.Integer, CultureInfo.InvariantCulture, out v))
+ return (T)(object)v;
+ return defaultValue;
+ }
+
+ // double
+ if (typeof(T) == typeof(double))
+ {
+ double v;
+ if (double.TryParse(trimmed, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out v))
+ return (T)(object)v;
+ return defaultValue;
+ }
+
+ // bool (accepts true/false, 1/0, yes/no, y/n)
+ if (typeof(T) == typeof(bool))
+ {
+ bool bv;
+ if (TryParseBool(trimmed, out bv))
+ return (T)(object)bv;
+ return defaultValue;
+ }
+
+ // Enums (case-insensitive; also accept underlying numeric)
+ var t = typeof(T);
+ if (t.IsEnum)
+ {
+ try
+ {
+ // Numeric?
+ var underlying = Enum.GetUnderlyingType(t);
+ object numericObj;
+ if (TryParseNumeric(trimmed, underlying, out numericObj))
+ {
+ var boxed = Enum.ToObject(t, numericObj);
+ return (T)boxed;
+ }
+
+ T parsed;
+ if (TryParseEnum(trimmed, out parsed))
+ return parsed;
+ }
+ catch { /* fall through to default */ }
+
+ return defaultValue;
+ }
+
+ // Fallback: try ChangeType
+ try
+ {
+ object any = System.Convert.ChangeType(trimmed, typeof(T), CultureInfo.InvariantCulture);
+ return (T)any;
+ }
+ catch
+ {
+ return defaultValue;
+ }
+ }
+
+ private static bool TryParseBool(string s, out bool value)
+ {
+ // Standard
+ if (bool.TryParse(s, out value)) return true;
+
+ // Common variants
+ switch (s.Trim().ToLowerInvariant())
+ {
+ case "1":
+ case "yes":
+ case "y":
+ case "true":
+ case "t":
+ value = true; return true;
+ case "0":
+ case "no":
+ case "n":
+ case "false":
+ case "f":
+ value = false; return true;
+ }
+ value = false; return false;
+ }
+
+ private static bool TryParseNumeric(string s, Type numericType, out object boxed)
+ {
+ if (numericType == typeof(byte)) { byte v; if (byte.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(sbyte)) { sbyte v; if (sbyte.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(short)) { short v; if (short.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(ushort)) { ushort v; if (ushort.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(int)) { int v; if (int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(uint)) { uint v; if (uint.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(long)) { long v; if (long.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+ if (numericType == typeof(ulong)) { ulong v; if (ulong.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out v)) { boxed = v; return true; } }
+
+ boxed = null;
+ return false;
+ }
+
+ private static bool TryParseEnum<T>(string s, out T value)
+ {
+ try
+ {
+ value = (T)Enum.Parse(typeof(T), s, ignoreCase: true);
+ return true;
+ }
+ catch
+ {
+ value = default(T);
+ return false;
+ }
+ }
+ }
+ }
+}
diff --git a/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs b/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs
index 1c460d5e3..70024954e 100644
--- a/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs
+++ b/Software/Visual_Studio/Tango.CSV/CsvDynamicWriter.cs
@@ -24,6 +24,26 @@ namespace Tango.CSV
private readonly List<Dictionary<string, string>> _rows = new List<Dictionary<string, string>>();
private Dictionary<string, string> _currentRow = new Dictionary<string, string>();
+ public void Write(int value, string columnName, int defaultValue, int index)
+ {
+ Write(value.ToString(), columnName, defaultValue.ToString(), index);
+ }
+
+ public void Write(double value, string columnName, double defaultValue, int index)
+ {
+ Write(value.ToString(), columnName, defaultValue.ToString(), index);
+ }
+
+ public void Write(bool value, string columnName, bool defaultValue, int index)
+ {
+ Write(value.ToString(), columnName, defaultValue.ToString(), index);
+ }
+
+ public void Write(Enum value, string columnName, Enum defaultValue, int index)
+ {
+ Write(value.ToString(), columnName, defaultValue.ToString(), index);
+ }
+
/// <summary>
/// Writes a value to the specified column in the current row.
/// If the column does not exist, it is created with the given default value and index.
diff --git a/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj b/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj
index 0fc1948a4..5674c1c56 100644
--- a/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj
+++ b/Software/Visual_Studio/Tango.CSV/Tango.CSV.csproj
@@ -90,6 +90,7 @@
<Compile Include="CsvSource.cs" />
<Compile Include="DynamicCsvFile.cs" />
<Compile Include="DynamicCsvFileColumn.cs" />
+ <Compile Include="CsvDynamicReader.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
diff --git a/Software/Visual_Studio/Web/Tango.MachineService/Properties/AssemblyInfo.cs b/Software/Visual_Studio/Web/Tango.MachineService/Properties/AssemblyInfo.cs
index 2ce4dcaa1..e8185c3d5 100644
--- a/Software/Visual_Studio/Web/Tango.MachineService/Properties/AssemblyInfo.cs
+++ b/Software/Visual_Studio/Web/Tango.MachineService/Properties/AssemblyInfo.cs
@@ -24,4 +24,4 @@ using System.Runtime.InteropServices;
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
-[assembly: AssemblyVersion("3.0.26.0")]
+[assembly: AssemblyVersion("3.0.27.0")]