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:
commit
964a841de1
3978 files changed
+58013356
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user