From 363c2a2a252a98cf450010625d2da12647a4fb1e Mon Sep 17 00:00:00 2001 From: Claude Haiku Date: Fri, 16 Oct 2026 15:06:18 +0900 Subject: [PATCH] =?UTF-8?q?step3-4:=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8?= =?UTF-8?q?=20=EA=B8=B0=EB=B3=B8=20=EA=B5=AC=EC=A1=B0=20&=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=20=EA=B3=84=ED=9A=8D=20=EC=88=98=EB=A6=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLAUDE.md: 프로젝트 전체 가이드 및 워크플로우 정의 - CONSIDERATIONS.md: 8개 영역 고려사항 분석 (기술, 품질, 배포, UX) - PROGRESS.md: 진행 상황 추적 및 체크리스트 - PLAN.md: 단계 5~9 상세 계획 및 에이전트 역할 정의 - 메모리: 프로젝트 정보 저장 다음: 단계 5 (에이전트/커맨드/스킬/훅 아이디어 제안) --- .claude/settings.local.json | 10 + CLAUDE.md | 289 ++++ CONSIDERATIONS.md | 242 ++++ PLAN.md | 518 +++++++ PROGRESS.md | 130 ++ docs/2026-08_AI-용어-최신강좌.html | 2069 ++++++++++++++++++++++++++++ docs/2d-3d-conversion.md | 1630 ++++++++++++++++++++++ docs/2d-3d-process.md | 524 +++++++ docs/dwg2model-CLAUDE.md | 217 +++ docs/methodology.html | 1661 ++++++++++++++++++++++ docs/next-session-p11.md | 194 +++ docs/next-session-pier.md | 143 ++ docs/parametric.md | 516 +++++++ docs/roadmap.md | 310 +++++ docs/youtube-p9.html | 535 +++++++ 작업지시서.md | 30 + 16 files changed, 9018 insertions(+) create mode 100644 .claude/settings.local.json create mode 100644 CLAUDE.md create mode 100644 CONSIDERATIONS.md create mode 100644 PLAN.md create mode 100644 PROGRESS.md create mode 100644 docs/2026-08_AI-용어-최신강좌.html create mode 100644 docs/2d-3d-conversion.md create mode 100644 docs/2d-3d-process.md create mode 100644 docs/dwg2model-CLAUDE.md create mode 100644 docs/methodology.html create mode 100644 docs/next-session-p11.md create mode 100644 docs/next-session-pier.md create mode 100644 docs/parametric.md create mode 100644 docs/roadmap.md create mode 100644 docs/youtube-p9.html create mode 100644 작업지시서.md diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ca7f30a --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,10 @@ +{ + "permissions": { + "allow": [ + "Bash(git init *)", + "Bash(git config *)", + "Bash(git add *)", + "Bash(git commit -m 'step3-4: 프로젝트 기본 구조 & 상세 계획 수립 *)" + ] + } +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5e983c4 --- /dev/null +++ b/CLAUDE.md @@ -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** 라이선스를 준수해야 한다. + diff --git a/CONSIDERATIONS.md b/CONSIDERATIONS.md new file mode 100644 index 0000000..76eb537 --- /dev/null +++ b/CONSIDERATIONS.md @@ -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, 정점 최적화 | +| 배포 시간 | 서비스 시작 지연 | 낮음 | 사전 인프라 준비 | +| 유지보수 | 기술 부채 | 중 | 명확한 문서 & 테스트 | + diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..19f996e --- /dev/null +++ b/PLAN.md @@ -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) | + diff --git a/PROGRESS.md b/PROGRESS.md new file mode 100644 index 0000000..56600ec --- /dev/null +++ b/PROGRESS.md @@ -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 재검토 + diff --git a/docs/2026-08_AI-용어-최신강좌.html b/docs/2026-08_AI-용어-최신강좌.html new file mode 100644 index 0000000..55469db --- /dev/null +++ b/docs/2026-08_AI-용어-최신강좌.html @@ -0,0 +1,2069 @@ + + + + + +2026 AI 용어 최신판 — 프롬프트에서 루프로 + + + + + + + + + + + + + +
+
2026.08 · 초보자 대상 강좌
+

AI 용어 최신 지도
프롬프트에서 루프로

+

"AI에게 말 잘 거는 법"에서 "AI에게 일 시키고 감독하는 법"으로. 지금 초보자가 반드시 아는 5개 키워드와 주변 용어 24개.

+
+ 루프 엔지니어링하네스 엔지니어링에이전틱 워크플로우종료 조건동적 컨텍스트90분 +
+
+ + + +
+ + +
+ +
+ 시작 전 — 대상 확인 + 토큰 · 컨텍스트 윈도우 · 할루시네이션을 설명 없이 써도 되는 사람들인가? + 아니라면 「AI 기초」 탭을 먼저 30분 하고 여기로 돌아온다. 그 순서를 지키지 않으면 나머지가 전부 헛돈다. +
+ +

한 문장으로

+
+ 프롬프트도 컨텍스트도 사라지지 않았다. 사람이 손으로 하던 그 일이 루프(Loop)라는 자동 공장 안으로 들어갔을 뿐이다. +
+

+ 지금 AI 생태계는 '대화하는 상대(Chatbot)'를 만드는 단계에서 + '일하는 시스템(Agent System)'을 만드는 단계로 넘어가는 중이다. + 용어가 갑자기 늘어난 게 아니라, 계층이 하나 더 생긴 것이다. +

+ +

엔지니어링 4층 구조

+

아래로 갈수록 오래된 개념. 위로 갈수록 2026년에 새로 생긴 층. 아래층이 없어진 게 아니라 위층에 흡수됐다.

+ +
+ + + 루프 엔지니어링 + 2026~ + 언제 다시 돌릴지, 언제 멈출지 — 반복 자체를 설계 + + + 하네스 엔지니어링 + 2025~ + 도구·권한·출력 형식·평가자 — 에이전트 1대가 사는 작업 환경 + + + 컨텍스트 엔지니어링 + 2024~ + 이번 턴에 무엇을 먹일지 — 문서·코드·기억 + + + 프롬프트 엔지니어링 + 2023~ + 이번 턴에 뭐라고 말할지 + 여전히 필요하다. 다만 이제 사람이 아니라 바깥 층이 자동으로 쓴다. + +
+

바깥이 안을 감싼다. 아래층은 사라진 게 아니라 위층 안으로 들어갔다.

+ +
+ 계층 관계 한 줄 + 하네스는 에이전트 한 대가 일하는 작업장이고, 루프는 그 작업장을 타이머로 계속 돌리고 인력을 더 뽑는 공장 관리 시스템이다. + (Addy Osmani: "Loop engineering sits one floor above the harness.") +
+ +

2024 vs 2026

+
+ + + + + + + + + +
구분2024년 (과거)2026년 (현재)
주체사람 (Human-in-the-loop)
사람이 루프 안에서 매번 개입
시스템/AI (Agent-in-the-loop)
사람은 루프 밖에서 설계·검수
행동사람이 매번 프롬프트를 고쳐서 입력시스템(Loop)이 상황에 맞춰 프롬프트를 자동 생성
핵심 질문"어떻게 질문해야 잘 대답할까?""어떻게 반복시켜야 스스로 고칠까?"
성공 기준답변의 퀄리티작업의 완수 여부
단위대화 1턴작업 1건 (수십~수백 턴)
실패 원인프롬프트가 부실종료 조건이 없음 / 검증자가 없음
비용 감각토큰 몇천 개작업 1건에 $10~$200 (하네스 사례)
+
+ +
+ + + + + + + + + 2024 · HUMAN-IN-THE-LOOP + + 루프 + + + 사람 + + AI + + 사람 + + AI + + + + + 사람이 매 턴 안에서 개입한다 + + + 2026 · AGENT-IN-THE-LOOP + + 사람 + + + 루프 + + + 계획 + + 실행 + + 검증 + + + + + + ↺ 반복 + + + + + + 설계 + 검수 + 사람은 루프 밖에 있다 + +
+

용어가 늘어난 게 아니라 사람의 자리가 옮겨간 것이다. 안에서 밖으로.

+ +
+ 초보자에게 이렇게 설명하세요 + "이제는 AI한테 말 잘 거는 법(프롬프트)을 배우는 게 아니라, + AI에게 일을 맡기고 관리·감독하는 법(루프·하네스)을 배우는 시대입니다." +
+ +

이 강의의 목표

+
    +
  • 키워드 5개를 남에게 한 줄로 설명할 수 있게 된다.
  • +
  • AI 뉴스·문서에 나오는 주변 용어 24개가 어느 층에 속하는지 안다.
  • +
  • 내 손으로 종료 조건 하나를 써 본다. (실습 4-2 — 오늘의 핵심)
  • +
  • 루프가 돈과 이해력을 어떻게 태우는지 알고 방어선을 친다.
  • +
+ +
출처 — Addy Osmani, "Loop Engineering" (2026-06-07) · Anthropic, "Harness design for long-running application development" · 사내 세미나 Part 7·8 노트
+
+ + +
+ +

+ 관통하는 관점 하나: "AI에게 정답을 한 번에 묻지 말고, 정답을 찾을 때까지 고치게 시켜라." + 아래 5개는 전부 이 문장의 부품이다. +

+ + +
+
01루프 엔지니어링Loop Engineering
+

"AI가 일을 끝낼 때까지 뺑뺑이 돌리는 규칙 만들기"

+ +
+
예전
사람이 질문 → AI가 답변 → 끝. 마음에 안 들면 사람이 또 질문.
+
지금
"목표를 달성할 때까지 계획 → 실행 → 검토 → 수정을 반복해"라고 시스템을 짜 둔다.
+
핵심
AI가 한 번에 완벽한 답을 못 낸다는 걸 인정하고, 스스로 고칠 기회를 구조로 만들어 주는 기술.
+
+ +
+ + + + + + + + + 목표 투입 + + + 계획 + + 실행 + + 검토 + + 수정 + + + + + + + + + ↺ 종료 조건이 거짓인 동안 계속 돈다 + + + + + + 정지 + 종료 조건 참 + + 사람이 손대는 곳 + + ← 여기 한 번. 그 다음부터는 안 만진다. + +
+

사람은 목표와 종료 조건만 넣는다. 가운데 네 칸은 시스템이 돌린다.

+ +

+ 업계 인용 두 개면 분위기가 잡힌다. + Peter Steinberger: "이제 코딩 에이전트에게 프롬프트를 쓰면 안 된다. 에이전트에게 프롬프트를 던지는 루프를 설계해야 한다." + Boris Cherny(Anthropic, Claude Code 총괄): "나는 더 이상 Claude에게 프롬프트를 쓰지 않는다. Claude에게 프롬프트를 던지고 다음 할 일을 판단하는 루프를 돌린다. 내 일은 루프를 짜는 것이다." +

+ +
+ 초보자가 가장 많이 하는 오해 + "루프 = for문 돌리기"가 아니다. 루프의 본질은 반복이 아니라 매 회차마다 상태를 남기고 다음 할 일을 스스로 정하는 것이다. + 상태를 안 남기면 같은 실수를 100번 반복하는 기계다. +
+
+ + +
+
02하네스 엔지니어링Harness Engineering
+

"AI가 딴 길로 새지 않게 채워두는 안전벨트와 작업복"

+ +

+ 하네스(Harness)의 원뜻은 마구(馬具) — 말을 제어하는 장비다. + AI를 루프에 태우면 반드시 엉뚱한 짓을 한다. 이를 막기 위해 + 쓸 수 있는 도구(Tool)를 제한하고, 결과물을 특정 형식(JSON 등)으로만 뱉게 강제하고, + 검사하는 별도 AI를 붙이는 기술이다. +

+ +
+
비유
요리사(AI)에게 아무 칼이나 주는 게 아니라, 이 요리에 필요한 채소 다지기용 칼만 쥐여주는 것.
+
구성물
도구 권한 / 출력 스키마 / 프로젝트 규칙 파일(CLAUDE.md, AGENTS.md) / 훅(Hook) / 평가자
+
가장 센 것
생성자와 평가자 분리. LLM은 자기가 쓴 결과물에 비정상적으로 관대하다. "이 정도면 훌륭하다"며 스스로를 설득한다.
+
+ +

회의적 평가자 (Skeptical Evaluator)

+

+ 모델은 남의 작업을 비판할 때 훨씬 날카롭다. 그래서 평가자에게 일부러 + "의심 많은 성격"을 부여하고, 통과 기준을 하드 임계값으로 못 박는다. + GAN(생성적 적대 신경망)의 생성자–판별자 구조에서 따온 발상이다. +

+ +

하네스가 만드는 차이 — 레트로 게임 메이커 사례

+
+ + + + + + +
구분Solo 실행 (하네스 없음)Full Harness
소요 시간20분6시간
비용$9$200
결과기본 UI만, 실제 작동 불가16개 기능 구현, 실제 플레이 가능
주요 결함런타임 오류, 캐릭터 이동 불가일부 물리 엔진 오차 외 정상 작동
+
+
+ 여기서 나오는 결론 + 하네스는 비용을 20배로 올린다. 그러나 '동작하지 않는 쓰레기'를 '값어치 있는 제품'으로 바꾸는 방법은 지금으로선 이것뿐이다. + $9로 쓰레기를 만드는 것보다 $200로 동작하는 앱을 만드는 게 싸다. +
+
+ + +
+
03에이전틱 워크플로우Agentic Workflow
+

"AI끼리 서로 일을 토스하며 협업하는 구조"

+ +

+ 한 명의 천재 AI가 다 하는 게 아니다. 기획하는 AI · 코드 짜는 AI · 검수하는 AI를 따로 두고 + 결과물을 주고받게 만든다. 프롬프트 한 줄을 잘 쓰는 것보다 이 팀워크를 어떻게 짜느냐가 더 중요해졌다. +

+ +
+ + + + + + + + + + + + + + + + + +
역할하는 일흔한 실패
Planner
기획자
모호한 요청을 상세 명세(Spec)로 확장. 제품 맥락과 기능 범위에 집중.기술 세부사항을 너무 일찍 못 박아 연쇄 오류 유발
Generator
생성자
명세를 작업 단위로 나눠 구현. 단계마다 Git 커밋으로 되돌아갈 지점 확보.자기 결과에 관대함 — 스스로 "완료" 선언
Evaluator
평가자
시작 전 "무엇이 완료인가"를 생성자와 협상하고, 끝나면 사용자 관점에서 공격하듯 테스트.기준이 물러서 전부 통과시킴
+
+ +
+ + + + + + + + + ✕ 같은 세션 — 자기 채점 + + AI 1대 + 만들고, 스스로 채점한다 + + + + "잘 만들어졌습니다" + 결함이 그대로 통과한다 + + + + + ✓ 분리 — Maker ≠ Checker + + 생성자 + 만들기만 + + + 평가자 + 깨기만 + + + 산출물 + + 반려 + + "실패할 이유 3가지" + 다른 지시문 · 가능하면 다른 모델 + +
+

실습 4-3이 이 그림을 직접 재현하는 순서다.

+ +
+ 원칙 한 줄 · Maker ≠ Checker + 만든 놈이 검사하면 안 된다. 코드를 쓴 모델은 자기 숙제를 채점하기엔 너무 착하다. + 다른 지시문, 가능하면 다른 모델을 쓴 두 번째 에이전트가 첫 번째가 스스로 납득해버린 것을 잡아낸다. +
+
+ + +
+
04종료 조건Termination Condition
+

"AI야, 이 정도면 됐으니까 그만해"

+ +

+ 루프 엔지니어링의 진짜 핵심. "완벽할 때까지 해"라고 하면 토큰만 태우고 영원히 안 끝난다. + 명확한 멈춤 신호가 필요하다. 그리고 멈춤 신호는 세 종류를 다 써야 한다. +

+ +
+ + + + + +
유형내용예시
성공 종료목표 달성 시 정지test/auth 전체 통과 && lint 오류 0
실패 종료더 해봐야 소용없을 때 정지같은 테스트 3회 연속 실패 시 중단
에스컬레이션사람 호출5회 시도해도 안 되면 사람 부르기 / 파일 삭제는 승인 요청
+
+ +
+ + + + + + + + + 매 회차 판정 + 생성자가 아닌 쪽이 + + + + + + 테스트 통과 && lint 오류 0 + + 성공 종료 + + + 같은 실패 3회 반복 + + 실패 종료 · 스킵 + + + 판단 애매 / 되돌릴 수 없는 작업 + + 사람 호출 + + 세 갈래 중 하나라도 빠지면 루프가 멈추지 못한다. + +
+

초보자는 맨 위 한 줄만 쓴다. 아래 두 줄이 없어서 요금이 터진다.

+ +
+
+
나쁜 종료 조건
+
    +
  • "완벽해질 때까지"
  • +
  • "괜찮아 보이면 멈춰"
  • +
  • "충분히 좋아지면"
  • +
+

AI가 스스로 판정 = 자기 채점. 무한 루프 또는 조기 종료.

+
+
+
좋은 종료 조건
+
    +
  • 기계가 판정 가능 (테스트·린트·빌드 종료코드)
  • +
  • 상한선 있음 (최대 N회, 최대 $X)
  • +
  • 판정 주체가 생성자와 다름
  • +
+

"done"이 주장이 아니라 증명이 된다.

+
+
+ +
+ 도구는 이미 이걸 내장했다 + Claude Code의 /goal은 조건이 참이 될 때까지 계속 돌면서, 매 턴 별도의 작은 모델이 완료 여부를 판정한다. + 코드를 쓴 모델이 채점하지 않는다는 뜻이다. /loop는 주기적으로 다시 실행하는 쪽. OpenAI Codex 앱에도 같은 이름의 /goal이 있다. +
+
+ + +
+
05동적 컨텍스트Dynamic Context
+

"지금 당장 필요한 기억만 골라서 머리에 넣어주기"

+ +

+ 컨텍스트 엔지니어링은 사라진 게 아니라 시스템 안으로 들어갔다. + 사람이 매번 문서를 붙여넣던 일을, 이제 AI가 루프를 돌면서 + 현재 단계에 필요한 정보만 검색(RAG)해 와서 스스로 컨텍스트를 갈아끼운다. +

+ +

왜 갈아끼워야 하나 — 문맥 불안 (Context Anxiety)

+

+ 컨텍스트 윈도우가 차오르면 모델이 작업을 성급히 마무리하거나 지시사항을 누락한다. 이걸 '문맥 불안'이라 부른다. + 대응은 모델 세대에 따라 갈린다. +

+
+ + + + + +
전략내용언제
리셋컨텍스트를 완전히 비우고, 구조화된 산출물(파일)로만 상태를 전달문맥 불안이 강한 구세대 모델
압축대화를 요약해 눌러 담으며 연속성 유지긴 문맥 유지력이 좋아진 최신 모델
외부 메모리진행 상황을 디스크의 마크다운 파일에 기록항상. 루프의 척추.
+
+ +
+ + + + + + + + + 리셋 + Reset + + 한계 + + + ↓ 비움 + ↓ 비움 + ↓ 비움 + + + 압축 + Compaction + + + + ↓ 요약 + ↓ 요약 + ↓ 요약 + + + 외부 메모리 + State file + + + + + + + + progress.md · log.md — 디스크에 남는다 + + 회차 → + 점선 = 컨텍스트 윈도우 한계 + +
+

세로축은 컨텍스트 사용량. 위 두 줄은 서로 대안이고, 세 번째 줄은 항상 같이 쓴다.

+ +
+ 📸 이미지 요청 #1 — 컨텍스트 윈도우 실물 + Claude Code 세션에서 컨텍스트가 절반 이상 찼을 때의 잔량 표시.
+ 위 다이어그램이 추상이라, 실제 숫자가 붙은 화면이 하나 있으면 초보자가 바로 이해한다. +
캡처 대상: 하단 상태줄의 context 잔량 표시 / /context 출력
+
+ +
+ 외워야 할 한 문장 + "에이전트는 잊어버리지만, 저장소는 잊지 않는다." + 모델은 실행과 실행 사이에 전부 까먹는다. 그래서 기억은 컨텍스트가 아니라 디스크에 있어야 한다. + 너무 단순해서 안 중요해 보이지만, 장기 실행 에이전트는 전부 이 트릭 위에 서 있다. +
+
+ +
+ + +
+ +

루프 = 5개 부품 + 기억 1개

+

+ 2025년까지는 루프를 만들려면 bash 스크립트 더미를 직접 짜고 평생 관리해야 했다. + 지금은 부품이 제품 안에 그냥 들어 있다. 그래서 "어느 도구를 쓸까"를 싸울 필요가 없다 — 어느 쪽에 앉아도 같은 모양의 루프를 설계하면 된다. +

+ +
+ + + + + + + + + ① 오토메이션 + 매일 09:00 발동 · CI 실패 · 열린 이슈 · 최근 커밋을 스스로 훑는다 + + + 트리아지 — 할 일 목록 + + + ③ 스킬 + 프로젝트 지식을 매 실행 주입 — 없으면 매번 맨바닥에서 재추론 + + + ② 워크트리 A + + 생성자 + + 평가자 + ⑤ 만든 놈 ≠ 검사한 놈 + + + ② 워크트리 B + + 생성자 + + 평가자 + ⑤ 서로 파일을 못 건드린다 + + + ④ 커넥터 — PR 생성 · 티켓 갱신 · 알림 + + + ⑥ 상태 + progress.md — 내일 아침 실행이 여기서 이어받는다. 이게 없으면 루프가 아니다. + + + + + + + + + + + + + + + + + +
+

왼쪽 점선 = 다음 실행이 상태 파일에서 이어받는 경로. 여기가 끊기면 매번 처음부터 시작한다.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
부품루프에서의 역할Claude CodeOpenAI Codex 앱
① 오토메이션정해진 주기로 할 일을 발굴·분류 (루프의 심장박동)스케줄 작업·cron, /loop, /goal, 훅, GitHub ActionsAutomations 탭, Triage 인박스, /goal
② 워크트리병렬 작업 충돌 방지git worktree, --worktree, isolation: worktree스레드마다 워크트리 내장
③ 스킬프로젝트 지식을 글로 박제Agent Skills (SKILL.md)Agent Skills (SKILL.md), $name 호출
④ 플러그인
커넥터
내가 실제로 쓰는 도구에 연결MCP 서버 + 플러그인Connectors(MCP) + 플러그인
⑤ 서브에이전트한 놈이 제안, 다른 놈이 검사.claude/agents/, 에이전트 팀.codex/agents/ TOML
⑥ 상태(기억)뭘 했고 뭐가 남았는지 — 대화 밖에 사는 파일마크다운(CLAUDE.md, progress) 또는 Linear(MCP)마크다운(AGENTS.md) 또는 Linear
+
+ +

부품별 해설

+ +

① 오토메이션 — 루프를 '루프'로 만드는 것

+

+ 이게 없으면 그냥 한 번 돌린 실행이지 루프가 아니다. + 주기를 정해 두면 AI가 어제의 CI 실패, 열린 이슈, 최근 커밋을 스스로 훑어서 + 할 일을 트리아지 인박스에 쌓아둔다. 찾은 게 없는 실행은 알아서 사라진다. +

+

/loop와 /goal의 차이 — 시험에 나온다:

+
+
+
/loop — 주기 반복
+

정해진 간격으로 다시 실행한다. "5분마다 배포 상태 확인해."
끝이 정해져 있지 않음.

+
+
+
/goal — 조건 달성까지
+

조건이 참이 될 때까지 계속 간다. "test/auth 전부 통과하고 lint 깨끗해질 때까지."
매 턴 별도 모델이 완료 판정 → 자기 채점 방지.

+
+
+ +
+ + /loop + 주기 반복 + + + + + + 5분 + 5분 + … + 끝이 정해져 있지 않다. 사람이 멈출 때까지 계속. + + + + /goal + 조건 달성까지 + + + + + + + + 정지 + 조건이 참이 됐다 + 매 턴 별도 모델이 판정 + +
+

/loop은 주기가 있고, /goal은 끝이 있다. 초보자가 가장 많이 헷갈리는 한 쌍.

+ +
+ 📸 이미지 요청 #2·#3 — 두 명령의 실제 화면 + #2 /loop 실행 중 화면 (반복 회차가 보이는 상태)
+ #3 /goal 실행 후 완료 판정이 뜨는 순간 — 이게 "다른 모델이 채점한다"의 증거라 제일 중요하다. +
캡처 대상: 명령 입력 직후 + 종료 시점 2컷
+
+ +

② 워크트리 — 병렬이 아수라장이 되지 않게

+

+ 에이전트를 두 대 이상 돌리는 순간 파일이 충돌한다. 두 사람이 같은 줄을 서로 말없이 고치는 것과 정확히 같은 문제다. + git worktree는 같은 저장소 히스토리를 공유하면서 별도 작업 디렉터리 + 별도 브랜치를 주기 때문에, + 한쪽 에이전트의 수정이 다른 쪽 체크아웃을 물리적으로 건드릴 수 없다. +

+
+ 오케스트레이션 세금 · Orchestration Tax + 워크트리는 기계적 충돌만 없앤다. 병렬 개수의 진짜 상한은 도구가 아니라 당신의 리뷰 대역폭이다. + 에이전트 10대를 돌려도 결과를 볼 수 있는 사람은 하나다. +
+ +

③ 스킬 — 매번 프로젝트를 다시 설명하지 않으려고

+

+ 에이전트는 모든 세션을 차가운 상태로 시작하고, 의도에 구멍이 있으면 자신 있게 추측으로 메운다. 이게 의도 부채(Intent Debt)다. + 스킬은 그 의도를 바깥에 한 번 적어 두는 것 — 관례, 빌드 절차, "그 사건 때문에 우리는 이렇게 안 한다" 같은 것. + 스킬이 없으면 루프는 매 사이클마다 프로젝트를 맨바닥에서 재추론한다. 있으면 누적된다. +

+

스킬 vs 플러그인: 스킬은 작성 형식, 플러그인은 배포 방법. 저장소 여러 곳에 나눠주고 싶으면 스킬을 플러그인으로 묶는다.

+ +

④ 플러그인·커넥터 — 루프가 실제 세계를 만진다

+

+ 파일 시스템밖에 못 보는 루프는 작은 루프다. MCP 위에 세워진 커넥터가 이슈트래커를 읽고, DB에 질의하고, 스테이징 API를 치고, 슬랙에 메시지를 남긴다. + "여기 고치는 법입니다"라고 말하는 에이전트와 PR을 열고 티켓을 연결하고 CI 초록불 뜨면 채널에 알리는 루프의 차이가 여기서 갈린다. +

+ +

⑤ 서브에이전트 — 만든 놈과 검사하는 놈 떼어놓기

+

+ 루프에서 구조적으로 가장 쓸모 있는 한 수. 루프는 당신이 안 보는 동안 돈다. + 그래서 자리를 뜰 수 있는 유일한 근거는 믿을 만한 검증자다. + 서브에이전트는 각자 모델과 도구를 돌리므로 토큰을 더 태운다 — 두 번째 의견이 값어치 있는 곳에만 쓴다. +

+ +

⑥ 상태 — 루프의 척추

+

+ 마크다운 파일 하나, 또는 이슈 보드. 대화 밖에 살면서 뭐가 끝났고 뭐가 남았는지 붙잡고 있는 것. + 이게 있어야 내일 아침 실행이 오늘 멈춘 자리에서 이어진다. +

+ +

루프 하나의 실제 모양

+
매일 아침 09:00  [오토메이션 발동]
+  └─ 트리아지 스킬 호출
+       ├─ 어제 CI 실패 읽기
+       ├─ 열린 이슈 읽기
+       └─ 최근 커밋 읽기
+  └─ 발견 사항을 progress.md 에 기록          ← ⑥ 상태
+
+  각 항목마다:
+    ├─ 격리된 워크트리 생성                    ← ② 충돌 방지
+    ├─ 서브에이전트 A: 수정안 작성             ← ⑤ 생성자
+    └─ 서브에이전트 B: 프로젝트 스킬 + 기존 테스트 기준으로 검수  ← ⑤ 평가자
+
+  통과 시 → 커넥터가 PR 생성 + 티켓 갱신 + 슬랙 알림   ← ④
+  실패 시 → 트리아지 인박스에 남겨 사람에게              ← 에스컬레이션
+
+  [종료] 내일 아침 실행이 progress.md 부터 이어받음
+

+ 여기서 당신이 프롬프트를 친 단계가 하나도 없다. 한 번 설계했을 뿐이다. 그게 루프 엔지니어링이다. +

+ +
출처 — Addy Osmani, "Loop Engineering" (2026-06-07, addyosmani.com): 5 primitives + state 표 및 도구 매핑
+
+ + +
+ +
+ 이 탭이 필요한 신호 + 수강생이 "토큰이 뭐예요?" "컨텍스트 윈도우가 뭐예요?"를 물으면 앞 탭은 통하지 않는다. + 앞 탭을 반복 설명하지 말고 여기서 30분 먼저 하고 넘어간다. 순서를 뒤집으면 나머지 90분이 전부 헛돈다. +
+ +

0. 지도 먼저 — AI · 머신러닝 · LLM의 관계

+

초보자가 가장 먼저 헷갈리는 것은 새 용어가 아니라 낡은 용어들끼리의 관계다. 포함 관계 하나면 정리된다.

+ +
+ + + 인공지능 (AI) + 사람이 하던 판단을 기계가 하는 것 전부 — 1950년대부터 + + + 머신러닝 (ML) + 규칙을 사람이 안 짜고 데이터에서 스스로 찾는 방식 + + + 딥러닝 (DL) + 신경망을 깊게 쌓은 머신러닝. 2010년대 이후 주류 + + + 생성형 AI + 분류·예측이 아니라 새로 만들어내는 딥러닝 (글·그림·소리) + + + LLM — 대규모 언어 모델 + 글을 다루는 생성형 AI. ChatGPT·Claude·Gemini가 전부 여기. + 우리가 "AI"라고 부를 때 실제로 가리키는 것은 대부분 이 칸이다. + +
+

"AI가 발전했다"는 말의 99%는 맨 안쪽 칸 이야기다.

+ +

1강. LLM은 무엇인가 — "다음 단어를 맞히는 기계"

+
+ LLM의 본질은 하나다. 지금까지의 글 다음에 올 단어를 확률로 고르는 것. 그게 전부다. +
+

+ "오늘 날씨가 참 ___" 다음에 뭐가 올까? '좋다'가 높은 확률, '자동차'는 낮은 확률. + 이걸 한 단어씩 반복하면 문장이 되고 문서가 된다. + 이 한 줄을 이해하면 아래 세 가지 문제가 전부 자동으로 설명된다. +

+ +

학습(Training) vs 추론(Inference) — 초보자가 가장 많이 섞는 둘

+
+ + + + + + + 학습 · TRAINING + + 인터넷 규모 + 텍스트 + + + 모델 만들기 + 가중치가 정해진다 + 수개월 · 수천억 원 · 딱 한 번 + 끝나면 모델은 더 이상 안 변한다 + → 내가 대화한 내용은 모델에 저장되지 않는다 + + + + 추론 · INFERENCE + + 내 질문 + + + 모델 + + + 답 + 몇 초 · 쓸 때마다 매번 · 요금은 여기서 발생 + 우리가 하는 건 전부 이쪽이다 + → "AI를 학습시킨다"는 말은 대개 틀린 표현이다 + +
+

"제가 AI를 학습시켰어요"라고 말하는 사람의 99%는 추론 단계에서 프롬프트를 잘 준 것이다.

+ +

여기서 파생되는 3가지 — 전부 같은 뿌리

+
+ + + + + + + + + + + + + + + + + +
현상왜 생기나대응
할루시네이션
환각
모르는 걸 모른다고 표시하는 기능이 없다. 그냥 "그럴듯한 다음 단어"를 계속 이어 쓴다. 거짓말이 아니라 구조상 그렇게 생겨먹은 것이다.근거·출처를 같이 요구 / 검색 붙이기(RAG) / 기계로 검증
지식 컷오프
knowledge cutoff
학습이 끝난 시점 이후의 일은 존재 자체를 모른다. 학습은 한 번뿐이니까.최신 정보는 검색 도구를 붙이거나 직접 붙여넣기
계산·세기 실수계산기가 아니라 글자 예측기다. 숫자도 "그럴듯한 다음 글자"로 뱉는다.계산은 코드 실행 도구에 시킨다
+
+ +
+ 파라미터 (매개변수) + "700억 파라미터 모델" 같은 말의 그 숫자. 학습으로 정해진 내부 조절 손잡이의 개수다. + 많을수록 대체로 똑똑하고 비싸고 느리다. 초보자는 이 숫자를 신경 쓸 필요가 없다 — 요즘은 크기보다 훈련 방식이 성능을 가른다. +
+ +

2강. 토큰과 컨텍스트 윈도우 — 기억력이자 요금

+ +

토큰 (Token)

+

+ AI가 글을 세는 단위. 글자도 단어도 아닌 그 중간이다. + 영어는 대략 4글자 ≈ 1토큰, 한글은 1글자 ≈ 1~2토큰이라 같은 내용이라도 한글이 더 많이 든다. + 요금은 글자 수가 아니라 토큰 수로 계산된다. +

+ +

컨텍스트 윈도우 (Context Window)

+

+ 한 번에 책상 위에 펼칠 수 있는 종이의 양. 이걸 넘으면 오래된 종이부터 책상에서 밀려난다. + "아까 말했잖아"가 안 통하는 순간이 바로 이때다. AI가 까먹은 게 아니라 애초에 안 보이는 상태가 된 것이다. +

+ +
+ + + + + + 1턴 + + + + 2턴 + + + + 3턴 + + + + 4턴 + + 이전 대화 전체 + 새로 친 질문 + AI에게는 매 턴이 첫 대화다. 이전 내용을 통째로 다시 읽고 나서 답한다. + +
+

대화가 길어질수록 같은 질문도 요금이 비싸진다. 파란 칸(새 질문)은 그대로인데 회색 칸이 계속 자라기 때문.

+ +

초보자가 오늘 바로 쓸 규칙 3개

+
    +
  • 주제가 바뀌면 새 대화를 연다. 긴 대화를 이어가는 게 미덕이 아니다. 요금과 정확도 둘 다 나빠진다.
  • +
  • 필요한 부분만 붙여넣는다. 파일 전체를 넣으면 중간 내용이 무시되기 쉽다(Lost in the Middle).
  • +
  • 넘어갈 땐 요약을 들고 간다. "지금까지 결정된 것만 5줄로 정리해줘" → 그걸 새 대화에 붙인다.
  • +
+ +
+ 📸 이미지 요청 #9 — 토큰·요금 실물 + 사용량 화면 또는 대화 하단의 토큰 표시. 위 막대그래프가 추상이라, 실제 숫자가 붙은 한 장이 있으면 초보자가 즉시 납득한다. +
캡처 대상: 사용량 대시보드 또는 세션 토큰 표시
+
+ +

3강. 프롬프트 — 말 거는 법의 기본

+ +
+ + + + +
종류누가 쓰나언제 적용되나
시스템 프롬프트서비스 만든 쪽 (또는 나, 설정에서)대화 전체에 계속 적용. "너는 ~다" 류의 성격·규칙
유저 프롬프트나이번 한 턴. 지금 시키는 일
+
+ +

좋은 프롬프트 4요소

+
+ + + + + + +
요소질문예
역할누구처럼 답하나너는 10년차 회계사다
목표무엇을 얻고 싶나이 거래 내역에서 이상 항목을 찾아라
제약하지 말아야 할 것추측 금지. 근거 행 번호를 반드시 인용
출력 형식어떤 모양으로표로. 열은 행번호·금액·의심 사유
+
+ +
+
+
나쁜 프롬프트
+

"엑셀 분석해줘"

+

역할·목표·제약·형식 전부 없음. AI는 빈칸을 자신 있는 추측으로 메운다.

+
+
+
좋은 프롬프트
+

"너는 10년차 회계사다. 붙여넣은 거래 내역에서 이상 항목을 찾아라. 추측 금지, 근거 행 번호 인용. 표로 출력하고 열은 행번호·금액·의심 사유."

+
+
+ +
# 프롬프트 기본 틀
+
+역할:   너는 ______________ 다.
+목표:   ______________ 를 해라.
+제약:   ______________ 는 하지 마라. 모르면 모른다고 해라.
+형식:   ______________ 형태로 출력해라.
+
+[자료 붙여넣기]
+ +
+ 예시 2개의 힘 — Few-shot + 설명을 세 문단 쓰는 것보다 원하는 결과의 예시를 2개 보여주는 것이 훨씬 잘 먹는다. + "이런 식으로 나오면 돼" + 예시 2개 → 형식이 즉시 잡힌다. +
+ +

4강. 모델 이름 읽는 법

+

+ 모델 이름은 대체로 [제품군] + [등급] + [세대] 구조다. + Claude의 Opus·Sonnet·Haiku, 다른 회사의 Pro·Flash·mini 같은 이름이 등급이고, 붙은 숫자가 세대다. +

+
+ + + + + + + +
축뜻고르는 기준
등급큰 모델일수록 어려운 추론에 강하고, 느리고, 비싸다어려운 설계·긴 코드 → 상위 등급 / 요약·분류·번역 → 하위 등급
세대숫자가 클수록 최신. 같은 등급이면 신세대가 대체로 더 낫다특별한 이유 없으면 최신 세대
추론 모드
thinking
답하기 전에 스스로 길게 생각하는 단계를 거친다. 느리고 비싸지만 정확도가 오른다수학·디버깅·다단계 계획 → 켠다 / 잡담·요약 → 끈다
멀티모달글 말고 이미지·소리·영상도 입력으로 받는다스크린샷·도면·사진을 다룰 일이 있으면 필수
공개 방식오픈웨이트 = 모델 파일을 받아 내 컴퓨터에서 돌릴 수 있음 / 비공개 = API로만보안·오프라인 요구 → 오픈웨이트 / 성능 우선 → 비공개 상위 모델
+
+
+ 초보자 함정 + "제일 비싼 모델 = 항상 정답"이 아니다. 분류·요약 같은 단순 작업에 상위 모델을 쓰면 + 결과는 똑같은데 요금만 몇 배 나온다. 작업 난이도에 등급을 맞추는 게 실력이다. +
+ +

5강. 내 정보를 AI에 넣는 3가지 방법

+

초보자 질문 1순위: "우리 회사 문서를 AI가 알게 하려면요?" 답은 셋 중 하나다.

+ +
+ + + + + + + + 내 문서 + + + ① 그냥 붙여넣기 + + + ② 검색해서 넣기 (RAG) + + + ③ 파인튜닝 + + + 모델 + + + 컨텍스트 + 매번 새로 채운다 · 대화 끝나면 사라진다 + + + 가중치 + 학습으로만 바뀐다 · 영구적 · 비싸다 + + + + + + + + +
+

①②는 컨텍스트에 들어간다(임시). ③만 가중치를 건드린다(영구). 대부분의 문제는 ①②로 끝난다.

+ +
+ + + + + + + + + + + + + + + + + + + + +
방법이럴 때비용·난이도주의
① 붙여넣기문서 몇 개, 지금 한 번만공짜에 가까움 / 즉시컨텍스트 윈도우를 넘으면 못 넣는다
② RAG문서 수백~수만 개. 대부분 여기중간 / 며칠검색이 엉뚱한 걸 물어오면 답도 엉뚱해진다
③ 파인튜닝말투·형식을 모델 자체에 새겨야 할 때높음 / 몇 주지식 주입 용도로는 대개 잘못된 선택. 최신 정보를 넣으려면 매번 다시 학습해야 한다
+
+ +
+ 한 줄 판단법 + "내용이 자주 바뀌나?" → 예: RAG. 아니오 + 말투 문제: 파인튜닝. + 대부분의 회사는 RAG면 끝난다. 파인튜닝부터 검토하는 건 거의 항상 과잉이다. +
+ +

6강. 챗봇에서 에이전트로 — 앞 탭과 이어지는 지점

+

+ 여기까지가 "AI와 대화하기"다. 그런데 2026년의 주제는 "AI에게 일 시키기"다. + 둘을 가르는 선은 딱 하나 — 도구를 쓸 수 있는가. +

+ +
+ + + + + + + 챗봇 — 말만 한다 + + 사람 + + LLM + + 텍스트 답변 + + + + "이렇게 고치시면 됩니다" + → 실제로 고치는 건 사람 + + + + 에이전트 — 실제로 한다 + + 사람 + + LLM + + 파일 읽기·쓰기 + + 웹 검색 + + 코드 실행 + + + + + + "고쳤습니다. 테스트도 통과했습니다" + → 결과물이 실제로 바뀌어 있다 + → 그래서 틀렸을 때 피해도 실제로 생긴다 + +
+

+ 도구를 쥐여준 순간 AI는 말하는 존재에서 행동하는 존재가 된다. + 하네스와 루프가 필요해진 이유가 정확히 이것이다 — 행동에는 안전벨트와 정지 버튼이 필요하니까. +

+ +
+ 여기서 앞 탭으로 넘어간다 + 이 6강이 끝나면 「핵심 키워드 5」 탭이 그대로 읽힌다. + 순서: AI 기초(이 탭) → 왜 헷갈리는가 → 핵심 키워드 5 → 실습. +
+ +

기초 용어 30 — 한 줄 사전

+

위 6강에 나온 것들의 요약. 수강생 배포용으로 이 표만 잘라 써도 된다.

+ +

모델·구조

+
+
인공지능 AI
사람이 하던 판단을 기계가 하는 것 전부. 가장 넓은 말.
+
머신러닝 ML
규칙을 사람이 안 짜고 데이터에서 스스로 찾는 방식.
+
딥러닝 DL
신경망을 깊게 쌓은 머신러닝. 지금 AI 붐의 기반.
+
생성형 AI Generative AI
분류·예측이 아니라 새로 만들어내는 AI. 글·그림·소리.
+
LLM 대규모 언어 모델
글을 다루는 생성형 AI. 다음 단어를 확률로 고르는 게 전부.
+
파운데이션 모델 Foundation Model
특정 용도가 아니라 바탕으로 쓰라고 만든 범용 대형 모델.
+
파라미터 Parameter
학습으로 정해진 내부 조절 손잡이 개수. "700억 파라미터"의 그 숫자.
+
추론 모델 Reasoning / Thinking
답하기 전에 스스로 길게 생각하는 단계를 거치는 모드. 느리고 비싸고 정확하다.
+
멀티모달 Multimodal
글 말고 이미지·소리·영상도 입력으로 받는 것.
+
오픈웨이트 Open-weight
모델 파일을 받아 내 컴퓨터에서 돌릴 수 있는 것. 반대는 API 전용.
+
+ +

동작·한계

+
+
학습 Training
모델을 만드는 단계. 수개월·수천억 원. 딱 한 번. 내 대화는 여기 안 들어간다.
+
추론 Inference
만들어진 모델을 쓰는 단계. 우리가 하는 건 전부 이쪽. 요금도 여기서 발생.
+
토큰 Token
AI가 글을 세는 단위. 영어 4자≈1, 한글 1자≈1~2. 요금의 단위.
+
컨텍스트 윈도우 Context Window
한 번에 책상에 펼칠 수 있는 종이 양. 넘으면 오래된 것부터 밀려난다.
+
할루시네이션 Hallucination
모르는 걸 그럴듯하게 지어내는 것. 거짓말이 아니라 구조상 그렇게 생긴 것.
+
지식 컷오프 Knowledge cutoff
학습이 끝난 시점. 그 이후 일은 존재 자체를 모른다.
+
온도 Temperature
답의 무작위성 조절값. 낮으면 딱딱하고 일관되게, 높으면 다양하고 창의적으로.
+
세션 · 스레드 Session
대화 한 뭉치. 새 세션 = 백지 상태. AI는 세션 사이를 기억하지 못한다.
+
+ +

쓰는 법

+
+
프롬프트 Prompt
AI에게 보내는 지시. 역할·목표·제약·형식 4요소면 충분하다.
+
시스템 프롬프트 System prompt
대화 전체에 계속 적용되는 규칙. 유저 프롬프트는 이번 한 턴.
+
Few-shot
원하는 결과의 예시를 2~3개 보여주는 것. 설명 세 문단보다 잘 먹는다.
+
RAG 검색 증강 생성
질문에 맞는 문서를 검색해 와서 프롬프트에 넣고 답하게 하는 방식.
+
파인튜닝 Fine-tuning
모델 가중치를 조금 다시 학습시키는 것. 지식 주입 용도로는 대개 오답.
+
임베딩 Embedding
글을 숫자 배열로 바꾼 것. "뜻이 비슷하면 숫자도 가깝다" — RAG 검색의 원리.
+
벡터 DB Vector DB
임베딩을 저장하고 "비슷한 것"을 빠르게 찾아주는 저장소.
+
API
프로그램이 AI를 부르는 창구. 사람이 채팅창에서 쓰는 것과 같은 모델을 코드로 부른다.
+
SDK
그 API를 언어별로 쉽게 쓰게 만든 꾸러미. pip install anthropic 같은 것.
+
챗봇 → 에이전트
가르는 선은 도구를 쓸 수 있는가. 말만 하면 챗봇, 실제로 하면 에이전트.
+
프롬프트 인젝션 Prompt injection
AI가 읽는 문서·웹페이지 안에 몰래 지시문을 심어 조종하는 공격. 에이전트가 도구를 쥐면 실제 피해가 난다.
+
+ +
+ 기초 확인 질문 — 이걸 못 답하면 앞 탭으로 못 넘어간다 + ① LLM이 하는 일을 한 문장으로? ② 대화가 길어지면 왜 요금이 오르나? + ③ 회사 문서 1만 건을 AI가 알게 하려면 셋 중 무엇? ④ 챗봇과 에이전트를 가르는 선은? +
+ +

심화 용어 24 — 앞 탭에서 쓰는 말들

+

여기서부터는 「핵심 키워드 5」 탭을 들은 뒤 읽는다. 층별로 묶었다.

+ +

루프 층

+
+
오토메이션 Automation
일정에 따라 스스로 발동하는 실행. 루프의 심장박동.
+
트리아지 Triage
AI가 발굴한 할 일을 중요도별로 분류해 쌓아두는 인박스. 사람은 여기만 본다.
+
워크트리 git worktree
같은 저장소, 다른 작업 폴더, 다른 브랜치. 에이전트 병렬 실행의 전제 조건.
+
상태 파일 State / Memory
대화 밖에 사는 진행 기록. progress.md, AGENTS.md 등. 루프의 척추.
+
장기 실행 에이전트 Long-running agent
몇 시간~며칠에 걸쳐 하나의 목표를 붙잡는 에이전트. 기억을 디스크에 둬야 성립.
+
런 언틸 던 run-until-done
"검증 가능한 정지 조건이 참이 될 때까지" 계속 도는 모드. /goal이 이것.
+
+ +

하네스 층

+
+
툴 유즈 Tool Use
AI가 대답만 하는 게 아니라 정해진 함수를 호출하는 것. 에이전트의 손발.
+
MCP Model Context Protocol
AI를 외부 도구·데이터에 연결하는 표준 규격. 한 번 만들면 여러 도구에서 그대로 쓰인다.
+
스킬 SKILL.md
프로젝트 지식을 담은 폴더 + 지시문 파일. 설명을 지루하고 정확하게 써야 잘 불려 나온다.
+
플러그인 Plugin
스킬·커넥터를 묶어 배포하는 단위. 동료가 한 번에 설치.
+
훅 Hook
에이전트 생애주기의 특정 시점에 자동 실행되는 셸 명령. 예: 편집 후 자동 포맷.
+
구조화 출력 Structured Output
결과를 반드시 JSON 스키마에 맞춰 뱉게 강제. 파싱 실패를 원천 차단.
+
가드레일 Guardrail
해도 되는 것과 안 되는 것의 경계. 권한·경로·비용 상한.
+
회의적 평가자 Skeptical Evaluator
일부러 의심 많게 튜닝한 검사 에이전트. 통과 기준을 하드 임계값으로 고정.
+
스프린트 계약 Sprint Contract
작업 시작 전에 생성자와 평가자가 "무엇이 완료인가"를 합의하는 절차.
+
+ +

컨텍스트 층

+
+
컨텍스트 윈도우 Context Window
AI가 한 번에 볼 수 있는 텍스트 총량. 토큰 단위로 센다.
+
토큰 Token
AI가 글을 세는 단위. 한글은 대략 1자 = 1~2토큰. 비용의 단위이기도 하다.
+
컴팩션 Compaction
대화가 길어질 때 자동 요약해 눌러 담기. 연속성 유지 전략.
+
컨텍스트 리셋 Reset
컨텍스트를 완전히 비우고 파일로만 상태를 넘기기. 압축의 반대 전략.
+
문맥 불안 Context Anxiety
윈도우가 차오를 때 모델이 급하게 마무리하거나 지시를 빠뜨리는 현상.
+
Lost in the Middle
긴 입력의 가운데 정보를 모델이 잘 놓치는 현상. 중요한 건 앞뒤에.
+
RAG Retrieval-Augmented Generation
필요한 문서를 검색해 와 컨텍스트에 넣고 답하게 하는 방식. 동적 컨텍스트의 엔진.
+
의도 부채 Intent Debt
적어두지 않은 의도. 에이전트는 구멍을 자신 있는 추측으로 메운다.
+
+ +

사람 쪽 용어 — 사실 이게 제일 중요하다

+
+
Human-in-the-loop → Agent-in-the-loop
사람이 루프 안에 있던 시대 → 사람은 루프 밖에서 설계·검수하는 시대.
+
이해 부채 Comprehension Debt
내가 안 쓴 코드가 쌓일수록 존재하는 것과 내가 이해한 것의 격차가 벌어진다. 루프가 매끄러울수록 빨리 자란다.
+
인지적 항복 Cognitive Surrender
루프가 알아서 도니까 의견 갖기를 그만두고 나오는 대로 받는 상태. 가장 편한 자세가 가장 위험하다.
+
오케스트레이션 세금 Orchestration Tax
에이전트를 늘려도 당신의 리뷰 대역폭이 천장이다. 도구가 아니라 사람이 상한선.
+
적대적 코드 리뷰 Adversarial Review
"이게 왜 틀렸는지 찾아라"를 기본값으로 시키는 리뷰. 동의보다 반박을 시킨다.
+
+ +
+ 수강생 확인 질문 (한 줄로 답하기) + ① 하네스와 루프의 차이는? ② 종료 조건을 AI가 판정하면 왜 위험한가? ③ 기억을 컨텍스트가 아니라 디스크에 두는 이유는? +
+
+ + +
+ +

실습 설계 원칙

+

+ 이 강좌의 실습은 코드 작성이 아니다. 루프를 글로 정의하는 연습이다. + 루프 엔지니어링에서 실제로 사람이 하는 일이 그것이기 때문이다. +

+

개발자비개발자 트랙을 나눠 표기했다. 실습 4-2는 공통 필수.

+ +

변수 — 여기부터 채운다

+

+ 실습 전체가 변수 치환식이다. 아래 5개를 정하면 나머지 프롬프트는 복사만 하면 된다. + 주제를 바꿔 다시 돌릴 때도 이 표만 갈아끼운다. +

+
+ + + + + + + +
변수뜻예시
{주제}반복하려는 작업 한 줄raw/ 자료를 wiki 페이지로 컴파일
{산출물}끝났을 때 손에 남는 물건wiki/sources/*.md + index 한 줄
{검증도구}기계가 판정하는 수단코드블록·표·헤딩 개수 일치
{상한}최대 횟수 / 금액 / 시간1회 10건, 3회 실패 시 중단
{도구}실제로 돌릴 환경Claude Code / n8n / Dify
+
+ +
+ 단 하나의 실격 조건 + {검증도구} 칸에 "AI가 판단"이라고 쓰면 이 실습은 거기서 끝난다. + 테스트·린트·빌드 종료코드·개수 비교·정규식 — 사람이나 프로그램이 셀 수 있는 것만 적는다. +
+ +
+ 배포 파일 + 빈 워크시트: 2026-08_실습-템플릿_루프설계.md
+ 채운 예시: 2026-08_실습-샘플_vault흡수루프.md
+ 복사 → {주제} 전체 치환 → STEP 4까지 채우면 그대로 강의 자료가 된다. +
+ +

실습 4-1 · 루프 6부품 채우기 개발자비개발자

+

내가 반복하는 업무 하나를 {주제}에 넣고 빈칸을 채운다. 도구는 아직 안 건드린다.

+
# {주제} 루프 설계서
+
+목표(Goal):     {주제}
+발동 주기:      □ 매일 __:__   □ 커밋할 때마다   □ 수동 실행
+{도구}:         ______________________________________
+
+① 오토메이션:  무엇을 스스로 찾아오게 할 것인가?
+   → ____________________________________________
+
+② 격리:        동시에 돌 작업이 서로 충돌하는가? (Y/N)
+   → ____________________________________________
+
+③ 스킬(지식):  매번 다시 설명하기 싫은 프로젝트 규칙 3개
+   1. __________  2. __________  3. __________
+
+④ 연결(도구):  루프가 만져야 하는 외부 시스템
+   → ____________________________________________
+
+⑤ 검증자:      누가 "잘 됐다"를 판정하는가? (생성자와 달라야 함)
+   → ____________________________________________
+
+⑥ 기억:        진행 상황을 어디에 적을 것인가?
+   → ____________________________________________
+
+ 채점 포인트 + ⑤에 "AI가 스스로"라고 쓴 사람은 탈락. ⑥을 비운 사람도 탈락. 이 둘이 루프의 성패를 가른다. +
+ +

실습 4-2 · 종료 조건 쓰기 개발자비개발자 ★ 핵심

+

같은 목표를 나쁜 종료 조건과 좋은 종료 조건 두 벌로 쓴다.

+ +
+
+
참가자들이 실제로 써 오는 것
+

"블로그 글이 좋아질 때까지 고쳐줘"

"버그가 다 없어질 때까지 수정해"

+
+
+
고쳐 쓴 것
+

"제목 2개 이상 대안 제시 + 문단당 3문장 이하 + 맞춤법 검사 통과. 3회 수정 후 무조건 정지."

"pytest 전체 통과 && ruff 오류 0. 같은 테스트 3회 연속 실패 시 중단하고 보고."

+
+
+ +

템플릿:

+
[{주제}] 종료 조건
+
+성공 시 정지:   {검증도구} 가 ______________ 이면
+실패 시 정지:   ______________ 를 ___회 반복해도 안 되면
+사람 호출:      ______________ 상황이 오면
+비용 상한:      {상한} — 토큰 ______ / 금액 $______ / 시간 ______분 / 건수 ______
+ +

자가 검사:

+
    +
  • 성공 조건을 사람 눈 없이 판정할 수 있는가?
  • +
  • 실패 종료가 따로 있는가? (성공 조건만 있으면 무한 루프)
  • +
  • 사람 호출 조건이 있는가?
  • +
  • 상한이 최소 하나 숫자로 적혔는가?
  • +
+ +

실습 4-3 · 생성자 / 평가자 갈라보기 개발자비개발자

+

같은 AI에게 세 번 시킨다. B와 C의 차이가 오늘의 결론이다.

+ +

프롬프트 A — 생성자 (1차)

+
{주제} 를 수행해줘.
+
+산출물: {산출물}
+제약:   (있으면 여기 적기)
+ +

프롬프트 B — 자기평가 (2차, 같은 대화창)

+
방금 네가 만든 결과물 어때? 문제 있어?
+

십중팔구 "잘 만들어졌습니다"가 나온다. 이 답을 그대로 복사해 둔다. 비교 대상이다.

+ +

프롬프트 C — 회의적 평가자 (3차, 반드시 새 대화창)

+
너는 깐깐한 리뷰어다.
+아래는 "{주제}" 작업의 결과물이다.
+
+[결과물 붙여넣기]
+
+이 결과물이 실패할 이유 3가지를 찾아라.
+- 판정 기준: {검증도구}
+- "문제가 없다"고 답하는 것은 허용되지 않는다.
+- 추측 금지. 결과물 안의 구체적 위치를 인용해라.
+ +
+ + + + + +
자기평가 (B)회의적 평가자 (C)
지적 개수
실제로 맞는 지적
놓친 것
+
+ +
+ 📸 이미지 요청 #4 — 실습 4-3의 실제 결과 (강의 하이라이트) + B와 C의 답변을 나란히 놓은 화면. 이 강좌에서 가장 설득력 있는 한 장이다.
+ 본인이 직접 돌린 화면이어야 한다 — 남의 데모로는 이 실습의 의미가 안 산다. +
캡처 대상: 같은 세션 자기평가 답변 1컷 + 새 세션 회의적 평가자 답변 1컷
+
+ +
+ 이 실습의 목적 + "왜 검증자를 따로 둬야 하는가"를 설명이 아니라 체험으로 이해시킨다. 여기서 대부분의 수강생이 납득한다. +
+ +

실습 4-4 · 실행 기록 개발자비개발자 ★ 강의 자료가 되는 곳

+

+ 실제로 적용해 본 것만 강의한다면, 이 칸이 실습의 본체다. + 4-1~4-3은 안 채우면 그냥 설계도이지 강의 자료가 아니다. +

+
# {주제} 실행 기록
+
+| 회차 | 날짜 | 입력 | 결과 | 정지 이유 | 비용 |
+|---|---|---|---|---|---|
+| 1 |  |  |  |  |  |
+| 2 |  |  |  |  |  |
+| 3 |  |  |  |  |  |
+
+## 실제로 부딪힌 것
+- 예상과 달랐던 것:
+- 종료 조건이 안 먹힌 순간:
+- 비용이 튄 지점:
+- 평가자가 잡아낸 것 중 나도 못 본 것:
+
+## 한 줄 결론
+> {주제} 에서 루프는 ______________ 였다.
+ +
+ 공유용 요약 — 수강생 배포분 + 여기만 잘라서 슬라이드로 쓴다. 문제 / 설계 / 종료 조건 / 결과 / 배운 것 다섯 줄이면 충분하다. + 자세한 건 워크시트 MD로 넘긴다. +
+ +

도구 실습 — 개발자 트랙 개발자

+
+ + + + + + + + + +
해볼 것방법
루프 돌리기Claude Code에서 /loop — 주기 반복. 인자 없이 쓰면 모델이 간격을 스스로 정한다.
조건까지 돌리기/goal — 검증 가능한 정지 조건을 주고 자리를 뜬다. 매 턴 별도 모델이 완료 판정.
서브에이전트 정의.claude/agents/에 마크다운 하나. 이름·설명·지시문·모델·도구 목록.
격리 실행서브에이전트에 isolation: worktree — 각자 새 체크아웃, 끝나면 자동 정리.
지식 박제CLAUDE.md 작성 → 매 세션 자동 로드. 반복 설명이 사라진다.
자동 실행 지점훅(Hook) 설정 — 도구 실행 전후에 셸 명령 자동 실행 (포맷·린트·로그).
외부 연결MCP 서버 연결 — 이슈트래커·DB·사내 API.
+
+

Codex 앱을 쓴다면 이름만 다르고 모양은 같다: Automations 탭 / 스레드별 워크트리 / .codex/agents/*.toml / Connectors.

+ +
+ 📸 이미지 요청 #5~#8 — 개발자 트랙 증빙 + #5 .claude/agents/ 폴더 + 서브에이전트 파일 하나 열린 화면
+ #6 서브에이전트 병렬 실행 중 화면 (여러 개가 동시에 도는 게 보이는 컷)
+ #7 git worktree list 출력 또는 워크트리 폴더 2개가 보이는 탐색기
+ #8 토큰·비용 표시 화면 — "루프는 돈을 태운다"의 증거 +
#6과 #8이 핵심. 나머지는 없으면 다이어그램으로 대체 가능.
+
+ +

도구 실습 — 비개발자 트랙 비개발자

+

코드 없이 루프의 모양을 만져보는 쪽. 셋 중 하나만 골라도 충분하다.

+
+ + + + + +
도구성격여기서 배울 부품
Dify웹 화면에서 노드를 이어 붙이는 LLM 앱 빌더③ 지식(RAG) · ④ 도구 연결 · 분기 조건
n8n범용 워크플로우 자동화 (스케줄·웹훅·수백 개 연동)① 오토메이션 · ④ 커넥터 · ⑥ 상태 저장
LangGraph루프·분기를 그래프로 명시하는 에이전트 프레임워크 (파이썬 필요)① 루프 구조 · ④ 종료 조건 · ⑤ 검증 노드
+
+
+ 비개발자에게 강조할 것 + 도구 사용법은 6개월이면 바뀐다. 안 바뀌는 것은 6개 부품과 종료 조건이다. + 도구를 배우지 말고 루프의 모양을 배우게 하라. +
+
+ + +
+ +

루프가 좋아질수록 더 날카로워지는 문제 3가지

+

루프는 일을 바꾸지, 당신을 지우지 않는다. 아래 셋은 루프가 매끄러울수록 쉬워지는 게 아니라 심해진다.

+ +
+
01검증은 여전히 당신 몫
+

+ 지켜보지 않고 도는 루프는 지켜보지 않고 실수하는 루프이기도 하다. + 검증자 서브에이전트를 생성자에서 떼어내는 이유가 루프의 "완료됐습니다"에 의미를 부여하기 위해서인데, + 그렇게 해도 '완료'는 여전히 주장이지 증명이 아니다. + 당신의 일은 동작을 확인한 코드를 내보내는 것이다. +

+
+ +
+
02이해력은 방치하면 썩는다Comprehension Debt
+

+ 루프가 내가 쓰지 않은 코드를 빨리 뱉을수록, 존재하는 것과 내가 이해한 것 사이의 간격이 벌어진다. + 매끄러운 루프는 이 부채를 더 빨리 키운다. 방어책은 하나뿐 — 루프가 만든 걸 읽는 것. +

+
+ +
+
03가장 편한 자세가 가장 위험하다Cognitive Surrender
+

+ 루프가 알아서 돌면 의견 갖기를 그만두고 주는 대로 받고 싶어진다. + 루프 설계는 판단력을 가지고 하면 해독제이고, 생각을 피하려고 하면 촉진제다. 같은 행동, 정반대 결과. +

+
+ 이 강의에서 가장 중요한 문단 + 두 사람이 완전히 똑같은 루프를 만들어도 결과는 정반대일 수 있다. + 한 사람은 깊이 이해한 일에서 더 빨라지려고 쓰고, 다른 한 사람은 이해 자체를 회피하려고 쓴다. + 루프는 그 차이를 모른다. 당신은 안다. +
+
+ +

비용 함정

+
    +
  • 토큰 부자와 토큰 빈자의 사용 패턴은 완전히 다르다. 남이 자랑하는 루프를 그대로 따라 하기 전에 내 예산부터 본다.
  • +
  • 서브에이전트는 각자 모델을 돌린다 → 에이전트 수 × 토큰. 두 번째 의견이 값어치 있는 곳에만 붙인다.
  • +
  • 종료 조건에 비용 상한을 반드시 넣는다. 시간·횟수만으로는 안 막힌다.
  • +
  • 하네스 사례 수치: Solo $9(20분, 작동 불가) vs Harness $200(6시간, 동작). 비쌀 값어치가 있는지는 만들 물건에 따라 매번 다시 판단한다.
  • +
+ +

루프 설계 체크리스트

+
    +
  • 종료 조건이 기계 판정 가능한가? ("좋아 보이면"은 조건이 아니다)
  • +
  • 실패 종료와 에스컬레이션이 따로 있는가? (성공 조건만 있으면 무한 루프)
  • +
  • 비용 상한이 있는가? (토큰·금액·시간 중 최소 하나)
  • +
  • 생성자와 평가자가 분리돼 있는가? (같은 대화창 = 자기 채점)
  • +
  • 평가자가 회의적으로 튜닝됐는가? ("문제없음" 답변을 금지했는가)
  • +
  • 완료의 정의를 시작 전에 합의했는가? (스프린트 계약)
  • +
  • 상태를 디스크에 적는가? (컨텍스트에만 있으면 다음 실행 때 증발)
  • +
  • 병렬 실행이 워크트리로 격리됐는가? (아니면 파일 충돌)
  • +
  • 기획자가 기술 세부에 너무 일찍 매몰되지 않았는가? (연쇄 오류)
  • +
  • 모델이 좋아져서 이제 필요 없어진 구조물은 없는가? (스캐폴딩 철거)
  • +
  • 루프가 만든 결과물을 내가 읽었는가? (이해 부채)
  • +
+ +
+ 마지막 항목 부연 — 하네스는 고정 장치가 아니다 + 모델의 내재 능력이 늘어나면 기존 하네스 요소는 불필요한 오버헤드가 된다. + 엔지니어의 임무는 모델이 스스로 하게 된 영역의 발판을 과감히 철거하고, 아직 정복되지 않은 다음 경계로 하네스를 옮기는 것이다. +
+ +

마무리 한 줄

+
+ 루프를 만들어라. 단, '시작 버튼 누르는 사람'이 아니라 '계속 엔지니어로 남을 사람'처럼 만들어라. +
+

+ 루프 설계가 프롬프트 엔지니어링보다 쉬워진 게 아니다. 지렛대의 받침점이 옮겨간 것이다. +

+
+ + +
+ +

0교시 · AI 기초 30분 (조건부)

+

+ 수강생이 토큰·컨텍스트 윈도우·할루시네이션을 모르면 본 강의는 성립하지 않는다. + 사전 확인 질문 4개를 던져 보고, 막히면 아래를 먼저 돌린다. → 「AI 기초」 탭 +

+
+ + + + + + + + + +
시간섹션핵심 한 줄
0–40. 지도AI ⊃ 머신러닝 ⊃ 딥러닝 ⊃ 생성형 ⊃ LLM. "AI 발전"의 99%는 맨 안쪽 얘기.
4–121강. LLM이란다음 단어를 맞히는 기계. 학습 ≠ 추론. 할루시네이션·컷오프·계산 실수가 전부 여기서 파생.
12–192강. 토큰·윈도우기억력이자 요금. 대화가 길수록 같은 질문도 비싸진다. 실전 규칙 3개.
19–233강. 프롬프트역할·목표·제약·형식 4요소. 나쁜 예/좋은 예 1쌍.
23–264강. 모델 고르기등급·세대·추론 모드. "제일 비싼 게 항상 정답이 아니다."
26–285강. 정보 주입 3법붙여넣기 / RAG / 파인튜닝. 대부분 RAG면 끝난다.
28–306강. 챗봇→에이전트가르는 선은 도구 사용. 여기서 본 강의로 넘어간다.
+
+
+ 사전 확인 질문 4개 + ① LLM이 하는 일을 한 문장으로? ② 대화가 길어지면 왜 요금이 오르나? + ③ 회사 문서 1만 건을 AI가 알게 하려면 셋 중 무엇? ④ 챗봇과 에이전트를 가르는 선은?
+ 셋 이상 막히면 0교시 필수. 전원이 답하면 건너뛰고 바로 본 강의. +
+ +

90분 진행표 (강사용)

+

대상: 기초 용어가 되는 초보자(0교시 통과자). 개발·비개발 혼합 가능. 실습 4-2와 4-3은 빼지 말 것.

+ +
+ + + + + + + + + + +
시간섹션내용형식
0–10왜 헷갈리는가챗봇 → 일하는 시스템. 4층 구조. 2024 vs 2026 표.강의
10–20키워드 ①②루프 엔지니어링 · 하네스 엔지니어링. $9 vs $200 사례로 못 박기.강의
20–30키워드 ③④⑤에이전틱 워크플로우 · 종료 조건 · 동적 컨텍스트.강의
30–45실습 4-3자기평가 vs 회의적 평가자 비교. 각자 실행 후 3~4명 결과 공유.실습·공유
45–55루프의 6조각오토메이션·워크트리·스킬·커넥터·서브에이전트·상태. 도구 매핑 표.강의
55–70실습 4-1·4-2내 업무로 루프 설계서 채우기 + 종료 조건 쓰기. 2인 1조 상호 첨삭.실습
70–80함정 3가지검증 책임 · 이해 부채 · 인지적 항복. 비용 함정.강의
80–90체크리스트·Q&A11항목 체크리스트 배포. 용어집 24 안내.정리
+
+ +

대상별 조정

+
+ + + + + + + + + + + + + + + + + + + + + + +
대상강조점덜어낼 것
완전 초보
용어부터 막힘
0교시 30분 필수. 특히 2강(토큰·윈도우)과 6강(챗봇→에이전트). 본 강의는 키워드 5 + 실습 4-3만 해도 된다루프의 6조각 · 함정 3가지 · 진행표. 2회차로 넘긴다
개발자
IT 실무자
/goal과 /loop의 차이, 워크트리 격리, 서브에이전트 정의 파일, 훅, MCP토큰·컨텍스트 윈도우 기초 설명
기획자
마케터
종료 조건 작성, 생성자–평가자 분리, Dify·n8n으로 흐름 만져보기워크트리, git, 서브에이전트 파일 포맷
경영진
의사결정자
$9 vs $200 표, 오케스트레이션 세금(사람이 상한선), 검증 책임 소재실습 전체 → 4-3 데모만 시연
+
+ +

기존 커리큘럼과의 접속

+

이 강좌는 사내 세미나 자료의 Part 7·8을 초보자용으로 앞당겨 묶은 것이다. 심화로 넘길 때 연결점:

+
    +
  • Part 2. 프롬프트 엔지니어링 → 4층 구조 맨 아래층. 여전히 유효하되 루프 안에서 자동 생성된다는 점만 갱신.
  • +
  • Part 3. 컨텍스트 엔지니어링 → 키워드 ⑤ 동적 컨텍스트로 이어짐. 리셋 vs 압축 전략은 여기서 심화.
  • +
  • Part 4. Skills / Part 5. SubAgent → 루프 6조각 중 ③ ⑤. 이 강좌는 왜 필요한지만, 심화는 어떻게 쓰는지.
  • +
  • Part 6. 오케스트레이션 → 키워드 ③ 에이전틱 워크플로우의 확장.
  • +
  • Part 7. 하네스 엔지니어링 → 키워드 ②의 심화. 평가자 보정, Playwright 기반 동적 테스트, 3-에이전트 아키텍처.
  • +
  • Part 8. Loop 엔지니어링 → 키워드 ①의 원문. 5 primitives 표의 출처.
  • +
+ +
+ 고지 사항 — 수강생에게 반드시 + 도구 이름과 슬래시 명령은 2026년 8월 기준이며 빠르게 바뀐다. + 바뀌지 않는 것은 6개 부품과 종료 조건, 그리고 만든 놈이 검사하지 않는다는 원칙이다. +
+
+ +
+ + + + + + + diff --git a/docs/2d-3d-conversion.md b/docs/2d-3d-conversion.md new file mode 100644 index 0000000..bf5126e --- /dev/null +++ b/docs/2d-3d-conversion.md @@ -0,0 +1,1630 @@ +# 2D → 3D 전환 노하우 + +DWG 도면(평면도 + 다수 단면도)을 읽어 3D 솔리드 모델로 세우는 작업의 축적 문서. +작업하면서 얻은 교훈을 계속 덧붙인다. 최신 항목이 아래로 쌓인다. + +- 대상 도면: `drawings-src/04manhole1-simplify.dwg` (맨홀 #1 및 출입구 일반도, 4매) +- 파이프라인 코드: [src/](../src/) +- 3D 생성 수단: EGModeler + `egm` CLI (`D:\GiteaAll\EG-BIM_Modeler`) + +> **절차**는 [`2d-3d-process.md`](2d-3d-process.md) 에 따로 있다. +> 이 문서는 **왜 그렇게 하는가**, 그 문서는 **무엇을 어떤 순서로 하는가**. + +--- + +## 1. 교훈 #1 — 기준점을 먼저 정하면 나머지가 쉬워진다 + +**가장 크게 시간을 아낀 판단.** 도면마다 기준점을 미리 확정해두면, 이후 모든 좌표가 +단순 뺄셈으로 나온다. 반대로 기준점 없이 형상부터 뽑으면 뷰마다 좌표계가 달라 +정합 단계에서 전부 다시 해야 한다. + +### 프로젝트 좌표계 (이 도면 기준) + +| 축 | 방향 | 원점 | +|---|---|---| +| X | 전력구 길이방향 | 구조물 최좌단(분기구 #1 쪽 구체 외면) | +| Y | 폭방향 | 전력구 중심선 | +| Z | 표고 | 도로 계획고 (아래로 음수) | + +단위는 **내부 계산 mm 정수**, egm에 넘기기 직전에만 m로 나눈다. +mm 정수로 반올림하면 DWG의 부동소수 노이즈(`-500.00000000001455`)가 자동으로 흡수된다. + +### 기준점은 도면에서 "검출"해야지 눈으로 읽으면 안 된다 + +Z 기준을 `도로 계획고` **텍스트 위치**로 잡았다가 713mm 틀렸다. +텍스트는 라벨이고, 실제 지반선은 그 아래에 따로 있다. + +``` +도로 계획고 TEXT DWG y = 483624.19 ← 라벨. 쓰면 안 됨 +도로 계획고 LINE DWG y = 483623.477 ← 정답 (레이어 `0지반`) +``` + +검증법: 기준점을 잡은 뒤 **도면의 치수 숫자와 대조**한다. 맞으면 확정. + +``` +구체 상단 Z = -4,907 ↔ B-B 도면 치수 4,907 ✓ +기초 하단 Z = -7,757 ↔ A-A 도면 치수 7,757 ✓ +``` + +### 기준선 후보가 둘이면 "라벨 바로 아래"로 가른다 + +계획고는 보통 **도로/토공 두 줄이 나란히** 그려진다. 둘 다 모든 뷰에 나타나므로 +"가장 많은 뷰에 반복되는 수평선"만으로는 갈리지 않는다. 실제로 토공 계획고를 +집어 420mm 어긋났다. + +라벨은 자기 선을 가리킨다. **라벨 바로 아래에 있는 선**이 그 라벨의 선이다. +출현 횟수는 잡음을 거르는 문턱으로만 쓰고, 최종 선택은 라벨 근접도로 한다. + +``` +도로 계획고 라벨 y = 483624.19 + → 도로 계획고 선 483623.477 (0.713 아래) ← 선택 + → 토공 계획고 선 483623.057 (1.133 아래) +``` + +### 공통 datum이 있으면 무조건 그것부터 찾아라 + +이 도면은 **모든 단면이 같은 y에 계획고 선을 그렸다.** 그래서 단면별 Z 오프셋 +계산이 통째로 사라졌다. 7개 단면에서 독립적으로 검출한 지반선이 전부 `Z=0`, +`Z=-420`으로 떨어져 기준점이 교차 검증됐다. + +토목 도면에서는 계획고/GL/기준선(EL.)이 이 역할을 한다. 건축이면 FL/층레벨. +**단면도를 배치할 때 표고선을 맞춰 그리는 관행**이 그대로 3D 정합 데이터가 된다. + +> 다음에 이 작업을 또 한다면: 도면 작성자에게 "각 뷰에 기준점 마커를 하나씩 +> 찍어달라"고 요청하는 게 가장 싸다. 검출 로직 전체가 필요 없어진다. + +--- + +## 2. 교훈 #2 — 뷰 분리는 제목 bbox가 아니라 기하 간격으로 + +여러 도면이 한 모델스페이스에 나열된 경우, 뷰를 먼저 갈라야 한다. + +**실패한 방법**: 제목 텍스트 위치로 Voronoi 분할 → A-A와 B-B 영역이 겹쳐 +B-B 추출 결과에 A-A 기하가 섞여 들어왔다. + +**간격 임계값 하나로는 절대 안 된다.** 넓게 잡으면 이웃이 딸려오고, 좁게 잡으면 +한 뷰가 쪼개진다. 이 도면에서 둘 다 실제로 터졌다. + +| 증상 | 원인 | +|---|---| +| A-A 하단(기초·잡석)이 잘려나감 | 구체와 기초 사이가 2.1m 비어 있어 임계값 1.2m에 잘림 | +| I-I 폭이 6,000 → 12,954 로 부풀음 | 4,239m 떨어진 HOOK 상세도 조각이 붙어옴 | + +### 되는 방법: 잘게 쪼갠 뒤 제목으로 다시 묶는다 + +``` +1. 구조 레이어 기하만 모은다 +2. x ↔ y 를 번갈아 가며 더 이상 갈라지지 않을 때까지 잘게 쪼갠다 (임계값 0.2m) +3. 조각마다 "바로 위에 있는 제목"을 찾아 귀속시킨다 +4. 같은 제목을 가진 조각끼리 합치면 그게 뷰다 +``` + +x→y 한 번씩만 하면 부족하다. **y로 나눈 뒤에야 드러나는 x 간격이 있다** — +I-I 옆 상세도 조각이 정확히 그 경우였다. 안정될 때까지 교대로 돌려야 한다. + +제목 귀속이 핵심 장치다. 상세도·재료표도 제목 후보에 넣어두면 자기 조각을 +가져가므로, 뷰 쪽으로 새어 들어오지 않는다. 뷰 제목이 아닌 것에 귀속된 조각은 +그대로 버리면 된다. + +### 제목 후보를 고를 때 — 글자 높이로 거른다 + +``` +h=0.5 시트명 (맨홀 #1 및 출입구 일반도 (1)~(4)) +h=0.35 뷰 제목 · 상세도 · 재료표 ← 여기까지가 표제 +h=0.30 부재 방향 라벨 (분기구 #1, 맨홀 #2) ← 표제 아님. 걸러야 함 +``` + +`h=0.30`짜리 방향 라벨을 안 걸렀더니 A-A 하단부를 가져가 뷰가 잘려나갔다. +또 "제목이 조각 상단보다 살짝 아래여도 허용"하는 여유값을 크게(0.6m) 잡았더니 +옆에 붙은 `상부 SLAB용` 표제가 I-I를 통째로 훔쳐갔다. 0.2m로 줄여 해결. + +### 분할 결과 검증은 폭·높이를 도면 치수와 대조 + +``` +H-H 10,200 × 2,800 도면 표기 10,200 / 2,800 ✓ +I-I 6,000 × 2,800 도면 표기 6,000 / 2,800 ✓ +J-J 6,000 × 2,800 도면 표기 6,000 / 2,800 ✓ +A-A 20,200 × 10,839 ↔ E-E 10,839 ← 심도 일치, 교차 검증 +``` + +폭이 도면값보다 크면 잡석/기초처럼 바깥으로 삐져나온 요소이거나(정상), +이웃 뷰가 딸려온 것(버그)이다. 차이값이 딱 떨어지는지 확인하면 구분된다. +서로 다른 뷰의 심도가 같게 나오면 기준점이 맞다는 강한 방증이다. + +--- + +## 3. 교훈 #3 — 닫힌 폴리라인을 DWG에서 만들 필요가 없다 + +처음엔 "AutoCAD에서 `BOUNDARY`로 닫힌 폴리라인을 만들어 달라"고 요청했다. +**불필요했다.** 3D 툴(egm)이 좌표 리스트로 폴리라인을 직접 만들 수 있으면, +DWG에서는 **꼭짓점 좌표만** 뽑으면 된다. + +``` +DWG 파싱 → 꼭짓점 순서 계산 → egm "Polyline x,y,z ..." → Extrude → Solid + ↑ 여기서 닫힌 커브가 새로 생김 +``` + +즉 문제가 "닫힌 영역 찾기(어려움)"에서 "꼭짓점 순서 정렬(쉬움)"로 바뀐다. +DWG 편집 권한도, DWG writer도 필요 없다. + +### 게다가 형상이 규칙적이면 기하 대신 치수를 쓰는 게 낫다 + +이 도면의 구조체는 거의 전부 **직사각형 + 모따기(헌치)**다. +LINE 위상을 추적하는 것보다, 치수 텍스트에서 파라미터를 뽑아 프로파일을 +파라메트릭으로 생성하는 쪽이 짧고 정확하다. + +단, **기하에서 뽑은 값과 치수 표기값을 서로 대조**하는 게 핵심이다. +둘이 일치하면 그 단면은 신뢰할 수 있다. 실제로 B-B에서 순수 기하만으로 뽑은 +`2,700 / 2,100 / 2,750 / 2,150 / 150x150 / THK100 / THK300`이 도면 표기와 +전부 일치했다. + +--- + +## 3.5. 교훈 #4 — DWG를 뷰별로 미리 쪼개지 마라 + +"뷰마다 별도 DWG로 분리해두면 편하지 않겠나"는 자연스러운 발상이지만, **손해다.** + +| | 한 도면 유지 | 뷰별 분리 | +|---|---|---| +| 표고 정합 | 공짜 — 전 단면이 같은 y에 계획고선을 공유 | 파일마다 원점이 따로 놀아 관계가 사라짐 | +| 절단기호 ↔ 단면 매핑 | `SYM` 좌표로 자동 연결 | 파일 간 참조가 끊겨 수동 매핑 | +| 뷰 분리 비용 | 간격 클러스터링 ~30줄 | (불필요하나 위 손실이 더 크다) | + +이 작업에서 가장 큰 이득이 **"모든 단면이 같은 표고선에 맞춰 배치돼 있다"**였는데, +이건 한 모델스페이스에 함께 있어야만 성립한다. 분리하면 3D 정합의 절반을 버린다. + +분리가 이득인 경우는 제한적이다 — 파일이 너무 커서 파싱이 안 되거나(이 도면은 +380KB / 7,035 엔티티로 여유), 축척·좌표계가 다른 프로젝트가 섞여 있을 때. + +### 대신 도면에 해주면 좋은 것 + +``` +레이어 "3D-ORIGIN" 을 만들고, 각 뷰에 POINT 를 하나씩 찍어 기준점을 표시 +``` + +기준점 검출 로직이 통째로 사라지고, 텍스트/선 혼동 같은 사고도 원천 차단된다. +분리보다 훨씬 싸고 효과가 크다. 여력이 되면 구조체 외곽선 레이어도 하나로 통일. + +--- + +## 3.6. 교훈 #5 — 캡처 이미지에 좌표 변환을 실어 보내라 + +기하 파싱만으로는 "이 폐곡선이 구체인가 치수보조선인가" 같은 판단이 어렵다. +반대로 이미지를 보면 사람은 즉시 안다. **둘을 합치는 장치**가 필요하다. + +렌더 캡처 PNG를 만들 때 **픽셀 ↔ DWG 좌표 변환을 사이드카로 같이 저장**하면, +이미지에서 짚은 지점을 곧바로 도면 좌표(그리고 프로젝트 mm)로 환산할 수 있다. + +```jsonc +// 04manhole1_단면B-B.json (같은 이름의 PNG 옆에) +{ + "view": "단 면 B-B", + "image": { "w": 1188, "h": 1400 }, + // 이미지 좌상단 픽셀에 대응하는 DWG 좌표와 픽셀당 DWG 단위 + "originDwg": [-48352.400, 483619.100], + "unitsPerPx": 0.0043, + "yFlipped": true, // 화면 y는 아래로 증가, DWG y는 위로 증가 + "roadLevelY": 483623.477 // 프로젝트 Z=0 기준 +} +``` + +변환: + +``` +dwgX = originDwg[0] + px * unitsPerPx +dwgY = originDwg[1] - py * unitsPerPx // yFlipped +프로젝트 Z(mm) = round((dwgY - roadLevelY) * 1000) +``` + +### 변환을 "역산"하지 말고 "지정"해라 — 뷰 박스로 정확히 줌해서 캡처 + +렌더 결과를 놓고 축척을 되짚으면 여백·DPI·레터박싱 어딘가에서 반드시 어긋난다. +**줌 rect 를 먼저 지정하고 캔버스 크기를 축척에서 역산**하면 어긋날 구석이 없다. + +``` +1. 뷰 분리 결과(views.json)에 각 뷰의 DWG bbox 가 이미 있다 +2. 축척을 고른다. 예: 5 mm/px +3. 캔버스 크기 = bbox 크기 / 축척 + 단면 B-B 4,300 × 3,150mm → 860 × 630 px +4. 그 rect 에 정확히 맞춰 줌 → 캡처 +``` + +이러면 `unitsPerPx` 가 계산값이 아니라 **내가 정한 값 그대로**다. 뷰 박스와 +캔버스의 종횡비가 자동으로 같아지므로 레터박싱 여백 보정도 필요 없다. + +부수 효과로 뷰별 캡처가 자동화된다 — 분리 결과를 읽어 루프만 돌리면 된다. + +이게 있으면 작업 방식이 바뀐다: + +- 추출 결과를 이미지 위에 겹쳐 그려 **눈으로 검증**할 수 있다 +- 애매한 형상을 이미지에서 짚으면 정확한 좌표가 나온다 +- "여기 폐곡선이 안 잡힌다" 같은 피드백을 좌표로 주고받을 수 있다 + +이 저장소의 Viewer2D는 이미 DWG→화면 변환을 알고 있으므로, 캡처 시 그 행렬을 +JSON으로 함께 떨구기만 하면 된다. 렌더러 밖에서 역산하려 들면 축척·여백·DPI에서 +반드시 어긋난다 — **변환은 렌더러가 알고 있을 때 받아 적는 게 유일하게 안전하다.** + +--- + +## 4. 도면 읽기 — 의미 추론에서 실제로 통한 단서들 + +| 단서 | 쓰임 | +|---|---| +| 제목 텍스트 높이 | `h=0.5` 시트명, `h=0.35` 뷰 제목, `h≤0.15` 치수/주기 | +| 레이어 `SYM` | 단면 절단 기호의 **글자 라벨만** 들어있다 (A~J 각 2개 = 절단선 양끝) | +| 절단 기호 좌표쌍 | 두 글자의 x가 같으면 **수직 절단**, y가 같으면 **수평 절단** | +| 재료 주기 텍스트 | `구체 콘크리트 fck=27MPa`, `기초 콘크리트 THK=100mm` → 두께·재질 파라미터 직독 | +| 치수 `actualMeasurement` | 실측값(mm). 도면 축척과 무관하게 정확 | + +### 수평 단면을 놓치지 말 것 + +`H-H` / `I-I` / `J-J`는 수직 단면이 아니라 **E-E 단면 위에 표고별로 찍힌 수평 절단**이다. +평면 형상을 층별로 주는 데이터라서, 내부 격실 배치를 세울 때 가장 중요하다. +절단 기호가 *다른 단면도 위에* 찍혀 있으면 십중팔구 수평 단면이다. + +--- + +## 4.5. 교훈 #6 — 재질은 **레이어 이름이 이미 인코딩**하고 있다 + +재질 주기 텍스트를 지시선 따라가며 형상에 잇는 건 어렵다. 그런데 할 필요가 없다. + +``` +레이어 -전력구 / 전력구 / 집수정 / A-신규선형-구조물-맨홀 … → 구체 콘크리트 +레이어 !-기초 → 기초 콘크리트 +레이어 잡석 → 잡석 +레이어 -보도 → 보도 콘크리트 +``` + +레이어가 재질을, 텍스트 주기가 **그 재질의 사양**(fck, THK)을 준다. 둘을 곱하면 +재질 배정이 끝난다. 지시선 추적이 통째로 사라진다. + +### 사양은 전역 상수로 박으면 안 된다 — 위치마다 다르다 + +`src/materials.mjs` 로 뷰별 사양을 뽑아보니 실제로 갈렸다. + +| 재질 | 사양 | 적용 뷰 | +|---|---|---| +| 구체 콘크리트 | fck=27MPa | 전 단면 공통 | +| 기초 콘크리트 | fck=18MPa, THK=100mm | 전 단면 공통 | +| **잡석** | **THK=500mm** | A-A, C-C, E-E, F-F, G-G | +| **잡석** | **THK=300mm** | **B-B, D-D** | +| **보도 콘크리트** | **fck=27MPa** | A-A | +| **보도 콘크리트** | **fck=21MPa** | E-E, F-F, G-G | + +잡석 두께와 보도 콘크리트 강도가 위치별로 다르다. "잡석 = THK 500" 으로 박으면 +전력구 구간이 200mm 두꺼워진다. **반드시 뷰별로 읽을 것.** + +### IFC 매핑 초안 + +레이어 → 재질이 잡히면 IFC 클래스·재질 배정까지 그대로 이어진다. + +| 부재 | IFC 클래스 | 재질 | 비고 | +|---|---|---|---| +| 구체 벽체 | `IfcWall` | Concrete C27 | | +| 구체 상·하부 슬래브 | `IfcSlab` | Concrete C27 | | +| 기초 콘크리트 | `IfcFooting` | Concrete C18 | 버림 콘크리트 | +| 보도 슬래브 | `IfcSlab` | Concrete C21/C27 | 뷰별 상이 | +| 잡석 부설 | civil 계열 (확인 필요) | Crushed stone | `IfcCourse`/`IfcEarthworksFill` 후보 | +| 토목섬유 | civil 계열 (확인 필요) | Geotextile | | +| 전력구 전체 | `IfcBuiltElement` 또는 civil 계열 | — | 집합 요소 | + +재질 속성은 `IfcMaterial` + `Pset_MaterialConcrete`(CompressiveStrength) 로, +층상 구성(잡석 → 기초 → 구체)은 `IfcMaterialLayerSet` 으로 표현한다. + +도면 표제란에서 같이 읽히는 설계 정보 — `IfcProject` 속성으로 붙이면 된다. + +``` +콘크리트 설계기준 강도 fck = 27 MPa +철근 항복강도 fy = 400 MPa +설계법 신뢰성 기반 설계법 +설계하중 한국형 표준트럭하중(KL-510) +``` + +> **미확인**: 잡석·토목섬유에 쓸 IFC4.3 civil 엔티티 이름은 스키마를 직접 확인하고 +> 확정할 것. 위 표의 후보는 검증 전이다. 건축계 엔티티(`IfcWall`/`IfcSlab`/ +> `IfcFooting`)와 재질 계열(`IfcMaterial*`, `Pset_MaterialConcrete`)은 IFC4 표준이라 +> 그대로 써도 된다. + +--- + +## 4.55. 교훈 #7 — 종단면은 "형상"이 아니라 **"구간 배정표"**로 읽어라 + +가장 크게 틀렸던 지점. 횡단면 하나(B-B)를 보고 "이게 이 구조물의 단면"이라 +단정해 15.4m 전체에 밀었다. **실제로는 절단 위치마다 단면이 다르다.** + +``` +X -1,000 ~ 0 입구 스터브 B-B 구체 2,700×2,750 +X 0 ~ 15,400 전력구 본체 C-C 구체 3,800×4,600 ← 본체는 이쪽 +X 18,200 ~ 19,200 출구 스터브 B-B +``` + +**어디서 어디까지 어느 단면인지는 종단면에만 있다.** 횡단면은 "그 지점의 모습"만 +알려줄 뿐, 그게 어디까지 유효한지는 말해주지 않는다. + +``` +잘못된 순서 횡단면 → 압출 +올바른 순서 종단면에서 구간 분할 → 구간별 횡단면 배정 → 압출 +``` + +### 종단면 읽는 법 — 수평선을 Z 내림차순으로 X 범위와 함께 늘어놓는다 + +각 수평선의 X 범위가 곧 "그 표고가 유효한 구간"이다. 구간 경계가 바로 단면이 +바뀌는 자리다. + +``` +Z= -3,157 X 0 ~ 1,700 · 3,400 ~ 12,400 … 본체 구체 상단 +Z= -3,557 X 550 ~ 15,250 본체 내부 천장 (슬래브 400) +Z= -4,907 X -1,000 ~ -299 스터브 구체 상단 ← 단면 전환 +Z= -7,357 X -1,000 ~ 19,200 (전 구간) 내부 바닥 — 두 구간이 공유 +Z= -7,757 X 0 ~ 15,100 본체 구체 하단 +``` + +여기서 **바닥(-7,357)은 전 구간 공통이고 천장만 달라진다**는 것도 바로 보인다. +횡단면만 봐서는 절대 알 수 없는 정보다. + +### 횡단면이 인접 형상과 붙어 한 덩어리로 잡히는 경우 + +C-C 의 구체 외곽 폐곡선은 3,800×**7,557** 로 나온다 — 위로 솟은 샤프트가 +같은 윤곽에 붙어 있기 때문이다. 이럴 때는 직사각으로 따로 만들되, +**치수는 반드시 다른 뷰에서 읽은 실측치**를 쓰고 교차검증한다. + +```js +expect('단 면 C-C', 3800, 7557, 'C-C 외곽은 샤프트 포함 형상이어야 한다'); +profile('main.outer', 'YZ', rect(-1900, -7757, 1900, -3157)); // A-A 에서 읽은 표고 +``` + +손으로 지어낸 숫자를 넣기 시작하면 파이프라인의 의미가 사라진다. + +--- + +## 4.56. 교훈 #8 — 절단 기호의 좌표쌍이 단면의 **평면**을 결정한다 + +`SYM` 레이어의 글자쌍은 절단선 양끝이다. 두 점의 어느 좌표가 같은지가 그 단면이 +어느 평면을 그리는지를 그대로 알려준다. + +``` +두 점의 x 가 같다 → 세로 절단선 → YZ 단면 (폭 방향을 본다) B, C, D, E +두 점의 y 가 같다 → 가로 절단선 → XZ 단면 (길이 방향을 본다) A, F, G +``` + +**폭 치수만으로는 구분이 안 되는 경우가 있다.** 이 환기구는 X 도 2,600, Y 도 +2,600 이라 숫자가 같다. 반드시 절단 기호 좌표로 판정할 것. + +### 압출축이 어느 방향 벽을 만드는지 항상 의식하라 + +``` +YZ 단면을 X 축으로 밀면 → Y 방향 벽만 생긴다. X 방향은 열린다. +XZ 단면을 Y 축으로 밀면 → X 방향 벽만 생긴다. Y 방향은 열린다. +``` + +맨홀을 E-E(YZ)만으로 X 축 압출했더니 X 방향 벽이 없어 안이 들여다보였다. + +### 나머지 방향 벽은 "공동의 압출 범위를 좁혀서" 만든다 + +두 방향 단면을 교집합할 필요가 없다. **외곽은 전 구간을 채우고 공동만 안쪽으로 +줄이면** 앞뒤로 벽이 남는다. + +```js +part({ id:'mh.solid', op:'extrude', from:15400, to:18200 }); // 외곽 전폭 +part({ id:'mh.cut.upper',op:'extrude', from:15800, to:17800 }); // 내벽 사이만 +// ↑ 좌우 400 벽이 남는다 +``` + +공동의 끝이 솔리드 **내부**라 면이 겹치지 않는다 — 여유(OVER)도 필요 없다. + +--- + +## 4.57. 교훈 #9 — 오목한 외곽은 "봉투"부터 만들어라 + +평면 분할은 오목한 윤곽의 **패인 부분을 별개 면으로** 뽑는다. 그 면이 +되메움부인지 실제 공동인지는 크기로 알 수 없다. + +이 맨홀에서 E-E 외곽(#0)은 상부 통로 자리를 오목하게 파고 지나간다. +**그 영역에는 애초에 솔리드가 없다.** 공동을 아무리 좁게 뚫어도 만들 벽이 없다. + +``` +mh.shell = 외곽(#0) 압출 ← 오목한 채로 +mh.fill = 통로면(#1) 압출 ← 패인 부분을 채움 +mh.solid = union(shell, fill) ← 비로소 전체 봉투 +mh.body = solid − 공동들 ← 이제 벽이 남는다 +``` + +**순서가 핵심이다.** 봉투를 먼저 만들고 그 다음에 뚫는다. 오목한 외곽을 그대로 +쓰면 "뚫을 것이 없는" 상태에서 뚫는 셈이라 아무 일도 일어나지 않는다. + +--- + +## 4.58. 교훈 #10 — 구간이 맞닿으면 union 후 **한 번만** difference + +단면이 다른 구간이 이어질 때, 구간별로 따로 빼면 **경계에서 반드시 구멍이 난다.** +큰 쪽 공동이 여유(OVER)만큼 작은 쪽으로 넘어가 단차 벽면을 파먹기 때문이다. + +``` +✗ main.body = main.solid − main.void (void 가 -200 까지 넘어감) + stubL.body = stubL.solid − stubL.void + → 스터브 끝벽이 사라져 안이 들여다보인다 + +✓ duct.solid = union(main.solid, stubL.solid) + duct.void = union(main.void, stubL.void) ← 전이점에서 딱 맞물리게 + duct.body = duct.solid − duct.void +``` + +공동끼리는 전이점에서 **겹치지도 벌어지지도 않게** 나눈다. +`main.void: 250→15,600`, `stubL.void: -1,200→250`. + +**단, 여기서 union 하는 것은 "구간 몸통"이지 "커터"가 아니다.** 서로 떨어진 커터를 +하나로 합치려 들면 안 된다 — 교훈 #14 를 볼 것. + +--- + +## 4.59. 교훈 #11 — 수평 단면은 세로축이 표고가 아니다 + +`H-H`/`I-I`/`J-J` 같은 수평 절단은 **도면 가로축이 프로젝트 Y, 세로축이 X** 다. +그 뷰가 도면 어느 높이에 놓였는지는 임의라, 세로 좌표를 그대로 Z 로 쓰면 안 된다. + +``` +H-H u 0~10,200 → Y -1,900~8,300 (평면도 맨홀 폭과 일치) + v -1,725~1,075 → X 15,400~18,200 (맨홀 X 범위와 일치) +``` + +앵커에 `v0` 와 `swapUV` 를 둬서 `[Y,X]` 를 `[X,Y]` 순서로 바꾼다. +**축을 뒤집거나 맞바꾸면 감김 방향이 반대가 되므로** 뒤집기 횟수가 홀수면 +꼭짓점 순서를 되돌려야 면적이 양수로 유지된다. + +맞게 잡혔는지는 다른 뷰와 교차검증하면 바로 안다. + +``` +J-J #1 X 15,800~17,800 × Y 200~3,700 집수조 3,500 ← E-E 와 일치 +J-J #2 X 15,800~17,800 × Y -1,500~0 침사조 1,500 ← E-E 와 일치 +H-H #3 X 15,800~17,000 × Y -500~1,500 환기 OPEN부 ← 주기 "2.0x1.2" 와 일치 +I-I #4 X 15,800~17,000 × Y 1,800~2,800 점검구 ← 주기 "1.0x1.2" 와 일치 +``` + +--- + +## 4.591. 교훈 #12 — 링은 못 믿어도 **bbox 는 믿을 수 있다** + +개구부 폐곡선을 그대로 썼더니 자기교차하는 쓰레기가 나왔다. + +``` +mh.ventHole [15950,1500] [15800,1500] [17000,-500] [17000,1500] [15800,-500] … + ↑ 대각선으로 튄다 +``` + +원인이 셋 겹쳤다. + +1. 개구부 윤곽이 `000-구조물실선` 에 **모서리가 안 닫힌 채로** 그려져 있다 +2. 그 자리에 `-전력구` 레이어 선이 겹쳐 있다 +3. 개구 기호(X 표시)와 지시선이 사각형 안을 가로지른다 + +평면 분할은 이 선들을 전부 경계로 삼으므로 엉뚱한 면을 따라 돈다. + +**사람 눈에는 멀쩡한 사각형으로 보인다.** 끊긴 폭이 몇 mm라 화면에서 안 보이고, +X 표시는 개구 기호로 읽고 넘어간다. **기계만 걸리는 종류의 결함**이다. + +### 대응 — bbox 로 재구성하고 폐곡선은 확인용으로만 + +끊기거나 오염돼도 **범위(bbox)는 정확하다**. 형상이 직사각형이라는 걸 알면 +bbox 만으로 복원할 수 있다. + +```js +expect('단 면 H-H', 1200, 2000, '환기 OPEN부 (2.0x1.2)'); // 실재 확인만 +profile('mh.ventHole', 'XY', rect(15800, -500, 17000, 1500)); // 실제 프로파일 +``` + +`expect` 를 남겨두는 게 중요하다. bbox 값이 도면과 어긋나면 거기서 걸린다. + +### 이런 자리를 미리 알아채는 법 + +`--loops` 출력의 **꼭짓점 수**가 신호다. 직사각형이어야 할 것이 4가 아니면 의심한다. +개구 기호가 들어간 면은 8, 케이블랙이 섞인 면은 114 가 나왔다. + +--- + +## 4.6. 교훈 #13 — 조립 스펙에서 폐곡선은 "면적 순서"가 아니라 "치수"로 집어라 + +`assemble()` 이 프로파일을 고를 때 `loops[0]`, `loops[1]` 처럼 순서로 집으면 +도면이 조금만 바뀌어도 조용히 다른 면을 집는다. 면적 순위는 흔들린다. + +**폭×높이로 집는다.** 그 값이 곧 도면 치수라서 사람이 읽고 검증할 수 있고, +못 찾거나 여러 개면 예외를 던져 조용한 오류가 안 생긴다. + +```js +profile('duct.outer', 'YZ', loop('단 면 B-B', 2700, 2750).ring); // 도면 표기 그대로 +profile('duct.inner', 'YZ', loop('단 면 B-B', 2100, 2150).ring); +``` + +### boolean 커터는 대상보다 크게 뽑아라 + +압출 구간이 딱 맞으면 커터와 대상의 면이 겹쳐 boolean 이 불안정해진다. +양쪽으로 조금(여기선 200mm) 빼서 확실히 관통시킨다. + +```js +part({ id:'duct.solid', op:'extrude', from: 0, to: 15400 }); +part({ id:'duct.void', op:'extrude', from: -200, to: 15600 }); // 양쪽 200 여유 +``` + +### 재료 경계선을 프로파일 추출에서 빼면 면이 뭉친다 + +구체만 넣고 뽑으면 기초·잡석이 한 덩어리가 된다. **면이 갈리려면 경계선이 +있어야 하므로** 추출에는 함께 넣고, 배정은 폐곡선의 레이어를 보고 나중에 한다. +단 케이블 지지대 같은 가구는 넣으면 안 된다 — 폐곡선이 그 윤곽을 따라 돈다. + +--- + +## 4.7. 교훈 #14 — boolean 은 **이항**이다. 커터는 하나씩 먹인다 + +`BooleanDifference` · `BooleanUnion` 플러그인은 선택 개수와 무관하게 +`TargetModels[0]`, `[1]` **두 개만** 쓴다. 셋 이상 넘기면 나머지는 예외도 경고도 +없이 무시되고 씬에 그대로 남는다. + +``` +✗ select(mh.solid); select(cut1 … cut9, add) + BooleanDifference + → 컷 1개만 적용. 커터 8개가 씬에 떠다닌다 +``` + +`emit/egm.mjs` 의 `fold()` 가 둘씩 접는다. 중간 결과는 `id~1`, `id~2` … 로 잡고 +마지막만 `id` 가 된다. + +``` +✓ mh.solid − cut.upper → mh.body~1 + mh.body~1 − cut.lower → mh.body~2 + … + mh.body~8 − cut.pumpHole → mh.body +``` + +**커터를 먼저 union 해서 하나로 만드는 방법은 못 쓴다.** 다리(legL/legR)와 +환기 개구처럼 서로 떨어진 커터는 교차하지 않아 union 이 `ArgumentNullException` +으로 죽는다. 교훈 #10 에서 union 하는 것은 "구간 몸통"이지 커터가 아니다. + +### 이 사고를 잡아내는 신호 + +`egm status` 의 `modelCount` 를 **최종 파트 수와 대조**한다. 가장 빠르다. + +``` +modelCount 11 정상 (최종 파트 11개) +modelCount 19 커터 8개가 안 먹혔다 +``` + +실행 직후 한 줄이면 되므로 매번 확인한다. 형상만 보면 컷 하나가 빠진 걸 +알아채기 어렵다 — 남은 커터 솔리드가 구체와 겹쳐 있어 겉보기엔 멀쩡하다. + +--- + +## 4.8. 교훈 #15 — 뷰어 모드면 명령이 **조용히** 무시된다 + +모델러가 뷰어 모드로 떠 있으면 모델링 명령이 **등록조차 안 된다.** 그런데 파이프는 +살아 있어서 `egm status` 는 정상 응답하고, `~Polyline` 은 아무 말 없이 +`created: []` 를 낸다. "모델러가 죽었나" 로 시간을 크게 버린 함정이다. + +원인은 `CommandManager.cs:430-434` 다. `EgEditorCommand` 를 상속한 명령은 +`EGViewport.Plugins` 에 해당 플러그인이 없으면 등록에서 건너뛴다. 모델링 플러그인은 +뷰포트가 한 번 렌더된 뒤 `LoadProjectPluginsAsync()` 로 올라오므로, 뷰어 모드거나 +창이 화면 밖에 있으면 같은 증상이 난다. + +**작업 시작 전에 이것부터 확인한다.** + +```sh +egm guide | python -c "import sys,json;print(len(json.load(sys.stdin)))" +# 39 → 뷰어 모드. 보기·선택·계측만 있다 +# 207 → 정상 + +egm guide Polyline # "Command 'Polyline' not found" 면 뷰어 모드 +``` + +39개 쪽에 남아 있는 것은 `SelAll` · `Zoom` · `Delete` · `Distance` · `Isolate` +같은 계열뿐이다. `Polyline` · `Box` · `Extrude` · `Boolean*` 가 통째로 빠진다. +`New` · `ModelClear` 는 뷰어 모드에서도 `result` 에 찍히므로, **"명령이 하나라도 +먹으니 연결은 정상"이라는 판단이 함정이다.** + +CLI 로는 못 바꾼다. 모델러 UI 에서 **View Mode → Edit Mode** 로 놓는다 +(`AppModeManager.ViewerMode`, 상단바 토글 또는 Display and Workspace → +Interaction Mode). + +**`Settings.json` 의 `ModelerMode` 와 헷갈리지 말 것.** 그쪽은 +Assistive Features → Working Mode Setting 의 별개 값이고, `0`(`ViewingOnly`) 인 +채로도 Edit Mode 면 모델링이 된다. 명령 등록을 가르는 것은 `ViewerMode` 다 +(`TestHost/MainWindow.xaml.cs:62` 가 같은 이유로 `ViewerMode = false` 를 세운다). + +--- + +## 4.9. 교훈 #16 — loft 는 **꺾인 자리를 못 만든다** + +전이부(스터브 B-B → 본체 C-C)를 세우면서 loft 를 처음 썼다. 스터브 단면에서 +본체 단면까지 **loft 하나로** 이었더니, 결과가 도면의 "챔퍼로 꺾인 뒤 수직 단차"가 +아니라 **끝까지 비스듬히 올라가는 긴 경사면 하나**가 됐다. + +원인은 loft 를 "단면 A 에서 단면 B 로 가는 길"로 본 것이다. loft 는 두 단면 +**사이를 곧게 이을 뿐**이다. 꺾이는 자리는 loft 가 만들어 주지 않는다 — +**꺾이는 자리마다 단면을 하나씩 놓고 구간을 끊어야** 그 자리가 생긴다. + +``` +챔퍼 loft 양 끝 단면이 다른 구간 +수직 단차 union 앞 구간이 작은 채로 끝나고 뒤 구간이 크게 시작한다. + 그 차이가 곧 단차 벽면이다. 따로 만들 것이 없다 +종방향 헌치 별도 쐐기 XZ 삼각형을 Y 로 압출해 union 으로 되붙인다 +``` + +### 꺾이는 X 가 다르면 loft 를 또 끊는다 + +이 전이부의 외곽은 **옆·위가 X -300…0**, **아래가 X -100…0** 에서 꺾인다. +한 번에 이으면 아래 챔퍼가 100mm 짜리인데 300mm 로 늘어난다. + +``` +stubL.solid extrude X -1000 → -300 +ch1.solid loft stub.outer@-300 → out.ch1@-100 옆·위만 벌어진다 +ch2.solid loft out.ch1@-100 → out.ch2@0 아래 챔퍼가 여기서 합류 +main.solid extrude X 0 → 15400 ← X=0 수직 단차 자동 생성 +``` + +중간 단면(X=-100)의 값은 **측정한 직선 위의 내분점**을 코드가 계산하게 둔다. +상수로 박으면 도면이 바뀔 때 조용히 틀린다. + +### 단면은 2개씩만 — 3개부터 부푼다 + +`Loft` 는 단면을 N개 받지만 3개 이상이면 스플라인 보간으로 부풀 수 있다. +**2단면 loft 는 직선 룰드 서피스**라 그럴 일이 없다. 확인법이 간단하다 — +결과의 정점 X 값에 **중간 행이 안 생기면** 곧게 이어진 것이다. + +``` +정점 X: -1000 -300 -100 0 100 400 550 15400 + └ 전부 단면을 놓은 자리. 사이에 중간 행이 없다 = 직선 +``` + +### 링 정규화가 없으면 loft 가 비틀린다 + +loft 는 두 프로파일의 꼭짓점을 **순서대로** 잇는다. 그런데 `buildLoops` 의 시작 +꼭짓점은 half-edge 순회가 어느 반변에서 출발했느냐에 달려 있어 프로파일마다 +제각각이다. 그대로 이으면 엉뚱한 꼭짓점끼리 짝지어 면이 꼬인다. + +**형상만 봐서는 원인을 못 찾는다** — 링 자체는 멀쩡하기 때문이다. +`addProfile` 이 등록할 때 정규화한다(반시계 · v 최소 → u 최소 꼭짓점에서 시작). +압출에는 영향이 없다. 닫힌 커브라 시작점이 어디든 같은 커브다. + +정규화 뒤 두 내부 단면이 인덱스별로 정확히 짝을 이뤘다. + +``` +i stub.inner main.inner 의미 +0 (-1050,-7357) (-1500,-7357) 바닥 좌단 +6 ( 1050,-5357) ( 1500,-3707) 헌치 시작 (우) +7 ( 900,-5207) ( 1350,-3557) 헌치 끝 (우) +``` + +위상이 다르면(꼭짓점 수가 다르면) 정규화로도 안 된다. `validateIR` 이 +**평면 일치 · 꼭짓점 수 일치 · 위치 오름차순**을 먼저 막는다. + +### 검증은 부피 대조가 제일 빠르다 + +비틀린 loft 는 부피가 크게 어긋난다. 반대로 부피가 맞으면 꼬이지 않은 것이다. +`ExportByPath` 로 OBJ 를 받아 발산 정리로 재고, IR 프로파일에서 계산한 +기대값과 맞춘다. 두 단면 사이 면적은 t 의 2차식이라 **심프슨이 정확**하다. + +``` +V = L·(A0 + 4·A(½) + A1) / 6 A(t) = 꼭짓점을 t 로 보간한 다각형 면적 +``` + +``` +외곽 277.0195 − 공동 176.0020 + 헌치 쐐기 0.0315 = 기대 101.0490 m³ + 실측 101.0492 m³ 오차 0.0002% +``` + +헌치 쐐기는 공동 안쪽에 들어가는 부분만 더해진다 — +높이 `d`(0…150)에서 x 폭이 `d`, y 폭이 `2(1500−d)` 이므로 +`∫₀¹⁵⁰ d·2(1500−d) dd = 0.0315 m³`. 이 값까지 맞으면 union 도 제대로 먹은 것이다. + +### 이음매에는 T 정점이 남는다 + +구간을 맞대어 붙이면 **양쪽 삼각분할이 달라** 공유 평면에서 변이 1:1로 안 맞는다. +이 모델에서 열린 변이 21개 나왔고 **전부 이음매 평면**(X=-1000 · 100 · 400)이었다. +면은 기하적으로 겹쳐 있어서 부피는 정확하다. + +렌더링·부피에는 문제가 없지만 **엄밀히 manifold 가 아니다.** IFC 로 내보낼 때 +걸릴 수 있다. 없애려면 이음매를 맞대지 말고 솔리드끼리 겹쳐 boolean 이 진짜 +교차를 만들게 해야 하는데, 공동 쪽은 교훈 #10 때문에 그렇게 못 한다. + +--- + +## 4.95. 교훈 #17 — 원은 선분과 **섞지 않는다**. 그리고 좌표를 쓰는 순간 앵커가 드러난다 + +`parse.mjs` 는 오랫동안 CIRCLE 을 버렸다. 넣을 때 두 갈래가 있었다. + +``` +✗ 원을 다각형으로 근사해 선분 목록에 넣는다 → 평면 분할이 그 윤곽을 따라 돈다. + 폐곡선이 오염된다 (교훈 #12 와 같은 사고) +✓ 따로 모아 지름으로 집는다 → 원은 그 자체가 형상이다. cylinder 로 직행 +``` + +원은 **관통 구멍·기둥**이라 애초에 "면을 가르는 경계선"이 아니다. 폐곡선 추적에 +참여시킬 이유가 없다. + +**뷰 귀속도 따로 한다.** 뷰 분리(조각 클러스터링)에 원을 넣으면 관통 구멍 하나가 +두 뷰를 잇는 다리가 될 수 있다. 뷰가 다 갈린 뒤 **중심 좌표로 귀속**시킨다. + +### 시트 밖을 반드시 거른다 — 블록 로컬 좌표가 섞여 있다 + +이 도면의 CIRCLE 253개 중 시트 안은 133개다. 나머지는 두 종류다. + +``` +x ≈ -565,472 유령 기하 (`000-구조물점선`. 선분에서 이미 겪은 것) +x ≈ 25.4 INSERT 블록 **로컬 좌표**. 원점 근처에 몰려 있다 +``` + +두 번째가 새로 나온 것이다. 안 거르면 원점 근처 뷰에 남의 원이 무더기로 붙는다. + +### 지름은 강한 식별자지만, 레이어까지 봐야 하는 경우가 있다 + +``` +Ø900 -전력구 2 사다리 샤프트 보어 +Ø100 환기구 4 · 배수관 1 · 금구류 2 ← 앞의 둘만 관통이다. 금구류는 철물 +``` + +그래서 `circles(뷰, 지름, 레이어정규식)` 로 좁힐 수 있게 뒀다. 폐곡선을 치수로 +집는 것과 같은 규칙이다 — 순서로 집으면 조용히 다른 것을 집는다. + +### 좌표를 쓰기 시작하면 숨어 있던 앵커가 드러난다 + +평면도는 **세로축이 표고가 아니라 폭방향(Y)** 이다. 그런데 지금까지는 평면도에서 +`expect(뷰, 폭, 높이)` 로 치수만 봤기 때문에 `v0` 앵커가 없어도 아무 문제가 없었다. +CIRCLE 좌표를 쓰는 순간 보어가 `Y = -9,778` 로 나왔다. + +**치수만 보는 동안은 틀린 앵커도 안 걸린다.** 수평 단면(H/I/J)에서 이미 겪은 +문제인데, 평면도는 "표고 축이 맞겠거니" 하고 넘어갔던 것이다. + +앵커는 지어내지 말고 **뷰 범위에서 유도하고 다른 것으로 교차검증**한다. + +``` +뷰 세로 -11,678 ~ -1,478 (10,200) `-전력구` 전폭. 아래 끝이 Y=-1,900 + → v0 = -11,678 + 1,900 = -9,778 +교차검증 + 샤프트 사각 1,300×1,300 v -10,428..-9,128 → Y -650..+650 중심선 대칭 ✓ + Ø900 보어 2개 v -9,778 → Y 0 ✓ + 맨홀 내부 2,000×7,200 v -11,278..-4,078 → Y -1,500..5,700 ✓ +``` + +### 손으로 적은 수는 도면이 바뀌어도 안 바뀐다 + +이 작업의 실제 소득은 아래 한 줄이 사라진 것이다. + +```js +const BORE_R = 450; // 원본 엔티티에서 눈으로 읽어 적음 +``` + +지금은 도면에서 집고 샤프트 중심과 대조한다. 어긋나면 빌드가 죽는다. + +```js +const bores = circles('평 면 도', 900, /^-전력구$/); +if (bore.u !== cx || bore.v !== 0) throw new Error(`${s.id} 보어가 샤프트 중심과 다르다 …`); +``` + +파라메트릭으로 갈 때 **이 끈이 없는 값은 파라미터로 올릴 수 없다** — +바꿔도 도면과 대조할 방법이 없기 때문이다. + +--- + +## 4.96. 교훈 #18 — 자기교차 링은 **조용히** boolean 을 무력화한다 + +교훈 #12 는 개구 기호(X 표시)가 링을 오염시킨다는 것이었고, 대응은 "bbox 로 +재구성"이었다. 그런데 **그 사고가 개구부에서만 나는 게 아니다.** + +맨홀 #2 구체를 재봤더니 이랬다. + +``` +기대 (IR 을 격자로 CSG 평가) 71.758 m³ +실측 (모델러) 141.207 m³ ← 챔버 커터 4개가 아무것도 못 뺐다 +``` + +원인은 외곽 프로파일 `mh.outer` (54꼭짓점) 의 **자기교차 2곳**이었다. + +``` +[2900,-7357]→[1800,-7607] × [2900,-7607]→[1800,-7357] 하부 슬래브 개구 기호 +[ 0,-7357]→[-800,-7607] × [ 0,-7607]→[-800,-7357] +``` + +자기교차 링을 압출하면 솔리드가 쓰레기다. 그 뒤 `BooleanDifference` 는 +**예외도 경고도 없이** 아무것도 못 뺀다. `modelCount` 는 정확히 맞는다 — +커터는 소모됐기 때문이다. **부피를 재기 전에는 알 방법이 없었다.** + +### 모델러를 의심하기 전에 격리 실험을 하라 + +처음엔 "커터가 대상 안에 완전히 갇히면 boolean 이 실패한다"고 의심했다. +프로브를 만들어 확인하니 **정상이었다.** + +``` +2×2×2 상자 − 안쪽 1×1×1 상자 → 7.0000 갇힌 커터도 정상 +2×2×2 상자 − 위로 뚫는 상자 → 6.5000 겹친 만큼만 정확히 +``` + +모델러가 아니라 **우리가 넣은 링**이 문제였다. 프로브 3줄이 잘못된 방향으로 +갈 뻔한 것을 막았다. + +### 왜 T 자 분할만으로는 부족한가 + +`core/loops.mjs` 는 **T 자 교차**(노드가 다른 선분 위에 얹힌 경우)만 쪼갠다. +두 선분이 중간에서 **X 자로 교차**하는데 그 자리에 노드가 없으면 안 쪼갠다. +개구 기호의 대각선 두 개가 정확히 그 모양이다. + +### 이 도면의 실태 — 링 10개가 오염돼 있었다 + +`build.mjs --loops` 에 자기교차 검사를 붙이자 한 번에 드러났다. + +``` +단 면 E-E #0 10200×10183 꼭짓점 54 ✗ 자기교차 2 ← 맨홀 외곽. 부피 2배 +단 면 H-H #3 1200× 2000 꼭짓점 8 ✗ 자기교차 6 ← 환기 OPEN부 (이미 알던 것) +단 면 I-I #4 1200× 1000 꼭짓점 8 ✗ 자기교차 6 ← 점검구 (이미 알던 것) +단 면 A-A #8 900× 400 꼭짓점 8 ✗ 자기교차 8 ← 샤프트 지붕 개구 +… +``` + +**꼭짓점 수보다 확실한 신호다.** 4가 아니면 "의심"이지만, 자기교차는 그 자체로 +결함이다. `selfIntersections()` 는 `core/views.mjs` 에 있고 O(n²) 라 뷰당 링 +수십 개 수준에서는 공짜다. + +### 순서 + +``` +1. --loops 에서 자기교차부터 본다 오염된 링을 전부 나열 +2. 레이어로 걸러지나 확인 이 도면은 X 표시가 `-전력구` 라 안 됐다 +3. 직사각형이면 bbox → rect() 재구성 교훈 #12 +4. 오목한 외곽이면 X 교차 분할이 필요 rect() 로 못 만든다 +``` + +--- + +## 4.97. 교훈 #19 — **의도**와 **결과**를 따로 재라 + +부피가 이상할 때 물어야 할 것은 둘이다. + +``` +우리 조립(IR)이 틀렸나? ← 의도 +모델러가 IR 대로 안 만들었나? ← 결과 +``` + +이걸 못 가르면 엉뚱한 데를 판다. 실제로 맨홀 부피를 쫓느라 손으로 기대값을 +세 번 조립했는데 세 번 다 커터 범위를 놓쳐서 틀렸다. 그 값으로 모델러를 의심하고, +격리 프로브를 만들고, 커터 순서를 바꾸고 — 전부 헛수고였다. + +**IR 을 격자로 그대로 평가하면 손계산이 필요 없다.** `src/volume.mjs` 가 +연산 트리(union=or, difference=and-not, loft=선형보간)를 따라가며 점을 세어 +"IR 이 뜻하는 부피"를 낸다. 25mm 격자에서 0.1% 안팎이라 소수 셋째 자리는 못 보지만, +**boolean 이 죽는 사고는 언제나 m³ 단위로 어긋나므로 충분하다.** + +```sh +node src/measure.mjs manhole1 --ir +``` + +``` +파트 부피 m³ IR 의도 차이 +맨홀1_전력구_구체 100.5403 100.5350 0.0053 ✓ +맨홀1_사다리샤프트_1 3.3217 3.3211 0.0006 ✓ +맨홀1_전력구_출구스터브 2.9725 2.9725 0.0000 ✓ +맨홀1_맨홀2_구체 65.3577 74.6819 −9.3242 ✗ ← 여기만 모델러가 흘렸다 +``` + +한 줄로 "우리 조립은 맞고 모델러가 흘린다"가 확정된다. 그 전까지는 몇 시간이 걸렸다. + +### 커터가 자기 부피보다 많이 빼면 boolean 이 죽은 것이다 + +가장 확실한 단일 신호다. 커터의 해석 부피는 `--primitives` 로 바로 나온다. + +``` +mh.cut.duct 자기 부피 9.04 실제로 뺀 것 15.28 ✗ +mh.cut.lower 자기 부피 20.08 실제로 뺀 것 29.53 ✗ +``` + +물리적으로 불가능하므로 형상 논쟁이 필요 없다. + +### 면으로만 맞닿은 솔리드를 union 하지 마라 + +이 사고의 원인이 여기 있다. 맨홀 외곽(`mh.outer`)의 윗변과 상부 통로 +(`mh.upper`)의 밑변이 **둘 다 Z=-4,957** 이다. union 하면 한 덩어리처럼 보이지만 +실은 **면으로 붙은 조각들**이고, 이어지는 difference 가 조각 하나를 통째로 흘린다. +손실이 어느 커터에서든 **9.43 으로 일정한 것**이 그 증거다 — 커터 탓이면 값이 +커터마다 달라야 한다. + +``` +✓ 솔리드끼리 **부피로 겹치게** 만든 뒤 union +✗ 면만 맞대고 union +``` + +단, 겹치게 만들 때 **압출축 방향으로만** 늘려야 한다. 프로파일을 통째로 1mm +부풀렸더니 옆으로도 커져 새 겹침이 생기고 결과가 더 나빠졌다(65.4 → 41.6). + +### 관통구는 합치기 **전에**, 단순한 조각에서 뚫는다 + +union 결과를 뚫으면 위처럼 흘린다. 조각마다 제 몫만 뚫고 나서 합치면 정확하다. + +``` +✗ solid = union(shell, endL, endR) → solid − ductHole 15.28 빠짐 (기대 5.83) +✓ shell − ductHole, endL − ductHole → union mh.solid 118.008 / 의도 118.034 ✓ +``` + +boolean 이 피연산자를 소모하므로 **같은 커터를 두 조각에 못 쓴다.** 커터를 조각 +수만큼 따로 만든다. 안 겹치는 조각에는 아예 넣지 말 것 — 교차가 없으면 +`BooleanDifference` 가 `created:[]` 로 죽는다. + +### 커터는 대상 **바깥면에서 시작하면 안 된다** + +환기구 다리 커터를 맨홀 외면(X 15,400)에서 정확히 시작했더니 46 m³ 를 파먹고 +(기대 2.48), 이어진 반대쪽 커터가 부피를 59 m³ **늘렸다**. 바깥으로 `OVER` 만큼 +빼자 둘 다 1.80 으로 정확해졌다. 교훈 #13 의 "커터는 대상보다 크게"가 압출축뿐 +아니라 **모든 방향**에 적용된다. + +--- + +## 4.98. 교훈 #20 — **모델스페이스가 실척이라고 믿지 마라** + +교량 도면군을 넣으면서 처음 깨진 가정이다. 두 층이 따로 논다. + +``` +① DWG 모델단위 1 단위가 몇 mm 인가 도면군마다 다르다 +② 도형 축척 그 도형이 실척으로 그려졌나 같은 도면 안에서도 다르다 +``` + +**① 모델단위.** 맨홀군은 1 단위 = 1 m 인데 교량 삽도군은 1 단위 = 1 mm 다. +`core/datum.mjs` 에 `UNIT_MM = 1000` 이 상수로 박혀 있었다. 숫자라서 「도면 +고유값」으로 안 보였을 뿐이고, 그대로 두면 **모든 치수가 1000× 로 틀린다.** +폐곡선도 나오고 부피도 나와서 알아채기 어렵다. + +확정하는 법은 도면 안에 있다 — `DIMENSION` 엔티티의 `actualMeasurement` 와 +그 치수의 **글자**를 비교한다. + +``` +actualMeasurement 3000.00 ↔ 글자 "3.000" (m) → 1 단위 = 1 mm +``` + +같은 파일의 다른 상수들도 같은 병을 앓고 있었다. 라벨 대역 `±3`, 뷰 구분 문턱 +`2` — 전부 「1단위=1m」를 전제한 **길이**다. 길이는 mm 로 적고 단위로 나눠 되돌린다. + +**② 도형 축척.** 중앙분리대 삽도의 방호벽 단면은 **5.805배**로 그려져 있었다. +같은 파일의 교량폭원 단면은 1:1 이다. 도형마다 다르다. + +되찾는 법 — **표기 치수 2개**로 역산해서 서로 맞는지 본다. 하나만 쓰면 우연히 맞는다. + +``` +높이 포장면 y=48,812.1 → 마루 y=56,184.4 = 7,372.3 단위 ↔ 표기 1,270 +바닥폭 x 58,176.9 → 61,570.8 = 3,393.9 단위 ↔ 표기 585 + +7372.3 / 1270 = 5.8049 3393.9 / 585 = 5.8015 차이 0.06% +``` + +옆 단면(단면B)도 같은 배율이 나온 것이 세 번째 검증이었다. + +> 뷰의 서술이 `{좌표평면 · 반전 · 앵커 · 용도}` 넷에서 **`축척`을 더해 다섯**이 됐다. + +--- + +## 4.99. 교훈 #21 — 뷰 제목은 **유일 키가 아니다** + +뷰 분리는 「잘게 쪼갠 뒤 제목으로 다시 묶는다」(교훈 #2)였다. 그 절차는 두 가지를 +전제한다. **교량 삽도에서는 둘 다 거짓이었다.** + +**(가) 뷰 사이에 간격이 있다** — 없었다. + +``` +gap 10 25 50 100 200 400 800 1600 3200 (mm) +조각 1 1 1 1 1 1 1 1 1 +``` + +임계값을 10mm 까지 좁혀도 조각이 하나다. 범인은 **상세 확대 테두리 상자**다 +(LWPOLYLINE, Δy=5,117, y 15,965…21,082 과 20,375…25,492 가 겹친다). 상세행과 +단면행을 세로로 잇는데, 구조선과 **같은 레이어·같은 색**이라 걸러낼 수도 없다. + +**(나) 제목이 뷰마다 유일하다** — 아니었다. + +``` +교량폭원 뷰 제목이 아예 없다. 표제는 시트명 하나, 나머지는 A · B · L of Road +중앙분리대 단면A · 단면B 가 각 4벌 (사례 3종 × 위치) +교량시종점 「교량 시·종점」이 6벌 (TYPE1 반일체식 ~ TYPE4 골조식) +``` + +유형별 삽도는 **같은 것의 변형을 N벌 늘어놓는 형식**이라 제목이 반복되는 게 정상이다. +표제 중복률을 세보면 7장 전부 47~84% 였다. + +**대응 — 좌표로 짚는다.** `cfg.viewBoxes` 에 뷰마다 `{box:[x0,y0,x1,y1], kind}`. + +```js +viewBoxes: { + '단면A': { box: [55000, 44000, 66000, 58000], kind: 'cross', unitMm: 1 / 5.805 }, + '단면B': { box: [70000, 44000, 82000, 58000], kind: 'cross', unitMm: 1 / 5.805 }, +}, +``` + +두 가지를 지킨다. + +- **조각이 아니라 선분 단위로 나눈다.** 조각 단위로 나누면 뷰를 잇는 다리 조각이 + 통째로 한쪽에 붙는다. 선분이면 다리가 어느 상자에도 안 들어가 자연히 버려진다. +- **양 끝이 다 상자 안에 들어와야** 취한다. 한쪽만 걸치는 선은 뷰를 넘나드는 선이라 + 넣으면 뷰 폭·높이가 조용히 부푼다. + +상자가 겹치면 같은 선이 두 뷰에 들어가므로 **예외를 던진다.** + +box 좌표는 눈대중이 아니다. 구조선 y 를 정렬해 400mm 이상 벌어진 곳에서 끊은 +**뭉치 경계**를 뽑고, 상자 변은 그 **빈틈 안**에 둔다. + +--- + +## 4.991. 교훈 #22 — 레이어가 못 가르면 **색이 가른다** + +`toSegments` 는 걸 수 있는 필터가 `layerFilter` 하나였다. 그런데 구조선과 치수선이 +**같은 레이어**에 있는 도면이 있다 — 교량폭원은 둘 다 `CS-CONC-MAJR` 이다. + +``` +CS-CONC-MAJR 안에서 + colorIndex 0 (ByBlock) LINE 576 ← 치수 블록 안의 선 + colorIndex 7 (ByLayer) LINE 316 + LWPOLYLINE 161 ← 구체 윤곽 +``` + +`colorIndex 0` 은 ByBlock 이다. 즉 **블록(치수) 안에 든 기하**라는 뜻이고, +그게 그대로 치수선/화살표/보조선을 가른다. 걸러내니 횡단면 3벌과 방호울타리 +상세가 깨끗하게 드러났다. + +무엇으로 가를지는 도면마다 다르므로 **판단은 `drawings/*.mjs` 에 두고 코어는 +술어만 받는다.** + +```js +entityFilter: (e) => (e.entityHeader?.colorIndex ?? -1) === 7, +``` + +> 일반화 — **거를 수 있는 축을 레이어 하나로 두지 마라.** 색·선종·선굵기가 +> 레이어가 못 가르는 자리를 가른다. `gaps.mjs` 같은 진단 도구도 같은 필터를 +> 걸어야 한다. 안 걸면 걸러낸 치수선이 「레이어 오분류」로 잘못 보고돼 +> 원인 판정이 통째로 어긋난다. + +--- + +## 4.992. 교훈 #23 — **끊김이 0인데 폐곡선도 0** + +중앙분리대 단면A 가 그랬다. `gaps.mjs` 는 방호벽 구간에 끊김을 하나도 못 찾는데 +`--loops` 는 아무것도 안 낸다. 증상만 보면 「닫혀 있는데 안 나온다」라서 +끊김 진단으로는 **절대 못 잡는다.** + +원인은 **같은 직선 위에서 겹치는 두 선**이었다. 끝점이 다르므로 1단계 중복 제거를 +통과한다. + +``` +방호벽 왼쪽 면 x = -292 에 두 선이 그려져 있었다 + z −48 … 30 + z 0 … 49 ← 0…30 이 겹친다 +``` + +T 자 교차 분할이 노드(−48, 0, 30, 49)마다 자르면서 겹친 구간을 **양쪽에서 똑같이** +잘라낸다. + +``` +−48…0 0…30 (첫 선에서) + 0…30 30…49 (둘째 선에서) + ↑ 같은 변이 두 벌 +``` + +그 변에 반변이 4개가 되어 면 순회가 엉키고, 폐곡선이 **통째로** 안 나온다. + +**대응 — 분할 뒤에 한 번 더 중복 제거.** `buildLoops` 는 원래 분할 **전에만** +중복을 지웠다. 순서가 문제였다. + +``` +1. 중복 제거 끝점이 똑같은 선만 지운다 +2. T 자 분할 +2b. 중복 제거 ← 분할이 만든 중복 변을 지운다 (추가) +3. half-edge 구성 +``` + +--- + +## 4.993. 교훈 #24 — 게이트에 넣기 전에 **「이 도면이 대상인가」**를 판정한다 + +교량 도면 7장 중 **6장이 어느 게이트도 통과 못 했다.** 그런데 그걸 알아내는 데 +게이트를 다 돌릴 필요가 없었다 — 파싱 직후 몇 초면 판정된다. + +**① 표기 치수가 숫자인가 기호인가.** 방법론 전체가 「도면 표기 치수와 대조」에 +걸려 있다. 치수가 기호면 **대조할 상대가 없다.** + +| 도면 | 숫자 치수 | 기호 치수 | 판정 | +|---|---|---|---| +| 거더배치 | **2** | 62 | `L · β · L' · β1..β4` — 불가 | +| 교량시종점 | **0** | 6 | 측면도 6벌인데 전부 라벨 — 불가 | +| 교대배치 | 51 | 37 | `θ` 가변 — 낮음 | +| 교면포장 | 14 | 2 | 층상 구성표 — 형상 아님 | +| 횡단경사 | 156 | 139 | `S% · TL` 가변 | +| 교량폭원 | 151 | 10 | 높음 → **G5 에서 막힘** | +| **중앙분리대** | **108** | 7 | 높음 → **관통** | + +**② 구조 윤곽이 닫히는가.** 교량폭원은 치수가 충분한데도 못 세웠다. +끊김 56곳 중 **54곳이 「도면에 선 없음」**이었기 때문이다. 폭원 **구성 도해**라 +애초에 닫힌 단면을 그리지 않는다 — 띠와 열린 외곽선뿐이다. + +``` +분리교량 폭원 경계 1 레이어 0 누락 16 +폭원 띠 중 경계 0 레이어 2 누락 2 +횡단면 하 경계 0 레이어 0 누락 10 +폭원 띠 상 경계 1 레이어 0 누락 12 +횡단면 중 경계 2 레이어 0 누락 14 +``` + +「레이어 오분류」면 설정 한 줄이지만(교훈 #3), **「선 없음」이 압도적이면 그 도면은 +솔리드 대상이 아니다.** 이 비율이 그대로 판정 기준이다. + +> 게이트 목록에 **G1.5 도면 적격**이 들어간다. 여기서 걸러야 나머지를 헛되이 채우지 않는다. + +--- + +## 4.994. 교훈 #25 — 경계 위반은 **두 번째 도면을 넣기 전엔 안 보인다** + +`CLAUDE.md` 에도 `README` 에도 「`core/*` 에 도면 고유값을 넣지 마라」가 큰 글씨로 +적혀 있었다. 그런데 두 번째 도면군을 넣자 **두 곳에서 이미 새 있었다.** + +- `core/datum.mjs` 의 `UNIT_MM = 1000` — 문자열이 아니라 **숫자**라서 안 보였다. + 「`'-전력구'` 같은 문자열이 들어가면 잘못된 것」이라고 써둔 탓에 숫자는 통과했다. +- `rules.mjs` 는 주석에 「도면도 3D도 모른다」고 적혀 있었지만 실제로는 + `챔퍼_시작X · 샤프트 · 맨홀_벽두께` 처럼 **첫 도면의 파라미터 이름**에 묶여 있었다. + 두 번째 도면에서 그 값이 없어 `undefined` 비교가 전부 실패로 떨어졌고, + **위상 위반**으로 판정돼 `build.mjs` 가 exit 1 로 죽었다 — 남의 도면 규칙에 걸려서. + +대응은 규칙마다 **요구 파라미터**를 명시하고, 없으면 **해당 없음**으로 건너뛰는 것이다. + +```js +{ id: 'chamfer-order', kind: 'topology', + needs: ['챔퍼_시작X', '챔퍼_중간X', '본체_시작X'], … } +``` + +부재 종류 태그로 규칙을 붙이는 제대로 된 해법은 아직 없다. 그때까지의 최소 조치다. + +> **문서에 「안 들어간다」고 쓰는 것으로는 안 지켜진다.** 두 번째 도면이 +> 유일한 검사 장치다. 그래서 성격이 다른 도면을 일찍 한 장 넣어보는 값이 크다. + +--- + +## 4.995. 교훈 #26 — 기준선 검출이 **예외 없이 틀린 답**을 낼 수 있다 + +교량 삽도 7장 중 `계획고` 라벨이 있는 것은 횡단경사 하나뿐이다. 그래서 여기서는 +기준선 검출(교훈 #1)이 통할 줄 알았다. **통하지 않았고, 더 나쁘게 통했다.** + +``` +교량 계획고 → y=54,885.8 뷰 10곳 +토공 계획고 → y=54,885.8 뷰 10곳 ← 서로 다른 표고인데 같은 답 +``` + +`교량 계획고` 와 `토공 계획고` 는 **정의상 다른 표고**다. 그 둘의 **차이(단차)**가 +이 도면의 주제다. 그런데 같은 선이 나왔다. 게다가 라벨마다 국소 탐색해보면 +그 y 는 **어느 라벨의 선도 아니다.** + +``` +라벨 자기 아래 가장 가까운 수평선 +교량 계획고 (73603, 81177) → y=80,105.8 +토공 계획고 (73221, 79089) → y=77,983.5 ← 같은 도형, 2,122 차이 (이게 단차다) +교량 계획고 (70915, 75535) → y=75,433.7 +토공 계획고 (70888, 70084) → y=69,836.0 +… 라벨 10개 → 국소 선 9개, 전부 다르다 +``` + +### 왜 틀렸나 — 전제가 숨어 있었다 + +`findLevelDatum` 은 라벨 y 를 **평균**낸 뒤 「그 평균 바로 아래 선」을 고른다. +숨은 전제는 **「라벨 하나가 선 하나를 가리킨다」**, 즉 라벨이 한 곳에 모여 있다는 것이다. + +유형 삽도는 같은 라벨을 **도형마다 반복**한다. 라벨 10개가 y 27,533~81,177 에 +흩어지면 평균 56,453 은 **어느 도형에도 속하지 않는 값**이다. 그 값 아래에서 +「출현 횟수가 많은 선」을 고르니 엉뚱한 선이 나온다. + +맨홀 도면에서는 이게 안 드러났다. 11개 뷰가 같은 계획고 선을 공유해서 +라벨도 한 무리로 모여 있었기 때문이다. **표본 하나로는 전제가 안 보인다.** + +### 대응 — 무리가 여럿이면 던진다 + +``` +라벨 y 를 정렬해 2m 이상 벌어진 곳에서 끊는다 +무리가 2개 이상 → 공통 기준선이 없는 도면이다 → 예외 +``` + +``` +Error: 표고 라벨이 4무리로 흩어져 있다 — 공통 기준선이 없는 도면이다: + y≈29934(2개), y≈69041(1개), y≈75535(1개), y≈81177(1개). + datum.mode:'none' 으로 두고 뷰별 anchors.v0 를 쓸 것 +``` + +맨홀은 그대로 통과한다 (뷰 11곳, 총연장 61,492mm). + +> **「조용히 틀리느니 예외를 던진다」가 검출기 자신에게도 적용된다.** +> 검출기는 답을 내는 게 일이라 답을 내려고 한다. 전제가 깨졌는지 스스로 검사하지 +> 않으면, 실패가 **예외가 아니라 그럴듯한 숫자**로 나온다. 그게 제일 비싸다. + +--- + +## 4.996. 교훈 #27 — **검사기가 거짓 경보를 내면 검사기를 안 믿게 된다** + +교량 상부공을 세우고 `measure --ir` 을 돌렸더니 6파트 중 5개가 **✗** 였다. + +``` +교량_상부공_좌측상판 59.0400 IR 의도 61.5000 −2.4600 ✗ +교량_방호울타리_1 8.3990 IR 의도 8.4625 −0.0635 ✗ +``` + +모델이 틀린 게 아니었다. **모델러 값이 해석값과 정확히 같았다** — +12,300 × 240 × 20,000 = 59.04 m³. 틀린 쪽은 「의도」를 재는 `volume.mjs` 였다. + +원인은 격자다. 25mm 격자로 **두께 240** 을 재면 셀 중심이 12.5, 37.5, … 로 +**10개** 잡혀 250 두께로 계산된다 — **+4.2%**. 맨홀은 부재가 두꺼워 이 오차가 +0.1% 수준으로 묻혔고, 얇은 교량 부재에서 처음 드러났다. + +같은 방호울타리 3기가 8.4625 / 8.7125 / 8.4625 로 **서로 다르게** 나온 것도 +같은 이유다 — Y 위치가 달라 격자에 걸리는 모양이 달랐다. + +### 대응 — boolean 이 없으면 격자를 안 쓴다 + +``` +box 변의 곱 +extrude 신발끈 면적 × 길이 +cylinder πr²·길이 +loft 심프슨. 꼭짓점을 선형 보간하면 단면적이 2차식이라 **정확**하다 +union 멤버 bbox 가 안 겹치면 합 (면으로만 맞닿은 건 안 겹친 것) +그 밖 격자 +``` + +결과가 전부 `0.0000 ✓` 로 떨어졌고, 중앙분리대(loft 포함)도 4.7970(격자) → +**4.8059(해석)** 로 바뀌어 모델러 4.8058 과 붙었다. + +출력에 **어느 방법으로 나온 값인지** 같이 찍는다. 격자 값을 해석 값인 양 +읽으면 다음 사람이 또 몇 % 를 쫓는다. + +> 「의도 vs 결과」 대조는 **의도 쪽이 정확해야** 성립한다. 근사로 재는 쪽이 +> 경보를 내기 시작하면 사람이 ✗ 를 무시하게 되고, 그때 진짜 사고가 지나간다. + +--- + +## 4.997. 교훈 #28 — 같은 부재가 여러 벌이면 **복수형 선택기**가 필요하다 + +`loop(뷰, 폭, 높이)` 는 여럿이면 던진다. 하나를 집는 게 목적이니 그게 맞다. +그런데 **여러 벌인 것이 정상**인 경우가 있다 — 교량 폭원 단면의 방호울타리 +440×1,320 이 3기였다. + +하나씩 좌표로 집으면 그 순간 **손으로 적은 수**가 된다(규칙 9 위반). +`circles()` 가 이미 같은 문제를 풀고 있었으므로 같은 꼴로 `loops()` 를 뒀다. +개수 확인은 부르는 쪽이 한다 — 몇 기여야 하는지는 도면이 아니라 조립하는 +사람이 아는 것이다. + +```js +const bars = loops('분리교량 폭원', 440, 1320); +if (bars.length !== 3) throw new Error(`방호울타리가 3기여야 하는데 ${bars.length}기`); +``` + +### 안 닫힌 4기째를 어떻게 되살렸나 + +우측 상판 바깥 방호울타리는 선 하나가 빠져 폐곡선이 안 됐다 +(`gaps` 가 `u=28,454` 를 「선 없음」으로 짚는다). **없는 게 아니라 안 닫힌 것**이다. + +지어내지 않고 되살리는 방법 — **규칙을 실측으로 확인한 뒤 그 규칙을 쓴다.** + +``` +들임(inset) 좌측 상판 좌단 bars[0].u0 − deck.u0 = 10 + 좌측 상판 우단 deck.u1 − bars[1].u1 = 10 ← 같다. 규칙 확인 +→ 우측 상판 우단도 10. 외면 u = 28,464 − 10 = 28,454 + ↑ 위 끊김 좌표와 **같은 값**. 이게 재구성의 검증이다 +``` + +모양은 `bars[1]`(같은 방향 부재)을 그 자리로 옮겨 쓴다. 등급은 `opaque` 가 +아니라 **`tolerant`** 로 둔다 — 도면 좌표를 그대로 옮긴 게 아니라 **만든** 것이라 +그 사실이 남아야 한다. + +--- + +## 4.998. 교훈 #29 — **한 시트에 날짜 다른 작업본이 겹쳐 그려져 있다** + +이번에 제일 크게 틀린 것이다. 교량시종점 삽도의 뷰 상자를 +`[5000, 64000, 31000, 77100]` 로 잡고 뽑았더니 나온 형상이 **어느 도면에도 없는 +조합**이었다 — 교대라고 부른 것이 실은 옆 도형의 날개벽이었고, 기초와 교각이 +통째로 빠졌다. + +전체 배치를 뜨고서야 봤다. 한 시트에 **두 벌**이 있다. + +``` +주황 테두리 TYPE1 반일체식 · TYPE2 단부슬래브 · TYPE3 일체식 · TYPE4 골조식 +자홍 교량 시·종점 (벽체 전면) · (흉벽 전면) · (슬래브 끝단) 각 2벌 +``` + +두 벌이 **서로 어긋나게 포개져** 있어서, 상자를 그 경계에 걸치면 두 도형의 +선이 같이 들어온다. 폐곡선은 멀쩡히 나오고 치수도 그럴듯해서 **틀렸다는 신호가 +전혀 없다.** 그림을 보고서야 알았다. + +표제란의 날짜 도장 두 개(`240820_교량시종점` · `240925_교량시종점`)가 힌트였는데, +처음엔 **개정 이력**으로 읽고 넘겼다. 실제로는 **작업한 날짜**이고 날짜마다 +내용이 다르다. + +### 대응 + +``` +1. 시트 전체를 먼저 뜬다 도형이 몇 벌인지 · 겹치는지 +2. 도형 테두리·표제로 벌을 가른다 테두리가 있으면 그게 경계다 +3. 도형 하나를 뜨고 눈으로 확인한 뒤 무엇이 들어 있는지 +4. 그 다음에 상자를 잡는다 +``` + +> **그림 없이 상자를 잡지 마라.** 숫자만 보면 「폐곡선 25개, 치수 그럴듯」이라 +> 통과한 것처럼 보인다. 이 실수는 검증표로 안 잡힌다. + +--- + +## 4.999. 교훈 #30 — 「표기 치수가 없다」와 「못 쓴다」는 다르다 + +교량시종점 삽도를 **숫자 치수 0개**라는 이유로 「솔리드 대상 아님」으로 잘랐다. +**오판이었다.** 표기 치수가 없으면 *검증*을 못 하는 것이지 *추출*을 못 하는 게 +아니다. 모델스페이스가 1:1 이면 좌표가 곧 mm 다. + +그럼 1:1 인지 어떻게 아나 — **크기를 아는 표준 부재**로 확정한다. 도면 밖에서 +온 눈금이다. + +``` +폐곡선 153×182 이 4개 ← 탄성받침 150×180. 다른 게 이 치수로 4개 나란할 이유가 없다 +교대 벽체 4,680×6,506 교량 교대 자릿수 +상부공 형고 1,421 지간 12,752 에 L/h≈9 — 반일체식으로 타당 +접속슬래브 두께 383 표준 300~400 +``` + +넷이 **동시에** 타당해지는 배율은 1 뿐이다. 10배만 달라도 받침이 1.5 m 나 +15 mm 가 된다. 이 도면 하나로 교대·교각·받침·기초·말뚝이 전부 열렸다. + +> 「대조할 상대가 없다」는 **그 도면 안에** 없다는 뜻이다. 다른 도면과 표준 치수는 +> 여전히 상대가 된다. 도면군 전체를 봐야 판정이 선다. + +--- + +## 4.9991. 교훈 #31 — 확대 상세는 **같은 뷰 안에 다른 축척으로** 들어온다 + +교량 종단면 안에 교좌받침을 크게 그린 상세가 있다(파란 테두리, `지간—지간` +표기). 같은 뷰 상자 안이라 폐곡선이 같이 나온다. + +``` +본 도형의 받침 153 × 182 ← 실제 치수 +확대 상세의 받침 461 × 196 ← 축척이 다르다. 게다가 **가로·세로 배율이 다르다** +``` + +치수로 집으면 저절로 갈리지만(그게 「치수로 집는다」의 값이다), **갈린다는 +사실을 확인해야** 안심할 수 있다. `fit()` 에 X 범위 검사를 넣었다. + +```js +if (brg.some((b) => b.bbox[0] > 15000 && b.bbox[0] < 19000)) { + throw new Error('확대 상세 안의 받침이 섞였다 — 축척이 다르다'); +} +``` + +같은 부재가 **1 mm 다르게** 나오는 것도 여기서 처음 봤다 — 교대 5,192 / 5,191, +받침 154 / 153×3. 도면이 다른 게 아니라 DWG 부동소수를 꼭짓점마다 **따로** +반올림하기 때문이다. 대칭 배치된 부재에서만 드러난다. `loops(…, tol)` 로 열되 +기본값은 0 이다 — 여는 것은 부르는 쪽의 판단이다. + +--- + +## 4.9992. 교훈 #32 — 진단 도구가 **본편과 다른 값**을 내면 도구를 못 믿는다 + +`probe` 가 상부공 폭을 12,951 로, `build` 가 12,752 로 냈다. 199 차이. + +원인 둘이 겹쳐 있었다. + +**(가) `probe.mjs` 가 `projectViews` 를 안 거쳤다.** `buildLoops` 를 직접 불러 +개구 기호 제거·앵커·평면 변환을 건너뛰었다. `core/views.mjs` 머리말이 +「복사해두면 뷰어가 거짓말한다」고 경고해둔 그대로다. + +**(나) `buildLoops` 가 bbox 를 collinear 제거 *전* 링으로 쟀다.** 상부공 윗선이 +199 mm 튀어나와 있는데(끝이 아무 데도 안 닿는 **매달린 변**), 면 순회가 거기까지 +갔다 되돌아온다. 왕복이라 **면적은 0** 이지만 bbox 는 부풀었다. + +``` +ring(왕복 포함) 으로 잰 bbox 12,951 ← area 는 멀쩡해서 안 걸린다 +dropCollinear 뒤로 잰 bbox 12,752 ← 이게 맞다 +``` + +`area` 만 보면 절대 못 잡는다. **폭·높이만 틀리는 오류**가 있다. + +--- + +## 4.9993. 교훈 #33 — 도면은 위치를 **중심선**으로도 준다 + +말뚝은 폐곡선이 안 된다(해치뿐). 그런데 위치는 도면에 있다 — `CS-CNTL` 레이어의 +수직선이 말뚝 중심선이고, 윤곽 수직선 두 줄 간격이 지름이다. + +``` +지름 518 윤곽 수직선 두 줄 간격 +위치 교대 3본 · 교각 4본 · 교대 3본 + −1,744 · 213 · 1,574 / 11,007 · 12,195 · 13,538 · 14,726 / 24,604 · 25,965 · 27,922 + 간격 1,957 / 1,361 — 시점과 종점이 대칭이다 (이게 검증이다) +``` + +**`pick` 에 이걸 집는 수단이 없다.** 폐곡선·원·개구부만 집는다. 그래서 스크립트로 +재서 설정에 적었고, 출처는 남겼지만 「도면에서 집는다」는 아니다. +`circles()` 를 만든 것과 같은 이유로 `centerlines()` 가 필요하다. + +그리고 **폭방향 배치는 어느 도면도 안 준다.** 교대배치 삽도가 평면이지만 +말뚝을 안 그리고, 종단면의 말뚝은 파단선으로 잘려 길이도 없다. 열 수·열 간격· +길이는 설계값으로 두고 그 사실을 남겼다. + +> 형상이 없다고 정보가 없는 게 아니다. **해치·중심선·기호도 도면이 말하는 방식**이다. +> 개구 기호(교훈 #12)를 되살린 것과 같은 종류의 이득이 남아 있다. + +--- + +## 5. 함정 모음 + +### DWG 쪽 + +- **유령 기하**: 정상 도면이 `x ≈ -48,400` 대에 몰려 있는데 레이어 + `000-구조물점선`/`000-구조물실선` 일부가 `x ≈ -565,692`에 떨어져 있다. + bbox·축척 계산 전에 시트 영역 밖 좌표를 먼저 버려야 한다. +- **레이어 일관성 없음**: 구조체 외곽선이 뷰마다 다른 레이어에 있다. + `-전력구`가 대부분이지만 C-C는 `기본`, J-J는 `집수정`에 있다. + **레이어 하나로 필터하면 뷰가 통째로 빠진다.** 후보 레이어를 넉넉히 잡을 것. +- **끊긴 곳은 대부분 "선이 없어서"가 아니라 "레이어가 틀려서"다.** 이 도면에서 + 프로파일이 안 닫히는 지점 50곳을 조사했더니 **전부** 레이어 오분류였고, + 도면에 실제로 없는 선은 하나도 없었다. 예: + + ``` + 단면 J-J 침사조·집수조 격실이 안 닫힘 + (2100,-13475)-(2100,-15175) 격벽 우면 → 레이어가 `-환기구` + (2100,-15175)-(2250,-15325) 집수조 좌하단 모따기 → `-환기구` + (1900,-15175)-(1750,-15325) 침사조 우하단 모따기 → `-환기구` + ``` + + **대응 비용이 100배 차이 난다** — 레이어 오분류는 설정 한 줄, 선 누락은 도면 + 수정이다. 진단 도구가 둘을 갈라주지 않으면 없는 도면 작업을 하게 된다. +- **가구(furniture)를 구체 프로파일에 넣지 말 것**: 케이블 지지대·철근이 같은 + 레이어에 있으면 폐곡선이 그 윤곽을 따라 돈다. C-C 내부 공동 꼭짓점이 114개가 + 나왔다. 뷰 분리용 레이어(`struct`)와 프로파일용 레이어(`body`)를 따로 둔다. +- **기존 닫힌 폴리라인은 쓸 게 없다**: LWPOLYLINE 140개 중 closed 96개인데 + 전부 해치 패턴/심볼 조각. 실제 구조체 프로파일은 0개. +- **좌표 노이즈**: `-500.00000000001455` 같은 값이 흔하다. mm 정수 반올림으로 흡수. +- **파싱 캐시 키를 잘못 잡으면 다른 도면이 같은 내용으로 나온다.** 두 번 다르게 밟았다. + ① 파일명 슬러그 `[^\w.-]→_` — `\w` 가 한글을 안 먹어 `거더배치`·`교대배치`·`교량폭원` + 이 전부 `____` 로 뭉개졌다. ② 고쳐서 쓴 **목록 인덱스** — 폴더에 파일이 하나 + 늘자 전부 한 칸씩 밀려 `[6]` 이 옆 도면 캐시를 읽었다. 인덱스는 **순서**에, + 슬러그는 **문자 집합**에 의존한다. **경로 해시**를 쓴다. + 캐시는 틀린 답을 **조용히 재사용**하므로 「같은 결과가 나온다」를 우연으로 넘기지 말 것. +- **acadrust의 `DIMENSION_LINEAR.pt10`은 0으로 온다**. 치수 정의점은 레이어 + `DEFPOINTS`의 POINT 또는 익명 `*D` 블록 안에 있다. `actualMeasurement`와 + `textMidPt`는 정상. + +### egm(EGModeler) 쪽 + +`Modeler_AI/docs/egm-usage.md`에 있는 내용 중 이 작업에서 특히 물리기 쉬운 것: + +- **`Extrude C=True` 단독은 항상 +Z로 돌출한다.** 커브 평면의 법선을 따르지 않는다. + XZ/YZ 평면 커브에는 반드시 방향을 명시할 것 — `Extrude D Y C=True {dist}`. +- **`Extrude` 전에 `SelByUids` 필수.** 선택 없이 실행하면 조용히 `created:[]`. +- **닫힌 `Polyline`(시작점=끝점)에 `Enter`를 붙이지 말 것.** 자동 종료되므로 + 남은 Enter가 다음 명령으로 새어나가 오작동한다. 열린 커브는 반대로 `Enter` 필수. +- **`SelByUid(s)`는 `Esc`로 닫아야 한다.** 안 닫으면 이후 명령이 전부 무시된다. +- **뷰 조작 명령(`SetViewmode`)을 `~` 접두로 보내면 끝나지 않는다.** `egm status` 의 + `command` 가 `"SetViewmode"` 로 물린 채 남고, 그 뒤 `~SelAll`·`~Delete` 가 전부 + 조용히 무시된다(씬은 그대로인데 아무 일도 안 일어난다). **접두 없는 `Esc`** 로 + 푼다. `run_egm.py` 가 `Esc`·`SelAll`·`Delete`·`Zoom E` 를 접두 없이 보내는 이유가 + 이것이다 — 흉내낼 때 `~` 를 붙이지 말 것. +- 명령은 비동기다. `egm status`의 `command`가 `null`이 될 때까지 대기할 것. +- **`BooleanDifference`/`BooleanUnion`은 두 개만 먹는다.** 셋 이상 선택하면 나머지는 + 조용히 무시되고 씬에 남는다. 둘씩 접을 것 → 교훈 #14. +- **뷰어 모드면 모델링 명령이 등록조차 안 된다.** `egm status`는 정상 응답하는데 + 생성만 안 된다. `egm guide` 개수로 먼저 확인할 것 (39 = 뷰어) → 교훈 #15. +- **`Loft` 는 기본이 서피스다.** 그대로 두면 boolean 이 죽는다. `C=True`(Cap)로 + 양 끝을 막아 솔리드로 받는다. `Enter` 는 한 번이면 된다. +- **`Loft` 의 `L`(DeleteOriginal)은 기본이 true** 라 단면 커브가 자동으로 지워진다. + 끄면 커브가 씬에 남아 `modelCount` 대조가 어긋난다. 건드리지 말 것. +- **별도 `Cap` 명령은 loft 뒤처리에 못 쓴다.** `Cap` 은 대상을 **제자리에서 고치므로** + 새 uid 가 안 나온다(`Originator_ChangeGeometry`). 파이프라인이 결과를 못 잡는다. +- **`Loft`·`Sweep2` 는 선택 순서가 결과를 바꾼다.** `SelByUids` 에 단면 순서 그대로 + 넘길 것. +- **씬 초기화(`SelAll`→`Delete`)가 파이프를 죽인 적이 있다.** 프로세스는 살아 있고 + 창도 응답하는데(`Responding=True`, CPU 유휴) 명령만 전부 "응답하지 않습니다"가 + 된다. CLI 로는 못 살린다 — 모델러를 재시작한다. +- **`ExportByPath` 의 경로에 `_` 가 있으면 큰따옴표 없이는 조용히 실패한다.** + `_` 는 egm 스크립트 특수문자다(`SetObjectName` 에서 이미 겪었다). 아래 두 줄의 + 차이가 "867KB OBJ" 와 "아무 일도 안 일어남" 이다. + + ```sh + egm '~ExportByPath D:/MYCLAUDE_PROJECT/dwg2model/out/a.obj Enter' # 파일 없음 + egm '~ExportByPath "D:/MYCLAUDE_PROJECT/dwg2model/out/a.obj" Enter' # 867KB + ``` + + **신호가 하나도 없다.** `egm status` 정상, `result` 는 `{"command":"ExportByPath", + "created":[],"deleted":[]}` 인데 이 명령은 원래 아무것도 생성하지 않으므로 + `created:[]` 가 정상이다. 파일 존재를 직접 확인하는 수밖에 없다 — + `run_egm.py` 의 `export_obj()` 가 mtime 까지 대조한다. + + 같이 알아낸 것: 내보내기 전에 `SelAll` 이 필요하고, **`.obj` 만 나온다** + (`.stl` 은 확장자를 받아들이지만 파일을 안 만든다). OBJ 는 `~SetObjectName` + 이름이 `g` 그룹으로 나오고 `.mtl` 이 같이 떨어진다. + +--- + +## 6. 작업 목록 — 무엇을 하고 있고, 무엇이 자동화 가능한가 + +파이프라인은 [`src/`](../src/) 에 있다. 각 단계가 무슨 +일을 하고, 사람 판단이 필요한지 아닌지를 아래에 정리한다. **판단이 필요 없는 +단계는 전부 자동화 대상**이다. + +| # | 단계 | 하는 일 | 입력 → 출력 | 사람 판단 | +|---|---|---|---|---| +| 1 | **파싱** `src/core/parse.mjs` | DWG를 엔티티로 읽고 LINE·LWPOLYLINE을 선분으로 편다 | DWG → 엔티티 JSON | 없음 | +| 2 | **기준점 검출** `src/core/datum.mjs` | 여러 뷰에 반복되는 수평선에서 표고 기준을 찾고, 도면 치수와 교차검증 | 엔티티 → datum + 검증표 | 검증 항목 지정 | +| 3 | **뷰 분리** `src/core/segment.mjs` | 기하를 잘게 쪼갠 뒤 표제로 다시 묶어 뷰를 만든다 | 엔티티 → 뷰 목록 | 뷰 제목 ↔ 평면 매핑 | +| 4 | **끊김 진단** `src/gaps.mjs` | 프로파일이 안 닫히는 지점을 찾고 원인을 **레이어 오분류 / 선 없음**으로 가른다 | 뷰 → 끊긴 좌표 목록 | 없음 | +| 5 | **원인 규명** `src/inspect.mjs` | 뷰 영역의 모든 엔티티를 레이어 무관하게 훑는다. 왜 안 잡히는지 볼 때 | 뷰 → 레이어별 실태 | 판정 | +| 6 | **프로파일 추출** `src/core/loops.mjs` | 선분 뭉치를 평면 분할해 면(=재료 영역)을 전부 뽑는다 | 선분 → 폐곡선 목록 | 없음 | +| 7 | **결과 확인** `src/probe.mjs` | 뽑힌 면의 크기·계층·레이어를 표로 본다 | 뷰 → 검증표 | 도면 치수 대조 | +| 8 | **조립 스펙 작성** `src/ir.mjs` | 어느 면을 어느 축으로 밀고 무엇을 뺄지 기술 | 폐곡선 → IR JSON | **필요** | +| 9 | **3D 생성** `src/emit/egm.mjs` | IR을 모델러 명령으로 번역해 실행 | IR → 솔리드 | 없음 | + +### 자동화 관점 + +**완전 자동 (1·4·5·6·9)** — 판단이 안 들어간다. 도면이 바뀌어도 그대로 돈다. + +**설정만 주면 자동 (2·3·7)** — 레이어 이름, 뷰 제목, 검증 항목만 도면별로 준다. +이게 `src/drawings/*.mjs` 한 파일에 모여 있다. 코드는 안 건드린다. + +**사람이 판단해야 하는 곳 (8)** — 딱 한 군데다. "이 면이 구체인가 공동인가", +"이 두 단면을 로프트로 이을 것인가 각각 압출할 것인가". 나머지는 기계가 한다. + +즉 **새 도면을 받으면 설정 한 파일 + 조립 스펙 한 번**이면 끝나는 구조다. +반복 실행·검증·회귀 확인은 전부 스크립트가 맡을 수 있다. + +### 반복 실행 훅으로 걸어두면 좋은 것 + +``` +node src/run.mjs <도면> 검증 실패 시 exit 1 → CI 게이트로 바로 쓸 수 있다 +node src/gaps.mjs <도면> 도면이 갱신될 때마다 끊김이 늘었는지 확인 +node src/probe.mjs <도면> "<뷰>" 프로파일 치수 회귀 확인 +``` + +`src/run.mjs` 는 검증이 깨지면 종료 코드 1을 낸다. 도면이 수정될 때마다 자동으로 +돌려두면, 기준점이나 뷰 분리가 깨지는 순간 바로 잡힌다. + +--- + +## 7. 검증 체크리스트 + +- [ ] **시트 전체를 먼저 떴는가** — 도형이 몇 벌인지, 날짜 다른 작업본이 겹쳐 있지 않은지 +- [ ] 뷰 상자를 잡기 전에 **그 도형만 떠서 눈으로 확인**했는가 +- [ ] 같은 뷰에 **확대 상세**가 섞여 있지 않은가 (축척이 다르다) +- [ ] **표기 치수가 숫자인가** (기호 `L·β·θ·VAR` 뿐이면 그 도면 안에는 대조 상대가 없다. + 다만 **다른 도면·표준 부재 치수**는 여전히 상대가 된다 — 바로 버리지 말 것) +- [ ] **DWG 모델단위**를 `DIMENSION.actualMeasurement` ↔ 치수 글자로 확정했는가 +- [ ] **도형 축척**이 1:1 인가. 아니면 표기 치수 2개로 역산해 교차검증했는가 +- [ ] 시트 영역 밖 유령 기하를 버렸는가 +- [ ] 표제 **중복률**을 세봤는가 (높으면 제목으로 뷰를 못 가른다) +- [ ] 레이어가 구조선과 치수선을 가르는가. 아니면 색·선종으로 걸렀는가 +- [ ] 표고 기준을 **선**에서 잡았는가 (텍스트 아님) +- [ ] 기준선이 **없는** 도면이면 그렇다고 명시했는가 (아무 선이나 자동 선택 금지) +- [ ] 기준선 후보가 둘일 때 라벨 근접도로 갈랐는가 +- [ ] 기준점이 도면 치수 최소 2개로 교차 검증됐는가 +- [ ] 뷰 폭·높이가 도면 표기값과 맞는가 +- [ ] 서로 다른 뷰의 심도가 일치하는가 (교차 검증) +- [ ] 끊긴 지점의 원인을 레이어/누락으로 갈랐는가 +- [ ] 프로파일 치수가 기하·치수 양쪽에서 같은 값인가 +- [ ] `Extrude`에 방향(`D X`/`D Y`)을 명시했는가 +- [ ] loft 구간이 **꺾이는 자리마다** 끊겨 있는가 (한 loft 로 두 번 꺾을 수 없다) +- [ ] loft 두 단면의 꼭짓점 수가 같은가 (다르면 위상을 맞춰서 넣어야 한다) +- [ ] 실행 전 `egm guide` 개수가 39가 아닌가 (뷰어 모드 확인) +- [ ] 실행 후 `modelCount` 가 최종 파트 수와 같은가 +- [ ] `node src/measure.mjs <도면>` 이 기준선과 맞는가 (비틀림·누락 컷을 잡는 가장 빠른 신호) +- [ ] 새 형상이면 기대값을 한 번 조립해 확인하고 `--save` 로 얼렸는가 + +--- + +## 변경 이력 + +| 날짜 | 내용 | +|---|---| +| 2026-08-05 | 최초 작성. 교훈 #1~#3, 함정 모음, 작업 순서 정리 | +| 2026-08-05 | 교훈 #4(DWG 분리하지 말 것), #5(캡처 이미지 좌표 사이드카) 추가 | +| 2026-08-05 | 교훈 #2 전면 개정 — 뷰 분리를 "교대 분할 + 제목 귀속"으로 교체. 11개 뷰 전부 도면 치수와 일치 | +| 2026-08-05 | 파이프라인을 `src/` 로 일반화 (설정/범용코드/IR 3계층). 기준점 자동 검출 + 자동 교차검증 | +| 2026-08-05 | 교훈 #5 보강 — 캡처는 역산 말고 "뷰 박스로 줌 + 축척에서 캔버스 역산" | +| 2026-08-05 | 프로파일 추출(평면 분할) 구현. 끊김 50곳 진단 → 실제 결함은 5곳, 그중 도면 수정 필요는 1mm 스냅 오차 1건 | +| 2026-08-05 | 교훈 #6 추가 — 레이어가 재질을 인코딩. 사양(THK·fck)은 위치별로 다르므로 뷰별로 읽어야 함. IFC 매핑 초안 | +| 2026-08-05 | **첫 3D 생성 성공** — 전력구 본체(구체·기초·잡석) 15.4m. DWG→IR→egm 경로 전 구간 관통 | +| 2026-08-05 | 단면 오배정 발견·수정 — 본체는 C-C(3,800×4,600), 스터브만 B-B. 스터브 2기·사다리 샤프트 2기 추가(7파트) | +| 2026-08-05 | 맨홀 #2 · 환기구 추가. 벽 누락 3건 수정 → 교훈 #8(절단기호↔평면), #9(봉투 union), #10(구간 경계), #11(수평단면 축) 추가 | +| 2026-08-05 | 전이부 구멍·환기구 다리 폭·상부 슬래브 개구부 2개 처리. 최종 11파트 | +| 2026-08-05 | 제작 절차를 [`2d-3d-process.md`](2d-3d-process.md) 로 분리. 이 문서는 노하우만 | +| 2026-08-05 | 독립 저장소로 이관 후 재현 실행. 교훈 #14(boolean 은 이항 — 커터 8개가 조용히 무시됐다), #15(뷰어 모드면 명령이 등록조차 안 된다) 추가 | +| 2026-08-06 | Loft 백엔드 구현 + 링 정규화. 전이부(B-B→C-C)를 챔퍼·단차·종방향 헌치까지 세움. 교훈 #16(loft 는 꺾인 자리를 못 만든다) 추가. 부피 대조로 검증 — 오차 0.0002% | +| 2026-08-06 | 부피 대조를 `src/measure.mjs` 로 자동화(손계산 → 회귀 검사). 기준선을 `baselines/` 에 커밋. `run_egm.py --export`. 함정 추가 — `ExportByPath` 경로의 `_` 는 따옴표 없이는 조용히 실패 | +| 2026-08-06 | CIRCLE 지원 (`collectCircles` → `circles()` → `cylinder`). 손으로 적은 `BORE_R=450` 이 도면 대조로 바뀜. 평면도 `v0` 앵커 필요성 발견. 교훈 #17 추가 | +| 2026-08-06 | 구조물만 남기고(11→5파트) 전이부를 `assembleDuct()` 로 공유. **맨홀 #2 부피가 2배로 나오는 기존 결함 발견** — `mh.outer` 자기교차. 자기교차 검사 + `report.mjs` 뷰어 추가. 교훈 #18 | +| 2026-08-07 | 교량시종점 삽도로 **2지간 교량 한 기** 관통 — 상부공·거더 10본·교대·날개벽·교각·받침·말뚝, 95파트 820.10 m³, 의도 대비 전부 0.0000. 교훈 #29~#33 추가 (날짜별 작업본 겹침 · 표기치수 0 ≠ 사용불가 · 확대 상세 혼입 · 진단도구 불일치 · 중심선) | +| 2026-08-07 | **두 번째 도면군(교량 설계지침 삽도 7장) 실증.** 1장 관통(중앙분리대 방호벽 4.8058 m³ — 모델러·해석 일치), 6장은 막힌 게이트와 이유를 기록. 코어 가정 7개가 깨져 전부 수정 — 모델단위·뷰별 축척·명시 뷰 영역·datum 없음·레이어 아닌 필터·겹치는 선분 폐곡선 유실·규칙 적용범위. 교훈 #20~#25 추가 | diff --git a/docs/2d-3d-process.md b/docs/2d-3d-process.md new file mode 100644 index 0000000..d01442d --- /dev/null +++ b/docs/2d-3d-process.md @@ -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 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=` 지원) | +| `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 브리지 | diff --git a/docs/dwg2model-CLAUDE.md b/docs/dwg2model-CLAUDE.md new file mode 100644 index 0000000..6056da8 --- /dev/null +++ b/docs/dwg2model-CLAUDE.md @@ -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 의무의 근거다. diff --git a/docs/methodology.html b/docs/methodology.html new file mode 100644 index 0000000..7b7162a --- /dev/null +++ b/docs/methodology.html @@ -0,0 +1,1661 @@ + + + + + +2D 도면 세트 → 파라메트릭 3D 방법론 v0.1 + + + +
+ + + +
+ +
+

2D 도면 세트 → 파라메트릭 3D 모델
일반 방법론

+

평면도 · 입면도 · 좌/우측면도 · 종단면 · 횡단면 · 수평단면을 한 좌표계로 묶어
솔리드와 설계 파라미터를 만드는 절차와 판단 규칙.

+
+ 버전 v0.2 — 교량 삽도 실증 반영 + 작성 2026-08-07 + 근거 전력구·맨홀 일반도 4매 (평면 1 + 단면 10) 전 구간 관통
+ 교량 설계지침 삽도 7장 (1장 관통 · 6장 어디서 왜 막혔는지 기록)
+ 다음 표기 치수가 있는 교량 일반도 — 11.1 기준 체인과 sweep이 아직 미검증이다 +
+
+ +
+ 이 문서를 읽는 법 + 각 주장에 신뢰 등급을 붙였다. + 검증 실제 도면으로 끝까지 돌려봤다 · + 부분 원리는 확인했으나 표본이 하나다 · + 가설 아직 안 해봤다 — 다음 도면에서 확인할 것. +
기존 문서와의 관계: docs/2d-3d-process.md는 이 저장소의 명령 절차, + docs/2d-3d-conversion.md는 사례별 교훈, 이 문서는 그 둘에서 도면 고유값을 걷어낸 + 방법론이다. +
+ + +

0한 장 요약

+ +

도면 세트를 3D로 옮기는 일은 형상 작업이 아니라 좌표계 통일 작업이다. +뷰마다 따로 노는 로컬 좌표를 하나의 프로젝트 좌표로 묶고 나면, 나머지는 기계가 한다.

+ +
도면 세트 ─┬─ 기준점(datum)  ─→ 전 뷰 공통 Z (또는 X·Y)
+           ├─ 뷰 분리        ─→ 뷰마다 로컬 (u,v)
+           └─ 뷰 앵커        ─→ 프로젝트 (X,Y,Z)
+                                    │
+                          프로파일(폐곡선) · 원 · 개구부
+                                    │
+                    fit ─→ params.json ─→ build ─→ IR ─→ 백엔드(3D/IFC)
+                              ↑ 사람은 여기만 만진다      ↓
+                                                  부피·물량·규칙 검사
+ +

변하지 않는 규칙 12개

+
    +
  1. 게이트에 넣기 전에 「이 도면이 솔리드 대상인가」를 먼저 판정한다. 표기 치수를 숫자형/기호형으로 세고, 구조 윤곽이 닫히는지 본다. 기호 치수뿐이거나(L·β·θ·VAR) 윤곽이 안 닫히는 도면은 어느 게이트에서도 안 통과한다 — 교량 삽도 7장 중 6장이 그랬다. 이 판정은 몇 초면 된다.
  2. +
  3. 단위와 축척을 도면에서 되찾고 두 치수로 교차검증한다. DWG 모델단위는 도면군마다 다르고(1m vs 1mm), 도형마다 배율이 또 다를 수 있다(5.805배). 상수로 두면 모든 값이 조용히 배수만큼 틀린다 — 폐곡선도 부피도 정상으로 보인다.
  4. +
  5. 기준점을 먼저 확정한다. 형상보다 앞이다. 기준점 없이 뽑은 형상은 정합 단계에서 전부 다시 한다. 단, 기준선이 아예 없는 도면이 실재한다 — 그때는 검출을 끄고 뷰별 v0로 간다. 아무 선이나 자동으로 고르지 않는다.
  6. +
  7. 기준은 텍스트가 아니라 선에서 잡고, 도면 치수 2개 이상으로 교차검증한다. 라벨은 후보를 좁히는 힌트일 뿐이다.
  8. +
  9. 뷰 하나는 [좌표평면 · 반전 · 원점 · 축척 · 용도] 다섯 속성으로 서술된다. 도면 종류는 9가지지만 좌표평면은 XY·XZ·YZ 셋뿐 — 나머지 차이는 보는 방향(좌우 반전)이다. 종류마다 코드를 늘리지 마라.
  10. +
  11. 입면도·측면도로 솔리드를 만들지 않는다. 잘린 면이 아니라 투영 윤곽이라 깊이가 없다. 검증과 위치에만 쓴다.
  12. +
  13. 구간 배정표는 종방향 뷰에만 있다. 횡단면은 "그 지점의 모습"일 뿐 어디까지 유효한지 말해주지 않는다.
  14. +
  15. 뽑아낸 도형 목록에서 무엇을 쓸지 고를 때, 목록 순서가 아니라 치수로 지정한다. "2,700×2,750 인 것"처럼. 순서로 집으면 도면이 조금 바뀔 때 아무 말 없이 다른 도형을 집는다.
  16. +
  17. 손으로 적은 수를 넣지 않는다. 도면과의 끈이 없는 값은 도면이 바뀌어도 안 바뀌고, 파라미터로 올릴 자격도 없다.
  18. +
  19. 속을 파낼 때는 빈 곳 없이 꽉 찬 덩어리를 먼저 만들고, 거기서 뺀다. 외곽선이 오목하면 파인 자리에 애초에 재료가 없어 벽을 만들 수 없다. 빼는 솔리드는 한 번에 하나씩, 대상보다 조금 크게.
  20. +
  21. 의도와 결과를 따로 잰다. IR을 직접 평가한 부피와 백엔드 결과 부피를 대조해야 "우리가 틀렸나 툴이 흘렸나"가 갈린다.
  22. +
  23. 조용히 틀리느니 예외를 던진다. 못 찾거나 여럿이면 멈춘다. 자동 선택은 나중에 몇 시간짜리 추적으로 돌아온다.
  24. +
+ + +

1범위와 계층

+ +

1.1 입력과 출력

+ + + + + + + + + + +
항목내용상태
입력 형식DWG (acadrust wasm, read-only). 한 모델스페이스에 여러 뷰가 나열된 형태검증 R2018
입력 형식 — DXF파서가 DWG 전용이다. DXF는 ① 변환(ODA File Converter 등) 후 투입 ② DXF 리더 추가 중 하나. 엔티티 모델이 같으므로 하류는 그대로가설
읽는 엔티티LINE · LWPOLYLINE → 선분, CIRCLE → 원(따로), TEXT/MTEXT → 표제·주기·치수검증
버리는 엔티티ARC · ELLIPSE · SPLINE · 해치 · 블록 내부 로컬 좌표부분 비중은 쟀다 — 교량 도면 7장에서 ARC 0~17%(거더배치 249/1,433). 원호가 형상인 도면은 아직 미검증
모델단위DWG 1단위가 몇 mm인가. 도면군마다 다르다 — 맨홀군 1m, 교량 삽도군 1mm. 게다가 도형마다 배율이 또 다를 수 있다(5.805배 사례)검증 cfg.unitMm · viewBoxes[뷰].unitMm
치수의 성격표기 치수가 숫자인가 기호(L·β·θ·VAR)인가. 기호뿐이면 방법론 전체가 기대는 「도면 치수와 대조」가 성립하지 않는다검증 세어서 판정한다 (11.0)
출력솔리드 파트(이름·재질 부착) · 설계 파라미터 · 재질별 물량 · 부피 기준선검증
출력 — IFCIR의 extrude가 IfcExtrudedAreaSolid, union/difference가 IfcBooleanResult와 1:1. 매핑 초안은 있고 emit은 미구현가설
+ +

1.2 계층 — 무엇이 어디에 있어야 하는가

+ +

이 경계가 방법론의 뼈대다. 무너지면 두 번째 도면에서 파이프라인이 아니라 복사본이 생긴다.

+ + + + + + + + + +
계층아는 것절대 몰라야 하는 것
도면 설정 drawings/*.mjs레이어명 · 뷰 제목 · 임계값 · 앵커 · 기준점 검증항목 · 조립 판단—
코어 core/*파싱 · 뷰 분리 · 좌표 변환 · 폐곡선 추출 · 개구 기호 · 자기교차도면 고유값 일체. '-전력구' 같은 문자열이 들어가면 설계가 틀린 것
파라미터 params.mjs · fit.mjs · rules.mjs스키마 · 식 · 검증 · 규칙도면도 3D도 모른다
+ 이 경계는 두 곳에서 이미 새 있었다(교량 도면에서 드러남).
+ ① core/datum.mjs의 UNIT_MM = 1000 — 도면 고유값이 코어에 상수로 있었다. + 같은 파일의 라벨 대역(±3)·뷰 분리 문턱(2)도 「1단위=1m」를 전제한 길이였다.
+ ② rules.mjs는 "도면도 3D도 모른다"고 적혀 있었지만 실제로는 + 챔퍼_시작X · 샤프트 · 맨홀_벽두께처럼 첫 도면의 파라미터 이름에 묶여 있었다. + 두 번째 도면에서 undefined 비교가 전부 위상 위반으로 떨어져 build가 죽었다.
+ 교훈: 경계 위반은 두 번째 도면을 넣기 전에는 안 보인다. 문서에 "안 들어간다"고 쓰는 것으로는 안 지켜진다. +
IR ir.mjs"이 프로파일을 이 축으로 밀고 저걸 뺀다"백엔드 사정 · 도면 사정
백엔드 emit/*모델러 명령 순서 · 비동기 대기 · boolean 이항 접기도면 사정
+ +
+ IR을 중간에 둔 이유 + 도면 해석과 3D 툴을 직접 붙이면 툴을 바꿀 때 전부 다시 짠다. IR은 도구 중립이라 + 3D 툴 없이도 검증·diff·부피 평가가 된다. 실제로 이 성질 하나가 "모델러가 흘리는가"를 + 판정하는 유일한 수단이 됐다(7장). +
+ + +

2좌표계 · 원점 · datum

+ +
+ 셋을 구분한다 — datum은 프로젝트 원점이 아니다 + datum은 Z 축 하나만 잡는 장치다. 3축 원점 전체가 아니다. + Z는 datum이, X·Y는 뷰 앵커가 준다. 갈라놓은 이유는 도면 관행 때문이다 — + 토목 도면은 모든 단면을 같은 표고선에 맞춰 배치하므로 Z는 전 뷰 공통으로 한 번에 + 잡히지만, 가로축 원점은 뷰마다 제각각이라 뷰별로 잡아야 한다. +
+ + + + + + + +
개념무엇을 정하는가이 저장소Revit 대응
프로젝트 원점3축 원점 전체. 선언한다frame.originProject Base Point
datum그중 Z=0 평면 하나. 도면에서 검출한다(점이 아니라 선)datum.levelLabel + checksLevel (기준 레벨)
앵커뷰별 가로·세로축 원점 (X·Y)anchors.u0 / v0없음 — Revit은 뷰가 원점을 공유한다
도면 월드 좌표측량 좌표. 빼고 버린다DWG x,y (예 y=483623.477)Survey Point / 공유 좌표
+ +

선형 구조물이면 X 원점을 측점에 맞추는 것이 유리하다(측점 0k000의 좌표를 프로젝트 원점으로). +그건 datum이 아니라 앵커 u0 로 준다 — 그 측점 선의 뷰 좌표를 u0에 넣고, +frame.origin에 "측점 0k000"이라고 적어 근거를 남긴다. +대상 도면은 측점이 아니라 구조물 최좌단을 X=0으로 잡았다(u0: 1000). +부분 측점 기반 원점은 미검증 — 곡선 선형·구배와 함께 12장 항목이다. 교량은 11장을 볼 것.

+ +

2.1 프로젝트 좌표계를 먼저 선언한다

+ +

축 셋과 원점을 문장으로 적고 시작한다. 이걸 안 적으면 뷰마다 다른 해석이 섞인다.

+ +
frame: {
+  x: '길이방향 (기점 → 종점)',
+  y: '폭방향 (중심선이 0, 평면도 위쪽이 +)',
+  z: '표고 (기준고가 0, 아래로 음수)',
+  origin: '구조물 최좌단 × 중심선 × 기준고',
+}
+ + + + + + +
구조물 종류XYZ 원점 후보
선형 토목(전력구·암거·터널)측점 방향중심선 기준 폭도로 계획고 · 토공 계획고 · GL · EL 기준선
점형 토목(맨홀·수조·기초)도면 가로도면 세로GL · 상단 슬래브 상면
건축그리드 X 통심선그리드 Y 통심선1FL · 기준층 FL
+ +

단위는 내부 mm 정수. 백엔드에 넘기기 직전에만 m로 나눈다. mm 정수 반올림이 DWG의 +부동소수 노이즈(-500.00000000001455)를 자동으로 흡수한다. 반올림을 뒤로 미루면 +boolean 단계에서 터진다. 검증

+ +

2.2 datum(Z=0 기준선) 검출 — 4단계

+ +
    +
  1. 라벨이 한 무리인지 먼저 본다. 신규 라벨 y를 정렬해 2m 이상 벌어진 곳에서 끊는다. + 무리가 둘 이상이면 공통 기준선이 없는 도면이므로 여기서 예외를 던진다. 아래 2단계의 숨은 전제가 바로 이것이다.
  2. +
  3. 후보 수집. 여러 뷰에 반복해서 나타나는 수평선(또는 수직 그리드선)을 모은다. 출현 횟수는 잡음 문턱으로만 쓴다.
  4. +
  5. 라벨 근접으로 확정. 후보가 둘 이상이면(계획고는 보통 도로/토공 두 줄이다) 라벨 바로 아래·옆의 선을 고른다. 라벨은 자기 선을 가리킨다.
  6. +
  7. 선에서 값을 읽는다. 텍스트 삽입점이 아니다. 실제로 713mm 틀렸다.
  8. +
  9. 도면 치수 2개 이상과 대조. 하나만 두면 우연히 맞을 수 있다. 하나라도 어긋나면 datum이 틀린 것이므로 다음 단계로 가지 않는다.
  10. +
+ +
+ 왜 1단계가 추가됐나 — 검출기가 예외 없이 틀린 답을 냈다 검증 + 3단계는 라벨 y를 평균내고 「그 평균 바로 아래 선」을 고른다. 숨은 전제는 + 「라벨 하나가 선 하나를 가리킨다」, 즉 라벨이 한 곳에 모여 있다는 것이다. + 전력구 도면은 11개 뷰가 같은 계획고 선을 공유해 그 전제가 참이었고, 그래서 안 보였다. +
유형 삽도는 같은 라벨을 도형마다 반복한다. 횡단경사 삽도에서 + 교량 계획고·토공 계획고 라벨 10개가 y 27,533~81,177에 흩어져 + 평균 56,453은 어느 도형에도 속하지 않는 값이 됐다. 결과: +
교량 계획고 → y=54,885.8
+토공 계획고 → y=54,885.8      ← 정의상 다른 표고인데 같은 답
+라벨별 국소 선 → 9개가 전부 다르다. 54,885.8 은 그중 어느 것도 아니다
+ 예외가 아니라 그럴듯한 숫자가 나왔다. 규칙 12(조용히 틀리느니 예외)는 + 검출기 자신에게도 적용된다 — 전제가 깨졌는지 스스로 검사하지 않으면 + 실패가 답의 모습으로 나온다. +
+ +
datum: {
+  levelLabel: /도로\s*계획고/,          // 후보를 좁히는 힌트
+  checks: [                             // 최소 2개. 도면에 적힌 치수를 그대로
+    ['B-B 구체 상단', (v) => v['단 면 B-B']?.layerZ['-전력구']?.[1], -4907],
+    ['B-B 기초 하단', (v) => v['단 면 B-B']?.layerZ['!-기초']?.[0],  -7757],
+  ],
+}
+ +
+ 공통 datum이 있으면 무조건 그것부터 찾는다 검증 + 단면도를 배치할 때 표고선을 맞춰 그리는 관행이 그대로 3D 정합 데이터가 된다. + 대상 도면은 11개 뷰가 같은 y에 계획고 선을 공유해 뷰별 Z 오프셋 계산이 통째로 사라졌다. + 건축이면 FL/층레벨, 선형이면 EL 기준선이 같은 역할을 한다. +
먼저 확인할 것: 뷰별로 검출한 기준선 y가 전부 같은 값인가. 다르면 뷰마다 Z 오프셋이 필요하고, 방법론 비용이 한 단계 올라간다. +
+ +
+ 공통 datum이 없는 도면이 실재한다 검증 + 교량 삽도 7장 전부가 그랬다. 지반 레이어는 7장 중 1장(CS-GRND)뿐이고 + 계획고 라벨은 1장(10개)뿐이다. 나머지는 표고선 자체가 안 그려져 있고, + 도형마다 제 바닥이 서로 다른 y에 앉는다. +
당시 코드는 이 경우 예외로 죽었다 — 표고 기준선을 찾지 못했다. + 그 자리에서 아무 수평선이나 자동으로 고르는 것은 틀린 대응이다(조용히 어긋난다). +
대응(구현됨): datum: {mode:'none'} 으로 검출을 끄고 DWG y를 그대로 두되, + 표고는 뷰마다 anchors[뷰].v0 로 준다. 위 후보 ①이 그대로 맞았다 — + 앵커 규칙을 세로축으로 확장한 것 이상이 필요하지 않았다. +
중앙분리대 삽도에서 두 단면의 기준면 y가 실제로 달랐다(포장면 48,812.1 vs 바닥판면 49,096.5). + 그 차이가 곧 「토공용은 포장에 49mm 묻히고 교량용은 바닥판 위에 얹힌다」라는 형상 정보였다. +
+ +
+ datum이 없으면 G2의 교차검증도 같이 사라진다 부분 + 위 4단계의 마지막 항목(도면 치수 2개 이상과 대조)은 datum이 있을 때의 절차다. + datum을 끄면 대조할 것이 없어져 datum.checks가 빈 배열이 되고, + G2가 「통과」가 아니라 「해당 없음」이 된다. 방법론은 이 구멍을 안 다뤘다. +
대신 검증을 G3로 미룬다 — 폐곡선의 폭·높이가 도면 표기 치수와 맞는지로 옮긴다. + 중앙분리대에서는 단면 폐곡선이 585 × 1,270으로 나온 것이 곧 기준·축척·앵커를 + 한꺼번에 검증한 것이었다. 다만 이건 늦은 검증이다 — 틀렸을 때 원인 후보가 + 기준·축척·앵커 셋으로 늘어난다. +
+ + +

3뷰의 일반 모델

+ +

도면 한 장을 3D 공간에 스티커처럼 붙인다고 보면 된다. 붙이려면 네 가지를 알아야 한다.

+ + + + + + + +
속성묻는 것예
plane어느 면에 붙이나평면도 → XY면, 정면도 → XZ면, 측면도 → YZ면
flip뒤집어 붙였나배면도는 정면도를 좌우 뒤집은 것
anchor어디에 맞춰 붙이나이 그림의 가로 1,000 자리가 X=0
role붙여서 뭘 하나형상을 만든다 / 검증만 한다
+ +
+ 핵심 일반화 — 도면 종류는 9가지, 좌표평면은 3가지 + plane은 평면도가 아니라 좌표평면이다(용어가 겹치니 주의). + 그림이 놓이는 면은 셋뿐이다. +
XY면 ← 평면도 · 저면도 · 수평단면
+XZ면 ← 정면도 · 배면도 · 종단면
+YZ면 ← 좌측면도 · 우측면도 · 횡단면
+ 같은 면에 놓인 것들끼리의 차이는 보는 방향뿐이고 그건 좌우 반전이다. + 정면도와 배면도는 둘 다 XZ면에 놓이고 X 방향만 반대다. + 도면 종류가 9개여도 처리 코드는 3개면 된다 — 종류마다 코드를 늘리지 마라. +
현재 코드의 plan/long/cross/horizontal이라는 kind 이름은 이 사실을 가린다. +
+ +

3.1 뷰 → 평면 대응표

+ + + + + + + + + + + + +
뷰보는 방향평면도면 (u,v) →반전절단면?상태
평면도−Z (위에서)XY(X, Y)—아니오검증
저면도+ZXY(X, −Y)flipV아니오가설
수평 단면−ZXY(Y, X) swapUV도면마다예검증
정면도+YXZ(X, Z)—아니오가설
배면도−YXZ(−X, Z)flip아니오가설
종단면±YXZ(X, Z)절단 방향예검증
좌측면도+XYZ(Y, Z)투상법아니오가설
우측면도−XYZ(−Y, Z)flip아니오가설
횡단면±XYZ(Y, Z)절단 방향예검증
+ +

3.2 뷰 역할 — 무엇을 주는 뷰인가

+ +

평면만 정해서는 부족하다. 같은 XZ 평면이라도 종단면과 정면도가 하는 일이 전혀 다르다. +역할을 배정하지 않으면 입면도로 압출하는 사고가 난다.

+ + + + + + + +
역할주는 것전형적인 뷰쓰는 법
생성 generator재료 영역 폐곡선 → 그대로 압출·로프트횡단면 · 종단면 · 수평단면loop(뷰, 폭, 높이).ring
배정 assigner어느 구간에 어느 단면이 유효한가종단면 (선형은 필수)수평선을 Z 내림차순 × X 범위로 늘어놓는다
위치 locator개구부·관통·부속물의 좌표평면도 · 수평단면 · 입면도opening() · circles() · rect()+expect()
검증 validator만든 3D가 맞는지 대조할 윤곽·치수입면도 · 좌/우측면도 · 상세도3D를 그 방향으로 정투영해 윤곽 비교
+ +
+ 입면도·측면도로 솔리드를 만들지 마라 부분 + 단면도는 잘린 면만 그리지만 입면도는 보이는 모든 것을 그린다. 그래서 입면도의 + 폐곡선은 재료 영역이 아니라 여러 깊이가 겹친 투영 윤곽이다. 압출하면 깊이가 + 거짓이 된다. 실제 압출은 평면도(footprint) + 단면도(높이)로 하고, 입면도는 + ① 외곽 검증 ② 개구부의 XZ/YZ 위치 ③ 마감선·레벨선 확인에만 쓴다. +
판별법: 그 뷰에 해치(절단면 표시)가 있으면 단면, 없으면 투영이다. 제목만 믿지 말 것. +
+ +

3.3 앵커와 flip 판정 절차 검증

+ +

뷰 안의 좌표는 뷰 좌단이 0인 u와 기준고가 0인 v다. 프로젝트 축으로 옮기려면 +뷰마다 "어느 u가 프로젝트 0인가"를 정해야 한다. 투상법(1각/3각)을 가정하지 마라 — +도면마다 다르고, 틀려도 치수만 보는 동안은 안 걸린다.

+ +
    +
  1. 뷰 폭·높이를 도면 표기 치수와 대조. 여기가 안 맞으면 뷰 분리부터 틀린 것이다.
  2. +
  3. 앵커를 지어내지 말고 유도한다. 예: 뷰 세로 범위 −11,678~−1,478이 전폭 10,200이고 아래 끝이 Y=−1,900이면 v0 = -11,678 + 1,900 = -9,778.
  4. +
  5. 비대칭 특징 하나로 flip을 판정한다. 중심선 대칭 형상만 보면 뒤집힘이 안 드러난다. 샤프트·개구부·노치처럼 한쪽에만 있는 것을 고른다.
  6. +
  7. 다른 뷰에서 이미 확정한 좌표와 교차검증. 최소 2개. 세 개면 확정이다.
  8. +
+ +
// 실제로 이렇게 확정했다 — 셋이 동시에 맞으면 앵커는 틀릴 수 없다
+샤프트 사각 1,300×1,300   v -10,428..-9,128 → Y -650..+650     중심선 대칭 ✓
+Ø900 보어 2개             v -9,778          → Y 0               ✓
+맨홀 내부 2,000×7,200     v -11,278..-4,078 → Y -1,500..5,700   ✓
+ +
+ 치수만 보는 동안은 틀린 앵커도 안 걸린다 검증 + 평면도에서 expect(뷰, 폭, 높이)로 치수만 확인하는 동안은 v0가 없어도 아무 문제가 + 없었다. 원(CIRCLE) 좌표를 쓰는 순간 보어가 Y=−9,778로 튀어나왔다. + 좌표를 쓰기 시작하면 숨어 있던 앵커가 드러난다. 수평 단면에서 이미 겪은 문제를 + 평면도에서 반복했다 — "세로축이 표고겠거니" 하고 넘어갔기 때문이다. +
+ +

축을 뒤집거나 맞바꾸면 감김 방향이 반대가 된다. 뒤집기 횟수가 홀수면 꼭짓점 순서를 +되돌려 면적을 양수로 유지한다. 코어가 자동으로 하지만, 새 평면을 추가할 때 잊기 쉬운 자리다.

+ +

3.4 뷰 분리 알고리즘 부분

+ +

여러 뷰가 한 모델스페이스에 나열돼 있으면 먼저 갈라야 한다. 간격 임계값 하나로는 절대 안 된다 — +넓게 잡으면 이웃이 딸려오고 좁게 잡으면 한 뷰가 쪼개진다. 둘 다 실제로 터졌다.

+ +
+ 배지를 검증 → 부분으로 내린다 — 이 알고리즘은 전제가 둘 있다 + 아래 절차는 (가) 뷰 사이에 간격이 존재하고 (나) 뷰마다 유일한 제목이 있다를 전제한다. + 전력구 도면 4매에서는 둘 다 참이었다. 교량 삽도 7장에서는 둘 다 거짓이었다. +
· (가) 실패 — 상세 확대 테두리 상자가 상세행과 단면행을 세로로 잇는다. + 임계값을 10mm까지 좁혀도 조각이 1개였다. 구조선과 같은 레이어·같은 색이라 걸러낼 수도 없다. +
· (나) 실패 — 표제 중복률 47~84%. 단면A가 4벌, 교량 시·종점이 6벌. + 교량폭원은 뷰 제목이 아예 없다(표제는 시트명 하나, 나머지는 A·B·L of Road). +
대응(구현됨): cfg.viewBoxes — 뷰마다 {box:[x0,y0,x1,y1], kind}를 설정에 적는다. + 선분 단위로 나눈다(조각 단위로 나누면 뷰를 잇는 다리 조각이 통째로 한쪽에 붙는다). + 양 끝이 다 들어오는 선만 취해 경계를 넘나드는 선을 자동으로 버리고, 상자가 겹치면 예외를 던진다. +
+ +

제목이 있고 간격이 있는 도면에서는 아래가 여전히 최선이다 — 좌표를 설정에 적지 않아도 되기 때문이다.

+ +
1. 구조 레이어 기하만 모은다 (넉넉한 struct 정규식)
+2. x ↔ y 를 번갈아 가며 더 이상 갈라지지 않을 때까지 잘게 쪼갠다 (임계 0.2m)
+   ↳ y로 나눈 뒤에야 드러나는 x 간격이 있다. 한 번씩만 하면 부족하다
+3. 조각마다 "바로 위에 있는 표제"를 찾아 귀속시킨다
+4. 같은 표제를 가진 조각끼리 합치면 그게 뷰다
+ + + + + + + +
장치왜튜닝 값
표제 최소 글자 높이부재 방향 라벨(h=0.30)이 표제로 잡히면 뷰를 훔쳐간다minHeight — 도면의 글자 높이 분포를 먼저 세어본다
표제 세로 여유크게 잡으면 옆 표제가 뷰를 통째로 가져간다 (0.6m→0.2m로 해결)ySlack
상세도·재료표도 표제 후보에 넣기자기 조각을 가져가므로 뷰로 새어들지 않는다. 뷰가 아닌 표제에 귀속된 조각은 버린다—
시트 영역 밖 버리기유령 기하와 블록 로컬 좌표가 bbox·축척을 깨뜨린다sheet: {x0,x1,y0,y1}
+ +
+ 다중 시트 가설 · 축척 혼재 검증 + 현재 sheet는 사각형 하나다. 도면이 여러 장으로 나뉘어 각각 다른 좌표대에 있으면 + 배열로 확장하고 시트별 오프셋을 둬야 한다. 대상 도면은 4매가 한 좌표대에 있어 필요 없었고, + 교량 삽도도 도형이 한 덩어리라 필요 없었다(빈 시트 테두리가 옆에 하나 더 있었지만 유령이다). +
축척 혼재는 실재한다. 같은 삽도 묶음 안에서 교량폭원은 1:1인데 + 중앙분리대 방호벽 단면은 5.805배였다. unitMm을 뷰 스펙에 두는 것으로 해결했다. +
+ +
+ DWG를 뷰별로 미리 쪼개지 마라 검증 + "뷰마다 별도 파일로 나누면 편하지 않나"는 손해다. 가장 큰 이득이었던 + "모든 단면이 같은 표고선에 맞춰 배치돼 있다"는 한 모델스페이스에 함께 있어야만 성립한다. + 분리하면 3D 정합의 절반을 버린다. 절단기호 ↔ 단면 매핑도 끊긴다. +
+ + +

4절차 — 11개 게이트

+ +

3D부터 세우면 어디가 틀렸는지 알 수 없다. 아래 순서를 건너뛰지 않는다. +각 게이트는 통과 기준이 있고, 실패하면 그 자리에서 멈춘다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
게이트통과 기준실패 시 도구대표 함정
G0 백엔드 준비모델링 명령이 등록돼 있는가 (연결 여부가 아니다)egm guide 개수 — 39면 뷰어 모드파이프는 살아 있고 status도 정상인데 생성만 조용히 실패
G1 파싱·시트엔티티 수가 나오고, 시트 밖 좌표가 걸러졌는가inspect — 레이어 무관 전수유령 기하 · INSERT 블록 로컬 좌표 · 캐시 파일명 슬러그 충돌
G1.5 도면 적격 신규표기 치수가 숫자인가(기호 L·β·θ·VAR 비율) · 모델단위·축척이 확정되는가 · 구조 윤곽이 닫히는가치수 텍스트 분류 계수 · DIMENSION.actualMeasurement ↔ 치수 글자 대조 · gaps의 「선 없음」 비율기호 치수뿐이라 대조할 상대가 없다 · 단위가 1000배 · 구성 도해라 애초에 안 닫힌다
G2 기준점도면 치수 2개 이상과 일치. 기준선이 없는 도면이면 「해당 없음」으로 명시하고 검증을 G5로 미룬다datum.checks 확장 · datum.mode:'none' + 뷰별 v0텍스트 위치로 잡음(713mm) · 계획고 후보 둘(420mm) · 기준선이 아예 없는데 아무거나 자동 선택
G3 뷰 분리뷰 폭·높이가 도면 표기와 일치. 서로 다른 뷰의 심도가 같게 나오는가임계값·minHeight 조정 → 안 되면 viewBoxes(명시 영역)이웃 뷰 혼입(폭 부풀음) · 뷰 잘림(하단 누락) · 제목 없음/중복 · 테두리 상자가 뷰를 이어 조각이 1개
G4 평면·앵커·flip비대칭 특징 좌표가 다른 뷰와 일치 (2개 이상). 뷰별 축척이 다르면 여기서 확정한다 — 표기 치수 2개로 역산3.3 절차 · viewBoxes[뷰].unitMm수평 단면의 세로축을 Z로 봄 · 평면도 v0 누락 · 도형마다 축척이 다름
G5 프로파일 품질자기교차 0. 꼭짓점 수가 예상과 맞는가. 끊김의 원인이 분류됐는가. + 폐곡선의 폭·높이가 도면 표기와 맞는가 — G2를 건너뛴 도면은 여기가 유일한 검증점이다gaps · --loops · report(그림)개구 기호가 링 오염 · 가구(케이블랙) 혼입 · 레이어 오분류 · + 끊김 0인데 폐곡선 0(겹치는 동일선상 선분) · 윤곽이 진짜로 안 닫힘
G6 조립(IR)참조 무결성 · 소모 파트 keep 플래그 · loft 단면 꼭짓점 수 일치validateIRloft 위상 불일치 · 중간 파트가 최종에 남음
G7 파라미터화등급 분포가 납득되는가. 위상 규칙 위반 0fit → params.json · rules도면과 끈 없는 값을 파라미터로 올림
G8 좌표 검산--dry 출력의 좌표가 예상과 같은가명령 목록 육안 검산압출 방향 미지정 · 단위 환산 누락
G9 생성·부피파트 수 == modelCount. IR 의도 부피 == 실측 부피. 기준선 회귀 통과measure --ir · --primitivesboolean이 인자를 흘림 · 면으로만 맞닿은 union · 커터가 자기 부피보다 많이 뺌
+ +
+ 게이트를 CI로 걸어둔다 검증 + G2·G3 단계는 실패 시 exit 1을 낸다. 도면이 갱신될 때마다 자동으로 돌려두면 + 기준점이나 뷰 분리가 깨지는 순간 바로 잡힌다. G9의 기준선 회귀도 마찬가지다. +
+ +

4.1 새 도면 인테이크에서 실제로 드는 시간

+ + + + + + + +
단계사람 판단비용
파싱 · 끊김 진단 · 폐곡선 추출 · 명령 생성없음0 — 도면이 바뀌어도 그대로 돈다
레이어 · 뷰 제목 · 앵커 · 기준점 검증항목설정값 채우기설정 파일 하나. 반나절
조립 스펙필요 — 여기 한 군데뿐형상 복잡도에 비례
변형 도면(같은 형식, 다른 치수)params 값만도면을 아예 안 읽는다
+ +

4.2 그림으로 읽는다 — 기하가 못 주는 의미

+ +

기하 파싱은 "이 선이 무엇인지" 모른다. 구체 외곽선인지 치수보조선인지, +여기가 벽인지 개구인지 좌표만으로는 안 갈린다. 실제로 맨홀 좌측 X=15,400 이 +벽인지 개구인지 선만 보고는 판정이 안 됐다. 사람은 그림을 보면 즉시 안다.

+ +

그래서 그림과 좌표를 한 쌍으로 묶는다. 이게 조립(5장) 판단의 재료다 — +조립이 "사람 판단이 들어가는 유일한 자리"인데, 그 판단을 그림 없이 하면 추측이 된다.

+ +
          ①캡처                    ②사이드카                ③조회
+도면 ────────────→ 이미지 ──── 픽셀↔좌표 변환 ────→ 프로젝트 mm ────→ 엔티티·링·레이어
+                     ↑                                                      │
+                     └──────────── ④오버레이(추출 결과를 그림 위에) ─────────┘
+
+사람: 이미지에 표시  →  ②로 좌표 확정  →  ③으로 "그 자리에 무슨 엔티티가 있나"
+ +

캡처 규칙 셋

+ + + + + +
규칙왜
전 레이어를 그린다우리가 버린 것까지 보여야 "왜 안 잡혔나"를 판단한다. 프로파일 레이어는 진하게, 나머지는 흐리게. 파싱이 버리는 ARC·ELLIPSE도 그린다 — 그게 빠진 자리가 보여야 한다
축척은 지정하고 캔버스를 역산렌더 결과에서 축척을 되짚으면 여백·DPI·레터박싱 어딘가에서 반드시 어긋난다. 뷰 bbox는 이미 아니까 "5 mm/px"을 정하고 캔버스 크기를 거기서 계산한다 → unitsPerPx가 계산값이 아니라 내가 정한 값이 된다
사이드카를 같이 떨군다이미지만 있으면 "여기"를 말로 설명해야 한다. 변환식이 붙어 있으면 픽셀이 곧 좌표가 된다
+ +

사이드카 — 그림과 좌표를 잇는 다리

+
{ "view": "단 면 B-B", "kind": "cross",
+  "image": { "w": 1675, "h": 1388 },
+  "originDwg": [-48351.647, 483619.770],   // 이미지 좌상단 픽셀의 도면 좌표
+  "unitsPerPx": 0.004,                     // 내가 정한 값 (역산 아님)
+  "yFlipped": true,                        // 화면 y는 아래로, 도면 y는 위로
+  "datumY": 483623.477,                    // 프로젝트 Z=0
+  "viewLeftDwgX": -48350.447,              // 뷰 좌단 = 로컬 u=0
+  "anchor": { "u0": 2150 },
+  "howTo": [ "dwgX = originDwg[0] + px * unitsPerPx",
+             "dwgY = originDwg[1] - py * unitsPerPx",
+             "u(mm) = round((dwgX - viewLeftDwgX) * 1000)",
+             "z(mm) = round((dwgY - datumY) * 1000)",
+             "프로젝트 좌표 = anchor 적용 (u0/v0/flip/swapUV)" ] }
+ +
+ 핵심 — 사람이 표시한 자리를 엔티티로 되찾는다 + 이 경로가 있어야 협업이 성립한다. 사람이 이미지에서 동그라미 친 자리가 + 픽셀 → 도면 좌표 → 프로젝트 mm 으로 내려가고, 거기서 그 자리의 선분·레이어·소속 + 폐곡선을 찾아낸다. 그래야 "여기 이상하다"가 "레이어 -환기구의 이 선분 + 때문에 링이 안 닫힌다"로 확정된다. 말로 주고받으면 서로 다른 곳을 본다. +
+ +

지금 되는 것 / 없는 것

+ + + + + + + + +
단계수단상태
① 뷰별 캡처 (SVG·PNG)capture.mjs. --crop으로 접합부만 확대있다
② 픽셀 → 프로젝트 좌표사이드카 .json있다
③ 좌표 → 엔티티 조회inspect.mjs(뷰 전수) · probe.mjs --ring(링 좌표) — 사람이 목록에서 눈으로 찾는다거칠다
③′ 점·영역으로 엔티티 집기"이 좌표 반경 200mm 안의 선분·링·원·개구부를 나열" 하는 도구없다
④ 추출 결과를 도면 위에 겹쳐 그리기report.mjs는 링만 따로 그린다. 캡처 위 오버레이는 아니다없다
3D 결과를 같은 규칙으로 렌더render.mjs → model-X/Y/Z.svg + 치수선있다
+ +

③′와 ④가 다음에 만들 것이다. 둘이 있으면 왕복이 닫힌다 — +사람이 그림에 표시 → 엔티티 확정 → 설정 한 줄 수정 → 다시 그림으로 확인. +지금은 ③에서 목록을 눈으로 훑느라 끊긴다.

+ +
3D 렌더도 같은 축척 규칙으로 뽑아야 도면과 나란히 놓고 대조된다. +입면도·측면도 검증(3.2 validator)이 붙는 자리가 여기다.
+ + +

5조립 스펙(IR) — 사람 판단이 들어가는 유일한 자리

+ +

5.1 IR 연산

+ + + + + + + + + + +
op필드쓰는 곳상태
boxmin[x,y,z], max[x,y,z]순수 직육면체검증
extrudeprofile, axis, from, to, cap, drift구간 내 단면 불변. drift 면 빗각 기둥(단면이 평면 안에서 미끄러짐)검증
loftsections:[{profile, at}, …]두 지점 사이 단면 선형 변화boolean 금지 결과가 안 닫힌다 → 11.4.6
intersectmembers:[id,…]두 방향이 동시에 맞아야 하는 형상 — 평면 윤곽 ∩ 정면 윤곽검증
cylinderradius, from, to원형 관통·기둥부분 Z축 한정
unionmembers:[id,…]봉투 만들기 · 구간 합치기검증
differencebase, cutters:[id,…]공동·개구부검증
sweep(측점 기반. 미구현)곡선 선형·구배가설
+ +

5.2 연산 결정 트리

+
이 형상을 어떻게 만들까?
+
+┌ 단면이 구간 내내 같은가?
+│   예 → extrude.  압출축 = 단면 평면의 법선
+│   아니오 ↓
+├ 두 지점 사이에서 선형으로 변하는가?
+│   예 → loft.  ★ 꺾이는 자리마다 단면을 놓고 구간을 끊는다
+│   아니오 ↓
+├ 축이 곡선이거나 구배가 있는가?
+│   예 → sweep (미구현). 지금은 구간을 잘게 끊어 loft/extrude로 근사
+│   아니오 ↓
+├ 원형 관통·기둥인가?
+│   예 → cylinder.  지름으로 집는다. 선분과 섞지 않는다
+│   아니오 ↓
+└ 층 구조가 수평 단면들로 주어지는가?
+    예 → 층별 XY 프로파일을 Z로 쌓아 union (조각이 Z로 진짜 겹치게)
+ +

5.3 조립 5원칙

+ +
+
+

① 치수로 집는다

+

순서(loops[0])로 집으면 도면이 조금만 바뀌어도 조용히 다른 면을 집는다. 면적 순위는 흔들리고 치수는 안 흔들린다. 못 찾거나 여럿이면 예외를 던진다.

+
loop('단 면 B-B', 2700, 2750).ring
+circles('평 면 도', 900, /^-전력구$/)
+opening('단 면 H-H', 1200, 2000)
+
+
+

② 꽉 찬 덩어리부터 만든다

+

외곽선이 오목하면(ㄷ자·계단형) 그대로 압출한 솔리드는 파인 자리가 비어 있다. 그 자리에 벽을 세우려고 공동을 빼도 뺄 재료가 없어 아무 일도 안 일어난다. 파인 면을 따로 압출해 합쳐서 빈 곳 없는 덩어리를 만든 다음에 뺀다.

+
shell = 오목한 외곽 압출
+fill  = 파인 면 압출        ← 폐곡선 추출이 이미 뽑아준 면
+solid = union(shell, fill)  ← 여기서 비로소 꽉 찬다
+body  = solid − 공동들      ← 이제 벽이 남는다
+
+
+

③ 구간은 합친 뒤 한 번만 뺀다

+

단면이 다른 구간을 각각 빼면 경계에서 반드시 구멍이 난다 — 큰 쪽 공동이 여유만큼 작은 쪽으로 넘어가 단차 벽면을 파먹는다. 공동끼리는 전이점에서 겹치지도 벌어지지도 않게 나눈다.

+
+
+

④ 커터는 하나씩, 밖으로

+

떨어진 커터끼리는 교차하지 않아 union이 죽는다. 커터는 하나씩 먹인다. 대상 면에서 정확히 시작하면 boolean이 불안정해지므로 모든 방향으로 여유(OVER)를 준다.

+
+
+

⑤ 꺾이는 자리마다 단면

+

loft는 두 단면 사이를 곧게 이을 뿐 꺾인 자리를 만들지 못한다. 꺾이는 좌표가 방향마다 다르면(옆·위 −300, 아래 −100) loft를 또 끊는다. 중간 단면 값은 상수로 박지 말고 내분점을 코드가 계산하게 둔다.

+
+
+

보조: 수직 단차는 저절로 생긴다

+

앞 구간이 작게 끝나고 뒤 구간이 크게 시작하면 그 차이가 곧 단차 벽면이다. 따로 만들 것이 없다. 종방향 헌치만 별도 쐐기로 되붙인다.

+
+
+ +

5.4 압출축과 벽 방향 검증

+ +
YZ 단면을 X축으로 밀면 → Y 방향 벽만 생긴다. X 방향은 열린다
+XZ 단면을 Y축으로 밀면 → X 방향 벽만 생긴다. Y 방향은 열린다
+ +

나머지 방향 벽은 두 단면을 교집합할 필요 없이 공동의 압출 범위를 좁혀서 만든다. +외곽은 전 구간을 채우고 공동만 안쪽으로 줄이면 앞뒤로 벽이 남는다. 공동의 끝이 솔리드 +내부라 면이 겹치지 않아 여유도 필요 없다.

+ +

단면이 어느 평면인지는 절단 기호가 결정한다. 절단선 양끝 두 점의 x가 같으면 YZ, +y가 같으면 XZ다. 폭 치수만으로는 구분이 안 되는 경우가 있다(X도 2,600, Y도 2,600).

+ +

5.5 프로파일이 못 쓸 때 — bbox 재구성

+ +

링은 못 믿어도 bbox는 믿을 수 있다. 형상이 직사각형이라는 걸 알면 bbox만으로 복원된다. +단, expect()를 남겨 도면과의 끈을 유지한다 — 값이 어긋나면 거기서 걸린다.

+ +
expect('단 면 H-H', 1200, 2000, '환기 OPEN부 (2.0x1.2)');   // 실재 확인만
+profile('mh.ventHole', 'XY', rect(15800, -500, 17000, 1500)); // 실제 프로파일
+ +

오목한 외곽은 rect()로 못 만든다. 그때는 X자 교차 분할이 필요하거나, +수평 단면에서 층별로 다시 세우는 쪽이 낫다.

+ + +

6파라메트릭화

+ +
DWG ──fit──→ params/<이름>.json ──build --params──→ IR ──→ 3D
+              ↑ 사람은 여기만 만진다.   build 는 도면을 안 본다
+ +
+ 불변식: build가 도면을 안 본다 검증 + 이것 하나가 나머지를 거의 다 푼다. 조립이 loop('단 면 B-B', 2700, 2750)처럼 + 치수를 도면 조회 키로 쓰는 한, 사용자가 2700을 3000으로 바꾸면 그런 폐곡선이 없어 예외가 난다. + 도면에 묶여 있으면 파라메트릭이 성립하지 않는다. +
검증: views.json을 치운 채 --params로 빌드해 명령 목록이 바이트 단위로 동일했다. +
+ +

6.1 값 하나의 생김새

+
{
+  "구체_외폭": { "v": 3800, "src": "measured", "from": "단 면 C-C 외곽 폭",
+                "fitted": 3800, "grade": "exact" },
+  "벽두께":    { "v": 400,  "src": "design", "min": 300, "max": 600, "step": 50 },
+  "구체_내폭": { "expr": "구체_외폭 - 2*벽두께" },
+  "샤프트":    [ { "no":1, "x0":1900, "x1":3200 }, { "no":2, … } ]
+}
+ + + + + + + + + +
필드뜻파생 효과
src: measured도면에서 뽑았다바꾸면 "도면과 다름" 경고. 막지는 않는다 — 설계를 바꾸는 게 목적이다
src: design사람이 정한다자유롭게 바꾼다
expr식 → 자동으로 derived위상 정렬로 평가. 순환이면 예외(구속 솔버가 아니다)
min/max/step유효 범위검증이자 UI 위젯이 그대로 나온다
fitted도면에서 뽑은 원값현재값과 비교해 도면 이탈 표시
배열치수가 아니라 개수가 바뀌는 것샤프트 2→3, 환기구 없음
+ +

6.2 재현 등급 — 파라미터로 못 만드는 형상 다루기 검증

+ +

프로파일을 파라미터로 "생성"하면 도면의 실제 형상을 잃는다. 50mm 보도 단차 같은 자잘한 것이 +생성기로 재현이 안 되면 조용히 빠진다. 그래서 완전일치를 요구하지 않고 등급을 매긴다.

+ + + + + + +
등급조건처리
exact꼭짓점 집합이 mm 단위로 일치파라미터로 완전 대체
tolerantbbox·면적·꼭짓점 수 일치, 개별 점 ±ε경고를 남기고 파라미터 사용
opaque생성기로 재현 불가도면 링을 좌표째 얼려 params에 박는다
+ +

opaque가 핵심이다. "재현 못 하면 파라미터화 안 한다"가 아니라 "재현 못 하는 링은 값으로 +얼려 넣는다". 그러면 build가 도면을 안 봐도 되면서 자잘한 형상이 빠지지도 않는다. +그 링은 못 바꾸는 것일 뿐이고, 그게 정직하다. +등급 분포가 곧 "이 도면이 얼마나 파라메트릭한가"의 척도다 +(대상 도면: opaque 8 · exact 2 · tolerant 1).

+ +

6.3 파라미터로 올릴 자격 검증

+
+ 도면과의 끈이 없는 값은 파라미터가 아니다 + const BORE_R = 450; // 원본 엔티티에서 눈으로 읽어 적음 — 이런 값은 바꿔도 + 도면과 대조할 방법이 없다. 도면이 바뀌어도 안 바뀐다. 먼저 circles()·loop()· + opening()으로 도면에서 집게 만들고, 다른 값과 대조해 어긋나면 빌드가 죽게 한 뒤에 + 파라미터로 올린다. +
+ +

6.4 규칙 층 — 폼과 도구를 가르는 것

+ + + + + +
종류뜻동작예
위상 topology형상을 만들 수 없게 된다막는다 (exit 1)loft 단면 위치 역전 · 벽두께가 절반 초과 · 보어가 벽을 뚫음 · 구간이 안 맞닿음
설계 design만들어지지만 기준 미달경고. 사람이 판단내공 높이 < 2,100 · 토피 < 1,000 · 최소 벽두께 · 개구 Ø600
+ +

규칙은 부재 종류에 붙는 것이지 도면에 붙는 게 아니다. 도면별 예외가 필요하면 +문턱 자체를 파라미터로 노출한다. 모델러까지 가서 비틀린 면으로 만나는 것보다 훨씬 싸다.

+ +

6.5 편집 UI는 얇다 검증

+
build --params → emit → run --reset --export → measure --ir → render --dims
+

파이프가 이미 CLI 단계 사슬이라 UI가 한 일은 params 편집 + 그 사슬 순서대로 실행뿐이다. +min/max/step이 그대로 입력 위젯이 됐고 src는 태그, fitted는 변경 표시가 됐다. +갱신 버튼이 검증을 태워야 도구가 된다 — 단순 실행이면 폼이다.

+ +
+ 못 박을 것 둘 + ① 모델러에서 직접 편집하지 않는다. 갱신하면 UID가 전부 바뀌어 날아간다. 편집은 params에서만.
+ ② 갱신 1회당 씬 초기화 1번. 초기화를 연달아 반복하면 모델러 파이프가 죽는다. +
+ + +

7검증 체계

+ +

7.1 의도와 결과를 따로 잰다 검증

+ +

부피가 이상할 때 물어야 할 것은 둘이다. 이걸 못 가르면 엉뚱한 데를 판다 — +실제로 손으로 기대값을 세 번 조립했는데 세 번 다 커터 범위를 놓쳐 틀렸고, +그 값으로 모델러를 의심하며 시간을 버렸다.

+ +
우리 조립(IR)이 틀렸나?         ← 의도 : IR을 격자로 CSG 평가 (모델러 없이)
+백엔드가 IR대로 안 만들었나?     ← 결과 : OBJ 삼각형을 발산 정리로 실측
+
+파트                    부피 m³     IR 의도       차이
+전력구_구체             100.5403   100.5350     0.0053 ✓
+맨홀2_구체               65.3577    74.6819    −9.3242 ✗  ← 여기만 백엔드가 흘렸다
+ +

격자 평가는 25mm 격자에서 0.1% 안팎이라 소수 셋째 자리는 못 보지만, +boolean이 죽는 사고는 언제나 m³ 단위로 어긋나므로 충분하다.

+ +
+ 「의도」를 재는 쪽이 근사면 대조가 무너진다 검증 + IR을 격자로 평가하던 방식은 얇은 부재에서 몇 %가 틀린다. 25mm 격자로 두께 240을 재면 + 셀 중심이 10개 잡혀 250 두께로 계산된다(+4.2%). 교량 상부공 바닥판(240)·마모층(50)· + 방호울타리에서 그대로 나왔고, 멀쩡한 모델 6파트 중 5개에 ✗가 찍혔다. + 같은 부재 3기가 서로 다른 값(8.4625 / 8.7125 / 8.4625)으로 나온 것도 격자 정렬 탓이다. +
대응: boolean이 없으면 격자를 안 쓴다 — box는 변의 곱, + extrude는 신발끈 면적 × 길이, loft는 심프슨(꼭짓점 선형 보간이면 + 단면적이 2차식이라 정확), union은 멤버 bbox가 안 겹치면 합. + 전부 0.0000 ✓로 떨어졌다. 출력에 해석/격자를 같이 찍는다. +
교훈: 검사기가 거짓 경보를 내면 사람이 ✗를 무시하게 되고, 그때 진짜 사고가 지나간다. +
+ +

7.2 이상 신호 우선순위

+ + + + + + + + + + + + +
#신호확정력뜻
1자기교차 링확정그 자체가 결함. 압출하면 솔리드가 쓰레기가 되고 이후 boolean이 조용히 무력화된다
2커터가 자기 부피보다 많이 뺌확정물리적으로 불가능 → boolean이 죽었다. 형상 논쟁이 필요 없다
3파트 수 ≠ modelCount확정boolean이 인자를 흘렸다. 남은 커터가 겹쳐 있어 겉보기엔 멀쩡하다
4IR에 없는 그룹이 결과에 등장확정3과 같은 사고인데 어느 커터가 남았는지까지 알려준다
5뷰 폭·높이 ≠ 도면 치수강함뷰 분리 또는 앵커 문제
6꼭짓점 수 이상의심4=정상 사각형 · 8=개구 기호 오염 · 114=가구 혼입 · 10=헌치 있는 정상
7의도 vs 실측 부피 차강함차이가 일정하면 union 조각 손실, 커터마다 다르면 커터 문제
8기준선 회귀 실패강함이전에 사람이 확인한 형상이 바뀌었다
9열린 변(비다양체)참고이음매 T 정점. 부피·렌더링은 정상이나 IFC에서 걸릴 수 있다
+ +

7.3 기대값은 자동으로 안 나온다 — 그리고 그게 맞다

+

커터는 관통을 확실히 하려고 대상 밖으로 튀어나와 있고 공동끼리 겹치기도 한다. 그래서 +Σ멤버도 base − Σ커터도 틀린 값이 된다. 틀린 기대값은 기대값이 없는 것보다 나쁘다. +새 형상의 기대값은 원시 파트 부피표를 재료로 사람이 한 번 조립하고, 맞으면 기준선으로 얼린다.

+ +

두 단면 사이 면적은 t의 2차식이므로 심프슨 V = L·(A0 + 4·A(½) + A1)/6이 +근사가 아니라 정확하다. loft 검산에 그대로 쓴다.

+ +
+ 기준선은 커밋한다 + baselines/<도면>.volumes.json은 out/과 달리 재생성으로 복구되지 않는다. + 사람이 한 번 확인한 부피가 남아 있어야 다음 변경이 형상을 건드렸는지 알 수 있다. +
+ +

7.4 그림으로 보는 검증 검증

+
    +
  • 추출 결과 뷰어 — 뷰별 링을 SVG로 그리고 자기교차·꼭짓점 수·원·IR·부피를 한 장에. 숫자로 반나절 걸린 원인이 그림에서는 빨간 X 두 개로 즉시 보였다.
  • +
  • 도면 캡처 + 좌표 사이드카 — 픽셀↔도면 좌표 변환을 JSON으로 같이 떨군다. 이미지에서 짚은 지점이 곧 프로젝트 좌표가 된다.
  • +
  • 3D 정투영 렌더 — 만든 모델을 X·Y·Z로 투영. 입면도·측면도 대조가 여기 붙는다(3.2의 validator 역할).
  • +
  • 치수선 렌더 — 파라미터를 가리키는 치수선을 렌더에 얹는다. 2D 도면이 치수로 형상을 증명하듯이.
  • +
+ +
+ 뷰어는 조립과 같은 변환 함수를 써야 한다 + 뷰어가 자기 변환을 따로 가지면 보여주는 것과 조립되는 것이 갈라진다. + 거짓말하는 뷰어는 없는 것보다 나쁘다. 캡처 변환도 역산하지 말고 + "뷰 박스를 지정하고 캔버스를 축척에서 역산"한다. +
+ + +

8함정 사전

+ +

전부 실제로 밟은 것이다. 증상 → 원인 → 대응 순.

+ +

8.1 도면·데이터

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
증상원인대응
bbox·축척이 통째로 이상유령 기하가 먼 좌표대에 있다sheet 영역 밖을 먼저 버린다
원점 근처 뷰에 남의 원이 무더기INSERT 블록 로컬 좌표같은 시트 필터로 거른다
뷰가 통째로 빠진다구조 외곽선이 뷰마다 다른 레이어에 있다후보 레이어를 넉넉히. struct는 넓게
프로파일 꼭짓점이 100개 넘음케이블랙·철근 같은 가구가 섞임body는 좁게. struct와 분리
폐곡선이 안 닫힘대부분 레이어 오분류(선은 있다)진단 도구로 "오분류 / 누락"을 가른다 — 대응 비용이 100배 차이난다
기존 닫힌 폴리라인이 쓸모없음전부 해치·심볼 조각기대하지 말 것. 좌표만 뽑아 새로 만든다
치수 정의점이 0으로 옴파서 한계actualMeasurement·textMidPt는 정상. DEFPOINTS 활용
그려진 길이가 실제가 아니다파단선. 기둥이 6,100 으로 그려져 있는데 실제 33,800. 폐곡선·부피가 그럴듯해 숫자로 안 걸린다CS-CUTL 류가 뷰 안에 있으면 의심한다. 생략량은 표고 텍스트 차 − 도형 길이 로 되찾는다
한 파일에 도형이 여러 벌 쌓여 있다도면집 스택. 제목·치수 없는 잔재가 65,000 간격으로 17벌뷰 상자를 눈으로 잡지 말고 도곽 블록에서 계산한다 — INSERT 위치 × scale × 용지(A1 841×594)
「단면도」 안쪽 선을 다른 부재로 읽음절단면이 아니라 투영이다. 먼 쪽 모서리가 겹쳐 보인다그 치수가 외곽 − 2×R 로 떨어지면 투영이다
평면 모서리가 45° 모따기파서가 LWPOLYLINE bulges 를 버려 90° 호가 현 하나로 눌린다호 끝점은 정확하므로 R 은 되찾는다. 링 면적을 그대로 쓰면 코너당 71,350 mm² 부푼다
구조 형상이 표로 잡힌다격자로 보면 평면도가 73행×83열짜리 표다표 색인에 레이어 필터(CS-TABL-*)를 준다
다른 DWG인데 엔티티 수·좌표가 똑같다파싱 캐시 키 충돌. 두 번 다르게 밟았다. + ① 슬러그 [^\w.-]→_ — \w가 한글을 안 먹어 거더배치·교대배치·교량폭원이 같은 이름이 됐다. + ② 고쳐서 쓴 목록 인덱스 — 폴더에 파일이 하나 늘자 전부 한 칸씩 밀려 [6]이 옆 도면 캐시를 읽었다캐시 키는 파일 경로 해시. 인덱스는 목록 순서에, 슬러그는 문자 집합에 의존한다. + 캐시는 틀린 답을 조용히 재사용하므로 "같은 결과가 나온다"를 우연으로 넘기지 말 것
모든 치수가 정확히 1000배DWG 모델단위가 도면군마다 다르다. 맨홀군 1단위=1m, 교량 삽도군 1단위=1mmunitMm을 설정으로. DIMENSION.actualMeasurement와 치수 글자를 비교하면 즉시 확정된다(3000.00 ↔ 3.000m)
도면 표기 치수와 대조가 불가능유형별 삽도는 치수가 기호다 — L · L1 · β · θ · VAR · n@3,600숫자 치수 대 기호 치수를 세어서 먼저 판정한다. 기호뿐이면 G2를 통과할 방법이 없으므로 그 도면은 대상에서 뺀다
구조선과 치수선이 같은 레이어표준 레이어(CS-CONC-MAJR)에 둘 다 올린 도면이 있다레이어 말고 색이 가른다 — colorIndex 0(ByBlock, 치수 블록 안) vs 7(ByLayer, 구체). 걸 수 있는 축을 레이어 하나로 두면 안 된다
뽑힌 형상이 어느 도면에도 없는 조합한 시트에 날짜 다른 작업본이 겹쳐 그려져 있다. 교량시종점 삽도는 TYPE1~4 한 벌(주황 테두리)과 + 「교량 시·종점 …전면」 한 벌(자홍)이 서로 어긋나게 포개져 있다. 뷰 상자가 그 경계에 걸치면 두 벌이 섞인다시트 전체를 먼저 뜬다 → 테두리·표제로 벌을 가른다 → 도형 하나를 떠서 눈으로 확인 → 그 다음 상자. + 숫자로는 안 걸린다 — 폐곡선도 치수도 그럴듯하게 나온다. 날짜 도장은 개정 이력이 아니라 작업본 구분일 수 있다
같은 뷰에 축척이 다른 도형확대 상세가 같은 도형 안에 들어 있다(교좌받침 상세). 가로·세로 배율이 서로 다르기도 하다 — 받침이 153×182 인데 상세에서는 461×196치수로 집으면 저절로 갈리지만 갈렸는지 확인한다. fit() 에 좌표 범위 검사를 넣는다
같은 부재가 1 mm 다르게 나온다DWG 부동소수를 꼭짓점마다 따로 반올림한다. 대칭 배치된 부재에서만 드러난다 (교대 5,192/5,191 · 받침 154/153×3)loops(…, tol) 로 열되 기본값은 0. 여는 것은 부르는 쪽의 판단이고 1~2 mm 는 형상이 아니라 잡음이라는 뜻이다
표제·개정 도장이 같은 좌표에 여러 개개정판을 지우지 않고 겹쳐 둔다(240912/241031/241115)기하는 대체로 안 겹친다(중복률 1~10%). 표제 개수로 뷰 수를 세지 말 것
+ +

8.2 좌표·뷰

+ + + + + + + + + + + + + +
증상원인대응
Z가 713mm 어긋남기준을 텍스트 위치로 잡음선에서 잡는다
Z가 420mm 어긋남계획고 선 후보가 둘(도로/토공)라벨 바로 아래 선
서로 다른 두 표고 라벨이 같은 선을 가리킨다라벨 y를 평균내는데, 같은 라벨이 도형마다 반복돼 평균이 어느 도형에도 안 속한다. 교량 계획고·토공 계획고 10개가 y 27,533~81,177에 흩어져 있었다라벨 y를 무리로 끊어보고 둘 이상이면 던진다. 예외가 아니라 그럴듯한 숫자가 나오는 실패라 대조 항목이 없으면 영영 안 잡힌다
뷰 폭이 부풀음이웃 상세도 조각이 붙어옴교대 분할 + 표제 귀속. 상세도도 표제 후보에
뷰 하단이 잘림부재 방향 라벨이 표제로 잡힘minHeight 상향
좌표가 엉뚱한 값수평 단면·평면도의 세로축을 Z로 봄v0·swapUV. 3.3 절차로 판정
면적이 음수 / 면이 뒤집힘반전·맞바꿈으로 감김 방향이 반대뒤집기 홀수면 링 역순
임계값을 10mm까지 좁혀도 조각이 1개상세 확대 테두리 상자(Δy=5,117)가 상세행과 단면행을 세로로 잇는다. 구조선과 같은 레이어·같은 색이라 못 거른다임계값 조절을 포기하고 명시 뷰 영역으로 간다. 「간격이 존재한다」는 전제 자체가 깨진 경우다
같은 제목이 N벌 — 뷰가 하나로 뭉침유형별 삽도는 사례마다 같은 제목을 붙인다 (단면A×4 · 교량 시·종점×6)제목은 뷰의 유일 키가 아니다. (제목, 위치) 쌍이거나 명시 영역이 필요하다. 표제 중복률을 먼저 센다 — 7장 전부 47~84%였다
뷰 제목 자체가 없다표제가 시트명 하나뿐이고 나머지는 A · B · L of Road 같은 기호글자 높이 하한을 어떻게 잡아도 안 나온다. 명시 영역 외에 길이 없다
도형마다 축척이 다르다모델스페이스가 1:1이라는 전제가 삽도에서 깨진다. 중앙분리대 방호벽 단면은 5.805배로 그려져 있었다배율은 도면 하나가 아니라 뷰마다 받는다. 값은 표기 치수 2개로 역산해 교차검증한다 (높이 7,372.3↔1,270 · 바닥폭 3,393.9↔585, 차이 0.06%)
+ +

8.3 프로파일

+ + + + + + + + + + + + + + + +
증상원인대응
부피가 정확히 2배개구 기호(X 표시)가 링을 자기교차시킴기호를 기하 서명(같은 bbox·반대 기울기 대각선 쌍)으로 걷어낸다. 레이어로는 못 거른다
사람 눈엔 멀쩡한데 안 잡힘모서리가 몇 mm 안 닫혀 있다bbox → rect() 재구성 + expect()
재료가 한 덩어리로 뭉침경계선 레이어를 추출에서 뺐다면이 갈리려면 경계선이 있어야 한다 — 함께 넣고 배정은 나중에
폐곡선이 원 윤곽을 따라 돔원을 다각형으로 근사해 선분에 섞음원은 따로 모아 지름으로 집는다
X자 교차가 안 쪼개짐평면 분할이 T자 교차만 처리기호는 사전 제거. 남으면 X자 분할 추가 필요 부분
끊김이 0인데 폐곡선도 0같은 직선 위에서 겹치는 두 선(끝점은 다르다). 중복 제거는 T자 분할 전에만 돌아서, 분할이 겹친 구간을 양쪽에서 똑같이 잘라 같은 변을 두 벌 만든다 → 반변이 4개가 되어 면 순회가 엉킨다T자 분할 뒤에 한 번 더 중복 제거. 증상이 "닫혔는데 안 나온다"라 끊김 진단으로는 절대 안 잡힌다
폭·높이만 틀리고 면적은 맞다끝이 아무 데도 안 닿는 매달린 변이 있으면 면 순회가 거기까지 갔다 되돌아온다. 왕복이라 면적은 0 인데 bbox 는 부푼다. + 게다가 bbox 를 collinear 제거 전 링으로 재고 있었다 (상부공 폭 12,752 → 12,951)bbox 는 collinear 를 뺀 링으로 잰다. area 만 보면 절대 못 잡는다
진단 도구와 본편이 다른 값진단 도구가 projectViews 를 안 거치고 buildLoops 를 직접 불렀다 — 개구 기호 제거·앵커·평면 변환이 빠진다본편과 같은 함수를 쓴다. 다르면 도구를 못 믿게 되고, 그 순간 검증 체계 전체가 죽는다
윤곽이 진짜로 안 닫혀 있다구성 도해(폭원·배치)는 닫힌 단면을 그리지 않는다. 띠와 열린 외곽선만 있다끊김 원인 분류에서 「선 없음」 비율을 본다. 교량폭원은 56곳 중 54곳이 그랬다 — 설정으로 못 고친다. 그 도면은 솔리드 대상이 아니다
+ +

8.4 조립

+ + + + + + + + + + + + + +
증상원인대응
전 구간이 같은 단면으로 나옴횡단면 하나를 전체에 압출종단면이 구간 배정표다. 수평선을 Z 내림차순 × X 범위로 읽는다
안이 들여다보임한 평면 단면만 압출해 반대 방향 벽이 없다공동의 압출 범위를 좁혀 벽을 남긴다
구간 경계에 구멍구간별로 따로 difference몸통을 union한 뒤 한 번만 뺀다
union이 예외로 죽음떨어진 커터끼리 union 시도커터는 하나씩. union하는 건 "구간 몸통"이지 커터가 아니다
커터가 46 m³를 파먹음커터를 대상 바깥면에서 시작모든 방향으로 OVER 여유
difference가 조각을 통째로 흘림면으로만 맞닿은 솔리드를 union부피로 겹치게 만든 뒤 union. 단 압출축 방향으로만 늘린다 (통째로 부풀리면 더 나빠진다)
같은 커터 재사용 실패boolean이 피연산자를 소모조각 수만큼 커터를 따로 만든다. 안 겹치는 조각엔 아예 넣지 않는다
경사면 하나가 끝까지 올라감loft로 두 번 꺾으려 함꺾이는 자리마다 단면. 방향별로 꺾이는 좌표가 다르면 또 끊는다
loft 면이 비틀림두 단면의 시작 꼭짓점이 어긋남링 정규화(반시계 · v최소→u최소 시작) + 꼭짓점 수 일치 강제
loft가 부풂단면 3개 이상 → 스플라인 보간2단면씩. 결과 정점에 중간 행이 없으면 직선 룰드다
두 번째 도면의 build가 위상 위반으로 죽음설계 규칙이 첫 도면의 파라미터 이름에 묶여 있다. 없는 값이 undefined로 비교돼 전부 실패로 떨어진다규칙에 needs(요구 파라미터)를 붙여 해당 없으면 건너뛴다. 규칙 층은 "도면 고유값 없음"을 자처했지만 실제로는 지켜지지 않았다
+ +

8.5 백엔드(모델러)

+

백엔드 특이사항은 전부 emit/ 안에 가둔다. 아래는 EGModeler 사례지만, +다른 툴로 바꿔도 같은 성격의 함정이 그 자리에 온다.

+ + + + + + + + + + + +
증상원인대응
명령이 조용히 무시됨, created:[]뷰어 모드라 모델링 명령이 등록조차 안 됐다작업 시작 전에 등록 명령 개수 확인. 연결 정상 ≠ 모델링 가능
커터 8개가 씬에 남음boolean이 이항이다 — 셋 이상은 무시둘씩 접는다. modelCount 대조로 즉시 잡힌다
압출이 엉뚱한 방향Cap 단독은 항상 +Z로 민다항상 방향을 명시. Cap 플래그는 방향 지정 앞에
서피스가 나옴loft 기본이 서피스Cap으로 막아 솔리드. 원본 삭제 플래그는 건드리지 않는다
부피는 맞는데 boolean 이 샌다loft 결과가 안 닫힌다. Cap 을 덧대도 4각형 단면조차 열린 변 12개. 발산 정리 부피는 정확해서 부피 검사로는 절대 안 걸린다loft 를 boolean 인자로 쓰지 않는다. 단면이 평행이동만 하면 자유방향 압출(drift)로 대체 — 그건 닫힌다. → 11.4.6
이후 명령이 전부 무시선택 명령을 안 닫음Esc로 닫는다
파일이 안 생기는데 오류도 없음경로의 _가 스크립트 특수문자큰따옴표 필수 + 파일 존재·mtime을 직접 확인
씬 초기화 명령이 조용히 무시됨뷰 조작 명령을 ~ 접두로 보내 끝나지 않는 상태로 물렸다. status.command가 그 이름으로 남는다명령 상태를 매번 확인. 접두 없는 Esc로 푼다. 연결 정상 ≠ 명령 수용은 뷰어 모드 말고 여기서도 성립한다
파이프가 죽음(프로세스는 살아 있음)씬 초기화 반복초기화는 갱신당 1회. 죽으면 재시작 외에 방법 없다
+ + +

9새 도면 인테이크 워크시트

+ +

9.1 채워야 할 것 (순서대로)

+
    +
  1. 도면 적격 판정 먼저 — 표기 치수를 숫자형/기호형으로 세고, 구조 윤곽이 닫히는지 본다. 기호뿐이거나 안 닫히면 솔리드 대상이 아니다. 여기서 걸러야 나머지 아홉 항목을 헛되이 채우지 않는다
  2. +
  3. 모델단위 — DIMENSION.actualMeasurement ↔ 치수 글자를 비교해 unitMm 확정. 도면군마다 다르다
  4. +
  5. 구조물 종류·좌표계 선언 — X·Y·Z 방향과 원점을 문장으로
  6. +
  7. 시트 영역 — 전체 좌표 분포를 히스토그램으로 보고 유령 기하를 가른다
  8. +
  9. 레이어 4갈래 — struct(넓게) / body(좁게) / material / ground. body ⊆ struct. + 레이어가 구조선과 치수선을 안 가르면 색·선종으로 거른다(entityFilter)
  10. +
  11. 표제 글자 높이 분포 — 시트명 / 뷰 제목 / 부재 라벨의 h를 세어 minHeight 결정. + 표제 중복률도 같이 센다 — 높으면 제목으로 뷰를 못 가른다
  12. +
  13. 뷰 목록 — 제목 → {plane, flip, role}. 단면은 절단 기호 좌표로 평면 판정. + 제목이 없거나 중복이면 viewBoxes로 좌표를 짚는다
  14. +
  15. 기준점 — 라벨 정규식 + 도면 치수 검증 2개 이상. 기준선이 없으면 mode:'none'으로 명시하고 검증을 G5로 미룬다
  16. +
  17. 앵커 — 뷰별 u0/v0/flip/flipV/swapUV/unitMm. 비대칭 특징으로 교차검증. + 축척이 다르면 표기 치수 2개로 역산해 교차검증한다
  18. +
  19. 구간 배정표 — 종방향 뷰에서 X 범위 × 단면 대응
  20. +
  21. 조립 스펙 — 5장의 결정 트리와 5원칙
  22. +
  23. 규칙 — 이 부재 종류의 위상 규칙 · 설계 기준
  24. +
+ +

9.2 설정 파일 스켈레톤

+
export default {
+  name: '<도면이름>',
+  dwg: path.join(DRAWINGS_SRC, '<파일>.dwg'),
+
+  // 이 밖의 좌표는 버린다 (유령 기하 · 블록 로컬 좌표)
+  sheet: { x0: …, x1: …, y0: …, y1: … },
+
+  // DWG 1단위가 몇 mm인가. **도면군마다 다르다** (맨홀군 1000, 교량 삽도군 1).
+  // DIMENSION.actualMeasurement 와 치수 글자를 비교해 확정한다.
+  unitMm: 1000,
+
+  // 레이어로 못 가르는 것을 거른다. colorIndex 0 = ByBlock = 치수 블록 안.
+  // 구조선과 치수선이 같은 레이어인 도면에서만 쓴다.
+  entityFilter: (e) => (e.entityHeader?.colorIndex ?? -1) === 7,
+
+  layers: {
+    struct:   /…/,   // 뷰 분리용. 넉넉하게 — 가구·주기가 들어가도 된다
+    body:     /…/,   // 프로파일용. 좁게 — 가구가 섞이면 링이 오염된다
+    material: /…/,   // 기초·잡석 등 별개 재료. 추출엔 함께 넣는다
+    ground:   /…/,   // 기준선(계획고·GL·FL) 검출용
+  },
+
+  titles: {
+    minHeight: 0.32,             // 부재 라벨과 뷰 제목을 가르는 글자 높이
+    views: {
+      '평 면 도':  'plan',        // → XY
+      '단 면 A-A': 'long',        // → XZ  (절단 기호 두 점의 y가 같다)
+      '단 면 B-B': 'cross',       // → YZ  (두 점의 x가 같다)
+      '단 면 H-H': 'horizontal',  // → XY  (다른 단면 위에 찍힌 수평 절단)
+      // 입면도·측면도를 넣을 때: 평면은 XZ/YZ지만 role은 validator다.
+      // 절단면이 아니므로 여기서 프로파일을 뽑아 압출하면 안 된다.
+    },
+  },
+
+  segment: { gap: 0.2, xTol: 0.5, ySlack: 0.2 },   // 단위는 DWG 단위다 (unitMm 에 맞춘다)
+
+  // 제목이 없거나 중복이면 **좌표로 짚는다**. titles.views 와 둘 중 하나만 쓴다.
+  // y 값은 눈대중이 아니라 구조선 좌표 뭉치의 경계에서 뽑고, 상자는 그 빈틈 안에 둔다.
+  viewBoxes: {
+    '<뷰>': { box: [x0, y0, x1, y1], kind: 'cross',
+              unitMm: 1 / 5.805 },   // 도형마다 축척이 다르면 여기서 준다
+  },
+
+  datum: {
+    mode: 'none',                // 기준선이 도면에 없을 때. 표고는 뷰별 v0 가 준다
+    levelLabel: /…/,             // 기준선 후보를 좁히는 힌트. 값은 선에서
+    checks: [                    // 최소 2개. 도면에 적힌 치수 그대로
+      ['<설명>', (v) => v['<뷰>']?.layerZ['<레이어>']?.[1], -0000],
+    ],
+  },
+
+  frame:  { x: '…', y: '…', z: '…', origin: '…' },
+
+  anchors: {                     // 유도하고 교차검증한다. 지어내지 않는다
+    '평 면 도':  { u0: …, v0: … },        // 평면도도 v0가 필요하다
+    '단 면 B-B': { u0: … },                // 수직 단면은 v가 이미 표고
+    '단 면 H-H': { u0: …, v0: …, swapUV: true },
+  },
+
+  expectExtents: { '<뷰>': [폭, 높이] },   // 분리 자동 확인
+
+  fit({ loop, circles, opening, expect }) {
+    // 도면을 보는 **유일한** 통로. 여기서 params.json이 나온다
+    expect('<뷰>', 폭, 높이, '왜 이게 있어야 하는가');
+    return { params: { … }, rings: { … } };
+  },
+
+  assemble({ p, ring, profile, part, rect }) {
+    // 도면 조회 도우미를 주지 않는다 — 주면 다시 도면에 묶인다
+  },
+};
+ +
+ 현재 구조의 알려진 거친 부분 + 뷰 하나의 서술이 titles.views(평면)와 anchors(앵커) 두 곳에 흩어져 있다. + 3장의 모델대로 { plane, u0, v0, flip, flipV, swapUV, role } 한 덩어리로 합치는 것이 맞다. + 뷰 종류를 늘리는 작업(입면도·측면도)을 시작하기 전에 정리하는 편이 싸다. +
+ + +

10도면 작성자에게 요청할 것

+ +

효과 대비 비용이 큰 순서. 1번 하나로 검출 로직 전체가 사라진다.

+ + + + + + + + + + +
요청없앨 수 있는 것비용
레이어 3D-ORIGIN에 뷰마다 기준점 POINT 하나기준점 검출 · 앵커 유도 · 텍스트/선 혼동 사고 전부뷰당 클릭 한 번
구조체 외곽선 레이어 통일레이어 오분류 진단(끊김 50곳 중 20곳이 이것이었다)낮음
개구 기호를 별도 레이어로자기교차 링 · bbox 재구성 우회낮음
가구(케이블랙·철근)를 구조 레이어에서 분리body/struct 이중 정의낮음
시트 영역 밖 기하 정리유령 기하 필터낮음
뷰 제목 표기·글자 높이 규약표제 임계값 튜닝낮음
단면 절단 기호를 모든 단면에 표기평면 판정을 제목 추측에 의존하는 것낮음
+ +
도면을 뷰별 파일로 쪼개달라고 하지는 말 것. 표고 정합과 절단기호 매핑을 같이 잃는다(3.4).
+ + +

11구조물 유형별 차이

+ +

1~4장(정렬 · 뷰 · 게이트)과 7장(검증)은 구조물이 뭐든 안 바뀐다. +바뀌는 것은 좌표 기준과 쓰는 연산이다.

+ + + + + + + + +
선형 토목
전력구·터널·도로
교량점형 토목
맨홀·수조·기초
건축
좌표 기준중심선 + 계획고선형(측점) + 받침
둘로 나뉜다
구조물 외면 + GL그리드 통심선 + FL
주 연산extrude · loftsweep · loft · 프리미티브 · revolveextrude · 프리미티브extrude (층별)
구간 배정 뷰종단면측면도(일반도)없음 — 단면이 하나층 평면도
반복 단위구간경간 · 거더 · 말뚝—층 · 스팬
상태검증부분검증가설
+ +

11.0 실증 결과 — 교량 도면 7장에서 실제로 일어난 일 검증

+ +
+ 먼저: 대상이 예상과 달랐다 + 받은 7장은 교량 일반도가 아니라 설계지침 삽도였다 — 번호가 붙은 유형 도해 + (03 교량시종점 · 04 교량폭원 · 05 중앙분리대 · 09 거더배치 · 10 횡단경사 · 13 교대배치). + 한 구조물을 여러 방향에서 본 좌표가 물린 도면 세트가 아니라, 서로 다른 개념을 + 각각 N벌 변형으로 그린 낱장들이다. 아래 결론은 그 조건에서 나온 것이고, + 실제 교량 일반도(측면도+평면도+횡단면이 물린 것)에는 11.1~11.3이 그대로 남아 있다. +
+ + + + + + + + + + +
도면엔티티숫자 치수기호 치수ARC막힌 곳
거더배치1,433262249 (17%)G1 — 치수가 L·β 뿐
교대배치2,1385137181 (8%)G1 — θ 가변 도해
교량시종점4,43206492 (11%)관통 — 2지간 교량 95파트 820.36 m³ (11.0.4). 「치수 0개 → 대상 아님」은 오판이었다
교량폭원2,824151100관통 — 상부공 7파트 180.36 m³ (11.0.3). 처음엔 G5에서 죽었다 — 폐곡선 버그를 고치자 나왔다
교면포장1,3041420G1 — 층상 구성표
중앙분리대2,15910870관통 — 방호벽 4.8058 m³ (11.0.1)
횡단경사5,63515613964 (1%)G1 — S% · TL 가변
+ +
+ 가장 값싼 사전 판정 — 숫자 치수와 기호 치수를 세어본다 + 방법론 전체가 「도면 표기 치수와 대조한다」에 걸려 있다(G2·G3·7.3). + 치수가 기호(L · β · θ · VAR · n@3,600 · S%)면 대조할 상대가 없다. + 파싱 직후 TEXT/MTEXT를 숫자형/기호형으로 나눠 세는 것만으로 + 「이 도면은 솔리드 대상이 아니다」가 몇 초에 판정된다. 위 표가 그 결과다. +
이 판정은 게이트 이전에 온다 — G0.5쯤이다. 방법론에 없던 단계다. +
+ +

11.0.1 통과한 것 — 중앙분리대 방호벽

+ +

7장 중 닫힌 구조 단면을 가진 유일한 도면이었다. 세운 것은 +토공용 구간 → 변화구간(2m) → 교량용 구간의 방호벽 한 줄이다.

+ +
단면A (토공용, 585×1319)  ──extrude X 0→5,000──┐
+                                               ├─ loft 5,000→7,000  ← 변화구간
+단면B (교량용, 584×1270)  ──extrude 7,000→12,000┘
+                    union → 중앙분리대_방호벽
+
+부피  모델러 4.8058 m³   IR 격자(25mm) 4.7970   해석 프리즈모이드 4.8058
+modelCount 1 = 최종 파트 수 1
+ +
    +
  • 축척을 되찾아야 했다. 이 단면은 5.805배로 그려져 있다. 표기 치수 2개(높이 1,270 · 바닥폭 585)로 역산해 교차검증했다 — 차이 0.06%
  • +
  • 두 단면의 기준면 y가 달랐다. 포장면 48,812.1 vs 바닥판면 49,096.5. 그 차이가 곧 「토공용은 포장에 49mm 묻힌다」는 형상이었다
  • +
  • difference가 하나도 없는 첫 모델. 방호벽은 속이 찬 콘크리트다. 뺄 게 없으니 boolean 함정(8.4)이 통째로 안 나왔다 — 즉 8.4는 아직 교량에서 검증되지 않았다
  • +
  • loft 하나로 끝났다. 변화구간이 실제로 직선 전이(꺾이는 자리 없음)였기 때문이다. 교훈 #16(꺾이면 끊는다)이 여기서는 해당 없었다
  • +
+ +

11.0.3 교량 상부공 — 폐곡선 버그를 고치자 나왔다

+ +

교량폭원은 처음에 G5에서 죽었다 — 끊김 56곳 중 54곳이 「도면에 선 없음」이었다. +그런데 겹치는 동일선상 선분 버그(8.3)를 고치고 다시 돌리자 폐곡선이 새로 나왔다. +「도면이 안 닫는다」의 상당 부분이 실은 우리가 못 닫은 것이었다. +판정을 내리기 전에 도구부터 의심할 것.

+ +
폭원 띠 상    24,300 × 240   바닥판 전폭   ↔ 도면 표기 24,300
+분리교량      12,300 × 240 · 15,900 × 240   좌·우 상판
+                 440 × 1,320 ×3            방호울타리
+              11,400 ×  50   마모층        ↔ 도면 표기 11,400 (도로 폭)
+ + + + + + +
값이 도면에서다른 도면에서
바닥판 두께폐곡선 240교면포장 삽도 「콘크리트 교량 바닥판 240mm」
마모층 두께폐곡선 50교면포장 삽도 「마모층 50mm」
방호울타리 높이폐곡선 1,320 (포장면 위 1,270)중앙분리대 삽도 표기 1,270
+ +

서로 다른 삽도가 같은 값을 준다. 유형 삽도 묶음은 좌표가 물려 있지 않아 +뷰 사이 교차검증이 안 된다고 봤는데(11.0), 치수 값 수준에서는 물려 있다. +공통 datum이 없는 도면군에서 쓸 수 있는 교차검증 경로가 이것이다.

+ +

세운 것: 좌·우 상판 · 마모층 · 방호울타리 4기를 경간 20m(설계값)로 직선 압출. +7파트 180.356 m³, 의도 대비 전부 0.0000. +넷째 방호울타리는 도면 윤곽이 안 닫혀 재구성했다 — 들임(10mm) 규칙을 +측정된 두 부재로 확인한 뒤 적용했고, 그렇게 놓은 외면 좌표가 gaps가 +짚은 끊김 좌표와 같은 값으로 떨어지는 것이 검증이다. 등급은 tolerant — +도면 좌표를 옮긴 게 아니라 만든 것이므로 opaque가 아니다.

+ +

11.0.4 교량 한 기 — 「표기 치수 0개」를 잘못 잘랐다

+ +
+ 이 문서가 두 번 틀렸다 + ① 11.0 표에서 교량시종점을 「숫자 치수 0개 → 솔리드 대상 아님」으로 잘랐다. + 표기 치수가 없으면 검증을 못 하는 것이지 추출을 못 하는 게 아니다. + 모델스페이스가 1:1 이면 좌표가 곧 mm 다. +
② 「대조할 상대가 없다」도 틀렸다 — 그 도면 안에 없다는 뜻일 뿐이고, + 다른 도면과 표준 부재 치수는 여전히 상대가 된다. +
+ +

1:1 인지는 크기를 아는 표준 부재로 확정했다. 도면 밖에서 온 눈금이다.

+ +
폐곡선 153×182 이 4개    ← 탄성받침 150×180. 다른 게 이 치수로 4개 나란할 이유가 없다
+교대 벽체 4,680×6,506     교량 교대 자릿수
+상부공 형고 1,421         지간 12,752 에 L/h≈9 — 반일체식으로 타당
+접속슬래브 두께 383        표준 300~400
+
+넷이 **동시에** 타당해지는 배율은 1 뿐이다. 10배만 달라도 받침이 1.5 m 나 15 mm 가 된다.
+ +

그 도면 하나로 2지간 교량 한 기가 열렸다 — 이 도면군에서 하부공이 +부재로 그려진 유일한 도면이다.

+ +
바닥판 26,178×277 · 거더 12,303/12,748×1,144 ×5본씩
+교대(벽체+확대기초) 4,680×7,953 ×2 · 날개벽 4,563×6,932 ×4
+교각 두부 2,244×1,334 · 기둥 1,586×3,904 · 확대기초 5,081×1,334
+교좌받침 153×182 ×8열 · 받침콘크리트 ×3 · 접속슬래브 3,705×383 ×2
+말뚝 Ø518 ×10위치 (연직이라 IR 의 Z축 cylinder 가 그대로 맞았다)
+
+95파트 · 820.36 m³ · 의도 대비 전부 0.0000
+ +
+ 뷰 상자를 두 번 잘못 잡았다 — 숫자로는 안 걸렸다 + 처음 상자가 날짜 다른 두 작업본의 경계에 걸쳐 있었다. 한 시트에 + TYPE1~4 한 벌(주황 테두리)과 「교량 시·종점 …전면」 한 벌(자홍)이 서로 어긋나게 + 포개져 있다. 뽑힌 것은 두 도형의 혼합이었고 — 교대라고 부른 것이 실은 + 옆 도형의 날개벽이었으며 기초·교각이 통째로 빠졌다. +
폐곡선 25개가 나왔고 치수도 그럴듯했다. 검증표는 전부 통과했다. + 그림을 보고서야 알았다. 4.2(캡처)가 선택이 아니라 필수인 이유다. +
+ +

11.0.5 도면이 정보를 주는 방식 — 형상만이 아니다

+ + + + + + + +
부재도면이 주는 형태읽는 법현재
구체·교대·교각닫힌 윤곽폐곡선있다
관통구·기둥CIRCLE지름으로 집는다있다
개구부X 기호기하 서명으로 되살린다있다
말뚝해치 + 중심선중심선 x = 위치, 윤곽 두 줄 간격 = 지름없다 — pick 이 선을 못 집는다
철근·수량주기 텍스트—없다
+

형상이 없다고 정보가 없는 게 아니다. 개구 기호를 되살린 것과 같은 종류의 +이득이 중심선·해치에 남아 있다. circles() 를 만든 것과 같은 이유로 +centerlines() 가 필요하다.

+ +

11.0.2 범위에서 뺀 것 — 명시

+ + + + + + + +
뺀 것왜
상부공(거더·바닥판)·하부공(교대·교각·말뚝)표기 치수가 있는 도면이 없다. 교량시종점은 측면도가 6벌 있지만 숫자 치수가 0개다
교량 바닥판 횡단면(교량폭원)폭원 구성 도해라 닫힌 단면을 안 그린다. 끊김 54곳이 「도면에 선 없음」 — 설정으로 못 고친다
방호벽 설치 연장도면이 「중앙분리대 설치 범위」로만 표시한다. 각 5,000mm를 설계값으로 명시했다 — 도면에서 읽은 척하지 않는다
변화구간 2,000의 기하 교차검증평면도 쪽 표기 치수가 하나뿐이라 그쪽 축척을 독립으로 되찾을 수 없었다. 출처를 「도면 주기 글자」로 남겼다
ARC · SPLINE모델링한 도면에는 0개였다(운이 좋았다). 버린 도면들에는 8~17%가 있다 — 11.2 참고
+ +

11.1 교량 — 기준이 하나가 아니라 체인이다 가설

+ +
+ 이 절은 아직 실증되지 않았다 — 확인할 도면이 없었다 + 받은 7장 중 하부공을 받침 중심 좌표 + 받침 표고로 기술한 도면은 없었다. + 교량시종점 삽도가 교대·교각·말뚝을 측면으로 그리긴 하지만 표기 치수가 0개라 + 기준 체인이 실제로 그렇게 기술되는지 확인할 수 없었다. 아래는 그대로 가설이다. +
+ +

전력구는 프로젝트 원점 하나로 전부 기술된다. 교량은 안 된다. +상부공은 선형을 타고(곡선·편경사로 기울어짐) 하부공은 연직으로 선다. +두 좌표계가 다르므로 잇는 지점이 필요하고, 그 지점이 교좌받침이다.

+ +
프로젝트 원점
+  └─ 선형 (측점 s · 오프셋 t · 표고)          ← 상부공은 전부 여기서 나온다
+       └─ 받침 (측점 + 오프셋 + 받침표고)      ← 두 좌표계를 잇는 유일한 접점
+            └─ 교각 로컬 (연직 · 사각)         ← 하부공은 전부 여기서 나온다
+                 └─ 기초 · 말뚝 배치 격자
+ +

도면이 이미 이렇게 기술한다 — 하부공 도면은 받침 중심 좌표와 받침 표고로 교각을 +잡고, 거기서 아래로 교각·확대기초·말뚝을 내린다. 그래서 받침을 앵커로 삼는 것이 +도면을 그대로 옮기는 것이 된다. 받침을 안 쓰고 절대좌표로 잡으면 사각(skew)·편경사가 +들어오는 순간 값이 전부 손계산이 된다.

+ +
+ 일반화 — 앵커는 뷰에만 붙는 게 아니다 + 3장의 앵커는 뷰를 3D에 정렬하는 장치였다. 교량에서는 같은 개념이 + 부재군에도 필요하다. 상부공군 · 교각군 · 기초군이 각자 로컬 프레임을 갖고, + 받침이 그 사이의 변환을 준다. 이 개념은 현재 방법론에 없다. +
+ +

11.2 교량에 필요한 확장

+ + + + + + + + + + + + + +
항목왜현재실증
측점 기반 sweep평면곡선 · 종단곡선 · 편경사. 직선축 from/to로는 표현 불가없음. IFC4.3 IfcSectionedSolidHorizontal과 의미가 같다미확인 — 곡선 선형을 준 도면이 없었다. 삽도는 전부 직선 구간 도해다
revolve원형 교각 두부 · 정착부없음불필요했다 — 교각은 사각 + 벌어진 두부다. 세웠다(11.0.4)
임의축 cylinder경사 말뚝 · 사교 교각Z축 한정불필요했다 — 말뚝이 전부 연직이라 Z축 cylinder 가 그대로 맞았다. Ø518 ×10위치를 세웠다
부재군 로컬 프레임상부(선형) ↔ 하부(연직) 좌표계 전환없음 — 단일 좌표계 전제미확인
ARC · SPLINE 파싱곡선 거더 · 원형 단면버린다비중 측정됨 — 거더배치 17% · 교량시종점 11% · 교대배치 8% · 횡단경사 1%. 모델링한 도면(중앙분리대)은 0%
배열 파라미터경간 N · 거더 M본 · 말뚝 배치있다 — 6장 배열이 그대로 맞는다미확인
뷰별 축척 신규삽도는 도형마다 배율이 다르다추가됨 — viewBoxes[뷰].unitMm검증 — 5.805배 사례
명시 뷰 영역 신규제목이 없거나 중복인 도면추가됨 — cfg.viewBoxes검증
레이어 아닌 필터 신규구조선과 치수선이 같은 레이어추가됨 — cfg.entityFilter(색·선종)검증
복수 선택기 신규같은 부재가 여러 벌인 것이 정상 (거더·받침·말뚝)추가됨 — loops(뷰, w, h, tol)검증
중심선 선택기 신규말뚝처럼 해치 + 중심선으로만 그려지는 부재없다 — 스크립트로 재서 설정에 적었다구멍
+ +
+ 예측 성적 — 11.2가 맞춘 것과 빗나간 것 + 맞음: ARC·SPLINE 비중이 실제로 컸다(최대 17%). 배열 파라미터가 필요한 구조(거더 M본·말뚝 배치)가 실재했다. +
빗나감: revolve와 임의축 cylinder는 필요 없었다. 원형 교각도 경사 말뚝도 7장 어디에도 없다. + 「교량이면 revolve가 필요하다」는 교량 일반론이지 이 도면군의 사실이 아니었다. +
못 예측한 것: 실제로 막은 것은 위 목록에 하나도 없었다 — + 단위(1mm vs 1m) · 뷰 제목 부재 · 뷰별 축척 · 레이어가 치수선을 안 가름 · 폐곡선 중복 변 버그. + 전부 연산이 아니라 입력 해석 층의 문제다. 11.2는 「무슨 연산이 더 필요한가」만 물었는데, + 실제 병목은 「도면을 읽어들이는 가정」이었다. +
+ +

11.3 교량에서는 측면도의 지위가 다르다 부분

+

3.2에서 입면도·측면도는 검증(validator) 역할로 못박았다. 교량에서도 +솔리드를 만들면 안 되는 것은 같지만, 교량 일반도의 측면도는 +구간 배정표(assigner)를 겸한다 — 경간 분할 · 교각 위치 · 형고 변화 · 받침 표고가 +전부 거기 있다. 전력구에서 종단면이 하던 일을 교량에서는 측면도가 한다.

+ +
+ 확인된 것과 확인 못 한 것 + 확인: 교량시종점 삽도의 측면도 6벌에 경간 분할(지간) · 신축유간 · + 접속슬래브 · 말뚝 배치가 전부 있었다. 측면도가 배정 정보를 든다는 주장은 형태상 맞다. +
확인 못 함: 그 측면도에 숫자 치수가 하나도 없다(기호 라벨뿐). + 배정표로 쓸 수 있는지는 표기 치수가 있는 실제 일반도가 있어야 판정된다. +
새로 드러난 것: 삽도에서는 측면도가 세 번째 역할을 한다 — + 유형 카탈로그(TYPE1 반일체식 · TYPE2 단부슬래브 · TYPE3 일체식 · TYPE4 골조식). + 같은 제목이 6벌 반복되는 원인이 이것이고, 그래서 제목 기반 뷰 분리가 깨졌다(3.4). +
+ +

11.4 삽도(유형 도해)는 별도의 구조물 유형이다 검증

+

일반도와 삽도는 같은 파이프라인으로 다룰 수 없다. 표로 갈라둔다.

+ + + + + + + + + +
일반도 전력구 4매유형 삽도 교량 7장
치수숫자. 대조 가능기호(L·β·θ·VAR)가 섞인다. 도면마다 비율을 세어야 한다
뷰 제목유일 (단 면 B-B)없거나 N벌 중복
축척모델스페이스 1:1도형마다 다르다. 표기 치수 2개로 역산
표고 기준전 뷰 공통 계획고선없다. 뷰별 v0
윤곽닫힌 재료 영역구성 도해는 안 닫는다(띠·열린 외곽선)
레이어구조/치수/지반이 갈림표준 레이어 하나에 구조선과 치수선이 같이 올라간다
솔리드 대상전부일부만 — 7장 중 1장
+ +

11.4 실증 결과 — 교각 P9 한 부재를 끝까지 검증

+ +
+ 이번엔 진짜 일반도였다 + 산외교_파이형_교각일반도(울산P9).dwg — 평면도 · 정면도 · 단면도 · 단면 A-A · + 기초 평면도 · 교량받침 상세 · 솔플레이트 상세 + 표 3개가 좌표가 물린 채 한 도곽에 있다. + 11장이 「교량 일반도에는 11.1~11.3이 그대로 남아 있다」고 유보해둔 그 조건이다. + 부재 하나(교각)를 코핑→기둥→기초→말뚝→받침→문양 순으로 끝까지 밀었다. +
+ +
파트 77 · 660.4154 m³ · 모델러 실측 대비 불일치 0건 · 열린 변 0
+  코핑 1(intersect) · 원형기둥 2 · 확대기초 2 · 버림 2 · 강관말뚝 44
+  받침콘크리트 6 · 모르타르 6 · 교좌받침 6 · 솔플레이트 6 · 미관문양 2
+
+구체 콘크리트 fck=27MPa   359.3411      미관 문양            3.3585
+기초 콘크리트 fck=27MPa   177.4500      받침콘크리트          1.8438
+강관말뚝 Φ508.0×9T       108.7954      탄성받침             0.3469
+버림 CON'C fck=15MPa       8.9780      무수축 모르타르        0.1555
+                                       솔플레이트            0.1299
+ +

11.4.1 방법론 판정 — 「해봤다」가 아니라 참/거짓으로

+ + + + + + + + + + + + + + + +
방법론 주장교각에서근거
2.2 표고 기준은 전 뷰 공통거짓 → 그런데 되찾을 수 있다공통 기준선이 없어 datum.mode:'none' 으로 시작했다. 그런데 받침 6기의 표 + EL.'X' = 코핑상면 + h1 + h2 가 6기 모두 같은 절대표고 79.447을 낸다. + 기준선이 없어도 표가 기준을 준다
3.4 제목으로 뷰를 가른다참이나 안 썼다제목이 있다(CZ-TEX1 h=700). 그런데 뷰끼리 치수선으로 이어져 있어 + 제목 그룹핑이 안 갈린다. 도곽 안 좌표(viewBoxes)로 짚었다
5.2 연산 결정 트리부족했다intersect 가 트리에 없었다. 코핑은 「평면 윤곽 ∩ 정면 윤곽」이라 + 어느 한 방향 압출로도 안 나온다. 추가함
11.1 기준 체인 (받침 → 교각 로컬)참 · 처음 검증받침배치도 「교량받침 좌표」의 절대 측량좌표 + 방위각으로 로컬 원점을 못 박았다. + 역산 오차 원점 2 mm · 끝단 37 mm. IFC IfcMapConversion 으로 나간다
11.2 revolve 필요불필요기둥이 Ø2,200 연직 원기둥이라 cylinder 로 그대로 선다. + 회색 보조선(ci=8) 8줄이 원기둥 모선이라 각기둥이 아님을 재확인해준다
11.2 임의축 cylinder해당없음경사말뚝이 없다. 44본 전부 연직
11.0.5 「형상이 없어도 정보는 있다」참 · 메웠다말뚝 44본이 CSPILEH INSERT 블록이다. 블록이 위치와 치수를 둘 다 준다 — + scale 254 = 반지름, 도면 주기 Φ508.0X9T 와 일치. blocks() 를 만들었다
+ +

11.4.2 도면이 정보를 주는 방식 — 표가 추가됐다

+ + + + + + + + +
부재도면이 주는 형태읽는 법현재
코핑·기둥·기초닫힌 윤곽폐곡선있다
기둥 단면CIRCLE지름으로 집는다있다
말뚝INSERT 블록삽입점 = 위치, 배율 = 치수있다 — blocks()
받침 적층·표고표이름표로 표를 집고 열 이름으로 값을있다 — tables
미관 문양다른 색의 윤곽뷰별 entityFilter있다
기둥 높이치수 텍스트 (형상은 파단)—없다 — 텍스트 셀렉터
+ +
+ 표는 형상만큼 중요한 입력이다 검증 + 받침 6기의 적층고(h1~h4) · 절대표고(EL.'X') · 교축 위치(S1/S2) · 솔플레이트(L·W·T·T1·T2) + 는 전부 표에만 있다. 정면도에 그려진 적층은 대표 하나고 기당 값이 다르다 + (h1 = 168/195/197). +
그래서 run.mjs 가 도면을 읽을 때 (이름표, 표) 쌍으로 색인해 + out/<도면>.tables.json 에 얼려두고, fit() 이 pick.tables 로 + 열 이름으로 꺼낸다. 없는 표·없는 열이면 던진다. +
+ +

11.4.3 세 갈래가 서로를 검산한다

+

형상 · 표 · 다른 도면이 같은 값을 서로 다른 길로 낸다. fit() 이 매번 확인하고 어긋나면 던진다.

+ + + + + + + + + + +
대조결과
표 S1/S2 → 교축 위치 ↔ 평면도 폐곡선 중심6/6 ≤3 mm
표 h4 ↔ 솔플레이트 표 T6/6 완전일치
h1+h2+h3+h4 ↔ 표 E6/6
S1+S2 ↔ 코핑 교축폭 3,2006/6
솔플 T ↔ (T1+T2)/26/6
솔플 경사쪽 판정: T2−T1=16 ↔ 전/후열 총고차 29 ÷ 1,600 × L 840 = 15.2가정 없이 유도됐다
파단 생략량: 표고 텍스트 · 치수 8.816/10.916 · 기둥 도형길이 6,100세 갈래 모두 27,700
단위: 도곽 A1×100 · 블록 scale 254 ↔ Φ508 · 격자 1,300 ↔ 4@1.300 · 받침배치도 A1×300넷 다 1단위 = 1 mm
+ +

11.4.4 파단선 — 이 도면 유형의 최대 함정 검증

+
+ 그려진 길이가 실제가 아니다. 그리고 숫자로는 안 걸린다 + 정면도의 기둥은 6,100 으로 그려져 있는데 실제는 33,800 이다. + 폐곡선도 멀쩡히 나오고 부피도 그럴듯하게 나온다 — 24 m 를 잃은 채로. +
신호는 CS-CUTL 레이어다. 가로 파단선(정면도·단면도 같은 y)과 + 말뚝마다의 파단 원호(R=655.6 ×45)가 거기 있다. +
대응 — 생략량을 손으로 적지 않는다. + 파단_생략량 = 기둥_높이 − 기둥_도형길이 로 derived 가 된다. + (기둥_높이는 치수 텍스트라 아직 design 이다) +
+ +

11.4.5 코어에 새로 들어간 것

+ + + + + + + + + + +
무엇왜 필요했나일반성
intersect 연산코핑 = 평면 윤곽 ∩ 정면 윤곽. 한 방향 압출로는 안 나온다「두 방향이 동시에 맞아야 하는 형상」 전부
extrude.drift (빗각 기둥)단면이 평행이동만 하는 전이부. Loft 를 못 쓰기 때문테이퍼 코핑·확폭부
blocks() 셀렉터말뚝이 INSERT 블록. 배율이 곧 치수다블록으로만 그린 부재 전부
표 추출 (core/table.mjs)받침 적층·표고가 표에만 있다모든 DWG. 격자·이름표·병합셀 자동
뷰별 entityFilter미관 문양이 본체와 다른 색이다「뷰마다 무엇이 구조선인가가 다르다」
dropCollinear 중복점 제거매달린 변 왕복이 길이 0 변을 남긴다전 도면
교집합 해석 부피격자로는 measure --ir 합격선 0.05 m³ 를 못 넘긴다압출∩압출 · 종단∩평면(높이 가변)
emit/ifc.mjsIFC 출력이 없었다IFC4 · 해석 솔리드 우선
+ +

11.4.6 Loft 는 boolean 에 못 쓴다 — 가장 비싼 발견 검증

+
+ 부피는 맞는데 껍질이 안 붙는다 + EGModeler 의 Loft 는 가이드상 서피스만 만든다. C=True 는 조용히 + 무시되고 Cap 을 덧대도 안 닫힌다 — 4각형 단면조차 열린 변 12개다. +
발산 정리로 재는 부피는 정확히 맞으므로 부피 검사만으로는 절대 안 걸린다. + 그런데 boolean 인자로 넣으면 샌다 — 코핑이 102.38 → 98.65 가 됐다. +
대안 — Extrude C=True D F {기준점} {방향점} 는 자유방향을 받고 + 결과가 닫힌 솔리드다(정점 8·면 12·열린 변 0 확인). 단면이 평행이동만 하면 + loft 대신 이것을 쓴다. +
기존 모델 재검증 필요 — manhole1 이 loft 결과를 union·difference 인자로 쓴다. +
+ +

11.4.7 새로 밟은 함정

+ + + + + + + + + +
증상원인대응
한 파일에 도형이 17벌 쌓여 있다도면집 스택. 제목·치수가 없는 잔재도곽 블록에서 상자를 계산한다 — CXBLKA1 INSERT 위치 × scale × A1(841×594)
「단면도」 안쪽 선을 다른 부재로 읽었다절단면이 아니라 투영이다. 먼 쪽 모서리가 겹쳐 보인다그 선의 치수가 외곽 − 2×R 로 떨어지면 투영이다 (3,200−2×500=2,200)
평면 모서리가 45° 모따기로 나온다파서가 LWPOLYLINE 의 bulges 를 버린다. 90° 호(bulge 0.41421)가 현 하나로 눌린다호 끝점은 정확하므로 R 은 되찾을 수 있다. 링 면적을 바로 쓰면 코너당 71,350 mm² 부푼다
평면 윤곽이 두 줄인데 하나만 썼다바깥 윤곽(Y±7,000)과 상면 단차선(Y±6,700)이 둘 다 R=500 필렛두 호의 중심 X 가 같으면 그 사이는 평면 띠다 → loft/빗각기둥
적분값이 0.2 m³ 샜다구간 끝점이 링 꼭짓점이라 스캔라인 교차 수가 어긋난다내부 노드 구적(가우스)을 쓴다. 심프슨은 끝점을 밟는다
구조 형상이 표로 잡힌다평면도 격자가 73행×83열짜리 「표」가 된다표 색인에 레이어 필터(CS-TABL-*)를 준다
DIMENSION.actualMeasurement 로 단위를 정하면 틀린다주요 치수는 m 표기(8.816), 상세는 mm 표기(950). 표시값이 온다도곽 블록 배율·블록 scale·격자 간격으로 교차검증한다
+ +

11.4.8 IFC 출력

+
node src/emit/ifc.mjs out/pier-p9.ir.json   →  out/pier-p9.ifc  (IFC4, 170 KB)
+
+IFCBEAM 1(코핑) · IFCCOLUMN 2 · IFCFOOTING 4 · IFCPILE 44
+IFCPLATE 6(솔플레이트) · IFCDISCRETEACCESSORY 6(받침) · IFCBUILDINGELEMENTPROXY 14
+

해석 솔리드를 우선한다. 원기둥은 IfcCircleProfileDef 로 나가 진짜 원이다 — +메시로 내면 검증하는 쪽이 「왜 Ø508 이 아니냐」를 다시 겪는다. boolean 결과(코핑·문양 2)만 +이미 검증된 IfcFacetedBrep 이다.

+

절대 위치는 IfcMapConversion 으로 준다. 모델은 로컬 원점에 두고 좌표를 +3억 mm 로 부풀리지 않는다 — 뷰어가 정밀도를 잃는다.

+
+ IFC 파일을 다시 읽어 검증했다 + 내보낸 파일의 자체 형상(압출 74 + Brep 3)으로 부피를 다시 재서 + 660.4154 m³ = IR 총부피. 왕복이 닫힌다. +
+ +

12미검증 영역 — 다음 도면에서 확인할 것

+ +

실증하며 채울 목록. 각 항목에 확인 방법과 예상 대응을 미리 적어뒀다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
항목확인 방법예상 대응
입면도 · 좌/우측면도 가설해치 유무로 단면/투영을 가른다. 폐곡선을 뽑아 재료 영역이 아닌 것이 확인되는지 본다role=validator로 고정. 3D 정투영 렌더와 윤곽 대조하는 경로를 만든다
flip 판정 가설비대칭 특징 하나로 3.3 절차 적용투상법을 설정값으로 두지 말고 도면에서 판정. 판정 결과를 앵커에 기록
DXF 입력 가설같은 도면을 DXF로 받아 엔티티 목록이 동등한지 대조변환 후 투입이 먼저. 상시 필요하면 DXF 리더를 parse.mjs 뒤에 붙인다 — 하류는 그대로
공통 datum이 없는 도면 검증뷰별 검출 기준선 y가 서로 다른가실재했다 — 교량 삽도 7장 전부. datum:{mode:'none'} + 뷰별 anchors.v0로 해결. + 예상 대응 ①이 그대로 맞았다. 다만 G2의 교차검증이 같이 사라져 검증이 G3로 밀린다(2.2). +
예상 못 한 것: 「기준선이 없다」가 예외로 드러나지 않는다. 계획고 라벨이 있는 도면 + (횡단경사)에서 검출기가 그럴듯한 틀린 y를 돌려줬다 — 서로 다른 두 표고 라벨이 같은 답을 냈다. + 라벨 무리 검사를 넣어 던지게 고쳤다
다중 시트 가설 · 축척 혼재 검증좌표 분포가 여러 덩어리인가. 같은 부재의 치수가 뷰마다 배수 관계인가다중 시트는 여전히 미확인(빈 시트 테두리만 봤다). + 축척 혼재는 실재. 교량폭원 1:1 · 중앙분리대 방호벽 5.805배. + viewBoxes[뷰].unitMm으로 해결 — 예상대로 「뷰별 축척 계수를 앵커에」가 맞았다. + 추가로 드러난 것: DWG 모델단위 자체가 도면군마다 다르다(1m vs 1mm). + 이건 예상 목록에 없었고 core에 상수로 박혀 있었다
원호·곡면 구조 부분ARC/ELLIPSE 엔티티 비중을 센다비중 측정됨: 거더배치 17% · 교량시종점 11% · 교대배치 8% · 횡단경사 1% · 나머지 0%. + 세는 것만으로는 부족했다 — 비중이 큰 도면들은 ARC 이전에 치수(기호)에서 먼저 막혔다. + 선분 근사는 아직 안 했다
곡선 선형 · 구배 가설종단이 직선 from/to로 표현되는가IR이 측점(station)을 다뤄야 한다. sweep 연산 추가
제목 없는 뷰 검증표제 귀속에 실패하는 조각이 남는가실재했다. 교량폭원은 제목이 아예 없고(조각 1개 → 뷰 0개 · orphan 1,810선), + 나머지는 제목이 N벌 중복이다(중복률 47~84%). + 「절단 기호 좌표로 역귀속」은 못 썼다 — 절단 기호 자체가 없는 도면이었다. + cfg.viewBoxes(위치 기반 수동 매핑)가 실제 해법이었다. + 예상 대응 중 fallback 쪽만 맞았다
교량 도면 부분상부공이 직선 from/to로 표현되는가. 하부공 도면이 받침 좌표·표고로 기술돼 있는가7장 중 1장(중앙분리대)만 끝까지 통과 — 4.8058 m³, 모델러·해석 일치. 상세는 11.0. + 상부공·하부공 질문은 답을 못 얻었다(표기 치수가 있는 일반도가 없었다). + 실제로 막은 것은 연산이 아니라 입력 해석이었다 — 단위 · 제목 · 축척 · 레이어 · 폐곡선 버그. + 11.2의 예측(sweep·revolve·경사 말뚝)은 하나도 병목이 아니었다
유형 삽도(도해) 검증 신규치수가 숫자인가 기호인가를 세어본다. 구조 윤곽이 닫히는가일반도와 다른 유형으로 갈라 다룬다(11.4). 기호 치수뿐이거나 윤곽이 안 닫히면 + 솔리드 대상이 아니다 — 게이트로 밀어넣기 전에 걸러낸다
DWG 모델단위 혼재 검증 신규DIMENSION.actualMeasurement와 치수 글자를 비교한다cfg.unitMm. 상수로 두면 두 번째 도면군에서 모든 값이 1000배로 + 조용히 틀린다 — 폐곡선도 부피도 정상적으로 나오기 때문에 알아채기 어렵다
건축 도면 가설datum이 FL, 앵커가 그리드 통심선으로 바뀌는가규칙 층을 부재 종류별로 분리(벽·슬래브·보). 그리드는 datum의 X·Y 확장
IFC 출력 가설part에 ifcClass 추가 후 왕복civil 계열 엔티티명은 스키마 직접 확인. 열린 변(T 정점) 처리 방침 결정
수평 단면 기반 층 스택 부분층별 XY 프로파일을 Z로 쌓았을 때 union 손실이 사라지는가조각이 Z로 진짜 겹치게 만든다. 평면 헌치가 자동으로 따라온다
+ +

12.1 실증할 때 기록할 것

+
    +
  • 어느 게이트에서 몇 번 막혔는가 — 방법론의 약한 고리다
  • +
  • 설정으로 해결됐는가, 코드 수정이 필요했는가 — 후자면 코어에 도면 고유값이 샌 것
  • +
  • 등급 분포(exact/tolerant/opaque) — 생성기가 얼마나 부족한지의 척도
  • +
  • 새로 나온 함정 — 증상·원인·대응 3열로 8장에 추가
  • +
  • 이 문서에서 틀린 것 — 표본 하나로 일반화한 자리가 어디였는지
  • +
+ + +

13용어·필드 사전

+ +

13.1 뷰 스펙

+ + + + + + + + +
필드뜻
plane이 뷰가 그리는 평면. XY · XZ · YZ
u0가로축 원점. 프로젝트 = u − u0
v0세로축 원점. 수직 단면은 생략(표고가 이미 기준)
flip / flipV축 방향이 반대. 뒤집기 홀수면 링 감김을 되돌린다
swapUV도면 가로축이 프로젝트 Y, 세로축이 X (수평 단면)
rolegenerator · assigner · locator · validator (3.2)
+ +

13.2 도면 조회 도우미 — fit()만 쓴다

+ + + + + + + +
도우미용도실패 시
loop(뷰, 폭, 높이)그 치수의 폐곡선0개·2개 이상이면 예외. 자기교차면 예외
circles(뷰, 지름, 레이어)원(관통·기둥). u→v 정렬없으면 예외 + 있는 목록 제시
opening(뷰, 폭, 높이)개구 기호에서 되찾은 개구부0개·2개 이상이면 예외
rect(u0,v0,u1,v1)직사각 프로파일— 값은 반드시 다른 뷰의 실측치
expect(뷰, 폭, 높이, 이유)실재 확인만. rect 교차검증용없으면 예외
+ +

13.3 등급·출처

+ + + + + + +
값뜻
src: measured / design / derived도면에서 뽑음 / 사람이 정함 / 식으로 나옴
grade: exact / tolerant / opaque파라미터로 완전 재현 / ±ε / 재현 불가 → 좌표째 동결
kind: topology / design규칙 종류 — 막는다 / 경고한다
keep: falseboolean에 소모될 중간 파트. 최종 산출물에만 이름·색을 준다
+ +
+

이 문서의 위치 — docs/methodology.html. + 절차는 docs/2d-3d-process.md, 사례별 교훈은 docs/2d-3d-conversion.md, + 파라메트릭 설계 노트는 docs/parametric.md, 남은 일은 docs/roadmap.md.

+

개정 방침 — 새 도면을 실증할 때마다 12장의 항목을 검증 등급으로 승격시키고, + 새로 밟은 함정을 8장에 3열로 추가한다. 표본 하나로 일반화한 자리를 표시해두는 것이 + 이 문서의 정직함이다.

+
+ +
+
+ + diff --git a/docs/next-session-p11.md b/docs/next-session-p11.md new file mode 100644 index 0000000..33e8949 --- /dev/null +++ b/docs/next-session-p11.md @@ -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 까지 diff --git a/docs/next-session-pier.md b/docs/next-session-pier.md new file mode 100644 index 0000000..5af96fe --- /dev/null +++ b/docs/next-session-pier.md @@ -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()` 를 만들어야 치수 상자가 나온다) diff --git a/docs/parametric.md b/docs/parametric.md new file mode 100644 index 0000000..2b9466b --- /dev/null +++ b/docs/parametric.md @@ -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 가 그 자리에 `` 을 얹으므로 **그림에서 값을 고칠 수 있다**. 비율이라 +창 크기가 바뀌어도 따라간다. 표와 그림의 입력칸은 서로 동기화된다. + +라벨이 많으면 겹치므로 위에서부터 훑으며 겹치는 것을 아래로 민다. 치수선 자체는 +안 움직인다 — 라벨만. + +**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` — 굴착·거푸집·되메움 같은 파생 물량 diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..c36dfac --- /dev/null +++ b/docs/roadmap.md @@ -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 | — | 기준선 없음. 통합 후 뜬다 | diff --git a/docs/youtube-p9.html b/docs/youtube-p9.html new file mode 100644 index 0000000..396917d --- /dev/null +++ b/docs/youtube-p9.html @@ -0,0 +1,535 @@ + + + + + +교각 P9 — 촬영 런시트 & 대본 + + + + +
+ +
+

촬영 런시트 · 컷별 대본2D 도면 한 장에서
교각 하나를 세운다

+
+ 11컷 + 5:54총길이 + 75부재 + 0.0000오차 +
+
+ +
+ + + + + +
+ + +
+ +
+
HOOK
+
01 도면
+
02
+
03
+
04 진단
+
05
+
06
+
07 모델링
+
08
+
09 IFC
+
10 마무리
+
+
0:00도면 → 진단 → 파라미터 → 모델링 → 검증 → IFC5:54
+ +
+
0:0014.0초 + 07 발췌
+
+

HOOK — 결과부터 보여준다

+

07번에서 교각이 서는 12~15초만 잘라 맨 앞에. 자막·설명 없이 화면만.

+

이거, 사람이 만든 모델이 아닙니다. + 2D 도면 한 장을 읽어서, 명령 한 줄로 자동으로 서는 겁니다. + 교각 하나. 부재 일흔다섯 개. 오차 0.0000.

+
+
+ +
+
0:1424.4초 + 01.mp4
+
+

도면 — 출발점

+

CAD 에서 교각 일반도. 뷰를 훑고 표까지.

+

시작은 이 도면입니다. 교각 일반도 한 장. + A1 한 장에 평면도, 정면도, 단면도, 기초 평면도, 그리고 표 세 개. + 토목 하시는 분들껜 익숙한 화면이죠. + 그런데 이 파일 하나에 도면이 놓이는 자리가 열일곱 개 있습니다. + 옛 작업 흔적이 그대로 쌓여 있는 거죠. + 출력용 도곽이 씌워진 건 그중 하나뿐입니다. + 어느 게 진짜인지 고르는 것부터가 일입니다.

+
+
+ +
+
0:3811.1초 + 02.mp4
+
+

뷰를 가른다

+
1단계  node src/run.mjs pier-p9
+

첫 명령입니다. 도면을 읽고 뷰를 가릅니다. + 평면도 14,000 × 3,200. 도면에 적힌 치수와 정확히 같습니다. + 여기가 안 맞으면 뒤는 볼 것도 없습니다.

+
+
+ +
+
0:5012.9초 + 03.mp4
+
+

끊김 진단

+
2단계  node src/gaps.mjs pier-p9
+

다음은 끊김 진단입니다. + 윤곽이 안 닫히는 이유가 레이어를 잘못 골랐다인지 + 도면에 선이 없다인지를 가릅니다. + 앞은 설정 한 줄, 뒤는 도면 수정. 대응 비용이 백 배 차이 납니다.

+
+
+ +
+
1:0248.5초 + 04.mp4
+
+

도면이 말하는 네 가지 방식

+
3단계  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"
+

도면은 형상으로만 말하지 않습니다. + 닫힌 윤곽, 원, 블록, 그리고 표. + 기둥은 지름 2,200 원으로 들어 있고, + 말뚝 마흔네 본은 폐곡선이 아니라 블록입니다. + 블록 배율 254가 곧 반지름이고요. 도면 주기의 Φ508 과 맞습니다. + 받침 여섯 기의 적층 높이와 절대표고는 도면 어디에도 안 그려져 있습니다. + 표에만 있습니다. 그래서 표도 읽습니다 — 이름표 아래를 찾아서, 병합 셀까지.

+
+
+ +
+
1:5124.8초 + 05.mp4
+
+

도면이 파라미터가 된다

+
4단계  node src/fit.mjs pier-p9
+5단계  node src/build.mjs pier-p9
+

여기서 도면이 파라미터가 됩니다. + 서른 개 중 스물한 개는 도면에서 잰 값, + 다섯 개는 도면이 안 주는 설계값, + 네 개는 식으로 나오는 값입니다. + 어느 게 어디서 왔는지 전부 기록됩니다. + 도면에서 읽은 척하지 않는 것 — 이게 핵심입니다.

+
+
+ +
+
2:1616.7초 + 06.mp4
+
+

3D 툴 없이 부피부터

+
6단계  node src/volume.mjs pier-p9
+

3D 툴을 켜기 전에 부피부터 냅니다. + 657.0569 세제곱미터. + 이게 우리 의도입니다. 이따 모델러가 만든 것과 대조할 기준이죠.

+
+
+ +
+
2:3282.0초 + 07.mp4
+
+

모델러에 교각이 선다

+
7단계  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
+

이제 실행입니다. + 78개 파트, 329단계 명령이 모델러로 갑니다. + 코핑, 원형 기둥 두 본, 확대기초 두 기, 버림, + 강관말뚝 마흔네 본, + 받침 여섯 기 — 받침콘크리트, 무수축 모르타르, 탄성받침, 솔플레이트까지. + 사람 손은 안 들어갑니다. + (말 없이 20초 — 세워지는 걸 보여준다) + 중요한 건 속도가 아닙니다. + 같은 명령을 다시 돌리면 같은 모델이 나온다는 것. + 도면이 바뀌면 파라미터만 고쳐서 다시 세울 수 있다는 것입니다.

+
+
+ +
+
3:5425.3초 + 08.mp4
+
+

의도 vs 결과 — 이 영상의 핵심

+
8단계  egm status
+        node src/measure.mjs pier-p9 --ir
+

그리고 검증입니다. + 우리가 뜻한 부피와 모델러가 실제로 만든 부피를 부재마다 대조합니다. + 일흔다섯 개 전부 차이 0.0000. 열린 변 0. + 부피가 맞으면 형상이 맞습니다. 눈으로 보고 「비슷하네」로 끝내지 않습니다.

+
+
+ +
+
4:2044.3초 + 09.mp4
+
+

IFC 로 넘긴다

+
9단계  node src/emit/ifc.mjs out/pier-p9.ir.json
+

마지막은 IFC 입니다. + IfcBeam, IfcColumn, IfcFooting, IfcPile 마흔네 개, IfcPlate, IfcDiscreteAccessory. + 메시로 안 내보냈습니다. 원기둥은 진짜 원으로 나갑니다. + 그래야 받는 쪽에서 「왜 지름이 508이 아니냐」를 다시 겪지 않습니다. + 절대 위치는 IfcMapConversion 으로 넣었습니다. + 측량좌표와 방위각은 받침배치도 표에서 가져왔고요. + 뷰어에서 열면 이렇게 나옵니다.

+
+
+ +
+
5:0450.1초 + 10.mp4
+
+

그림으로도 본다 · 마무리

+
10단계 node src/render.mjs pier-p9 --png --width 900
+

부피가 맞아도 형상이 맞는 건 아닙니다. 그래서 그림으로도 봅니다. + 정투영 세 방향. 검은 자리는 그 방향으로 재료가 없다는 뜻입니다. + 관통구나 빠진 부재가 바로 드러나죠. + 정리하면 — 도면 한 장에서 부재 일흔다섯 개, 657 세제곱미터, + 오차 0.0000, IFC 까지. + 사람이 판단한 건 「무엇을 어떻게 조립할까」 하나뿐입니다. + 나머지는 전부 도면이 말한 겁니다. + 다음 편에서는 꺾인 교각을 해보겠습니다.

+
+
+ +
+ + +
+

작업 디렉터리 d:\MYCLAUDE_PROJECT\dwg2model · PowerShell. + 씬은 비워 뒀고 기준선은 문양 제외본으로 갱신돼 있다. 0번부터 순서대로.

+ +

0 · 촬영 전 확인 필수

+
egm guide | python -c "import sys,json;print('명령',len(json.loads(sys.stdin.buffer.read().decode('utf-8-sig'))))"
+

208 이어야 한다. 39 면 뷰어 모드 — 모델링 명령이 등록조차 안 된다. + 모델러 UI 에서 Assistive Features → Working Mode Setting → Viewer & Modeler Use.

+ +

1–10 · 본편

+
# 1  도면 읽기 — 뷰 분리 · 표 색인          [02.mp4]
+node src/run.mjs pier-p9
+
+# 2  끊김 진단                              [03.mp4]
+node src/gaps.mjs pier-p9
+
+# 3  도면이 말하는 네 방식                   [04.mp4]
+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"
+
+# 4+5  파라미터 → 조립 스펙                 [05.mp4]
+node src/fit.mjs pier-p9
+node src/build.mjs pier-p9
+
+# 6  모델러 없이 부피 — 「의도」             [06.mp4]
+node src/volume.mjs pier-p9
+
+# 7  번역 · 실행 — 교각이 선다               [07.mp4] ★
+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
+
+# 8  의도 vs 결과                            [08.mp4] ★
+egm status
+node src/measure.mjs pier-p9 --ir
+
+# 9  IFC                                     [09.mp4]
+node src/emit/ifc.mjs out/pier-p9.ir.json
+
+# 10 그림                                    [10.mp4]
+node src/render.mjs pier-p9 --png --width 900
+ +

덤 · 파라메트릭 (다음 편 예고용)

+
node src/ui.mjs pier-p9 --port 7333
+# 브라우저 http://localhost:7333 → 값 수정 → 갱신 → 검증 사슬이 돈다
+ +
+ 미관 문양은 뺐다 + 단면(50 띠 + 하단 100 돌출)은 도면에서 나왔지만 정면도의 내부 능선을 아직 못 살렸다. + params/pier-p9.json 의 미관문양_포함 을 1 로 바꾸면 다시 선다. + 지금 기준선은 문양 제외 · 75파트 · 657.0569 m³ 기준이다. +
+
+ + +
+

컷마다 화면에 반드시 보여야 할 것. 안 나오면 다시 찍는다.

+
+ + + + + + + + + + + + + +
컷화면에 나와야 하는 것길이
HOOK모델러 뷰포트에 교각이 서는 순간. 색이 부재별로 갈려 있어야 한다 — + 코핑 주황 · 기둥 파랑 · 기초 초록 · 말뚝 보라14.0
01도곽 · 평면도 · 정면도 · 단면도 · 기초평면도 · 표. 한 화면에 다 보이는 장면 하나24.4
02평면도 14000 3200 ✓ 와 ✓ 검증 통과, 그리고 표 3개11.1
03「레이어 오분류 0 / 선 누락 n」 갈라지는 줄12.9
04Ø2200 원 2개 · CSPILEH 배율 254 44개 · + 표 6행 18열(NO./h1~h4/A/B/S1/S2)48.5
05measured 21 · design 5 · derived 4 · 최종 산출 7524.8
06합계 657.056916.7
07파트 78 → 단계 329 · 뷰포트에 부재가 쌓이는 과정 · + 끝에 modelCount 7582.0
08차이 0.0000 열이 쭉 이어지는 화면 · 열린변 0 · 재질별 물량25.3
09IFCPILE 44 등 클래스 목록 · IFC 뷰어에서 열린 모델44.3
10정투영 X·Y·Z 세 장. 검은 배경과 부재 색이 대비되는 것50.1
+
+ +

영상에서 쓸 숫자

+
+ + + + + + + + +
말할 것값
도곽 한 장 안에뷰 7 + 표 3.
+ 그런데 파일에는 도면 자리가 17개 — 도곽이 씌워진 건 1개, + 나머지는 구조선만 남은 옛 작업 흔적
단위 확정독립 근거 4개로 1단위 = 1 mm
파단선그려진 기둥 6,100 ↔ 실제 33,800. 생략량 27,700 을 도면에서 되찾는다
말뚝5×5=25 가 아니라 22본 — 둘레 16 + 내부 3등분점 6
받침형상 × 표 × 다른 도면 세 겹 검산, 6/6 일치
결과75 파트 · 657.0569 m³ · 차이 0.0000 · 열린 변 0
+
+
+ + +
+

녹화 중 걸릴 만한 것만. 전부 실제로 겪었다.

+
+ + + + + + + + + + + + + + + + + +
증상원인대응
run_egm 이 「명령이 끝나지 않는다」이전 명령이 물려 있다. egm status 의 command 가 남는다egm "Esc" — 접두 ~ 없이. 그리고 재실행
씬에 이전 모델이 남음—egm "Esc" → "SelAll" → "Delete" → "Esc"
modelCount 가 75 가 아님boolean 이 인자를 흘렸다씬 비우고 재실행. 반복되면 촬영 멈추고 확인
명령이 조용히 무시됨뷰어 모드0단계로 돌아간다. 연결 정상 ≠ 모델링 가능
Access to the path is denied모델러가 다른 권한으로 떠 있어 파이프에 못 붙는다모델러를 종료하고 + C:\Program Files\Baron\EG-BIM Modeler\EG-BIM_Modeler.exe 로 다시 띄운다
파이프가 죽음 (창은 멀쩡)씬 초기화를 여러 번 반복했다재시작 외에 방법 없다. 초기화는 갱신당 1회
+
+
+ --reset 은 붙이지 않는다 + 지난번 그것 때문에 Select 가 물렸다. 씬은 촬영 전에 미리 비워 둔다. +
+
+ + +
+

제목 후보

+
+ + + + + +
안제목
A2D 도면 한 장으로 교각 3D 모델 자동 생성 — 부재 75개, 오차 0.0000
B토목 도면을 BIM 으로 바꾸는 법 : 손으로 안 그리고 IFC 까지
C도면이 말하는 네 가지 방식 — 윤곽 · 원 · 블록 · 표 | 2D→3D 전환설계
+
+ +

설명란

+
교각 일반도(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 는 메시가 아니라 해석 솔리드로. 원기둥은 진짜 원으로
+ +

태그

+
토목엔지니어, BIM, 3D전환설계, 토목BIM, IFC, 교량, 교각, 도면자동화,
+DWG, 캐드, 파라메트릭모델링, 스마트건설, civil engineering, BIM automation
+ +

썸네일

+

왼쪽 흑백 2D 도면(01.mp4 한 프레임), 오른쪽 색 입은 3D 교각(07.mp4 마지막 프레임). + 가운데 화살표. 카피는 숫자 하나만 — 「오차 0.0000」 또는 「부재 75개, 자동」.

+ +
+ 다음 편 훅 + 마지막 컷에서 꺾인 교각(P11) 을 예고했다. 꺾임각 6.30° — + 코핑 축선이 중간에 꺾여 P9 방식이 그대로는 안 통한다. 그게 다음 편 주제다. +
+
+ +
+ + + + diff --git a/작업지시서.md b/작업지시서.md new file mode 100644 index 0000000..1646a7c --- /dev/null +++ b/작업지시서.md @@ -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. 어떤 모델을 사용할 것인지 확인이 필요합니다. + + +