"""철근 배치(간격, 수량, 중량) 계산 모듈. 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), }