- CLAUDE.md: 프로젝트 전체 가이드 및 워크플로우 정의 - CONSIDERATIONS.md: 8개 영역 고려사항 분석 (기술, 품질, 배포, UX) - PROGRESS.md: 진행 상황 추적 및 체크리스트 - PLAN.md: 단계 5~9 상세 계획 및 에이전트 역할 정의 - 메모리: 프로젝트 정보 저장 다음: 단계 5 (에이전트/커맨드/스킬/훅 아이디어 제안)
23 KiB
2D → 3D 제작 과정
DWG 도면 한 벌을 3D 솔리드로 세우기까지 실제로 무엇을 어떤 순서로 했는지를 기록한다. 다른 프로젝트로 옮길 때 이 문서만 따라가면 재현된다.
- 판단의 근거·함정·교훈은
2d-3d-conversion.md에 따로 있다. 이 문서는 절차, 그 문서는 왜 그렇게 하는가. - 코드:
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 다.
연결 확인:
egm status # {"command":null,"modelCount":0,"selectedCount":0}
EGModeler가 실행 중이지 않거나 응답하지 않습니다. 가 나오면 앱을 먼저 띄운다.
status 가 응답한다고 모델링이 되는 건 아니다. 뷰어 모드면 모델링 명령이
등록조차 안 된 채로 파이프만 살아 있다. 등록된 명령 개수로 가른다.
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 교훈 #15.
1. 전체 흐름
DWG ─parse─→ 엔티티 ─segment─→ 뷰 ─datum─→ 프로젝트좌표 ─loops─→ 프로파일
│
조립 스펙 (IR, JSON)
↙ ↓ ↘
egm IFC three.js
명령 4개로 끝난다.
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
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
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 부모 -
폭×높이가 도면 표기 치수와 일치하는지 먼저 눈으로 확인한다. 여기가 맞으면 그 뒤는 거의 틀리지 않는다.
한 뷰만 자세히 보려면:
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
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 레이어 알아내기
node src/run.mjs <도면> --report # 일단 실패해도 좋다
node src/inspect.mjs <도면> "<뷰>" # 그 영역의 레이어 실태
src/inspect.mjs 는 레이어 필터를 무시하고 전부 보여준다. 여기서
구조체 외곽선이 어느 레이어에 있는지 눈으로 고른다.
3.3 레이어를 세 갈래로 나눈다
layers: {
struct: /…/, // 뷰 분리용. 넉넉하게. 가구·주기까지 들어가도 된다
body: /…/, // 구체 프로파일용. 좁게. 케이블랙 같은 가구는 빼야 한다
material: /…/, // 기초·잡석·보도 등 별개 재료. 프로파일 추출엔 함께 넣는다
ground: /…/, // 계획고·GL 선. 표고 기준 검출용
}
body ⊆ struct 는 코드가 강제한다(src/run.mjs). 어겨도 조용히 틀리지 않는다.
3.4 표고 기준 잡기
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 를 보고 도면 치수와 맞춰가며 찾는다.
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 조립 스펙 쓰기
파이프라인에서 사람 판단이 들어가는 유일한 자리다.
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 교훈 #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 는 형상만 봐서는 못 잡는다. 부피가 어긋나는 것으로만 드러난다.
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 직후 상태가 남아 다음 명령이 씹히는 경우가 있다.
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 브리지 |