bdd296597ad63b6c7b249b6c6aeb93dfb44fbeb6
이슈 #1 중 코치 몫(SPEC·CLAUDE·검사). - SPEC.md: 지금 지킬 규약만 남긴 새 판 - CLAUDE.md: 이슈 방식 작업 규칙 (하이쿠용) - check.sh: F17 기준을 짧은 README 기준으로 바꾸고 이슈 #1 완료 기준 F47 추가 코치가 저장소 밖 시험 사본(별도 README 참조안)으로 F47 이 통과 가능함을 확인. 이 저장소에서는 README·FEATURES·PROGRESS 작업 전이라 지금 F47 이 실패하는 것이 정상. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
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: 다각형 점 배열을 SVGpath의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).
- 화가 알고리즘: 면마다 가운데 점의 깊이를 재서 먼 면부터 칠한다. 겹침은 이 순서로 해결한다.
- 벽은
alpha0.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)
Languages
JavaScript
35%
Shell
28.8%
Python
28.4%
HTML
7.4%
CSS
0.4%