Files
s-canvas/ARCHITECTURE_PLAN.md
HYUNJUNGLEE e9cc6bfcf4 Phase 0 of expert feedback (#1~#11): infrastructure + design + 1차 fixes
Implementations (즉시 동작):
- #1 crash logging: harness/crash_logger.py (sys.excepthook + threading +
  faulthandler, 회전 파일 logs/scanvas.log). main 진입점 통합.
- #2 smooth curves (1차): gate_3d_builder ogee profile를 arc-length parametric
  CubicSpline로 4× densify (8pt→32pt, 36→132 cells, 60 FPS 안전).
- #3 TIN colormap: matplotlib "terrain"의 파란색 범위 제거 → 짙은갈색→황토→
  모래→능선 LinearSegmentedColormap. 9 사이트 교체. 회귀 테스트 추가.
- #5 uv: pyproject.toml + UV_GUIDE.md. base/[py313]/[dev]/[build] extras + hatchling.
- #6,#7,#8 dev cycle infra: .pre-commit-config.yaml (ruff+secrets+위생),
  .gitea/workflows/ci.yml (Py3.11+3.13 matrix), tests/test_regressions.py
  (18 회귀 테스트, iter=1~7 fix 박제), CONTRIBUTING.md (Red→Green 알고리즘).

Design docs (다음 세션 마이그레이션 청사진):
- #4 UI/UX 전면 수정: UI_REDESIGN_PLAN.md (12 popup→1 inspector, vtkTkRenderWidget
  embedding 게이트, 4 phase × 7 sessions).
- #10 Core/Plugin: ARCHITECTURE_PLAN.md (Core 14 / Plugin 7 구조물 + 2 렌더 + 1 QA,
  STRUCTURE_REGISTRY 확장, manifest 기반 디스커버리).
- #11 perf hotspots: PERFORMANCE_BASELINE.md (19 핫스팟, P1: 타일 직렬DL 5~30s,
  캡처 직렬 4.5~15s, numpy 벡터화 가능 Python loops, 텍스처 4회 반복read).

Behavior preservation: ruff 0 errors, pytest 17 passed/1 skipped(bpy),
import 33/33 OK on Py3.13.13.

Item #2 P2/P3 곡선, #4 UI 마이그레이션, #10 Phase 1 추출, #11 P1 최적화는 차기 세션.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 11:45:30 +09:00

29 KiB
Raw Permalink Blame History

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)

# 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 의 비전을 그대로 차용하되, 현재 단계에 필요한 필드만:

{
  "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 값:

  • structureStructurePlugin 로딩, STRUCTURE_REGISTRY 에 등록
  • renderRenderPlugin 로딩, RENDER_REGISTRY 에 등록
  • qaQAPlugin 로딩

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


4. 디스커버리 메커니즘

4.1 단계별 디스커버리 정책

core/plugin_manager.pydiscover() 알고리즘:

# 의사코드
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.pyREGISTRY._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줄만 추가:

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.pycore/geo/
    • detail_parser.py, view_detector.py, view_reconstructor.py, optional_detector.py, filename_classifier.pycore/parsing/
    • structure_placement.py, params_to_json.pycore/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_geometryview_detector 같은 사이클이 잠재. Mitigation: 이동 전 python -c "import scanvas_maker" smoke test. 한 모듈씩 이동하면서 매번 검증.

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

Backwards compat: 다음 5개 파일을 shim 으로 유지:

# /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.pySpillwayGateTemplate 클래스를 추출하여 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.pyplugins/render/gemini/plugin.py (RenderPlugin 인터페이스 적용)
  2. blender_renderer.pyplugins/render/blender/plugin.py
  3. structure_vlm_feedback.pyplugins/qa/vlm_feedback/plugin.py
  4. core/plugin_manager.pybootstrap_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.pyshim 한 줄로 축소
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 끝나면 도달.