Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b84508ff4 | |||
| 6fdf0ad3c5 | |||
| 4f7bfcc3ad | |||
| 3c53d6fecd | |||
| e239967a7b | |||
| 9d57d3875a | |||
| 0e299d7f6f | |||
| c6f544c5d7 | |||
| 9563094c2b |
@@ -128,6 +128,12 @@ namespace OpenNest.CNC
|
|||||||
{
|
{
|
||||||
var code = Codes[i];
|
var code = Codes[i];
|
||||||
|
|
||||||
|
if (code is SubProgramCall subpgm)
|
||||||
|
{
|
||||||
|
subpgm.Offset = new Geometry.Vector(
|
||||||
|
subpgm.Offset.X + x, subpgm.Offset.Y + y);
|
||||||
|
}
|
||||||
|
|
||||||
if (code is Motion == false)
|
if (code is Motion == false)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -150,6 +156,12 @@ namespace OpenNest.CNC
|
|||||||
{
|
{
|
||||||
var code = Codes[i];
|
var code = Codes[i];
|
||||||
|
|
||||||
|
if (code is SubProgramCall subpgm)
|
||||||
|
{
|
||||||
|
subpgm.Offset = new Geometry.Vector(
|
||||||
|
subpgm.Offset.X + voffset.X, subpgm.Offset.Y + voffset.Y);
|
||||||
|
}
|
||||||
|
|
||||||
if (code is Motion == false)
|
if (code is Motion == false)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
using OpenNest.Geometry;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenNest.CNC
|
||||||
|
{
|
||||||
|
public static class RapidEnumerator
|
||||||
|
{
|
||||||
|
public readonly record struct Segment(Vector From, Vector To);
|
||||||
|
|
||||||
|
public static List<Segment> Enumerate(Program pgm, Vector basePos, Vector startPos)
|
||||||
|
{
|
||||||
|
var results = new List<Segment>();
|
||||||
|
|
||||||
|
// Draw the rapid from the previous tool position to the program's first
|
||||||
|
// pierce point. This also primes pos so the interior walk interprets
|
||||||
|
// Incremental deltas from the correct absolute location (basePos), which
|
||||||
|
// matters for raw pre-lead-in programs that are emitted Incremental.
|
||||||
|
var firstPierce = FirstPiercePoint(pgm, basePos);
|
||||||
|
results.Add(new Segment(startPos, firstPierce));
|
||||||
|
|
||||||
|
var pos = firstPierce;
|
||||||
|
Walk(pgm, basePos, ref pos, skipFirst: true, results);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector FirstPiercePoint(Program pgm, Vector basePos)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < pgm.Length; i++)
|
||||||
|
{
|
||||||
|
if (pgm[i] is SubProgramCall call && call.Program != null)
|
||||||
|
return FirstPiercePoint(call.Program, basePos + call.Offset);
|
||||||
|
|
||||||
|
if (pgm[i] is Motion motion)
|
||||||
|
return motion.EndPoint + basePos;
|
||||||
|
}
|
||||||
|
return basePos;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Walk(Program pgm, Vector basePos, ref Vector pos, bool skipFirst, List<Segment> results)
|
||||||
|
{
|
||||||
|
var skipped = !skipFirst;
|
||||||
|
|
||||||
|
for (var i = 0; i < pgm.Length; ++i)
|
||||||
|
{
|
||||||
|
var code = pgm[i];
|
||||||
|
|
||||||
|
if (code is SubProgramCall { Program: { } program } call)
|
||||||
|
{
|
||||||
|
var holeBase = basePos + call.Offset;
|
||||||
|
var firstPierce = FirstPiercePoint(program, holeBase);
|
||||||
|
|
||||||
|
if (!skipped)
|
||||||
|
skipped = true;
|
||||||
|
else
|
||||||
|
results.Add(new Segment(pos, firstPierce));
|
||||||
|
|
||||||
|
var subPos = holeBase;
|
||||||
|
Walk(program, holeBase, ref subPos, skipFirst: true, results);
|
||||||
|
pos = subPos;
|
||||||
|
}
|
||||||
|
else if (code is Motion motion)
|
||||||
|
{
|
||||||
|
var endpt = pgm.Mode == Mode.Incremental
|
||||||
|
? motion.EndPoint + pos
|
||||||
|
: motion.EndPoint + basePos;
|
||||||
|
|
||||||
|
if (code.Type == CodeType.RapidMove)
|
||||||
|
{
|
||||||
|
if (!skipped)
|
||||||
|
skipped = true;
|
||||||
|
else
|
||||||
|
results.Add(new Segment(pos, endpt));
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = endpt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace OpenNest
|
||||||
|
{
|
||||||
|
public interface IMaterialProvidingPostProcessor
|
||||||
|
{
|
||||||
|
IEnumerable<string> GetMaterialNames();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace OpenNest
|
||||||
|
{
|
||||||
|
public interface IPostProcessorNestAware
|
||||||
|
{
|
||||||
|
void PrepareForNest(Nest nest);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,33 +3,38 @@ using System.Collections.Generic;
|
|||||||
|
|
||||||
namespace OpenNest.Shapes
|
namespace OpenNest.Shapes
|
||||||
{
|
{
|
||||||
public class OctagonShape : ShapeDefinition
|
public class NgonShape : ShapeDefinition
|
||||||
{
|
{
|
||||||
|
public int Sides { get; set; }
|
||||||
public double Width { get; set; }
|
public double Width { get; set; }
|
||||||
|
|
||||||
public override void SetPreviewDefaults()
|
public override void SetPreviewDefaults()
|
||||||
{
|
{
|
||||||
|
Sides = 8;
|
||||||
Width = 8;
|
Width = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Drawing GetDrawing()
|
public override Drawing GetDrawing()
|
||||||
{
|
{
|
||||||
|
var n = Sides < 3 ? 3 : Sides;
|
||||||
var center = Width / 2.0;
|
var center = Width / 2.0;
|
||||||
var circumRadius = Width / (2.0 * System.Math.Cos(System.Math.PI / 8.0));
|
var circumRadius = Width / (2.0 * System.Math.Cos(System.Math.PI / n));
|
||||||
|
var step = 2.0 * System.Math.PI / n;
|
||||||
|
var start = System.Math.PI / n;
|
||||||
|
|
||||||
var vertices = new Vector[8];
|
var vertices = new Vector[n];
|
||||||
for (var i = 0; i < 8; i++)
|
for (var i = 0; i < n; i++)
|
||||||
{
|
{
|
||||||
var angle = System.Math.PI / 8.0 + i * System.Math.PI / 4.0;
|
var angle = start + i * step;
|
||||||
vertices[i] = new Vector(
|
vertices[i] = new Vector(
|
||||||
center + circumRadius * System.Math.Cos(angle),
|
center + circumRadius * System.Math.Cos(angle),
|
||||||
center + circumRadius * System.Math.Sin(angle));
|
center + circumRadius * System.Math.Sin(angle));
|
||||||
}
|
}
|
||||||
|
|
||||||
var entities = new List<Entity>();
|
var entities = new List<Entity>();
|
||||||
for (var i = 0; i < 8; i++)
|
for (var i = 0; i < n; i++)
|
||||||
{
|
{
|
||||||
var next = (i + 1) % 8;
|
var next = (i + 1) % n;
|
||||||
entities.Add(new Line(vertices[i], vertices[next]));
|
entities.Add(new Line(vertices[i], vertices[next]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ namespace OpenNest.Engine.Strategies
|
|||||||
public int PlateNumber { get; init; }
|
public int PlateNumber { get; init; }
|
||||||
public CancellationToken Token { get; init; }
|
public CancellationToken Token { get; init; }
|
||||||
public IProgress<NestProgress> Progress { get; init; }
|
public IProgress<NestProgress> Progress { get; init; }
|
||||||
public FillPolicy Policy { get; init; }
|
public FillPolicy Policy { get; init; } = new FillPolicy(new DefaultFillComparer());
|
||||||
public int MaxQuantity { get; init; }
|
public int MaxQuantity { get; init; }
|
||||||
public PartType PartType { get; set; }
|
public PartType PartType { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,16 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
{
|
{
|
||||||
private readonly CincinnatiPostConfig _config;
|
private readonly CincinnatiPostConfig _config;
|
||||||
private readonly CincinnatiFeatureWriter _featureWriter;
|
private readonly CincinnatiFeatureWriter _featureWriter;
|
||||||
|
private readonly CoordinateFormatter _fmt;
|
||||||
|
private readonly Dictionary<int, int> _holeSubprograms;
|
||||||
|
|
||||||
public CincinnatiPartSubprogramWriter(CincinnatiPostConfig config)
|
public CincinnatiPartSubprogramWriter(CincinnatiPostConfig config,
|
||||||
|
Dictionary<int, int> holeSubprograms = null)
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_featureWriter = new CincinnatiFeatureWriter(config);
|
_featureWriter = new CincinnatiFeatureWriter(config);
|
||||||
|
_fmt = new CoordinateFormatter(config.PostedAccuracy);
|
||||||
|
_holeSubprograms = holeSubprograms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -44,6 +49,15 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
for (var i = 0; i < ordered.Count; i++)
|
for (var i = 0; i < ordered.Count; i++)
|
||||||
{
|
{
|
||||||
var (codes, isEtch) = ordered[i];
|
var (codes, isEtch) = ordered[i];
|
||||||
|
var isLastFeature = i == ordered.Count - 1;
|
||||||
|
|
||||||
|
// SubProgramCall features are emitted as M98 hole calls
|
||||||
|
if (codes.Count == 1 && codes[0] is SubProgramCall holeCall)
|
||||||
|
{
|
||||||
|
WriteHoleSubprogramCall(w, holeCall, i, isLastFeature);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var featureNumber = i == 0
|
var featureNumber = i == 0
|
||||||
? _config.FeatureLineNumberStart
|
? _config.FeatureLineNumberStart
|
||||||
: 1000 + i + 1;
|
: 1000 + i + 1;
|
||||||
@@ -55,7 +69,7 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
FeatureNumber = featureNumber,
|
FeatureNumber = featureNumber,
|
||||||
PartName = drawingName,
|
PartName = drawingName,
|
||||||
IsFirstFeatureOfPart = false,
|
IsFirstFeatureOfPart = false,
|
||||||
IsLastFeatureOnSheet = i == ordered.Count - 1,
|
IsLastFeatureOnSheet = isLastFeature,
|
||||||
IsSafetyHeadraise = false,
|
IsSafetyHeadraise = false,
|
||||||
IsExteriorFeature = false,
|
IsExteriorFeature = false,
|
||||||
IsEtch = isEtch,
|
IsEtch = isEtch,
|
||||||
@@ -70,6 +84,30 @@ public sealed class CincinnatiPartSubprogramWriter
|
|||||||
w.WriteLine($"M99 (END OF {drawingName})");
|
w.WriteLine($"M99 (END OF {drawingName})");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteHoleSubprogramCall(TextWriter w, SubProgramCall call,
|
||||||
|
int featureIndex, bool isLastFeature)
|
||||||
|
{
|
||||||
|
var postSubNum = _holeSubprograms != null && _holeSubprograms.TryGetValue(call.Id, out var num)
|
||||||
|
? num : call.Id;
|
||||||
|
|
||||||
|
var featureNumber = featureIndex == 0
|
||||||
|
? _config.FeatureLineNumberStart
|
||||||
|
: 1000 + featureIndex + 1;
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
if (_config.UseLineNumbers)
|
||||||
|
sb.Append($"N{featureNumber} ");
|
||||||
|
sb.Append($"G52 X{_fmt.FormatCoord(call.Offset.X)} Y{_fmt.FormatCoord(call.Offset.Y)}");
|
||||||
|
w.WriteLine(sb.ToString());
|
||||||
|
|
||||||
|
w.WriteLine($"M98 P{postSubNum}");
|
||||||
|
|
||||||
|
w.WriteLine("G52 X0 Y0");
|
||||||
|
|
||||||
|
if (!isLastFeature)
|
||||||
|
w.WriteLine("M47");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the program has no leading rapid, inserts a synthetic rapid at the
|
/// If the program has no leading rapid, inserts a synthetic rapid at the
|
||||||
/// last motion endpoint (the contour return point). This ensures the feature
|
/// last motion endpoint (the contour return point). This ensures the feature
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace OpenNest.Posts.Cincinnati
|
namespace OpenNest.Posts.Cincinnati
|
||||||
{
|
{
|
||||||
@@ -277,6 +279,24 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
[DisplayName("Etch Libraries")]
|
[DisplayName("Etch Libraries")]
|
||||||
[Description("Gas-to-library mapping for etch operations.")]
|
[Description("Gas-to-library mapping for etch operations.")]
|
||||||
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
|
public List<EtchLibraryEntry> EtchLibraries { get; set; } = new();
|
||||||
|
|
||||||
|
[Category("B. Libraries")]
|
||||||
|
[DisplayName("Selected Library")]
|
||||||
|
[Description("Overrides Material/Thickness/Gas auto-resolution. Pick an existing entry from Material Libraries, or leave blank to auto-resolve.")]
|
||||||
|
[TypeConverter(typeof(MaterialLibraryNameConverter))]
|
||||||
|
public string SelectedLibrary { get; set; } = "";
|
||||||
|
|
||||||
|
public string FindBestLibrary(string materialName, double thickness)
|
||||||
|
{
|
||||||
|
if (MaterialLibraries == null || string.IsNullOrEmpty(materialName))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
return MaterialLibraries
|
||||||
|
.Where(e => string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
.OrderBy(e => System.Math.Abs(e.Thickness - thickness))
|
||||||
|
.Select(e => e.Library)
|
||||||
|
.FirstOrDefault() ?? "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MaterialLibraryEntry
|
public class MaterialLibraryEntry
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using OpenNest.CNC;
|
|||||||
|
|
||||||
namespace OpenNest.Posts.Cincinnati
|
namespace OpenNest.Posts.Cincinnati
|
||||||
{
|
{
|
||||||
public sealed class CincinnatiPostProcessor : IConfigurablePostProcessor
|
public sealed class CincinnatiPostProcessor : IConfigurablePostProcessor, IPostProcessorNestAware, IMaterialProvidingPostProcessor
|
||||||
{
|
{
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
@@ -25,6 +25,23 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
|
|
||||||
object IConfigurablePostProcessor.Config => Config;
|
object IConfigurablePostProcessor.Config => Config;
|
||||||
|
|
||||||
|
public IEnumerable<string> GetMaterialNames()
|
||||||
|
{
|
||||||
|
if (Config?.MaterialLibraries == null)
|
||||||
|
return System.Array.Empty<string>();
|
||||||
|
|
||||||
|
return Config.MaterialLibraries
|
||||||
|
.Select(e => e.Material)
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PrepareForNest(Nest nest)
|
||||||
|
{
|
||||||
|
var materialName = nest?.Material?.Name ?? "";
|
||||||
|
var thickness = nest?.Thickness ?? 0.0;
|
||||||
|
Config.SelectedLibrary = Config.FindBestLibrary(materialName, thickness);
|
||||||
|
}
|
||||||
|
|
||||||
public CincinnatiPostProcessor()
|
public CincinnatiPostProcessor()
|
||||||
{
|
{
|
||||||
var configPath = GetConfigPath();
|
var configPath = GetConfigPath();
|
||||||
@@ -128,7 +145,8 @@ namespace OpenNest.Posts.Cincinnati
|
|||||||
// Part sub-programs (if enabled)
|
// Part sub-programs (if enabled)
|
||||||
if (subprogramEntries != null)
|
if (subprogramEntries != null)
|
||||||
{
|
{
|
||||||
var partSubWriter = new CincinnatiPartSubprogramWriter(Config);
|
var partSubWriter = new CincinnatiPartSubprogramWriter(Config,
|
||||||
|
holeMapping.Count > 0 ? holeMapping : null);
|
||||||
var sheetDiagonal = firstPlate != null
|
var sheetDiagonal = firstPlate != null
|
||||||
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
|
? System.Math.Sqrt(firstPlate.Size.Width * firstPlate.Size.Width
|
||||||
+ firstPlate.Size.Length * firstPlate.Size.Length)
|
+ firstPlate.Size.Length * firstPlate.Size.Length)
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OpenNest.Posts.Cincinnati
|
||||||
|
{
|
||||||
|
public sealed class MaterialLibraryNameConverter : StringConverter
|
||||||
|
{
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => false;
|
||||||
|
|
||||||
|
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
|
||||||
|
{
|
||||||
|
var config = context?.Instance as CincinnatiPostConfig;
|
||||||
|
var names = new List<string> { "" };
|
||||||
|
|
||||||
|
if (config?.MaterialLibraries != null)
|
||||||
|
{
|
||||||
|
names.AddRange(config.MaterialLibraries
|
||||||
|
.Select(e => e.Library)
|
||||||
|
.Where(s => !string.IsNullOrWhiteSpace(s))
|
||||||
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
|
.OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StandardValuesCollection(names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,15 +10,20 @@ public sealed class MaterialLibraryResolver
|
|||||||
|
|
||||||
private readonly List<MaterialLibraryEntry> _materialLibraries;
|
private readonly List<MaterialLibraryEntry> _materialLibraries;
|
||||||
private readonly List<EtchLibraryEntry> _etchLibraries;
|
private readonly List<EtchLibraryEntry> _etchLibraries;
|
||||||
|
private readonly string _selectedLibrary;
|
||||||
|
|
||||||
public MaterialLibraryResolver(CincinnatiPostConfig config)
|
public MaterialLibraryResolver(CincinnatiPostConfig config)
|
||||||
{
|
{
|
||||||
_materialLibraries = config.MaterialLibraries ?? new List<MaterialLibraryEntry>();
|
_materialLibraries = config.MaterialLibraries ?? new List<MaterialLibraryEntry>();
|
||||||
_etchLibraries = config.EtchLibraries ?? new List<EtchLibraryEntry>();
|
_etchLibraries = config.EtchLibraries ?? new List<EtchLibraryEntry>();
|
||||||
|
_selectedLibrary = config.SelectedLibrary ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ResolveCutLibrary(string materialName, double thickness, string gas)
|
public string ResolveCutLibrary(string materialName, double thickness, string gas)
|
||||||
{
|
{
|
||||||
|
if (!string.IsNullOrEmpty(_selectedLibrary))
|
||||||
|
return EnsureLibExtension(_selectedLibrary);
|
||||||
|
|
||||||
var entry = _materialLibraries.FirstOrDefault(e =>
|
var entry = _materialLibraries.FirstOrDefault(e =>
|
||||||
string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase) &&
|
string.Equals(e.Material, materialName, StringComparison.OrdinalIgnoreCase) &&
|
||||||
System.Math.Abs(e.Thickness - thickness) <= ThicknessTolerance &&
|
System.Math.Abs(e.Thickness - thickness) <= ThicknessTolerance &&
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
<ProjectReference Include="..\OpenNest.Core\OpenNest.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="OpenNest.Posts.Cincinnati.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
<Target Name="CopyToPostsDir" AfterTargets="Build">
|
<Target Name="CopyToPostsDir" AfterTargets="Build">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PostsDir>..\OpenNest\bin\$(Configuration)\$(TargetFramework)\Posts\</PostsDir>
|
<PostsDir>..\OpenNest\bin\$(Configuration)\$(TargetFramework)\Posts\</PostsDir>
|
||||||
|
<ConfigJson>$(MSBuildProjectDirectory)\OpenNest.Posts.Cincinnati.json</ConfigJson>
|
||||||
|
<DeployedConfigJson>$(PostsDir)OpenNest.Posts.Cincinnati.json</DeployedConfigJson>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<MakeDir Directories="$(PostsDir)" />
|
<MakeDir Directories="$(PostsDir)" />
|
||||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(PostsDir)" SkipUnchangedFiles="true" ContinueOnError="true" />
|
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(PostsDir)" SkipUnchangedFiles="true" ContinueOnError="true" />
|
||||||
|
<Copy SourceFiles="$(ConfigJson)" DestinationFolder="$(PostsDir)" SkipUnchangedFiles="true" ContinueOnError="true" Condition="!Exists('$(DeployedConfigJson)')" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -0,0 +1,163 @@
|
|||||||
|
{
|
||||||
|
"ConfigurationName": "CL940",
|
||||||
|
"PostedUnits": "Inches",
|
||||||
|
"PostedAccuracy": 4,
|
||||||
|
"UseLineNumbers": true,
|
||||||
|
"FeatureLineNumberStart": 1,
|
||||||
|
"UseSheetSubprograms": true,
|
||||||
|
"SheetSubprogramStart": 101,
|
||||||
|
"UsePartSubprograms": false,
|
||||||
|
"PartSubprogramStart": 200,
|
||||||
|
"VariableDeclarationSubprogram": 100,
|
||||||
|
"CoordModeBetweenParts": "G92",
|
||||||
|
"ProcessParameterMode": "LibraryFile",
|
||||||
|
"DefaultAssistGas": "O2",
|
||||||
|
"DefaultEtchGas": "N2",
|
||||||
|
"UseExactStopMode": false,
|
||||||
|
"UseSpeedGas": false,
|
||||||
|
"UseAntiDive": true,
|
||||||
|
"UseSmartRapids": false,
|
||||||
|
"KerfCompensation": "ControllerSide",
|
||||||
|
"DefaultKerfSide": "Left",
|
||||||
|
"InteriorM47": "Always",
|
||||||
|
"ExteriorM47": "Always",
|
||||||
|
"M47OverrideDistanceThreshold": null,
|
||||||
|
"SafetyHeadraiseDistance": 2000,
|
||||||
|
"PalletExchange": "EndOfSheet",
|
||||||
|
"LeadInFeedratePercent": 0.5,
|
||||||
|
"LeadInArcLine2FeedratePercent": 0.5,
|
||||||
|
"LeadOutFeedratePercent": 0.5,
|
||||||
|
"CircleFeedrateMultiplier": 0.8,
|
||||||
|
"ArcFeedrate": "None",
|
||||||
|
"ArcFeedrateRanges": [
|
||||||
|
{ "MaxRadius": 0.125, "FeedratePercent": 0.25, "VariableNumber": 123 },
|
||||||
|
{ "MaxRadius": 0.75, "FeedratePercent": 0.5, "VariableNumber": 124 },
|
||||||
|
{ "MaxRadius": 4.5, "FeedratePercent": 0.8, "VariableNumber": 125 }
|
||||||
|
],
|
||||||
|
"UserVariableStart": 200,
|
||||||
|
"SheetWidthVariable": 110,
|
||||||
|
"SheetLengthVariable": 111,
|
||||||
|
"MaterialLibraries": [
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.032, "Gas": "AIR", "Library": "AL032AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.032, "Gas": "N2", "Library": "AL032N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.032, "Gas": "O2", "Library": "AL032O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.050, "Gas": "AIR", "Library": "AL050AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.050, "Gas": "N2", "Library": "AL050N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.050, "Gas": "O2", "Library": "AL050O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.063, "Gas": "AIR", "Library": "AL063AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.063, "Gas": "N2", "Library": "AL063N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.063, "Gas": "O2", "Library": "AL063O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.080, "Gas": "AIR", "Library": "AL080AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.080, "Gas": "N2", "Library": "AL080N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.080, "Gas": "O2", "Library": "AL080O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.090, "Gas": "AIR", "Library": "AL090AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.090, "Gas": "N2", "Library": "AL090N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.090, "Gas": "O2", "Library": "AL090O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.100, "Gas": "AIR", "Library": "AL100AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.100, "Gas": "N2", "Library": "AL100N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.100, "Gas": "O2", "Library": "AL100O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.125, "Gas": "AIR", "Library": "AL125AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.125, "Gas": "N2", "Library": "AL125N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.125, "Gas": "O2", "Library": "AL125O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.190, "Gas": "AIR", "Library": "AL190AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.190, "Gas": "N2", "Library": "AL190N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.190, "Gas": "O2", "Library": "AL190O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.250, "Gas": "AIR", "Library": "AL250AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.250, "Gas": "N2", "Library": "AL250N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.250, "Gas": "O2", "Library": "AL250O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.375, "Gas": "AIR", "Library": "AL375AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.375, "Gas": "N2", "Library": "AL375N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.375, "Gas": "O2", "Library": "AL375O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.500, "Gas": "AIR", "Library": "AL500AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.500, "Gas": "N2", "Library": "AL500N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.500, "Gas": "O2", "Library": "AL500O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.625, "Gas": "N2", "Library": "AL625N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.750, "Gas": "AIR", "Library": "AL750AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.750, "Gas": "N2", "Library": "AL750N2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 0.750, "Gas": "O2", "Library": "AL750O2" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 1.000, "Gas": "AIR", "Library": "AL1000AIR" },
|
||||||
|
{ "Material": "Aluminum", "Thickness": 1.000, "Gas": "N2", "Library": "AL1000N2" },
|
||||||
|
|
||||||
|
{ "Material": "Galvanized Steel", "Thickness": 0.135, "Gas": "N2", "Library": "GALV135N2" },
|
||||||
|
{ "Material": "Galvanized Steel", "Thickness": 0.188, "Gas": "N2", "Library": "GALV188N2" },
|
||||||
|
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.036, "Gas": "AIR", "Library": "MS036AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.036, "Gas": "N2", "Library": "MS036N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.048, "Gas": "AIR", "Library": "MS048AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.048, "Gas": "N2", "Library": "MS048N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.060, "Gas": "AIR", "Library": "MS060AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.060, "Gas": "N2", "Library": "MS060N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.075, "Gas": "AIR", "Library": "MS075AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.075, "Gas": "N2", "Library": "MS075N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.075, "Gas": "N2", "Library": "MS075N2FE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.090, "Gas": "N2", "Library": "MS090N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.105, "Gas": "AIR", "Library": "MS105AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.105, "Gas": "N2", "Library": "MS105N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.120, "Gas": "AIR", "Library": "MS120AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.120, "Gas": "N2", "Library": "MS120N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.120, "Gas": "N2", "Library": "MS120N2FE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "AIR", "Library": "MS135AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "N2", "Library": "MS135N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "N2", "Library": "MS135N2FE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.135, "Gas": "N2", "Library": "MS135N2Panel" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "AIR", "Library": "MS188AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "N2", "Library": "MS188N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "N2", "Library": "MS188N2FLOORPLATE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.188, "Gas": "O2", "Library": "MS188O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "AIR", "Library": "MS250AIR" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "N2", "Library": "MS250N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "N2", "Library": "MS250N2FLOORPLATE" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.250, "Gas": "O2", "Library": "MS250O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.313, "Gas": "O2", "Library": "MS313O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.375, "Gas": "O2", "Library": "MS375O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.500, "Gas": "N2", "Library": "MS500N2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.500, "Gas": "O2", "Library": "MS500O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.625, "Gas": "O2", "Library": "MS625O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 0.750, "Gas": "O2", "Library": "MS750O2" },
|
||||||
|
{ "Material": "Carbon Steel", "Thickness": 1.000, "Gas": "O2", "Library": "MS1000O2" },
|
||||||
|
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.036, "Gas": "AIR", "Library": "SS036AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.036, "Gas": "N2", "Library": "SS036N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.048, "Gas": "AIR", "Library": "SS048AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.048, "Gas": "N2", "Library": "SS048N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.060, "Gas": "AIR", "Library": "SS060AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.060, "Gas": "N2", "Library": "SS060N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.075, "Gas": "AIR", "Library": "SS075AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.075, "Gas": "N2", "Library": "SS075N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.075, "Gas": "N2", "Library": "SS075N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.105, "Gas": "AIR", "Library": "SS105AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.105, "Gas": "N2", "Library": "SS105N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.105, "Gas": "N2", "Library": "SS105N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.120, "Gas": "AIR", "Library": "SS120AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.120, "Gas": "N2", "Library": "SS120N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.120, "Gas": "N2", "Library": "SS120N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.135, "Gas": "AIR", "Library": "SS135AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.135, "Gas": "N2", "Library": "SS135N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.135, "Gas": "N2", "Library": "SS135N2FE" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.188, "Gas": "AIR", "Library": "SS188AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.188, "Gas": "N2", "Library": "SS188N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.250, "Gas": "AIR", "Library": "SS250AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.250, "Gas": "N2", "Library": "SS250N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.313, "Gas": "N2", "Library": "SS313N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.375, "Gas": "AIR", "Library": "SS375AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.375, "Gas": "N2", "Library": "SS375N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.500, "Gas": "AIR", "Library": "SS500AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.500, "Gas": "N2", "Library": "SS500N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.625, "Gas": "N2", "Library": "SS625N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.750, "Gas": "AIR", "Library": "SS750AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 0.750, "Gas": "N2", "Library": "SS750N2" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 1.000, "Gas": "AIR", "Library": "SS1000AIR" },
|
||||||
|
{ "Material": "Stainless Steel", "Thickness": 1.000, "Gas": "N2", "Library": "SS1000N2" },
|
||||||
|
|
||||||
|
{ "Material": "Phenolic", "Thickness": 0.0, "Gas": "", "Library": "Phenolic" },
|
||||||
|
{ "Material": "Gasket", "Thickness": 0.250, "Gas": "N2", "Library": "GASKET250N2" }
|
||||||
|
],
|
||||||
|
"EtchLibraries": [
|
||||||
|
{ "Gas": "AIR", "Library": "EtchAIR" },
|
||||||
|
{ "Gas": "N2", "Library": "EtchN2" },
|
||||||
|
{ "Gas": "N2", "Library": "EtchN2_fast" },
|
||||||
|
{ "Gas": "N2", "Library": "Etchn2_no_mark_pvc" },
|
||||||
|
{ "Gas": "O2", "Library": "EtchO2" },
|
||||||
|
{ "Gas": "O2", "Library": "ETCHO2FINE" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using OpenNest.CNC;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace OpenNest.Tests.CNC
|
||||||
|
{
|
||||||
|
public class RapidEnumeratorTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Enumerate_AbsoluteProgram_OffsetsMotionsByBasePos()
|
||||||
|
{
|
||||||
|
var pgm = new Program(Mode.Absolute);
|
||||||
|
pgm.Codes.Add(new RapidMove(1, 0));
|
||||||
|
pgm.Codes.Add(new LinearMove(2, 0));
|
||||||
|
pgm.Codes.Add(new RapidMove(3, 3));
|
||||||
|
|
||||||
|
var segments = RapidEnumerator.Enumerate(pgm, basePos: new Vector(100, 200), startPos: new Vector(0, 0));
|
||||||
|
|
||||||
|
// Origin → first pierce, then interior rapid from contour end to next rapid target.
|
||||||
|
Assert.Equal(2, segments.Count);
|
||||||
|
Assert.Equal(new Vector(0, 0), segments[0].From);
|
||||||
|
Assert.Equal(new Vector(101, 200), segments[0].To);
|
||||||
|
Assert.Equal(new Vector(102, 200), segments[1].From);
|
||||||
|
Assert.Equal(new Vector(103, 203), segments[1].To);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Enumerate_IncrementalProgram_InterpretsDeltasFromBasePos()
|
||||||
|
{
|
||||||
|
// Pre-lead-in raw program: first rapid normalized to (0,0), Mode=Incremental
|
||||||
|
// (matches ConvertGeometry.ToProgram output).
|
||||||
|
var pgm = new Program(Mode.Incremental);
|
||||||
|
pgm.Codes.Add(new RapidMove(0, 0));
|
||||||
|
pgm.Codes.Add(new LinearMove(5, 0));
|
||||||
|
pgm.Codes.Add(new LinearMove(0, 5));
|
||||||
|
pgm.Codes.Add(new RapidMove(1, 1));
|
||||||
|
|
||||||
|
var segments = RapidEnumerator.Enumerate(pgm, basePos: new Vector(100, 200), startPos: new Vector(0, 0));
|
||||||
|
|
||||||
|
Assert.Equal(2, segments.Count);
|
||||||
|
// First rapid: plate origin → part pierce at basePos.
|
||||||
|
Assert.Equal(new Vector(0, 0), segments[0].From);
|
||||||
|
Assert.Equal(new Vector(100, 200), segments[0].To);
|
||||||
|
// Interior rapid: after deltas (5,0) and (0,5) from basePos, rapid delta (1,1).
|
||||||
|
Assert.Equal(new Vector(105, 205), segments[1].From);
|
||||||
|
Assert.Equal(new Vector(106, 206), segments[1].To);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Enumerate_SubProgramCall_RapidEndsAtAbsoluteHolePierce()
|
||||||
|
{
|
||||||
|
// Main program: lead-in rapid, a line, then a SubProgramCall for a hole.
|
||||||
|
// Sub-program (incremental) starts with RapidMove(radius, 0) to the hole pierce.
|
||||||
|
var sub = new Program(Mode.Incremental);
|
||||||
|
sub.Codes.Add(new RapidMove(0.5, 0));
|
||||||
|
sub.Codes.Add(new LinearMove(0, 0.1));
|
||||||
|
|
||||||
|
var pgm = new Program(Mode.Absolute);
|
||||||
|
pgm.Codes.Add(new RapidMove(0.2, 0.3)); // first pierce (perimeter lead-in)
|
||||||
|
pgm.Codes.Add(new LinearMove(1.0, 1.0)); // contour move
|
||||||
|
pgm.Codes.Add(new SubProgramCall
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Program = sub,
|
||||||
|
Offset = new Vector(2, 2), // hole center (drawing-local)
|
||||||
|
});
|
||||||
|
|
||||||
|
var basePos = new Vector(100, 200); // part.Location
|
||||||
|
var segments = RapidEnumerator.Enumerate(pgm, basePos, startPos: new Vector(0, 0));
|
||||||
|
|
||||||
|
// Expected rapids:
|
||||||
|
// 1. origin → first pierce (0.2+100, 0.3+200) = (100.2, 200.3)
|
||||||
|
// 2. end of contour (1+100, 1+200) = (101, 201) → hole pierce (2+100+0.5, 2+200) = (102.5, 202)
|
||||||
|
// The sub's internal first rapid is skipped (already drawn in #2).
|
||||||
|
Assert.Equal(2, segments.Count);
|
||||||
|
|
||||||
|
Assert.Equal(new Vector(0, 0), segments[0].From);
|
||||||
|
Assert.Equal(new Vector(100.2, 200.3), segments[0].To);
|
||||||
|
|
||||||
|
Assert.Equal(new Vector(101, 201), segments[1].From);
|
||||||
|
Assert.Equal(new Vector(102.5, 202), segments[1].To);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using OpenNest.Shapes;
|
||||||
|
|
||||||
|
namespace OpenNest.Tests.Shapes;
|
||||||
|
|
||||||
|
public class NgonShapeTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void GetDrawing_Octagon_BoundingBoxFitsWithinExpectedSize()
|
||||||
|
{
|
||||||
|
var shape = new NgonShape { Sides = 8, Width = 20 };
|
||||||
|
var drawing = shape.GetDrawing();
|
||||||
|
|
||||||
|
var bbox = drawing.Program.BoundingBox();
|
||||||
|
// Corner-to-corner is larger than flat-to-flat
|
||||||
|
Assert.True(bbox.Width >= 20 - 0.01);
|
||||||
|
Assert.True(bbox.Length >= 20 - 0.01);
|
||||||
|
// But should not be wildly larger (corner-to-corner ~ width / cos(22.5deg) ~ width * 1.0824)
|
||||||
|
Assert.True(bbox.Width < 22);
|
||||||
|
Assert.True(bbox.Length < 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(3)]
|
||||||
|
[InlineData(4)]
|
||||||
|
[InlineData(5)]
|
||||||
|
[InlineData(6)]
|
||||||
|
[InlineData(8)]
|
||||||
|
[InlineData(12)]
|
||||||
|
public void GetDrawing_HasOneLinearMovePerSide(int sides)
|
||||||
|
{
|
||||||
|
var shape = new NgonShape { Sides = sides, Width = 20 };
|
||||||
|
var drawing = shape.GetDrawing();
|
||||||
|
|
||||||
|
var moves = drawing.Program.Codes
|
||||||
|
.OfType<OpenNest.CNC.LinearMove>()
|
||||||
|
.Count();
|
||||||
|
Assert.Equal(sides, moves);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void GetDrawing_ClampsSidesBelowThreeToTriangle()
|
||||||
|
{
|
||||||
|
var shape = new NgonShape { Sides = 2, Width = 20 };
|
||||||
|
var drawing = shape.GetDrawing();
|
||||||
|
|
||||||
|
var moves = drawing.Program.Codes
|
||||||
|
.OfType<OpenNest.CNC.LinearMove>()
|
||||||
|
.Count();
|
||||||
|
Assert.Equal(3, moves);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
using OpenNest.Shapes;
|
|
||||||
|
|
||||||
namespace OpenNest.Tests.Shapes;
|
|
||||||
|
|
||||||
public class OctagonShapeTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void GetDrawing_BoundingBoxFitsWithinExpectedSize()
|
|
||||||
{
|
|
||||||
var shape = new OctagonShape { Width = 20 };
|
|
||||||
var drawing = shape.GetDrawing();
|
|
||||||
|
|
||||||
var bbox = drawing.Program.BoundingBox();
|
|
||||||
// Corner-to-corner is larger than flat-to-flat
|
|
||||||
Assert.True(bbox.Width >= 20 - 0.01);
|
|
||||||
Assert.True(bbox.Length >= 20 - 0.01);
|
|
||||||
// But should not be wildly larger (corner-to-corner ~ width / cos(22.5deg) ~ width * 1.0824)
|
|
||||||
Assert.True(bbox.Width < 22);
|
|
||||||
Assert.True(bbox.Length < 22);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetDrawing_HasEightEdges()
|
|
||||||
{
|
|
||||||
var shape = new OctagonShape { Width = 20 };
|
|
||||||
var drawing = shape.GetDrawing();
|
|
||||||
|
|
||||||
// An octagon program should have 8 linear moves (one per edge)
|
|
||||||
var moves = drawing.Program.Codes
|
|
||||||
.OfType<OpenNest.CNC.LinearMove>()
|
|
||||||
.Count();
|
|
||||||
Assert.Equal(8, moves);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -385,85 +385,20 @@ namespace OpenNest.Controls
|
|||||||
|
|
||||||
private void DrawRapids(Graphics g)
|
private void DrawRapids(Graphics g)
|
||||||
{
|
{
|
||||||
|
var pen = view.ColorScheme.RapidPen;
|
||||||
var pos = new Vector(0, 0);
|
var pos = new Vector(0, 0);
|
||||||
|
|
||||||
for (var i = 0; i < view.Plate.Parts.Count; ++i)
|
for (var i = 0; i < view.Plate.Parts.Count; ++i)
|
||||||
{
|
{
|
||||||
var part = view.Plate.Parts[i];
|
var part = view.Plate.Parts[i];
|
||||||
var pgm = part.Program;
|
var segments = RapidEnumerator.Enumerate(part.Program, part.Location, pos);
|
||||||
|
|
||||||
var piercePoint = GetFirstPiercePoint(pgm, part.Location);
|
foreach (var seg in segments)
|
||||||
DrawLine(g, pos, piercePoint, view.ColorScheme.RapidPen);
|
|
||||||
|
|
||||||
pos = piercePoint;
|
|
||||||
DrawRapids(g, pgm, part.Location, ref pos, skipFirstRapid: true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Vector GetFirstPiercePoint(Program pgm, Vector partLocation)
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < pgm.Length; i++)
|
DrawLine(g, seg.From, seg.To, pen);
|
||||||
{
|
pos = seg.To;
|
||||||
if (pgm[i] is SubProgramCall call && call.Program != null)
|
|
||||||
return GetFirstPiercePoint(call.Program, partLocation + call.Offset);
|
|
||||||
|
|
||||||
if (pgm[i] is Motion motion)
|
|
||||||
{
|
|
||||||
return motion.EndPoint + partLocation;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return partLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawRapids(Graphics g, Program pgm, Vector basePos, ref Vector pos, bool skipFirstRapid = false)
|
|
||||||
{
|
|
||||||
var firstRapidSkipped = false;
|
|
||||||
|
|
||||||
for (var i = 0; i < pgm.Length; ++i)
|
|
||||||
{
|
|
||||||
var code = pgm[i];
|
|
||||||
|
|
||||||
if (code is SubProgramCall { Program: { } program } call)
|
|
||||||
{
|
|
||||||
// A SubProgramCall is a coordinate-frame shift, not a physical
|
|
||||||
// rapid to the hole center. The Cincinnati post emits it as a
|
|
||||||
// G52 bracket, so the physical rapid is the sub-program's first
|
|
||||||
// motion, which goes straight from here to the lead-in pierce.
|
|
||||||
// Look ahead for that pierce point and draw the direct rapid,
|
|
||||||
// then recurse with skipFirstRapid so the sub doesn't also draw
|
|
||||||
// its first rapid on top. See docs/cincinnati-post-output.md.
|
|
||||||
var holeBase = basePos + call.Offset;
|
|
||||||
var firstPierce = GetFirstPiercePoint(program, holeBase);
|
|
||||||
|
|
||||||
if (ShouldDrawRapid(skipFirstRapid, ref firstRapidSkipped))
|
|
||||||
DrawLine(g, pos, firstPierce, view.ColorScheme.RapidPen);
|
|
||||||
|
|
||||||
var subPos = holeBase;
|
|
||||||
DrawRapids(g, program, holeBase, ref subPos, skipFirstRapid: true);
|
|
||||||
pos = subPos;
|
|
||||||
}
|
|
||||||
else if (code is Motion motion)
|
|
||||||
{
|
|
||||||
var endpt = pgm.Mode == Mode.Incremental
|
|
||||||
? motion.EndPoint + pos
|
|
||||||
: motion.EndPoint;
|
|
||||||
|
|
||||||
if (code.Type == CodeType.RapidMove && ShouldDrawRapid(skipFirstRapid, ref firstRapidSkipped))
|
|
||||||
DrawLine(g, pos, endpt, view.ColorScheme.RapidPen);
|
|
||||||
|
|
||||||
pos = endpt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool ShouldDrawRapid(bool skipFirstRapid, ref bool firstRapidSkipped)
|
|
||||||
{
|
|
||||||
if (skipFirstRapid && !firstRapidSkipped)
|
|
||||||
{
|
|
||||||
firstRapidSkipped = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawAllPiercePoints(Graphics g)
|
private void DrawAllPiercePoints(Graphics g)
|
||||||
|
|||||||
Generated
+1
-1
@@ -81,8 +81,8 @@
|
|||||||
//
|
//
|
||||||
// tabControl1
|
// tabControl1
|
||||||
//
|
//
|
||||||
tabControl1.Controls.Add(tabPage1);
|
|
||||||
tabControl1.Controls.Add(tabPage2);
|
tabControl1.Controls.Add(tabPage2);
|
||||||
|
tabControl1.Controls.Add(tabPage1);
|
||||||
tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
|
tabControl1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||||
tabControl1.ItemSize = new System.Drawing.Size(100, 22);
|
tabControl1.ItemSize = new System.Drawing.Size(100, 22);
|
||||||
tabControl1.Location = new System.Drawing.Point(0, 0);
|
tabControl1.Location = new System.Drawing.Point(0, 0);
|
||||||
|
|||||||
+4
-3
@@ -63,7 +63,7 @@
|
|||||||
this.textBox2 = new System.Windows.Forms.TextBox();
|
this.textBox2 = new System.Windows.Forms.TextBox();
|
||||||
this.label5 = new System.Windows.Forms.Label();
|
this.label5 = new System.Windows.Forms.Label();
|
||||||
this.labelMaterial = new System.Windows.Forms.Label();
|
this.labelMaterial = new System.Windows.Forms.Label();
|
||||||
this.materialBox = new System.Windows.Forms.TextBox();
|
this.materialBox = new System.Windows.Forms.ComboBox();
|
||||||
this.tabPage2 = new System.Windows.Forms.TabPage();
|
this.tabPage2 = new System.Windows.Forms.TabPage();
|
||||||
this.tabPage3 = new System.Windows.Forms.TabPage();
|
this.tabPage3 = new System.Windows.Forms.TabPage();
|
||||||
this.notesBox = new System.Windows.Forms.TextBox();
|
this.notesBox = new System.Windows.Forms.TextBox();
|
||||||
@@ -516,9 +516,10 @@
|
|||||||
// materialBox
|
// materialBox
|
||||||
//
|
//
|
||||||
this.materialBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
this.materialBox.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
|
||||||
|
this.materialBox.FormattingEnabled = true;
|
||||||
this.materialBox.Location = new System.Drawing.Point(135, 159);
|
this.materialBox.Location = new System.Drawing.Point(135, 159);
|
||||||
this.materialBox.Name = "materialBox";
|
this.materialBox.Name = "materialBox";
|
||||||
this.materialBox.Size = new System.Drawing.Size(224, 22);
|
this.materialBox.Size = new System.Drawing.Size(224, 24);
|
||||||
this.materialBox.TabIndex = 11;
|
this.materialBox.TabIndex = 11;
|
||||||
//
|
//
|
||||||
// label3
|
// label3
|
||||||
@@ -729,6 +730,6 @@
|
|||||||
private System.Windows.Forms.RadioButton radioButton2;
|
private System.Windows.Forms.RadioButton radioButton2;
|
||||||
private System.Windows.Forms.Label label5;
|
private System.Windows.Forms.Label label5;
|
||||||
private System.Windows.Forms.Label labelMaterial;
|
private System.Windows.Forms.Label labelMaterial;
|
||||||
private System.Windows.Forms.TextBox materialBox;
|
private System.Windows.Forms.ComboBox materialBox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,9 @@ namespace OpenNest.Forms
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
foreach (var name in PostProcessorMaterials.Names)
|
||||||
|
materialBox.Items.Add(name);
|
||||||
|
|
||||||
timer = new Timer
|
timer = new Timer
|
||||||
{
|
{
|
||||||
SynchronizingObject = this,
|
SynchronizingObject = this,
|
||||||
|
|||||||
@@ -351,6 +351,9 @@ namespace OpenNest.Forms
|
|||||||
postProcessorMenuItem.Tag = postProcessor;
|
postProcessorMenuItem.Tag = postProcessor;
|
||||||
postProcessorMenuItem.Click += PostProcessor_Click;
|
postProcessorMenuItem.Click += PostProcessor_Click;
|
||||||
mnuNestPost.DropDownItems.Add(postProcessorMenuItem);
|
mnuNestPost.DropDownItems.Add(postProcessorMenuItem);
|
||||||
|
|
||||||
|
if (postProcessor is IMaterialProvidingPostProcessor materialProvider)
|
||||||
|
PostProcessorMaterials.AddFrom(materialProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1157,6 +1160,9 @@ namespace OpenNest.Forms
|
|||||||
if (postProcessor == null)
|
if (postProcessor == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (postProcessor is IPostProcessorNestAware nestAware)
|
||||||
|
nestAware.PrepareForNest(activeForm.Nest);
|
||||||
|
|
||||||
if (postProcessor is IConfigurablePostProcessor configurable)
|
if (postProcessor is IConfigurablePostProcessor configurable)
|
||||||
{
|
{
|
||||||
using var configForm = new PostProcessorConfigForm(configurable);
|
using var configForm = new PostProcessorConfigForm(configurable);
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace OpenNest
|
||||||
|
{
|
||||||
|
public static class PostProcessorMaterials
|
||||||
|
{
|
||||||
|
private static readonly List<string> materials = new();
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> Names => materials;
|
||||||
|
|
||||||
|
public static void AddFrom(IMaterialProvidingPostProcessor provider)
|
||||||
|
{
|
||||||
|
if (provider == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var name in provider.GetMaterialNames())
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(name)
|
||||||
|
&& !materials.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
materials.Add(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
materials.Sort(StringComparer.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user