"""파서 결과 → 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 .py -- ^ --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 [dxf_2 ...]") print(" python params_to_json.py gate [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()