Files
drawing3d-web/README.md
T
오문성andClaude Haiku 5.5 9068b6c7f2 공개용 정리본: 1차 제출본(도면 좌표·과업명 제거)으로 새 이력 시작 (SPEC 34장)
- 정리는 코치 세션이 저장소 밖 도구로 수행, 하이쿠 세션이 커밋.
- 옛 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>
2026-10-27 14:27:50 +09:00

185 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 를 `<img>` 로 띄우고, 같은 크기의 `<svg id="hotspots">` 를 겹친다. 좌표는 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)