feat(m2): K2 halls 6-10 DWG underlays - DWG tab complete for all 10 halls
Owner directive 2026-07-15: render exhibition center 2 and complete the DWG tab. - fast_render.py: --rotate/--center options (align tilted halls axis-straight); slim-DXF workflow notes (K2 plan 106MB DXF, basement xref/detail layers dropped) - Hall geometry measured programmatically (long-line angle clusters) and verified visually: H6 -12deg 60x93, H7/H8 axis-aligned 90x126 stacked (block exactly 252m = 2x126, floor width exactly 90m), H9 -11deg / H10 +11deg 99x132 - public/media/floorplans/dwg/hall6..10.png: floor+3m crops, calibration stays deterministic (same margin formula as K1) - hallFloorplan.ts: DWG_HALL_DIMS extended to all 10 halls (rotation noted) - docs/analysis/cad-extraction.md: K2 section with confirmed coordinates table Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
fc0c5acc0a
commit
832ab7f8a9
@ -62,3 +62,25 @@ Y밴드(홀) 앵커는 확보했으나, 이 상세도만으로는 **홀별 전
|
|||||||
동일해 언더레이 표시는 유효하나 표기 확정 필요 ②A3002 평면도 DWG는 LibreDWG 크래시
|
동일해 언더레이 표시는 유효하나 표기 확정 필요 ②A3002 평면도 DWG는 LibreDWG 크래시
|
||||||
(r2000 다운컨버전 산출물도 ezdxf 파싱 실패) — 트렌치 통합도면으로 대체 ③제2전시장(H6~10)
|
(r2000 다운컨버전 산출물도 ezdxf 파싱 실패) — 트렌치 통합도면으로 대체 ③제2전시장(H6~10)
|
||||||
렌더는 `원본-1층 평면도.dwg` 변환 검증 후 확장.
|
렌더는 `원본-1층 평면도.dwg` 변환 검증 후 확장.
|
||||||
|
|
||||||
|
## K2(제2전시장) 홀 6~10 렌더 확정 (2026-07-15)
|
||||||
|
|
||||||
|
원본-1층 평면도.dwg(24.6MB → DXF 106.6MB) 기준. 대형 DXF 대응: 구조 레이어 슬림 추출(`k2_slim.py` 패턴,
|
||||||
|
지하층 xref·TOIL·INSUL 등 제외 145k→53k 엔티티) + **고속 렌더러 `tools/cad/fast_render.py`**
|
||||||
|
(LineCollection 일괄 — ezdxf drawing addon의 10분+ 병목을 수십 초로) + `--rotate/--center`(기울어진 홀 축정렬).
|
||||||
|
|
||||||
|
**확정 좌표(전건물 m 좌표계, floor=바닥면·crop=floor+3m):**
|
||||||
|
| 홀 | 회전 | 기준(center) | floor u(폭) | floor v(깊이) |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| H6 | -12° | (530,405) | 482..542 (60m) | 350.5..443.5 (93m) |
|
||||||
|
| H7 | 0° | — | 439..529 (90m) | 215.5..341.5 (126m) |
|
||||||
|
| H8 | 0° | — | 439..529 (90m) | 89.5..215.5 (126m) |
|
||||||
|
| H9 | -11° | (245,155) | 196..295 (99m) | 95.5..227.5 (132m) |
|
||||||
|
| H10 | +11° | (255,330) | 194.5..293.5 (99m) | 250..382 (132m) |
|
||||||
|
|
||||||
|
검증: 장선 각도 클러스터(`k2_hall_finder/tilt_measure`)로 각도·깊이 프로그램 측정(H6 93m·H9/H10 132m·
|
||||||
|
H7/H8 블록 252m=126×2·폭 90m 정확 일치) + 회전 프로브 렌더 시각 확인(6·9·10) + 최종 crop 확인(8).
|
||||||
|
홀 번호 배치 근거: 미니맵(6 북동→7→8 남측 축정렬 스택, 9 남서·10 북서 날개) + 치수 일치.
|
||||||
|
|
||||||
|
한계: 홀 번호는 미니맵·치수 정합 기반(도면 내 텍스트 라벨 직접 확인은 아님 — 치수가 홀별로 상이해
|
||||||
|
오배정 가능성 낮음). 트렌치 실좌표 V61 시드는 K1과 함께 후속.
|
||||||
|
|||||||
BIN
src/frontend/public/media/floorplans/dwg/hall10.png
Normal file
BIN
src/frontend/public/media/floorplans/dwg/hall10.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 328 KiB |
BIN
src/frontend/public/media/floorplans/dwg/hall6.png
Normal file
BIN
src/frontend/public/media/floorplans/dwg/hall6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 274 KiB |
BIN
src/frontend/public/media/floorplans/dwg/hall7.png
Normal file
BIN
src/frontend/public/media/floorplans/dwg/hall7.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 KiB |
BIN
src/frontend/public/media/floorplans/dwg/hall8.png
Normal file
BIN
src/frontend/public/media/floorplans/dwg/hall8.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
BIN
src/frontend/public/media/floorplans/dwg/hall9.png
Normal file
BIN
src/frontend/public/media/floorplans/dwg/hall9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 459 KiB |
@ -48,12 +48,19 @@ export function hallFloorRegion(hallId: string | null | undefined): FloorRegion
|
|||||||
* 현재 제1전시장 H1~H5 렌더 완료분만 노출 — 미보유 홀은 null(탭 비활성).
|
* 현재 제1전시장 H1~H5 렌더 완료분만 노출 — 미보유 홀은 null(탭 비활성).
|
||||||
*/
|
*/
|
||||||
const DWG_MARGIN_M = 3;
|
const DWG_MARGIN_M = 3;
|
||||||
|
// 제2전시장(H6~10)은 CAD 원본에서 기울어 배치 — 렌더 시 회전 보정 완료(fast_render --rotate):
|
||||||
|
// H6 -12° · H9 -11° · H10 +11° · H7/H8 축정렬. 좌표 근거 docs/analysis/cad-extraction.md §K2.
|
||||||
const DWG_HALL_DIMS: Record<string, [number, number]> = {
|
const DWG_HALL_DIMS: Record<string, [number, number]> = {
|
||||||
H1: [171, 63],
|
H1: [171, 63],
|
||||||
H2: [171, 63],
|
H2: [171, 63],
|
||||||
H3: [171, 63],
|
H3: [171, 63],
|
||||||
H4: [171, 63],
|
H4: [171, 63],
|
||||||
H5: [171, 63],
|
H5: [171, 63],
|
||||||
|
H6: [60, 93],
|
||||||
|
H7: [90, 126],
|
||||||
|
H8: [90, 126],
|
||||||
|
H9: [99, 132],
|
||||||
|
H10: [99, 132],
|
||||||
};
|
};
|
||||||
|
|
||||||
export function hallDwgUrl(hallId: string | null | undefined): string | null {
|
export function hallDwgUrl(hallId: string | null | undefined): string | null {
|
||||||
|
|||||||
107
tools/cad/fast_render.py
Normal file
107
tools/cad/fast_render.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""CAD DXF 고속 렌더러 — ezdxf drawing addon(엔티티당 개별 패스, 대형 도면에서 10분+) 대신
|
||||||
|
전 엔티티를 세그먼트로 평탄화해 matplotlib LineCollection 한 방에 그린다(수십만 세그먼트도 수 초).
|
||||||
|
|
||||||
|
사용:
|
||||||
|
python tools/cad/fast_render.py <dxf> <out.png> [x0 y0 x1 y1 (m, 생략 시 전체)] [--size WxH --dpi N]
|
||||||
|
[--rotate DEG --center CX,CY]
|
||||||
|
예:
|
||||||
|
python tools/cad/fast_render.py k2_slim.dxf overview.png
|
||||||
|
python tools/cad/fast_render.py k2_slim.dxf hall7.png 300 200 390 326 --size 9x12.6 --dpi 160
|
||||||
|
# 기울어진 홀: center 기준 -DEG 회전해 축정렬 후 crop(crop 좌표는 회전 후 좌표계 m)
|
||||||
|
python tools/cad/fast_render.py k2_slim.dxf hall9.png 200 90 300 230 --rotate -11 --center 245,160
|
||||||
|
"""
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import ezdxf
|
||||||
|
import matplotlib
|
||||||
|
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.collections import LineCollection
|
||||||
|
|
||||||
|
MM = 1000.0
|
||||||
|
FLATTEN_SAGITTA = 50.0 # mm — 곡선 근사 허용 오차
|
||||||
|
|
||||||
|
|
||||||
|
def seg_points(e, depth=0):
|
||||||
|
"""엔티티 → 폴리라인 점열들(세그먼트 체인). INSERT는 가상 엔티티로 재귀."""
|
||||||
|
t = e.dxftype()
|
||||||
|
try:
|
||||||
|
if t == "LINE":
|
||||||
|
yield [(e.dxf.start[0], e.dxf.start[1]), (e.dxf.end[0], e.dxf.end[1])]
|
||||||
|
elif t in ("LWPOLYLINE", "POLYLINE"):
|
||||||
|
pts = [(p[0], p[1]) for p in (e.get_points() if t == "LWPOLYLINE" else [(v.dxf.location[0], v.dxf.location[1]) for v in e.vertices])]
|
||||||
|
if len(pts) >= 2:
|
||||||
|
if getattr(e, "closed", False) or (t == "POLYLINE" and e.is_closed):
|
||||||
|
pts.append(pts[0])
|
||||||
|
yield pts
|
||||||
|
elif t in ("ARC", "CIRCLE", "ELLIPSE", "SPLINE"):
|
||||||
|
pts = [(p[0], p[1]) for p in e.flattening(FLATTEN_SAGITTA)]
|
||||||
|
if len(pts) >= 2:
|
||||||
|
yield pts
|
||||||
|
elif t == "INSERT" and depth < 4:
|
||||||
|
for ve in e.virtual_entities():
|
||||||
|
yield from seg_points(ve, depth + 1)
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
src, out = sys.argv[1], sys.argv[2]
|
||||||
|
rest = [a for a in sys.argv[3:] if not a.startswith("--")]
|
||||||
|
crop = [float(v) for v in rest[:4]] if len(rest) >= 4 else None
|
||||||
|
size = (18.0, 18.0)
|
||||||
|
dpi = 80
|
||||||
|
rotate = 0.0
|
||||||
|
center = (0.0, 0.0)
|
||||||
|
for i, a in enumerate(sys.argv):
|
||||||
|
if a == "--size":
|
||||||
|
w, h = sys.argv[i + 1].split("x")
|
||||||
|
size = (float(w), float(h))
|
||||||
|
elif a == "--dpi":
|
||||||
|
dpi = int(sys.argv[i + 1])
|
||||||
|
elif a == "--rotate":
|
||||||
|
rotate = float(sys.argv[i + 1])
|
||||||
|
elif a == "--center":
|
||||||
|
cx, cy = sys.argv[i + 1].split(",")
|
||||||
|
center = (float(cx), float(cy))
|
||||||
|
|
||||||
|
doc = ezdxf.readfile(src)
|
||||||
|
msp = doc.modelspace()
|
||||||
|
|
||||||
|
cos_t = math.cos(math.radians(-rotate))
|
||||||
|
sin_t = math.sin(math.radians(-rotate))
|
||||||
|
|
||||||
|
def xform(x, y):
|
||||||
|
"""m 단위 점을 center 기준 -rotate 회전(기울어진 홀 → 축정렬)."""
|
||||||
|
if rotate == 0.0:
|
||||||
|
return (x, y)
|
||||||
|
dx, dy = x - center[0], y - center[1]
|
||||||
|
return (center[0] + dx * cos_t - dy * sin_t, center[1] + dx * sin_t + dy * cos_t)
|
||||||
|
|
||||||
|
segments = []
|
||||||
|
for e in msp:
|
||||||
|
if e.dxftype() in ("TEXT", "MTEXT", "ATTDEF", "HATCH", "POINT"):
|
||||||
|
continue
|
||||||
|
for pts in seg_points(e):
|
||||||
|
segments.append([xform(x / MM, y / MM) for x, y in pts])
|
||||||
|
print(f"polylines={len(segments)}", flush=True)
|
||||||
|
|
||||||
|
fig = plt.figure(figsize=size, dpi=dpi)
|
||||||
|
ax = fig.add_axes([0, 0, 1, 1])
|
||||||
|
ax.add_collection(LineCollection(segments, colors="#2b3a55", linewidths=0.35))
|
||||||
|
if crop:
|
||||||
|
ax.set_xlim(crop[0], crop[2])
|
||||||
|
ax.set_ylim(crop[1], crop[3])
|
||||||
|
else:
|
||||||
|
ax.autoscale()
|
||||||
|
ax.set_aspect("equal")
|
||||||
|
ax.axis("off")
|
||||||
|
fig.savefig(out, facecolor="white")
|
||||||
|
print(f"OK -> {out}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user