Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ dev = [
]

[tool.setuptools.packages.find]
include = ["sketch_canonical*"]
include = ["sketch_canonical*", "sketch_adapter_freecad*", "sketch_adapter_fusion*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
Expand Down
2 changes: 1 addition & 1 deletion sketch_adapter_freecad/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
When FreeCAD is not available, a MockFreeCADAdapter is provided for testing.
"""

from .adapter import FreeCADAdapter, FREECAD_AVAILABLE
from .adapter import FREECAD_AVAILABLE, FreeCADAdapter
from .vertex_map import VertexMap, get_vertex_index

__all__ = [
Expand Down
71 changes: 35 additions & 36 deletions sketch_adapter_freecad/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,30 @@
"""

import math
from typing import Any, Dict, List, Optional, Tuple, Union
from typing import Any

from sketch_canonical import (
SketchBackendAdapter,
SketchDocument,
SolverStatus,
SketchPrimitive,
SketchConstraint,
Point2D,
PointType,
PointRef,
Line,
Arc,
Circle,
Point,
Spline,
ConstraintError,
ConstraintType,
ConstraintStatus,
ExportError,
GeometryError,
ConstraintError,
Line,
Point,
Point2D,
PointRef,
PointType,
SketchBackendAdapter,
SketchConstraint,
SketchCreationError,
ExportError,
SketchDocument,
SketchPrimitive,
SolverStatus,
Spline,
)

from .vertex_map import VertexMap, get_vertex_index, get_point_type_from_vertex
from .vertex_map import VertexMap, get_point_type_from_vertex, get_vertex_index

# Try to import FreeCAD modules
FREECAD_AVAILABLE = False
Expand Down Expand Up @@ -79,7 +78,7 @@ class FreeCADAdapter(SketchBackendAdapter):
ConstraintType.CONCENTRIC: 'Coincident', # Concentric uses Coincident on centers
}

def __init__(self, document: Optional[Any] = None):
def __init__(self, document: Any | None = None):
"""
Initialize the FreeCAD adapter.

Expand All @@ -94,11 +93,11 @@ def __init__(self, document: Optional[Any] = None):

self._document = document
self._sketch = None
self._sketch_doc: Optional[SketchDocument] = None
self._sketch_doc: SketchDocument | None = None

# ID to FreeCAD geometry index mapping
self._id_to_index: Dict[str, int] = {}
self._index_to_id: Dict[int, str] = {}
self._id_to_index: dict[str, int] = {}
self._index_to_id: dict[int, str] = {}

def _get_document(self) -> Any:
"""Get the FreeCAD document, creating one if needed."""
Expand All @@ -114,7 +113,7 @@ def _get_active_sketch(self) -> Any:
raise SketchCreationError("No active sketch. Call create_sketch() first.")
return self._sketch

def create_sketch(self, name: str, plane: Optional[Any] = None) -> None:
def create_sketch(self, name: str, plane: Any | None = None) -> None:
"""
Create a new empty sketch in FreeCAD.

Expand Down Expand Up @@ -298,7 +297,7 @@ def _add_spline(self, sketch: Any, spline: Spline) -> int:

return sketch.addGeometry(bspline, spline.construction)

def _extract_knots_and_mults(self, knots: List[float]) -> Tuple[List[float], List[int]]:
def _extract_knots_and_mults(self, knots: list[float]) -> tuple[list[float], list[int]]:
"""
Extract unique knots and multiplicities from an expanded knot vector.

Expand Down Expand Up @@ -333,7 +332,7 @@ def _extract_knots_and_mults(self, knots: List[float]) -> Tuple[List[float], Lis

return unique_knots, mults

def _compute_multiplicities(self, spline: Spline) -> List[int]:
def _compute_multiplicities(self, spline: Spline) -> list[int]:
"""Compute knot multiplicities from the knot vector."""
_, mults = self._extract_knots_and_mults(spline.knots)
return mults
Expand Down Expand Up @@ -393,7 +392,7 @@ def add_constraint(self, constraint: SketchConstraint) -> bool:
except Exception as e:
raise ConstraintError(f"Failed to add constraint: {e}") from e

def _point_ref_to_freecad(self, ref: PointRef) -> Tuple[int, int]:
def _point_ref_to_freecad(self, ref: PointRef) -> tuple[int, int]:
"""
Convert a PointRef to FreeCAD (geometry_index, vertex_index).

Expand Down Expand Up @@ -440,7 +439,7 @@ def _infer_vertex_index(self, geo: Any, point_type: PointType) -> int:
else:
return 1

def _get_element_index(self, ref: Union[str, PointRef]) -> int:
def _get_element_index(self, ref: str | PointRef) -> int:
"""Get the geometry index for an element reference."""
if isinstance(ref, PointRef):
element_id = ref.element_id
Expand Down Expand Up @@ -607,7 +606,7 @@ def _add_symmetric(self, sketch: Any, constraint: SketchConstraint) -> None:
'Symmetric', idx1, idx2, axis_idx
))

def get_solver_status(self) -> Tuple[SolverStatus, int]:
def get_solver_status(self) -> tuple[SolverStatus, int]:
"""Get the constraint solver status."""
sketch = self._get_active_sketch()

Expand Down Expand Up @@ -643,8 +642,8 @@ def capture_image(self, width: int, height: int) -> bytes:
view.setImageSize(width, height)

# Capture to temp file and read bytes
import tempfile
import os
import tempfile

with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as f:
temp_path = f.name
Expand All @@ -658,9 +657,9 @@ def capture_image(self, width: int, height: int) -> bytes:
return data

except ImportError:
raise ExportError("FreeCADGui not available for image capture")
raise ExportError("FreeCADGui not available for image capture") from None
except Exception as e:
raise ExportError(f"Failed to capture image: {e}")
raise ExportError(f"Failed to capture image: {e}") from e

def close_sketch(self) -> None:
"""Close the current sketch."""
Expand All @@ -671,7 +670,7 @@ def close_sketch(self) -> None:
self._id_to_index.clear()
self._index_to_id.clear()

def get_element_by_id(self, element_id: str) -> Optional[Any]:
def get_element_by_id(self, element_id: str) -> Any | None:
"""Get the FreeCAD geometry object for a canonical element ID."""
if element_id not in self._id_to_index:
return None
Expand All @@ -694,7 +693,7 @@ def supports_feature(self, feature: str) -> bool:

# Export helpers

def _geometry_to_primitive(self, geo: Any, index: int) -> Optional[SketchPrimitive]:
def _geometry_to_primitive(self, geo: Any, index: int) -> SketchPrimitive | None:
"""Convert FreeCAD geometry to canonical primitive."""
geo_type = type(geo).__name__
# Construction flag is stored on the sketch, not the geometry
Expand Down Expand Up @@ -751,7 +750,7 @@ def _geometry_to_primitive(self, geo: Any, index: int) -> Optional[SketchPrimiti
# Expand knots with multiplicities
mults = geo.getMultiplicities()
full_knots = []
for k, m in zip(knots, mults):
for k, m in zip(knots, mults, strict=False):
full_knots.extend([k] * m)

return Spline(
Expand All @@ -765,7 +764,7 @@ def _geometry_to_primitive(self, geo: Any, index: int) -> Optional[SketchPrimiti

return None

def _fc_constraint_to_canonical(self, fc_constraint: Any) -> Optional[SketchConstraint]:
def _fc_constraint_to_canonical(self, fc_constraint: Any) -> SketchConstraint | None:
"""Convert FreeCAD constraint to canonical form."""
fc_type = fc_constraint.Type

Expand Down Expand Up @@ -814,7 +813,7 @@ def _fc_constraint_to_canonical(self, fc_constraint: Any) -> Optional[SketchCons

def _extract_constraint_references(
self, fc_constraint: Any, constraint_type: ConstraintType
) -> Optional[List[Union[str, PointRef]]]:
) -> list[str | PointRef] | None:
"""Extract references from a FreeCAD constraint."""
first = fc_constraint.First
second = fc_constraint.Second if hasattr(fc_constraint, 'Second') else -1
Expand All @@ -824,10 +823,10 @@ def _extract_constraint_references(
second_pos = fc_constraint.SecondPos if hasattr(fc_constraint, 'SecondPos') else 0

# Convert geometry indices to element IDs
def idx_to_id(idx: int) -> Optional[str]:
def idx_to_id(idx: int) -> str | None:
return self._index_to_id.get(idx)

def idx_to_point_ref(idx: int, pos: int) -> Optional[PointRef]:
def idx_to_point_ref(idx: int, pos: int) -> PointRef | None:
elem_id = idx_to_id(idx)
if elem_id is None:
return None
Expand Down
7 changes: 3 additions & 4 deletions sketch_adapter_freecad/vertex_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
"""

from dataclasses import dataclass
from typing import Optional

from sketch_canonical import PointType, Line, Arc, Circle, Point, Spline
from sketch_canonical import Arc, Circle, Line, Point, PointType, Spline


@dataclass
Expand Down Expand Up @@ -49,7 +48,7 @@ class VertexMap:
EXTERNAL_GEO_BASE = -2


def get_vertex_index(primitive_type: type, point_type: PointType) -> Optional[int]:
def get_vertex_index(primitive_type: type, point_type: PointType) -> int | None:
"""
Get the FreeCAD vertex index for a point type on a primitive.

Expand Down Expand Up @@ -90,7 +89,7 @@ def get_vertex_index(primitive_type: type, point_type: PointType) -> Optional[in
return mapping.get(point_type)


def get_point_type_from_vertex(primitive_type: type, vertex_index: int) -> Optional[PointType]:
def get_point_type_from_vertex(primitive_type: type, vertex_index: int) -> PointType | None:
"""
Get the canonical point type from a FreeCAD vertex index.

Expand Down
43 changes: 43 additions & 0 deletions sketch_adapter_fusion/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Fusion 360 adapter for canonical sketch representation.

This module provides the FusionAdapter class for translating between
the canonical sketch representation and Autodesk Fusion 360's native
sketch API.

Example usage (within Fusion 360):

from sketch_adapter_fusion import FusionAdapter
from sketch_canonical.document import SketchDocument
from sketch_canonical.primitives import Line
from sketch_canonical.types import Point2D

# Create adapter (requires running within Fusion 360)
adapter = FusionAdapter()

# Create a new sketch
adapter.create_sketch("MySketch", plane="XY")

# Add geometry
line = Line(start=Point2D(0, 0), end=Point2D(100, 0))
adapter.add_primitive(line)

# Or load an entire SketchDocument
doc = SketchDocument(name="ImportedSketch")
# ... add primitives and constraints to doc ...
adapter.load_sketch(doc)

# Export back to canonical format
exported_doc = adapter.export_sketch()

Note: This adapter must be run within Fusion 360's Python environment
where the 'adsk' module is available.
"""

from .adapter import FusionAdapter
from .vertex_map import VertexMap, get_point_from_sketch_entity

__all__ = [
"FusionAdapter",
"VertexMap",
"get_point_from_sketch_entity",
]
Loading
Loading