Files
Pier_v1/Code/rebar_pipeline/rhino/08_clash_check.py
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

196 lines
7.8 KiB
Python

# -*- coding: utf-8 -*-
# 08_clash_check.py (대호지교 P1, 2026-10-06) -- READ ONLY: 모델을 바꾸지 않고 CSV/JSON만 쓴다.
# 중심선 간섭 검사: 각 철근(리프 Brep)의 파이프면에서 중심선(원주 반대편 두 isocurve의 중점)을 뽑아 선분화하고,
# RTree 로 후보를 찾아 선분-선분 최단거리 < (rA + rB) - 0.5 이면 간섭(관입 = rA + rB - 거리).
# 같은 박스(뚜껑+바닥+연장 조각)끼리는 겹침이음 모델링이므로 제외한다.
# 실행: execute_rhino_code (IronPython 2.7). 소켓 타임아웃이 나도 Rhino 안에서 계속 도므로 결과 파일이 생길 때까지 기다린다.
# 주의: 함수/lambda/컴프리헨션 금지(exec 스코프). 콜백만 기본인자로 변수를 묶어 정의한다.
import Rhino, json, io, os, math, System
import scriptcontext as sc
doc = sc.doc
folder = os.path.dirname(doc.Path)
OX, OY = 480714926.0, 153833329.5
ANG = math.atan2(4255.0, 12284.0)
ca = math.cos(ANG)
sa = math.sin(ANG)
RAD = {"C9": 14.3, "C26": 9.55}
for m in ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C10", "C11"]:
RAD[m] = 12.7
for m in ["C12", "C13", "C14", "C15", "C16", "C17", "C18"]:
RAD[m] = 11.1
for m in ["C19", "C20", "C21", "C22", "C23", "C24", "C25"]:
RAD[m] = 7.95
MIN_PEN = 0.5
es = Rhino.DocObjects.ObjectEnumeratorSettings()
es.HiddenObjects = True # 꺼진 레이어도 포함
es.NormalObjects = True
es.LockedObjects = True
cache = {} # 리프 id -> 로컬 중심선 폴리라인 목록 [[Point3d,...], ...]
segs = [] # [mark, barIdx, group, p0, p1]
bars = [] # [mark, label]
tree = Rhino.Geometry.RTree()
group_counter = [0]
# ---- collect centerline segments of every rebar mark
for o in doc.Objects.GetObjectList(es):
g = o.Geometry
ly = doc.Layers[o.Attributes.LayerIndex].FullPath
if "CYCLE" not in ly or not ly.startswith("P1_"): # P2/P10/P12 복사본(숨김 레이어)은 제외
continue
mk = ly.split("::")[-1].split("_")[0]
if mk not in RAD:
continue
r = RAD[mk]
circ = 2.0 * math.pi * r
items = [] # [leaf id, brep, xform, group]
if isinstance(g, Rhino.Geometry.InstanceReferenceGeometry):
stack = [[doc.InstanceDefinitions.FindId(g.ParentIdefId), g.Xform, 0, -1]]
while len(stack) > 0:
it = stack.pop()
idf = it[0]
for ob in idf.GetObjects():
og = ob.Geometry
if isinstance(og, Rhino.Geometry.InstanceReferenceGeometry):
grp = it[3]
if it[2] + 1 == 2: # 박스 인스턴스 = 한 그룹
group_counter[0] += 1
grp = group_counter[0]
stack.append([doc.InstanceDefinitions.FindId(og.ParentIdefId), it[1] * og.Xform, it[2] + 1, grp])
elif isinstance(og, Rhino.Geometry.Brep):
grp = it[3]
if grp < 0:
group_counter[0] += 1
grp = group_counter[0]
items.append([ob.Id, og, it[1], grp])
elif isinstance(g, Rhino.Geometry.Brep):
group_counter[0] += 1
items.append([o.Id, g, Rhino.Geometry.Transform.Identity, group_counter[0]])
for it in items:
key = str(it[0])
if key not in cache:
lst = []
for f in it[1].Faces:
du = f.Domain(0)
dv = f.Domain(1)
c1 = f.IsoCurve(1, du.Mid)
c0 = f.IsoCurve(0, dv.Mid)
ra = None
rb = None
along_u = True
if c1 is not None and c1.IsClosed and abs(c1.GetLength() - circ) < 3.0:
ra = f.IsoCurve(0, dv.ParameterAt(0.0))
rb = f.IsoCurve(0, dv.ParameterAt(0.5))
elif c0 is not None and c0.IsClosed and abs(c0.GetLength() - circ) < 3.0:
along_u = False
ra = f.IsoCurve(1, du.ParameterAt(0.0))
rb = f.IsoCurve(1, du.ParameterAt(0.5))
else:
continue
pc = ra.ToPolyline(0, 0, 0.05, 0, 0, 0.3, 0, 1.0e6, True)
pts = []
for i in range(pc.PointCount):
p = pc.Point(i)
okc, t = ra.ClosestPoint(p)
# 중심 = 그 위치 단면 원의 중심 (v0.5 점은 NURBS 매개변수가 균등하지 않아 반대편이 아닐 수 있음)
if along_u:
circ_c = f.IsoCurve(1, t)
else:
circ_c = f.IsoCurve(0, t)
okr, cir = circ_c.TryGetCircle(0.1)
if okr:
pts.append(cir.Center)
else:
q = rb.PointAt(t)
pts.append(Rhino.Geometry.Point3d((p.X + q.X) / 2.0, (p.Y + q.Y) / 2.0, (p.Z + q.Z) / 2.0))
lst.append(pts)
cache[key] = lst
bidx = len(bars)
bars.append([mk, key[:8]])
for pl in cache[key]:
pts = []
for p in pl:
q = Rhino.Geometry.Point3d(p)
q.Transform(it[2])
pts.append(q)
for i in range(len(pts) - 1):
if pts[i].DistanceTo(pts[i + 1]) < 1.0e-6:
continue
sidx = len(segs)
segs.append([mk, bidx, it[3], pts[i], pts[i + 1]])
bb = Rhino.Geometry.BoundingBox([pts[i], pts[i + 1]]) # 순서 무관 (min/max 자동) - 두 점 생성자는 순서가 뒤바뀌면 무효 상자가 됨
bb.Inflate(r + 1.0)
tree.Insert(bb, sidx)
# ---- search overlapping segments
pairs = {}
hits = []
cur = [0]
def cb(sender, e, hits=hits, cur=cur):
if e.Id > cur[0]:
hits.append(e.Id)
handler = System.EventHandler[Rhino.Geometry.RTreeEventArgs](cb)
for i in range(len(segs)):
sA = segs[i]
ra_ = RAD[sA[0]]
lineA = Rhino.Geometry.Line(sA[3], sA[4])
bb = Rhino.Geometry.BoundingBox([sA[3], sA[4]])
bb.Inflate(ra_ + 1.0)
del hits[:]
cur[0] = i
tree.Search(bb, handler)
for j in hits:
sB = segs[j]
if sB[1] == sA[1] or sB[2] == sA[2]:
continue
rb_ = RAD[sB[0]]
d = lineA.MinimumDistanceTo(Rhino.Geometry.Line(sB[3], sB[4]))
pen = ra_ + rb_ - d
if pen > MIN_PEN:
pk = (sA[1], sB[1])
old = pairs.get(pk)
if old is None or pen > old[0]:
mx = (sA[3].X + sA[4].X + sB[3].X + sB[4].X) / 4.0
my = (sA[3].Y + sA[4].Y + sB[3].Y + sB[4].Y) / 4.0
mz = (sA[3].Z + sA[4].Z + sB[3].Z + sB[4].Z) / 4.0
pairs[pk] = [pen, mx, my, mz]
# ---- write CSV (UTF-8 BOM) + summary
rows = []
for pk in pairs:
v = pairs[pk]
a = bars[pk[0]]
b = bars[pk[1]]
dx = v[1] - OX
dy = v[2] - OY
rows.append([a[0], a[1], b[0], b[1], round(v[0], 1), round(dx * ca + dy * sa, 0), round(-dx * sa + dy * ca, 0), round(v[3], 0)])
rows.sort()
f = io.open(os.path.join(folder, "261006_간섭재검사_목록.csv"), "w", encoding="utf-8")
f.write(u"마크A,철근A,마크B,철근B,관입(mm),s,w,z\n")
for r_ in rows:
f.write(u"%s,%s,%s,%s,%s,%s,%s,%s\n" % tuple(r_))
f.close()
summ = {}
for r_ in rows:
k = r_[0] + "|" + r_[2]
if r_[0] > r_[2]:
k = r_[2] + "|" + r_[0]
s_ = summ.get(k, [0, 0.0])
s_[0] += 1
if r_[4] > s_[1]:
s_[1] = r_[4]
summ[k] = s_
res = {"bars": len(bars), "segments": len(segs), "pairs": len(rows), "by_markpair": summ}
f = io.open(os.path.join(folder, "261006_간섭재검사_요약.json"), "w", encoding="utf-8")
txt = json.dumps(res, ensure_ascii=False)
try:
f.write(txt)
except TypeError:
f.write(txt.decode("utf-8"))
f.close()