using System.Collections.Generic;
using System.Linq;
using OpenNest.Converters;
using OpenNest.Geometry;
namespace OpenNest
{
public static class PartGeometry
{
///
/// Nesting material of a program, without lead-ins and lead-outs: a lead is a cut
/// into scrap, not part material, and chaining it into the perimeter leaves an open
/// spur, so the offset contour no longer closes and every slide contact against it
/// blocks.
///
private static List MaterialEntities(CNC.Program program) =>
ConvertProgram.ToGeometry(program)
.Where(e => e.Layer != SpecialLayers.Leadin && e.Layer != SpecialLayers.Leadout)
.ForNesting();
public static List GetPartLines(Part part, double chordTolerance = 0.001)
{
var entities = MaterialEntities(part.Program);
var shapes = ShapeBuilder.GetShapes(entities);
var lines = new List();
foreach (var shape in shapes)
{
var polygon = shape.ToPolygonWithTolerance(chordTolerance);
polygon.Offset(part.Location);
lines.AddRange(polygon.ToLines());
}
return lines;
}
public static List GetPartLines(
Part part,
PushDirection facingDirection,
double chordTolerance = 0.001
)
{
return GetDirectionalPartLines(
part, chordTolerance, useVector: false, facingDirection, default
);
}
///
/// Returns the perimeter entities (Line, Arc, Circle) with spacing offset applied,
/// without tessellation, which keeps arc-heavy parts fast in directional-distance loops.
///
public static List GetOffsetPerimeterEntities(Part part, double spacing)
{
var entities = GetOffsetPerimeterEntities(part.Program, spacing);
foreach (var entity in entities)
entity.Offset(part.Location);
return entities;
}
///
/// Prepares a fresh offset perimeter in the program's local frame, without translation.
///
public static List GetOffsetPerimeterEntities(CNC.Program program, double spacing)
{
PerfCounters.CountOffsetPerimeterEntities();
var geoEntities = MaterialEntities(program);
var profile = new ShapeProfile(geoEntities);
var offsetShape = profile.Perimeter.OffsetOutward(spacing);
if (offsetShape == null)
return new List();
return offsetShape.Entities;
}
///
/// Returns all entities (perimeter + cutouts) with spacing offset applied,
/// without tessellation. Perimeter is offset outward, cutouts inward.
///
public static List GetOffsetPartEntities(Part part, double spacing)
{
var geoEntities = MaterialEntities(part.Program);
var profile = new ShapeProfile(geoEntities);
var entities = new List();
var perimeter = profile.Perimeter.OffsetOutward(spacing);
if (perimeter != null)
{
foreach (var entity in perimeter.Entities)
entity.Offset(part.Location);
entities.AddRange(perimeter.Entities);
}
foreach (var cutout in profile.Cutouts)
{
var inset = cutout.OffsetInward(spacing);
if (inset == null)
continue;
foreach (var entity in inset.Entities)
entity.Offset(part.Location);
entities.AddRange(inset.Entities);
}
return entities;
}
///
/// Returns perimeter entities at the part's world location, without tessellation
/// or spacing offset.
///
public static List GetPerimeterEntities(Part part)
{
var geoEntities = MaterialEntities(part.Program);
var profile = new ShapeProfile(geoEntities);
return CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location);
}
///
/// Returns all entities (perimeter + cutouts) at the part's world location,
/// without tessellation or spacing offset.
///
public static List GetPartEntities(Part part)
{
var geoEntities = MaterialEntities(part.Program);
var profile = new ShapeProfile(geoEntities);
var entities = CopyEntitiesAtLocation(profile.Perimeter.Entities, part.Location);
foreach (var cutout in profile.Cutouts)
entities.AddRange(CopyEntitiesAtLocation(cutout.Entities, part.Location));
return entities;
}
private static List CopyEntitiesAtLocation(List source, Vector location)
{
var result = new List(source.Count);
foreach (var entity in source)
{
var copy = entity.Clone();
copy.Offset(location);
result.Add(copy);
}
return result;
}
public static List GetPartLines(
Part part,
Vector facingDirection,
double chordTolerance = 0.001
)
{
return GetDirectionalPartLines(
part, chordTolerance, useVector: true, default, facingDirection
);
}
private static List GetDirectionalPartLines(
Part part,
double chordTolerance,
bool useVector,
PushDirection cardinalDirection,
Vector vectorDirection
)
{
var entities = MaterialEntities(part.Program);
var shapes = ShapeBuilder.GetShapes(entities);
var lines = new List();
foreach (var shape in shapes)
{
var polygon = shape.ToPolygonWithTolerance(chordTolerance);
polygon.Offset(part.Location);
// Keep the overload-specific arithmetic and invalid-direction behavior.
lines.AddRange(useVector
? GetDirectionalLines(polygon, vectorDirection)
: GetDirectionalLines(polygon, cardinalDirection));
}
return lines;
}
///
/// Returns only polygon edges whose outward normal faces the specified direction vector.
///
private static List GetDirectionalLines(Polygon polygon, Vector direction)
{
if (polygon.Vertices.Count < 3)
return polygon.ToLines();
var sign = polygon.RotationDirection() == RotationType.CCW ? 1.0 : -1.0;
var lines = new List();
var last = polygon.Vertices[0];
for (var i = 1; i < polygon.Vertices.Count; i++)
{
var current = polygon.Vertices[i];
var edx = current.X - last.X;
var edy = current.Y - last.Y;
var keep = sign * (edy * direction.X - edx * direction.Y) > 0;
if (keep)
lines.Add(new Line(last, current));
last = current;
}
return lines;
}
///
/// Returns only polygon edges whose outward normal faces the specified direction.
///
private static List GetDirectionalLines(
Polygon polygon,
PushDirection facingDirection
)
{
if (polygon.Vertices.Count < 3)
return polygon.ToLines();
var sign = polygon.RotationDirection() == RotationType.CCW ? 1.0 : -1.0;
var lines = new List();
var last = polygon.Vertices[0];
for (int i = 1; i < polygon.Vertices.Count; i++)
{
var current = polygon.Vertices[i];
var dx = current.X - last.X;
var dy = current.Y - last.Y;
bool keep;
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;
}
if (keep)
lines.Add(new Line(last, current));
last = current;
}
return lines;
}
}
}