mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-27 17:21:34 -04:00
Some jobs cut only cutouts into a supplied blank, such as instrument holes in a purchased door that is the whole sheet. Its outline has to be known to the planner (the blank is material that leads, rapids and cut-offs must respect) but must never be pierced, led or cut. A new DisplayPerimeter layer marks that outline. It is material, so nesting, containment, lead clearance and automatic cut-offs all see the blank; a door that fills its sheet leaves cut-offs nothing to cut. Cutting preparation keeps it out of the contours entirely: with no cut perimeter every contour is a hole with internal leads, and the search, best-effort and safest-route paths stop offering a perimeter. Replay refuses any run along the display perimeter and completes once every other ring is cut. A display perimeter must be one whole closed contour enclosing every other contour; a mixed ring, one inside another contour, or a second one is refused with that reason. Saving, drawing and posting it are still to do.
225 lines
7.1 KiB
C#
225 lines
7.1 KiB
C#
using System.Collections.Generic;
|
|
using OpenNest.CNC;
|
|
using OpenNest.Geometry;
|
|
using OpenNest.Math;
|
|
|
|
namespace OpenNest.Converters
|
|
{
|
|
public static class ConvertProgram
|
|
{
|
|
public static List<Entity> ToGeometry(Program pgm)
|
|
{
|
|
var geometry = new List<Entity>();
|
|
var curpos = new Vector();
|
|
var mode = Mode.Absolute;
|
|
|
|
AddProgram(pgm, ref mode, ref curpos, ref geometry);
|
|
|
|
return geometry;
|
|
}
|
|
|
|
private static void AddProgram(
|
|
Program program,
|
|
ref Mode mode,
|
|
ref Vector curpos,
|
|
ref List<Entity> geometry
|
|
)
|
|
{
|
|
// Capture the frame origin at entry. Sub-program Offsets are relative
|
|
// to this fixed origin, not to the current tool position.
|
|
var frameOrigin = curpos;
|
|
mode = program.Mode;
|
|
|
|
for (int i = 0; i < program.Length; ++i)
|
|
{
|
|
var code = program[i];
|
|
|
|
switch (code.Type)
|
|
{
|
|
case CodeType.ArcMove:
|
|
AddArcMove((ArcMove)code, ref mode, ref curpos, ref geometry);
|
|
break;
|
|
|
|
case CodeType.LinearMove:
|
|
AddLinearMove((LinearMove)code, ref mode, ref curpos, ref geometry);
|
|
break;
|
|
|
|
case CodeType.RapidMove:
|
|
AddRapidMove((RapidMove)code, ref mode, ref curpos, ref geometry);
|
|
break;
|
|
|
|
case CodeType.SubProgramCall:
|
|
var subpgm = (SubProgramCall)code;
|
|
var savedMode = mode;
|
|
|
|
// The sub-program's frame origin in this program's frame is
|
|
// frameOrigin + Offset — independent of current tool position.
|
|
curpos = new Vector(
|
|
frameOrigin.X + subpgm.Offset.X,
|
|
frameOrigin.Y + subpgm.Offset.Y
|
|
);
|
|
|
|
AddProgram(subpgm.Program, ref mode, ref curpos, ref geometry);
|
|
mode = savedMode;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AddLinearMove(
|
|
LinearMove linearMove,
|
|
ref Mode mode,
|
|
ref Vector curpos,
|
|
ref List<Entity> geometry
|
|
)
|
|
{
|
|
var pt = linearMove.EndPoint;
|
|
|
|
if (mode == Mode.Incremental)
|
|
pt += curpos;
|
|
|
|
var layer = ConvertLayer(linearMove.Layer);
|
|
var line = new Line(curpos, pt) { Layer = layer, Color = layer.Color };
|
|
geometry.Add(line);
|
|
curpos = pt;
|
|
}
|
|
|
|
private static void AddRapidMove(
|
|
RapidMove rapidMove,
|
|
ref Mode mode,
|
|
ref Vector curpos,
|
|
ref List<Entity> geometry
|
|
)
|
|
{
|
|
var pt = rapidMove.EndPoint;
|
|
|
|
if (mode == Mode.Incremental)
|
|
pt += curpos;
|
|
|
|
var line = new Line(curpos, pt)
|
|
{
|
|
Layer = SpecialLayers.Rapid,
|
|
Color = SpecialLayers.Rapid.Color,
|
|
};
|
|
geometry.Add(line);
|
|
curpos = pt;
|
|
}
|
|
|
|
private static void AddArcMove(
|
|
ArcMove arcMove,
|
|
ref Mode mode,
|
|
ref Vector curpos,
|
|
ref List<Entity> geometry
|
|
)
|
|
{
|
|
var center = arcMove.CenterPoint;
|
|
var endpt = arcMove.EndPoint;
|
|
|
|
if (mode == Mode.Incremental)
|
|
{
|
|
endpt += curpos;
|
|
center += curpos;
|
|
}
|
|
|
|
center = FitCenterToEndpoints(center, curpos, endpt);
|
|
|
|
var startAngle = center.AngleTo(curpos);
|
|
var endAngle = center.AngleTo(endpt);
|
|
|
|
var dx = endpt.X - center.X;
|
|
var dy = endpt.Y - center.Y;
|
|
|
|
var radius = System.Math.Sqrt(dx * dx + dy * dy);
|
|
var layer = ConvertLayer(arcMove.Layer);
|
|
|
|
if (startAngle.IsEqualTo(endAngle))
|
|
geometry.Add(
|
|
new Circle(center, radius)
|
|
{
|
|
Layer = layer,
|
|
Color = layer.Color,
|
|
Rotation = arcMove.Rotation,
|
|
}
|
|
);
|
|
else
|
|
geometry.Add(
|
|
new Arc(
|
|
center,
|
|
radius,
|
|
startAngle,
|
|
endAngle,
|
|
arcMove.Rotation == RotationType.CW
|
|
)
|
|
{
|
|
Layer = layer,
|
|
Color = layer.Color,
|
|
}
|
|
);
|
|
|
|
curpos = endpt;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Programs can carry arc centers that are not quite equidistant from the
|
|
/// start and end points (e.g. I0.03 on a 0.0598 chord). Building the arc from
|
|
/// the end radius alone then leaves its start point off the previous move's
|
|
/// end, which breaks contour chaining. Project the center onto the chord's
|
|
/// perpendicular bisector so the arc passes through both endpoints exactly.
|
|
/// </summary>
|
|
private static Vector FitCenterToEndpoints(Vector center, Vector start, Vector end)
|
|
{
|
|
var startRadius = center.DistanceTo(start);
|
|
var endRadius = center.DistanceTo(end);
|
|
|
|
if (startRadius.IsEqualTo(endRadius))
|
|
return center;
|
|
|
|
var chord = end - start;
|
|
var chordLengthSq = chord.X * chord.X + chord.Y * chord.Y;
|
|
|
|
// Full circle (start == end): no chord to fit against.
|
|
if (chordLengthSq < Tolerance.Epsilon * Tolerance.Epsilon)
|
|
return center;
|
|
|
|
var mid = new Vector((start.X + end.X) * 0.5, (start.Y + end.Y) * 0.5);
|
|
var normal = new Vector(-chord.Y, chord.X);
|
|
var t = ((center.X - mid.X) * normal.X + (center.Y - mid.Y) * normal.Y) / chordLengthSq;
|
|
|
|
return new Vector(mid.X + normal.X * t, mid.Y + normal.Y * t);
|
|
}
|
|
|
|
private static Layer ConvertLayer(LayerType layer)
|
|
{
|
|
switch (layer)
|
|
{
|
|
case LayerType.Cut:
|
|
return SpecialLayers.Cut;
|
|
|
|
case LayerType.Display:
|
|
return SpecialLayers.Display;
|
|
|
|
case LayerType.Leadin:
|
|
return SpecialLayers.Leadin;
|
|
|
|
case LayerType.Leadout:
|
|
return SpecialLayers.Leadout;
|
|
|
|
case LayerType.Scribe:
|
|
return SpecialLayers.Scribe;
|
|
|
|
case LayerType.Break:
|
|
return SpecialLayers.Break;
|
|
|
|
case LayerType.Tab:
|
|
return SpecialLayers.Tab;
|
|
|
|
case LayerType.DisplayPerimeter:
|
|
return SpecialLayers.DisplayPerimeter;
|
|
|
|
default:
|
|
return new Layer(layer.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|