a76879f4a03862be897d3bfbfbf11fe23f5d2316
1
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0545b79674 |
stl: export a member as a sealed mesh
prism_mesh takes a region and a length, not a Result. A member, its support, or the support alone are then three calls with different regions rather than three special cases inside one function -- support is a designed part of an artifact where no printable orientation exists, not something a slicer adds, and flattening everything into one mesh now would be expensive to undo later. Named and documented as a MEMBER exporter. ROADMAP section 2 has three artifact classes: members are prismatic and exist, nodes are non-prismatic and do not. A prism sweep will never produce a node and no amount of extending this should be attempted. A module called stl.py would invite exactly that. No CAD kernel. Caps are a constrained Delaunay triangulation of the section with its holes, walls are a quad strip per ring. That is why STL costs no new dependency while STEP would. Winding is forced, never assumed. region_parts returns each part outer-clockwise with holes counter-clockwise because region_area depends on that to make holes subtract; a prism swept along +Z needs the opposite, since traversing the outer boundary counter-clockwise puts material on the left and the wall normal on the right, which is outward. Both are reversed here. The identifiers in the 80-byte header are PROVENANCE, NOT A CHECKSUM. Principle 4: mesh bytes are not reproducible across toolchain versions, so two exports carrying the same build_id may differ byte for byte and both be correct -- a GEOS release can move a triangulation without moving the geometry, which is the F-034 mechanism applied to tessellation. The docstring says so where someone diffing two exports will find it. Facet normals are written as zeros: every consumer recomputes from vertex order, and a stored normal would be a second source of truth for which way a face points. A section failing is_region_simple is refused rather than exported. Its own docstring names the case: an outline that measures perfectly, touches itself at a point, and cannot be sealed. Emitting it would produce a file that opens in a slicer and prints wrong. member_stl defaults to the report's LENGTH_MM, the value the record's VOLUME_MM3 and MASS_G were computed against. Any other sweep makes the record describe a different object than the file beside it. 61 tests across all eleven profiles in both families, four properties each, every negative assertion with a positive control. Edge pairing catches a missing wall and inconsistent winding; Euler catches a filled or invented hole; area overlap catches a bore paved over; volume against region_area times length catches inverted normals and a wrong length together. Two test defects found by mutation and fixed before landing, both worth recording because both passed while being wrong. The hole check originally tested triangle centroids. Ignoring holes at triangulation time slipped through it -- two triangles can pave a bore with both centroids outside it. Replaced with intersection area, which catches it. The sweep-length test built at the family defaults, where preview_length_mm is 100, so a mutation hardcoding 100.0 passed the entire suite: the assertion was comparing a value against the constant that had replaced it. Now parametrised over 37.5 mm and 3048 mm, and proven against hardcoded mutations at both values, each caught by the other case. Mutation results against the real sections: holes not reversed fails 23, exterior-only triangulation fails 45, outer ring not forced CCW fails 25, simplicity precondition removed fails exactly 1 -- the refusal test written for it. Restoration verified by checksum rather than by a green run, after a same-length mutation plus a cached .pyc produced a false clean reading earlier in the session. PYTHONDONTWRITEBYTECODE=1 throughout, per HANDOFF section 8. Suite 627 passed. None of the 566 moved. |