Research-Stack/5-Applications/text-to-cad/skills/cad/scripts/common/render.py
allaun 5f80fd8429 chore(repo): push local 768-commit branch state onto clean remote baseline
This squashes all local history (768 commits) onto the scrubbed PR #90
baseline. Individual commits were lost during filter-repo corruption;
the working tree content is preserved intact.

Build: N/A (working tree state only)
2026-06-15 22:46:50 -05:00

141 lines
4.2 KiB
Python

from __future__ import annotations
import hashlib
import json
import tempfile
from pathlib import Path
from common.catalog import (
cad_ref_from_step_path as fallback_cad_ref_from_step_path,
find_source_by_cad_ref,
find_source_by_path,
viewer_artifact_path_for_step_path,
viewer_directory_for_step_path,
)
REPO_ROOT = Path.cwd().resolve()
CAD_ROOT = REPO_ROOT
def _source_for_cad_ref(cad_ref: str):
source = find_source_by_cad_ref(cad_ref)
if source is None:
raise ValueError(f"CAD STEP ref not found: {cad_ref}")
return source
def cad_ref_from_step_path(step_path: Path) -> str:
source = find_source_by_path(step_path)
if source is not None:
return source.cad_ref
return fallback_cad_ref_from_step_path(step_path)
def part_stl_path_for_cad_ref(cad_ref: str) -> Path:
source = _source_for_cad_ref(cad_ref)
if source.stl_path is None:
raise ValueError(f"CAD STEP ref has no configured STL output: {cad_ref}")
return source.stl_path
def viewer_dir_for_cad_ref(cad_ref: str) -> Path:
source = _source_for_cad_ref(cad_ref)
if source.step_path is None:
raise ValueError(f"CAD STEP ref has no STEP path: {cad_ref}")
return viewer_directory_for_step_path(source.step_path)
def viewer_artifact_path(cad_ref: str, suffix: str) -> Path:
source = _source_for_cad_ref(cad_ref)
if source.step_path is None:
raise ValueError(f"CAD STEP ref has no STEP path: {cad_ref}")
return viewer_artifact_path_for_step_path(source.step_path, suffix)
def part_glb_path_for_cad_ref(cad_ref: str) -> Path:
return viewer_artifact_path(cad_ref, ".glb")
def part_selector_manifest_path_for_cad_ref(cad_ref: str) -> Path:
return viewer_artifact_path(cad_ref, ".topology.json")
def part_selector_binary_path_for_cad_ref(cad_ref: str) -> Path:
return viewer_artifact_path(cad_ref, ".topology.bin")
def native_component_glb_dir(step_path: Path) -> Path:
return viewer_directory_for_step_path(step_path) / "components"
def part_stl_path(step_path: Path) -> Path:
return part_stl_path_for_cad_ref(cad_ref_from_step_path(step_path))
def part_glb_path(step_path: Path) -> Path:
return viewer_artifact_path_for_step_path(step_path, ".glb")
def part_selector_manifest_path(step_path: Path) -> Path:
return viewer_artifact_path_for_step_path(step_path, ".topology.json")
def part_selector_binary_path(step_path: Path) -> Path:
return viewer_artifact_path_for_step_path(step_path, ".topology.bin")
def render_artifact_paths_for_cad_ref(cad_ref: str) -> tuple[Path, Path, Path, Path]:
return (
part_stl_path_for_cad_ref(cad_ref),
part_glb_path_for_cad_ref(cad_ref),
part_selector_manifest_path_for_cad_ref(cad_ref),
part_selector_binary_path_for_cad_ref(cad_ref),
)
def render_artifact_paths(step_path: Path) -> tuple[Path, Path, Path, Path]:
return render_artifact_paths_for_cad_ref(cad_ref_from_step_path(step_path))
def relative_to_repo(path: Path) -> str:
resolved = path.resolve()
try:
return resolved.relative_to(REPO_ROOT).as_posix()
except ValueError:
return resolved.as_posix()
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def versioned_repo_url(path: Path, content_hash: str) -> str:
resolved = path.resolve()
try:
relative_path = resolved.relative_to(CAD_ROOT.resolve()).as_posix()
except ValueError:
relative_path = resolved.as_posix().lstrip("/")
suffix = f"?v={content_hash}" if content_hash else ""
return f"/{relative_path}{suffix}"
def atomic_write_json(output_path: Path, payload: object) -> Path:
output_path.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(
"w",
dir=output_path.parent,
prefix=f".{output_path.name}.",
suffix=".tmp",
delete=False,
encoding="utf-8",
) as handle:
json.dump(payload, handle, separators=(",", ":"), ensure_ascii=True)
handle.write("\n")
temp_path = Path(handle.name)
temp_path.replace(output_path)
return output_path