Files
OpenNest/OpenNest.IO/CuttingParametersSerializer.cs
T
ajandClaude Opus 5.5 6de23fb831 feat(tabs): replace "Tab every part" with tabbing parts by size
Nobody tabs every part, but tabbing by size is useful even on a plate
that has a support grid. Cutting Settings > Tabs now offers "Only tab
parts the support grid can't hold level" or "Tab parts by size"
(CuttingParameters.TabBySize), which ignores the bed and tabs each part
whose minimum-area bounding rectangle's narrow side is in the range.
The support grid choice still falls back to that range on plates with
no bed, as before.

The range fields become TabSizeMinWidth/TabSizeMaxWidth; their JSON
keys stay noBedTabMinWidth/noBedTabMaxWidth so saved settings and nests
read unchanged. TabsEnabled stays as the per-contour flag Plan Cutting,
Place Lead-in and drawing pins set on the parts they tab.

Saved user settings with "Tab every part" read as tabs by size from 0
to 9999, which tabs the same parts. Only the user's settings migrate;
a plate's recorded parameters and pins replay as planned.

Plan Cutting, Place Lead-in and the pre-post review all go through
SupportTabSettings, so they agree on which parts need a tab. A
size-tabbed part is held by any clear tab, so it is never a rapid
obstacle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 07:46:24 -05:00

307 lines
12 KiB
C#

using System;
using System.Text.Json;
using OpenNest.CNC.CuttingStrategy;
using static OpenNest.IO.CuttingParametersDto;
namespace OpenNest.IO;
public static class CuttingParametersSerializer
{
private static readonly JsonSerializerOptions JsonOptions = new()
{
WriteIndented = false,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
};
/// <summary>Writes desktop settings, retaining the legacy null-tab width fallback.</summary>
public static string Serialize(CuttingParameters parameters)
{
ArgumentNullException.ThrowIfNull(parameters);
var dto = ToDto(parameters);
if (parameters.TabConfig == null)
dto.TabConfig = null;
return JsonSerializer.Serialize(dto, JsonOptions);
}
/// <summary>Reads desktop settings with their historical defaults and angle fallback.</summary>
public static CuttingParameters Deserialize(string json)
{
var dto = JsonSerializer.Deserialize<CuttingParametersDto>(json, JsonOptions);
var parameters = FromDto(dto) ?? new CuttingParameters();
if (parameters.LeadInAngleIncrement <= 0)
parameters.LeadInAngleIncrement = 5.0;
return parameters;
}
/// <summary>
/// Reads settings a caller supplied explicitly (a file or tool argument). A JSON null is
/// refused rather than read as the defaults, so missing settings are never invented.
/// </summary>
public static CuttingParameters DeserializeExplicit(string json)
{
if (JsonSerializer.Deserialize<CuttingParametersDto>(json, JsonOptions) == null)
throw new JsonException("Cutting settings must be a JSON object.");
return Deserialize(json);
}
/// <summary>Captures an owned snapshot. Null stays null for absent plate parameters.</summary>
public static CuttingParametersDto ToDto(CuttingParameters parameters)
{
if (parameters == null)
return null;
return new CuttingParametersDto
{
Id = parameters.Id,
MachineName = parameters.MachineName,
MaterialName = parameters.MaterialName,
Grade = parameters.Grade,
Thickness = parameters.Thickness,
Kerf = parameters.Kerf,
PartSpacing = parameters.PartSpacing,
ExternalLeadIn = ToLeadInDto(parameters.ExternalLeadIn),
ExternalLeadOut = ToLeadOutDto(parameters.ExternalLeadOut),
InternalLeadIn = ToLeadInDto(parameters.InternalLeadIn),
InternalLeadOut = ToLeadOutDto(parameters.InternalLeadOut),
TabsEnabled = parameters.TabsEnabled,
TabWidth = parameters.TabConfig?.Size ?? 0.25,
TabConfig = ToTabDto(parameters.TabConfig),
PierceClearance = parameters.PierceClearance,
PierceDiameter = parameters.PierceDiameter,
RoundLeadInAngles = parameters.RoundLeadInAngles,
LeadInAngleIncrement = parameters.LeadInAngleIncrement,
SupportTabs = parameters.SupportTabs,
TabBySize = parameters.TabBySize,
NoBedTabMinWidth = parameters.TabSizeMinWidth,
NoBedTabMaxWidth = parameters.TabSizeMaxWidth,
TabHeldMaxLength = parameters.TabHeldMaxLength,
Sequencing = ToSequenceDto(parameters.Sequencing),
Assignment = ToAssignmentDto(parameters.Assignment),
};
}
/// <summary>Restores an owned snapshot without the desktop settings' value normalization.</summary>
public static CuttingParameters FromDto(CuttingParametersDto dto)
{
if (dto == null)
return null;
return new CuttingParameters
{
Id = dto.Id,
MachineName = dto.MachineName,
MaterialName = dto.MaterialName,
Grade = dto.Grade,
Thickness = dto.Thickness,
Kerf = dto.Kerf,
PartSpacing = dto.PartSpacing,
ExternalLeadIn = FromLeadInDto(dto.ExternalLeadIn),
ExternalLeadOut = FromLeadOutDto(dto.ExternalLeadOut),
InternalLeadIn = FromLeadInDto(dto.InternalLeadIn),
InternalLeadOut = FromLeadOutDto(dto.InternalLeadOut),
TabsEnabled = dto.TabsEnabled,
TabConfig = dto.TabConfig == null
? new NormalTab { Size = dto.TabWidth }
: FromTabDto(dto.TabConfig),
PierceClearance = dto.PierceClearance,
PierceDiameter = dto.PierceDiameter,
RoundLeadInAngles = dto.RoundLeadInAngles,
LeadInAngleIncrement = dto.LeadInAngleIncrement,
SupportTabs = dto.SupportTabs,
TabBySize = dto.TabBySize,
TabSizeMinWidth = dto.NoBedTabMinWidth,
TabSizeMaxWidth = dto.NoBedTabMaxWidth,
TabHeldMaxLength = dto.TabHeldMaxLength,
Sequencing = FromSequenceDto(dto.Sequencing),
Assignment = FromAssignmentDto(dto.Assignment),
};
}
internal static LeadInDto ToLeadInDto(LeadIn leadIn) => leadIn switch
{
LineLeadIn line => new LeadInDto
{
Type = "Line",
Length = line.Length,
ApproachAngle = line.ApproachAngle,
},
ArcLeadIn arc => new LeadInDto { Type = "Arc", Radius = arc.Radius, SweepAngle = arc.SweepAngle },
LineArcLeadIn lineArc => new LeadInDto
{
Type = "LineArc",
LineLength = lineArc.LineLength,
ArcRadius = lineArc.ArcRadius,
ApproachAngle = lineArc.ApproachAngle,
},
CleanHoleLeadIn cleanHole => new LeadInDto
{
Type = "CleanHole",
LineLength = cleanHole.LineLength,
ArcRadius = cleanHole.ArcRadius,
Kerf = cleanHole.Kerf,
},
LineLineLeadIn lineLine => new LeadInDto
{
Type = "LineLine",
Length1 = lineLine.Length1,
Angle1 = lineLine.ApproachAngle1,
Length2 = lineLine.Length2,
Angle2 = lineLine.ApproachAngle2,
},
_ => new LeadInDto { Type = "None" },
};
internal static LeadIn FromLeadInDto(LeadInDto dto) => dto?.Type switch
{
"Line" => new LineLeadIn { Length = dto.Length, ApproachAngle = dto.ApproachAngle },
"Arc" => new ArcLeadIn { Radius = dto.Radius, SweepAngle = dto.SweepAngle },
"LineArc" => new LineArcLeadIn
{
LineLength = dto.LineLength,
ArcRadius = dto.ArcRadius,
ApproachAngle = dto.ApproachAngle,
},
"CleanHole" => new CleanHoleLeadIn
{
LineLength = dto.LineLength,
ArcRadius = dto.ArcRadius,
Kerf = dto.Kerf,
},
"LineLine" => new LineLineLeadIn
{
Length1 = dto.Length1,
ApproachAngle1 = dto.Angle1,
Length2 = dto.Length2,
ApproachAngle2 = dto.Angle2,
},
_ => new NoLeadIn(),
};
internal static LeadOutDto ToLeadOutDto(LeadOut leadOut) => leadOut switch
{
LineLeadOut line => new LeadOutDto
{
Type = "Line",
Length = line.Length,
ApproachAngle = line.ApproachAngle,
},
ArcLeadOut arc => new LeadOutDto { Type = "Arc", Radius = arc.Radius },
_ => new LeadOutDto { Type = "None" },
};
internal static LeadOut FromLeadOutDto(LeadOutDto dto) => dto?.Type switch
{
"Line" => new LineLeadOut { Length = dto.Length, ApproachAngle = dto.ApproachAngle },
"Arc" => new ArcLeadOut { Radius = dto.Radius },
_ => new NoLeadOut(),
};
internal static TabDto ToTabDto(Tab tab)
{
var dto = tab switch
{
NormalTab normal => new TabDto
{
Type = "Normal",
CutoutMinWidth = normal.CutoutMinWidth,
CutoutMinHeight = normal.CutoutMinHeight,
CutoutMaxWidth = normal.CutoutMaxWidth,
CutoutMaxHeight = normal.CutoutMaxHeight,
},
MachineTab machine => new TabDto { Type = "Machine", MachineTabId = machine.MachineTabId },
BreakerTab breaker => new TabDto
{
Type = "Breaker",
BreakerDepth = breaker.BreakerDepth,
BreakerLeadInLength = breaker.BreakerLeadInLength,
BreakerAngle = breaker.BreakerAngle,
},
_ => new TabDto { Type = "None" },
};
if (tab != null)
{
dto.Size = tab.Size;
dto.TabLeadIn = tab.TabLeadIn == null ? null : ToLeadInDto(tab.TabLeadIn);
dto.TabLeadOut = tab.TabLeadOut == null ? null : ToLeadOutDto(tab.TabLeadOut);
}
return dto;
}
internal static Tab FromTabDto(TabDto dto)
{
var tab = dto.Type switch
{
"Normal" => (Tab)new NormalTab
{
CutoutMinWidth = dto.CutoutMinWidth,
CutoutMinHeight = dto.CutoutMinHeight,
CutoutMaxWidth = dto.CutoutMaxWidth,
CutoutMaxHeight = dto.CutoutMaxHeight,
},
"Machine" => new MachineTab { MachineTabId = dto.MachineTabId },
"Breaker" => new BreakerTab
{
BreakerDepth = dto.BreakerDepth,
BreakerLeadInLength = dto.BreakerLeadInLength,
BreakerAngle = dto.BreakerAngle,
},
_ => null,
};
if (tab != null)
{
tab.Size = dto.Size;
tab.TabLeadIn = dto.TabLeadIn == null ? null : FromLeadInDto(dto.TabLeadIn);
tab.TabLeadOut = dto.TabLeadOut == null ? null : FromLeadOutDto(dto.TabLeadOut);
}
return tab;
}
private static SequenceDto ToSequenceDto(SequenceParameters parameters) => parameters == null
? null
: new SequenceDto
{
Method = parameters.Method,
SmallCutoutWidth = parameters.SmallCutoutWidth,
SmallCutoutHeight = parameters.SmallCutoutHeight,
MediumCutoutWidth = parameters.MediumCutoutWidth,
MediumCutoutHeight = parameters.MediumCutoutHeight,
DistanceMediumSmall = parameters.DistanceMediumSmall,
AlternateRowsColumns = parameters.AlternateRowsColumns,
AlternateCutoutsWithinRowColumn = parameters.AlternateCutoutsWithinRowColumn,
MinDistanceBetweenRowsColumns = parameters.MinDistanceBetweenRowsColumns,
};
private static SequenceParameters FromSequenceDto(SequenceDto dto) => dto == null
? null
: new SequenceParameters
{
Method = dto.Method,
SmallCutoutWidth = dto.SmallCutoutWidth,
SmallCutoutHeight = dto.SmallCutoutHeight,
MediumCutoutWidth = dto.MediumCutoutWidth,
MediumCutoutHeight = dto.MediumCutoutHeight,
DistanceMediumSmall = dto.DistanceMediumSmall,
AlternateRowsColumns = dto.AlternateRowsColumns,
AlternateCutoutsWithinRowColumn = dto.AlternateCutoutsWithinRowColumn,
MinDistanceBetweenRowsColumns = dto.MinDistanceBetweenRowsColumns,
};
private static AssignmentDto ToAssignmentDto(AssignmentParameters parameters) => parameters == null
? null
: new AssignmentDto
{
Method = parameters.Method,
Preference = parameters.Preference,
MinGeometryLength = parameters.MinGeometryLength,
};
private static AssignmentParameters FromAssignmentDto(AssignmentDto dto) => dto == null
? null
: new AssignmentParameters
{
Method = dto.Method,
Preference = dto.Preference,
MinGeometryLength = dto.MinGeometryLength,
};
}