# S-CANVAS

> **Generative Design & Visualization Engine**
> CAD 도면(DXF) · 공개 DEM · 위성영상 · Generative AI 를 결합해 **건설/토목 프로젝트의 3D 조감도와 구조물 시각화**를 자동 생성하는 데스크톱 엔진. **Saman Corp.** 자체 개발.

[![Python](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
[![Platform](https://img.shields.io/badge/platform-Windows%2010%2F11-lightgrey.svg)]()
[![GUI](https://img.shields.io/badge/GUI-CustomTkinter-success.svg)]()
[![3D](https://img.shields.io/badge/3D-PyVista%20%2B%20VTK-orange.svg)]()

---

## 1. 프로그램 개요

S-CANVAS 는 토목·건설 도면(DXF)을 **클릭 4번**에 사실 기반 3D 조감도로 변환하는 자동 파이프라인입니다.

```
[DXF 도면]                                              [최종 결과물]
   │                                                         ▲
   ▼                                                         │
레이어 분류 → 등고선/구조물 분리                       AI 렌더링 (구조 보존)
   │                                                         ▲
   ▼                                                         │
TIN 생성 (Delaunay 삼각화)                     ← 제어맵 추출 (Depth + Texture)
   │                                                         ▲
   ▼                                                         │
DEM 자동 페치 (AWS Terrarium) → smoothstep blend             │
   │                                                         │
   ▼                                                         │
구조물 자동 빌드 (4단계):                                    │
  ① 위치 인식 (PCA frame)                                    │
  ② 굴착 pad + smoothstep 전이                                │
  ③ TIN 재-Delaunay (수정된 지형)                            │
  ④ 3D 메시 배치 (취수탑/옹벽/수문/제수변실)                 │
   │                                                         │
   ▼                                                         │
위성 타일 UV 매핑 (Seam-free 통합 메시) ────────────────────┘
```

### 1.1 핵심 진입점

- **`scanvas_maker.py`** — 메인 GUI 엔트리 (~6,300 LOC, CustomTkinter 기반)
- **`splash.py`** — 인트로 MP4 스플래시 (cv2 + Tk Toplevel)
- 메인 클래스: `SCanvasApp(ctk.CTk)` — 사이드바 워크플로 + 메인 맵뷰
- 상태 저장: `%LOCALAPPDATA%\S-CANVAS\` (DB · 로그 · 캐시)

### 1.2 모듈 구성

| 모듈 | 역할 |
|---|---|
| `scanvas_maker.py` | 메인 GUI, Step 1~4 워크플로 |
| `dem_extender.py` | AWS Terrarium DEM 페치 + 도넛 링 메시 |
| `geo_referencing.py` | 4점 매칭 GeoReferencing |
| `structure_placement.py` | 구조물 위치/방향 자동 인식 + TIN 굴착 |
| `structure_templates.py` | 구조물 유형 레지스트리 (Registry 패턴) |
| `intake_tower_*.py` | 취수탑 파서 + 3D 빌더 |
| `retaining_wall_*.py` | 옹벽 파서 + 3D 빌더 |
| `gate_*.py` | 수문 파서 + 3D 빌더 |
| `valve_chamber_*.py` | 제수변실 파서 + 3D 빌더 |
| `detail_parser.py` | 상세도면 DXF 치수 파서 (TEXT/MTEXT/DIMENSION) |
| `filename_classifier.py` | 파일명 → 구조물 유형 추정 |
| `dxf_geometry.py` | DXF 엔티티 → 좌표 추출 |
| `tile_downloader.py` | 위성 XYZ 타일 다운로드 |
| `gemini_renderer.py` | Gemini API/Vertex AI 렌더링 워커 |
| `structure_vlm_feedback.py` | Gemini Vision 기반 구조물 결과 검증 |
| `harness/` | 품질·재현성·이력 서브시스템 (SQLite + structlog) |

### 1.3 주요 기능

- **TIN 자동 생성**: DXF 등고선 → Delaunay 삼각화 → zero-basing
- **DEM 자동 확장**: 도면 범위 밖을 AWS 글로벌 DEM 으로 자동 채움 (3-Zone smoothstep 블렌드)
- **구조물 자동 빌드**: 도면 → 4단계 (위치인식 → 굴착 → TIN수정 → 3D배치). 취수탑·옹벽·수문·제수변실
- **Seam-free 통합 메시**: `merge_points=True` + `compute_normals(feature_angle=180)` 로 도면-DEM 경계 쉐이딩 불연속 제거
- **위성 타일 UV 매핑**: Google / ArcGIS / Bing / Vworld 등 9종 지원
- **AI 조감도 렌더링**: Gemini (Vertex / API Key) + Stability AI 3단계 폴백
- **VLM 피드백 루프**: Gemini Vision 으로 빌더 결과 ↔ 원본 도면 자동 검증
- **결정론적 재현성 (Harness)**: DXF 해시 → seed → 같은 입력 → 같은 출력
- **자동 품질 검증**: OpenCV 기반 해상도/선명도/채도 게이트

### 1.4 구조물 자동 빌드 워크플로 (핵심 차별화)

위성지도 결합 **이전** 에 실행되어 TIN 자체를 수정합니다. 구조물이 자연스럽게 지형에 묻히도록 4단계로 처리:

| 단계 | 내용 | 핵심 모듈 |
|---|---|---|
| **① 위치 인식** | DXF 폴리곤 추출 → PCA 로 frame 방향 결정 | `structure_placement.py` (`compute_orientation_from_points`) |
| **② 굴착 (Excavation)** | 폴리곤 평탄 pad + smoothstep 전이 — 구조물 발자국 영역만 매끈하게 | `structure_placement.py` (`apply_placement`) |
| **③ TIN 수정** | 굴착된 영역으로 Delaunay 재계산 → 메시 재구성 | `scanvas_maker.py` 내부 |
| **④ 3D 메시 배치** | 4개 quad 코너에 fit + `embed_offset` 으로 TIN 관통 방지 | `structure_placement.py` (`fit_meshes_to_quad`) |

**지원 구조물 4종** (`structure_types/structure_v1.yaml` 정의):

| 유형 | 빌더 | 입력 |
|---|---|---|
| 취수탑 (Intake Tower) | `intake_tower_3d_builder.py` | DXF 평면도 → 외곽 폴리곤 + 높이 |
| 옹벽 (Retaining Wall) | `retaining_wall_3d_builder.py` | DXF 단면도 → 단면 형상 + 길이 |
| 수문 (Gate) | `gate_3d_builder.py` | DXF 정면도 → 게이트 + 슬라브 |
| 제수변실 (Valve Chamber) | `valve_chamber_3d_builder.py` | DXF 평면도 → 박스 + 슬래브 + 점검구 |

**자동 분류**: `filename_classifier.py` 가 파일명 (예: "신설 취수탑.dxf") 으로 1차 후보 제시 → 사용자가 GUI 다이얼로그에서 최종 확정 → `structure_templates.REGISTRY` 가 적절한 빌더로 라우팅.

**치수 자동 추출**: `detail_parser.py` 가 DXF TEXT/MTEXT/DIMENSION 엔티티에서 치수값 파싱 → 빌더 파라미터로 변환 (수동 입력 불필요).

**메시 방향 보정** (디버깅에서 얻은 교훈):
- CW(시계방향) picks + Y-flip + detail/TIN 상대회전 + PCA frame_angle
- → 앞뒤 뒤집힘 / 좌우 반전 문제 자동 해결

**VLM 검증 루프**: 빌드 완료 후 `structure_vlm_feedback.py` 가 렌더 결과와 원본 DXF 평면도를 Gemini Vision 에 동시 전송 → 차이점 자연어 응답 → 사용자가 재시도 결정.

---

## 2. 필수 Package

### 2.1 시스템 요구사항

- **Python**: 3.9+ (권장 3.11+, 빌드 머신 검증 = 3.9.13)
- **Platform**: Windows 10/11 (Linux/macOS 는 GUI 일부 제한)
- **메모리**: 8GB+ (PyVista + VTK + 위성 타일 캐시)
- **디스크**: 1GB+ 여유 (DEM/타일 캐시 포함)

### 2.2 설치

```bash
pip install -r requirements.txt
```

### 2.3 카테고리별 패키지

```text
[GUI]
customtkinter==5.2.2       모던 Tk 기반 GUI 프레임워크
tkintermapview==1.29       위성 지도 인터랙티브 뷰
Pillow==11.3.0             이미지 처리 (PIL)

[3D / Mesh]
pyvista==0.46.5            메시 처리 + 렌더링 (VTK 자동 설치, ~150MB)

[Geospatial / DXF]
ezdxf==1.4.2               DXF 파싱 (R12~R2024)
pyproj==3.6.1              좌표계 변환 (EPSG)
rasterio==1.4.3            (선택) 로컬 GeoTIFF DEM (NGII 5m 등)

[Numerical]
numpy==2.0.2
scipy==1.13.1              Delaunay 삼각화, KDTree
matplotlib==3.9.4          signed-distance polygon paths

[Image / Video]
opencv-python==4.13.0.92   인트로 MP4 + Harness QualityValidator

[Network]
requests==2.32.5           HTTP 타일/DEM/AI 페치

[AI Rendering]
google-genai==1.47.0       Gemini SDK (Vertex AI / API Key 통합)
google-auth==2.49.2

[Persistence / Logging]
SQLAlchemy==2.0.49         JobRecord ORM
structlog==25.5.0          구조화 로깅
PyYAML==6.0.3              prompt_v*.yaml / structure_v*.yaml

[Build (선택)]
pyinstaller==6.18.0        Windows .exe 빌드
```

---

## 3. 간단 사용법

### 3.1 실행

```bash
python scanvas_maker.py
```

실행 흐름:
1. `splash.py` 가 `Design/logo_intro.mp4` 8초 인트로 재생 (페이드 인/아웃)
2. `SCanvasApp` 메인 창 기동 (1200×900)
3. 사이드바에서 워크플로 진행

### 3.2 사이드바 워크플로

```
SETTINGS
  ├─ Satellite Source         (Google / ArcGIS / Vworld 등 선택)
  ├─ Vworld API Key           (Vworld 사용 시)
  ├─ AI Engine                (Gemini Vertex / Gemini API / Stability)
  ├─ GCP / API Key            (선택한 엔진의 인증 정보)
  └─ Project CRS              (EPSG:5187 / 5186 / 5185 / 5181 / 3857)

WORKFLOW
  1.   TIN 생성 (DXF)            ← DXF 파일 선택 + Delaunay 삼각화
  🎯  TIN 이용 범위               ← (선택) 정밀 보존 영역 지정
  1.5  DEM 으로 TIN 확장          ← AWS Terrarium 도넛 링 메시 추가
  ──  구조물 상세 3D 빌드          ← 위치인식 → 굴착 → TIN수정 → 3D배치
                                    (취수탑·옹벽·수문·제수변실)
  2.   위성지도 결합              ← 위성 타일 다운로드 + UV 매핑
                                    (구조물 굴착 반영된 TIN 위에 매핑)
  3.   제어맵 추출                ← Depth / Texture 캡처 (PyVista offscreen)
  4.   AI 렌더링                 ← 최종 조감도 생성 (Gemini / Stability)

OPTIONS
  ├─ □ 와이어프레임 보기
  ├─ 뷰 버퍼 (%)              (Step 2/3 외곽 여유)
  ├─ □ 지형 확장 (DEM)
  └─ Light / Dark 테마 토글
```

### 3.3 결과 파일

| 파일 | 의미 |
|---|---|
| `rendered_birdseye.png` | 최종 AI 렌더 결과 |
| `capture_textured.png` | Step 3 위성+DEM 합성 control map |
| `depth_map.png` | Step 3 깊이 맵 |
| `lineart_map.png` | Step 3 라인아트 |
| `guide_composite.png` | 합성 가이드 이미지 |
| `scanvas_jobs.db` | SQLite 작업 이력 (Harness) |
| `scanvas_harness.log` | structlog 출력 |

### 3.4 인증 / API 키 설정

**모든 키는 코드에 하드코딩되지 않습니다.** 환경변수 또는 사이드바 입력란을 사용하세요.

| 키 | 환경변수 | 발급처 | 용도 |
|---|---|---|---|
| GCP Service Account | (파일) `gcp-key.json` | GCP Console → IAM | Gemini Vertex AI |
| GCP Project ID | `GCP_PROJECT_ID` | (gcp-key.json 자동 추출) | Vertex AI 라우팅 |
| GCP Location | `GCP_LOCATION` | — | Vertex AI 리전 (기본 `global`) |
| Gemini API Key | (UI 입력) | [aistudio.google.com](https://aistudio.google.com/) | Gemini API 직접 호출 |
| Stability API Key | (UI 입력) | [platform.stability.ai](https://platform.stability.ai/) | Stability AI 폴백 |
| **Vworld API Key** | **`VWORLD_API_KEY`** | [vworld.kr/dev](https://www.vworld.kr/dev/v4api.do) | 한국 위성 타일 (선택) |
| Google API Key (VLM) | `GOOGLE_API_KEY` 또는 `GEMINI_API_KEY` | aistudio.google.com | structure_vlm_feedback |

**Windows 환경변수 설정 예**:
```powershell
# 영구 (사용자 환경)
[Environment]::SetEnvironmentVariable("VWORLD_API_KEY", "your-key-here", "User")
[Environment]::SetEnvironmentVariable("GCP_PROJECT_ID", "your-project-id", "User")

# 현재 세션만
$env:VWORLD_API_KEY = "your-key-here"
```

**Gemini Vertex AI (서비스 어카운트)**:
1. GCP 프로젝트에서 서비스 어카운트 키 (JSON) 발급
2. `gcp-key.json` 을 프로젝트 루트 또는 `%LOCALAPPDATA%\S-CANVAS\` 에 배치
3. 자동으로 `GOOGLE_APPLICATION_CREDENTIALS` 설정 + `project_id` 추출
4. **`gcp-key.json` 은 `.gitignore` 차단됨** — 절대 커밋 금지

---

## 4. URL 기반 외부 데이터 소스

S-CANVAS 가 사용하는 모든 외부 URL 엔드포인트입니다. **인터넷 연결 필요**.

### 4.1 DEM (지형 고도) — 인증 불필요

| 소스 | URL 패턴 | 비고 |
|---|---|---|
| AWS Open Terrain Tiles | `https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png` | 글로벌, ~30m 정확도, **API 키 없음**. terrarium PNG 디코딩: `elev_m = (R*256 + G + B/256) - 32768`. SHA1(bbox+zoom) 캐시 → 재실행 시 네트워크 0 |

`dem_extender.py:48` 에 정의 → `cache/dem/` 에 PNG 캐시.
로컬 GeoTIFF (`cache/dem/local.tif`) 가 있으면 우선 사용 (rasterio 필요).

### 4.2 위성 타일 (Texture)

`scanvas_maker.py:248-258` — UI 에서 사용자 선택.

| 서버 | URL 패턴 | 인증 |
|---|---|---|
| Google Satellite | `https://mt{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}` | 불필요 |
| Google Hybrid | `https://mt{s}.google.com/vt/lyrs=y&x={x}&y={y}&z={z}` | 불필요 |
| ArcGIS World Imagery | `https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}` | 불필요 |
| ArcGIS Hybrid | `https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}` | 불필요 |
| Bing Aerial | `https://ecn.t{s}.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1` | 불필요 |
| OpenStreetMap | `https://tile.openstreetmap.org/{z}/{x}/{y}.png` | 불필요 |
| OpenTopo | `https://tile.opentopomap.org/{z}/{x}/{y}.png` | 불필요 |
| Vworld 위성 | `https://api.vworld.kr/req/wmts/1.0.0/{vworld_key}/Satellite/{z}/{y}/{x}.jpeg` | **Vworld 키 필요** |
| Vworld 기본 | `https://api.vworld.kr/req/wmts/1.0.0/{vworld_key}/Base/{z}/{y}/{x}.png` | **Vworld 키 필요** |
| Vworld 하이브리드 | `https://api.vworld.kr/req/wmts/1.0.0/{vworld_key}/Hybrid/{z}/{y}/{x}.png` | **Vworld 키 필요** |

`tile_downloader.py:11` 에서 `tile.openstreetmap.org` 폴백 처리.

### 4.3 AI 렌더링 — 인증 필수

| 엔진 | 엔드포인트 | 인증 방식 |
|---|---|---|
| **Gemini (Vertex AI)** | `google-genai` SDK 내부 호출 | GCP Service Account (`gcp-key.json`) — `GOOGLE_APPLICATION_CREDENTIALS` 자동 설정 |
| **Gemini (API Key)** | `google-genai` SDK 내부 호출 | aistudio.google.com 키 |
| **Stability Conservative Upscale** | `https://api.stability.ai/v2beta/stable-image/upscale/conservative` | Bearer API Key |
| **Stability Creative Upscale** | `https://api.stability.ai/v2beta/stable-image/upscale/creative` | Bearer API Key |
| **Stability img2img (sd3.5-large)** | `https://api.stability.ai/v2beta/stable-image/generate/sd3` | Bearer API Key |

`scanvas_maker.py:6432` (Conservative/Creative) · `scanvas_maker.py:6477` (img2img) · `gemini_renderer.py` (Gemini 워커).

Stability 는 3단계 자동 폴백 (Conservative → Creative → img2img).

### 4.4 위성 지도 미리보기 (GUI)

`scanvas_maker.py:572` — `tkintermapview` 의 기본 타일 서버:
- `https://mt0.google.com/vt/lyrs=s&x={x}&y={y}&z={z}`

GeoReferencing 4점 매칭 시 사용자가 위성 위에 클릭하는 인터랙티브 맵.

### 4.5 캐시 정책

| 캐시 위치 | 내용 | 무효화 키 |
|---|---|---|
| `cache/dem/` | AWS Terrarium PNG 타일 | SHA1(bbox + zoom) |
| `cache/tiles/` | 위성 타일 PNG | URL 해시 |
| `scanvas_jobs.db` | SQLite Job 이력 (재현성) | dxf_hash + seed + prompt_hash |

**오프라인 동작**: 캐시 hit 시 네트워크 호출 0회. 첫 실행 후 동일 영역은 오프라인 가능.

---

## 5. 좌표계 / 단위

지원 EPSG (사이드바 선택):

| EPSG | 이름 | 영역 |
|---|---|---|
| **5187** | Korea 2000 / Central Belt 2010 | 한국 중부 (서울·경기·충청) — **기본값** |
| 5186 | Korea 2000 / Central Belt | 한국 중부 (구) |
| 5185 | Korea 2000 / West Belt 2010 | 한국 서부 |
| 5181 | Korea 2000 / Unified CS | 통합 좌표계 |
| 3857 | Web Mercator | 글로벌 (위성 타일 호환) |

**단위 강제**: DXF `$INSUNITS` 헤더가 부정확한 경우가 많아 **항상 m 로 강제** (`unit_override="m"`). KATEC 류 대형 좌표값을 mm 로 오판하는 사고 차단.

**Zero-Basing**: 큰 절대좌표(EPSG:5187 의 200,000m+) 의 float32 정밀도 손실 방지 → bbox 좌하단을 origin 으로 평행이동.

---

## 6. 디렉토리 구조

```
.
├── scanvas_maker.py              # 메인 진입점
├── splash.py                     # 인트로 스플래시
├── requirements.txt              # 의존성
├── S-CANVAS_brief.md             # 상세 기술 문서 (NotebookLM 소스)
├── CHANGELOG.md                  # 수정 이력 (역순)
│
├── dem_extender.py               # DEM 페치/링 메시
├── geo_referencing.py            # 4점 매칭
├── structure_placement.py        # 구조물 배치/굴착
├── structure_templates.py        # 구조물 레지스트리
├── intake_tower_parser.py + _3d_builder.py
├── retaining_wall_parser.py + _3d_builder.py
├── gate_parser.py + _3d_builder.py
├── valve_chamber_parser.py + _3d_builder.py
├── detail_parser.py
├── filename_classifier.py
├── polygon_reconstructor.py
├── dxf_geometry.py
├── tile_downloader.py
├── view_detector.py
├── view_reconstructor.py
├── optional_detector.py
├── gemini_renderer.py
├── structure_vlm_feedback.py
├── resource_paths.py             # PyInstaller 번들/소스 경로 분기
│
├── harness/                      # 품질·재현성 서브시스템
│   ├── seed_manager.py           # DXF 해시 → seed
│   ├── quality_validator.py      # OpenCV 자동 QA
│   ├── prompt_registry.py        # YAML 버전 관리
│   └── logger.py                 # SQLite ORM + structlog
│
├── prompt_templates/
│   └── prompt_v1.yaml            # 시간대/앙각/구조보존 프롬프트
│
├── structure_types/
│   └── structure_v1.yaml         # 구조물 유형 정의
│
├── Design/                       # 브랜딩 자산
│   ├── logo_V2.png
│   ├── SAMAN_CI.gif
│   └── logo_intro.mp4            # 8s, 24fps, 1280×720
│
├── SAMPLE_CAD/                   # 테스트용 DXF 샘플
└── cache/dem/                    # 런타임 DEM 캐시 (gitignore)
```

---

## 7. 추가 문서

- **`S-CANVAS_brief.md`** — 시스템 아키텍처/혁신 포인트/메모리화된 결정 사항 (개발자/리뷰어용 심층 자료)
- **`CHANGELOG.md`** — 모든 수정 이력 (역순, 날짜/파일/사유/diff 요지)
- **`Build_log.txt`** — 사용자 원본 요청 이력

---

## 8. 라이선스 / 저작권

© 2026 **Saman Corp.** All rights reserved.

본 프로그램은 Saman Corp. 자체 개발 자산이며, 외부 라이선스가 부여된 패키지는 각 패키지의 라이선스를 따릅니다.

문의: 프로젝트 관리자에게 직접 연락
