feat(ui): #4 핵심 — 인라인 로그 제거 + InlinePanel 12 popup 인라인화
All checks were successful
CI / Ruff + Test (Py3.11 + Py3.13) (3.11) (push) Successful in 22s
CI / Ruff + Test (Py3.11 + Py3.13) (3.13) (push) Successful in 22s

사용자 명시 요청 (#4 잔여 핵심):
"기존 구조에 지도 아래에 있는 로그는 백엔드로 빼고, 프로세스를 클릭할 때마다
새로운 창이 뜨는 것이 아니라 한 화면에서 바로 구동되게끔 적용".

1. 인라인 로그 패널 완전 제거:
- self.textbox CTkTextbox 위젯 제거 (이전 라운드 80px 축소 → 본 라운드 완전 삭제).
- main_frame layout: row 0 weight 3→1 (지도 전체 차지), row 1 (로그) 제거,
  status_bar row 2→1.
- self.log() 동작 변경: textbox.insert 대신 백엔드 logger.info (logs/scanvas.log
  RotatingFileHandler 5MB×5). status_text 가 짧은 미리보기 (≤80자) 즉시 표시.
- 효과: 메인 캔버스 영역 ~25% 확대 + GUI 메인 thread 부담 감소.

2. harness/inline_panel.py 신규 (231 LOC):
- InlinePanel: ctk.CTkToplevel 호환 인라인 오버레이 (CTkFrame 상속).
- API 호환: title/geometry/transient/grab_set/protocol/wait_window/destroy +
  iconbitmap/wm_* no-op.
- 핵심 트릭: tk.Misc.wait_window(self) 가 Frame 에서도 동작 (widget destruction
  대기) — wait_window 호출 5곳 (T1/T6/T7/T8/T10) 그대로 유지 가능.
- 다중 패널 z-order (_z_counter + lift), main_frame 95% cap, MC Red 타이틀 바.

3. 12 ctk.CTkToplevel → InlinePanel 일괄 치환:
- T1 (DXF 레이어), T2 (구조물 빌드), T3 (빌드 진행), T6 (상세도면), T7 (치수),
  T8 (계획선 고도), T9 (TIN core), T10 (렌더 옵션), T11 (Blender 결과),
  T12 (AI 렌더 결과) — 10 main popups.
- T4 (렌더 sub-옵션), T5 (VLM 결과) — T3 자식 popups.
- 2 replace_all 패턴: ctk.CTkToplevel(self) → InlinePanel(self),
  ctk.CTkToplevel(win) → InlinePanel(win).
- 결과: 12 popups 모두 main 창 안 floating frame 으로 렌더, 별도 OS 창 안 뜸.
  사용자가 ALT-TAB 으로 창 사이 오갈 필요 없음.

검증:
- py_compile + AST OK (scanvas_maker, perf, crash_logger, inline_panel 4개).
- ruff check All checks passed (0 errors).
- import smoke test: scanvas_maker import 성공, InlinePanel 가 진짜 harness 클래스로 로드.
- self.textbox 잔존 refs: 0. CTkToplevel refs: 3 (모두 import fallback/주석).
  InlinePanel refs: 15 (12 호출지 + import).

잔여 (#4 next round, multi-session):
- InlinePanel 실 GUI 워크플로 검증 (사용자 도면 로드 후 T1~T12 한 번씩).
- VTK 임베딩 (pv.Plotter().show() 6곳 → pyvistaqt.QtInteractor).
- messagebox 63회 → 인라인 토스트.
- Inspector 패널 영구 컬럼 (3-column 레이아웃).
- 메인 thread 블로킹 작업 worker thread 분리.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 17:33:45 +09:00
parent fc963007b7
commit 1fcbf14ed8
3 changed files with 342 additions and 26 deletions

View File

@@ -10,6 +10,82 @@
---
## 2026-05-08 (후속 — UI 3차: 인라인 로그 제거 + InlinePanel)
### [feat] 사용자 피드백 #4 핵심 — 인라인 로그 제거 + 모든 popup 인라인화
> **사용자 명시 요청**: "기존 구조에 지도 아래에 있는 로그는 백엔드로 빼고, 프로세스를 클릭할 때마다 새로운 창이 뜨는 것이 아니라 한 화면에서 바로 구동되게끔 적용".
#### 1. 인라인 로그 패널 제거 (`scanvas_maker.py`)
- **제거**: `self.textbox = ctk.CTkTextbox(self.main_frame, height=80, ...)` 위젯과 그 grid 배치 (이전 라운드에서 이미 80 으로 축소했지만 본 라운드에서 **완전 제거**).
- **layout 재배치**:
- `main_frame.grid_rowconfigure(0)` weight 3 → 1, 지도/캔버스가 row 0 전체 차지.
- `main_frame.grid_rowconfigure(1)` (로그 행) 제거 — weight 설정 자체 삭제.
- `status_bar.grid(row=2, ...)``row=1`. 로그 행 제거 후 한 칸 위로.
- **`self.log()` 동작 변경**: textbox.insert 대신 백엔드 logger 사용.
- 파일 sink: `logs/scanvas.log` (RotatingFileHandler 5MB×5, `harness/crash_logger.py``get_logger()` 활용).
- 추가 sink: `%LOCALAPPDATA%\\S-CANVAS\\scanvas_harness.log` (`harness/logger.py``setup_logging` 통해 구조화 로그).
- **즉시 가시성**: status_bar 의 `status_text` 가 짧은 미리보기 (≤80자, 그 이상은 잘림 + …). 사용자가 진행 상황을 한 눈에 보되 인라인 로그 패널은 사라짐.
- **효과**: 메인 캔버스 영역 ~25% 확대 (이전 row 1 weight 1 영역 흡수). GUI 메인 thread 의 textbox.insert 부담 제거.
#### 2. `harness/inline_panel.py` 신규 — `ctk.CTkToplevel` 호환 인라인 오버레이
별도 OS 창 없이 main_frame 안에 floating frame 으로 렌더하는 drop-in 대체. **드러난 인터페이스가 CTkToplevel 와 동일**해서 호출지 코드는 1줄만 변경 (`ctk.CTkToplevel``InlinePanel`).
**구현 핵심**:
- `ctk.CTkFrame` 상속 → `tk.Misc.wait_window(self)` 가 widget destruction 을 기다리는 매커니즘이라 Frame 에서도 동작 (Toplevel 만 동작 X). `wait_window()` 호출지 5곳 (line 971, 2518, 2662, 2968, 6662) 그대로 유지 가능.
- `place(relx=0.5, rely=0.5, anchor="center")` 으로 main_frame 중앙 배치.
- `geometry("WxH+X+Y")` 파싱 — X/Y 무시 (always center). main_frame 의 95% cap.
- 다중 패널 z-order: `_z_counter` + `lift()` 으로 최신 패널이 위.
- 타이틀 바: MC Red (#EB001B) 배경, 흰 텍스트, ✕ 닫기 버튼 (피드백 #4 색감 일관).
- `protocol("WM_DELETE_WINDOW", fn)` → 내부 핸들러 등록. `grab_set()` → lift+focus 시뮬레이트.
- **Toplevel 전용 메서드 no-op**: `iconbitmap`, `iconphoto`, `wm_*`, `attributes`, `overrideredirect`. 호출은 silently ignored.
#### 3. 12개 `ctk.CTkToplevel` → `InlinePanel` 일괄 치환
`scanvas_maker.py` 의 popup 생성 라인 12 곳:
| # | line | 호출 | 용도 |
|---|---|---|---|
| T1 | 851 | `win = ctk.CTkToplevel(self)` | DXF 레이어 분류 (900×650) |
| T2 | 1504 | `win = ctk.CTkToplevel(self)` | 구조물 상세 3D 빌드 (1100×650) |
| T3 | 1681 | `win = ctk.CTkToplevel(self)` | 빌드 진행 |
| T4 | 1974 | `opt_win = ctk.CTkToplevel(win)` | 렌더 옵션 (T3 자식) |
| T5 | 2129 | `dwin = ctk.CTkToplevel(win)` | VLM 결과 (T3 자식) |
| T6 | 2451 | `win = ctk.CTkToplevel(self)` | 상세도면 업로드 |
| T7 | 2571 | `win = ctk.CTkToplevel(self)` | 치수 확인 (650×500) |
| T8 | 2808 | `win = ctk.CTkToplevel(self)` | 계획선 고도 설정 (1280×560) |
| T9 | 4710 | `win = ctk.CTkToplevel(self)` | TIN 이용 범위 (1100×920) |
| T10 | 6625 | `time_win = ctk.CTkToplevel(self)` | 렌더링 옵션 (380×360) |
| T11 | 6985 | `win = ctk.CTkToplevel(self)` | Blender 결과 |
| T12 | 7058 | `win = ctk.CTkToplevel(self)` | AI 렌더 결과 |
치환 패턴 2종 (replace_all): `ctk.CTkToplevel(self)``InlinePanel(self)` (10곳), `ctk.CTkToplevel(win)``InlinePanel(win)` (2곳, 자식 패널).
**12 popup 모두 별도 OS 창 없이 main 창 안에서 동작.** 사용자가 ALT-TAB 으로 창 사이 오갈 필요 없음.
#### 4. 검증
- `python -m py_compile scanvas_maker.py harness/perf.py harness/crash_logger.py harness/inline_panel.py` 통과.
- AST parse OK.
- Import smoke test: `import scanvas_maker` 성공, `InlinePanel` 이 진짜 `harness.inline_panel.InlinePanel` 클래스로 로드 (CTkToplevel 폴백 아님).
- ruff `check scanvas_maker.py harness/`: All checks passed.
- 잔존 검사:
- `self.textbox` refs: **0** (완전 제거).
- `CTkToplevel` refs: 3 (모두 import 폴백/주석).
- `InlinePanel` refs: 15 (12 호출지 + import + fallback).
#### 5. 잔여 (#4 next round, multi-session)
- **InlinePanel 동작 검증 (실 GUI)**: 자동 import 검증은 끝났지만 사용자가 실제로 도면 로드 → DXF 레이어 분류 (T1) → 구조물 빌드 (T2) → 등 워크플로 한 번 돌려봐야 모달 동작/wait_window/grab_set 시뮬레이션의 실효성 확인.
- **VTK 임베딩**: 6개 `pv.Plotter().show()``vtkmodules.tk.vtkTkRenderWidget` 또는 `pyvistaqt.QtInteractor`. PyQt 의존성 추가 필요.
- **`messagebox` 63회** → 인라인 토스트/배너 (위험 4건 askyesno 만 모달 유지).
- **Inspector 패널 컬럼 (3-column 레이아웃)**: 현재 InlinePanel 은 floating overlay. 영구적 우측 인스펙터 컬럼 (UI_REDESIGN_PLAN.md §2.1) 은 별도 작업.
- **메인 thread 블로킹 작업 worker thread 분리**: 그래야 progress_bar animation 실제 동작.
---
## 2026-05-08 (후속 — CI fix + UI 2차)
### [fix] Gitea CI uv setup 실패 — `**/uv.lock` 미존재 → setup-uv 액션 abort (#6 후속)