Truss attachment candidates
Fixture mounting is a separate continuous-path domain. See Fixture attachment paths on trusses.
Truss-to-truss Magnet snapping is translation-only and uses deterministic attachment points. Candidate scoring applies the active view's axis weights, but the selected translation always retains all three components.
Priority and data
Each truss resolves candidates independently. Usable Magnet geometry nodes
from its GDTF archive suppress all inferred points. Their Name, optional
Model, complete composed Position transform, stable traversal identity, and
diagnostic path are retained by a read-only core service. Malformed positions
are diagnosed and skipped. If no usable explicit point remains, inference is
used without writing to the GDTF or MVR data.
Public GDTFSpec is resolved first, relative to MvrScene::basePath when it is
not absolute. If that reference is absent or cannot be resolved, the Perastage
auxiliary GDTF archive reference is resolved the same way. modelFile is never
treated as a GDTF source. Missing and unreadable sources remain visible as
structured diagnostics before conservative inference is used.
An explicit resolver caches type-level local candidates by normalized absolute archive identity, file size, and a fixed-width nanosecond last-write timestamp. Instance transforms are applied after lookup, so dragging or rotating a truss does not reopen its archive. Replacing an archive changes the version key and reparses its local definition. Viewers own their resolver lifetime; the core has no static resource-owning cache.
Inference
Raw 3DS and GLB trusses are measured through one CPU-only bounds resolver that reuses the render mesh loaders and their unit/transform conventions. The resolver retains local minimum and maximum coordinates, and caches results by normalized path, size, and modification time. Valid standardized dimensions read from an existing GDTF remain authoritative metadata; missing or invalid values are recovered from measured geometry without modifying that archive. Perastage-generated GDTFs validate dimensions before writing, while legacy synthetic 1000 x 400 x 400 mm metadata is replaced only for raw/legacy geometry whose measured bounds differ. A genuinely matching model and deliberate valid metadata remain unchanged.
MVR import resolves both direct Geometry3D resources and Symbol/Symdef
resources before inserting a truss into the scene. Raw mesh bounds remain in
mesh-local coordinates; the existing composed truss, Symbol, and Symdef
matrices are applied later by candidate consumers. Dimension provenance is
tracked as unknown, authoritative GDTF Model, geometry-derived, Perastage
metadata, recovered legacy synthetic fallback, or manual override. Geometry
replaces invalid values and conservatively identified legacy metadata, but
never valid GDTF Model dimensions or a manual override. Historical
1000 x 400 x 400 mm
metadata is considered synthetic only for geometry-backed Perastage metadata
without GDTF authority and only when measured bounds differ; geometry that
actually measures 1000 x 400 x 400 mm is therefore preserved.
The bounds resolver and Viewer3D both use the native MVR 3DS vertex convention: the optional 3DS object basis is not applied. This keeps measured bounds aligned with rendered geometry, including assets that contain a non-identity basis.
Dimensions are evaluated in truss-local X, Y, and Z. A shape is longitudinal only when its largest positive finite dimension is strictly greater than twice the second-largest dimension. The strict comparison makes the 2:1 boundary ambiguous while classifying 3000 x 400 x 400 mm as longitudinal.
Longitudinal shapes expose exactly the negative and positive centers of the dominant-axis terminal planes using the measured local minimum and maximum, not an assumed origin. Ambiguous shapes, including invalid-dimension fallbacks, expose the six oriented bounds face centers. These inferred points are conservative snapping aids, not verified mechanical connectors.
Groups expose candidates resolved from their actual member trusses. Candidate pairs from different members are treated as occupied internal joints when their positions are within 25 mm and, when both have inferred directions, their direction dot product is at most -0.996. Deterministic one-to-one matching removes occupied candidates. Every remaining member candidate is exposed with its member UUID and original stable identity. Aggregate face centers are used only if no member candidates can be built.
Inferred longitudinal ends and ambiguous face centers carry runtime-only outward directions transformed into world space. Explicit GDTF Magnets do not receive an invented direction because GDTF does not standardize one.
For a moving truss with exactly two inferred longitudinal ends, target acquisition remains view-driven. After acquiring one target candidate, both source ends are evaluated against that same target. If the nearby end would produce more than 50 mm of substantial oriented-bounds penetration into the actual target truss or owning group member and the opposite end would not, the opposite end is selected. Opposing inferred directions break only an all-overlapping tie. This correction changes translation only and never rotates or flips the truss.
Cache invalidation normally uses normalized identity, size, and timestamp.
Project/resource lifecycle owners may call Clear() when replacing a resource
while deliberately preserving both metadata values; the interactive path does
not hash archives on every query.
View weighting
Top and Bottom ignore Z in scoring, Front ignores Y, and Side ignores X. The 3D camera-direction weights remain available to the existing non-screen-space paths. Weighting changes selection only; it never removes a component from the final translation.
Viewer3D truss-to-truss acquisition instead projects both candidates from one captured camera snapshot and accepts a separation of at most 16 logical pixels. Framebuffer coordinates are divided by the content scale, so the aperture is DPI-independent. Accepted pairs rank by screen separation, absolute view-depth difference, full world translation length, target UUID, source candidate ID, and target candidate ID. The 250 mm world threshold is not an eligibility cap for this Viewer3D path.