|
19 | 19 | Circle, |
20 | 20 | ConstraintError, |
21 | 21 | ConstraintType, |
| 22 | + Ellipse, |
| 23 | + EllipticalArc, |
22 | 24 | ExportError, |
23 | 25 | GeometryError, |
24 | 26 | Line, |
@@ -247,6 +249,10 @@ def add_primitive(self, primitive: SketchPrimitive) -> int: |
247 | 249 | idx = self._add_point(sketch, primitive) |
248 | 250 | elif isinstance(primitive, Spline): |
249 | 251 | 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) |
250 | 256 | else: |
251 | 257 | raise GeometryError(f"Unsupported primitive type: {type(primitive).__name__}") |
252 | 258 |
|
@@ -325,6 +331,69 @@ def _add_spline(self, sketch: Any, spline: Spline) -> int: |
325 | 331 |
|
326 | 332 | return sketch.addGeometry(bspline, spline.construction) |
327 | 333 |
|
| 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 | + |
328 | 397 | def _extract_knots_and_mults(self, knots: list[float]) -> tuple[list[float], list[int]]: |
329 | 398 | """ |
330 | 399 | 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: |
462 | 531 |
|
463 | 532 | if 'Line' in geo_type: |
464 | 533 | 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 |
465 | 536 | elif 'Arc' in geo_type: |
466 | 537 | 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 |
467 | 540 | elif 'Circle' in geo_type: |
468 | 541 | return get_vertex_index(Circle, point_type) or 3 |
469 | 542 | elif 'Point' in geo_type: |
@@ -784,6 +857,8 @@ def supports_feature(self, feature: str) -> bool: |
784 | 857 | """Check if a feature is supported.""" |
785 | 858 | supported = { |
786 | 859 | "spline": True, |
| 860 | + "ellipse": True, |
| 861 | + "elliptical_arc": True, |
787 | 862 | "three_point_arc": True, |
788 | 863 | "image_capture": True, |
789 | 864 | "solver_status": True, |
@@ -861,6 +936,51 @@ def _geometry_to_primitive(self, geo: Any, index: int) -> SketchPrimitive | None |
861 | 936 | periodic=geo.isPeriodic(), |
862 | 937 | construction=is_construction |
863 | 938 | ) |
| 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 | + ) |
864 | 984 |
|
865 | 985 | return None |
866 | 986 |
|
@@ -1085,8 +1205,12 @@ def _geo_to_prim_type(self, geo: Any) -> type: |
1085 | 1205 | geo_type = type(geo).__name__ |
1086 | 1206 | if 'Line' in geo_type: |
1087 | 1207 | return Line |
| 1208 | + elif 'ArcOfEllipse' in geo_type: |
| 1209 | + return EllipticalArc |
1088 | 1210 | elif 'Arc' in geo_type: |
1089 | 1211 | return Arc |
| 1212 | + elif 'Ellipse' in geo_type: |
| 1213 | + return Ellipse |
1090 | 1214 | elif 'Circle' in geo_type: |
1091 | 1215 | return Circle |
1092 | 1216 | elif 'Point' in geo_type: |
|
0 commit comments