step3-4: 프로젝트 기본 구조 & 상세 계획 수립

- CLAUDE.md: 프로젝트 전체 가이드 및 워크플로우 정의
- CONSIDERATIONS.md: 8개 영역 고려사항 분석 (기술, 품질, 배포, UX)
- PROGRESS.md: 진행 상황 추적 및 체크리스트
- PLAN.md: 단계 5~9 상세 계획 및 에이전트 역할 정의
- 메모리: 프로젝트 정보 저장

다음: 단계 5 (에이전트/커맨드/스킬/훅 아이디어 제안)
This commit is contained in:
Claude Haiku committed 2026-10-16 15:06:18 +09:00
commit 363c2a2a25
16 files changed
+9018

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(git init *)",
"Bash(git config *)",
"Bash(git add *)",
"Bash(git commit -m 'step3-4: 프로젝트 기본 구조 & 상세 계획 수립 *)"
]
}
}
+289
View File
@@ -0,0 +1,289 @@
# CLAUDE.md — DXF to 3D Web App 프로젝트 가이드
> **이것은 AI 에이전트를 위한 프로젝트 지침서입니다.**
> 이 파일을 먼저 읽고 작업을 시작하세요.
---
## 프로젝트 개요
### 목표
2D DXF 도면(가시설 일반도)을 분석하여 3D 모델로 변환하고, 웹상에서 실시간으로 검토할 수 있는 웹앱을 만든다.
### 최종 산출물
```
사용자 → 웹사이트 (무료 도메인)
↓
DXF 업로드 & 자동 변환
↓
3D 모델 실시간 시각화
↓
부재 정보 조회, 측정, 다운로드
```
### 핵심 요구사항
- ✅ **Git 관리**: 모든 작업 과정 기록
- ✅ **무료 배포**: Vercel + 커스텀 도메인
- ✅ **품질**: Haiku 4.5 모델임에도 우수한 결과물
- ✅ **단계별 검증**: 각 단계 전에 작업 리마인드 + 진행 확인
---
## 프로젝트 구조
```
D:\Obsidian Vault\999ETC\261016_HW_DwfTo3D\
│
├── docs/ # 기초 자료 (읽기 전용)
│ ├── roadmap.md # 진행 상황 & 우선순위
│ ├── 2d-3d-conversion.md # 19개 교훈 (매우 중요!)
│ ├── 2d-3d-process.md # 절차 문서
│ ├── methodology.html # 방법론 상세
│ └── dwg2model-CLAUDE.md # 기존 프로젝트 가이드
│
├── RAG/ # 대상 DXF 파일
│ ├── 가시설_평면.dxf # 평면도
│ ├── 가시설_단면.dxf # 단면도
│ └── 가시설_상세1~4.dxf # 상세도
│
├── src/ # 소스 코드 (구현 예정)
│ ├── drawings/
│ │ ├── index.mjs # 모든 도면의 중앙 진입점
│ │ └── gasiseol.mjs # 가시설 도면 설정 & 조립 로직
│ │
│ ├── core/ # 도면 고유값 없음 (절대 건드리지 말 것)
│ │ ├── parse.mjs # DXF 파싱
│ │ ├── views.mjs # 뷰 분리
│ │ ├── loops.mjs # 폐곡선 추출
│ │ └── datum.mjs # 기준점 검출
│ │
│ ├── build/ # IR 생성 (도면 독립)
│ │ ├── assemble.mjs # 부재 조립
│ │ └── validate.mjs # 검증
│ │
│ ├── emit/ # 3D 출력 (도면 독립)
│ │ ├── egm.mjs # EGModeler 명령 생성
│ │ └── ifc.mjs # IFC 출력
│ │
│ ├── web/ # React 웹앱 (새로)
│ │ ├── components/
│ │ │ ├── Viewer3D.tsx # Three.js 뷰어
│ │ │ ├── FileUpload.tsx # DXF 업로드
│ │ │ ├── PropertyPanel.tsx # 부재 정보
│ │ │ └── Controls.tsx # 조작 UI
│ │ ├── pages/
│ │ │ ├── index.tsx # 메인 페이지
│ │ │ └── api/convert.ts # 변환 API
│ │ ├── styles/
│ │ ├── App.tsx
│ │ └── main.tsx
│ │
│ ├── run.mjs # 도면 검증 (기존)
│ ├── build.mjs # IR 생성 (기존)
│ ├── measure.mjs # 부피 측정 (기존)
│ └── paths.mjs # 경로 관리 (한 곳)
│
├── .claude/ # 에이전트 설정 (단계 6에서)
│ ├── agents/
│ │ ├── research.md
│ │ ├── pipeline.md
│ │ ├── webui.md
│ │ └── deploy.md
│ ├── commands/
│ ├── skills/
│ └── hooks/
│
├── PROGRESS.md # 진행 상황 추적 (매 세션 업데이트)
├── PLAN.md # 상세 계획 및 에이전트 역할 분담
├── CONSIDERATIONS.md # 고려사항 (단계 2 결과)
├── CLAUDE.md # 이 파일
│
└── .gitignore # git 설정
node_modules/
dist/
*.obj
*.log
```
---
## 개발 워크플로우
### 0️⃣ 세션 시작 전
```
1. PROGRESS.md 읽기 ← 진행 상황 파악
2. PLAN.md 읽기 ← 다음 작업 확인
3. 메모리 로드 ← 이전 세션 컨텍스트
```
### 1️⃣ 각 단계 시작 시
```
리마인드 (작업 내용 명시화)
├─ 이 단계에서 할 것
├─ 예상 결과물
└─ 성공 기준
진행 확인 (사용자 승인)
└─ "다음으로 진행해도 될까요?" → 대기
작업 수행 (git 커밋 포함)
└─ 의미 있는 커밋 메시지
검증 (문제 없는지 확인)
└─ 자동 테스트 또는 수동 검사
완료 리포트 (PROGRESS.md 업데이트)
└─ 결과물 요약 & 다음 단계 예고
```
### 2️⃣ Git 커밋 원칙
```
좋은 예시:
step2: CONSIDERATIONS.md 작성 - 고려사항 8개 영역 정리
step3: CLAUDE.md 작성 - 프로젝트 구조 & 워크플로우 정의
나쁜 예시:
❌ update (너무 모호)
❌ fix bugs (어떤 버그?)
❌ step1-draft (완료 전 중간 저장)
```
---
## 핵심 원칙 (반드시 지킬 것)
### 🚫 절대 어기면 안 되는 경계
```
src/core/* · src/build/* → 도면 고유값 금지 (= 설정파일로 분리)
src/drawings/*.mjs → 레이어명, 좌표, 단위 모두 여기
src/web/* → 웹앱만 담당 (도면 파싱 X)
src/emit/* → 3D 도구 사정만 (도면값 X)
```
**위반하면**: 두 번째 도면(다른 가시설)을 할 때 파이프라인이 아니라 복사본이 생긴다.
### 📊 검증 체크리스트 (필수)
#### 도면 검증
- [ ] 기준점이 도면 치수 2개 이상과 일치
- [ ] 뷰 폭·높이가 도면 표기와 ±2% 이내
- [ ] 폐곡선 수가 예상과 일치
- [ ] 자기교차 링 없음 (`--loops` 확인)
#### 3D 모델 검증
- [ ] 부피 오차 < 0.5% (IR vs 실제)
- [ ] manifold 체크 (닫힌 면)
- [ ] 렌더링 성공 (Three.js)
- [ ] 부재 정보 표시 가능
#### 배포 검증
- [ ] 웹앱 로딩 < 3초
- [ ] 3D 렌더링 60fps
- [ ] 모바일 반응형 확인
- [ ] 도메인 접속 가능
---
## 에이전트 역할 (단계 5에서 상세 정의)
| 에이전트 | 담당 영역 | 도구 |
|---------|---------|------|
| **Research** | 가시설 도면 분석, 설정 파일 작성 | DXF 파싱, 기하학 분석 |
| **Pipeline** | dwg2model 통합, 테스트 자동화 | 빌드, 검증, 측정 |
| **WebUI** | React 컴포넌트, Three.js 통합 | 개발 서버, 빌드 |
| **Deploy** | CI/CD, Vercel 배포, 도메인 설정 | GitHub Actions, npm |
---
## 빠른 참조
### 자주 실행하는 명령
```bash
# 도면 검증 (Node.js 필요)
node src/run.mjs 가시설
# 3D 생성 (Python & egm CLI 필요)
python src/run_egm.py 가시설 --export=out/gasiseol.obj
# 부피 측정
node src/measure.mjs 가시설 --ir
# 웹앱 개발 서버 (나중)
cd src/web && npm run dev
# 배포 (CI/CD 자동)
git push
```
### 주요 문서 (필독)
| 문서 | 언제 읽을까 |
|------|-----------|
| [`docs/2d-3d-conversion.md`](docs/2d-3d-conversion.md) | **새 도면 추가 전 필수** (19개 함정) |
| [`docs/2d-3d-process.md`](docs/2d-3d-process.md) | 절차 확인 시 |
| [`docs/roadmap.md`](docs/roadmap.md) | 우선순위 확인 시 |
| [`CONSIDERATIONS.md`](CONSIDERATIONS.md) | 고려사항 확인 시 |
| [`PLAN.md`](PLAN.md) | 에이전트 분담 시 |
| [`PROGRESS.md`](PROGRESS.md) | 진행 상황 업데이트 시 |
---
## 품질 기준
### 코드 스타일
```
- 주석은 "왜"를 적는다. "무엇"은 코드가 말한다.
- 조용히 틀리느니 예외를 던진다
- 좌표·치수는 mm 정수로 (egm에 넘기기 전에만 m로 변환)
- 가정하지 마라. 검증하고 단정해라
```
### 테스트
```
- 도면 검증: 자동화 (--loops, --ir, measure.mjs)
- 웹앱: 단위 테스트 + E2E 테스트
- 배포: 리뷰 URL에서 수동 테스트 후 병합
```
### 문서
```
- 코드와 문서를 따로 유지 (업데이트가 다르다)
- 변경사항은 PROGRESS.md에 즉시 기록
- 함정/해결책은 이 파일에 추가
```
---
## 트러블슈팅
### "도면 로드가 실패했어요"
→ `docs/2d-3d-conversion.md` 교훈 #1~#6 확인
### "폐곡선이 4개가 아니에요"
→ `docs/2d-3d-conversion.md` 교훈 #12, #18 (자기교차 검사)
### "부피가 IR과 맞지 않아요"
→ `src/measure.mjs --ir` 로 의도(IR) vs 결과(모델러) 분리
### "웹앱이 느려요"
→ 모델 정점 수 확인, LOD 적용, 번들 최적화
### "배포 실패"
→ GitHub Actions 로그 확인, 환경 변수 설정 검사
---
## 변경 이력
| 버전 | 날짜 | 내용 |
|------|------|------|
| 1.0 | 2026-10-16 | 초안 작성 (단계 3) |
---
## 라이선스
이 프로젝트는 [`docs/dwg2model-CLAUDE.md`](docs/dwg2model-CLAUDE.md)를 상속받는다.
특히 `vendor/acadrust-dwg/` 의 **MPL-2.0** 라이선스를 준수해야 한다.
+242
View File
@@ -0,0 +1,242 @@
# DXF to 3D 웹앱 구현 고려사항
## 1. 가시설 도면의 특수성
### 1.1 구조적 특징
- **모듈화**: 표준 부재 조합 → 조합 가능 옵션 제공
- **임시성**: 설치/해체 순서 명시 필요
- **안전성**: 구조 안정성 검증 필수 (처짐, 강도)
- **확장성**: 설계 변경 쉬워야 함 (파라메트릭)
### 1.2 도면 특성
- 평면도, 단면도, 상세도 조합
- 치수 표기가 중심 (기하보다 수치 신뢰)
- 레이어로 재질/용도 구분
- 절단 기호로 단면 위치 명시
### 1.3 이전 도면(맨홀)과의 차이
| 항목 | 맨홀 | 가시설 |
|------|------|--------|
| 목적 | 지하 구조물 | 임시 가설물 |
| 부재 | 공사별 맞춤 | 표준 규격 + 조합 |
| 검증 | 부피, 구조 | 구조, 안정성, 조립도 |
| 변경 | 드물음 | 빈번 (설계 변경) |
---
## 2. 기술 구현 전략
### 2.1 DXF 파싱 및 변환
**결정**: 기존 dwg2model 파이프라인 재사용
- ✅ 검증된 파싱 로직
- ✅ 기준점 자동 검출
- ⚠️ 가시설 설정 파일 신규 작성 필요
**관리 항목**:
```
- 도면 좌표계 일치성 검증 (X, Y, Z 기준점)
- 레이어 필터링 (구조선 vs 치수선)
- 단위 설정 (mm vs m 자동 변환)
```
### 2.2 3D 모델 생성
**선택지**:
1. EGModeler (egm CLI) - 기존 검증됨
2. Open3D/PyMesh - 오픈소스, 더 가볍지만 테스트 필요
3. Rhino.Compute - 클라우드 기반, 비용 발생
**추천**: EGModeler 지속 사용
- 가장 안정적 (19개 교훈)
- 부피/기하학 검증 자동화 완성
- 이전 도면 사례와 비교 검증 가능
### 2.3 웹 시각화
**요구사항**:
- 실시간 3D 렌더링 (60fps 목표)
- 부재 정보 표시 (클릭 시)
- 단면/뷰 전환
- 측정 기능 (거리, 면적)
**기술 선택**:
| 라이브러리 | 크기 | 성능 | 학습곡선 | 선택 |
|-----------|------|------|---------|------|
| Three.js | 200KB | 매우 좋음 | 중간 | ✅ |
| Babylon.js | 300KB | 좋음 | 높음 | △ |
| Cesium.js | 1.2MB | 보통 | 높음 | ✗ |
**선택 이유**: Three.js
- 가벼운 로드
- 풍부한 예제
- React 통합 용이 (drei)
### 2.4 웹앱 프레임워크
```
React (상태 관리, UI 컴포넌트)
↓
Vite (빠른 개발 & 빌드)
↓
Three.js + react-three-fiber (3D 렌더링)
↓
TypeScript (타입 안정성)
```
---
## 3. 품질 보증 전략
### 3.1 도면 검증 (단계별)
```
Level 1: 기본 파싱
├─ 도면 로딩 성공
├─ 엔티티 수 > 0
└─ 기준점 감지 가능
Level 2: 기하학 검증
├─ 뷰 폭/높이 vs 도면 치수 비교 (±2%)
├─ 폐곡선 수 확인
├─ 자기교차 감지
└─ 좌표 범위 타당성
Level 3: 3D 모델 검증
├─ 부피 오차 < 0.5%
├─ 폐면 여부 (manifold)
├─ 면 법선 방향
└─ 경계 무결성
Level 4: 시각적 검증
├─ 웹 렌더링 성공
├─ 부재 식별 가능
└─ 기본 상호작용 작동
```
### 3.2 자동화 테스트
```
도면 → 설정 파일 → IR 생성 → 부피 계산 → 기준값 비교
↓
✅ 0.5% 이내 → PASS
❌ 초과 → FAIL (알림)
```
### 3.3 회귀 검증
- 이전 도면(맨홀) 재처리 → 부피 일치 확인
- 새 도면(가시설) 첫 처리 → 기준값 수립
- 매 수정 후 부피 추적
---
## 4. 배포 전략
### 4.1 호스팅 선택
```
Vercel (추천)
├─ Next.js/React 최적화
├─ 무료 SSL
├─ 자동 배포 (git 푸시)
├─ 환경 변수 관리
└─ 서버리스 함수 지원
Netlify (대안)
├─ 마찬가지로 무료
├─ Netlify Functions
└─ 더 유연한 구성
```
### 4.2 도메인
- Freenom (완전 무료, .tk/.ml/.ga)
- DNS 리다이렉트 → Vercel
### 4.3 CI/CD 파이프라인
```
git push
↓
GitHub Actions
├─ 린트 & 타입 체크
├─ 빌드 테스트
├─ DXF 도면 검증
└─ 부피 회귀 테스트
↓
Vercel 자동 배포
├─ 프리뷰 URL 생성
└─ 프로덕션 배포
```
---
## 5. 사용자 경험 (UX)
### 5.1 핵심 기능
1. **도면 업로드** → DXF 선택
2. **자동 변환** → 진행 상황 표시
3. **3D 뷰** → 회전, 줌, 패닝
4. **부재 정보** → 클릭 시 표시
5. **다운로드** → OBJ, IFC, 스크린샷
### 5.2 대시보드
```
┌─────────────────────────────┐
│ DXF to 3D 변환기 │
├─────────────────────────────┤
│ 📁 업로드 │ 최근 변환 목록 │
├──────────┼─────────────────┤
│ 3D 뷰 │ 부재 정보 패널 │
│ (Three.js)│ - 이름 │
│ │ - 재질 │
│ │ - 부피 │
└──────────┴─────────────────┘
```
### 5.3 모바일 반응형
- 터치 제스처 (pinch zoom)
- 모바일 레이아웃 최적화
- 가로/세로 자동 전환
---
## 6. 보안 및 성능
### 6.1 보안
- DXF 파일 크기 제한 (50MB)
- 바이러스 스캔 (선택사항)
- 개인정보 없음 (파일 자동 삭제)
- HTTPS만 (Vercel 기본)
### 6.2 성능
| 지표 | 목표 | 수단 |
|------|------|------|
| 로딩 | < 3초 | 번들 최적화, 이미지 압축 |
| 렌더링 | 60fps | Three.js 최적화, LOD |
| 변환 | < 10초 | 병렬 처리 (Worker) |
### 6.3 캐싱
```
정적 파일 → CDN 캐시 (1년)
API 응답 → 메모리 캐시 (1시간)
3D 모델 → 로컬스토리지 (세션)
```
---
## 7. 개발 일정 (추정)
| 단계 | 작업 | 기간 |
|------|------|------|
| 1 | 가시설 설정 파일 작성 | 2-3일 |
| 2 | 웹앱 기본 구조 | 2-3일 |
| 3 | Three.js 통합 | 2-3일 |
| 4 | 품질 테스트 | 2-3일 |
| 5 | 배포 & 도메인 | 1일 |
**전체**: ~2주 (병렬 처리 시)
---
## 8. 위험 요소 및 완화 방안
| 위험 | 영향 | 확률 | 완화 |
|------|------|------|------|
| DXF 파일 다양성 | 파싱 실패 | 중 | 설정 파일 테스트 |
| 3D 모델 복잡도 | 웹 렌더링 느림 | 중 | LOD, 정점 최적화 |
| 배포 시간 | 서비스 시작 지연 | 낮음 | 사전 인프라 준비 |
| 유지보수 | 기술 부채 | 중 | 명확한 문서 & 테스트 |
+518
View File
@@ -0,0 +1,518 @@
# PLAN.md — 상세 계획 및 에이전트 역할
> **PROGRESS.md의 예정 항목을 상세히 풀어 쓴 것입니다.**
> 각 단계 시작 전에 여기서 자신의 역할을 확인하세요.
---
## 전체 일정 (단계 4~9)
```
단계 4 → 단계 5 → 단계 6 → 단계 7 → 단계 8 → 단계 9
↓ ↓ ↓ ↓ ↓ ↓
계획 아이디어 구현 보강 병렬화 모델확정
2일 2일 3일 1일 2일 1일
(총 ~12일)
```
---
## 단계 4: PROGRESS.md & PLAN.md 작성 ✅ (완료)
### 목표
- PROGRESS.md: 진행 상황 추적용 (에이전트가 매 세션 읽기)
- PLAN.md: 상세 계획 및 에이전트 역할 (이 파일)
### 완료 기준
- [ ] PROGRESS.md 작성 (2026-10-16)
- [ ] PLAN.md 작성 (지금)
- [ ] 메모리에 저장
### 산출물
- `PROGRESS.md`: 현재 상태, 완료 단계, 예정 항목
- `PLAN.md`: 상세 계획 (이 파일)
---
## 단계 5: 에이전트/커맨드/스킬 아이디어 제안
### 목표
Claude Code의 에이전트, 커맨드, 스킬, 훅 구조를 이해하고, 이 프로젝트에 필요한 것들을 제안한다.
### 작업 항목
#### 5.1 에이전트 설계
```
4개 에이전트 (병렬 처리 가능):
[Agent-Research]
목적: 가시설 도면 분석 & 설정 파일 작성
담당: DXF 파싱, 좌표 검증, 기하학 분석
주요 스킬: Grep (도면 패턴 찾기), 계산
입력: 원본 DXF 파일
출력: src/drawings/gasiseol.mjs (설정 파일)
[Agent-Pipeline]
목적: dwg2model 파이프라인 최적화 & 테스트
담당: 빌드, 검증, 부피 측정
주요 커맨드: node src/run.mjs, node src/build.mjs, node src/measure.mjs
입력: 설정 파일 (src/drawings/gasiseol.mjs)
출력: IR (중간 표현), 검증 리포트
[Agent-WebUI]
목적: React 웹앱 & Three.js 통합
담당: UI 컴포넌트, 3D 뷰어, API
주요 기술: React, Three.js, TypeScript
입력: 3D 모델 (OBJ/IFC)
출력: 웹앱 (src/web/)
[Agent-Deploy]
목적: CI/CD, 배포, 도메인 설정
담당: GitHub Actions, Vercel, 도메인
주요 도구: git, npm, gh CLI
입력: 웹앱 소스 코드
출력: 배포된 웹사이트 + 무료 도메인
```
#### 5.2 커맨드 설계
```
프로젝트 특화 커맨드 (구현 예정):
/step-X 단계별 워크플로우 실행
/validate 도면 & 3D 모델 자동 검증
/convert DXF → 3D 한 번에 (3개 에이전트 병렬)
/deploy 웹앱 배포 (CI/CD 자동)
/report 진행 상황 & 검증 결과 리포트
```
#### 5.3 스킬 설계
```
프로젝트 특화 스킬:
/dxf-analyze
입력: DXF 파일
출력: 도면 분석 리포트 (좌표 범위, 레이어, 엔티티 수)
/3d-validate
입력: OBJ 또는 IFC 파일
출력: 검증 리포트 (manifold, 부피, 면 수)
/web-dev
입력: 3D 모델
출력: 웹앱 로컬 서버 실행
/ci-setup
입력: 저장소 설정
출력: GitHub Actions 워크플로우 파일
```
#### 5.4 훅 설계
```
자동 실행 훅 (settings.json):
[git commit 후]
훅: 자동 검증
→ node src/measure.mjs <도면> 실행
→ 부피 변화 감지
→ 문제 시 경고
[PROGRESS.md 수정 후]
훅: 상태 반영
→ 다음 세션에 알림
[배포 전]
훅: 모든 테스트 통과 확인
→ npm run test
→ npm run lint
→ node src/build.mjs --validate
```
### 완료 기준
- [ ] 4개 에이전트 정의 (이름, 목적, 담당, 입출력)
- [ ] 5개 커맨드 아이디어 제안
- [ ] 3개 스킬 아이디어 제안
- [ ] 4개 훅 아이디어 제안
- [ ] 사용자 승인 받기
### 산출물
- 텍스트 문서 또는 이 파일 확장
### 의존성
- 단계 4 완료 후
---
## 단계 6: Claude Code 구조 조사 및 정확한 포맷 구현
### 목표
Claude Code의 공식 에이전트, 커맨드, 스킬, 훅 포맷을 학습하고, 단계 5의 아이디어를 정확한 포맷으로 구현한다.
### 작업 항목
#### 6.1 Claude Code 구조 조사
```
학습 대상:
- .claude/agents/*.md 포맷 (에이전트 정의)
- .claude/commands/*.yaml 포맷 (커맨드)
- .claude/skills/*.yaml 포맷 (스킬)
- settings.json hooks 포맷 (자동 실행)
공식 문서:
- /help claude-code
- /help agents
- https://claude.com/claude-code (설명)
```
#### 6.2 정확한 포맷으로 구현
```
폴더 구조:
D:\Obsidian Vault\999ETC\261016_HW_DwfTo3D\.claude\
├── agents/
│ ├── dxf-research.md [Agent-Research]
│ ├── pipeline-build.md [Agent-Pipeline]
│ ├── web-ui-dev.md [Agent-WebUI]
│ └── deploy-ci.md [Agent-Deploy]
│
├── commands/
│ ├── step.yaml (/step-X)
│ ├── validate.yaml (/validate)
│ ├── convert.yaml (/convert)
│ └── deploy.yaml (/deploy)
│
├── skills/
│ ├── dxf-analyze.yaml (/dxf-analyze)
│ ├── 3d-validate.yaml (/3d-validate)
│ └── web-dev.yaml (/web-dev)
│
└── settings.json
└── hooks: [자동 실행 규칙]
```
#### 6.3 각 파일의 내용
```
agents/dxf-research.md:
- name: 도면 분석 & 설정 파일
- description: DXF 파일 파싱, 좌표 검증, 설정 파일 작성
- tools: [Read, Edit, Write, Grep, Bash]
- instructions: [...상세 지침...]
commands/convert.yaml:
- name: convert
- description: DXF → 3D 변환 (병렬 처리)
- agents:
- dxf-research (stage 1)
- pipeline-build (stage 2)
- web-ui-dev (stage 3)
- sequential: false (병렬 실행)
skills/dxf-analyze.yaml:
- 트리거: /dxf-analyze <파일>
- 실행: Agent-Research 호출
- 결과: 도면 분석 리포트
```
### 완료 기준
- [ ] .claude/ 폴더 구조 생성
- [ ] 4개 에이전트 정의 파일 작성
- [ ] 4개 커맨드 파일 작성
- [ ] 3개 스킬 파일 작성
- [ ] settings.json 훅 설정
- [ ] 모든 파일 git 커밋
### 산출물
```
.claude/ 폴더 완성
├── agents/ (4개 파일)
├── commands/ (4개 파일)
├── skills/ (3개 파일)
└── settings.json (훅 포함)
```
### 의존성
- 단계 5 완료 + 아이디어 승인 후
- Claude Code 공식 문서 학습 필요
---
## 단계 7: 추가 하네스 및 보강
### 목표
Anthropic 엔지니어링 블로그의 "harness-design-long-running-apps" 자료를 읽고, 장기 실행 앱 설계를 프로젝트에 적용한다.
### 작업 항목
#### 7.1 하네스 개념 학습
```
읽을 자료:
https://www.anthropic.com/engineering/harness-design-long-running-apps
학습 항목:
- 에이전트 상태 관리
- 장기 실행 작업의 진행률 추적
- 오류 복구 전략
- 단계별 검증 포인트
```
#### 7.2 프로젝트에 적용
```
개선 사항:
1. 에이전트 체크포인트
- 각 에이전트의 중간 결과 저장
- 실패 시 재개 가능
2. 진행 상황 실시간 추적
- PROGRESS.md 자동 업데이트
- 웹 대시보드 (옵션)
3. 롤백 가능성
- git 커밋 포인트 명확
- 문제 시 이전 버전 복구
4. 재시도 로직
- 네트워크 오류 시 재시도
- 지수 백오프
```
#### 7.3 CLAUDE.md 업데이트
```
추가 내용:
- 에이전트 체크포인트 정의
- 오류 복구 절차
- 진행 상황 모니터링
- 롤백 가이드
```
### 완료 기준
- [ ] 하네스 설계 개념 문서 작성
- [ ] 프로젝트 구조 다이어그램 (상태 관리)
- [ ] CLAUDE.md 보강
- [ ] 에이전트에 체크포인트 로직 추가
### 산출물
- 보강된 CLAUDE.md
- 하네스 설계 다이어그램
- 각 에이전트의 체크포인트 정의
### 의존성
- 단계 6 완료 후
---
## 단계 8: 동적 에이전트 + 병렬화 워크플로우 구현
### 목표
복합 작업을 여러 에이전트가 병렬로 처리하는 워크플로우를 구현한다. 안정성을 유지하면서 속도를 높인다.
### 작업 항목
#### 8.1 병렬 처리 구조
```
사용자 입력
↓
Orchestrator 에이전트
├─→ [Agent-Research]
│ (DXF 분석, 설정 파일)
│
├─→ [Agent-Pipeline]
│ (빌드, 검증)
│ (Research 완료 대기)
│
└─→ [Agent-WebUI]
(UI 개발)
(Pipeline 완료 대기)
병렬화 규칙:
- Research와 WebUI: 병렬 가능
- Pipeline: Research 완료 후 시작
- 최종 병합: 모든 에이전트 완료 대기
```
#### 8.2 워크플로우 정의 (코드 또는 설정)
```
example: convert.yaml
stages:
stage1:
agents:
- dxf-research
parallel: false
stage2:
agents:
- pipeline-build
depends_on: stage1
parallel: false
stage3:
agents:
- web-ui-dev
parallel: true (stage1과 동시)
stage4:
agents:
- orchestrator (병합)
depends_on: [stage2, stage3]
```
#### 8.3 안정성 보장
```
체크포인트 전략:
- 각 stage 시작 전: 상태 저장
- 각 stage 완료 후: 결과 검증
- 실패 시: 해당 stage부터 재시작
타임아웃:
- Research: 5분
- Pipeline: 10분
- WebUI: 10분
- 전체: 30분
에러 처리:
- 중단 불가: Research + WebUI (서로 독립)
- 중단 가능: Pipeline 실패 → 전체 실패
```
#### 8.4 모니터링 대시보드 (옵션)
```
실시간 추적:
├─ 각 에이전트 진행률
├─ 현재 병목 지점
├─ 예상 완료 시간
└─ 오류 알림
구현:
- 웹 UI (React)
- 또는 CLI 대시보드
```
### 완료 기준
- [ ] 워크플로우 YAML 정의
- [ ] Orchestrator 에이전트 구현
- [ ] 병렬 실행 테스트 (2회 이상)
- [ ] 오류 복구 테스트
- [ ] 모니터링 (CLI 또는 웹)
### 산출물
- .claude/commands/convert.yaml (병렬 워크플로우)
- Orchestrator 에이전트 구현
- 워크플로우 다이어그램
- 테스트 리포트
### 의존성
- 단계 7 완료 후
---
## 단계 9: 모델 선택 확정
### 목표
Haiku 4.5로 작업한 결과를 평가하고, 필요시 더 강력한 모델로 전환할지 결정한다.
### 작업 항목
#### 9.1 성능 평가
```
평가 항목:
- 코드 품질 (정확성, 버그)
- 개발 속도 (각 단계별 소요 시간)
- 결과물 품질 (웹앱 기능성, 3D 정확도)
- 비용 (Haiku 비용 vs 상위 모델)
기준:
✅ Haiku 충분: 모든 항목 80점 이상
△ 개선 필요: 1~2개 항목 60~80점
❌ Haiku 부족: 2개 이상 항목 60점 미만
```
#### 9.2 대안 평가
```
모델 선택지:
현재: Claude Haiku 4.5
- 비용: 저 (토큰당 0.80/3.2)
- 속도: 매우 빠름
- 추론: 기본적 작업 우수
대안 1: Claude Sonnet 5
- 비용: 중 (3/15)
- 속도: 빠름
- 추론: 복잡한 구조 이해 우수
대안 2: Claude Opus 5
- 비용: 높음 (15/60)
- 속도: 느림
- 추론: 최고 수준
```
#### 9.3 의사결정
```
선택 기준:
Haiku 유지:
✓ 결과물 품질 만족
✓ 개발 일정 충족
✓ 비용 절감 (목표)
→ Sonnet 전환:
✗ 정확도 부족 (3D 모델 오류)
✗ 개발 지연 (단계별 시간 초과)
✗ 특정 기술 이해 미흡
→ Opus 전환:
✗ Sonnet도 부족한 경우만
✗ 비용 대폭 증가
```
### 완료 기준
- [ ] 1단계별 소요 시간 기록
- [ ] 코드 품질 평가 (린트, 테스트)
- [ ] 3D 모델 정확도 검증
- [ ] 웹앱 기능 평가
- [ ] 최종 모델 결정
### 산출물
- 평가 리포트 (성능 지표)
- 모델 선택 의사결정 문서
### 의존성
- 단계 8 완료 + 전체 구현 완료 후
---
## 크리티컬 패스
```
단계 5 (아이디어)
↓
단계 4 (계획) → 단계 6 (구현) → 단계 7 (보강)
↓ ↓
단계 8 (병렬화)
↓
단계 9 (평가)
병목:
- 단계 5→6: 포맷 학습 (1일)
- 단계 6→7: 구조 확정 (1일)
- 단계 8: 병렬 안정성 (2일)
```
---
## 리스크 및 완화책
| 단계 | 리스크 | 완화책 |
|------|--------|--------|
| 5 | 아이디어 과다 | 핵심만 4개 에이전트로 제한 |
| 6 | 포맷 불일치 | 공식 문서 읽고 예제 확인 |
| 7 | 복잡도 증가 | 단계별 검증점 명확 |
| 8 | 병렬 관리 어려움 | Orchestrator 패턴 활용 |
| 9 | 모델 전환 비용 | 초기 평가 명확 기준 설정 |
---
## 변경 이력
| 버전 | 날짜 | 내용 |
|------|------|------|
| 1.0 | 2026-10-16 | 초안 (단계 4) |
+130
View File
@@ -0,0 +1,130 @@
# PROGRESS.md — 프로젝트 진행 상황
> **매 세션마다 업데이트하세요.**
> 에이전트는 이 파일을 읽고 현재 상태를 파악합니다.
---
## 현재 상태 (2026-10-16)
### ✅ 완료된 단계
- **단계 0**: 리마인드 및 절차 확인
- **단계 1**: 웹 조사 + 기술 선택
- 기존 dwg2model 파이프라인 검증
- 가시설 도면 샘플 확인 (7개 DXF 파일)
- 에이전트 역할 정의 (4개)
- **단계 2**: 고려사항 분석
- CONSIDERATIONS.md 작성 (8개 영역)
- 기술 스택 결정 (Three.js, React, Vite)
- 품질 기준 수립 (4단계 검증)
- 배포 전략 정의 (Vercel + 무료 도메인)
- **단계 3**: 프로젝트 가이드 작성
- CLAUDE.md 완성 (프로젝트 구조, 워크플로우)
### 🔄 진행 중
- 단계 4: PROGRESS.md & PLAN.md 작성 (현재)
### ⏳ 예정
- **단계 4**: PROGRESS.md & PLAN.md 작성
- **단계 5**: 에이전트/커맨드/스킬 아이디어 수집
- **단계 6**: Claude Code 구조 조사 후 정확한 포맷으로 구현
- **단계 7**: 추가 넘침 및 하네스 설계 학습
- **단계 8**: 동적 에이전트 + 병렬화 워크플로우 구현
- **단계 9**: 모델 선택 확정
---
## 산출물 목록
### 생성된 파일
```
D:\Obsidian Vault\999ETC\261016_HW_DwfTo3D\
├── CONSIDERATIONS.md (단계 2 결과)
├── CLAUDE.md (단계 3 결과)
├── PROGRESS.md (이 파일)
└── PLAN.md (작성 예정)
```
### 메모리 저장
```
C:\Users\User\.claude\projects\d--Obsidian-Vault-999ETC-261016_HW_DwfTo3D\memory\
├── MEMORY.md
├── project_dxf_to_3d.md
├── feedback_project_process.md
└── step1_findings.md
```
---
## 주요 결정사항
| 항목 | 결정 | 근거 |
|------|------|------|
| **DXF 파싱** | dwg2model 재사용 | 검증됨, 19개 교훈 |
| **3D 시각화** | Three.js | 가볍고 React 통합 용이 |
| **웹앱** | React + Vite | 개발 속도, 번들 크기 |
| **배포** | Vercel | React 최적화, CI/CD 자동 |
| **호스팅** | Freenom + 커스텀 도메인 | 완전 무료 |
| **모델** | Haiku 4.5 | 제약 조건 (능력 검증 목적) |
---
## 알려진 위험 및 완화책
| 위험 | 영향 | 완화 방안 |
|------|------|----------|
| DXF 파일 형식 다양성 | 파싱 실패 | 설정 파일 사전 작성 & 테스트 |
| 3D 모델 복잡도 | 웹 렌더링 느림 | LOD, 정점 최적화 |
| 새 기술 통합 (Three.js) | 학습 곡선 | 기존 예제 활용, react-three-fiber |
| 무료 도메인 신뢰도 | 사용자 진입 난색 | 정식 도메인으로 전환 옵션 |
---
## 다음 세션 체크리스트
### 시작 전 (읽기 전용)
- [ ] CLAUDE.md 빠른 참조 확인
- [ ] 이 파일(PROGRESS.md) 전체 읽기
- [ ] PLAN.md 읽어서 에이전트 역할 파악
### 작업 시작 시
- [ ] 단계 리마인드 (이번 단계 목표 명시)
- [ ] 작업 수행 (git 커밋 포함)
- [ ] 검증 (문제 확인)
- [ ] PROGRESS.md 업데이트
---
## 메모 및 교훈
### 성공한 접근법
- ✅ 기존 프로젝트(dwg2model) 활용 → 6개월치 실패 경험 활용 가능
- ✅ 사전 계획 문서 작성 → 에이전트 지침 명확
- ✅ 단계별 리마인드 → 의도하지 않은 실수 방지
### 주의할 점
- ⚠️ 19개 교훈(2d-3d-conversion.md) 필독 필수
- ⚠️ src/core/* 에 도면 고유값 절대 금지
- ⚠️ 단계 4 완료 전에 셋업 시작하지 말 것
---
## 이전 세션 기록
### 2026-10-16 (초기화)
- 작업지시서.md 읽음
- 프로젝트 메모리 생성
- 단계 0~3 완료
- CLAUDE.md 작성
---
## 문의 및 피드백
문제 발생 시:
1. CLAUDE.md "트러블슈팅" 절 확인
2. `docs/2d-3d-conversion.md` 교훈 검색
3. CONSIDERATIONS.md 재검토
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+524
View File
@@ -0,0 +1,524 @@
# 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 브리지 |
+217
View File
@@ -0,0 +1,217 @@
# CLAUDE.md — dwg2model 작업 가이드
이 저장소에서 작업할 때 지켜야 할 것. 사람이 읽어도 된다.
---
## 이 프로젝트가 하는 일
2D 도면(평면 · 입면 · 단면 · 좌/우측면)을 읽어 3D 솔리드와 IFC 로 옮긴다.
**도면마다 설정 파일 하나만 바꾸면 도는 구조**가 목표다.
문서 셋을 먼저 읽어라.
| 문서 | 읽어야 할 때 |
|---|---|
| [`README.md`](README.md) | 구조 · 실행 방법 |
| [`docs/2d-3d-process.md`](docs/2d-3d-process.md) | **절차** — 새 도면 추가, 검증 순서 |
| [`docs/2d-3d-conversion.md`](docs/2d-3d-conversion.md) | **노하우** — 함정, 실패 사례, 왜 그렇게 하는지 |
| [`docs/roadmap.md`](docs/roadmap.md) | 남은 일 · 우선순위 |
| [`docs/parametric.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:[]` 를 낸다. 원인을 찾는 데
제일 오래 걸린 함정이다.
```sh
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 의무의 근거다.
File diff suppressed because it is too large. Load diff
+194
View File
@@ -0,0 +1,194 @@
# 다음 세션 프롬프트 — 교각 P11 (꺾임)
> 그대로 복사해 붙이면 된다. 아래 `---` 부터가 프롬프트다.
---
# 교각 P11 — 꺾인 코핑
## 지난 세션에 P9 를 끝냈다
`산외교_파이형_교각일반도(울산P9).dwg` 로 **교각 한 기를 끝까지** 세웠다.
코핑 · 원형기둥 2 · 확대기초 2 · 버림 2 · 강관말뚝 44 · 받침 6기(교좌대·모르타르·
받침·솔플레이트) · 미관문양 2 = **77 파트 · 660.4154 m³**.
모델러 실측 대비 **불일치 0건**, 열린 변 0, IFC 내보내기까지 왕복 검증됐다.
이번엔 **P11** 이다. `산외교_파이형_교각일반도(울산P11-꺾임).dwg`.
P9 와 뷰 구성이 같지만 **코핑 축선이 꺾여 있다.** 그게 이번 세션의 전부다.
## 먼저 읽어라 (순서대로)
1. `CLAUDE.md` — 경계 · 작업 순서 · 함정표
2. `docs/methodology.html` **11.4 절** — P9 실증 결과. 판정표 · 코어 변경 · 새 함정
3. `src/drawings/pier-p9.mjs` — **이번 작업의 원본이다.** 통째로 읽어라
4. `docs/2d-3d-conversion.md` 교훈 #29~#33 (지난 교량 세션)
## 대상
```
drawings-src/산외교_파이형_교각일반도(울산P11-꺾임).dwg
도곽 CXBLKA1 INSERT (1,406,622, 390,000) × scale 100 × A1(841×594)
→ sheet x[1,406,622 … 1,490,722] y[390,000 … 449,400]
```
**도곽 밖은 전부 다른 벌이다.** 이 파일도 도면집 스택이라 17벌이 y 방향 65,000
간격으로 쌓여 있고, 그중 P9 자리는 치수선만 남아 있다. 상자를 눈으로 잡지 마라 —
도곽 블록에서 계산한다.
## 이미 확인한 것 (DWG 데이터에서 직접 잰 값)
```
측점 STA.0+594.570
표고 EL. 82.799 · 82.306 · 81.713 (코핑 상면 3단으로 추정)
EL. 44.413 (기초 저면)
기둥 Ø2,200 원형 3본 · CS-CONC-MINR CIRCLE
평면도 (1,416,488, 437,383) (1,425,088, 437,383) (1,433,616, 436,442)
기초평면 (1,416,488, 403,537) (1,425,088, 403,537) (1,433,616, 402,596)
받침 블록 500x500 ×3 · 530x530 ×2 · 470x470 ×1
연단거리 L=170.600 · H=36.203 · Theta=0
```
### 꺾임각 = **6.30°**
```
기둥 1→2 Δx 8,600 Δy 0 → 0.00° 간격 8,600
기둥 2→3 Δx 8,528 Δy −941 → −6.30° 간격 8,580
```
**두 뷰(평면도·기초평면도)에서 같은 941 이 나온다.**
> ⚠ 지난 세션에 이걸 한 번 「8.7°」로 잘못 말했다. 전 색 각도 분포에 있던
> `81.3°/171.3°` 를 꺾임각으로 읽은 것인데, 구조 외곽선만(`ci=256`) 세면 그
> 대역이 없다 — **보조선 각도였다.** 기둥 축선에서 직접 잰 6.30° 가 맞다.
> **각도를 선 분포에서 읽지 말고 부재 좌표에서 재라.**
`(1,453,587, 436,249)` 의 Ø2,200 은 `CS-CONC-MAJR` 이고 x 가 19,971 떨어져 있다 —
**단면 A-A** 의 기둥 단면이지 평면도 기둥이 아니다.
## 1단계 — 꺾임의 정체 (여기서 멈추고 보고)
**캡처를 먼저 봐라.** 좌표만으로는 못 가른다.
1. **평면도** — 꺾이는 자리가 **기둥 3번 위**인가 그 **사이**인가.
코핑이 «각진 두 토막»인가 «한 곡선»인가. 꺾임부 폭·필렛
2. **정면도** — 파단선 위치 · 코핑 상면 단수 · EL 지시 위치
3. **단면도** — 코핑 교축폭 · 헌치
4. **기초 평면도** — 기초 3기인가 · 말뚝 배치(P9 는 22본×2)
## 2단계 — 형상 전략 (보고 후 진행)
P9 코핑은 「정면 윤곽(YZ) × 교축 압출」 ∩ 「평면 윤곽 빗각기둥 2장」으로 섰다.
**꺾이면 그 전제가 깨진다** — 정면도가 투영이라 실제 길이를 안 준다.
후보 셋. 캡처 보고 고른다.
| 방법 | 되는 조건 | 걸림돌 |
|---|---|---|
| 꺾임점에서 **끊어** 두 토막을 각자 압출 | 두 토막이 각각 직선 | 이음매 union — 면으로만 맞닿으면 다음 boolean 이 흘린다 |
| 토막마다 **로컬 좌표계** | 가장 정직 | IR 에 배치 개념이 없다. 코어 변경 |
| 평면 압출 ∩ 정면 압출 | 꺾임이 평면에만 있고 정면이 실제 길이를 줄 때 | 대개 안 준다 |
**끊는 쪽이 유력하다.** loft 는 쓰지 마라 — 결과가 안 닫혀 boolean 에서 샌다(아래).
## 절대 규칙
- `src/core/*` · `src/ir.mjs` 에 도면 고유값 금지. 레이어명 · 제목 · 좌표 · 단위 ·
축척은 전부 `src/drawings/pier-p11.mjs`
- **코어 수정이 필요해 보이면 먼저 보고해라.** 그게 방법론의 구멍이다
- 못 찾거나 여러 개면 예외를 던져라. 자동 선택 금지
- **손으로 읽은 수를 코드에 적지 마라.** 도면에서 집고 다른 값과 대조해라
- **그림을 먼저 봐라.** 뷰 상자를 잡기 전에 시트 → 도형 → 확인 → 상자
## 게이트 (P9 에서 전부 통과시킨 순서)
```
0. egm guide | 명령 수 208 이면 편집 모드 · 39 면 뷰어 모드(모델링 불가)
1. node src/run.mjs pier-p11 뷰 폭·높이가 도면 표기와 맞는가
2. node src/gaps.mjs pier-p11 끊김의 원인이 형상인가 파단선인가
3. node src/build.mjs pier-p11 --loops 꼭짓점 수·자기교차
node src/build.mjs pier-p11 --circles / --blocks
node src/table.mjs pier-p11 --title "교량받침 ELEVATION TABLE"
4. node src/build.mjs pier-p11 IR 검증
5. node src/volume.mjs pier-p11 해석 부피 (모델러 없이)
6. node src/emit/egm.mjs out/pier-p11.ir.json
python src/run_egm.py out/pier-p11.egm.json --export=out/pier-p11.obj
7. egm status modelCount == 최종 파트 수
8. node src/measure.mjs pier-p11 --ir 의도 vs 결과 (합격선 절대 0.05 m³)
9. node src/emit/ifc.mjs out/pier-p11.ir.json
```
## 지난 세션에 코어를 이만큼 고쳤다 (되돌리지 마라)
```
C12 core/loops.mjs dropCollinear 중복점 제거
매달린 변 왕복이 길이 0 인 변을 남긴다. 압출하면 퇴화 면이 나온다
C13 ir.mjs · emit/egm.mjs · volume.mjs intersect 연산
BooleanIntersection 은 이항. 「두 방향이 동시에 맞아야 하는 형상」용
C14 ir.mjs · emit/egm.mjs · volume.mjs extrude.drift (빗각 기둥)
Extrude C=True D F 가 자유방향을 받고 결과가 닫힌다
C15 core/parse.mjs collectBlocks · run.mjs · core/views.mjs · core/pick.mjs blocks()
말뚝이 INSERT 블록. 삽입점=위치, 배율=치수
C16 core/table.mjs (신규) · src/table.mjs (CLI) · run.mjs 색인 · pick.tables
표를 (이름표, CSV) 쌍으로 색인. 이름표 아래 글자높이 ×2 로 표를 찾는다
C17 run.mjs 뷰별 entityFilter
뷰마다 무엇이 구조선인지 다르다 (문양은 ci=2, 본체는 ci=256)
C18 volume.mjs 교집합 해석 부피
① 압출∩압출 닫힌 식 ② 종단∩평면(높이 가변) Z 슬라이스 가우스
C19 emit/ifc.mjs (신규) IFC4 출력. 해석 솔리드 우선 + IfcMapConversion
C20 emit/egm.mjs loft → `~Loft Enter` + `~Cap` (그래도 boolean 엔 못 쓴다)
```
## 새로 밟은 함정 (또 밟지 마라)
| 증상 | 원인 | 대응 |
|---|---|---|
| **부피는 맞는데 boolean 이 샌다** | **`Loft` 결과가 안 닫힌다.** 4각형 단면도 열린 변 12개. 발산 정리 부피는 정확해 **부피 검사로 절대 안 걸린다** | loft 를 boolean 인자로 쓰지 마라. 평행이동뿐이면 `drift` 압출 |
| 그려진 길이가 실제가 아니다 | **파단선.** 기둥이 6,100 인데 실제 33,800 | `CS-CUTL` 을 본다. 생략량 = 실제 − 도형 (derived) |
| 한 파일에 도형 17벌 | 도면집 스택 | **도곽 블록에서 상자를 계산** |
| 「단면도」 안쪽 선을 부재로 읽음 | **절단면이 아니라 투영** | 치수가 `외곽 − 2×R` 이면 투영 |
| 평면 모서리가 45° 모따기 | 파서가 `bulges` 를 버려 90° 호가 현으로 눌림 | 호 끝점은 정확 → R 은 되찾는다. 링 면적은 못 믿는다 |
| 적분값이 0.2 m³ 샘 | 구간 끝점이 링 꼭짓점 → 스캔라인 교차 수 어긋남 | 내부 노드 구적(가우스). 심프슨 금지 |
| 구조 형상이 **표**로 잡힘 | 평면도 격자가 73행×83열 「표」 | 표 색인에 `layerFilter` |
| `actualMeasurement` 로 단위 판정 실패 | 주요 m 표기 · 상세 mm 표기 (**표시값**이 온다) | 도곽 배율 · 블록 scale · 격자 간격으로 교차검증 |
## P9 에서 통한 방식 — 그대로 가져다 써라
```
단위 도곽 A1×100 · 블록 scale ↔ Φ 텍스트 · 격자 ↔ 「4@1.300」 → 넷 다 1단위=1mm
공통중심 부재 대칭으로 자기검증 (코핑 ±7,000 · 기초 ±3,250 · 기둥 ±4,100)
표고 공통 기준선이 없어도 **표가 준다** — EL.'X' = 코핑상면 + h1 + h2
6기가 모두 같은 절대표고를 내면 그게 원점이다
받침 평면도(위치·평면치수) × 표(적층고·표고·교축위치) 세 겹 검산
파단 생략량 = 기둥_높이 − 기둥_도형길이 (derived. 손으로 적지 마라)
```
## 보고 형식 (매 단계)
```
쓴 도면·도형: 무엇을, 왜 그것을
통과한 게이트:
막힌 곳: 게이트 번호 + 증상 + 원인
설정으로 해결: 무엇을
코드가 필요: 무엇이 왜 (← 방법론의 구멍)
방법론에서 틀린 것: 어느 절의 어느 주장이
새 함정: 증상 → 원인 → 대응
```
## 아직 안 한 일 (P11 과 별개, 여력 되면)
1. **`manhole1` 재검증** — loft 결과를 union·difference 인자로 쓴다.
C20 의 함정 위에 서 있을 수 있다. 부피가 맞아 보여도 샜을 수 있다
2. **P9 미관문양 형상 재검토** — 사용자가 「제대로 안 된다」고 했다.
지금은 코핑 양 측면의 50 두께 띠 + 하단 250 구간 100 돌출로 세워져 있고,
정면도의 매달린 대각선 6줄(문양 내부 능선)은 재현 안 한다
3. **`docs/2d-3d-conversion.md` 에 교훈 #34~ 추가** — 위 함정표가 아직 안 옮겨졌다
4. **텍스트 셀렉터** — 기둥 높이·말뚝 길이가 아직 `design` 이다.
치수/표고 **텍스트**를 집는 수단이 없다. `blocks()` 를 만든 것과 같은 종류
## 마지막
1. `docs/2d-3d-conversion.md` 에 교훈 추가
2. `docs/methodology.html` **11.5 절** 신설 — P11 이 무엇을 밝혔는지.
판정표를 그대로 싣고, 배지를 올리되 **틀린 주장은 지우지 말고 왜 틀렸는지 적어라**
3. `node src/ui.mjs pier-p11 --port 7333` 로 파라메트릭 UI 까지 확인
4. `node src/emit/ifc.mjs out/pier-p11.ir.json` 로 IFC 까지
+143
View File
@@ -0,0 +1,143 @@
# 다음 세션 프롬프트 — 교각(Pier) 하나로 방법론 실증
> 그대로 복사해 붙이면 된다. 아래 `---` 부터가 프롬프트다.
---
# 교각(Pier) 한 부재로 방법론 실증
## 왜 부재를 쪼개나
지난 세션에 교량 도면 7장으로 한꺼번에 갔다가 **뷰 상자를 두 번 잘못 잡았다.**
한 시트에 날짜가 다른 작업본이 겹쳐 있는 걸 못 보고 두 벌을 섞어 뽑았고,
교대라고 부른 것이 실은 옆 도형의 날개벽이었다. **폐곡선도 치수도 그럴듯해서
숫자로는 안 걸렸다.**
이번엔 **교각 하나만** 한다. 부재 하나를 끝까지 밀어 방법론의 어느 절이
그 부재에 대해 참인지 거짓인지를 확정하는 것이 목적이다. 모델 완성이 아니다.
## 먼저 읽어라 (순서대로)
1. `CLAUDE.md` — 경계 · 작업 순서 · 함정표(마지막 7행이 지난 세션 것)
2. `docs/2d-3d-conversion.md` — **교훈 #29~#33 을 반드시 읽어라.** 지난 세션에
내가 밟은 것들이다
3. `docs/methodology.html` — **11.0.4 · 11.0.5** 가 교량 실증 결과다
4. `src/drawings/bridge-end.mjs` — 지난 세션 산출물. 교각을 이미 세워봤다
(두부 2,244×1,334 · 기둥 1,586×3,904 · 확대기초 5,081×1,334)
## 대상
`drawings-src/` 의 **교각 도면**.
- 새 DWG 가 들어와 있으면 그것을 쓴다
- 없으면 기존 7장 중 교각이 그려진 것 (`교량시종점` 의 2지간 도형,
`C-BRDG-PIER` 레이어를 가진 `교량폭원` · `횡단경사`)
**어느 것을 쓰는지 먼저 보고해라.**
## 절대 규칙 (지난 세션에 어긴 것 포함)
- `src/core/*` · `src/ir.mjs` 에 도면 고유값 금지. 레이어명 · 제목 · 좌표 · **단위** ·
**축척**은 전부 `src/drawings/<이름>.mjs`
- **코어 수정이 필요해 보이면 먼저 보고해라.** 그게 방법론의 구멍이다
- 못 찾거나 여러 개면 예외를 던져라. 자동 선택 금지
- 손으로 읽은 수를 코드에 적지 마라. 도면에서 집고 **다른 값과 대조**해라
- **그림을 먼저 봐라.** 뷰 상자를 잡기 전에 반드시:
① 시트 전체 캡처 → 도형이 몇 벌인지, 겹치는지
② 도형 하나만 캡처 → 무엇이 들어 있는지 눈으로 확인
③ 그 다음에 상자
**이 순서를 건너뛰면 지난 세션과 같은 실수가 난다**
- 캡처는 `src/capture.mjs` 를 써라 (`--crop`, `--scale`, `--png`).
프로젝트 격자와 사이드카가 같이 나온다. 즉석 렌더를 던지지 마라
## 1단계 — 도면 파악 (여기서 멈추고 보고)
1. **시트 전수 캡처.** 도형이 몇 벌인가. 날짜 도장이 몇 개이고 **작업본이
겹쳐 있는가** (교훈 #29)
2. **교각이 그려진 도형을 전부 나열**하고 각각 무엇을 주는지:
- 종단면(측면) / 횡단면 / 평면 중 무엇인가
- 두부 · 기둥 · 확대기초 · 말뚝 중 무엇이 부재로 그려졌나
- 확대 상세가 섞여 있나 (축척이 다르다 — 교훈 #31)
3. **단위와 축척.** `DIMENSION.actualMeasurement` ↔ 치수 글자로 `unitMm` 확정.
치수 글자가 없으면 **크기를 아는 표준 부재**로 축척을 되찾아라 (교훈 #30).
두 값 이상으로 교차검증할 것
4. **표기 치수가 숫자인가 기호인가** 세라. 기호뿐이면 그 도면 안에는 대조
상대가 없다는 뜻일 뿐 — 다른 도면·표준 치수는 여전히 상대가 된다
## 2단계 — 교각 하나 세우기 (보고 후 진행)
**부재를 하나씩** 올려라. 한 번에 다 넣지 말 것.
```
1. 기둥 가장 단순한 것부터. 폐곡선 → 압출 → 부피 대조
2. 두부 기둥과 맞닿는 자리 확인
3. 확대기초
4. 말뚝 원기둥. 위치·지름을 도면에서 어떻게 얻는가 (교훈 #33)
5. 받침 두부 위. 확대 상세와 섞이지 않는지
```
각 부재마다 **게이트를 다 통과한 뒤** 다음으로 간다.
`--loops` → `gaps` → `build` → `measure --ir` 이 0.0000 인지.
## 3단계 — 이 부재에서 방법론이 어디까지 참인가
교각에 대해 **명시적으로 판정**해라. 「해봤다」가 아니라 「참/거짓/해당없음」으로.
| 방법론 주장 | 교각에서 |
|---|---|
| 2.2 표고 기준은 전 뷰 공통 | ? |
| 3.4 제목으로 뷰를 가른다 | ? |
| 5.2 연산 결정 트리 | 교각에 필요한 연산이 트리에 있나 |
| 11.1 기준 체인 (받침 → 교각 로컬) | **여기서 처음 검증 가능하다.** 하부공 도면이 받침 좌표·표고로 교각을 잡나 |
| 11.2 revolve 필요 | 원형 교각·두부가 있나 |
| 11.2 임의축 cylinder | 경사 말뚝이 있나 |
## 알려진 구멍 (지난 세션에 찾았고 아직 안 메웠다)
- **`pick` 이 선을 못 집는다.** 말뚝 위치는 `CS-CNTL` 중심선인데 집을 수단이
없어 스크립트로 재서 설정에 적었다. `circles()` 처럼 `centerlines()` 가 필요하다.
교각에서 또 필요해지면 **그때 코어에 넣자고 보고**해라
- 말뚝 **폭방향 배치**와 길이를 주는 도면이 없었다. 교각 도면에 평면이 있으면
거기서 나올 것이다 — **있는지 먼저 확인**해라
- 개별 거더 치수 없음 (거더배치는 위상만 준다)
## 지난 세션에 코어를 이만큼 고쳤다 (되돌리지 마라)
```
C1 cfg.unitMm DWG 모델단위가 도면군마다 다르다
C2 groupByBox 제목 없는/중복인 도면의 뷰 영역
C3 datum.mode:'none' 공통 기준선이 없는 도면
C4 toSegments entityFilter 레이어 아닌 필터 (색·선종)
C5 viewBoxes[뷰].unitMm 도형별 축척
C6 buildLoops 분할 뒤 중복 제거 겹치는 동일선상 선분
C7 rules[].needs 규칙 적용 범위
C8 findLevelDatum 라벨 무리 검사 예외 없이 틀린 답을 막는다
C9 pick.loops(w,h,tol) 같은 부재 여러 벌
C10 volume.mjs 해석 부피 격자 거짓 경보 제거
C11 buildLoops bbox collinear 제거 뒤로 (매달린 변)
capture.mjs 단위·격자·사이드카
probe.mjs → projectViews 경유
render.mjs --part 정규식 · --clip 걸침 허용
```
## 보고 형식 (매 단계)
```
쓴 도면·도형: 무엇을, 왜 그것을
통과한 게이트:
막힌 곳: 게이트 번호 + 증상 + 원인
설정으로 해결: 무엇을
코드가 필요: 무엇이 왜 (← 방법론의 구멍)
방법론에서 틀린 것: 어느 절의 어느 주장이
새 함정: 증상 → 원인 → 대응
```
## 마지막
1. `docs/2d-3d-conversion.md` 에 교훈 추가 (번호는 #34 부터)
2. `docs/methodology.html`
- 11장에 **교각 절** 신설 — 부재 단위 실증이 무엇을 밝혔는지
- 3단계 판정표를 그대로 싣는다
- 배지를 `가설` → `검증`/`부분` 으로 올리되 **틀린 주장은 지우지 말고 왜
틀렸는지 적어라**
3. `node src/ui.mjs <도면> --port 7333` 으로 파라메트릭 UI 까지 확인
(`dims()` 를 만들어야 치수 상자가 나온다)
+516
View File
@@ -0,0 +1,516 @@
# 파라메트릭 설계 층 — 설계 노트
2026-08-06 토론 정리 → **구현 완료.** 12절에 실제로 만든 것과 확인된 것을 적었다.
결정 근거는 11절(순서) · 8절 (b)(대조 등급) · 8.5절(UI).
- 절차는 [`2d-3d-process.md`](2d-3d-process.md), 노하우는 [`2d-3d-conversion.md`](2d-3d-conversion.md)
- 남은 일 전체는 [`roadmap.md`](roadmap.md)
---
## 0. 대전제 — 목표는 바뀌지 않았다
**이 저장소의 목표는 여전히 `2D DWG → 3D 모델 · IFC` 다.**
파라메트릭은 그 위에 얹는 층이지 대체가 아니다. 맨홀 도면 모델링은 **파이프라인을
검증하는 과정**이지 그 자체가 목적이 아니다.
순서를 헷갈리면 안 된다.
```
DWG 를 읽어 3D 를 세운다 ← 본체. 지금 여기
그 위에
치수를 파라미터로 뽑아 바꾼다 ← 이 문서
그 위에
바꾼 결과가 맞는지 평가한다 ← 설계 도구가 되는 지점
```
---
## 1. 사용자가 원하는 것
> 설계란, 비슷한 도면을 가져와 **새 환경에 맞게 치수를 바꿔보고**, 맞는지 검토하고,
> 다시 바꾸는 **반복 작업**이다.
그래서 필요한 것은 이것뿐이다.
```
값을 바꾼다 → 모델이 다시 만들어진다
```
**속도는 요구사항이 아니다.** 전체 재생성으로 충분하다(현재 28파트 1~2분).
실시간 프리뷰·부분 재생성은 있으면 좋은 것이지 필수가 아니다 — 우선순위 뒤로.
### 모델러에 패밀리 개념이 없다는 것
EGModeler 는 Revit 패밀리 같은 것이 없다. 매번 새로 만들어야 한다.
**그래도 된다 — 패밀리를 모델러가 아니라 우리가 들고 있으면 된다.**
| Revit | 우리 |
|---|---|
| 패밀리 `.rfa` | `assemble()` 조립 로직 |
| 타입 파라미터 | `params.json` |
| 인스턴스 배치 | egm 재생성 |
모델러는 **덤 렌더러**다. 형상 지능은 전부 IR 위쪽에 둔다. 지금 계층 구조가
이미 그렇게 생겼다 — `core/*` 는 3D 를 모르고 `emit/*` 만 모델러 사정을 안다.
---
## 2. D-Cubed 검토 결과 — 쓰지 않는다
Siemens PLM 의 CAD 부품 라이브러리. 원래 영국 D-Cubed Ltd, 2004년 UGS 인수.
| 컴포넌트 | 하는 일 |
|---|---|
| **2D DCM** | 2D 스케치 구속 솔버 (치수·평행·접선·동심) |
| **3D DCM** | 3D 어셈블리 구속 솔버 |
| AEM / CDM / PGM | 기구학 · 간섭검사 · 2D 프로파일 연산 |
SolidWorks · Solid Edge · NX 가 쓴다.
**쓰지 않기로 한 이유**
- CAD 벤더용 **상용 C++ SDK**. Siemens 와 개별 계약해야 하고 공개 가격이 없다
- 우리는 Node 다. FFI/N-API 바인딩을 직접 짜야 한다
- **무엇보다, 우리 문제에 구속 솔버가 거의 필요 없다** (아래 3절)
오픈소스 대안 (필요해질 때 검토)
| | 라이선스 | 비고 |
|---|---|---|
| **planegcs** (FreeCAD 스케처 솔버) | LGPL | Eigen 기반. WASM 포팅본이 있다고 알고 있으나 **미확인** |
| **SolveSpace `slvs`** | GPL-3 | C 라이브러리 + Python 바인딩. 2D·3D |
GPL 배포 조건 확인 필요. `vendor/acadrust-dwg` MPL-2.0 을 이미 다루고 있으니 낯선 문제는 아니다.
---
## 3. 구속 솔버가 필요 없는 이유
파라메트릭에는 성격이 다른 두 가지가 있다.
```
① 명시적 (history-based) 치수 = 프로그램의 입력. 값 → 계산 → 형상
OpenSCAD · Grasshopper · 그리고 지금 우리 파이프라인
② 변분/구속 (variational) 엔티티 + 구속을 선언하고 솔버가 위치를 찾는다
SolidWorks 스케치 · D-Cubed
```
요구사항은 **①로 거의 다 된다.** 증거가 이미 코드에 있다 —
`drawings/manhole1-trans.mjs` 의 외곽 챔퍼 중간 단면은 상수가 아니라 **식**이다.
```js
const t = (CH1 - CH0) / (CH2 - CH0);
const oY = Math.round(1350 + (1650 - 1350) * t); // 1550
const oZ = Math.round(-4907 + (-4608 + 4907) * t); // -4708
```
②가 필요한 것은 **관계가 순환할 때**다. "헌치가 두 면에 접하면서 벽 두께는
유지" 같은 것. 우리 형상은 거의 직각·45° 라 순환이 잘 안 생긴다.
**결론: 파라미터 + 식 + 위상 정렬로 시작한다. 솔버는 필요한 자리를 실제로
세어본 뒤에 결정한다.**
---
## 4. 핵심 구조 변경 — `params.json` 을 파이프라인 한가운데 끼운다
이 하나가 나머지를 거의 다 푼다.
```
지금 DWG ──build.mjs──→ IR ──→ 3D 치수가 코드 안에 박혀 있다
바꿀 것 DWG ──fit──→ params.json ──build──→ IR ──→ 3D
↑ 사람이 여기만 만진다 도면을 다시 안 본다
```
**빌드가 도면을 안 보는 것이 핵심이다.**
지금 `assemble()` 은 `loop('단 면 B-B', 2700, 2750)` 로 **치수를 도면 조회 키로**
쓴다. 사용자가 2700 을 3000 으로 바꾸면 그런 폐곡선이 없어 예외가 난다.
도면에 묶여 있는 한 파라메트릭이 성립하지 않는다.
```
① fit 도면에서 값을 뽑아 params.json 을 쓴다. 출처까지 기록
② build params.json 만 읽어 IR 을 만든다. 도면 없이도 돈다
```
부수 효과가 크다.
- `git diff params.json` 이 곧 "무엇을 바꿨나"
- 설계안 A/B/C 를 파일 셋으로 두고 나란히 비교
- **새 도면 설정 비용이 첫 도면 한 번으로 끝난다** — 변형은 도면을 안 읽는다
---
## 5. 파일을 셋으로 가른다
지금 `drawings/manhole1.mjs` 한 파일이 세 가지를 섞고 있다.
```
types/전력구_박스.mjs 조립 로직. 파라미터를 받는다 ← 패밀리
params/맨홀1.json 그 도면에서 뽑은 값 ← 타입 파라미터
drawings/manhole1.mjs DWG 읽기 설정 (레이어·앵커·기준점) ← 추출 규칙
```
**"비슷한 도면 가져와 치수만 바꾼다" = 같은 type + 다른 params.json.**
```sh
cp params/맨홀1.json params/신규현장.json
# 구체_외폭 3800 → 4200, 토피 2957 → 3500 수정
node src/build.mjs --params 신규현장 # 도면 안 읽는다
```
`src/core/*` 와 `src/ir.mjs` 에 도면 고유값이 들어가면 안 된다는 기존 경계는
그대로 유지된다. `types/*` 는 도면 고유값이 아니라 **부재 종류**다.
---
## 6. 파라미터 스키마 — 값만으로는 부족하다
```jsonc
{
"구체_외폭": { "v": 3800, "src": "measured", "from": "단면 C-C #1 폭" },
"벽두께": { "v": 400, "src": "design", "min": 300, "max": 600, "step": 50 },
"구체_내폭": { "expr": "구체_외폭 - 2*벽두께" },
"헌치": { "v": 150, "src": "design" },
"샤프트": [
{ "x": 2550, "저부Z": -3157, "정상Z": 0 },
{ "x": 13250, "저부Z": -3157, "정상Z": -39 }
]
}
```
넷이 필요하다.
| 항목 | 왜 |
|---|---|
| `expr` 식 | 파생값. 위상 정렬해 평가. 솔버 불필요 |
| `src` 출처 | `measured` 는 도면과 묶여 있다. 바꾸면 "도면과 다름" 경고 |
| `min`/`max`/`step` | 유효 범위. 검증이자 나중에 UI |
| **배열** | 치수만이 아니라 **개수**가 바뀐다. 샤프트 2→3, 환기구 없음 |
배열이 특히 중요하다. `manhole1-trans.mjs` 의 샤프트를 이미
`for (const s of shafts)` 로 짜놨다 — 그대로 파라미터로 올라간다.
### `src` 3분류가 도면과의 계약이다
```
measured 도면에서 뽑았다. 바꾸면 도면과 어긋난다 → 경고하되 막지는 않는다
design 사람이 정한다. 자유롭게 바꾼다
derived 식으로 나온다. 직접 못 바꾼다
```
기존 `datum.checks` 가 이미 이 자리를 갖고 있다. 확장하면 된다.
---
## 7. 설계 도구가 되는 지점 — 규칙과 물량
치수를 바꾸는 것보다 **바꾼 결과를 즉시 평가하는 것**이 설계다.
지금 파이프라인에 없는 층이 이것이고, 실제 가치는 여기서 나온다.
```
rules.mjs 벽두께 ≥ 최소, 내공 높이 ≥ 2,100, 피복 ≥ 기준,
헌치가 부재보다 크지 않은가, 샤프트 보어가 벽 안에 있는가,
개구부 최소 치수, 케이블랙 이격
quantity.mjs 콘크리트 m³ · 굴착 m³ · 거푸집 m² · 되메움
```
**물량은 이미 공짜로 나온다.** 오늘 OBJ 를 발산 정리로 재서 0.0003% 오차로
검증했다. 그 코드가 그대로 물량 산출이다.
```
안 A 구체 3,800×4,600 콘크리트 100.5 m³ ✓ 전 규칙 통과
안 B 구체 4,200×4,600 콘크리트 108.2 m³ ✓
안 C 구체 4,200×4,600 · 벽 300 — ✗ 최소 벽두께 미달
```
이 표가 나오는 순간 도구가 쓸모를 얻는다.
---
## 8. 위험한 곳
**(a) 파라미터가 형상 위상(topology)을 바꾼다.**
벽두께를 키우다 헌치보다 커지면 꼭짓점 수가 달라져 loft 대응이 깨진다.
`validateIR` 의 꼭짓점 수 검사가 여기서 걸린다(교훈 #16). `min`/`max` 로
미리 막고, 걸리면 명확한 메시지를 낸다.
**(b) 프로파일을 파라미터로 "생성"하면 도면의 실제 형상을 잃는다.**
```
지금 loop('단 면 B-B', 2700, 2750).ring 도면이 준 실제 링
이후 box(p.스터브_외폭, p.스터브_외고, p.헌치) 파라미터로 만든 링
```
B-B 내부 링에 있는 **50mm 보도 단차** 같은 자잘한 것이 생성기로 재현이 안 되면
조용히 빠진다. **fit 단계에서 "이 링이 생성기로 재현되는가"를 대조**해야 한다.
그게 도면과의 마지막 끈이다.
#### 결정 (2026-08-06) — 3등급으로 기록한다
완전일치를 요구하지 않는다. `fit` 이 링마다 등급을 매겨 `params.json` 에 남긴다.
```
exact 꼭짓점 집합이 mm 단위로 일치 → 파라미터로 완전 대체
tolerant bbox · 면적 · 꼭짓점 수 일치 → 경고를 남기고 파라미터 사용
개별 점은 ±ε
opaque 생성기로 재현 불가 → 파라미터화하지 않는다.
도면 링을 **리터럴로 얼려** params 에 박는다
```
`opaque` 가 핵심이다. "재현 못 하면 파라미터화 안 한다"가 아니라 **"재현 못 하는
링은 값으로 얼려 넣는다"** (`src: "measured"` + `ring` 배열). 그러면
- 4절 불변식(build 가 도면을 안 본다)이 안 깨진다
- 50mm 보도 단차 같은 것이 조용히 빠지지 않는다
- 그 링은 **못 바꾸는 것**일 뿐이고, 그게 정직하다
등급 분포가 곧 "이 도면이 얼마나 파라메트릭한가" 의 척도가 된다. `opaque` 가
많으면 생성기가 부족한 것이지 도면이 이상한 게 아니다.
**(c) 재생성하면 모델러의 UID 가 전부 바뀐다.**
이름·색은 우리가 다시 붙이니 괜찮지만 사용자가 모델러에서 직접 손댄 것은 날아간다.
**"모델러에서 편집하지 않는다"를 규칙으로 못 박아야 한다.** 편집은 params 에서만.
**(d) 추출 설정은 여전히 도면당 반나절이다.**
4절의 fit/build 분리가 이 비용을 **첫 도면 한 번**으로 묶어준다. 그래서 구조
변경이 이 위험까지 같이 없앤다.
---
## 8.5. 모델러 밖 UI — 값을 바꾸고 "갱신"
요구사항으로 확정(2026-08-06). 파이프가 이미 CLI 단계 사슬이라 **새 아키텍처가
아니라 얇은 껍데기**다.
### 1단계는 이미 있다 — 읽기 전용 뷰어 (`report.mjs`)
`node src/report.mjs <도면>` → `out/<도면>.html`. 의존성 없는 한 장짜리다.
뷰별 링을 SVG 로 그리고 자기교차·꼭짓점 수·원·IR·부피를 같이 보여준다.
**만든 날 바로 값을 했다** — 맨홀 #2 의 부피가 2배로 나온 원인(`mh.outer`
자기교차)을 숫자로 찾는 데 반나절 걸렸는데, 그림에서는 빨간 X 두 개로 즉시
보인다(교훈 #18).
`build.mjs` 와 **같은 `projectViews()`** 를 쓴다. 뷰어가 자기 변환을 따로 갖고
있으면 보여주는 것과 조립되는 것이 갈라진다 — **거짓말하는 뷰어는 없는 것보다
나쁘다.**
편집은 아직 안 된다. `params.json` 이 없어서 편집할 대상이 없다.
```
UI(로컬 웹앱) → params.json → build(도면 안 봄) → IR → emit/egm → run_egm.py → 모델러
↘ three.js 프리뷰 (9절·나중)
```
- **입력 위젯이 공짜로 나온다.** 6절의 `min`/`max`/`step`/`src` 가 그대로
슬라이더·범위·경고다. 그러라고 스키마를 그렇게 잡았다.
- **갱신 버튼이 검증을 태워야 도구가 된다.** 단순 실행이면 폼이고,
`validateIR` → `rules.mjs`(7절) → `measure.mjs` 기준선까지 통과 못 하면
거부하면 설계 도구다. 여기가 값이 나오는 지점이다.
- 재생성 1~2분. 버튼 누르고 기다리는 UX 로 견딘다(1절).
못 박을 것 둘.
```
갱신 = 씬 초기화 포함. Esc → SelAll → Delete → Esc. `!Cancel` 은 쓰지 않는다
모델러에서 직접 편집 금지. 8절 (c) — 갱신하면 UID 가 전부 바뀐다. 편집은 params 에서만
```
**순서상 위치: 10절 1~4 뒤.** 지금 만들면 편집할 `params.json` 이 없다.
---
## 9. 나중에 (필수 아님)
- **브라우저 프리뷰** — IR 은 이미 도구 중립이다. three.js + CSG(`manifold-3d` WASM 이
빠르다고 알고 있으나 **미확인**)로 모델러 없이 확인. 속도가 요구사항이 아니므로 뒤로.
- **선택 재생성** — 파라미터 → 파트 의존 그래프로 바뀐 파트만 다시. 마찬가지로 뒤로.
- **구속 솔버** — 1~5 를 하고 나서 정말 필요한 자리를 세어본 뒤 결정.
- **모델 → 도면 역방향** — DWG writer 가 필요하다. 현재 범위 밖.
---
## 10. 1차 목표 — **전부 됐다**
```
✔ 1. params.json 스키마 확정 v / expr / src / grade / min·max·step / 배열
✔ 2. build.mjs 를 fit / build 로 분리 --params 면 도면을 아예 안 읽는다
✔ 3. manhole1 을 params 로 전환 egm 명령 목록이 **바이트 단위로 동일**했다
✔ 4. 파라미터 바꿔 재생성 시연 본체_외폭 3800→4200, 종헌치 150→200
✔ 5. 물량 measure.mjs 가 재질별로 낸다
```
3번이 좋은 시험대다. `manhole1-trans.mjs` 에 이미 이름이 뚜렷한 값들이 있다 —
**챔퍼 200×200, 헌치 150×150, 벽두께 200, 보어 Ø900, 칼라 900, 토피 2,957.**
5번은 [`../src/measure.mjs`](../src/measure.mjs) 로 **먼저 끝났다.** 실측·기준선
대조·재질별 물량이 돈다. 남은 것은 굴착·거푸집·되메움 같은 파생 물량이다.
---
## 11. 순서 결정 (2026-08-06)
**본체(A)를 좁혀서 먼저, 그 다음 파라메트릭(B).** 근거 셋.
**① 인스턴스 1개로 추상화를 얼리게 된다.**
5절의 가치 명제는 "같은 type + 다른 params" 인데 도면이 하나뿐이라 type/params
경계를 **검증할 두 번째 인스턴스가 없다.** 맨홀 #2(격실·환기구·침사조)가 스키마에
요구할 것 — 배열이 아니라 *선택적 존재*("환기구 없음"), 중첩 격실, 구간별 단면
배정표 — 은 지금 `manhole1-trans` 3파트에 안 보인다.
재작업이 대칭이 아니다. A 먼저 → `assemble()` 본문이 늘어 변환 비용이 커지지만
그 변환은 10절 3번 그 자체다(어차피 한 번은 한다). B 먼저 → 스키마를 **두 번** 짠다.
**② 8절 (b) 결정이 A 에 의존한다.**
"어디까지 엄격하게 대조하나"는 *불규칙한 링이 몇 개냐*에 달렸다. 지금 아는
사례(B-B 내부 50mm 보도 단차, C-C 외곽 샤프트 융합, 개구 기호로 정점 8,
케이블랙 정점 114)는 전부 `manhole1-trans` 밖에서 나왔다. 등급 기준은 정했지만
`ε` 와 등급 분포는 표본을 세어본 뒤에 확정한다.
**③ CIRCLE 파서가 B 의 전제다.**
`manhole1-trans.mjs` 의 `BORE_R = 450` 은 원본 엔티티에서 눈으로 읽어 손으로 적은
값이다. 8절 (b) 가 말하는 "도면과의 마지막 끈"이 보어에는 **아예 없다.**
```
✔ 0. measure.mjs B 에서 유일하게 당겨온 항목(10절 5번).
앞으로 만드는 모든 파트의 회귀 검사가 된다
1. CIRCLE 파서 → circle() 헬퍼 손으로 적은 R450 을 대조로. Φ100 ×5 도 풀린다
2. manhole1-trans 를 본편으로 통합 기초·잡석 이관. 지금 반쪽 모델이 둘이다
3. 맨홀 #2 + 출구 스터브 파라미터 표본을 대표성 있게
4. → 10절 1~4
5. → 8.5절 UI
```
---
## 부록 — 2026-08-06 기준 현재 상태
**되는 것**
| | |
|---|---|
| `manhole1` | 11파트. 전이부는 X=250 계단 근사, **지붕 개구 없음**(결함) |
| `manhole1-trans` | 3파트. 스터브 + 챔퍼 전이 + 본체 + 종방향 헌치 + 샤프트 2기(Ø900 원형 보어) |
| 새 IR 연산 | `loft` (2단면, `Loft C=True`), `cylinder` (Z축 한정, `Cylinder S=True`) |
| 새 유틸 | `normalizeRing` — loft 꼭짓점 대응용. `addProfile` 이 자동 적용 |
| 검증 | 부피 대조 107.1425 실측 / 107.1422 기대 (0.0003%) |
| `measure.mjs` | OBJ 실측 · 기준선 회귀 · 재질별 물량. 기준선 `baselines/manhole1-trans.volumes.json` |
| CIRCLE | `circles(뷰, 지름, 레이어)`. 보어 R450 이 손으로 적은 수에서 도면 대조로 바뀌었다 |
**안 되는 것 / 남은 것**
```
맨홀 #2 (X 15,400~18,200) 챔버·격실·환기구·침사조. 제일 크다
출구 스터브 (X 18,200~19,200) 맨홀 #2 뒤라 그전에 맨홀을 해야 자리를 얻는다
기초·잡석 manhole1.mjs 에 있다. trans 로 옮기면 됨
Ø100 관통 5개 환기구 4 · 배수관 1. 평면도의 1개는 X=17,690 으로
맨홀 #2 영역이라 그 형상을 세운 뒤에 뚫는다
이음매 T 정점 열린 변 49개. 기하는 닫힘. IFC 갈 때 재검토
IFC 출력 emit/ifc.mjs 미구현
```
---
## 12. 구현 결과 (2026-08-06)
### 만든 것
| 파일 | 하는 일 |
|---|---|
| [`../src/params.mjs`](../src/params.mjs) | 스키마 · 식 평가(위상 정렬) · 검증 · 도면 이탈 감지 |
| [`../src/fit.mjs`](../src/fit.mjs) | 도면 → `params/<이름>.json`. 출처(`from`)와 원값(`fitted`)까지 |
| [`../src/rules.mjs`](../src/rules.mjs) | 규칙 13개. 위상 8(막는다) · 설계 5(경고) |
| [`../src/volume.mjs`](../src/volume.mjs) | IR 을 격자로 평가 — **모델러 없이** "의도" 부피 |
| [`../src/ui.mjs`](../src/ui.mjs) | 편집 + 갱신. `node src/ui.mjs manhole1` → `localhost:7331` |
| [`../src/core/pick.mjs`](../src/core/pick.mjs) | 도면 조회 도우미. **`fit()` 만 쓴다** |
### 확인된 것
```
파라미터 스칼라 49 (measured 38 · design 3 · derived 8) + 배열 1(샤프트)
링 11 (opaque 8 · exact 2 · tolerant 1)
규칙 13개 중 위반 0
기하 불변 전환 전후 egm.json **바이트 동일**
도면 없이 views.json 을 치운 채 --params 로 빌드 → 역시 바이트 동일
```
### 8절 (b) 결정이 실제로 어떻게 쓰였나
`opaque` 8개가 결정의 값을 증명한다. B-B 내부(50mm 보도 단차), C-C 내부(헌치),
E-E 외곽(23꼭짓점 오목 윤곽) 같은 것은 파라미터로 재현이 안 된다. 좌표째 얼려
넣으니 **도면 없이도 형상이 그대로** 나오고, 자잘한 형상이 조용히 빠지지도 않는다.
그 링들은 못 바꿀 뿐이고, 그게 정직하다.
`exact` 2개는 개구부다 — 개구 기호의 네 모서리라 직사각형으로 완전히 재현된다.
### 규칙이 폼과 도구를 가른다
```
✗ 맨홀 내벽이 뒤집혔다: 16900 ≥ 16700 (벽두께 1500)
(맨홀 벽두께가 절반을 넘으면 내부가 사라진다)
```
벽두께를 1,500 으로 바꾸면 **빌드가 거부한다**(exit 1). 모델러까지 가서 비틀린
면으로 만나는 것보다 훨씬 싸다. 설계 규칙(내공 높이·토피·최소 벽두께)은 경고만
하고 사람이 판단한다.
### 도면 이탈은 막지 않고 남긴다
```
⚠ 도면에서 뽑은 값이 바뀌어 있다 — 이 모델은 원 도면과 다르다
본체_외폭 도면 3800 → 지금 4200 C-C 구체 외폭
```
설계를 바꾸는 게 이 층의 목적이니 막지 않는다. 다만 "도면대로 만든 모델"인 척하게
두면 안 된다.
### 그림 위에서 치수를 고친다
`render.mjs --dims` 가 **파라미터를 가리키는 치수선**을 렌더에 얹는다. 2D 도면이
치수를 적어 형상을 증명하듯이, 우리 렌더도 "이 길이가 이 파라미터다" 를 그림에서
바로 보이게 한다.
```
span 두 점 사이 **길이** 연장선 + 치수선 + 이름 상자
level 한 좌표의 **위치** 그 자리를 가로지르는 선 + 이름 상자
```
둘로 나눈 이유가 있다. 파라미터의 절반은 길이가 아니라 **좌표**다(`본체_상단Z`
같은 표고). 그런 것을 억지로 길이로 그리면 거짓말이 된다 — 표고선으로 그린다.
같이 나오는 `model-<축>.dims.json` 이 각 라벨의 자리를 **캔버스 비율**로 담는다.
UI 가 그 자리에 `<input>` 을 얹으므로 **그림에서 값을 고칠 수 있다**. 비율이라
창 크기가 바뀌어도 따라간다. 표와 그림의 입력칸은 서로 동기화된다.
라벨이 많으면 겹치므로 위에서부터 훑으며 겹치는 것을 아래로 민다. 치수선 자체는
안 움직인다 — 라벨만.
**47개**가 그림에 올라간다. 빠진 둘(`커터여유`·`면겹침_여유`)은 형상이 아니라
수치 여유값이라 패널에만 둔다.
### UI 는 정말로 얇았다
파이프가 이미 CLI 단계 사슬이라, UI 가 한 일은 `params.json` 편집 + 그 사슬을
순서대로 돌리는 것뿐이다. 갱신 사슬:
```
build --params → emit → run_egm --reset --export → measure --ir → render --dims
```
`--reset` 이 필수다. 안 비우면 갱신할 때마다 객체가 쌓여 `modelCount` 대조가
무의미해진다. 다만 **연달아 여러 번 하면 안 된다** — 초기화를 10번쯤 반복했더니
모델러 파이프가 죽었다. 갱신 1회당 1번이면 문제없다.
`min`/`max`/`step` 이 그대로 입력 위젯이 됐고, `src` 는 태그, `fitted` 는 변경
표시가 됐다. **그러라고 6절에서 그렇게 설계했던 것이 그대로 맞았다.**
### 남은 것
- **맨홀 #2 부피 9.32 손실** — 우리 IR 은 맞고(74.68) 모델러가 흘린다(65.36).
원인은 면으로만 맞닿은 union (교훈 #19). 제대로 된 해법은 맨홀을 **수평 단면에서
Z 로 쌓는 것** — 조각이 Z 로 진짜 겹치고 평면 헌치도 같이 따라온다
- 외부 헌치 5개 (평면 좌표 확정, Z 구간 미확정)
- `quantity.mjs` — 굴착·거푸집·되메움 같은 파생 물량
+310
View File
@@ -0,0 +1,310 @@
# 로드맵 — 일반 도면 세트 → 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 | — | 기준선 없음. 통합 후 뜬다 |
+535
View File
@@ -0,0 +1,535 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>교각 P9 — 촬영 런시트 &amp; 대본</title>
<style>
html{color-scheme:light dark}
/* ── 팔레트는 모델 자체에서 가져왔다 ────────────────────────────
코핑 주황 · 기둥 파랑 · 기초 초록 · 말뚝 보라. 그 색이 곧 이 영상의 주인공이다. */
:root{
--ground:#F5F2EE; --surface:#FFFFFF; --slab:#181C22; --slab-ink:#D9E2ED;
--line:#DED7CE; --line-soft:#EBE5DC;
--ink:#191E25; --ink-2:#4A5462; --ink-3:#7C8794;
--cap:#C2732F; --col:#3E7CBC; --ftg:#4E9750; --pil:#8B4A9A; --brg:#C4413F;
--ok:#3E8F5C; --warn:#B07E14;
--accent:var(--cap);
--mono:ui-monospace,"Cascadia Mono","Consolas","D2Coding",monospace;
--sans:"Pretendard","Malgun Gothic","Apple SD Gothic Neo",system-ui,sans-serif;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--ground:#0E1116; --surface:#151A21; --slab:#0A0D11; --slab-ink:#CBD6E3;
--line:#252C36; --line-soft:#1C222B;
--ink:#DDE5EE; --ink-2:#94A2B3; --ink-3:#65717F;
--cap:#D68444; --col:#5894D0; --ftg:#74B06C; --pil:#A968B8; --brg:#DC5A58;
--ok:#5FBF7F; --warn:#E0B33C;
}
}
:root[data-theme="dark"]{
--ground:#0E1116; --surface:#151A21; --slab:#0A0D11; --slab-ink:#CBD6E3;
--line:#252C36; --line-soft:#1C222B;
--ink:#DDE5EE; --ink-2:#94A2B3; --ink-3:#65717F;
--cap:#D68444; --col:#5894D0; --ftg:#74B06C; --pil:#A968B8; --brg:#DC5A58;
--ok:#5FBF7F; --warn:#E0B33C;
}
*{box-sizing:border-box}
body{margin:0; background:var(--ground); color:var(--ink);
font:15.5px/1.7 var(--sans); -webkit-font-smoothing:antialiased}
.wrap{max-width:1080px; margin:0 auto; padding:36px 22px 90px}
/* ── 머리 ────────────────────────────────────────────────── */
header{display:flex; flex-wrap:wrap; align-items:flex-end; gap:18px 28px;
padding-bottom:20px; border-bottom:2px solid var(--ink)}
h1{margin:0; font-size:29px; line-height:1.2; letter-spacing:-.02em; text-wrap:balance}
h1 small{display:block; font:600 12px/1.6 var(--mono); letter-spacing:.16em;
color:var(--accent); text-transform:uppercase; margin-bottom:7px}
.facts{margin-left:auto; display:flex; gap:22px; font:600 12px/1.5 var(--mono);
font-variant-numeric:tabular-nums; color:var(--ink-3)}
.facts b{display:block; font-size:19px; color:var(--ink); letter-spacing:-.01em}
/* ── 탭 ──────────────────────────────────────────────────── */
.tabs{display:flex; flex-wrap:wrap; gap:2px; margin:22px 0 0;
border-bottom:1px solid var(--line)}
.tabs button{appearance:none; border:0; background:none; cursor:pointer;
font:600 13.5px/1 var(--sans); color:var(--ink-3);
padding:12px 15px; border-bottom:2px solid transparent; margin-bottom:-1px;
transition:color .12s}
.tabs button:hover{color:var(--ink-2)}
.tabs button[aria-selected="true"]{color:var(--ink); border-bottom-color:var(--accent)}
.tabs button:focus-visible{outline:2px solid var(--accent); outline-offset:-2px; border-radius:3px}
.panel{display:none; padding-top:26px}
.panel.on{display:block}
/* ── 타임라인 띠 ─────────────────────────────────────────── */
.tl{display:flex; height:34px; border:1px solid var(--line); border-radius:3px;
overflow:hidden; margin-bottom:6px}
.tl div{position:relative; display:grid; place-items:center; min-width:0;
font:600 10.5px/1 var(--mono); color:#fff; letter-spacing:.02em;
border-right:1px solid rgba(0,0,0,.28)}
.tl div:last-child{border-right:0}
.tl span{opacity:.92; white-space:nowrap; overflow:hidden}
.tl-key{display:flex; justify-content:space-between; font:600 11px/1 var(--mono);
color:var(--ink-3); font-variant-numeric:tabular-nums; margin-bottom:30px}
/* ── 컷 ──────────────────────────────────────────────────── */
.cut{display:grid; grid-template-columns:118px 1fr; gap:0 22px;
padding:22px 0; border-top:1px solid var(--line-soft)}
.cut:first-of-type{border-top:0}
.rail{font:600 12px/1.55 var(--mono); font-variant-numeric:tabular-nums;
color:var(--ink-3); padding-top:3px}
.rail .tc{display:block; font-size:15px; color:var(--ink); letter-spacing:-.02em}
.rail .dur{display:block}
.rail .clip{display:inline-block; margin-top:8px; padding:2px 7px; border-radius:3px;
background:var(--line-soft); color:var(--ink-2); font-size:11px; letter-spacing:.04em}
.cut h3{margin:0 0 3px; font-size:17.5px; letter-spacing:-.01em}
.cut .sub{margin:0 0 13px; color:var(--ink-3); font-size:13px}
.hookmark{color:var(--accent)}
pre{margin:0 0 13px; background:var(--slab); color:var(--slab-ink);
border-radius:4px; padding:12px 14px; overflow-x:auto;
font:12.5px/1.75 var(--mono)}
pre .c{color:#6E7D8E}
pre .h{color:#E9B872}
.say{margin:0; padding:14px 16px 14px 18px; background:var(--surface);
border:1px solid var(--line); border-left:3px solid var(--accent);
border-radius:0 4px 4px 0; font-size:15px}
.say b{color:var(--accent)}
.say .beat{display:block; height:9px}
.screen{margin:12px 0 0; padding:0; list-style:none;
font:12.5px/1.85 var(--mono); color:var(--ink-2)}
.screen li{padding-left:17px; position:relative}
.screen li:before{content:"›"; position:absolute; left:3px; color:var(--accent); font-weight:700}
.screen b{color:var(--ink); font-weight:600}
/* ── 표 ──────────────────────────────────────────────────── */
table{width:100%; border-collapse:collapse; margin:0 0 24px; font-size:13.5px}
th,td{text-align:left; padding:9px 12px; border-bottom:1px solid var(--line-soft);
vertical-align:top}
th{font:600 11px/1.5 var(--mono); letter-spacing:.09em; text-transform:uppercase;
color:var(--ink-3); border-bottom:1px solid var(--line)}
td code{font:12.5px/1.6 var(--mono); color:var(--accent)}
.num{font-variant-numeric:tabular-nums; text-align:right; white-space:nowrap}
.tw{overflow-x:auto}
h2{margin:34px 0 12px; font-size:20px; letter-spacing:-.01em}
h2:first-child{margin-top:0}
p{margin:0 0 14px; max-width:68ch}
.lede{color:var(--ink-2); font-size:14.5px}
.chip{display:inline-block; padding:1px 8px; border-radius:3px; font:600 11px/1.7 var(--mono);
letter-spacing:.04em}
.c-cap{background:color-mix(in srgb,var(--cap) 18%,transparent); color:var(--cap)}
.c-ok{background:color-mix(in srgb,var(--ok) 18%,transparent); color:var(--ok)}
.c-warn{background:color-mix(in srgb,var(--warn) 20%,transparent); color:var(--warn)}
.note{border:1px solid var(--line); border-radius:4px; padding:14px 16px;
background:var(--surface); margin:0 0 20px}
.note b{display:block; margin-bottom:4px}
@media (max-width:660px){
.cut{grid-template-columns:1fr; gap:10px}
.rail{display:flex; gap:12px; align-items:baseline; padding:0}
.facts{margin-left:0}
}
@media (prefers-reduced-motion:reduce){*{transition:none!important}}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1><small>촬영 런시트 · 컷별 대본</small>2D 도면 한 장에서<br>교각 하나를 세운다</h1>
<div class="facts">
<span><b>11</b>컷</span>
<span><b>5:54</b>총길이</span>
<span><b>75</b>부재</span>
<span><b>0.0000</b>오차</span>
</div>
</header>
<div class="tabs" role="tablist">
<button role="tab" aria-selected="true" data-p="p1">컷 순서 · 대본</button>
<button role="tab" aria-selected="false" data-p="p2">실행 명령</button>
<button role="tab" aria-selected="false" data-p="p3">화면 체크</button>
<button role="tab" aria-selected="false" data-p="p4">사고 대응</button>
<button role="tab" aria-selected="false" data-p="p5">업로드</button>
</div>
<!-- ══════════ 1. 컷 순서 · 대본 ══════════ -->
<section class="panel on" id="p1" role="tabpanel">
<div class="tl">
<div style="flex:14; background:var(--cap)"><span>HOOK</span></div>
<div style="flex:24.4; background:var(--ink-3)"><span>01 도면</span></div>
<div style="flex:11.1; background:var(--col)"><span>02</span></div>
<div style="flex:12.9; background:var(--col)"><span>03</span></div>
<div style="flex:48.5; background:var(--col)"><span>04 진단</span></div>
<div style="flex:24.8; background:var(--ftg)"><span>05</span></div>
<div style="flex:16.7; background:var(--ftg)"><span>06</span></div>
<div style="flex:82; background:var(--cap)"><span>07 모델링</span></div>
<div style="flex:25.3; background:var(--pil)"><span>08</span></div>
<div style="flex:44.3; background:var(--pil)"><span>09 IFC</span></div>
<div style="flex:50.1; background:var(--brg)"><span>10 마무리</span></div>
</div>
<div class="tl-key"><span>0:00</span><span>도면 → 진단 → 파라미터 → 모델링 → 검증 → IFC</span><span>5:54</span></div>
<div class="cut">
<div class="rail"><span class="tc">0:00</span><span class="dur">14.0초</span>
<span class="clip">07 발췌</span></div>
<div>
<h3 class="hookmark">HOOK — 결과부터 보여준다</h3>
<p class="sub">07번에서 교각이 서는 12~15초만 잘라 맨 앞에. 자막·설명 없이 화면만.</p>
<p class="say">이거, 사람이 만든 모델이 아닙니다.<span class="beat"></span>
2D 도면 한 장을 읽어서, 명령 한 줄로 자동으로 서는 겁니다.<span class="beat"></span>
교각 하나. 부재 <b>일흔다섯 개</b>. 오차 <b>0.0000</b>.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">0:14</span><span class="dur">24.4초</span>
<span class="clip">01.mp4</span></div>
<div>
<h3>도면 — 출발점</h3>
<p class="sub">CAD 에서 교각 일반도. 뷰를 훑고 표까지.</p>
<p class="say">시작은 이 도면입니다. 교각 일반도 한 장.<span class="beat"></span>
A1 한 장에 평면도, 정면도, 단면도, 기초 평면도, 그리고 표 세 개.
토목 하시는 분들껜 익숙한 화면이죠.<span class="beat"></span>
그런데 이 파일 하나에 <b>도면이 놓이는 자리가 열일곱 개</b> 있습니다.
옛 작업 흔적이 그대로 쌓여 있는 거죠.
<b>출력용 도곽이 씌워진 건 그중 하나뿐</b>입니다.<span class="beat"></span>
어느 게 진짜인지 고르는 것부터가 일입니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">0:38</span><span class="dur">11.1초</span>
<span class="clip">02.mp4</span></div>
<div>
<h3>뷰를 가른다</h3>
<pre><span class="c">1단계</span> node src/run.mjs pier-p9</pre>
<p class="say">첫 명령입니다. 도면을 읽고 뷰를 가릅니다.<span class="beat"></span>
평면도 <b>14,000 × 3,200</b>. 도면에 적힌 치수와 정확히 같습니다.
여기가 안 맞으면 뒤는 볼 것도 없습니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">0:50</span><span class="dur">12.9초</span>
<span class="clip">03.mp4</span></div>
<div>
<h3>끊김 진단</h3>
<pre><span class="c">2단계</span> node src/gaps.mjs pier-p9</pre>
<p class="say">다음은 끊김 진단입니다.<span class="beat"></span>
윤곽이 안 닫히는 이유가 <b>레이어를 잘못 골랐다</b>인지
<b>도면에 선이 없다</b>인지를 가릅니다.<span class="beat"></span>
앞은 설정 한 줄, 뒤는 도면 수정. 대응 비용이 백 배 차이 납니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">1:02</span><span class="dur">48.5초</span>
<span class="clip">04.mp4</span></div>
<div>
<h3>도면이 말하는 네 가지 방식</h3>
<pre><span class="c">3단계</span> node src/build.mjs pier-p9 --loops
node src/build.mjs pier-p9 --circles
node src/build.mjs pier-p9 --blocks
node src/table.mjs pier-p9 --title "교량받침 ELEVATION TABLE"</pre>
<p class="say">도면은 형상으로만 말하지 않습니다.<span class="beat"></span>
닫힌 윤곽, 원, 블록, 그리고 표.<span class="beat"></span>
기둥은 지름 <b>2,200 원</b>으로 들어 있고,
말뚝 <b>마흔네 본</b>은 폐곡선이 아니라 <b>블록</b>입니다.
블록 배율 <b>254</b>가 곧 반지름이고요. 도면 주기의 Φ508 과 맞습니다.<span class="beat"></span>
받침 여섯 기의 적층 높이와 절대표고는 도면 어디에도 안 그려져 있습니다.
<b>표에만</b> 있습니다. 그래서 표도 읽습니다 — 이름표 아래를 찾아서, 병합 셀까지.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">1:51</span><span class="dur">24.8초</span>
<span class="clip">05.mp4</span></div>
<div>
<h3>도면이 파라미터가 된다</h3>
<pre><span class="c">4단계</span> node src/fit.mjs pier-p9
<span class="c">5단계</span> node src/build.mjs pier-p9</pre>
<p class="say">여기서 도면이 <b>파라미터</b>가 됩니다.<span class="beat"></span>
서른 개 중 스물한 개는 도면에서 잰 값,
다섯 개는 도면이 안 주는 설계값,
네 개는 식으로 나오는 값입니다.<span class="beat"></span>
어느 게 어디서 왔는지 전부 기록됩니다.
<b>도면에서 읽은 척하지 않는 것</b> — 이게 핵심입니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">2:16</span><span class="dur">16.7초</span>
<span class="clip">06.mp4</span></div>
<div>
<h3>3D 툴 없이 부피부터</h3>
<pre><span class="c">6단계</span> node src/volume.mjs pier-p9</pre>
<p class="say">3D 툴을 켜기 전에 부피부터 냅니다.<span class="beat"></span>
<b>657.0569</b> 세제곱미터.<span class="beat"></span>
이게 <b>우리 의도</b>입니다. 이따 모델러가 만든 것과 대조할 기준이죠.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">2:32</span><span class="dur">82.0초</span>
<span class="clip">07.mp4</span></div>
<div>
<h3 class="hookmark">모델러에 교각이 선다</h3>
<pre><span class="c">7단계</span> node src/emit/egm.mjs out/pier-p9.ir.json
python src/run_egm.py out/pier-p9.egm.json --export=out/pier-p9.obj</pre>
<p class="say">이제 실행입니다.<span class="beat"></span>
78개 파트, <b>329단계</b> 명령이 모델러로 갑니다.<span class="beat"></span>
코핑, 원형 기둥 두 본, 확대기초 두 기, 버림,
강관말뚝 마흔네 본,
받침 여섯 기 — 받침콘크리트, 무수축 모르타르, 탄성받침, 솔플레이트까지.<span class="beat"></span>
사람 손은 안 들어갑니다.<span class="beat"></span>
<em>(말 없이 20초 — 세워지는 걸 보여준다)</em><span class="beat"></span>
중요한 건 속도가 아닙니다.<span class="beat"></span>
같은 명령을 다시 돌리면 <b>같은 모델</b>이 나온다는 것.
도면이 바뀌면 파라미터만 고쳐서 다시 세울 수 있다는 것입니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">3:54</span><span class="dur">25.3초</span>
<span class="clip">08.mp4</span></div>
<div>
<h3>의도 vs 결과 — 이 영상의 핵심</h3>
<pre><span class="c">8단계</span> egm status
node src/measure.mjs pier-p9 --ir</pre>
<p class="say">그리고 검증입니다.<span class="beat"></span>
<b>우리가 뜻한 부피</b>와 <b>모델러가 실제로 만든 부피</b>를 부재마다 대조합니다.<span class="beat"></span>
일흔다섯 개 전부 차이 <b>0.0000</b>. 열린 변 0.<span class="beat"></span>
부피가 맞으면 형상이 맞습니다. 눈으로 보고 「비슷하네」로 끝내지 않습니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">4:20</span><span class="dur">44.3초</span>
<span class="clip">09.mp4</span></div>
<div>
<h3>IFC 로 넘긴다</h3>
<pre><span class="c">9단계</span> node src/emit/ifc.mjs out/pier-p9.ir.json</pre>
<p class="say">마지막은 IFC 입니다.<span class="beat"></span>
IfcBeam, IfcColumn, IfcFooting, <b>IfcPile 마흔네 개</b>, IfcPlate, IfcDiscreteAccessory.<span class="beat"></span>
메시로 안 내보냈습니다. 원기둥은 <b>진짜 원</b>으로 나갑니다.
그래야 받는 쪽에서 「왜 지름이 508이 아니냐」를 다시 겪지 않습니다.<span class="beat"></span>
절대 위치는 <b>IfcMapConversion</b> 으로 넣었습니다.
측량좌표와 방위각은 받침배치도 표에서 가져왔고요.<span class="beat"></span>
뷰어에서 열면 이렇게 나옵니다.</p>
</div>
</div>
<div class="cut">
<div class="rail"><span class="tc">5:04</span><span class="dur">50.1초</span>
<span class="clip">10.mp4</span></div>
<div>
<h3>그림으로도 본다 · 마무리</h3>
<pre><span class="c">10단계</span> node src/render.mjs pier-p9 --png --width 900</pre>
<p class="say">부피가 맞아도 형상이 맞는 건 아닙니다. 그래서 그림으로도 봅니다.<span class="beat"></span>
정투영 세 방향. <b>검은 자리는 그 방향으로 재료가 없다</b>는 뜻입니다.
관통구나 빠진 부재가 바로 드러나죠.<span class="beat"></span>
정리하면 — 도면 한 장에서 부재 일흔다섯 개, 657 세제곱미터,
오차 0.0000, IFC 까지.<span class="beat"></span>
사람이 판단한 건 <b>「무엇을 어떻게 조립할까」 하나뿐</b>입니다.
나머지는 전부 도면이 말한 겁니다.<span class="beat"></span>
다음 편에서는 <b>꺾인 교각</b>을 해보겠습니다.</p>
</div>
</div>
</section>
<!-- ══════════ 2. 실행 명령 ══════════ -->
<section class="panel" id="p2" role="tabpanel">
<p class="lede">작업 디렉터리 <code>d:\MYCLAUDE_PROJECT\dwg2model</code> · PowerShell.
씬은 비워 뒀고 기준선은 문양 제외본으로 갱신돼 있다. 0번부터 순서대로.</p>
<h2>0 · 촬영 전 확인 <span class="chip c-warn">필수</span></h2>
<pre>egm guide | python -c "import sys,json;print('명령',len(json.loads(sys.stdin.buffer.read().decode('utf-8-sig'))))"</pre>
<p><b>208</b> 이어야 한다. <b>39</b> 면 뷰어 모드 — 모델링 명령이 등록조차 안 된다.
모델러 UI 에서 <code>Assistive Features → Working Mode Setting → Viewer &amp; Modeler Use</code>.</p>
<h2>1–10 · 본편</h2>
<pre><span class="c"># 1 도면 읽기 — 뷰 분리 · 표 색인 [02.mp4]</span>
node src/run.mjs pier-p9
<span class="c"># 2 끊김 진단 [03.mp4]</span>
node src/gaps.mjs pier-p9
<span class="c"># 3 도면이 말하는 네 방식 [04.mp4]</span>
node src/build.mjs pier-p9 --loops
node src/build.mjs pier-p9 --circles
node src/build.mjs pier-p9 --blocks
node src/table.mjs pier-p9 --title "교량받침 ELEVATION TABLE"
<span class="c"># 4+5 파라미터 → 조립 스펙 [05.mp4]</span>
node src/fit.mjs pier-p9
node src/build.mjs pier-p9
<span class="c"># 6 모델러 없이 부피 — 「의도」 [06.mp4]</span>
node src/volume.mjs pier-p9
<span class="c"># 7 번역 · 실행 — 교각이 선다 [07.mp4] ★</span>
node src/emit/egm.mjs out/pier-p9.ir.json
python src/run_egm.py out/pier-p9.egm.json --export=out/pier-p9.obj
<span class="c"># 8 의도 vs 결과 [08.mp4] ★</span>
egm status
node src/measure.mjs pier-p9 --ir
<span class="c"># 9 IFC [09.mp4]</span>
node src/emit/ifc.mjs out/pier-p9.ir.json
<span class="c"># 10 그림 [10.mp4]</span>
node src/render.mjs pier-p9 --png --width 900</pre>
<h2>덤 · 파라메트릭 (다음 편 예고용)</h2>
<pre>node src/ui.mjs pier-p9 --port 7333
<span class="c"># 브라우저 http://localhost:7333 → 값 수정 → 갱신 → 검증 사슬이 돈다</span></pre>
<div class="note">
<b>미관 문양은 뺐다</b>
단면(50 띠 + 하단 100 돌출)은 도면에서 나왔지만 정면도의 내부 능선을 아직 못 살렸다.
<code>params/pier-p9.json</code> 의 <code>미관문양_포함</code> 을 <code>1</code> 로 바꾸면 다시 선다.
지금 기준선은 <b>문양 제외 · 75파트 · 657.0569 m³</b> 기준이다.
</div>
</section>
<!-- ══════════ 3. 화면 체크 ══════════ -->
<section class="panel" id="p3" role="tabpanel">
<p class="lede">컷마다 <b>화면에 반드시 보여야 할 것</b>. 안 나오면 다시 찍는다.</p>
<div class="tw">
<table>
<tr><th>컷</th><th>화면에 나와야 하는 것</th><th class="num">길이</th></tr>
<tr><td><b>HOOK</b></td><td>모델러 뷰포트에 교각이 서는 순간. 색이 부재별로 갈려 있어야 한다 —
코핑 주황 · 기둥 파랑 · 기초 초록 · 말뚝 보라</td><td class="num">14.0</td></tr>
<tr><td><b>01</b></td><td>도곽 · 평면도 · 정면도 · 단면도 · 기초평면도 · 표. 한 화면에 다 보이는 장면 하나</td><td class="num">24.4</td></tr>
<tr><td><b>02</b></td><td><code>평면도 14000 3200 ✓</code> 와 <code>✓ 검증 통과</code>, 그리고 <code>표 3개</code></td><td class="num">11.1</td></tr>
<tr><td><b>03</b></td><td>「레이어 오분류 0 / 선 누락 n」 갈라지는 줄</td><td class="num">12.9</td></tr>
<tr><td><b>04</b></td><td><code>Ø2200</code> 원 2개 · <code>CSPILEH 배율 254</code> 44개 ·
표 6행 18열(NO./h1~h4/A/B/S1/S2)</td><td class="num">48.5</td></tr>
<tr><td><b>05</b></td><td><code>measured 21 · design 5 · derived 4</code> · <code>최종 산출 75</code></td><td class="num">24.8</td></tr>
<tr><td><b>06</b></td><td><code>합계 657.0569</code></td><td class="num">16.7</td></tr>
<tr><td><b>07</b></td><td><code>파트 78 → 단계 329</code> · 뷰포트에 부재가 쌓이는 과정 ·
끝에 <code>modelCount 75</code></td><td class="num">82.0</td></tr>
<tr><td><b>08</b></td><td><b>차이 0.0000 열이 쭉 이어지는 화면</b> · <code>열린변 0</code> · 재질별 물량</td><td class="num">25.3</td></tr>
<tr><td><b>09</b></td><td><code>IFCPILE 44</code> 등 클래스 목록 · IFC 뷰어에서 열린 모델</td><td class="num">44.3</td></tr>
<tr><td><b>10</b></td><td>정투영 X·Y·Z 세 장. 검은 배경과 부재 색이 대비되는 것</td><td class="num">50.1</td></tr>
</table>
</div>
<h2>영상에서 쓸 숫자</h2>
<div class="tw">
<table>
<tr><th>말할 것</th><th>값</th></tr>
<tr><td>도곽 한 장 안에</td><td>뷰 7 + 표 3.<br>
그런데 파일에는 도면 자리가 <b>17개</b> — 도곽이 씌워진 건 <b>1개</b>,
나머지는 구조선만 남은 옛 작업 흔적</td></tr>
<tr><td>단위 확정</td><td>독립 근거 4개로 <b>1단위 = 1 mm</b></td></tr>
<tr><td>파단선</td><td>그려진 기둥 6,100 ↔ 실제 <b>33,800</b>. 생략량 27,700 을 도면에서 되찾는다</td></tr>
<tr><td>말뚝</td><td>5×5=25 가 아니라 <b>22본</b> — 둘레 16 + 내부 3등분점 6</td></tr>
<tr><td>받침</td><td>형상 × 표 × 다른 도면 <b>세 겹 검산</b>, 6/6 일치</td></tr>
<tr><td>결과</td><td><b>75 파트 · 657.0569 m³ · 차이 0.0000 · 열린 변 0</b></td></tr>
</table>
</div>
</section>
<!-- ══════════ 4. 사고 대응 ══════════ -->
<section class="panel" id="p4" role="tabpanel">
<p class="lede">녹화 중 걸릴 만한 것만. 전부 실제로 겪었다.</p>
<div class="tw">
<table>
<tr><th>증상</th><th>원인</th><th>대응</th></tr>
<tr><td><code>run_egm</code> 이 「명령이 끝나지 않는다」</td>
<td>이전 명령이 물려 있다. <code>egm status</code> 의 <code>command</code> 가 남는다</td>
<td><code>egm "Esc"</code> — 접두 <code>~</code> <b>없이</b>. 그리고 재실행</td></tr>
<tr><td>씬에 이전 모델이 남음</td><td>—</td>
<td><code>egm "Esc"</code> → <code>"SelAll"</code> → <code>"Delete"</code> → <code>"Esc"</code></td></tr>
<tr><td><code>modelCount</code> 가 75 가 아님</td>
<td>boolean 이 인자를 흘렸다</td>
<td>씬 비우고 재실행. 반복되면 촬영 멈추고 확인</td></tr>
<tr><td>명령이 조용히 무시됨</td><td>뷰어 모드</td>
<td>0단계로 돌아간다. <b>연결 정상 ≠ 모델링 가능</b></td></tr>
<tr><td><code>Access to the path is denied</code></td>
<td>모델러가 다른 권한으로 떠 있어 파이프에 못 붙는다</td>
<td>모델러를 종료하고
<code>C:\Program Files\Baron\EG-BIM Modeler\EG-BIM_Modeler.exe</code> 로 다시 띄운다</td></tr>
<tr><td>파이프가 죽음 (창은 멀쩡)</td><td>씬 초기화를 여러 번 반복했다</td>
<td>재시작 외에 방법 없다. 초기화는 갱신당 1회</td></tr>
</table>
</div>
<div class="note">
<b><code>--reset</code> 은 붙이지 않는다</b>
지난번 그것 때문에 <code>Select</code> 가 물렸다. 씬은 촬영 전에 미리 비워 둔다.
</div>
</section>
<!-- ══════════ 5. 업로드 ══════════ -->
<section class="panel" id="p5" role="tabpanel">
<h2>제목 후보</h2>
<div class="tw">
<table>
<tr><th>안</th><th>제목</th></tr>
<tr><td>A</td><td>2D 도면 한 장으로 교각 3D 모델 자동 생성 — 부재 75개, 오차 0.0000</td></tr>
<tr><td>B</td><td>토목 도면을 BIM 으로 바꾸는 법 : 손으로 안 그리고 IFC 까지</td></tr>
<tr><td>C</td><td>도면이 말하는 네 가지 방식 — 윤곽 · 원 · 블록 · 표 | 2D→3D 전환설계</td></tr>
</table>
</div>
<h2>설명란</h2>
<pre>교각 일반도(DWG) 한 장을 읽어 3D 솔리드와 IFC 까지 만드는 과정입니다.
부재 75개 · 콘크리트 657.06 m³ · 모델러 실측 대비 오차 0.0000 · 열린 변 0
00:00 결과 먼저
00:14 도면 — 한 파일에 도형 17벌
00:38 뷰 가르기
00:50 끊김 진단 — 레이어 오분류 vs 선 누락
01:02 도면이 말하는 네 방식 : 윤곽 · 원 · 블록 · 표
01:51 도면이 파라미터가 된다
02:16 3D 툴 없이 부피부터
02:32 모델러에 교각이 선다
03:54 의도 vs 결과 — 부재마다 대조
04:20 IFC 로 넘긴다
05:04 그림으로도 본다 · 정리
다룬 것
· 도곽 블록으로 시트 경계를 계산한다 (한 파일에 여러 벌이 쌓여 있다)
· 파단선 — 그려진 길이가 실제가 아니다. 생략량을 도면에서 되찾는 법
· 말뚝은 폐곡선이 아니라 INSERT 블록. 배율이 곧 치수다
· 받침 적층·절대표고는 형상에 없고 표에만 있다
· IFC 는 메시가 아니라 해석 솔리드로. 원기둥은 진짜 원으로</pre>
<h2>태그</h2>
<pre>토목엔지니어, BIM, 3D전환설계, 토목BIM, IFC, 교량, 교각, 도면자동화,
DWG, 캐드, 파라메트릭모델링, 스마트건설, civil engineering, BIM automation</pre>
<h2>썸네일</h2>
<p>왼쪽 <b>흑백 2D 도면</b>(01.mp4 한 프레임), 오른쪽 <b>색 입은 3D 교각</b>(07.mp4 마지막 프레임).
가운데 화살표. 카피는 숫자 하나만 — <b>「오차 0.0000」</b> 또는 <b>「부재 75개, 자동」</b>.</p>
<div class="note">
<b>다음 편 훅</b>
마지막 컷에서 <b>꺾인 교각(P11)</b> 을 예고했다. 꺾임각 6.30° —
코핑 축선이 중간에 꺾여 P9 방식이 그대로는 안 통한다. 그게 다음 편 주제다.
</div>
</section>
</div>
<script>
const tabs = document.querySelectorAll('.tabs button');
tabs.forEach(b => b.addEventListener('click', () => {
tabs.forEach(x => x.setAttribute('aria-selected', String(x === b)));
document.querySelectorAll('.panel').forEach(p => p.classList.toggle('on', p.id === b.dataset.p));
window.scrollTo({ top: 0, behavior: 'instant' });
}));
</script>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
# 프로젝트 목표(주제)
2D기반의 Dxf도면을 분석하여 3D모델로 구현하고 구현된 모델을 웹상에서 검토할 수 있게 하고 싶다.
## 절차
0. 절차는 반드시 지킨다. 각 단계마다 작업전 작업내용을 리마인드를 시켜주고 다음단계로의 진행여부를 확인 받아야한다.
1. {주제}를 만들기를 원한다. 일단 심도 있게 웹 조사를 하고 어떤 방법으로 할지 하나씩 agent를 만드는 작업을 해볼 수 있을까?무엇을 할지 메모리에 저장해라.
2. 우리가 이러한 {주제}를 기술들을 구현하는 과정에서 고려해야 할 점들은 무엇일까요?
3. 위를 구현하기 위한 프로젝트를 셋업 하려고 합니다. 지금 셋업을 하지 말고 이러한{주제}을 위한 CLAUDE.md 작성해주세요.
4. 일을 여러 에이전트들이 나눠서 할 때, 어디가지 진행되고 무슨 일을 해야 하는지에 대한 내용들을 각각 PROGRESS.md 와 PLAN.md 에서 관리하도록 합시다. 그리고 새로 시작할 때 두 파일을 읽어서 내가 무슨 일을 해야 하는지 에이전트들이 알 수 있게 합시다. 이렇게 CLAUDE.md 를 업데이트 합시다.
5. 그러면 이제 이런 일들을 할 때 필요한 에이전트, 커맨드, 스킬, Hook 들이 무엇이 있을지 고민해 보고, 아이디어들을 알려주세요.
6. 그러면, Claude code의 agent, command, skill, hook 구조를 조사한 후, 그에 따라서 정확한 포맷을 지켜서.cluade 하위에 위에서 제안한 것들을 만들어 주세요.
단, 추가 하네스 == 이제 “ [https://www.anthropic.com/engineering/harness-design-long-running-apps](https://www.anthropic.com/engineering/harness-design-long-running-apps) ” 이 사이트를 읽으세요.
7. 이 방향으로 CLAUDE.md / 에이전트 구조를 보강해 주세요.
8. 지금까지 작성한 계획으로 workflow 를 구현하세요. 다이나믹 agent를 사용하여 병렬화가 가능한 부분은 병렬로 해주세요. 병렬로 하더라도 안정된 구현을 해주기 원합니다.
9. 어떤 모델을 사용할 것인지 확인이 필요합니다.