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

13 KiB
Raw Blame History

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 의무의 근거다.