# 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. `/plugins/` — 번들 plugin (소스 트리 또는 PyInstaller `_MEIPASS`) 2. `/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__`) 을 부여한다. ### 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//` 로 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//` 로 분할 + 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//` 풀어주는 헬퍼 `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//manifest.json × 7 plugins/structures//parameters.json × 7 plugins/structures//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///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 끝나면 도달.