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>
This commit is contained in:
ahnyongjooandClaude Sonnet 5.5 committed 2026-10-28 11:20:18 +09:00
commit 964a841de1
3978 files changed
+58013356

No files matched your search

+164
View File
@@ -0,0 +1,164 @@
"""철근 중심선 간섭 검사 모듈.
Rhino 없이 실행됩니다. 단위는 mm. 좌표는 (x, y, z) 3D 점입니다.
계산을 정확하게 하려면 월드 좌표(약 4.8억)보다 frame.py 로 바꾼 로컬 좌표를 쓰는 것이 좋습니다.
규칙 (기존 프로젝트 rebar_pipeline/rhino/08_clash_check.py):
- 철근을 중심선 선분으로 쪼개서, 두 선분의 최단거리 d 에 대해
관입 = rA + rB - d 가 min_penetration(기본 0.5mm)보다 크면 간섭입니다.
- 같은 철근, 같은 그룹(겹침이음 모델링인 박스) 선분끼리는 제외합니다.
- 철근 쌍마다 가장 깊은 관입 하나만 기록합니다.
"""
import math
EPS = 1e-12
BBOX_MARGIN = 1.0 # 후보를 찾을 때 반지름에 더하는 여유(mm)
class Seg(object):
"""철근 중심선의 선분 하나.
p, q : 끝점 (x, y, z)
radius : 철근 반지름
bar : 철근 번호 (같은 철근끼리는 검사 제외)
group : 그룹 번호 (같은 박스의 뚜껑·바닥 조각처럼 검사 제외할 묶음)
mark : 철근 마크 이름 (보고용)
"""
def __init__(self, p, q, radius, bar, group, mark=""):
self.p = (float(p[0]), float(p[1]), float(p[2]))
self.q = (float(q[0]), float(q[1]), float(q[2]))
self.radius = float(radius)
self.bar = bar
self.group = group
self.mark = mark
def _sub(a, b):
return (a[0] - b[0], a[1] - b[1], a[2] - b[2])
def _dot(a, b):
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
def _clamp01(x):
if x < 0.0:
return 0.0
if x > 1.0:
return 1.0
return x
def segment_distance(p1, q1, p2, q2):
"""두 선분 p1-q1, p2-q2 사이의 최단거리 (3D, 길이 0인 선분도 가능)."""
d1 = _sub(q1, p1)
d2 = _sub(q2, p2)
r = _sub(p1, p2)
a = _dot(d1, d1)
e = _dot(d2, d2)
f = _dot(d2, r)
if a <= EPS and e <= EPS:
s = 0.0
t = 0.0
elif a <= EPS:
s = 0.0
t = _clamp01(f / e)
else:
c = _dot(d1, r)
if e <= EPS:
t = 0.0
s = _clamp01(-c / a)
else:
b = _dot(d1, d2)
denom = a * e - b * b
if denom > 1e-12 * a * e:
s = _clamp01((b * f - c * e) / denom)
else:
s = 0.0
t = (b * s + f) / e
if t < 0.0:
t = 0.0
s = _clamp01(-c / a)
elif t > 1.0:
t = 1.0
s = _clamp01((b - c) / a)
c1 = (p1[0] + d1[0] * s, p1[1] + d1[1] * s, p1[2] + d1[2] * s)
c2 = (p2[0] + d2[0] * t, p2[1] + d2[1] * t, p2[2] + d2[2] * t)
diff = _sub(c1, c2)
return math.sqrt(_dot(diff, diff))
def _box(s):
"""선분을 반지름 + 여유만큼 부풀린 상자 (xmin, ymin, zmin, xmax, ymax, zmax)."""
m = s.radius + BBOX_MARGIN
return (min(s.p[0], s.q[0]) - m, min(s.p[1], s.q[1]) - m, min(s.p[2], s.q[2]) - m,
max(s.p[0], s.q[0]) + m, max(s.p[1], s.q[1]) + m, max(s.p[2], s.q[2]) + m)
def _boxes_overlap(a, b):
return (a[0] <= b[3] and b[0] <= a[3] and
a[1] <= b[4] and b[1] <= a[4] and
a[2] <= b[5] and b[2] <= a[5])
def find_clashes(segments, min_penetration=0.5):
"""간섭 목록을 돌려줍니다 (관입이 깊은 것부터).
각 항목: {"bar_a", "bar_b", "mark_a", "mark_b", "penetration", "point"}
bar_a < bar_b 로 정렬되고, point 는 간섭 선분 네 끝점의 평균입니다.
"""
boxes = [_box(s) for s in segments]
order = sorted(range(len(segments)), key=lambda i: boxes[i][0])
best = {}
for oi in range(len(order)):
i = order[oi]
a = segments[i]
for oj in range(oi + 1, len(order)):
j = order[oj]
if boxes[j][0] > boxes[i][3]:
break # x 방향으로 이미 떨어져 있음. 뒤쪽은 더 멀리 있음
b = segments[j]
if a.bar == b.bar or a.group == b.group:
continue
if not _boxes_overlap(boxes[i], boxes[j]):
continue
d = segment_distance(a.p, a.q, b.p, b.q)
pen = a.radius + b.radius - d
if pen <= min_penetration:
continue
first, second = (a, b) if a.bar <= b.bar else (b, a)
key = (first.bar, second.bar)
old = best.get(key)
if old is None or pen > old["penetration"]:
point = ((a.p[0] + a.q[0] + b.p[0] + b.q[0]) / 4.0,
(a.p[1] + a.q[1] + b.p[1] + b.q[1]) / 4.0,
(a.p[2] + a.q[2] + b.p[2] + b.q[2]) / 4.0)
best[key] = {
"bar_a": first.bar, "bar_b": second.bar,
"mark_a": first.mark, "mark_b": second.mark,
"penetration": pen, "point": point,
}
hits = list(best.values())
hits.sort(key=lambda h: -h["penetration"])
return hits
def summarize_by_mark(hits):
"""마크 쌍별 간섭 개수와 최대 관입. 키는 'C1|C8' 처럼 이름순으로 정렬된 마크 쌍."""
summary = {}
for h in hits:
names = sorted([h["mark_a"], h["mark_b"]])
key = names[0] + "|" + names[1]
item = summary.get(key)
if item is None:
summary[key] = {"count": 1, "max_penetration": h["penetration"]}
else:
item["count"] += 1
if h["penetration"] > item["max_penetration"]:
item["max_penetration"] = h["penetration"]
return summary