Files
s-canvas/UI_REDESIGN_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

25 KiB
Raw Blame History

S-CANVAS UI/UX 전면 재설계 (Phase 0 — 디자인)

사용자 피드백 #4 인용 "느리고, 조작이 어렵게 느껴지므로, UI/UX를 전면 수정할 필요가 있음(기존 구조에 로그는 백엔드로 빼고, 프로세스를 클릭할 때마다 새로운 창이 뜨는 것이 아니라 한 화면에서 바로 구동되게끔 적용)"

요지: (1) 인라인 로그 패널 제거 → 백엔드 파일 (2) 단계마다 새 팝업창 → 단일 창 인스펙터.

본 문서는 Phase 0(읽기 전용 분석 + 청사진). 이번 라운드에서는 코드 한 줄도 수정하지 않는다.


1. 현재 UI 진단

1.1 메인 셸 구조 (scanvas_maker.py)

  • 클래스: SCanvasApp(ctk.CTk) — 단일 창. line 160.
  • 창 크기: 1200x900, light theme, blue color theme. line 165167.
  • 메인 레이아웃: 2-column grid.
    • Left (col 0): sidebar_container (270px 고정) + sidebar_frame (CTkScrollableFrame 250px). line 274283.
      • 섹션: 로고 → SETTINGS (위성 소스/Vworld 키/AI 엔진/GCP/Vertex Loc/CRS) → WORKFLOW (Step1, 1.5, 2, 3, 4, 구조물 빌드, 상세 치수, 3D 다시 열기) → OPTIONS (와이어프레임, 뷰 버퍼 %, DEM 확장 m, 테마) → SAMAN 푸터.
    • Right (col 1): main_frame (corner_radius=15, transparent). line 558562.
      • row 0 weight=3 → map_frame (TkinterMapView, 위성 지도 미리보기).
      • row 1 weight=1 → textbox (CTkTextbox, height=120, 인라인 로그 패널). line 582583.
      • row 2 → status_bar (28px, ● READY 인디케이터 + status_text). line 586593.

1.2 인라인 로그 (제거 대상)

  • 표면화 위치: main_frame.row=1, self.textbox 한 위젯.
  • 호출지점: self.log(message)180회. line 691696.
  • 동작: datetime timestamp prefix → textbox.insert("end", ...) → auto-scroll.
  • 별도로 self._diag(...) (구조물 분류 진단)와 harness.logger.setup_logging(log_file=harness_log_path())이미 백엔드 파일로 흘러가고 있음 (line 228, 698710). 즉 인프라 절반은 이미 존재.
  • 결론: harness_log_path() (%LOCALAPPDATA%\S-CANVAS\scanvas_harness.log) 표준에 self.log()도 포함시키면 됨. 새 파일 만들 필요 없음.

1.3 팝업 다이얼로그 카탈로그 (제거/이식 대상)

CTkToplevel(...) 12개 + messagebox.* 63회 + filedialog.* 다수.

# 위치 (line) 트리거 제목 크기 이식 우선순위
T1 766 Step 1 DXF 로드 후 자동 DXF 레이어 분류 900×650 HIGH — 첫 인상
T2 1419 사이드바 구조물 상세 3D 빌드 구조물 상세 3D 빌드 (템플릿) 1100×650 HIGH
T3 1596 T2 내부 상세 빌드 버튼 빌드 진행 다이얼로그 MED
T4 1889 T3 내부 옵션 렌더 옵션 (서브) LOW (T3 흡수)
T5 2044 T3 내부 VLM 결과 AI 검증 결과 LOW (인라인 토스트)
T6 2366 사이드바 간단 치수 추가 상세도면 업로드 MED
T7 2486 T6 후속 치수 확인/편집 650×500 MED
T8 2723 Step 1 후속 계획선 고도 설정 1280×560 HIGH — 워크플로 핵심
T9 4624 사이드바 🎯 TIN 이용 범위 TIN core 선택 (matplotlib 내장) 1100×920 CRITICAL — interactive canvas
T10 6537 Step 4 시작 렌더링 옵션 (시간대/화질) 380×360 HIGH
T11 6897 Blender 렌더 결과 결과 이미지 뷰어 동적 MED
T12 6970 AI 렌더 결과 결과 이미지 뷰어 동적 MED

messagebox 63회 분포: 거의 모두 (a) 모듈 미설치 경고, (b) 전제조건 안내("먼저 Step N을 수행하세요"), (c) 최종 완료/실패 메시지. 진짜 위험한 결정(askyesno) 은 line 2191/2213/2223/2238 (구조물 빌드 시 덮어쓰기 확인) — 4건.

1.4 PyVista 3D 뷰포트 — 가장 큰 함정

현재: pv.Plotter(title=...).show() 를 호출 → 별도 OS 윈도우 가 뜸 (VTK render window). line 1769, 5463, 5567, 5888, 6228 등 6개 호출지.

  • 메인 CustomTkinter 창과 물리적으로 다른 창. 사용자가 "한 화면에서" 보길 원하는 핵심 위반.
  • pyvistaqt/QtInteractor 임포트는 코드베이스에 없음 (전체 검색 0건).
  • Tk + VTK 임베딩 표준 경로: vtkmodules.tk.vtkTkRenderWidget (Python 3.13 + PyVista 0.43+ 환경에서 동작 확인 필요) OR PyQt 의존성 추가 + pyvistaqt.QtInteractor.

1.5 사용자 워크플로 막힘 지점 (5분 룰 위반)

  1. 첫 화면이 위성지도 — 사용자는 DXF 작업이 목적인데 첫 화면에 빈 한국 지도가 떠 있음. 다음 단계가 사이드바 1. TIN 생성 (DXF) 임이 아이콘/색으로 강조되지 않음.
  2. 각 Step 후 새 창 강제 — Step 1 → T1 팝업(레이어 분류) → T8 팝업(고도 설정) → 닫힘 → 사이드바 클릭 → Step 2 → 위성 지도 갱신 → Step 3 → T9-likely 팝업 → Step 4 → T10 팝업. 창 45개를 ALT-TAB 으로 오가야 한다.
  3. 3D 뷰가 별도 창 — 위성지도(메인)와 3D(VTK 별창)가 분리. 사용자는 두 창 사이에서 컨텍스트 잃음.
  4. 로그 패널 차지 면적 — 메인 영역 25% (main_frame row 1 weight=1, weight=3 vs 1) 가 로그. 사용자가 보지도 않는 텍스트가 차지.
  5. 상태바가 정보 부족● READY + 짧은 텍스트 1줄. 진행률 게이지 없음 (단순 set_status 호출).
  6. 에러 = messagebox 63회 — 매 실패마다 모달 창. 사용자 흐름 차단.

2. 새 레이아웃 (text mockup)

2.1 메인 셸 (단일 창)

┌──────────────────────────────────────────────────────────────────────┐
│  S-CANVAS                                                  ─  □  ✕   │
├────────┬─────────────────────────────────────────┬───────────────────┤
│ Sidebar│              Main Canvas                │     Inspector     │
│ (240)  │             (flex, weight=1)            │     (340)         │
│        │                                         │                   │
│ ┌────┐ │  ┌───────────────────────────────────┐  │ ┌───────────────┐ │
│ │ S  │ │  │                                   │  │ │ Step 1: DXF   │ │
│ │CANVAS│ │  │                                   │  │ │ 로드          │ │
│ └────┘ │  │     [PyVista 3D Viewport]         │  │ ├───────────────┤ │
│        │  │       (or fallback                │  │ │               │ │
│ Pipeline│  │        TkinterMapView when no    │  │ │ DXF 파일:     │ │
│        │  │        TIN yet)                   │  │ │ [filepath...] │ │
│ ① DXF  │  │                                   │  │ │ [📂 찾아보기] │ │
│ ② GeoR │  │                                   │  │ │               │ │
│ ③ TIN  │  │                                   │  │ │ CRS: ▼ EPSG.. │ │
│ ④ Strct│  │                                   │  │ │               │ │
│ ⑤ Rndr │  │                                   │  │ │ ┌──────────┐  │ │
│        │  └───────────────────────────────────┘  │ │ │ 시작 →   │  │ │
│ ──     │  ┌─Tab strip──────────────────────────┐ │ │ └──────────┘  │ │
│ Settings│  │ [3D] [위성지도] [DXF 미리보기]    │ │ │               │ │
│ • Theme│  └───────────────────────────────────┘  │ │ ── 도움말 ─── │ │
│ • API  │                                         │ │ Step 1은 ...  │ │
│ • CRS  │                                         │ └───────────────┘ │
├────────┴─────────────────────────────────────────┴───────────────────┤
│  ● Ready    Step 1/5: DXF 로드 대기      [▮▮▮▮▮▯▯▯ 50%]  [📋 log]  │
└──────────────────────────────────────────────────────────────────────┘

2.2 영역 정의

영역 책임 구현 위젯
Sidebar (left) 240px 고정 네비게이션 (5단계 rail) + 글로벌 settings 토글 CTkScrollableFrame 분리: 위 = pipeline rail, 아래 = settings (collapsed by default)
Main Canvas (center) flex 항상 켜진 3D 뷰포트 + 보조 탭 (지도/DXF) top: VTK 임베디드 위젯, bottom: 탭 strip
Inspector (right) 340px 활성 step 의 폼. step 바뀌면 내용만 swap (창 X) CTkFrame + step 별 _build_inspector_step{N}() 메서드
Status Bar (bottom) 32px ● 인디케이터 + 현재 단계 + 진행률 + log 버튼 기존 status_bar 확장

2.3 사이드바 — 5단계 rail (텍스트만)

PIPELINE
─────────
✓ ① DXF 로드          ← 완료 (체크, 녹)
▶ ② GeoRef            ← 활성 (▶, 청록 강조)
○ ③ TIN + DEM         ← 미진행 (회색)
○ ④ Structures
○ ⑤ Render (AI)
─────────
SETTINGS  ▼ (collapsible)
  • API Key
  • CRS
  • Theme
─────────
SAMAN © Footer
  • 클릭 시: inspector 내용을 해당 step 폼으로 swap. 창 안 띄움.
  • 진행 상태 아이콘: ✓ (완료) / ▶ (활성) / ○ (대기) / ✕ (실패) — 일관된 4가지.

2.4 Inspector — step 별 폼

각 step 의 inspector 는 다음 표준을 따른다 (5분 룰 강제):

┌───────────────────────┐
│ ① DXF 로드            │  ← h1 (18pt bold)
├───────────────────────┤
│                       │
│ [현재 step 의 입력]   │  ← 결정 ≤ 3개
│                       │
│ ┌─ 시작 → ──────┐     │  ← primary button (#16A085, height=40)
│ └────────────────┘    │
│                       │
│ ── 도움말 ──          │  ← caption (10pt gray)
│ "DXF 파일을 골라      │
│  CRS 를 확인하세요"   │
└───────────────────────┘

크리티컬 케이스: step 9 (TIN core 선택) 의 matplotlib interactive canvas — 이건 inspector 가 아니라 메인 canvas 영역에 임시 오버레이 로 띄워야 함 (창 신설 금지). PyVista 뷰포트 위에 matplotlib FigureCanvasTkAgg 를 잠시 덮고, 확정 시 다시 PyVista 로 복귀.

2.5 Status Bar — 미니 로그 인디케이터

● Running   Step 3/5: 제어맵 추출 중       [▮▮▮▮▮▮▯▯ 70%]   📋 log (3 new)
  • ● 색: ready=녹(#2ECC71), running=청(#3498DB), warn=주황(#D35400), error=빨(#E74C3C).
  • 진행률: 8칸 ASCII bar, 단계별 percent.
  • 📋 log (N new) 버튼 클릭 → log drawer (창 아님, 메인 canvas 위로 슬라이드 다운, 350px) 열림. 닫기 ✕ 로 즉시 사라짐. 내용은 harness_log_path() 의 tail 200줄.

3. 컴포넌트 매핑 (현재 → 신규)

현재 (popup) 신규 (single-window) 처리 방식
T1 DXF 레이어 분류 (900×650) Inspector — Step 1 의 sub-form (스크롤) 탭 또는 expandable section
T2 구조물 상세 3D 빌드 (1100×650) Inspector — Step 4 main form sidebar ④ Structures 클릭 시 swap
T3 빌드 다이얼로그 Inspector 내부 진행 영역 + status bar 진행률 inline progress
T4 옵션 서브창 T3 폼의 expandable "고급" 섹션 같은 inspector 안
T5 AI 검증 결과 inline toast (canvas 우상단 8s) 비모달
T6 상세도면 업로드 Step 4 sub-form 내 파일 슬롯 Drag&drop or browse
T7 치수 확인 (650×500) Step 4 sub-form 내 confirm panel 같은 inspector 폼 안
T8 계획선 고도 설정 (1280×560) Inspector — Step 3 sub-form (Step 2.5 스타일) scrollable form
T9 TIN core 선택 (interactive) Main canvas overlay (matplotlib) 임시 mode swap, ESC=취소
T10 렌더링 옵션 (380×360) Inspector — Step 5 main form sidebar ⑤ Render 클릭
T11/T12 결과 이미지 뷰어 Main canvas 의 신규 탭 [결과] tab swap, 우측에 메타
현재 (messagebox) 신규
showerror (≈30회) inline toast (빨, 8s) + status bar 빨
showwarning (≈18회) inline toast (주황, 6s)
showinfo (≈12회) inline toast (청, 4s)
askyesno 4건 (덮어쓰기 확인) 유지 (모달, 진짜 데이터 손실 위험 시만)
현재 (log) 신규
self.log() 180회 → CTkTextbox self.log() API 유지 + 내부에서 logging.getLogger("scanvas").info(...) 로 routing → harness_log_path()
main_frame row 1 (CTkTextbox) 삭제. 그 공간을 main canvas 가 차지
status bar 의 📋 log 버튼 log drawer (slide-down panel) — 개발자만 클릭

4. 마이그레이션 단계 (4 phase)

Phase A — Backend log 일원화 (1 session, 코드 ≈ 60 lines)

  • 목표: self.log() 가 textbox 가 아니라 logger 로 흐르게. UI 구조는 그대로.
  • 변경:
    1. self.log(msg) 내부 → logging.getLogger("scanvas").info(msg) 호출 추가 (textbox 도 유지 — 안전망).
    2. setup_logging(log_file=harness_log_path()) 가 이미 line 228 에 있음. 이걸 HARNESS_AVAILABLE=False 분기에서도 fallback 으로 작동하게 보강.
    3. status bar 의 ● READY 옆에 📋 log 버튼 prototype 추가 (클릭 → 별도 창으로 textbox 띄움 — Phase D 에서 drawer 로 교체).
  • 위험: 거의 없음. log drawer 가 Phase D 에 있어서 백엔드 라우팅만 먼저.
  • 결과 게이트: harness_log_path() 에 모든 메시지가 쌓이는지 grep 으로 확인.

Phase B — Single-window shell (2 sessions, 코드 ≈ 350 lines)

  • 목표: 3-column grid (sidebar | main canvas | inspector) + status bar 골격. 기능은 placeholder.
  • 변경:
    1. __init__ 의 grid 를 grid_columnconfigure(0, weight=0); (1, weight=1); (2, weight=0) 3-column 으로.
    2. 신규 self.inspector_frame (340px, 우측). 현재 사이드바 의 WORKFLOW 섹션을 inspector 의 step1 placeholder 로 옮김.
    3. 사이드바를 rail 모드로 단순화: 5단계 rail + collapsible SETTINGS.
    4. main_frame 에서 textbox 삭제 → 그 자리에 PyVista placeholder (지금은 빈 frame, Phase C 에서 채움).
    5. _show_inspector(step_id) 메서드 추가: step_id 로 해당 폼 swap.
  • 위험: window resize 깨짐 — grid_rowconfigure(0, weight=1)weight=0 분리 정확히 해야. inspector 폭 340 고정 + sidebar 240 고정 + canvas weight=1.
  • 결과 게이트: 빈 inspector 가 step 클릭에 따라 라벨만 바꾸는 데모. 기능 미작동.

Phase C — Step inspector forms 이식 (3 sessions, 코드 ≈ 800 lines)

  • 목표: T1, T2, T8, T10 (HIGH 4개) 의 내용을 inspector 폼으로 옮김. 팝업은 아직 코드에 남겨둠 (병행 동작).
  • 변경:
    1. Step 1 inspector ← T1 DXF 레이어 분류 의 scroll_frame + dropdown 을 inspector 폼으로 복제.
    2. Step 3 inspector ← T8 계획선 고도 설정 의 10-column scroll 을 inspector 폼으로. 폭이 좁으니 (340 < 1280) 한 row 당 2-line 으로 wrap.
    3. Step 4 inspector ← T2 구조물 상세 3D 빌드 의 row-per-structure 를 inspector accordion 으로.
    4. Step 5 inspector ← T10 렌더링 옵션 (시간대/화질).
    5. inline toast 위젯 (CTkFrame 우상단 절대 배치, after(N, destroy)) — messagebox 대체용.
  • 위험: T8 의 10-column 이 340px 에 안 들어감 → row 별 expandable section 으로 디자인 변경 필요. T9 (interactive matplotlib) 는 Phase D 까지 팝업 유지.
  • 결과 게이트: 사이드바 5단계 모두 클릭 가능 + inspector 가 step 마다 정확한 폼 표시 + Step 1~5 회귀 테스트 통과.

Phase D — 팝업 제거 + log drawer + 회귀 (1 session, 코드 ≈ 200 lines)

  • 목표: 모든 CTkToplevel(...) 12개 제거 (T9 main canvas overlay 로 교체) + log drawer 완성.
  • 변경:
    1. T1, T2, T8, T10 의 CTkToplevel 호출 코드 삭제 (Phase C 에서 inspector 가 이미 동작 중).
    2. T3, T4, T5, T6, T7 의 호출지 → inspector 의 sub-state 로 바뀜.
    3. T9 → main canvas 위에 matplotlib FigureCanvasTkAgg 를 띄우고 ESC/확정 시 사라짐. PyVista 모드 ↔ matplotlib 모드 swap.
    4. T11, T12 → main canvas 의 신규 [결과] 탭 으로.
    5. log drawer: status bar 📋 log 클릭 → 메인 canvas 위에 slide-down (높이 350) panel + 닫기 ✕.
    6. messagebox 63회 → inline toast (showerror/warning/info) + askyesno 4건만 유지.
  • 위험: T9 의 matplotlib pick event 가 main canvas 에서 동작 안 할 수 있음 — pyvista 와 matplotlib 가 같은 Tk frame 안에서 mode swap 시 grab 충돌.
  • 결과 게이트: Grep CTkToplevel 결과 0개, Grep "messagebox.show" 결과 4개 미만 (askyesno 만), full Step 1~5 시나리오 manual 테스트.

누적 일정

  • Phase A: 1 session
  • Phase B: 2 sessions
  • Phase C: 3 sessions
  • Phase D: 1 session
  • 총 7 sessions.

5. 5축 / Cities / Workflow 영향

5.1 5축 비전 영향

  • 축 1 (AI 물리 조감도): 영향 중립. AI 렌더 트랙(D003) 손대지 않음 — Step 5 inspector 는 기존 T10 폼의 lift-and-shift.
  • 축 2 (Cities-Skylines like, 5분 룰): 강한 개선. 단일 창에서 사이드바 → inspector → canvas 가 한눈에. 새 사용자가 ALT-TAB 안 해도 됨.
  • 축 3 (심미성): 일관성 향상. 12개 팝업의 제각각 폰트/패딩이 1개 inspector 표준으로 통일. 8px 그리드 강제.
  • 축 4 (라이브러리): ⚠️ 중립. STRUCTURE_REGISTRY 패턴 유지. 단, inspector 폼이 새 구조물 타입 추가 시 자동 확장돼야 함 (data-driven, 코드 수정 X).
  • 축 5 (성능): 팝업 생성/파괴 오버헤드 제거 (window create/destroy 는 비용). 단, PyVista 임베딩이 Tk 에서 60FPS 유지될지 검증 필요 — 이게 Phase B 의 게이트.

5.2 두 워크플로 호환

  • Workflow A (Engineering, DXF 기반): Step 1~5 이 그대로 사이드바 rail. 기존 사용자 학습곡선 단발성.
  • Workflow B (Cities, 도면 없이): cities_placement_widget.py 가 별도 위젯 — Step 1 inspector 의 toggle 또는 사이드바 별도 모드 버튼으로 attach. 메인 canvas 공유 가능.

6. 위험 / 트레이드오프

위험 영향 완화
PyVista Tk 임베딩 미검증 치명. Phase B 의존. Phase B 진입 전 5-line 프로토타입으로 vtkTkRenderWidget 동작 확인. 실패 시 Plan B = 메인 canvas 는 BackgroundPlotter 별창 유지하되 status bar 위에 thumbnail 동기화.
사용자 학습 곡선 중. 12개 팝업 다 사라짐 → "어디로 갔지?" 첫 실행 시 5초 onboarding tooltip ("기존 팝업이 우측 inspector 로 이동했습니다").
window resize 깨짐 중. CustomTkinter 약점. grid_columnconfigure(1, weight=1) 만 flex, 0/2 는 fixed. 최소 폭 900px (사이드바 240+canvas 320+inspector 340).
백엔드 log 의 디버깅 불편 저. inline log 가 사라져 개발자 불편. log drawer (status bar 버튼 1클릭, 350px) 가 즉시 표시. 파일 위치 status bar 우클릭 메뉴에 노출.
T9 matplotlib mode swap 복잡도 중. PyVista ↔ matplotlib 같은 frame 안 swap. Phase D 까지 T9 만 팝업 유지하는 안전 fallback. 사용자에게 "1회만 별창" 양해.
Phase B-C 사이 코드 중복 저. 팝업 + inspector 병존. Phase D 끝에 일괄 삭제. 중간 git tag 유지.

7. 디자인 토큰 (ux-designer.md 기준 강제)

# 색상 (현재 사용 중 + 신규 0개)
COLOR_PRIMARY        = "#16A085"  # rail 활성, primary button
COLOR_PRIMARY_HOVER  = "#117A65"
COLOR_SUCCESS        = "#2ECC71"  # ● ready, ✓ 완료
COLOR_INFO           = "#3498DB"  # ● running
COLOR_WARN           = "#D35400"  # toast warning
COLOR_DANGER         = "#E74C3C"  # ● error, ✕ 실패
COLOR_NEUTRAL_LIGHT  = "#F4F6F7"  # canvas bg (light)
COLOR_NEUTRAL_MID    = "#95A5A6"  # caption text
COLOR_NEUTRAL_DARK   = "#2C3E50"  # body text

# 폰트 (5단계만)
FONT_H1     = ctk.CTkFont(size=18, weight="bold")  # inspector 제목
FONT_H2     = ctk.CTkFont(size=14, weight="bold")  # inspector 섹션 헤더, sidebar PIPELINE 헤더
FONT_BODY   = ctk.CTkFont(size=12)                 # form 라벨, status text
FONT_BUTTON = ctk.CTkFont(size=11, weight="bold")  # buttons
FONT_MICRO  = ctk.CTkFont(size=10)                 # caption, log timestamp

# 8px 그리드
PAD_XS = 8
PAD_SM = 16
PAD_MD = 24
PAD_LG = 32

# 폭/높이
SIDEBAR_W   = 240
INSPECTOR_W = 340
STATUS_BAR_H = 32
LOG_DRAWER_H = 350
MAIN_MIN_W  = 320  # window 최소 폭 = 240+320+340 = 900

# CustomTkinter 한계 인지 → 하지 말 것
# (1) 부드러운 애니메이션 (slide-down log drawer 는 즉각 show/hide)
# (2) 그라디언트
# (3) drop shadow (border 색 변화로 흉내)

8. 영향 받는 파일 (Phase 별 예상)

Phase A (백엔드 log)

  • D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py (line 691696, 228, 595)
  • D:\2026\PROGRAM\1_S-CANVAS\harness\logger.py (이미 작성됨, 변경 없음 검증만)

Phase B (single-window shell)

  • D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py (line 268600 layout 전면)
  • 신규 메서드 _build_sidebar_rail(), _build_main_canvas(), _build_inspector(), _build_status_bar(), _show_inspector(step_id)
  • PyVista 임베딩 프로토타입 (별도 5-line 파일에서 검증 후 본 코드 통합)

Phase C (inspector forms)

  • D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py (T1/T2/T8/T10 본문을 _inspector_step{N} 로 이전)
  • 신규: _inline_toast(level, msg) 헬퍼 (≈ 30 lines)

Phase D (팝업 제거)

  • D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py (line 766, 1419, 1596, 1889, 2044, 2366, 2486, 2723, 4624, 6537, 6897, 6970 의 CTkToplevel(self) 12 개소 정리)
  • 신규: _log_drawer.py (slide-down panel) — 또는 inline class

무영향 (보호 영역)

  • harness/, gemini_renderer.py, blender_renderer.py — AI 렌더 백엔드 (D003)
  • dem_extender.py, geo_referencing.py, dxf_geometry.py — 데이터 레이어
  • structure_templates.py, structure_placement.py — 라이브러리 (축 4)
  • cities_placement_widget.py — Workflow B (별도 트랙)

9. 결정 게이트 (사용자/검수자 확인 요청 항목)

다음은 사용자 결정 이 필요한 사항. 코드 작성 전 확정:

  1. PyVista 임베딩: vtkmodules.tk.vtkTkRenderWidget (Tk 네이티브, 외부 의존성 없음) vs pyvistaqt.QtInteractor (Qt 의존성 추가) — 어느 쪽?
    • 권장: vtkTkRenderWidget (D001 결정 정신: 외부 의존성 최소).
  2. inspector 폭 340px: 1280×560 폭의 T8 폼이 row 당 2-line wrap 으로 들어가는 게 OK 인가, 아니면 inspector 폭을 480px 까지 늘릴 것인가?
  3. 사이드바 SETTINGS 접기: 기본 collapsed 인가 expanded 인가? (5분 룰 = collapsed 권장 — 첫 사용자에게 옵션 폭격 X)
  4. Workflow B 통합: cities_placement_widget 을 사이드바 별도 toggle 로 둘 건지, 아니면 Step 1 inspector 의 sub-tab 인지?

10. 결론

디자인 권장: 진행 가능. 7 sessions 분량.

핵심 가치 제안:

  • 팝업 12개 → inspector 1개 (사용자가 보는 창의 수: 5+ → 1).
  • 인라인 로그 패널 차지 면적 (메인 25%) → log drawer (필요 시만, 0%).
  • messagebox 63회 → inline toast (비모달, 흐름 끊김 X).

가장 큰 위험: PyVista 임베딩의 기술적 미검증. Phase B 의 게이트로 5-line 프로토타입 먼저.

가장 큰 보상: 5분 룰 합격. 사용자가 ALT-TAB 없이 한 화면에서 워크플로 완주.

— Phase 0 끝. 구현은 사용자 GO 사인 후 Phase A 부터.