cadgen: keep the component placeholder off the staging name

The component builder names a synthetic source STEP from the path it is
writing to, and that name reaches the payload as faceProxy.source. The
sibling temp file used to end in one suffix, .tmp<pid>, which with_suffix()
replaced whole, so the staging name never survived. temp_suffix() adds two
components and only .tmp is replaced, so pid and the random draw landed in
every component GLB and two builds of the same geometry stopped matching --
test_components_are_clean_and_byte_deterministic on both runners.

Take the destination path for the placeholder instead of the file being
written. Appended rather than substituted so the placeholder stays exactly
what the old temp name reduced to; substituting would rewrite every
component and invalidate the caches keyed on their content.
This commit is contained in:
NgoQuocViet2001
2026-08-17 22:31:45 +07:00
parent b90931e8f3
commit f3dc314cfb
@@ -393,6 +393,7 @@ def _write_component_glb_atomic(
cad_ref=cad_ref,
linear_deflection=linear_deflection,
angular_deflection=angular_deflection,
identity_path=out_glb,
)
replace_atomic(temp_path, out_glb)
finally:
@@ -410,6 +411,7 @@ def build_component_glb_from_shape(
cad_ref: str,
linear_deflection: float,
angular_deflection: float,
identity_path: Path | None = None,
) -> Path:
"""Mesh an in-memory part shape (in its local frame) to a *clean* component GLB.
@@ -417,9 +419,21 @@ def build_component_glb_from_shape(
disk), so it is meshed and selector-extracted directly from the shape. The embedded
STEP_TOPOLOGY is stripped of all source provenance (see ``COMPONENT_PROVENANCE_KEYS``)
so the component is a pure function of geometry + mesh tolerances — byte-deterministic
and content-addressable. Provenance lives on the per-model descriptor, not the leaf."""
and content-addressable. Provenance lives on the per-model descriptor, not the leaf.
``identity_path`` is the path the artifact will have once it is in place, for callers
that write through a staging file. The synthetic STEP name derived below reaches the
embedded manifest as ``faceProxy.source``, so naming the staging file instead would
put that name in the payload and make the bytes depend on where a build staged them
rather than on the geometry."""
out_glb.parent.mkdir(parents=True, exist_ok=True)
placeholder = out_glb.with_suffix(".step")
# Appended to the name rather than substituted into it, which keeps the placeholder
# byte-identical to the one a sibling temp file used to reduce to: `<cid>.glb.tmp<pid>`
# has `.tmp<pid>` as its last suffix, so with_suffix() yielded `<cid>.glb.step`.
# Substituting reads better but would rewrite every component GLB and invalidate the
# content-addressed caches keyed on them, which is a separate change.
identity = identity_path or out_glb
placeholder = identity.with_name(f"{identity.name}.step")
# The shape arrives LOCATED (the occurrence is ``part.moved(transform)``). Strip the location
# so the GLB is emitted in the part's LOCAL frame with an identity node: world placement is
# supplied solely by the descriptor occurrence transform, and content-addressed dedup needs