Skip to content

Commit 6c6dd69

Browse files
Fixes to Fusion round-trip tests for ellipses and elliptical arcs.
1 parent 32ec1c0 commit 6c6dd69

5 files changed

Lines changed: 297 additions & 31 deletions

File tree

sketch_adapter_fusion/adapter.py

Lines changed: 80 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,11 @@ def export_sketch(self) -> SketchDocument:
169169
try:
170170
doc = SketchDocument(name=self._sketch.name)
171171

172+
# Collect ellipse axis line tokens to skip during line export
173+
ellipse_axis_tokens = self._collect_ellipse_axis_tokens()
174+
172175
# Export all geometry
173-
self._export_lines(doc)
176+
self._export_lines(doc, ellipse_axis_tokens)
174177
self._export_arcs(doc)
175178
self._export_circles(doc)
176179
self._export_ellipses(doc)
@@ -324,60 +327,80 @@ def _add_spline(self, spline: Spline) -> Any:
324327
def _add_ellipse(self, ellipse: Ellipse) -> Any:
325328
"""Add an ellipse to the sketch.
326329
327-
Fusion 360 ellipse API: addByCenter(center, majorAxisPoint, minorAxisLength)
328-
- center: Point3D at ellipse center
330+
Fusion 360 ellipse API: add(centerPoint, majorAxisPoint, point)
331+
- centerPoint: Point3D at ellipse center
329332
- majorAxisPoint: Point3D defining the major axis endpoint
330-
- minorAxisLength: Distance from center to minor axis endpoint (as float)
333+
- point: A Point3D that the ellipse passes through (defines minor axis)
331334
"""
332335
ellipses = self._sketch.sketchCurves.sketchEllipses
333336

334337
center_pt = self._point2d_to_point3d(ellipse.center)
335338

336339
# Calculate major axis endpoint
337340
# The major axis is at angle 'rotation' from the X-axis
338-
major_axis_x = ellipse.major_radius * math.cos(ellipse.rotation)
339-
major_axis_y = ellipse.major_radius * math.sin(ellipse.rotation)
341+
cos_r = math.cos(ellipse.rotation)
342+
sin_r = math.sin(ellipse.rotation)
343+
major_axis_x = ellipse.major_radius * cos_r
344+
major_axis_y = ellipse.major_radius * sin_r
340345
major_pt = self._adsk_core.Point3D.create(
341346
(ellipse.center.x + major_axis_x) * MM_TO_CM,
342347
(ellipse.center.y + major_axis_y) * MM_TO_CM,
343348
0
344349
)
345350

346-
# Minor axis length in cm
347-
minor_axis_cm = ellipse.minor_radius * MM_TO_CM
351+
# Calculate a point on the ellipse (use minor axis endpoint)
352+
# Minor axis is perpendicular to major axis (rotation + 90 degrees)
353+
minor_axis_x = -ellipse.minor_radius * sin_r
354+
minor_axis_y = ellipse.minor_radius * cos_r
355+
minor_pt = self._adsk_core.Point3D.create(
356+
(ellipse.center.x + minor_axis_x) * MM_TO_CM,
357+
(ellipse.center.y + minor_axis_y) * MM_TO_CM,
358+
0
359+
)
348360

349-
return ellipses.addByCenter(center_pt, major_pt, minor_axis_cm)
361+
return ellipses.add(center_pt, major_pt, minor_pt)
350362

351363
def _add_elliptical_arc(self, arc: EllipticalArc) -> Any:
352364
"""Add an elliptical arc to the sketch.
353365
354-
Fusion 360 API: addByCenter(center, majorAxisPoint, minorAxisLength, startAngle, sweepAngle)
366+
Fusion 360 API: addByAngle(centerPoint, majorAxis, minorAxis, startAngle, sweepAngle)
367+
- centerPoint: Point3D at ellipse center
368+
- majorAxis: Vector3D defining major axis direction and magnitude (= major radius)
369+
- minorAxis: Vector3D defining minor axis direction and magnitude (= minor radius)
370+
- startAngle: Start angle in radians (0 = along major axis)
371+
- sweepAngle: Sweep angle in radians (positive = CCW)
355372
"""
356373
elliptical_arcs = self._sketch.sketchCurves.sketchEllipticalArcs
357374

358375
center_pt = self._point2d_to_point3d(arc.center)
359376

360-
# Calculate major axis endpoint
361-
major_axis_x = arc.major_radius * math.cos(arc.rotation)
362-
major_axis_y = arc.major_radius * math.sin(arc.rotation)
363-
major_pt = self._adsk_core.Point3D.create(
364-
(arc.center.x + major_axis_x) * MM_TO_CM,
365-
(arc.center.y + major_axis_y) * MM_TO_CM,
377+
# Calculate major axis vector (direction and magnitude)
378+
cos_r = math.cos(arc.rotation)
379+
sin_r = math.sin(arc.rotation)
380+
major_axis = self._adsk_core.Vector3D.create(
381+
arc.major_radius * cos_r * MM_TO_CM,
382+
arc.major_radius * sin_r * MM_TO_CM,
366383
0
367384
)
368385

369-
# Minor axis length in cm
370-
minor_axis_cm = arc.minor_radius * MM_TO_CM
386+
# Calculate minor axis vector (perpendicular to major, direction and magnitude)
387+
minor_axis = self._adsk_core.Vector3D.create(
388+
-arc.minor_radius * sin_r * MM_TO_CM,
389+
arc.minor_radius * cos_r * MM_TO_CM,
390+
0
391+
)
371392

372393
# Calculate sweep angle
373394
sweep = arc.sweep_param
395+
if not arc.ccw:
396+
sweep = -sweep
374397

375398
# Fusion uses start angle from major axis direction
376399
# Our start_param is already the parametric angle
377400
start_angle = arc.start_param
378401

379-
return elliptical_arcs.addByCenter(
380-
center_pt, major_pt, minor_axis_cm,
402+
return elliptical_arcs.addByAngle(
403+
center_pt, major_axis, minor_axis,
381404
start_angle, sweep
382405
)
383406

@@ -1167,8 +1190,34 @@ def _point3d_to_point2d(self, point3d) -> Point2D:
11671190

11681191
# Export helper methods
11691192

1170-
def _export_lines(self, doc: SketchDocument) -> None:
1171-
"""Export all lines from the sketch."""
1193+
def _collect_ellipse_axis_tokens(self) -> set:
1194+
"""Collect entity tokens for ellipse axis lines to skip during export.
1195+
1196+
Fusion 360 creates major and minor axis lines for each ellipse.
1197+
These should not be exported as separate line primitives.
1198+
"""
1199+
tokens = set()
1200+
ellipses = self._sketch.sketchCurves.sketchEllipses
1201+
for i in range(ellipses.count):
1202+
ellipse = ellipses.item(i)
1203+
# Collect major axis line token if it exists
1204+
if ellipse.majorAxisLine:
1205+
tokens.add(ellipse.majorAxisLine.entityToken)
1206+
# Collect minor axis line token if it exists
1207+
if ellipse.minorAxisLine:
1208+
tokens.add(ellipse.minorAxisLine.entityToken)
1209+
return tokens
1210+
1211+
def _export_lines(self, doc: SketchDocument, skip_tokens: set = None) -> None:
1212+
"""Export all lines from the sketch.
1213+
1214+
Args:
1215+
doc: The SketchDocument to add lines to
1216+
skip_tokens: Optional set of entity tokens to skip (e.g., ellipse axis lines)
1217+
"""
1218+
if skip_tokens is None:
1219+
skip_tokens = set()
1220+
11721221
lines = self._sketch.sketchCurves.sketchLines
11731222
for i in range(lines.count):
11741223
line = lines.item(i)
@@ -1177,6 +1226,10 @@ def _export_lines(self, doc: SketchDocument) -> None:
11771226
if line.isReference:
11781227
continue
11791228

1229+
# Skip ellipse axis lines
1230+
if line.entityToken in skip_tokens:
1231+
continue
1232+
11801233
start = self._point3d_to_point2d(line.startSketchPoint.geometry)
11811234
end = self._point3d_to_point2d(line.endSketchPoint.geometry)
11821235

@@ -1261,13 +1314,12 @@ def _export_ellipses(self, doc: SketchDocument) -> None:
12611314

12621315
center = self._point3d_to_point2d(ellipse.centerSketchPoint.geometry)
12631316

1264-
# Get the ellipse geometry to extract major/minor radii and rotation
1265-
geom = ellipse.geometry
1266-
major_radius = geom.majorRadius * CM_TO_MM
1267-
minor_radius = geom.minorRadius * CM_TO_MM
1317+
# Use SketchEllipse properties directly for better accuracy
1318+
major_radius = ellipse.majorAxisRadius * CM_TO_MM
1319+
minor_radius = ellipse.minorAxisRadius * CM_TO_MM
12681320

1269-
# Get rotation from major axis direction
1270-
major_axis = geom.majorAxis
1321+
# Get rotation from major axis direction vector
1322+
major_axis = ellipse.majorAxis
12711323
rotation = math.atan2(major_axis.y, major_axis.x)
12721324

12731325
canonical_ellipse = Ellipse(

sketch_adapter_fusion/test_scripts/test_roundtrip.py renamed to sketch_adapter_fusion/script/CanonicalSketchTests/CanonicalSketchTests.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424
from pathlib import Path
2525

2626
# Add the project root to path for imports
27-
# Adjust this path based on where you've installed the canonical_sketch package
28-
SCRIPT_DIR = Path(__file__).parent
29-
PROJECT_ROOT = SCRIPT_DIR.parent.parent
27+
# Use resolve() to follow the symlink to the actual source location
28+
SCRIPT_DIR = Path(__file__).resolve().parent # .../script/CanonicalSketchTests
29+
PROJECT_ROOT = SCRIPT_DIR.parent.parent.parent # .../canonical_sketch
3030
if str(PROJECT_ROOT) not in sys.path:
3131
sys.path.insert(0, str(PROJECT_ROOT))
3232

File renamed without changes.

sketch_adapter_fusion/test_scripts/__init__.py renamed to sketch_adapter_fusion/script/CanonicalSketchTests/__init__.py

File renamed without changes.

0 commit comments

Comments
 (0)