- 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>
106 lines
3.9 KiB
Python
106 lines
3.9 KiB
Python
"""철근 배치(간격, 수량, 중량) 계산 모듈.
|
|
|
|
Rhino 없이 실행됩니다. 단위는 mm이고, 중량만 kg입니다.
|
|
기준값 출처: docs/design_rules.md, 기존 프로젝트 rebar_pipeline/README.md
|
|
"""
|
|
import rebar_calc
|
|
|
|
# 단위중량 kg/m (기존 프로젝트 재료표 값). 아래에 없는 규격은 값을 확인한 뒤 추가하세요.
|
|
UNIT_WEIGHT = {
|
|
"H16": 1.560,
|
|
"H19": 2.250,
|
|
"H22": 3.040,
|
|
"H25": 3.980,
|
|
"H29": 5.040,
|
|
}
|
|
|
|
DEFAULT_GMAX = 25.0 # 굵은골재 최대치수(mm). 가정값이므로 설계서의 값으로 바꿔 쓰세요.
|
|
EPS = 1e-9
|
|
|
|
|
|
def center_out_positions(count, spacing):
|
|
"""중심(0)에서 바깥으로 spacing 간격으로 놓은 폭방향 위치 목록(작은 값부터).
|
|
|
|
짝수 개이면 중심에 철근이 없고(±spacing/2부터), 홀수 개이면 중심(0)에 철근이 있습니다.
|
|
"""
|
|
if count < 0:
|
|
raise ValueError("철근 수는 0 이상이어야 합니다: %r" % (count,))
|
|
if spacing <= 0:
|
|
raise ValueError("간격은 0보다 커야 합니다: %r" % (spacing,))
|
|
mid = (count - 1) / 2.0
|
|
return [spacing * (i - mid) for i in range(count)]
|
|
|
|
|
|
def snapped_array(count, spacing, width, cover):
|
|
"""바깥 철근 2본은 피복에 맞추고, 나머지는 중심에서 바깥으로 배치합니다 (1CYCLE 방식).
|
|
|
|
바깥 철근 위치 = ±(width/2 - cover). 안쪽 철근은 중심에서 바깥으로 spacing 간격입니다.
|
|
안쪽 철근이 바깥 철근 위치를 넘어가면 ValueError.
|
|
"""
|
|
if count < 2:
|
|
raise ValueError("바깥 철근 2본이 필요하므로 count는 2 이상이어야 합니다: %r" % (count,))
|
|
outer = width / 2.0 - cover
|
|
inner = center_out_positions(count - 2, spacing)
|
|
if inner and max(inner) >= outer:
|
|
raise ValueError("폭 %r 안에 철근 %r본을 간격 %r로 놓을 수 없습니다" % (width, count, spacing))
|
|
return [-outer] + inner + [outer]
|
|
|
|
|
|
def min_horizontal_clear(name, gmax=DEFAULT_GMAX):
|
|
"""수평 최소 순간격 = max(1.5db, 1.5Gmax, 40mm) (KDS 24 14 21 4.5.3)."""
|
|
db = rebar_calc.nominal_diameter(name)
|
|
return max(1.5 * db, 1.5 * gmax, 40.0)
|
|
|
|
|
|
def clear_spacing(center_spacing, name):
|
|
"""중심 간격에서 철근 직경을 뺀 순간격."""
|
|
return center_spacing - rebar_calc.nominal_diameter(name)
|
|
|
|
|
|
def horizontal_spacing_ok(positions, name, gmax=DEFAULT_GMAX):
|
|
"""이웃한 철근 사이 순간격이 모두 수평 최소 순간격 이상이면 True."""
|
|
ordered = sorted(positions)
|
|
need = min_horizontal_clear(name, gmax)
|
|
for a, b in zip(ordered, ordered[1:]):
|
|
if clear_spacing(b - a, name) < need - EPS:
|
|
return False
|
|
return True
|
|
|
|
|
|
def min_vertical_clear(name):
|
|
"""다단 배근의 연직 최소 순간격 = max(25mm, db)."""
|
|
return max(25.0, rebar_calc.nominal_diameter(name))
|
|
|
|
|
|
def vertical_pitch_ok(name, pitch):
|
|
"""단 사이 중심 간격(pitch)의 순간격이 연직 최소 순간격 이상이면 True."""
|
|
return clear_spacing(pitch, name) >= min_vertical_clear(name) - EPS
|
|
|
|
|
|
def tier_elevations(top_z, cover, tiers, pitch):
|
|
"""맨 위 단은 상면에서 cover만큼 아래, 다음 단부터 pitch씩 아래로 내린 높이 목록."""
|
|
return [top_z - cover - i * pitch for i in range(tiers)]
|
|
|
|
|
|
def unit_weight(name):
|
|
"""단위중량(kg/m)."""
|
|
key = "H%d" % rebar_calc.bar_number(name)
|
|
if key not in UNIT_WEIGHT:
|
|
raise ValueError("단위중량이 등록되지 않은 규격입니다: %r" % (name,))
|
|
return UNIT_WEIGHT[key]
|
|
|
|
|
|
def bar_weight(name, length_mm, count=1):
|
|
"""철근 length_mm 길이 count본의 중량(kg)."""
|
|
return unit_weight(name) * length_mm / 1000.0 * count
|
|
|
|
|
|
def array_summary(name, length_mm, positions):
|
|
"""한 배열의 본수, 총길이(m), 중량(kg)."""
|
|
count = len(positions)
|
|
return {
|
|
"count": count,
|
|
"total_length_m": length_mm / 1000.0 * count,
|
|
"weight_kg": bar_weight(name, length_mm, count),
|
|
}
|