Files
Pier_v1/context-migration/memory/feedback_ironpython_exec_scoping.md
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

12 KiB

name, description, metadata
name description metadata
feedback-ironpython-exec-scoping RhinoMCP execute_rhino_code (IronPython 2.7) scoping gotchas that caused repeated "name X is not defined" errors — avoid nested def/comprehensions and remember Z is independent of horizontal L/W axes
node_type type originSessionId modified
memory feedback d49157ae-9661-437c-a1d1-639728d3fff0 2026-09-21T08:00:01.071Z

When writing Python for mcp__rhino__execute_rhino_code (IronPython 2.7, run via exec with separate globals/locals):

Why: the code runs with exec(code, globals, locals) where globals != locals. Any def-ined function or list/dict/set comprehension created inside the script cannot see the script's own top-level local variables — only globals. This repeatedly produced name 'X' is not defined errors even though X was clearly assigned earlier in the same script.

How to apply:

  • Never define a helper def function(...) inside an execute_rhino_code script if it needs to reference other local variables from that script — inline the logic instead, or recompute needed values inside the function from parameters only.
  • Avoid list/dict comprehensions that reference outer local variables (e.g. [pts[i].DistanceTo(pts[i+1]) for i in range(3)] inside the same script scope) — same failure mode. Use an explicit loop or separate statements instead.
  • When building 3D points from a rotated local coordinate frame (length axis L, width axis W) that lie flat in a horizontal plane (Lz = Wz = 0, as is the case for project_p1_pier_structure's skewed-in-plan coping), the formula Z = Oz + s*Lz + w*Wz will always just give Oz — Z must be set explicitly per point, it is not derivable from the L/W parametrization.
  • Rhino.Geometry.Intersect.Intersection.RayShoot(ray, [geometry], maxReflections) returns a Point3d[] of hit points directly, not an array of ray parameters — don't call ray.PointAt(hit) on the results.
  • rs.AddBlock(object_ids, base_point, name, delete_input) only defines the block and deletes the source objects; it does not insert an instance. Follow it with rs.InsertBlock2(name, xform) where xform is Transform.Translation(base_point) to place it back at the original location — inserting with Transform.Identity puts it at world origin instead.
  • To hide a solid brep so embedded/buried curves or pipes become visible, use Wireframe display mode (or hide the specific object) — Shaded mode's opaque surface hides anything inside it regardless of layer visibility toggles in some cases.
  • Nested layer visibility is gated by the parent. Setting rs.LayerVisible("Parent::Child", True) has no visible effect if "Parent" itself is off — worse, turning a parent off with rs.LayerVisible("Parent", False) can also flip the child's own visibility flag to False as a side effect. When isolating a nested rebar mark layer (e.g. P1_두부_주철근_1CYCLE::C26_H19) by looping rs.LayerNames() and turning everything off except a keep-list, always add the PARENT layer name to the keep-list too, not just the full child path — otherwise the mark silently fails to render even though the objects exist and its own layer flag reads True. This caused repeated "rebar isn't visible" reports from the user in one session (project_p1_pier_structure) before being traced to this cause — always verify with rs.LayerVisible(parent) AND rs.LayerVisible(child) when rebar seems to vanish, before suspecting a placement/scale bug.
  • After any isolate-for-screenshot operation, restore ALL layer visibility immediately afterward (loop rs.LayerNames() and set every one to True) — don't leave the document in an isolated state, since the user may look at the live Rhino viewport themselves between turns.
  • rs.AddBlock re-bases raw geometry relative to base_point, but does NOT re-base nested block-instance objects — when the objects passed to rs.AddBlock are themselves InstanceReferences (e.g. bundling several already-placed block instances into one wrapper block), their world-coordinate transform is captured as-is inside the new definition. Re-inserting with Transform.Translation(base_point) then double-applies the offset (coordinates come out roughly doubled). For a wrapper block made of existing instances, insert with Transform.Identity instead. For a block made from raw curves/breps freshly created at base_point, Transform.Translation(base_point) is correct (this is the case in feedback_coping_haunch_rebar_method's leaf-level boxes). When in doubt, insert once and check the resulting bounding box against the expected world coordinates before moving on.
  • To bulk-remove unused/orphaned block definitions (e.g. after re-generating geometry and leaving old block names behind), loop sc.doc.InstanceDefinitions, check idef.UseCount() (no arguments — the 1-arg overload doesn't exist here), and call sc.doc.InstanceDefinitions.Delete(idef.Index, True, True) for any with a zero count.
  • Whether a freshly-AddBlock-ed instance needs Transform.Identity or Transform.Translation(base_point) on reinsertion is NOT reliably predictable when the bundled objects are themselves block instances (as opposed to raw curves/breps) — it varies between calls in the same session for reasons not pinned down (possibly stale/colliding block-definition names from earlier failed attempts, seen once as a confirmed cause). The robust pattern: insert with Identity, check the resulting bbox's X against the known expected world X (from the pre-bundle base_point); if off by more than ~1 unit, delete that instance and reinsert with Translation(base_point) instead. Always verify this way rather than assuming either transform is correct. A large constant (non-varying) Z across many wrapped stations is not automatically a bug — check whether that span of the structure is genuinely flat (e.g. sits on a flat bearing shelf) before assuming the transform is broken.
  • Before reusing a block base-name across repeated build/rebuild attempts, purge stale unused definitions matching that name pattern first (see the UseCount()-zero purge above) — leftover definitions from an earlier failed attempt can collide with or shadow a later rebuild using the same names and produce corrupted (wrong-position) instances. This bit twice in the same session (C18's station 1/19, then C15's entire station level) — the safe sequence every time a mark gets rebuilt at the same station-numbering scheme is: (1) delete the mark's layer objects, (2) explicitly purge every definition whose name starts with that mark (idef.Name.startswith("C15") etc, not just an exact-name match — the per-station and per-box-type sub-block names share the prefix), (3) only then regenerate. Also always delete throwaway verification instances (e.g. from rs.InsertBlock2 used just to check a definition's position) immediately — a leftover one keeps that definition's UseCount() above zero and defeats the purge.
  • Rhino.Geometry.PipeCapMode.None is a syntax error (invalid syntax, not a runtime error) — None is a reserved Python keyword and can't follow a dot as an attribute name. The actual enum member for "no cap" is spelled Rhino.Geometry.PipeCapMode.NONE (all-caps) in this RhinoCommon build — check with dir(Rhino.Geometry.PipeCapMode) if unsure rather than guessing the casing.
  • To lengthen just one leg of an existing ⊓/⊔ box leaf (e.g. extending a lid's leg while its overlap point with a separate base piece must stay fixed — see project_p1_pier_structure's C13/14/15/16/17 lid-extend fix), don't rigidly translate the whole Brep. Instead: duplicate the Brep, translate the duplicate by the desired offset, then bridge the gap with a new straight filler pipe from the original (fixed) end point to the shifted end point (Brep.CreatePipe(LineCurve(Line(p, p_shifted)), radius, False, PipeCapMode.NONE, False, tol, angleTol)), and put [shifted_original, ...unrelated_pieces_unchanged, filler1, filler2] back into the definition via ModifyGeometry. This preserves the exact original bend/corner geometry (never touched) and the exact original overlap length with any neighboring piece, while genuinely adding real bar length at the correct end — the general pattern is "shift + fill the gap with a new straight segment," not "stretch/scale the existing solid." To find each leg's exact fixed end point, use brep.Vertices — for a simple open-ended (uncapped) pipe swept along a bent rail with no sharp corners, the topological vertex list contains exactly the 2 open-end points (both located at the Brep's own bbox Zmin if it opens downward, or Zmax if it opens upward) and nothing else; this also doubles as a fast way to tell two overlapping box pieces apart (the one whose vertices sit at ITS OWN Zmin opens downward/"lid"-style, the one whose vertices sit at ITS OWN Zmax opens upward/"base"-style).
  • ROOT CAUSE of "rebar color keeps not matching its diameter after any edit" (recurring user complaint): InstanceDefinitions.ModifyGeometry(index, geometryList) called with the 2-arg (geometry-only) overload silently RESETS the ObjectAttributes of every object in that definition — including ones passed through unchanged by reference — to ColorSource=ColorFromLayer on whatever incidental layer index the definition happened to store (seen: layer P1_두부(코핑), not the mark's own rebar layer). The correct/original state (confirmed by comparing against an untouched box-def, e.g. C12_1005_01) is ColorSource=ColorFromParent on every leaf object — that's what makes a nested block instance actually display the mark layer's diameter-based color (e.g. H22=dark red 104,0,25) instead of whatever the leaf's own stored layer says. Any use of the geometry-only ModifyGeometry overload (as used throughout project_p1_pier_structure's C13/14/15/16/17 lid-extend fix on 2026-09-21) silently breaks color for every object in that definition, not just newly-added ones. Fix (queued for project_p1_pier_structure's next session, not yet applied): use the 3-arg overload instead — InstanceDefinitions.ModifyGeometry(idef.Index, geometryList, attributesList) — where attributesList is a matching list of Rhino.DocObjects.ObjectAttributes() with .ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromParent set on each (this overload exists; confirmed via InstanceDefinitionTable.ModifyGeometry.__doc__ listing 3 overloads including (Int32, IEnumerable<GeometryBase>, IEnumerable<ObjectAttributes>)). Going forward, always pass an attributes list alongside geometry on any ModifyGeometry call on these rebar box-defs, never the bare 2-arg form, to avoid re-breaking color on every future edit.
  • To rigidly move ONE station's sub-block within a nested "mark" block definition (e.g. shift just C13_13_r3 inside the C13 definition up in Z), rs.TransformObject/sc.doc.Objects.Transform on the id from idef.GetObjectIds() silently no-ops (returns None, bbox unchanged) — those ids are not transformable as live doc objects. The working method: objs = idef.GetObjects(); for each o, geo = o.Geometry (an InstanceReferenceGeometry whose .Xform is the placement transform and .ParentIdefId names the nested sub-block); build a new list where the target entries are replaced by Rhino.Geometry.InstanceReferenceGeometry(geo.ParentIdefId, Rhino.Geometry.Transform.Translation(0,0,dz) * geo.Xform) and all others keep the original geo; then call sc.doc.InstanceDefinitions.ModifyGeometry(idef.Index, newlist) (note: .Index not .Id — passing the Guid throws a type-conversion error since the method wants the integer index) and sc.doc.Views.Redraw(). Mechanically verified working (the .Index vs .Id call itself succeeds and redraws correctly), but this whole-sub-block rigid-move was the WRONG fix for project_p1_pier_structure's C13/14/15/16/17 datum-raise — it was tried first, then reverted, because those stations are lid+base overlapping boxes and rigidly moving the whole thing also drags the base (which must stay put); see the lid-extend bullet above for the corrected approach. Keep this ModifyGeometry/.Index mechanism in mind for cases where a genuine whole-station rigid move IS correct (e.g. no base/lid split, or intentionally repositioning an entire self-contained station). Identify which sub-block belongs to which named station via sc.doc.InstanceDefinitions.FindId(geo.ParentIdefId).Name (not .Find(guid) — that overload wants a string and throws).