# -*- coding: utf-8 -*- # 05_realign_base_pieces.py (대호지교 P1, 2026-10-06 검증) # 목적: 수직전단철근 박스(C12/C14/C16/C18 등)에서 바닥(⊔) 조각이 뚜껑(⊓) 평면에서 s 방향으로 # 어긋난 것을 되돌린다. 피복(헌치 경사면에 수직인 거리)은 유지하려고 경사 접선 방향으로 움직이고, # 바닥 상단을 뚜껑 윗변 중심에서 다시 자른다(겹침 510 유지). # 실행: rhino_mcp execute_rhino_code (IronPython 2.7). MARK 하나씩 실행 (소켓 타임아웃 방지). # 주의: lambda/리스트컴프리헨션 금지(exec 스코프), f-string 금지. 결과는 JSON 파일로 쓰고 따로 읽는다. # 구조 가정: 마크 idef -> 스테이션 idef -> 박스 idef(리프 Brep: 뚜껑, 바닥, [연장 조각...]) import Rhino, json, io, os, math import scriptcontext as sc doc = sc.doc MARK = "C12" # C12 / C14 / C16 / C18 ... OX, OY = 480714926.0, 153833329.5 # 코핑 로컬 원점 (코핑 bbox 중심) ANG = math.atan2(4255.0, 12284.0) # 코핑 축 각도 (두 기둥 중심 연결선) # 헌치 경사 dz/ds (그 스테이션에서 콘크리트 밑면의 기울기): 끝단 헌치 ±0.375, 안쪽 헌치 ±1/3 SLOPES = {"C12": -0.375, "C18": 0.375, "C14": 1.0 / 3.0, "C16": -1.0 / 3.0} RBAR = 11.1 # 철근 반지름 (H22) TOL = 0.5 # 이보다 작은 어긋남은 건너뜀 ca = math.cos(ANG) sa = math.sin(ANG) k = SLOPES[MARK] folder = os.path.dirname(doc.Path) out = os.path.join(folder, "fix_result_" + MARK + ".json") inst = None for o in doc.Objects: g = o.Geometry if isinstance(g, Rhino.Geometry.InstanceReferenceGeometry): if doc.Layers[o.Attributes.LayerIndex].FullPath.endswith(MARK + "_H22"): inst = g mxf = inst.Xform midf = doc.InstanceDefinitions.FindId(inst.ParentIdefId) # 박스 idef 이름 -> 대표 인스턴스 월드 변환 boxT = {} for ob in midf.GetObjects(): og = ob.Geometry if not isinstance(og, Rhino.Geometry.InstanceReferenceGeometry): continue sidf = doc.InstanceDefinitions.FindId(og.ParentIdefId) sxf = mxf * og.Xform for b in sidf.GetObjects(): bg = b.Geometry if isinstance(bg, Rhino.Geometry.InstanceReferenceGeometry): bn = doc.InstanceDefinitions.FindId(bg.ParentIdefId).Name if bn not in boxT: boxT[bn] = sxf * bg.Xform report = [] names = list(boxT.keys()) names.sort() for bn in names: T = boxT[bn] ok, inv = T.TryGetInverse() bidf = doc.InstanceDefinitions.Find(bn) objs = bidf.GetObjects() geoms = [] attrs = [] info = [] # [s, area, bbox minZ, bbox maxZ] (월드) for ob in objs: bg = ob.Geometry amp = Rhino.Geometry.AreaMassProperties.Compute(bg) c = amp.Centroid c.Transform(T) s = (c.X - OX) * ca + (c.Y - OY) * sa w = bg.Duplicate() w.Transform(T) bbox = w.GetBoundingBox(True) info.append([s, bg.GetArea(), bbox.Min.Z, bbox.Max.Z]) geoms.append(bg.Duplicate()) attrs.append(ob.Attributes.Duplicate()) # ColorFromParent 보존 # 면적 큰 2개 = 뚜껑/바닥 (연장 조각은 작다). 뚜껑 = bbox 최저 z가 더 높은 쪽 i1 = -1 i2 = -1 for i in range(len(info)): if i1 < 0 or info[i][1] > info[i1][1]: i2 = i1 i1 = i elif i2 < 0 or info[i][1] > info[i2][1]: i2 = i if info[i1][2] > info[i2][2]: lid = i1 base = i2 else: lid = i2 base = i1 dev = info[base][0] - info[lid][0] row = {"box": bn, "n": len(info), "dev_before": round(dev, 3)} if abs(dev) <= TOL: row["action"] = "skip" report.append(row) continue bkname = bn + "_bk1006b" # 백업 idef if doc.InstanceDefinitions.Find(bkname) is None: bl = [] al = [] for ob in objs: bl.append(ob.Geometry.Duplicate()) al.append(ob.Attributes.Duplicate()) doc.InstanceDefinitions.Add(bkname, "backup before s-realign", Rhino.Geometry.Point3d.Origin, bl, al) ds = -dev dz = k * ds # 경사 접선 방향 이동 -> 수직 피복 유지 v = Rhino.Geometry.Vector3d(ca * ds, sa * ds, dz) v.Transform(inv) # 월드 이동벡터 -> idef 로컬 ztop = info[lid][3] - RBAR # 뚜껑 윗변 중심 z (월드) pl = Rhino.Geometry.Plane(Rhino.Geometry.Point3d(0.0, 0.0, ztop), Rhino.Geometry.Vector3d.ZAxis) pl.Transform(inv) newgeoms = [] for i in range(len(geoms)): if abs(info[i][0] - info[lid][0]) > TOL: # 뚜껑 평면에서 벗어난 조각만 이동 m = geoms[i] m.Translate(v) chk = m.Duplicate() chk.Transform(T) if chk.GetBoundingBox(True).Max.Z > ztop + 0.05: tr = m.Trim(pl, 0.01) # +Z 쪽 제거 if tr is not None and len(tr) > 0: m = tr[0] newgeoms.append(m) else: newgeoms.append(geoms[i]) doc.InstanceDefinitions.ModifyGeometry(bidf.Index, newgeoms, attrs) # 3-인자: 속성 보존 row["action"] = "fixed" row["dz"] = round(dz, 3) report.append(row) doc.Views.Redraw() txt = json.dumps(report) f = io.open(out, "w", encoding="utf-8") try: f.write(txt) except TypeError: f.write(txt.decode("utf-8")) f.close()