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>
This commit is contained in:
2026-05-08 11:45:30 +09:00
parent b9342f6726
commit e9cc6bfcf4
15 changed files with 2617 additions and 15 deletions

View File

@@ -1,10 +1,12 @@
import contextlib
import customtkinter as ctk
import datetime
import hashlib
import os
import sys
import io
import json
import logging
import os
import sys
import threading
import time as _time
from pathlib import Path
@@ -26,6 +28,22 @@ import pyproj
import requests
from PIL import Image, ImageDraw, ImageFilter
import tkintermapview
from matplotlib.colors import LinearSegmentedColormap
# 지형(TIN) 컬러맵 — **파란색 금지** (피드백 #3: 물과 헷갈림).
# 어두운 토양 → 밝은 모래/건조 톤 → 능선 광택. matplotlib "terrain" 대체.
_TIN_EARTH_CMAP = LinearSegmentedColormap.from_list(
"scanvas_earth",
[
(0.00, "#3F2E1A"), # 저지대 — 짙은 갈색 (배수로 음영)
(0.20, "#6E5235"), # 토양
(0.45, "#9C7B4F"), # 황토
(0.70, "#C7AA7C"), # 모래/건조
(0.88, "#E5D5B0"), # 고지 능선
(1.00, "#F5EBD3"), # 정상 광택
],
N=256,
)
# Harness 모듈 (동일 디렉토리의 harness/ 폴더)
try:
@@ -79,9 +97,7 @@ except ImportError as _e:
STRUCTURE_VLM_AVAILABLE = False
print(f"[Warning] structure_vlm_feedback not available: {_e}")
# 폰트 에러 방지를 위한 처리
import logging
import contextlib
# 폰트 에러 방지 — matplotlib font_manager 로그 비활성.
logging.getLogger('matplotlib.font_manager').disabled = True
os.environ['PYTHONIOENCODING'] = 'utf-8'
@@ -4660,7 +4676,7 @@ class SCanvasApp(ctk.CTk):
sample = pts_abs if len(pts_abs) <= 30000 else pts_abs[
np.random.RandomState(0).choice(len(pts_abs), 30000, replace=False)]
sc = ax.scatter(sample[:, 0], sample[:, 1], c=sample[:, 2],
cmap="terrain", s=2, alpha=0.85)
cmap=_TIN_EARTH_CMAP, s=2, alpha=0.85)
fig.colorbar(sc, ax=ax, label="Elevation (m)")
# 도면 bbox 표시
ax.add_patch(_MplRect((x0p, y0p), x1p - x0p, y1p - y0p,
@@ -5515,7 +5531,7 @@ class SCanvasApp(ctk.CTk):
p.add_mesh(unified_mesh, texture=texture_obj,
show_edges=self.wireframe_var.get(), edge_color="white")
else:
p.add_mesh(unified_mesh, scalars="Elevation", cmap="terrain",
p.add_mesh(unified_mesh, scalars="Elevation", cmap=_TIN_EARTH_CMAP,
show_edges=self.wireframe_var.get(), edge_color="white",
scalar_bar_args={'title': 'Elevation (m)'})
else:
@@ -5524,7 +5540,7 @@ class SCanvasApp(ctk.CTk):
p.add_mesh(target_mesh, texture=texture_obj,
show_edges=self.wireframe_var.get(), edge_color="white")
else:
p.add_mesh(target_mesh, scalars="Elevation", cmap="terrain",
p.add_mesh(target_mesh, scalars="Elevation", cmap=_TIN_EARTH_CMAP,
show_edges=self.wireframe_var.get(), edge_color="white",
scalar_bar_args={'title': 'Elevation (m)'})
if ext_mesh is not None:
@@ -5533,7 +5549,7 @@ class SCanvasApp(ctk.CTk):
p.add_mesh(ext_mesh, texture=texture_obj,
show_edges=False, lighting=True)
else:
p.add_mesh(ext_mesh, scalars="Elevation", cmap="terrain",
p.add_mesh(ext_mesh, scalars="Elevation", cmap=_TIN_EARTH_CMAP,
show_edges=False, lighting=True,
show_scalar_bar=False)
except Exception as e:
@@ -5894,7 +5910,7 @@ class SCanvasApp(ctk.CTk):
tex = pv.read_texture("satellite_temp.png")
p.add_mesh(target, texture=tex, show_edges=self.wireframe_var.get(), edge_color="#444444")
else:
p.add_mesh(target, scalars="Elevation", cmap="terrain",
p.add_mesh(target, scalars="Elevation", cmap=_TIN_EARTH_CMAP,
show_edges=self.wireframe_var.get(), edge_color="#444444")
# DEM 외곽 확장 메시 — 뷰포인트 선택/캡처/AI에 **같은 장면**을 쓰기 위해 같이 렌더
@@ -5904,7 +5920,7 @@ class SCanvasApp(ctk.CTk):
if tex is not None and self.tin_extension_textured is not None:
p.add_mesh(ext_mesh_view, texture=tex, show_edges=False, lighting=True)
else:
p.add_mesh(ext_mesh_view, scalars="Elevation", cmap="terrain",
p.add_mesh(ext_mesh_view, scalars="Elevation", cmap=_TIN_EARTH_CMAP,
show_edges=False, lighting=True, show_scalar_bar=False)
except Exception as e:
self.log(f" [뷰어] 확장 메시 추가 경고: {e}")
@@ -6315,19 +6331,19 @@ class SCanvasApp(ctk.CTk):
if textured and tex is not None:
p.add_mesh(unified, texture=tex)
else:
p.add_mesh(unified, scalars="Elevation", cmap="terrain")
p.add_mesh(unified, scalars="Elevation", cmap=_TIN_EARTH_CMAP)
else:
if textured and tex is not None:
p.add_mesh(target, texture=tex)
else:
p.add_mesh(target, scalars="Elevation", cmap="terrain")
p.add_mesh(target, scalars="Elevation", cmap=_TIN_EARTH_CMAP)
if ext_mesh is not None:
try:
if textured and tex is not None:
p.add_mesh(ext_mesh, texture=tex,
show_edges=False, lighting=True)
else:
p.add_mesh(ext_mesh, scalars="Elevation", cmap="terrain",
p.add_mesh(ext_mesh, scalars="Elevation", cmap=_TIN_EARTH_CMAP,
show_edges=False, lighting=True,
show_scalar_bar=False)
except Exception as e:
@@ -7000,6 +7016,14 @@ class SCanvasApp(ctk.CTk):
self.log(f" 결과 표시 오류: {e}")
if __name__ == "__main__":
# 크래시 핸들러 — Python 미처리 예외 + thread 예외 + faulthandler. logs/ 에 회전 저장.
try:
from harness.crash_logger import install_crash_handlers
_log_dir = install_crash_handlers()
print(f"[crash_logger] logs → {_log_dir}")
except Exception as _ch_err:
print(f"[crash_logger] 설치 실패 (계속 진행): {_ch_err}")
# 인트로 스플래시 — Design/logo_intro.mp4 재생 후 메인 앱 기동.
# 실패·파일 없음 시 조용히 skip(메인 앱은 항상 뜸).
try: