From 61a44229b8f40cc6144fbcc61f89f1364342adc1 Mon Sep 17 00:00:00 2001 From: ylzha Date: Sat, 30 May 2026 21:04:12 -0400 Subject: [PATCH] Insulate body-finding behind sp (increment 1 of API-containment) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Part of making the `sp` layer a deliberate insulation boundary: route the raw Fusion-API uses in templates through named helpers so each quirk has ONE implementation site to maintain when the API drifts (instead of N copies across templates). This increment: body-finding. Six templates each carried a private `_all_bodies` / `_find_body` loop over `comp.bRepBodies` + name matching — the exact pattern the audit flagged as fragile (name collisions after mirror/pattern renames, proxy vs native). Added standalone `sp.bodies_in` / `sp.find_body` / `sp.find_bodies` (thin wrappers over the already-canonical `_util` recursive helpers that `DesignContext` uses), and routed drawbore, tusk_tenon, splayed_legs, domino, scarf_joint, bed_rail_fastener through them. No raw `.bRepBodies` iteration remains in those files. Behavior-preserving by construction: `bodies_in` returns the same shallow list as `_all_bodies`; `find_body` is `_find_body_recursive` (checks the component's own bodies first, then descendants — the same fallback the locals already used). The fragile name-based identification now lives in one hardenable place. py_compile clean. The template Fusion fixtures (tests/test_template_*) are the regression guard and should be run in Fusion to confirm. Co-Authored-By: Claude Opus 4.8 (1M context) --- helpers/sp/context.py | 32 ++++++++++++++++++++++ woodworking/templates/bed_rail_fastener.py | 4 +-- woodworking/templates/domino.py | 9 ++---- woodworking/templates/drawbore.py | 5 ++-- woodworking/templates/scarf_joint.py | 6 +--- woodworking/templates/splayed_legs.py | 8 ++---- woodworking/templates/tusk_tenon.py | 2 +- 7 files changed, 43 insertions(+), 23 deletions(-) diff --git a/helpers/sp/context.py b/helpers/sp/context.py index 74a550e..4329a30 100644 --- a/helpers/sp/context.py +++ b/helpers/sp/context.py @@ -42,3 +42,35 @@ def find_bodies(self, pattern, component=None): results = [] _collect_bodies_recursive(comp, pattern, results) return results + + +# ── Standalone body lookups (no DesignContext instance needed) ─────────────── +# These are the ONE site for raw `bRepBodies` iteration / name-based body +# identification. Templates call these instead of rolling their own +# `_all_bodies`/`_find_body` loops, so the fragile bits (name collisions after +# mirror/pattern renames, proxy vs native) live in a single hardenable place. + +def bodies_in(comp, recursive=False): + """All bodies in a component. Shallow by default (the component's own + bodies — matches the old per-template `_all_bodies`); pass recursive=True to + walk descendants.""" + if recursive: + out = [] + _collect_bodies_recursive(comp, "*", out) + return out + return [comp.bRepBodies.item(i) for i in range(comp.bRepBodies.count)] + + +def find_body(name, comp): + """Find a body by exact name within `comp` (checks the component's own + bodies first, then descendants). Canonical replacement for local + `_find_body` helpers.""" + return _find_body_recursive(comp, name) + + +def find_bodies(pattern, comp): + """Bodies whose name matches a glob `pattern` within `comp` (walks + descendants).""" + out = [] + _collect_bodies_recursive(comp, pattern, out) + return out diff --git a/woodworking/templates/bed_rail_fastener.py b/woodworking/templates/bed_rail_fastener.py index d43264a..0de7c86 100644 --- a/woodworking/templates/bed_rail_fastener.py +++ b/woodworking/templates/bed_rail_fastener.py @@ -76,8 +76,8 @@ def _get_or_import(part_id, step_file): return None tmpl_occ = imported[0][0] tmpl_occ.isLightBulbOn = False - for bi in range(tmpl_occ.component.bRepBodies.count): - tmpl_occ.component.bRepBodies.item(bi).isVisible = False + for b in sp.bodies_in(tmpl_occ.component): + b.isVisible = False _plate_cache[part_id] = tmpl_occ hw_mgr._hardware_occurrences.append((tmpl_occ, root)) return hw_mgr._copy_from_template(tmpl_occ, root) diff --git a/woodworking/templates/domino.py b/woodworking/templates/domino.py index 43e38a9..65a91be 100644 --- a/woodworking/templates/domino.py +++ b/woodworking/templates/domino.py @@ -500,10 +500,5 @@ def four_corners(comp, plane, center, long_axis, long_expr, short_expr, def _find_body(comp, name): - """Find body by name in component (non-recursive, fast).""" - for i in range(comp.bRepBodies.count): - b = comp.bRepBodies.item(i) - if b.name == name: - return b - # Fall back to recursive search - return sp.DesignContext().find_body(name, comp) + """Find body by name in component (delegates to sp.find_body).""" + return sp.find_body(name, comp) diff --git a/woodworking/templates/drawbore.py b/woodworking/templates/drawbore.py index cfdf133..db3fe2d 100644 --- a/woodworking/templates/drawbore.py +++ b/woodworking/templates/drawbore.py @@ -281,5 +281,6 @@ def blind(comp, tenon_plane, tenon_plane_offset, tenon_origin, tenon_size, def _all_bodies(comp): - """Get all bodies in a component.""" - return [comp.bRepBodies.item(i) for i in range(comp.bRepBodies.count)] + """All bodies in a component (delegates to sp.bodies_in — the single site + for raw bRepBodies iteration).""" + return sp.bodies_in(comp) diff --git a/woodworking/templates/scarf_joint.py b/woodworking/templates/scarf_joint.py index d178ebf..6882824 100644 --- a/woodworking/templates/scarf_joint.py +++ b/woodworking/templates/scarf_joint.py @@ -241,11 +241,7 @@ def kanawa_tsugi(comp, body_a, body_b, splice_face, features.append(split_feat) # Find the two halves (everything except the two timbers) - halves = [] - for i in range(comp.bRepBodies.count): - b = comp.bRepBodies.item(i) - if b != body_a and b != body_b: - halves.append(b) + halves = [b for b in sp.bodies_in(comp) if b != body_a and b != body_b] # Sort: half closer to body_a = half_a a_center = _body_center(body_a, grain_axis) diff --git a/woodworking/templates/splayed_legs.py b/woodworking/templates/splayed_legs.py index 9654f8a..7f1e7f8 100644 --- a/woodworking/templates/splayed_legs.py +++ b/woodworking/templates/splayed_legs.py @@ -357,9 +357,5 @@ def define_stretcher_params(params, name, height_expr, def _find_body(comp, name): - """Find body by name in component.""" - for i in range(comp.bRepBodies.count): - b = comp.bRepBodies.item(i) - if b.name == name: - return b - return sp.DesignContext().find_body(name, comp) + """Find body by name in component (delegates to sp.find_body).""" + return sp.find_body(name, comp) diff --git a/woodworking/templates/tusk_tenon.py b/woodworking/templates/tusk_tenon.py index 6bae1d1..4a7f376 100644 --- a/woodworking/templates/tusk_tenon.py +++ b/woodworking/templates/tusk_tenon.py @@ -321,4 +321,4 @@ def _s(v): def _all_bodies(comp): - return [comp.bRepBodies.item(i) for i in range(comp.bRepBodies.count)] + return sp.bodies_in(comp) # single site for raw bRepBodies iteration