Files
dxf-to-3d-gasiseol/docs/2d-3d-process.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

525 lines
23 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.
# 2D → 3D 제작 과정
DWG 도면 한 벌을 3D 솔리드로 세우기까지 **실제로 무엇을 어떤 순서로 했는지**를
기록한다. 다른 프로젝트로 옮길 때 이 문서만 따라가면 재현된다.
- 판단의 근거·함정·교훈은 [`2d-3d-conversion.md`](2d-3d-conversion.md) 에 따로 있다.
이 문서는 **절차**, 그 문서는 **왜 그렇게 하는가**.
- 코드: [`src/`](../src/)
- 대상: `drawings-src/04manhole1-simplify.dwg` (맨홀 #1 및 출입구 일반도, 4매)
---
## 0. 준비물
| 항목 | 이 프로젝트에서의 위치 | 옮길 때 확인할 것 |
|---|---|---|
| DWG 파서 | `vendor/acadrust-dwg/` (wasm, read-only) | wasm 파일 2개(`.js`, `_bg.wasm`)를 같이 옮긴다 |
| Node | 18+ (ESM, top-level await 사용) | — |
| 3D 툴 | EGModeler + `egm` CLI (`D:\GiteaAll\EG-BIM_Modeler`) | 앱이 **실행 중**이어야 한다. `egm status` 로 확인 |
| Python | 3.10+ (`src/run_egm.py`) | 표준 라이브러리만 쓴다 |
`egm` 은 Named Pipe 로 실행 중인 EGModeler 에 명령을 보내는 CLI 다.
연결 확인:
```sh
egm status # {"command":null,"modelCount":0,"selectedCount":0}
```
`EGModeler가 실행 중이지 않거나 응답하지 않습니다.` 가 나오면 앱을 먼저 띄운다.
**`status` 가 응답한다고 모델링이 되는 건 아니다.** 뷰어 모드면 모델링 명령이
등록조차 안 된 채로 파이프만 살아 있다. 등록된 명령 개수로 가른다.
```sh
egm guide | python -c "import sys,json;print(len(json.load(sys.stdin)))"
# 39 → 뷰어 모드. Polyline·Extrude·Boolean* 이 통째로 없다. 모델링 불가
# 207 → 정상
```
39 이 나오면 모델러 UI 에서 **Edit Mode** 로 바꾼다 (View Mode / Edit Mode 토글).
Assistive Features 의 `ModelerMode` 와는 다른 값이다. 자세한 배경은
[`2d-3d-conversion.md`](2d-3d-conversion.md) 교훈 #15.
---
## 1. 전체 흐름
```
DWG ─parse─→ 엔티티 ─segment─→ 뷰 ─datum─→ 프로젝트좌표 ─loops─→ 프로파일
│
조립 스펙 (IR, JSON)
↙ ↓ ↘
egm IFC three.js
```
명령 4개로 끝난다.
```sh
cd src
node src/run.mjs manhole1 # ① 파싱 · 기준점 · 뷰 분리 → out/manhole1.views.json
node src/build.mjs manhole1 # ② 프로파일 추출 · 조립 → out/manhole1.ir.json
node src/emit/egm.mjs out/manhole1.ir.json # ③ 모델러 명령으로 번역 → out/manhole1.egm.json
python src/run_egm.py out/manhole1.egm.json # ④ 실행
```
`src/run.mjs` 는 검증이 깨지면 **종료 코드 1** 을 낸다. CI 게이트로 바로 쓸 수 있다.
---
## 2. 단계별 절차
### ① 파싱 · 기준점 · 뷰 분리 — `src/run.mjs`
```sh
node src/run.mjs manhole1 # 산출물까지 저장
node src/run.mjs manhole1 --report # 검증표만 출력
```
출력 예:
```
도면 manhole1 — R2018, 엔티티 7035
표고 기준선 DWG y = 483623.47666155506 (뷰 11곳에 반복, 총연장 61492mm)
✓ B-B 구체 상단 실측 -4907 도면 -4907
✓ B-B 기초 하단 실측 -7757 도면 -7757
✓ B-B 잡석 하단 실측 -8057 도면 -8057
✓ D-D 구체 상단 실측 -4907 도면 -4907
뷰 kind 조각 선 폭mm 높이mm Z 범위
평 면 도 plan 1 72 20200 10200 -1478 ~ -11678 ✓
단 면 A-A long 2 123 20200 10839 232 ~ -10607
...
✓ 검증 통과 — 뷰 분리와 표고 기준이 도면 치수와 일치
```
**이 단계를 통과하지 못하면 다음으로 가지 않는다.** 여기서 틀린 채로 3D 를
세우면 어디가 원인인지 알 수 없게 된다.
문제가 나오면:
| 증상 | 확인 도구 |
|---|---|
| 뷰가 없다 / 크기가 안 맞는다 | `node src/inspect.mjs manhole1 "<뷰>"` — 그 영역의 모든 엔티티를 레이어 무관하게 훑는다 |
| 기준점 검증 실패 | `datum.checks` 의 레이어 이름이 맞는지, 계획고 선이 `layers.ground` 에 잡히는지 |
### ② 프로파일 추출 · 조립 — `src/build.mjs`
```sh
node src/build.mjs manhole1 --loops # 뷰별 폐곡선 목록 (조립 스펙 작성용)
node src/build.mjs manhole1 # IR 생성
```
`--loops` 출력이 조립 스펙을 쓰는 **재료 목록**이다.
```
단 면 B-B (cross)
# 0 2700× 2750 bbox[-1350..1350, -7657..-4907] 꼭짓점 4 부모 -
# 1 2100× 2150 bbox[-1050..1050, -7357..-5207] 꼭짓점 10 부모 #0
# 2 4300× 300 bbox[-2150..2150, -8057..-7757] 꼭짓점 4 부모 -
# 3 2900× 100 bbox[-1450..1450, -7757..-7657] 꼭짓점 4 부모 -
```
폭×높이가 도면 표기 치수와 일치하는지 먼저 눈으로 확인한다. 여기가 맞으면
그 뒤는 거의 틀리지 않는다.
한 뷰만 자세히 보려면:
```sh
node src/probe.mjs manhole1 "단 면 B-B" # 표
node src/probe.mjs manhole1 "단 면 B-B" --ring # 꼭짓점 좌표까지
```
### ③ 모델러 명령으로 번역 — `src/emit/egm.mjs`
IR 의 연산을 egm 명령으로 바꾼다. 여기서만 egm 의 사정을 안다.
```
extrude → Polyline(닫힌 커브) → SelByUids → Extrude C=True D F {벡터}
loft → 단면마다 Polyline → SelByUids(단면 순서대로) → Loft C=True Enter
box → Box {대각1} {대각2} {높이}
difference → SelByUids(base) → SelByUids(A=true, cutters) → BooleanDifference
union → SelByUids(첫째) → SelByUids(A=true, 나머지) → BooleanUnion
```
`Loft` 는 기본이 **서피스**라 `C=True`(Cap)로 막아야 솔리드가 나온다.
단면 커브는 `L`(DeleteOriginal, 기본 true)로 자동 삭제된다. 별도 `Cap` 명령은
제자리 수정이라 새 uid 가 안 나오므로 여기서는 못 쓴다.
### ④ 실행 — `src/run_egm.py`
```sh
python src/run_egm.py out/manhole1.egm.json # 실행
python src/run_egm.py out/manhole1.egm.json --dry # 보내지 않고 명령만 출력
```
`--dry` 로 좌표를 먼저 검산하는 습관을 들이면 모델러를 왕복하지 않아도 된다.
```
[ 0] duct.solid — extrude
egm ~Polyline 0,-1.9,-7.757 0,1.9,-7.757 0,1.9,-3.157 0,-1.9,-3.157 0,-1.9,-7.757
egm ~SelByUids A=false <duct.solid$crv> Esc
egm ~Extrude C=True D F 0,0,0 15.4,0,0
```
---
## 3. 새 도면을 추가하는 절차
**코드는 건드리지 않는다.** `src/drawings/` 에 파일 하나만 추가한다.
### 3.1 뼈대 만들기
`src/drawings/manhole1.mjs` 를 복사해 이름을 바꾸고 `dwg` 경로를 고친다.
`layers` / `titles.views` 는 일단 대충 채우고 ①을 돌린다.
### 3.2 레이어 알아내기
```sh
node src/run.mjs <도면> --report # 일단 실패해도 좋다
node src/inspect.mjs <도면> "<뷰>" # 그 영역의 레이어 실태
```
`src/inspect.mjs` 는 레이어 필터를 무시하고 전부 보여준다. 여기서
구조체 외곽선이 어느 레이어에 있는지 눈으로 고른다.
### 3.3 레이어를 세 갈래로 나눈다
```js
layers: {
struct: /…/, // 뷰 분리용. 넉넉하게. 가구·주기까지 들어가도 된다
body: /…/, // 구체 프로파일용. 좁게. 케이블랙 같은 가구는 빼야 한다
material: /…/, // 기초·잡석·보도 등 별개 재료. 프로파일 추출엔 함께 넣는다
ground: /…/, // 계획고·GL 선. 표고 기준 검출용
}
```
`body ⊆ struct` 는 코드가 강제한다(`src/run.mjs`). 어겨도 조용히 틀리지 않는다.
### 3.4 표고 기준 잡기
```js
datum: {
levelLabel: /도로\s*계획고/, // 기준선을 찾는 힌트. 값은 선에서 가져온다
checks: [ // 도면 치수와 대조. 최소 2개
['B-B 구체 상단', (v) => v['단 면 B-B']?.layerZ['-전력구']?.[1], -4907],
['B-B 기초 하단', (v) => v['단 면 B-B']?.layerZ['!-기초']?.[0], -7757],
],
}
```
`layerZ[레이어] = [최저 Z, 최고 Z]`. 도면에 적힌 치수를 그대로 넣는다.
### 3.5 뷰별 앵커 잡기
각 뷰의 u(가로) 좌표 중 무엇이 프로젝트 0 인지 정한다.
`--loops` 로 폐곡선 bbox 를 보고 도면 치수와 맞춰가며 찾는다.
```js
anchors: {
'평 면 도': { u0: 1000 }, // u → X
'단 면 B-B': { u0: 2150 }, // u → Y (중심선)
'단 면 F-F': { u0: -14700 }, // XZ 단면
'단 면 H-H': { u0: 1900, v0: -17125, swapUV: true }, // 수평 단면
}
```
| 옵션 | 뜻 |
|---|---|
| `u0` | 가로축 원점. `프로젝트 = u - u0` |
| `v0` | 세로축 원점. 수직 단면은 생략(표고가 이미 기준) |
| `flip` / `flipV` | 축 방향이 반대일 때 |
| `swapUV` | 도면 가로축이 프로젝트 Y, 세로축이 X 일 때 (수평 단면) |
### 3.6 조립 스펙 쓰기
파이프라인에서 **사람 판단이 들어가는 유일한 자리**다.
```js
assemble({ loop, profile, part, rect, expect }) {
profile('duct.outer', 'YZ', loop('단 면 B-B', 2700, 2750).ring);
part({ id: 'duct.solid', op: 'extrude', profile: 'duct.outer', axis: 'X',
from: 0, to: 15400, cap: true, keep: false });
...
}
```
| 도우미 | 용도 |
|---|---|
| `loop(뷰, 폭, 높이)` | 그 치수의 폐곡선을 집는다. 0개나 2개 이상이면 예외 |
| `profile(id, 평면, ring)` | 프로파일 등록. 평면은 `'XY' \| 'XZ' \| 'YZ'` |
| `part({...})` | 파트 추가 |
| `rect(u0,v0,u1,v1)` | 직사각 프로파일. 폐곡선이 인접 형상과 붙어 못 쓸 때 |
| `expect(뷰, 폭, 높이, 이유)` | 그 폐곡선이 실재하는지 확인만. `rect` 값 교차검증용 |
IR 연산:
| op | 필드 |
|---|---|
| `box` | `min:[x,y,z]`, `max:[x,y,z]` |
| `extrude` | `profile`, `axis`, `from`, `to`, `cap` |
| `union` | `members:[id,…]` |
| `difference` | `base`, `cutters:[id,…]` |
| `loft` | `sections:[{profile, at},…]` — `at` 은 평면 법선축 위 위치 |
**loft 는 꺾인 자리를 못 만든다.** 두 단면 사이를 곧게 이을 뿐이라, 형상이
꺾이는 X 마다 단면을 놓고 구간을 끊어야 한다. 단면은 2개씩만 쓴다 — 3개
이상이면 스플라인 보간으로 부푼다. 두 단면은 **꼭짓점 수가 같아야** 하고,
링 정규화는 `addProfile` 이 자동으로 한다. 자세한 배경은
[`2d-3d-conversion.md`](2d-3d-conversion.md) 교훈 #16.
`keep: false` 는 boolean 에 소모될 중간 파트. 최종 산출물에만 `name`/`color` 를 준다.
---
## 4. 이 도면에서 실제로 만든 것
### 4.1 좌표계
```
X = 전력구 길이방향, 0 = 구조물 최좌단
Y = 폭방향, 0 = 전력구 중심선
Z = 표고, 0 = 도로 계획고 (DWG y = 483623.477, 자동 검출)
단위 = 내부 mm 정수, egm 넘길 때만 m
```
### 4.2 구간 구성 (A-A 종단면에서 읽음)
```
X -1,000 ~ 0 입구 스터브 B-B 구체 2,700×2,750
X 0 ~ 15,400 전력구 본체 C-C 구체 3,800×4,600 / 내부 3,000×3,800
X 1,900 ~ 3,200 사다리 샤프트 1 Z -3,157 ~ 0
X 12,600 ~ 13,900 사다리 샤프트 2 Z -3,157 ~ -39
X 15,400 ~ 18,200 맨홀 #2 Z -10,007 ~ +182, Y -1,900 ~ 8,300
X 18,200 ~ 19,200 출구 스터브 B-B
```
### 4.3 뷰 ↔ 부재 대응
| 뷰 | 평면 | 쓰인 곳 |
|---|---|---|
| 평 면 도 | XY | X 구간 확정, 샤프트 위치, 환기구 footprint |
| 단 면 A-A | XZ | **구간 배정표** — 어디서 어디까지 어느 단면인지 |
| 단 면 B-B / D-D | YZ | 스터브 단면 (2,700×2,750) |
| 단 면 C-C | YZ | 전력구 본체 단면 (3,800×4,600) |
| 단 면 E-E | YZ | 맨홀 전체 (챔버·격실·환기구 다리) |
| 단 면 F-F / G-G | XZ | 환기구 X 벽, 기초·잡석 |
| 단 면 H-H | XY | 환기 OPEN부 개구 (1.2×2.0) |
| 단 면 I-I | XY | 집수정 점검구 개구 (1.2×1.0) |
| 단 면 J-J | XY | 침사조·집수조 평면 (E-E 교차검증) |
### 4.4 최종 산출 파트
```
● 맨홀1_전력구_구체 스터브+본체 union 후 공동 차감
● 맨홀1_전력구_기초 4,000 × 100 fck=18MPa
● 맨홀1_전력구_잡석 5,400 × 500
● 맨홀1_전력구_출구스터브
● 맨홀1_사다리샤프트_1 / _2
● 맨홀1_맨홀2_구체 봉투 union 후 공동·개구부 차감
● 맨홀1_맨홀2_기초 / _잡석
● 맨홀1_환기구_기초 / _잡석
```
트리 이름은 `대분류_중분류_소분류` 형식이라 ObjectTreeViewer 에서 자동 그룹화된다.
---
## 5. 검증 절차
3D 를 세우기 **전에** 아래를 순서대로 통과시킨다. 한 단계라도 건너뛰면
나중에 어디가 원인인지 추적이 불가능해진다.
```
0. egm guide | wc -l 뷰어 모드가 아닌가 (39 면 뷰어 — 모델링 불가)
1. node src/run.mjs <도면> 지반선 Z 가 전 뷰에서 일치하는가
기준점이 도면 치수 2개 이상과 맞는가
뷰 폭·높이가 도면 치수와 맞는가
2. node src/gaps.mjs <도면> 프로파일이 안 닫히는 곳이 있는가
원인이 레이어 오분류인가 선 누락인가
3. node src/build.mjs <도면> --loops
폐곡선 치수가 도면 표기와 맞는가
node src/build.mjs <도면> --circles
원 지름이 도면 표기(Ø900·Ø100)와 맞는가
node src/report.mjs <도면> → out/<도면>.html
**자기교차 링이 있는가** (있으면 압출부터 쓰레기다)
링을 그림으로 확인. 표로는 못 보이는 것이 보인다
4. node src/build.mjs <도면> IR 검증 (참조 무결성, 소모 파트 keep 플래그)
5. python run_egm.py ... --dry
좌표가 예상대로 나오는가
6. python run_egm.py ... --export=out/<도면>.obj
실행 + OBJ 내보내기
7. egm status modelCount 가 최종 파트 수와 같은가
8. node src/measure.mjs <도면> --ir
의도(IR 격자 평가) vs 결과(모델러 OBJ) 대조
+ 기준선 (실패 시 exit 1)
```
7 이 어긋나면 boolean 이 인자를 흘린 것이다 (교훈 #14). 형상만 봐서는 못 잡는다 —
남은 커터 솔리드가 구체와 겹쳐 있어 겉보기엔 멀쩡하다.
### 꼭짓점 수를 먼저 본다
`--loops` 출력에서 **직사각형이어야 할 것이 4가 아니면 의심**한다. 링이 오염됐다는
신호다. 이 도면에서 실제로 나온 값:
```
꼭짓점 4 정상 직사각형
꼭짓점 10 헌치(150×150)가 있는 정상 형상
꼭짓점 8 개구 기호(X 표시)가 사각형 안을 가로질러 링이 자기교차
꼭짓점 114 케이블 지지대가 프로파일 레이어에 섞임
```
링이 오염됐으면 **bbox 는 여전히 정확하므로** `rect()` 로 재구성하고
`expect()` 로 치수만 확인한다.
### 부피를 대조한다 — `src/measure.mjs`
비틀린 loft 는 형상만 봐서는 못 잡는다. 부피가 어긋나는 것으로만 드러난다.
```sh
python src/run_egm.py out/manhole1-trans.egm.json --export=out/manhole1-trans.obj
node src/measure.mjs manhole1-trans # 기준선 대조 (실패 시 exit 1)
node src/measure.mjs manhole1-trans --save # 지금 값을 기준선으로 얼린다
node src/measure.mjs manhole1-trans --primitives # IR 원시 파트 부피표도
```
OBJ 삼각형을 발산 정리로 잰다. `~SetObjectName` 으로 붙인 이름이 OBJ 의 `g` 로
나오므로 파트 대조가 이름 하나로 끝난다. 나오는 것:
```
파트 부피 m³ 표면적 m² 삼각형 열린변 재질
맨홀1_전력구_구체 100.5403 507.471 10862 28 구체 콘크리트 fck=27MPa
맨홀1_사다리샤프트_1 3.3217 28.356 3127 13 …
합계 107.1425
```
**IR 에 없는 그룹이 나오면 boolean 이 인자를 흘린 것이다** — `modelCount` 대조와
같은 신호인데 어느 커터가 남았는지까지 알려준다.
`열린변`은 다양체 여부다. 이음매를 맞대어 붙이면 양쪽 삼각분할이 달라 T 정점이
남는다(교훈 #16). 부피·렌더링에는 문제가 없지만 IFC 로 갈 때 걸릴 수 있다.
#### 기준선은 `baselines/` 에 두고 **커밋한다**
`out/` 과 달리 재생성으로 복구되지 않는다. 사람이 한 번 확인한 부피가 남아야
다음 변경이 형상을 건드렸는지 알 수 있다.
#### 기대값은 자동으로 안 나온다 — 그리고 그게 맞다
`measure.mjs` 는 **boolean 을 합성하지 않는다.** 커터는 관통을 확실히 하려고
대상 밖으로 `OVER` 만큼 튀어나와 있고(교훈 #13), 샤프트 보어는 본체 공동과
겹친다. 그래서 `Σ멤버` 도 `base − Σ커터` 도 틀린 값이 된다. **틀린 기대값은
기대값이 없는 것보다 나쁘다.**
새 형상의 기대값은 `--primitives` 표를 재료로 사람이 한 번 조립하고, 맞으면
`--save` 로 얼린다. 두 단면 사이 면적은 t 의 2차식이라 심프슨
`L·(A0 + 4·A(½) + A1)/6` 이 **근사가 아니라 정확**하다.
```
duct.solid 5.1975 + 1.6540 + 0.9760 + 269.1920 = 277.0195
샤프트 1 원시합 5.3299 − 보어(솔리드 범위로 클립) 2.0084 = 3.3215 실측 3.3217
└ 원기둥이 다면체로 나와 미세하게 크다
```
결과의 **정점 X 값에 중간 행이 없는지**도 같이 본다. 없으면 직선 룰드다.
### `src/gaps.mjs` 읽는 법
```
뷰 채택선 경계 레이어 누락 고치면 되는 레이어
단 면 J-J 22 0 0 0 —
단 면 E-E 69 1 3 0 잡석, 선형
```
| 열 | 뜻 | 대응 |
|---|---|---|
| 경계 | 뷰 밖으로 이어져 잘린 것 | 결함 아님. 무시 |
| 레이어 | 선은 있는데 다른 레이어에 있음 | `layers.body` 에 추가 (설정 한 줄) |
| 누락 | 도면에 선이 없음 | 도면 수정 또는 스냅 오차 확인 |
이 도면은 끊김 50곳 중 29곳이 뷰 경계, 20곳이 레이어 오분류, **선이 실제로
없는 곳은 1곳**(그것도 1mm 스냅 오차)이었다.
---
## 6. 도구 목록
| 파일 | 역할 |
|---|---|
| `src/run.mjs` | 파싱 · 기준점 검출 · 뷰 분리 · 검증 |
| `src/build.mjs` | 프로파일 추출 · 조립 스펙(IR) 생성 |
| `src/emit/egm.mjs` | IR → egm 명령 목록 |
| `src/run_egm.py` | 명령 실행 (`--dry`, `--export=<obj>` 지원) |
| `src/measure.mjs` | OBJ 부피 실측 · 기준선 대조 · 재질별 물량 |
| `src/report.mjs` | 추출 결과 뷰어 (한 장짜리 HTML). 링 그림 · 자기교차 · 원 · IR |
| `src/capture.mjs` | 뷰별 **도면** 캡처 + 좌표 사이드카 (전 레이어, `--crop`·`--png`) |
| `src/render.mjs` | 만든 **3D** 를 정투영 렌더 (`--axis`·`--part`·`--clip`·`--png`) |
| `src/volume.mjs` | IR 을 격자로 평가 — 모델러 없이 "의도" 부피 |
| `src/fit.mjs` | 도면 → `params/<이름>.json` |
| `src/params.mjs` | 파라미터 스키마·식·검증 |
| `src/rules.mjs` | 설계 규칙 (위상은 막고, 설계는 경고) |
| `src/ui.mjs` | 파라미터 편집 + 갱신 UI (로컬 서버) |
| `src/core/views.mjs` | 뷰 → 프로젝트 좌표. `build.mjs` 와 `report.mjs` 가 **같이 쓴다** |
| `src/probe.mjs` | 뷰 하나의 폐곡선을 표로 (`--ring` 으로 좌표까지) |
| `src/gaps.mjs` | 프로파일 끊김 진단 + 원인 분류 |
| `src/inspect.mjs` | 뷰 영역의 모든 엔티티를 레이어 무관하게 |
| `src/materials.mjs` | 뷰별 재질 주기 수집 · 레이어 ↔ 재질 대응 확인 |
| `src/core/parse.mjs` | DWG → 엔티티, 선분 펴기, 표제 수집 |
| `src/core/datum.mjs` | 표고 기준 검출 · 좌표 변환 |
| `src/core/segment.mjs` | 뷰 분리 (교대 간격분할 + 제목 귀속) |
| `src/core/loops.mjs` | 선분 → 닫힌 프로파일 (평면 분할) |
| `src/ir.mjs` | 조립 스펙 스키마 · 빌더 · 검증 |
| `src/drawings/*.mjs` | **도면별 설정.** 새 도면은 여기에 파일 하나 |
| `src/drawings/manhole1-trans.mjs` | 전이부만 세우는 최소 모델. `manhole1` 설정을 상속하고 `assemble()` 만 교체 — 한 부분을 떼어 검증할 때의 본보기 |
---
## 7. 모델러 조작 시 주의
`Modeler_AI/docs/egm-usage.md` 중 이 작업에서 특히 물리기 쉬운 것.
- **`Extrude C=True` 단독은 항상 +Z 로 민다.** 커브 평면의 법선을 따르지 않는다.
`src/emit/egm.mjs` 는 항상 `D F {기준점} {방향점}` 으로 방향을 명시한다.
- **`C=True` 는 `D F` 앞에** 와야 한다. 뒤에 오면 무시되고 서피스가 나온다.
- **닫힌 `Polyline` 에 `Enter` 를 붙이지 말 것.** 자동 종료되므로 남은 신호가
다음 명령으로 새어나간다.
- **`SelByUids` 는 `Esc` 로 닫는다.** 안 닫으면 이후 명령이 전부 무시된다.
- 명령은 비동기다. `egm status` 의 `command` 가 `null` 이 될 때까지 기다린다.
`src/run_egm.py` 의 `wait()` 가 그 일을 한다.
- **`BooleanDifference`/`BooleanUnion` 은 두 개만 먹는다.** 플러그인이
`TargetModels[0]`, `[1]` 만 쓰므로 셋 이상 선택하면 나머지는 조용히 무시된다.
`src/emit/egm.mjs` 의 `fold()` 가 둘씩 접는다.
- **뷰어 모드면 모델링 명령이 등록조차 안 된다.** 0절의 `egm guide` 확인을
건너뛰지 말 것.
- **`ExportByPath` 의 경로는 반드시 큰따옴표로 감싼다.** `_` 가 egm 스크립트
특수문자라, 경로에 밑줄이 있으면 오류도 경고도 없이 **아무 파일도 안 생긴다**.
`result` 는 정상으로 보이고 `created:[]` 도 원래 그런 명령이라 신호가 없다.
내보내기 전에 `SelAll` 이 필요하고, `.obj` 만 나온다(`.stl` 은 파일이 안 생긴다).
`src/run_egm.py` 의 `export_obj()` 가 이걸 다 하고 파일 생성까지 확인한다.
### 씬 초기화가 실패할 때
`SelAll` → `Delete` 직후 상태가 남아 다음 명령이 씹히는 경우가 있다.
```sh
egm "Esc"; egm "!Cancel"; egm "SelAll"; egm "Delete"; egm "Esc"; egm "!Cancel"
egm status # command 가 null 인지 확인
```
---
## 8. 현재 미완
| 항목 | 상태 | 필요한 것 |
|---|---|---|
| 전이부 챔퍼 | `manhole1-trans` 에서 완성 (챔퍼·단차·종방향 헌치) | `manhole1` 본편에 이관 |
| 이음매 T 정점 | 열린 변 21개 (기하는 닫힘) | IFC 내보낼 때 재검토 |
| Φ100 관통 ×5 | 미구현 | 평면도에서 전선인입 2 · 우수관연결 2 · 유도배수관 1 위치 특정 |
| 나선계단 | 미구현 | 현 IR 연산으로 표현 불가. `Sweep1` 또는 전용 연산 추가 |
| 방호난간 L=7.2m | 미구현 | H-H 에 표기 |
| IFC 출력 | 미구현 | `emit/ifc.mjs`. 레이어 ↔ 재질 매핑은 이미 확보 |
| 브라우저 연동 | 미구현 | `src/core/*` 순수화 + localhost 브리지 |