# S-CANVAS Core/Plugin 분리 설계 (Phase 0 — 분석)

> **상태**: 분석/설계 단계 (Phase 0). 코드 변경 없음.
> **작성**: library-architect subagent (read-only survey).
> **기준 일자**: 2026-05-08
> **이전 단계**: 사용자 피드백 #10 — "코드 구조를 Core(핵심), plug-in(추가)으로 구분 필요".

---

## 0. 설계 원칙

이 계획은 다음 4가지를 동시에 만족해야 한다.

1. **현재 동작 깨지 않음** — `python scanvas_maker.py` 가 모든 단계에서 그대로 동작.
2. **점진적 마이그레이션** — 한 번에 모든 파일을 옮기지 않음. 2~3 세션에 걸쳐.
3. **`STRUCTURE_REGISTRY` 확장(replace 아님)** — 이미 잘 짜인 싱글톤 레지스트리를
   plugin entry-point 의 첫 번째 사용처로 만든다.
4. **library-architect.md 의 Phase 2 비전과 정렬** — `manifest.json` 기반 동적
   디스커버리, `.scanvas-lib` ZIP 배포가 최종 목표.

> **비목표**: PyPI 엔트리포인트, 온라인 마켓, Phase 3 의 "꿈" 단계는 본 문서 범위가
> 아니다. 로컬 폴더/ZIP 디스커버리만 다룬다.

---

## 1. 현재 구조 진단

### 1.1 모듈 그래프 (의존 방향)

```
                ┌──────────────────────────────┐
                │ scanvas_maker.py  (~7000 LOC)│  ← GUI 진입점, 거의 모든 것 import
                │  CustomTkinter SCanvasApp    │
                └─────────────┬────────────────┘
                              │
        ┌─────────────┬───────┼───────┬─────────────┬─────────────┐
        │             │       │       │             │             │
        ▼             ▼       ▼       ▼             ▼             ▼
  resource_paths  splash  dem_extender  geo_referencing  tile_downloader  dxf_geometry
                                                                                 ▲
                                                                                 │ (공유)
        ┌────────────────────────────────────────────────────────────────────────┘
        │
  structure_templates.py  ── REGISTRY (싱글톤)
        │           ├── SpillwayGateTemplate ──→ gate_parser, gate_3d_builder
        │           ├── IntakeTowerTemplate ──→ intake_tower_parser, intake_tower_3d_builder
        │           ├── ValveChamberTemplate ──→ valve_chamber_parser, valve_chamber_3d_builder
        │           ├── DetailedRetainingWallTemplate ──→ retaining_wall_parser, retaining_wall_3d_builder
        │           ├── BuildingTemplate / BridgeTemplate / TunnelPortalTemplate (placeholder)
        │           └── GenericStructureTemplate
        │
  structure_placement / structure_vlm_feedback / filename_classifier
  view_detector / view_reconstructor / polygon_reconstructor / detail_parser / optional_detector

  렌더링 백엔드 (선택 가능):
        ├── gemini_renderer.py        (Gemini Vertex/API)
        └── blender_renderer.py       (Blender 헤드리스 + bpy 빌더)
              └── gate_3d_builder_bpy.py + apply_blender_patch.py + fix_bpy_import.py
                    + validate_gate_params.py + params_to_json.py

  품질·이력:
        harness/
          ├── seed_manager.py
          ├── quality_validator.py
          ├── prompt_registry.py
          └── logger.py (SQLite ORM JobRecord)

  런타임 자산 (읽기):
        Design/, prompt_templates/, structure_types/

  사용자 데이터 (쓰기, %LOCALAPPDATA%\S-CANVAS\):
        cache/dem/, scanvas_jobs.db, scanvas_*.log
```

### 1.2 Core 후보 vs Plugin 후보

판정 기준: "이걸 빼면 GUI 가 떠도 1·1.5·2 단계 파이프라인이 되는가?"

| 모듈 | 분류 | 사유 |
|---|---|---|
| `scanvas_maker.py` | **Core** | GUI 엔트리·이벤트 루프·상태 보유 |
| `splash.py` | **Core** | 부팅 1회 호출, 의존성도 cv2/PIL 만 |
| `resource_paths.py` | **Core** | 모든 IO 경로의 진실. 대체 불가 |
| `dxf_geometry.py` | **Core** | 모든 파서/빌더의 공통 전처리 |
| `geo_referencing.py` | **Core** | 4점 매칭은 파이프라인 필수 |
| `dem_extender.py` | **Core** | 1.5 단계의 핵심. AWS 의존이지만 옵션 분기 있음 |
| `tile_downloader.py` | **Core** | 2 단계 (위성 결합) 필수 |
| `polygon_reconstructor.py` | **Core (util)** | 여러 파서가 공유 |
| `optional_detector.py` | **Core (util)** | 부속 컴포넌트 검출, 파서 공통 |
| `filename_classifier.py` | **Core (util)** | 키워드 → template_id 매핑, GUI 가 직접 호출 |
| `detail_parser.py` | **Core (util)** | 모든 구조물 파서가 공유하는 치수 추출 |
| `view_detector.py` / `view_reconstructor.py` | **Core (util)** | template 의 `try_view_based_*` 가 호출 |
| `structure_placement.py` | **Core (util)** | 파이프라인의 "위치인식 → 굴착 → 배치" 4단계 중 핵심 |
| `harness/*` | **Core** (선택적) | 이미 try/except 로 import 보호되어 있음. 항상 로드는 하지만 없어도 동작 |
| `structure_templates.py` | **Core (host)** + Plugin | `StructureTemplate` ABC 와 `TemplateRegistry` 는 Core, 7개 구체 클래스는 Plugin |
| `gate_*`, `intake_tower_*`, `valve_chamber_*`, `retaining_wall_*` (parser+builder 쌍) | **Plugin (structure)** | 각각 1종의 구조물. 빠져도 다른 구조물은 작동 |
| `gate_3d_builder_bpy.py` | **Plugin (structure-renderer variant)** | Blender 백엔드용 같은 구조물의 변종 |
| `validate_gate_params.py` | **Plugin (helper)** | gate-bpy 와 같이 묶여야 의미가 있음 |
| `apply_blender_patch.py`, `fix_bpy_import.py` | **Plugin (tooling)** | 일회성 패치 도구. 마이그레이션 대상 아님 |
| `params_to_json.py` | **Core (util)** | 모든 파서 결과를 JSON 직렬화. plugin 도 사용 |
| `gemini_renderer.py` | **Plugin (render)** | AI 렌더 백엔드의 한 종류. 다른 백엔드로 교체 가능 |
| `blender_renderer.py` | **Plugin (render)** | 같은 위치, 다른 엔진 |
| `structure_vlm_feedback.py` | **Plugin (qa)** | Gemini Vision 기반 QA. 빠져도 빌드 자체는 됨 |
| `structure_types/*.yaml`, `prompt_templates/*.yaml` | **데이터** | 자체로는 코드 아님. 각 plugin 이 자기 yaml 동봉 가능 |

### 1.3 발견된 사실

- `STRUCTURE_REGISTRY` 는 **이미** 잘 설계된 plugin host 다 (Singleton + ABC + dict).
  단지 `_register_defaults()` 가 import-bound 하드코딩이라는 점만 동적으로
  바꾸면 된다.
- `harness/` 는 **이미 plugin-스타일** (`try: from harness... except ImportError`).
  이 패턴을 다른 옵션 모듈(`structure_vlm_feedback`, 렌더러들)이 모방한다.
- `scanvas_maker.py` 의 import 블록(line 31-80) 자체가 **암묵적 디스커버리**다:
  각 모듈을 try-import 해서 가용성 플래그(`HARNESS_AVAILABLE`,
  `STRUCTURE_TEMPLATES_AVAILABLE`, `DEM_EXTENDER_AVAILABLE`,
  `STRUCTURE_VLM_AVAILABLE` 등)를 세운다. → 명시적 plugin manager 로 통합 가능.
- 구조물 plugin 은 `parser + builder` 2-파일 쌍이 표준. 일관성 양호.
- Blender 트랙은 `*_bpy.py` 접미사로 구분되어 있지만, 같은 디렉토리에 섞여 있다.

---

## 2. 제안 레이아웃

### 2.1 디렉토리 트리 (목표)

```
D:\2026\PROGRAM\1_S-CANVAS\
├── scanvas_maker.py                    # 메인 진입점 (그대로)
├── splash.py                           # (그대로)
├── resource_paths.py                   # (그대로)
│
├── core/                               # ★ 신규 패키지
│   ├── __init__.py
│   ├── plugin_manager.py               # ★ 플러그인 로더 (신규)
│   ├── manifest.py                     # ★ Manifest dataclass + 검증 (신규)
│   ├── interfaces.py                   # ★ Plugin abstract bases (신규)
│   │
│   ├── geo/                            # 좌표·DXF 공통
│   │   ├── __init__.py
│   │   ├── dxf_geometry.py             ← 이동
│   │   ├── geo_referencing.py          ← 이동
│   │   ├── tile_downloader.py          ← 이동
│   │   ├── dem_extender.py             ← 이동
│   │   └── polygon_reconstructor.py    ← 이동
│   │
│   ├── parsing/                        # 공통 파서·검출
│   │   ├── __init__.py
│   │   ├── detail_parser.py            ← 이동
│   │   ├── view_detector.py            ← 이동
│   │   ├── view_reconstructor.py       ← 이동
│   │   ├── optional_detector.py        ← 이동
│   │   └── filename_classifier.py      ← 이동
│   │
│   ├── structures/                     # 구조물 호스트 (ABC + Registry)
│   │   ├── __init__.py
│   │   ├── base.py                     ← structure_templates.py 의 추상부
│   │   ├── registry.py                 ← TemplateRegistry + REGISTRY
│   │   ├── placement.py                ← structure_placement.py
│   │   └── params_io.py                ← params_to_json.py
│   │
│   └── harness/                        ← 그대로 (이미 패키지)
│       ├── __init__.py
│       ├── seed_manager.py
│       ├── quality_validator.py
│       ├── prompt_registry.py
│       └── logger.py
│
├── plugins/                            # ★ 신규 패키지 (디스커버리 루트)
│   ├── __init__.py
│   │
│   ├── structures/                     # ── 구조물 라이브러리 ──
│   │   ├── spillway_gate/
│   │   │   ├── manifest.json           # ★ 신규
│   │   │   ├── plugin.py               # SpillwayGateTemplate (구 structure_templates.py 의 클래스)
│   │   │   ├── parser.py               ← gate_parser.py
│   │   │   ├── builder_pyvista.py      ← gate_3d_builder.py
│   │   │   ├── builder_blender.py      ← gate_3d_builder_bpy.py
│   │   │   ├── validate.py             ← validate_gate_params.py
│   │   │   ├── parameters.json         # ★ 신규 (UI 폼 자동 생성용)
│   │   │   └── samples/                # ★ 신규 (1~3개 sample params)
│   │   │
│   │   ├── intake_tower/
│   │   │   ├── manifest.json
│   │   │   ├── plugin.py
│   │   │   ├── parser.py               ← intake_tower_parser.py
│   │   │   └── builder_pyvista.py      ← intake_tower_3d_builder.py
│   │   │
│   │   ├── valve_chamber/              … (동일 구조)
│   │   ├── retaining_wall/             … (동일 구조)
│   │   │
│   │   ├── building/                   # 현재 placeholder. 같은 레이아웃에 진입
│   │   ├── bridge/
│   │   └── tunnel_portal/
│   │
│   ├── render/                         # ── 렌더 백엔드 ──
│   │   ├── gemini/
│   │   │   ├── manifest.json
│   │   │   └── plugin.py               ← gemini_renderer.py
│   │   └── blender/
│   │       ├── manifest.json
│   │       ├── plugin.py               ← blender_renderer.py
│   │       └── tooling/                ← apply_blender_patch.py, fix_bpy_import.py
│   │
│   └── qa/                             # ── 품질/피드백 ──
│       └── vlm_feedback/
│           ├── manifest.json
│           └── plugin.py               ← structure_vlm_feedback.py
│
├── structure_types/                    # (그대로) — yaml은 plugin 외부 공유 데이터
│   └── structure_v1.yaml
│
├── prompt_templates/                   # (그대로)
│   └── prompt_v1.yaml
│
├── Design/                             # (그대로)
│
├── user_plugins/                       # ★ 신규 — 사용자 설치 라이브러리 (외부 ZIP 풀어 놓는 곳)
│   └── (ex: SpillwayGate_v2.scanvas-lib 풀린 폴더)
│
├── _unused/                            # (그대로) — 격리된 옛 코드
└── venv313/                            # (그대로)
```

### 2.2 핵심 결정

- **Core 는 단일 파이썬 패키지**(`core/`) 로 통일. 모든 공유 유틸·ABC·레지스트리는
  여기 들어간다.
- **Plugins 는 디렉토리 한 개 + manifest.json 한 개** = 한 plugin. 파일 수는
  그 안에서 자유롭게.
- **두 디스커버리 루트**:
  1. `plugins/` (소스 트리, 번들 plugin) — 항상 로드
  2. `user_plugins/` (사용자 데이터 폴더) — 옵트인. ZIP 압축 해제 위치
- **렌더링 백엔드도 plugin** — Gemini/Blender 가 동급. 미래에 ComfyUI·Stability 등을
  추가할 수 있게.

---

## 3. 플러그인 인터페이스

### 3.1 Abstract Bases (`core/interfaces.py`)

```python
# core/interfaces.py
from abc import ABC, abstractmethod
from pathlib import Path
import pyvista as pv

# (1) 구조물 plugin — 기존 StructureTemplate 의 외부 노출 이름
class StructurePlugin(ABC):
    plugin_id: str        # "spillway_gate" — manifest.id 와 일치
    template_id: str      # registry 키 (legacy 호환을 위해 별도 유지)
    name_ko: str
    description: str
    icon_hint: str = ""
    required_files: tuple[int, int, int] = (1, 2, 1)
    supports_view_based: bool = True

    @abstractmethod
    def get_parameter_schema(self) -> "list[ParamField]": ...
    @abstractmethod
    def parse(self, dxf_paths: list[str]) -> "StructureParams": ...
    @abstractmethod
    def build_meshes(self, params) -> list[tuple[pv.PolyData, str, float]]: ...

    def validate_params(self, params) -> tuple[bool, str]:
        return True, ""  # 기본 통과

# (2) 렌더 백엔드 plugin
class RenderPlugin(ABC):
    plugin_id: str        # "gemini", "blender"
    name_ko: str
    requires_credentials: bool

    @abstractmethod
    def is_available(self) -> tuple[bool, str]:
        """라이브러리/외부 의존이 갖춰졌는지 검사."""
    @abstractmethod
    def render(self, app, *, control_map: Path, prompt: str, **kwargs) -> Path:
        """제어맵 + 프롬프트 → PNG 경로."""

# (3) QA plugin
class QAPlugin(ABC):
    plugin_id: str

    @abstractmethod
    def evaluate(self, *, build_meshes, original_dxf, current_params) -> dict:
        """JSON-able 결과 (missing/incorrect/excess 등)."""

# (4) 데이터 클래스 (기존 그대로 노출)
# StructureParams, ParamField — core/structures/base.py 에서 재export
```

> **핵심**: `StructurePlugin` 은 **현재의 `StructureTemplate` 와 100% 호환** 되도록
> 같은 메서드 시그니처를 유지. 즉 기존 클래스에 부모 하나만 바꾸는 것으로 충분.

### 3.2 Manifest 스키마 (`core/manifest.py`)

library-architect.md 의 비전을 그대로 차용하되, **현재 단계에 필요한 필드만**:

```json
{
  "schema_version": "1.0",
  "id": "spillway_gate",
  "name": "여수로 래디얼 수문",
  "name_en": "Spillway Radial Gate",
  "kind": "structure",
  "category": "hydraulic_structure",
  "subcategory": "gate",
  "version": "1.2.0",
  "author": "Saman Corp.",
  "license": "Proprietary",
  "description": "ogee 여수로 + 래디얼 수문 + 공도교 + 개폐장치",
  "min_scanvas_version": "0.5.0",

  "entry": {
    "type": "python_module",
    "module": "plugin",
    "class": "SpillwayGatePlugin",
    "registry_id": "spillway_gate"
  },

  "parameters_schema": "parameters.json",
  "samples": ["samples/sample_default.json"],

  "metadata": {
    "complexity": "high",
    "polygon_estimate": 35000,
    "supports_pyvista": true,
    "supports_blender": true,
    "requires_gpu": false
  }
}
```

`kind` 값:
- `structure` → `StructurePlugin` 로딩, `STRUCTURE_REGISTRY` 에 등록
- `render` → `RenderPlugin` 로딩, `RENDER_REGISTRY` 에 등록
- `qa` → `QAPlugin` 로딩

`Manifest` 데이터클래스는 `core/manifest.py` 에서 정의하고, JSON 스키마 검증을
거친 뒤 plugin manager 에 넘긴다.

---

## 4. 디스커버리 메커니즘

### 4.1 단계별 디스커버리 정책

`core/plugin_manager.py` 의 `discover()` 알고리즘:

```python
# 의사코드
def discover(roots: list[Path]) -> dict[str, LoadedPlugin]:
    found = {}
    for root in roots:
        for plugin_dir in root.iterdir():
            if not plugin_dir.is_dir():
                continue
            mf = plugin_dir / "manifest.json"
            if not mf.exists():
                continue
            try:
                manifest = Manifest.load_validated(mf)
            except ValidationError as e:
                log.warning(f"[plugin] {plugin_dir.name}: invalid manifest: {e}")
                continue
            if not _version_compatible(manifest.min_scanvas_version):
                log.warning(f"[plugin] {plugin_dir.name}: needs >= {manifest.min_scanvas_version}")
                continue
            try:
                cls = _import_entry(plugin_dir, manifest.entry)
                instance = cls()
            except Exception as e:
                log.warning(f"[plugin] {plugin_dir.name}: failed to load: {e}")
                continue
            found[manifest.id] = LoadedPlugin(manifest, instance, plugin_dir)
    return found
```

**디스커버리 루트 우선순위**:

1. `<asset_root>/plugins/` — 번들 plugin (소스 트리 또는 PyInstaller `_MEIPASS`)
2. `<user_data_dir>/user_plugins/` — 사용자 설치 plugin
3. `(미래)` ZIP 경로 직접 — `*.scanvas-lib` 파일 압축 해제 후 (2) 로 이동

같은 `manifest.id` 가 둘 이상 발견되면 **버전 비교 후 최신 우선**, 동률이면
**user_plugins 우선** (사용자 override).

### 4.2 진입점 로딩

`plugin_dir / manifest.entry.module + ".py"` 에서 `manifest.entry.class` 를 import.
파이썬 import path 충돌 회피를 위해 `importlib.util.spec_from_file_location` 으로
**고유 모듈 이름**(`scanvas_plugin_<id>_<sha8>`) 을 부여한다.

### 4.3 등록

- `kind == "structure"` → `core/structures/registry.py` 의 `REGISTRY._templates[mf.id] = instance`
  (기존 `_register_defaults()` 가 하던 일)
- `kind == "render"` → `core/render/registry.py` (신규) 의 `RENDER_REGISTRY[mf.id] = instance`
- `kind == "qa"` → `QA_REGISTRY[mf.id] = instance`

`scanvas_maker.py` 의 부팅 코드 1줄만 추가:

```python
from core.plugin_manager import bootstrap_plugins
bootstrap_plugins()  # discover → register, 모든 REGISTRY 가 이 시점 이후 사용 가능
```

---

## 5. 마이그레이션 단계

### Phase 1 — Core 추출 (1 세션, 2~3시간)

**범위**: 디렉토리 이동 + import 경로 갱신만.

**작업**:
1. `core/` 패키지 생성 + 빈 `__init__.py`
2. 다음 파일들을 `core/<subdir>/` 로 git mv (또는 단순 이동):
   - `dxf_geometry.py`, `geo_referencing.py`, `tile_downloader.py`,
     `dem_extender.py`, `polygon_reconstructor.py` → `core/geo/`
   - `detail_parser.py`, `view_detector.py`, `view_reconstructor.py`,
     `optional_detector.py`, `filename_classifier.py` → `core/parsing/`
   - `structure_placement.py`, `params_to_json.py` → `core/structures/`
   - `harness/` → `core/harness/`
3. 각 파일의 상대 import 갱신 (e.g., `from view_detector import ...` →
   `from core.parsing.view_detector import ...`)
4. `scanvas_maker.py` 의 import 블록 갱신
5. **`structure_templates.py` 는 아직 그대로 둔다.** 거대 파일이라 분할은
   Phase 2 에서.

**Risk**: 순환 import. `dxf_geometry` ↔ `view_detector` 같은 사이클이 잠재.
**Mitigation**: 이동 전 `python -c "import scanvas_maker"` smoke test. 한 모듈씩
이동하면서 매번 검증.

**Rollback**: Phase 1 은 단순 이동이라 git revert 로 즉시 복구.

**Backwards compat**: 다음 5개 파일을 **shim 으로 유지**:
```python
# /dxf_geometry.py  (1줄짜리 shim)
from core.geo.dxf_geometry import *  # noqa: F401, F403
from core.geo.dxf_geometry import __all__  # noqa: F401
```
→ 외부 사용자(있다면)나 일회성 스크립트가 깨지지 않음. Phase 3 에 제거.

---

### Phase 2 — Structure plugin 추출 (1~2 세션)

**범위**: `structure_templates.py` 의 7개 구체 클래스를 `plugins/structures/<id>/`
로 분할 + manifest.json 작성.

**작업** (1 plugin 씩, 가장 단순한 것부터):
1. `plugins/structures/spillway_gate/` 생성
2. `gate_parser.py`, `gate_3d_builder.py`, `gate_3d_builder_bpy.py`,
   `validate_gate_params.py` 를 그 안으로 이동
3. `structure_templates.py` 의 `SpillwayGateTemplate` 클래스를 추출하여
   `plugins/structures/spillway_gate/plugin.py` 로 이동, 부모를 `StructurePlugin`
   으로 변경
4. `manifest.json`, `parameters.json`, `samples/sample_default.json` 작성
5. `core/structures/registry.py` 의 `_register_defaults()` 에서 `SpillwayGateTemplate`
   import 를 제거 — plugin manager 가 대신 등록
6. **레거시 import 호환**: `gate_parser.py` 등 기존 경로 import 가 있는 파일
   (현재 `gemini_renderer.py`, `apply_blender_patch.py`, `params_to_json.py` 등)을
   확인하여 새 경로로 갱신. 외부 사용자용 shim 은 아래 backward compat 섹션 참조.
7. 동일 절차로 IntakeTower → ValveChamber → RetainingWall → Building/Bridge/Tunnel
   → Generic 순서.

**Risk**: 6번 항목. 특히 `from gate_parser import GateParams` 같은 import 가
plugin 외부 코드에서 발견될 가능성.
**Mitigation**: 각 plugin 추출 후 grep 으로 잔존 import 확인. shim 추가.

**Rollback**: plugin 단위로 git tag 를 찍어 두면 한 plugin 만 되돌리기 가능.

---

### Phase 3 — Render/QA plugin 추출 + 사용자 plugin 디스커버리 (1 세션)

**범위**:
1. `gemini_renderer.py` → `plugins/render/gemini/plugin.py` (RenderPlugin 인터페이스 적용)
2. `blender_renderer.py` → `plugins/render/blender/plugin.py`
3. `structure_vlm_feedback.py` → `plugins/qa/vlm_feedback/plugin.py`
4. `core/plugin_manager.py` 의 `bootstrap_plugins()` 가 `user_data_dir() / "user_plugins"`
   를 두 번째 디스커버리 루트로 사용하도록 활성화
5. `*.scanvas-lib` (ZIP) → `user_plugins/<id>/` 풀어주는 헬퍼 `import_library_zip(path)`
6. GUI 사이드바에 "라이브러리 가져오기" 버튼 (드래그 앤 드롭 → import_library_zip)

**Risk**: PyInstaller 번들에서 `_MEIPASS` 안의 plugin 디렉토리를 importlib 가
정확히 찾는지. **Mitigation**: Phase 1 의 smoke test 를 PyInstaller 빌드 후에도
재실행. spec 파일에 `--add-data "plugins/;plugins/"` 추가.

**Rollback**: plugin manager bootstrap 호출 1줄을 주석 처리하면 즉시 옛 동작
복귀.

---

## 6. Backward Compatibility

### 6.1 기존 import 가 살아남는 방법

| 옛 경로 | 새 경로 | shim 전략 |
|---|---|---|
| `from gate_parser import GateParams, parse_gate_dxf` | `plugins.structures.spillway_gate.parser` | **별도 shim 모듈 안 둠**. 호출자 모두 내부 코드라 직접 갱신 |
| `from gate_3d_builder import GateBuilder` | `plugins.structures.spillway_gate.builder_pyvista` | 위와 동일 |
| `from structure_templates import REGISTRY as STRUCTURE_REGISTRY` | `from core.structures.registry import REGISTRY as STRUCTURE_REGISTRY` | `structure_templates.py` 를 **shim 한 줄**로 축소 |
| `from harness.logger import ...` | `from core.harness.logger import ...` | `/harness/__init__.py` 안에 `from core.harness import *` |
| `from dxf_geometry import ...` | `from core.geo.dxf_geometry import ...` | 루트의 같은 이름 .py 를 1줄 shim 으로 유지 |
| `from gemini_renderer import run_gemini_render` | `plugins.render.gemini.plugin` | 루트 shim |

### 6.2 shim 수명

- Phase 1·2 동안: shim 적극 사용 → 단계마다 외부 진입점이 안 깨짐.
- Phase 3 이후 1 release: shim 유지하되 `DeprecationWarning` 한 줄 발사.
- Phase 3 + 1 release 후: shim 제거.

### 6.3 데이터 호환

- `scanvas_jobs.db` (SQLite) — 스키마 변경 없음. ORM 모듈 위치만 이동.
- `prompt_templates/*.yaml`, `structure_types/*.yaml` — 파일 위치/포맷 변경 없음.
- 사용자 입력 파라미터 (StructureParams) — dataclass 형태 그대로.

---

## 7. 영향 받는 파일 (예상)

### 7.1 이동 (총 ~25 파일)

```
[Phase 1, Core 추출, 14 파일]
  dxf_geometry.py, geo_referencing.py, tile_downloader.py, dem_extender.py,
  polygon_reconstructor.py
  detail_parser.py, view_detector.py, view_reconstructor.py,
  optional_detector.py, filename_classifier.py
  structure_placement.py, params_to_json.py
  harness/seed_manager.py, harness/quality_validator.py,
  harness/prompt_registry.py, harness/logger.py

[Phase 2, Structure plugin, 8 파일 + 7 manifest]
  gate_parser.py, gate_3d_builder.py, gate_3d_builder_bpy.py, validate_gate_params.py
  intake_tower_parser.py, intake_tower_3d_builder.py
  valve_chamber_parser.py, valve_chamber_3d_builder.py
  retaining_wall_parser.py, retaining_wall_3d_builder.py
  + structure_templates.py 에서 7 클래스 추출 분리

[Phase 3, Render/QA plugin, 3 파일]
  gemini_renderer.py
  blender_renderer.py (+ apply_blender_patch.py, fix_bpy_import.py 동반)
  structure_vlm_feedback.py
```

### 7.2 신규 (총 ~30 파일)

```
core/__init__.py, core/plugin_manager.py, core/manifest.py, core/interfaces.py
core/geo/__init__.py, core/parsing/__init__.py, core/structures/__init__.py
core/structures/base.py, core/structures/registry.py
core/render/__init__.py, core/render/registry.py
core/qa/__init__.py, core/qa/registry.py

plugins/__init__.py
plugins/structures/<id>/manifest.json × 7
plugins/structures/<id>/parameters.json × 7
plugins/structures/<id>/samples/sample_default.json × 7
plugins/render/gemini/manifest.json
plugins/render/blender/manifest.json
plugins/qa/vlm_feedback/manifest.json
```

### 7.3 수정 (그대로 두지만 import 갱신)

```
scanvas_maker.py        — import 블록 + bootstrap_plugins() 호출 1줄 추가
structure_templates.py  — 7 클래스 추출 후 shim 으로 축소 (선택)
splash.py               — 변경 없음
resource_paths.py       — user_plugins() 헬퍼 추가
```

### 7.4 삭제 후보 (이미 `_unused/` 가 있으므로 동일 정책)

```
apply_blender_patch.py, fix_bpy_import.py
  → plugins/render/blender/tooling/ 로 이동 (아카이브 성격)
```

### 7.5 변경 없음

```
Design/, prompt_templates/, structure_types/, cache/, venv313/, _unused/
```

---

## 8. 검증 체크리스트 (각 Phase 완료 시)

- [ ] `python scanvas_maker.py` 가 정상 부팅 (스플래시 → GUI)
- [ ] DXF 로드 → Step 1 TIN 생성 → Step 1.5 DEM 확장 → Step 2 위성 결합 → Step 3 캡처
- [ ] 7종 구조물 모두 `STRUCTURE_REGISTRY.list_choices()` 에 나타남
- [ ] 1종 구조물 빌드 (예: spillway_gate) 후 PyVista 미리보기 정상
- [ ] Step 4 AI 렌더 (Gemini 또는 Stability) 1회 성공
- [ ] `harness` SQLite 작업 이력 1건 기록 확인
- [ ] PyInstaller `pyinstaller scanvas.spec` 빌드 성공 (Phase 3 종료 시)

---

## 9. 미결 / 추가 논의 필요

1. **새 카테고리 트리** (`hydraulic_structure / transportation / building / landscape /
   terrain`) 와 **기존 `structure_v1.yaml` 의 type 키** 의 연결 — yaml 의 `terrain`,
   `road`, `embankment` 같은 type 은 카테고리가 아니라 *render mode* 다. 따라서
   카테고리는 **manifest 에서만** 사용하고, yaml type 은 별개로 둔다.
2. **PyPI 엔트리포인트 활용 여부** — 본 계획은 manifest 스캔만 채택. 진짜 외부
   배포가 필요해지면 별도 phase 4 로 검토.
3. **테스트 전략** — 현재 코드에 unit test 가 거의 없다. Phase 1 이전에 최소
   smoke test (`python scanvas_maker.py --self-test`) 를 추가해 마이그레이션 안전망
   확보 권장.

---

## 10. 요약 (의사결정자용 한 페이지)

- **Core**: GUI · 파이프라인 · DXF I/O · TIN · DEM · 위성타일 · harness · structure ABC/Registry.
  → `core/` 단일 패키지로 격리.
- **Plugin**: 7종 구조물 빌더 · 2종 렌더(Gemini/Blender) · 1종 QA(VLM).
  → `plugins/<kind>/<id>/manifest.json + plugin.py` 표준.
- **호스트**: 기존 `STRUCTURE_REGISTRY` (싱글톤) 가 그대로 plugin host. 단지
  `_register_defaults()` 를 `plugin_manager.discover()` 로 교체.
- **마이그레이션**: 3 phase, 각 1~2 세션, 각 phase 완료 시 동작 검증 가능.
- **호환성**: 모든 기존 import 는 1~2줄 shim 으로 살아남음 → 사용자 임팩트 0.
- **최종 비전**: 사용자가 `*.scanvas-lib` ZIP 을 GUI 에 드롭 → 즉시 새 구조물 사용.
  Phase 3 끝나면 도달.
