kintex/tools/cad/extract_trench_seed.py
zio 02c6b8ddee fix(m2): V67 K1 band-to-hall mapping corrected - was reversed
Owner directive 2026-07-16: verify the K1 band-hall numbering assumption.

Decisive evidence: 832 electrical circuit labels (P{panel}-{hall}{zone}-{no})
in the trench drawing, zero exceptions: band1=hall5(165) band2=hall4(168)
band3=hall3(168) band4=hall2(168) band5=hall1(163) => band n = hall (6-n).
The previous assumption (band n = hall n) was WRONG - only hall 3 matched by
luck. Mirror features in official JPGs (notch corners, exterior entrances)
independently agree.

- V67: delete all K1 measured rows (old mapping) and reseed with corrected
  generators (trench 875, pillars, obstacles, K1 exits). Assembly strips the
  V61 y-flip UPDATE (would double-flip) and the global obstacle DELETE
  (would wipe K2 rows)
- Swap DWG-tab underlays hall1<->hall5, hall2<->hall4
- All 5 generators now canonically map band = 6 - hall

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 06:53:55 +09:00

121 lines
5.3 KiB
Python

# -*- coding: utf-8 -*-
"""K1 트렌치 실측 시드 생성기 — CAD(전기,설비 트렌치.dwg → DXF)에서 홀1~5 트렌치 채널을
추출해 V61 마이그레이션 SQL을 산출한다(PLANNING R4 실측 대체).
실측 구조(레이어 5-트랜치): 홀당 가로 채널 7개(쌍선 폭 ~1.3m·피치 9m·로컬 y≈5.85 시작),
채널을 따라 6m 간격 공급 포인트를 생성한다(포인트 간격은 샘플링 — 채널 위치·연장은 실측).
사용:
python tools/cad/extract_trench_seed.py <k1_trench.dxf> <out.sql>
좌표 규약: CAD 전건물 mm → 홀 로컬 m. 홀 바닥 X원점 293.5m·폭 171m, Y밴드 80.1m+63m 피치.
★밴드↔홀 매핑(2026-07-16 확증): band n = 홀 (6-n) — 전기 회로 라벨 P{패널}-{홀}{구역}-{번호}
832건 전수 일치(band1=5A 165건 … band5=1A 163건). 예전 "밴드 n=홀 n" 가정은 오류였음(V67 교정).
"""
import io
import math
import sys
from collections import defaultdict
import ezdxf
MM = 1000.0
FLOOR_X0, FLOOR_W = 293.5, 171.0
BAND_Y0, BAND_H = 80.1, 63.0
HALLS = [1, 2, 3, 4, 5]
LAYER_KEYS = ("트렌치", "트랜치")
MIN_RUN_M = 30.0
POINT_STEP_M = 6.0
ROW_SNAP_M = 1.5 # 쌍선(채널 양변) 병합 허용
def main():
src, out_sql = sys.argv[1], sys.argv[2]
doc = ezdxf.readfile(src)
msp = doc.modelspace()
# 홀별 → 채널 행(스냅된 로컬 y) → x구간 목록
runs = {n: defaultdict(list) for n in HALLS}
for e in msp:
if not any(k in e.dxf.layer for k in LAYER_KEYS):
continue
pts = None
if e.dxftype() == "LINE":
pts = [(e.dxf.start[0] / MM, e.dxf.start[1] / MM), (e.dxf.end[0] / MM, e.dxf.end[1] / MM)]
elif e.dxftype() == "LWPOLYLINE":
p = [(q[0] / MM, q[1] / MM) for q in e.get_points()]
if len(p) >= 2:
pts = p
if not pts:
continue
for a, b in zip(pts, pts[1:]):
if math.hypot(b[0] - a[0], b[1] - a[1]) < MIN_RUN_M:
continue
if abs(b[1] - a[1]) > 0.5: # 가로 채널만(세로 피더는 M4 후속)
continue
my = (a[1] + b[1]) / 2
for n in HALLS:
y0 = BAND_Y0 + (5 - n) * BAND_H # band = 6-n (홀번호 역순, 2026-07-16 확증)
if y0 <= my < y0 + BAND_H:
lx0 = max(0.0, min(a[0], b[0]) - FLOOR_X0)
lx1 = min(FLOOR_W, max(a[0], b[0]) - FLOOR_X0)
if lx1 - lx0 >= MIN_RUN_M:
# 홀 로컬 y=0은 도면 상단(DWG 언더레이 기준) — CAD y 상하 반전.
# (V61은 미반전 시드 — V62에서 63-y 보정 완료. 재생성 시 본 반전이 정본.)
runs[n][round(BAND_H - (my - y0), 1)].append((lx0, lx1))
sql = io.StringIO()
sql.write(
"-- V61: K1 홀1~5 트렌치 실측 시드 — CAD(전기,설비 트렌치.dwg) 레이어 5-트랜치 추출.\n"
"-- 생성기: tools/cad/extract_trench_seed.py (재생성 가능·결정적). 근거: docs/analysis/cad-extraction.md.\n"
"-- 실측: 홀당 가로 채널(쌍선 병합 중심선)·연장 실좌표. 포인트 6m 간격은 샘플링 규약.\n"
"-- 정책: 실측 확보 홀(H1~H5)의 가정(is_assumed) 포인트를 실측 포인트로 대체. K2(H6~10)는 가정 유지.\n"
"-- 공급 플래그는 V5 정책 유지(공동구 — 전 서비스 true·gas는 홀 보유 여부), 위치만 실측 대체.\n\n"
)
total_rows = 0
report = []
for n in HALLS:
# 쌍선 병합: 인접(<=ROW_SNAP_M) 행을 채널 하나로
rows = sorted(runs[n].items())
merged = [] # (center_y, x0, x1)
for y, spans in rows:
x0 = min(s[0] for s in spans)
x1 = max(s[1] for s in spans)
if merged and y - merged[-1][0] <= ROW_SNAP_M:
py, px0, px1 = merged[-1]
merged[-1] = ((py + y) / 2, min(px0, x0), max(px1, x1))
else:
merged.append((y, x0, x1))
report.append(f"H{n}: 채널 {len(merged)}" + ", ".join(f"y={m[0]:.1f} x[{m[1]:.1f}..{m[2]:.1f}]" for m in merged))
if not merged:
continue
total_rows += len(merged)
sql.write(f"-- H{n}: 실측 채널 {len(merged)}\n")
sql.write(f"DELETE FROM trench WHERE hall_id = 'H{n}' AND is_assumed;\n")
values = []
for ri, (cy, x0, x1) in enumerate(merged, 1):
k = 0
x = x0
while x <= x1 + 1e-6:
k += 1
values.append(
f"('H{n}-RT-{ri}-{k}', 'H{n}', ST_SetSRID(ST_MakePoint({x:.2f}, {cy:.2f}), 0), "
f"true, true, true, true, (SELECT has_gas FROM hall WHERE id='H{n}'), false)"
)
x += POINT_STEP_M
sql.write(
"INSERT INTO trench (id, hall_id, geom, supply_power, supply_water, supply_air, supply_network, supply_gas, is_assumed)\nVALUES\n "
+ ",\n ".join(values)
+ "\nON CONFLICT (id) DO UPDATE SET geom = EXCLUDED.geom, is_assumed = EXCLUDED.is_assumed;\n\n"
)
with open(out_sql, "w", encoding="utf-8") as f:
f.write(sql.getvalue())
for line in report:
print(line)
print(f"OK -> {out_sql} (채널 {total_rows}개)")
if __name__ == "__main__":
main()