Merge pull request #13 from Rich43/codex/refactor-api-for-difference-method

Add safe_difference helper
This commit is contained in:
Richard Ward
2025-07-17 01:27:59 +01:00
committed by GitHub
5 changed files with 46 additions and 27 deletions

View File

@@ -1,9 +1,9 @@
"""Consolidated parametric_cad package."""
from .core import tm
from .core import tm, safe_difference
from .primitives.box import Box
from .primitives.gear import SpurGear
from .mechanisms.butthinge import ButtHinge
from .export.stl import STLExporter
__all__ = ["tm", "Box", "SpurGear", "ButtHinge", "STLExporter"]
__all__ = ["tm", "safe_difference", "Box", "SpurGear", "ButtHinge", "STLExporter"]

View File

@@ -8,8 +8,38 @@ wrapper allows the backend to be swapped or mocked easily.
import trimesh as _trimesh
def safe_difference(mesh, other, *, engine="scad"):
"""Perform a boolean difference with graceful fallback.
Parameters
----------
mesh : _trimesh.Trimesh
Base mesh to subtract from.
other : _trimesh.Trimesh or list
Mesh or list of meshes to subtract.
engine : str or None, optional
Preferred boolean engine. ``"scad"`` is tried by default.
Returns
-------
_trimesh.Trimesh
Resulting mesh if the operation succeeds, otherwise the original
``mesh`` if all boolean attempts fail.
"""
try:
if engine:
return mesh.difference(other, engine=engine)
return mesh.difference(other)
except Exception:
try:
return mesh.difference(other)
except Exception:
return mesh
# Public alias so that other modules can use the backend without
# importing ``trimesh`` themselves.
tm = _trimesh
__all__ = ["tm"]
__all__ = ["tm", "safe_difference"]

View File

@@ -1,5 +1,6 @@
from parametric_cad.primitives.box import Box
from parametric_cad.export.stl import STLExporter
from parametric_cad.core import safe_difference
outer = Box(100, 60, 40).at(0, 0, 0)
inner = Box(90, 50, 30).at(5, 5, 5)
@@ -7,13 +8,7 @@ inner = Box(90, 50, 30).at(5, 5, 5)
# Create hollow box by subtracting inner from outer
outer_mesh = outer.mesh()
inner_mesh = inner.mesh()
try:
hollow_box = outer_mesh.difference(inner_mesh, engine='scad')
except Exception:
try:
hollow_box = outer_mesh.difference(inner_mesh)
except Exception:
hollow_box = outer_mesh
hollow_box = safe_difference(outer_mesh, inner_mesh)
exporter = STLExporter(output_dir="output/hollow_box_output")
exporter.export_mesh(hollow_box, "hollow_box")

View File

@@ -1,5 +1,5 @@
import numpy as np
from parametric_cad.core import tm
from parametric_cad.core import tm, safe_difference
from shapely.geometry import Polygon
from math import pi, sin, cos, tan
import logging
@@ -91,14 +91,7 @@ class SpurGear:
bore = tm.creation.cylinder(radius=self.bore_diameter / 2, height=self.width + 0.1)
bore.apply_translation([0, 0, self.width / 2])
try:
gear = gear_body.difference(bore, engine='scad')
except Exception:
try:
gear = gear_body.difference(bore)
except Exception:
logging.warning("Boolean difference not available, skipping bore subtraction")
gear = gear_body
gear = safe_difference(gear_body, bore)
logging.debug(f"Subtracted bore, resulting mesh has {len(gear.vertices)} vertices")
if self.hole_count > 0:
@@ -112,13 +105,7 @@ class SpurGear:
if not hole.is_volume:
hole = hole.convex_hull
hole_cylinders.append(hole)
try:
gear = gear.difference(hole_cylinders, engine='scad')
except Exception:
try:
gear = gear.difference(hole_cylinders)
except Exception:
logging.warning("Boolean difference not available, skipping hole subtraction")
gear = safe_difference(gear, hole_cylinders)
logging.debug(f"Subtracted {self.hole_count} holes, resulting mesh has {len(gear.vertices)} vertices")
if not gear.is_watertight:

View File

@@ -1,6 +1,6 @@
import pytest
import numpy as np
from parametric_cad.core import tm
from parametric_cad.core import tm, safe_difference
from math import cos, pi
from parametric_cad.primitives.box import Box
@@ -34,3 +34,10 @@ def test_cylinder_and_sphere_meshes():
assert isinstance(sph_mesh, tm.Trimesh)
assert cyl_mesh.is_watertight
assert sph_mesh.is_watertight
def test_safe_difference_returns_mesh():
outer = Box(1.0, 1.0, 1.0)
inner = Box(0.5, 0.5, 0.5).at(0.25, 0.25, 0.25)
result = safe_difference(outer.mesh(), inner.mesh(), engine="invalid")
assert isinstance(result, tm.Trimesh)