diff --git a/CLAUDE.md b/CLAUDE.md index 76da158..0037d41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,37 +1,23 @@ -# drawing3d-web — 작업 규칙 (Haiku 세션용) +# drawing3d-web — 작업 규칙 (Haiku) -원형 수직구 일반도를 라이브러리 없이 Canvas 2D 로 보여 주는 3D 뷰어. 설계는 `SPEC.md`, 할 일은 `FEATURES.json`. +규약은 `SPEC.md`. **작업 목록과 설계 배경은 Gitea 이슈(tdc_engineer/drawing3d-web)에 있다.** -## 시작할 때마다 -1. `PROGRESS.md` 마지막 5줄과 `git log --oneline -5` 를 읽는다. -2. `FEATURES.json` 에서 `"passes": false` 인 **맨 앞 기능 하나**만 고른다. 두 개를 같이 하지 않는다. -3. 그 기능의 `steps` 와 SPEC 의 해당 장을 읽고 시작한다. +## 시작 +1. 지시받은 이슈를 **제목이나 번호로 찾아 그 이슈만** 읽는다. 다른 이슈·긴 문서를 미리 다 읽지 않는다. +2. `git log --oneline -5` 를 본다. -## 실행과 검증 -- 화면 보기: `explorer.exe index.html` (Windows 브라우저로 열림). 수석이 화면을 확인한다. -- 판정: `bash check.sh F0n` (n = 지금 기능 번호). 그 기능과 앞 기능 항목이 전부 `[PASS]` 여야 끝이다. -- 끝났다고 말할 때는 **check.sh 출력 전체를 그대로 붙인다.** 요약하거나 고쳐 쓰지 않는다. - -## 끝낼 때 (기능 1개 = 커밋 1개) -1. `FEATURES.json` 에서 그 기능의 `passes` 만 `true` 로 바꾼다. 다른 글자는 건드리지 않는다. -2. `PROGRESS.md` 에 한 줄 추가(양식은 파일 첫 줄). -3. `git add -A` 후 커밋. 메시지 첫 줄 `F0n: 한 일`, 본문에 check.sh 마지막 줄. -4. 다음 기능으로 넘어가지 말고 수석에게 보고하고 멈춘다. +## 끝 (이슈 하나 = 커밋 하나) +1. `bash check.sh` 가 FAIL 0 이어야 끝이다. 아니면 커밋하지 않고 멈춰 보고한다. +2. 커밋 메시지 첫 줄 끝에 `(#이슈번호)`, 본문에 check.sh 마지막 줄과 `Co-Authored-By` 줄. +3. 이슈 댓글(완료 기준 확인 표·검사 결과·커밋 해시·경위 한 줄)과 push·이슈 닫기는 **지시가 있을 때만** 한다. ## 금지 -- `check.sh`, `SPEC.md`, `CLAUDE.md` 수정 금지. 틀렸다고 생각되면 고치지 말고 수석에게 말한다. -- three.js 등 라이브러리, CDN, `import`, `require`, 외부 URL 금지. 파일은 index.html, viewer.js, model.js, README.md, .gitignore, guide/ (구동설명서 HTML), tools/prep_drawing.py, tools/extract_from_dxf.py, model_extracted.js, compare.html. F33~F34 에서는 model.js 를 고치지 않는다. -- 도면은 `private/` 의 공개용 정리본만 커밋한다: `drawing.dxf`·`drawing.svg`·`drawing_gen.svg` (SPEC 34장). 원본 DWG·`full.dxf`·지명·과업명·실제 좌표가 든 파일은 절대 커밋하지 않는다. `git add -A` 전에 `git status` 로 확인한다. -- `model.js` 는 `const MODEL = ` 뒤에 순수 JSON(따옴표 키, 주석·끝 쉼표 없음) 그리고 `;`. -- 커밋 메시지의 `Co-Authored-By` 줄을 지우지 않는다. `--no-verify`, `--amend`, `rebase`, 날짜 지정 금지. -- 실패한 시도도 커밋을 지우지 않는다. 되돌릴 때는 새 커밋으로. -- 원본 도면 파일(DWG·정리 전 DXF), 회사 파일명, 비밀값을 리포에 넣지 않는다. - -## 코드 -- 순수 JavaScript 한 파일(viewer.js). 함수 이름은 SPEC 7장 목록을 쓴다. -- 길이 단위는 mm, z 위로 +. 각도는 X축에서 반시계, 도. -- 면 = 점 배열 + 색 + alpha. 먼 면부터 그린다. +- `check.sh`·`SPEC.md`·`CLAUDE.md` 수정. 틀렸다고 생각되면 고치지 말고 보고한다. +- 검사를 통과시키려고 파일을 숨기거나 검사를 우회하기. +- 화면 코드의 라이브러리·CDN·import·외부 URL. 도구는 표준 라이브러리만. +- 정리본 3개(`private/drawing.dxf`·`drawing.svg`·`drawing_gen.svg`) 밖의 도면, 원본 DWG, 지명·과업명·실제 좌표, 비밀값 커밋. `git add -A` 전에 `git status`. +- `model.js` 는 `const MODEL = ` 뒤 순수 JSON 과 `;`. +- `Co-Authored-By` 줄 지우기, `--no-verify`·`--amend`·`rebase`·날짜 지정, 실패한 커밋 지우기. ## 막히면 -- 같은 문제를 두 번 고쳐도 안 되면 멈추고 수석에게 무엇이 안 되는지 보고한다. -- 모르는 치수는 지어내지 않는다. SPEC 에 없으면 수석에게 묻는다. +같은 문제를 두 번 고쳐도 안 되면 멈추고 무엇이 안 되는지 보고한다. 모르는 치수는 지어내지 않는다. diff --git a/SPEC.md b/SPEC.md index 9179f27..4d0d7cd 100644 --- a/SPEC.md +++ b/SPEC.md @@ -1,615 +1,40 @@ -# SPEC — 전력구 수직구 3D 뷰어 (MVP) +# drawing3d-web 설계 규약 -> 🔒 사내교육용 — 열람 외 재배포 등의 활동을 금함 (회사 도면의 치수를 옮겨 적음) -> 설계 면담(수석 + 코치 세션, 2026-10-19~20)으로 확정. 이 파일은 하이쿠가 고치지 않는다. +지금 지켜야 할 규약만 둔다. **작업 목록·설계 배경·결정 이력은 Gitea 이슈**에 있고, 옛 설계서(34장)는 git 이력(9068b6c)에 남아 있다. -## 1. 목적 -원형 수직구 일반도 한 장을 **라이브러리 없이** 브라우저 3D 화면으로 보여 준다. -청중은 교육 담당자와 동료이고, 3분 시연에서 아래 세 문장을 화면을 돌려 가며 말할 수 있으면 끝이다. -1. 외경 11m, 깊이 70.3m 원통이다. -2. 안에 슬래브 19장 + 하부 슬래브가 있고, 층마다 계단이 지그재그로 내려간다. -3. 바닥 근처 19B 층에서 4시 방향으로 TBM 터널(내경 2.6m)이 나가고, 터널 바닥 패드와 19B 패드 상면 높이가 같다. +## 1. 무엇 +원형 수직구 일반도(DWG) 한 장을 도구가 읽어 3D 데이터(`model_extracted.js`)와 도면 그림(`private/drawing_gen.svg`)을 만들고, `index.html` 이 라이브러리 없이 Canvas 2D 로 보여 준다. `model.js` 는 손으로 만든 정답 모델 — 검사 기준이자 `answer.html` 화면. -## 2. 범위 -| 넣는 것 | 빼는 것 (범위 밖) | +## 2. 좌표·단위 +mm, z 위 +, 지표 z 0. 각도는 X축에서 반시계, 도. 모델 XY 는 슬래브 평면 틀이다(평면도는 −30.3° 돌아 있어 views 의 `rot_deg`). 도면 DXF 좌표는 공개용으로 평행이동된 값이다. + +## 3. 데이터 형식 +`const MODEL = {순수 JSON};` — `units` "mm", `parts[]`, `drawing{src, viewBox, fit, section, plan, views[]}`. +부재 공통 `id·kind·type·name·color`. type 별: + +| type | 필드 | |---|---| -| 벽체, 중간 슬래브 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` 하나, 그 안에 ``(도면)과 같은 크기로 겹친 인라인 ``(클릭 영역). 확대·이동은 `#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 -- 부재별 클릭 영역(다각형, 도면 단위). 아래 순서대로 만들고, 겹치면 나중 것이 위: - 1. stair: 단면, u = x0 ± 4.85, v = y0 − (z_high − 300)/1000 ~ y0 − z_low/1000 - 2. wall: 단면 두 띠, u = x0 − 5.5 ~ x0 − 4.85 와 x0 + 4.85 ~ x0 + 5.5, v = y0 − z1/1000 ~ y0 − z0/1000 - 3. tunnel: 평면, 축 시작점에서 angle_deg 방향으로 length, 폭 ±r_out 인 사각형 - 4. slab, pad, bottom, lean (disc): 단면, u = x0 ± r/1000, v = y0 − z1/1000 ~ y0 − z0/1000 - 5. 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) -제목 순서(## 단계 제목에 아래 낱말을 그대로 넣는다): -1. 목적 — 무엇을 누구에게 보여 주는가(SPEC 1장 세 문장) -2. 기획 — 도면 선정 → 범위 결정 → 설계 면담으로 치수·규칙 확정(SPEC 3~5장 요지) → SPEC·FEATURES·check.sh → 기능 하나씩 커밋. 결정된 것 중 핵심 5개 이상(예: 지표 = z 0, 터널 패드 = 19B 패드 높이, 계단 첫 단 100) -3. 범위 — 넣은 것 / 뺀 것 (SPEC 2장, 9장) -4. 구동 — 더블클릭으로 연다, 도면은 `private/drawing.svg` 에 둔다, 브라우저 -5. 조작 — 3D 드래그·휠, 가운데 막대, 도면 확대·이동, 도면 클릭 → 3D 강조 -6. 작업 방식 — WSL2, Claude Code(Haiku 5.5)가 코드 작성·수정·커밋, 합격 판정은 check.sh, 설계 면담·검토와 F02 이후 하이쿠 호출은 수석 위임으로 상위 모델 코치 세션이 맡음, 기능 1개 = 커밋 1개 -7. 한계 — 계단은 위에서 보면 슬래브에 가려 옆에서 본다, 슬래브 개구부 없음, 3D → 도면 방향 없음 등 -8. 이력 — `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` 로 가는 링크 - - 🔒 한 줄, `

` 쪽 제목 - - 위키처럼 본문 낱말에 다른 쪽 링크를 건다(예: 기획 쪽의 「범위」 → scope.html). 쪽마다 다른 쪽 링크 2개 이상 - - 아래에 「목차」 링크와 이전/다음 쪽 링크 -- 3D 화면(`index.html`): 오른쪽 3D 칸 **오른쪽 위**에 「구동 설명서」 버튼 ``. 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 로 바꾸고 루트에 ``, 색마다 ``. 원본처럼 선은 화면 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 벽 5~8)의 축 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/」 기준을 이것으로 바꾼다. F13 의 「도면 파일 커밋 금지」 항목도 「커밋된 도면은 정리본 3개뿐」으로 바꾼다(처음 바꿀 때 빠뜨림 — 첫 커밋 뒤 이 항목이 FAIL 이었다). +| disc | cx cy r z0 z1 (holes) | +| ring | cx cy r_in r_out z0 z1 | +| pipe | x y z angle_deg length r_in r_out | +| fill | x y z angle_deg length r v0 v1 half_width | +| stair | z_low z_high risers first_riser riser tread x_top dir y0 y1 | +| prism | polygon z0 z1 (holes) | +| zone | r x_min x_max z0 z1 alpha | +| ladder | x0 x1 yc width z0 z1 rung (angle_deg) | +| obox | cx cy angle_deg s0 s1 t0 t1 z0 z1 | +| railing | z h paths | + +## 4. 도면 → 데이터 (`tools/`) +`bash tools/make_model.sh`: DWG → DXF(GNU LibreDWG) → `views.json` 의 그림 상자만 → `extract_from_dxf.py`(+ 도면에 없는 값은 `design_rules.json`) → `model_extracted.js`, `dxf_to_svg.py` → `drawing_gen.svg`. 규칙의 근거는 코드 주석과 이슈. + +## 5. 검사 원칙 +- `check.sh` 가 합격 판정이다. 검사를 먼저 커밋하고 그 뒤 구현한다. +- 기준값은 도면에서 가져온다. 코드에서 역산하지 않는다. +- 변조 시험: 도면을 바꾸면 결과도 그만큼 바뀌어야 한다(하드코딩 방지). + +## 6. 공개 규칙 +커밋하는 도면은 `private/drawing.dxf`·`drawing.svg`·`drawing_gen.svg` 뿐. 원본 DWG·지명·과업명·실제 좌표는 넣지 않는다. + +## 7. 금지 +화면 코드: 라이브러리·CDN·import·외부 URL. 도구: 표준 라이브러리만(LibreDWG 명령 제외). diff --git a/check.sh b/check.sh index 70e8310..467ab48 100644 --- a/check.sh +++ b/check.sh @@ -7,10 +7,10 @@ cd "$(dirname "$0")" || exit 2 python3 - "$@" <<'PY' import json, math, os, re, sys -ORDER = ["F01", "F02", "F03", "F04", "F05", "F06", "F07", "F08", "F09", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "F25", "F26", "F27", "F28", "F29", "F30", "F31", "F32", "F33", "F34", "F35", "F36", "F37", "F38", "F39", "F40", "F41", "F42", "F43", "F44", "F45", "F46"] +ORDER = ["F01", "F02", "F03", "F04", "F05", "F06", "F07", "F08", "F09", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", "F25", "F26", "F27", "F28", "F29", "F30", "F31", "F32", "F33", "F34", "F35", "F36", "F37", "F38", "F39", "F40", "F41", "F42", "F43", "F44", "F45", "F46", "F47"] upto = sys.argv[1].upper() if len(sys.argv) > 1 else ORDER[-1] if upto not in ORDER: - print("사용법: bash check.sh [F01~F46]") + print("사용법: bash check.sh [F01~F47]") sys.exit(2) LIMIT = ORDER.index(upto) TOL = 1.0 @@ -233,12 +233,9 @@ miss = [w for w in want if not any(w in h for h in heads)] report("F16", bool(guide) and not miss, "구동설명서에 제목 목적·구동·조작 (SPEC 18.3)" + (f" — 없음: {', '.join(miss)}" if miss else "")) rd = readme or "" rheads = [l for l in rd.splitlines() if l.startswith("## ")] -rwant = ["파일 구조", "실행", "데이터", "코드", "그리기", "도면 연결", "검사"] +rwant = ["실행", "파일 구조", "검사", "사용 도구"] rmiss = [w for w in rwant if not any(w in h for h in rheads)] -report("F17", "🔒" in rd and not rmiss, "README.md 에 🔒 와 ## 제목 7개(파일 구조~검사)" + (f" — 없음: {', '.join(rmiss)}" if rmiss else "")) -allf = sorted({n for fid2, names in FUNCS.items() if ORDER.index(fid2) <= LIMIT for n in names} | {"facesBox"}) -fmiss = [n for n in allf if n not in rd] -report("F17", not fmiss, f"README.md 에 함수 이름 {len(allf)}개 전부" + (f" — 없음: {', '.join(fmiss)}" if fmiss else "")) +report("F17", "🔒" in rd and not rmiss, "README.md 는 짧은 안내서: 🔒 와 ## 제목(실행·파일 구조·검사·사용 도구) (이슈 #1)" + (f" — 없음: {', '.join(rmiss)}" if rmiss else "")) # ---- F18: 구동설명서 HTML 위키 (SPEC 12장) ---- KEEP = ["index", "purpose", "run", "control"] @@ -1083,6 +1080,19 @@ report("F46", re.search(r"Ubuntu\s+\S+\s+2\b", ev) is not None and "설치일" i report("F46", "microsoft-standard-WSL2" in ev and "PRETTY_NAME" in ev and re.search(r"\$ claude --version\s*\n\s*\S", ev) is not None, "evidence/wsl2.txt WSL 안쪽: microsoft-standard-WSL2 커널·Ubuntu 버전·claude 버전") report("F46", "evidence/wsl2.txt" in (readme or ""), "README 사용 도구가 evidence/wsl2.txt 를 가리킨다") +# ---- F47: md 가이드 최소화 (이슈 #1) ---- +try: + trk47 = [t for t in subprocess.run(["git", "ls-files"], capture_output=True, text=True).stdout.split("\n") if t] +except OSError: + trk47 = [] +md47 = [t for t in trk47 if t.lower().endswith(".md")] +sz47 = sum(os.path.getsize(t) for t in md47 if os.path.exists(t)) +report("F47", bool(trk47) and len(md47) <= 3 and sz47 <= 8192, f"저장소 md 3개 이하·합계 8KB 이하 (실제 {len(md47)}개 {sz47} B: {', '.join(md47)})") +gone47 = [t for t in ("FEATURES.json", "PROGRESS.md") if t in trk47 or os.path.exists(t)] +report("F47", bool(trk47) and not gone47, "FEATURES.json·PROGRESS.md 없음 — 할 일은 이슈, 경과는 커밋" + (f" — 남음: {gone47}" if gone47 else "")) +cl47 = read("CLAUDE.md") or "" +report("F47", "Gitea 이슈" in cl47 and "그 이슈만" in cl47 and "(#이슈번호)" in cl47, "CLAUDE.md 에 이슈 규칙(작업 목록은 이슈·그 이슈만 읽기·커밋에 #번호)") + print(f"결과: PASS {count['PASS']} / FAIL {count['FAIL']} / SKIP {count['SKIP']} (검사 범위 F01~{upto})") sys.exit(0 if count["FAIL"] == 0 else 1) PY