-
2026-08-01 — Exporter blind spots: duplicate-material merges are now VERIFIED, NLA/data-level animation reaches the bake range, and the selection funnel can no longer silently drop (or be killed by) unshippable objects. Three fixes, mirrors of mayatk's new two-phase duplicate design where one exists. (1)
MatUtils.find_materials_with_duplicate_textures(materials, strict=False, verify=True)is now two-phase (mirror ofmtk's): phase 1 fingerprints on(surface shader idname, {(surface socket, texture id)})— TEX_IMAGE nodes are collected through nested node groups (previously invisible) and attributed to the surface-shader input they feed, so one shared detail/normal map no longer merges two different materials (confirmed false positive:reassign_duplicate_materials(delete=True), the default-on exporter task, DELETED one of them); phase 2 (verify=True, default) pairwise-verifies each candidate against its group's keeper — same shader type, equal unlinked shader inputs (1e-5), and per-slot identical color space,ShaderNodeMappingplacement, and image CONTENT (size + partial hash via the new_texture_content_id, library-aware paths).reassign_duplicate_materials(..., verify=True)re-proves each group right before its destructive merge. (2)AnimUtils.get_animated_extent/has_nla_or_data_animation:_actionsreads onlyo.animation_data.action, so NLA-strip-only or shape-key/data-level animation was invisible while the FBX write bakes the evaluated scene —set_bake_animation_rangenow covers active-action fcurves ∪ non-muted NLA strip extents (scene time) ∪ data/shape-key fcurve ranges, andcheck_untied_keyframes/check_floating_point_keys/check_framerateWARN once per run ("NLA/data-level animation present — only validates active object actions") instead of staying vacuously green (edit tasks deliberately NOT extended to strips: strip actions may be shared/library-linked with their own frame mapping). (3) Export funnel content loss:FbxUtils.exportselected with unguardedselect_set— hidden objects silently vanished from the FBX and a view-layer-excluded member RAISED and killed the export; unselectable members are now collected + WARNED (count + names; newstrict=Trueraises instead), the selection restore tolerates a no-longer-selectable prior member,SceneExporterpre-filters the export set with an INFO log (the funnel warning is the backstop;check_hidden_geometryremains the failing gate), andexport_data_nodelinks a collection-hidden/excludeddata_exportcarrier to the scene root for the write (deferred unlink after) so its metadata ships regardless.test_mat_anim_utils.py+12 checks (socket identity, tiling/content/colorspace/settings near-misses each NOT duplicates, node-group texture seen, true duplicates merged, reassign's own verify gate) → 160/160;test_smart_bake.py+11 (NLA-only strip extent reaches the scene range, muted strips skipped, shape-key extent, once-per-run check advisory) → 138/138;test_scene_exporter.py+10 (funnel drop warning + strict raise, excluded member no longer fatal, pre-filter INFO + FBX content, excluded-collection carrier ships + root link removed) → 58/58. -
2026-08-01 — Texture checks become storage-aware (library / packed / UDIM), and the glb-only sidecar no longer rolls forward on a failed conversion. Three fixes from the exporter tasks/checks audit. (1)
_MatUtilsInternal._abspathis now library-aware: an image linked from a library .blend stores its//path relative to the LIBRARY file, so resolving withoutlibrary=img.librarypointed every linked texture at the wrong directory — false "missing" incheck_valid_paths/get_image_records, wrong duplicate fingerprints and size probes for every_abspathconsumer. (2) Packed + UDIM images in the three texture checks (mirrors mayatk's<UDIM>-collapse semantics): PACKED images are skipped bycheck_valid_paths,check_absolute_paths, andcheck_texture_file_size— the FBX embeds them from memory, so their (often stale, absolute) bookkeeping path never ships; TILED (UDIM) images — previously invisible tocheck_valid_paths(get_image_recordsis FILE-only, so a deleted tile set passed) — are validated by probing the first declared tile on disk (tiles[0].number, 1001 fallback), and size-gated at their largest existing tile via the new_MatUtilsInternal._udim_first_tile_path/_udim_tile_paths(previouslyos.path.getsizeon the raw token path raisedOSErrorinto a silentcontinue, letting multi-GB tile sets through unmeasured). (3)_write_exportwrites the scene-data sidecar only after the deliverable is confirmed: in glb-only mode it was written before the FBX→GLB conversion, so a failed conversion produced no deliverable but still rolled the hierarchy-diff baseline forward (same ordering bug as mayatk's).test_scene_exporter.py+10 checks (packed passes both path checks, tile-less UDIM fails / first-tile UDIM passes validity, largest-tile size gate, failed-glb-leaves-no-sidecar + success-writes-it) → 48/48;test_texture_path_editor.py+3 (a linked image resolves against its library and reports as existing) → 16/16. -
2026-08-01 —
Naming.renamecollapses separator residue after strip/replace formatting (mayatk parity). Stripping a token from'a__tok__tokB'left'a____B'; now collapsed to'a_B'via the newptk.collapse_delimiter_runs— an explicit__typed in the pattern is honored. Mirror of the mayatk fix root-caused from the VDATS mangled-shape-name incident (mayatk CHANGELOG 2026-08-01 has the full story; the uninstance scratch-token source is Maya-only, so only the rename half applies here). -
2026-08-01 — Scene Exporter: the smart_bake session is now actually RESTORED after export (first fix from the tasks/checks audit). The task stored
result.session_idbut nothing in production ever consumed it, so every export with Smart Bake on (default) permanently muted the user's constraints/drivers and left the baked Action in place — despite the task docstring's and tooltip's "restorable" contract.perform_export's outerfinallynow mirrors mayatk's:SmartBake.restore(session_id)on every exit path (successful write, failed check, raising task), restore-failure surfaced at WARNING with the session id (the session stays in the manifest for a manual retry), and exceptions never mask the export result. Also: a check-blocked export now warns that task edits (material cleanup, key snapping/tying, path rewrites) remain in the scene — checks run after tasks and there is no automatic rollback; andcheck_hidden_geometry's message + tooltip now state the actual Blender hazard: the selection-based funnel (use_selection=True) silently DROPS hidden meshes from the FBX — the old Maya-inherited message claimed they "will be exported", the exact inverse.test_smart_bake.py+10 exporter-level checks (_run_exporter_bake_restore_checks: success path and blocked-export path, both asserting the constraint unmutes and no session is left) → 127/127. -
2026-08-01 —
BlenderUiHandler+BlenderNativeMenusjoin the package namespace (mayatk parity). mayatk exposesmtk.MayaUiHandler/mtk.MayaNativeMenus; blendertk exposed neither, sotcl_blendercomposed its handler set withmtk.MayaUiHandler's twin spelled as a deep module path — the two DCC hosts reading differently for no reason, in a package whose whole contract is mirroring mayatk's names. Nowbtk.BlenderUiHandler/btk.BlenderNativeMenus, class-only like their Maya counterparts. -
2026-08-01 — Scene Exporter now refreshes every metadata producer at export:
FbxUtils.run_export_preparers+_KNOWN_PRODUCERS(mayatk parity), so a mesh deleted after the last bake can no longer ship a staledata_exportmanifest. From the DCC→Unity data_nodes architecture review. Blender has no before-FBX-export event, so authoring-time publishes were the only refresh —export_data_nodeclaimed "the carrier is already current at export", which scene edits since the last bake/commit quietly falsified. The task now runs the new producer registry (shadow / emissive_groups / lightmap —LightmapBaker.refresh_export_metadataadded as the public no-arg entry point, mirroring the other producers) via_refresh_scene_data_nodebefore folding the carrier in; producers are isolated and clear-on-empty, and non-Scene-Exporter export paths still ship the authoring-time state (documented as the remaining divergence from mayatk's session hook). Also:DataNodes._set_stringhonors its documented return contract (Nonewhen an empty value had nothing to clear — it returned the carrier name), anddocs/data_nodes.md's channel table gains theshadow_metadata/emissive_groupsrows it was missing (plus de-staled Shots wording).test_scene_exporter.py's carrier round-trip now authors a real lightmap marker and publishes through the producer instead of hand-stamping producer-owned state (which the refresh correctly regenerates away — that's the feature). Suites: emissive_groups 60/60, lightmap_baker 38/38, node_utils 58/58, shadow_rig 63/63, smart_bake 117/117, scene_exporter 38/38.