- 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>
12 KiB
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 |
|
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 anexecute_rhino_codescript 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*Wzwill always just giveOz— 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 aPoint3d[]of hit points directly, not an array of ray parameters — don't callray.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 withrs.InsertBlock2(name, xform)wherexformisTransform.Translation(base_point)to place it back at the original location — inserting withTransform.Identityputs 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 withrs.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 loopingrs.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 withrs.LayerVisible(parent)ANDrs.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 toTrue) — don't leave the document in an isolated state, since the user may look at the live Rhino viewport themselves between turns. rs.AddBlockre-bases raw geometry relative tobase_point, but does NOT re-base nested block-instance objects — when the objects passed tors.AddBlockare themselvesInstanceReferences (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 withTransform.Translation(base_point)then double-applies the offset (coordinates come out roughly doubled). For a wrapper block made of existing instances, insert withTransform.Identityinstead. For a block made from raw curves/breps freshly created atbase_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, checkidef.UseCount()(no arguments — the 1-arg overload doesn't exist here), and callsc.doc.InstanceDefinitions.Delete(idef.Index, True, True)for any with a zero count. - Whether a freshly-
AddBlock-ed instance needsTransform.IdentityorTransform.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 withIdentity, check the resulting bbox's X against the known expected world X (from the pre-bundlebase_point); if off by more than ~1 unit, delete that instance and reinsert withTranslation(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. fromrs.InsertBlock2used just to check a definition's position) immediately — a leftover one keeps that definition'sUseCount()above zero and defeats the purge. Rhino.Geometry.PipeCapMode.Noneis a syntax error (invalid syntax, not a runtime error) —Noneis a reserved Python keyword and can't follow a dot as an attribute name. The actual enum member for "no cap" is spelledRhino.Geometry.PipeCapMode.NONE(all-caps) in this RhinoCommon build — check withdir(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 viaModifyGeometry. 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, usebrep.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 theObjectAttributesof every object in that definition — including ones passed through unchanged by reference — toColorSource=ColorFromLayeron whatever incidental layer index the definition happened to store (seen: layerP1_두부(코핑), not the mark's own rebar layer). The correct/original state (confirmed by comparing against an untouched box-def, e.g.C12_1005_01) isColorSource=ColorFromParenton 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-onlyModifyGeometryoverload (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)— whereattributesListis a matching list ofRhino.DocObjects.ObjectAttributes()with.ColorSource = Rhino.DocObjects.ObjectColorSource.ColorFromParentset on each (this overload exists; confirmed viaInstanceDefinitionTable.ModifyGeometry.__doc__listing 3 overloads including(Int32, IEnumerable<GeometryBase>, IEnumerable<ObjectAttributes>)). Going forward, always pass an attributes list alongside geometry on anyModifyGeometrycall 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_r3inside theC13definition up in Z),rs.TransformObject/sc.doc.Objects.Transformon the id fromidef.GetObjectIds()silently no-ops (returnsNone, bbox unchanged) — those ids are not transformable as live doc objects. The working method:objs = idef.GetObjects(); for eacho,geo = o.Geometry(anInstanceReferenceGeometrywhose.Xformis the placement transform and.ParentIdefIdnames the nested sub-block); build a new list where the target entries are replaced byRhino.Geometry.InstanceReferenceGeometry(geo.ParentIdefId, Rhino.Geometry.Transform.Translation(0,0,dz) * geo.Xform)and all others keep the originalgeo; then callsc.doc.InstanceDefinitions.ModifyGeometry(idef.Index, newlist)(note:.Indexnot.Id— passing the Guid throws a type-conversion error since the method wants the integer index) andsc.doc.Views.Redraw(). Mechanically verified working (the.Indexvs.Idcall 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 thisModifyGeometry/.Indexmechanism 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 viasc.doc.InstanceDefinitions.FindId(geo.ParentIdefId).Name(not.Find(guid)— that overload wants a string and throws).