mirror of
https://github.com/johndoe6345789/bamboogenerator.git
synced 2026-04-24 13:24:54 +00:00
Add scaffolding generation for overhangs
This commit is contained in:
15
README.md
15
README.md
@@ -55,6 +55,21 @@ unioned = combine(boxes)
|
||||
result = safe_difference(unioned, Cylinder(0.5, 1).mesh())
|
||||
```
|
||||
|
||||
## Overhang Scaffolding
|
||||
|
||||
`generate_scaffolding` creates simple cylindrical supports beneath
|
||||
downward facing surfaces that exceed a chosen overhang angle. The supports
|
||||
are meant to be easy to remove after printing.
|
||||
|
||||
```python
|
||||
from parametric_cad import generate_scaffolding, Box, combine
|
||||
|
||||
base = Box(20, 20, 10)
|
||||
ledge = Box(10, 10, 5).at(15, 5, 10)
|
||||
model = combine([base, ledge])
|
||||
supports = generate_scaffolding(model)
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](LICENSE).
|
||||
|
||||
@@ -11,6 +11,7 @@ from .primitives.sphere import Sphere
|
||||
from .mechanisms.butthinge import ButtHinge
|
||||
from .export.stl import STLExporter
|
||||
from .printability import PrintabilityValidator
|
||||
from .scaffolding import generate_scaffolding
|
||||
|
||||
__all__ = [
|
||||
"tm",
|
||||
@@ -26,6 +27,7 @@ __all__ = [
|
||||
"ButtHinge",
|
||||
"STLExporter",
|
||||
"PrintabilityValidator",
|
||||
"generate_scaffolding",
|
||||
"Polygon",
|
||||
"Point",
|
||||
"box",
|
||||
|
||||
68
parametric_cad/scaffolding.py
Normal file
68
parametric_cad/scaffolding.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .core import tm
|
||||
|
||||
|
||||
def generate_scaffolding(
|
||||
mesh: tm.Trimesh,
|
||||
*,
|
||||
max_angle_deg: float = 45.0,
|
||||
support_radius: float = 0.5,
|
||||
grid_size: float = 5.0,
|
||||
sections: int = 8,
|
||||
) -> tm.Trimesh:
|
||||
"""Return support scaffolding for downward overhangs of ``mesh``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh:
|
||||
Mesh to analyze for overhangs.
|
||||
max_angle_deg:
|
||||
Angles from vertical greater than this require support.
|
||||
support_radius:
|
||||
Radius of the cylindrical support columns.
|
||||
grid_size:
|
||||
Grid spacing for clustering support columns. ``0`` disables clustering.
|
||||
sections:
|
||||
Number of cylinder sections used to generate the columns.
|
||||
"""
|
||||
normals = mesh.face_normals
|
||||
centers = mesh.triangles_center
|
||||
min_z = float(mesh.bounds[0, 2])
|
||||
|
||||
angles = np.degrees(np.arccos(np.clip(normals[:, 2], -1.0, 1.0)))
|
||||
overhang = (angles > 90.0 + max_angle_deg) & (centers[:, 2] > min_z + 1e-6)
|
||||
if not np.any(overhang):
|
||||
return tm.Trimesh()
|
||||
|
||||
pts = centers[overhang]
|
||||
if grid_size > 0:
|
||||
xy = np.round(pts[:, :2] / grid_size) * grid_size
|
||||
unique_xy, inverse = np.unique(xy, axis=0, return_inverse=True)
|
||||
top_z = np.zeros(len(unique_xy))
|
||||
for i in range(len(unique_xy)):
|
||||
top_z[i] = pts[inverse == i][:, 2].max()
|
||||
else:
|
||||
unique_xy = pts[:, :2]
|
||||
top_z = pts[:, 2]
|
||||
|
||||
supports = []
|
||||
for (x, y), z in zip(unique_xy, top_z):
|
||||
height = z - min_z
|
||||
if height <= 0:
|
||||
continue
|
||||
cyl = tm.creation.cylinder(
|
||||
radius=support_radius, height=height, sections=sections
|
||||
)
|
||||
cyl.apply_translation([0, 0, height / 2])
|
||||
cyl.apply_translation([x, y, min_z])
|
||||
supports.append(cyl)
|
||||
|
||||
if not supports:
|
||||
return tm.Trimesh()
|
||||
return tm.util.concatenate(supports)
|
||||
|
||||
|
||||
__all__ = ["generate_scaffolding"]
|
||||
14
tests/test_scaffolding.py
Normal file
14
tests/test_scaffolding.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import pytest
|
||||
from parametric_cad import Box, combine, generate_scaffolding
|
||||
|
||||
|
||||
def test_scaffolding_generation():
|
||||
base = Box(2.0, 2.0, 1.0)
|
||||
overhang = Box(1.0, 1.0, 0.5).at(1.5, 0.5, 1.0)
|
||||
mesh = combine([base, overhang])
|
||||
scaff = generate_scaffolding(mesh)
|
||||
assert isinstance(scaff, type(mesh))
|
||||
# Should extend from model base upwards
|
||||
assert scaff.bounds[0, 2] == pytest.approx(mesh.bounds[0, 2])
|
||||
assert scaff.bounds[1, 2] <= mesh.bounds[1, 2]
|
||||
assert scaff.vertices.shape[0] > 0
|
||||
Reference in New Issue
Block a user