Import S-CANVAS source + iter=1~7 lint cleanup

S-CANVAS (Saman Corp.) — DXF + DEM + AI 기반 3D 조감도 생성 엔진.
~24k LOC Python (scanvas_maker.py 7072 LOC GUI + 구조물 파서/빌더 다수).

이 커밋은 7-iter cleanup이 적용된 상태로 import:
- F821 8 + B023 6: 비동기 lambda + except/loop 변수 캡처 NameError
  (Py3.13에서 reproduce 확인된 진짜 버그)
- RUF012 4 + RUF013 1: ClassVar / implicit Optional 명시화
- F811/B905/B904/F401/F841/W293/F541/UP/SIM/RUF/PLR 700+ cleanup/modernization

신규 파일:
- ruff.toml: target=py313, Korean unicode/저자 스타일/도메인 복잡도 무력화
- requirements-py313.txt: pyproj>=3.7, scipy>=1.14, numpy>=2.0.2 (Py3.13 wheel)
- .gitignore: gcp-key.json, 캐시, 백업, 생성 이미지 제외

검증: ruff 0 errors, py_compile 0 errors, import 33/33 OK on Py3.13.13.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 10:29:08 +09:00
parent 53d8b53c2f
commit b9342f6726
92 changed files with 3413501 additions and 0 deletions

119
params_to_json.py Normal file
View File

@@ -0,0 +1,119 @@
"""파서 결과 → JSON 브리지 (S-CANVAS env 측 generic 헬퍼).
이 모듈은 bpy를 import하지 않으므로 기존 S-CANVAS conda 환경에서 그대로 사용 가능.
파서가 생성한 dataclass 파라미터(IntakeTowerParams, GateParams, ...)를 JSON으로 직렬화.
사용:
# 취수탑
from intake_tower_parser import parse_intake_tower
from params_to_json import dump_dataclass_to_json
p = parse_intake_tower(dxf_paths)
dump_dataclass_to_json(p, "intake_params.json")
# 여수로 수문
from gate_parser import parse_gate_dxf
from params_to_json import dump_dataclass_to_json
p = parse_gate_dxf(plan_dxf, section_dxf)
dump_dataclass_to_json(p, "gate_params.json")
이후 Blender 헤드리스로:
blender --background --python <builder_bpy>.py -- ^
--params <params>.json --blend out.blend --render out.png
"""
from __future__ import annotations
import json
from dataclasses import asdict
from pathlib import Path
def _to_serializable(obj):
"""dataclass / list / tuple / dict / 기본형 → JSON 직렬화 가능 구조."""
if hasattr(obj, "__dataclass_fields__"):
return {k: _to_serializable(v) for k, v in asdict(obj).items()}
if isinstance(obj, (list, tuple)):
return [_to_serializable(x) for x in obj]
if isinstance(obj, dict):
return {k: _to_serializable(v) for k, v in obj.items()}
if isinstance(obj, (str, int, float, bool)) or obj is None:
return obj
# numpy / 기타: 가능하면 float, 아니면 str로 폴백
try:
return float(obj)
except (TypeError, ValueError):
return str(obj)
def dump_dataclass_to_json(params, path: str) -> str:
"""dataclass 인스턴스 → JSON 파일.
구조 종류와 무관하게 동작 (IntakeTowerParams, GateParams, ...).
Returns: 작성한 절대 경로 (str)
"""
payload = _to_serializable(params)
out_path = Path(path).resolve()
out_path.write_text(
json.dumps(payload, ensure_ascii=False, indent=2),
encoding="utf-8",
)
return str(out_path)
# 이전 버전 호환 alias (intake_tower_3d_builder_bpy 가이드와의 호환)
dump_intake_tower_params = dump_dataclass_to_json
# ---------------------------------------------------------------------------
# CLI: 구조물 종류 자동 감지 → JSON 변환
# ---------------------------------------------------------------------------
def _cli_intake_tower(out_json: str, dxf_paths: list[str]) -> None:
from intake_tower_parser import parse_intake_tower
params = parse_intake_tower(dxf_paths)
print(params.summary())
final = dump_dataclass_to_json(params, out_json)
print(f"\nJSON written: {final}")
def _cli_gate(out_json: str, plan_dxf: str, section_dxf: str | None = None) -> None:
from gate_parser import parse_gate_dxf
params = parse_gate_dxf(plan_dxf, section_dxf)
print(params.summary())
final = dump_dataclass_to_json(params, out_json)
print(f"\nJSON written: {final}")
def _print_usage_and_exit():
print("Usage:")
print(" python params_to_json.py intake <out.json> <dxf_1> [dxf_2 ...]")
print(" python params_to_json.py gate <out.json> <plan.dxf> [section.dxf]")
print("")
print("Examples:")
print(" python params_to_json.py intake intake.json \\")
print(" SAMPLE_CAD/취수탑1.dxf SAMPLE_CAD/취수탑2.dxf")
print("")
print(" python params_to_json.py gate gate.json \\")
print(" Gate_Sample/수문_1.dxf Gate_Sample/수문_2.dxf")
raise SystemExit(1)
if __name__ == "__main__":
import sys
if len(sys.argv) < 4:
_print_usage_and_exit()
kind = sys.argv[1].lower()
out_json = sys.argv[2]
rest = sys.argv[3:]
if kind in ("intake", "intake_tower", "tower"):
_cli_intake_tower(out_json, rest)
elif kind in ("gate", "spillway", "weir"):
plan = rest[0]
section = rest[1] if len(rest) > 1 else None
_cli_gate(out_json, plan, section)
else:
print(f"Unknown structure kind: {kind!r}")
_print_usage_and_exit()