Files
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

23 KiB
Raw Permalink Blame History

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 브리지