diff --git a/README.md b/README.md index 5cd2a1f..4eb43d1 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ # bamboogenerator -Python scripts for generating 3D printable models using the -`trimesh` library. The repository contains a consolidated -`parametric_cad` package with example scripts that produce STL files. +Python scripts for generating 3D printable models. The project now +leverages the [CadQuery](https://github.com/CadQuery/cadquery) +library for creating geometry while still providing utilities for +scaffolding generation and printability validation. The repository +contains a consolidated `parametric_cad` package with example scripts +that produce STL files. ## Folder layout @@ -18,7 +21,9 @@ installer script in the repository root. From the desired project directory run python install_requirements.py ``` -This installs all Python dependencies and attempts to set up `trimesh` for OpenSCAD rendering. +This installs all Python dependencies including CadQuery. It also +configures `trimesh` for OpenSCAD rendering which is still used under +the hood for mesh operations. ## Running the examples @@ -55,6 +60,31 @@ unioned = combine(boxes) result = safe_difference(unioned, Cylinder(0.5, 1).mesh()) ``` +### Using CadQuery + +CadQuery models can also be used with the validation and scaffolding +utilities by converting a `Workplane` to a `trimesh` mesh: + +```python +import cadquery as cq +from parametric_cad import workplane_to_mesh, generate_scaffolding + +wp = cq.Workplane("XY").box(10, 10, 5) +mesh = workplane_to_mesh(wp) +supports = generate_scaffolding(mesh) +``` + +You can also operate on CadQuery models directly using helper wrappers: + +```python +import cadquery as cq +from parametric_cad import generate_scaffolding_from_workplane, PrintabilityValidator + +wp = cq.Workplane("XY").box(10, 10, 5) +supports = generate_scaffolding_from_workplane(wp) +errors = PrintabilityValidator().validate_workplane(wp) +``` + ## Overhang Scaffolding `generate_scaffolding` creates simple cylindrical supports beneath diff --git a/install_requirements.py b/install_requirements.py index ddf5883..8b3a032 100644 --- a/install_requirements.py +++ b/install_requirements.py @@ -20,8 +20,22 @@ def install_openscad(): def install_python_packages(): logging.debug("Starting Python package installation process") print("Installing all required Python packages including triangulation engines...") - packages = ["trimesh", "numpy", "matplotlib", "pyglet<2", "networkx", "scipy", "shapely", - "triangle", "mapbox_earcut", "manifold3d", "pillow", "requests", "beautifulsoup4"] + packages = [ + "trimesh", + "cadquery", + "numpy", + "matplotlib", + "pyglet<2", + "networkx", + "scipy", + "shapely", + "triangle", + "mapbox_earcut", + "manifold3d", + "pillow", + "requests", + "beautifulsoup4", + ] try: subprocess.run([sys.executable, "-m", "pip", "install"] + packages, check=True) logging.info("Python packages installed successfully") diff --git a/parametric_cad/__init__.py b/parametric_cad/__init__.py index 11a75f3..67096c8 100644 --- a/parametric_cad/__init__.py +++ b/parametric_cad/__init__.py @@ -11,7 +11,8 @@ from .primitives.sphere import Sphere from .mechanisms.butthinge import ButtHinge from .export.stl import STLExporter from .printability import PrintabilityValidator -from .scaffolding import generate_scaffolding +from .scaffolding import generate_scaffolding, generate_scaffolding_from_workplane +from .cadquery_utils import workplane_to_mesh __all__ = [ "tm", @@ -28,6 +29,8 @@ __all__ = [ "STLExporter", "PrintabilityValidator", "generate_scaffolding", + "generate_scaffolding_from_workplane", + "workplane_to_mesh", "Polygon", "Point", "box", diff --git a/parametric_cad/cadquery_utils.py b/parametric_cad/cadquery_utils.py new file mode 100644 index 0000000..05fa704 --- /dev/null +++ b/parametric_cad/cadquery_utils.py @@ -0,0 +1,13 @@ +import io +import cadquery as cq +from cadquery.occ_impl import exporters + +from .core import tm + + +def workplane_to_mesh(wp: cq.Workplane) -> tm.Trimesh: + """Convert a CadQuery Workplane to a Trimesh mesh.""" + stl = exporters.toString(wp.val(), exporters.ExportTypes.STL) + return tm.load(io.BytesIO(stl.encode("utf-8")), file_type="stl") + +__all__ = ["workplane_to_mesh"] diff --git a/parametric_cad/printability.py b/parametric_cad/printability.py index 467aee9..4629620 100644 --- a/parametric_cad/printability.py +++ b/parametric_cad/printability.py @@ -5,6 +5,8 @@ from pathlib import Path from typing import List from .core import tm +import cadquery as cq +from .cadquery_utils import workplane_to_mesh import numpy as np @@ -85,5 +87,11 @@ class PrintabilityValidator: mesh = tm.load(file_path) return self.validate_mesh(mesh) + def validate_workplane(self, wp: cq.Workplane) -> List[str]: + """Validate a CadQuery Workplane.""" + + mesh = workplane_to_mesh(wp) + return self.validate_mesh(mesh) + __all__ = ["PrintabilityValidator"] diff --git a/parametric_cad/scaffolding.py b/parametric_cad/scaffolding.py index 2625e4e..c0294c1 100644 --- a/parametric_cad/scaffolding.py +++ b/parametric_cad/scaffolding.py @@ -3,6 +3,8 @@ from __future__ import annotations import numpy as np from .core import tm +import cadquery as cq +from .cadquery_utils import workplane_to_mesh def generate_scaffolding( @@ -65,4 +67,14 @@ def generate_scaffolding( return tm.util.concatenate(supports) -__all__ = ["generate_scaffolding"] +def generate_scaffolding_from_workplane( + wp: cq.Workplane, + **kwargs, +) -> tm.Trimesh: + """Convert a Workplane and generate scaffolding.""" + + mesh = workplane_to_mesh(wp) + return generate_scaffolding(mesh, **kwargs) + + +__all__ = ["generate_scaffolding", "generate_scaffolding_from_workplane"] diff --git a/tests/test_cadquery_utils.py b/tests/test_cadquery_utils.py new file mode 100644 index 0000000..d15e8e6 --- /dev/null +++ b/tests/test_cadquery_utils.py @@ -0,0 +1,10 @@ +import cadquery as cq +from parametric_cad.cadquery_utils import workplane_to_mesh +from parametric_cad.core import tm + + +def test_workplane_to_mesh(): + wp = cq.Workplane("XY").box(1, 1, 1) + mesh = workplane_to_mesh(wp) + assert isinstance(mesh, tm.Trimesh) + assert mesh.volume > 0 diff --git a/tests/test_cadquery_wrappers.py b/tests/test_cadquery_wrappers.py new file mode 100644 index 0000000..69e74d9 --- /dev/null +++ b/tests/test_cadquery_wrappers.py @@ -0,0 +1,22 @@ +import cadquery as cq +from parametric_cad import ( + generate_scaffolding_from_workplane, + PrintabilityValidator, + tm, +) + + +def test_scaffolding_from_workplane(): + base = cq.Workplane("XY").box(2, 2, 1) + overhang = cq.Workplane("XY").box(1, 1, 0.5).translate((1.5, 0.5, 1)) + wp = base.union(overhang) + scaff = generate_scaffolding_from_workplane(wp) + assert isinstance(scaff, tm.Trimesh) + assert scaff.vertices.shape[0] > 0 + + +def test_validate_workplane(): + wp = cq.Workplane("XY").box(10, 10, 10) + validator = PrintabilityValidator() + errors = validator.validate_workplane(wp) + assert errors == []