# -*- 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()