88 lines
2.9 KiB
Python
88 lines
2.9 KiB
Python
import simplecadapi as scad
|
|
import simplecadapi._mesh as _mesh
|
|
|
|
|
|
def _write_cached_mesh_as_obj(solid, path):
|
|
mesh = _mesh.cached_mesh(solid)
|
|
assert mesh is not None
|
|
|
|
lines = ["# SimpleCAD internal cached mesh OBJ"]
|
|
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 test_solid_creation_attaches_internal_mesh_cache():
|
|
solid = scad.make_box_rsolid(width=10.0, height=20.0, depth=30.0)
|
|
|
|
mesh = _mesh.cached_mesh(solid)
|
|
|
|
assert mesh is not None
|
|
assert _mesh.mesh_error(solid) is None
|
|
assert mesh.vertex_count > 0
|
|
assert mesh.triangle_count > 0
|
|
assert mesh.vertices.shape[1] == 3
|
|
assert mesh.triangles.shape[1] == 3
|
|
assert int(mesh.triangles.min()) >= 0
|
|
assert int(mesh.triangles.max()) < mesh.vertex_count
|
|
|
|
|
|
def test_internal_mesh_preserves_face_triangle_ranges():
|
|
solid = scad.make_box_rsolid(width=10.0, height=20.0, depth=30.0)
|
|
|
|
mesh = _mesh.cached_mesh(solid)
|
|
|
|
assert mesh is not None
|
|
|
|
ranges = mesh.face_triangle_ranges
|
|
assert len(ranges) == len(solid.get_faces())
|
|
assert sum(item.count for item in ranges) == mesh.triangle_count
|
|
assert [item.face_index for item in ranges] == list(range(len(ranges)))
|
|
assert all(item.source_topo_id for item in ranges)
|
|
|
|
|
|
def test_internal_mesh_bounds_track_solid_extent():
|
|
solid = scad.make_box_rsolid(width=10.0, height=20.0, depth=30.0)
|
|
|
|
mesh = _mesh.cached_mesh(solid)
|
|
assert mesh is not None
|
|
|
|
lower, upper = mesh.bounds
|
|
assert lower == (-5.0, -10.0, 0.0)
|
|
assert upper == (5.0, 10.0, 30.0)
|
|
|
|
|
|
def test_transformed_solid_keeps_mesh_cache_on_transformed_geometry():
|
|
solid = scad.make_box_rsolid(width=10.0, height=20.0, depth=30.0)
|
|
|
|
translated = scad.translate_shape(shape=solid, vector=(1.0, 2.0, 3.0))
|
|
mesh = _mesh.cached_mesh(translated)
|
|
|
|
assert mesh is not None
|
|
lower, upper = mesh.bounds
|
|
assert lower == (-4.0, -8.0, 3.0)
|
|
assert upper == (6.0, 12.0, 33.0)
|
|
|
|
|
|
def test_mesh_internals_are_not_top_level_public_api():
|
|
assert not hasattr(scad, "tessellate_rmesh")
|
|
assert "tessellate_rmesh" not in scad.__all__
|
|
assert "TriMesh" not in scad.__all__
|
|
|
|
|
|
def test_cached_mesh_can_export_obj_without_public_stl_api(tmp_path):
|
|
solid = scad.make_cylinder_rsolid(radius=3.0, height=5.0)
|
|
output_path = tmp_path / "cached_mesh.obj"
|
|
|
|
mesh = _write_cached_mesh_as_obj(solid, output_path)
|
|
|
|
content = output_path.read_text(encoding="utf-8")
|
|
vertex_lines = [line for line in content.splitlines() if line.startswith("v ")]
|
|
face_lines = [line for line in content.splitlines() if line.startswith("f ")]
|
|
assert len(vertex_lines) == mesh.vertex_count
|
|
assert len(face_lines) == mesh.triangle_count
|
|
assert content.startswith("# SimpleCAD internal cached mesh OBJ\n")
|