ShapeProfile.NormalizeEntities called Shape.Reverse() which flipped arc directions on the original entity objects shared with the CAD view. Switching to the Program tab and back would leave arcs reversed. Clone entities before normalizing so the originals stay untouched. Adds abstract Entity.Clone() with implementations on Line, Arc, Circle, Polygon, and Shape (deep-clones children). Also adds CloneAll() extension and replaces manual duplication in PartGeometry.CopyEntitiesAtLocation and ProgramEditorControl.CloneEntity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
442 lines
13 KiB
C#
442 lines
13 KiB
C#
using OpenNest.Math;
|
|
using System.Collections.Generic;
|
|
|
|
namespace OpenNest.Geometry
|
|
{
|
|
public class Circle : Entity
|
|
{
|
|
private Vector center;
|
|
private double radius;
|
|
|
|
public Circle()
|
|
{
|
|
}
|
|
|
|
public Circle(double x, double y, double radius)
|
|
: this(new Vector(x, y), radius)
|
|
{
|
|
}
|
|
|
|
public Circle(Vector center, double radius)
|
|
{
|
|
this.center = center;
|
|
this.radius = radius;
|
|
this.Rotation = RotationType.CCW;
|
|
UpdateBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a circle from two points.
|
|
/// </summary>
|
|
/// <param name="pt1"></param>
|
|
/// <param name="pt2"></param>
|
|
/// <returns></returns>
|
|
public static Circle CreateFrom2Points(Vector pt1, Vector pt2)
|
|
{
|
|
var line = new Line(pt1, pt2);
|
|
return new Circle(line.MidPoint, line.Length * 0.5);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Center point of the circle.
|
|
/// </summary>
|
|
public Vector Center
|
|
{
|
|
get { return center; }
|
|
set
|
|
{
|
|
var offset = value - center;
|
|
boundingBox.Offset(offset);
|
|
center = value;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Radius of the circle.
|
|
/// </summary>
|
|
public double Radius
|
|
{
|
|
get { return radius; }
|
|
set
|
|
{
|
|
radius = value;
|
|
UpdateBounds();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Radius * 2. Value NOT stored.
|
|
/// </summary>
|
|
public double Diameter
|
|
{
|
|
get { return Radius * 2.0; }
|
|
set { Radius = value / 2.0; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotation direction.
|
|
/// </summary>
|
|
public RotationType Rotation { get; set; }
|
|
|
|
/// <summary>
|
|
/// Area of the circle.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public double Area()
|
|
{
|
|
return System.Math.PI * Radius * Radius;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Linear distance around the circle.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public double Circumference()
|
|
{
|
|
return System.Math.PI * Diameter;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given circle has the same radius as this.
|
|
/// </summary>
|
|
/// <param name="circle"></param>
|
|
/// <returns></returns>
|
|
public bool IsConcentricTo(Circle circle)
|
|
{
|
|
return center == circle.center;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given circle has the same radius as this.
|
|
/// </summary>
|
|
/// <param name="arc"></param>
|
|
/// <returns></returns>
|
|
public bool IsConcentricTo(Arc arc)
|
|
{
|
|
return center == arc.Center;
|
|
}
|
|
|
|
public bool ContainsPoint(Vector pt)
|
|
{
|
|
return Center.DistanceTo(pt) <= Radius;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the minimum number of segments needed so that the chord-to-arc
|
|
/// deviation (sagitta) does not exceed the given tolerance.
|
|
/// </summary>
|
|
public int SegmentsForTolerance(double tolerance)
|
|
{
|
|
if (tolerance >= Radius)
|
|
return 3;
|
|
|
|
var maxAngle = 2.0 * System.Math.Acos(1.0 - tolerance / Radius);
|
|
return System.Math.Max(3, (int)System.Math.Ceiling(Angle.TwoPI / maxAngle));
|
|
}
|
|
|
|
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 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));
|
|
}
|
|
|
|
return points;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Linear distance around the circle.
|
|
/// </summary>
|
|
public override double Length
|
|
{
|
|
get { return Circumference(); }
|
|
}
|
|
|
|
public override Entity Clone()
|
|
{
|
|
var copy = new Circle(center, radius) { Rotation = Rotation };
|
|
CopyBaseTo(copy);
|
|
return copy;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reverses the rotation direction.
|
|
/// </summary>
|
|
public override void Reverse()
|
|
{
|
|
if (Rotation == RotationType.CCW)
|
|
Rotation = RotationType.CW;
|
|
else
|
|
Rotation = RotationType.CCW;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves the center point to the given coordinates.
|
|
/// </summary>
|
|
/// <param name="x"></param>
|
|
/// <param name="y"></param>
|
|
public override void MoveTo(double x, double y)
|
|
{
|
|
Center = new Vector(x, y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Moves the center point to the given point.
|
|
/// </summary>
|
|
/// <param name="pt"></param>
|
|
public override void MoveTo(Vector pt)
|
|
{
|
|
Center = pt;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Offsets the center point by the given distances.
|
|
/// </summary>
|
|
/// <param name="x"></param>
|
|
/// <param name="y"></param>
|
|
public override void Offset(double x, double y)
|
|
{
|
|
Center = new Vector(Center.X + x, Center.Y + y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Offsets the center point by the given distances.
|
|
/// </summary>
|
|
/// <param name="voffset"></param>
|
|
public override void Offset(Vector voffset)
|
|
{
|
|
Center += voffset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scales the circle from the zero point.
|
|
/// </summary>
|
|
/// <param name="factor"></param>
|
|
public override void Scale(double factor)
|
|
{
|
|
center *= factor;
|
|
radius *= factor;
|
|
UpdateBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Scales the circle from the origin.
|
|
/// </summary>
|
|
/// <param name="factor"></param>
|
|
/// <param name="origin"></param>
|
|
public override void Scale(double factor, Vector origin)
|
|
{
|
|
center = center.Scale(factor, origin);
|
|
radius *= factor;
|
|
UpdateBounds();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rotates the circle from the zero point.
|
|
/// </summary>
|
|
/// <param name="angle"></param>
|
|
public override void Rotate(double angle)
|
|
{
|
|
Center = Center.Rotate(angle);
|
|
}
|
|
|
|
/// <summary>
|
|
/// /// Rotates the circle from the origin.
|
|
/// </summary>
|
|
/// <param name="angle"></param>
|
|
/// <param name="origin"></param>
|
|
public override void Rotate(double angle, Vector origin)
|
|
{
|
|
Center = Center.Rotate(angle, origin);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the bounding box.
|
|
/// </summary>
|
|
public override void UpdateBounds()
|
|
{
|
|
boundingBox.X = Center.X - Radius;
|
|
boundingBox.Y = Center.Y - Radius;
|
|
boundingBox.Width = Diameter;
|
|
boundingBox.Length = Diameter;
|
|
}
|
|
|
|
public override Entity OffsetEntity(double distance, OffsetSide side)
|
|
{
|
|
if (side == OffsetSide.Left && Rotation == RotationType.CCW)
|
|
{
|
|
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
|
{
|
|
Layer = Layer,
|
|
Rotation = Rotation
|
|
};
|
|
}
|
|
else
|
|
{
|
|
return new Circle(center, Radius + distance) { Layer = Layer };
|
|
}
|
|
}
|
|
|
|
public override Entity OffsetEntity(double distance, Vector pt)
|
|
{
|
|
if (ContainsPoint(pt))
|
|
{
|
|
return Radius <= distance ? null : new Circle(center, Radius - distance)
|
|
{
|
|
Layer = Layer,
|
|
Rotation = Rotation
|
|
};
|
|
}
|
|
else
|
|
{
|
|
return new Circle(center, Radius + distance) { Layer = Layer };
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the closest point on the circle to the specified point.
|
|
/// </summary>
|
|
/// <param name="pt"></param>
|
|
/// <returns></returns>
|
|
public override Vector ClosestPointTo(Vector pt)
|
|
{
|
|
var angle = Center.AngleTo(pt);
|
|
|
|
return new Vector(
|
|
System.Math.Cos(angle) * Radius + Center.X,
|
|
System.Math.Sin(angle) * Radius + Center.Y);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given arc is intersecting this.
|
|
/// </summary>
|
|
/// <param name="arc"></param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Arc arc)
|
|
{
|
|
List<Vector> pts;
|
|
return Intersect.Intersects(arc, this, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given arc is intersecting this.
|
|
/// </summary>
|
|
/// <param name="arc"></param>
|
|
/// <param name="pts">Points of intersection.</param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Arc arc, out List<Vector> pts)
|
|
{
|
|
return Intersect.Intersects(arc, this, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given circle is intersecting this.
|
|
/// </summary>
|
|
/// <param name="circle"></param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Circle circle)
|
|
{
|
|
var dist = Center.DistanceTo(circle.Center);
|
|
return (dist < (Radius + circle.Radius) && dist > System.Math.Abs(Radius - circle.Radius));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given circle is intersecting this.
|
|
/// </summary>
|
|
/// <param name="circle"></param>
|
|
/// <param name="pts">Points of intersection.</param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Circle circle, out List<Vector> pts)
|
|
{
|
|
return Intersect.Intersects(this, circle, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given line is intersecting this.
|
|
/// </summary>
|
|
/// <param name="line"></param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Line line)
|
|
{
|
|
List<Vector> pts;
|
|
return Intersect.Intersects(this, line, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given line is intersecting this.
|
|
/// </summary>
|
|
/// <param name="line"></param>
|
|
/// <param name="pts">Points of intersection.</param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Line line, out List<Vector> pts)
|
|
{
|
|
return Intersect.Intersects(this, line, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given polygon is intersecting this.
|
|
/// </summary>
|
|
/// <param name="polygon"></param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Polygon polygon)
|
|
{
|
|
List<Vector> pts;
|
|
return Intersect.Intersects(this, polygon, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given polygon is intersecting this.
|
|
/// </summary>
|
|
/// <param name="polygon"></param>
|
|
/// <param name="pts">Points of intersection.</param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Polygon polygon, out List<Vector> pts)
|
|
{
|
|
return Intersect.Intersects(this, polygon, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given shape is intersecting this.
|
|
/// </summary>
|
|
/// <param name="shape"></param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Shape shape)
|
|
{
|
|
List<Vector> pts;
|
|
return Intersect.Intersects(this, shape, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the given shape is intersecting this.
|
|
/// </summary>
|
|
/// <param name="shape"></param>
|
|
/// <param name="pts">Points of intersection.</param>
|
|
/// <returns></returns>
|
|
public override bool Intersects(Shape shape, out List<Vector> pts)
|
|
{
|
|
return Intersect.Intersects(this, shape, out pts);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Type of entity.
|
|
/// </summary>
|
|
public override EntityType Type
|
|
{
|
|
get { return EntityType.Circle; }
|
|
}
|
|
}
|
|
}
|