fix(ci): uv lock 생성 + setup-uv 옵션 정리 + UI 진행률 인디케이터 (#6, #4 부분)
All checks were successful
CI / Ruff + Test (Py3.11 + Py3.13) (3.11) (push) Successful in 23s
CI / Ruff + Test (Py3.11 + Py3.13) (3.13) (push) Successful in 23s

CI uv setup 실패 (#6 후속):
- 원인: astral-sh/setup-uv@v3 의 enable-cache:true 가 **/uv.lock 미발견 시 fail.
  7개 push 모두 ::error::No file ... matched to [**/uv.lock] → 10-20초 만에 abort.
- 해결: uv.lock 생성 (438KB, 89 packages 해결) + cache-dependency-glob 명시.

연쇄 수정 (uv.lock 생성 과정에서 노출):
- pyproject.toml: scipy/pyproj/numpy 핀을 hard-pin == 에서 range > = 로 완화
  (base vs [py313] extras 충돌 해소). requires-python ">=3.9" → ">=3.11"
  (pyproj>=3.7 wheel 가용 환경과 일치). [tool.uv] no-progress = false 제거 (deprecated).
- .gitea/workflows/ci.yml: 별도 Setup Python step 제거 (uv venv가 자동 fetch),
  install step 단순화 (matrix 분기 EXTRAS 변수), 모든 run: 에 shell: bash 명시.

UI 진행률 인디케이터 (#4 부분):
- self.progress_bar (CTkProgressBar mode=indeterminate, MC overlap orange #FF5F00)
  status_bar 우측에 hidden 배치. start_progress(label)/stop_progress() 메서드 추가.
- self.textbox height 120 → 80 (인라인 로그 비중 축소, 백엔드 파일이 주 기록처).

ruff cleanup (harness/perf.py):
- Optional[Callable[...]] → Callable[...] | None (UP045).
- try/except/pass → contextlib.suppress (SIM105).
- 미사용 # noqa: BLE001 제거 (RUF100).

검증: uv lock 성공, ruff check All checks passed, py_compile + AST OK.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 17:13:33 +09:00
parent 5a44c90ea6
commit fc963007b7
6 changed files with 2333 additions and 21 deletions

View File

@@ -37,25 +37,31 @@ jobs:
uses: astral-sh/setup-uv@v3 uses: astral-sh/setup-uv@v3
with: with:
enable-cache: true enable-cache: true
cache-dependency-glob: "uv.lock"
- name: Setup Python ${{ matrix.python-version }}
run: uv python install ${{ matrix.python-version }}
- name: Install deps - name: Install deps
shell: bash
run: | run: |
uv venv .venv --python ${{ matrix.python-version }} set -e
uv pip install --python .venv -e ".[dev]"
# Py3.13은 호환 핀 별도
if [ "${{ matrix.python-version }}" = "3.13" ]; then if [ "${{ matrix.python-version }}" = "3.13" ]; then
uv pip install --python .venv -e ".[py313,dev]" EXTRAS=".[py313,dev]"
else
EXTRAS=".[dev]"
fi fi
# uv 가 자동으로 Py 버전 fetch + .venv 생성. 별도 `uv python install`
# step 불필요 (uv venv 가 내부적으로 처리).
uv venv .venv --python ${{ matrix.python-version }}
source .venv/bin/activate
uv pip install -e "$EXTRAS"
- name: Ruff lint - name: Ruff lint
shell: bash
run: | run: |
source .venv/bin/activate source .venv/bin/activate
ruff check --output-format=github ruff check --output-format=github
- name: py_compile (전체 .py) - name: py_compile (전체 .py)
shell: bash
run: | run: |
source .venv/bin/activate source .venv/bin/activate
python -c " python -c "
@@ -74,6 +80,7 @@ jobs:
" "
- name: pytest (회귀) - name: pytest (회귀)
shell: bash
run: | run: |
source .venv/bin/activate source .venv/bin/activate
pytest -ra --tb=short -m "not slow and not integration" pytest -ra --tb=short -m "not slow and not integration"
@@ -81,6 +88,7 @@ jobs:
- name: pytest (slow + integration, allow failure) - name: pytest (slow + integration, allow failure)
if: ${{ matrix.python-version == '3.13' }} if: ${{ matrix.python-version == '3.13' }}
continue-on-error: true continue-on-error: true
shell: bash
run: | run: |
source .venv/bin/activate source .venv/bin/activate
pytest -ra --tb=short -m "slow or integration" pytest -ra --tb=short -m "slow or integration"

View File

@@ -10,6 +10,59 @@
--- ---
## 2026-05-08 (후속 — CI fix + UI 2차)
### [fix] Gitea CI uv setup 실패 — `**/uv.lock` 미존재 → setup-uv 액션 abort (#6 후속)
- **증상**: 7개 push 모두 CI run 10-20초 만에 `failure`. 로그:
```
::error::No file in /workspace/HYUNJUNGLEE/s-canvas matched to [**/uv.lock]
❌ Failure - Main Setup uv (fast Python pkg manager)
```
- **원인**: `astral-sh/setup-uv@v3` 의 `enable-cache: true` 옵션이 cache key 산출용 `**/uv.lock` 검색 → 미발견 시 hard fail. UV_GUIDE.md §3 에서 권장만 했고 실제 lock 파일은 없었음.
- **추가 발견 (uv.lock 생성 시도 시 노출)**:
1. `[tool.uv] no-progress = false` — uv 0.11+ 가 모르는 옵션 (deprecated). 제거.
2. **dependency 핀 충돌**: `dependencies` 의 `scipy==1.13.1` / `pyproj==3.6.1` vs `[py313]` extras 의 `scipy>=1.14` / `pyproj>=3.7,<4` — uv resolver 가 동시 만족 불가.
3. `pyproj>=3.7` 가 Py3.11+ 만 wheel 배포 — `requires-python = ">=3.9"` 와 충돌.
#### 수정안 (5건)
- **`.gitea/workflows/ci.yml`**:
- `enable-cache: true` + `cache-dependency-glob: "uv.lock"` (lock 파일 변경 시에만 캐시 갱신).
- 별도 `Setup Python` step 제거 — `uv venv --python <ver>` 가 자동 fetch.
- install step 단순화: matrix 분기에서 EXTRAS 변수로 `[dev]` vs `[py313,dev]` 선택 후 단일 `uv pip install`.
- 모든 `run:` 에 `shell: bash` 명시 (Gitea act-runner 호환).
- **`pyproject.toml`**:
- `scipy==1.13.1` → `scipy>=1.13,<2`.
- `pyproj==3.6.1` → `pyproj>=3.6,<4`.
- `numpy==2.0.2` → `numpy>=2.0.2,<3`.
- `requires-python = ">=3.9"` → `">=3.11"` (CI matrix Py3.11/3.13 와 일치, Py3.9/3.10 legacy 종료).
- `[tool.uv] no-progress = false` 제거.
- **`uv.lock` 신규** (438 KB, 89 packages 해결): 다른 머신/CI에서 동일 환경 재현. `uv sync --frozen` 또는 `uv pip install -e ".[dev]" --frozen` 으로 lock 기준 install.
검증 (로컬): `uv lock` 성공 89 packages 해결, `ruff check` All checks passed.
### [feat] UI 진행률 인디케이터 + 로그 패널 축소 (#4 부분)
- **사용자 피드백 #4**: "느리게 느껴짐" → 긴 작업 중 시각적 피드백 부재.
- **신규 위젯**: `self.progress_bar = ctk.CTkProgressBar(self.status_bar, mode="indeterminate", width=180, height=10, progress_color="#FF5F00")`. 기본 hidden (pack 안 함). MC overlap orange 색.
- **신규 메서드** (`scanvas_maker.py` `SCanvasApp` 안):
- `start_progress(label: str | None = None)`: progress_bar pack(side="right") + indeterminate animation 시작 + 옵션 status_text 갱신. `self.after(0, ...)` 로 메인 thread 안전.
- `stop_progress(final_label: str | None = None)`: animation 정지 + pack_forget + status_text 옵션 갱신.
- **로그 패널 축소**: `self.textbox` height 120 → 80. 인라인 로그 비중 줄여 캔버스 영역 확보. 사용자 피드백 "로그는 백엔드로" 의 점진적 적용 — 완전 제거가 아니라 디스크 (`%LOCALAPPDATA%\\S-CANVAS\\scanvas_harness.log` + `logs/scanvas.log`)가 주 기록처임을 주석으로 명시. 다음 라운드에서 toggle 버튼 또는 완전 제거.
- **잔여 (#4 next round)**:
- `start_progress`/`stop_progress` 를 실제 핫스팟 호출지에 wire (capture pipeline, 위성 타일, TIN densify 등).
- 메인 thread 블로킹 작업을 worker thread 로 분리 — 그래야 progress animation 실제 동작.
- 12개 `CTkToplevel` 인스펙터 패널 통합 (별도 multi-session).
### [chore] `harness/perf.py` ruff 정리
- `Optional[Callable[...]]` → `Callable[...] | None` (UP045, Py3.11+ native union).
- `try: ...; except Exception: pass` → `with contextlib.suppress(Exception):` (SIM105).
- 사용 안 되는 `# noqa: BLE001` 제거 (RUF100).
- 결과: ruff `--no-cache` All checks passed.
---
## 2026-05-08 (후속 — UI 1차) ## 2026-05-08 (후속 — UI 1차)
### [feat] Mastercard 팔레트 1차 적용 + 인트로 비디오 제거 (#4 부분) ### [feat] Mastercard 팔레트 1차 적용 + 인트로 비디오 제거 (#4 부분)

View File

@@ -19,13 +19,13 @@ CPU 이용률이 대폭 증가하는 프로세스를 ms 단위로 추적해서
""" """
from __future__ import annotations from __future__ import annotations
import contextlib
import logging import logging
import time import time
from collections.abc import Callable from collections.abc import Callable
from contextlib import contextmanager from contextlib import contextmanager
from typing import Optional
_log_callable: Optional[Callable[[str], None]] = None _log_callable: Callable[[str], None] | None = None
_logger = logging.getLogger("scanvas.perf") _logger = logging.getLogger("scanvas.perf")
@@ -38,10 +38,9 @@ def set_perf_log(fn: Callable[[str], None] | None) -> None:
def _emit(line: str) -> None: def _emit(line: str) -> None:
_logger.info(line) _logger.info(line)
if _log_callable is not None: if _log_callable is not None:
try: # 로그 sink 실패가 측정 흐름을 끊으면 안 됨 — 폭넓게 suppress.
with contextlib.suppress(Exception):
_log_callable(line) _log_callable(line)
except Exception: # noqa: BLE001 (로그 sink 실패가 측정 흐름을 끊으면 안 됨)
pass
@contextmanager @contextmanager

View File

@@ -16,7 +16,9 @@ name = "scanvas"
version = "0.7.0" version = "0.7.0"
description = "S-CANVAS — Generative Design & Visualization Engine (DXF + DEM + AI)" description = "S-CANVAS — Generative Design & Visualization Engine (DXF + DEM + AI)"
readme = "README.md" readme = "README.md"
requires-python = ">=3.9" # pyproj>=3.7 (py313 extras) 이 Py3.11+ 만 지원. CI matrix Py3.11/3.13 와 일치.
# Py3.9/3.10 legacy 지원이 필요하면 pyproj 범위 좁혀야 함.
requires-python = ">=3.11"
license = { text = "Proprietary" } license = { text = "Proprietary" }
authors = [ authors = [
{ name = "Saman Corp.", email = "saman@example.com" }, { name = "Saman Corp.", email = "saman@example.com" },
@@ -35,12 +37,12 @@ dependencies = [
# --- Geospatial / DXF --- # --- Geospatial / DXF ---
"ezdxf==1.4.2", "ezdxf==1.4.2",
"pyproj==3.6.1", "pyproj>=3.6,<4", # 3.6.1 (build pin) ~ 3.7+ (py313 extras) 동시 수용. lock 파일이 정확 핀.
"rasterio==1.4.3", "rasterio==1.4.3",
# --- Numerical --- # --- Numerical ---
"numpy==2.0.2", "numpy>=2.0.2,<3", # py313 extras 와 충돌 방지 위해 범위 핀.
"scipy==1.13.1", "scipy>=1.13,<2", # 1.13.x (Py3.9~3.12) ~ 1.14+ (Py3.13) 둘 다 lock 가능.
"matplotlib==3.9.4", "matplotlib==3.9.4",
# --- Image / video --- # --- Image / video ---
@@ -99,9 +101,6 @@ Repository = "https://gitea.hmac.kr/HYUNJUNGLEE/scanvas.git"
# Python 인터프리터 선택 우선순위 (uv가 자동 검색). # Python 인터프리터 선택 우선순위 (uv가 자동 검색).
python-preference = "managed" # managed = uv가 직접 받아 관리 (3.13 자동 다운로드 가능) python-preference = "managed" # managed = uv가 직접 받아 관리 (3.13 자동 다운로드 가능)
# 색상/진행률 표시.
no-progress = false
# ───────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────
# 빌드 시스템 (편집 가능 설치 / pip install -e . 가능) # 빌드 시스템 (편집 가능 설치 / pip install -e . 가능)
# ───────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────

View File

@@ -620,8 +620,10 @@ class SCanvasApp(ctk.CTk):
self.map_view.set_zoom(6) self.map_view.set_zoom(6)
self.map_view.set_position(36.5, 127.5) self.map_view.set_position(36.5, 127.5)
# 2. 로그 (하단 — 스크롤 가능, 높이 줄임) # 2. 로그 (하단 — 스크롤 가능. 피드백 #4: 인라인 로그 비중 축소, 백엔드 파일이
self.textbox = ctk.CTkTextbox(self.main_frame, height=120, font=ctk.CTkFont(family="Consolas", size=12), border_width=1) # 주 기록처. height 120 → 80 으로 캔버스 영역 확보.
# 파일 로그: %LOCALAPPDATA%\\S-CANVAS\\scanvas_harness.log + logs/scanvas.log)
self.textbox = ctk.CTkTextbox(self.main_frame, height=80, font=ctk.CTkFont(family="Consolas", size=12), border_width=1)
self.textbox.grid(row=1, column=0, padx=0, pady=0, sticky="nsew") self.textbox.grid(row=1, column=0, padx=0, pady=0, sticky="nsew")
# 3. 하단 상태 바 # 3. 하단 상태 바
@@ -634,6 +636,16 @@ class SCanvasApp(ctk.CTk):
self.status_text = ctk.CTkLabel(self.status_bar, text="지형 데이터를 로드해 주세요.", font=ctk.CTkFont(size=12)) self.status_text = ctk.CTkLabel(self.status_bar, text="지형 데이터를 로드해 주세요.", font=ctk.CTkFont(size=12))
self.status_text.pack(side="left") self.status_text.pack(side="left")
# 진행률 인디케이터 (피드백 #4 — "느리게 느껴짐" 일부 해결).
# 기본 hidden. start_progress/stop_progress 로 토글. indeterminate animation
# 으로 "시스템이 살아있다" 시그널 — 실 진행률 측정은 future work (#11 perf 와 연계).
# MC accent 색상 (#FF5F00 overlap orange) 적용.
self.progress_bar = ctk.CTkProgressBar(
self.status_bar, mode="indeterminate", width=180, height=10,
progress_color="#FF5F00", fg_color=("#E0E0E0", "#333333"),
)
# 초기엔 hidden (pack 안 함). start_progress 시 등장.
self.log("S-CANVAS Generative Design Engine 구동 완료.") self.log("S-CANVAS Generative Design Engine 구동 완료.")
# Perf 측정 라인을 GUI 로그에도 함께 표시 (#11). harness/perf.py 폴백 import 시 # Perf 측정 라인을 GUI 로그에도 함께 표시 (#11). harness/perf.py 폴백 import 시
@@ -741,6 +753,33 @@ class SCanvasApp(ctk.CTk):
self.textbox.see("end") self.textbox.see("end")
self.after(0, _update) self.after(0, _update)
def start_progress(self, label: str | None = None) -> None:
"""진행률 인디케이터 표시 + indeterminate animation 시작.
피드백 #4 — 긴 작업 시 "시스템 살아있음" 시그널. label 주면 status_text 도
함께 갱신. 메인 thread 블로킹 작업이라도 호출 직전/직후에 표시 가능 (실
애니메이션은 idle time 에 의존).
"""
def _start():
with contextlib.suppress(Exception):
self.progress_bar.pack(side="right", padx=(8, 12), pady=4)
self.progress_bar.start()
if label is not None:
self.status_text.configure(text=label)
self.update_idletasks()
self.after(0, _start)
def stop_progress(self, final_label: str | None = None) -> None:
"""진행률 인디케이터 숨김 + animation 정지."""
def _stop():
with contextlib.suppress(Exception):
self.progress_bar.stop()
self.progress_bar.pack_forget()
if final_label is not None:
self.status_text.configure(text=final_label)
self.update_idletasks()
self.after(0, _stop)
def _diag(self, message, *, reset=False): def _diag(self, message, *, reset=False):
"""구조물 분류/추출 진단 로그 (scanvas_diagnostic.log). """구조물 분류/추출 진단 로그 (scanvas_diagnostic.log).

2214
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff