# -*- mode: python ; coding: utf-8 -*- """S-CANVAS PyInstaller spec — onedir 배포 빌드 설정. 빌드: pyinstaller --clean scanvas_maker.spec 결과: dist/S-CANVAS/ ← 통째로 zip 해서 배포 S-CANVAS.exe ← 더블클릭 진입점 Design/, prompt_templates/, structure_types/ _internal/ ← Python 런타임 + 의존 라이브러리 ... 런타임 데이터(DB·로그·캐시): %LOCALAPPDATA%\\S-CANVAS\\ ← 사용자별 분리, 설치 폴더 쓰기 권한 불필요 """ from PyInstaller.utils.hooks import collect_all, collect_submodules from pathlib import Path block_cipher = None PROJECT_DIR = Path(SPECPATH).resolve() # ────────────────────── 자산 번들링 ────────────────────── # (소스경로, 번들 내 상대경로) — 런타임에서 sys._MEIPASS 아래에 같은 트리로 추출됨. datas = [ (str(PROJECT_DIR / "Design"), "Design"), (str(PROJECT_DIR / "prompt_templates"), "prompt_templates"), (str(PROJECT_DIR / "structure_types"), "structure_types"), ] # ────────────────────── 거대 패키지 collect_all ────────────────────── # pyvista/vtk: PyInstaller 자동 감지 부분이 거나 → 명시적 collect_all 로 누락 방지. # pyproj: PROJ 데이터(proj.db) 자동 누락 빈번 → collect_all 로 datas/binaries 모두 집어 옴. binaries = [] hiddenimports = [] for pkg in [ "pyvista", "vtkmodules", "pyproj", "ezdxf", "tkintermapview", "structlog", "customtkinter", "PIL", ]: try: _d, _b, _h = collect_all(pkg) datas += _d binaries += _b hiddenimports += _h except Exception: pass # google-genai 의 서브모듈은 collect_all 로 충분히 커버되지 않을 때가 있어 별도 추가 hiddenimports += collect_submodules("google.genai") hiddenimports += [ "sqlalchemy.dialects.sqlite", "sqlalchemy.dialects.sqlite.pysqlite", "sqlalchemy.ext.declarative", "scipy.spatial.transform._rotation_groups", "scipy.special.cython_special", "encodings.idna", ] # ────────────────────── 분석 단계 ────────────────────── a = Analysis( ["scanvas_maker.py"], pathex=[str(PROJECT_DIR)], binaries=binaries, datas=datas, hiddenimports=hiddenimports, hookspath=[], hooksconfig={}, runtime_hooks=[], # 번들 크기 절감: 불필요한 거대 패키지 제외 excludes=[ "pytest", "IPython", "jupyter", "notebook", "tornado", "zmq", "matplotlib.tests", "numpy.tests", "scipy.tests", "pyvista.examples", # 거대 샘플 데이터 제외 "vtkmodules.test", ], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False, ) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) # ────────────────────── 실행파일 ────────────────────── # 아이콘: 빌드 전 build.bat 가 cache/icons/scanvas_S.ico 를 생성. 없으면 None 폴백. _icon_path = PROJECT_DIR / "cache" / "icons" / "scanvas_S.ico" _exe_icon = str(_icon_path) if _icon_path.exists() else None exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name="S-CANVAS", debug=False, bootloader_ignore_signals=False, strip=False, upx=True, # UPX 가 PATH 에 있으면 압축. 없어도 빌드 진행. upx_exclude=[ "vcruntime140.dll", "python311.dll", "python312.dll", "VCRUNTIME140.dll", ], console=False, # GUI 앱 — 콘솔 창 안 띄움 disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, icon=_exe_icon, ) # ────────────────────── onedir 패키지 ────────────────────── coll = COLLECT( exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, upx_exclude=[], name="S-CANVAS", )