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>
29 KiB
S-CANVAS Core/Plugin 분리 설계 (Phase 0 — 분석)
상태: 분석/설계 단계 (Phase 0). 코드 변경 없음. 작성: library-architect subagent (read-only survey). 기준 일자: 2026-05-08 이전 단계: 사용자 피드백 #10 — "코드 구조를 Core(핵심), plug-in(추가)으로 구분 필요".
0. 설계 원칙
이 계획은 다음 4가지를 동시에 만족해야 한다.
- 현재 동작 깨지 않음 —
python scanvas_maker.py가 모든 단계에서 그대로 동작. - 점진적 마이그레이션 — 한 번에 모든 파일을 옮기지 않음. 2~3 세션에 걸쳐.
STRUCTURE_REGISTRY확장(replace 아님) — 이미 잘 짜인 싱글톤 레지스트리를 plugin entry-point 의 첫 번째 사용처로 만든다.- library-architect.md 의 Phase 2 비전과 정렬 —
manifest.json기반 동적 디스커버리,.scanvas-libZIP 배포가 최종 목표.
비목표: 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 + builder2-파일 쌍이 표준. 일관성 양호. - 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. 파일 수는 그 안에서 자유롭게.
- 두 디스커버리 루트:
plugins/(소스 트리, 번들 plugin) — 항상 로드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 값:
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() 알고리즘:
# 의사코드
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
디스커버리 루트 우선순위:
<asset_root>/plugins/— 번들 plugin (소스 트리 또는 PyInstaller_MEIPASS)<user_data_dir>/user_plugins/— 사용자 설치 plugin(미래)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] = instancekind == "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 경로 갱신만.
작업:
core/패키지 생성 + 빈__init__.py- 다음 파일들을
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/
- 각 파일의 상대 import 갱신 (e.g.,
from view_detector import ...→from core.parsing.view_detector import ...) scanvas_maker.py의 import 블록 갱신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 으로 유지:
# /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 씩, 가장 단순한 것부터):
plugins/structures/spillway_gate/생성gate_parser.py,gate_3d_builder.py,gate_3d_builder_bpy.py,validate_gate_params.py를 그 안으로 이동structure_templates.py의SpillwayGateTemplate클래스를 추출하여plugins/structures/spillway_gate/plugin.py로 이동, 부모를StructurePlugin으로 변경manifest.json,parameters.json,samples/sample_default.json작성core/structures/registry.py의_register_defaults()에서SpillwayGateTemplateimport 를 제거 — plugin manager 가 대신 등록- 레거시 import 호환:
gate_parser.py등 기존 경로 import 가 있는 파일 (현재gemini_renderer.py,apply_blender_patch.py,params_to_json.py등)을 확인하여 새 경로로 갱신. 외부 사용자용 shim 은 아래 backward compat 섹션 참조. - 동일 절차로 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 세션)
범위:
gemini_renderer.py→plugins/render/gemini/plugin.py(RenderPlugin 인터페이스 적용)blender_renderer.py→plugins/render/blender/plugin.pystructure_vlm_feedback.py→plugins/qa/vlm_feedback/plugin.pycore/plugin_manager.py의bootstrap_plugins()가user_data_dir() / "user_plugins"를 두 번째 디스커버리 루트로 사용하도록 활성화*.scanvas-lib(ZIP) →user_plugins/<id>/풀어주는 헬퍼import_library_zip(path)- 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회 성공
harnessSQLite 작업 이력 1건 기록 확인- PyInstaller
pyinstaller scanvas.spec빌드 성공 (Phase 3 종료 시)
9. 미결 / 추가 논의 필요
- 새 카테고리 트리 (
hydraulic_structure / transportation / building / landscape / terrain) 와 기존structure_v1.yaml의 type 키 의 연결 — yaml 의terrain,road,embankment같은 type 은 카테고리가 아니라 render mode 다. 따라서 카테고리는 manifest 에서만 사용하고, yaml type 은 별개로 둔다. - PyPI 엔트리포인트 활용 여부 — 본 계획은 manifest 스캔만 채택. 진짜 외부 배포가 필요해지면 별도 phase 4 로 검토.
- 테스트 전략 — 현재 코드에 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-libZIP 을 GUI 에 드롭 → 즉시 새 구조물 사용. Phase 3 끝나면 도달.