Compare commits

...
17 Commits
Author SHA1 Message Date
Antigravity Agent 8e39c20f19 docs: 특정 구조물 종속 정보를 배제하고 범용 모델링 시스템 관점으로 README.md 전면 개정 2026-10-29 14:44:41 +09:00
Antigravity Agent b0222089c6 docs: 단계별 상세 기능 및 조작 가이드로 README.md 개정 2026-10-29 14:36:59 +09:00
swkim3 e44b4dc391 run_wsl.sh 삭제 2026-10-29 14:27:50 +09:00
swkim3 2397e47364 test_pipeline.py 삭제 2026-10-29 14:27:33 +09:00
swkim3 e944278e64 [바로가기]_3D모델러_웹접속.url 삭제 2026-10-29 14:26:22 +09:00
swkim3 9bc2a22726 [실행]_암거및날개벽_3D외형배근모델러.bat 삭제 2026-10-29 14:26:13 +09:00
swkim3 1d1ba627e4 [실행바로가기]_암거및날개벽_3D외형배근모델러.lnk 삭제 2026-10-29 14:26:04 +09:00
Antigravity Agent ae30ca321b feat: update title to 옹벽형날개벽 3D 모델러 2026-10-23 09:08:02 +09:00
Antigravity Agent 98a3339f9b docs: v0.1.10 원클릭 실행 스크립트 및 바로가기 문서 추가 2026-10-22 20:57:01 +09:00
Antigravity Agent 0bc81d4531 feat: 윈도우 원클릭 실행 배치 스크립트 및 바로가기 파일 추가 2026-10-22 20:55:29 +09:00
Antigravity Agent 49b96c255c docs: v0.1.09 GitHub 원격 저장소 연동 실행 보고서 추가 2026-10-22 20:32:28 +09:00
Antigravity Agent 2302a5407b docs: 기획, 분석, 설계 및 v0.1.00~v0.1.08 실행 보고서 문서 추가 2026-10-22 20:30:21 +09:00
Antigravity Agent 3cc9afeac8 refactor: 전체 코드 정리, 중복 로직 헬퍼 공통화, 미사용 임포트/변수 제거 및 리소스 정리 2026-10-22 18:01:56 +09:00
Antigravity Agent af65700afd fix(rebar): 3단계 W1 철근 벽체-저판 접합부 불필요 꺾임 제거 및 저판 하면 1회 꺾임(ㄱ자 형상) 수정 2026-10-22 17:46:57 +09:00
Antigravity Agent 145030c230 style(header): 공통 상단바 S/W 제목(21px) 및 프로세스(16.5px/13.5px) 텍스트 1.5배 확대 2026-10-22 17:11:29 +09:00
Antigravity Agent ec50cf733c feat(viewer3d): 2단계 날개벽 치수선 수평화, 사각형 테이블 제거, 치수 표기 간소화(L=00.0m, H=00.0m) 및 흰색 적용 2026-10-22 16:35:05 +09:00
Antigravity Agent 90a178a7e5 feat(modeling): 2단계 날개벽 선택 시 강조/흐림 대비 및 주요 치수(시작고, 종점고, 연장) 3D 치수선 표출 2026-10-22 15:29:11 +09:00
23 changed files with 1325 additions and 326 deletions

No files matched your search

@@ -0,0 +1,81 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어, WSL2 환경 연동 및 Git 형상 관리 구축 완료 |
---
## 1. 개요 및 배경
- **업무 명칭**: 2D CAD 도면 기반 수로암거 및 옹벽형 날개벽 3D 모델 변환 및 웹 뷰어 표출
- **기준 도면 (RAG)**: 대산당진 수로암거(`STA.13+611.21`) 일반도 및 구조도/배근도 DWG 5종
- **작업 환경**: WSL2 (Ubuntu Linux)
- **코드 저장소**: `Code/3d-drawing/` (Git 형상 관리 및 자동 커밋·로그)
- **적용 지침**: `.claude/taskexecutionagent.md` (v0.3.01 전역 지침 엄격 준수)
---
## 2. 단계별 구현 및 엔지니어링 결과
### 2.1 1단계: 도면 불러오기 및 도면 종류 자동 판별
- **파싱 엔진**: `ezdwg` 및 `ezdxf`를 통합하여 바이너리 DWG 및 텍스트 DXF 도면 파싱 지원
- **키워드 자동 판별**:
- 도면명에 `'일반'` 포함 ➔ **일반도(`GENERAL`)** 자동 분류 (`13. 수로암거 일반도(STA.13+611.21)(02).dwg`)
- 도면명에 `'배근'` 또는 `'구조'` 포함 ➔ **구조도/배근도(`REBAR`)** 자동 분류 (`14. 수로암거 구조도 4종`)
- 키워드 매칭 불가 시 ➔ **미분류(`UNCLASSIFIED`)** 상태로 수동 토글 UI 제공
- **파라미터 추출**: 일반도 평면도 상·하부 점선(`CS-CONC-MINR`, `A-DASHD01`) 분석을 통해 내공 스팬 $B=2.00\text{ m}$, 사각 $\theta=105.879^\circ$, 외폭 $3.40\text{ m}$ 자동 산출
### 2.2 2단계: 일반도 기반 3D 외형 파라메트릭 솔리드 모델링
- **모델링 원칙 준수**:
- 도면에 없는 시점부 날개벽(`START_LEFT`, `START_RIGHT`) 임의 생성 차단 (모델링 제외)
- 도면에 표기된 종점부 좌측(`END_LEFT`, $L=4.35\text{ m}$, $134^\circ$) 및 종점부 우측(`END_RIGHT`, $L=5.40\text{ m}$, $151^\circ$) 날개벽 100% 반영
- **수밀성(Watertight Solid) 2-Manifold 검증**:
- 암거 본체 및 종점부 2개 날개벽 모든 메시에 대해 모든 변(Edge)이 정확히 2개의 면(Face)을 공유하는 닫힌 다양체(2-Manifold Closed Solid) 구조 검증 완료 (Non-manifold edges: 0개)
- **체적 산출**:
- 암거 본체: $59.04\text{ m}^3$
- 종점부 좌측 날개벽: $7.32\text{ m}^3$
- 종점부 우측 날개벽: $9.09\text{ m}^3$
- **총 콘크리트 체적**: **$75.45\text{ m}^3$**
### 2.3 3단계: 배근도 기반 3D 철근 배근 모델링
- **피복두께 준수**: 날개벽 전면/배면/저판 피복두께 80mm 준수
- **19종 철근 기호 및 총 508본 3D 입체 배근**:
- 우각부 절곡 보강근: `C1, C2` (H19 @250, 각 13본, $a=700, b=700$, 사각 절곡)
- 벽체 수직 주철근: `W1, W5, W6` (H16 @250 / 1·2Cycle 교대 @125)
- 벽체 수평 배력근: `W2~W4, W7~W9` (H13 @250, 하부 4단, 상부 8단, 천단 연단 1열)
- 저판 횡방향 주철근: `F1, F2, F6` (H16 @250 / 1·2Cycle 교대 @125, 양단 후크)
- 저판 종방향 배력근: `F3~F5, F7~F9` (H13 @250, 연단근 및 사면연단근 포함)
- 헌치/전단보강근: `K1, K2, S1~S3` (H16, H13)
- **철근 가공 재료표 (BBS)**:
- 종점부 좌측: 231본, 총연장 $485.111\text{ m}$, 중량 $0.688\text{ TON}$ (할증 3% 포함 $0.709\text{ TON}$)
- 종점부 우측: 277본, 총연장 $607.664\text{ m}$, 중량 $0.855\text{ TON}$ (할증 3% 포함 $0.881\text{ TON}$)
- **합계**: **508본**, 총연장 **$1,092.775\text{ m}$**, 총중량 **$1.543\text{ TON}$** (할증 3% 포함 **$1.590\text{ TON}$**)
### 2.4 Three.js 기반 3D 웹 뷰어 인터페이스
- **기능**:
- 마우스 궤도 회전(Orbit), 팬(Pan), 줌(Zoom) 제어
- 부재별 가시성(암거 본체, 날개벽, 철근군) 개별 On/Off 및 콘크리트 반투명(X-Ray) 모드 지원
- 철근 직경별(H19 빨강, H16 파랑, H13 초록) 색상 코딩 및 굵기 반영
- 3D 모델 OBJ, STL, DXF 및 철근 재료표 UTF-8 BOM CSV 내보내기
---
## 3. Git 형상 관리 및 커밋 이력
`Code/3d-drawing/` 리포지토리에 작업 단위별 4개의 Semantic Commit을 순차 적용하고 각 커밋 직후 `git log`를 자동 수행함.
```text
* aa3ae54 feat(wsl2): add WSL2 execution script, automated test suite and documentation
* e5abc6f feat(viewer): implement Three.js interactive 3D web viewer and Flask API server
* 42726ff feat(modeling): implement 3D parametric solid and rebar modeling engines
* 5ddc97c feat(parser): add 2D CAD DWG/DXF parsing and drawing classification engine
```
---
## 4. 검증 결과 요약
- **테스트 스위트**: `Code/3d-drawing/test_pipeline.py`
- **검증 결과**: 전수 6/6 통과 (`Ran 6 tests in 0.298s, OK`)
- **실행 서버 상태**: Flask 3.1.3 서버가 WSL2 Ubuntu 환경에서 백그라운드 구동 중 (`http://localhost:5000`, HTTP 200 OK)
@@ -0,0 +1,70 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (3단계 세로 2단 UI 개편)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 2단계와 일관된 세로 2단 분할 레이아웃 적용, 부재 선택 시 3D 카메라 줌 및 선택 부재/철근 집중 표출, 우측 인터랙티브 HUD 및 철근 기호 상세제원 플로팅 카드 구현 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어, WSL2 환경 연동 및 Git 형상 관리 구축 완료 |
---
## 1. 개요 및 배경
- **업무 명칭**: 2D CAD 도면 기반 수로암거 및 옹벽형 날개벽 3D 모델 변환 및 웹 뷰어 표출 (3단계 세로 2단 UI 개편)
- **기준 도면 (RAG)**: 대산당진 수로암거(`STA.13+611.21`) 일반도 및 구조도/배근도 DWG 5종
- **작업 환경**: WSL2 (Ubuntu Linux)
- **코드 저장소**: `Code/3d-drawing/` (Git 형상 관리: Commit `573c0bc`)
- **적용 지침**: `.claude/taskexecutionagent.md` (v0.3.01 전역 지침 엄격 준수)
---
## 2. 3단계 UI 개편 상세 내역
### 2.1 세로 2단 레이아웃 일관성 확보
- **레이아웃 통일**: 기존의 좌·중·우 3열 배치에서 2단계와 완전히 동일한 **좌측 제어 패널(420px) + 우측 3D 뷰어(전체 폭)** 세로 2단 레이아웃으로 전면 개편.
- **도면 확인 탭 및 미리보기 삭제**: 3단계 진입 시 불필요한 2D 도면 확인 탭과 2D 캔버스 뷰어를 제거하고 3D 모델링 공간을 최대화.
### 2.2 좌측 UI: 구조물 중심 선택 체계
- **표시 구조물**: 실제 도면에 표기된 부재만 선별 표출 (시점부 임의 생성 방지 지침 준수)
1. `암거 본체 (STA.13+611.21)`: 클릭 시 전체 보기(`fitView()`), 모든 부재 정상 표출
2. `종점 좌측 날개벽 (L=4.35m)`: 클릭 시 해당 날개벽 중심 3D 카메라 확대 Focus
3. `종점 우측 날개벽 (L=5.40m)`: 클릭 시 해당 날개벽 중심 3D 카메라 확대 Focus
- **3D 상호작용 및 시각적 위계**:
- **선택된 날개벽**: 콘크리트 외형 및 내부 19종 철근을 정상 색상과 선명도(100%)로 강조.
- **비선택 구조물 (암거 및 반대편 날개벽)**: 모델에서 완전히 숨기지 않고 투명도(0.08) 및 옅은 그레이(0.12)로 비활성화하여 위치·형상 참고 가능 상태 유지.
### 2.3 우측 UI: 3D 모델 뷰어 및 인터랙티브 HUD
- **철근 기호 상세 제원 플로팅 카드**:
- 평상시(미선택): 화면을 가리지 않도록 완전 숨김(`display: none`).
- 3D 철근 클릭 시: 우측 상단 플로팅 카드로 즉시 표출 (기호, 직경 SD400, 배근 부위, 간격/피복, 좌/우 본수 및 길이, 중량, 철근 절곡 상세 형상 SVG).
- 닫기(`✕`) 버튼 또는 배경 클릭 시 즉시 닫힘.
- **3D 철근 투시 및 표현 제어 HUD**:
- 콘크리트 외형 투명도(X-Ray) 슬라이더 (0.05 ~ 0.90)
- 철근 굵기 표현 배율 슬라이더 (1.0x ~ 3.0x)
- 직경별(H19 빨강, H16 파랑, H13 초록) 원클릭 필터 필
- 부위별(우각부, 벽체, 저판, 헌치) 원클릭 필터 필
- 원클릭 접기/펼치기(`▾`/`▸`) 토글 지원
- **철근 재료표 및 배근 내보내기**:
- `📊 전체 철근 재료표 (BOM) 보기` 모달 연동
- CSV / OBJ / STL / DXF 내보내기 퀵 액션 그리드 배치
---
## 3. Git 형상 관리 및 커밋 이력
`Code/3d-drawing/` 리포지토리에 커밋을 적용하고 `git log`를 자동 수행함.
```text
* 573c0bc feat(ui): update Step 3 rebar modeling to vertical 2-column layout and interactive controls
* aa3ae54 feat(wsl2): add WSL2 execution script, automated test suite and documentation
* e5abc6f feat(viewer): implement Three.js interactive 3D web viewer and Flask API server
* 42726ff feat(modeling): implement 3D parametric solid and rebar modeling engines
* 5ddc97c feat(parser): add 2D CAD DWG/DXF parsing and drawing classification engine
```
---
## 4. 검증 결과 요약
- **단위 테스트 (`test_pipeline.py`)**: 6/6 전수 PASS (`Ran 6 tests in 0.294s, OK`)
- **구문 검증**: Node.js JS 구문 및 HTML5 파서 에러 0건
- **실행 서버 상태**: Flask 3.1.3 서버가 WSL2 Ubuntu 환경에서 정상 구동 중 (`http://localhost:5000`, HTTP/1.1 200 OK)
@@ -0,0 +1,75 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (3단계 날개벽별 철근 선택 분리 및 UI 개선)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선: 1) 선택 날개벽 종속 철근 격리 활성화 (타 날개벽 동일 기호 활성화 방지), 2) 카메라 시점 보존 (빈 바닥 클릭 시 구도 초기화 방지, 좌측 바 클릭 시에만 구도 변경), 3) Three.js Sprite 줌 반응형 스케일 보정을 통한 3D 텍스트 크기 고정, 4) 좌측 바/우측 창 UI 재배치 및 1.5배 텍스트 확대, 5) 암거본체 초기 선택 시 날개벽 명칭만 표출 |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 2단계와 일관된 세로 2단 분할 레이아웃 적용, 부재 선택 시 3D 카메라 줌 및 선택 부재/철근 집중 표출, 우측 인터랙티브 HUD 및 철근 기호 상세제원 플로팅 카드 구현 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어, WSL2 환경 연동 및 Git 형상 관리 구축 완료 |
---
## 1. 개요 및 배경
- **업무 명칭**: 3단계 배근도 기반 3D 철근배근 UI 개선 및 날개벽별 철근 선택·활성화 분리
- **작업 환경**: WSL2 (Ubuntu Linux), Python 3.14 (`~/.venv-3d-drawing`), Three.js WebGL
- **코드 저장소**: `Code/3d-drawing/` (Git 형상 관리: Commit `c1f67c6`)
- **실행 서버**: Flask 웹 서버 (`http://localhost:5000`)
---
## 2. 세부 수정 내역 및 구현 결과
### 2.1 좌측 바 UI 수정
1. **적용배근도/구조도 테이블 삭제**: 좌측 바 상단 `#step3-rebar-summary-card` 완전 제거, 구조물 선택 리스트를 최상단에 배치.
2. **구조물 텍스트 1.5배 확대**: `.struct-card-step3-title` 폰트 크기를 기존 대비 1.5배인 `17.5px !important` (굵기 700)로 확대.
3. **'3D 철근 투시 및 표현제어' 패널 이동**: 기존 우측 플로팅 HUD에서 좌측 바 구조물 선택 패널 하단으로 이동 배치하여 뷰포트 가림 방지.
### 2.2 우측 창 UI 수정
1. **철근 상세 정보 표시 조건**: 철근이 선택된 경우에만 플로팅 카드를 표출(`display: flex`), 미선택 시에는 완전 숨김(`display: none`).
2. **철근 번호 텍스트 1.5배 확대**: 상세 정보 창 상단 철근 번호(`#step3-detail-title`)를 `18px !important` (굵기 800, 황금색 골드 `#facc15`)로 확대. 소속 날개벽 제원 및 중량 강조.
### 2.3 우측 메인창 UI 및 상호작용 기능 수정
1. **불필요한 안내 문구 제거**: `[3단계: 배근도 기반 3D 철근배근]` 뱃지 및 `좌클릭: 회전 | 우클릭: 이동 | 휠: 줌...` 오버레이 칩 제거.
2. **초기 선택 구조물 '암거본체' 설정**: 3단계 진입 시 기본 선택 구조물을 `'CULVERT'`로 설정하고, 3D 모델 위에 "종점부 좌측 날개벽", "종점부 우측 날개벽" 2개 명칭만 깔끔하게 표출. 암거본체의 철근 상세 정보나 불필요한 단면 마커는 미표시.
3. **3D 텍스트 크기 화면상 고정 (Constant Screen Size)**: Three.js 렌더 루프(`animate()`)에서 카메라와 텍스트 스프라이트 간의 거리에 비례하여 `scale.set(dist * 0.040 * aspect, dist * 0.040, 1)`로 동적 스케일링. 마우스 휠로 줌 인/줌 아웃을 수행해도 텍스트 크기가 확대/축소되지 않고 모델 가림 방지.
4. **날개벽별 철근 선택 및 활성화 분리 (최우선 과제)**:
- 철근 선택 및 활성화는 현재 선택된 날개벽(`wingwallId`)에 완전 종속.
- 특정 날개벽에서 철근을 클릭하면 오직 해당 날개벽에 속한 철근만 골드(`#facc15`) 하이라이트 및 3D 라벨 표시.
- 타 날개벽의 동일 기호 철근은 `opacity: 0.10, color: #64748b`로 비활성화.
- 좌측 바에서 날개벽을 변경하면 이전 철근 선택 상태 및 상세 정보 창을 즉시 초기화.
5. **3D 모델 기본 구도 변경 조건 수정**:
- 사용자가 회전/이동 조작 후 빈 바닥 영역을 클릭하더라도 카메라 시점을 절대 리셋하지 않고 사용자의 시점 유지.
- 좌측 바에서 날개벽 카드를 직접 클릭한 경우에만 해당 날개벽을 중심으로 기본 구도(시점) 전환.
---
## 3. 완료 검증 기준 대조 결과 (8/8 충족)
| # | 완료 검증 기준 | 구현 및 검증 결과 | 판정 |
|---|---|---|:---:|
| 1 | 3단계 진입 시 암거본체가 기본 선택 상태로 표시된다. | `setStep(3)`에서 `selectStep3Structure('CULVERT', true)` 자동 호출 | **PASS** |
| 2 | 철근을 선택하지 않으면 철근 상세 정보 창이 표시되지 않는다. | `#step3-rebar-detail-card` 기본 `display:none`, 철근 미선택 시 완전 은폐 | **PASS** |
| 3 | 특정 날개벽에서 철근을 선택했을 때 다른 날개벽의 동일 번호 철근은 활성화되지 않는다. | `selectRebarMark(mark, wwId)`에서 `u.wingwallId === wwId && u.mark === mark`만 하이라이트, 타 부재는 dim(0.10) 처리 | **PASS** |
| 4 | 날개벽을 변경하면 이전 날개벽의 철근 선택 상태가 해제되고 새 날개벽의 철근 정보가 표시된다. | `selectStep3Structure`에서 `hideRebarDetailCard()` 및 `clearRebarSelection()` 선행 호출 | **PASS** |
| 5 | 모델 확대·축소 시 텍스트 크기가 일정하게 유지된다. | `animate()` 루프에서 거리 비례 스케일 동적 보정 (`dist * 0.040`) 적용 | **PASS** |
| 6 | 모델 회전·이동 후 빈 바닥 영역을 클릭해도 모델의 시점이 초기화되지 않는다. | `onPointerDown`에서 빈 바닥 클릭 시 `clearRebarSelection()`만 호출하여 시점 100% 보존 | **PASS** |
| 7 | 좌측 바에서 날개벽 이름을 클릭한 경우에만 해당 날개벽 기준으로 모델의 기본 구도가 변경된다. | 좌측 카드 클릭 이벤트에서만 `updateCamera=true` 전달하여 시점 전환 | **PASS** |
| 8 | 좌측 바 및 우측 창의 항목 배치와 텍스트 크기가 위 요구사항대로 반영된다. | CSS 17.5px/18px 적용, 좌측 바 요약 제거 및 투시 제어 패널 이동 배치 완료 | **PASS** |
---
## 4. Git 형상 관리 이력
```text
c1f67c6 feat(ui): 3단계 배근도 기반 3D 철근배근 UI/UX 및 날개벽별 철근 격리 개선
573c0bc feat(ui): update Step 3 rebar modeling to vertical 2-column layout and interactive controls
aa3ae54 feat(wsl2): add WSL2 execution script, automated test suite and documentation
```
---
## 5. 실행 서버 상태
- **가상환경**: WSL2 Ubuntu `~/.venv-3d-drawing` (Python 3.14)
- **테스트**: `python test_pipeline.py` (6/6 전수 PASS, 0.319s)
- **HTTP 상태**: `http://localhost:5000` (HTTP/1.1 200 OK)
@@ -0,0 +1,71 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (2단계 외형 모델링 UI 개선)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선: 1) 좌측 바 불필요 테이블(적용 일반도, 평면도 부재 판별) 삭제, 2) '3D 구조물 선택' 날개벽 명칭 텍스트 1.5배(17.5px) 확대, 3) 메인창 상단 도면명/단계문구/조작안내문구/단면 0-0 표기 완전 삭제, 4) 2단계 초기 기본 선택 부재를 '암거본체'로 설정 및 날개벽 명칭만 표출, 5) 2단계·3단계 메인 화면 3D 모델 표시 크기 약 80% 바운딩 구 최적화 스케일링 일괄 적용 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 (날개벽별 철근 격리, 카메라 시점 보존, 텍스트 크기 고정 및 UI 재배치) |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용, 부재 선택 시 3D 카메라 줌 및 선택 부재/철근 집중 표출 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어, WSL2 환경 연동 및 Git 형상 관리 구축 완료 |
---
## 1. 개요 및 배경
- **업무 명칭**: 2단계 일반도 기반 3D 외형 모델링 UI 개선 및 뷰포트 최적화
- **작업 환경**: WSL2 (Ubuntu Linux), Python 3.14 (`~/.venv-3d-drawing`), Three.js WebGL
- **코드 저장소**: `Code/3d-drawing/` (Git 형상 관리: Commit `fe6b09f`)
- **실행 서버**: Flask 웹 서버 (`http://localhost:5000`)
---
## 2. 세부 수정 내역 및 구현 결과
### 2.1 좌측 바 UI 수정
1. **불필요한 테이블 삭제**: 좌측 바의 ‘적용 일반도 정보 & 체적 요약’ (`#step2-general-dwg-card`) 및 ‘평면도 부재 판별 현황’ 패널을 완전 삭제.
2. **구조물 선택 텍스트 1.5배 확대**: `.struct-card-step2-title`에 `font-size: 17.5px !important; font-weight: 700;`를 적용하여 날개벽 명칭 텍스트를 1.5배로 확대하고 '3D 구조물 선택'으로 섹션명을 통일.
### 2.2 우측 메인창 UI 및 기능 수정
1. **불필요한 안내 텍스트 완전 제거**:
- 뷰포트 상단 도면 탭(`#viewport-drawing-tabs`)을 2단계에서 숨김 처리(`display: none`).
- 상단 스텝 뱃지(`[2단계: 일반도 전용 외형 모델]`) 삭제.
- 메인 화면 오버레이 조작 안내(`좌클릭: 회전 | 우클릭: 이동 | 휠: 줌...`) 문구 완전 삭제.
2. **초기 선택 구조물 '암거본체' 설정 및 단면 0-0 표기 삭제**:
- 2단계 진입 시 `selectObject('CULVERT')`를 자동 호출하여 암거본체가 기본 선택되도록 설정.
- 암거본체 선택 시 날개벽 구조물 명칭("종점부 좌측 날개벽", "종점부 우측 날개벽")만 3D 모델 위에 깔끔하게 표출.
- "단면 A-A", "단면 B-B", "단면 C-C", "단면 D-D" 등 단면 0-0 텍스트 표기 및 EL 마커를 완전 미생성/삭제.
3. **텍스트 크기 고정 (Screen-space Constant Size)**:
- Three.js 렌더 루프(`animate()`)에서 카메라 거리에 비례하여 텍스트 스프라이트를 동적 스케일링(`dist * 0.040`). 휠 줌 조작 시에도 텍스트 크기가 일정하게 유지되어 모델 가림 방지.
4. **메인 화면 모델링 크기 조정 (약 80% 화면 점유)**:
- `fitView(coverage = 0.80)` 함수에서 3D 모델 전체 바운딩 구(Bounding Sphere)의 반경과 카메라 화각(FOV), 뷰포트 종횡비(Aspect)를 연립 계산하여 모델이 메인 화면 표시 영역의 약 80%를 안정적으로 차지하도록 카메라 거리를 정밀 산출.
- 2단계와 3단계 모두 동일하게 80% 기준을 적용.
---
## 3. 완료 검증 기준 대조 결과 (5/5 전 항목 PASS)
| # | 완료 검증 기준 | 구현 및 검증 결과 | 판정 |
|---|---|---|:---:|
| 1 | 좌측 바에서 지정된 테이블 항목이 삭제되고, 날개벽 이름의 텍스트가 기존 대비 1.5배로 확대된다. | `#step2-general-dwg-card` 및 평면도 부재 판별 패널 삭제 완료. `.struct-card-step2-title`에 17.5px(1.5배) 적용 | **PASS** |
| 2 | 메인창 상단의 도면명, 단계 안내 문구, 조작 안내 문구 및 단면 0-0 표기가 표시되지 않는다. | 뷰포트 상단 탭/뱃지 은폐, 오버레이 범례 공백 처리, 단면 A~D 텍스트 스프라이트 미생성 | **PASS** |
| 3 | 2단계 진입 시 암거본체가 기본 선택 상태로 설정된다. | `setStep(2)`에서 `this.selectObject('CULVERT')` 호출 및 암거 카드 선택 활성화 | **PASS** |
| 4 | 모델을 확대·축소해도 모델 위 텍스트의 크기가 일정하게 유지된다. | `animate()` 루프 내 거리 비례 스케일링(`dist * 0.040`)으로 화면상 텍스트 크기 불변 | **PASS** |
| 5 | 2단계와 3단계 모두 초기 모델이 메인 화면 표시 영역의 약 80%를 차지하도록 표시된다. | `fitView(coverage = 0.80)` 기하학적 바운딩 구 기반 거리 계산을 2단계/3단계 공통 적용 | **PASS** |
---
## 4. Git 형상 관리 이력
```text
fe6b09f feat(ui): 2단계 일반도 기반 3D 외형 모델링 UI 개선 및 80% 뷰포트 스케일링
c1f67c6 feat(ui): 3단계 배근도 기반 3D 철근배근 UI/UX 및 날개벽별 철근 격리 개선
573c0bc feat(ui): update Step 3 rebar modeling to vertical 2-column layout and interactive controls
aa3ae54 feat(wsl2): add WSL2 execution script, automated test suite and documentation
```
---
## 5. 실행 서버 상태
- **가상환경**: WSL2 Ubuntu `~/.venv-3d-drawing` (Python 3.14)
- **테스트**: `python test_pipeline.py` (6/6 전수 PASS, 0.291s)
- **HTTP 응답**: `http://localhost:5000` (HTTP/1.1 200 OK)
@@ -0,0 +1,85 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (2단계 날개벽 선택 및 치수 표출)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현: 1) 암거 본체 선택 시 기존 정상 표시 유지, 2) 날개벽 선택 시 해당 날개벽 진하게 강조(1.0 + Emissive glow) 및 나머지 구조물 흐리게(0.18) 반투명 처리, 3) 선택된 날개벽에 시작단면 높이(H=3.70m), 종점단면 높이(H=1.50m), 날개벽 연장(L=4.35m/5.40m) 3D 치수선, 보조선, 끝단 틱 및 화면크기 고정 치수 라벨 동시 표출 |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선: 좌측 바 테이블 삭제, 텍스트 1.5배 확대, 상단 탭/단계문구/조작안내/단면 0-0 삭제, 80% 뷰포트 스케일링 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 (날개벽별 철근 격리, 카메라 시점 보존, 텍스트 크기 고정 및 UI 재배치) |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용, 부재 선택 시 3D 카메라 줌 및 선택 부재/철근 집중 표출 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어, WSL2 환경 연동 및 Git 형상 관리 구축 완료 |
---
## 1. 개요 및 배경
- **업무 명칭**: 2단계 UI 수정 및 날개벽 선택·모델링 동작(강조/흐림 대비 및 3D 치수선) 구현
- **작업 환경**: WSL2 (Ubuntu Linux), Python 3.14 (`~/.venv-3d-drawing`), Three.js WebGL
- **코드 저장소**: `Code/3d-drawing/` (Git 형상 관리: Commit `90a178a`)
- **실행 서버**: Flask 웹 서버 (`http://localhost:5000`)
---
## 2. 세부 구현 내역
### 2.1 메인 화면 모델링 표시 및 암거 본체 선택 유지
- 2단계 진입 시 기존 3D 모델링 표시 화면과 표시 방식을 그대로 유지.
- 좌측 바에서 ‘암거 본체’를 클릭하거나 초기 진입 시:
- 모든 구조물(암거 및 날개벽)을 100% 정상 불투명도와 고유 색상으로 표시.
- 치수선은 깔끔하게 미표시하고, 날개벽 구조물 명칭("종점부 좌측 날개벽", "종점부 우측 날개벽")만 표출.
### 2.2 날개벽 선택 및 강조/흐림 시각적 위계 대비
- 좌측 바에서 특정 날개벽(종점부 좌측/우측)을 클릭하면:
- **선택된 날개벽**: 100% 불투명도 + 부재 고유 색상 + 부드러운 발광(`emissive: 0.30`) + 선명한 모서리(`edges opacity: 0.85`)로 **진하게 강조**.
- **나머지 구조물(암거 본체 및 반대편 날개벽)**: 반투명(`opacity: 0.18`) + 슬레이트 그레이 색상으로 **흐리게 표시**하여, 전체 모델링의 상대적 위치와 형상을 완벽히 파악하면서도 선택 부재가 한눈에 부각되도록 처리.
- 다른 날개벽을 클릭하면 즉시 이전 상태가 해제되고 새로 선택된 날개벽 기준으로 강조/흐림 대상이 실시간 갱신됨.
### 2.3 선택된 날개벽의 3D 치수 표출
선택된 날개벽의 실제 3D 모델링 형상 및 설계 데이터(`modelData.objects`)와 100% 일치하는 3가지 주요 치수를 3D 뷰포트 내에 정밀하게 표시:
1. **시작단면 높이**:
- 좌/우 날개벽 암거 접합부(A-A / C-C 단면): $H = 3.70\text{ m}$ ($37.104\text{ m} - 33.404\text{ m}$)
- 외측 법선 방향으로 0.55m 오프셋 배치.
2. **종점단면 높이**:
- 좌/우 날개벽 말단부(B-B / D-D 단면): $H = 1.50\text{ m}$ ($35.023\text{ m} - 33.523\text{ m}$)
- 날개벽 진행 방향 외측으로 0.55m 오프셋 배치.
3. **날개벽 연장**:
- 종점부 좌측 날개벽: $L = 4.35\text{ m}$
- 종점부 우측 날개벽: $L = 5.40\text{ m}$
- 날개벽 상단 천단 경사선 상부(+Z 0.65m)에 오프셋 배치.
4. **치수선 및 치수 라벨 규격**:
- 시작점과 끝점을 잇는 주 치수선(Dimension Line).
- 모델 모서리로부터 인출되는 보조선(Extension Line) 및 0.15m 오버슈트.
- 치수 측정 방향과 양 끝단을 명확히 정의하는 끝단 틱 마커(End Ticks).
- 줌 인/줌 아웃 시에도 화면상에서 일정한 픽셀 크기를 유지하는 동적 반응형 3D 스프라이트 치수값(`시작단면 H = 3.70m`, `종점단면 H = 1.50m`, `날개벽 연장 L = 4.35m/5.40m`).
---
## 3. 검증 결과 대조
| 요구 항목 | 요구사항 내용 | 구현 및 동작 검증 결과 | 판정 |
|---|---|---|:---:|
| 1. 메인 화면 모델링 표시 | 2단계 진입 시 기존 3D 모델링 화면과 표시 방식 유지 | 기존 모델링 뷰 및 Three.js 씬 구조 완전 유지 | **PASS** |
| 2.1 암거 본체 선택 | 암거 본체 클릭 시 메인 화면에 암거 본체 및 전체 정상 표시 | 모든 구조물 불투명도 1.0, 치수선 해제, 날개벽 명칭 정상 표출 | **PASS** |
| 2.2 날개벽 선택 | 선택 날개벽은 진하게, 나머지 구조물은 흐리게 구분 표시 | 선택 날개벽 emissive/1.0 진하게, 타 구조물 opacity 0.18 흐리게 | **PASS** |
| 3.1 시작단면 높이 치수 | 날개벽 시작단면 높이 치수선 및 치수값 표출 | A-A/C-C 단면 $H = 3.70\text{ m}$ 치수선/보조선/라벨 표출 | **PASS** |
| 3.2 종점단면 높이 치수 | 날개벽 종점단면 높이 치수선 및 치수값 표출 | B-B/D-D 단면 $H = 1.50\text{ m}$ 치수선/보조선/라벨 표출 | **PASS** |
| 3.3 날개벽 연장 치수 | 날개벽 시작~종점 연장 길이 치수선 및 치수값 표출 | 좌 $L = 4.35\text{ m}$, 우 $L = 5.40\text{ m}$ 상단 치수선/라벨 표출 | **PASS** |
| 4. 동시 및 갱신 동작 | 강조 및 치수 동시 표출, 부재 변경 시 갱신, 가시성 유지 | 부재 클릭 시 즉시 갱신, 이전 치수 자동 소멸 및 가시성 확보 | **PASS** |
---
## 4. Git 형상 관리 이력
```text
90a178a feat(modeling): 2단계 날개벽 선택 시 강조/흐림 대비 및 주요 치수(시작고, 종점고, 연장) 3D 치수선 표출
fe6b09f feat(ui): 2단계 일반도 기반 3D 외형 모델링 UI 개선 및 80% 뷰포트 스케일링
c1f67c6 feat(ui): 3단계 배근도 기반 3D 철근배근 UI/UX 및 날개벽별 철근 격리 개선
573c0bc feat(ui): update Step 3 rebar modeling to vertical 2-column layout and interactive controls
```
---
## 5. 실행 서버 상태
- **가상환경**: WSL2 Ubuntu `~/.venv-3d-drawing` (Python 3.14)
- **테스트**: `python test_pipeline.py` (6/6 전수 PASS, 0.306s)
- **HTTP 응답**: `http://localhost:5000` (HTTP/1.1 200 OK)
@@ -0,0 +1,73 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (2단계 날개벽 치수선 및 치수 표기 개선)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.05` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 치수선 및 치수 표기 개선: 1) 날개벽 연장 치수선 수평 방향 배치(경사/기울어짐 방지 및 $Z$ 높이 일치), 2) 치수값 사각형 테이블/박스 제거 및 투명 캔버스 적용, 3) 치수 설명 문구 삭제 및 `L=00.0m`, `H=00.0m` 간소화 표기, 4) 치수선, 보조선, 끝단 틱 및 치수값 전원 흰색(`0xffffff`) 통일 적용 |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현: 1) 암거 본체 선택 시 기존 정상 표시 유지, 2) 날개벽 선택 시 해당 날개벽 진하게 강조(1.0 + Emissive glow) 및 나머지 구조물 흐리게(0.18) 반투명 처리, 3) 선택된 날개벽에 시작단면 높이(H=3.70m), 종점단면 높이(H=1.50m), 날개벽 연장(L=4.35m/5.40m) 3D 치수선, 보조선, 끝단 틱 및 화면크기 고정 치수 라벨 동시 표출 |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선: 좌측 바 테이블 삭제, 텍스트 1.5배 확대, 상단 탭/단계문구/조작안내/단면 0-0 삭제, 80% 뷰포트 스케일링 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 (날개벽별 철근 격리, 카메라 시점 보존, 텍스트 크기 고정 및 UI 재배치) |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용, 부재 선택 시 3D 카메라 줌 및 선택 부재/철근 집중 표출 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어, WSL2 환경 연동 및 Git 형상 관리 구축 완료 |
---
## 1. 개요 및 배경
- **업무 명칭**: 2단계 UI 수정 – 날개벽 치수선 및 치수 표기 개선
- **작업 환경**: WSL2 (Ubuntu Linux), Python 3.14 (`~/.venv-3d-drawing`), Three.js WebGL
- **코드 저장소**: `Code/3d-drawing/` (Git 형상 관리: Commit `ec50cf7`)
- **실행 서버**: Flask 웹 서버 (`http://localhost:5000`)
---
## 2. 세부 구현 내역
### 2.1 날개벽 연장 치수선 수평 방향 배치 (경사/기울어짐 방지)
- **수평 치수 정의**: 날개벽의 시작단면과 종점단면 사이의 수평 투영 거리($L_{horiz} = \sqrt{\Delta X^2 + \Delta Y^2}$)를 연장으로 정의.
- 종점부 좌측 날개벽: $L = 4.35\text{ m}$
- 종점부 우측 날개벽: $L = 5.40\text{ m}$
- **수평 치수선 기하 배치**:
- 기존의 천단 경사면을 따라 기울어지던 경사 치수선을 전면 수정하여, 시작단면과 종점단면의 양 끝점 $Z$ 좌표를 동일한 기준 높이($z_{dim} = \max(Z_{start}, Z_{end}) + 0.60\text{ m}$)로 완전 수평화.
- 보조선(Extension Line)은 각 단면 상단 모서리로부터 수직($+Z$ 방향)으로 올라와 수평 치수선과 직교하며, $0.15\text{ m}$ 오버슈트를 적용.
- 수평 치수선 양 끝단에는 직교 방향($+Z$) 끝단 틱 마커를 배치하여 측정 기준점을 명확히 정의.
### 2.2 치수 표기 방식 간소화 및 사각형 테이블 제거
- **사각형 테이블 박스 제거**: 치수값을 둘러싸고 있던 사각형 배경 필(fill)과 테두리(stroke) 박스를 완전히 삭제하고, 투명 캔버스에 순수 텍스트만 렌더링.
- **불필요한 설명 문구 삭제**: ‘날개벽 연장’, ‘시작단면’, ‘종점단면’, ‘00단면’ 등의 수식어 문구를 모두 삭제.
- **표준 간소화 포맷 적용**:
- **연장:** `L=00.0m` (예: `L=4.35m`, `L=5.40m`)
- **시작단면 높이:** `H=00.0m` (예: `H=3.70m`)
- **종점단면 높이:** `H=00.0m` (예: `H=1.50m`)
- 각 치수값은 해당 치수선(외측 0.32m 및 상단 0.28m) 중앙에 명확히 대응되도록 배치.
### 2.3 치수선 및 치수값 전원 흰색(White) 통일 적용
- **치수선/보조선/틱**: `THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 })`로 흰색 적용.
- **치수값 텍스트**: `#ffffff` 흰색 텍스트로 치수선과 시각적 일관성 확보.
- **가독성 외곽선 처리**: 3D 공간에서 배경(어두운 뷰포트) 및 모델(밝은 회색 콘크리트) 위에서도 흰색 글자가 또렷이 식별되도록 글자 자체에 미세하고 부드러운 다크 외곽선(`rgba(15, 23, 42, 0.90)`, `lineWidth: 3.5`)을 적용하여 사각형 박스 없이도 최고 수준의 가독성 확보.
### 2.4 기존 모델링 동작 및 가시성 보존
- 2단계 진입 시 및 암거 본체 선택 시 기존 정상 표시(100% 솔리드) 동작 보존.
- 날개벽 선택 시 해당 날개벽 진하게 강조(1.0 + Emissive glow), 타 구조물 흐리게(0.18 반투명) 동작 유지.
- 2단계에서 날개벽 선택 시 모델 상단 구조물 명칭("종점부 좌측 날개벽")은 치수선과의 겹침을 방지하기 위해 자동 숨김 처리되고, 깔끔한 흰색 치수선과 치수값만 표시됨.
---
## 3. 완료 검증 결과 대조
| 요구 항목 | 요구사항 내용 | 구현 및 동작 검증 결과 | 판정 |
|---|---|---|:---:|
| 1. 날개벽 치수 표시 & 강조/흐림 유지 | 2단계 날개벽 클릭 시 치수 표출, 선택 날개벽 진하게, 타 부재 흐리게 유지 | 선택 부재 solid/emissive 진하게, 비선택 부재 opacity 0.18 흐리게 정상 유지 | **PASS** |
| 2. 날개벽 연장 수평 치수선 | 경사 방향이 아닌 수평 방향으로 치수선 배치, 시작~종점 수평거리 산정 | $Z$ 높이 일치($\Delta Z = 0$) 수평 치수선 및 수평 투영거리($L=4.35\text{ m}, 5.40\text{ m}$) 표출 | **PASS** |
| 3. 사각형 테이블 삭제 | 치수값을 둘러싼 사각형 테이블(배경 박스 및 테두리) 삭제 | 투명 캔버스 적용, 배경 박스/테두리 완전 삭제 완료 | **PASS** |
| 4. 치수 설명 문구 삭제 | ‘날개벽 연장’, ‘시작단면’, ‘종점단면’ 등 문구 미표시 | 설명 문구 완전 삭제 | **PASS** |
| 5. 간소화 치수 포맷 | 연장: `L=00.0m`, 높이: `H=00.0m` 형식만 사용 | `L=4.35m`/`L=5.40m`, 시작단면 `H=3.70m`, 종점단면 `H=1.50m` 정확히 표출 | **PASS** |
| 6. 흰색 치수선 및 흰색 치수값 | 모든 치수선, 보조선, 틱 및 치수값을 흰색(`0xffffff`)으로 일관성 유지 | 치수선/보조선/틱 `0xffffff`, 치수값 `#ffffff` 흰색 통일 적용 | **PASS** |
| 7. 모델링 데이터 기반 산정 | 임의 고정값 배제, 실제 3D 모델링 좌표로부터 치수 산출 | `sections_3d` 실제 3D 정점 좌표로부터 `Math.hypot`, `Math.abs`로 동적 산출 | **PASS** |
---
## 4. Git 형상 관리 이력
- **커밋 해시**: [`ec50cf7`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing)
- **커밋 메시지**: `feat(viewer3d): 2단계 날개벽 치수선 수평화, 사각형 테이블 제거, 치수 표기 간소화(L=00.0m, H=00.0m) 및 흰색 적용`
- **단위 테스트**: `python test_pipeline.py` 6/6 PASS (`Ran 6 tests in 0.280s, OK`)
@@ -0,0 +1,60 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (상단바 S/W 제목 및 프로세스 텍스트 확대)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.06` | 2026-10-22 | 사용자 (Agent) | 공통 상단바(Header) UI 개선: 1) S/W 제목 텍스트(`h1`) 14px -> 21px (1.5배 확대) 및 서브타이틀 11px -> 16.5px (1.5배 확대), 2) 프로세스(Workflow Stepper) 단계 제목(`step-title`) 11px -> 16.5px (1.5배 확대) 및 서브설명(`step-sub`) 9px -> 13.5px (1.5배 확대), 3) 단계 번호 배지(28px) 및 헤더 높이(70px) 비율 균형 조정 |
| `v0.1.05` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 치수선 및 치수 표기 개선: 1) 날개벽 연장 치수선 수평 방향 배치, 2) 치수값 사각형 테이블 제거, 3) `L=00.0m`, `H=00.0m` 간소화 표기, 4) 치수선/값 흰색(White) 통일 |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현 (강조/흐림 대비 및 3대 치수 표출) |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어 구축 |
---
## 1. 개요 및 배경
- **요청 사항**: 공통 상단바의 S/W 제목과 프로세스의 텍스트를 기존 대비 **1.5배**로 확대
- **수정 대상 파일**:
- [`Code/3d-drawing/static/css/style.css`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/static/css/style.css)
- [`Code/3d-drawing/index.html`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/index.html)
- **작업 환경**: WSL2 (Ubuntu Linux), Python 3.14 (`~/.venv-3d-drawing`), Three.js WebGL
---
## 2. 세부 수정 내역
### 2.1 상단바 S/W 제목 텍스트 1.5배 확대
- **S/W 제목 (`.brand h1`)**:
- 기존: `font-size: 14px;`
- **수정**: `font-size: 21px;` (**1.5배 확대**)
- 줄간격 `line-height: 1.2` 및 자간 `-0.3px` 적용으로 가독성 강화.
- **S/W 부제목 (`.brand .header-subtitle`)**:
- 기존: 인라인 `font-size: 11px;`
- **수정**: `font-size: 16.5px;` (**1.5배 확대**)
- **로고 아이콘 (`.brand .logo-icon`)**:
- 기존 `32px × 32px` (폰트 15px)에서 확대된 제목과 어울리도록 `38px × 38px` (폰트 18px)로 비례 조정.
### 2.2 상단바 프로세스(Workflow Stepper) 텍스트 1.5배 확대
- **단계 메인 제목 (`.step-title`)**:
- 기존: `font-size: 11px;`
- **수정**: `font-size: 16.5px;` (**1.5배 확대**)
- **단계 보조 설명 (`.step-sub`)**:
- 기존: `font-size: 9px;`
- **수정**: `font-size: 13.5px;` (**1.5배 확대**)
- **단계 번호 원형 배지 (`.step-num`)**:
- 기존: `20px × 20px`, 폰트 `11px`
- **수정**: `28px × 28px`, 폰트 `15px`로 텍스트 확대에 맞춰 비례 조정.
- **단계 구분 화살표 (`.step-arrow`)**:
- 기존: `font-size: 11px;`
- **수정**: `font-size: 16.5px;` (**1.5배 확대**)
- **상단바 전체 높이 (`header`)**:
- 1.5배 확대된 텍스트와 프로세스 버튼이 여유 있게 배치되도록 `60px`에서 `70px`로 쾌적하게 확장.
---
## 3. 검증 및 형상 관리
- **단위 테스트**: `python test_pipeline.py` 6/6 PASS (`OK`)
- **Git 커밋**: [`145030c`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing) (`style(header): 공통 상단바 S/W 제목(21px) 및 프로세스(16.5px/13.5px) 텍스트 1.5배 확대`)
- **실서버 응답**: `http://localhost:5000` 정상 서비스 중 (HTTP 200 OK)
@@ -0,0 +1,76 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (3단계 W1 철근 3D 형상 수정)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.07` | 2026-10-22 | 사용자 (Agent) | 3단계 W1 철근 3D 형상 수정: 1) 벽체와 저판이 만나는 접합부(`h_foot_top`)에서 발생하던 불필요한 꺾임 정점 및 오프셋(`+0.06m`) 제거, 2) 벽체 구간(`u_stem_front`)의 기존 방향을 그대로 직선 연장하여 저판 하면(`z_foot_bot + 0.016`)까지 연결, 3) 저판 하면에서만 1회 꺾이는 순수 ㄱ자(L-shape, 3정점) 형상으로 모델링 및 상세도 SVG 동기화 |
| `v0.1.06` | 2026-10-22 | 사용자 (Agent) | 공통 상단바(Header) UI 개선: S/W 제목(`21px`) 및 프로세스(`16.5px`/`13.5px`) 텍스트 1.5배 확대 |
| `v0.1.05` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 치수선 및 치수 표기 개선: 연장 치수선 수평 배치, 사각형 테이블 제거, `L=00.0m`/`H=00.0m` 간소화 표기 및 흰색 통일 |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현 (강조/흐림 대비 및 3대 치수 표출) |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어 구축 |
---
## 1. 개요 및 배경
- **업무 명칭**: 3단계 철근 3D 모델링 수정 – **W1 철근** 형상 수정
- **문제 원인 분석**:
- 기존 [`rebar_model.py`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/rebar_model.py#L278-L285)에서 `w1_bars` 생성 시 4개의 정점(저판 하면 끝점 $\rightarrow$ 저판 하면 `u_stem_front + 0.06` $\rightarrow$ 벽체-저판 접합부 `h_foot_top`의 `u_stem_front` $\rightarrow$ 벽체 상단 `z_stem_top`의 `u_stem_front`)으로 구성되어 있었음.
- 이로 인해 벽체와 저판이 만나는 접합부(`h_foot_top`)에서 저판 하면으로 내려갈 때 `+0.06m`만큼 횡방향 편차가 발생하여 접합부에서 한 번 꺾이고 저판 하면에서 또 한 번 꺾이는 이중 꺾임 현상이 나타남.
- **수정 목표**:
- 벽체와 저판이 만나는 접합부(`h_foot_top`)의 불필요한 꺾임을 제거.
- 벽체 내부에 배근된 W1 철근의 긴 구간이 기존 방향(`u = sec['u_stem_front']`)을 그대로 유지한 직선으로 저판 하면까지 연장되도록 수정.
- 오직 **저판 하면(`z = sec['z_foot_bot'] + 0.016`)에서만 1회 꺾이는 ㄱ자 형태**로 구현하고, 타 철근은 일절 변경하지 않음.
---
## 2. 세부 수정 내역
### 2.1 W1 철근 3D 폴리라인(Polyline) 기하 수정 ([`rebar_model.py`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/rebar_model.py#L278-L285))
- **수정 전 (4정점, 2회 꺾임)**:
```python
w1_bars.append([
to_world(s, max(sec['u_toe_rebar'] + 0.02, sec['u_stem_front'] - 0.200), sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'] + 0.06, sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'], sec['h_foot_top']),
to_world(s, sec['u_stem_front'], sec['z_stem_top'])
])
```
- **수정 후 (3정점, 저판 하면 1회 꺾임 ㄱ자 형상)**:
```python
w1_bars.append([
to_world(s, max(sec['u_toe_rebar'] + 0.02, sec['u_stem_front'] - 0.200), sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'], sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'], sec['z_stem_top'])
])
```
- **정점 0 (저판 하면 끝단)**: 기존과 동일한 저판 하면 정착부 위치 유지 (`z = sec['z_foot_bot'] + 0.016`).
- **정점 1 (저판 하면 유일 꺾임점)**: 벽체 내부 배근선(`u = sec['u_stem_front']`)이 직선 그대로 내려와 저판 하면(`z = sec['z_foot_bot'] + 0.016`)과 만나는 지점.
- **정점 2 (벽체 상단 끝단)**: 기존과 동일한 벽체 상단 위치 유지 (`u = sec['u_stem_front']`, `z = sec['z_stem_top']`).
- 접합부(`h_foot_top`) 중간 정점과 `+0.06` 오프셋을 제거하여 벽체 상단부터 저판 하면까지 **완벽한 단일 직선**으로 연결됨.
### 2.2 우측 상세 정보 창 W1 형상 다이어그램 및 번들 데이터 동기화
- [`app.js`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/static/js/app.js#L1273-L1284)의 `STEM_VERTICAL_FRONT` SVG 다이어그램도 1회만 꺾인 직선 ㄱ자 형태(`points="85,105 155,105 155,20"`)로 일치화.
- `bundle_data.py`를 재실행하여 [`static/data/model_3d.js`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/static/data/model_3d.js) 및 오프라인 번들 데이터를 갱신하고, WSL2 Flask 서버를 재기동하여 메모리 내 모델을 즉시 갱신.
---
## 3. 완료 검증 결과 대조
| 요구 항목 | 요구사항 내용 | 구현 및 동작 검증 결과 | 판정 |
|---|---|---|:---:|
| 1. 접합부 꺾임 제거 | 벽체와 저판이 만나는 접합부에서 철근이 꺾이지 않도록 수정 | `h_foot_top` 중간 정점 및 `+0.06` 편차 제거 완료 | **PASS** |
| 2. 벽체 구간 직선 연장 | 벽체 내부 긴 부분의 기존 방향을 유지하여 직선으로 연장 | `u = sec['u_stem_front']` 일직선 유지 (`poly[1]`~`poly[2]` 수평 편차 0.0000m) | **PASS** |
| 3. 저판 하면 1회 꺾임 | 저판 하면에서만 한 번 꺾이는 ㄱ자 형태로 모델링 | 총 3개 정점(2개 직선 선분, 저판 하면 1회 꺾임) 단위 테스트 검증 완료 | **PASS** |
| 4. 저판 하면 구간 유지 | 저판 하면에 배근되는 철근 부분은 현재와 같이 유지 | 저판 하면 표고(`z_foot_bot + 0.016`) 및 앞굽 정착 길이 동일 유지 | **PASS** |
| 5. 타 철근 형상 보존 | 다른 철근(C1~C2, F1~F9, K1~K2, S1~S3, W2~W9)은 변경하지 않음 | 타 철근 코드 및 좌표 100% 보존 (전체 508본 수량/중량 동일) | **PASS** |
---
## 4. Git 형상 관리 이력
- **커밋 해시**: [`af65700`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing)
- **커밋 메시지**: `fix(rebar): 3단계 W1 철근 벽체-저판 접합부 불필요 꺾임 제거 및 저판 하면 1회 꺾임(ㄱ자 형상) 수정`
- **단위 테스트**: `python test_pipeline.py` 6/6 PASS (`Ran 6 tests in 0.283s, OK`)
@@ -0,0 +1,50 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (전체 코드 리팩토링 및 최적화)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.08` | 2026-10-22 | 사용자 (Agent) | 전체 코드 리팩토링(Refactoring): 1) 기존 기능·동작 로직 100% 동일 유지, 2) Three.js 치수선/텍스트 등록 로직 공통 헬퍼(`_renderDimensionEntry`) 추출, 3) Flask 분류 처리 공통 헬퍼(`_resolve_classification`) 추출 및 업로드 임시 파일 안전 해제(`try...finally`), 4) 미사용 import(`json`, `sys` 등) 및 미사용 변수(`is_start` 등) 제거, 5) 단위 테스트 ResourceWarning 해제 및 전 항목 PASS 검증 |
| `v0.1.07` | 2026-10-22 | 사용자 (Agent) | 3단계 W1 철근 3D 형상 수정: 벽체-저판 접합부 불필요 꺾임 제거 및 저판 하면 1회 꺾임(ㄱ자 형상) 수정 |
| `v0.1.06` | 2026-10-22 | 사용자 (Agent) | 공통 상단바 UI 개선: S/W 제목(21px) 및 프로세스(16.5px/13.5px) 텍스트 1.5배 확대 |
| `v0.1.05` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 치수선 및 치수 표기 개선 |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현 |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어 구축 |
---
## 1. 리팩토링 개요 및 목적
- **원칙 준수**: 기존 모든 기능, API 인터페이스, 3D 모델링 기하학, UI 상호작용 로직은 **100% 동일하게 유지** (동작 변경 일절 없음).
- **공통화 (DRY)**: 중복 생성되던 치수선 LineSegments 및 텍스트 Sprite 씬 등록 로직, Flask 도면 분류 로직 헬퍼화.
- **클린 코드**: 미사용 모듈 import, 미사용 변수 제거 및 파일 핸들/임시 파일 리소스 누수 방지.
---
## 2. 세부 개선 내역
### 2.1 3D 웹 뷰어 치수선 공통화 ([`viewer3d.js`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/static/js/viewer3d.js#L890-L935))
- `createHorizontalDimension3D`와 `createDimension3D`에서 중복되던 BufferGeometry, LineSegments 생성, 재질 설정(`0xffffff`), 렌더 오더 부여 및 Sprite 배치 로직을 `_renderDimensionEntry(linePoints, spritePos, text)` 공통 헬퍼로 통합.
- 중복 코드 약 30라인 감소 및 치수선 속성 수정 시 단일 진실 공급원(Single Source of Truth) 확보.
### 2.2 Flask 백엔드 서버 로직 정리 ([`app.py`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/app.py#L1-L150))
- **미사용 모듈 제거**: 모듈 레벨의 미사용 `import json` 제거.
- **도면 분류 헬퍼 공통화**: `_resolve_classification(drawing_name, manual_type)` 헬퍼 함수를 신설하여 도면 분류 및 수동 오버라이드 객체 생성 로직 캡슐화.
- **임시 파일 안전 해제**: `api_upload` 처리 중 업로드된 임시 파일이 디스크에 잔류하지 않도록 `finally` 구문에서 안전하게 자동 삭제(Unlink) 처리.
### 2.3 3D 솔리드 모델러 정리 ([`model_3d.py`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/model_3d.py#L1-L130))
- 미사용 `import json` 및 `get_wingwall_mesh` 내부의 미사용 로컬 변수 `is_start` 정리.
- 필요한 경우 CLI 진입점(`if __name__ == '__main__':`) 내부에서만 모듈 로컬 임포트 수행.
### 2.4 CAD 파서 및 테스트 파이프라인 정리 ([`cad_parser.py`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/cad_parser.py), [`test_pipeline.py`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing/test_pipeline.py))
- `cad_parser.py`: 미사용 `import json` 제거.
- `test_pipeline.py`: 미사용 `import sys` 제거, Flask `send_file('index.html')` 응답 객체의 명시적 `close()` 호출로 테스트 실행 시 출력되던 `ResourceWarning` 완전 해제 (경고 0건, 클린 패스).
---
## 3. 검증 및 형상 관리
- **단위 테스트**: `python test_pipeline.py` **6/6 PASS** (경고 0건, `Ran 6 tests in 0.304s, OK`)
- **Git 커밋**: [`3cc9afe`](file:///e:/11.%202026%20PROJECT/30%20도면을%203D로%20구현/01%20대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing) (`refactor: 전체 코드 정리, 중복 로직 헬퍼 공통화, 미사용 임포트/변수 제거 및 리소스 정리`)
- **실서버 응답**: `http://localhost:5000` (HTTP 200 OK 정상 서비스 중)
@@ -0,0 +1,42 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (GitHub 원격 저장소 연동 및 푸시 완료)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.09` | 2026-10-22 | 사용자 (Agent) | GitHub 원격 저장소 연동 완료: 1) SSH 키 페어 생성 및 GitHub 호스트 인증 완료, 2) 원격 저장소(`git@github.com:sangwogi-art/wingwall-3d-drawing.git`) 등록, 3) 기본 브랜치 `main` 설정 및 전체 코드/문서 푸시(`git push -u origin main`) 완료 |
| `v0.1.08` | 2026-10-22 | 사용자 (Agent) | 전체 코드 리팩토링 및 최적화 (공통 헬퍼 분리, 미사용 import/변수 제거, 테스트 경고 0건 클린 패스) |
| `v0.1.07` | 2026-10-22 | 사용자 (Agent) | 3단계 W1 철근 3D 형상 수정: 벽체-저판 접합부 불필요 꺾임 제거 및 저판 하면 1회 꺾임(ㄱ자 형상) 수정 |
| `v0.1.06` | 2026-10-22 | 사용자 (Agent) | 공통 상단바 UI 개선: S/W 제목(21px) 및 프로세스(16.5px/13.5px) 텍스트 1.5배 확대 |
| `v0.1.05` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 치수선 및 치수 표기 개선 |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현 |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어 구축 |
---
## 1. 개요 및 배경
- **GitHub 저장소**: [`https://github.com/sangwogi-art/wingwall-3d-drawing`](https://github.com/sangwogi-art/wingwall-3d-drawing)
- **원격 프로토콜**: SSH (`git@github.com:sangwogi-art/wingwall-3d-drawing.git`)
- **브랜치 명칭**: `main` (추적 브랜치: `origin/main`)
---
## 2. 작업 수행 내역
1. **SSH 보안 인증 구축**:
- `ed25519` 기반 SSH 비대칭 키 페어 생성 및 GitHub 계정(`sangwogi-art`) 인증 성공 확인.
2. **원격 저장소(Remote Origin) 바인딩**:
- 원격 저장소 URL 등록: `git remote add origin git@github.com:sangwogi-art/wingwall-3d-drawing.git`
3. **전체 프로젝트 형상 푸시**:
- 로컬 `master` 브랜치를 표준 `main` 브랜치로 전환.
- 전체 소스 코드(Three.js 3D 뷰어, Flask 백엔드, DWG CAD 파서, 3D 외형·철근 배근 엔진) 및 기획/분석/설계/실행 보고서 문서(`DOC/`)를 포함한 총 12개 커밋 이력을 GitHub 원격 저장소로 최초 푸시 완료 (`git push -u origin main`).
---
## 3. 원격 저장소 동기화 상태
- **GitHub Repository URL**: [https://github.com/sangwogi-art/wingwall-3d-drawing](https://github.com/sangwogi-art/wingwall-3d-drawing)
- **로컬 상태**: `Your branch is up to date with 'origin/main'. nothing to commit, working tree clean`
- **최신 커밋**: [`2302a54`](https://github.com/sangwogi-art/wingwall-3d-drawing/commit/2302a54) (`docs: 기획, 분석, 설계 및 v0.1.00~v0.1.08 실행 보고서 문서 추가`)
@@ -0,0 +1,42 @@
# [실행] 2D CAD 도면 기반 3D 모델 변환 및 웹 뷰어 표출 시스템 구축 (원클릭 실행 스크립트 및 바로가기 링크 생성)
| 버전 | 일자 | 작성자 | 변경 요약 |
| :--- | :--- | :--- | :--- |
| `v0.1.10` | 2026-10-22 | 사용자 (Agent) | Windows 원클릭 실행기 및 웹 브라우저 바로가기 생성: 1) `[실행]_암거및날개벽_3D외형배근모델러.bat` 스크립트 작성(서버 동작 감지, WSL2/Python 자동 기동, 브라우저 자동 오픈), 2) `[실행바로가기]_암거및날개벽_3D외형배근모델러.lnk` 윈도우 단축아이콘 생성, 3) `[바로가기]_3D모델러_웹접속.url` 웹 바로가기 생성, 4) GitHub 원격 저장소 동기화 |
| `v0.1.09` | 2026-10-22 | 사용자 (Agent) | GitHub 원격 저장소 연동 완료 (`git@github.com:sangwogi-art/wingwall-3d-drawing.git`) |
| `v0.1.08` | 2026-10-22 | 사용자 (Agent) | 전체 코드 리팩토링 및 최적화 (공통 헬퍼 분리, 미사용 import/변수 제거, 테스트 경고 0건 클린 패스) |
| `v0.1.07` | 2026-10-22 | 사용자 (Agent) | 3단계 W1 철근 3D 형상 수정: 벽체-저판 접합부 불필요 꺾임 제거 및 저판 하면 1회 꺾임(ㄱ자 형상) 수정 |
| `v0.1.06` | 2026-10-22 | 사용자 (Agent) | 공통 상단바 UI 개선: S/W 제목(21px) 및 프로세스(16.5px/13.5px) 텍스트 1.5배 확대 |
| `v0.1.05` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 치수선 및 치수 표기 개선 |
| `v0.1.04` | 2026-10-22 | 사용자 (Agent) | 2단계 날개벽 선택 및 3D 치수 표출 구현 |
| `v0.1.03` | 2026-10-22 | 사용자 (Agent) | 2단계 일반도 기반 3D 외형 모델링 UI 개선 |
| `v0.1.02` | 2026-10-22 | 사용자 (Agent) | 3단계 배근도 기반 3D 철근배근 UI 및 상호작용 개선 |
| `v0.1.01` | 2026-10-22 | 사용자 (Agent) | 3단계 UI 개편: 세로 2단 분할 레이아웃 적용 |
| `v0.1.00` | 2026-10-21 | 사용자 (Agent) | 최초 작성: 2D DWG 도면 3D 변환 파이프라인, Three.js 웹 뷰어 구축 |
---
## 1. 개요 및 목적
사용자가 터미널 명령어 입력 없이 Windows 탐색기에서 더블클릭 한 번으로 3D CAD/BIM 모델러 프로그램 백엔드를 자동 점검·구동하고 웹 브라우저(`http://localhost:5000`)를 즉시 열어볼 수 있는 원클릭 실행 파일 및 바로가기 링크를 제공합니다.
---
## 2. 생성된 실행 파일 및 바로가기 구성
1. **`[실행]_암거및날개벽_3D외형배근모델러.bat`**:
- 포트 `5000`의 수신 대기(Listening) 상태를 우선 검사
- 이미 서버가 구동 중인 경우: 즉시 기본 웹 브라우저(`http://localhost:5000/`)를 실행하고 종료
- 서버가 정지된 경우: WSL2 Ubuntu 가상환경(또는 Windows Python 환경)에서 백그라운드 서버를 자동 기동 후 브라우저 오픈
- UTF-8 인코딩 및 안전한 딜레이 처리 적용
2. **`[실행바로가기]_암거및날개벽_3D외형배근모델러.lnk`**:
- Windows 셸 단축 아이콘으로 더블클릭 시 위 배치 실행기를 호출
3. **`[바로가기]_3D모델러_웹접속.url`**:
- 웹 서버가 동작 중일 때 원클릭으로 웹 브라우저 3D 뷰어 창을 띄우는 인터넷 바로가기
---
## 3. 원격 저장소 동기화 상태
- **GitHub Repository**: [`https://github.com/sangwogi-art/wingwall-3d-drawing`](https://github.com/sangwogi-art/wingwall-3d-drawing)
- **최신 커밋**: 원클릭 실행 배치 스크립트 및 바로가기 파일 등록 완료
+199 -52
View File
@@ -1,71 +1,218 @@
# 2D 도면 기반 3D 수로암거 및 옹벽형 날개벽 모델링 웹 시스템 (Code/3d-drawing)
# 2D CAD 기반 암거 및 날개벽 3D 외형·배근 자동 모델링 시스템
### (Automated 3D Solid & Rebar Modeling System for Culverts and Wingwalls)
2D CAD 도면(일반도 및 구조도/배근도 DWG/DXF 5종)을 분석하여 수로암거(`STA.13+611.21`) 및 옹벽형 날개벽의 3D 솔리드 외형과 3D 내부 철근 배근(19종 508본)을 자동 구축하고, Three.js 기반의 웹 뷰어를 통해 인터랙티브하게 확인·검증·내보내기하는 시스템입니다.
> **토목 구조물의 2D CAD 도면(DWG/DXF)을 자동 파싱하여 암거 본체와 날개벽의 3D 외형 솔리드(Solid Mesh)를 구축하고, 내부 철근 배근을 3차원 공간에 자동 모델링하여 BIM 데이터와 물량표를 산출하는 범용 웹 기반 엔지니어링 시스템입니다.**
---
## 1. 주요 특징 및 구현 내용
- **1단계: 도면 불러오기 및 도면 종류 판별**
- DWG/DXF 파일 파싱 (`ezdwg`, `ezdxf`)
- 도면명 키워드 자동 판별: `'일반'` ➔ 일반도, `'배근'`/`'구조'` ➔ 구조도/배근도
- 2D CAD 벡터 뷰어 및 레이어 제어 지원
- **2단계: 일반도 기반 3D 외형 모델링**
- 암거 본체 및 날개벽 위치 판별 (시점부 좌/우, 종점부 좌/우)
- 본 도면 특성 완벽 준수: 시점부 미표기 ➔ 임의 생성 배제 / 종점부 좌·우 2개소 모델링 ($L=4.35\text{m}$, $L=5.40\text{m}$)
- 완전 폐합 2-Manifold 수밀성(Watertight) 3D 솔리드 메시 생성 (총 콘크리트 체적: $75.45\text{ m}^3$)
- **3단계: 배근도 기반 3D 철근 배근 모델링**
- 피복두께 80mm 준수
- 19종 철근 기호, 총 508본 3D 입체 배근 (종점 좌 231본 + 종점 우 277본)
- 철근 재료표(BBS) 자동 산출 (총중량: $1.543\text{ TON}$, 할증 포함 $1.590\text{ TON}$)
- **웹 3D 뷰어 (Three.js)**
- 3차원 자유 시점 궤도 제어 (OrbitControls)
- 암거 본체 / 날개벽 / 철근 레이어별 가시화 및 투명도 제어
- 3D 모델 OBJ, STL, DXF 내보내기
## 📌 목차
1. [시스템 개요](#1-시스템-개요)
2. [전체 프로세스 파이프라인](#2-전체-프로세스-파이프라인)
3. [단계별 상세 기능 및 알고리즘 메커니즘](#3-단계별-상세-기능-및-알고리즘-메커니즘)
- [1단계: 도면 불러오기 및 도면 종류 판별](#1단계-도면-불러오기-및-도면-종류-판별)
- [2단계: 일반도 기반 암거 및 날개벽 3D 외형 모델링](#2단계-일반도-기반-암거-및-날개벽-3d-외형-모델링)
- [3단계: 구조도 기반 날개벽 내부 3D 철근 배근 및 물량 집계](#3단계-구조도-기반-날개벽-내부-3d-철근-배근-및-물량-집계)
- [4단계: BIM 데이터 및 철근 가공조립표 내보내기](#4단계-bim-데이터-및-철근-가공조립표-내보내기)
4. [화면 조작 인터랙션 가이드](#4-화면-조작-인터랙션-가이드)
5. [설치 및 실행 방법](#5-설치-및-실행-방법)
6. [프로젝트 디렉터리 구조](#6-프로젝트-디렉터리-구조)
7. [자동화 파이프라인 검증 체계](#7-자동화-파이프라인-검증-체계)
---
## 2. 작업 환경 및 디렉터리 구성
## 1. 시스템 개요
- **작업 환경**: WSL2 (Ubuntu Linux)
- **저장소 위치**: `Code/3d-drawing/` (Git 관리)
본 시스템은 특정 단일 구조물에 한정되지 않고, 다양한 암거 및 날개벽 도면을 범용적으로 수용하여 3D 디지털 모델로 변환할 수 있도록 설계된 자동화 소프트웨어입니다.
- **도면 처리 범위**: 암거 일반도(형상·치수) 및 구조도/배근도(철근 제원·상세) DWG/DXF 다중 처리
- **핵심 목표**:
1. 2D 벡터 도면 엔티티에서 형상 파라미터 및 치수 정보 자동 추출
2. 암거 사각(Skew), 내공 단면, 날개벽 설치 각도 및 경사 변화를 반영한 수밀 솔리드(Watertight Solid) 메시 생성
3. 피복두께를 엄격히 준수한 3차원 철근 중심선(Polyline) 공간 배치 및 규격별 물량 자동 산출
4. 웹 기반 무설치/로컬 구동 지원 및 개방형 BIM 포맷(OBJ, STL, DXF, CSV) 상호운용성 제공
---
## 2. 전체 프로세스 파이프라인
```mermaid
flowchart TD
subgraph Step1["1단계: 도면 입력 및 분류 (Drawing Intake & Classification)"]
DWG["2D CAD 도면군 (DWG/DXF)"] --> AutoClassify["키워드 기반 도면 자동 분류<br>(일반도 / 구조도 / 미분류)"]
AutoClassify --> VectorViewer["2D CAD 벡터 엔진 렌더링<br>(도면 식별 및 레이어 검토)"]
end
subgraph Step2["2단계: 3D 외형 솔리드 모델링 (Solid Geometry Generation)"]
VectorViewer --> CulvertExtract["암거 본체 기하 자동 인식<br>(내공 스팬, 폭, 높이, 사각)"]
CulvertExtract --> WingwallExtract["날개벽 배치 판별 및 단면 보간<br>(시점/종점 좌·우 날개벽 형상 생성)"]
WingwallExtract --> MeshGen["완전 폐합 2-Manifold 메시 생성<br>(콘크리트 체적 실시간 적분)"]
end
subgraph Step3["3단계: 3D 철근 배근 및 물량 산출 (Rebar BIM Modeling)"]
MeshGen --> RebarExtract["재료표(BBS) 및 단면 배근 규칙 파싱"]
RebarExtract --> RebarGen["부재별 3D 공간 철근 폴리라인 생성<br>(설계 피복두께 자동 옵셋 적용)"]
RebarGen --> RebarBOM["철근 가공조립표 및 총중량 집계<br>(본수, 연장, 규격별 중량 산출)"]
end
subgraph Step4["4단계: 3D 인터랙션 및 BIM 내보내기 (Interoperability)"]
RebarGen --> Interactive3D["Three.js 기반 3D 웹 뷰어 표출<br>(부재별 격리, 텍스트 라벨링)"]
Interactive3D --> Export["BIM 엔지니어링 포맷 내보내기<br>(OBJ / STL / 3D DXF / CSV / JSON)"]
end
```
---
## 3. 단계별 상세 기능 및 알고리즘 메커니즘
### 1단계: 도면 불러오기 및 도면 종류 판별
다양한 프로젝트의 도면 파일을 수용할 수 있도록 유연한 업로드 파이프라인과 규칙 기반 자동 분류기를 제공합니다.
#### 1) 도면 자동 판별 알고리즘
- **일반도 자동 분류**: 파일명에 `'일반'` 키워드가 포함된 경우 구조물 외형 및 치수를 표현하는 일반도(`GENERAL`)로 자동 분류합니다.
- **배근도/구조도 자동 분류**: 파일명에 `'배근'` 또는 `'구조'` 키워드가 포함된 경우 내부 철근 배치 정보를 담은 구조도(`REBAR`)로 자동 분류합니다.
- **미분류 및 대화형 수동 지정**: 도면명 규칙이 상이하여 키워드가 검출되지 않는 도면(`UNCLASSIFIED`)은 사용자에게 수동 분류 선택 UI를 제공하여 즉시 지정할 수 있습니다.
#### 2) 2D CAD 벡터 뷰어
- 업로드된 DWG/DXF 파일의 벡터 엔티티(LINE, LWPOLYLINE, ARC, TEXT, DIMENSION 등)를 브라우저 Canvas에 정밀 투영합니다.
- 도면 레이어별 On/Off 제어, 마우스 휠 줌(Zoom) 및 드래그 팬(Pan)을 통해 입력 도면의 원본 상태를 시각적으로 검증할 수 있습니다.
---
### 2단계: 일반도 기반 암거 및 날개벽 3D 외형 모델링
일반도의 평면도 및 단면도를 분석하여 암거와 날개벽의 기하학적 형태를 3차원 솔리드 메시로 생성합니다.
#### 1) 암거 본체 기하 자동 인식
- 평면도 내의 중심 기준선 및 콘크리트 외곽선, 점선 레이어 패턴을 분석하여 암거 본체의 **내공 스팬(Span), 형하고(Clear Height), 본체 외폭, 암거 사각(Skew Angle), 연장(Length)**을 자동으로 추출합니다.
#### 2) 날개벽 위치 판별 및 동적 위상 제어
- 암거 본체를 기준으로 4개 영역(시점부 좌/우, 종점부 좌/우)의 날개벽 배치 상태를 판별합니다.
- **도면 기반 모델링 원칙**:
- 도면에 표기된 날개벽 위치는 누락 없이 정밀 모델링합니다.
- 도면에 표기되지 않은 위치는 임의 생성하지 않고 모델링에서 제외합니다.
- 다양한 현장 조건(양측 날개벽, 일측 날개벽, 무날개벽 등)을 웹 UI 제어기를 통해 실시간으로 선택 및 시뮬레이션할 수 있습니다.
#### 3) 단면 보간 및 완전 폐합 수밀 메시(Watertight Solid) 생성
- 날개벽의 시점부 단면(암거 접합부)과 종점부 단면(천단부)의 기하 치수(벽체 두께, 높이, 저판 폭, 헌치 등)를 선형/곡선 가변 보간(Lofting)합니다.
- 3D 프린팅 및 BIM 솔리드 기준을 만족하는 **Open Edge 0건의 2-Manifold 방수(Watertight) 메시**를 구축하고, 미소 삼각 요소를 수치 적분하여 **콘크리트 총체적(Volume)**을 실시간 자동 계산합니다.
---
### 3단계: 구조도 기반 날개벽 내부 3D 철근 배근 및 물량 집계
구조도에서 추출된 철근 가공조립표(Bar Bending Schedule) 및 단면 배근도를 3차원 공간 좌표계로 투영하여 정밀 배근 모델을 구축합니다.
#### 1) 철근 카테고리별 3차원 공간 배치 알고리즘
- **우각부 사각 보강근**: 암거 본체와 날개벽이 접하는 우각부의 응력 집중을 방어하는 사각 절곡 정착근을 경사 각도에 맞춰 3D 배치합니다.
- **벽체 수직 주철근**: 저판 기초 내부 정착 후크를 포함한 수직 주철근을 피복두께를 고려하여 지정 간격 및 교대 사이클에 맞춰 입체 배열합니다.
- **벽체 수평 배력철근**: 날개벽 경사 변화에 맞춰 하부 연속 구간과 상부 가변 구간, 천단 경사 연단근을 단계별로 가변 배치합니다.
- **저판 주철근 및 배력철근**: 저판 폭 변화 및 구배에 맞추어 상·하면 주철근(양단 후크 포함)과 종방향 배력철근을 입체 구성합니다.
- **헌치 및 전단 보강근**: 벽체와 저판이 만나는 취약부 헌치 보강근 및 결속 타이바/스터럽을 자동 생성합니다.
#### 2) 피복두께 준수 및 물량 자동 산출
- 콘크리트 외형 표면으로부터 법선 방향으로 **설계 피복두께(Cover Thickness)**를 엄격히 감산하여 철근이 외형 밖으로 돌출되지 않도록 기하학적 제약 조건을 적용합니다.
- 배근된 모든 철근의 규격(D13, D16, D19, D22 등), 본수, 본당 길이, 할증률을 반영한 **총연장(Total Length) 및 총중량(Total Weight)**을 실시간 집계합니다.
---
### 4단계: BIM 데이터 및 철근 가공조립표 내보내기
생성된 3D 모델과 물량 데이터를 외부 상용 CAD, BIM 소프트웨어, 견적 프로그램과 연계할 수 있는 표준 규격으로 내보냅니다.
| 포맷 | 파일 유형 | 호환성 및 활용 분야 |
| :--- | :--- | :--- |
| **OBJ** | 3D 형상 + 철근 폴리라인 | Blender, 3ds Max, Rhino 등 범용 3D 그래픽 및 시각화 도구 |
| **STL** | 삼각망 수밀 솔리드 | 3D 프린팅 슬라이서, 지반-구조 상호작용 수치해석 프로그램 |
| **3D DXF** | AutoCAD 3D Face / Polyline | AutoCAD, Civil 3D 등 CAD 프로그램에서 레이어별 3D 검토 |
| **CSV** | 철근 가공조립표 (BBS) | Excel 등 스프레드시트 호환 규격별·부재별 철근 집계표 |
| **JSON** | 기하 및 속성 메타데이터 | 웹 API 연동, 외부 BIM 플랫폼 커스텀 데이터 파이프라인 |
---
## 4. 화면 조작 인터랙션 가이드
웹 브라우저 환경에서 엔지니어가 3D 모델을 직관적으로 검토할 수 있는 다양한 상호작용 기능을 제공합니다.
### 1) 3D 뷰포트 카메라 제어
- **회전 (Orbit)**: 마우스 좌클릭 드래그로 모델 주위를 자유롭게 회전합니다.
- **이동 (Pan)**: 마우스 우클릭 드래그(또는 Shift + 좌클릭)로 카메라 시점을 수평/수직 이동합니다.
- **확대/축소 (Zoom)**: 마우스 휠 스크롤로 관심 영역을 확대하거나 축소합니다.
### 2) 부재 및 철근 상호작용
- **부재 선택 (2단계)**: 암거 본체 또는 좌/우 날개벽을 직접 클릭하면 해당 부재가 강조 표시되며 상세 치수 카드가 나타납니다.
- **철근 격리 표출 (3단계 Isolate Mode)**: 좌측 사이드바에서 특정 철근 기호를 선택하면 해당 철근군만 선명하게 단독 표출되고, 타 철근 및 콘크리트 외형은 반투명 고스트(Ghost) 모드로 전환되어 간섭 여부를 즉시 확인할 수 있습니다.
- **거리 비례 3D 라벨링**: 줌 인/아웃 시 카메라 거리에 비례하여 철근 기호 텍스트의 크기가 자동으로 보정되어 항상 선명한 가독성을 유지합니다.
---
## 5. 설치 및 실행 방법
### 환경 요구사항
- **Python**: 3.10 이상 권장
- **웹 브라우저**: WebGL 2.0을 지원하는 최신 브라우저 (Chrome, Edge, Firefox, Safari)
### 의존 패키지 설치
```bash
pip install -r requirements.txt
```
---
### 실행 모드 (3가지 방식 지원)
#### [모드 1] 로컬 웹 서버 실행 (권장)
- 백엔드 서버를 구동하여 동적 도면 업로드, 실시간 3D 변환, 파일 내보내기 전체 기능을 사용합니다:
```powershell
python app.py
```
- 구동 후 브라우저에서 `http://127.0.0.1:5000/` 에 접속합니다.
#### [모드 2] 독립형 정적 브라우저 실행 (서버 불필요)
- [`index.html`](index.html) 파일을 웹 브라우저로 직접 열어 실행합니다.
- `static/data/`의 번들 데이터를 활용하여 서버 설치 없이 오프라인 환경에서도 1~3단계 3D 뷰어와 인터랙션을 시연할 수 있습니다.
#### [모드 3] Linux / WSL2 환경 실행
- Linux 및 WSL2 환경에서는 제공된 쉘 스크립트로 간편하게 구동할 수 있습니다:
```bash
bash run_wsl.sh
```
---
## 6. 프로젝트 디렉터리 구조
```text
Code/3d-drawing/
├── cad_parser.py # 2D DWG/DXF 도면 파싱 및 종류 판별
├── model_3d.py # 3D 수로암거 및 날개벽 파라메트릭 솔리드 모델러
├── rebar_model.py # 3D 철근 배근 생성기 (19종 기호, 508본)
├── exporters.py # OBJ / STL / DXF 3D 파일 내보내기 모듈
├── bundle_data.py # 도면 및 3D 데이터 번들링 유틸리티
├── app.py # Flask 웹 REST API 서버
├── index.html # Three.js 기반 3D CAD/BIM 웹 뷰어
├── static/ # CSS, JS(app.js, viewer3d.js), Three.js 라이브러리 및 데이터
├── run_wsl.sh # WSL2 원클릭 실행 스크립트
├── test_pipeline.py # 파이프라인 전수 자동화 검증 테스트
├── requirements.txt # Python 패키지 의존성
└── README.md # 본 설명서
├── app.py # Flask 백엔드 REST API 서버
├── cad_parser.py # ezdwg 기반 DWG/DXF 파서 및 자동 분류기
├── model_3d.py # 암거 및 날개벽 3D 외형 솔리드 모델러
├── rebar_model.py # 구조도 기반 3D 철근 배근 및 물량 산출 엔진
├── exporters.py # OBJ / STL / 3D-DXF / CSV 내보내기 변환기
├── bundle_data.py # 서버리스 정적 구동용 데이터 번들러
├── index.html # Three.js 프론트엔드 단일 페이지 웹 애플리케이션 (SPA)
├── requirements.txt # 필수 파이썬 라이브러리 목록
├── static/
│ ├── css/ # 반응형 UI 레이아웃 스타일시트
│ ├── js/ # 3D 렌더러, 2D 벡터 캔버스, UI 상태 관리 스크립트
│ ├── libs/ # Three.js 및 컨트롤러 라이브러리
│ └── data/ # 정적 오프라인 구동용 번들 데이터
├── DOC/ # 개발 및 기능 검증 기술 문서
└── README.md # 본 시스템 종합 명세서
```
---
## 3. WSL2 실행 방법
## 7. 자동화 파이프라인 검증 체계
WSL2(Ubuntu) 터미널에서 아래 명령을 실행합니다:
본 시스템은 다양한 도면 변환 작업의 정확성을 유지하기 위해 자동화된 검증 파이프라인을 내장하고 있습니다.
### 테스트 실행
```bash
cd Code/3d-drawing
chmod +x run_wsl.sh
./run_wsl.sh
python test_pipeline.py
```
실행 후 웹 브라우저에서 아래 주소로 접속합니다:
- **`http://localhost:5000`** 또는 **`http://127.0.0.1:5000`**
---
## 4. 자동 테스트 및 검증 실행
```bash
cd Code/3d-drawing
~/.venv-3d-drawing/bin/python test_pipeline.py
```
### 주요 검증 항목
1. **도면 분류 무결성**: 다양한 도면명 키워드 패턴에 대한 자동 분류 정확도 및 예외 도면 처리 검증
2. **외형 솔리드 수밀성**: 3D 메시의 비공유 변(Open Edge) = 0, Non-manifold 요소 = 0 무결성 검증
3. **철근 배근 정밀도**: 철근 재료표 수량과 3D 생성 인스턴스 간의 본수, 길이, 중량 100% 일치성 검증
4. **엔지니어링 내보내기 검증**: 생성된 OBJ, STL, DXF, CSV 파일의 문법 및 외부 CAD 호환성 검증
+33 -23
View File
@@ -1,5 +1,4 @@
import os
import json
import tempfile
from flask import Flask, request, jsonify, send_file, Response
from flask_cors import CORS
@@ -17,6 +16,27 @@ current_drawing_data = None
current_model_data = None
def _resolve_classification(drawing_name, manual_type=None):
"""도면명 기반 분류 및 사용자 수동 오버라이드 객체 생성 헬퍼"""
res = cad_parser.classify_drawing_by_name(drawing_name)
selected_type = manual_type if manual_type in ('GENERAL', 'REBAR') else res['auto_type']
label_map = {
'GENERAL': '일반도',
'REBAR': '배근도/구조도',
'UNCLASSIFIED': '미분류 (수동 선택 필요)'
}
return {
'autoType': res['auto_type'],
'autoLabel': res['auto_label'],
'matchedKeyword': res['matched_keyword'],
'reason': res['reason'],
'needsManualSelection': res['needs_manual_selection'],
'selectedType': selected_type,
'selectedLabel': label_map.get(selected_type, res['auto_label']),
'isManualOverride': bool(manual_type and manual_type != res['auto_type'])
}
@app.route('/')
def index():
return send_file('index.html')
@@ -62,24 +82,7 @@ def api_classify():
drawing_name = body.get('drawingName', '')
manual_type = body.get('manualType') # Optional: 'GENERAL' or 'REBAR'
res = cad_parser.classify_drawing_by_name(drawing_name)
selected_type = manual_type if manual_type in ('GENERAL', 'REBAR') else res['auto_type']
label_map = {
'GENERAL': '일반도',
'REBAR': '배근도/구조도',
'UNCLASSIFIED': '미분류 (수동 선택 필요)'
}
classification_obj = {
'autoType': res['auto_type'],
'autoLabel': res['auto_label'],
'matchedKeyword': res['matched_keyword'],
'reason': res['reason'],
'needsManualSelection': res['needs_manual_selection'],
'selectedType': selected_type,
'selectedLabel': label_map.get(selected_type, res['auto_label']),
'isManualOverride': bool(manual_type and manual_type != res['auto_type'])
}
classification_obj = _resolve_classification(drawing_name, manual_type)
# Update active drawing classification in memory and regenerate 3D model
for d in current_drawings_list:
@@ -135,12 +138,19 @@ def api_upload():
# 2. Parse each uploaded DWG file
parsed_drawings = []
for filename, temp_path in saved_files:
try:
try:
for filename, temp_path in saved_files:
drawing_data = cad_parser.parse_dwg(temp_path, custom_filename=filename)
parsed_drawings.append(drawing_data)
except Exception as e:
return jsonify({'status': 'error', 'message': f'도면 분석 중 오류 발생 ({filename}): {str(e)}'}), 500
except Exception as e:
return jsonify({'status': 'error', 'message': f'도면 분석 중 오류 발생: {str(e)}'}), 500
finally:
for _, temp_path in saved_files:
try:
if os.path.exists(temp_path):
os.remove(temp_path)
except OSError:
pass
global current_drawing_data, current_model_data, current_drawings_list
if reset_flag or current_drawings_list is None:
-1
View File
@@ -1,7 +1,6 @@
import os
import re
import math
import json
import ezdwg
+3 -3
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DWG 도면 기반 암거 및 날개벽 3D 외형·배근 통합 모델러</title>
<title>DWG 도면 기반 옹벽형날개벽 3D 모델러</title>
<link rel="stylesheet" href="static/css/style.css">
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
</head>
@@ -14,8 +14,8 @@
<div class="brand">
<div class="logo-icon">3D</div>
<div>
<h1>암거 및 날개벽 3D 외형·배근 모델러</h1>
<div style="font-size: 11px; color: var(--text-muted);" id="header-subtitle">도면을 불러와주세요 (1단계)</div>
<h1>옹벽형날개벽 3D 모델러</h1>
<div class="header-subtitle" id="header-subtitle">도면을 불러와주세요 (1단계)</div>
</div>
</div>
+1 -2
View File
@@ -1,6 +1,5 @@
import math
import numpy as np
import json
import rebar_model
@@ -122,7 +121,6 @@ def get_wingwall_mesh(side='LEFT', custom_params=None, culvert_params=None, num_
pos_str = 'START_RIGHT'
is_left_side = ('LEFT' in pos_str)
is_start = ('START' in pos_str)
cp = custom_params or {}
culv = culvert_params or {}
@@ -554,6 +552,7 @@ def generate_full_model(params=None, drawings_list=None):
if __name__ == '__main__':
import json
res = generate_full_model()
print("Full model generated:")
print(" Total volume:", res['summary']['total_volume'], "m3")
+4 -3
View File
@@ -275,10 +275,11 @@ def generate_wingwall_rebars(side='LEFT', custom_params=None, culvert_params=Non
to_world(s, sec['u_stem_back_bot'] - 0.02, min(sec['z_stem_top'] - 0.05, sec['h_foot_top'] + 0.48)),
to_world(s, sec['u_stem_front'] + 0.04, min(sec['z_stem_top'] - 0.05, sec['h_foot_top'] + 0.48))
])
# W1 철근: 벽체-저판 접합부(h_foot_top)에서 꺾이지 않고 벽체 배근 방향(u_stem_front)을
# 그대로 직선 연장하여 저판 하면(z_foot_bot + 0.016)에서만 1회 꺾이는 ㄱ자 형상
w1_bars.append([
to_world(s, max(sec['u_toe_rebar'] + 0.02, sec['u_stem_front'] - 0.200), sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'] + 0.06, sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'], sec['h_foot_top']),
to_world(s, sec['u_stem_front'], sec['z_foot_bot'] + 0.016),
to_world(s, sec['u_stem_front'], sec['z_stem_top'])
])
w5_bars.append([
@@ -398,7 +399,7 @@ def generate_wingwall_rebars(side='LEFT', custom_params=None, culvert_params=Non
'desc': '벽체 전면 수직 주철근 (수로측 전면, 하단 저판 정착)',
'spacing': '@250',
'shape_type': 'STEM_VERTICAL_FRONT',
'shape_params': {'a': f"{total_h_0-0.20:.3f}~1.372", 'b': '200', 'c': '142'},
'shape_params': {'a': f"{total_h_0-0.20:.3f}~1.372", 'b': '200'},
'length_m': w1_len,
'count': n1,
'total_length_m': round(w1_len * n1, 3),
-40
View File
@@ -1,40 +0,0 @@
#!/usr/bin/env bash
# ==============================================================================
# DWG 도면 기반 3D 모델링 및 웹 뷰어 실행 스크립트 (WSL2 / Ubuntu 환경)
# ==============================================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
VENV_PATH="$HOME/.venv-3d-drawing"
echo "======================================================================"
echo " [3D Drawing Converter & Viewer] WSL2(Ubuntu) 실행 환경 초기화"
echo "======================================================================"
# 1. 가상환경 확인 및 필요 시 자동 생성
if [ ! -f "$VENV_PATH/bin/python" ]; then
echo ">> 가상환경이 존재하지 않아 생성 중: $VENV_PATH"
python3 -m venv "$VENV_PATH"
"$VENV_PATH/bin/pip" install --upgrade pip
"$VENV_PATH/bin/pip" install -r requirements.txt
fi
echo ">> Python 가상환경 활성화: $VENV_PATH"
source "$VENV_PATH/bin/activate"
# 2. 필수 의존성 검증
python -c "import flask, flask_cors, ezdxf, ezdwg, numpy; print('>> 모든 필수 패키지 검증 완료 (flask, ezdxf, ezdwg, numpy)')"
# 3. Flask 웹 서버 기동
PORT=${PORT:-5000}
HOST=${HOST:-0.0.0.0}
echo "----------------------------------------------------------------------"
echo ">> 3D 도면 뷰어 웹 서버를 실행합니다..."
echo ">> 접속 주소: http://localhost:$PORT / http://127.0.0.1:$PORT"
echo ">> 종료하려면 Ctrl+C 를 누르세요."
echo "----------------------------------------------------------------------"
exec python app.py
+30 -22
View File
@@ -35,42 +35,49 @@ body {
/* Header */
header {
height: 60px;
height: 70px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 14px;
gap: 10px;
padding: 0 16px;
gap: 12px;
z-index: 100;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
gap: 12px;
flex-shrink: 0;
}
.brand .logo-icon {
width: 32px;
height: 32px;
width: 38px;
height: 38px;
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
border-radius: 8px;
border-radius: 9px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 800;
font-size: 15px;
font-size: 18px;
color: white;
}
.brand h1 {
font-size: 14px;
font-size: 21px; /* 기존 14px 대비 1.5배 확대 */
font-weight: 700;
letter-spacing: -0.3px;
color: var(--text-primary);
line-height: 1.2;
}
.brand .header-subtitle {
font-size: 16.5px; /* 기존 11px 대비 1.5배 확대 */
color: var(--text-muted);
margin-top: 2px;
}
.badge {
@@ -87,9 +94,9 @@ header {
.workflow-stepper {
display: flex;
align-items: center;
gap: 4px;
gap: 6px;
background: var(--bg-primary);
padding: 4px 6px;
padding: 5px 8px;
border-radius: 8px;
border: 1px solid var(--border-color);
}
@@ -97,8 +104,8 @@ header {
.step-pill {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 10px;
gap: 10px;
padding: 6px 12px;
border-radius: 6px;
border: 1px solid transparent;
background: transparent;
@@ -125,17 +132,18 @@ header {
}
.step-num {
width: 20px;
height: 20px;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--bg-card);
border: 1px solid var(--border-color);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-size: 15px; /* 기존 11px 대비 확대 */
font-weight: 800;
color: var(--text-secondary);
flex-shrink: 0;
}
.step-pill.active .step-num {
@@ -156,15 +164,15 @@ header {
}
.step-title {
font-size: 11px;
font-size: 16.5px; /* 기존 11px 대비 1.5배 확대 */
font-weight: 700;
line-height: 1.2;
line-height: 1.25;
}
.step-sub {
font-size: 9px;
font-size: 13.5px; /* 기존 9px 대비 1.5배 확대 */
color: var(--text-muted);
margin-top: 1px;
margin-top: 2px;
}
.step-pill.active .step-sub {
@@ -177,8 +185,8 @@ header {
.step-arrow {
color: var(--text-muted);
font-size: 11px;
padding: 0 2px;
font-size: 16.5px; /* 기존 11px 대비 1.5배 확대 */
padding: 0 4px;
}
.header-tools {
+2 -2
View File
@@ -1,2 +1,2 @@
window.KNOWN_MODELS_MAP = {"STA.13+611.21": {"station": "STA.13+611.21", "generalDrawingName": "13. 수로암거 일반도(STA.13+611.21)(01).dwg", "rebarDrawingNames": ["14. 수로암거 구조도(STA.13+611.21, 종점 우)(01).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 우)(02).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(03).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(04).dwg"], "detection": {"culvert_detected": true, "culvert_bounds": {"start_x": 339.77, "end_x": 638.17, "center_y": 447.38, "top_dashed_y": 457.38, "bot_dashed_y": 437.38, "clear_span": 2.0, "detection_method": "평면도 중앙 평행사변형/사다리꼴 및 상·하부 점선(CS-CONC-MINR) 자동 판별"}, "configuration": "END_ONLY", "configuration_label": "종점부 날개벽만 존재 (2개소 모델링 대상)", "rule_statement": "불러온 일반도 평면도 분석 결과, 암거 우측(종점부)에만 날개벽이 표기되어 있으며 암거 좌측(시점부)에는 날개벽이 존재하지 않으므로 종점부 날개벽만 모델링합니다.", "quadrants": {"START_LEFT": {"id": "WW_START_LEFT", "name": "시점부 좌측 날개벽", "location": "시점부", "side": "좌측", "position_desc": "암거 좌측(시점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [337.45, 447.38, 0.0], "p2": [337.51, 447.38, 0.0], "length": 0.07}], "texts": ["(BLOCK2)", "19.980"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 상단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "START_RIGHT": {"id": "WW_START_RIGHT", "name": "시점부 우측 날개벽", "location": "시점부", "side": "우측", "position_desc": "암거 좌측(시점부) 평면도 하단", "lines": [], "texts": ["(BLOCK2)", "19.980", "19.980", "(Block2)"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 하단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "END_LEFT": {"id": "WW_END_LEFT", "name": "종점부 좌측 날개벽", "location": "종점부", "side": "좌측", "position_desc": "암거 우측(종점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [339.77, 447.38, 0.0], "p2": [436.77, 447.38, 0.0], "length": 97.0}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [437.45, 447.38, 0.0], "p2": [437.51, 447.38, 0.0], "length": 0.07}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [439.77, 447.38, 0.0], "p2": [751.56, 447.38, 0.0], "length": 311.79}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.17, 464.38, 0.0], "p2": [388.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [339.77, 464.38, 0.0], "p2": [388.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.17, 464.38, 0.0], "p2": [488.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [388.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [436.77, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [439.77, 464.38, 0.0], "p2": [488.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [488.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [638.17, 464.38, 0.0], "length": 149.8}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 494.38, 0.0], "p2": [438.27, 494.38, 0.0], "length": 100.0}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [438.27, 494.38, 0.0], "p2": [726.72, 494.38, 0.0], "length": 288.45}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 484.38, 0.0], "p2": [388.17, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 484.38, 0.0], "p2": [388.37, 484.38, 0.0], "length": 0.2}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 474.38, 0.0], "p2": [388.37, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 484.38, 0.0], "p2": [438.27, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML"Line truncated
window.DEFAULT_MODEL_3D = {"station": "STA.13+611.21", "generalDrawingName": "13. 수로암거 일반도(STA.13+611.21)(01).dwg", "rebarDrawingNames": ["14. 수로암거 구조도(STA.13+611.21, 종점 우)(01).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 우)(02).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(03).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(04).dwg"], "detection": {"culvert_detected": true, "culvert_bounds": {"start_x": 339.77, "end_x": 638.17, "center_y": 447.38, "top_dashed_y": 457.38, "bot_dashed_y": 437.38, "clear_span": 2.0, "detection_method": "평면도 중앙 평행사변형/사다리꼴 및 상·하부 점선(CS-CONC-MINR) 자동 판별"}, "configuration": "END_ONLY", "configuration_label": "종점부 날개벽만 존재 (2개소 모델링 대상)", "rule_statement": "불러온 일반도 평면도 분석 결과, 암거 우측(종점부)에만 날개벽이 표기되어 있으며 암거 좌측(시점부)에는 날개벽이 존재하지 않으므로 종점부 날개벽만 모델링합니다.", "quadrants": {"START_LEFT": {"id": "WW_START_LEFT", "name": "시점부 좌측 날개벽", "location": "시점부", "side": "좌측", "position_desc": "암거 좌측(시점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [337.45, 447.38, 0.0], "p2": [337.51, 447.38, 0.0], "length": 0.07}], "texts": ["(BLOCK2)", "19.980"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 상단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "START_RIGHT": {"id": "WW_START_RIGHT", "name": "시점부 우측 날개벽", "location": "시점부", "side": "우측", "position_desc": "암거 좌측(시점부) 평면도 하단", "lines": [], "texts": ["(BLOCK2)", "19.980", "19.980", "(Block2)"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 하단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "END_LEFT": {"id": "WW_END_LEFT", "name": "종점부 좌측 날개벽", "location": "종점부", "side": "좌측", "position_desc": "암거 우측(종점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [339.77, 447.38, 0.0], "p2": [436.77, 447.38, 0.0], "length": 97.0}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [437.45, 447.38, 0.0], "p2": [437.51, 447.38, 0.0], "length": 0.07}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [439.77, 447.38, 0.0], "p2": [751.56, 447.38, 0.0], "length": 311.79}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.17, 464.38, 0.0], "p2": [388.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [339.77, 464.38, 0.0], "p2": [388.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.17, 464.38, 0.0], "p2": [488.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [388.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [436.77, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [439.77, 464.38, 0.0], "p2": [488.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [488.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [638.17, 464.38, 0.0], "length": 149.8}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 494.38, 0.0], "p2": [438.27, 494.38, 0.0], "length": 100.0}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [438.27, 494.38, 0.0], "p2": [726.72, 494.38, 0.0], "length": 288.45}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 484.38, 0.0], "p2": [388.17, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 484.38, 0.0], "p2": [388.37, 484.38, 0.0], "length": 0.2}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 474.38, 0.0], "p2": [388.37, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 484.38, 0.0], "p2": [438.27, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4Line truncated
window.KNOWN_MODELS_MAP = {"STA.13+611.21": {"station": "STA.13+611.21", "generalDrawingName": "13. 수로암거 일반도(STA.13+611.21)(01).dwg", "rebarDrawingNames": ["14. 수로암거 구조도(STA.13+611.21, 종점 우)(01).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 우)(02).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(03).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(04).dwg"], "detection": {"culvert_detected": true, "culvert_bounds": {"start_x": 339.77, "end_x": 638.17, "center_y": 447.38, "top_dashed_y": 457.38, "bot_dashed_y": 437.38, "clear_span": 2.0, "detection_method": "평면도 중앙 평행사변형/사다리꼴 및 상·하부 점선(CS-CONC-MINR) 자동 판별"}, "configuration": "END_ONLY", "configuration_label": "종점부 날개벽만 존재 (2개소 모델링 대상)", "rule_statement": "불러온 일반도 평면도 분석 결과, 암거 우측(종점부)에만 날개벽이 표기되어 있으며 암거 좌측(시점부)에는 날개벽이 존재하지 않으므로 종점부 날개벽만 모델링합니다.", "quadrants": {"START_LEFT": {"id": "WW_START_LEFT", "name": "시점부 좌측 날개벽", "location": "시점부", "side": "좌측", "position_desc": "암거 좌측(시점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [337.45, 447.38, 0.0], "p2": [337.51, 447.38, 0.0], "length": 0.07}], "texts": ["(BLOCK2)", "19.980"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 상단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "START_RIGHT": {"id": "WW_START_RIGHT", "name": "시점부 우측 날개벽", "location": "시점부", "side": "우측", "position_desc": "암거 좌측(시점부) 평면도 하단", "lines": [], "texts": ["(BLOCK2)", "19.980", "19.980", "(Block2)"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 하단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "END_LEFT": {"id": "WW_END_LEFT", "name": "종점부 좌측 날개벽", "location": "종점부", "side": "좌측", "position_desc": "암거 우측(종점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [339.77, 447.38, 0.0], "p2": [436.77, 447.38, 0.0], "length": 97.0}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [437.45, 447.38, 0.0], "p2": [437.51, 447.38, 0.0], "length": 0.07}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [439.77, 447.38, 0.0], "p2": [751.56, 447.38, 0.0], "length": 311.79}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.17, 464.38, 0.0], "p2": [388.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [339.77, 464.38, 0.0], "p2": [388.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.17, 464.38, 0.0], "p2": [488.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [388.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [436.77, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [439.77, 464.38, 0.0], "p2": [488.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [488.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [638.17, 464.38, 0.0], "length": 149.8}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 494.38, 0.0], "p2": [438.27, 494.38, 0.0], "length": 100.0}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [438.27, 494.38, 0.0], "p2": [726.72, 494.38, 0.0], "length": 288.45}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 484.38, 0.0], "p2": [388.17, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 484.38, 0.0], "p2": [388.37, 484.38, 0.0], "length": 0.2}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 474.38, 0.0], "p2": [388.37, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 484.38, 0.0], "p2": [438.27, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML"Line truncated
window.DEFAULT_MODEL_3D = {"station": "STA.13+611.21", "generalDrawingName": "13. 수로암거 일반도(STA.13+611.21)(01).dwg", "rebarDrawingNames": ["14. 수로암거 구조도(STA.13+611.21, 종점 우)(01).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 우)(02).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(03).dwg", "14. 수로암거 구조도(STA.13+611.21, 종점 좌)(04).dwg"], "detection": {"culvert_detected": true, "culvert_bounds": {"start_x": 339.77, "end_x": 638.17, "center_y": 447.38, "top_dashed_y": 457.38, "bot_dashed_y": 437.38, "clear_span": 2.0, "detection_method": "평면도 중앙 평행사변형/사다리꼴 및 상·하부 점선(CS-CONC-MINR) 자동 판별"}, "configuration": "END_ONLY", "configuration_label": "종점부 날개벽만 존재 (2개소 모델링 대상)", "rule_statement": "불러온 일반도 평면도 분석 결과, 암거 우측(종점부)에만 날개벽이 표기되어 있으며 암거 좌측(시점부)에는 날개벽이 존재하지 않으므로 종점부 날개벽만 모델링합니다.", "quadrants": {"START_LEFT": {"id": "WW_START_LEFT", "name": "시점부 좌측 날개벽", "location": "시점부", "side": "좌측", "position_desc": "암거 좌측(시점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [337.45, 447.38, 0.0], "p2": [337.51, 447.38, 0.0], "length": 0.07}], "texts": ["(BLOCK2)", "19.980"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 상단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "START_RIGHT": {"id": "WW_START_RIGHT", "name": "시점부 우측 날개벽", "location": "시점부", "side": "우측", "position_desc": "암거 좌측(시점부) 평면도 하단", "lines": [], "texts": ["(BLOCK2)", "19.980", "19.980", "(Block2)"], "exists": false, "length": 0.0, "flare_angle": 0.0, "sections": [], "status_label": "도면 미표기 (모델링 제외)", "reason": "평면도 암거 좌측(시점부) 평면도 하단에 날개벽 외곽선 및 치수선 미표기 (도면 미존재)"}, "END_LEFT": {"id": "WW_END_LEFT", "name": "종점부 좌측 날개벽", "location": "종점부", "side": "좌측", "position_desc": "암거 우측(종점부) 평면도 상단", "lines": [{"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [339.77, 447.38, 0.0], "p2": [436.77, 447.38, 0.0], "length": 97.0}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [437.45, 447.38, 0.0], "p2": [437.51, 447.38, 0.0], "length": 0.07}, {"type": "LINE", "layer": "CS-CNTL", "color": "#ff4d4f", "p1": [439.77, 447.38, 0.0], "p2": [751.56, 447.38, 0.0], "length": 311.79}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.17, 464.38, 0.0], "p2": [388.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [339.77, 464.38, 0.0], "p2": [388.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.17, 464.38, 0.0], "p2": [488.17, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [388.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [388.37, 464.38, 0.0], "p2": [436.77, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [439.77, 464.38, 0.0], "p2": [488.17, 464.38, 0.0], "length": 48.4}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [488.37, 430.38, 0.0], "length": 34.0}, {"type": "LINE", "layer": "CS-CONC-MAJR", "color": "#06b6d4", "p1": [488.37, 464.38, 0.0], "p2": [638.17, 464.38, 0.0], "length": 149.8}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 494.38, 0.0], "p2": [438.27, 494.38, 0.0], "length": 100.0}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [438.27, 494.38, 0.0], "p2": [726.72, 494.38, 0.0], "length": 288.45}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [338.27, 484.38, 0.0], "p2": [388.17, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 484.38, 0.0], "p2": [388.37, 484.38, 0.0], "length": 0.2}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 474.38, 0.0], "p2": [388.37, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.17, 474.38, 0.0], "p2": [388.17, 484.88, 0.0], "length": 10.5}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4f", "p1": [388.37, 484.38, 0.0], "p2": [438.27, 484.38, 0.0], "length": 49.9}, {"type": "LINE", "layer": "CS-DIML", "color": "#ff4d4Line truncated
+7 -1
View File
@@ -1270,7 +1270,13 @@ class WingwallApp {
<text x="140" y="34" fill="#e2e8f0" font-size="11" text-anchor="middle">a = ${p.a} m (2Cycle 보강)</text>
<text x="250" y="75" fill="#94a3b8" font-size="10" text-anchor="middle">b=${p.b}</text>
`;
} else if (st === 'STEM_VERTICAL_FRONT' || st === 'STEM_VERTICAL_BACK') {
} else if (st === 'STEM_VERTICAL_FRONT') {
svgBody = `
<polyline points="85,105 155,105 155,20" fill="none" stroke="${col}" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="120" y="120" fill="#94a3b8" font-size="10" text-anchor="middle">저판하면 ${p.b || '200'}mm</text>
<text x="208" y="65" fill="#e2e8f0" font-size="11" text-anchor="middle">벽체부 ${p.a} m</text>
`;
} else if (st === 'STEM_VERTICAL_BACK') {
svgBody = `
<polyline points="85,105 155,105 162,20" fill="none" stroke="${col}" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<text x="120" y="120" fill="#94a3b8" font-size="10" text-anchor="middle">기초정착 ${p.c || p.a}mm</text>
+321 -14
View File
@@ -10,6 +10,8 @@ class Viewer3D {
this.labelSprites = [];
this.rebarLabelSprites = [];
this.wingwallNameSprites = [];
this.dimensionGroup = null;
this.dimensionSprites = [];
this.displayMode = 'solid'; // 'solid', 'wireframe', 'transparent'
this.stepMode = 2; // 2: Exterior only, 3: Exterior (ghosted) + 3D Rebars
this.selectedObjectId = null;
@@ -79,6 +81,11 @@ class Viewer3D {
this.rebarGroupRoot = new THREE.Group();
this.scene.add(this.rebarGroupRoot);
// Dimension group for Step 2 selected wingwall measurements
this.dimensionGroup = new THREE.Group();
this.scene.add(this.dimensionGroup);
this.dimensionSprites = [];
// Ground Grid & Axes
this.setupGridAndAxes();
@@ -115,6 +122,7 @@ class Viewer3D {
this.meshes = {};
this.clearSectionMarkers();
this.clearWingwallNameSprites();
this.clearDimensionAnnotations();
const objects = modelData.objects || {};
const botEl = objects.culvert?.dimensions?.bot_el ?? 33.404;
@@ -325,6 +333,9 @@ class Viewer3D {
setStepMode(stepNumber) {
this.stepMode = stepNumber;
if (stepNumber === 3) {
this.clearDimensionAnnotations();
}
this.applyStepVisualState();
}
@@ -644,7 +655,7 @@ class Viewer3D {
const center = new THREE.Vector3();
box.getCenter(center);
const sprite = this.createTextSprite(item.name, item.color);
sprite.position.set(center.x, center.y, box.max.z + 0.65);
sprite.position.set(center.x, center.y, box.max.z + 1.25);
sprite.userData.wingwallId = item.id;
sprite.visible = false;
this.scene.add(sprite);
@@ -671,8 +682,8 @@ class Viewer3D {
// 암거본체 선택 시 날개벽 구조물의 이름만 표시 (Requirement 2.2.2 & 3.2.2)
sp.visible = true;
} else {
// 특정 날개벽 선택 시 해당 날개벽 이름만 표시
sp.visible = (sp.userData.wingwallId === currentFocus);
// 특정 날개벽 선택 시: 2단계에서는 치수선 가독성을 위해 숨김, 3단계에서는 선택된 날개벽 이름 표시
sp.visible = (this.stepMode === 3 && sp.userData.wingwallId === currentFocus);
}
}
}
@@ -680,18 +691,314 @@ class Viewer3D {
selectObject(objectId) {
this.selectedObjectId = objectId;
if (this.stepMode === 3) return;
for (const id in this.meshes) {
const m = this.meshes[id];
const mat = m.group.userData.mat;
if (id === objectId) {
mat.emissive = new THREE.Color(0x2563eb);
mat.emissiveIntensity = 0.45;
} else {
mat.emissive = new THREE.Color(0x000000);
const isCulvert = (!objectId || objectId === 'CULVERT');
if (isCulvert) {
// 2.1 암거 본체 선택: 메인 화면에 암거 본체 및 모든 구조물을 정상 진하게 표시
this.clearDimensionAnnotations();
for (const id in this.meshes) {
const m = this.meshes[id];
const mat = m.group.userData.mat;
const edgesMat = m.group.userData.edgesMat;
mat.transparent = false;
mat.opacity = 1.0;
mat.depthWrite = true;
mat.color.setHex(m.baseColor);
mat.emissive.setHex(0x000000);
mat.emissiveIntensity = 0;
if (edgesMat) {
edgesMat.transparent = true;
edgesMat.opacity = 0.35;
}
}
} else {
// 2.2 날개벽 선택: 선택된 날개벽은 진하게, 나머지 구조물은 흐리게 표시
this.clearDimensionAnnotations();
for (const id in this.meshes) {
const m = this.meshes[id];
const mat = m.group.userData.mat;
const edgesMat = m.group.userData.edgesMat;
if (id === objectId) {
// 선택된 날개벽: 진하게 선명하게 강조
mat.transparent = false;
mat.opacity = 1.0;
mat.depthWrite = true;
mat.color.setHex(m.baseColor);
mat.emissive.setHex((id === 'WW_END_LEFT' || id === 'ww_left') ? 0x0284c7 : 0xd97706);
mat.emissiveIntensity = 0.30;
if (edgesMat) {
edgesMat.transparent = false;
edgesMat.opacity = 0.85;
}
} else {
// 나머지 구조물: 흐리게 (위치와 형상은 파악 가능한 반투명)
mat.transparent = true;
mat.opacity = 0.18;
mat.depthWrite = false;
mat.color.setHex(0x64748b);
mat.emissive.setHex(0x000000);
mat.emissiveIntensity = 0;
if (edgesMat) {
edgesMat.transparent = true;
edgesMat.opacity = 0.18;
}
}
}
// 3. 선택된 날개벽의 주요 치수(시작단면 높이, 종점단면 높이, 날개벽 연장) 표시
this.showWingwallDimensions(objectId);
}
this.updateWingwallNameSpritesVisibility();
}
// Requirement 1 & 2 & 3 & 4: 선택된 날개벽 치수(시작단면 높이, 종점단면 높이, 날개벽 수평연장) 흰색 3D 치수선 및 간소화 라벨 표출
showWingwallDimensions(wingwallId) {
this.clearDimensionAnnotations();
if (!wingwallId || wingwallId === 'CULVERT') return;
const objects = this.modelData?.objects || {};
const isLeft = (wingwallId === 'WW_END_LEFT' || wingwallId === 'ww_left' || wingwallId === 'ww_end_left');
const wwData = isLeft
? (objects.ww_end_left || objects.ww_left)
: (objects.ww_end_right || objects.ww_right);
let pStartBot, pStartTop, pEndBot, pEndTop;
const sec = wwData?.sections_3d || {};
if (isLeft) {
const secA = sec['A-A'];
const secB = sec['B-B'];
if (secA && secA.length >= 6) {
pStartBot = new THREE.Vector3(secA[5][0], secA[5][1], secA[0][2]);
pStartTop = new THREE.Vector3(secA[5][0], secA[5][1], secA[4][2]);
} else {
pStartBot = new THREE.Vector3(15.731, 42.650, 33.404);
pStartTop = new THREE.Vector3(15.731, 42.650, 37.104);
}
if (secB && secB.length >= 6) {
pEndBot = new THREE.Vector3(secB[5][0], secB[5][1], secB[0][2]);
pEndTop = new THREE.Vector3(secB[5][0], secB[5][1], secB[4][2]);
} else {
pEndBot = new THREE.Vector3(18.759, 45.773, 33.523);
pEndTop = new THREE.Vector3(18.759, 45.773, 35.023);
}
} else {
const secC = sec['C-C'];
const secD = sec['D-D'];
if (secC && secC.length >= 6) {
pStartBot = new THREE.Vector3(secC[5][0], secC[5][1], secC[0][2]);
pStartTop = new THREE.Vector3(secC[5][0], secC[5][1], secC[4][2]);
} else {
pStartBot = new THREE.Vector3(16.287, 40.650, 33.404);
pStartTop = new THREE.Vector3(16.287, 40.650, 37.104);
}
if (secD && secD.length >= 6) {
pEndBot = new THREE.Vector3(secD[5][0], secD[5][1], secD[0][2]);
pEndTop = new THREE.Vector3(secD[5][0], secD[5][1], secD[4][2]);
} else {
pEndBot = new THREE.Vector3(21.004, 38.022, 33.523);
pEndTop = new THREE.Vector3(21.004, 38.022, 35.023);
}
}
this.updateWingwallNameSpritesVisibility();
// 실제 3D 모델링 기하 좌표로부터 치수값 정밀 산출 (Requirement 5: 임의 고정값 배제)
const hStartVal = Math.abs(pStartTop.z - pStartBot.z);
const hEndVal = Math.abs(pEndTop.z - pEndBot.z);
// 시작단면과 종점단면 사이의 수평거리를 날개벽 연장으로 정의 (Requirement 2)
const lengthVal = Math.hypot(pEndTop.x - pStartTop.x, pEndTop.y - pStartTop.y);
// 벽체 연장 축 (XY 평면)
const vWall = new THREE.Vector3(pEndTop.x - pStartTop.x, pEndTop.y - pStartTop.y, 0);
const uWall = vWall.clone().normalize();
// 벽체 외측 법선 벡터 (XY 평면)
const nWall = isLeft
? new THREE.Vector3(-uWall.y, uWall.x, 0).normalize()
: new THREE.Vector3(uWall.y, -uWall.x, 0).normalize();
// 1. 시작단면 높이 치수선 (외측 법선 방향 배치, H=00.0m) - Requirement 3 & 4
this.createDimension3D(
pStartBot,
pStartTop,
nWall,
0.60,
`H=${hStartVal.toFixed(2)}m`
);
// 2. 종점단면 높이 치수선 (종점 축 외측 방향 배치, H=00.0m) - Requirement 3 & 4
this.createDimension3D(
pEndBot,
pEndTop,
uWall,
0.60,
`H=${hEndVal.toFixed(2)}m`
);
// 3. 날개벽 연장 수평 치수선 (경사가 아닌 수평 방향 배치, L=00.0m) - Requirement 2 & 3 & 4
this.createHorizontalDimension3D(
pStartTop,
pEndTop,
nWall,
0.20,
`L=${lengthVal.toFixed(2)}m`
);
}
// Requirement 2: 날개벽 연장 수평 치수선 (동일 Z 높이 수평 배치로 경사/기울어짐 방지)
createHorizontalDimension3D(p1, p2, normalDir, normalDist, text) {
const zDim = Math.max(p1.z, p2.z) + 0.60;
const offH = (normalDir && normalDist)
? normalDir.clone().normalize().multiplyScalar(normalDist)
: new THREE.Vector3(0, 0, 0);
const startBase = new THREE.Vector3(p1.x + offH.x, p1.y + offH.y, p1.z);
const endBase = new THREE.Vector3(p2.x + offH.x, p2.y + offH.y, p2.z);
// 치수선의 양 끝단 Z 좌표를 zDim으로 일치시켜 완벽한 수평 보장
const d1 = new THREE.Vector3(startBase.x, startBase.y, zDim);
const d2 = new THREE.Vector3(endBase.x, endBase.y, zDim);
const linePoints = [];
// 보조선 (Extension Lines): 각 단면 상단 기준점에서 수평 치수선 높이까지 수직 연장
const overshoot = 0.15;
linePoints.push(startBase.clone(), new THREE.Vector3(d1.x, d1.y, zDim + overshoot));
linePoints.push(endBase.clone(), new THREE.Vector3(d2.x, d2.y, zDim + overshoot));
// 치수선 (Horizontal Dimension Line)
linePoints.push(d1.clone(), d2.clone());
// 끝단 틱 마커 (End Ticks): 수평 치수선에 직교하는 수직 틱
const tickLen = 0.14;
linePoints.push(
new THREE.Vector3(d1.x, d1.y, zDim - tickLen),
new THREE.Vector3(d1.x, d1.y, zDim + tickLen)
);
linePoints.push(
new THREE.Vector3(d2.x, d2.y, zDim - tickLen),
new THREE.Vector3(d2.x, d2.y, zDim + tickLen)
);
// 치수선 및 텍스트 렌더링
const mid = new THREE.Vector3().addVectors(d1, d2).multiplyScalar(0.5);
const spritePos = new THREE.Vector3(mid.x, mid.y, zDim + 0.28);
this._renderDimensionEntry(linePoints, spritePos, text);
}
// 높이 측정용 3D 치수선 생성 (Requirement 3 & 4: 흰색 치수선 및 간소화 표기)
createDimension3D(p1, p2, offsetDir, offsetDist, text) {
const off = offsetDir.clone().normalize().multiplyScalar(offsetDist);
const d1 = p1.clone().add(off);
const d2 = p2.clone().add(off);
const linePoints = [];
// 보조선 (Extension Lines)
const extOver = offsetDir.clone().normalize().multiplyScalar(0.15);
linePoints.push(p1.clone(), d1.clone().add(extOver));
linePoints.push(p2.clone(), d2.clone().add(extOver));
// 치수선 (Main Dimension Line)
linePoints.push(d1.clone(), d2.clone());
// 끝단 틱 표시 (End Ticks)
const dimDir = new THREE.Vector3().subVectors(d2, d1).normalize();
let tickDir = new THREE.Vector3().crossVectors(dimDir, offsetDir).normalize();
if (tickDir.lengthSq() < 0.01) {
tickDir = new THREE.Vector3(0, 0, 1);
}
const tickLen = 0.14;
linePoints.push(d1.clone().addScaledVector(tickDir, tickLen), d1.clone().addScaledVector(tickDir, -tickLen));
linePoints.push(d2.clone().addScaledVector(tickDir, tickLen), d2.clone().addScaledVector(tickDir, -tickLen));
// 치수값 텍스트 라벨 (치수선 외측 중앙에 명확히 대응되도록 배치)
const mid = new THREE.Vector3().addVectors(d1, d2).multiplyScalar(0.5);
const spritePos = mid.clone().addScaledVector(offsetDir.clone().normalize(), 0.32);
this._renderDimensionEntry(linePoints, spritePos, text);
}
// 치수선 LineSegments 및 텍스트 Sprite 씬 등록 공통 헬퍼
_renderDimensionEntry(linePoints, spritePos, text) {
const geom = new THREE.BufferGeometry().setFromPoints(linePoints);
const mat = new THREE.LineBasicMaterial({
color: 0xffffff, // Requirement 4: 모든 치수선 흰색
linewidth: 2,
depthTest: false,
transparent: true,
opacity: 0.95
});
const lineSegs = new THREE.LineSegments(geom, mat);
lineSegs.renderOrder = 999;
this.dimensionGroup.add(lineSegs);
const sprite = this.createDimensionTextSprite(text);
sprite.position.copy(spritePos);
sprite.renderOrder = 1000;
this.scene.add(sprite);
this.dimensionSprites.push(sprite);
}
// Requirement 3 & 4: 사각형 테이블(배경 박스 및 테두리) 삭제, 순수 흰색 치수값 텍스트 생성
createDimensionTextSprite(text) {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 64;
const ctx = canvas.getContext('2d');
// 투명 배경 (사각형 박스 및 테두리 완전 삭제)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 22px "Segoe UI", Roboto, -apple-system, sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// 3D 뷰포트에서 배경/모델 색상과 무관하게 글자가 또렷이 식별되도록 글자 자체에 미세한 다크 외곽선 적용
ctx.strokeStyle = 'rgba(15, 23, 42, 0.90)';
ctx.lineWidth = 3.5;
ctx.lineJoin = 'round';
ctx.miterLimit = 2;
ctx.strokeText(text, 128, 32);
// 치수값 흰색 표시
ctx.fillStyle = '#ffffff';
ctx.fillText(text, 128, 32);
const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.LinearFilter;
const spriteMat = new THREE.SpriteMaterial({
map: texture,
depthTest: false,
transparent: true
});
const sprite = new THREE.Sprite(spriteMat);
sprite.scale.set(2.8, 0.7, 1);
sprite.userData.aspect = 256 / 64; // 4.0
return sprite;
}
clearDimensionAnnotations() {
if (this.dimensionGroup) {
while (this.dimensionGroup.children.length > 0) {
const obj = this.dimensionGroup.children[0];
if (obj.geometry) obj.geometry.dispose();
if (obj.material) obj.material.dispose();
this.dimensionGroup.remove(obj);
}
}
for (const sp of this.dimensionSprites) {
this.scene.remove(sp);
if (sp.material?.map) sp.material.map.dispose();
if (sp.material) sp.material.dispose();
}
this.dimensionSprites = [];
}
toggleObjectVisibility(objectId, visible) {
@@ -837,8 +1144,8 @@ class Viewer3D {
requestAnimationFrame(() => this.animate());
this.controls.update();
// Requirement 3.3: 줌 확대/축소 시에도 3D 텍스트 크기 일정 유지 (Screen-space constant size)
const sprites = [...this.rebarLabelSprites, ...this.labelSprites, ...this.wingwallNameSprites];
// Requirement 2.3 & 3.3: 줌 확대/축소 시에도 3D 텍스트 크기 일정 유지 (Screen-space constant size)
const sprites = [...this.rebarLabelSprites, ...this.labelSprites, ...this.wingwallNameSprites, ...this.dimensionSprites];
for (const sp of sprites) {
if (sp && sp.visible) {
const dist = this.camera.position.distanceTo(sp.position);
-163
View File
@@ -1,163 +0,0 @@
#!/usr/bin/env python3
"""
Comprehensive End-to-End Pipeline Verification Test
Verifies 2D drawing classification, DWG parsing, 3D solid & rebar modeling,
mesh watertightness, exporter functionality, and Flask API endpoints.
"""
import os
import sys
import json
import unittest
import cad_parser
import model_3d
import rebar_model
import exporters
import app
class Test3DDrawingPipeline(unittest.TestCase):
@classmethod
def setUpClass(cls):
current_dir = os.path.dirname(os.path.abspath(__file__))
cls.project_root = os.path.abspath(os.path.join(current_dir, '..', '..'))
cls.rag_dir = os.path.join(cls.project_root, 'RAG')
cls.general_dwg = os.path.join(cls.rag_dir, '13. 수로암거 일반도(STA.13+611.21)(02).dwg')
app.app.testing = True
cls.flask_app = app.app.test_client()
def test_01_drawing_classification(self):
"""1.2 도면 종류 키워드 기반 자동 판별 검증"""
# 일반도 판별
res_gen = cad_parser.classify_drawing_by_name('13. 수로암거 일반도(STA.13+611.21)(02).dwg')
self.assertEqual(res_gen['auto_type'], 'GENERAL')
self.assertEqual(res_gen['matched_keyword'], '일반')
self.assertFalse(res_gen['needs_manual_selection'])
# 배근도/구조도 판별
res_rebar1 = cad_parser.classify_drawing_by_name('14. 수로암거 구조도(STA.13+611.21, 종점 우)(01).dwg')
self.assertEqual(res_rebar1['auto_type'], 'REBAR')
self.assertIn('구조', res_rebar1['matched_keyword'])
self.assertFalse(res_rebar1['needs_manual_selection'])
res_rebar2 = cad_parser.classify_drawing_by_name('날개벽 배근도(종점 좌).dwg')
self.assertEqual(res_rebar2['auto_type'], 'REBAR')
self.assertIn('배근', res_rebar2['matched_keyword'])
# 미분류 도면
res_unclass = cad_parser.classify_drawing_by_name('기타 도면#01.dwg')
self.assertEqual(res_unclass['auto_type'], 'UNCLASSIFIED')
self.assertTrue(res_unclass['needs_manual_selection'])
def test_02_dwg_parsing(self):
"""1.1 DWG 도면 파싱 및 파라미터 추출 검증"""
if not os.path.exists(self.general_dwg):
self.skipTest(f"DWG file not found at {self.general_dwg}")
drawing_data = cad_parser.parse_dwg(self.general_dwg)
self.assertIsNotNone(drawing_data)
self.assertIn('entities', drawing_data)
self.assertIn('structuralParams', drawing_data)
self.assertGreater(len(drawing_data['entities']), 100)
# 수로암거 제원 확인
sp = drawing_data['structuralParams']
self.assertEqual(sp.get('station'), 'STA.13+611.21')
self.assertAlmostEqual(sp.get('culvert_span', 2.0), 2.0, delta=0.1)
def test_03_3d_solid_modeling_and_watertightness(self):
"""2단계 일반도 기반 3D 외형 모델링 및 수밀성(Watertight) 검증"""
model_res = model_3d.generate_full_model()
self.assertIsNotNone(model_res)
self.assertIn('objects', model_res)
objects = model_res['objects']
self.assertIn('culvert', objects)
self.assertIn('ww_end_left', objects)
self.assertIn('ww_end_right', objects)
# 도면에 없는 시점부 날개벽 임의 생성 차단 확인
self.assertNotIn('ww_start_left', objects)
self.assertNotIn('ww_start_right', objects)
# 수밀성 (Watertight 2-Manifold Mesh) 검증
for part_key in ['culvert', 'ww_end_left', 'ww_end_right']:
part_data = objects[part_key]
vertices = part_data['vertices']
faces = part_data['faces']
self.assertGreater(len(vertices), 0, f"{part_key} has no vertices")
self.assertGreater(len(faces), 0, f"{part_key} has no faces")
# Edge manifold check: Every edge in a watertight 2-manifold closed surface is shared by exactly 2 faces
edge_count = {}
for f in faces:
for i in range(3):
v1, v2 = f[i], f[(i + 1) % 3]
edge = tuple(sorted([v1, v2]))
edge_count[edge] = edge_count.get(edge, 0) + 1
non_manifold = [e for e, count in edge_count.items() if count != 2]
self.assertEqual(len(non_manifold), 0,
f"{part_key} mesh is not watertight! Non-manifold edges: {len(non_manifold)}")
def test_04_3d_rebar_modeling(self):
"""3단계 구조도/배근도 기반 날개벽 내부 3D 철근 배근(508본) 검증"""
rebar_data = rebar_model.generate_full_rebar_model()
self.assertIsNotNone(rebar_data)
self.assertIn('wingwalls', rebar_data)
self.assertIn('summary', rebar_data)
summary = rebar_data['summary']
total_bars = summary.get('total_bars', 0)
self.assertEqual(total_bars, 508, f"Expected 508 total rebars, got {total_bars}")
# 좌측(231본) 및 우측(277본) 검증
left_bars = summary.get('left_bars', 0)
right_bars = summary.get('right_bars', 0)
self.assertEqual(left_bars, 231)
self.assertEqual(right_bars, 277)
def test_05_exporters(self):
"""3D 모델 OBJ, STL, DXF 내보내기 검증"""
model_res = model_3d.generate_full_model()
obj_str = exporters.export_obj(model_res)
self.assertIn('v ', obj_str)
self.assertIn('f ', obj_str)
stl_str = exporters.export_stl(model_res)
self.assertIn('solid ', stl_str)
self.assertIn('facet normal', stl_str)
dxf_str = exporters.export_dxf_3d(model_res)
self.assertIn('SECTION', dxf_str)
self.assertIn('ENTITIES', dxf_str)
csv_str = exporters.export_rebar_bom_csv(model_res)
self.assertIn('구분(날개벽)', csv_str)
self.assertIn('기호(Mark)', csv_str)
def test_06_flask_web_endpoints(self):
"""Flask API 및 웹 엔드포인트 응답 검증"""
# 1. 메인 웹페이지
r_index = self.flask_app.get('/')
self.assertEqual(r_index.status_code, 200)
# 2. 기본 도면 데이터 조회 API
r_def = self.flask_app.get('/api/default-drawing')
self.assertEqual(r_def.status_code, 200)
data_def = json.loads(r_def.data)
self.assertEqual(data_def.get('status'), 'success')
# 3. 도면 분류 API
r_cls = self.flask_app.post('/api/classify',
data=json.dumps({'drawingName': '13. 수로암거 일반도.dwg'}),
content_type='application/json')
self.assertEqual(r_cls.status_code, 200)
data_cls = json.loads(r_cls.data)
self.assertEqual(data_cls['classification']['selectedType'], 'GENERAL')
if __name__ == '__main__':
unittest.main()