AI Flow
Public Made by Adomby adom
Adom's AI Flow: a tool to help the AI follow all of the steps it takes to build a board.
Prevent laser-mark STEP flattening from collapsing repeated terminals #30
fix/repeated-step-terminal-guard → mainSTEP files with two occurrences of one child inside the same assembly were silently corrupted by the laser-mark planner: flatten() keyed placement by child representation and only rejected different parents. The second terminal's transform replaced the first, putting both capacitor terminals on one side.
This patch rejects every repeated child before mutating frames or geometry, matching the function's existing documented unsupported-instancing contract. It preserves the working single-occurrence transform path. It is a fail-closed repair, not new support for flattening repeated instances; an assembly-preserving mark workflow or occurrence expansion remains the appropriate follow-up.
Reproduced on Panasonic EEE-FK1V470P's manufacturer DS_Alumi_D STEP (original SHA-256 106f4f2536962b705e711953ebb791b3bf960613fa523da4aafc93386a18a86b). The preserved original and original GLB have opposing terminals; the prior marked STEP and hero contain overlapping terminal occurrences. The component page is repaired by retaining the original XCAF assembly and adding only the marking sheet: https://wiki.adom.inc/john/eee-fk1v470p
Base: fdc07ea6f58626f3c4fd2249d0a8702e12eff5e7. Two Python files only; no Rust, bridge, runtime or release changes. Current immutable base blob compared; tools/pr_preflight.py passed and every diff hunk reviewed.
Validation: python3 -m unittest discover -s tools/laser-etch -p test_step_instances.py -v: 3 passed (same-parent instances, different-parent instances, single-occurrence transform/idempotence). Manufacturer fixture also refuses before mutation. Component repair separately passes native OpenCascade STEP solid validity, bounds, volume and Boolean intersection checks and decoded GLB world-coordinate terminal checks. No native Fusion/Altium/KiCad application-import claim.
Maintainer follow-up: merge and release the guard, update chip-thumbnailer's vendored planner/step_text.py, and support occurrence-preserving mark composition so instanced manufacturer models can be processed without flattening. The marked component itself is rebuilt without the damaged flattener. This PR is not deployed to installed packages or shared runtimes.
Diff Skip to comments (1)
tools/laser-etch/step_text.py+5−3@@ -254,8 +254,6 @@ class Step: """Bake assembly transforms (and a root representation's own frame, which OpenCascade applies as a placement) into the geometry and reset every frame to the identity. Afterwards local == world, so turning the part and moving the mark are plain coordinate edits. Refuses instanced parts."""- frames = self._isolate_frames()- fents = self._frame_ents(frames) rels = [] # (child rep, parent rep, child frame, parent frame) for k in self.order: b = self.body[k]@@ -269,10 +267,14 @@ class Step: raise SystemExit("step_text: MAPPED_ITEM instancing is not supported; pass a flattened STEP") parent = {} for c, p, a1, a2 in rels:- if c in parent and parent[c][0] != p:+ if c in parent: raise SystemExit("step_text: an instanced sub-shape (used twice) cannot be flattened as text") T = self._mul(self._mat(self._frame(a2)), self._inv(self._mat(self._frame(a1)))) parent[c] = (p, T)+ # Check all occurrences before isolating frames or modifying any geometry.+ # The same child can appear twice in ONE parent (e.g. opposing capacitor leads).+ frames = self._isolate_frames()+ fents = self._frame_ents(frames) reps = [k for k in self.order if (self.kind(k).endswith("REPRESENTATION") and "SHAPE" in self.kind(k)) or self.kind(k) == "SHAPE_REPRESENTATION"] reps = [r for r in reps if self.kind(r) not in ("SHAPE_DEFINITION_REPRESENTATION", "CONTEXT_DEPENDENT_SHAPE_REPRESENTATION")] # SRR-linked reps (shape rep <-> its brep) share the frame of their partner
tools/laser-etch/test_step_instances.pyadded+47@@ -0,0 +1,47 @@+"""The same child repeated inside one parent must never be flattened onto one placement."""+import unittest+from step_text import Step++HEADER="ISO-10303-21; HEADER; ENDSEC; DATA;\n"+TAIL="\nENDSEC; END-ISO-10303-21;"++def fixture(two=True, other_parent=False):+ s="""+#1=CARTESIAN_POINT('',(0.,0.,0.));+#2=DIRECTION('',(0.,0.,1.));+#3=DIRECTION('',(1.,0.,0.));+#4=AXIS2_PLACEMENT_3D('',#1,#2,#3);+#5=CARTESIAN_POINT('',(-2.,0.,0.));+#6=AXIS2_PLACEMENT_3D('',#5,#2,#3);+#7=CARTESIAN_POINT('',(2.,0.,0.));+#8=AXIS2_PLACEMENT_3D('',#7,#2,#3);+#9=CARTESIAN_POINT('',(0.,1.,0.));+#10=SHAPE_REPRESENTATION('',(#4,#9),#99);+#11=SHAPE_REPRESENTATION('',(#4,#6,#8),#99);+#12=ITEM_DEFINED_TRANSFORMATION('','',#4,#6);+#13=(REPRESENTATION_RELATIONSHIP('','',#10,#11) REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#12) SHAPE_REPRESENTATION_RELATIONSHIP());+#99=GEOMETRIC_REPRESENTATION_CONTEXT(3);+"""+ if two:+ s+="""+#14=ITEM_DEFINED_TRANSFORMATION('','',#4,#8);+#15=(REPRESENTATION_RELATIONSHIP('','',#10,#PARENT) REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION(#14) SHAPE_REPRESENTATION_RELATIONSHIP());+""".replace('#PARENT','#16' if other_parent else '#11')+ if other_parent:s+="#16=SHAPE_REPRESENTATION('',(#4,#8),#99);"+ return Step(HEADER+s+TAIL)++class Instances(unittest.TestCase):+ def test_same_parent_occurrences_fail_without_mutation(self):+ s=fixture();before=s.text+ with self.assertRaisesRegex(SystemExit,'instanced'):s.flatten()+ self.assertEqual(before,s.text)+ def test_different_parent_occurrences_fail_without_mutation(self):+ s=fixture(other_parent=True);before=s.text+ with self.assertRaisesRegex(SystemExit,'instanced'):s.flatten()+ self.assertEqual(before,s.text)+ def test_single_occurrence_still_bakes_placement(self):+ s=fixture(two=False);s.flatten()+ self.assertEqual(s.coords(9),[-2.,1.,0.])+ before=s.text;s.flatten();self.assertEqual(s.coords(9),[-2.,1.,0.])++if __name__=='__main__':unittest.main()
Comments
Log in to comment.