- 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>
196 lines
7.8 KiB
Python
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()
|