Compare commits

...
10 Commits
Author SHA1 Message Date
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
25 changed files with 1226 additions and 73 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)
- **최신 커밋**: 원클릭 실행 배치 스크립트 및 바로가기 파일 등록 완료
+8 -1
View File
@@ -48,8 +48,15 @@ Code/3d-drawing/
--- ---
## 3. WSL2 실행 방법 ## 3. 원클릭 실행 방법
### 3.1. Windows 환경 (권장)
최상위 폴더 또는 `Code/3d-drawing/` 내의 실행 파일을 더블클릭합니다:
- **`[실행]_암거및날개벽_3D외형배근모델러.bat`**: 백엔드 서버(WSL2 또는 Windows Python) 자동 기동 및 웹 브라우저(`http://localhost:5000`) 자동 오픈
- **`[실행바로가기]_암거및날개벽_3D외형배근모델러.lnk`**: 윈도우 바로가기 아이콘
- **`[바로가기]_3D모델러_웹접속.url`**: 서버 동작 중 브라우저 즉시 열기
### 3.2. WSL2(Ubuntu) 터미널 환경
WSL2(Ubuntu) 터미널에서 아래 명령을 실행합니다: WSL2(Ubuntu) 터미널에서 아래 명령을 실행합니다:
```bash ```bash
+4
View File
@@ -0,0 +1,4 @@
[InternetShortcut]
URL=http://localhost:5000/
IconIndex=13
IconFile=C:\Windows\System32\shell32.dll
@@ -0,0 +1,74 @@
@echo off␍
chcp 65001 > nul␍
setlocal␍
title 암거 및 날개벽 3D 모델러␍
␍
echo ===============================================================================␍
echo [암거 및 날개벽 3D 외형·배근 모델러] 원클릭 실행기␍
echo - 프로젝트: 대산-당진 수로암거 (STA.13+611.21)␍
echo - 2단계 일반도 외형 / 3단계 배근도 철근 3D 모델링␍
echo ===============================================================================␍
echo.␍
␍
set "SCRIPT_DIR=%~dp0"␍
if exist "%SCRIPT_DIR%Code\3d-drawing\app.py" (␍
set "WORK_DIR=%SCRIPT_DIR%Code\3d-drawing"␍
) else (␍
set "WORK_DIR=%SCRIPT_DIR%"␍
)␍
␍
REM 1. 서버 실행 여부 점검 (포트 5000)␍
netstat -ano | findstr /R ":5000.*LISTENING" > nul 2>&1␍
if %ERRORLEVEL% equ 0 (␍
echo [안내] 백엔드 웹 서버가 이미 정상 동작 중입니다.␍
echo [접속] 기본 웹 브라우저를 실행합니다: http://localhost:5000/␍
start "" http://localhost:5000/␍
goto done␍
)␍
␍
REM 2. 서버 미실행 시 백엔드 구동␍
echo [*] 백엔드 웹 서버가 중지되어 있습니다. 서버 구동을 시작합니다...␍
␍
REM WSL2 환경 확인 및 구동 시도␍
where wsl > nul 2>&1␍
if %ERRORLEVEL% equ 0 (␍
echo [*] WSL2(Ubuntu) 가상환경을 감지하였습니다. WSL2 백그라운드 서버를 실행합니다...␍
start /min "3D Drawing Server (WSL2)" wsl -d Ubuntu bash -c "cd '/mnt/e/11. 2026 PROJECT/30 도면을 3D로 구현/01 대산당진_수로암거(STA13+611)_옹벽형날개벽/Code/3d-drawing' && ./run_wsl.sh"␍
goto wait_loop␍
)␍
␍
REM WSL2가 없을 경우 Windows Python 환경으로 구동␍
echo [*] Windows Python 환경으로 웹 서버를 실행합니다...␍
cd /d "%WORK_DIR%"␍
start /min "3D Drawing Server" python app.py␍
␍
:wait_loop␍
echo [*] 서버 초기화 및 기동 대기 중...␍
set retry=0␍
␍
:check_port␍
ping -n 2 127.0.0.1 > nul␍
netstat -ano | findstr /R ":5000.*LISTENING" > nul 2>&1␍
if %ERRORLEVEL% equ 0 (␍
echo [성공] 서버 기동 완료! (포트 5000 활성화됨)␍
echo [접속] 기본 웹 브라우저를 실행합니다: http://localhost:5000/␍
start "" http://localhost:5000/␍
goto done␍
)␍
␍
set /a retry=%retry%+1␍
if %retry% lss 10 (␍
goto check_port␍
)␍
␍
echo [주의] 서버 기동 대기 시간이 초과되었습니다. 브라우저를 열어 확인합니다...␍
start "" http://localhost:5000/␍
␍
:done␍
echo.␍
echo ===============================================================================␍
echo 실행이 완료되었습니다. 웹 브라우저에서 3D 모델을 확인하실 수 있습니다.␍
echo - 웹 접속 주소: http://localhost:5000/␍
echo ===============================================================================␍
echo.␍
ping -n 3 127.0.0.1 > nul␍
+33 -23
View File
@@ -1,5 +1,4 @@
import os import os
import json
import tempfile import tempfile
from flask import Flask, request, jsonify, send_file, Response from flask import Flask, request, jsonify, send_file, Response
from flask_cors import CORS from flask_cors import CORS
@@ -17,6 +16,27 @@ current_drawing_data = None
current_model_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('/') @app.route('/')
def index(): def index():
return send_file('index.html') return send_file('index.html')
@@ -62,24 +82,7 @@ def api_classify():
drawing_name = body.get('drawingName', '') drawing_name = body.get('drawingName', '')
manual_type = body.get('manualType') # Optional: 'GENERAL' or 'REBAR' manual_type = body.get('manualType') # Optional: 'GENERAL' or 'REBAR'
res = cad_parser.classify_drawing_by_name(drawing_name) classification_obj = _resolve_classification(drawing_name, manual_type)
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'])
}
# Update active drawing classification in memory and regenerate 3D model # Update active drawing classification in memory and regenerate 3D model
for d in current_drawings_list: for d in current_drawings_list:
@@ -135,12 +138,19 @@ def api_upload():
# 2. Parse each uploaded DWG file # 2. Parse each uploaded DWG file
parsed_drawings = [] 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) drawing_data = cad_parser.parse_dwg(temp_path, custom_filename=filename)
parsed_drawings.append(drawing_data) parsed_drawings.append(drawing_data)
except Exception as e: except Exception as e:
return jsonify({'status': 'error', 'message': f'도면 분석 중 오류 발생 ({filename}): {str(e)}'}), 500 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 global current_drawing_data, current_model_data, current_drawings_list
if reset_flag or current_drawings_list is None: if reset_flag or current_drawings_list is None:
-1
View File
@@ -1,7 +1,6 @@
import os import os
import re import re
import math import math
import json
import ezdwg import ezdwg
+3 -3
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <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" 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" /> <link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
</head> </head>
@@ -14,8 +14,8 @@
<div class="brand"> <div class="brand">
<div class="logo-icon">3D</div> <div class="logo-icon">3D</div>
<div> <div>
<h1>암거 및 날개벽 3D 외형·배근 모델러</h1> <h1>옹벽형날개벽 3D 모델러</h1>
<div style="font-size: 11px; color: var(--text-muted);" id="header-subtitle">도면을 불러와주세요 (1단계)</div> <div class="header-subtitle" id="header-subtitle">도면을 불러와주세요 (1단계)</div>
</div> </div>
</div> </div>
+1 -2
View File
@@ -1,6 +1,5 @@
import math import math
import numpy as np import numpy as np
import json
import rebar_model import rebar_model
@@ -122,7 +121,6 @@ def get_wingwall_mesh(side='LEFT', custom_params=None, culvert_params=None, num_
pos_str = 'START_RIGHT' pos_str = 'START_RIGHT'
is_left_side = ('LEFT' in pos_str) is_left_side = ('LEFT' in pos_str)
is_start = ('START' in pos_str)
cp = custom_params or {} cp = custom_params or {}
culv = culvert_params or {} culv = culvert_params or {}
@@ -554,6 +552,7 @@ def generate_full_model(params=None, drawings_list=None):
if __name__ == '__main__': if __name__ == '__main__':
import json
res = generate_full_model() res = generate_full_model()
print("Full model generated:") print("Full model generated:")
print(" Total volume:", res['summary']['total_volume'], "m3") 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_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)) 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([ 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, 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['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']) to_world(s, sec['u_stem_front'], sec['z_stem_top'])
]) ])
w5_bars.append([ w5_bars.append([
@@ -398,7 +399,7 @@ def generate_wingwall_rebars(side='LEFT', custom_params=None, culvert_params=Non
'desc': '벽체 전면 수직 주철근 (수로측 전면, 하단 저판 정착)', 'desc': '벽체 전면 수직 주철근 (수로측 전면, 하단 저판 정착)',
'spacing': '@250', 'spacing': '@250',
'shape_type': 'STEM_VERTICAL_FRONT', '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, 'length_m': w1_len,
'count': n1, 'count': n1,
'total_length_m': round(w1_len * n1, 3), 'total_length_m': round(w1_len * n1, 3),
+30 -22
View File
@@ -35,42 +35,49 @@ body {
/* Header */ /* Header */
header { header {
height: 60px; height: 70px;
background: var(--bg-secondary); background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 0 14px; padding: 0 16px;
gap: 10px; gap: 12px;
z-index: 100; z-index: 100;
} }
.brand { .brand {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 12px;
flex-shrink: 0; flex-shrink: 0;
} }
.brand .logo-icon { .brand .logo-icon {
width: 32px; width: 38px;
height: 32px; height: 38px;
background: linear-gradient(135deg, #3b82f6, #8b5cf6); background: linear-gradient(135deg, #3b82f6, #8b5cf6);
border-radius: 8px; border-radius: 9px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-weight: 800; font-weight: 800;
font-size: 15px; font-size: 18px;
color: white; color: white;
} }
.brand h1 { .brand h1 {
font-size: 14px; font-size: 21px; /* 기존 14px 대비 1.5배 확대 */
font-weight: 700; font-weight: 700;
letter-spacing: -0.3px; letter-spacing: -0.3px;
color: var(--text-primary); 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 { .badge {
@@ -87,9 +94,9 @@ header {
.workflow-stepper { .workflow-stepper {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: 6px;
background: var(--bg-primary); background: var(--bg-primary);
padding: 4px 6px; padding: 5px 8px;
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
} }
@@ -97,8 +104,8 @@ header {
.step-pill { .step-pill {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 10px;
padding: 5px 10px; padding: 6px 12px;
border-radius: 6px; border-radius: 6px;
border: 1px solid transparent; border: 1px solid transparent;
background: transparent; background: transparent;
@@ -125,17 +132,18 @@ header {
} }
.step-num { .step-num {
width: 20px; width: 28px;
height: 20px; height: 28px;
border-radius: 50%; border-radius: 50%;
background: var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 11px; font-size: 15px; /* 기존 11px 대비 확대 */
font-weight: 800; font-weight: 800;
color: var(--text-secondary); color: var(--text-secondary);
flex-shrink: 0;
} }
.step-pill.active .step-num { .step-pill.active .step-num {
@@ -156,15 +164,15 @@ header {
} }
.step-title { .step-title {
font-size: 11px; font-size: 16.5px; /* 기존 11px 대비 1.5배 확대 */
font-weight: 700; font-weight: 700;
line-height: 1.2; line-height: 1.25;
} }
.step-sub { .step-sub {
font-size: 9px; font-size: 13.5px; /* 기존 9px 대비 1.5배 확대 */
color: var(--text-muted); color: var(--text-muted);
margin-top: 1px; margin-top: 2px;
} }
.step-pill.active .step-sub { .step-pill.active .step-sub {
@@ -177,8 +185,8 @@ header {
.step-arrow { .step-arrow {
color: var(--text-muted); color: var(--text-muted);
font-size: 11px; font-size: 16.5px; /* 기존 11px 대비 1.5배 확대 */
padding: 0 2px; padding: 0 4px;
} }
.header-tools { .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.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.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="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> <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 = ` svgBody = `
<polyline points="85,105 155,105 162,20" fill="none" stroke="${col}" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/> <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> <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.labelSprites = [];
this.rebarLabelSprites = []; this.rebarLabelSprites = [];
this.wingwallNameSprites = []; this.wingwallNameSprites = [];
this.dimensionGroup = null;
this.dimensionSprites = [];
this.displayMode = 'solid'; // 'solid', 'wireframe', 'transparent' this.displayMode = 'solid'; // 'solid', 'wireframe', 'transparent'
this.stepMode = 2; // 2: Exterior only, 3: Exterior (ghosted) + 3D Rebars this.stepMode = 2; // 2: Exterior only, 3: Exterior (ghosted) + 3D Rebars
this.selectedObjectId = null; this.selectedObjectId = null;
@@ -79,6 +81,11 @@ class Viewer3D {
this.rebarGroupRoot = new THREE.Group(); this.rebarGroupRoot = new THREE.Group();
this.scene.add(this.rebarGroupRoot); 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 // Ground Grid & Axes
this.setupGridAndAxes(); this.setupGridAndAxes();
@@ -115,6 +122,7 @@ class Viewer3D {
this.meshes = {}; this.meshes = {};
this.clearSectionMarkers(); this.clearSectionMarkers();
this.clearWingwallNameSprites(); this.clearWingwallNameSprites();
this.clearDimensionAnnotations();
const objects = modelData.objects || {}; const objects = modelData.objects || {};
const botEl = objects.culvert?.dimensions?.bot_el ?? 33.404; const botEl = objects.culvert?.dimensions?.bot_el ?? 33.404;
@@ -325,6 +333,9 @@ class Viewer3D {
setStepMode(stepNumber) { setStepMode(stepNumber) {
this.stepMode = stepNumber; this.stepMode = stepNumber;
if (stepNumber === 3) {
this.clearDimensionAnnotations();
}
this.applyStepVisualState(); this.applyStepVisualState();
} }
@@ -644,7 +655,7 @@ class Viewer3D {
const center = new THREE.Vector3(); const center = new THREE.Vector3();
box.getCenter(center); box.getCenter(center);
const sprite = this.createTextSprite(item.name, item.color); 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.userData.wingwallId = item.id;
sprite.visible = false; sprite.visible = false;
this.scene.add(sprite); this.scene.add(sprite);
@@ -671,8 +682,8 @@ class Viewer3D {
// 암거본체 선택 시 날개벽 구조물의 이름만 표시 (Requirement 2.2.2 & 3.2.2) // 암거본체 선택 시 날개벽 구조물의 이름만 표시 (Requirement 2.2.2 & 3.2.2)
sp.visible = true; sp.visible = true;
} else { } else {
// 특정 날개벽 선택 시 해당 날개벽 이름만 표시 // 특정 날개벽 선택 시: 2단계에서는 치수선 가독성을 위해 숨김, 3단계에서는 선택된 날개벽 이름 표시
sp.visible = (sp.userData.wingwallId === currentFocus); sp.visible = (this.stepMode === 3 && sp.userData.wingwallId === currentFocus);
} }
} }
} }
@@ -680,18 +691,314 @@ class Viewer3D {
selectObject(objectId) { selectObject(objectId) {
this.selectedObjectId = objectId; this.selectedObjectId = objectId;
if (this.stepMode === 3) return; if (this.stepMode === 3) return;
for (const id in this.meshes) {
const m = this.meshes[id]; const isCulvert = (!objectId || objectId === 'CULVERT');
const mat = m.group.userData.mat;
if (id === objectId) { if (isCulvert) {
mat.emissive = new THREE.Color(0x2563eb); // 2.1 암거 본체 선택: 메인 화면에 암거 본체 및 모든 구조물을 정상 진하게 표시
mat.emissiveIntensity = 0.45; this.clearDimensionAnnotations();
} else { for (const id in this.meshes) {
mat.emissive = new THREE.Color(0x000000); 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; 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) { toggleObjectVisibility(objectId, visible) {
@@ -837,8 +1144,8 @@ class Viewer3D {
requestAnimationFrame(() => this.animate()); requestAnimationFrame(() => this.animate());
this.controls.update(); this.controls.update();
// Requirement 3.3: 줌 확대/축소 시에도 3D 텍스트 크기 일정 유지 (Screen-space constant size) // Requirement 2.3 & 3.3: 줌 확대/축소 시에도 3D 텍스트 크기 일정 유지 (Screen-space constant size)
const sprites = [...this.rebarLabelSprites, ...this.labelSprites, ...this.wingwallNameSprites]; const sprites = [...this.rebarLabelSprites, ...this.labelSprites, ...this.wingwallNameSprites, ...this.dimensionSprites];
for (const sp of sprites) { for (const sp of sprites) {
if (sp && sp.visible) { if (sp && sp.visible) {
const dist = this.camera.position.distanceTo(sp.position); const dist = this.camera.position.distanceTo(sp.position);
+14 -1
View File
@@ -5,7 +5,6 @@ Verifies 2D drawing classification, DWG parsing, 3D solid & rebar modeling,
mesh watertightness, exporter functionality, and Flask API endpoints. mesh watertightness, exporter functionality, and Flask API endpoints.
""" """
import os import os
import sys
import json import json
import unittest import unittest
@@ -118,6 +117,19 @@ class Test3DDrawingPipeline(unittest.TestCase):
self.assertEqual(left_bars, 231) self.assertEqual(left_bars, 231)
self.assertEqual(right_bars, 277) self.assertEqual(right_bars, 277)
# W1 철근 3D 형상 검증: 벽체-저판 접합부에서 꺾이지 않고 저판 하면에서 1회만 꺾이는 ㄱ자 형상(3개 정점) 확인
for ww_key in ['WW_END_LEFT', 'WW_END_RIGHT']:
ww_groups = rebar_data['wingwalls'][ww_key]['groups']
w1_group = next((g for g in ww_groups if g['mark'] == 'W1'), None)
self.assertIsNotNone(w1_group)
for poly in w1_group['polylines']:
self.assertEqual(len(poly), 3, f"{ww_key} W1 bar must have 3 points (1 bend at footing bottom)")
# 저판 하면 구간(poly[0] -> poly[1]) 동일 Z 표고 유지
self.assertAlmostEqual(poly[0][2], poly[1][2], places=4)
# 벽체 긴 구간(poly[1] -> poly[2]) 접합부 꺾임 없이 일직선 연장 확인
self.assertAlmostEqual(poly[1][0], poly[2][0], places=4)
self.assertAlmostEqual(poly[1][1], poly[2][1], places=4)
def test_05_exporters(self): def test_05_exporters(self):
"""3D 모델 OBJ, STL, DXF 내보내기 검증""" """3D 모델 OBJ, STL, DXF 내보내기 검증"""
model_res = model_3d.generate_full_model() model_res = model_3d.generate_full_model()
@@ -143,6 +155,7 @@ class Test3DDrawingPipeline(unittest.TestCase):
# 1. 메인 웹페이지 # 1. 메인 웹페이지
r_index = self.flask_app.get('/') r_index = self.flask_app.get('/')
self.assertEqual(r_index.status_code, 200) self.assertEqual(r_index.status_code, 200)
r_index.close()
# 2. 기본 도면 데이터 조회 API # 2. 기본 도면 데이터 조회 API
r_def = self.flask_app.get('/api/default-drawing') r_def = self.flask_app.get('/api/default-drawing')