Files
dxf-to-3d-gasiseol/docs/dwg2model-CLAUDE.md
T
Claude Haiku 363c2a2a25 step3-4: 프로젝트 기본 구조 & 상세 계획 수립
- CLAUDE.md: 프로젝트 전체 가이드 및 워크플로우 정의
- CONSIDERATIONS.md: 8개 영역 고려사항 분석 (기술, 품질, 배포, UX)
- PROGRESS.md: 진행 상황 추적 및 체크리스트
- PLAN.md: 단계 5~9 상세 계획 및 에이전트 역할 정의
- 메모리: 프로젝트 정보 저장

다음: 단계 5 (에이전트/커맨드/스킬/훅 아이디어 제안)
2026-10-16 15:06:18 +09:00

218 lines
13 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.
# CLAUDE.md — dwg2model 작업 가이드
이 저장소에서 작업할 때 지켜야 할 것. 사람이 읽어도 된다.
---
## 이 프로젝트가 하는 일
2D 도면(평면 · 입면 · 단면 · 좌/우측면)을 읽어 3D 솔리드와 IFC 로 옮긴다.
**도면마다 설정 파일 하나만 바꾸면 도는 구조**가 목표다.
문서 셋을 먼저 읽어라.
| 문서 | 읽어야 할 때 |
|---|---|
| [`README.md`](README.md) | 구조 · 실행 방법 |
| [`docs/2d-3d-process.md`](docs/2d-3d-process.md) | **절차** — 새 도면 추가, 검증 순서 |
| [`docs/2d-3d-conversion.md`](docs/2d-3d-conversion.md) | **노하우** — 함정, 실패 사례, 왜 그렇게 하는지 |
| [`docs/roadmap.md`](docs/roadmap.md) | 남은 일 · 우선순위 |
| [`docs/parametric.md`](docs/parametric.md) | **파라메트릭 설계 층** — 설계 노트, 미구현 |
`2d-3d-conversion.md` 의 함정 목록은 실제로 다 밟아본 것이다. 새로 시작하기 전에
훑어보면 같은 곳에서 두 번 넘어지지 않는다.
---
## 절대 어기면 안 되는 경계
```
src/core/* · src/ir.mjs → 도면 고유값이 들어가면 안 된다
src/drawings/*.mjs → 레이어명 · 제목 · 임계값 · 좌표 · 단위 · 축척은 전부 여기
src/emit/* → 여기서만 3D 툴의 사정을 안다
```
이 경계가 무너지면 두 번째 도면에서 **파이프라인이 아니라 복사본**이 생긴다.
`core/` 에 `'-전력구'` 같은 문자열을 넣고 싶어지면 설계가 잘못된 것이다.
**이 경계는 이미 두 번 새 있었다.** 교량 도면을 넣어보고서야 드러났다.
- `core/datum.mjs` 의 `UNIT_MM = 1000` — 숫자라서 눈에 안 띄었을 뿐 도면 고유값이다.
같은 파일의 라벨 대역(`±3`)·뷰 구분 문턱(`2`)도 「1단위=1m」를 전제한 길이였다.
- `rules.mjs` 는 주석에 "도면도 3D도 모른다"고 적혀 있었지만 `챔퍼_시작X`·`샤프트`
같은 **첫 도면의 파라미터 이름**에 묶여 있었다. 두 번째 도면에서 `undefined` 비교가
전부 위상 위반으로 떨어져 `build` 가 죽었다. 지금은 `needs` 로 적용 범위를 명시한다.
교훈: **문서에 "안 들어간다"고 쓰는 것으로는 안 지켜진다.** 두 번째 도면을 넣기 전에는 안 보인다.
경로는 `src/paths.mjs` 한 곳에서만 정한다. 스크립트에서 `../..` 을 세지 말 것.
---
## 작업 순서 — 이 순서를 건너뛰지 마라
3D 부터 세우면 **어디가 틀렸는지 알 수 없다.** 반드시 아래 순서로 통과시킨다.
```
0. egm guide | wc -l 뷰어 모드가 아닌가 (39 면 뷰어 — 모델링 불가)
1. node src/run.mjs <도면> 기준점이 도면 치수 2개 이상과 맞는가
뷰 폭·높이가 도면 치수와 맞는가
2. node src/gaps.mjs <도면> 프로파일이 안 닫히는 곳 · 그 원인
3. node src/build.mjs <도면> --loops 폐곡선 치수가 도면 표기와 맞는가
꼭짓점 수가 예상과 맞는가
4. node src/build.mjs <도면> IR 검증
5. python src/run_egm.py … --dry 좌표 검산
6. python src/run_egm.py … --export=out/<도면>.obj 실행 + OBJ
7. egm status modelCount 가 최종 파트 수와 같은가
8. node src/measure.mjs <도면> --ir 의도(IR) vs 결과(모델러) 대조 + 기준선
```
`--ir` 이 핵심이다. 부피가 이상할 때 **우리 조립이 틀렸는지 모델러가 흘렸는지**를
한 줄로 가른다. 손으로 기대값을 조립하면 커터 범위를 놓쳐 세 번 다 틀렸다.
`run.mjs` 는 검증 실패 시 **exit 1** 을 낸다. 그 상태로 다음 단계에 가지 말 것.
7 이 어긋나면 boolean 이 인자를 흘린 것이다. 「모델러 조작」 절을 볼 것.
---
## 자주 틀리는 것 (전부 실제로 겪었다)
| 함정 | 올바른 방법 |
|---|---|
| 표고 기준을 **텍스트 위치**로 잡음 | **선**에서 잡는다. 713mm 틀렸다 |
| 계획고 선 후보가 둘 (도로/토공) | 라벨 **바로 아래** 선을 고른다. 420mm 틀렸다 |
| 횡단면 하나를 전 구간에 압출 | 종단면이 **구간 배정표**다. 구간마다 단면이 다르다 |
| 폐곡선을 `loops[0]` 로 집음 | **치수**로 집는다. `loop(뷰, 2700, 2750)` |
| 자기교차 링을 그냥 압출 | 솔리드가 쓰레기가 되고 이후 boolean 이 **조용히** 무력화된다. `--loops` 의 `✗ 자기교차` 를 먼저 볼 것 |
| 원 좌표·반지름을 손으로 적음 | `circles(뷰, 900, /^-전력구$/)`. 손으로 적은 수는 도면이 바뀌어도 안 바뀐다 |
| 원을 선분에 섞어 넣음 | 따로 모은다. 폐곡선이 원 윤곽을 따라 돌아 오염된다 |
| 평면도 세로축을 표고로 봄 | 세로=Y. `v0` 앵커 필요. 치수만 보는 동안은 안 걸린다 |
| 개구부 폐곡선을 그대로 씀 | X 기호 때문에 링이 자기교차. **bbox → `rect()`** 로 재구성 |
| 오목한 외곽을 그대로 뚫음 | 노치를 합쳐 **봉투부터** 만들고 그 다음에 뚫는다 |
| 구간별로 따로 difference | 구간 **몸통**을 union 한 뒤에 뚫는다. 경계에 구멍 난다 |
| 커터를 union 해서 한 번에 | 떨어진 커터끼리는 교차하지 않아 union 이 죽는다. 커터는 **하나씩** |
| 커터가 자기 부피보다 많이 뺌 | boolean 이 죽은 것이다. `--primitives` 로 커터 부피를 보면 즉시 확정 |
| 면으로만 맞닿은 솔리드를 union | 조각으로 붙어 있다가 다음 difference 가 통째로 흘린다. **부피로 겹치게** |
| union 결과를 뚫음 | 조각마다 제 몫을 먼저 뚫고 합친다. 커터는 조각 수만큼 따로 만든다 |
| 커터를 대상 바깥면에서 시작 | `OVER` 만큼 밖으로 뺀다. 압출축뿐 아니라 **모든 방향** |
| 수평 단면의 세로축을 Z 로 봄 | 가로=Y, 세로=X. `swapUV` |
| 압출축을 의식 안 함 | YZ→X 압출은 **Y 방향 벽만** 만든다 |
| 전이부를 loft 하나로 이음 | loft 는 **꺾인 자리를 못 만든다**. 꺾이는 X 마다 단면을 놓고 끊는다 |
| loft 단면을 그냥 넣음 | 시작 꼭짓점이 어긋나면 면이 비틀린다. 링 정규화 + 꼭짓점 수 일치 |
| DWG 1단위 = 1m 로 가정 | **도면군마다 다르다**. 맨홀군 1m, 교량 삽도군 1mm. `cfg.unitMm` 로 준다. 안 맞추면 전 치수가 1000× 로 **조용히** 틀린다 |
| 모델스페이스가 1:1 이라고 가정 | 삽도는 **도형마다 배율이 다르다** (5.805배 사례). 표기 치수 2개로 역산해 교차검증 |
| 표고 기준선이 있다고 가정 | 없는 도면이 있다. `datum.mode:'none'` + 뷰별 `anchors.v0`. 아무 수평선이나 자동 선택하면 안 된다 |
| 뷰 제목으로 뷰를 가름 | 제목이 없거나 **N벌 중복**인 도면이 있다. `cfg.viewBoxes` 로 좌표를 짚는다 |
| 레이어로 치수선을 거름 | 구조선과 같은 레이어인 도면이 있다. `colorIndex`(0=ByBlock=치수블록)가 가른다 → `cfg.entityFilter` |
| 끊김 0인데 폐곡선 0 | 겹치는 **동일선상 선분**. T자 분할 뒤 중복 변이 남는다. `loops.mjs` 가 분할 뒤 한 번 더 지운다 |
| 파싱 캐시 파일명에 `[^\w.-]→_` | `\w` 가 한글을 안 먹어 다른 도면이 **같은 캐시**를 쓴다. 인덱스·해시로 |
| 도면을 바로 게이트에 넣음 | 먼저 **숫자 치수 vs 기호 치수**를 센다. 다만 「그 도면 안에 대조 상대가 없다」일 뿐 — 다른 도면·표준 부재 치수로 축척을 되찾으면 쓸 수 있다 |
| 그림 안 보고 뷰 상자를 잡음 | **한 시트에 날짜 다른 작업본이 겹쳐** 있을 수 있다. 두 벌이 섞여도 폐곡선·치수가 그럴듯해 **숫자로는 안 걸린다.** 시트 전체 → 도형 하나 → 확인 → 상자 |
| 같은 뷰에 확대 상세가 섞임 | 축척이 다르다(받침 153×182 ↔ 상세 461×196). 치수로 집으면 갈리지만 **갈렸는지 검사**를 넣는다 |
| 같은 부재가 1mm 다르게 나옴 | 꼭짓점마다 따로 반올림한다. `loops(…, tol)` 로 열되 기본값 0 |
| 면적은 맞는데 폭·높이가 틀림 | **매달린 변**(끝이 안 닿는 선) 왕복이 bbox 를 부풀린다. 면적은 0 이라 안 걸린다 |
| 진단 도구가 본편과 다른 값 | 도구가 `projectViews` 를 안 거쳤다. 본편과 **같은 함수**를 써야 한다 |
| 형상이 없으면 정보도 없다고 봄 | 말뚝은 **해치 + 중심선**이다. 중심선 x = 위치, 윤곽 두 줄 간격 = 지름 |
`--loops` 의 **꼭짓점 수**가 가장 빠른 이상 신호다.
```
4 정상 직사각형
10 헌치가 있는 정상 형상
8 개구 기호(X)가 링을 오염시킴
114 케이블랙 같은 가구가 프로파일 레이어에 섞임
```
---
## 모델러(EGModeler) 조작
```
Extrude C=True 단독은 항상 +Z 로 민다. → 항상 D F {기준점} {방향점} 로 방향 명시
C=True 는 D F 앞에. → 뒤에 오면 무시되고 서피스가 나온다
닫힌 Polyline 에 Enter 금지 → 남은 신호가 다음 명령으로 샌다
SelByUids 는 Esc 로 닫는다 → 안 닫으면 이후 명령이 전부 무시된다
명령은 비동기 → command 가 null 될 때까지 대기
보이드 객체 없음 → 커터 솔리드 + BooleanDifference 뿐
Boolean 은 두 개만 먹는다 → 셋 이상 선택하면 나머지는 조용히 무시된다
Loft 는 기본이 서피스 → C=True 로 막아야 솔리드. Enter 는 한 번
Loft 의 L(원본삭제)은 기본 true → 끄면 커브가 남아 modelCount 가 어긋난다
Cap 은 제자리 수정 → 새 uid 가 안 나와 파이프라인이 못 잡는다
ExportByPath 경로에 _ 가 있으면 → 큰따옴표 필수. 없으면 조용히 파일이 안 생긴다
SelAll 먼저. .obj 만 나온다 (.stl 은 안 됨)
```
`SelAll`→`Delete` 가 파이프를 죽인 적이 있다. 프로세스도 창도 멀쩡한데
(`Responding=True`, CPU 유휴) 명령만 전부 "응답하지 않습니다"가 된다.
CLI 로는 못 살린다 — 모델러를 재시작한다.
### 시작 전에 뷰어 모드부터 확인한다
뷰어 모드면 **모델링 명령이 등록조차 안 된다.** 파이프는 살아 있어서 `egm status` 는
정상 응답하고, `~Polyline` 은 아무 말 없이 `created:[]` 를 낸다. 원인을 찾는 데
제일 오래 걸린 함정이다.
```sh
egm guide | python -c "import sys,json;print(len(json.load(sys.stdin)))"
# 39 → 뷰어 모드. 보기·선택·계측만 있다. 모델링 불가
# 207 → 정상
egm guide Polyline # "not found" 면 뷰어 모드
```
CLI 로는 못 바꾼다. 모델러 UI 에서 **Assistive Features → Working Mode Setting** 을
`Viewer & Modeler Use` 로 놓는다. 모델링 플러그인은 뷰포트가 한 번 렌더된 뒤
`LoadProjectPluginsAsync()` 로 올라오므로, 창이 화면 밖에 있으면 같은 증상이 난다.
### Boolean 은 이항이다
`BooleanDifference` · `BooleanUnion` 플러그인은 선택 개수와 무관하게
`TargetModels[0]`, `[1]` **두 개만** 쓴다. 커터 9개를 한 번에 넘겼더니 컷 1개만
적용되고 커터 8개가 씬에 그대로 남았다 (`modelCount` 11 이어야 할 게 19).
`emit/egm.mjs` 의 `fold()` 가 둘씩 접는다. 중간 결과는 `id~1`, `id~2` … 로 잡고
마지막만 `id` 가 된다.
**`modelCount` 가 최종 파트 수와 같은지 매번 확인한다.** 가장 빠른 이상 신호다.
### 씬 초기화
`!Cancel` 을 쓰면 모델러가 명령을 안 받는 상태에 빠진 적이 있다.
초기화는 `Esc` → `SelAll` → `Delete` → `Esc` 로 하고 `egm status` 로 확인한다.
---
## 파라메트릭 층
```
DWG ──fit──→ params/<이름>.json ──build --params──→ IR ──→ 3D
↑ 사람은 여기만 만진다. build 는 도면을 안 본다
```
| | |
|---|---|
| `src/fit.mjs` | 도면 → 파라미터. 재현 안 되는 링은 `grade: opaque` 로 좌표째 얼린다 |
| `src/params.mjs` | 스키마·식(위상정렬)·검증·도면 이탈 감지 |
| `src/rules.mjs` | 위상 규칙(**막는다**) · 설계 규칙(경고) |
| `src/volume.mjs` | IR 을 격자로 평가 — 모델러 없이 "의도" 부피 |
| `src/ui.mjs` | 편집 + 갱신 버튼. `node src/ui.mjs manhole1` |
- `drawings/*.mjs` 는 `fit()` 과 `assemble()` 둘로 나뉜다. **`assemble()` 에는
도면 조회 도우미를 주지 않는다** — 주면 다시 도면에 묶인다.
- **모델러에서 직접 편집하지 않는다.** 갱신하면 UID 가 전부 바뀌어 날아간다.
## 코드 스타일
- 주석은 **왜** 를 적는다. 무엇을 하는지는 코드가 말한다.
- 실패했던 방법과 그 이유를 주석에 남긴다. 다음 사람이 같은 걸 시도한다.
- 좌표·치수는 mm 정수. `egm` 에 넘기기 직전에만 m 로 나눈다.
- 조용히 틀리느니 예외를 던진다 (`loop()` 가 0개나 2개 이상이면 throw).
---
## 라이선스
`vendor/acadrust-dwg/` 는 **MPL-2.0 수정본**이다.
이 wasm 을 다른 저장소로 옮길 때 `THIRD-PARTY-NOTICES.md` 와 `licenses/` 두 파일을
반드시 함께 옮겨야 한다. 패치 파일이 MPL-2.0 §3.2 의무의 근거다.