- CLAUDE.md: 프로젝트 전체 가이드 및 워크플로우 정의 - CONSIDERATIONS.md: 8개 영역 고려사항 분석 (기술, 품질, 배포, UX) - PROGRESS.md: 진행 상황 추적 및 체크리스트 - PLAN.md: 단계 5~9 상세 계획 및 에이전트 역할 정의 - 메모리: 프로젝트 정보 저장 다음: 단계 5 (에이전트/커맨드/스킬/훅 아이디어 제안)
13 KiB
CLAUDE.md — dwg2model 작업 가이드
이 저장소에서 작업할 때 지켜야 할 것. 사람이 읽어도 된다.
이 프로젝트가 하는 일
2D 도면(평면 · 입면 · 단면 · 좌/우측면)을 읽어 3D 솔리드와 IFC 로 옮긴다. 도면마다 설정 파일 하나만 바꾸면 도는 구조가 목표다.
문서 셋을 먼저 읽어라.
| 문서 | 읽어야 할 때 |
|---|---|
README.md |
구조 · 실행 방법 |
docs/2d-3d-process.md |
절차 — 새 도면 추가, 검증 순서 |
docs/2d-3d-conversion.md |
노하우 — 함정, 실패 사례, 왜 그렇게 하는지 |
docs/roadmap.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:[] 를 낸다. 원인을 찾는 데
제일 오래 걸린 함정이다.
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 의무의 근거다.