style: apply CSharpier formatting to all C# sources
Repo-wide sweep with the pinned CSharpier 1.3.0 tool. Whitespace and line-wrapping only; OpenNest.Engine.Tests (109) and OpenNest.IO.Tests pass after reformat, full solution builds 0 errors. Added .csharpierignore so csproj/config XML keeps its existing layout (CSharpier's XML wrapping churns attributes with zero benefit). Formatting is now enforceable: dotnet csharpier check . passes.
This commit is contained in:
+35
-12
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -7,7 +7,10 @@ namespace OpenNest
|
||||
{
|
||||
public static void Vertically(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Center.X - movableEntity.BoundingBox.Center.X, 0);
|
||||
movableEntity.Offset(
|
||||
fixedEntity.BoundingBox.Center.X - movableEntity.BoundingBox.Center.X,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public static void Vertically(Entity fixedEntity, List<Entity> entities)
|
||||
@@ -17,7 +20,10 @@ namespace OpenNest
|
||||
|
||||
public static void Vertically(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(fixedPart.BoundingBox.Center.X - movablePart.BoundingBox.Center.X, 0);
|
||||
movablePart.Offset(
|
||||
fixedPart.BoundingBox.Center.X - movablePart.BoundingBox.Center.X,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public static void Vertically(Part fixedPart, List<Part> parts)
|
||||
@@ -27,7 +33,10 @@ namespace OpenNest
|
||||
|
||||
public static void Horizontally(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Center.Y - movableEntity.BoundingBox.Center.Y);
|
||||
movableEntity.Offset(
|
||||
0,
|
||||
fixedEntity.BoundingBox.Center.Y - movableEntity.BoundingBox.Center.Y
|
||||
);
|
||||
}
|
||||
|
||||
public static void Horizontally(Entity fixedEntity, List<Entity> entities)
|
||||
@@ -37,7 +46,10 @@ namespace OpenNest
|
||||
|
||||
public static void Horizontally(Part fixedPart, Part movablePart)
|
||||
{
|
||||
movablePart.Offset(0, fixedPart.BoundingBox.Center.Y - movablePart.BoundingBox.Center.Y);
|
||||
movablePart.Offset(
|
||||
0,
|
||||
fixedPart.BoundingBox.Center.Y - movablePart.BoundingBox.Center.Y
|
||||
);
|
||||
}
|
||||
|
||||
public static void Horizontally(Part fixedPart, List<Part> parts)
|
||||
@@ -67,7 +79,10 @@ namespace OpenNest
|
||||
|
||||
public static void Right(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(fixedEntity.BoundingBox.Right - movableEntity.BoundingBox.Right, 0);
|
||||
movableEntity.Offset(
|
||||
fixedEntity.BoundingBox.Right - movableEntity.BoundingBox.Right,
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
public static void Right(Entity fixedEntity, List<Entity> entities)
|
||||
@@ -107,7 +122,10 @@ namespace OpenNest
|
||||
|
||||
public static void Bottom(Entity fixedEntity, Entity movableEntity)
|
||||
{
|
||||
movableEntity.Offset(0, fixedEntity.BoundingBox.Bottom - movableEntity.BoundingBox.Bottom);
|
||||
movableEntity.Offset(
|
||||
0,
|
||||
fixedEntity.BoundingBox.Bottom - movableEntity.BoundingBox.Bottom
|
||||
);
|
||||
}
|
||||
|
||||
public static void Bottom(Entity fixedEntity, List<Entity> entities)
|
||||
@@ -137,14 +155,19 @@ namespace OpenNest
|
||||
return;
|
||||
|
||||
var list = new List<Part>(parts);
|
||||
list.Sort((p1, p2) => horizontal
|
||||
? p1.BoundingBox.Center.X.CompareTo(p2.BoundingBox.Center.X)
|
||||
: p1.BoundingBox.Center.Y.CompareTo(p2.BoundingBox.Center.Y));
|
||||
list.Sort(
|
||||
(p1, p2) =>
|
||||
horizontal
|
||||
? p1.BoundingBox.Center.X.CompareTo(p2.BoundingBox.Center.X)
|
||||
: p1.BoundingBox.Center.Y.CompareTo(p2.BoundingBox.Center.Y)
|
||||
);
|
||||
|
||||
var lastIndex = list.Count - 1;
|
||||
|
||||
var start = horizontal ? list[0].BoundingBox.Center.X : list[0].BoundingBox.Center.Y;
|
||||
var end = horizontal ? list[lastIndex].BoundingBox.Center.X : list[lastIndex].BoundingBox.Center.Y;
|
||||
var end = horizontal
|
||||
? list[lastIndex].BoundingBox.Center.X
|
||||
: list[lastIndex].BoundingBox.Center.Y;
|
||||
|
||||
var spacing = (end - start) / lastIndex;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum AlignType
|
||||
{
|
||||
@@ -10,6 +9,6 @@ namespace OpenNest
|
||||
Horizontally,
|
||||
Vertically,
|
||||
EvenlySpaceHorizontally,
|
||||
EvenlySpaceVertically
|
||||
EvenlySpaceVertically,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Bending
|
||||
{
|
||||
@@ -10,7 +10,7 @@ namespace OpenNest.Bending
|
||||
public static readonly Layer EtchLayer = new Layer("ETCH")
|
||||
{
|
||||
Color = Color.Green,
|
||||
IsVisible = true
|
||||
IsVisible = true,
|
||||
};
|
||||
|
||||
private const double DefaultEtchLength = 1.0;
|
||||
@@ -32,9 +32,8 @@ namespace OpenNest.Bending
|
||||
|
||||
public double Length => StartPoint.DistanceTo(EndPoint);
|
||||
|
||||
public double AngleRadians => Angle.HasValue
|
||||
? OpenNest.Math.Angle.ToRadians(Angle.Value)
|
||||
: 0;
|
||||
public double AngleRadians =>
|
||||
Angle.HasValue ? OpenNest.Math.Angle.ToRadians(Angle.Value) : 0;
|
||||
|
||||
public Line ToLine() => new Line(StartPoint, EndPoint);
|
||||
|
||||
@@ -66,7 +65,9 @@ namespace OpenNest.Bending
|
||||
var dx = System.Math.Cos(angle) * etchLength;
|
||||
var dy = System.Math.Sin(angle) * etchLength;
|
||||
|
||||
result.Add(CreateEtchLine(StartPoint, new Vector(StartPoint.X + dx, StartPoint.Y + dy)));
|
||||
result.Add(
|
||||
CreateEtchLine(StartPoint, new Vector(StartPoint.X + dx, StartPoint.Y + dy))
|
||||
);
|
||||
result.Add(CreateEtchLine(new Vector(EndPoint.X - dx, EndPoint.Y - dy), EndPoint));
|
||||
}
|
||||
|
||||
@@ -79,7 +80,8 @@ namespace OpenNest.Bending
|
||||
public static void UpdateEtchEntities(List<Entity> entities, List<Bend> bends)
|
||||
{
|
||||
entities.RemoveAll(e => e.Tag == BendEtchTag);
|
||||
if (bends == null) return;
|
||||
if (bends == null)
|
||||
return;
|
||||
|
||||
foreach (var bend in bends)
|
||||
entities.AddRange(bend.GetEtchEntities());
|
||||
@@ -87,7 +89,12 @@ namespace OpenNest.Bending
|
||||
|
||||
private static Line CreateEtchLine(Vector start, Vector end)
|
||||
{
|
||||
return new Line(start, end) { Layer = EtchLayer, Color = Color.Green, Tag = BendEtchTag };
|
||||
return new Line(start, end)
|
||||
{
|
||||
Layer = EtchLayer,
|
||||
Color = Color.Green,
|
||||
Tag = BendEtchTag,
|
||||
};
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace OpenNest.Bending
|
||||
{
|
||||
Unknown,
|
||||
Up,
|
||||
Down
|
||||
Down,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,16 +5,22 @@ namespace OpenNest.CNC
|
||||
{
|
||||
public class ArcMove : Motion
|
||||
{
|
||||
public ArcMove()
|
||||
{
|
||||
}
|
||||
public ArcMove() { }
|
||||
|
||||
public ArcMove(double x, double y, double i, double j, RotationType rotation = RotationType.CCW)
|
||||
: this(new Vector(x, y), new Vector(i, j), rotation)
|
||||
{
|
||||
}
|
||||
public ArcMove(
|
||||
double x,
|
||||
double y,
|
||||
double i,
|
||||
double j,
|
||||
RotationType rotation = RotationType.CCW
|
||||
)
|
||||
: this(new Vector(x, y), new Vector(i, j), rotation) { }
|
||||
|
||||
public ArcMove(Vector endPoint, Vector centerPoint, RotationType rotation = RotationType.CCW)
|
||||
public ArcMove(
|
||||
Vector endPoint,
|
||||
Vector centerPoint,
|
||||
RotationType rotation = RotationType.CCW
|
||||
)
|
||||
{
|
||||
EndPoint = endPoint;
|
||||
CenterPoint = centerPoint;
|
||||
@@ -68,7 +74,8 @@ namespace OpenNest.CNC
|
||||
{
|
||||
Layer = Layer,
|
||||
Suppressed = Suppressed,
|
||||
VariableRefs = VariableRefs != null ? new Dictionary<string, string>(VariableRefs) : null
|
||||
VariableRefs =
|
||||
VariableRefs != null ? new Dictionary<string, string>(VariableRefs) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -85,9 +92,9 @@ namespace OpenNest.CNC
|
||||
var i = CenterPoint.X.ToString(dp);
|
||||
var j = CenterPoint.Y.ToString(dp);
|
||||
|
||||
return Rotation == RotationType.CW ?
|
||||
string.Format("G02 X{0} Y{1} I{2} J{3}", x, y, i, j) :
|
||||
string.Format("G03 X{0} Y{1} I{2} J{3}", x, y, i, j);
|
||||
return Rotation == RotationType.CW
|
||||
? string.Format("G02 X{0} Y{1} I{2} J{3}", x, y, i, j)
|
||||
: string.Format("G03 X{0} Y{1} I{2} J{3}", x, y, i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum CodeType
|
||||
{
|
||||
@@ -9,6 +8,6 @@ namespace OpenNest.CNC
|
||||
RapidMove,
|
||||
SetFeedrate,
|
||||
SetKerf,
|
||||
SubProgramCall
|
||||
SubProgramCall,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
{
|
||||
public class Comment : ICode
|
||||
{
|
||||
public Comment()
|
||||
{
|
||||
}
|
||||
public Comment() { }
|
||||
|
||||
public Comment(string value)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -48,7 +48,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
for (var iter = 0; iter < 3; iter++)
|
||||
{
|
||||
var lastCutoutPt = cutoutEntries[cutoutEntries.Count - 1].Point;
|
||||
perimeterSeed = FindPerimeterIntersection(profile.Perimeter, lastCutoutPt, nextPartStart, out _);
|
||||
perimeterSeed = FindPerimeterIntersection(
|
||||
profile.Perimeter,
|
||||
lastCutoutPt,
|
||||
nextPartStart,
|
||||
out _
|
||||
);
|
||||
|
||||
orderedCutouts = SequenceCutouts(profile.Cutouts, perimeterSeed);
|
||||
orderedCutouts.Reverse();
|
||||
@@ -56,7 +61,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
}
|
||||
|
||||
var finalLastCutout = cutoutEntries[cutoutEntries.Count - 1].Point;
|
||||
perimeterPt = FindPerimeterIntersection(profile.Perimeter, finalLastCutout, nextPartStart, out perimeterEntity);
|
||||
perimeterPt = FindPerimeterIntersection(
|
||||
profile.Perimeter,
|
||||
finalLastCutout,
|
||||
nextPartStart,
|
||||
out perimeterEntity
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -79,18 +89,25 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
if (!profile.Perimeter.IsClosed())
|
||||
EmitRawContour(result, profile.Perimeter);
|
||||
else
|
||||
EmitContour(result, profile.Perimeter, perimeterPt, perimeterEntity, ContourType.External);
|
||||
EmitContour(
|
||||
result,
|
||||
profile.Perimeter,
|
||||
perimeterPt,
|
||||
perimeterEntity,
|
||||
ContourType.External
|
||||
);
|
||||
|
||||
result.Mode = Mode.Incremental;
|
||||
|
||||
return new CuttingResult
|
||||
{
|
||||
Program = result,
|
||||
LastCutPoint = perimeterPt
|
||||
};
|
||||
return new CuttingResult { Program = result, LastCutPoint = perimeterPt };
|
||||
}
|
||||
|
||||
public CuttingResult ApplySingle(Program partProgram, Vector point, Entity entity, ContourType contourType)
|
||||
public CuttingResult ApplySingle(
|
||||
Program partProgram,
|
||||
Vector point,
|
||||
Entity entity,
|
||||
ContourType contourType
|
||||
)
|
||||
{
|
||||
var entities = partProgram.ToGeometry();
|
||||
entities.RemoveAll(e => e.Layer == SpecialLayers.Rapid);
|
||||
@@ -141,14 +158,14 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
result.Mode = Mode.Incremental;
|
||||
|
||||
return new CuttingResult
|
||||
{
|
||||
Program = result,
|
||||
LastCutPoint = point
|
||||
};
|
||||
return new CuttingResult { Program = result, LastCutPoint = point };
|
||||
}
|
||||
|
||||
private static (Shape Shape, Entity Entity) FindTargetShape(ShapeProfile profile, Vector point, Entity clickedEntity)
|
||||
private static (Shape Shape, Entity Entity) FindTargetShape(
|
||||
ShapeProfile profile,
|
||||
Vector point,
|
||||
Entity clickedEntity
|
||||
)
|
||||
{
|
||||
var matched = FindMatchingEntity(profile.Perimeter, clickedEntity);
|
||||
if (matched != null)
|
||||
@@ -190,20 +207,26 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
if (shapeEntity is Line sLine && clickedEntity is Line cLine)
|
||||
{
|
||||
if (sLine.StartPoint.DistanceTo(cLine.StartPoint) < Math.Tolerance.Epsilon
|
||||
&& sLine.EndPoint.DistanceTo(cLine.EndPoint) < Math.Tolerance.Epsilon)
|
||||
if (
|
||||
sLine.StartPoint.DistanceTo(cLine.StartPoint) < Math.Tolerance.Epsilon
|
||||
&& sLine.EndPoint.DistanceTo(cLine.EndPoint) < Math.Tolerance.Epsilon
|
||||
)
|
||||
return shapeEntity;
|
||||
}
|
||||
else if (shapeEntity is Arc sArc && clickedEntity is Arc cArc)
|
||||
{
|
||||
if (System.Math.Abs(sArc.Radius - cArc.Radius) < Math.Tolerance.Epsilon
|
||||
&& sArc.Center.DistanceTo(cArc.Center) < Math.Tolerance.Epsilon)
|
||||
if (
|
||||
System.Math.Abs(sArc.Radius - cArc.Radius) < Math.Tolerance.Epsilon
|
||||
&& sArc.Center.DistanceTo(cArc.Center) < Math.Tolerance.Epsilon
|
||||
)
|
||||
return shapeEntity;
|
||||
}
|
||||
else if (shapeEntity is Circle sCircle && clickedEntity is Circle cCircle)
|
||||
{
|
||||
if (System.Math.Abs(sCircle.Radius - cCircle.Radius) < Math.Tolerance.Epsilon
|
||||
&& sCircle.Center.DistanceTo(cCircle.Center) < Math.Tolerance.Epsilon)
|
||||
if (
|
||||
System.Math.Abs(sCircle.Radius - cCircle.Radius) < Math.Tolerance.Epsilon
|
||||
&& sCircle.Center.DistanceTo(cCircle.Center) < Math.Tolerance.Epsilon
|
||||
)
|
||||
return shapeEntity;
|
||||
}
|
||||
}
|
||||
@@ -218,7 +241,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
program.Codes.AddRange(ConvertShapeToMoves(shape, startPoint));
|
||||
}
|
||||
|
||||
private static List<ContourEntry> ResolveLeadInPoints(List<Shape> cutouts, Vector startPoint)
|
||||
private static List<ContourEntry> ResolveLeadInPoints(
|
||||
List<Shape> cutouts,
|
||||
Vector startPoint
|
||||
)
|
||||
{
|
||||
var entries = new ContourEntry[cutouts.Count];
|
||||
var currentPoint = startPoint;
|
||||
@@ -235,7 +261,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return new List<ContourEntry>(entries);
|
||||
}
|
||||
|
||||
private static Vector FindPerimeterIntersection(Shape perimeter, Vector lastCutout, Vector nextPartStart, out Entity entity)
|
||||
private static Vector FindPerimeterIntersection(
|
||||
Shape perimeter,
|
||||
Vector lastCutout,
|
||||
Vector nextPartStart,
|
||||
out Entity entity
|
||||
)
|
||||
{
|
||||
var ray = new Line(lastCutout, nextPartStart);
|
||||
|
||||
@@ -269,7 +300,13 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return HashCode.Combine(r, a);
|
||||
}
|
||||
|
||||
private void EmitContour(Program program, Shape shape, Vector point, Entity entity, ContourType? forceType = null)
|
||||
private void EmitContour(
|
||||
Program program,
|
||||
Shape shape,
|
||||
Vector point,
|
||||
Entity entity,
|
||||
ContourType? forceType = null
|
||||
)
|
||||
{
|
||||
var contourType = forceType ?? DetectContourType(shape);
|
||||
var winding = DetermineWinding(shape);
|
||||
@@ -289,7 +326,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var outwardAngle = normal - System.Math.PI;
|
||||
point = new Vector(
|
||||
circle.Center.X + circle.Radius * System.Math.Cos(outwardAngle),
|
||||
circle.Center.Y + circle.Radius * System.Math.Sin(outwardAngle));
|
||||
circle.Center.Y + circle.Radius * System.Math.Sin(outwardAngle)
|
||||
);
|
||||
}
|
||||
|
||||
leadIn = ClampLeadInForCircle(leadIn, circle, point, normal);
|
||||
@@ -297,7 +335,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
// Build hole sub-program relative to (0,0)
|
||||
var holeCenter = circle.Center;
|
||||
var relativePoint = new Vector(point.X - holeCenter.X, point.Y - holeCenter.Y);
|
||||
var relativeCircle = new Circle(new Vector(0, 0), circle.Radius) { Rotation = circle.Rotation };
|
||||
var relativeCircle = new Circle(new Vector(0, 0), circle.Radius)
|
||||
{
|
||||
Rotation = circle.Rotation,
|
||||
};
|
||||
var relativeShape = new Shape();
|
||||
relativeShape.Entities.Add(relativeCircle);
|
||||
|
||||
@@ -314,12 +355,14 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
if (!program.SubPrograms.ContainsKey(key))
|
||||
program.SubPrograms[key] = subPgm;
|
||||
|
||||
program.Codes.Add(new SubProgramCall
|
||||
{
|
||||
Id = key,
|
||||
Program = program.SubPrograms[key],
|
||||
Offset = holeCenter
|
||||
});
|
||||
program.Codes.Add(
|
||||
new SubProgramCall
|
||||
{
|
||||
Id = key,
|
||||
Program = program.SubPrograms[key],
|
||||
Offset = holeCenter,
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -328,7 +371,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
var reindexedShape = shape.ReindexAt(point, entity);
|
||||
|
||||
if (Parameters.TabsEnabled && Parameters.TabConfig != null && contourType == ContourType.External)
|
||||
if (
|
||||
Parameters.TabsEnabled
|
||||
&& Parameters.TabConfig != null
|
||||
&& contourType == ContourType.External
|
||||
)
|
||||
reindexedShape = TrimShapeForTab(reindexedShape, point, Parameters.TabConfig.Size);
|
||||
|
||||
program.Codes.AddRange(ConvertShapeToMoves(reindexedShape, point));
|
||||
@@ -337,7 +384,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
private void EmitScribeContours(Program program, List<Entity> scribeEntities)
|
||||
{
|
||||
if (scribeEntities.Count == 0) return;
|
||||
if (scribeEntities.Count == 0)
|
||||
return;
|
||||
|
||||
var shapes = ShapeBuilder.GetShapes(scribeEntities);
|
||||
foreach (var shape in shapes)
|
||||
@@ -388,8 +436,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return ContourType.Internal;
|
||||
}
|
||||
|
||||
public static double ComputeNormal(Vector point, Entity entity, ContourType contourType,
|
||||
RotationType winding = RotationType.CW)
|
||||
public static double ComputeNormal(
|
||||
Vector point,
|
||||
Entity entity,
|
||||
ContourType contourType,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
double normal;
|
||||
|
||||
@@ -442,7 +494,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
return polygon.RotationDirection();
|
||||
}
|
||||
|
||||
private LeadIn ClampLeadInForCircle(LeadIn leadIn, Circle circle, Vector contourPoint, double normalAngle)
|
||||
private LeadIn ClampLeadInForCircle(
|
||||
LeadIn leadIn,
|
||||
Circle circle,
|
||||
Vector contourPoint,
|
||||
double normalAngle
|
||||
)
|
||||
{
|
||||
if (leadIn is NoLeadIn || Parameters.PierceClearance <= 0)
|
||||
return leadIn;
|
||||
@@ -492,7 +549,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
ContourType.ArcCircle => Parameters.ArcCircleLeadIn ?? Parameters.InternalLeadIn,
|
||||
ContourType.Internal => Parameters.InternalLeadIn,
|
||||
_ => Parameters.ExternalLeadIn
|
||||
_ => Parameters.ExternalLeadIn,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -502,7 +559,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
ContourType.ArcCircle => Parameters.ArcCircleLeadOut ?? Parameters.InternalLeadOut,
|
||||
ContourType.Internal => Parameters.InternalLeadOut,
|
||||
_ => Parameters.ExternalLeadOut
|
||||
_ => Parameters.ExternalLeadOut,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -565,12 +622,18 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
private static Vector EntityStartPoint(Entity entity)
|
||||
{
|
||||
if (entity is Line line) return line.StartPoint;
|
||||
if (entity is Arc arc) return arc.StartPoint();
|
||||
if (entity is Line line)
|
||||
return line.StartPoint;
|
||||
if (entity is Arc arc)
|
||||
return arc.StartPoint();
|
||||
return Vector.Zero;
|
||||
}
|
||||
|
||||
private List<ICode> ConvertShapeToMoves(Shape shape, Vector startPoint, LayerType layer = LayerType.Display)
|
||||
private List<ICode> ConvertShapeToMoves(
|
||||
Shape shape,
|
||||
Vector startPoint,
|
||||
LayerType layer = LayerType.Display
|
||||
)
|
||||
{
|
||||
var moves = new List<ICode>();
|
||||
|
||||
@@ -582,15 +645,28 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
}
|
||||
else if (entity is Arc arc)
|
||||
{
|
||||
moves.Add(new ArcMove(arc.EndPoint(), arc.Center, arc.IsReversed ? RotationType.CW : RotationType.CCW) { Layer = layer });
|
||||
moves.Add(
|
||||
new ArcMove(
|
||||
arc.EndPoint(),
|
||||
arc.Center,
|
||||
arc.IsReversed ? RotationType.CW : RotationType.CCW
|
||||
)
|
||||
{
|
||||
Layer = layer,
|
||||
}
|
||||
);
|
||||
}
|
||||
else if (entity is Circle circle)
|
||||
{
|
||||
moves.Add(new ArcMove(startPoint, circle.Center, circle.Rotation) { Layer = layer });
|
||||
moves.Add(
|
||||
new ArcMove(startPoint, circle.Center, circle.Rotation) { Layer = layer }
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new System.InvalidOperationException($"Unsupported entity type: {entity.Type}");
|
||||
throw new System.InvalidOperationException(
|
||||
$"Unsupported entity type: {entity.Type}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -600,9 +676,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
private static Vector GetShapeStartPoint(Shape shape)
|
||||
{
|
||||
var first = shape.Entities[0];
|
||||
if (first is Line line) return line.StartPoint;
|
||||
if (first is Arc arc) return arc.StartPoint();
|
||||
if (first is Circle circle) return new Vector(circle.Center.X + circle.Radius, circle.Center.Y);
|
||||
if (first is Line line)
|
||||
return line.StartPoint;
|
||||
if (first is Arc arc)
|
||||
return arc.StartPoint();
|
||||
if (first is Circle circle)
|
||||
return new Vector(circle.Center.X + circle.Radius, circle.Center.Y);
|
||||
return Vector.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
External,
|
||||
Internal,
|
||||
ArcCircle
|
||||
ArcCircle,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public LeadIn ExternalLeadIn { get; set; } = new NoLeadIn();
|
||||
public LeadOut ExternalLeadOut { get; set; } = new NoLeadOut();
|
||||
|
||||
public LeadIn InternalLeadIn { get; set; } = new LineLeadIn { Length = 0.125, ApproachAngle = 90 };
|
||||
public LeadIn InternalLeadIn { get; set; } =
|
||||
new LineLeadIn { Length = 0.125, ApproachAngle = 90 };
|
||||
public LeadOut InternalLeadOut { get; set; } = new NoLeadOut();
|
||||
|
||||
public LeadIn ArcCircleLeadIn { get; set; } = new NoLeadIn();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -7,19 +7,23 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
public double Radius { get; set; }
|
||||
|
||||
public override List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle);
|
||||
|
||||
var arcCenter = new Vector(
|
||||
contourStartPoint.X + Radius * System.Math.Cos(contourNormalAngle),
|
||||
contourStartPoint.Y + Radius * System.Math.Sin(contourNormalAngle));
|
||||
contourStartPoint.Y + Radius * System.Math.Sin(contourNormalAngle)
|
||||
);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(piercePoint),
|
||||
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin }
|
||||
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,10 +34,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
return new Vector(
|
||||
arcCenterX + Radius * System.Math.Cos(contourNormalAngle),
|
||||
arcCenterY + Radius * System.Math.Sin(contourNormalAngle));
|
||||
arcCenterY + Radius * System.Math.Sin(contourNormalAngle)
|
||||
);
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new ArcLeadIn { Radius = Radius * factor };
|
||||
public override LeadIn Scale(double factor) => new ArcLeadIn { Radius = Radius * factor };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -10,8 +10,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double ArcRadius { get; set; }
|
||||
public double Kerf { get; set; }
|
||||
|
||||
public override List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle);
|
||||
|
||||
@@ -22,13 +25,14 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var lineAngle = contourNormalAngle + Angle.ToRadians(135.0);
|
||||
var arcStart = new Vector(
|
||||
arcCenterX + ArcRadius * System.Math.Cos(lineAngle),
|
||||
arcCenterY + ArcRadius * System.Math.Sin(lineAngle));
|
||||
arcCenterY + ArcRadius * System.Math.Sin(lineAngle)
|
||||
);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(piercePoint),
|
||||
new LinearMove(arcStart) { Layer = LayerType.Leadin },
|
||||
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin }
|
||||
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,10 +47,16 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
return new Vector(
|
||||
arcStartX + LineLength * System.Math.Cos(lineAngle),
|
||||
arcStartY + LineLength * System.Math.Sin(lineAngle));
|
||||
arcStartY + LineLength * System.Math.Sin(lineAngle)
|
||||
);
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new CleanHoleLeadIn { LineLength = LineLength * factor, ArcRadius = ArcRadius * factor, Kerf = Kerf };
|
||||
new CleanHoleLeadIn
|
||||
{
|
||||
LineLength = LineLength * factor,
|
||||
ArcRadius = ArcRadius * factor,
|
||||
Kerf = Kerf,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
public abstract class LeadIn
|
||||
{
|
||||
public abstract List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW);
|
||||
public abstract List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
);
|
||||
|
||||
public abstract Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -10,8 +10,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double ApproachAngle { get; set; } = 135.0;
|
||||
public double ArcRadius { get; set; }
|
||||
|
||||
public override List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle);
|
||||
|
||||
@@ -22,13 +25,14 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var lineAngle = contourNormalAngle + Angle.ToRadians(ApproachAngle);
|
||||
var arcStart = new Vector(
|
||||
arcCenterX + ArcRadius * System.Math.Cos(lineAngle),
|
||||
arcCenterY + ArcRadius * System.Math.Sin(lineAngle));
|
||||
arcCenterY + ArcRadius * System.Math.Sin(lineAngle)
|
||||
);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(piercePoint),
|
||||
new LinearMove(arcStart) { Layer = LayerType.Leadin },
|
||||
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin }
|
||||
new ArcMove(contourStartPoint, arcCenter, winding) { Layer = LayerType.Leadin },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -43,10 +47,16 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
return new Vector(
|
||||
arcStartX + LineLength * System.Math.Cos(lineAngle),
|
||||
arcStartY + LineLength * System.Math.Sin(lineAngle));
|
||||
arcStartY + LineLength * System.Math.Sin(lineAngle)
|
||||
);
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new LineArcLeadIn { LineLength = LineLength * factor, ArcRadius = ArcRadius * factor, ApproachAngle = ApproachAngle };
|
||||
new LineArcLeadIn
|
||||
{
|
||||
LineLength = LineLength * factor,
|
||||
ArcRadius = ArcRadius * factor,
|
||||
ApproachAngle = ApproachAngle,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -9,15 +9,18 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double Length { get; set; }
|
||||
public double ApproachAngle { get; set; } = 90.0;
|
||||
|
||||
public override List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(piercePoint),
|
||||
new LinearMove(contourStartPoint) { Layer = LayerType.Leadin }
|
||||
new LinearMove(contourStartPoint) { Layer = LayerType.Leadin },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -26,7 +29,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var approachAngle = contourNormalAngle - Angle.HalfPI + Angle.ToRadians(ApproachAngle);
|
||||
return new Vector(
|
||||
contourStartPoint.X + Length * System.Math.Cos(approachAngle),
|
||||
contourStartPoint.Y + Length * System.Math.Sin(approachAngle));
|
||||
contourStartPoint.Y + Length * System.Math.Sin(approachAngle)
|
||||
);
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -11,21 +11,25 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double Length2 { get; set; }
|
||||
public double ApproachAngle2 { get; set; } = 90.0;
|
||||
|
||||
public override List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var piercePoint = GetPiercePoint(contourStartPoint, contourNormalAngle);
|
||||
|
||||
var secondAngle = contourNormalAngle - Angle.HalfPI + Angle.ToRadians(ApproachAngle1);
|
||||
var midPoint = new Vector(
|
||||
contourStartPoint.X + Length2 * System.Math.Cos(secondAngle),
|
||||
contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle));
|
||||
contourStartPoint.Y + Length2 * System.Math.Sin(secondAngle)
|
||||
);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(piercePoint),
|
||||
new LinearMove(midPoint) { Layer = LayerType.Leadin },
|
||||
new LinearMove(contourStartPoint) { Layer = LayerType.Leadin }
|
||||
new LinearMove(contourStartPoint) { Layer = LayerType.Leadin },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,10 +42,17 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var firstAngle = secondAngle + Angle.ToRadians(ApproachAngle2);
|
||||
return new Vector(
|
||||
midX + Length1 * System.Math.Cos(firstAngle),
|
||||
midY + Length1 * System.Math.Sin(firstAngle));
|
||||
midY + Length1 * System.Math.Sin(firstAngle)
|
||||
);
|
||||
}
|
||||
|
||||
public override LeadIn Scale(double factor) =>
|
||||
new LineLineLeadIn { Length1 = Length1 * factor, ApproachAngle1 = ApproachAngle1, Length2 = Length2 * factor, ApproachAngle2 = ApproachAngle2 };
|
||||
new LineLineLeadIn
|
||||
{
|
||||
Length1 = Length1 * factor,
|
||||
ApproachAngle1 = ApproachAngle1,
|
||||
Length2 = Length2 * factor,
|
||||
ApproachAngle2 = ApproachAngle2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
public class NoLeadIn : LeadIn
|
||||
{
|
||||
public override List<ICode> Generate(Vector contourStartPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourStartPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(contourStartPoint)
|
||||
};
|
||||
return new List<ICode> { new RapidMove(contourStartPoint) };
|
||||
}
|
||||
|
||||
public override Vector GetPiercePoint(Vector contourStartPoint, double contourNormalAngle)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -7,8 +7,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
public double Radius { get; set; }
|
||||
|
||||
public override List<ICode> Generate(Vector contourEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var arcCenterX = contourEndPoint.X + Radius * System.Math.Cos(contourNormalAngle);
|
||||
var arcCenterY = contourEndPoint.Y + Radius * System.Math.Sin(contourNormalAngle);
|
||||
@@ -16,11 +19,12 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
var endPoint = new Vector(
|
||||
arcCenterX + Radius * System.Math.Cos(contourNormalAngle + System.Math.PI / 2),
|
||||
arcCenterY + Radius * System.Math.Sin(contourNormalAngle + System.Math.PI / 2));
|
||||
arcCenterY + Radius * System.Math.Sin(contourNormalAngle + System.Math.PI / 2)
|
||||
);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new ArcMove(endPoint, arcCenter, winding) { Layer = LayerType.Leadout }
|
||||
new ArcMove(endPoint, arcCenter, winding) { Layer = LayerType.Leadout },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
public abstract class LeadOut
|
||||
{
|
||||
public abstract List<ICode> Generate(Vector contourEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW);
|
||||
public abstract List<ICode> Generate(
|
||||
Vector contourEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -9,18 +9,19 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double Length { get; set; }
|
||||
public double ApproachAngle { get; set; } = 90.0;
|
||||
|
||||
public override List<ICode> Generate(Vector contourEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var overcutAngle = contourNormalAngle + Angle.HalfPI - Angle.ToRadians(ApproachAngle);
|
||||
var endPoint = new Vector(
|
||||
contourEndPoint.X + Length * System.Math.Cos(overcutAngle),
|
||||
contourEndPoint.Y + Length * System.Math.Sin(overcutAngle));
|
||||
contourEndPoint.Y + Length * System.Math.Sin(overcutAngle)
|
||||
);
|
||||
|
||||
return new List<ICode>
|
||||
{
|
||||
new LinearMove(endPoint) { Layer = LayerType.Leadout }
|
||||
};
|
||||
return new List<ICode> { new LinearMove(endPoint) { Layer = LayerType.Leadout } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
public class NoLeadOut : LeadOut
|
||||
{
|
||||
public override List<ICode> Generate(Vector contourEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
public override List<ICode> Generate(
|
||||
Vector contourEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
return new List<ICode>();
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
BottomSide = 4,
|
||||
EdgeStart = 5,
|
||||
LeftSide = 7,
|
||||
RightSideAlt = 8
|
||||
RightSideAlt = 8,
|
||||
}
|
||||
|
||||
public class SequenceParameters
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -10,8 +10,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double BreakerAngle { get; set; }
|
||||
|
||||
public override List<ICode> Generate(
|
||||
Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
Vector tabStartPoint,
|
||||
Vector tabEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var codes = new List<ICode>();
|
||||
|
||||
@@ -21,7 +24,8 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
var scoreAngle = contourNormalAngle + System.Math.PI;
|
||||
var scoreEnd = new Vector(
|
||||
tabStartPoint.X + BreakerDepth * System.Math.Cos(scoreAngle),
|
||||
tabStartPoint.Y + BreakerDepth * System.Math.Sin(scoreAngle));
|
||||
tabStartPoint.Y + BreakerDepth * System.Math.Sin(scoreAngle)
|
||||
);
|
||||
codes.Add(new LinearMove(scoreEnd));
|
||||
codes.Add(new RapidMove(tabEndPoint));
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -8,13 +8,13 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public int MachineTabId { get; set; }
|
||||
|
||||
public override List<ICode> Generate(
|
||||
Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
Vector tabStartPoint,
|
||||
Vector tabEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
return new List<ICode>
|
||||
{
|
||||
new RapidMove(tabEndPoint)
|
||||
};
|
||||
return new List<ICode> { new RapidMove(tabEndPoint) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -11,8 +11,11 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public double CutoutMaxHeight { get; set; }
|
||||
|
||||
public override List<ICode> Generate(
|
||||
Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW)
|
||||
Vector tabStartPoint,
|
||||
Vector tabEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
)
|
||||
{
|
||||
var codes = new List<ICode>();
|
||||
|
||||
@@ -29,8 +32,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
|
||||
public bool AppliesToCutout(double cutoutWidth, double cutoutHeight)
|
||||
{
|
||||
return cutoutWidth >= CutoutMinWidth && cutoutWidth <= CutoutMaxWidth
|
||||
&& cutoutHeight >= CutoutMinHeight && cutoutHeight <= CutoutMaxHeight;
|
||||
return cutoutWidth >= CutoutMinWidth
|
||||
&& cutoutWidth <= CutoutMaxWidth
|
||||
&& cutoutHeight >= CutoutMinHeight
|
||||
&& cutoutHeight <= CutoutMaxHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC.CuttingStrategy
|
||||
{
|
||||
@@ -10,7 +10,10 @@ namespace OpenNest.CNC.CuttingStrategy
|
||||
public LeadOut TabLeadOut { get; set; }
|
||||
|
||||
public abstract List<ICode> Generate(
|
||||
Vector tabStartPoint, Vector tabEndPoint, double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW);
|
||||
Vector tabStartPoint,
|
||||
Vector tabEndPoint,
|
||||
double contourNormalAngle,
|
||||
RotationType winding = RotationType.CW
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
public const int UseMax = -2;
|
||||
|
||||
public Feedrate()
|
||||
{
|
||||
}
|
||||
public Feedrate() { }
|
||||
|
||||
public Feedrate(double value)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum KerfType
|
||||
{
|
||||
None,
|
||||
Left,
|
||||
Right
|
||||
Right,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum LayerType
|
||||
{
|
||||
@@ -7,6 +6,6 @@ namespace OpenNest.CNC
|
||||
Scribe,
|
||||
Cut,
|
||||
Leadin,
|
||||
Leadout
|
||||
Leadout,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,10 @@ namespace OpenNest.CNC
|
||||
public class LinearMove : Motion
|
||||
{
|
||||
public LinearMove()
|
||||
: this(new Vector())
|
||||
{
|
||||
}
|
||||
: this(new Vector()) { }
|
||||
|
||||
public LinearMove(double x, double y)
|
||||
: this(new Vector(x, y))
|
||||
{
|
||||
}
|
||||
: this(new Vector(x, y)) { }
|
||||
|
||||
public LinearMove(Vector endPoint)
|
||||
{
|
||||
@@ -34,7 +30,8 @@ namespace OpenNest.CNC
|
||||
{
|
||||
Layer = Layer,
|
||||
Suppressed = Suppressed,
|
||||
VariableRefs = VariableRefs != null ? new Dictionary<string, string>(VariableRefs) : null
|
||||
VariableRefs =
|
||||
VariableRefs != null ? new Dictionary<string, string>(VariableRefs) : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
namespace OpenNest.CNC
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
public enum Mode
|
||||
{
|
||||
Absolute,
|
||||
Incremental
|
||||
Incremental,
|
||||
}
|
||||
}
|
||||
|
||||
+186
-175
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
@@ -10,7 +10,8 @@ namespace OpenNest.CNC
|
||||
{
|
||||
public List<ICode> Codes;
|
||||
|
||||
public Dictionary<string, VariableDefinition> Variables { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
public Dictionary<string, VariableDefinition> Variables { get; } =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public Dictionary<int, Program> SubPrograms { get; } = new();
|
||||
|
||||
@@ -66,9 +67,17 @@ namespace OpenNest.CNC
|
||||
{
|
||||
if (code is Motion m)
|
||||
{
|
||||
var cmd = m is RapidMove ? "G00" : (m is ArcMove am ? (am.Rotation == RotationType.CW ? "G02" : "G03") : "G01");
|
||||
var cmd =
|
||||
m is RapidMove
|
||||
? "G00"
|
||||
: (
|
||||
m is ArcMove am
|
||||
? (am.Rotation == RotationType.CW ? "G02" : "G03")
|
||||
: "G01"
|
||||
);
|
||||
sb.Append($"{cmd}X{m.EndPoint.X:F4}Y{m.EndPoint.Y:F4}");
|
||||
if (m is ArcMove arc) sb.Append($"I{arc.CenterPoint.X:F4}J{arc.CenterPoint.Y:F4}");
|
||||
if (m is ArcMove arc)
|
||||
sb.Append($"I{arc.CenterPoint.X:F4}J{arc.CenterPoint.Y:F4}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
@@ -97,7 +106,8 @@ namespace OpenNest.CNC
|
||||
var dy = subpgm.Offset.Y - origin.Y;
|
||||
subpgm.Offset = new Geometry.Vector(
|
||||
origin.X + dx * cos - dy * sin,
|
||||
origin.Y + dx * sin + dy * cos);
|
||||
origin.Y + dx * sin + dy * cos
|
||||
);
|
||||
}
|
||||
|
||||
if (subpgm.Program != null)
|
||||
@@ -130,8 +140,7 @@ namespace OpenNest.CNC
|
||||
|
||||
if (code is SubProgramCall subpgm)
|
||||
{
|
||||
subpgm.Offset = new Geometry.Vector(
|
||||
subpgm.Offset.X + x, subpgm.Offset.Y + y);
|
||||
subpgm.Offset = new Geometry.Vector(subpgm.Offset.X + x, subpgm.Offset.Y + y);
|
||||
}
|
||||
|
||||
if (code is Motion == false)
|
||||
@@ -159,7 +168,9 @@ namespace OpenNest.CNC
|
||||
if (code is SubProgramCall subpgm)
|
||||
{
|
||||
subpgm.Offset = new Geometry.Vector(
|
||||
subpgm.Offset.X + voffset.X, subpgm.Offset.Y + voffset.Y);
|
||||
subpgm.Offset.X + voffset.X,
|
||||
subpgm.Offset.Y + voffset.Y
|
||||
);
|
||||
}
|
||||
|
||||
if (code is Motion == false)
|
||||
@@ -258,35 +269,37 @@ namespace OpenNest.CNC
|
||||
switch (Mode)
|
||||
{
|
||||
case Mode.Absolute:
|
||||
{
|
||||
for (int i = Codes.Count; i >= 0; --i)
|
||||
{
|
||||
for (int i = Codes.Count; i >= 0; --i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
if (motion == null) continue;
|
||||
if (motion == null)
|
||||
continue;
|
||||
|
||||
return motion.EndPoint;
|
||||
}
|
||||
break;
|
||||
return motion.EndPoint;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case Mode.Incremental:
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var pos = new Vector(0, 0);
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
|
||||
for (int i = 0; i < Codes.Count; ++i)
|
||||
{
|
||||
var code = Codes[i];
|
||||
var motion = code as Motion;
|
||||
if (motion == null)
|
||||
continue;
|
||||
|
||||
if (motion == null) continue;
|
||||
|
||||
pos += motion.EndPoint;
|
||||
}
|
||||
|
||||
return pos;
|
||||
pos += motion.EndPoint;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
|
||||
return new Vector(0, 0);
|
||||
@@ -316,161 +329,163 @@ namespace OpenNest.CNC
|
||||
switch (code.Type)
|
||||
{
|
||||
case CodeType.LinearMove:
|
||||
{
|
||||
var line = (LinearMove)code;
|
||||
var pt = Mode == Mode.Absolute ?
|
||||
frameOrigin + line.EndPoint :
|
||||
line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.RapidMove:
|
||||
{
|
||||
var line = (RapidMove)code;
|
||||
var pt = Mode == Mode.Absolute
|
||||
{
|
||||
var line = (LinearMove)code;
|
||||
var pt =
|
||||
Mode == Mode.Absolute
|
||||
? frameOrigin + line.EndPoint
|
||||
: line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.RapidMove:
|
||||
{
|
||||
var line = (RapidMove)code;
|
||||
var pt =
|
||||
Mode == Mode.Absolute
|
||||
? frameOrigin + line.EndPoint
|
||||
: line.EndPoint + pos;
|
||||
|
||||
if (pt.X > maxX)
|
||||
maxX = pt.X;
|
||||
else if (pt.X < minX)
|
||||
minX = pt.X;
|
||||
|
||||
if (pt.Y > maxY)
|
||||
maxY = pt.Y;
|
||||
else if (pt.Y < minY)
|
||||
minY = pt.Y;
|
||||
|
||||
pos = pt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.ArcMove:
|
||||
{
|
||||
var arc = (ArcMove)code;
|
||||
var radius = arc.CenterPoint.DistanceTo(arc.EndPoint);
|
||||
|
||||
Vector endpt;
|
||||
Vector centerpt;
|
||||
|
||||
if (Mode == Mode.Incremental)
|
||||
{
|
||||
var arc = (ArcMove)code;
|
||||
var radius = arc.CenterPoint.DistanceTo(arc.EndPoint);
|
||||
|
||||
Vector endpt;
|
||||
Vector centerpt;
|
||||
|
||||
if (Mode == Mode.Incremental)
|
||||
{
|
||||
endpt = arc.EndPoint + pos;
|
||||
centerpt = arc.CenterPoint + pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
endpt = frameOrigin + arc.EndPoint;
|
||||
centerpt = frameOrigin + arc.CenterPoint;
|
||||
}
|
||||
|
||||
double minX1;
|
||||
double minY1;
|
||||
double maxX1;
|
||||
double maxY1;
|
||||
|
||||
if (pos.X < endpt.X)
|
||||
{
|
||||
minX1 = pos.X;
|
||||
maxX1 = endpt.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
minX1 = endpt.X;
|
||||
maxX1 = pos.X;
|
||||
}
|
||||
|
||||
if (pos.Y < endpt.Y)
|
||||
{
|
||||
minY1 = pos.Y;
|
||||
maxY1 = endpt.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
minY1 = endpt.Y;
|
||||
maxY1 = pos.Y;
|
||||
}
|
||||
|
||||
var startAngle = pos.AngleFrom(centerpt);
|
||||
var endAngle = endpt.AngleFrom(centerpt);
|
||||
|
||||
// switch the angle to counter clockwise.
|
||||
if (arc.Rotation == RotationType.CW)
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
|
||||
startAngle = Angle.NormalizeRad(startAngle);
|
||||
endAngle = Angle.NormalizeRad(endAngle);
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, startAngle, endAngle))
|
||||
maxY1 = centerpt.Y + radius;
|
||||
|
||||
if (Angle.IsBetweenRad(System.Math.PI, startAngle, endAngle))
|
||||
minX1 = centerpt.X - radius;
|
||||
|
||||
const double oneHalfPI = System.Math.PI * 1.5;
|
||||
|
||||
if (Angle.IsBetweenRad(oneHalfPI, startAngle, endAngle))
|
||||
minY1 = centerpt.Y - radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, startAngle, endAngle))
|
||||
maxX1 = centerpt.X + radius;
|
||||
|
||||
if (maxX1 > maxX)
|
||||
maxX = maxX1;
|
||||
|
||||
if (minX1 < minX)
|
||||
minX = minX1;
|
||||
|
||||
if (maxY1 > maxY)
|
||||
maxY = maxY1;
|
||||
|
||||
if (minY1 < minY)
|
||||
minY = minY1;
|
||||
|
||||
pos = endpt;
|
||||
|
||||
break;
|
||||
endpt = arc.EndPoint + pos;
|
||||
centerpt = arc.CenterPoint + pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
endpt = frameOrigin + arc.EndPoint;
|
||||
centerpt = frameOrigin + arc.CenterPoint;
|
||||
}
|
||||
|
||||
double minX1;
|
||||
double minY1;
|
||||
double maxX1;
|
||||
double maxY1;
|
||||
|
||||
if (pos.X < endpt.X)
|
||||
{
|
||||
minX1 = pos.X;
|
||||
maxX1 = endpt.X;
|
||||
}
|
||||
else
|
||||
{
|
||||
minX1 = endpt.X;
|
||||
maxX1 = pos.X;
|
||||
}
|
||||
|
||||
if (pos.Y < endpt.Y)
|
||||
{
|
||||
minY1 = pos.Y;
|
||||
maxY1 = endpt.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
minY1 = endpt.Y;
|
||||
maxY1 = pos.Y;
|
||||
}
|
||||
|
||||
var startAngle = pos.AngleFrom(centerpt);
|
||||
var endAngle = endpt.AngleFrom(centerpt);
|
||||
|
||||
// switch the angle to counter clockwise.
|
||||
if (arc.Rotation == RotationType.CW)
|
||||
Generic.Swap(ref startAngle, ref endAngle);
|
||||
|
||||
startAngle = Angle.NormalizeRad(startAngle);
|
||||
endAngle = Angle.NormalizeRad(endAngle);
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.HalfPI, startAngle, endAngle))
|
||||
maxY1 = centerpt.Y + radius;
|
||||
|
||||
if (Angle.IsBetweenRad(System.Math.PI, startAngle, endAngle))
|
||||
minX1 = centerpt.X - radius;
|
||||
|
||||
const double oneHalfPI = System.Math.PI * 1.5;
|
||||
|
||||
if (Angle.IsBetweenRad(oneHalfPI, startAngle, endAngle))
|
||||
minY1 = centerpt.Y - radius;
|
||||
|
||||
if (Angle.IsBetweenRad(Angle.TwoPI, startAngle, endAngle))
|
||||
maxX1 = centerpt.X + radius;
|
||||
|
||||
if (maxX1 > maxX)
|
||||
maxX = maxX1;
|
||||
|
||||
if (minX1 < minX)
|
||||
minX = minX1;
|
||||
|
||||
if (maxY1 > maxY)
|
||||
maxY = maxY1;
|
||||
|
||||
if (minY1 < minY)
|
||||
minY = minY1;
|
||||
|
||||
pos = endpt;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case CodeType.SubProgramCall:
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program == null)
|
||||
break;
|
||||
|
||||
// Sub-program frame origin in this program's frame
|
||||
// is frameOrigin + Offset, regardless of current pos.
|
||||
pos = frameOrigin + subpgm.Offset;
|
||||
var box = subpgm.Program.BoundingBox(ref pos);
|
||||
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
|
||||
{
|
||||
var subpgm = (SubProgramCall)code;
|
||||
if (subpgm.Program == null)
|
||||
break;
|
||||
}
|
||||
|
||||
// Sub-program frame origin in this program's frame
|
||||
// is frameOrigin + Offset, regardless of current pos.
|
||||
pos = frameOrigin + subpgm.Offset;
|
||||
var box = subpgm.Program.BoundingBox(ref pos);
|
||||
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,11 +494,7 @@ namespace OpenNest.CNC
|
||||
|
||||
public object Clone()
|
||||
{
|
||||
var pgm = new Program()
|
||||
{
|
||||
mode = this.mode,
|
||||
Rotation = this.Rotation
|
||||
};
|
||||
var pgm = new Program() { mode = this.mode, Rotation = this.Rotation };
|
||||
|
||||
var codes = new ICode[Length];
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ namespace OpenNest.CNC
|
||||
|
||||
public List<string> EmitDeclarations()
|
||||
{
|
||||
return _variables.Values
|
||||
.Where(v => v.Expression != null)
|
||||
return _variables
|
||||
.Values.Where(v => v.Expression != null)
|
||||
.OrderBy(v => v.Number)
|
||||
.Select(v => $"{v.Reference}={v.Expression} ({FormatComment(v.Name)})")
|
||||
.ToList();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.CNC
|
||||
{
|
||||
@@ -36,7 +36,13 @@ namespace OpenNest.CNC
|
||||
return basePos;
|
||||
}
|
||||
|
||||
private static void Walk(Program pgm, Vector basePos, ref Vector pos, bool skipFirst, List<Segment> results)
|
||||
private static void Walk(
|
||||
Program pgm,
|
||||
Vector basePos,
|
||||
ref Vector pos,
|
||||
bool skipFirst,
|
||||
List<Segment> results
|
||||
)
|
||||
{
|
||||
var skipped = !skipFirst;
|
||||
|
||||
@@ -60,9 +66,10 @@ namespace OpenNest.CNC
|
||||
}
|
||||
else if (code is Motion motion)
|
||||
{
|
||||
var endpt = pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint + basePos;
|
||||
var endpt =
|
||||
pgm.Mode == Mode.Incremental
|
||||
? motion.EndPoint + pos
|
||||
: motion.EndPoint + basePos;
|
||||
|
||||
if (code.Type == CodeType.RapidMove)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace OpenNest.CNC
|
||||
return new RapidMove(EndPoint)
|
||||
{
|
||||
Suppressed = Suppressed,
|
||||
VariableRefs = VariableRefs != null ? new Dictionary<string, string>(VariableRefs) : null
|
||||
VariableRefs =
|
||||
VariableRefs != null ? new Dictionary<string, string>(VariableRefs) : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -9,9 +9,7 @@ namespace OpenNest.CNC
|
||||
private double rotation;
|
||||
private Program program;
|
||||
|
||||
public SubProgramCall()
|
||||
{
|
||||
}
|
||||
public SubProgramCall() { }
|
||||
|
||||
public SubProgramCall(Program program, double rotation)
|
||||
{
|
||||
|
||||
@@ -8,8 +8,13 @@ namespace OpenNest.CNC
|
||||
public bool Inline { get; }
|
||||
public bool Global { get; }
|
||||
|
||||
public VariableDefinition(string name, string expression, double value,
|
||||
bool inline = false, bool global = false)
|
||||
public VariableDefinition(
|
||||
string name,
|
||||
string expression,
|
||||
double value,
|
||||
bool inline = false,
|
||||
bool global = false
|
||||
)
|
||||
{
|
||||
Name = name;
|
||||
Expression = expression;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -44,7 +44,8 @@ namespace OpenNest
|
||||
if (drawing?.Program == null)
|
||||
return 0.0;
|
||||
|
||||
var entities = ConvertProgram.ToGeometry(drawing.Program)
|
||||
var entities = ConvertProgram
|
||||
.ToGeometry(drawing.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid);
|
||||
|
||||
var shapes = ShapeBuilder.GetShapes(entities);
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
|
||||
namespace OpenNest.Collections
|
||||
{
|
||||
public class DrawingCollection : HashSet<Drawing>
|
||||
{
|
||||
}
|
||||
public class DrawingCollection : HashSet<Drawing> { }
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Converters
|
||||
{
|
||||
@@ -10,7 +10,7 @@ namespace OpenNest.Converters
|
||||
Perimeter,
|
||||
Hole,
|
||||
Etch,
|
||||
Open
|
||||
Open,
|
||||
}
|
||||
|
||||
public sealed class ContourInfo
|
||||
@@ -91,7 +91,8 @@ namespace OpenNest.Converters
|
||||
// Non-perimeter shapes first (matches CNC cut order: holes before perimeter)
|
||||
for (var i = 0; i < shapes.Count; i++)
|
||||
{
|
||||
if (i == perimeterIndex) continue;
|
||||
if (i == perimeterIndex)
|
||||
continue;
|
||||
var shape = shapes[i];
|
||||
var type = ClassifyShape(shape);
|
||||
|
||||
@@ -116,7 +117,13 @@ namespace OpenNest.Converters
|
||||
}
|
||||
|
||||
// Perimeter last
|
||||
result.Add(new ContourInfo(shapes[perimeterIndex], ContourClassification.Perimeter, "Perimeter"));
|
||||
result.Add(
|
||||
new ContourInfo(
|
||||
shapes[perimeterIndex],
|
||||
ContourClassification.Perimeter,
|
||||
"Perimeter"
|
||||
)
|
||||
);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -124,8 +131,12 @@ namespace OpenNest.Converters
|
||||
private static ContourClassification ClassifyShape(Shape shape)
|
||||
{
|
||||
// Check etch layer — all entities must be on ETCH layer
|
||||
if (shape.Entities.Count > 0 &&
|
||||
shape.Entities.All(e => string.Equals(e.Layer?.Name, "ETCH", StringComparison.OrdinalIgnoreCase)))
|
||||
if (
|
||||
shape.Entities.Count > 0
|
||||
&& shape.Entities.All(e =>
|
||||
string.Equals(e.Layer?.Name, "ETCH", StringComparison.OrdinalIgnoreCase)
|
||||
)
|
||||
)
|
||||
return ContourClassification.Etch;
|
||||
|
||||
if (shape.IsClosed())
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.CNC;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Converters
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.CNC;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Converters
|
||||
{
|
||||
@@ -18,7 +18,12 @@ namespace OpenNest.Converters
|
||||
return geometry;
|
||||
}
|
||||
|
||||
private static void AddProgram(Program program, ref Mode mode, ref Vector curpos, ref List<Entity> 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.
|
||||
@@ -49,7 +54,10 @@ namespace OpenNest.Converters
|
||||
|
||||
// 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);
|
||||
curpos = new Vector(
|
||||
frameOrigin.X + subpgm.Offset.X,
|
||||
frameOrigin.Y + subpgm.Offset.Y
|
||||
);
|
||||
|
||||
AddProgram(subpgm.Program, ref mode, ref curpos, ref geometry);
|
||||
mode = savedMode;
|
||||
@@ -58,7 +66,12 @@ namespace OpenNest.Converters
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddLinearMove(LinearMove linearMove, ref Mode mode, ref Vector curpos, ref List<Entity> geometry)
|
||||
private static void AddLinearMove(
|
||||
LinearMove linearMove,
|
||||
ref Mode mode,
|
||||
ref Vector curpos,
|
||||
ref List<Entity> geometry
|
||||
)
|
||||
{
|
||||
var pt = linearMove.EndPoint;
|
||||
|
||||
@@ -66,16 +79,17 @@ namespace OpenNest.Converters
|
||||
pt += curpos;
|
||||
|
||||
var layer = ConvertLayer(linearMove.Layer);
|
||||
var line = new Line(curpos, pt)
|
||||
{
|
||||
Layer = layer,
|
||||
Color = layer.Color
|
||||
};
|
||||
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)
|
||||
private static void AddRapidMove(
|
||||
RapidMove rapidMove,
|
||||
ref Mode mode,
|
||||
ref Vector curpos,
|
||||
ref List<Entity> geometry
|
||||
)
|
||||
{
|
||||
var pt = rapidMove.EndPoint;
|
||||
|
||||
@@ -85,13 +99,18 @@ namespace OpenNest.Converters
|
||||
var line = new Line(curpos, pt)
|
||||
{
|
||||
Layer = SpecialLayers.Rapid,
|
||||
Color = SpecialLayers.Rapid.Color
|
||||
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)
|
||||
private static void AddArcMove(
|
||||
ArcMove arcMove,
|
||||
ref Mode mode,
|
||||
ref Vector curpos,
|
||||
ref List<Entity> geometry
|
||||
)
|
||||
{
|
||||
var center = arcMove.CenterPoint;
|
||||
var endpt = arcMove.EndPoint;
|
||||
@@ -112,9 +131,28 @@ namespace OpenNest.Converters
|
||||
var layer = ConvertLayer(arcMove.Layer);
|
||||
|
||||
if (startAngle.IsEqualTo(endAngle))
|
||||
geometry.Add(new Circle(center, radius) { Layer = layer, Color = layer.Color, Rotation = arcMove.Rotation });
|
||||
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 });
|
||||
geometry.Add(
|
||||
new Arc(
|
||||
center,
|
||||
radius,
|
||||
startAngle,
|
||||
endAngle,
|
||||
arcMove.Rotation == RotationType.CW
|
||||
)
|
||||
{
|
||||
Layer = layer,
|
||||
Color = layer.Color,
|
||||
}
|
||||
);
|
||||
|
||||
curpos = endpt;
|
||||
}
|
||||
|
||||
+53
-14
@@ -1,14 +1,14 @@
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum CutOffAxis
|
||||
{
|
||||
Horizontal,
|
||||
Vertical
|
||||
Vertical,
|
||||
}
|
||||
|
||||
public class CutOff
|
||||
@@ -26,7 +26,11 @@ namespace OpenNest
|
||||
Drawing = new Drawing(GetName()) { IsCutOff = true };
|
||||
}
|
||||
|
||||
public void Regenerate(Plate plate, CutOffSettings settings, Dictionary<Part, Entity> cache = null)
|
||||
public void Regenerate(
|
||||
Plate plate,
|
||||
CutOffSettings settings,
|
||||
Dictionary<Part, Entity> cache = null
|
||||
)
|
||||
{
|
||||
var segments = ComputeSegments(plate, settings, cache);
|
||||
var program = BuildProgram(segments, settings);
|
||||
@@ -40,11 +44,17 @@ namespace OpenNest
|
||||
return $"CutOff-{axisChar}-{coord:F2}";
|
||||
}
|
||||
|
||||
private List<(double Start, double End)> ComputeSegments(Plate plate, CutOffSettings settings, Dictionary<Part, Entity> cache)
|
||||
private List<(double Start, double End)> ComputeSegments(
|
||||
Plate plate,
|
||||
CutOffSettings settings,
|
||||
Dictionary<Part, Entity> cache
|
||||
)
|
||||
{
|
||||
var bounds = plate.BoundingBox(includeParts: false);
|
||||
|
||||
double lineStart, lineEnd, cutPosition;
|
||||
double lineStart,
|
||||
lineEnd,
|
||||
cutPosition;
|
||||
|
||||
if (Axis == CutOffAxis.Vertical)
|
||||
{
|
||||
@@ -68,7 +78,14 @@ namespace OpenNest
|
||||
|
||||
Entity perimeter = null;
|
||||
cache?.TryGetValue(part, out perimeter);
|
||||
var partExclusions = GetPartExclusions(part, perimeter, cutPosition, lineStart, lineEnd, settings.PartClearance);
|
||||
var partExclusions = GetPartExclusions(
|
||||
part,
|
||||
perimeter,
|
||||
cutPosition,
|
||||
lineStart,
|
||||
lineEnd,
|
||||
settings.PartClearance
|
||||
);
|
||||
exclusions.AddRange(partExclusions);
|
||||
}
|
||||
|
||||
@@ -107,7 +124,13 @@ namespace OpenNest
|
||||
private static readonly List<(double Start, double End)> EmptyExclusions = new();
|
||||
|
||||
private List<(double Start, double End)> GetPartExclusions(
|
||||
Part part, Entity perimeter, double cutPosition, double lineStart, double lineEnd, double clearance)
|
||||
Part part,
|
||||
Entity perimeter,
|
||||
double cutPosition,
|
||||
double lineStart,
|
||||
double lineEnd,
|
||||
double clearance
|
||||
)
|
||||
{
|
||||
var bb = part.BoundingBox;
|
||||
var (partMin, partMax) = AxisBounds(bb, clearance);
|
||||
@@ -118,7 +141,13 @@ namespace OpenNest
|
||||
|
||||
if (perimeter != null)
|
||||
{
|
||||
var perimeterExclusions = IntersectPerimeter(perimeter, cutPosition, lineStart, lineEnd, clearance);
|
||||
var perimeterExclusions = IntersectPerimeter(
|
||||
perimeter,
|
||||
cutPosition,
|
||||
lineStart,
|
||||
lineEnd,
|
||||
clearance
|
||||
);
|
||||
if (perimeterExclusions != null)
|
||||
return perimeterExclusions;
|
||||
}
|
||||
@@ -127,17 +156,24 @@ namespace OpenNest
|
||||
}
|
||||
|
||||
private List<(double Start, double End)> IntersectPerimeter(
|
||||
Entity perimeter, double cutPosition, double lineStart, double lineEnd, double clearance)
|
||||
Entity perimeter,
|
||||
double cutPosition,
|
||||
double lineStart,
|
||||
double lineEnd,
|
||||
double clearance
|
||||
)
|
||||
{
|
||||
var target = OffsetOutward(perimeter, clearance) ?? perimeter;
|
||||
var usedOffset = target != perimeter;
|
||||
var cutLine = new Line(MakePoint(cutPosition, lineStart), MakePoint(cutPosition, lineEnd));
|
||||
var cutLine = new Line(
|
||||
MakePoint(cutPosition, lineStart),
|
||||
MakePoint(cutPosition, lineEnd)
|
||||
);
|
||||
|
||||
if (!target.Intersects(cutLine, out var pts) || pts.Count < 2)
|
||||
return null;
|
||||
|
||||
var coords = pts
|
||||
.Select(pt => Axis == CutOffAxis.Vertical ? pt.Y : pt.X)
|
||||
var coords = pts.Select(pt => Axis == CutOffAxis.Vertical ? pt.Y : pt.X)
|
||||
.OrderBy(c => c)
|
||||
.ToList();
|
||||
|
||||
@@ -184,7 +220,10 @@ namespace OpenNest
|
||||
? (bb.Y - clearance, bb.Y + bb.Width + clearance)
|
||||
: (bb.X - clearance, bb.X + bb.Length + clearance);
|
||||
|
||||
private Program BuildProgram(List<(double Start, double End)> segments, CutOffSettings settings)
|
||||
private Program BuildProgram(
|
||||
List<(double Start, double End)> segments,
|
||||
CutOffSettings settings
|
||||
)
|
||||
{
|
||||
var program = new Program();
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace OpenNest
|
||||
public enum CutDirection
|
||||
{
|
||||
TowardOrigin,
|
||||
AwayFromOrigin
|
||||
AwayFromOrigin,
|
||||
}
|
||||
|
||||
public class CutOffSettings
|
||||
|
||||
@@ -11,11 +11,12 @@ public class CutParameters
|
||||
public string PostProcessor { get; set; }
|
||||
public Units Units { get; set; }
|
||||
|
||||
public static CutParameters Default => new()
|
||||
{
|
||||
Feedrate = 100,
|
||||
RapidTravelRate = 300,
|
||||
PierceTime = TimeSpan.FromSeconds(0.5),
|
||||
Units = OpenNest.Units.Inches
|
||||
};
|
||||
public static CutParameters Default =>
|
||||
new()
|
||||
{
|
||||
Feedrate = 100,
|
||||
RapidTravelRate = 300,
|
||||
PierceTime = TimeSpan.FromSeconds(0.5),
|
||||
Units = OpenNest.Units.Inches,
|
||||
};
|
||||
}
|
||||
|
||||
+22
-24
@@ -1,12 +1,12 @@
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using OpenNest.Bending;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -18,18 +18,18 @@ namespace OpenNest
|
||||
|
||||
public static Color[] PartColors = new Color[]
|
||||
{
|
||||
Color.FromArgb(205, 92, 92), // Indian Red
|
||||
Color.FromArgb(148, 103, 189), // Medium Purple
|
||||
Color.FromArgb(75, 180, 175), // Teal
|
||||
Color.FromArgb(210, 190, 75), // Goldenrod
|
||||
Color.FromArgb(190, 85, 175), // Orchid
|
||||
Color.FromArgb(185, 115, 85), // Sienna
|
||||
Color.FromArgb(120, 100, 190), // Slate Blue
|
||||
Color.FromArgb(200, 100, 140), // Rose
|
||||
Color.FromArgb(80, 175, 155), // Sea Green
|
||||
Color.FromArgb(195, 160, 85), // Dark Khaki
|
||||
Color.FromArgb(175, 95, 160), // Plum
|
||||
Color.FromArgb(215, 130, 130), // Light Coral
|
||||
Color.FromArgb(205, 92, 92), // Indian Red
|
||||
Color.FromArgb(148, 103, 189), // Medium Purple
|
||||
Color.FromArgb(75, 180, 175), // Teal
|
||||
Color.FromArgb(210, 190, 75), // Goldenrod
|
||||
Color.FromArgb(190, 85, 175), // Orchid
|
||||
Color.FromArgb(185, 115, 85), // Sienna
|
||||
Color.FromArgb(120, 100, 190), // Slate Blue
|
||||
Color.FromArgb(200, 100, 140), // Rose
|
||||
Color.FromArgb(80, 175, 155), // Sea Green
|
||||
Color.FromArgb(195, 160, 85), // Dark Khaki
|
||||
Color.FromArgb(175, 95, 160), // Plum
|
||||
Color.FromArgb(215, 130, 130), // Light Coral
|
||||
};
|
||||
|
||||
public static Color GetNextColor()
|
||||
@@ -40,14 +40,10 @@ namespace OpenNest
|
||||
}
|
||||
|
||||
public Drawing()
|
||||
: this(string.Empty, new Program())
|
||||
{
|
||||
}
|
||||
: this(string.Empty, new Program()) { }
|
||||
|
||||
public Drawing(string name)
|
||||
: this(name, new Program())
|
||||
{
|
||||
}
|
||||
: this(name, new Program()) { }
|
||||
|
||||
public Drawing(string name, Program pgm)
|
||||
{
|
||||
@@ -127,7 +123,9 @@ namespace OpenNest
|
||||
|
||||
public void UpdateArea()
|
||||
{
|
||||
var geometry = ConvertProgram.ToGeometry(Program).Where(entity => entity.Layer != SpecialLayers.Rapid);
|
||||
var geometry = ConvertProgram
|
||||
.ToGeometry(Program)
|
||||
.Where(entity => entity.Layer != SpecialLayers.Rapid);
|
||||
var shapes = ShapeBuilder.GetShapes(geometry);
|
||||
|
||||
if (shapes.Count == 0)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -12,16 +12,18 @@ namespace OpenNest.Geometry
|
||||
private Vector center;
|
||||
private bool reversed;
|
||||
|
||||
public Arc()
|
||||
{
|
||||
}
|
||||
public Arc() { }
|
||||
|
||||
public Arc(double x, double y, double r, double a1, double a2, bool reversed = false)
|
||||
: this(new Vector(x, y), r, a1, a2, reversed)
|
||||
{
|
||||
}
|
||||
: this(new Vector(x, y), r, a1, a2, reversed) { }
|
||||
|
||||
public Arc(Vector center, double radius, double startAngle, double endAngle, bool reversed = false)
|
||||
public Arc(
|
||||
Vector center,
|
||||
double radius,
|
||||
double startAngle,
|
||||
double endAngle,
|
||||
bool reversed = false
|
||||
)
|
||||
{
|
||||
this.center = center;
|
||||
this.radius = radius;
|
||||
@@ -93,8 +95,7 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsFullCircle() =>
|
||||
SweepAngle() >= Angle.TwoPI - Tolerance.Epsilon;
|
||||
public bool IsFullCircle() => SweepAngle() >= Angle.TwoPI - Tolerance.Epsilon;
|
||||
|
||||
/// <summary>
|
||||
/// Angle in radians between start and end angles.
|
||||
@@ -130,10 +131,7 @@ namespace OpenNest.Geometry
|
||||
public RotationType Rotation
|
||||
{
|
||||
get { return IsReversed ? RotationType.CW : RotationType.CCW; }
|
||||
set
|
||||
{
|
||||
IsReversed = (value == RotationType.CW);
|
||||
}
|
||||
set { IsReversed = (value == RotationType.CW); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -144,7 +142,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * System.Math.Cos(StartAngle),
|
||||
Center.Y + Radius * System.Math.Sin(StartAngle));
|
||||
Center.Y + Radius * System.Math.Sin(StartAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,7 +154,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
return new Vector(
|
||||
Center.X + Radius * System.Math.Cos(EndAngle),
|
||||
Center.Y + Radius * System.Math.Sin(EndAngle));
|
||||
Center.Y + Radius * System.Math.Sin(EndAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -166,7 +166,8 @@ namespace OpenNest.Geometry
|
||||
var midAngle = StartAngle + (IsReversed ? -SweepAngle() / 2 : SweepAngle() / 2);
|
||||
return new Vector(
|
||||
Center.X + Radius * System.Math.Cos(midAngle),
|
||||
Center.Y + Radius * System.Math.Sin(midAngle));
|
||||
Center.Y + Radius * System.Math.Sin(midAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -231,7 +232,10 @@ namespace OpenNest.Geometry
|
||||
return 1;
|
||||
|
||||
var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius);
|
||||
return System.Math.Max(1, (int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle));
|
||||
return System.Math.Max(
|
||||
1,
|
||||
(int)System.Math.Ceiling(System.Math.Abs(SweepAngle()) / maxAngle)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -242,21 +246,23 @@ namespace OpenNest.Geometry
|
||||
public List<Vector> ToPoints(int segments = 1000, bool circumscribe = false)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = reversed
|
||||
? -SweepAngle() / segments
|
||||
: SweepAngle() / segments;
|
||||
var stepAngle = reversed ? -SweepAngle() / segments : SweepAngle() / segments;
|
||||
|
||||
var r = circumscribe && segments > 0
|
||||
? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0)
|
||||
: Radius;
|
||||
var r =
|
||||
circumscribe && segments > 0
|
||||
? Radius / System.Math.Cos(System.Math.Abs(stepAngle) / 2.0)
|
||||
: Radius;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i + StartAngle;
|
||||
|
||||
points.Add(new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y));
|
||||
points.Add(
|
||||
new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -470,7 +476,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
return new Vector(
|
||||
System.Math.Cos(angle) * Radius + Center.X,
|
||||
System.Math.Sin(angle) * Radius + Center.Y);
|
||||
System.Math.Sin(angle) * Radius + Center.Y
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -500,7 +507,8 @@ namespace OpenNest.Geometry
|
||||
/// <returns></returns>
|
||||
public override bool Intersects(Arc arc, out List<Vector> pts)
|
||||
{
|
||||
return Intersect.Intersects(this, arc, out pts); ;
|
||||
return Intersect.Intersects(this, arc, out pts);
|
||||
;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -14,7 +14,9 @@ namespace OpenNest.Geometry
|
||||
/// the arc passes through both endpoints and departs P1 in the given direction.
|
||||
/// </summary>
|
||||
internal static (Vector center, double radius, double deviation) FitWithStartTangent(
|
||||
List<Vector> points, Vector tangent)
|
||||
List<Vector> points,
|
||||
Vector tangent
|
||||
)
|
||||
{
|
||||
if (points.Count < 3)
|
||||
return (Vector.Invalid, 0, double.MaxValue);
|
||||
@@ -64,7 +66,10 @@ namespace OpenNest.Geometry
|
||||
/// deviation includes any endpoint gap at Pn.
|
||||
/// </summary>
|
||||
internal static (Vector center, double radius, double deviation) FitWithDualTangent(
|
||||
List<Vector> points, Vector startTangent, Vector endTangent)
|
||||
List<Vector> points,
|
||||
Vector startTangent,
|
||||
Vector endTangent
|
||||
)
|
||||
{
|
||||
if (points.Count < 3)
|
||||
return (Vector.Invalid, 0, double.MaxValue);
|
||||
@@ -72,7 +77,9 @@ namespace OpenNest.Geometry
|
||||
var p1 = points[0];
|
||||
var pn = points[^1];
|
||||
|
||||
var stLen = System.Math.Sqrt(startTangent.X * startTangent.X + startTangent.Y * startTangent.Y);
|
||||
var stLen = System.Math.Sqrt(
|
||||
startTangent.X * startTangent.X + startTangent.Y * startTangent.Y
|
||||
);
|
||||
var etLen = System.Math.Sqrt(endTangent.X * endTangent.X + endTangent.Y * endTangent.Y);
|
||||
if (stLen < 1e-10 || etLen < 1e-10)
|
||||
return (Vector.Invalid, 0, double.MaxValue);
|
||||
@@ -113,7 +120,12 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Computes the maximum radial deviation of interior points from a circle.
|
||||
/// </summary>
|
||||
internal static double MaxRadialDeviation(List<Vector> points, double cx, double cy, double radius)
|
||||
internal static double MaxRadialDeviation(
|
||||
List<Vector> points,
|
||||
double cx,
|
||||
double cy,
|
||||
double radius
|
||||
)
|
||||
{
|
||||
var maxDev = 0.0;
|
||||
for (var i = 1; i < points.Count - 1; i++)
|
||||
@@ -122,7 +134,8 @@ namespace OpenNest.Geometry
|
||||
var py = points[i].Y - cy;
|
||||
var dist = System.Math.Sqrt(px * px + py * py);
|
||||
var dev = System.Math.Abs(dist - radius);
|
||||
if (dev > maxDev) maxDev = dev;
|
||||
if (dev > maxDev)
|
||||
maxDev = dev;
|
||||
}
|
||||
return maxDev;
|
||||
}
|
||||
|
||||
@@ -17,10 +17,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
if (box.Left < minX) minX = box.Left;
|
||||
if (box.Right > maxX) maxX = box.Right;
|
||||
if (box.Bottom < minY) minY = box.Bottom;
|
||||
if (box.Top > maxY) maxY = box.Top;
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
@@ -41,11 +45,15 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var vertex = pts[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
if (vertex.X < minX)
|
||||
minX = vertex.X;
|
||||
else if (vertex.X > maxX)
|
||||
maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
if (vertex.Y < minY)
|
||||
minY = vertex.Y;
|
||||
else if (vertex.Y > maxY)
|
||||
maxY = vertex.Y;
|
||||
}
|
||||
|
||||
return new Box(minX, minY, maxX - minX, maxY - minY);
|
||||
@@ -65,10 +73,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
foreach (var box in items)
|
||||
{
|
||||
if (box.Left < left) left = box.Left;
|
||||
if (box.Right > right) right = box.Right;
|
||||
if (box.Bottom < bottom) bottom = box.Bottom;
|
||||
if (box.Top > top) top = box.Top;
|
||||
if (box.Left < left)
|
||||
left = box.Left;
|
||||
if (box.Right > right)
|
||||
right = box.Right;
|
||||
if (box.Bottom < bottom)
|
||||
bottom = box.Bottom;
|
||||
if (box.Top > top)
|
||||
top = box.Top;
|
||||
}
|
||||
|
||||
return new Box(left, bottom, right - left, top - bottom);
|
||||
|
||||
@@ -8,9 +8,7 @@ namespace OpenNest.Geometry
|
||||
public static readonly Box Empty = new Box();
|
||||
|
||||
public Box()
|
||||
: this(0, 0, 0, 0)
|
||||
{
|
||||
}
|
||||
: this(0, 0, 0, 0) { }
|
||||
|
||||
public Box(double x, double y, double w, double h)
|
||||
{
|
||||
@@ -117,10 +115,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
public bool Intersects(Box box)
|
||||
{
|
||||
if (Left >= box.Right) return false;
|
||||
if (Right <= box.Left) return false;
|
||||
if (Top <= box.Bottom) return false;
|
||||
if (Bottom >= box.Top) return false;
|
||||
if (Left >= box.Right)
|
||||
return false;
|
||||
if (Right <= box.Left)
|
||||
return false;
|
||||
if (Top <= box.Bottom)
|
||||
return false;
|
||||
if (Bottom >= box.Top)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -146,18 +148,24 @@ namespace OpenNest.Geometry
|
||||
|
||||
public bool Contains(Box box)
|
||||
{
|
||||
if (box.Top > Top) return false;
|
||||
if (box.Left < Left) return false;
|
||||
if (box.Right > Right) return false;
|
||||
if (box.Bottom < Bottom) return false;
|
||||
if (box.Top > Top)
|
||||
return false;
|
||||
if (box.Left < Left)
|
||||
return false;
|
||||
if (box.Right > Right)
|
||||
return false;
|
||||
if (box.Bottom < Bottom)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Contains(Vector pt)
|
||||
{
|
||||
return pt.X >= Left - Tolerance.Epsilon && pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon && pt.Y <= Top + Tolerance.Epsilon;
|
||||
return pt.X >= Left - Tolerance.Epsilon
|
||||
&& pt.X <= Right + Tolerance.Epsilon
|
||||
&& pt.Y >= Bottom - Tolerance.Epsilon
|
||||
&& pt.Y <= Top + Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
public bool IsHorizontalTo(Box box)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -8,14 +8,10 @@ namespace OpenNest.Geometry
|
||||
private Vector center;
|
||||
private double radius;
|
||||
|
||||
public Circle()
|
||||
{
|
||||
}
|
||||
public Circle() { }
|
||||
|
||||
public Circle(double x, double y, double radius)
|
||||
: this(new Vector(x, y), radius)
|
||||
{
|
||||
}
|
||||
: this(new Vector(x, y), radius) { }
|
||||
|
||||
public Circle(Vector center, double radius)
|
||||
{
|
||||
@@ -137,21 +133,22 @@ namespace OpenNest.Geometry
|
||||
public List<Vector> ToPoints(int segments = 1000, bool circumscribe = false)
|
||||
{
|
||||
var points = new List<Vector>();
|
||||
var stepAngle = Rotation == RotationType.CW
|
||||
? -Angle.TwoPI / segments
|
||||
: Angle.TwoPI / segments;
|
||||
var stepAngle =
|
||||
Rotation == RotationType.CW ? -Angle.TwoPI / segments : Angle.TwoPI / segments;
|
||||
|
||||
var r = circumscribe && segments > 0
|
||||
? Radius / System.Math.Cos(stepAngle / 2.0)
|
||||
: Radius;
|
||||
var r =
|
||||
circumscribe && segments > 0 ? Radius / System.Math.Cos(stepAngle / 2.0) : Radius;
|
||||
|
||||
for (int i = 0; i <= segments; ++i)
|
||||
{
|
||||
var angle = stepAngle * i;
|
||||
|
||||
points.Add(new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y));
|
||||
points.Add(
|
||||
new Vector(
|
||||
System.Math.Cos(angle) * r + Center.X,
|
||||
System.Math.Sin(angle) * r + Center.Y
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return points;
|
||||
@@ -278,11 +275,9 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
if (side == OffsetSide.Left && Rotation == RotationType.CCW)
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
return Radius <= distance
|
||||
? null
|
||||
: new Circle(center, Radius - distance) { Layer = Layer, Rotation = Rotation };
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -294,11 +289,9 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
if (ContainsPoint(pt))
|
||||
{
|
||||
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
||||
{
|
||||
Layer = Layer,
|
||||
Rotation = Rotation
|
||||
};
|
||||
return Radius <= distance
|
||||
? null
|
||||
: new Circle(center, Radius - distance) { Layer = Layer, Rotation = Rotation };
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -317,7 +310,8 @@ namespace OpenNest.Geometry
|
||||
|
||||
return new Vector(
|
||||
System.Math.Cos(angle) * Radius + Center.X,
|
||||
System.Math.Sin(angle) * Radius + Center.Y);
|
||||
System.Math.Sin(angle) * Radius + Center.Y
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -350,7 +344,9 @@ namespace OpenNest.Geometry
|
||||
public override bool Intersects(Circle circle)
|
||||
{
|
||||
var dist = Center.DistanceTo(circle.Center);
|
||||
return (dist < (Radius + circle.Radius) && dist > System.Math.Abs(Radius - circle.Radius));
|
||||
return (
|
||||
dist < (Radius + circle.Radius) && dist > System.Math.Abs(Radius - circle.Radius)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class Collision
|
||||
{
|
||||
public static CollisionResult Check(Polygon a, Polygon b,
|
||||
List<Polygon> holesA = null, List<Polygon> holesB = null)
|
||||
public static CollisionResult Check(
|
||||
Polygon a,
|
||||
Polygon b,
|
||||
List<Polygon> holesA = null,
|
||||
List<Polygon> holesB = null
|
||||
)
|
||||
{
|
||||
// Step 1: Bounding box pre-filter
|
||||
if (!BoundingBoxesOverlap(a.BoundingBox, b.BoundingBox))
|
||||
@@ -46,8 +50,12 @@ namespace OpenNest.Geometry
|
||||
return new CollisionResult(true, regions, intersectionPoints);
|
||||
}
|
||||
|
||||
public static bool HasOverlap(Polygon a, Polygon b,
|
||||
List<Polygon> holesA = null, List<Polygon> holesB = null)
|
||||
public static bool HasOverlap(
|
||||
Polygon a,
|
||||
Polygon b,
|
||||
List<Polygon> holesA = null,
|
||||
List<Polygon> holesB = null
|
||||
)
|
||||
{
|
||||
if (!BoundingBoxesOverlap(a.BoundingBox, b.BoundingBox))
|
||||
return false;
|
||||
@@ -57,8 +65,10 @@ namespace OpenNest.Geometry
|
||||
return Check(a, b, holesA, holesB).Overlaps;
|
||||
}
|
||||
|
||||
public static List<CollisionResult> CheckAll(List<Polygon> polygons,
|
||||
List<List<Polygon>> holes = null)
|
||||
public static List<CollisionResult> CheckAll(
|
||||
List<Polygon> polygons,
|
||||
List<List<Polygon>> holes = null
|
||||
)
|
||||
{
|
||||
var results = new List<CollisionResult>();
|
||||
|
||||
@@ -78,8 +88,7 @@ namespace OpenNest.Geometry
|
||||
return results;
|
||||
}
|
||||
|
||||
public static bool HasAnyOverlap(List<Polygon> polygons,
|
||||
List<List<Polygon>> holes = null)
|
||||
public static bool HasAnyOverlap(List<Polygon> polygons, List<List<Polygon>> holes = null)
|
||||
{
|
||||
for (var i = 0; i < polygons.Count; i++)
|
||||
{
|
||||
@@ -98,10 +107,8 @@ namespace OpenNest.Geometry
|
||||
|
||||
private static bool BoundingBoxesOverlap(Box a, Box b)
|
||||
{
|
||||
var overlapX = System.Math.Min(a.Right, b.Right)
|
||||
- System.Math.Max(a.Left, b.Left);
|
||||
var overlapY = System.Math.Min(a.Top, b.Top)
|
||||
- System.Math.Max(a.Bottom, b.Bottom);
|
||||
var overlapX = System.Math.Min(a.Right, b.Right) - System.Math.Max(a.Left, b.Left);
|
||||
var overlapY = System.Math.Min(a.Top, b.Top) - System.Math.Max(a.Bottom, b.Bottom);
|
||||
|
||||
return overlapX > Tolerance.Epsilon && overlapY > Tolerance.Epsilon;
|
||||
}
|
||||
@@ -164,13 +171,19 @@ namespace OpenNest.Geometry
|
||||
var output = new List<Vector>(subject.Vertices);
|
||||
|
||||
// Remove closing vertex if present
|
||||
if (output.Count > 1 && output[0].X == output[output.Count - 1].X
|
||||
&& output[0].Y == output[output.Count - 1].Y)
|
||||
if (
|
||||
output.Count > 1
|
||||
&& output[0].X == output[output.Count - 1].X
|
||||
&& output[0].Y == output[output.Count - 1].Y
|
||||
)
|
||||
output.RemoveAt(output.Count - 1);
|
||||
|
||||
var clipVerts = new List<Vector>(clip.Vertices);
|
||||
if (clipVerts.Count > 1 && clipVerts[0].X == clipVerts[clipVerts.Count - 1].X
|
||||
&& clipVerts[0].Y == clipVerts[clipVerts.Count - 1].Y)
|
||||
if (
|
||||
clipVerts.Count > 1
|
||||
&& clipVerts[0].X == clipVerts[clipVerts.Count - 1].X
|
||||
&& clipVerts[0].Y == clipVerts[clipVerts.Count - 1].Y
|
||||
)
|
||||
clipVerts.RemoveAt(clipVerts.Count - 1);
|
||||
|
||||
for (var i = 0; i < clipVerts.Count; i++)
|
||||
@@ -231,7 +244,7 @@ namespace OpenNest.Geometry
|
||||
private static double Cross(Vector edgeStart, Vector edgeEnd, Vector point)
|
||||
{
|
||||
return (edgeEnd.X - edgeStart.X) * (point.Y - edgeStart.Y)
|
||||
- (edgeEnd.Y - edgeStart.Y) * (point.X - edgeStart.X);
|
||||
- (edgeEnd.Y - edgeStart.Y) * (point.X - edgeStart.X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -255,12 +268,17 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Subtracts holes from overlap regions.
|
||||
/// </summary>
|
||||
private static List<Polygon> SubtractHoles(List<Polygon> regions,
|
||||
List<Polygon> holesA, List<Polygon> holesB)
|
||||
private static List<Polygon> SubtractHoles(
|
||||
List<Polygon> regions,
|
||||
List<Polygon> holesA,
|
||||
List<Polygon> holesB
|
||||
)
|
||||
{
|
||||
var allHoles = new List<Polygon>();
|
||||
if (holesA != null) allHoles.AddRange(holesA);
|
||||
if (holesB != null) allHoles.AddRange(holesB);
|
||||
if (holesA != null)
|
||||
allHoles.AddRange(holesA);
|
||||
if (holesB != null)
|
||||
allHoles.AddRange(holesB);
|
||||
|
||||
if (allHoles.Count == 0)
|
||||
return regions;
|
||||
@@ -313,9 +331,16 @@ namespace OpenNest.Geometry
|
||||
var holeCount = holeTri.IsClosed() ? holeVerts.Count - 1 : holeVerts.Count;
|
||||
var survived = false;
|
||||
for (var i = 0; i < holeCount; i++)
|
||||
survived |= AddIfPositiveArea(next,
|
||||
ClipOutsideHalfSpace(pieceTri, holeVerts[i], holeVerts[(i + 1) % holeCount]));
|
||||
if (!survived) continue; // piece lies entirely within the hole
|
||||
survived |= AddIfPositiveArea(
|
||||
next,
|
||||
ClipOutsideHalfSpace(
|
||||
pieceTri,
|
||||
holeVerts[i],
|
||||
holeVerts[(i + 1) % holeCount]
|
||||
)
|
||||
);
|
||||
if (!survived)
|
||||
continue; // piece lies entirely within the hole
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +354,11 @@ namespace OpenNest.Geometry
|
||||
/// Sutherland-Hodgman clip of a convex polygon to the strict outside of the
|
||||
/// infinite line edgeStart->edgeEnd of a CCW hole edge (Cross < -Epsilon).
|
||||
/// </summary>
|
||||
private static List<Vector> ClipOutsideHalfSpace(Polygon piece, Vector edgeStart, Vector edgeEnd)
|
||||
private static List<Vector> ClipOutsideHalfSpace(
|
||||
Polygon piece,
|
||||
Vector edgeStart,
|
||||
Vector edgeEnd
|
||||
)
|
||||
{
|
||||
var verts = piece.Vertices;
|
||||
var count = piece.IsClosed() ? verts.Count - 1 : verts.Count;
|
||||
@@ -340,22 +369,27 @@ namespace OpenNest.Geometry
|
||||
var next = verts[(i + 1) % count];
|
||||
var currentInside = Cross(edgeStart, edgeEnd, current) >= -Tolerance.Epsilon;
|
||||
var nextInside = Cross(edgeStart, edgeEnd, next) >= -Tolerance.Epsilon;
|
||||
if (!currentInside) kept.Add(current);
|
||||
if (currentInside == nextInside) continue;
|
||||
if (!currentInside)
|
||||
kept.Add(current);
|
||||
if (currentInside == nextInside)
|
||||
continue;
|
||||
var intersection = LineIntersection(edgeStart, edgeEnd, current, next);
|
||||
if (intersection.IsValid()) kept.Add(intersection);
|
||||
if (intersection.IsValid())
|
||||
kept.Add(intersection);
|
||||
}
|
||||
return kept;
|
||||
}
|
||||
|
||||
private static bool AddIfPositiveArea(List<Polygon> polygons, List<Vector> vertices)
|
||||
{
|
||||
if (vertices.Count < 3) return false;
|
||||
if (vertices.Count < 3)
|
||||
return false;
|
||||
var polygon = new Polygon();
|
||||
polygon.Vertices.AddRange(vertices);
|
||||
polygon.Close();
|
||||
polygon.UpdateBounds();
|
||||
if (polygon.Area() <= Tolerance.Epsilon) return false;
|
||||
if (polygon.Area() <= Tolerance.Epsilon)
|
||||
return false;
|
||||
polygons.Add(polygon);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,17 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
public class CollisionResult
|
||||
{
|
||||
public static readonly CollisionResult None = new(false, new List<Polygon>(), new List<Vector>());
|
||||
public static readonly CollisionResult None = new(
|
||||
false,
|
||||
new List<Polygon>(),
|
||||
new List<Vector>()
|
||||
);
|
||||
|
||||
public CollisionResult(bool overlaps, List<Polygon> overlapRegions, List<Vector> intersectionPoints)
|
||||
public CollisionResult(
|
||||
bool overlaps,
|
||||
List<Polygon> overlapRegions,
|
||||
List<Vector> intersectionPoints
|
||||
)
|
||||
{
|
||||
Overlaps = overlaps;
|
||||
OverlapRegions = overlapRegions;
|
||||
|
||||
@@ -19,8 +19,11 @@ namespace OpenNest.Geometry
|
||||
var verts = new List<Vector>(polygon.Vertices);
|
||||
|
||||
// Remove closing vertex if polygon is closed.
|
||||
if (verts.Count > 1 && verts[0].X == verts[verts.Count - 1].X
|
||||
&& verts[0].Y == verts[verts.Count - 1].Y)
|
||||
if (
|
||||
verts.Count > 1
|
||||
&& verts[0].X == verts[verts.Count - 1].X
|
||||
&& verts[0].Y == verts[verts.Count - 1].Y
|
||||
)
|
||||
verts.RemoveAt(verts.Count - 1);
|
||||
|
||||
if (verts.Count < 3)
|
||||
@@ -84,8 +87,14 @@ namespace OpenNest.Geometry
|
||||
/// Tests whether the vertex at curr forms an ear (a convex vertex whose
|
||||
/// triangle contains no other polygon vertices).
|
||||
/// </summary>
|
||||
private static bool IsEar(Vector prev, Vector curr, Vector next,
|
||||
List<Vector> verts, List<int> indices, int n)
|
||||
private static bool IsEar(
|
||||
Vector prev,
|
||||
Vector curr,
|
||||
Vector next,
|
||||
List<Vector> verts,
|
||||
List<int> indices,
|
||||
int n
|
||||
)
|
||||
{
|
||||
// Must be convex (CCW turn).
|
||||
if (Cross(prev, curr, next) <= 0)
|
||||
|
||||
@@ -20,7 +20,10 @@ namespace OpenNest.Geometry
|
||||
|
||||
foreach (var p in sorted)
|
||||
{
|
||||
while (lower.Count >= 2 && Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0)
|
||||
while (
|
||||
lower.Count >= 2
|
||||
&& Cross(lower[lower.Count - 2], lower[lower.Count - 1], p) <= 0
|
||||
)
|
||||
lower.RemoveAt(lower.Count - 1);
|
||||
|
||||
lower.Add(p);
|
||||
@@ -32,7 +35,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var p = sorted[i];
|
||||
|
||||
while (upper.Count >= 2 && Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0)
|
||||
while (
|
||||
upper.Count >= 2
|
||||
&& Cross(upper[upper.Count - 2], upper[upper.Count - 1], p) <= 0
|
||||
)
|
||||
upper.RemoveAt(upper.Count - 1);
|
||||
|
||||
upper.Add(p);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -9,7 +9,13 @@ namespace OpenNest.Geometry
|
||||
private const int MaxSubdivisionDepth = 12;
|
||||
private const int DeviationSamples = 20;
|
||||
|
||||
internal static Vector EvaluatePoint(double semiMajor, double semiMinor, double rotation, Vector center, double t)
|
||||
internal static Vector EvaluatePoint(
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
Vector center,
|
||||
double t
|
||||
)
|
||||
{
|
||||
var x = semiMajor * System.Math.Cos(t);
|
||||
var y = semiMinor * System.Math.Sin(t);
|
||||
@@ -17,12 +23,15 @@ namespace OpenNest.Geometry
|
||||
var cos = System.Math.Cos(rotation);
|
||||
var sin = System.Math.Sin(rotation);
|
||||
|
||||
return new Vector(
|
||||
center.X + x * cos - y * sin,
|
||||
center.Y + x * sin + y * cos);
|
||||
return new Vector(center.X + x * cos - y * sin, center.Y + x * sin + y * cos);
|
||||
}
|
||||
|
||||
internal static Vector EvaluateTangent(double semiMajor, double semiMinor, double rotation, double t)
|
||||
internal static Vector EvaluateTangent(
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t
|
||||
)
|
||||
{
|
||||
var tx = -semiMajor * System.Math.Sin(t);
|
||||
var ty = semiMinor * System.Math.Cos(t);
|
||||
@@ -30,12 +39,15 @@ namespace OpenNest.Geometry
|
||||
var cos = System.Math.Cos(rotation);
|
||||
var sin = System.Math.Sin(rotation);
|
||||
|
||||
return new Vector(
|
||||
tx * cos - ty * sin,
|
||||
tx * sin + ty * cos);
|
||||
return new Vector(tx * cos - ty * sin, tx * sin + ty * cos);
|
||||
}
|
||||
|
||||
internal static Vector EvaluateNormal(double semiMajor, double semiMinor, double rotation, double t)
|
||||
internal static Vector EvaluateNormal(
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t
|
||||
)
|
||||
{
|
||||
// Inward normal: perpendicular to tangent, pointing toward center of curvature.
|
||||
// In local coords: N(t) = (-b*cos(t), -a*sin(t))
|
||||
@@ -45,9 +57,7 @@ namespace OpenNest.Geometry
|
||||
var cos = System.Math.Cos(rotation);
|
||||
var sin = System.Math.Sin(rotation);
|
||||
|
||||
return new Vector(
|
||||
nx * cos - ny * sin,
|
||||
nx * sin + ny * cos);
|
||||
return new Vector(nx * cos - ny * sin, nx * sin + ny * cos);
|
||||
}
|
||||
|
||||
internal static Vector IntersectNormals(Vector p1, Vector n1, Vector p2, Vector n2)
|
||||
@@ -83,11 +93,21 @@ namespace OpenNest.Geometry
|
||||
return new Vector(ux + c.X, uy + c.Y);
|
||||
}
|
||||
|
||||
public static List<Entity> Convert(Vector center, double semiMajor, double semiMinor,
|
||||
double rotation, double startParam, double endParam, double tolerance = 0.001)
|
||||
public static List<Entity> Convert(
|
||||
Vector center,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double startParam,
|
||||
double endParam,
|
||||
double tolerance = 0.001
|
||||
)
|
||||
{
|
||||
if (tolerance <= 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(tolerance), "Tolerance must be positive.");
|
||||
throw new ArgumentOutOfRangeException(
|
||||
nameof(tolerance),
|
||||
"Tolerance must be positive."
|
||||
);
|
||||
if (semiMajor <= 0 || semiMinor <= 0)
|
||||
throw new ArgumentOutOfRangeException("Semi-axis lengths must be positive.");
|
||||
|
||||
@@ -102,14 +122,28 @@ namespace OpenNest.Geometry
|
||||
|
||||
var entities = new List<Entity>();
|
||||
for (var i = 0; i < splits.Count - 1; i++)
|
||||
FitSegment(center, semiMajor, semiMinor, rotation,
|
||||
splits[i], splits[i + 1], tolerance, entities, 0);
|
||||
FitSegment(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
splits[i],
|
||||
splits[i + 1],
|
||||
tolerance,
|
||||
entities,
|
||||
0
|
||||
);
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static List<Entity> ConvertCircle(Vector center, double radius,
|
||||
double rotation, double startParam, double endParam)
|
||||
private static List<Entity> ConvertCircle(
|
||||
Vector center,
|
||||
double radius,
|
||||
double rotation,
|
||||
double startParam,
|
||||
double endParam
|
||||
)
|
||||
{
|
||||
var sweep = endParam - startParam;
|
||||
var isFull = System.Math.Abs(sweep - Angle.TwoPI) < 0.01;
|
||||
@@ -123,7 +157,7 @@ namespace OpenNest.Geometry
|
||||
return new List<Entity>
|
||||
{
|
||||
new Arc(center, radius, startAngle1, midAngle, false),
|
||||
new Arc(center, radius, midAngle, endAngle2, false)
|
||||
new Arc(center, radius, midAngle, endAngle2, false),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,7 +170,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var splits = new List<double> { startParam };
|
||||
|
||||
var firstQuadrant = System.Math.Ceiling(startParam / (System.Math.PI / 2)) * (System.Math.PI / 2);
|
||||
var firstQuadrant =
|
||||
System.Math.Ceiling(startParam / (System.Math.PI / 2)) * (System.Math.PI / 2);
|
||||
for (var q = firstQuadrant; q < endParam; q += System.Math.PI / 2)
|
||||
{
|
||||
if (q > startParam + 1e-10 && q < endParam - 1e-10)
|
||||
@@ -147,8 +182,17 @@ namespace OpenNest.Geometry
|
||||
return splits;
|
||||
}
|
||||
|
||||
private static void FitSegment(Vector center, double semiMajor, double semiMinor,
|
||||
double rotation, double t0, double t1, double tolerance, List<Entity> results, int depth)
|
||||
private static void FitSegment(
|
||||
Vector center,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t0,
|
||||
double t1,
|
||||
double tolerance,
|
||||
List<Entity> results,
|
||||
int depth
|
||||
)
|
||||
{
|
||||
var p0 = EvaluatePoint(semiMajor, semiMinor, rotation, center, t0);
|
||||
var p1 = EvaluatePoint(semiMajor, semiMinor, rotation, center, t1);
|
||||
@@ -168,12 +212,29 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
var radius = p0.DistanceTo(arcCenter);
|
||||
var maxDev = MeasureDeviation(center, semiMajor, semiMinor, rotation,
|
||||
t0, t1, arcCenter, radius);
|
||||
var maxDev = MeasureDeviation(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
t0,
|
||||
t1,
|
||||
arcCenter,
|
||||
radius
|
||||
);
|
||||
|
||||
if (maxDev <= tolerance)
|
||||
{
|
||||
var arc = CreateArc(arcCenter, radius, center, semiMajor, semiMinor, rotation, t0, t1);
|
||||
var arc = CreateArc(
|
||||
arcCenter,
|
||||
radius,
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
t0,
|
||||
t1
|
||||
);
|
||||
if (arc.SweepAngle() < Tolerance.Epsilon)
|
||||
results.Add(new Line(p0, p1));
|
||||
else
|
||||
@@ -182,13 +243,41 @@ namespace OpenNest.Geometry
|
||||
else
|
||||
{
|
||||
var tMid = (t0 + t1) / 2.0;
|
||||
FitSegment(center, semiMajor, semiMinor, rotation, t0, tMid, tolerance, results, depth + 1);
|
||||
FitSegment(center, semiMajor, semiMinor, rotation, tMid, t1, tolerance, results, depth + 1);
|
||||
FitSegment(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
t0,
|
||||
tMid,
|
||||
tolerance,
|
||||
results,
|
||||
depth + 1
|
||||
);
|
||||
FitSegment(
|
||||
center,
|
||||
semiMajor,
|
||||
semiMinor,
|
||||
rotation,
|
||||
tMid,
|
||||
t1,
|
||||
tolerance,
|
||||
results,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static double MeasureDeviation(Vector center, double semiMajor, double semiMinor,
|
||||
double rotation, double t0, double t1, Vector arcCenter, double radius)
|
||||
private static double MeasureDeviation(
|
||||
Vector center,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t0,
|
||||
double t1,
|
||||
Vector arcCenter,
|
||||
double radius
|
||||
)
|
||||
{
|
||||
var maxDev = 0.0;
|
||||
for (var i = 1; i <= DeviationSamples; i++)
|
||||
@@ -197,14 +286,22 @@ namespace OpenNest.Geometry
|
||||
var p = EvaluatePoint(semiMajor, semiMinor, rotation, center, t);
|
||||
var dist = p.DistanceTo(arcCenter);
|
||||
var dev = System.Math.Abs(dist - radius);
|
||||
if (dev > maxDev) maxDev = dev;
|
||||
if (dev > maxDev)
|
||||
maxDev = dev;
|
||||
}
|
||||
return maxDev;
|
||||
}
|
||||
|
||||
private static Arc CreateArc(Vector arcCenter, double radius,
|
||||
Vector ellipseCenter, double semiMajor, double semiMinor, double rotation,
|
||||
double t0, double t1)
|
||||
private static Arc CreateArc(
|
||||
Vector arcCenter,
|
||||
double radius,
|
||||
Vector ellipseCenter,
|
||||
double semiMajor,
|
||||
double semiMinor,
|
||||
double rotation,
|
||||
double t0,
|
||||
double t1
|
||||
)
|
||||
{
|
||||
var p0 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t0);
|
||||
var p1 = EvaluatePoint(semiMajor, semiMinor, rotation, ellipseCenter, t1);
|
||||
@@ -225,8 +322,10 @@ namespace OpenNest.Geometry
|
||||
var points = new List<Vector> { p0, pMid, p1 };
|
||||
var isReversed = SumSignedAngles(arcCenter, points) < 0;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
if (startAngle < 0)
|
||||
startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0)
|
||||
endAngle += Angle.TwoPI;
|
||||
|
||||
return new Arc(arcCenter, radius, startAngle, endAngle, isReversed);
|
||||
}
|
||||
@@ -239,8 +338,10 @@ namespace OpenNest.Geometry
|
||||
var a1 = System.Math.Atan2(points[i].Y - center.Y, points[i].X - center.X);
|
||||
var a2 = System.Math.Atan2(points[i + 1].Y - center.Y, points[i + 1].X - center.X);
|
||||
var da = a2 - a1;
|
||||
while (da > System.Math.PI) da -= Angle.TwoPI;
|
||||
while (da < -System.Math.PI) da += Angle.TwoPI;
|
||||
while (da > System.Math.PI)
|
||||
da -= Angle.TwoPI;
|
||||
while (da < -System.Math.PI)
|
||||
da += Angle.TwoPI;
|
||||
total += da;
|
||||
}
|
||||
return total;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -331,7 +331,11 @@ namespace OpenNest.Geometry
|
||||
return points;
|
||||
}
|
||||
|
||||
public static BoundingRectangleResult FindBestRotation(this List<Entity> entities, double startAngle = 0, double endAngle = Angle.TwoPI)
|
||||
public static BoundingRectangleResult FindBestRotation(
|
||||
this List<Entity> entities,
|
||||
double startAngle = 0,
|
||||
double endAngle = Angle.TwoPI
|
||||
)
|
||||
{
|
||||
// Check for Shape entity first (recursive case returns early)
|
||||
foreach (var entity in entities)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public enum EntityType
|
||||
{
|
||||
@@ -7,6 +6,6 @@ namespace OpenNest.Geometry
|
||||
Circle,
|
||||
Line,
|
||||
Shape,
|
||||
Polygon
|
||||
Polygon,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class GeometryOptimizer
|
||||
{
|
||||
public static void Optimize(IList<Arc> arcs) =>
|
||||
MergePass(arcs,
|
||||
MergePass(
|
||||
arcs,
|
||||
(list, item, i) => list.GetCoradialArs(item, i),
|
||||
(Arc a, Arc b, out Arc joined) => TryJoinArcs(a, b, out joined));
|
||||
(Arc a, Arc b, out Arc joined) => TryJoinArcs(a, b, out joined)
|
||||
);
|
||||
|
||||
public static void Optimize(IList<Line> lines) =>
|
||||
MergePass(lines,
|
||||
MergePass(
|
||||
lines,
|
||||
(list, item, i) => list.GetCollinearLines(item, i),
|
||||
(Line a, Line b, out Line joined) => TryJoinLines(a, b, out joined));
|
||||
(Line a, Line b, out Line joined) => TryJoinLines(a, b, out joined)
|
||||
);
|
||||
|
||||
public static void Deduplicate(IList<Circle> circles)
|
||||
{
|
||||
@@ -23,8 +27,10 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
for (var j = i - 1; j >= 0; j--)
|
||||
{
|
||||
if (circles[i].Center.DistanceTo(circles[j].Center) <= Tolerance.Epsilon
|
||||
&& circles[i].Radius.IsEqualTo(circles[j].Radius))
|
||||
if (
|
||||
circles[i].Center.DistanceTo(circles[j].Center) <= Tolerance.Epsilon
|
||||
&& circles[i].Radius.IsEqualTo(circles[j].Radius)
|
||||
)
|
||||
{
|
||||
circles.RemoveAt(i);
|
||||
break;
|
||||
@@ -39,9 +45,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
for (var j = arcs.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (arcs[j].Center.DistanceTo(circles[i].Center) <= Tolerance.Epsilon
|
||||
if (
|
||||
arcs[j].Center.DistanceTo(circles[i].Center) <= Tolerance.Epsilon
|
||||
&& arcs[j].Radius.IsEqualTo(circles[i].Radius)
|
||||
&& arcs[j].IsFullCircle())
|
||||
&& arcs[j].IsFullCircle()
|
||||
)
|
||||
{
|
||||
arcs.RemoveAt(j);
|
||||
}
|
||||
@@ -51,9 +59,12 @@ namespace OpenNest.Geometry
|
||||
|
||||
private delegate bool TryJoin<T>(T a, T b, out T joined);
|
||||
|
||||
private static void MergePass<T>(IList<T> items,
|
||||
private static void MergePass<T>(
|
||||
IList<T> items,
|
||||
Func<IList<T>, T, int, List<T>> findCandidates,
|
||||
TryJoin<T> tryJoin) where T : class
|
||||
TryJoin<T> tryJoin
|
||||
)
|
||||
where T : class
|
||||
{
|
||||
for (var i = 0; i < items.Count; ++i)
|
||||
{
|
||||
@@ -117,10 +128,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (!onPoint)
|
||||
{
|
||||
if (t1 < b2 - Tolerance.Epsilon) return false;
|
||||
if (b1 > t2 + Tolerance.Epsilon) return false;
|
||||
if (l1 > r2 + Tolerance.Epsilon) return false;
|
||||
if (r1 < l2 - Tolerance.Epsilon) return false;
|
||||
if (t1 < b2 - Tolerance.Epsilon)
|
||||
return false;
|
||||
if (b1 > t2 + Tolerance.Epsilon)
|
||||
return false;
|
||||
if (l1 > r2 + Tolerance.Epsilon)
|
||||
return false;
|
||||
if (r1 < l2 - Tolerance.Epsilon)
|
||||
return false;
|
||||
}
|
||||
|
||||
var l = l1 < l2 ? l1 : l2;
|
||||
@@ -129,9 +144,17 @@ namespace OpenNest.Geometry
|
||||
var b = b1 < b2 ? b1 : b2;
|
||||
|
||||
if (!line1.IsVertical() && line1.Slope() < 0)
|
||||
lineOut = new Line(new Vector(l, t), new Vector(r, b)) { Layer = line1.Layer, Color = line1.Color };
|
||||
lineOut = new Line(new Vector(l, t), new Vector(r, b))
|
||||
{
|
||||
Layer = line1.Layer,
|
||||
Color = line1.Color,
|
||||
};
|
||||
else
|
||||
lineOut = new Line(new Vector(l, b), new Vector(r, t)) { Layer = line1.Layer, Color = line1.Color };
|
||||
lineOut = new Line(new Vector(l, b), new Vector(r, t))
|
||||
{
|
||||
Layer = line1.Layer,
|
||||
Color = line1.Color,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -177,33 +200,47 @@ namespace OpenNest.Geometry
|
||||
if (sweep >= Angle.TwoPI - Tolerance.Epsilon)
|
||||
return false;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
if (startAngle < 0)
|
||||
startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0)
|
||||
endAngle += Angle.TwoPI;
|
||||
|
||||
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle) { Layer = arc1.Layer, Color = arc1.Color };
|
||||
arcOut = new Arc(arc1.Center, arc1.Radius, startAngle, endAngle)
|
||||
{
|
||||
Layer = arc1.Layer,
|
||||
Color = arc1.Color,
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static List<Line> GetCollinearLines(this IList<Line> lines, Line line, int startIndex)
|
||||
private static List<Line> GetCollinearLines(
|
||||
this IList<Line> lines,
|
||||
Line line,
|
||||
int startIndex
|
||||
)
|
||||
{
|
||||
var collinearLines = new List<Line>();
|
||||
|
||||
Parallel.For(startIndex, lines.Count, index =>
|
||||
{
|
||||
var compareLine = lines[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
Parallel.For(
|
||||
startIndex,
|
||||
lines.Count,
|
||||
index =>
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
var compareLine = lines[index];
|
||||
|
||||
if (Object.ReferenceEquals(line, compareLine))
|
||||
return;
|
||||
|
||||
if (!line.IsCollinearTo(compareLine))
|
||||
return;
|
||||
|
||||
lock (collinearLines)
|
||||
{
|
||||
collinearLines.Add(compareLine);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return collinearLines;
|
||||
}
|
||||
@@ -212,21 +249,25 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var coradialArcs = new List<Arc>();
|
||||
|
||||
Parallel.For(startIndex, arcs.Count, index =>
|
||||
{
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
Parallel.For(
|
||||
startIndex,
|
||||
arcs.Count,
|
||||
index =>
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
var compareArc = arcs[index];
|
||||
|
||||
if (Object.ReferenceEquals(arc, compareArc))
|
||||
return;
|
||||
|
||||
if (!arc.IsCoradialTo(compareArc))
|
||||
return;
|
||||
|
||||
lock (coradialArcs)
|
||||
{
|
||||
coradialArcs.Add(compareArc);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
return coradialArcs;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,10 @@ public class ArcCandidate
|
||||
public double MaxDeviation { get; set; }
|
||||
public Box BoundingBox { get; set; }
|
||||
public bool IsSelected { get; set; } = true;
|
||||
|
||||
/// <summary>First point of the original line segments this candidate covers.</summary>
|
||||
public Vector FirstPoint { get; set; }
|
||||
|
||||
/// <summary>Last point of the original line segments this candidate covers.</summary>
|
||||
public Vector LastPoint { get; set; }
|
||||
}
|
||||
@@ -46,9 +48,7 @@ public class MirrorAxisResult
|
||||
var dx = p.X - Point.X;
|
||||
var dy = p.Y - Point.Y;
|
||||
var dot = dx * Direction.X + dy * Direction.Y;
|
||||
return new Vector(
|
||||
p.X - 2 * (dx - dot * Direction.X),
|
||||
p.Y - 2 * (dy - dot * Direction.Y));
|
||||
return new Vector(p.X - 2 * (dx - dot * Direction.X), p.Y - 2 * (dy - dot * Direction.Y));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,9 +74,14 @@ public class GeometrySimplifier
|
||||
var runStart = i;
|
||||
var layerName = entities[i].Layer?.Name;
|
||||
var lineCount = 0;
|
||||
while (i < entities.Count && (entities[i] is Line || entities[i] is Arc) && entities[i].Layer?.Name == layerName)
|
||||
while (
|
||||
i < entities.Count
|
||||
&& (entities[i] is Line || entities[i] is Arc)
|
||||
&& entities[i].Layer?.Name == layerName
|
||||
)
|
||||
{
|
||||
if (entities[i] is Line) lineCount++;
|
||||
if (entities[i] is Line)
|
||||
lineCount++;
|
||||
i++;
|
||||
}
|
||||
var runEnd = i - 1;
|
||||
@@ -90,10 +95,7 @@ public class GeometrySimplifier
|
||||
|
||||
public Shape Apply(Shape shape, List<ArcCandidate> candidates)
|
||||
{
|
||||
var selected = candidates
|
||||
.Where(c => c.IsSelected)
|
||||
.OrderBy(c => c.StartIndex)
|
||||
.ToList();
|
||||
var selected = candidates.Where(c => c.IsSelected).OrderBy(c => c.StartIndex).ToList();
|
||||
|
||||
var newEntities = new List<Entity>();
|
||||
var i = 0;
|
||||
@@ -132,11 +134,10 @@ public class GeometrySimplifier
|
||||
foreach (var e in shape.Entities)
|
||||
midpoints.Add(e.BoundingBox.Center);
|
||||
|
||||
if (midpoints.Count < 4) return MirrorAxisResult.None;
|
||||
if (midpoints.Count < 4)
|
||||
return MirrorAxisResult.None;
|
||||
|
||||
var centroid = new Vector(
|
||||
midpoints.Average(p => p.X),
|
||||
midpoints.Average(p => p.Y));
|
||||
var centroid = new Vector(midpoints.Average(p => p.X), midpoints.Average(p => p.Y));
|
||||
var cx = centroid.X;
|
||||
var cy = centroid.Y;
|
||||
|
||||
@@ -190,8 +191,7 @@ public class GeometrySimplifier
|
||||
return bestResult.Score >= 0.8 ? bestResult : MirrorAxisResult.None;
|
||||
}
|
||||
|
||||
private static double NormalizeAngle(double angle) =>
|
||||
angle < 0 ? angle + Angle.TwoPI : angle;
|
||||
private static double NormalizeAngle(double angle) => angle < 0 ? angle + Angle.TwoPI : angle;
|
||||
|
||||
private static Vector Normalize(Vector v)
|
||||
{
|
||||
@@ -231,7 +231,8 @@ public class GeometrySimplifier
|
||||
|
||||
for (var j = 0; j < points.Count; j++)
|
||||
{
|
||||
if (i == j) continue;
|
||||
if (i == j)
|
||||
continue;
|
||||
var d = reflected.DistanceTo(points[j]);
|
||||
if (d < matchTol)
|
||||
{
|
||||
@@ -251,17 +252,20 @@ public class GeometrySimplifier
|
||||
/// </summary>
|
||||
public void Symmetrize(List<ArcCandidate> candidates, MirrorAxisResult axis)
|
||||
{
|
||||
if (!axis.IsValid || candidates.Count < 2) return;
|
||||
if (!axis.IsValid || candidates.Count < 2)
|
||||
return;
|
||||
|
||||
var paired = new HashSet<int>();
|
||||
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
if (paired.Contains(i)) continue;
|
||||
if (paired.Contains(i))
|
||||
continue;
|
||||
|
||||
var ci = candidates[i];
|
||||
var ciCenter = ci.BoundingBox.Center;
|
||||
if (PerpendicularDistance(ciCenter, axis.Point, axis.Direction) < 0.1) continue; // on the axis
|
||||
if (PerpendicularDistance(ciCenter, axis.Point, axis.Direction) < 0.1)
|
||||
continue; // on the axis
|
||||
|
||||
var mirrorCenter = axis.Reflect(ciCenter);
|
||||
|
||||
@@ -269,7 +273,8 @@ public class GeometrySimplifier
|
||||
var bestDist = double.MaxValue;
|
||||
for (var j = i + 1; j < candidates.Count; j++)
|
||||
{
|
||||
if (paired.Contains(j)) continue;
|
||||
if (paired.Contains(j))
|
||||
continue;
|
||||
var d = mirrorCenter.DistanceTo(candidates[j].BoundingBox.Center);
|
||||
if (d < bestDist)
|
||||
{
|
||||
@@ -279,7 +284,8 @@ public class GeometrySimplifier
|
||||
}
|
||||
|
||||
var matchTol = System.Math.Max(ci.BoundingBox.Width, ci.BoundingBox.Length) * 0.5;
|
||||
if (bestJ < 0 || bestDist > matchTol) continue;
|
||||
if (bestJ < 0 || bestDist > matchTol)
|
||||
continue;
|
||||
|
||||
paired.Add(i);
|
||||
paired.Add(bestJ);
|
||||
@@ -287,7 +293,10 @@ public class GeometrySimplifier
|
||||
var cj = candidates[bestJ];
|
||||
var sourceIdx = i;
|
||||
var targetIdx = bestJ;
|
||||
if (cj.LineCount > ci.LineCount || (cj.LineCount == ci.LineCount && cj.MaxDeviation < ci.MaxDeviation))
|
||||
if (
|
||||
cj.LineCount > ci.LineCount
|
||||
|| (cj.LineCount == ci.LineCount && cj.MaxDeviation < ci.MaxDeviation)
|
||||
)
|
||||
{
|
||||
sourceIdx = bestJ;
|
||||
targetIdx = i;
|
||||
@@ -323,8 +332,12 @@ public class GeometrySimplifier
|
||||
var mirrorEp = axis.Reflect(ep);
|
||||
|
||||
// Mirroring reverses winding — swap start/end to preserve arc direction
|
||||
var mirrorStart = NormalizeAngle(System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X));
|
||||
var mirrorEnd = NormalizeAngle(System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X));
|
||||
var mirrorStart = NormalizeAngle(
|
||||
System.Math.Atan2(mirrorEp.Y - mirrorCenter.Y, mirrorEp.X - mirrorCenter.X)
|
||||
);
|
||||
var mirrorEnd = NormalizeAngle(
|
||||
System.Math.Atan2(mirrorSp.Y - mirrorCenter.Y, mirrorSp.X - mirrorCenter.X)
|
||||
);
|
||||
|
||||
var result = new Arc(mirrorCenter, arc.Radius, mirrorStart, mirrorEnd, arc.IsReversed);
|
||||
result.Layer = arc.Layer;
|
||||
@@ -332,7 +345,12 @@ public class GeometrySimplifier
|
||||
return result;
|
||||
}
|
||||
|
||||
private void FindCandidatesInRun(List<Entity> entities, int runStart, int runEnd, List<ArcCandidate> candidates)
|
||||
private void FindCandidatesInRun(
|
||||
List<Entity> entities,
|
||||
int runStart,
|
||||
int runEnd,
|
||||
List<ArcCandidate> candidates
|
||||
)
|
||||
{
|
||||
var j = runStart;
|
||||
var chainedTangent = Vector.Invalid;
|
||||
@@ -349,30 +367,45 @@ public class GeometrySimplifier
|
||||
|
||||
chainedTangent = ComputeEndTangent(result.Center, result.Points);
|
||||
var arc = CreateArc(result.Center, result.Radius, result.Points, entities[j]);
|
||||
candidates.Add(new ArcCandidate
|
||||
{
|
||||
StartIndex = j,
|
||||
EndIndex = result.EndIndex,
|
||||
FittedArc = arc,
|
||||
MaxDeviation = result.Deviation,
|
||||
BoundingBox = result.Points.GetBoundingBox(),
|
||||
FirstPoint = arc.StartPoint(),
|
||||
LastPoint = arc.EndPoint(),
|
||||
});
|
||||
candidates.Add(
|
||||
new ArcCandidate
|
||||
{
|
||||
StartIndex = j,
|
||||
EndIndex = result.EndIndex,
|
||||
FittedArc = arc,
|
||||
MaxDeviation = result.Deviation,
|
||||
BoundingBox = result.Points.GetBoundingBox(),
|
||||
FirstPoint = arc.StartPoint(),
|
||||
LastPoint = arc.EndPoint(),
|
||||
}
|
||||
);
|
||||
|
||||
j = result.EndIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
private record ArcFitResult(Vector Center, double Radius, double Deviation, List<Vector> Points, int EndIndex);
|
||||
private record ArcFitResult(
|
||||
Vector Center,
|
||||
double Radius,
|
||||
double Deviation,
|
||||
List<Vector> Points,
|
||||
int EndIndex
|
||||
);
|
||||
|
||||
private ArcFitResult TryFitArcAt(List<Entity> entities, int start, int runEnd, Vector chainedTangent)
|
||||
private ArcFitResult TryFitArcAt(
|
||||
List<Entity> entities,
|
||||
int start,
|
||||
int runEnd,
|
||||
Vector chainedTangent
|
||||
)
|
||||
{
|
||||
var k = start + MinLines - 1;
|
||||
if (k > runEnd) return null;
|
||||
if (k > runEnd)
|
||||
return null;
|
||||
|
||||
var points = CollectPoints(entities, start, k);
|
||||
if (points.Count < 3) return null;
|
||||
if (points.Count < 3)
|
||||
return null;
|
||||
|
||||
var startTangent = chainedTangent.IsValid()
|
||||
? chainedTangent
|
||||
@@ -380,15 +413,20 @@ public class GeometrySimplifier
|
||||
|
||||
var endTangent = GetExitDirection(entities[k]);
|
||||
var (center, radius, dev) = TryFit(points, startTangent, endTangent);
|
||||
if (!center.IsValid()) return null;
|
||||
if (!center.IsValid())
|
||||
return null;
|
||||
|
||||
// Extend the arc as far as possible
|
||||
while (k + 1 <= runEnd)
|
||||
{
|
||||
var extPoints = CollectPoints(entities, start, k + 1);
|
||||
var extEndTangent = GetExitDirection(entities[k + 1]);
|
||||
var (nc, nr, nd) = extPoints.Count >= 3 ? TryFit(extPoints, startTangent, extEndTangent) : (Vector.Invalid, 0, 0d);
|
||||
if (!nc.IsValid()) break;
|
||||
var (nc, nr, nd) =
|
||||
extPoints.Count >= 3
|
||||
? TryFit(extPoints, startTangent, extEndTangent)
|
||||
: (Vector.Invalid, 0, 0d);
|
||||
if (!nc.IsValid())
|
||||
break;
|
||||
|
||||
k++;
|
||||
center = nc;
|
||||
@@ -407,7 +445,11 @@ public class GeometrySimplifier
|
||||
return new ArcFitResult(center, radius, dev, points, k);
|
||||
}
|
||||
|
||||
private (Vector center, double radius, double deviation) TryFit(List<Vector> points, Vector startTangent, Vector endTangent)
|
||||
private (Vector center, double radius, double deviation) TryFit(
|
||||
List<Vector> points,
|
||||
Vector startTangent,
|
||||
Vector endTangent
|
||||
)
|
||||
{
|
||||
// Try dual-tangent fit first (matches direction at both endpoints)
|
||||
if (endTangent.IsValid())
|
||||
@@ -488,9 +530,17 @@ public class GeometrySimplifier
|
||||
var dInit = (maxSagitta * maxSagitta - halfChord * halfChord) / (2 * maxSagitta);
|
||||
var range = System.Math.Max(System.Math.Abs(dInit) * 2, halfChord);
|
||||
|
||||
var dOpt = GoldenSectionMin(dInit - range, dInit + range,
|
||||
d => ArcFit.MaxRadialDeviation(points, mx + d * nx, my + d * ny,
|
||||
System.Math.Sqrt(halfChord * halfChord + d * d)));
|
||||
var dOpt = GoldenSectionMin(
|
||||
dInit - range,
|
||||
dInit + range,
|
||||
d =>
|
||||
ArcFit.MaxRadialDeviation(
|
||||
points,
|
||||
mx + d * nx,
|
||||
my + d * ny,
|
||||
System.Math.Sqrt(halfChord * halfChord + d * d)
|
||||
)
|
||||
);
|
||||
|
||||
var center = new Vector(mx + dOpt * nx, my + dOpt * ny);
|
||||
var radius = System.Math.Sqrt(halfChord * halfChord + dOpt * dOpt);
|
||||
@@ -542,13 +592,22 @@ public class GeometrySimplifier
|
||||
return points;
|
||||
}
|
||||
|
||||
private static Arc CreateArc(Vector center, double radius, List<Vector> points, Entity sourceEntity)
|
||||
private static Arc CreateArc(
|
||||
Vector center,
|
||||
double radius,
|
||||
List<Vector> points,
|
||||
Entity sourceEntity
|
||||
)
|
||||
{
|
||||
var firstPoint = points[0];
|
||||
var lastPoint = points[^1];
|
||||
|
||||
var startAngle = NormalizeAngle(System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X));
|
||||
var endAngle = NormalizeAngle(System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X));
|
||||
var startAngle = NormalizeAngle(
|
||||
System.Math.Atan2(firstPoint.Y - center.Y, firstPoint.X - center.X)
|
||||
);
|
||||
var endAngle = NormalizeAngle(
|
||||
System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X)
|
||||
);
|
||||
var isReversed = SumSignedAngles(center, points) < 0;
|
||||
|
||||
var arc = new Arc(center, radius, startAngle, endAngle, isReversed);
|
||||
@@ -560,14 +619,18 @@ public class GeometrySimplifier
|
||||
/// <summary>
|
||||
/// Returns the exit direction (tangent at endpoint) of an entity.
|
||||
/// </summary>
|
||||
private static Vector GetExitDirection(Entity entity) => entity switch
|
||||
{
|
||||
Line line => new Vector(line.EndPoint.X - line.StartPoint.X, line.EndPoint.Y - line.StartPoint.Y),
|
||||
Arc arc => arc.IsReversed
|
||||
? new Vector(System.Math.Sin(arc.EndAngle), -System.Math.Cos(arc.EndAngle))
|
||||
: new Vector(-System.Math.Sin(arc.EndAngle), System.Math.Cos(arc.EndAngle)),
|
||||
_ => Vector.Invalid,
|
||||
};
|
||||
private static Vector GetExitDirection(Entity entity) =>
|
||||
entity switch
|
||||
{
|
||||
Line line => new Vector(
|
||||
line.EndPoint.X - line.StartPoint.X,
|
||||
line.EndPoint.Y - line.StartPoint.Y
|
||||
),
|
||||
Arc arc => arc.IsReversed
|
||||
? new Vector(System.Math.Sin(arc.EndAngle), -System.Math.Cos(arc.EndAngle))
|
||||
: new Vector(-System.Math.Sin(arc.EndAngle), System.Math.Cos(arc.EndAngle)),
|
||||
_ => Vector.Invalid,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Sums signed angular change traversing consecutive points around a center.
|
||||
@@ -581,8 +644,10 @@ public class GeometrySimplifier
|
||||
var a1 = System.Math.Atan2(points[i].Y - center.Y, points[i].X - center.X);
|
||||
var a2 = System.Math.Atan2(points[i + 1].Y - center.Y, points[i + 1].X - center.X);
|
||||
var da = a2 - a1;
|
||||
while (da > System.Math.PI) da -= Angle.TwoPI;
|
||||
while (da < -System.Math.PI) da += Angle.TwoPI;
|
||||
while (da > System.Math.PI)
|
||||
da -= Angle.TwoPI;
|
||||
while (da < -System.Math.PI)
|
||||
da += Angle.TwoPI;
|
||||
total += da;
|
||||
}
|
||||
return total;
|
||||
@@ -593,7 +658,12 @@ public class GeometrySimplifier
|
||||
/// back to the original line segments. This catches cases where points lie
|
||||
/// on a large circle but the arc bulges far from the original straight geometry.
|
||||
/// </summary>
|
||||
private static double MaxArcToSegmentDeviation(List<Vector> points, Vector center, double radius, bool isReversed)
|
||||
private static double MaxArcToSegmentDeviation(
|
||||
List<Vector> points,
|
||||
Vector center,
|
||||
double radius,
|
||||
bool isReversed
|
||||
)
|
||||
{
|
||||
var startAngle = System.Math.Atan2(points[0].Y - center.Y, points[0].X - center.X);
|
||||
var endAngle = System.Math.Atan2(points[^1].Y - center.Y, points[^1].X - center.X);
|
||||
@@ -601,11 +671,13 @@ public class GeometrySimplifier
|
||||
var sweep = endAngle - startAngle;
|
||||
if (isReversed)
|
||||
{
|
||||
if (sweep > 0) sweep -= Angle.TwoPI;
|
||||
if (sweep > 0)
|
||||
sweep -= Angle.TwoPI;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sweep < 0) sweep += Angle.TwoPI;
|
||||
if (sweep < 0)
|
||||
sweep += Angle.TwoPI;
|
||||
}
|
||||
|
||||
var sampleCount = System.Math.Max(10, (int)(System.Math.Abs(sweep) * radius * 10));
|
||||
@@ -624,9 +696,11 @@ public class GeometrySimplifier
|
||||
for (var j = 0; j < points.Count - 1; j++)
|
||||
{
|
||||
var dist = DistanceToSegment(arcPt, points[j], points[j + 1]);
|
||||
if (dist < minDist) minDist = dist;
|
||||
if (dist < minDist)
|
||||
minDist = dist;
|
||||
}
|
||||
if (minDist > maxDev) maxDev = minDist;
|
||||
if (minDist > maxDev)
|
||||
maxDev = minDist;
|
||||
}
|
||||
return maxDev;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public interface IBoundable
|
||||
{
|
||||
|
||||
@@ -28,10 +28,14 @@ namespace OpenNest.Geometry
|
||||
|
||||
for (var i = 1; i < verts.Count; i++)
|
||||
{
|
||||
if (verts[i].X < minX) minX = verts[i].X;
|
||||
if (verts[i].X > maxX) maxX = verts[i].X;
|
||||
if (verts[i].Y < minY) minY = verts[i].Y;
|
||||
if (verts[i].Y > maxY) maxY = verts[i].Y;
|
||||
if (verts[i].X < minX)
|
||||
minX = verts[i].X;
|
||||
if (verts[i].X > maxX)
|
||||
maxX = verts[i].X;
|
||||
if (verts[i].Y < minY)
|
||||
minY = verts[i].Y;
|
||||
if (verts[i].Y > maxY)
|
||||
maxY = verts[i].Y;
|
||||
}
|
||||
|
||||
// The IFP is the work area shrunk inward by the part's extent in each direction.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -18,8 +18,19 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(arc1.Center.AngleTo(pt), arc1.StartAngle, arc1.EndAngle, arc1.IsReversed) &&
|
||||
Angle.IsBetweenRad(arc2.Center.AngleTo(pt), arc2.StartAngle, arc2.EndAngle, arc2.IsReversed))
|
||||
Angle.IsBetweenRad(
|
||||
arc1.Center.AngleTo(pt),
|
||||
arc1.StartAngle,
|
||||
arc1.EndAngle,
|
||||
arc1.IsReversed
|
||||
)
|
||||
&& Angle.IsBetweenRad(
|
||||
arc2.Center.AngleTo(pt),
|
||||
arc2.StartAngle,
|
||||
arc2.EndAngle,
|
||||
arc2.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
@@ -35,11 +46,15 @@ namespace OpenNest.Geometry
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -54,11 +69,15 @@ namespace OpenNest.Geometry
|
||||
return false;
|
||||
}
|
||||
|
||||
pts = pts.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -74,11 +93,15 @@ namespace OpenNest.Geometry
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts2.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -95,11 +118,15 @@ namespace OpenNest.Geometry
|
||||
pts2.AddRange(pts3);
|
||||
}
|
||||
|
||||
pts = pts2.Where(pt => Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed)).ToList();
|
||||
pts = pts2.Where(pt =>
|
||||
Angle.IsBetweenRad(
|
||||
arc.Center.AngleTo(pt),
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed
|
||||
)
|
||||
)
|
||||
.ToList();
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
@@ -123,20 +150,22 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
var d = circle2.Center - circle1.Center;
|
||||
var a = (circle1.Radius * circle1.Radius - circle2.Radius * circle2.Radius + distance * distance) / (2.0 * distance);
|
||||
var a =
|
||||
(
|
||||
circle1.Radius * circle1.Radius
|
||||
- circle2.Radius * circle2.Radius
|
||||
+ distance * distance
|
||||
) / (2.0 * distance);
|
||||
var h = System.Math.Sqrt(circle1.Radius * circle1.Radius - a * a);
|
||||
|
||||
var pt = new Vector(
|
||||
circle1.Center.X + (a * d.X) / distance,
|
||||
circle1.Center.Y + (a * d.Y) / distance);
|
||||
circle1.Center.Y + (a * d.Y) / distance
|
||||
);
|
||||
|
||||
var i1 = new Vector(
|
||||
pt.X + (h * d.Y) / distance,
|
||||
pt.Y - (h * d.X) / distance);
|
||||
var i1 = new Vector(pt.X + (h * d.Y) / distance, pt.Y - (h * d.X) / distance);
|
||||
|
||||
var i2 = new Vector(
|
||||
pt.X - (h * d.Y) / distance,
|
||||
pt.Y + (h * d.X) / distance);
|
||||
var i2 = new Vector(pt.X - (h * d.Y) / distance, pt.Y + (h * d.X) / distance);
|
||||
|
||||
pts = i1 != i2 ? new List<Vector> { i1, i2 } : new List<Vector> { i1 };
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace OpenNest.Geometry
|
||||
public static readonly Layer Default = new Layer("0")
|
||||
{
|
||||
Color = Color.White,
|
||||
IsVisible = true
|
||||
IsVisible = true,
|
||||
};
|
||||
|
||||
public Layer(string name)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -9,14 +9,10 @@ namespace OpenNest.Geometry
|
||||
internal Vector pt1;
|
||||
internal Vector pt2;
|
||||
|
||||
public Line()
|
||||
{
|
||||
}
|
||||
public Line() { }
|
||||
|
||||
public Line(double x1, double y1, double x2, double y2)
|
||||
: this(new Vector(x1, y1), new Vector(x2, y2))
|
||||
{
|
||||
}
|
||||
: this(new Vector(x1, y1), new Vector(x2, y2)) { }
|
||||
|
||||
public Line(Vector startPoint, Vector endPoint)
|
||||
{
|
||||
@@ -83,9 +79,7 @@ namespace OpenNest.Geometry
|
||||
return EndPoint;
|
||||
else
|
||||
{
|
||||
return new Vector(
|
||||
StartPoint.X + param * diff2.X,
|
||||
StartPoint.Y + param * diff2.Y);
|
||||
return new Vector(StartPoint.X + param * diff2.X, StartPoint.Y + param * diff2.Y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +366,7 @@ namespace OpenNest.Geometry
|
||||
/// <summary>
|
||||
/// Updates the bounding box.
|
||||
/// </summary>
|
||||
public override sealed void UpdateBounds()
|
||||
public sealed override void UpdateBounds()
|
||||
{
|
||||
if (StartPoint.X < EndPoint.X)
|
||||
{
|
||||
@@ -429,13 +423,13 @@ namespace OpenNest.Geometry
|
||||
/// <returns>A tuple of (first, second) sub-lines.</returns>
|
||||
public (Line first, Line second) SplitAt(Vector point)
|
||||
{
|
||||
var first = point.DistanceTo(StartPoint) < Tolerance.Epsilon
|
||||
? null
|
||||
: new Line(StartPoint, point);
|
||||
var first =
|
||||
point.DistanceTo(StartPoint) < Tolerance.Epsilon
|
||||
? null
|
||||
: new Line(StartPoint, point);
|
||||
|
||||
var second = point.DistanceTo(EndPoint) < Tolerance.Epsilon
|
||||
? null
|
||||
: new Line(point, EndPoint);
|
||||
var second =
|
||||
point.DistanceTo(EndPoint) < Tolerance.Epsilon ? null : new Line(point, EndPoint);
|
||||
|
||||
return (first, second);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using Clipper2Lib;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -99,14 +99,15 @@ namespace OpenNest.Geometry
|
||||
var startB = FindBottomLeft(b);
|
||||
|
||||
var result = new Polygon();
|
||||
|
||||
// The starting point of the Minkowski sum A + B is the sum of the
|
||||
// starting points of A and B. For NFP = A + (-B), this is
|
||||
|
||||
// The starting point of the Minkowski sum A + B is the sum of the
|
||||
// starting points of A and B. For NFP = A + (-B), this is
|
||||
// startA + startReflectedB.
|
||||
var current = new Vector(
|
||||
a.Vertices[startA].X + b.Vertices[startB].X,
|
||||
a.Vertices[startA].Y + b.Vertices[startB].Y);
|
||||
|
||||
a.Vertices[startA].Y + b.Vertices[startB].Y
|
||||
);
|
||||
|
||||
result.Vertices.Add(current);
|
||||
|
||||
var ia = 0;
|
||||
@@ -132,10 +133,12 @@ namespace OpenNest.Geometry
|
||||
else
|
||||
{
|
||||
var angleA = System.Math.Atan2(orderedA[ia].Y, orderedA[ia].X);
|
||||
if (angleA < 0) angleA += Angle.TwoPI;
|
||||
if (angleA < 0)
|
||||
angleA += Angle.TwoPI;
|
||||
|
||||
var angleB = System.Math.Atan2(orderedB[ib].Y, orderedB[ib].X);
|
||||
if (angleB < 0) angleB += Angle.TwoPI;
|
||||
if (angleB < 0)
|
||||
angleB += Angle.TwoPI;
|
||||
|
||||
if (angleA < angleB)
|
||||
{
|
||||
@@ -149,7 +152,8 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
edge = new Vector(
|
||||
orderedA[ia].X + orderedB[ib].X,
|
||||
orderedA[ia].Y + orderedB[ib].Y);
|
||||
orderedA[ia].Y + orderedB[ib].Y
|
||||
);
|
||||
ia++;
|
||||
ib++;
|
||||
}
|
||||
@@ -203,8 +207,10 @@ namespace OpenNest.Geometry
|
||||
|
||||
for (var i = 1; i < n; i++)
|
||||
{
|
||||
if (verts[i].Y < verts[best].Y ||
|
||||
(verts[i].Y == verts[best].Y && verts[i].X < verts[best].X))
|
||||
if (
|
||||
verts[i].Y < verts[best].Y
|
||||
|| (verts[i].Y == verts[best].Y && verts[i].X < verts[best].X)
|
||||
)
|
||||
best = i;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,14 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
public static class PolyLabel
|
||||
{
|
||||
public static Vector Find(Polygon outer, IList<Polygon> holes = null, double precision = 0.5)
|
||||
public static Vector Find(
|
||||
Polygon outer,
|
||||
IList<Polygon> holes = null,
|
||||
double precision = 0.5
|
||||
)
|
||||
{
|
||||
if (outer.Vertices.Count < 3)
|
||||
return outer.Vertices.Count > 0
|
||||
? outer.Vertices[0]
|
||||
: new Vector();
|
||||
return outer.Vertices.Count > 0 ? outer.Vertices[0] : new Vector();
|
||||
|
||||
var minX = double.MaxValue;
|
||||
var minY = double.MaxValue;
|
||||
@@ -19,10 +21,14 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < outer.Vertices.Count; i++)
|
||||
{
|
||||
var v = outer.Vertices[i];
|
||||
if (v.X < minX) minX = v.X;
|
||||
if (v.Y < minY) minY = v.Y;
|
||||
if (v.X > maxX) maxX = v.X;
|
||||
if (v.Y > maxY) maxY = v.Y;
|
||||
if (v.X < minX)
|
||||
minX = v.X;
|
||||
if (v.Y < minY)
|
||||
minY = v.Y;
|
||||
if (v.X > maxX)
|
||||
maxX = v.X;
|
||||
if (v.Y > maxY)
|
||||
maxY = v.Y;
|
||||
}
|
||||
|
||||
var width = maxX - minX;
|
||||
@@ -37,8 +43,8 @@ namespace OpenNest.Geometry
|
||||
var queue = new List<Cell>();
|
||||
|
||||
for (var x = minX; x < maxX; x += cellSize)
|
||||
for (var y = minY; y < maxY; y += cellSize)
|
||||
queue.Add(new Cell(x + halfCell, y + halfCell, halfCell, outer, holes));
|
||||
for (var y = minY; y < maxY; y += cellSize)
|
||||
queue.Add(new Cell(x + halfCell, y + halfCell, halfCell, outer, holes));
|
||||
|
||||
queue.Sort((a, b) => b.MaxDist.CompareTo(a.MaxDist));
|
||||
|
||||
@@ -194,7 +200,12 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
}
|
||||
|
||||
private static double PointToAllEdgesDist(double x, double y, Polygon outer, IList<Polygon> holes)
|
||||
private static double PointToAllEdgesDist(
|
||||
double x,
|
||||
double y,
|
||||
Polygon outer,
|
||||
IList<Polygon> holes
|
||||
)
|
||||
{
|
||||
var minDist = PointToPolygonDist(x, y, outer);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -107,7 +107,9 @@ namespace OpenNest.Geometry
|
||||
public RotationType RotationDirection()
|
||||
{
|
||||
if (Vertices.Count < 3)
|
||||
throw new Exception("Not enough points to determine direction. Must have at least 3 points.");
|
||||
throw new Exception(
|
||||
"Not enough points to determine direction. Must have at least 3 points."
|
||||
);
|
||||
|
||||
return CalculateArea() > 0 ? RotationType.CCW : RotationType.CW;
|
||||
}
|
||||
@@ -309,11 +311,15 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var vertex = Vertices[i];
|
||||
|
||||
if (vertex.X < minX) minX = vertex.X;
|
||||
else if (vertex.X > maxX) maxX = vertex.X;
|
||||
if (vertex.X < minX)
|
||||
minX = vertex.X;
|
||||
else if (vertex.X > maxX)
|
||||
maxX = vertex.X;
|
||||
|
||||
if (vertex.Y < minY) minY = vertex.Y;
|
||||
else if (vertex.Y > maxY) maxY = vertex.Y;
|
||||
if (vertex.Y < minY)
|
||||
minY = vertex.Y;
|
||||
else if (vertex.Y > maxY)
|
||||
maxY = vertex.Y;
|
||||
}
|
||||
|
||||
boundingBox.X = minX;
|
||||
@@ -354,10 +360,19 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var prev = (i - 1 + count) % count;
|
||||
|
||||
var a1 = new Vector(Vertices[prev].X + normals[prev].X, Vertices[prev].Y + normals[prev].Y);
|
||||
var a2 = new Vector(Vertices[i].X + normals[prev].X, Vertices[i].Y + normals[prev].Y);
|
||||
var a1 = new Vector(
|
||||
Vertices[prev].X + normals[prev].X,
|
||||
Vertices[prev].Y + normals[prev].Y
|
||||
);
|
||||
var a2 = new Vector(
|
||||
Vertices[i].X + normals[prev].X,
|
||||
Vertices[i].Y + normals[prev].Y
|
||||
);
|
||||
var b1 = new Vector(Vertices[i].X + normals[i].X, Vertices[i].Y + normals[i].Y);
|
||||
var b2 = new Vector(Vertices[(i + 1) % count].X + normals[i].X, Vertices[(i + 1) % count].Y + normals[i].Y);
|
||||
var b2 = new Vector(
|
||||
Vertices[(i + 1) % count].X + normals[i].X,
|
||||
Vertices[(i + 1) % count].Y + normals[i].Y
|
||||
);
|
||||
|
||||
var edgeA = new Line(a1, a2);
|
||||
var edgeB = new Line(b1, b2);
|
||||
@@ -365,7 +380,9 @@ namespace OpenNest.Geometry
|
||||
if (edgeA.Intersects(edgeB, out var pt) && pt.IsValid())
|
||||
result.Vertices.Add(pt);
|
||||
else
|
||||
result.Vertices.Add(new Vector(Vertices[i].X + normals[i].X, Vertices[i].Y + normals[i].Y));
|
||||
result.Vertices.Add(
|
||||
new Vector(Vertices[i].X + normals[i].X, Vertices[i].Y + normals[i].Y)
|
||||
);
|
||||
}
|
||||
|
||||
result.Close();
|
||||
@@ -379,8 +396,10 @@ namespace OpenNest.Geometry
|
||||
var left = OffsetEntity(distance, OffsetSide.Left);
|
||||
var right = OffsetEntity(distance, OffsetSide.Right);
|
||||
|
||||
if (left == null) return right;
|
||||
if (right == null) return left;
|
||||
if (left == null)
|
||||
return right;
|
||||
if (right == null)
|
||||
return left;
|
||||
|
||||
var distLeft = left.ClosestPointTo(pt).DistanceTo(pt);
|
||||
var distRight = right.ClosestPointTo(pt).DistanceTo(pt);
|
||||
@@ -581,13 +600,25 @@ namespace OpenNest.Geometry
|
||||
var bj = edgeBounds[j];
|
||||
|
||||
// Prune with bounding box check.
|
||||
if (bi.maxX < bj.minX || bj.maxX < bi.minX ||
|
||||
bi.maxY < bj.minY || bj.maxY < bi.minY)
|
||||
if (
|
||||
bi.maxX < bj.minX
|
||||
|| bj.maxX < bi.minX
|
||||
|| bi.maxY < bj.minY
|
||||
|| bj.maxY < bi.minY
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SegmentsIntersect(Vertices[i], Vertices[i + 1], Vertices[j], Vertices[j + 1], out pt))
|
||||
if (
|
||||
SegmentsIntersect(
|
||||
Vertices[i],
|
||||
Vertices[i + 1],
|
||||
Vertices[j],
|
||||
Vertices[j + 1],
|
||||
out pt
|
||||
)
|
||||
)
|
||||
{
|
||||
edgeI = i;
|
||||
edgeJ = j;
|
||||
@@ -620,7 +651,13 @@ namespace OpenNest.Geometry
|
||||
return areaA >= areaB ? loopA : loopB;
|
||||
}
|
||||
|
||||
private static bool SegmentsIntersect(Vector a1, Vector a2, Vector b1, Vector b2, out Vector pt)
|
||||
private static bool SegmentsIntersect(
|
||||
Vector a1,
|
||||
Vector a2,
|
||||
Vector b1,
|
||||
Vector b2,
|
||||
out Vector pt
|
||||
)
|
||||
{
|
||||
var da = a2 - a1;
|
||||
var db = b2 - b1;
|
||||
@@ -636,8 +673,12 @@ namespace OpenNest.Geometry
|
||||
var t = (dc.X * db.Y - dc.Y * db.X) / cross;
|
||||
var u = (dc.X * da.Y - dc.Y * da.X) / cross;
|
||||
|
||||
if (t > Tolerance.Epsilon && t < 1.0 - Tolerance.Epsilon &&
|
||||
u > Tolerance.Epsilon && u < 1.0 - Tolerance.Epsilon)
|
||||
if (
|
||||
t > Tolerance.Epsilon
|
||||
&& t < 1.0 - Tolerance.Epsilon
|
||||
&& u > Tolerance.Epsilon
|
||||
&& u < 1.0 - Tolerance.Epsilon
|
||||
)
|
||||
{
|
||||
pt = new Vector(a1.X + t * da.X, a1.Y + t * da.Y);
|
||||
return true;
|
||||
@@ -701,8 +742,10 @@ namespace OpenNest.Geometry
|
||||
var vi = Vertices[i];
|
||||
var vj = Vertices[j];
|
||||
|
||||
if ((vi.Y > pt.Y) != (vj.Y > pt.Y) &&
|
||||
pt.X < (vj.X - vi.X) * (pt.Y - vi.Y) / (vj.Y - vi.Y) + vi.X)
|
||||
if (
|
||||
(vi.Y > pt.Y) != (vj.Y > pt.Y)
|
||||
&& pt.X < (vj.X - vi.X) * (pt.Y - vi.Y) / (vj.Y - vi.Y) + vi.X
|
||||
)
|
||||
{
|
||||
inside = !inside;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -71,18 +71,24 @@ namespace OpenNest.Geometry
|
||||
var vy = ux;
|
||||
|
||||
// Project all hull vertices onto edge direction (u) and perpendicular (v)
|
||||
double minU = double.MaxValue, maxU = double.MinValue;
|
||||
double minV = double.MaxValue, maxV = double.MinValue;
|
||||
double minU = double.MaxValue,
|
||||
maxU = double.MinValue;
|
||||
double minV = double.MaxValue,
|
||||
maxV = double.MinValue;
|
||||
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
var projU = vertices[j].X * ux + vertices[j].Y * uy;
|
||||
var projV = vertices[j].X * vx + vertices[j].Y * vy;
|
||||
|
||||
if (projU < minU) minU = projU;
|
||||
if (projU > maxU) maxU = projU;
|
||||
if (projV < minV) minV = projV;
|
||||
if (projV > maxV) maxV = projV;
|
||||
if (projU < minU)
|
||||
minU = projU;
|
||||
if (projU > maxU)
|
||||
maxU = projU;
|
||||
if (projV < minV)
|
||||
minV = projV;
|
||||
if (projV > maxV)
|
||||
maxV = projV;
|
||||
}
|
||||
|
||||
var width = maxU - minU;
|
||||
@@ -99,7 +105,11 @@ namespace OpenNest.Geometry
|
||||
return best ?? new BoundingRectangleResult(0, 0, 0);
|
||||
}
|
||||
|
||||
public static BoundingRectangleResult MinimumBoundingRectangle(Polygon hull, double startAngle, double endAngle)
|
||||
public static BoundingRectangleResult MinimumBoundingRectangle(
|
||||
Polygon hull,
|
||||
double startAngle,
|
||||
double endAngle
|
||||
)
|
||||
{
|
||||
var vertices = hull.Vertices;
|
||||
int n = hull.IsClosed() ? vertices.Count - 1 : vertices.Count;
|
||||
@@ -153,23 +163,33 @@ namespace OpenNest.Geometry
|
||||
return best ?? new BoundingRectangleResult(startAngle, 0, 0);
|
||||
}
|
||||
|
||||
private static BoundingRectangleResult EvaluateAtAngle(IList<Vector> vertices, int n, double angle)
|
||||
private static BoundingRectangleResult EvaluateAtAngle(
|
||||
IList<Vector> vertices,
|
||||
int n,
|
||||
double angle
|
||||
)
|
||||
{
|
||||
var cos = System.Math.Cos(angle);
|
||||
var sin = System.Math.Sin(angle);
|
||||
|
||||
double minU = double.MaxValue, maxU = double.MinValue;
|
||||
double minV = double.MaxValue, maxV = double.MinValue;
|
||||
double minU = double.MaxValue,
|
||||
maxU = double.MinValue;
|
||||
double minV = double.MaxValue,
|
||||
maxV = double.MinValue;
|
||||
|
||||
for (int j = 0; j < n; j++)
|
||||
{
|
||||
var projU = vertices[j].X * cos + vertices[j].Y * sin;
|
||||
var projV = -vertices[j].X * sin + vertices[j].Y * cos;
|
||||
|
||||
if (projU < minU) minU = projU;
|
||||
if (projU > maxU) maxU = projU;
|
||||
if (projV < minV) minV = projV;
|
||||
if (projV > maxV) maxV = projV;
|
||||
if (projU < minU)
|
||||
minU = projU;
|
||||
if (projU > maxU)
|
||||
maxU = projU;
|
||||
if (projV < minV)
|
||||
minV = projV;
|
||||
if (projV > maxV)
|
||||
maxV = projV;
|
||||
}
|
||||
|
||||
var width = maxU - minU;
|
||||
|
||||
@@ -282,11 +282,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)entity;
|
||||
polygon.Vertices.AddRange(new[]
|
||||
{
|
||||
line.StartPoint,
|
||||
line.EndPoint
|
||||
});
|
||||
polygon.Vertices.AddRange(new[] { line.StartPoint, line.EndPoint });
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
@@ -320,21 +316,21 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
case EntityType.Arc:
|
||||
var arc = (Arc)entity;
|
||||
polygon.Vertices.AddRange(arc.ToPoints(arc.SegmentsForTolerance(tolerance), circumscribe));
|
||||
polygon.Vertices.AddRange(
|
||||
arc.ToPoints(arc.SegmentsForTolerance(tolerance), circumscribe)
|
||||
);
|
||||
break;
|
||||
|
||||
case EntityType.Line:
|
||||
var line = (Line)entity;
|
||||
polygon.Vertices.AddRange(new[]
|
||||
{
|
||||
line.StartPoint,
|
||||
line.EndPoint
|
||||
});
|
||||
polygon.Vertices.AddRange(new[] { line.StartPoint, line.EndPoint });
|
||||
break;
|
||||
|
||||
case EntityType.Circle:
|
||||
var circle = (Circle)entity;
|
||||
polygon.Vertices.AddRange(circle.ToPoints(circle.SegmentsForTolerance(tolerance), circumscribe));
|
||||
polygon.Vertices.AddRange(
|
||||
circle.ToPoints(circle.SegmentsForTolerance(tolerance), circumscribe)
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -462,9 +458,7 @@ namespace OpenNest.Geometry
|
||||
/// </summary>
|
||||
public override void UpdateBounds()
|
||||
{
|
||||
boundingBox = Entities.Select(geo => geo.BoundingBox)
|
||||
.ToList()
|
||||
.GetBoundingBox();
|
||||
boundingBox = Entities.Select(geo => geo.BoundingBox).ToList().GetBoundingBox();
|
||||
}
|
||||
|
||||
public override Entity OffsetEntity(double distance, OffsetSide side)
|
||||
@@ -493,22 +487,27 @@ namespace OpenNest.Geometry
|
||||
switch (entity.Type)
|
||||
{
|
||||
case EntityType.Line:
|
||||
{
|
||||
var line = (Line)entity;
|
||||
var offsetLine = (Line)offsetEntity;
|
||||
|
||||
if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line)
|
||||
{
|
||||
var line = (Line)entity;
|
||||
var offsetLine = (Line)offsetEntity;
|
||||
|
||||
if (lastOffsetEntity != null && lastOffsetEntity.Type == EntityType.Line)
|
||||
{
|
||||
JoinOffsetLines(
|
||||
(Line)lastEntity, (Line)lastOffsetEntity,
|
||||
line, offsetLine,
|
||||
distance, side, offsetShape);
|
||||
}
|
||||
|
||||
offsetShape.Entities.Add(offsetLine);
|
||||
break;
|
||||
JoinOffsetLines(
|
||||
(Line)lastEntity,
|
||||
(Line)lastOffsetEntity,
|
||||
line,
|
||||
offsetLine,
|
||||
distance,
|
||||
side,
|
||||
offsetShape
|
||||
);
|
||||
}
|
||||
|
||||
offsetShape.Entities.Add(offsetLine);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
offsetShape.Entities.Add(offsetEntity);
|
||||
break;
|
||||
@@ -519,27 +518,42 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
// Close the shape: join last offset entity back to first
|
||||
if (lastOffsetEntity != null && firstOffsetEntity != null
|
||||
if (
|
||||
lastOffsetEntity != null
|
||||
&& firstOffsetEntity != null
|
||||
&& lastOffsetEntity != firstOffsetEntity
|
||||
&& lastOffsetEntity.Type == EntityType.Line
|
||||
&& firstOffsetEntity.Type == EntityType.Line)
|
||||
&& firstOffsetEntity.Type == EntityType.Line
|
||||
)
|
||||
{
|
||||
JoinOffsetLines(
|
||||
(Line)lastEntity, (Line)lastOffsetEntity,
|
||||
(Line)firstEntity, (Line)firstOffsetEntity,
|
||||
distance, side, offsetShape);
|
||||
(Line)lastEntity,
|
||||
(Line)lastOffsetEntity,
|
||||
(Line)firstEntity,
|
||||
(Line)firstOffsetEntity,
|
||||
distance,
|
||||
side,
|
||||
offsetShape
|
||||
);
|
||||
}
|
||||
|
||||
foreach (var cutout in definedShape.Cutouts)
|
||||
offsetShape.Entities.AddRange(((Shape)cutout.OffsetEntity(distance, side)).Entities);
|
||||
offsetShape.Entities.AddRange(
|
||||
((Shape)cutout.OffsetEntity(distance, side)).Entities
|
||||
);
|
||||
|
||||
return offsetShape;
|
||||
}
|
||||
|
||||
private static void JoinOffsetLines(
|
||||
Line lastLine, Line lastOffsetLine,
|
||||
Line line, Line offsetLine,
|
||||
double distance, OffsetSide side, Shape offsetShape)
|
||||
Line lastLine,
|
||||
Line lastOffsetLine,
|
||||
Line line,
|
||||
Line offsetLine,
|
||||
double distance,
|
||||
OffsetSide side,
|
||||
Shape offsetShape
|
||||
)
|
||||
{
|
||||
// Determine if this is a convex corner using the cross product of
|
||||
// the original line directions. Convex corners need an arc; concave
|
||||
@@ -548,8 +562,9 @@ namespace OpenNest.Geometry
|
||||
var d2 = line.EndPoint - line.StartPoint;
|
||||
var cross = d1.X * d2.Y - d1.Y * d2.X;
|
||||
|
||||
var isConvex = (side == OffsetSide.Left && cross < -OpenNest.Math.Tolerance.Epsilon) ||
|
||||
(side == OffsetSide.Right && cross > OpenNest.Math.Tolerance.Epsilon);
|
||||
var isConvex =
|
||||
(side == OffsetSide.Left && cross < -OpenNest.Math.Tolerance.Epsilon)
|
||||
|| (side == OffsetSide.Right && cross > OpenNest.Math.Tolerance.Epsilon);
|
||||
|
||||
if (isConvex)
|
||||
{
|
||||
@@ -559,11 +574,13 @@ namespace OpenNest.Geometry
|
||||
line.StartPoint.AngleTo(lastOffsetLine.EndPoint),
|
||||
line.StartPoint.AngleTo(offsetLine.StartPoint),
|
||||
side == OffsetSide.Left
|
||||
);
|
||||
);
|
||||
|
||||
offsetShape.Entities.Add(arc);
|
||||
}
|
||||
else if (Intersect.IntersectsUnbounded(offsetLine, lastOffsetLine, out var intersection))
|
||||
else if (
|
||||
Intersect.IntersectsUnbounded(offsetLine, lastOffsetLine, out var intersection)
|
||||
)
|
||||
{
|
||||
offsetLine.StartPoint = intersection;
|
||||
lastOffsetLine.EndPoint = intersection;
|
||||
@@ -576,7 +593,7 @@ namespace OpenNest.Geometry
|
||||
line.StartPoint.AngleTo(lastOffsetLine.EndPoint),
|
||||
line.StartPoint.AngleTo(offsetLine.StartPoint),
|
||||
side == OffsetSide.Left
|
||||
);
|
||||
);
|
||||
|
||||
offsetShape.Entities.Add(arc);
|
||||
}
|
||||
@@ -596,8 +613,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (poly == null || poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CW)
|
||||
if (
|
||||
poly == null
|
||||
|| poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CW
|
||||
)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Shape is CCW — reverse to CW so Left offset goes outward.
|
||||
@@ -611,10 +631,21 @@ namespace OpenNest.Geometry
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||
copy.Entities.Add(
|
||||
new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed)
|
||||
{
|
||||
Layer = a.Layer,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer, Rotation = RotationType.CW });
|
||||
copy.Entities.Add(
|
||||
new Circle(c.Center, c.Radius)
|
||||
{
|
||||
Layer = c.Layer,
|
||||
Rotation = RotationType.CW,
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -631,8 +662,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var poly = ToPolygon();
|
||||
|
||||
if (poly == null || poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW)
|
||||
if (
|
||||
poly == null
|
||||
|| poly.Vertices.Count < 3
|
||||
|| poly.RotationDirection() == RotationType.CCW
|
||||
)
|
||||
return OffsetEntity(distance, OffsetSide.Left) as Shape;
|
||||
|
||||
// Create a reversed copy to avoid mutating shared entity objects.
|
||||
@@ -646,10 +680,21 @@ namespace OpenNest.Geometry
|
||||
copy.Entities.Add(new Line(l.EndPoint, l.StartPoint) { Layer = l.Layer });
|
||||
break;
|
||||
case Arc a:
|
||||
copy.Entities.Add(new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed) { Layer = a.Layer });
|
||||
copy.Entities.Add(
|
||||
new Arc(a.Center, a.Radius, a.EndAngle, a.StartAngle, !a.IsReversed)
|
||||
{
|
||||
Layer = a.Layer,
|
||||
}
|
||||
);
|
||||
break;
|
||||
case Circle c:
|
||||
copy.Entities.Add(new Circle(c.Center, c.Radius) { Layer = c.Layer, Rotation = RotationType.CCW });
|
||||
copy.Entities.Add(
|
||||
new Circle(c.Center, c.Radius)
|
||||
{
|
||||
Layer = c.Layer,
|
||||
Rotation = RotationType.CCW,
|
||||
}
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
public static class ShapeBuilder
|
||||
{
|
||||
public static List<Shape> GetShapes(IEnumerable<Entity> entities, double? weldTolerance = null)
|
||||
public static List<Shape> GetShapes(
|
||||
IEnumerable<Entity> entities,
|
||||
double? weldTolerance = null
|
||||
)
|
||||
{
|
||||
var lines = new List<Line>();
|
||||
var arcs = new List<Arc>();
|
||||
@@ -141,7 +144,11 @@ namespace OpenNest.Geometry
|
||||
|
||||
private static void AddToGroup(
|
||||
List<List<(Entity entity, bool isStart, Vector point)>> groups,
|
||||
Entity entity, bool isStart, Vector point, double tolerance)
|
||||
Entity entity,
|
||||
bool isStart,
|
||||
Vector point,
|
||||
double tolerance
|
||||
)
|
||||
{
|
||||
foreach (var group in groups)
|
||||
{
|
||||
|
||||
@@ -84,8 +84,7 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var poly = shape.ToPolygon();
|
||||
|
||||
if (poly != null && poly.Vertices.Count >= 3
|
||||
&& poly.RotationDirection() != desired)
|
||||
if (poly != null && poly.Vertices.Count >= 3 && poly.RotationDirection() != desired)
|
||||
{
|
||||
shape.Reverse();
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace OpenNest.Geometry
|
||||
|
||||
public override string ToString() => $"{Width} x {Length}";
|
||||
|
||||
public string ToString(int decimalPlaces) => $"{System.Math.Round(Width, decimalPlaces)} x {System.Math.Round(Length, decimalPlaces)}";
|
||||
public string ToString(int decimalPlaces) =>
|
||||
$"{System.Math.Round(Width, decimalPlaces)} x {System.Math.Round(Length, decimalPlaces)}";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -13,57 +13,72 @@ namespace OpenNest.Geometry
|
||||
private static double RayEdgeDistance(Vector vertex, Line edge, PushDirection direction)
|
||||
{
|
||||
return RayEdgeDistance(
|
||||
vertex.X, vertex.Y,
|
||||
edge.pt1.X, edge.pt1.Y, edge.pt2.X, edge.pt2.Y,
|
||||
direction);
|
||||
vertex.X,
|
||||
vertex.Y,
|
||||
edge.pt1.X,
|
||||
edge.pt1.Y,
|
||||
edge.pt2.X,
|
||||
edge.pt2.Y,
|
||||
direction
|
||||
);
|
||||
}
|
||||
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
private static double RayEdgeDistance(
|
||||
double vx, double vy,
|
||||
double p1x, double p1y, double p2x, double p2y,
|
||||
PushDirection direction)
|
||||
double vx,
|
||||
double vy,
|
||||
double p1x,
|
||||
double p1y,
|
||||
double p2x,
|
||||
double p2y,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left:
|
||||
case PushDirection.Right:
|
||||
{
|
||||
var dy = p2y - p1y;
|
||||
if (System.Math.Abs(dy) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vy - p1y) / dy;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var ix = p1x + t * (p2x - p1x);
|
||||
var dist = direction == PushDirection.Left ? vx - ix : ix - vx;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
{
|
||||
var dy = p2y - p1y;
|
||||
if (System.Math.Abs(dy) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
var t = (vy - p1y) / dy;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var ix = p1x + t * (p2x - p1x);
|
||||
var dist = direction == PushDirection.Left ? vx - ix : ix - vx;
|
||||
|
||||
if (dist > Tolerance.Epsilon)
|
||||
return dist;
|
||||
if (dist >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
case PushDirection.Down:
|
||||
case PushDirection.Up:
|
||||
{
|
||||
var dx = p2x - p1x;
|
||||
if (System.Math.Abs(dx) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var t = (vx - p1x) / dx;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var iy = p1y + t * (p2y - p1y);
|
||||
var dist = direction == PushDirection.Down ? vy - iy : iy - vy;
|
||||
|
||||
if (dist > Tolerance.Epsilon) return dist;
|
||||
if (dist >= -Tolerance.Epsilon) return 0;
|
||||
{
|
||||
var dx = p2x - p1x;
|
||||
if (System.Math.Abs(dx) < Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
var t = (vx - p1x) / dx;
|
||||
if (t < -Tolerance.Epsilon || t > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
var iy = p1y + t * (p2y - p1y);
|
||||
var dist = direction == PushDirection.Down ? vy - iy : iy - vy;
|
||||
|
||||
if (dist > Tolerance.Epsilon)
|
||||
return dist;
|
||||
if (dist >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
default:
|
||||
return double.MaxValue;
|
||||
@@ -75,11 +90,18 @@ namespace OpenNest.Geometry
|
||||
/// Returns double.MaxValue if the ray does not hit the segment.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
public static double RayEdgeDistance(
|
||||
double vx, double vy,
|
||||
double p1x, double p1y, double p2x, double p2y,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
double p1x,
|
||||
double p1y,
|
||||
double p2x,
|
||||
double p2y,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
var ex = p2x - p1x;
|
||||
var ey = p2y - p1y;
|
||||
@@ -99,8 +121,10 @@ namespace OpenNest.Geometry
|
||||
if (s < -Tolerance.Epsilon || s > 1.0 + Tolerance.Epsilon)
|
||||
return double.MaxValue;
|
||||
|
||||
if (t > Tolerance.Epsilon) return t;
|
||||
if (t >= -Tolerance.Epsilon) return 0;
|
||||
if (t > Tolerance.Epsilon)
|
||||
return t;
|
||||
if (t >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
return double.MaxValue;
|
||||
}
|
||||
|
||||
@@ -109,12 +133,19 @@ namespace OpenNest.Geometry
|
||||
/// Returns false if no real intersection exists.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
private static bool SolveRayCircle(
|
||||
double vx, double vy,
|
||||
double cx, double cy, double r,
|
||||
double dirX, double dirY,
|
||||
out double t1, out double t2)
|
||||
double vx,
|
||||
double vy,
|
||||
double cx,
|
||||
double cy,
|
||||
double r,
|
||||
double dirX,
|
||||
double dirY,
|
||||
out double t1,
|
||||
out double t2
|
||||
)
|
||||
{
|
||||
var ox = vx - cx;
|
||||
var oy = vy - cy;
|
||||
@@ -143,12 +174,20 @@ namespace OpenNest.Geometry
|
||||
/// angular span. Returns double.MaxValue if no hit.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
public static double RayArcDistance(
|
||||
double vx, double vy,
|
||||
double cx, double cy, double r,
|
||||
double startAngle, double endAngle, bool reversed,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
double cx,
|
||||
double cy,
|
||||
double r,
|
||||
double startAngle,
|
||||
double endAngle,
|
||||
bool reversed,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (!SolveRayCircle(vx, vy, cx, cy, r, dirX, dirY, out var t1, out var t2))
|
||||
return double.MaxValue;
|
||||
@@ -157,16 +196,18 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (t1 > -Tolerance.Epsilon)
|
||||
{
|
||||
var hitAngle = Angle.NormalizeRad(System.Math.Atan2(
|
||||
vy + t1 * dirY - cy, vx + t1 * dirX - cx));
|
||||
var hitAngle = Angle.NormalizeRad(
|
||||
System.Math.Atan2(vy + t1 * dirY - cy, vx + t1 * dirX - cx)
|
||||
);
|
||||
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
|
||||
best = t1 > Tolerance.Epsilon ? t1 : 0;
|
||||
}
|
||||
|
||||
if (t2 > -Tolerance.Epsilon && t2 < best)
|
||||
{
|
||||
var hitAngle = Angle.NormalizeRad(System.Math.Atan2(
|
||||
vy + t2 * dirY - cy, vx + t2 * dirX - cx));
|
||||
var hitAngle = Angle.NormalizeRad(
|
||||
System.Math.Atan2(vy + t2 * dirY - cy, vx + t2 * dirX - cx)
|
||||
);
|
||||
if (Angle.IsBetweenRad(hitAngle, startAngle, endAngle, reversed))
|
||||
best = t2 > Tolerance.Epsilon ? t2 : 0;
|
||||
}
|
||||
@@ -179,19 +220,29 @@ namespace OpenNest.Geometry
|
||||
/// Returns double.MaxValue if no hit.
|
||||
/// </summary>
|
||||
[System.Runtime.CompilerServices.MethodImpl(
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
|
||||
System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining
|
||||
)]
|
||||
public static double RayCircleDistance(
|
||||
double vx, double vy,
|
||||
double cx, double cy, double r,
|
||||
double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
double cx,
|
||||
double cy,
|
||||
double r,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (!SolveRayCircle(vx, vy, cx, cy, r, dirX, dirY, out var t1, out var t2))
|
||||
return double.MaxValue;
|
||||
|
||||
if (t1 > Tolerance.Epsilon) return t1;
|
||||
if (t1 >= -Tolerance.Epsilon) return 0;
|
||||
if (t2 > Tolerance.Epsilon) return t2;
|
||||
if (t2 >= -Tolerance.Epsilon) return 0;
|
||||
if (t1 > Tolerance.Epsilon)
|
||||
return t1;
|
||||
if (t1 >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
if (t2 > Tolerance.Epsilon)
|
||||
return t2;
|
||||
if (t2 >= -Tolerance.Epsilon)
|
||||
return 0;
|
||||
|
||||
return double.MaxValue;
|
||||
}
|
||||
@@ -201,7 +252,11 @@ namespace OpenNest.Geometry
|
||||
/// any edge of movingLines contacts any edge of stationaryLines.
|
||||
/// Returns double.MaxValue if no collision path exists.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(List<Line> movingLines, List<Line> stationaryLines, PushDirection direction)
|
||||
public static double DirectionalDistance(
|
||||
List<Line> movingLines,
|
||||
List<Line> stationaryLines,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
return DirectionalDistance(movingLines, 0, 0, stationaryLines, direction);
|
||||
}
|
||||
@@ -211,8 +266,12 @@ namespace OpenNest.Geometry
|
||||
/// by (movingDx, movingDy) without creating new Line objects.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
List<Line> movingLines, double movingDx, double movingDy,
|
||||
List<Line> stationaryLines, PushDirection direction)
|
||||
List<Line> movingLines,
|
||||
double movingDx,
|
||||
double movingDy,
|
||||
List<Line> stationaryLines,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var movingOffset = new Vector(movingDx, movingDy);
|
||||
@@ -226,7 +285,8 @@ namespace OpenNest.Geometry
|
||||
foreach (var mv in movingVertices)
|
||||
{
|
||||
var d = OneWayDistance(mv, stationaryEdges, Vector.Zero, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
// Case 2: Each stationary vertex -> each moving edge (opposite direction)
|
||||
@@ -239,7 +299,8 @@ namespace OpenNest.Geometry
|
||||
foreach (var sv in stationaryVertices)
|
||||
{
|
||||
var d = OneWayDistance(sv, movingEdges, movingOffset, opposite);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
@@ -267,9 +328,12 @@ namespace OpenNest.Geometry
|
||||
/// to avoid all intermediate object allocations.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
(Vector start, Vector end)[] movingEdges, Vector movingOffset,
|
||||
(Vector start, Vector end)[] stationaryEdges, Vector stationaryOffset,
|
||||
PushDirection direction)
|
||||
(Vector start, Vector end)[] movingEdges,
|
||||
Vector movingOffset,
|
||||
(Vector start, Vector end)[] stationaryEdges,
|
||||
Vector stationaryOffset,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
|
||||
@@ -281,7 +345,8 @@ namespace OpenNest.Geometry
|
||||
foreach (var mv in movingVertices)
|
||||
{
|
||||
var d = OneWayDistance(mv, stationaryEdges, stationaryOffset, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
// Case 2: Each stationary vertex -> each moving edge (opposite direction)
|
||||
@@ -293,15 +358,19 @@ namespace OpenNest.Geometry
|
||||
foreach (var sv in stationaryVertices)
|
||||
{
|
||||
var d = OneWayDistance(sv, movingEdges, movingOffset, opposite);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
}
|
||||
|
||||
public static double OneWayDistance(
|
||||
Vector vertex, (Vector start, Vector end)[] edges, Vector edgeOffset,
|
||||
PushDirection direction)
|
||||
Vector vertex,
|
||||
(Vector start, Vector end)[] edges,
|
||||
Vector edgeOffset,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var vx = vertex.X;
|
||||
@@ -315,7 +384,9 @@ namespace OpenNest.Geometry
|
||||
var e1 = edges[i].start + edgeOffset;
|
||||
var e2 = edges[i].end + edgeOffset;
|
||||
|
||||
double perpValue, edgeMin, edgeMax;
|
||||
double perpValue,
|
||||
edgeMin,
|
||||
edgeMax;
|
||||
if (horizontal)
|
||||
{
|
||||
perpValue = vy;
|
||||
@@ -337,7 +408,8 @@ namespace OpenNest.Geometry
|
||||
continue;
|
||||
|
||||
var d = RayEdgeDistance(vx, vy, e1.X, e1.Y, e2.X, e2.Y, direction);
|
||||
if (d < minDist) minDist = d;
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
|
||||
return minDist;
|
||||
@@ -347,11 +419,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return PushDirection.Right;
|
||||
case PushDirection.Right: return PushDirection.Left;
|
||||
case PushDirection.Up: return PushDirection.Down;
|
||||
case PushDirection.Down: return PushDirection.Up;
|
||||
default: return direction;
|
||||
case PushDirection.Left:
|
||||
return PushDirection.Right;
|
||||
case PushDirection.Right:
|
||||
return PushDirection.Left;
|
||||
case PushDirection.Up:
|
||||
return PushDirection.Down;
|
||||
case PushDirection.Down:
|
||||
return PushDirection.Up;
|
||||
default:
|
||||
return direction;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,11 +441,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return box.Left - boundary.Left;
|
||||
case PushDirection.Right: return boundary.Right - box.Right;
|
||||
case PushDirection.Up: return boundary.Top - box.Top;
|
||||
case PushDirection.Down: return box.Bottom - boundary.Bottom;
|
||||
default: return double.MaxValue;
|
||||
case PushDirection.Left:
|
||||
return box.Left - boundary.Left;
|
||||
case PushDirection.Right:
|
||||
return boundary.Right - box.Right;
|
||||
case PushDirection.Up:
|
||||
return boundary.Top - box.Top;
|
||||
case PushDirection.Down:
|
||||
return box.Bottom - boundary.Bottom;
|
||||
default:
|
||||
return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,11 +458,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return new Vector(-distance, 0);
|
||||
case PushDirection.Right: return new Vector(distance, 0);
|
||||
case PushDirection.Up: return new Vector(0, distance);
|
||||
case PushDirection.Down: return new Vector(0, -distance);
|
||||
default: return new Vector();
|
||||
case PushDirection.Left:
|
||||
return new Vector(-distance, 0);
|
||||
case PushDirection.Right:
|
||||
return new Vector(distance, 0);
|
||||
case PushDirection.Up:
|
||||
return new Vector(0, distance);
|
||||
case PushDirection.Down:
|
||||
return new Vector(0, -distance);
|
||||
default:
|
||||
return new Vector();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -388,11 +475,16 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case PushDirection.Left: return from.Left - to.Right;
|
||||
case PushDirection.Right: return to.Left - from.Right;
|
||||
case PushDirection.Up: return to.Bottom - from.Top;
|
||||
case PushDirection.Down: return from.Bottom - to.Top;
|
||||
default: return double.MaxValue;
|
||||
case PushDirection.Left:
|
||||
return from.Left - to.Right;
|
||||
case PushDirection.Right:
|
||||
return to.Left - from.Right;
|
||||
case PushDirection.Up:
|
||||
return to.Bottom - from.Top;
|
||||
case PushDirection.Down:
|
||||
return from.Bottom - to.Top;
|
||||
default:
|
||||
return double.MaxValue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,23 +501,27 @@ namespace OpenNest.Geometry
|
||||
if (direction.X < -Tolerance.Epsilon)
|
||||
{
|
||||
var d = (box.Left - boundary.Left) / -direction.X;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
else if (direction.X > Tolerance.Epsilon)
|
||||
{
|
||||
var d = (boundary.Right - box.Right) / direction.X;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
|
||||
if (direction.Y < -Tolerance.Epsilon)
|
||||
{
|
||||
var d = (box.Bottom - boundary.Bottom) / -direction.Y;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
else if (direction.Y > Tolerance.Epsilon)
|
||||
{
|
||||
var d = (boundary.Top - box.Top) / direction.Y;
|
||||
if (d < dist) dist = d;
|
||||
if (d < dist)
|
||||
dist = d;
|
||||
}
|
||||
|
||||
return dist < 0 ? 0 : dist;
|
||||
@@ -463,7 +559,11 @@ namespace OpenNest.Geometry
|
||||
/// Computes the minimum translation distance along an arbitrary unit direction
|
||||
/// before any edge of movingLines contacts any edge of stationaryLines.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(List<Line> movingLines, List<Line> stationaryLines, Vector direction)
|
||||
public static double DirectionalDistance(
|
||||
List<Line> movingLines,
|
||||
List<Line> stationaryLines,
|
||||
Vector direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var dirX = direction.X;
|
||||
@@ -476,8 +576,18 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < stationaryLines.Count; i++)
|
||||
{
|
||||
var e = stationaryLines[i];
|
||||
var d = RayEdgeDistance(mv.X, mv.Y, e.pt1.X, e.pt1.Y, e.pt2.X, e.pt2.Y, dirX, dirY);
|
||||
if (d < minDist) minDist = d;
|
||||
var d = RayEdgeDistance(
|
||||
mv.X,
|
||||
mv.Y,
|
||||
e.pt1.X,
|
||||
e.pt1.Y,
|
||||
e.pt2.X,
|
||||
e.pt2.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -491,8 +601,18 @@ namespace OpenNest.Geometry
|
||||
for (var i = 0; i < movingLines.Count; i++)
|
||||
{
|
||||
var e = movingLines[i];
|
||||
var d = RayEdgeDistance(sv.X, sv.Y, e.pt1.X, e.pt1.Y, e.pt2.X, e.pt2.Y, oppX, oppY);
|
||||
if (d < minDist) minDist = d;
|
||||
var d = RayEdgeDistance(
|
||||
sv.X,
|
||||
sv.Y,
|
||||
e.pt1.X,
|
||||
e.pt1.Y,
|
||||
e.pt2.X,
|
||||
e.pt2.Y,
|
||||
oppX,
|
||||
oppY
|
||||
);
|
||||
if (d < minDist)
|
||||
minDist = d;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -505,9 +625,16 @@ namespace OpenNest.Geometry
|
||||
/// stationaryEntities. Delegates to the Vector-based overload.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
List<Entity> movingEntities, List<Entity> stationaryEntities, PushDirection direction)
|
||||
List<Entity> movingEntities,
|
||||
List<Entity> stationaryEntities,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
return DirectionalDistance(movingEntities, stationaryEntities, DirectionToOffset(direction, 1.0));
|
||||
return DirectionalDistance(
|
||||
movingEntities,
|
||||
stationaryEntities,
|
||||
DirectionToOffset(direction, 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -517,7 +644,10 @@ namespace OpenNest.Geometry
|
||||
/// without tessellation.
|
||||
/// </summary>
|
||||
public static double DirectionalDistance(
|
||||
List<Entity> movingEntities, List<Entity> stationaryEntities, Vector direction)
|
||||
List<Entity> movingEntities,
|
||||
List<Entity> stationaryEntities,
|
||||
Vector direction
|
||||
)
|
||||
{
|
||||
var minDist = double.MaxValue;
|
||||
var dirX = direction.X;
|
||||
@@ -536,7 +666,8 @@ namespace OpenNest.Geometry
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -557,7 +688,8 @@ namespace OpenNest.Geometry
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,10 +698,24 @@ namespace OpenNest.Geometry
|
||||
// Phases 1-2 sample arc endpoints and cardinal extremes, but the actual
|
||||
// closest point on a small corner arc to a straight edge may lie between
|
||||
// those samples. Use ClosestPointTo to find it and fire a ray from there.
|
||||
minDist = ArcToLineClosestDistance(movingEntities, stationaryEntities, dirX, dirY, minDist);
|
||||
if (minDist <= 0) return 0;
|
||||
minDist = ArcToLineClosestDistance(stationaryEntities, movingEntities, oppX, oppY, minDist);
|
||||
if (minDist <= 0) return 0;
|
||||
minDist = ArcToLineClosestDistance(
|
||||
movingEntities,
|
||||
stationaryEntities,
|
||||
dirX,
|
||||
dirY,
|
||||
minDist
|
||||
);
|
||||
if (minDist <= 0)
|
||||
return 0;
|
||||
minDist = ArcToLineClosestDistance(
|
||||
stationaryEntities,
|
||||
movingEntities,
|
||||
oppX,
|
||||
oppY,
|
||||
minDist
|
||||
);
|
||||
if (minDist <= 0)
|
||||
return 0;
|
||||
|
||||
// Phase 4: Curve-to-curve direct distance.
|
||||
// The vertex-to-entity approach misses the closest contact between two
|
||||
@@ -605,20 +751,35 @@ namespace OpenNest.Geometry
|
||||
if (me is Arc mArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(toCy, toCx));
|
||||
if (!Angle.IsBetweenRad(angle, mArc.StartAngle, mArc.EndAngle, mArc.IsReversed))
|
||||
if (
|
||||
!Angle.IsBetweenRad(
|
||||
angle,
|
||||
mArc.StartAngle,
|
||||
mArc.EndAngle,
|
||||
mArc.IsReversed
|
||||
)
|
||||
)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (se is Arc sArc)
|
||||
{
|
||||
var angle = Angle.NormalizeRad(System.Math.Atan2(-toCy, -toCx));
|
||||
if (!Angle.IsBetweenRad(angle, sArc.StartAngle, sArc.EndAngle, sArc.IsReversed))
|
||||
if (
|
||||
!Angle.IsBetweenRad(
|
||||
angle,
|
||||
sArc.StartAngle,
|
||||
sArc.EndAngle,
|
||||
sArc.IsReversed
|
||||
)
|
||||
)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
minDist = d;
|
||||
if (d <= 0) return 0;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,8 +787,12 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static double ArcToLineClosestDistance(
|
||||
List<Entity> arcEntities, List<Entity> lineEntities,
|
||||
double dirX, double dirY, double minDist)
|
||||
List<Entity> arcEntities,
|
||||
List<Entity> lineEntities,
|
||||
double dirX,
|
||||
double dirY,
|
||||
double minDist
|
||||
)
|
||||
{
|
||||
for (var i = 0; i < arcEntities.Count; i++)
|
||||
{
|
||||
@@ -662,15 +827,30 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
var theta = k == 0 ? theta1 : theta2;
|
||||
|
||||
if (!Angle.IsBetweenRad(theta, arc.StartAngle, arc.EndAngle, arc.IsReversed))
|
||||
if (
|
||||
!Angle.IsBetweenRad(theta, arc.StartAngle, arc.EndAngle, arc.IsReversed)
|
||||
)
|
||||
continue;
|
||||
|
||||
var qx = cx + r * System.Math.Cos(theta);
|
||||
var qy = cy + r * System.Math.Sin(theta);
|
||||
|
||||
var d = RayEdgeDistance(qx, qy, p1x, p1y, line.pt2.X, line.pt2.Y,
|
||||
dirX, dirY);
|
||||
if (d < minDist) { minDist = d; if (d <= 0) return 0; }
|
||||
var d = RayEdgeDistance(
|
||||
qx,
|
||||
qy,
|
||||
p1x,
|
||||
p1y,
|
||||
line.pt2.X,
|
||||
line.pt2.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
if (d < minDist)
|
||||
{
|
||||
minDist = d;
|
||||
if (d <= 0)
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -678,28 +858,54 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static double RayEntityDistance(
|
||||
double vx, double vy, Entity entity, double dirX, double dirY)
|
||||
double vx,
|
||||
double vy,
|
||||
Entity entity,
|
||||
double dirX,
|
||||
double dirY
|
||||
)
|
||||
{
|
||||
if (entity is Line line)
|
||||
{
|
||||
return RayEdgeDistance(vx, vy,
|
||||
line.pt1.X, line.pt1.Y, line.pt2.X, line.pt2.Y,
|
||||
dirX, dirY);
|
||||
return RayEdgeDistance(
|
||||
vx,
|
||||
vy,
|
||||
line.pt1.X,
|
||||
line.pt1.Y,
|
||||
line.pt2.X,
|
||||
line.pt2.Y,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
if (entity is Arc arc)
|
||||
{
|
||||
return RayArcDistance(vx, vy,
|
||||
arc.Center.X, arc.Center.Y, arc.Radius,
|
||||
arc.StartAngle, arc.EndAngle, arc.IsReversed,
|
||||
dirX, dirY);
|
||||
return RayArcDistance(
|
||||
vx,
|
||||
vy,
|
||||
arc.Center.X,
|
||||
arc.Center.Y,
|
||||
arc.Radius,
|
||||
arc.StartAngle,
|
||||
arc.EndAngle,
|
||||
arc.IsReversed,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
if (entity is Circle circle)
|
||||
{
|
||||
return RayCircleDistance(vx, vy,
|
||||
circle.Center.X, circle.Center.Y, circle.Radius,
|
||||
dirX, dirY);
|
||||
return RayCircleDistance(
|
||||
vx,
|
||||
vy,
|
||||
circle.Center.X,
|
||||
circle.Center.Y,
|
||||
circle.Radius,
|
||||
dirX,
|
||||
dirY
|
||||
);
|
||||
}
|
||||
|
||||
return double.MaxValue;
|
||||
@@ -759,7 +965,10 @@ namespace OpenNest.Geometry
|
||||
return CollectVertices(ToEdgeArray(lines), offset);
|
||||
}
|
||||
|
||||
private static HashSet<Vector> CollectVertices((Vector start, Vector end)[] edges, Vector offset)
|
||||
private static HashSet<Vector> CollectVertices(
|
||||
(Vector start, Vector end)[] edges,
|
||||
Vector offset
|
||||
)
|
||||
{
|
||||
var vertices = new HashSet<Vector>();
|
||||
for (var i = 0; i < edges.Length; i++)
|
||||
@@ -778,26 +987,48 @@ namespace OpenNest.Geometry
|
||||
return edges;
|
||||
}
|
||||
|
||||
private static void SortEdgesForPruning((Vector start, Vector end)[] edges, PushDirection direction)
|
||||
private static void SortEdgesForPruning(
|
||||
(Vector start, Vector end)[] edges,
|
||||
PushDirection direction
|
||||
)
|
||||
{
|
||||
if (direction == PushDirection.Left || direction == PushDirection.Right)
|
||||
System.Array.Sort(edges, (a, b) =>
|
||||
System.Math.Min(a.start.Y, a.end.Y).CompareTo(System.Math.Min(b.start.Y, b.end.Y)));
|
||||
System.Array.Sort(
|
||||
edges,
|
||||
(a, b) =>
|
||||
System
|
||||
.Math.Min(a.start.Y, a.end.Y)
|
||||
.CompareTo(System.Math.Min(b.start.Y, b.end.Y))
|
||||
);
|
||||
else
|
||||
System.Array.Sort(edges, (a, b) =>
|
||||
System.Math.Min(a.start.X, a.end.X).CompareTo(System.Math.Min(b.start.X, b.end.X)));
|
||||
System.Array.Sort(
|
||||
edges,
|
||||
(a, b) =>
|
||||
System
|
||||
.Math.Min(a.start.X, a.end.X)
|
||||
.CompareTo(System.Math.Min(b.start.X, b.end.X))
|
||||
);
|
||||
}
|
||||
|
||||
private static bool TryGetCurveParams(Entity entity, out double cx, out double cy, out double r)
|
||||
private static bool TryGetCurveParams(
|
||||
Entity entity,
|
||||
out double cx,
|
||||
out double cy,
|
||||
out double r
|
||||
)
|
||||
{
|
||||
if (entity is Circle circle)
|
||||
{
|
||||
cx = circle.Center.X; cy = circle.Center.Y; r = circle.Radius;
|
||||
cx = circle.Center.X;
|
||||
cy = circle.Center.Y;
|
||||
r = circle.Radius;
|
||||
return true;
|
||||
}
|
||||
if (entity is Arc arc)
|
||||
{
|
||||
cx = arc.Center.X; cy = arc.Center.Y; r = arc.Radius;
|
||||
cx = arc.Center.X;
|
||||
cy = arc.Center.Y;
|
||||
r = arc.Radius;
|
||||
return true;
|
||||
}
|
||||
cx = cy = r = 0;
|
||||
@@ -850,7 +1081,13 @@ namespace OpenNest.Geometry
|
||||
return new Box(lft, btm, rgt - lft, top - btm);
|
||||
}
|
||||
|
||||
private static bool FindVerticalLimits(Vector pt, Box bounds, List<Box> boxes, out double top, out double btm)
|
||||
private static bool FindVerticalLimits(
|
||||
Vector pt,
|
||||
Box bounds,
|
||||
List<Box> boxes,
|
||||
out double top,
|
||||
out double btm
|
||||
)
|
||||
{
|
||||
top = double.MaxValue;
|
||||
btm = double.MinValue;
|
||||
@@ -868,20 +1105,30 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (top == double.MaxValue)
|
||||
{
|
||||
if (bounds.Top > pt.Y) top = bounds.Top;
|
||||
else return false;
|
||||
if (bounds.Top > pt.Y)
|
||||
top = bounds.Top;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (btm == double.MinValue)
|
||||
{
|
||||
if (bounds.Bottom < pt.Y) btm = bounds.Bottom;
|
||||
else return false;
|
||||
if (bounds.Bottom < pt.Y)
|
||||
btm = bounds.Bottom;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool FindHorizontalLimits(Vector pt, Box bounds, List<Box> boxes, out double lft, out double rgt)
|
||||
private static bool FindHorizontalLimits(
|
||||
Vector pt,
|
||||
Box bounds,
|
||||
List<Box> boxes,
|
||||
out double lft,
|
||||
out double rgt
|
||||
)
|
||||
{
|
||||
lft = double.MinValue;
|
||||
rgt = double.MaxValue;
|
||||
@@ -899,14 +1146,18 @@ namespace OpenNest.Geometry
|
||||
|
||||
if (rgt == double.MaxValue)
|
||||
{
|
||||
if (bounds.Right > pt.X) rgt = bounds.Right;
|
||||
else return false;
|
||||
if (bounds.Right > pt.X)
|
||||
rgt = bounds.Right;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
if (lft == double.MinValue)
|
||||
{
|
||||
if (bounds.Left < pt.X) lft = bounds.Left;
|
||||
else return false;
|
||||
if (bounds.Left < pt.X)
|
||||
lft = bounds.Left;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -8,7 +8,11 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
private const int MinPointsForArc = 3;
|
||||
|
||||
public static List<Entity> Convert(List<Vector> points, bool isClosed, double tolerance = 0.001)
|
||||
public static List<Entity> Convert(
|
||||
List<Vector> points,
|
||||
bool isClosed,
|
||||
double tolerance = 0.001
|
||||
)
|
||||
{
|
||||
if (points == null || points.Count < 2)
|
||||
return new List<Entity>();
|
||||
@@ -37,8 +41,12 @@ namespace OpenNest.Geometry
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static ArcFitResult TryFitArc(List<Vector> points, int start,
|
||||
Vector chainedTangent, double tolerance)
|
||||
private static ArcFitResult TryFitArc(
|
||||
List<Vector> points,
|
||||
int start,
|
||||
Vector chainedTangent,
|
||||
double tolerance
|
||||
)
|
||||
{
|
||||
var minEnd = start + MinPointsForArc - 1;
|
||||
if (minEnd >= points.Count)
|
||||
@@ -83,7 +91,8 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static (Vector center, double radius, double deviation) FitCircumscribed(
|
||||
List<Vector> points)
|
||||
List<Vector> points
|
||||
)
|
||||
{
|
||||
if (points.Count < 3)
|
||||
return (Vector.Invalid, 0, double.MaxValue);
|
||||
@@ -131,11 +140,16 @@ namespace OpenNest.Geometry
|
||||
}
|
||||
|
||||
private static (Vector center, double radius, double deviation) FitWithStartTangent(
|
||||
List<Vector> points, Vector tangent) =>
|
||||
ArcFit.FitWithStartTangent(points, tangent);
|
||||
List<Vector> points,
|
||||
Vector tangent
|
||||
) => ArcFit.FitWithStartTangent(points, tangent);
|
||||
|
||||
private static double MaxRadialDeviation(List<Vector> points, double cx, double cy, double radius) =>
|
||||
ArcFit.MaxRadialDeviation(points, cx, cy, radius);
|
||||
private static double MaxRadialDeviation(
|
||||
List<Vector> points,
|
||||
double cx,
|
||||
double cy,
|
||||
double radius
|
||||
) => ArcFit.MaxRadialDeviation(points, cx, cy, radius);
|
||||
|
||||
private static double SumSignedAngles(Vector center, List<Vector> points)
|
||||
{
|
||||
@@ -145,8 +159,10 @@ namespace OpenNest.Geometry
|
||||
var a1 = System.Math.Atan2(points[i].Y - center.Y, points[i].X - center.X);
|
||||
var a2 = System.Math.Atan2(points[i + 1].Y - center.Y, points[i + 1].X - center.X);
|
||||
var da = a2 - a1;
|
||||
while (da > System.Math.PI) da -= Angle.TwoPI;
|
||||
while (da < -System.Math.PI) da += Angle.TwoPI;
|
||||
while (da > System.Math.PI)
|
||||
da -= Angle.TwoPI;
|
||||
while (da < -System.Math.PI)
|
||||
da += Angle.TwoPI;
|
||||
total += da;
|
||||
}
|
||||
return total;
|
||||
@@ -160,9 +176,7 @@ namespace OpenNest.Geometry
|
||||
var rx = lastPt.X - center.X;
|
||||
var ry = lastPt.Y - center.Y;
|
||||
|
||||
return totalAngle >= 0
|
||||
? new Vector(-ry, rx)
|
||||
: new Vector(ry, -rx);
|
||||
return totalAngle >= 0 ? new Vector(-ry, rx) : new Vector(ry, -rx);
|
||||
}
|
||||
|
||||
private static Arc CreateArc(Vector center, double radius, List<Vector> points)
|
||||
@@ -174,8 +188,10 @@ namespace OpenNest.Geometry
|
||||
var endAngle = System.Math.Atan2(lastPoint.Y - center.Y, lastPoint.X - center.X);
|
||||
var isReversed = SumSignedAngles(center, points) < 0;
|
||||
|
||||
if (startAngle < 0) startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0) endAngle += Angle.TwoPI;
|
||||
if (startAngle < 0)
|
||||
startAngle += Angle.TwoPI;
|
||||
if (endAngle < 0)
|
||||
endAngle += Angle.TwoPI;
|
||||
|
||||
return new Arc(center, radius, startAngle, endAngle, isReversed);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Math;
|
||||
using System;
|
||||
using System;
|
||||
using OpenNest.Math;
|
||||
|
||||
namespace OpenNest.Geometry
|
||||
{
|
||||
@@ -31,7 +31,7 @@ namespace OpenNest.Geometry
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
// Use a simple but effective hash combine.
|
||||
// Use a simple but effective hash combine.
|
||||
// We use a small epsilon-safe rounding if needed, but for uniqueness in HashSet
|
||||
// during a single operation, raw bits or slightly rounded is usually fine.
|
||||
// However, IsEqualTo uses Tolerance.Epsilon, so we should probably round to some precision.
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
{
|
||||
public class Material
|
||||
{
|
||||
public Material()
|
||||
{
|
||||
}
|
||||
public Material() { }
|
||||
|
||||
public Material(string name)
|
||||
{
|
||||
|
||||
@@ -90,8 +90,7 @@
|
||||
a1 = Angle.NormalizeRad(angle - a1);
|
||||
a2 = Angle.NormalizeRad(a2 - angle);
|
||||
|
||||
return diff >= a1 - Tolerance.Epsilon ||
|
||||
diff >= a2 - Tolerance.Epsilon;
|
||||
return diff >= a1 - Tolerance.Epsilon || diff >= a2 - Tolerance.Epsilon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -116,8 +115,7 @@
|
||||
a1 = Angle.NormalizeRad(angle - a1);
|
||||
a2 = Angle.NormalizeRad(a2 - angle);
|
||||
|
||||
return diff >= a1 - Tolerance.Epsilon ||
|
||||
diff >= a2 - Tolerance.Epsilon;
|
||||
return diff >= a1 - Tolerance.Epsilon || diff >= a2 - Tolerance.Epsilon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,18 @@ namespace OpenNest.Math
|
||||
/// </summary>
|
||||
public static class ExpressionEvaluator
|
||||
{
|
||||
public static double Evaluate(string expression, IReadOnlyDictionary<string, double> variables)
|
||||
public static double Evaluate(
|
||||
string expression,
|
||||
IReadOnlyDictionary<string, double> variables
|
||||
)
|
||||
{
|
||||
var parser = new Parser(expression, variables);
|
||||
var result = parser.ParseExpression();
|
||||
parser.SkipWhitespace();
|
||||
if (!parser.IsEnd)
|
||||
throw new FormatException($"Unexpected character at position {parser.Position}: '{parser.Current}'");
|
||||
throw new FormatException(
|
||||
$"Unexpected character at position {parser.Position}: '{parser.Current}'"
|
||||
);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -52,10 +57,12 @@ namespace OpenNest.Math
|
||||
while (true)
|
||||
{
|
||||
SkipWhitespace();
|
||||
if (IsEnd) break;
|
||||
if (IsEnd)
|
||||
break;
|
||||
|
||||
var op = Current;
|
||||
if (op != '+' && op != '-') break;
|
||||
if (op != '+' && op != '-')
|
||||
break;
|
||||
|
||||
_pos++;
|
||||
SkipWhitespace();
|
||||
@@ -75,10 +82,12 @@ namespace OpenNest.Math
|
||||
while (true)
|
||||
{
|
||||
SkipWhitespace();
|
||||
if (IsEnd) break;
|
||||
if (IsEnd)
|
||||
break;
|
||||
|
||||
var op = Current;
|
||||
if (op != '*' && op != '/') break;
|
||||
if (op != '*' && op != '/')
|
||||
break;
|
||||
|
||||
_pos++;
|
||||
SkipWhitespace();
|
||||
@@ -129,7 +138,10 @@ namespace OpenNest.Math
|
||||
{
|
||||
_pos++; // consume '$'
|
||||
var start = _pos;
|
||||
while (_pos < _input.Length && (char.IsLetterOrDigit(_input[_pos]) || _input[_pos] == '_'))
|
||||
while (
|
||||
_pos < _input.Length
|
||||
&& (char.IsLetterOrDigit(_input[_pos]) || _input[_pos] == '_')
|
||||
)
|
||||
_pos++;
|
||||
if (_pos == start)
|
||||
throw new FormatException("Expected variable name after '$'.");
|
||||
@@ -145,10 +157,19 @@ namespace OpenNest.Math
|
||||
_pos++;
|
||||
|
||||
if (_pos == numStart)
|
||||
throw new FormatException($"Unexpected character '{Current}' at position {_pos}.");
|
||||
throw new FormatException(
|
||||
$"Unexpected character '{Current}' at position {_pos}."
|
||||
);
|
||||
|
||||
var numSpan = _input.Slice(numStart, _pos - numStart).ToString();
|
||||
if (!double.TryParse(numSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out var number))
|
||||
if (
|
||||
!double.TryParse(
|
||||
numSpan,
|
||||
NumberStyles.Float,
|
||||
CultureInfo.InvariantCulture,
|
||||
out var number
|
||||
)
|
||||
)
|
||||
throw new FormatException($"Invalid number: '{numSpan}'");
|
||||
|
||||
return number;
|
||||
|
||||
@@ -7,8 +7,9 @@ namespace OpenNest.Math
|
||||
{
|
||||
public static class Fraction
|
||||
{
|
||||
public static readonly Regex FractionRegex =
|
||||
new Regex(@"((?<WholeNum>\d+)(\ |-))?(?<Fraction>\d+\/\d+)");
|
||||
public static readonly Regex FractionRegex = new Regex(
|
||||
@"((?<WholeNum>\d+)(\ |-))?(?<Fraction>\d+\/\d+)"
|
||||
);
|
||||
|
||||
public static bool IsValid(string s)
|
||||
{
|
||||
@@ -59,7 +60,8 @@ namespace OpenNest.Math
|
||||
{
|
||||
var sb = new StringBuilder(input);
|
||||
|
||||
var fractionMatches = FractionRegex.Matches(sb.ToString())
|
||||
var fractionMatches = FractionRegex
|
||||
.Matches(sb.ToString())
|
||||
.Cast<Match>()
|
||||
.OrderByDescending(m => m.Index);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Geometry;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -11,9 +11,7 @@ namespace OpenNest
|
||||
public DrawingCollection Drawings;
|
||||
|
||||
public Nest()
|
||||
: this(string.Empty)
|
||||
{
|
||||
}
|
||||
: this(string.Empty) { }
|
||||
|
||||
public Nest(string name)
|
||||
{
|
||||
@@ -128,7 +126,7 @@ namespace OpenNest
|
||||
EdgeSpacing = EdgeSpacing,
|
||||
PartSpacing = PartSpacing,
|
||||
Quadrant = Quadrant,
|
||||
Quantity = 1
|
||||
Quantity = 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum OffsetSide
|
||||
{
|
||||
Left,
|
||||
Right
|
||||
Right,
|
||||
}
|
||||
}
|
||||
|
||||
+41
-17
@@ -1,9 +1,9 @@
|
||||
using OpenNest.CNC;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.CNC;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -27,9 +27,7 @@ namespace OpenNest
|
||||
public readonly Drawing BaseDrawing;
|
||||
|
||||
public Part(Drawing baseDrawing)
|
||||
: this(baseDrawing, new Vector())
|
||||
{
|
||||
}
|
||||
: this(baseDrawing, new Vector()) { }
|
||||
|
||||
public Part(Drawing baseDrawing, Vector location)
|
||||
{
|
||||
@@ -61,15 +59,25 @@ namespace OpenNest
|
||||
|
||||
public CNC.CuttingStrategy.CuttingParameters CuttingParameters { get; set; }
|
||||
|
||||
public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint)
|
||||
public void ApplyLeadIns(
|
||||
CNC.CuttingStrategy.CuttingParameters parameters,
|
||||
Vector approachPoint
|
||||
)
|
||||
{
|
||||
ApplyLeadIns(parameters, approachPoint, Geometry.Vector.Invalid);
|
||||
}
|
||||
|
||||
public void ApplyLeadIns(CNC.CuttingStrategy.CuttingParameters parameters, Vector approachPoint, Vector nextPartStart)
|
||||
public void ApplyLeadIns(
|
||||
CNC.CuttingStrategy.CuttingParameters parameters,
|
||||
Vector approachPoint,
|
||||
Vector nextPartStart
|
||||
)
|
||||
{
|
||||
preLeadInRotation = Rotation;
|
||||
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters };
|
||||
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy
|
||||
{
|
||||
Parameters = parameters,
|
||||
};
|
||||
var result = strategy.Apply(Program, approachPoint, nextPartStart);
|
||||
Program = result.Program;
|
||||
CuttingParameters = parameters;
|
||||
@@ -77,11 +85,18 @@ namespace OpenNest
|
||||
UpdateBounds();
|
||||
}
|
||||
|
||||
public void ApplySingleLeadIn(CNC.CuttingStrategy.CuttingParameters parameters,
|
||||
Geometry.Vector point, Geometry.Entity entity, CNC.CuttingStrategy.ContourType contourType)
|
||||
public void ApplySingleLeadIn(
|
||||
CNC.CuttingStrategy.CuttingParameters parameters,
|
||||
Geometry.Vector point,
|
||||
Geometry.Entity entity,
|
||||
CNC.CuttingStrategy.ContourType contourType
|
||||
)
|
||||
{
|
||||
preLeadInRotation = Rotation;
|
||||
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy { Parameters = parameters };
|
||||
var strategy = new CNC.CuttingStrategy.ContourCuttingStrategy
|
||||
{
|
||||
Parameters = parameters,
|
||||
};
|
||||
var result = strategy.ApplySingle(Program, point, entity, contourType);
|
||||
Program = result.Program;
|
||||
CuttingParameters = parameters;
|
||||
@@ -214,10 +229,12 @@ namespace OpenNest
|
||||
{
|
||||
pts = new List<Vector>();
|
||||
|
||||
var entities1 = ConvertProgram.ToGeometry(Program)
|
||||
var entities1 = ConvertProgram
|
||||
.ToGeometry(Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
var entities2 = ConvertProgram.ToGeometry(part.Program)
|
||||
var entities2 = ConvertProgram
|
||||
.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
|
||||
@@ -286,10 +303,17 @@ namespace OpenNest
|
||||
{
|
||||
// Share the Program instance — offset-only copies don't modify the program codes.
|
||||
// This is a major performance win for tiling large patterns.
|
||||
var part = new Part(BaseDrawing, Program,
|
||||
var part = new Part(
|
||||
BaseDrawing,
|
||||
Program,
|
||||
location + offset,
|
||||
new Box(BoundingBox.X + offset.X, BoundingBox.Y + offset.Y,
|
||||
BoundingBox.Length, BoundingBox.Width));
|
||||
new Box(
|
||||
BoundingBox.X + offset.X,
|
||||
BoundingBox.Y + offset.Y,
|
||||
BoundingBox.Length,
|
||||
BoundingBox.Width
|
||||
)
|
||||
);
|
||||
|
||||
return part;
|
||||
}
|
||||
|
||||
+134
-43
@@ -1,7 +1,7 @@
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Converters;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -10,7 +10,9 @@ namespace OpenNest
|
||||
public static List<Line> GetPartLines(Part part, double chordTolerance = 0.001)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var shapes = ShapeBuilder.GetShapes(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
);
|
||||
var lines = new List<Line>();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
@@ -23,10 +25,16 @@ namespace OpenNest
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static List<Line> GetPartLines(Part part, PushDirection facingDirection, double chordTolerance = 0.001)
|
||||
public static List<Line> GetPartLines(
|
||||
Part part,
|
||||
PushDirection facingDirection,
|
||||
double chordTolerance = 0.001
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var shapes = ShapeBuilder.GetShapes(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
);
|
||||
var lines = new List<Line>();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
@@ -47,7 +55,8 @@ namespace OpenNest
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
|
||||
var offsetShape = profile.Perimeter.OffsetOutward(spacing);
|
||||
if (offsetShape == null)
|
||||
@@ -69,7 +78,8 @@ namespace OpenNest
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
var entities = new List<Entity>();
|
||||
|
||||
var perimeter = profile.Perimeter.OffsetOutward(spacing);
|
||||
@@ -83,7 +93,8 @@ namespace OpenNest
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
{
|
||||
var inset = cutout.OffsetInward(spacing);
|
||||
if (inset == null) continue;
|
||||
if (inset == null)
|
||||
continue;
|
||||
foreach (var entity in inset.Entities)
|
||||
entity.Offset(part.Location);
|
||||
entities.AddRange(inset.Entities);
|
||||
@@ -100,7 +111,8 @@ namespace OpenNest
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
|
||||
return CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location);
|
||||
}
|
||||
@@ -113,7 +125,8 @@ namespace OpenNest
|
||||
{
|
||||
var geoEntities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
geoEntities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
var entities = CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location);
|
||||
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
@@ -136,50 +149,85 @@ namespace OpenNest
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, double chordTolerance = 0.001,
|
||||
bool perimeterOnly = false)
|
||||
public static List<Line> GetOffsetPartLines(
|
||||
Part part,
|
||||
double spacing,
|
||||
double chordTolerance = 0.001,
|
||||
bool perimeterOnly = false
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
var lines = new List<Line>();
|
||||
var totalSpacing = spacing;
|
||||
|
||||
AddOffsetLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance, part.Location);
|
||||
AddOffsetLines(
|
||||
lines,
|
||||
profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance,
|
||||
part.Location
|
||||
);
|
||||
|
||||
if (!perimeterOnly)
|
||||
{
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetLines(lines, cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance, part.Location);
|
||||
AddOffsetLines(
|
||||
lines,
|
||||
cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance,
|
||||
part.Location
|
||||
);
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, PushDirection facingDirection, double chordTolerance = 0.001)
|
||||
public static List<Line> GetOffsetPartLines(
|
||||
Part part,
|
||||
double spacing,
|
||||
PushDirection facingDirection,
|
||||
double chordTolerance = 0.001
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
var lines = new List<Line>();
|
||||
var totalSpacing = spacing;
|
||||
|
||||
AddOffsetDirectionalLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
AddOffsetDirectionalLines(
|
||||
lines,
|
||||
profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance,
|
||||
part.Location,
|
||||
facingDirection
|
||||
);
|
||||
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetDirectionalLines(lines, cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
AddOffsetDirectionalLines(
|
||||
lines,
|
||||
cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance,
|
||||
part.Location,
|
||||
facingDirection
|
||||
);
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static List<Line> GetPartLines(Part part, Vector facingDirection, double chordTolerance = 0.001)
|
||||
public static List<Line> GetPartLines(
|
||||
Part part,
|
||||
Vector facingDirection,
|
||||
double chordTolerance = 0.001
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var shapes = ShapeBuilder.GetShapes(entities.Where(e => e.Layer != SpecialLayers.Rapid));
|
||||
var shapes = ShapeBuilder.GetShapes(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
);
|
||||
var lines = new List<Line>();
|
||||
|
||||
foreach (var shape in shapes)
|
||||
@@ -192,20 +240,36 @@ namespace OpenNest
|
||||
return lines;
|
||||
}
|
||||
|
||||
public static List<Line> GetOffsetPartLines(Part part, double spacing, Vector facingDirection, double chordTolerance = 0.001)
|
||||
public static List<Line> GetOffsetPartLines(
|
||||
Part part,
|
||||
double spacing,
|
||||
Vector facingDirection,
|
||||
double chordTolerance = 0.001
|
||||
)
|
||||
{
|
||||
var entities = ConvertProgram.ToGeometry(part.Program);
|
||||
var profile = new ShapeProfile(
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList());
|
||||
entities.Where(e => e.Layer != SpecialLayers.Rapid).ToList()
|
||||
);
|
||||
var lines = new List<Line>();
|
||||
var totalSpacing = spacing;
|
||||
|
||||
AddOffsetDirectionalLines(lines, profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
AddOffsetDirectionalLines(
|
||||
lines,
|
||||
profile.Perimeter.OffsetOutward(totalSpacing),
|
||||
chordTolerance,
|
||||
part.Location,
|
||||
facingDirection
|
||||
);
|
||||
|
||||
foreach (var cutout in profile.Cutouts)
|
||||
AddOffsetDirectionalLines(lines, cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance, part.Location, facingDirection);
|
||||
AddOffsetDirectionalLines(
|
||||
lines,
|
||||
cutout.OffsetInward(totalSpacing),
|
||||
chordTolerance,
|
||||
part.Location,
|
||||
facingDirection
|
||||
);
|
||||
|
||||
return lines;
|
||||
}
|
||||
@@ -242,7 +306,10 @@ namespace OpenNest
|
||||
/// <summary>
|
||||
/// Returns only polygon edges whose outward normal faces the specified direction.
|
||||
/// </summary>
|
||||
private static List<Line> GetDirectionalLines(Polygon polygon, PushDirection facingDirection)
|
||||
private static List<Line> GetDirectionalLines(
|
||||
Polygon polygon,
|
||||
PushDirection facingDirection
|
||||
)
|
||||
{
|
||||
if (polygon.Vertices.Count < 3)
|
||||
return polygon.ToLines();
|
||||
@@ -261,11 +328,21 @@ namespace OpenNest
|
||||
|
||||
switch (facingDirection)
|
||||
{
|
||||
case PushDirection.Left: keep = -sign * dy > 0; break;
|
||||
case PushDirection.Right: keep = sign * dy > 0; break;
|
||||
case PushDirection.Up: keep = -sign * dx > 0; break;
|
||||
case PushDirection.Down: keep = sign * dx > 0; break;
|
||||
default: keep = true; break;
|
||||
case PushDirection.Left:
|
||||
keep = -sign * dy > 0;
|
||||
break;
|
||||
case PushDirection.Right:
|
||||
keep = sign * dy > 0;
|
||||
break;
|
||||
case PushDirection.Up:
|
||||
keep = -sign * dx > 0;
|
||||
break;
|
||||
case PushDirection.Down:
|
||||
keep = sign * dx > 0;
|
||||
break;
|
||||
default:
|
||||
keep = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (keep)
|
||||
@@ -277,8 +354,12 @@ namespace OpenNest
|
||||
return lines;
|
||||
}
|
||||
|
||||
private static void AddOffsetLines(List<Line> lines, Shape offsetEntity,
|
||||
double chordTolerance, Vector location)
|
||||
private static void AddOffsetLines(
|
||||
List<Line> lines,
|
||||
Shape offsetEntity,
|
||||
double chordTolerance,
|
||||
Vector location
|
||||
)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
@@ -289,8 +370,13 @@ namespace OpenNest
|
||||
lines.AddRange(polygon.ToLines());
|
||||
}
|
||||
|
||||
private static void AddOffsetDirectionalLines(List<Line> lines, Shape offsetEntity,
|
||||
double chordTolerance, Vector location, PushDirection facingDirection)
|
||||
private static void AddOffsetDirectionalLines(
|
||||
List<Line> lines,
|
||||
Shape offsetEntity,
|
||||
double chordTolerance,
|
||||
Vector location,
|
||||
PushDirection facingDirection
|
||||
)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
@@ -301,8 +387,13 @@ namespace OpenNest
|
||||
lines.AddRange(GetDirectionalLines(polygon, facingDirection));
|
||||
}
|
||||
|
||||
private static void AddOffsetDirectionalLines(List<Line> lines, Shape offsetEntity,
|
||||
double chordTolerance, Vector location, Vector facingDirection)
|
||||
private static void AddOffsetDirectionalLines(
|
||||
List<Line> lines,
|
||||
Shape offsetEntity,
|
||||
double chordTolerance,
|
||||
Vector location,
|
||||
Vector facingDirection
|
||||
)
|
||||
{
|
||||
if (offsetEntity == null)
|
||||
return;
|
||||
|
||||
+32
-33
@@ -1,10 +1,10 @@
|
||||
using OpenNest.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenNest.Collections;
|
||||
using OpenNest.Geometry;
|
||||
using OpenNest.Math;
|
||||
using OpenNest.Shapes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -31,14 +31,10 @@ namespace OpenNest
|
||||
}
|
||||
|
||||
public Plate()
|
||||
: this(60, 120)
|
||||
{
|
||||
}
|
||||
: this(60, 120) { }
|
||||
|
||||
public Plate(double width, double length)
|
||||
: this(new Size(width, length))
|
||||
{
|
||||
}
|
||||
: this(new Size(width, length)) { }
|
||||
|
||||
public Plate(Size size)
|
||||
{
|
||||
@@ -140,7 +136,8 @@ namespace OpenNest
|
||||
Geometry.Entity perimeter = null;
|
||||
try
|
||||
{
|
||||
var entities = Converters.ConvertProgram.ToGeometry(part.Program)
|
||||
var entities = Converters
|
||||
.ConvertProgram.ToGeometry(part.Program)
|
||||
.Where(e => e.Layer != SpecialLayers.Rapid)
|
||||
.ToList();
|
||||
|
||||
@@ -413,7 +410,10 @@ namespace OpenNest
|
||||
}
|
||||
|
||||
foreach (var cutoff in CutOffs)
|
||||
cutoff.Position = new Vector(cutoff.Position.X + voffset.X, cutoff.Position.Y + voffset.Y);
|
||||
cutoff.Position = new Vector(
|
||||
cutoff.Position.X + voffset.X,
|
||||
cutoff.Position.Y + voffset.Y
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -461,21 +461,19 @@ namespace OpenNest
|
||||
var boundingBox = new Box();
|
||||
var partsBox = Parts.GetBoundingBox();
|
||||
|
||||
boundingBox.X = partsBox.Left < plateBox.Left
|
||||
? partsBox.Left
|
||||
: plateBox.Left;
|
||||
boundingBox.X = partsBox.Left < plateBox.Left ? partsBox.Left : plateBox.Left;
|
||||
|
||||
boundingBox.Y = partsBox.Bottom < plateBox.Bottom
|
||||
? partsBox.Bottom
|
||||
: plateBox.Bottom;
|
||||
boundingBox.Y = partsBox.Bottom < plateBox.Bottom ? partsBox.Bottom : plateBox.Bottom;
|
||||
|
||||
boundingBox.Length = partsBox.Right > plateBox.Right
|
||||
? partsBox.Right - boundingBox.X
|
||||
: plateBox.Right - boundingBox.X;
|
||||
boundingBox.Length =
|
||||
partsBox.Right > plateBox.Right
|
||||
? partsBox.Right - boundingBox.X
|
||||
: plateBox.Right - boundingBox.X;
|
||||
|
||||
boundingBox.Width = partsBox.Top > plateBox.Top
|
||||
? partsBox.Top - boundingBox.Y
|
||||
: plateBox.Top - boundingBox.Y;
|
||||
boundingBox.Width =
|
||||
partsBox.Top > plateBox.Top
|
||||
? partsBox.Top - boundingBox.Y
|
||||
: plateBox.Top - boundingBox.Y;
|
||||
|
||||
return boundingBox;
|
||||
}
|
||||
@@ -546,7 +544,8 @@ namespace OpenNest
|
||||
|
||||
Size = new Size(
|
||||
Rounding.RoundUpToNearest(yExtent, roundingFactor),
|
||||
Rounding.RoundUpToNearest(xExtent, roundingFactor));
|
||||
Rounding.RoundUpToNearest(xExtent, roundingFactor)
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -601,9 +600,9 @@ namespace OpenNest
|
||||
|
||||
// Plate convention: Length = X axis, Width = Y axis.
|
||||
if (xExtent >= yExtent)
|
||||
Size = new Size(result.Width, result.Length); // X is the long axis
|
||||
Size = new Size(result.Width, result.Length); // X is the long axis
|
||||
else
|
||||
Size = new Size(result.Length, result.Width); // Y is the long axis
|
||||
Size = new Size(result.Length, result.Width); // Y is the long axis
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -639,7 +638,8 @@ namespace OpenNest
|
||||
/// <returns>Returns a number between 0.0 and 1.0</returns>
|
||||
public double Utilization()
|
||||
{
|
||||
return Parts.Where(p => !p.BaseDrawing.IsCutOff).Sum(part => part.BaseDrawing.Area) / Area();
|
||||
return Parts.Where(p => !p.BaseDrawing.IsCutOff).Sum(part => part.BaseDrawing.Area)
|
||||
/ Area();
|
||||
}
|
||||
|
||||
public bool HasOverlappingParts(out List<Vector> pts)
|
||||
@@ -661,10 +661,10 @@ namespace OpenNest
|
||||
// Floating-point rounding can produce sub-epsilon overlaps for
|
||||
// parts that are merely edge-touching, so require the overlap
|
||||
// region to exceed Epsilon in both dimensions.
|
||||
var overlapX = System.Math.Min(b1.Right, b2.Right)
|
||||
- System.Math.Max(b1.Left, b2.Left);
|
||||
var overlapY = System.Math.Min(b1.Top, b2.Top)
|
||||
- System.Math.Max(b1.Bottom, b2.Bottom);
|
||||
var overlapX =
|
||||
System.Math.Min(b1.Right, b2.Right) - System.Math.Max(b1.Left, b2.Left);
|
||||
var overlapY =
|
||||
System.Math.Min(b1.Top, b2.Top) - System.Math.Max(b1.Bottom, b2.Bottom);
|
||||
|
||||
if (overlapX <= Math.Tolerance.Epsilon || overlapY <= Math.Tolerance.Epsilon)
|
||||
continue;
|
||||
@@ -676,6 +676,5 @@ namespace OpenNest
|
||||
|
||||
return pts.Count > 0;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Collections;
|
||||
using System;
|
||||
using OpenNest.Collections;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -44,7 +44,8 @@ namespace OpenNest
|
||||
|
||||
public bool IsLast => CurrentIndex + 1 >= Count;
|
||||
|
||||
public bool CanRemoveCurrent => Count > 1 && CurrentPlate != null && CurrentPlate.Parts.Count > 0;
|
||||
public bool CanRemoveCurrent =>
|
||||
Count > 1 && CurrentPlate != null && CurrentPlate.Parts.Count > 0;
|
||||
|
||||
public void LoadFirst()
|
||||
{
|
||||
@@ -101,9 +102,11 @@ namespace OpenNest
|
||||
if (Count == 0 || nest.Plates[^1].Parts.Count > 0)
|
||||
nest.CreatePlate();
|
||||
|
||||
while (Count > 1
|
||||
while (
|
||||
Count > 1
|
||||
&& nest.Plates[^1].Parts.Count == 0
|
||||
&& nest.Plates[^2].Parts.Count == 0)
|
||||
&& nest.Plates[^2].Parts.Count == 0
|
||||
)
|
||||
{
|
||||
nest.Plates.RemoveAt(Count - 1);
|
||||
}
|
||||
@@ -226,7 +229,10 @@ namespace OpenNest
|
||||
|
||||
private void FireCurrentPlateChanged()
|
||||
{
|
||||
CurrentPlateChanged?.Invoke(this, new PlateChangedEventArgs(CurrentPlate, CurrentIndex));
|
||||
CurrentPlateChanged?.Invoke(
|
||||
this,
|
||||
new PlateChangedEventArgs(CurrentPlate, CurrentIndex)
|
||||
);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum PushDirection
|
||||
@@ -6,6 +5,6 @@ namespace OpenNest
|
||||
Up,
|
||||
Down,
|
||||
Left,
|
||||
Right
|
||||
Right,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum RelativePosition
|
||||
{
|
||||
@@ -8,6 +7,6 @@ namespace OpenNest
|
||||
Right,
|
||||
Top,
|
||||
Bottom,
|
||||
None
|
||||
None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
|
||||
namespace OpenNest
|
||||
namespace OpenNest
|
||||
{
|
||||
public enum RotationType
|
||||
{
|
||||
@@ -11,6 +10,6 @@ namespace OpenNest
|
||||
/// <summary>
|
||||
/// Counter-Clockwise
|
||||
/// </summary>
|
||||
CCW
|
||||
CCW,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest
|
||||
{
|
||||
@@ -28,7 +28,14 @@ namespace OpenNest
|
||||
seqList.Add(lastPart);
|
||||
dupList.Remove(lastPart);
|
||||
|
||||
for (int i = 0; i < parts.Count - 1 /*STOP BEFORE LAST PART*/; i++)
|
||||
for (
|
||||
int i = 0;
|
||||
i
|
||||
< parts.Count
|
||||
- 1 /*STOP BEFORE LAST PART*/
|
||||
;
|
||||
i++
|
||||
)
|
||||
{
|
||||
var nextPart = GetClosestPart(lastPart.Location, dupList);
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -16,10 +16,7 @@ namespace OpenNest.Shapes
|
||||
|
||||
public override Drawing GetDrawing()
|
||||
{
|
||||
var entities = new List<Entity>
|
||||
{
|
||||
new Circle(0, 0, Diameter / 2.0)
|
||||
};
|
||||
var entities = new List<Entity> { new Circle(0, 0, Diameter / 2.0) };
|
||||
|
||||
return CreateDrawing(entities);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -24,7 +24,7 @@ namespace OpenNest.Shapes
|
||||
{
|
||||
new Line(0, 0, Base, 0),
|
||||
new Line(Base, 0, midX, Height),
|
||||
new Line(midX, Height, 0, 0)
|
||||
new Line(midX, Height, 0, 0),
|
||||
};
|
||||
|
||||
return CreateDrawing(entities);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -32,7 +32,7 @@ namespace OpenNest.Shapes
|
||||
new Line(Width, lh, lw, lh),
|
||||
new Line(lw, lh, lw, Height),
|
||||
new Line(lw, Height, 0, Height),
|
||||
new Line(0, Height, 0, 0)
|
||||
new Line(0, Height, 0, 0),
|
||||
};
|
||||
|
||||
return CreateDrawing(entities);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -30,7 +30,8 @@ namespace OpenNest.Shapes
|
||||
var angle = start + i * step;
|
||||
vertices[i] = new Vector(
|
||||
center + circumRadius * System.Math.Cos(angle),
|
||||
center + circumRadius * System.Math.Sin(angle));
|
||||
center + circumRadius * System.Math.Sin(angle)
|
||||
);
|
||||
}
|
||||
|
||||
var entities = new List<Entity>();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -50,7 +50,11 @@ namespace OpenNest.Shapes
|
||||
entities.Add(new Circle(cx, cy, holeRadius));
|
||||
}
|
||||
|
||||
if (!Blind && !string.IsNullOrEmpty(PipeSize) && PipeSizes.TryGetOD(PipeSize, out var pipeOD))
|
||||
if (
|
||||
!Blind
|
||||
&& !string.IsNullOrEmpty(PipeSize)
|
||||
&& PipeSizes.TryGetOD(PipeSize, out var pipeOD)
|
||||
)
|
||||
{
|
||||
var boreDiameter = pipeOD + PipeClearance;
|
||||
entities.Add(new Circle(0, 0, boreDiameter / 2.0));
|
||||
|
||||
@@ -6,44 +6,45 @@ namespace OpenNest.Shapes
|
||||
{
|
||||
public readonly record struct Entry(string Label, double OuterDiameter);
|
||||
|
||||
public static IReadOnlyList<Entry> All { get; } = new[]
|
||||
{
|
||||
new Entry("1/8", 0.405),
|
||||
new Entry("1/4", 0.540),
|
||||
new Entry("3/8", 0.675),
|
||||
new Entry("1/2", 0.840),
|
||||
new Entry("3/4", 1.050),
|
||||
new Entry("1", 1.315),
|
||||
new Entry("1 1/4", 1.660),
|
||||
new Entry("1 1/2", 1.900),
|
||||
new Entry("2", 2.375),
|
||||
new Entry("2 1/2", 2.875),
|
||||
new Entry("3", 3.500),
|
||||
new Entry("3 1/2", 4.000),
|
||||
new Entry("4", 4.500),
|
||||
new Entry("4 1/2", 5.000),
|
||||
new Entry("5", 5.563),
|
||||
new Entry("6", 6.625),
|
||||
new Entry("7", 7.625),
|
||||
new Entry("8", 8.625),
|
||||
new Entry("9", 9.625),
|
||||
new Entry("10", 10.750),
|
||||
new Entry("11", 11.750),
|
||||
new Entry("12", 12.750),
|
||||
new Entry("14", 14.000),
|
||||
new Entry("16", 16.000),
|
||||
new Entry("18", 18.000),
|
||||
new Entry("20", 20.000),
|
||||
new Entry("24", 24.000),
|
||||
new Entry("26", 26.000),
|
||||
new Entry("28", 28.000),
|
||||
new Entry("30", 30.000),
|
||||
new Entry("32", 32.000),
|
||||
new Entry("34", 34.000),
|
||||
new Entry("36", 36.000),
|
||||
new Entry("42", 42.000),
|
||||
new Entry("48", 48.000),
|
||||
};
|
||||
public static IReadOnlyList<Entry> All { get; } =
|
||||
new[]
|
||||
{
|
||||
new Entry("1/8", 0.405),
|
||||
new Entry("1/4", 0.540),
|
||||
new Entry("3/8", 0.675),
|
||||
new Entry("1/2", 0.840),
|
||||
new Entry("3/4", 1.050),
|
||||
new Entry("1", 1.315),
|
||||
new Entry("1 1/4", 1.660),
|
||||
new Entry("1 1/2", 1.900),
|
||||
new Entry("2", 2.375),
|
||||
new Entry("2 1/2", 2.875),
|
||||
new Entry("3", 3.500),
|
||||
new Entry("3 1/2", 4.000),
|
||||
new Entry("4", 4.500),
|
||||
new Entry("4 1/2", 5.000),
|
||||
new Entry("5", 5.563),
|
||||
new Entry("6", 6.625),
|
||||
new Entry("7", 7.625),
|
||||
new Entry("8", 8.625),
|
||||
new Entry("9", 9.625),
|
||||
new Entry("10", 10.750),
|
||||
new Entry("11", 11.750),
|
||||
new Entry("12", 12.750),
|
||||
new Entry("14", 14.000),
|
||||
new Entry("16", 16.000),
|
||||
new Entry("18", 18.000),
|
||||
new Entry("20", 20.000),
|
||||
new Entry("24", 24.000),
|
||||
new Entry("26", 26.000),
|
||||
new Entry("28", 28.000),
|
||||
new Entry("30", 30.000),
|
||||
new Entry("32", 32.000),
|
||||
new Entry("34", 34.000),
|
||||
new Entry("36", 36.000),
|
||||
new Entry("42", 42.000),
|
||||
new Entry("48", 48.000),
|
||||
};
|
||||
|
||||
public static bool TryGetOD(string label, out double outerDiameter)
|
||||
{
|
||||
|
||||
@@ -29,17 +29,18 @@ namespace OpenNest.Shapes
|
||||
/// Standard mill sheet sizes (inches), sorted by area ascending.
|
||||
/// Canonical orientation: Width <= Length.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<Entry> All { get; } = new[]
|
||||
{
|
||||
new Entry("48x96", 48, 96), // 4608
|
||||
new Entry("48x120", 48, 120), // 5760
|
||||
new Entry("48x144", 48, 144), // 6912
|
||||
new Entry("60x120", 60, 120), // 7200
|
||||
new Entry("60x144", 60, 144), // 8640
|
||||
new Entry("72x120", 72, 120), // 8640
|
||||
new Entry("72x144", 72, 144), // 10368
|
||||
new Entry("96x240", 96, 240), // 23040
|
||||
};
|
||||
public static IReadOnlyList<Entry> All { get; } =
|
||||
new[]
|
||||
{
|
||||
new Entry("48x96", 48, 96), // 4608
|
||||
new Entry("48x120", 48, 120), // 5760
|
||||
new Entry("48x144", 48, 144), // 6912
|
||||
new Entry("60x120", 60, 120), // 7200
|
||||
new Entry("60x144", 60, 144), // 8640
|
||||
new Entry("72x120", 72, 120), // 8640
|
||||
new Entry("72x144", 72, 144), // 10368
|
||||
new Entry("96x240", 96, 240), // 23040
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a standard size by label. Case-insensitive.
|
||||
@@ -77,7 +78,10 @@ namespace OpenNest.Shapes
|
||||
/// <summary>
|
||||
/// Recommends a plate size for the envelope of the given boxes.
|
||||
/// </summary>
|
||||
public static PlateSizeResult Recommend(IEnumerable<Box> boxes, PlateSizeOptions options = null)
|
||||
public static PlateSizeResult Recommend(
|
||||
IEnumerable<Box> boxes,
|
||||
PlateSizeOptions options = null
|
||||
)
|
||||
{
|
||||
if (boxes == null)
|
||||
throw new ArgumentNullException(nameof(boxes));
|
||||
@@ -91,10 +95,14 @@ namespace OpenNest.Shapes
|
||||
foreach (var box in boxes)
|
||||
{
|
||||
hasAny = true;
|
||||
if (box.Left < minX) minX = box.Left;
|
||||
if (box.Bottom < minY) minY = box.Bottom;
|
||||
if (box.Right > maxX) maxX = box.Right;
|
||||
if (box.Top > maxY) maxY = box.Top;
|
||||
if (box.Left < minX)
|
||||
minX = box.Left;
|
||||
if (box.Bottom < minY)
|
||||
minY = box.Bottom;
|
||||
if (box.Right > maxX)
|
||||
maxX = box.Right;
|
||||
if (box.Top > maxY)
|
||||
maxY = box.Top;
|
||||
}
|
||||
|
||||
if (!hasAny)
|
||||
@@ -109,7 +117,11 @@ namespace OpenNest.Shapes
|
||||
/// Recommends a plate size for a (width, length) pair.
|
||||
/// Inputs are treated as orientation-independent.
|
||||
/// </summary>
|
||||
public static PlateSizeResult Recommend(double width, double length, PlateSizeOptions options = null)
|
||||
public static PlateSizeResult Recommend(
|
||||
double width,
|
||||
double length,
|
||||
PlateSizeOptions options = null
|
||||
)
|
||||
{
|
||||
options ??= new PlateSizeOptions();
|
||||
|
||||
@@ -174,10 +186,23 @@ namespace OpenNest.Shapes
|
||||
if (!string.IsNullOrWhiteSpace(label))
|
||||
{
|
||||
var parts = label.Split(new[] { 'x', 'X' }, 2);
|
||||
if (parts.Length == 2
|
||||
&& double.TryParse(parts[0].Trim(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var a)
|
||||
&& double.TryParse(parts[1].Trim(), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var b)
|
||||
&& a > 0 && b > 0)
|
||||
if (
|
||||
parts.Length == 2
|
||||
&& double.TryParse(
|
||||
parts[0].Trim(),
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var a
|
||||
)
|
||||
&& double.TryParse(
|
||||
parts[1].Trim(),
|
||||
System.Globalization.NumberStyles.Float,
|
||||
System.Globalization.CultureInfo.InvariantCulture,
|
||||
out var b
|
||||
)
|
||||
&& a > 0
|
||||
&& b > 0
|
||||
)
|
||||
{
|
||||
var width = System.Math.Min(a, b);
|
||||
var length = System.Math.Max(a, b);
|
||||
@@ -190,13 +215,20 @@ namespace OpenNest.Shapes
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Entry? PickBest(IReadOnlyList<Entry> catalog, double width, double length, PlateSizeSelection selection)
|
||||
private static Entry? PickBest(
|
||||
IReadOnlyList<Entry> catalog,
|
||||
double width,
|
||||
double length,
|
||||
PlateSizeSelection selection
|
||||
)
|
||||
{
|
||||
var fitting = catalog.Where(e => e.Fits(width, length));
|
||||
|
||||
fitting = selection switch
|
||||
{
|
||||
PlateSizeSelection.NarrowestFirst => fitting.OrderBy(e => e.Width).ThenBy(e => e.Area),
|
||||
PlateSizeSelection.NarrowestFirst => fitting
|
||||
.OrderBy(e => e.Width)
|
||||
.ThenBy(e => e.Area),
|
||||
_ => fitting.OrderBy(e => e.Area).ThenBy(e => e.Width),
|
||||
};
|
||||
|
||||
@@ -249,6 +281,7 @@ namespace OpenNest.Shapes
|
||||
{
|
||||
/// <summary>Pick the cheapest sheet that contains the bbox (smallest area).</summary>
|
||||
SmallestArea,
|
||||
|
||||
/// <summary>Prefer narrower-width sheets (e.g. 48-wide before 60-wide).</summary>
|
||||
NarrowestFirst,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -23,7 +23,7 @@ namespace OpenNest.Shapes
|
||||
new Line(0, 0, Length, 0),
|
||||
new Line(Length, 0, Length, Width),
|
||||
new Line(Length, Width, 0, Width),
|
||||
new Line(0, Width, 0, 0)
|
||||
new Line(0, Width, 0, 0),
|
||||
};
|
||||
|
||||
return CreateDrawing(entities);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -22,7 +22,7 @@ namespace OpenNest.Shapes
|
||||
{
|
||||
new Line(0, 0, Width, 0),
|
||||
new Line(Width, 0, 0, Height),
|
||||
new Line(0, Height, 0, 0)
|
||||
new Line(0, Height, 0, 0),
|
||||
};
|
||||
|
||||
return CreateDrawing(entities);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using OpenNest.Geometry;
|
||||
using System.Collections.Generic;
|
||||
using OpenNest.Geometry;
|
||||
|
||||
namespace OpenNest.Shapes
|
||||
{
|
||||
@@ -21,7 +21,7 @@ namespace OpenNest.Shapes
|
||||
var entities = new List<Entity>
|
||||
{
|
||||
new Circle(0, 0, OuterDiameter / 2.0),
|
||||
new Circle(0, 0, InnerDiameter / 2.0)
|
||||
new Circle(0, 0, InnerDiameter / 2.0),
|
||||
};
|
||||
|
||||
return CreateDrawing(entities);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user