- CLAUDE.md: 프로젝트 전체 가이드 및 워크플로우 정의 - CONSIDERATIONS.md: 8개 영역 고려사항 분석 (기술, 품질, 배포, UX) - PROGRESS.md: 진행 상황 추적 및 체크리스트 - PLAN.md: 단계 5~9 상세 계획 및 에이전트 역할 정의 - 메모리: 프로젝트 정보 저장 다음: 단계 5 (에이전트/커맨드/스킬/훅 아이디어 제안)
218 lines
13 KiB
Markdown
218 lines
13 KiB
Markdown
# 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 의무의 근거다.
|