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>
25 KiB
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.
- row 0 weight=3 →
- Left (col 0):
1.2 인라인 로그 (제거 대상)
- 표면화 위치:
main_frame.row=1,self.textbox한 위젯. - 호출지점:
self.log(message)— 180회. line 691–696. - 동작:
datetimetimestamp 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분 룰 위반)
- 첫 화면이 위성지도 — 사용자는 DXF 작업이 목적인데 첫 화면에 빈 한국 지도가 떠 있음. 다음 단계가 사이드바
1. TIN 생성 (DXF)임이 아이콘/색으로 강조되지 않음. - 각 Step 후 새 창 강제 — Step 1 → T1 팝업(레이어 분류) → T8 팝업(고도 설정) → 닫힘 → 사이드바 클릭 → Step 2 → 위성 지도 갱신 → Step 3 → T9-likely 팝업 → Step 4 → T10 팝업. 창 4–5개를 ALT-TAB 으로 오가야 한다.
- 3D 뷰가 별도 창 — 위성지도(메인)와 3D(VTK 별창)가 분리. 사용자는 두 창 사이에서 컨텍스트 잃음.
- 로그 패널 차지 면적 — 메인 영역 25% (
main_framerow 1 weight=1, weight=3 vs 1) 가 로그. 사용자가 보지도 않는 텍스트가 차지. - 상태바가 정보 부족 —
● READY+ 짧은 텍스트 1줄. 진행률 게이지 없음 (단순 set_status 호출). - 에러 = 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 구조는 그대로. - 변경:
self.log(msg)내부 →logging.getLogger("scanvas").info(msg)호출 추가 (textbox 도 유지 — 안전망).setup_logging(log_file=harness_log_path())가 이미 line 228 에 있음. 이걸HARNESS_AVAILABLE=False분기에서도 fallback 으로 작동하게 보강.- 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.
- 변경:
__init__의 grid 를grid_columnconfigure(0, weight=0); (1, weight=1); (2, weight=0)3-column 으로.- 신규
self.inspector_frame(340px, 우측). 현재 사이드바 의 WORKFLOW 섹션을 inspector 의 step1 placeholder 로 옮김. - 사이드바를 rail 모드로 단순화: 5단계 rail + collapsible SETTINGS.
main_frame에서 textbox 삭제 → 그 자리에 PyVista placeholder (지금은 빈 frame, Phase C 에서 채움)._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 폼으로 옮김. 팝업은 아직 코드에 남겨둠 (병행 동작).
- 변경:
- Step 1 inspector ← T1
DXF 레이어 분류의 scroll_frame + dropdown 을 inspector 폼으로 복제. - Step 3 inspector ← T8
계획선 고도 설정의 10-column scroll 을 inspector 폼으로. 폭이 좁으니 (340 < 1280) 한 row 당 2-line 으로 wrap. - Step 4 inspector ← T2
구조물 상세 3D 빌드의 row-per-structure 를 inspector accordion 으로. - Step 5 inspector ← T10
렌더링 옵션(시간대/화질). - inline toast 위젯 (CTkFrame 우상단 절대 배치, after(N, destroy)) — messagebox 대체용.
- Step 1 inspector ← T1
- 위험: 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 완성. - 변경:
- T1, T2, T8, T10 의
CTkToplevel호출 코드 삭제 (Phase C 에서 inspector 가 이미 동작 중). - T3, T4, T5, T6, T7 의 호출지 → inspector 의 sub-state 로 바뀜.
- T9 → main canvas 위에 matplotlib FigureCanvasTkAgg 를 띄우고 ESC/확정 시 사라짐. PyVista 모드 ↔ matplotlib 모드 swap.
- T11, T12 → main canvas 의 신규
[결과]탭 으로. - log drawer: status bar
📋 log클릭 → 메인 canvas 위에 slide-down (높이 350) panel + 닫기 ✕. - messagebox 63회 → inline toast (showerror/warning/info) + askyesno 4건만 유지.
- T1, T2, T8, T10 의
- 위험: 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 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. 결정 게이트 (사용자/검수자 확인 요청 항목)
다음은 사용자 결정 이 필요한 사항. 코드 작성 전 확정:
- PyVista 임베딩:
vtkmodules.tk.vtkTkRenderWidget(Tk 네이티브, 외부 의존성 없음) vspyvistaqt.QtInteractor(Qt 의존성 추가) — 어느 쪽?- 권장: vtkTkRenderWidget (D001 결정 정신: 외부 의존성 최소).
- inspector 폭 340px: 1280×560 폭의 T8 폼이 row 당 2-line wrap 으로 들어가는 게 OK 인가, 아니면 inspector 폭을 480px 까지 늘릴 것인가?
- 사이드바 SETTINGS 접기: 기본 collapsed 인가 expanded 인가? (5분 룰 = collapsed 권장 — 첫 사용자에게 옵션 폭격 X)
- 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 부터.