Skip to content

Commit 32ec1c0

Browse files
Add ellipse and elliptical arc support to adapters.
1 parent 1aee139 commit 32ec1c0

File tree

12 files changed

+1311
-6
lines changed

12 files changed

+1311
-6
lines changed

sketch_adapter_freecad/adapter.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
Circle,
2020
ConstraintError,
2121
ConstraintType,
22+
Ellipse,
23+
EllipticalArc,
2224
ExportError,
2325
GeometryError,
2426
Line,
@@ -247,6 +249,10 @@ def add_primitive(self, primitive: SketchPrimitive) -> int:
247249
idx = self._add_point(sketch, primitive)
248250
elif isinstance(primitive, Spline):
249251
idx = self._add_spline(sketch, primitive)
252+
elif isinstance(primitive, Ellipse):
253+
idx = self._add_ellipse(sketch, primitive)
254+
elif isinstance(primitive, EllipticalArc):
255+
idx = self._add_elliptical_arc(sketch, primitive)
250256
else:
251257
raise GeometryError(f"Unsupported primitive type: {type(primitive).__name__}")
252258

@@ -325,6 +331,69 @@ def _add_spline(self, sketch: Any, spline: Spline) -> int:
325331

326332
return sketch.addGeometry(bspline, spline.construction)
327333

334+
def _add_ellipse(self, sketch: Any, ellipse: Ellipse) -> int:
335+
"""Add an ellipse to the sketch."""
336+
center = App.Vector(ellipse.center.x, ellipse.center.y, 0)
337+
338+
# Calculate major axis endpoint (for FreeCAD ellipse construction)
339+
# Major axis is at angle 'rotation' from the X-axis
340+
major_axis_x = ellipse.major_radius * math.cos(ellipse.rotation)
341+
major_axis_y = ellipse.major_radius * math.sin(ellipse.rotation)
342+
major_point = App.Vector(
343+
ellipse.center.x + major_axis_x,
344+
ellipse.center.y + major_axis_y,
345+
0
346+
)
347+
348+
# Calculate minor axis endpoint (perpendicular to major axis)
349+
minor_axis_x = ellipse.minor_radius * math.cos(ellipse.rotation + math.pi / 2)
350+
minor_axis_y = ellipse.minor_radius * math.sin(ellipse.rotation + math.pi / 2)
351+
minor_point = App.Vector(
352+
ellipse.center.x + minor_axis_x,
353+
ellipse.center.y + minor_axis_y,
354+
0
355+
)
356+
357+
# Create ellipse using center, major axis point, and minor axis point
358+
geo = Part.Ellipse(center, major_point, minor_point)
359+
return sketch.addGeometry(geo, ellipse.construction)
360+
361+
def _add_elliptical_arc(self, sketch: Any, arc: EllipticalArc) -> int:
362+
"""Add an elliptical arc to the sketch."""
363+
center = App.Vector(arc.center.x, arc.center.y, 0)
364+
365+
# Calculate major axis endpoint
366+
major_axis_x = arc.major_radius * math.cos(arc.rotation)
367+
major_axis_y = arc.major_radius * math.sin(arc.rotation)
368+
major_point = App.Vector(
369+
arc.center.x + major_axis_x,
370+
arc.center.y + major_axis_y,
371+
0
372+
)
373+
374+
# Calculate minor axis endpoint (perpendicular to major axis)
375+
minor_axis_x = arc.minor_radius * math.cos(arc.rotation + math.pi / 2)
376+
minor_axis_y = arc.minor_radius * math.sin(arc.rotation + math.pi / 2)
377+
minor_point = App.Vector(
378+
arc.center.x + minor_axis_x,
379+
arc.center.y + minor_axis_y,
380+
0
381+
)
382+
383+
# Create base ellipse
384+
ellipse = Part.Ellipse(center, major_point, minor_point)
385+
386+
# Adjust parameters for CW direction if needed
387+
start_param = arc.start_param
388+
end_param = arc.end_param
389+
if not arc.ccw:
390+
# Swap start and end for CW direction
391+
start_param, end_param = end_param, start_param
392+
393+
# Create arc from ellipse
394+
geo = Part.ArcOfEllipse(ellipse, start_param, end_param)
395+
return sketch.addGeometry(geo, arc.construction)
396+
328397
def _extract_knots_and_mults(self, knots: list[float]) -> tuple[list[float], list[int]]:
329398
"""
330399
Extract unique knots and multiplicities from an expanded knot vector.
@@ -462,8 +531,12 @@ def _infer_vertex_index(self, geo: Any, point_type: PointType) -> int:
462531

463532
if 'Line' in geo_type:
464533
return get_vertex_index(Line, point_type) or 1
534+
elif 'ArcOfEllipse' in geo_type:
535+
return get_vertex_index(EllipticalArc, point_type) or 1
465536
elif 'Arc' in geo_type:
466537
return get_vertex_index(Arc, point_type) or 1
538+
elif 'Ellipse' in geo_type:
539+
return get_vertex_index(Ellipse, point_type) or 3
467540
elif 'Circle' in geo_type:
468541
return get_vertex_index(Circle, point_type) or 3
469542
elif 'Point' in geo_type:
@@ -784,6 +857,8 @@ def supports_feature(self, feature: str) -> bool:
784857
"""Check if a feature is supported."""
785858
supported = {
786859
"spline": True,
860+
"ellipse": True,
861+
"elliptical_arc": True,
787862
"three_point_arc": True,
788863
"image_capture": True,
789864
"solver_status": True,
@@ -861,6 +936,51 @@ def _geometry_to_primitive(self, geo: Any, index: int) -> SketchPrimitive | None
861936
periodic=geo.isPeriodic(),
862937
construction=is_construction
863938
)
939+
elif 'ArcOfEllipse' in geo_type:
940+
# Elliptical arc - extract base ellipse properties
941+
ellipse = geo.Ellipse
942+
center = ellipse.Center
943+
major_radius = ellipse.MajorRadius
944+
minor_radius = ellipse.MinorRadius
945+
946+
# Calculate rotation from the major axis direction
947+
# FreeCAD ellipse MajorAxis is a vector, we need the angle
948+
major_axis = ellipse.XAxis if hasattr(ellipse, 'XAxis') else App.Vector(1, 0, 0)
949+
rotation = math.atan2(major_axis.y, major_axis.x)
950+
951+
# Get parameter range
952+
start_param = geo.FirstParameter
953+
end_param = geo.LastParameter
954+
955+
# Determine CCW based on parameter order
956+
ccw = end_param > start_param
957+
958+
return EllipticalArc(
959+
center=Point2D(center.x, center.y),
960+
major_radius=major_radius,
961+
minor_radius=minor_radius,
962+
rotation=rotation,
963+
start_param=start_param,
964+
end_param=end_param,
965+
ccw=ccw,
966+
construction=is_construction
967+
)
968+
elif 'Ellipse' in geo_type and 'Arc' not in geo_type:
969+
center = geo.Center
970+
major_radius = geo.MajorRadius
971+
minor_radius = geo.MinorRadius
972+
973+
# Calculate rotation from the major axis direction
974+
major_axis = geo.XAxis if hasattr(geo, 'XAxis') else App.Vector(1, 0, 0)
975+
rotation = math.atan2(major_axis.y, major_axis.x)
976+
977+
return Ellipse(
978+
center=Point2D(center.x, center.y),
979+
major_radius=major_radius,
980+
minor_radius=minor_radius,
981+
rotation=rotation,
982+
construction=is_construction
983+
)
864984

865985
return None
866986

@@ -1085,8 +1205,12 @@ def _geo_to_prim_type(self, geo: Any) -> type:
10851205
geo_type = type(geo).__name__
10861206
if 'Line' in geo_type:
10871207
return Line
1208+
elif 'ArcOfEllipse' in geo_type:
1209+
return EllipticalArc
10881210
elif 'Arc' in geo_type:
10891211
return Arc
1212+
elif 'Ellipse' in geo_type:
1213+
return Ellipse
10901214
elif 'Circle' in geo_type:
10911215
return Circle
10921216
elif 'Point' in geo_type:

sketch_adapter_freecad/vertex_map.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from dataclasses import dataclass
1414

15-
from sketch_canonical import Arc, Circle, Line, Point, PointType, Spline
15+
from sketch_canonical import Arc, Circle, Ellipse, EllipticalArc, Line, Point, PointType, Spline
1616

1717

1818
@dataclass
@@ -41,6 +41,14 @@ class VertexMap:
4141
SPLINE_START = 1
4242
SPLINE_END = 2
4343

44+
# Ellipse vertex indices (similar to Circle)
45+
ELLIPSE_CENTER = 3
46+
47+
# EllipticalArc vertex indices (similar to Arc)
48+
ELLIPTICAL_ARC_START = 1
49+
ELLIPTICAL_ARC_END = 2
50+
ELLIPTICAL_ARC_CENTER = 3
51+
4452
# Origin reference
4553
ORIGIN_GEO_INDEX = -1
4654
ORIGIN_VERTEX = 1
@@ -84,6 +92,16 @@ def get_vertex_index(primitive_type: type, point_type: PointType) -> int | None:
8492
PointType.START: VertexMap.SPLINE_START,
8593
PointType.END: VertexMap.SPLINE_END,
8694
}
95+
elif primitive_type == Ellipse:
96+
mapping = {
97+
PointType.CENTER: VertexMap.ELLIPSE_CENTER,
98+
}
99+
elif primitive_type == EllipticalArc:
100+
mapping = {
101+
PointType.START: VertexMap.ELLIPTICAL_ARC_START,
102+
PointType.END: VertexMap.ELLIPTICAL_ARC_END,
103+
PointType.CENTER: VertexMap.ELLIPTICAL_ARC_CENTER,
104+
}
87105
else:
88106
return None
89107

@@ -125,6 +143,16 @@ def get_point_type_from_vertex(primitive_type: type, vertex_index: int) -> Point
125143
VertexMap.SPLINE_START: PointType.START,
126144
VertexMap.SPLINE_END: PointType.END,
127145
}
146+
elif primitive_type == Ellipse:
147+
mapping = {
148+
VertexMap.ELLIPSE_CENTER: PointType.CENTER,
149+
}
150+
elif primitive_type == EllipticalArc:
151+
mapping = {
152+
VertexMap.ELLIPTICAL_ARC_START: PointType.START,
153+
VertexMap.ELLIPTICAL_ARC_END: PointType.END,
154+
VertexMap.ELLIPTICAL_ARC_CENTER: PointType.CENTER,
155+
}
128156
else:
129157
return None
130158

0 commit comments

Comments
 (0)