# 로드맵 — 일반 도면 세트 → 3D → IFC 지금 파이프라인은 **평면도 + 단면도** 조합에서 검증됐다(맨홀 일반도 4매, 11파트). 2026-08-07 에 **두 번째 도면군**(교량 설계지침 삽도 7장)을 넣어 1장을 관통시켰다 (중앙분리대 방호벽, 4.8058 m³ — 모델러·해석 일치). 궁극 목표는 **평면 · 입면 · 단면 · 좌측면 · 우측면**을 읽어 IFC 구조로 내보내는 것. 이 문서는 거기까지 무엇이 남았는지, 어떤 순서로 하면 되는지를 적는다. > **두 번째 도면군이 바꾼 것.** 남은 일 목록이 「연산이 부족하다」에서 > 「**입력 해석 가정이 부족하다**」로 옮겨갔다. 교량에서 실제로 막은 것은 > sweep·revolve 가 아니라 단위·축척·뷰 제목·레이어·폐곡선이었다. > 자세한 것은 [`methodology.html`](methodology.html) 11장, 교훈은 > [`2d-3d-conversion.md`](2d-3d-conversion.md) #20~#26. > 그 위에 얹은 **파라메트릭 설계 층**은 [`parametric.md`](parametric.md) 에 따로 있다. > **구현됐다**(fit/build 분리 · 규칙 · 편집 UI). 본체 목표(2D→3D)를 대체하지 않는다. --- ## 1. 뷰 종류의 일반화 지금 `titles.views` 는 뷰 제목을 네 가지 `kind` 로 분류한다. ``` plan 평면도 DWG(x,y) → 프로젝트(X, Y) long 종단면 DWG(x,y) → 프로젝트(X, Z) cross 횡단면 DWG(x,y) → 프로젝트(Y, Z) horizontal 수평 단면 DWG(x,y) → 프로젝트(Y, X) (swapUV) ``` 정면도·배면도·좌측면도·우측면도를 넣으면 이렇게 된다. | 뷰 | 보는 방향 | 평면 | 비고 | |---|---|---|---| | 평면도 | −Z (위에서) | XY | | | 저면도 | +Z | XY | 좌우 반전 → `flip` | | 정면도 | +Y | XZ | | | 배면도 | −Y | XZ | 좌우 반전 → `flip` | | 좌측면도 | +X | YZ | | | 우측면도 | −X | YZ | 좌우 반전 → `flip` | | 종단면 | ±Y | XZ | 정면도와 같은 평면 | | 횡단면 | ±X | YZ | 측면도와 같은 평면 | | 수평 단면 | −Z | XY | 평면도와 같은 평면 | **결국 평면은 셋(XY · XZ · YZ)뿐이고, 나머지는 반전 여부의 문제다.** `kind` 라는 이름이 오해를 부른다 — 앞으로는 `plane` + `flip` 으로 부르는 게 맞다. ### 할 일 - `titles.views` 항목을 `{ plane, u0, v0, flip, flipV, swapUV }` 한 덩어리로 합친다. 지금은 `titles.views` 와 `anchors` 로 나뉘어 있어 뷰 하나가 두 곳에 흩어져 있다. - 뷰가 어느 방향에서 본 것인지 **도면에서 검출**할 방법을 찾는다. 단면은 절단 기호(`SYM`)로 알 수 있지만, 입면도는 제목 말고 단서가 없다. → 제목 규칙(`정면도`/`좌측면도` …)을 설정에 두는 게 현실적이다. --- ## 2. 입면도가 단면도와 다른 점 단면도는 **잘린 면**만 그리지만 입면도는 **보이는 모든 것**을 그린다. 그래서 입면도의 폐곡선은 구조체 경계가 아니라 **투영 윤곽**이다. | | 단면도 | 입면도 | |---|---|---| | 폐곡선의 의미 | 재료 영역 | 보이는 면의 윤곽 (여러 깊이가 겹침) | | 압출 가능? | 그대로 가능 | **불가.** 깊이 정보가 없다 | | 쓸모 | 형상 생성 | 형상 **검증**, 높이·폭 확인, 개구부 위치 | **입면도로 솔리드를 만들려 하지 말 것.** 입면도는 - 다른 뷰에서 만든 형상의 **외곽 검증** - 창·문·개구부의 **XZ(또는 YZ) 위치** 확보 - 마감선·레벨선 확인 에 쓰고, 실제 압출은 평면도(footprint) + 단면도(높이)로 한다. ### 할 일 - 입면도용 검증 도구: 만든 3D 를 그 방향으로 투영해 입면도 윤곽과 대조 - 개구부 위치만 뽑아 쓰는 경로 (지금 `rect()` + `expect()` 로 하는 것과 같은 방식) --- ## 3. 곡면 · 원호 지금은 **직선 선분만** 다룬다(`src/core/parse.mjs` 의 `toSegments`). ARC · CIRCLE · ELLIPSE 는 버린다. | 대상 | 필요한 것 | |---|---| | 모따기·필렛 | 이미 직선으로 그려져 있으면 그대로 됨 (지금 잘 되고 있다) | | **원형 관통 (Ø900·Ø100)** | **됨** — `collectCircles` → `circles()` → `cylinder` | | 원호 벽체 | ARC 를 선분으로 근사(tessellate)하거나 IR 에 호 지원 추가 | | 나선계단 | 전용 연산. `Sweep1` 또는 파라메트릭 생성 | ### CIRCLE — 된다 (2026-08-06) ```sh node src/build.mjs <도면> --circles # 뷰별 원 목록 (지름 · 레이어 · 좌표) ``` ```js circles('평 면 도', 900, /^-전력구$/) // 지름으로 집는다. 폐곡선과 같은 규칙 circle('평 면 도', 100) // 정확히 하나일 때. 여럿이면 예외 ``` **선분과 섞지 않는다.** 원을 다각형으로 근사해 평면 분할에 넣으면 폐곡선이 그 윤곽을 따라 돌아 프로파일이 오염된다. 원은 그 자체가 형상이라 `cylinder` 로 바로 간다. 뷰 귀속은 중심 좌표로 한다 — 뷰 분리(조각 클러스터링)에 원을 넣으면 관통 구멍 하나가 뷰를 잇는 다리가 될 수 있다. 이 도면은 133개 중 129개가 귀속됐다. **부수 효과: 평면도에 `v0` 앵커가 필요해졌다.** 평면도는 세로축이 표고가 아니라 폭방향(Y)인데, 지금까지 치수(폭×높이)만 봐서 드러나지 않았다. 좌표를 직접 쓰는 순간 필요해진다. 수평 단면(H/I/J)과 같은 문제였다. 남은 것: **Ø100 관통 5개**(`환기구` 4 · `배수관` 1). 평면도에 있는 1개는 X=17,690 으로 맨홀 #2 영역이라 그 형상을 세운 뒤에 뚫는다. --- ## 4. IFC 출력 ### 이미 확보된 것 레이어가 재질을 인코딩한다는 사실(노하우 문서 교훈 #6). 그래서 클래스·재질 배정에 필요한 정보가 이미 IR 에 실려 있다. ```js part({ …, name: '맨홀1_전력구_구체', material: '구체 콘크리트 fck=27MPa' }) ``` ### 매핑 초안 | 부재 | IFC 클래스 | 재질 | |---|---|---| | 벽체 | `IfcWall` | Concrete C27 | | 슬래브 | `IfcSlab` | Concrete C27 | | 기초 | `IfcFooting` | Concrete C18 | | 잡석·토목섬유 | civil 계열 (**IFC4.3 엔티티명 확인 필요**) | — | | 집합 요소 | `IfcBuiltElement` 또는 civil 계열 | — | - 재질 속성 → `IfcMaterial` + `Pset_MaterialConcrete`(CompressiveStrength) - 층상 구성(잡석 → 기초 → 구체) → `IfcMaterialLayerSet` - 설계 정보(fck · fy · 설계법 · 하중) → `IfcProject` 속성 > `IfcWall`/`IfcSlab`/`IfcFooting`/`IfcMaterial*`/`Pset_MaterialConcrete` 는 IFC4 > 표준이라 그대로 써도 된다. **잡석·토목섬유용 IFC4.3 civil 엔티티 이름은 > 스키마를 직접 확인하고 확정할 것.** 위 표의 후보는 검증 전이다. ### 할 일 - `part` 에 `ifcClass` 필드 추가 (지금은 `material` 만 있다) - `src/emit/ifc.mjs` — IR → IFC4 STEP(.ifc) - 형상 표현: `IfcExtrudedAreaSolid` 가 지금 IR 의 `extrude` 와 1:1 로 맞는다. `difference`/`union` 은 `IfcBooleanResult` 로 그대로 옮겨진다. **IR 을 IFC 를 염두에 두고 설계해둔 게 여기서 값을 한다.** --- ## 5. 우선순위 제안 2026-08-06 에 순서를 정했다 — **본체(A)를 좁혀서 먼저, 그 다음 파라메트릭(B).** 근거는 [`parametric.md`](parametric.md) 11절. ``` ✔ 0. 부피 측정 자동화 (measure.mjs) 손계산 → 회귀 검사. B 의 quantity 몸통 ✔ 1. CIRCLE → circles() 헬퍼 손으로 적은 R450 이 도면 대조로 바뀌었다 2. manhole1-trans 를 본편으로 통합 지금 반쪽 모델이 둘이다. 기초·잡석 이관 3. 맨홀 #2 + 출구 스터브 제일 크다. 파라미터 표본을 대표성 있게 만든다 ✔ 4. 파라메트릭 (parametric.md 10·12절) fit/build 분리 · 규칙 · 편집 UI 까지 완료 5. 뷰 설정 통합 (titles.views + anchors + viewBoxes → 한 덩어리) ← 아래 참고 6. emit/ifc.mjs IR 이 이미 준비돼 있다 7. 입면도 검증 경로 새 도면 종류를 받으려면 필요 8. Sweep 백엔드 (선형 곡선부·구배) Loft 는 완료 ``` **5 의 값이 올라갔다.** 뷰 하나를 서술하는 자리가 이제 **셋**이다 — `titles.views`(평면) · `anchors`(앵커·축척) · `viewBoxes`(영역·평면·축척). `unitMm` 이 `viewBoxes` 와 `anchors` 양쪽에 있고, 뷰 분리 방식에 따라 어느 쪽이 읽히는지가 갈린다. 뷰 종류를 늘리기 전에 정리하는 편이 싸다 — `{ plane, box?, u0, v0, flip, flipV, swapUV, unitMm, role }` 한 덩어리로. **교량군에서 새로 생긴 것 (전부 구현됨, 정리 필요):** ``` cfg.unitMm DWG 모델단위. 도면군마다 다르다 cfg.entityFilter 레이어 말고 색·선종으로 거른다 cfg.viewBoxes 제목 없는/중복인 도면의 뷰 영역 cfg.datum.mode='none' 공통 기준선이 없는 도면 viewBoxes[뷰].unitMm 도형별 축척 rules[].needs 규칙 적용 범위 (부재 타입 태그의 임시 대용) ``` **아직 없는 것 (교량 일반도를 받으면 바로 필요해진다):** ``` G1.5 도면 적격 판정 도구 숫자/기호 치수 계수 + 윤곽 닫힘률. 지금은 손으로 센다 축척 역산 도구 표기 치수 2개 ↔ 기하 길이 대조. 지금은 손으로 잰다 부재 타입 태그 rules 를 파라미터 이름이 아니라 부재 종류에 붙인다 받침 기반 로컬 프레임 상부(선형) ↔ 하부(연직) 좌표계 전환 (methodology 11.1) ``` 1 을 3 보다 앞에 두는 이유는 로드맵상 흔해서만이 아니다. **파라메트릭의 전제이기도 하다** — 지금 샤프트 보어 R450 은 원본 엔티티에서 눈으로 읽어 손으로 적은 값이라 도면과의 끈이 아예 없다. CIRCLE 지원이 들어와야 `loop()`/`expect()` 로 대조되고, 그제서야 보어 반지름이 정당한 파라미터가 된다. 1·2·3 은 서로 독립이라 순서를 바꿔도 된다. **Loft 백엔드는 끝났다** — 전이부(챔퍼·수직 단차·종방향 헌치)를 세워 부피 오차 0.0002% 로 검증했다. 배경은 [`2d-3d-conversion.md`](2d-3d-conversion.md) 교훈 #16. 남은 것은 **rail 을 따라가는 스윕**이다 (`Sweep1 C=True V=True` 가 IFC4.3 `IfcSectionedSolidHorizontal` 과 의미가 같다). 그러려면 IR 이 직선축 `from/to` 대신 **측점(station)** 을 다뤄야 한다. 4 는 실제 입면도가 있는 도면을 하나 받아야 설계가 확정된다. --- ## 6. 미완 목록 (맨홀 도면 기준) | 항목 | 상태 | |---|---| | 스터브↔본체 전이부 | **완료.** `assembleDuct()` 를 본편·trans 가 공유한다 | | 관로↔맨홀 관통구 | **완료.** 개구 기호에서 좌표가 나온다. 합치기 **전에** 뚫는다 | | 구조물만 (기초·잡석 제외) | **완료.** 5파트 | | **맨홀 #2 부피 9.32 손실** | IR 은 맞고(74.68) 모델러가 흘린다(65.36). 원인은 면으로만 맞닿은 union — 교훈 #19 | | 외부 헌치 5개 | **완료.** 아래 참조 | | 내부 헌치 (100×100·150×150) | 미구현. 맨홀을 **수평 단면에서 Z 로 쌓아야** 따라온다 | | H/I/J 내부 링 끊김 | H-H 내부가 Y 1,800~3,700 만 닫힌다. 위 항목의 전제 | | Ø100 관통 5개 | 위치 확인됨 (전선인입 ×2 · 우수관연결 ×2 · 유도배수관 ×1). 미구현 | | 침사구 (0.8×0.8) | I-I 개구 기호로 **잡힌다**. `opening('단 면 I-I', 800, 800)` | | 나선계단 · 방호난간 | 현 IR 연산으로 표현 불가 | | 기초 · 잡석 · 보도 | 뺐다. 되살리는 데 필요한 값은 `manhole1.mjs` 주석에 있다 | ### 외부 헌치 — 완료 (2026-08-06) 평면도 대각선 21개 중 **외부 표면 모서리**인 5개를 세 종류로 나눠 넣었다. Z 구간은 도면에서 뽑았지 지어내지 않았다. | 종류 | 도면 근거 | 구현 | 부피 변화 | |---|---|---|---| | 출구 스터브 챔퍼 | A-A `(18200,-4607)→(18500,-4907)` 300×300 · `(18200,-8007)→(18550,-7657)` **350×350** | loft 2단 | +0.577 | | 전력구↔맨홀 접합 | 평면 `(15100,1900)→(15400,2200)` 300×300 | XY 삼각형 → Z 압출 → union | +0.224 | | 챔버↔환기구 다리 | 평면 `(15400,4100)→(15500,4200)` 100×100 ×2 | XY 사각 쐐기 → Z 압출 → 커터 | −0.029 | **3D 검증 완료** — `modelCount 5`, 의도 vs 결과가 셋 다 맞는다. ``` 파트 실측 IR 의도 차이 맨홀1_전력구_구체 100.7473 100.7592 −0.0119 ✓ 접합 헌치 들어감 맨홀1_전력구_출구스터브 3.5474 3.5490 −0.0016 ✓ 출구 챔퍼 2단 들어감 맨홀1_사다리샤프트_1·2 3.3217/3.2805 ✓ 건드리지 않은 것 확인 맨홀1_맨홀2_구체 66.0875 74.6527 −8.5652 ✗ 기존 손실 (수평단면 재구성 대기) ``` **출구는 입구와 대칭이 아니다.** 위·옆은 300×300(X 18,200~18,500)인데 아래는 350×350(18,200~**18,550**)이라 꺾이는 X 가 다르다. loft 하나로 이으면 아래 챔퍼가 300 짜리로 짧아진다 — 입구에서 이미 겪은 교훈 #16 이 그대로 반복됐다. 검산: 격자 +0.5765 vs 심프슨 해석 +0.5749 — **0.3% 일치**. **평면 방향 헌치는 XZ 단면에 안 나타난다.** 다리 100×100 의 Z 범위를 F-F·G-G 에서 찾으려 했지만 없다(거기 있는 150×150 은 다리 *내부* 헌치다). 그래서 `fit()` 이 E-E 외곽 링에서 `Y>4,200` 구간의 Z 범위를 **코드로** 뽑는다 → −5,257 ~ 176. 다리 테이퍼의 부피 변화가 작은(−0.029) 데는 이유가 있다. E-E 링을 보면 하부 블록이 **Y=4,100 에서 끝나고**(`[4100,-10007]→[4100,-5557]`) 그 위로 다리가 선다. 그래서 테이퍼가 걸리는 Y 4,100~4,200 구간에는 재료가 100mm 높이밖에 없다. ### 맨홀 #2 를 수평 단면 기반으로 다시 세우면 셋이 한꺼번에 풀린다 ``` 부피 9.32 손실 조각이 Z 로 진짜 겹쳐 union 이 한 덩어리가 된다 내부 헌치 H/I/J 의 XY 프로파일에 모따기가 이미 들어 있다 층별 정확도 E-E 치수 사슬이 각 층의 Z 범위를 그대로 준다 ``` 전제는 H/I/J 의 내부 링 끊김 수리다. **조사는 끝났다.** `gaps.mjs` 는 H-H·I-I 에 끊김 **0** 이라고 한다 — 선이 없는 게 아니라 개구부 윤곽이 안 닫혀 있어 면이 새는 것이다(교훈 #12 와 같은 종류). 선분을 직접 뽑아 실태를 확정했다. ``` H-H 내부는 격벽으로 3칸이다 (전부 X 15,800~17,800, 모서리 150×150 모따기) A Y -1,500 ~ 1,500 3,000 ← 환기 OPEN부가 들어 있어 안 닫힘 B Y 1,800 ~ 3,700 1,900 ← 유일하게 폐곡선으로 잡힌다 (#2) C Y 3,700 ~ 8,000 4,300 ← 점검구·방호난간이 들어 있어 안 닫힘 격벽 Y 1,500~1,800 (300) ``` A·C 가 안 닫히는 이유는 개구부 좌측 변이 `000-구조물실선` 에 **Y -1,000~1,000 로만** 그려져 있기 때문이다(개구는 Y -500~1,500). 대응은 교훈 #12 대로 `rect()`/`opening()` 으로 재구성 — 위 좌표가 그 재료다. --- ## 7. 검증 기준선 `baselines/<도면>.volumes.json` 은 **커밋한다.** `out/` 과 달리 재생성으로 복구되지 않기 때문이다 — 사람이 한 번 확인한 부피가 남아 있어야 다음 변경이 형상을 건드렸는지 알 수 있다. | 도면 | 파트 | 부피 m³ | 확인 | |---|---|---|---| | `manhole1-trans` | 3 | 107.1425 | 원시 파트 합으로 교차검증 | | `manhole1` | 11 | — | 기준선 없음. 통합 후 뜬다 |