# 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 165–167. - **메인 레이아웃**: 2-column grid. - **Left (col 0)**: `sidebar_container` (270px 고정) + `sidebar_frame` (CTkScrollableFrame 250px). line 274–283. - 섹션: 로고 → 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 558–562. - row 0 weight=3 → `map_frame` (TkinterMapView, 위성 지도 미리보기). - row 1 weight=1 → `textbox` (CTkTextbox, **height=120, 인라인 로그 패널**). line 582–583. - row 2 → `status_bar` (28px, ● READY 인디케이터 + status_text). line 586–593. ### 1.2 인라인 로그 (제거 대상) - 표면화 위치: `main_frame.row=1`, `self.textbox` 한 위젯. - 호출지점: `self.log(message)` — **180회**. line 691–696. - 동작: `datetime` timestamp prefix → `textbox.insert("end", ...)` → auto-scroll. - 별도로 `self._diag(...)` (구조물 분류 진단)와 `harness.logger.setup_logging(log_file=harness_log_path())`는 **이미 백엔드 파일**로 흘러가고 있음 (line 228, 698–710). 즉 인프라 절반은 이미 존재. - 결론: `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 팝업. **창 4–5개를 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 기준 강제) ```python # 색상 (현재 사용 중 + 신규 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 691–696, 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 268–600 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 부터.