- 정리는 코치 세션이 저장소 밖 도구로 수행, 하이쿠 세션이 커밋. - 옛 81커밋은 로컬에만 보존. 기록은 evidence/gitlog_v1.txt. - F13 기준 변경: .gitignore 에 private/*.dwg·private/full.dxf 제외 (SPEC 34장 끝 문장). - check.sh 결과: 결과: PASS 151 / FAIL 0 / SKIP 3 (검사 범위 F01~F46) Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
74 KiB
SPEC — 전력구 수직구 3D 뷰어 (MVP)
🔒 사내교육용 — 열람 외 재배포 등의 활동을 금함 (회사 도면의 치수를 옮겨 적음) 설계 면담(수석 + 코치 세션, 2026-10-19~20)으로 확정. 이 파일은 하이쿠가 고치지 않는다.
1. 목적
원형 수직구 일반도 한 장을 라이브러리 없이 브라우저 3D 화면으로 보여 준다. 청중은 교육 담당자와 동료이고, 3분 시연에서 아래 세 문장을 화면을 돌려 가며 말할 수 있으면 끝이다.
- 외경 11m, 깊이 70.3m 원통이다.
- 안에 슬래브 19장 + 하부 슬래브가 있고, 층마다 계단이 지그재그로 내려간다.
- 바닥 근처 19B 층에서 4시 방향으로 TBM 터널(내경 2.6m)이 나가고, 터널 바닥 패드와 19B 패드 상면 높이가 같다.
2. 범위
| 넣는 것 | 빼는 것 (범위 밖) |
|---|---|
| 벽체, 중간 슬래브 19장, 19B 패드, 하부 슬래브, 바닥콘크리트 | 상부 분기구·환기구·관로, 지표→1B 구간의 계단·설비 |
| 계단 18런(1B→19B) | 슬래브 개구부(계단·케이블 slot·펌프 배출구 구멍), 방호 난간 |
| TBM 터널 세그먼트(속 빈 원통) + 터널 안 잡석·패드 2단 | 집수정 사다리, 집수조·침사조·월류벽, 배관 |
| 등각 초기 화면, 드래그 회전, 휠 확대, 벽 반투명 | 프리셋 뷰, 클릭 선택·속성 패널, 표시/숨김, 축·그리드, 단면, 치수 라벨, PNG 저장 |
3. 좌표계와 단위
- 데이터의 길이는 모두 mm, 각도는 도.
- 원점 = 수직구 축 중심 × 지표면(구조물 상단). Z 위로 +, 땅속은 음수.
- X = 평면도 오른쪽, Y = 평면도 위쪽. 수평 각도는 X축에서 반시계(시계의 3시 = 0°, 12시 = 90°, 4시 = 330°).
4. 부재와 치수
4.1 벽체·바닥
| 부재 | 형태 | 치수 |
|---|---|---|
| 벽체 | 속 빈 원통 | 외경 11,000(r 5,500), 내경 9,700(r 4,850), z −70,200 ~ 0 |
| 하부 슬래브(BOTTOM, 20B) | 원판 | r 4,850, z −70,200 ~ −69,000 (두께 1,200, 벽 안쪽) |
| 바닥콘크리트 | 원판 | r 5,500(= 벽 외경, 슬러리월 안에서 타설), z −70,300 ~ −70,200 (두께 100) |
4.2 중간 슬래브 (원판, r 4,850, 두께 300)
슬래브 상면 z. 하면 = 상면 − 300.
| 층 | 상면 z | 층 | 상면 z | 층 | 상면 z |
|---|---|---|---|---|---|
| 1B | −7,400 | 8B | −30,500 | 15B | −53,600 |
| 2B | −10,700 | 9B | −33,800 | 16B | −56,900 |
| 3B | −14,000 | 10B | −37,100 | 17B | −60,200 |
| 4B | −17,300 | 11B | −40,400 | 18B | −63,500 |
| 5B | −20,600 | 12B | −43,700 | 19B | −66,200 |
| 6B | −23,900 | 13B | −47,000 | ||
| 7B | −27,200 | 14B | −50,300 |
규칙: 1B = −7,400(지표에서 5,400+500+1,300+200). 1B~18B 는 3,300 간격. 18B→19B 2,700. 19B→하부 슬래브 상면 2,800. 총높이 70,300.
4.3 19B 패드
원판, 벽 안쪽면에서 100 떨어짐 → r 4,750(지름 9,500), z −66,200 ~ −66,100 (두께 100).
4.4 계단 (18런)
- 런 i 는 i 층 슬래브(위)에서 i+1 층 슬래브(아래)로 내려간다. i = 1~18 (마지막 런은 18B→19B).
- 단 높이 200, 맨 아래 첫 단만 100. 디딤판 깊이 300. 폭 1,000.
- 층고 3,300 → 단 수 17(= 100 + 200×16), 디딤판 16장, 런 길이 4,800
- 층고 2,700(런 18) → 단 수 14(= 100 + 200×13), 디딤판 13장, 런 길이 3,900
- 계단은 디딤판마다 블록(아래 슬래브 상면부터 그 디딤판 높이까지 꽉 찬 상자)으로 쌓는다.
- 위치 (🔶 확인 필요 — 코치 추론):
- 런의 윗끝 = 위 슬래브의 계단 개구부 가장자리. 개구부(4,200)는 X 가운데 −2,100 ~ +2,100 으로 본다.
- 홀수 런: 윗끝 x = −2,100, 내려가며 +X 방향, Y 띠 −1,000 ~ 0.
- 짝수 런: 윗끝 x = +2,100, 내려가며 −X 방향, Y 띠 0 ~ +1,000.
- 블록 k(아래부터 1, 2, …, 단 수−1)의 윗면 z = 아래 슬래브 상면 + 100 + 200×(k−1). 맨 위 단(200)은 위 슬래브로 올라선다.
- 블록 k 의 X 구간: 윗끝 x 에서 (단 수−1−k)×300 ~ (단 수−k)×300 만큼 내려가는 방향으로 떨어진 곳.
- 개구부를 그리지 않으므로 맨 위 블록이 위 슬래브 두께에 100 겹친다. MVP 에서는 받아들인다.
4.5 TBM 터널
- 방향 330°(평면도 4시). 축 높이 z −65,050.
- 세그먼트: 속 빈 원통, 두께 200, 내경 2,600(r 1,300), 외경 3,000(r 1,500).
- 원통은 벽 안쪽면(r 4,850)에서 시작해 벽을 관통하고 벽 바깥면에서 20,000 더 나간다 → 길이 20,650. 시작점 = (4,850·cos330°, 4,850·sin330°, −65,050).
- 근거(수석): 세그먼트가 벽체 개구부의 거푸집 역할을 해 18B 슬래브와 19B 벽체부를 동시 타설한다. 그래서 세그먼트 천장 외면(−63,550)이 18B 슬래브 두께(−63,800 ~ −63,500) 안에 들어가는 것은 실제 형태다.
- 높이 검산: 세그먼트 바닥 외면 −66,550(= 19B 슬래브 하면 −66,500 − 50) → 내면 −66,350 → 잡석 상면 −66,225 → 패드 상면 −66,100 = 19B 패드 상면.
- 터널 안 채움(누운 원통 속을 높이까지 채움). 높이 v 는 터널 축에서 잰 값(위 +).
| 채움 | v 범위 | 폭 제한 |
|---|---|---|
| 잡석 | −1,300 ~ −1,175 (125) | 세그먼트 안쪽 벽까지 |
| 패드 2단(아래) | −1,175 ~ −1,100 (75) 🔶 | 세그먼트 안쪽 벽까지 |
| 패드 1단(위) | −1,100 ~ −1,050 (50) 🔶 | 축 중심 폭 800 |
🔶 확인 필요: 패드 합계 125 를 「양측 트렌치 깊이 50」에 맞춰 75 + 50 으로 나눈 것은 코치 추론.
5. 데이터 파일 model.js
- 첫 줄은
const MODEL =로 시작하고 그 뒤는 순수 JSON(따옴표 있는 키, 주석·끝 쉼표 없음), 마지막은;. check.sh 가 이 꼴을 python 으로 읽는다. - 최상위:
units("mm"),parts(배열). - 모든 부재 공통 필드:
id(겹치지 않음),name(한글 이름),kind,type,color("#rrggbb"),alpha(0~1, 없으면 1). kind값:wall,slab,pad,bottom,lean,stair,tunnel,fill.
| type | 쓰는 kind | 필드 |
|---|---|---|
ring (세운 속 빈 원통) |
wall | cx, cy, z0, z1, r_out, r_in |
disc (세운 원판) |
slab, pad, bottom, lean | cx, cy, z0, z1, r (z0 아래, z1 위) |
pipe (누운 속 빈 원통) |
tunnel | x, y, z(축 시작점), angle_deg, length, r_out, r_in |
stair (계단 런) |
stair | z_low, z_high, risers, first_riser, riser, tread, x_top, dir(+1/−1), y0, y1 |
fill (누운 원통 속 채움) |
fill | x, y, z, angle_deg, length(pipe 와 같은 축), r(세그먼트 안쪽 반지름), v0, v1, half_width(0 이면 제한 없음) |
부재 수: wall 1 · slab 19 · pad 1 · bottom 1 · lean 1 · stair 18 · tunnel 1 · fill 3 = 45.
색은 자유. 단 벽은 alpha 0.25.
6. 화면
index.html을 브라우저로 더블클릭해서 열면 바로 보인다(서버·인터넷 불필요). 캔버스 하나가 창을 채운다.- 처음 화면: 등각 정사 투영, 모델 전체(지하 70m 원통과 20m 터널)가 화면에 들어온다.
- 마우스 드래그: 궤도 회전(좌우 = Z축 둘레, 위아래 = 기울기). 휠: 확대/축소.
- 면은 방향에 따라 밝기가 다르다(면 음영). 먼 면부터 그린다(화가 알고리즘).
- 원은 32각형. 벽만 반투명(0.25), 나머지는 불투명.
7. 그리기 방식 (함수 이름 목록 — 구현은 하이쿠)
- 부재 → 면:
facesRing,facesDisc,facesPipe,facesStair,facesFill,facesBox - 모든 면 모으기:
buildFaces - 카메라:
rotatePoint,project - 그리기:
shadeColor,sortFaces,draw - 입력:
onMouseDown,onMouseMove,onMouseUp,onWheel - 라이브러리·외부 파일 불러오기 금지. 파일은
index.html,viewer.js,model.js셋(+ README.md).
8. 합격 기준
8.1 check.sh (기계 판정)
bash check.sh → 항목마다 [PASS]/[FAIL], 전부 PASS 면 종료 코드 0. 항목 이름 앞에 기능 ID(F01…)가 붙는다.
- 파일 셋이 있고
index.html이model.js다음에viewer.js를 읽는다. - 외부 라이브러리 흔적이 없다(
http://,https://,import,require(). model.js가 위 꼴의 JSON 이고, 부재 수·치수가 4장 표와 같다(허용 오차 1mm).
8.2 수석 육안 체크리스트 (check.sh 로 못 재는 것)
- 더블클릭으로 열린다(Chrome, Edge)
- 첫 화면이 등각이고 모델 전체가 보인다
- 드래그 회전·휠 확대가 된다
- 벽 너머로 슬래브·계단·터널이 보인다
- 계단이 층마다 방향을 바꾼다
- 터널이 평면 4시 방향, 19B 높이에 붙어 있다
- 1장의 세 문장을 화면으로 말할 수 있다
9. 나중 (MVP 뒤, 시간이 남으면)
프리셋 뷰·리셋 → 클릭 선택·속성 패널 → 부재별 표시/숨김 → 축·그리드·단위 → 슬래브 개구부 → 단면·거리·치수 라벨·PNG.
10. 추가 — 도면 분할 화면과 도면 클릭 연결 (2026-10-20 수석 지시, F13~F15)
10.1 화면 배치 (F13)
index.html본문: 왼쪽#drawPane· 가운데 세로 막대#splitter(폭 6px) · 오른쪽#viewPane(기존 캔버스).- 처음 비율 50:50. 막대를 끌면 왼쪽 폭이 바뀐다(창 폭의 15%~85%). 바뀔 때마다 캔버스 크기를 오른쪽 칸에 맞추고 다시 그린다.
- 도면 파일은
private/drawing.svg고정.private/는.gitignore로 커밋하지 않는다(제출 압축에만 넣는다). 파일이 없으면 왼쪽 칸에 「도면 파일 없음 — private/drawing.svg 를 넣으세요」.
10.2 도면 표시·확대·이동 (F14)
#drawPane안에#drawStage하나, 그 안에<img id="drawing">(도면)과 같은 크기로 겹친 인라인<svg id="hotspots" viewBox="0 0 443 443">(클릭 영역). 확대·이동은#drawStage의 CSS transform 으로 한다.- 처음 화면: 도면 SVG 좌표 상자
fit(아래 MODEL.drawing) 이 왼쪽 칸에 꽉 차게(fitDrawing). - 휠 = 마우스 위치 기준 확대/축소(0.5~40배), 드래그 = 이동.
10.3 도면 ↔ 부재 연결 (F15)
model.js최상위에drawing추가:src"private/drawing.svg" ·viewBox[0, 0, 443, 443] ·fit[118, 160, 245, 250](u0, v0, u1, v1) ·section{x0 139.0, y0 174.08, mm_per_unit 1000} ·plan{cx 227.51, cy 177.1, mm_per_unit 1000}- 좌표 (u = 도면 가로, v = 도면 세로, 아래로 +):
- 단면 A-A: u = x0 + X/1000, v = y0 − z/1000 (도면 선으로 확인: 벽 바깥 133.5/144.5, 안쪽 134.15/143.85, 1B 상면 181.48)
- 평면도: u = cx + x/1000, v = cy − y/1000
- 부재별 클릭 영역(다각형, 도면 단위). 아래 순서대로 만들고, 겹치면 나중 것이 위:
- stair: 단면, u = x0 ± 4.85, v = y0 − (z_high − 300)/1000 ~ y0 − z_low/1000
- wall: 단면 두 띠, u = x0 − 5.5 ~ x0 − 4.85 와 x0 + 4.85 ~ x0 + 5.5, v = y0 − z1/1000 ~ y0 − z0/1000
- tunnel: 평면, 축 시작점에서 angle_deg 방향으로 length, 폭 ±r_out 인 사각형
- slab, pad, bottom, lean (disc): 단면, u = x0 ± r/1000, v = y0 − z1/1000 ~ y0 − z0/1000
- fill: 영역 없음
- 동작: 영역 위에 마우스 → 노란 테두리. 클릭 → 그 부재 선택: 도면 영역을 #ffd400(불투명도 0.35)으로 칠하고, 3D 에서 그 부재를 #ffd400·alpha 1 로 그리고, 3D 칸 왼쪽 위에 「선택: 부재 이름」. 영역 밖 클릭 → 선택 해제.
- 함수 이름:
onSplitDown,onSplitMove,onSplitUp,fitDrawing,onDrawWheel,onDrawDown,onDrawMove,onDrawUp,sectionPoly,planPoly,buildHotspots,selectPart - 3D → 도면 방향(3D 클릭으로 도면 표시)은 하지 않는다(나중).
11. 추가 — 제출 문서 두 개 (2026-10-20 수석 지시, F16~F17)
제출물은 git log, 코드, 문서. 문서는 둘로 나눈다. 둘 다 첫머리에 🔒 한 줄. 사실만 쓰고, git log·PROGRESS.md·SPEC.md 에 근거가 있는 내용만 쓴다.
11.1 구동설명서.md — 무엇을 어떤 기획으로 만들었고 어떻게 돌리나 (F16)
제목 순서(## 단계 제목에 아래 낱말을 그대로 넣는다):
- 목적 — 무엇을 누구에게 보여 주는가(SPEC 1장 세 문장)
- 기획 — 도면 선정 → 범위 결정 → 설계 면담으로 치수·규칙 확정(SPEC 3~5장 요지) → SPEC·FEATURES·check.sh → 기능 하나씩 커밋. 결정된 것 중 핵심 5개 이상(예: 지표 = z 0, 터널 패드 = 19B 패드 높이, 계단 첫 단 100)
- 범위 — 넣은 것 / 뺀 것 (SPEC 2장, 9장)
- 구동 — 더블클릭으로 연다, 도면은
private/drawing.svg에 둔다, 브라우저 - 조작 — 3D 드래그·휠, 가운데 막대, 도면 확대·이동, 도면 클릭 → 3D 강조
- 작업 방식 — WSL2, Claude Code(Haiku 5.5)가 코드 작성·수정·커밋, 합격 판정은 check.sh, 설계 면담·검토와 F02 이후 하이쿠 호출은 수석 위임으로 상위 모델 코치 세션이 맡음, 기능 1개 = 커밋 1개
- 한계 — 계단은 위에서 보면 슬래브에 가려 옆에서 본다, 슬래브 개구부 없음, 3D → 도면 방향 없음 등
- 이력 —
git log --oneline결과 표(해시·첫 줄)
11.2 README.md — 코드 설명 (F17, 기존 README 를 고쳐 쓴다)
제목 순서: 파일 구조 · 실행 · 데이터(model.js) · 코드(viewer.js) · 그리기 방식 · 도면 연결 · 검사
- 데이터: type 별 필드 표(SPEC 5장), drawing 항목(SPEC 10.3)
- 코드: SPEC 7장·10.3 의 함수 이름을 모두 적고 각각 한 줄 설명
- 그리기 방식: 회전·정사 투영·면 음영·먼 면부터(화가 알고리즘)·벽 반투명
- 도면 연결: 단면·평면 좌표 변환식
- 검사:
bash check.sh,bash check.sh F0n
12. 변경 — 구동설명서를 HTML 위키로 (2026-10-20 수석 지시, F18)
- 11.1 의
구동설명서.md를 HTML 여러 쪽으로 옮기고, md 파일은 지운다(내용은 그대로 옮기고 git 기록에는 남는다). - 폴더
guide/: 목차index.html+ 8쪽. 파일 이름(쪽 제목):purpose.html(목적) ·plan.html(기획) ·scope.html(범위) ·run.html(구동) ·control.html(조작) ·method.html(작업 방식) ·limits.html(한계) ·history.html(이력). 모양은guide/guide.css하나. - 모든 쪽(목차 포함):
- 맨 위에 「← 3D 화면으로 돌아가기」 버튼 —
../index.html로 가는 링크 - 🔒 한 줄,
<h1>쪽 제목 - 위키처럼 본문 낱말에 다른 쪽 링크를 건다(예: 기획 쪽의 「범위」 → scope.html). 쪽마다 다른 쪽 링크 2개 이상
- 아래에 「목차」 링크와 이전/다음 쪽 링크
- 맨 위에 「← 3D 화면으로 돌아가기」 버튼 —
- 3D 화면(
index.html): 오른쪽 3D 칸 오른쪽 위에 「구동 설명서」 버튼<a id="guideLink" href="guide/index.html">. 3D 드래그를 방해하지 않게 작게. - 라이브러리·외부 URL 금지는 그대로. 파일 목록에
guide/추가.
13. 추가 — 선명한 도면과 모든 그림 연결 (2026-10-20 수석 지시, F19~F20)
13.1 선명한 도면 (F19)
- 원인: 도면 SVG 의 선 굵기가 0.75 도면단위로 박혀 있어 확대할수록 굵어지고,
#drawStage를 CSS scale 로 키워 그림이 뭉개진다. tools/prep_drawing.py(python3, 표준 라이브러리만):python3 tools/prep_drawing.py <원본.svg> private/drawing.svg. 원본은 고치지 않고, 출력 SVG 는 0 이 아닌 stroke-width 를 모두 1 로 바꾸고 루트에<style>로 모든 요소에vector-effect: non-scaling-stroke를 건다(화면에서 1px 선). 원본 파일 이름·경로를 코드나 문서에 적지 않는다.- 확대·이동은 CSS scale 대신
#drawing과#hotspots의 실제 width/height 를 배율에 맞춰 바꾸고(브라우저가 벡터를 다시 그림), 위치만 translate 로 옮긴다. 함수applyDrawView.
13.2 모든 그림 연결 (F20)
MODEL.drawing.views배열을 추가한다(10.3 의 section·plan 은 그대로 둔다). 값은 아래 표 그대로.
| id | type | 그림 | 기준 |
|---|---|---|---|
| secA | section | 단면 A-A | x0 139.0, y0 174.08 |
| secB | section | 단면 B-B | x0 179.01, y0 174.08 |
| secA2 | section_break | 단면 A-A (4B~15B 생략) | x0 263.41, y0_upper 173.04, y0_lower 140.04, z_upper_min −17600, z_lower_max −53600 |
| secB2 | section_break | 단면 B-B (4B~15B 생략) | x0 284.23, 나머지 secA2 와 같음 |
| plan1 | plan | 평면도 | cx 227.51, cy 177.1, tunnel true |
| plan2 | plan | 평면도 2 | cx 227.51, cy 197.1, tunnel false |
| slabOdd | slabplan | 중간슬래브 (홀수층) | cx 227.51, cy 226.31, kind slab, z1 = 1B·3B·…·17B 상면 |
| slabEven | slabplan | 중간슬래브 (짝수층) | cx 252.27, cy 226.31, kind slab, z1 = 2B·4B·…·16B 상면 |
| slab18 | slabplan | 중간슬래브 (18B) | cx 277.02, cy 226.31, kind slab, z1 [−63500] |
| slab19 | slabplan | 중간슬래브 (19B) | cx 227.51, cy 247.32, kind slab, z1 [−66200], tunnel true |
| slab20 | slabplan | 하부슬래브 (20B) | cx 252.27, cy 247.32, kind bottom, z1 [−69000] |
- 그림별 클릭 영역(모두 도면 단위, 각 view 의 기준으로 계산):
- section: 10.3 규칙 그대로(stair → wall → slab·pad·bottom·lean). x0·y0 만 view 값.
- section_break: section 과 같되, 부재의 z 범위가 전부 z_upper_min 이상이면 y0_upper, 전부 z_lower_max 이하이면 y0_lower 로 계산하고, 그 밖(생략 구간에 걸친 것)은 영역 없음. 벽은 위 조각(0 ~ z_upper_min)과 아래 조각(z_lower_max ~ 바닥) 두 개로 나눈다.
- plan: 고리(반지름 4.85 ~ 5.5, 32각형 두 개를 evenodd 로) → 벽. tunnel true 면 10.3 의 터널 사각형도(그 view 의 cx·cy 기준).
- slabplan: 안쪽 원판(반지름 4.85) → z1 상면이 일치하는 kind 부재 전부를 한 번에 선택. 고리 → 벽. slab19 는 19B 패드도 함께, tunnel true 면 터널 사각형.
- 선택은 여러 부재를 한 번에 할 수 있다:
selectPart가 부재 id 배열을 받는다. 「선택:」 글자는 view 이름과 부재 이름(여럿이면 「1B, 3B, … 17B」처럼). - 함수:
viewHotspots(view 하나의 영역 목록),buildHotspots는 views 를 돈다. - 참고(구현하지 않음): 슬래브 평면에 있는 개구부(계단·케이블 slot·펌프 배출구·전기 배선구)는 3D 에 없다(2장 범위 밖). 그래서 3D 슬래브는 꽉 찬 원판이고, 계단 맨 윗단이 슬래브를 뚫고 보인다. 개구부를 넣으려면 슬래브 평면에서 개구 위치·크기를 읽어 원판 윗면·아랫면을 구멍 난 다각형(evenodd)으로 그려야 한다 — 다음 단계 후보.
14. 변경 — 구동설명서는 사용 설명서 (2026-10-20 수석 지시)
guide/는 누구나 이 웹을 작동할 수 있게 하는 사용 설명서다. 수석 지시로 코치 세션이 직접 쓴다. 파일 이름·목차·돌아가기 버튼 규칙(12장)과 check.sh 검사 항목은 그대로 지킨다.
15. 추가 — 슬래브 구멍, 계단 방향 정정, 카메라 이동, 사람 애니메이션 (2026-10-20 수석 지시, F21~F23)
15.1 슬래브 구멍 (F21)
- 슬래브 평면 도면에서 읽은 구멍(슬래브 중심 기준 mm, X 오른쪽·Y 위쪽). 노란 선(방호 난간)이 비어 있는 쪽이 계단 입구.
- disc 부재에
holes필드: 다각형 목록, 다각형은 [x, y] 점 목록(mm). 구멍이 없으면 필드를 생략하거나 빈 목록.
| 구멍 | 다각형 (mm) |
|---|---|
| T_odd (계단 + 위 slot) | (−2100,0) (2100,0) (2100,1000) (1250,1000) (1250,2200) (−1250,2200) (−1250,1000) (−2100,1000) |
| T_even (계단 + 아래 slot) | (−2100,0) (2100,0) (2100,−1000) (1250,−1000) (1250,−2200) (−1250,−2200) (−1250,−1000) (−2100,−1000) |
| slot_up | 사각형 x −1250 |
| slot_dn | 사각형 x −1250 |
| stair_even | 사각형 x −2100 |
| pump | 사각형 x −750 |
| pump_pad | 사각형 x −750 |
| elec | 사각형 x 2080 |
| 부재 | holes |
|---|---|
| 홀수층 슬래브 1B·3B·…·17B | T_odd, slot_dn, pump, elec |
| 짝수층 슬래브 2B·4B·…·16B | T_even, slot_up, pump, elec |
| 18B 슬래브 | stair_even, pump, elec (slot 없음 — 수석 확인 Q1) |
| 19B 슬래브 | pump (집수정 사다리도 이 구멍으로 — 수석 확인 Q2) |
| 19B 패드 | pump_pad |
| 하부 슬래브·바닥콘크리트 | 없음 |
- 그리기: 원판 윗면·아랫면 = 바깥 32각형 + 구멍 다각형을 한 면으로 묶어 evenodd 로 칠한다. 구멍 둘레마다 세로 옆면을 더한다. 함수
facesDiscHoles.
15.2 계단 방향 정정 (F21, 4.4 의 🔶 추론을 도면으로 바로잡음)
- 홀수 런(1·3·…·17, 홀수층 슬래브에서 내려감): x_top +2100, dir −1, Y 띠 0 ~ +1000
- 짝수 런(2·4·…·18, 짝수층·18B 에서 내려감): x_top −2100, dir +1, Y 띠 −1000 ~ 0
- 블록 계산식(4.4)은 그대로.
15.3 카메라 이동 (F22)
- 3D 는 보는 중심
CAM.target[x, y, z](처음 = 모델 중심)을 기준으로 그린다. - 왼쪽 드래그 = 회전(그대로). 오른쪽 드래그 또는 Shift + 왼쪽 드래그 = 화면 이동(target 을 화면 평면에서 옮김). 캔버스에서 오른쪽 클릭 메뉴는 막는다.
- 휠 = 마우스 아래 점을 고정한 채 확대·축소, 배율 0.5~40.
- 더블클릭 또는 3D 칸 오른쪽 위
<button id="resetView">처음 화면</button>= 처음 등각 화면(yaw·pitch·배율·target 초기값). - 함수
panView,zoomAt,resetView.
15.4 사람 애니메이션 (F23)
- 사람 = 상자 4개(mm): 다리 2개 150×150×800(좌우 100 간격), 몸통 400×250×600(z 800
1400), 머리 220×220×220(z 14501670). 색 #ff8c1a, 불투명. 데이터 파일에는 넣지 않고 viewer.js 가 그린다. - 경로
buildWalkPath: 계단 데이터(stair 18개)로 만든다. 런마다- 위 슬래브 위, 입구 앞 300 지점 (x_top − dir·300, 띠 중심 y, z_high)
- 디딤판마다 가운데 점: k = 단 수−1 … 1 순서로 x = x_top + dir·300·(단 수 − 1 − k + 0.5), z = z_low + 100 + 200·(k − 1)
- 아래 슬래브에 내려선 점 (x_top + dir·300·단 수, 띠 중심 y, z_low)
- 다음 런 입구 앞 점으로 옆걸음(같은 높이)
- 1B 런 입구에서 19B 까지 내려가고, 같은 길로 1B 까지 올라오기를 쉬지 않고 반복(속도 1000 mm/s, 점 사이 직선 보간). 함수
walkerAt(시각 → 위치),facesPerson,animate(requestAnimationFrame). - 3D 칸 오른쪽 위
<button id="walkToggle">— 움직일 때 「사람 ❚❚」, 멈췄을 때 「사람 ▶」. 처음에는 움직인다. - 멈춰 있을 때는 지금처럼 필요할 때만 다시 그린다. 느리면 초당 30번으로 줄인다.
16. 추가 — 바닥부: 월류벽·집수조·침사조·침사구·사다리 (2026-10-20 수석 사전 승인, F24~F25)
도면 단면 A-A 하부와 19B·20B 슬래브 평면에서 읽음(mm, 슬래브 중심 기준).
- 정정: 15.1 의 「19B 집수정 사다리는 펌프 배출구로(Q2)」는 도면과 다르다. 19B 동쪽 벽 쪽에 침사구 1000×1000 구멍이 따로 있고, 사다리는 그 아래 동쪽 벽에 붙어 있다. (수석 부재 중 도면 근거로 코치가 바로잡음)
16.1 부재 (F24)
| 부재 | kind | type | 값 |
|---|---|---|---|
| 월류벽 (넘침 홈 양옆 2, 홈 아래 1) | weir | prism | ① 다각형 사각형 X 1400 |
| 집수조 (펌프 쪽, 서쪽) | pit | zone | r 4850, x_min −4850, x_max 1400, z −69000~−67000, 반투명 0.25 |
| 침사조 (터널 쪽, 동쪽) | pit | zone | r 4850, x_min 1600, x_max 4850, z −69000~−67000, 반투명 0.25 |
| 사다리 | ladder | ladder | x0 4668, x1 4708(기둥 두께), yc 0, width 400, z0 −68700, z1 −66200, rung 300 |
| 19B 슬래브 구멍 추가 | — | — | 침사구 사각형 X 3850 |
| 19B 패드 구멍 추가 | — | — | 침사구 사각형 X 3850 |
prism:polygon([x, y] 점 목록),z0,z1— 다각형을 세로로 늘인 기둥(윗면·아랫면·옆면). 함수facesPrism.zone: 원(반지름 r) 안에서 x_min ≤ X ≤ x_max 인 부분을 z0~z1 로 세운 덩어리(활꼴 기둥). 함수facesZone.ladder: 기둥 2개(X x0~x1, Y = yc ± width/2 에 두께 40), 가로대(rung 간격, 두께 25)를 상자로. 함수facesLadder.- 색은 자유(물칸은 서로 다른 옅은 색). 🔶 사다리 폭 400·가로대 300 은 도면에 없어 코치가 정한 값(산업안전보건기준 사다리 폭 30cm 이상 참고).
16.2 도면 연결 (F25)
- 단면 A-A 두 그림(view id
secA,secA2)에서: 월류벽·집수조·침사조·사다리를 단면 규칙(u = x0 + X/1000, v = y0 − z/1000)으로 X 범위 × z 범위 사각형 영역. 순서 집수조 → 침사조 → 월류벽 → 사다리(나중 것이 위). - 하부슬래브(20B) 평면(
slab20)에서: 위 네 부재를 평면 규칙(u = cx + X/1000, v = cy − Y/1000)으로. 집수조·침사조는 원과 세로선으로 자른 활꼴 다각형. - 19B 슬래브 평면(
slab19)의 침사구 자리를 누르면 사다리를 선택. - 함수
bottomHotspots.
17. 추가 — 상부 구조·환기구·방호 난간·배관 (2026-10-20 수석 사전 승인, F26~F29)
목표: 2D 도면에 있는 것을 모두 3D 로(수석 지시). 단면 A-A 상부, 단면 B-B(환기 덕트를 펼친 단면), 평면도 1·2, 슬래브 평면 노란 선에서 읽음. 🔶 = 도면에 수치가 없어 코치가 정한 값.
17.1 상부 슬래브·맨홀 목·벽 상단 (F26)
- 정정: 벽체 윗면은 지표가 아니라 상부 슬래브 밑면 z −2000 (벽 위로는 흙 1500 과 맨홀 목). 4.1 의 벽체 z1 0 → −2000.
- 상부 슬래브: kind
topslab, type disc, r 5500(벽 바깥면까지), z −2000 ~ −1500, holes: 원 (0, 0, 450), 원 (2223, 3803, 450). - holes 항목은 다각형 외에
{"circle": [x, y, r]}도 된다(16각형으로 그림). prism 에도holes를 쓸 수 있다. 함수holePolygon. - 맨홀 목 2개: kind
neck, type prism, z −1500 ~ 0, 각각 1300 정사각형을 30° 돌린 다각형 + 가운데 원 구멍 r 450.- 목 1: 238, 888], [-888, 238], [-238, -888], [888, -238, holes [{"circle": [0, 0, 450]}]
- 목 2: 2461, 4691], [1335, 4041], [1985, 2915], [3111, 3565, holes [{"circle": [2223, 3803, 450]}]
- 정정: 전기 배선구 구멍은 벽을 따라 돈 1000×300 사각형이다(난간 선으로 확인). 15.1 의 elec 를 다각형 2079, 4079], [2922, 3528], [3072, 3753], [2229, 4304 로 바꾼다(홀수층·짝수층·18B).
17.2 분기구·환기구 (F27)
obox(돌린 상자):cx, cy, angle_deg, s0, s1, t0, t1, z0, z1. 점 = (cx, cy) + s·(cosθ, sinθ) + t·(−sinθ, cosθ). 함수facesObox.- 분기구 kind
branch, cx = cy = 0, 안 2200×2100, 벽 350, 바닥 450:
| 이름 | angle | s | t | z |
|---|---|---|---|---|
| 바닥 | 167 | 4850 ~ 8350 | -1450 ~ 1450 | -4550 ~ -4100 |
| 지붕 | 167 | 4850 ~ 8350 | -1450 ~ 1450 | -2000 ~ -1650 |
| 옆벽 1 | 167 | 4850 ~ 8350 | 1100 ~ 1450 | -4100 ~ -2000 |
| 옆벽 2 | 167 | 4850 ~ 8350 | -1450 ~ -1100 | -4100 ~ -2000 |
| 끝벽 | 167 | 8000 ~ 8350 | -1100 ~ 1100 | -4100 ~ -2000 |
- 환기구(강제) 덕트·환기탑 kind
vent, cx = cy = 0, angle 0 (s = X, t = Y). 덕트 안 3000×2100, 남쪽으로 내려가 동쪽으로 꺾여 환기탑(안 3000 정사각형, 지표 z 0 까지)에 닿는다:
| 이름 | angle | s | t | z |
|---|---|---|---|---|
| 덕트 바닥 1 | 0 | -1850 ~ 1850 | -10500 ~ -4610 | -4600 ~ -4100 |
| 덕트 바닥 2 | 0 | 1850 ~ 7504 | -10500 ~ -6800 | -4600 ~ -4100 |
| 덕트 지붕 1 | 0 | -1850 ~ 1850 | -10500 ~ -4610 | -2000 ~ -1650 |
| 덕트 지붕 2 | 0 | 1850 ~ 7504 | -10500 ~ -6800 | -2000 ~ -1650 |
| 덕트 서벽 | 0 | -1850 ~ -1500 | -10500 ~ -4610 | -4100 ~ -2000 |
| 덕트 동벽 | 0 | 1500 ~ 1850 | -6800 ~ -4610 | -4100 ~ -2000 |
| 덕트 북벽 | 0 | 1500 ~ 7504 | -7150 ~ -6800 | -4100 ~ -2000 |
| 덕트 남벽 | 0 | -1850 ~ 7504 | -10500 ~ -10150 | -4100 ~ -2000 |
| 환기탑 바닥 | 0 | 7504 ~ 11205 | -10500 ~ -6799 | -4600 ~ -4100 |
| 환기탑 서벽 위 | 0 | 7504 ~ 7854 | -10149 ~ -7149 | -2000 ~ 0 |
| 환기탑 동벽 | 0 | 10855 ~ 11205 | -10500 ~ -6799 | -4100 ~ 0 |
| 환기탑 북벽 | 0 | 7504 ~ 11205 | -7149 ~ -6799 | -4100 ~ 0 |
| 환기탑 남벽 | 0 | 7504 ~ 11205 | -10500 ~ -10149 | -4100 ~ 0 |
17.3 방호 난간·배관·위쪽 사다리 (F28)
- 방호 난간 kind
railing, typerailing:z(설치 슬래브 윗면),h1100 🔶,paths(꺾은선 목록, 각 [x, y] 점). 꺾은선을 따라 높이 h 의 얇은 면(난간 판)을 세운다. 함수facesRailing.- 홀수층 9장(1B·3B·…·17B 윗면): paths [2140, 4350], [1940, 4050], [2950, 3390], [3150, 3690, 1350, -900], [1350, -2300], [-1350, -2300], [-1350, -900], [1350, -900, 2100, -100], [-2200, -100], [-2200, 1100], [-1350, 1100], [-1350, 2300], [1350, 2300], [1350, 1100], [2100, 1100, -850, 4780], [-850, 3550], [850, 3550], [850, 4780]
- 짝수층 8장(2B·…·16B 윗면): paths [-850, 4780], [-850, 3550], [850, 3550], [850, 4780, -2100, 100], [2200, 100], [2200, -1100], [1350, -1100], [1350, -2300], [-1350, -2300], [-1350, -1100], [-2100, -1100, 1350, 2300], [1350, 900], [-1350, 900], [-1350, 2300], [1350, 2300, 2140, 4350], [1940, 4050], [2950, 3390], [3150, 3690]
- 19B(z −66100, 패드 위): paths [-850, 4780], [-850, 3550], [850, 3550], [850, 4780]
- 18B: 도면에 난간 없음.
- 펌프 배출관(STS Ø100) kind
pipe, type ring: cx 0, cy 4220, r_out 57, r_in 50, z −68800 ~ −2000 🔶(도면 L=64.0m 는 경로 길이로 보고 집수조 바닥 근처부터 상부 슬래브 밑까지). - 위쪽 사다리 kind
ladder_up, type ladder:- 서쪽(1B → 분기구 바닥): x0 −4720, x1 −4680, yc 1080, width 400, z0 −7400, z1 −4100, rung 300
- 가운데(1B → 맨홀 → 지표): x0 −20, x1 20, yc 0, width 360, z0 −7400, z1 0, rung 300 🔶
17.4 도면 연결 (F29)
- 단면 A-A(secA, secA2 위쪽): topslab 은 disc 규칙, neck·branch·vent·ladder_up 은 꼭짓점들의 X 범위 × z 범위 사각형.
- 평면도 1·2(plan1, plan2): 원판 안(r 4850)을 먼저 topslab 영역으로, 그 위에 neck 다각형, branch·vent obox 바닥 윤곽(꼭짓점 4개), 그 위에 기존 벽 고리·터널.
- 함수
upperHotspots.
18. 변경 — 사람 두 명, 콘크리트 계단, 구동설명서 3쪽 (2026-10-21 수석 지시, F30~F32)
18.1 사람 두 명 (F30)
- 지하시설물에 들어갈 때는 2명 이상이 조를 이루어 이동한다. 사람을 두 명으로 하고, 두 번째 사람은 첫 사람과 같은 경로를 2초 뒤에 따라온다(속도 1000 mm/s → 약 2m 간격). 경로 끝에서 돌아설 때 잠깐 스쳐 지나가는 것은 받아들인다.
- 색: 첫 사람 #ff8c1a, 두 번째 #e07010. 함수
teamAt(시각 → 두 위치). 「사람 ▶/❚❚」 버튼은 두 사람을 함께 멈춘다.
18.2 콘크리트 계단 (F31)
- 철근콘크리트 계단: 경사판(계단 슬래브) 위에 단면이 직각삼각형인 단이 올라간 꼴이고, 밑면은 경사지며 계단 아래는 빈 공간이다(수석 제공 측면도로 비례 확인: 안쪽 모서리 선에서 수직 200 아래에 밑면, 위끝은 위 슬래브 밑보다 100 낮음, 아래끝은 마지막 디딤판보다 약 450 앞에서 아래 슬래브에 닿음).
- 런마다 단면 다각형(s = x_top 에서 내려가는 방향 거리, 실제 x = x_top + dir·s)을 만들어 Y 띠(y0~y1) 폭만큼 늘인다. 함수
stairProfile(런 → 점 목록),facesStair가 이것으로 면을 만든다(옆면 2개 + 둘레 변마다 사각형).- (0, z_high) 에서 시작
- j = 0 … n−2: 챌판 (j·300, z_high − 200·(j+1)) 로 내려가고, 디딤판 ((j+1)·300, z_high − 200·(j+1)) 로 간다
- 첫 단 챌판: ((n−1)·300, z_low)
- 아래 슬래브 윗면을 따라 (s_b, z_low), s_b = (z_high − z_low − 400) × 1.5
- 경사 밑면: (0, z_high − 400)
- 다시 (0, z_high) 로 닫는다
- 디딤판 높이·위치는 4.4·15.2 와 같다(사람 경로 그대로). 색은 그대로.
18.3 구동설명서 3쪽 (F32)
guide/는 목차(index) + 목적(purpose)·구동(run)·조작(control) 3쪽만 남긴다. plan·scope·limits·method·history 쪽은 지운다(git 기록에는 남는다).- 🔒 줄은 구동설명서에 넣지 않는다(수석 지시, 외부에 내보낼 문서). README·SPEC 의 표기는 그대로.
- 사람 문장: 「주황색 사람 두 명 — 지하시설물에 들어갈 때는 2명 이상이 조를 이루어 이동해야 한다 — 이 시설물 계획 중 유지관리 동선계획을 따라 이동하는 과정을 보여 준다.」
- 「3D 를 눌러 도면을 찾는 방향은 아직 없다」 문장은 뺀다. 지운 쪽을 가리키는 링크·문구는 남은 쪽으로 바꾸거나 지운다. 도면 연결 표는 조작 쪽으로 옮긴다.
- 쪽마다: 맨 위 「← 3D 화면으로 돌아가기」, h1, 다른 두 쪽 링크, 목차·이전/다음 링크. 수석 지시로 코치 세션이 쓰고, 하이쿠가 커밋한다.
19. 추가 — 도면 추출 도구와 대조 검사 (2026-10-21 수석 지시, F33~F34)
목적: 「도면 → 데이터」 단계를 저장소 안 코드로 옮긴다. 범용 변환기가 아니라 이 도면에 대한 반자동 변환: 도면에서 기계적으로 읽을 수 있는 값은 도구가 읽고, 사람이 정한 규칙(패드 높이, 계단 첫 단 등)은 SPEC 의 설계 결정으로 남는다.
19.1 추출 도구 (F33)
python3 tools/extract_from_svg.py <도면.svg> model_extracted.js— 표준 라이브러리만. 출력은 model.js 와 같은 꼴(const MODEL =+ 순수 JSON +;),units"mm".- 도면 좌표를 코드에 숫자로 적지 않는다(그림 위치·기준점은 모두 도면에서 찾는다). 규칙:
- 축척: 단면도·평면도 모두 1 도면단위 = 1000 mm. 평면은 X = (u − 중심u)·1000, Y = −(v − 중심v)·1000. 단면은 X = (u − 축u)·1000, z = −(v − 지표v)·1000.
- 그림 찾기:
<text>의 글자(공백 무시)와 transform 의 평행이동 값으로 그림 이름 위치를 얻는다. 「단면A-A」 중 가장 왼쪽 것이 전체 단면이다. 슬래브 평면은 「중간슬래브(홀수층)」「(짝수층)」「(18B)」「(19B)」 글자 바로 아래에 있는 원(반지름 5.5 와 4.85 두 개)의 중심. - 단면 A-A(전체): 글자 아래에 있는 하늘색 rgb(0,255,255) 세로선 중 가장 긴 두 쌍이 벽 — 바깥 두 선 간격 = 외경, 안 두 선 간격 = 내경, 축u = 바깥 두 선의 가운데. 지표v = 그 근처 녹색 rgb(0,255,0) 수평선(지반선)의 v.
- 슬래브 윗면: 단면 A-A 의 하늘색 수평선 중 길이가 내경과 같은(±0.05) 선. 바로 0.3 아래에 같은 길이 선이 또 있으면 위 선만 윗면이다. 결과는 위에서부터 19개(1B~19B).
- 슬래브 구멍: 슬래브 평면 원(반지름 4.85) 안의 검은색 rgb(0,0,0)·회색 rgb(128,128,128) 꺾은선 중 닫힌 사각형은 그대로, 세 변만 있는 꺾은선(벽에 붙은 구멍)은 두 끝점을 이어 닫은 다각형을 구멍으로 한다. 다른 후보를 감싸는 후보(난간 윤곽, 18B 처럼 회색으로 그려진 경우)는 뺀다. 계단 구멍은 5b 로 뽑는다.
- 방호 난간: 슬래브 평면 원 안의 노란색 rgb(255,255,0) 꺾은선을 그대로 paths 로. 5b. 계단 구멍(난간으로만 그려짐): 노란 난간 꺾은선 중 두 끝점이 서로 다르고 둘 다 벽(반지름 4.85)에서 0.3 넘게 떨어진 것이 계단 입구 난간이다. 꺾은선을 두 끝점으로 닫은 다각형에서, 닫는 변(입구)은 그대로 두고 나머지 변을 0.1(=100 mm) 안쪽으로 옮긴 다각형을 구멍으로 한다. 홀수층·짝수층 평면에서 하나씩 나온다.
- 평면 → 층: 홀수층 평면 = 1B·3B·…·17B, 짝수층 = 2B·…·16B, 18B, 19B (4 의 윗면 목록 순서로).
- 색은 요소 자신 또는 가장 가까운 조상
<g>의 stroke 를 따른다.
- 출력 부재: 벽 1(type ring, r_out·r_in, z1 −2000, z0 −70200 — z 는 고정값 허용), 슬래브 19(type disc, z1 = 윗면, z0 = 윗면 − 300, r = 내경/2, holes = 5 의 구멍), 난간(type railing, 층마다 1, z = 슬래브 윗면, h 1100, paths = 6). 각 부재
name에 층 이름. - 함수(파이썬):
load_svg,find_views,section_walls,slab_tops,plan_holes,plan_railings,build_model.
19.2 대조 검사와 비교 화면 (F34)
compare.html: index.html 과 같은 화면인데model.js대신model_extracted.js를 읽는다(도면 연결 데이터가 없으면 왼쪽은 안내 글자). 3D 칸 위에 「추출 데이터로 그린 화면」 글자. 추출 결과와 수작업 모델을 눈으로 비교하는 용도.- check.sh 가 도구를 직접 돌려 model.js 와 대조한다(벽 반지름 ±10, 슬래브 윗면 ±60, 구멍은 추출된 각 구멍이 model.js 의 어느 구멍 범위 안(±40)에 들고, 난간 꺾은선 수·꼭짓점이 ±40 안에서 같다). 또 도면 좌표를 통째로 옮긴 사본으로 다시 돌려 결과가 같아야 한다(숫자를 코드에 박으면 실패). 그리고 도면 내용을 바꾼 사본 두 개로 돌려 결과가 그만큼 바뀌어야 한다 — 1B 슬래브 윗면 선만 0.5 내린 사본(1B 만 약 500 낮아짐), 홀수층 아래 slot 하나를 지운 사본(홀수층 9장에서 구멍이 하나씩 줄어듦). 공식이나 정답 값을 코드에 넣으면 실패한다.
model_extracted.js는 도구의 출력이며 커밋한다(도면 자체는 들어 있지 않다).
20. 변경 — 추출 입력을 DXF 로 (19장 대체, 2026-10-21 수석 지시, F33~F34)
- 수석이 원본 DWG 를 주었고, 코치가 GNU LibreDWG
dwg2dxf로 DXF 로 바꾼 뒤 우리 도면 세트의 그림(전체 단면·평면도·생략 단면·슬래브 평면)만 남겼다(표제란·다른 도면 제외). 파일private/drawing.dxf(ASCII, git 제외). 19장의 SVG 규칙은 이 장으로 대체한다(SVG 는 왼쪽 도면 표시에만 쓴다). - 이 SVG 규칙은 도면에 맞지 않았다(18B 윗면이 구멍에 잘려 18개만 잡힘, T자 구멍의 출처 불명확) — 하이쿠가 하드코딩 없이 보고해 드러남.
20.1 추출 도구 (F33)
-
python3 tools/extract_from_dxf.py <도면.dxf> model_extracted.js— 표준 라이브러리만(DXF 는 「그룹 코드 줄 + 값 줄」의 텍스트다). 출력 꼴은 19.1 과 같다. 도면 좌표·정답 값·층 높이 공식을 코드에 적지 않는다. -
DXF 읽기: ENTITIES 구역의 LINE·LWPOLYLINE(코드 70 의 1 비트 = 닫힘)·POLYLINE(+VERTEX…SEQEND, 머리의 10/20 은 기준점이라 쓰지 않음)·CIRCLE·TEXT. 레이어 = 코드 8, 색 = 코드 62(없으면 레이어 색).
-
규칙(단위: 모델 1 = 1 m → ×1000 mm, Y 위 +):
- 전체 단면: 레이어
구조물의 세로 선분 중 길이 60 m 넘는 것들. X 가 가장 작은 네 선 = 단면 A-A 의 벽(바깥 둘·안 둘): 외경 = 바깥 두 선 간격, 내경 = 안 두 선 간격, 축 X = 바깥 두 선 가운데. - 지표: 색 3(녹색) 선 중 단면 A-A 의 축 X 를 지나는 것의 그 자리 Y. z = (Y − 지표 Y)·1000.
- 슬래브 윗면: 레이어
구조물의 수평 선분 중 왼쪽 안쪽 벽 X 에서 시작하고(±0.01), 0.3(±0.01) 아래에 역시 왼쪽 안쪽 벽 근처(0.2 안)에서 시작하는구조물수평 선분이 있는 것. 위에서부터 19개 = 1B~19B (구멍에 잘린 층도 왼쪽 조각이 남는다). - 슬래브 평면: 레이어
S-TITLE글자 「중간슬래브(홀수층)」「(짝수층)」「(18B)」「(19B)」(공백 무시) 바로 아래에서 가장 가까운, 반지름 4.85 와 5.5 원이 겹친 중심. - 구멍: 그 평면의 안쪽 원 안에 있는 레이어
오프닝·구조물 계단객체로 만든다. (가) 꼭짓점 4개 이상인 LWPOLYLINE/POLYLINE 하나는 그대로 후보(닫혀 있지 않으면 두 끝점을 이어 닫는다 — 벽에 붙은 세 변 구멍). (나) 꼭짓점 3개인 열린 LWPOLYLINE 은 같은 레이어의 수평·수직 LINE 중 그 끝점에서 이어지는 것을 차례로 붙여 닫히면 후보. 기울어진 LINE(대각선)과 끝점에 붙지 않는 LINE(디딤판 선)은 쓰지 않는다. 같은 모양 중복은 하나만, 다른 후보를 감싸는 후보(난간 윤곽)는 뺀다. - 방호 난간: 그 평면 원 안의 레이어
계단, 색 2 인 꼭짓점 3개 이상 꺾은선 → paths (닫힌 것은 첫 점을 끝에 한 번 더). - 평면 → 층: 홀수층 = 1B·3B·…·17B, 짝수층 = 2B·…·16B, 18B, 19B (3 의 순서).
- 전체 단면: 레이어
-
출력 부재: 19.1 과 같음(벽 1, 슬래브 19 + holes, 난간 층마다 1).
-
함수(파이썬):
load_dxf,section_walls,ground_y,slab_tops,find_slab_plans,plan_holes,plan_railings,build_model. -
코치는 이 규칙대로만 짠 별도 구현으로 채점이 모두 통과함을 미리 확인했다(그 구현은 주지 않는다). 막히면 규칙 결함일 수 있으니 숫자를 넣지 말고 보고한다.
20.2 대조 검사 (F34)
- 19.2 와 같은 대조(벽 ±5, 윗면 ±5, 구멍 ⊆ model.js 구멍 ±10, 난간 ±10)와 compare.html.
- 계단 구멍(폭 4 m 넘는 구멍) 18개(홀수 9·짝수 8·18B 1).
- 변조 사본 셋: 좌표 전체 (+7, +5) m 이동 → 같은 결과 / 1B 슬래브(윗면·아랫면 선)만 0.5 m 내림 → 1B 만 500 낮아짐 / 홀수층 아래 slot 지움 → 구멍 9개 감소.
21. 추가 — 계단 추출 (2026-10-21 수석 지시, F35) — 계단이 생기면 사람 두 명도 추출 모델에서 걷는다
도면(DXF)에서 읽는다. 20.1 의 함수에 더한다. 함수 section_stairs, stair_parts.
- 단면 계단 윤곽: 레이어
구조물, 색 8, 닫힌 꺾은선, 꼭짓점 6개 이상, 단면 A-A 바깥 벽 두 선 사이. - 단 높이·디딤판: 윤곽들의 세로 변 길이(mm) 중 가장 많이 나온 값 = riser, 가장 작은 값 = first_riser, 가로 변 중 가장 많이 나온 값 = tread.
- 런 i(1~18): z_high = i 층 윗면, z_low = i+1 층 윗면(20.1 ③). risers = (z_high − z_low − first_riser) / riser + 1 (반올림).
- 방향 dir: 가장 낮은 꼭짓점의 z 가 z_low 인 단면 윤곽에서, 그 가장 낮은 꼭짓점들이 윤곽의 왼쪽 끝에 붙어 있으면 −1, 오른쪽 끝이면 +1. 단면에 윤곽이 없는 런(18)은 바로 위 런의 반대.
- x_top, y0, y1: i 층 슬래브 평면의 계단 구멍(20.1 ⑤ 의 구멍 중 폭 4 m 넘는 것). dir −1 이면 구멍의 오른쪽(X 최대) 가장자리, +1 이면 왼쪽(X 최소) 가장자리가 x_top. y0·y1 = 그 구멍의 Y 범위.
- 출력: kind·type
stair18개, 필드는 model.js 와 같다(z_low, z_high, risers, first_riser, riser, tread, x_top, dir, y0, y1, 이름·색은 자유).
- 코치는 이 규칙대로만 짠 별도 구현으로 F35 채점(대조·변조 시험)이 통과함을 미리 확인했다(그 구현은 주지 않는다).
22. 정정 — 평면도 좌표틀 (2026-10-21 수석 지시, F36)
- 발견(코치, 도면 DXF 재확인): 슬래브 평면(홀수·짝수·18B·19B·20B)과 단면 A-A 는 같은 틀이다(계단 구멍 긴 변 = X). 평면도 1·2 는 이 틀을 −30.3° 돌려 그렸다(평면도의 계단 구멍 긴 변·터널 중심선이 −30.3°). 4.5 와 17장의 터널·채움·분기구·환기구·상부 맨홀·목은 평면도에서 읽어 그 틀 그대로 넣었기 때문에, 모델 안에서 안쪽 부재와 30.3° 어긋났다. 예: 19B 평면에서 터널은 +X(계단과 나란히)인데 모델은 330°.
- 원칙: 모델 좌표 = 슬래브 평면 틀. 평면도에서 온 점 (x, y) 는 +30.3° 돌린다: (x cos30.3° − y sin30.3°, x sin30.3° + y cos30.3°). 그렇게 돌리면 맨홀 2 가 (0, 4405) = 펌프 구멍 바로 위, 목 두 개가 축에 맞는 1300 정사각형이 된다.
- 새 값(나머지 필드는 그대로):
| 부재 | 바꿀 것 |
|---|---|
| tunnel_TBM | x 4850, y 0, angle_deg 0 (19B 평면의 터널 선이 X 축과 나란하고 Y ±1300·±1500) |
| fill 3개 | x 4850, y 0, angle_deg 0 (터널과 같은 축) |
| topslab | holes [{"circle": [0, 0, 450]}, {"circle": [0, 4405, 450]}] |
| neck_1 | polygon -650, -650], [650, -650], [650, 650], [-650, 650, holes [{"circle": [0, 0, 450]}] |
| neck_2 | polygon -650, 3755], [650, 3755], [650, 5055], [-650, 5055, holes [{"circle": [0, 4405, 450]}] |
| branch 5개 | angle_deg 167 → 197.3 (s, t, z 그대로) |
| vent 13개 | angle_deg 0 → 30.3 (s, t, z 그대로) |
| ladder_up_W | x0 −4622, x1 −4582 (단면 A-A 의 사다리 중심 X −4602), yc −1260 🔶 (분기구 입구 쪽, 폭 400 이 벽 안에 드는 한도) |
- 17.1 의 목 다각형은 돌린 방향도 틀렸다(위 표로 대체). 슬래브 구멍·계단·난간·패드·바닥부·배관·가운데 사다리는 이미 슬래브 평면 틀이라 그대로.
- 도면 연결:
MODEL.drawing.views의 plan1·plan2 에"rot_deg": -30.3을 더한다. 평면 그림에 놓을 때 모델 (X, Y) 를 rot_deg 만큼 돌린 뒤(X' = X cos − Y sin, Y' = X sin + Y cos) u = cx + X'/k, v = cy − Y'/k. rot_deg 가 없는 그림은 0.planXY와planPoly(터널 사각형)에 넣는다(원은 그대로). - 결과: 3D 에서 터널이 계단과 나란히 +X 로 나가 침사구 쪽 벽으로 들어오고, 평면도 클릭 영역은 지금처럼 도면 그림 위에 놓인다.
23. 추가 — 터널 추출 (2026-10-21 수석 지시, F37)
도면(DXF)에서 TBM 터널과 터널 안 채움 3개를 읽는다. 20.1·21장 함수에 더한다. 함수 section_tunnel, plan_tunnel, tunnel_parts.
- 단면(높이·지름): 단면 A-A 오른쪽 바깥 벽선(20.1 ① 의 X 가 가장 큰 선)에서 시작해 바깥으로 1 m 넘게 뻗는 레이어
구조물수평선(기울기 5% 미만, 터널 경사가 조금 있다). 벽 쪽 끝의 z(20.1 ② 방식) 다섯 개를 위에서부터 [바깥 위, 안 위, 패드 윗면, 안 아래, 바깥 아래] 로 본다.- 축 z = (바깥 위 + 바깥 아래)/2, r_out = (바깥 위 − 바깥 아래)/2, r_in = (안 위 − 안 아래)/2.
- 평면(방향): 「중간슬래브(19B)」 평면 중심(20.1 ④)에서 두 끝이 4.8 m 이상 8 m 이하인 레이어
-평면계획색 8 선분. 각 선분을 중심에 가까운 끝 → 먼 끝으로 본 방향의 합 = 터널 방향 d. 축 어긋남 = 그 선분들 가까운 끝의 d 에 수직인 거리 평균(왼쪽 +). - 터널(kind
tunnel, typepipe): angle_deg = d 의 각도(X축에서 반시계, 0~360), 시작점 = 축 어긋남 + 벽 안쪽 반지름(20.1 ①)만큼 d 방향, 길이 = 벽 두께 + 20000(4.5 의 설계값: 벽 바깥에서 20 m). - 채움 3개(kind
fill, 터널과 같은 축·길이, r = r_in). v 는 축에서 잰 높이: 패드 윗면 v = ① 의 패드 윗면 − 축 z. 4.5 의 설계값(도면에 없음): 잡석 두께 125, 패드 1단 두께 50, 1단 폭 800.- 잡석 −r_in ~ −r_in + 125 / 패드 2단 −r_in + 125 ~ 패드 윗면 − 50 / 패드 1단 패드 윗면 − 50 ~ 패드 윗면(half_width 400, 나머지 0).
- 도면 좌표·정답 값(축 높이, 지름, 패드 높이, 방향 각도)은 코드에 적지 않는다. 고정값은 위 설계값(20000, 125, 50, 400)과 20.1 의 허용값만.
- 대조(F37): 터널 x·y·z·r·길이 ±10, 방향 ±0.5°, 채움 v·폭 ±5. 변조 사본 둘: 단면 A-A 터널 선만 0.5 m 내림 → 터널 축 500 낮아짐 / 19B 평면 터널 선을 좌우로 뒤집음 → 방향 180°.
- 코치는 이 규칙대로만 짠 별도 구현으로 F37 채점이 통과함을 미리 확인했다(그 구현은 주지 않는다).
24. 추가 — 분기구·환기구 추출 (2026-10-21 수석 지시, F38)
도면(DXF)에서 분기구(kind branch)와 환기구(덕트·환기탑, kind vent)를 obox(17.2) 로 읽는다. 함수 plan_center, frame_angle, section_heights, branch_parts, vent_parts.
- 평면 모양은 평면도 1, 높이는 단면(분기구 = 단면 A-A, 환기구 = 단면 B-B「환기 덕트를 펼친 단면」)에서 읽는다. 평면도는 슬래브 평면 틀과 각도 φ 만큼 다르다(22장) — φ 도 도면에서 구한다.
24.1 평면도 1 과 φ
- 평면도 중심 C: 레이어
S-TITLE글자 「평면도」(공백 무시) 아래에서 가장 가까운 반지름 4.85 원의 중심. - 레이어
-평면계획색 1, 꼭짓점 3개이고 가운데 꼭짓점이 C(±0.01)인 꺾은선 → C 에서 나가는 두 다리. - 터널 다리 = 그 다리와 나란하고(±0.01 rad) 다리에서 r_out(23장, ±0.02 m) 떨어진
-평면계획색 8 선분이 2개 이상 있는 다리. 다른 다리 = 분기구 다리. - φ = 23장 터널 angle_deg − 터널 다리의 각도. 평면도의 점·방향은 모두 C 기준 mm 로 바꾼 뒤 φ 만큼 돌려 모델 좌표로 쓴다(obox 는 cx·cy 를 돌리고 angle_deg 에 φ 를 더하면 된다).
24.2 단면 높이 (section_heights)
- 단면 B-B 의 벽 네 선 = 20.1 ① 처럼 길이 60 m 넘는
구조물세로선을 X 순으로 놓았을 때 58번째(14번째가 A-A). - 벽 개구부 X 표시: 한쪽(왼쪽 = 바깥 왼·안 왼, 오른쪽 = 안 오·바깥 오) 벽 두 면 사이를 잇는 길이 1 m 넘는
구조물사선, 지표 아래 10 m 안. 그 z 범위의 아래끝 = 바닥 윗면, 위끝 = 지붕 아랫면. X 가 있는 쪽이 그 구조물 쪽. - 헌치: 그 쪽 벽 바깥면(±0.01)에 한 끝이 있고 다른 끝이 벽 바깥으로 나간 짧은
구조물사선(길이 0.1~0.5 m). 바깥 끝이 지붕 아랫면보다 위이고 아래로 내려가는 것의 바깥 끝 z = 지붕 윗면, 바닥 윗면보다 아래이고 위로 올라가는 것의 바깥 끝 z = 바닥 아랫면. - 도면 값(참고): 분기구 바닥 −4,550 ~ −4,100, 환기구 바닥 −4,600 ~ −4,100, 둘 다 지붕 −2,000 ~ −1,650. 이 숫자를 코드에 쓰지 않는다.
24.3 분기구 (branch_parts)
- 분기구 다리 방향 = s, 왼쪽 수직 = t. 평면도의 닫히지 않은
구조물꺾은선(꼭짓점 4개 이상) 중 모든 꼭짓점이 s > 4.55 m, |t| < 2 m, s < 다리 길이 + 0.5 m 인 것 둘: |t| 최대가 작은 것 = 안 윤곽(t_in, 끝 s_in = 최대 s), 큰 것 = 바깥 윤곽(t_out, s_out). - obox 5개(cx = cy = 0, angle = 다리 각도 + φ, s 는 벽 안쪽면 4850 부터): 바닥·지붕(t ±t_out, s ~ s_out), 옆벽 둘(t_in ~ t_out 양쪽, 높이 바닥 윗면 ~ 지붕 아랫면), 끝벽(s_in ~ s_out, t ±t_in).
24.4 환기구 (vent_parts)
- 중심선: 레이어
환기구, 꼭짓점 3개, 첫 꼭짓점이 C 인 꺾은선 → 꺾인 점 V1, 끝점 T. 다리 1 = C→V1, 다리 2 = V1→T. - 덕트 벽선: 닫히지 않은 꼭짓점 3개
구조물꺾은선 중 가운데 꼭짓점이 V1 에서 3 m 안인 것 4개. 다리 1 에서 잰 |t| 의 작은 둘 평균 = t_in, 큰 둘 평균 = t_out. - 환기탑: T 를 품는 닫힌
구조물꺾은선 — 꼭짓점 4개 = 바깥 사각형, 더 많은 것 = 안 윤곽. 다리 2 축으로 잰 범위(s, t)를 쓴다. - 덕트 obox(높이는 24.2 의 B-B 값):
- 다리 1: 바닥·지붕 s 4850 ~ L1 + t_out, t ±t_out. 꺾이는 안쪽 벽(다리 2 가 나가는 쪽) s 4850 ~ L1 − t_out, 바깥 벽 s 4850 ~ L1 + t_out (t_in ~ t_out).
- 다리 2(V1 에서): 바닥·지붕 s t_out ~ 탑 바깥 사각형의 가까운 변, t ±t_out. 안쪽 벽 s t_in ~ 탑 변, 바깥 벽 s −t_out ~ 탑 변.
- 환기탑 obox: 바닥 = 바깥 사각형(덕트 바닥 높이). 벽 넷 = 바깥 사각형과 안 윤곽 범위 사이의 띠, 높이 바닥 윗면 ~ 지표 z 0. 단 덕트가 들어오는 쪽 벽은 지붕 아랫면 ~ 0 만(B-B 에서 탑 벽이 덕트 지붕 위에만 있음). 탑에는 지붕이 없다.
- 덕트·탑을 obox 로 나누는 방법은 위와 다르게 해도 된다 — 채점은 입체(차지하는 공간) 로 비교한다.
24.5 대조 (F38)
- 분기구·환기구 각각: 높이 5곳(−4575, −4300, −3000, −1800, −1000) × 100 mm 격자(수직구 바깥 r 5520 넘는 곳)에서 「추출 부재 안인가」가 model.js 와 같은지. 경계 ±20 mm 는 봐준다. 어긋난 점이 안쪽 점의 1% 이하.
- 변조 사본 둘: 평면도 1 의 환기구(중심선·덕트·탑)를 C 기준 좌우로 뒤집음 → 추출 환기구도 뒤집혀 나옴 / 단면 A-A 의 분기구 지붕 헌치 끝을 0.2 m 내림 → 분기구 지붕 윗면만 200 낮아짐.
- 코치는 이 규칙대로만 짠 별도 구현으로 F38 채점이 통과함을 미리 확인했다(그 구현은 주지 않는다).
25. 추가 — 나머지 부재 추출 (2026-10-21 수석 지시, F39)
도면(DXF)에서 상부 슬래브·맨홀 목·19B 패드·하부 슬래브·바닥 콘크리트·월류벽·집수조·침사조를 읽고, 19B 슬래브에 침사구 구멍을 더한다. 함수 top_parts, pad_part, bottom_parts, weir_parts. id·kind·type·필드는 model.js 와 같게(topslab, neck_1·neck_2, pad_19B, bottom_20B, lean_concrete, weir_L·weir_R·weir_mid, pit_pump·pit_tbm).
- 단면 A-A 의 「벽 전체 수평선」: 레이어
구조물수평선 중 바깥 왼 벽선에서 바깥 오른 벽선까지(양 끝 ±0.01) 걸친 것. 가장 높은 것 = 상부 슬래브 윗면, 가장 낮은 것 = 바닥 콘크리트 밑면, 두 번째로 낮은 것 = 하부 슬래브 밑면(= 바닥 콘크리트 윗면). - 상부 슬래브 밑면 = 윗면보다 낮은
구조물수평선 중 바깥 벽 사이에 있고 길이가 안쪽 지름의 0.9 배 넘는 것 중 가장 높은 것. 상부 슬래브: type disc, r = 바깥 반지름, holes = 평면도 1(24.1) 안의 반지름 1 m 미만구조물원(중복 하나로) → C 기준 mm 를 φ 만큼 돌린{"circle": [x, y, r]}. - 맨홀 목: 평면도 1 의 닫힌 꼭짓점 4개
구조물꺾은선 중 2 의 원 중심을 품는 것(중복 하나로). polygon = 꼭짓점을 φ 만큼 돌린 것, holes = 그 원, z 상부 슬래브 윗면 ~ 지표 0. 구멍 y 가 작은 쪽이 neck_1. - 하부 슬래브 윗면 = 단면 A-A 에서 안쪽 벽면에 한 끝이 있고 안쪽·아래로 내려가는 짧은
구조물사선(길이 0.1~0.6 m) 아래 끝 중 가장 낮은 것. 하부 슬래브 disc r = 안쪽 반지름, 바닥 콘크리트 disc r = 바깥 반지름. - 19B 패드: 19B 슬래브 윗면(20.1) 위 0~0.3 m 안에 있는, 안쪽 벽 사이 길이 9 m 넘는
구조물수평선 → 윗면, r = 길이/2. 구멍 = 19B 평면 안쪽 원 안의 X 표시(같은 레이어(오프닝또는구조물)의 엇갈린 사선 LINE 두 개, 길이 0.5 m 넘고 X 범위가 같은 것)의 끝점 범위 사각형, 패드 반지름 밖은 ±r 로 자른다. 같은 X 표시 사각형(자르지 않은 것)이 19B 슬래브 구멍에 없으면 더한다(침사구). - 월류벽: 「하부슬래브(20B)」 평면(제목 아래 가장 가까운 반지름 4.85 원) 안쪽 원 안의 길이 5 m 넘는
구조물세로 LINE 두 개 → x0, x1. 엇갈린 사선 두 개(X 표시)의 Y 범위 → 낮은 칸 y0~y1. 양쪽 칸은 낮은 칸 끝 ~ ±√(안쪽 반지름² − x1²).- 높이: 단면 A-A 에서 안쪽 벽 사이(벽선에서 0.05 m 넘게 떨어진) 길이 1 m 넘는 세로
구조물선 중 아래 끝이 하부 슬래브 윗면 위 0~0.3 m 안인 것들 → 위끝 = 월류벽 윗면. 그 선들 사이에 걸친 수평선 중 윗면보다 낮은 것 = 낮은 칸 윗면. z0 = 하부 슬래브 윗면.
- 높이: 단면 A-A 에서 안쪽 벽 사이(벽선에서 0.05 m 넘게 떨어진) 길이 1 m 넘는 세로
- 집수조·침사조(type zone, alpha 0.25, r = 안쪽 반지름): 월류벽 왼쪽 x −r ~ x0, 오른쪽 x1 ~ r, z 하부 슬래브 윗면 ~ 월류벽 윗면. 단면 A-A 글자 「집수조」가 축 왼쪽이면 왼쪽이 pit_pump, 오른쪽이 pit_tbm.
- 도면 값(참고): 상부 슬래브 −2,000~−1,500, 19B 패드 −66,200~−66,100 r 4,750, 하부 슬래브 −70,200~−69,000, 바닥 콘크리트 −70,300~−70,200, 월류벽 x 1,400~1,600·윗면 −67,000·낮은 칸 −67,500. 이 숫자를 코드에 쓰지 않는다.
- 대조(F39): model.js 와 r·z ±5, 원 구멍·윤곽 ±10, 패드 구멍 ±40(정답지는 원 경계에 맞춰 손으로 줄였다). 변조 사본 둘: 20B 평면 월류벽 선을 중심 쪽으로 0.3 m 옮김 → 월류벽·조 경계가 300 옮겨짐 / 평면도 맨홀 2(원·목)를 0.5 m 옮김 → 구멍 2·목 2 만 φ 돌린 만큼 옮겨짐.
- 벽체 높이도 이제 도면에서 읽는다(19.1 의 고정값 허용을 끝냄): wall z0 = 하부 슬래브 밑면, z1 = 상부 슬래브 밑면.
- 사다리·펌프 배출관·사람 등 도면에 위치가 다 나오지 않는 것(🔶)은 이 장에서 다루지 않는다(다음 장의 설계 규칙).
- 코치는 이 규칙대로만 짠 별도 구현으로 F39 채점이 통과함을 미리 확인했다(그 구현은 주지 않는다).
26. 추가 — 정답지 보강: 환기구 사다리 2개 (2026-10-21 수석 지시 「2D 도면에 있는 것 모두」, F40)
- 발견(코치, DXF 재확인): 단면 B-B(환기 덕트를 펼친 단면)에 레이어
사다리로 벽 사다리(축에서 +4.6 m, 1B ~ 덕트 바닥 위)와 환기탑 안 사다리(펼친 거리 18.75 m 근처, 덕트 바닥 ~ 지표 위)가 그려져 있는데 정답지에 없다. 단면 A-A 의 서쪽 벽 사다리(ladder_up_W)와 짝이다. ladder에 선택 필드angle_deg를 더한다: 지금처럼 (x0~x1, yc ± width/2) 로 만든 면의 모든 점을 원점 (0, 0) 기준으로 angle_deg 만큼 돌린다(법선도 같이). 없으면 0(지금과 같음). 함수facesLadder.- 새 부재(kind
ladder_vent, typeladder, color "#d0a040"). kind 가 새것이라 도면 클릭 영역(17.4)에는 넣지 않는다:
| id | name | angle_deg | x0 | x1 | yc | width | z0 | z1 | rung |
|---|---|---|---|---|---|---|---|---|---|
| ladder_vent_wall | 환기구 벽 사다리 (1B→덕트 바닥) | 300.3 | 4582 | 4622 | 0 | 400 | −7400 | −4100 | 300 |
| ladder_vent_tower | 환기탑 사다리 (덕트 바닥→지표) | 30.3 | 10100 | 10140 🔶 | −8650 🔶 | 400 | −4100 | 0 | 300 |
- 300.3° = 환기 덕트 다리 1 방향(24장, 평면도 −90° + 30.3°). 그 방향으로 4.6 m(B-B 의 사다리 앞 기둥) = 벽 안쪽 4.85 m 에서 0.25 m.
- 30.3° 틀의 (x, y) = 평면도의 (x, y). 탑 안 사다리 앞 기둥 = 펼친 거리 18.75 − 덕트 다리 1 길이 8.65 ≈ 10.1 m, yc = 탑 안 가운데(평면도 y −8,650) 🔶.
27. 추가 — 설계 규칙 파일, 추출 모델 = 정답지 전체 (2026-10-21 수석 지시, F41)
목표: 추출 모델(model_extracted.js)이 정답지(model.js)의 부재를 모두 갖게 한다. 도면에서 읽는 것은 20~25장, 도면에 위치가 다 나오지 않아 사람이 정한 것(🔶)과 화면용 값은 설계 규칙 파일에서 온다.
- 파일
tools/design_rules.json(하이쿠 작성, 순수 JSON):"parts": 🔶 부재 6개를 model.js 의 객체 그대로(id·name·kind·type·color 와 모든 수치) — ladder_19B, ladder_up_W, ladder_up_C, ladder_vent_wall, ladder_vent_tower, pump_pipe. 근거는 16.1·17.3·22·26장(사다리 위치·배관 경로는 도면 수치가 없거나 일부만 있어 설계 결정)."style": kind 별{"color": ..., "alpha": ...(있을 때만)}와"ids": id 별 덮어쓰기(채움 3개·집수조·침사조처럼 같은 kind 안에서 색이 다른 것). 값은 model.js 의 색·투명도와 같게.- 도면에서 읽는 부재(벽·슬래브·계단·터널·채움·분기구·환기구·상부 슬래브·목·패드·하부 슬래브·바닥 콘크리트·월류벽·집수조·침사조·난간)의 치수·좌표는 넣지 않는다.
- 추출 도구: 도면에서 읽은 부재 뒤에
parts를 더하고, 모든 부재에 style(kind → ids 순으로 덮어씀)의 color·alpha 를 넣는다. 모든 부재에name이 있다(한국어, 자유). 함수apply_design_rules. - id: 도면에서 읽는 부재도 model.js 와 같은 id 를 쓴다(분기구·환기구는 나누는 방법이 달라도 되므로 예외).
- 대조(F41): design_rules.json 의 6개 = model.js(±1), 그 밖의 kind 는 넣지 않음, 도면 값 숫자 없음 / 추출 모델의 kind 집합 = model.js, 분기구·환기구 말고 id 집합 = model.js / 같은 id 의 color·alpha = model.js / 모든 부재에 name·color.
28. 추가 — 변환 패키지: DWG → DXF → 도면 범위 (2026-10-21 수석 지시, F42)
설계도면은 DWG 로 주고받는다. DWG 는 바이너리라 GNU LibreDWG(dwg2dxf)로 DXF(글자 파일)로 바꾸고, 그 안에서 이 과제가 쓰는 그림만 남겨 추출 도구(20~27장)에 넣는다. 지금까지 코치가 리포 밖에서 하던 전처리를 리포 안 도구로 옮긴다.
- 입력:
private/drawing.dwg(원본 DWG 사본, git 제외 — 코치가 넣었다).
tools/dwg_to_dxf.sh <입력.dwg> <출력.dxf>:dwg2dxf -y -o <출력> <입력>. dwg2dxf 를 PATH 와~/.local/bin에서 찾고, 없으면 README 의 설치 절을 가리키는 한 줄을 내고 종료 코드 2.tools/views.json(사람 입력, 이 장과 함께 커밋): DWG 한 파일에 수직구 두 개와 옛 개정판이 함께 있어 어느 그림을 쓸지는 사람이 정한다.boxes(그림 상자 4개, 모형 공간 m),drop_layers,drop_block_word.tools/select_views.py <전체.dxf> <views.json> <출력.dxf>(표준 라이브러리만, 좌표 숫자 없음 — 상자는 views.json 에서만):- ENTITIES 구역을 객체 묶음으로 나눈다: POLYLINE 은 SEQEND 까지, 코드 66 이 1 인 INSERT 는 ATTRIB…SEQEND 까지 한 묶음.
- 뺄 것: 배치 공간(코드 67 이 1), 레이어가 drop_layers 인 것, 블록 이름(INSERT 의 코드 2)에 drop_block_word 가 들어 있는 INSERT, 좌표가 없는 것.
- 좌표: 코드 10·11 의 X 와 그 뒤 20·21 의 Y. POLYLINE 은 머리의 10/20(기준점)을 빼고 VERTEX 부터, INSERT 는 ATTRIB 도 포함. CIRCLE·ARC 는 중심 ± 반지름(코드 40)도 넣는다.
- 남길 것: 좌표 범위(최소~최대)가 상자 하나 안에 다 들어가는 묶음. 순서는 원본 그대로.
- 출력: ENTITIES 앞(HEADER·TABLES·BLOCKS 등)은 원본 그대로, 단 $EXTMIN·$EXTMAX 의 10/20 은 남긴 좌표의 최소·최대. 남긴 묶음, 그 뒤 원본의 나머지. 줄 끝 CRLF.
tools/make_model.sh [입력.dwg] [출력 폴더](기본 private/drawing.dwg, private/): 1 →full.dxf, 3 →drawing.dxf, 추출 도구 →model_extracted.js(출력 폴더). 출력 폴더가 private/ 이면 리포 루트의 model_extracted.js 도 새로 쓴다.- README 에 「변환 패키지」 절: 설치(LibreDWG 0.14 소스 빌드 —
PKG_CONFIG=/bin/true ./configure --prefix=$HOME/.local --disable-bindings --disable-docs && make && make install, WSL Ubuntu), 실행(bash tools/make_model.sh), 각 파일의 용도(DWG = 주고받는 원본, DXF = 추출 입력, SVG = 웹 표시(다음 장)).
- 대조(F42): make_model.sh 를 임시 폴더로 돌려 나온 drawing.dxf 의 객체가 private/drawing.dxf 와 같고(숫자 1e-6 까지 — LibreDWG 가 z 에 1e-11 잡음을 넣을 때가 있다), 나온 model_extracted.js 의 부재가 리포의 것과 같다. 상자 하나만 남긴 views.json 으로 돌리면 그 상자 안 객체만 남는다.
- 코치는 이 규칙대로만 짠 별도 구현으로 원본 DWG → 3,356 객체가 private/drawing.dxf 와 같게 나옴을 미리 확인했다.
29. 추가 — DXF → SVG 와 도면 연결값을 도면에서 (2026-10-21 수석 지시 「웹에서 DXF 를 못 보이면 SVG 로 변환까지 패키지로」, F43)
웹 왼쪽 칸의 도면 SVG 를 DXF 에서 직접 만들고, 그림 연결값(MODEL.drawing)도 추출 도구가 도면에서 계산한다. 화면은 지금과 같아야 한다 — 수석이 준 SVG 는 DXF 의 그림 상자 4개를 축척 없이 평행이동해 놓은 배치였다(오차 0.01 m).
tools/views.json에 더함(사람 입력 — 배치): 상자마다place[dx, dy] — 그 상자의 점 (X, Y) 를 SVG (X − dx, dy − Y) 에 놓는다(1 = 1 m).viewBox[0, 0, 443, 443],fit[118, 160, 245, 250](처음 화면).
29.1 tools/dxf_to_svg.py <drawing.dxf> <views.json> <출력.svg> (표준 라이브러리만)
- 객체마다 그 좌표 범위가 들어가는 상자(28장과 같은 방법)의 place 로 옮긴다. 상자에 안 들어가는 객체는 그리지 않는다.
- 레이어 표(TABLES 의 LAYER: 2 이름, 62 색 — 음수면 꺼짐, 70 의 1 비트 = 얼림)에서 꺼짐·얼림·
Defpoints레이어는 그리지 않는다. - 색: 객체 62 가 없거나 256 = 레이어 색, 0 = 블록을 넣은 INSERT 의 색. ACI → RGB: 1 빨강 (255,0,0), 2 노랑, 3 초록, 4 하늘 (0,255,255), 5 파랑, 6 자홍, 7 검정 (0,0,0)(원본 SVG 와 같게), 8 (128,128,128), 9 (192,192,192), 250
255 회색 6단(51, 91, 132, 173, 214, 255), 10249 는 색상 (i−10)//10 × 15°, i%10 짝수 = 채도 1·홀수 = 0.5, 밝기 [1, 1, .8, .8, .6, .6, .5, .5, .3, .3][i%10] (HSV). - 그리기: LINE·LWPOLYLINE(코드 42 볼록 → 원호)·POLYLINE(VERTEX, 42)·LEADER(꼭짓점 꺾은선)·CIRCLE·ARC(50·51 도, 반시계)·SOLID(10·11·13·12 순 채움)·TEXT·MTEXT·INSERT(BLOCKS 구역의 블록을 기준점 10/20 빼고, 41·42 배율, 50 회전, 10/20 위치로; 겹친 INSERT 도)·DIMENSION(코드 2 의 블록을 그대로). Y 를 뒤집으므로 원호 방향도 뒤집힌다.
- 글자: 높이 40, 회전 50, 72/73 이 0 이 아니면 11/21 을 기준점, 72 → text-anchor(0 start·1 middle·2 end·4 middle). %%c → Ø, %%d → °, %%p → ±, %%u 지움. MTEXT 는 3·1 을 이어 붙이고 서식 코드(\P → 공백, \f…; 등, 중괄호)를 지운다. font-family "Dotum,'sans-serif'".
- 출력: 루트
<svg viewBox="0 0 443 443">+<style>* { vector-effect: non-scaling-stroke; }</style>, 색마다<g fill="none" stroke="rgb(…)" stroke-width="1">. 원본처럼 선은 화면 1 px. make_model.sh가 이것도 돌려 출력 폴더에drawing_gen.svg를 만든다(private/ 이면 private/drawing_gen.svg).
29.2 도면 연결값 (추출 도구 drawing_views)
- 추출 도구는
tools/views.json(세 번째 인수로 다른 파일을 줄 수 있다)을 읽어MODEL.drawing을 만든다: src "private/drawing_gen.svg", viewBox·fit = views.json, section {x0, y0, mm_per_unit 1000} = secA 값, plan {cx, cy, mm_per_unit 1000} = plan1 값, views 11개(id·type·label·필드는 13.2·22장과 같다). 모든 좌표 = 도면 점을 그 상자 place 로 옮긴 값(소수 둘째 자리). - secA·secB: 전체 단면 A-A(20.1 ① 벽 1
4)·B-B(24.2 벽 58)의 축 X 와 그 축의 지반선(20.1 ②) → x0, y0. - secA2·secB2(section_break):
S-TITLE「단면 A-A」·「단면 B-B」(공백 무시) 글자 X 에서 가까운, 길이 10 m 넘는구조물세로선 X(1 cm 로 반올림, 중복 하나로) 4개 = 그 생략 단면의 벽. 축 = 바깥 두 선 가운데, 지반선(초록, ±0.05 m 로 축을 지나는 선) → x0, y0_upper. 층 글자(「숫자B」, 축에서 8 m 안)마다, 안쪽 왼쪽 벽선에서 시작하고 글자 아래 0.3 m 안에 있는구조물수평선 Y 로 그 층의 「지표에 해당하는 Y」 = Y − 그 층 윗면 z(20.1 ③)/1000 을 구한다. 지반선과 같은(±0.05) 층들 = 위 구간, 나머지 = 아래 구간: y0_lower = 아래 구간 값의 평균을 같은 place 로 옮긴 것, z_upper_min = 위 구간 가장 깊은 층 윗면 − 300, z_lower_max = 아래 구간 가장 높은 층 윗면. - plan1 = 평면도 1 중심(24.1 ①), plan2 = 그 아래 30 m 안의 가장 가까운 반지름 4.85 원. rot_deg = −φ(24.1, 소수 첫째 자리). tunnel: plan1 true, plan2 false.
- 슬래브 평면 5개: 20.1 ④ 의 중심과 「하부슬래브(20B)」 중심. z1 = 홀수층(1·3·…·17B 윗면)·짝수층(2·…·16B)·[18B]·[19B]·[하부 슬래브 윗면], kind slab(20B 만 bottom), tunnel 은 19B 만 true.
- 대조(F43): 생성 SVG 가 XML 로 읽히고 viewBox 443, 요소 7000 개 이상·글자 400 개 이상, 원본 SVG(private/drawing.svg)와 선이 지나는 0.5 칸 비교(상자 안)에서 재현율·정밀도 0.85 이상(원본의 해치는 DXF 에서 기준점이 (0,0) 이라 범위에서 빠져 1 이 되진 않는다) / 추출 drawing = model.js drawing(views 11개 숫자 ±0.05, z1 ±5, 플래그 같음, rot_deg ±0.1, viewBox·fit 같음) / views.json 의 평면도 상자 place 를 10 m 바꾼 사본으로 추출 → plan1·plan2 cx 만 10 바뀜.
- 코치는 이 규칙대로만 짠 별도 구현으로 SVG(재현율 0.885·정밀도 0.928)와 views 11개(정답지와 ±0.01)를 미리 확인했다.
30. 변경 — 화면을 도면에서 만든 모델로 전환 (2026-10-21 수석 지시, F44)
이제 3D 와 도면 연결이 모두 도면(DWG)에서 만들어진다(20~29장). 첫 화면을 그 모델로 바꾸고, 손으로 만든 정답 모델은 비교용 화면으로 남긴다.
index.html:model_extracted.js다음viewer.js를 읽는다(model.js 는 읽지 않는다). 1.x 의 F01 「model.js 다음 viewer.js」 를 이것으로 바꾼다. 왼쪽 도면 = MODEL.drawing.src(private/drawing_gen.svg).answer.html(새 파일, 정답지 화면): index.html 과 같은 화면이되model.js다음viewer.js. 제목에 「정답지」, 맨 위 안내 한 줄: 「손으로 만든 정답 모델 — 도면에서 만든 모델(index.html)을 검사하는 기준」. 왼쪽 도면 = model.js 의 drawing.src(private/drawing.svg, 수석이 준 SVG 처리본).viewer.js: 도면 그림 파일을MODEL.drawing.src에서 읽는다(없으면 'private/drawing.svg'). 「도면 파일 없음」 안내도 그 경로를 보인다.- compare.html 은 그대로 둔다(F34 — 추출 모델 비교 화면, 이제 index.html 과 같은 데이터).
- README: 첫 화면 = 도면에서 만든 모델, answer.html = 정답지, 다시 만들기 =
bash tools/make_model.sh. - 구동 설명서(코치 작성, 이 장과 함께 커밋): 구동 쪽 — 여는 방법·도면에서 다시 만들기(형식별 용도 표)·잘 안 열릴 때 / 목적 쪽 — 3D 가 도면에서 만들어진다는 한 줄, 사다리 4개(위 2 + 환기구 쪽 2).
31. 추가 — README 「사용 도구」 절 (제출 점검, F45)
- 11.2 로 README 를 코드 설명으로 고쳐 쓸 때 빠진 「사용 도구」 절을 되살린다(제출 가산점: WSL2·git).
- README 에
## 사용 도구절: WSL2 Ubuntu(그 아래wsl.exe -l -v출력을 코드 블록에 그대로 — WSL 안에서는wsl.exe -l -v, 글자 사이 빈 칸은 지워도 된다), Claude Code(Haiku 5.5)가 코드 작성·수정·커밋(커밋마다 Co-Authored-By 줄), 설계 문서·검사(SPEC.md·FEATURES.json·check.sh·구동 설명서)는 수석 지시로 코치 세션이 쓰고 하이쿠가 KIT 커밋, GNU LibreDWG 0.14(DWG → DXF), Python 3 표준 라이브러리, git, 브라우저(Chrome·Edge). - 대조(F45): README 에 「사용 도구」,
wsl -l -v출력(Ubuntu 와 VERSION 2), Haiku, LibreDWG, check.sh.
32. 변경 — 환경에 따라 못 하는 검사는 SKIP (2026-10-21 수석 지시 「압축에 DXF 를 넣어라, 실패가 나오는 건 싫어」)
- 제출 압축에는
private/drawing.dxf·drawing.svg·drawing_gen.svg가 들어가고 원본 DWG 는 빠진다. 평가자 PC 에는 GNU LibreDWG(dwg2dxf)가 없을 수 있다. - 그래서 F42 의 「원본 DWG 로 make_model.sh 를 돌려 보는」 검사 3개는 dwg2dxf 나 private/drawing.dwg 가 없으면 FAIL 이 아니라 SKIP(이유 표시)으로 센다. 둘이 다 있는 개발 환경에서는 지금처럼 돈다. 다른 검사는 그대로.
33. 추가 — WSL2 설치 증빙 파일 (2026-10-22 수석 지시 「WSL2 를 설치했다는 증빙자료가 없다」, F46)
evidence/wsl2.txt: 명령 출력 그대로 모은 증빙.- 앞부분 「Windows 쪽」: 코치 세션이 Windows PowerShell 에서 실행해 모은 출력(wsl --version, wsl -l -v, wsl --status, 등록된 배포판과 가상 디스크 ext4.vhdx 를 만든 날 = 설치일). 이 장과 함께 커밋한다.
- 뒷부분 「WSL 안쪽」: 하이쿠가 WSL 안에서 직접 실행해 덧붙인다 — 수집 시각(date),
uname -r(커널 이름에 microsoft-standard-WSL2),cat /proc/version,/etc/os-release의 PRETTY_NAME,pwd(작업 폴더 /mnt/d/…),claude --version,git --version,python3 --version,dwg2dxf --version첫 줄. 명령마다$ 명령줄 다음에 출력 그대로.
- README 「사용 도구」: 예전 커밋에서 옮겨 적은 출력 대신
evidence/wsl2.txt를 가리키고, 설치일(가상 디스크 만든 날)과 오늘wsl -l -v출력을 적는다. - 대조(F46): evidence/wsl2.txt 에
wsl -l -v출력(Ubuntu, VERSION 2)·설치일·microsoft-standard-WSL2·Ubuntu 버전·claude 버전, README 가 그 파일을 가리킨다.
34. 공개용 정리본 (2026-10-27 수석 지시 — 사내 Gitea tdc_engineer/drawing3d-web 에 올림)
- 이 저장소는 1차 제출본(커밋 81개, 로컬 보존)을 공개용으로 정리해 새 이력으로 시작한 것이다. 옛 이력에는 도면 실제 좌표가 들어 있어 그대로 올리지 않았다. 옛 커밋 기록(메시지)은
evidence/gitlog_v1.txt. - 도면 정리(코치, 저장소 밖 도구): 우리가 쓰는 그림 객체와 거기 딸린 블록·레이어·선종류만 남기고, 과업명·지명·외부 참조 이름·저장자·XDATA·OBJECTS 를 지웠다. 모든 좌표를 같은 양만큼 평행이동해 실제 위치를 알 수 없게 했다(옮긴 양은 기록하지 않는다).
- 같은 양을
tools/views.json(그림 상자·배치)과check.sh(채점기만 아는 기준점·변조 시험 좌표)에도 적용했다. 추출 결과(3D 데이터 95부재)·도면 연결 11개·생성 SVG 는 옮기기 전과 같다(SVG 바이트 동일, 부재는 1e-9 mm 잡음 하나). private/에 올리는 것:drawing.dxf(정리본),drawing.svg(정답지 화면용 그림),drawing_gen.svg(첫 화면 그림). 원본 DWG 는 올리지 않으므로 F42 의 DWG 재변환 검사 3건은 SKIP(32장)..gitignore는private/전체 대신private/*.dwg·private/full.dxf등을 뺀다 — F13 의 「.gitignore 에 private/」 기준을 이것으로 바꾼다.