reorganized build directory

This commit is contained in:
ykcheungab
2026-08-03 22:14:19 +08:00
parent 4acdce4456
commit 9a69f01278
43 changed files with 833 additions and 356 deletions
+1
View File
@@ -0,0 +1 @@
"""Standalone GUI packaging targets (PyInstaller)."""
+1
View File
@@ -0,0 +1 @@
"""Shared helpers for product GUI builds."""
+141
View File
@@ -0,0 +1,141 @@
"""Shared PyInstaller driver for product GUI standalone folders."""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from common.paths import PYINSTALLER_WORK_DIR, REPO_ROOT, gui_dist_dir
@dataclass
class GuiBuildConfig:
product: str
entry_script: Path
exe_name: str
dist_name: str
hidden_imports: list[str] = field(default_factory=list)
exclude_modules: list[str] = field(default_factory=list)
data_dirs: list[tuple[str, str]] = field(default_factory=list)
readme_lines: list[str] = field(default_factory=list)
windowed: bool = True
upx_dir: str | None = r"C:\tools\upx"
logo_name: str = "logo.png"
work_dir: Path = PYINSTALLER_WORK_DIR
spec_dir: Path = REPO_ROOT / "build" / "pyinstaller_work"
def clean_dir(path: Path) -> None:
if path.exists():
shutil.rmtree(path)
path.mkdir(parents=True)
def build_with_pyinstaller(
config: GuiBuildConfig,
*,
verify_fn: Callable[[Path], None] | None = None,
) -> Path:
"""Run PyInstaller for one GUI product and copy bundled data folders."""
print("Using Python:", sys.executable)
print(f"Building GUI product: {config.product}")
dist_dir = gui_dist_dir(config.dist_name)
clean_dir(config.work_dir)
clean_dir(dist_dir)
config.spec_dir.mkdir(parents=True, exist_ok=True)
entry = config.entry_script
if not entry.is_file():
raise FileNotFoundError(f"GUI entry script not found: {entry}")
cmd = [
sys.executable,
"-m",
"PyInstaller",
"--onedir",
"--name",
config.dist_name,
f"--distpath={dist_dir.parent}",
f"--workpath={config.work_dir}",
f"--specpath={config.spec_dir}",
"--noconfirm",
"--clean",
"--windowed" if config.windowed else "--console",
]
for src_rel, dest_rel in config.data_dirs:
src = (REPO_ROOT / src_rel).resolve()
if not src.is_dir():
raise FileNotFoundError(f"Data directory missing: {src}")
cmd.append(f"--add-data={src};{dest_rel}")
logo = REPO_ROOT / config.logo_name
if logo.is_file():
cmd.append(f"--add-data={logo.resolve()};.")
for mod in config.hidden_imports:
cmd.append(f"--hidden-import={mod}")
for mod in config.exclude_modules:
cmd.append(f"--exclude-module={mod}")
if config.upx_dir and Path(config.upx_dir).is_dir():
cmd.append(f"--upx-dir={config.upx_dir}")
cmd.append(str(entry.resolve()))
env = os.environ.copy()
env["PYTHONPATH"] = (
str((REPO_ROOT / "src").resolve())
+ os.pathsep
+ str((REPO_ROOT / "examples").resolve())
+ os.pathsep
+ env.get("PYTHONPATH", "")
)
print("\n=== Running PyInstaller ===")
print("Command:", " ".join(cmd))
subprocess.check_call(cmd, env=env, cwd=REPO_ROOT)
final_exe = dist_dir / config.exe_name
if not final_exe.is_file():
raise RuntimeError(f"Executable not found at {final_exe}")
print("\n=== Copying data directories ===")
for src_rel, dest_rel in config.data_dirs:
src = REPO_ROOT / src_rel
dest = dist_dir / dest_rel
if not src.is_dir():
raise FileNotFoundError(f"Data directory not found: {src}")
if dest.exists():
shutil.rmtree(dest)
shutil.copytree(src, dest, dirs_exist_ok=True)
print(f" {src} -> {dest}")
if logo.is_file():
shutil.copy2(logo, dist_dir / config.logo_name)
print(" Logo copied")
if config.readme_lines:
(dist_dir / "README.txt").write_text("\n".join(config.readme_lines) + "\n", encoding="utf-8")
print(f"\n[SUCCESS] Standalone GUI folder: {dist_dir}")
print(f"Executable: {final_exe}")
if verify_fn is not None:
try:
print("\n=== Smoke test ===")
verify_fn(dist_dir)
except Exception as exc:
print(f"\n[WARNING] Smoke test failed: {exc}")
return dist_dir
+1
View File
@@ -0,0 +1 @@
"""ORY-BOX internal calibration/testing GUI packaging (scaffold)."""
+30
View File
@@ -0,0 +1,30 @@
"""Scaffold for ORY-BOX internal GUI packaging.
Not implemented yet. When ready, wire a dedicated calibration/testing entry
script and call ``gui.common.pyinstaller_driver.build_with_pyinstaller``.
"""
from __future__ import annotations
import sys
from pathlib import Path
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from gui.ory_box.config import DIST_NAME, ENTRY_SCRIPT, make_config
def main() -> None:
config = make_config()
raise NotImplementedError(
"ORY-BOX GUI packaging is a scaffold only. "
f"Planned dist={DIST_NAME!r}, entry={ENTRY_SCRIPT}. "
"Implement the internal calibration/testing UI entry, then call "
"gui.common.pyinstaller_driver.build_with_pyinstaller(config)."
)
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
"""Product configuration scaffold for the ORY-BOX internal GUI."""
from __future__ import annotations
import sys
from pathlib import Path
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from common.paths import REPO_ROOT
from gui.common.pyinstaller_driver import GuiBuildConfig
ENTRY_SCRIPT = REPO_ROOT / "examples" / "products" / "ory_box" / "basic_pipeline.py"
EXE_NAME = "OryBox.exe"
DIST_NAME = "OryBox"
HIDDEN_IMPORTS = [
"PyQt5",
"pyqtgraph",
"cv2",
"orisys",
"gui",
"pipeline",
"viz",
]
EXCLUDE_MODULES = [
"cupy",
"cupyx",
"pyopencl",
"open3d",
"tkinter",
"pip",
"wheel",
]
DATA_DIRS = [
("config", "config"),
]
README_LINES = [
"ORY-BOX internal calibration/testing GUI (scaffold)",
"",
"This product packaging target is reserved for a future internal tool.",
"Entry point placeholder:",
f" {ENTRY_SCRIPT.as_posix()}",
]
def make_config() -> GuiBuildConfig:
return GuiBuildConfig(
product="ory_box",
entry_script=ENTRY_SCRIPT,
exe_name=EXE_NAME,
dist_name=DIST_NAME,
hidden_imports=list(HIDDEN_IMPORTS),
exclude_modules=list(EXCLUDE_MODULES),
data_dirs=list(DATA_DIRS),
readme_lines=list(README_LINES),
windowed=False,
)
+1
View File
@@ -0,0 +1 @@
"""ORY-FINGER standalone GUI packaging."""
+31
View File
@@ -0,0 +1,31 @@
"""Build the ORY-FINGER standalone Windows GUI folder with PyInstaller.
Usage:
python build/run.py gui finger
python build/gui/ory_finger/build.py
"""
from __future__ import annotations
import sys
from pathlib import Path
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from common.verify.gui import verify_gui_dist
from gui.common.pyinstaller_driver import build_with_pyinstaller
from gui.ory_finger.config import make_config
def main() -> None:
config = make_config()
build_with_pyinstaller(
config,
verify_fn=lambda dist_dir: verify_gui_dist(dist_dir, product="ory_finger"),
)
if __name__ == "__main__":
main()
+75
View File
@@ -0,0 +1,75 @@
"""Product configuration for the ORY-FINGER Windows dashboard."""
from __future__ import annotations
import sys
from pathlib import Path
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from common.paths import REPO_ROOT
from gui.common.pyinstaller_driver import GuiBuildConfig
ENTRY_SCRIPT = REPO_ROOT / "examples" / "products" / "ory_finger" / "finger_pipeline_3dview.py"
EXE_NAME = "OrisysDashboard.exe"
DIST_NAME = "OrisysDashboard"
HIDDEN_IMPORTS = [
"PyQt5",
"pyqtgraph",
"open3d",
"cv2",
"orisys",
"gui",
"pipeline",
"viz",
"gui.app_paths",
"plotly",
"_plotly_utils",
"open3d.cpu.pybind",
]
EXCLUDE_MODULES = [
"cupy",
"cupyx",
"pyopencl",
"tkinter",
"pip",
"wheel",
"matplotlib",
]
DATA_DIRS = [
("config", "config"),
("assets", "assets"),
]
README_LINES = [
"Orisys Finger Dashboard (standalone)",
"",
"Run:",
f" {EXE_NAME}",
f" {EXE_NAME} --video path\\to\\video.mp4",
f" {EXE_NAME} --config config\\dd02-ov.json",
"",
"Bundled folders:",
" config/ sensor JSON configs",
" assets/ finger mesh and visualization data",
"",
"CPU-only build (no CUDA/CuPy).",
]
def make_config() -> GuiBuildConfig:
return GuiBuildConfig(
product="ory_finger",
entry_script=ENTRY_SCRIPT,
exe_name=EXE_NAME,
dist_name=DIST_NAME,
hidden_imports=list(HIDDEN_IMPORTS),
exclude_modules=list(EXCLUDE_MODULES),
data_dirs=list(DATA_DIRS),
readme_lines=list(README_LINES),
)
+1
View File
@@ -0,0 +1 @@
"""ORY-MINI internal calibration/testing GUI packaging (scaffold)."""
+30
View File
@@ -0,0 +1,30 @@
"""Scaffold for ORY-MINI internal GUI packaging.
Not implemented yet. When ready, wire a dedicated calibration/testing entry
script and call ``gui.common.pyinstaller_driver.build_with_pyinstaller``.
"""
from __future__ import annotations
import sys
from pathlib import Path
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from gui.ory_mini.config import DIST_NAME, ENTRY_SCRIPT, make_config
def main() -> None:
config = make_config()
raise NotImplementedError(
"ORY-MINI GUI packaging is a scaffold only. "
f"Planned dist={DIST_NAME!r}, entry={ENTRY_SCRIPT}. "
"Implement the internal calibration/testing UI entry, then call "
"gui.common.pyinstaller_driver.build_with_pyinstaller(config)."
)
if __name__ == "__main__":
main()
+63
View File
@@ -0,0 +1,63 @@
"""Product configuration scaffold for the ORY-MINI internal GUI."""
from __future__ import annotations
import sys
from pathlib import Path
_BUILD_ROOT = Path(__file__).resolve().parents[2]
if str(_BUILD_ROOT) not in sys.path:
sys.path.insert(0, str(_BUILD_ROOT))
from common.paths import REPO_ROOT
from gui.common.pyinstaller_driver import GuiBuildConfig
ENTRY_SCRIPT = REPO_ROOT / "examples" / "products" / "ory_mini" / "basic_pipeline.py"
EXE_NAME = "OryMini.exe"
DIST_NAME = "OryMini"
HIDDEN_IMPORTS = [
"PyQt5",
"pyqtgraph",
"cv2",
"orisys",
"gui",
"pipeline",
"viz",
]
EXCLUDE_MODULES = [
"cupy",
"cupyx",
"pyopencl",
"open3d",
"tkinter",
"pip",
"wheel",
]
DATA_DIRS = [
("config", "config"),
]
README_LINES = [
"ORY-MINI internal calibration/testing GUI (scaffold)",
"",
"This product packaging target is reserved for a future internal tool.",
"Entry point placeholder:",
f" {ENTRY_SCRIPT.as_posix()}",
]
def make_config() -> GuiBuildConfig:
return GuiBuildConfig(
product="ory_mini",
entry_script=ENTRY_SCRIPT,
exe_name=EXE_NAME,
dist_name=DIST_NAME,
hidden_imports=list(HIDDEN_IMPORTS),
exclude_modules=list(EXCLUDE_MODULES),
data_dirs=list(DATA_DIRS),
readme_lines=list(README_LINES),
windowed=False,
)