102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
"""Example 15: export an internal cached mesh as OBJ.
|
|
|
|
Run from the repository root with:
|
|
uv run python examples/15_cached_mesh_obj_export.py
|
|
|
|
This is a developer-facing example for the mesh-cache groundwork. It
|
|
intentionally does not call the public STL exporter. Instead it builds a normal
|
|
SimpleCAD solid, reads the framework's internal cached mesh, and writes a common
|
|
Wavefront OBJ mesh file from that pure triangle data.
|
|
|
|
Application code should not depend on ``simplecadapi._mesh``. Future structural
|
|
checking APIs will consume the same internal mesh cache without exposing mesh
|
|
extraction to framework users.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import simplecadapi as scad
|
|
import simplecadapi._mesh as internal_mesh
|
|
|
|
|
|
OUT_DIR = Path("examples/out/cached_mesh_obj_export")
|
|
|
|
|
|
def build_demo_solid() -> scad.Solid:
|
|
"""Build a small bracket-like solid using only normal modeling APIs."""
|
|
|
|
base = scad.make_box_rsolid(
|
|
width=34.0,
|
|
height=20.0,
|
|
depth=6.0,
|
|
bottom_face_center=(0.0, 0.0, 0.0),
|
|
)
|
|
through_hole = scad.make_cylinder_rsolid(
|
|
radius=4.0,
|
|
height=12.0,
|
|
bottom_face_center=(0.0, 0.0, -3.0),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
mount_slot = scad.make_box_rsolid(
|
|
width=8.0,
|
|
height=24.0,
|
|
depth=10.0,
|
|
bottom_face_center=(10.0, 0.0, -2.0),
|
|
)
|
|
boss = scad.make_cylinder_rsolid(
|
|
radius=7.0,
|
|
height=5.0,
|
|
bottom_face_center=(-10.0, 0.0, 6.0),
|
|
axis=(0.0, 0.0, 1.0),
|
|
)
|
|
|
|
bracket = scad.cut_rsolid(
|
|
base,
|
|
through_hole,
|
|
mount_slot,
|
|
skip_non_intersecting=False,
|
|
)
|
|
bracket = scad.union_rsolid([bracket, boss])
|
|
return scad.apply_tag(shape=bracket, tag="role.cached_mesh_obj_demo")
|
|
|
|
|
|
def write_cached_mesh_obj(solid: scad.Solid, path: Path) -> internal_mesh.TriMesh:
|
|
"""Write a Solid's internal cached mesh to Wavefront OBJ."""
|
|
|
|
mesh = internal_mesh.cached_mesh(solid)
|
|
if mesh is None:
|
|
detail = internal_mesh.mesh_error(solid) or "no internal mesh cache"
|
|
raise RuntimeError(f"Solid has no cached mesh: {detail}")
|
|
|
|
lines = [
|
|
"# OBJ written from SimpleCAD internal cached mesh",
|
|
"# This example intentionally bypasses scad.export_stl(...).",
|
|
]
|
|
for x, y, z in mesh.vertices:
|
|
lines.append(f"v {x:.9g} {y:.9g} {z:.9g}")
|
|
for a, b, c in mesh.triangles:
|
|
lines.append(f"f {int(a) + 1} {int(b) + 1} {int(c) + 1}")
|
|
|
|
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
return mesh
|
|
|
|
|
|
def main() -> None:
|
|
OUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
solid = build_demo_solid()
|
|
obj_path = OUT_DIR / "cached_mesh_bracket.obj"
|
|
mesh = write_cached_mesh_obj(solid=solid, path=obj_path)
|
|
lower, upper = mesh.bounds
|
|
|
|
print("volume", round(solid.get_volume(), 3))
|
|
print("faces", len(solid.get_faces()))
|
|
print("mesh", f"vertices={mesh.vertex_count}", f"triangles={mesh.triangle_count}")
|
|
print("bounds", f"min={tuple(round(v, 3) for v in lower)}", f"max={tuple(round(v, 3) for v in upper)}")
|
|
print("wrote_obj", obj_path)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|