# drawing3d-web — 코드 설명 이 저장소는 1차 과제 제출본을 사내 공개용으로 정리한 것입니다 (도면 실제 좌표·과업명 제거, 좌표 평행이동, 화면·검사 결과는 같음, SPEC 34장). 1차의 커밋 81개 기록은 `evidence/gitlog_v1.txt` 에 있습니다. > 🔒 사내교육용 — 열람 외 재배포 등의 활동을 금함 (회사 도면의 치수를 옮겨 적음) 원형 수직구 일반도를 라이브러리 없이 Canvas 2D 로 그리는 3D 뷰어. 설계 근거는 `SPEC.md`, 구동 설명은 `guide/index.html` (HTML 위키). ## 파일 구조 | 파일 | 내용 | |---|---| | `index.html` | 첫 화면(도면에서 만든 모델). 화면 배치(도면 칸 · 가운데 막대 · 3D 칸)와 스크립트 읽기 순서: `model_extracted.js` 다음에 `viewer.js` | | `answer.html` | 정답지 화면. `index.html` 과 같은 화면이되 `model.js` 를 읽는다. 손으로 만든 정답 모델이고, 도면에서 만든 모델(`index.html`)을 검사하는 기준이다 | | `model.js` | 데이터. `const MODEL = {...};` 한 줄짜리 순수 JSON | | `viewer.js` | 그리기·입력·도면 연결 코드 전부 | | `private/drawing.svg` | 도면 원본. `.gitignore` 로 커밋 제외 | | `check.sh` | 합격 판정 (수정 금지) | | `tools/extract_from_dxf.py` | 도면 DXF(`private/drawing.dxf`)에서 데이터를 뽑는 도구. 벽·슬래브·구멍·난간에 더해 계단 18런(`section_stairs`, `stair_parts`)과 TBM 터널·채움 3개(`section_tunnel`, `plan_tunnel`, `tunnel_parts`)도 뽑는다. 분기구·환기구(덕트·환기탑)는 평면도 중심과 φ(`plan_center`, `frame_angle`), 단면 높이(`section_heights`), 상자 묶음(`branch_parts`, `vent_parts`)으로 뽑는다. 나머지 부재(상부 슬래브·맨홀 목·19B 패드·침사구·하부 슬래브·바닥 콘크리트·월류벽·집수조·침사조)는 `top_slab_part`, `neck_parts`, `pad_and_sump`, `weir_parts`, `pit_parts` 로 뽑는다. 나머지 부재 뒤에 설계 규칙 파일(`tools/design_rules.json`, `load_design_rules`·`apply_design_rules`)의 🔶 부재 6개(사다리 3·환기구 사다리 2·배출관)를 붙이고, 모든 부재의 색·투명도를 kind → id 순으로 덮어쓴다. 표준 라이브러리만 쓴다. `python3 tools/extract_from_dxf.py private/drawing.dxf model_extracted.js` | | `model_extracted.js` | 위 도구가 만든 데이터(`model.js` 와 같은 꼴). 값은 도면에서 읽은 것이라 `model.js` 와 대조해 본다 | | `SPEC.md`, `FEATURES.json`, `PROGRESS.md` | 설계, 기능 목록, 진행 기록 | ## 실행 - `index.html` 이 첫 화면이다. 도면에서 만든 모델(`model_extracted.js`)을 보여 준다. `answer.html` 은 손으로 만든 정답지 화면이다. - `index.html` 을 더블클릭해서 Chrome 또는 Edge 로 연다. 서버·인터넷은 필요 없다. - WSL 에서 열려면 이 폴더에서 `explorer.exe index.html` 을 실행한다. - 다시 만들기: `bash tools/make_model.sh` (DWG 를 읽어 `model_extracted.js` 와 `private/drawing_gen.svg` 를 만든다). - 도면은 모델의 `drawing.src` 경로에서 읽는다(첫 화면은 `private/drawing_gen.svg`, 정답지는 `private/drawing.svg`). 없으면 왼쪽 칸에 그 경로를 보이는 안내 글자가 나온다. ## 데이터 (`model.js`) - 단위는 mm, 각도는 도. 원점은 수직구 축 중심 × 지표면, Z 는 위로 +. - 최상위: `units`("mm"), `parts`(배열), `drawing`(도면 연결, 아래 「도면 연결」 참고). - 모든 부재 공통 필드: `id`, `name`, `kind`, `type`, `color`("#rrggbb"), `alpha`(없으면 1). - `kind`: `wall`, `topslab`, `neck`, `slab`, `pad`, `bottom`, `lean`, `stair`, `tunnel`, `fill`, `weir`, `pit`, `ladder`, `branch`, `vent`, `railing`, `ladder_up` | type | 쓰는 kind | 필드 | |---|---|---| | `ring` (세운 속 빈 원통) | wall, pipe(펌프 배출관) | `cx, cy, z0, z1, r_out, r_in` | | `disc` (세운 원판) | topslab, slab, pad, bottom, lean | `cx, cy, z0, z1, r`, `holes`(선택) | | `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 이면 폭 제한 없음) | | `prism` (다각형 기둥) | weir, neck | `polygon`([x, y] 점 목록), `z0, z1`, `holes`(선택) | | `zone` (원 안 활꼴 기둥) | pit | `r, x_min, x_max, z0, z1`(alpha 0.25) | | `ladder` (사다리) | ladder, ladder_up | `x0, x1, yc, width, z0, z1, rung`(가로대 간격) | | `obox` (돌린 직육면체) | branch, vent | `cx, cy, angle_deg, s0, s1, t0, t1, z0, z1`(s·t 는 돌린 축 방향) | | `railing` (방호 난간) | railing | `z`(설치 슬래브 윗면), `h`(높이), `paths`(꺾은선 목록, 각 [x, y] 점) | - `holes` 목록의 한 원소는 [x, y] 점 목록(다각형)이나 `{"circle": [x, y, r]}`(16각형으로 그림)이다. - 부재 93개: wall 1 · topslab 1 · neck 2 · slab 19 · pad 1 · bottom 1 · lean 1 · stair 18 · tunnel 1 · fill 3 · weir 3 · pit 2 · ladder 1 · branch 5 · vent 13 · railing 18 · pipe 1 · ladder_up 2. - `drawing`: `src`(도면 파일), `viewBox`([0, 0, 443, 443]), `fit`([u0, v0, u1, v1], 처음 화면에 꽉 찰 도면 상자), `section`(`x0, y0, mm_per_unit`), `plan`(`cx, cy, mm_per_unit`), `views`(그림 11장: `id, type, label` 과 그림마다 기준 값, 아래 「도면 연결」). ## 코드 (`viewer.js`) 함수 이름은 SPEC 7장과 10장의 목록을 그대로 쓴다. **부재 → 면** (각 면은 점 배열 · 색 · alpha) - `facesBox`: 직육면체 6면을 만든다. 분기구·환기구 등 상자 부재가 쓴다. - `facesDisc`: 세운 원판(32각형)을 뚜껑 2장과 옆면 32장으로 만든다. - `holePolygon`: 구멍 하나를 점 목록으로 바꾼다. `{"circle": [x, y, r]}` 는 16각형이 된다. - `addHoles`: 뚜껑 2장(윗면·아랫면)에 구멍 고리를 넣고(evenodd), 구멍 둘레마다 세로 옆면을 더한다. `facesDiscHoles`·`facesPrism` 이 함께 쓴다. - `facesDiscHoles`: `facesDisc` 에 구멍(`holes`)을 얹는다. 구멍 점을 `cx, cy` 만큼 옮긴 뒤 `addHoles` 를 부른다. - `facesRing`: 세운 속 빈 원통(32각형)을 바깥면·안쪽면·윗고리·아랫고리로 만든다. - `facesPipe`: 누운 속 빈 원통(32각형)을 축 시작점에서 `angle_deg` 방향으로 만든다. - `facesFill`: 원을 수평선 `v0`·`v1` 로 자른 단면을 터널 축 방향으로 늘인다. `half_width` 가 있으면 그 폭 안으로만. - `facesPrism`: 다각형을 `z0`~`z1` 로 세운 기둥(뚜껑 2장과 변마다 옆면)을 만든다. `holes` 가 있으면 `addHoles` 로 구멍을 뚫는다. 월류벽·맨홀 목이 쓴다. - `facesZone`: 원 안에서 `x_min`~`x_max` 만 남긴 활꼴 기둥을 `facesPrism` 으로 만든다. 집수조·침사조가 쓴다. - `facesLadder`: 사다리 기둥 2개와 가로대(`rung` 간격, 두께 25)를 상자로 만든다. - `facesObox`: 돌린 직육면체. `facesBox` 로 (s, t, z) 상자를 만든 뒤 `angle_deg` 만큼 돌리고 (`cx, cy`) 에 놓는다. 분기구·환기구 덕트·환기탑이 쓴다. - `facesRailing`: 방호 난간. 꺾은선 구간마다 두께 40 의 얇은 판을 `z` 부터 `z + h` 까지 세운다(옆 2·끝 2·윗면·아랫면, 판당 6장). - `stairProfile`: 계단 한 런의 단면(경사판·단·밑면)을 (s, z) 점 목록으로 만든다. s 는 `x_top` 에서 내려가는 방향 거리다. - `facesStair`: `stairProfile` 의 단면을 Y 띠(`y0`~`y1`) 폭만큼 늘인다. 옆면 2개와 둘레 변마다 사각형 하나. - `buildFaces`: 모든 부재의 면을 한 줄로 모은다. **카메라와 투영** - `rotatePoint`: 세계 좌표를 yaw(Z축 둘레)와 pitch(기울기)로 돌린다. - `project`: 돌린 점을 화면 기준 좌표 [오른쪽, 위, 깊이]로 바꾸고, 캔버스 픽셀로 옮긴다. **그리기** - `shadeColor`: `#rrggbb` 색을 밝기 계수만큼 밝게 또는 어둡게 한다. - `sortFaces`: 면 가운데 점의 깊이가 큰 것부터 정렬한다(먼 면부터). - `draw`: 캔버스를 지우고 면을 정렬해 차례로 칠한다. 선택된 부재는 노란색으로 그린다. **입력 (3D 칸)** - `onMouseDown`: 드래그 시작 위치를 기억하고, 오른쪽 버튼 또는 Shift 가 눌렸으면 화면 이동으로 정한다. - `onMouseMove`: 드래그 중 회전이면 yaw·pitch 를, 화면 이동이면 `panView` 를 부르고 `draw` 를 다시 부른다. - `onMouseUp`: 드래그를 끝낸다. - `panView`: 드래그한 픽셀만큼 보는 중심(`CAM.target`)을 화면 평면에서 옮긴다. - `unrotateVector`: 화면 기준 벡터를 세계 좌표로 되돌린다(`rotatePoint` 의 역). - `scaleFor`: 배율 값에 맞는 mm 당 픽셀 수를 돌려준다. - `zoomAt`: 마우스 아래 점이 그대로 머물게 배율을 바꾸고 보는 중심을 옮긴다(0.5~40 사이). - `resetView`: 등각 화면·배율 1·보는 중심 = 모델 중심으로 되돌린다(더블클릭, 「처음 화면」 버튼). - `onWheel`: 휠로 `zoomAt` 을 부른다. **화면 나누기** - `onSplitDown`: 가운데 막대 끌기를 시작한다. - `onSplitMove`: 왼쪽 칸 폭을 창 폭의 15%~85% 로 바꾸고 3D 캔버스를 다시 맞춘다. - `onSplitUp`: 막대 끌기를 끝낸다. **사람 애니메이션** - `buildWalkPath`: 계단 18런의 데이터로 내려가는 길을 만들고, 같은 길을 되짚어 올라오는 한 바퀴 경로(구간마다 길이·진행 방향)를 만든다. - `walkerAt`: 시각(ms)에 맞는 사람의 발 위치와 진행 방향을 돌려준다. 경로 끝에 오면 처음으로 돌아간다. - `teamAt`: 두 사람의 위치를 돌려준다. 두 번째 사람은 첫 사람보다 2초 뒤 시각(`TEAM_LAG`)으로 `walkerAt` 을 부른다. - `facesPerson`: 사람 한 명(다리 2·몸통·머리 상자 4개)을 진행 방향에 맞춰 돌린 면들로 만든다. 색은 인자로 받는다(`WALK_COLORS`: #ff8c1a, #e07010). - `animate`: 움직이는 동안 `requestAnimationFrame` 으로 시간을 더하고 초당 30번 이하로 `draw` 를 부른다. - `setWalking`: 움직임을 켜거나 끄고, 「사람 ❚❚」·「사람 ▶」 글자를 바꾼다. **도면 칸** - `applyDrawView`: 배율을 `#drawing` 과 `#hotspots` 의 실제 width/height 로 넣고(벡터를 다시 그린다), 위치만 translate 로 옮긴다. - `fitDrawing`: 처음 화면에서 `drawing.fit` 상자가 왼쪽 칸에 꽉 차게 배율과 위치를 정한다. - `onDrawWheel`: 마우스 아래의 도면 점이 그대로 머물도록 확대·축소한다(0.5~40배). - `onDrawDown`: 도면 드래그(이동)를 시작한다. - `onDrawMove`: 드래그 중 도면을 옮긴다. - `onDrawUp`: 드래그를 끝낸다. 움직였으면 다음 클릭은 선택으로 치지 않는다. **도면 ↔ 부재** - `region`: 클릭 영역 하나(부재 id 들과 다각형들)를 만든다. 다각형이 없으면 영역도 없다. - `pathD`: 다각형 점 배열을 SVG `path` 의 `d` 글자로 바꾼다. - `sectionPoly`: 단면 그림 한 장에서 부재 하나의 클릭 영역을 만든다. `section_break` 그림은 생략 구간을 뺀다(벽은 위·아래 두 조각). - `circlePoly`: 평면 그림의 원(32각형)을 만든다. - `planPoly`: 평면 그림에서 벽(고리, 가운데가 빈다)과 터널(사각형)의 클릭 영역을 만든다. - `viewHotspots`: 그림 한 장(`MODEL.drawing.views` 의 한 항목)의 클릭 영역 목록을 만든다. 단면·평면·중간슬래브 그림 세 가지를 나눠 처리한다. - `obox4`: 상자(obox) 바닥 윤곽 네 꼭짓점(mm)을 돌려준다. `facesObox` 와 같은 회전을 쓴다. - `xSpan`: 부재가 덮는 X 범위(mm)를 돌려준다. 계단·집수조·침사조·사다리·월류벽·맨홀 목·상자마다 다르다. - `planXY`: 평면 그림의 점 하나(X, Y mm)를 도면 좌표로 바꾼다. - `bottomPlanPoly`: 하부 슬래브 평면에서 집수조·침사조(원을 X 범위로 자른 활꼴), 월류벽, 사다리의 클릭 영역을 만든다. - `bottomHotspots`: 바닥부 클릭 영역을 만든다. 단면 A-A 두 그림, 하부 슬래브 평면, 19B 평면의 침사구(사다리 선택)를 처리한다. - `upperHotspots`: 상부 부재의 클릭 영역을 만든다. 단면 A-A 두 그림은 상부 슬래브·맨홀 목·분기구·환기구·위쪽 사다리를 X·z 사각형으로, 평면도 1·2 는 원판(r 4850)·맨홀 목·분기구와 환기구 바닥 윤곽을 처리한다. - `buildHotspots`: 11개 그림의 클릭 영역을 `svg#hotspots` 에 `path` 로 그린다. 같은 그림 안에서는 뒤에 나온 영역이 위에 온다. `upperHotspots` → `viewHotspots` → `bottomHotspots` 순으로 이어 붙인다. - `selectPart`: 부재 id 배열을 고른다. 겹치는 도면 영역을 칠하고, 3D 에서 그 부재들을 노란색으로 그리고, 「선택: 그림 이름 — 부재 이름」을 쓴다. 빈 배열이면 해제한다. ## 그리기 방식 - 투영은 정사 투영이다. 처음 화면은 등각(좌우 45도, 기울기 35도). - 드래그 = 궤도 회전: 좌우는 Z축 둘레, 위아래는 기울기(89도에서 멈춤). - 면 음영: 법선이 빛 방향에 가까울수록 밝다(계수 0.4~1.0). - 화가 알고리즘: 면마다 가운데 점의 깊이를 재서 먼 면부터 칠한다. 겹침은 이 순서로 해결한다. - 벽은 `alpha` 0.25 로 반투명, 나머지는 불투명. 원은 32각형으로 근사한다. ## 도면 연결 도면은 SVG 를 `` 로 띄우고, 같은 크기의 `` 를 겹친다. 좌표는 mm 를 도면 단위로 바꿔 쓴다. `u` 는 도면 가로, `v` 는 도면 세로(아래로 +). - 단면 A-A: `u = x0 + X / 1000`, `v = y0 − z / 1000` - 평면도: `u = cx + x / 1000`, `v = cy − y / 1000` `section` 과 `plan` 의 값은 도면 선을 재서 얻었다. 값은 `model.js` 의 `drawing` 에 있다. ## 변환 패키지 (DWG → DXF → 모델) 설계도면은 DWG 로 주고받는다. DWG 는 바이너리라 GNU LibreDWG 로 DXF(글자 파일)로 바꾸고, 과제가 쓰는 그림 상자만 남겨 추출 도구에 넣는다. - 설치 (WSL Ubuntu, LibreDWG 0.14 소스 빌드): `PKG_CONFIG=/bin/true ./configure --prefix=$HOME/.local --disable-bindings --disable-docs && make && make install`. `dwg2dxf` 가 `~/.local/bin` 에 생긴다. - 실행: `bash tools/make_model.sh` (입력 `private/drawing.dwg`, 출력 `private/` 와 리포 루트의 `model_extracted.js`). 웹 왼쪽 도면은 `private/drawing_gen.svg` 를 읽는다. - 파일 용도: - `private/drawing.dwg`: 주고받는 원본 DWG. 커밋하지 않는다. - `private/drawing.dxf`: 추출 입력. 그림 상자 안 객체만 남긴 것. - `private/full.dxf`: DWG 를 바꾼 전체 DXF (중간 산출물). - `tools/views.json`: 그림 상자 4개와 뺄 레이어·블록 이름. 사람이 정한다. - `private/drawing_gen.svg`: 웹 표시용 그림. DXF 에서 `tools/dxf_to_svg.py` 가 만든다(상자 배치는 `views.json`). - `private/drawing.svg`: 예전 수작업 SVG 원본. 비교용으로만 쓴다. - 각 도구: `tools/dwg_to_dxf.sh`(DWG → DXF), `tools/select_views.py`(상자 안 객체만 남김), `tools/dxf_to_svg.py`(DXF → SVG, 표준 라이브러리만), `tools/make_model.sh`(위 도구와 추출 도구를 차례로 돌림). 추출 도구는 `views.json` 을 세 번째 인수로 받아 도면 연결값(`drawing`: 단면·평면도·슬래브 평면의 좌표와 층 높이)을 도면에서 계산한다. ## 검사 - `bash check.sh` : F01~F17 전체 검사 - `bash check.sh F06` : F01~F06 만 검사, 뒤 기능은 SKIP - 결과 끝 줄이 `PASS n / FAIL 0` 이면 통과. 육안으로 볼 항목은 SPEC 8.2 에 있다. ## 사용 도구 - WSL2 (Ubuntu) 에서 작업. 설치일은 Ubuntu 가상 디스크를 만든 날 2026-07-09. 설치 증빙(Windows 쪽 `wsl --version`·`wsl --status`, WSL 안쪽 커널·Ubuntu·claude 버전)은 `evidence/wsl2.txt` 참고. WSL 버전 확인 (Windows 명령 프롬프트): `wsl -l -v` ``` NAME STATE VERSION * Ubuntu Stopped 2 ``` - Claude Code, 모델은 Haiku 5.5 (코드 작성·수정·커밋. 커밋 메시지마다 `Co-Authored-By: Claude Haiku 5.5` 줄) - 설계 문서·검사(`SPEC.md`, `FEATURES.json`, `check.sh`)와 구동 설명서(`guide/`)는 설계를 맡은 별도 Claude(Opus) 세션이 쓰고, 하이쿠가 커밋한다(커밋 첫 줄 KIT·DOC). - GNU LibreDWG 0.14 (DWG → DXF), Python 3 표준 라이브러리, git, 브라우저(Chrome·Edge)