-
2026-08-17 —
MeshConvert.prune_glb_unreferenced_textures(file_utils/mesh_convert/_mesh_convert.py): the applier-tail sweep that drops textures no material samples, the images only they read, and the bufferViews only those images owned. Every channel writer rebinds a slot to the image it embeds and leaves the displaced one in place; for the ORM repack that is FBX2glTF's ownao_met_rough_<mat>packing, a full-size PNG in the BIN. Measured on HOOKS_PINS.glb: 4 images / 1 unreferenced / 2.05 MB dead payload, flagged by the reviewer as an orphaned texture, one per repacked material every export. Referenced = anytextureInfoin the material tree (key endingTexturewith an intindex, which covers the core slots and everyKHR_materials_*extension); images survive throughsourceor an extension source; a view is dropped only when the pruned images were its ONLY readers (FBX2glTF does share views between images). Survivors are renumbered everywhere — material refs, the session embed cache,textures[].source, and everybufferViewkey in the document via a generic walk — and the BIN is rebuilt from the surviving views. Runs at the tail ofapply_scene_sidecarBEFORE the embedded texture map records image indices; a clean file is not rewritten. Verified on the production GLB: 12.96 → 10.90 MB, geometry decodes identically, ORM channels intact. 6 failing-first tests (incl. theEXT_lights_image_basedbail, since that extension names images from the root and the walk cannot see it, and a bail when anybufferViewlives outside the embedded buffer 0 — the BIN rebuild re-slices every kept view out of that one buffer, so an external-URI view would keep itsbufferbut get a byteOffset into the rebuilt BIN);test_mesh_convert117/117. -
2026-08-17 —
MapFactory.pack_orm_texturerecognises the SAME packed map named in several slots when the paths are equal but distinct string objects (core_utils/engines/textures/map_factory/_map_factory.py::_resolve_orm_sources). A StingrayPBS material that wires one ORM file node into its AO, roughness and metallic inputs reachesMeshConvert.set_glb_metallic_roughnessas three equal paths that are three distinct objects (the export sidecar is a JSON round trip). The resolver matched slots to the packed source by identity, so only the first slot was recognised; the other two stayed a packed path, were cleared as "carries no channel", and took their black fills. Measured on a production delivery (HOOKS_PINS.glb): the referenced ORM had AO intact and roughness 0 / metallic 0 at every one of 20,279 sampled texels, while the on-disk ORM was fully authored. Equality now, one failing-first test;test_img+test_map_factory292/292. -
2026-08-17 —
MapCompositor._seed_masksvets each mask source before OR-ing it into the union (core_utils/engines/textures/map_compositor.py). The union masked from every detectable-background type trusted each source equally, so one type with dilated islands (Painter edge padding bleeding into a neighbour's UV region) or a mis-detected background inflated every layer mask and dragged an otherwise clean batch into the >5% overlap WARNING — with no attempt to prefer the reliable source. Each type's own inter-layer overlap is now measured (_max_pairwise_overlap, threshold_MASK_OVERLAP_MAX); a source past it is dropped, with an INFO naming it and its overlap, whenever at least one clean type exists. Only when every source agrees do the layers overlap is the WARNING emitted, and it now says so — that is a genuine object-in-both-sets / shared-UV issue the user must act on, not a masking artefact. Two failing-first tests (noisy source dropped when a clean one exists / all-source overlap still warns);test_map_compositor72/72.