-
2026-08-07 — WebXR viewer: binds baked lightmaps, and stops double-lighting a pre-lit model. glTF 2.0 has no lightmap slot, so a baked map arrives disguised as a real texture slot on
TEXCOORD_1(blendertk'sLightmapWebExportwrites it intoocclusionTexture, leaving the emissive slot free for the scene's authored emissive).preview_viewer.htmlnow reads thelightmap_webmanifest from the sceneextrasand puts it back:material.lightMap,channelfrom the manifest,lightMapIntensityfrom the recorded encode divisor, and the carrier slot cleared so the same texture is not applied twice under two interpretations. Two details decide whether it looks right or merely plausible —GLTFLoadertreats occlusion as data and loads it linear, but the map is sRGB-encoded to survive 8 bits (left alone it renders far too dark), and the map is packed against UV2 rather than UV0. Acarrier: "fused"manifest is correctly a no-op:commit_unlitalready made the bake the material's own appearance.A lightmapped model also already contains its lighting, so the viewer's own
RoomEnvironmentIBL and key light are switched off while one is loaded (and restored for the next un-lightmapped model). Left on, the baked shadows stay put while everything around them lifts — which reads as a washed-out model rather than as double lighting, and is easy to misdiagnose as a bad bake. -
2026-08-07 - RPC plugin core: two bugs that made every host-side op silently not happen. Found by probing the live Substance Painter after a Maya Substance-bridge send delivered no mesh maps; both are in
net_utils/rpc/plugin_core.py, so both were vendored into all four_rpc_core.pypayloads (Maya/Blender x marmoset/substance) and into extapps' in-place Painter plugin.MainThreadMarshaller.runnever returned inside a host. The hop usedQTimer.singleShot(0, functor)on the comment that its 0-delay overload "schedules onto the application's own thread". It does not: with no context object Qt builds the helperQObjectin the calling thread, which for an RPC op is the HTTP server's daemon thread, and that thread runs no event loop - so the functor was queued somewhere nothing pumps and every marshalled op raisedTimeoutErrorafter 60s. Invisible to the existing tests, which have noQCoreApplicationand so all took the direct-call branch. It now hands the call to a relayQObjectwhose affinity is the main thread and whose queued signal crosses the boundary (built lazily, per binding, rebuilt if the host swaps applications); measured against PySide6 6.5.3 and 6.10.1 - the shortersingleShot(0, context, functor)spelling does not exist in PySide6 at all. Five new tests drive the real path (worker thread + a pumped main loop): the hop lands on the main thread, exceptions propagate verbatim, two hops in a row both land,is_active()is True off-thread, and a genuinely blocked main thread still times out.A host reload left the plugin serving an empty op table.
RpcPlugin.import_ops(package)replaces the plugins'from . import ops: the op modules register at import time, so once a host re-executes the plugin package (Painter's Python > Reload Plugins Folder, a disable/re-enable,importlib.reload) the freshRpcPluginhas an empty registry while the ops submodules are still cached - the import is a no-op, no decorator fires, and the server comes back answeringUnknown opfor every feature it exists to provide whilesystem.pingstill says it is alive.import_opsdrops the subtree fromsys.modulesfirst, so registration is always tied to the registry that is live now. Three tests, one of which pins the plain-import baseline so the helper cannot be "simplified" away.TestWireContractnow takes the marshaller's documented opt-out - it blocks the calling thread on the client call, so once anything in the session stands up aQCoreApplication(the new Qt cases do) the server's hop would wait on a loop that is waiting on the server. pythontk 2741 green. -
2026-08-07 — Scene-data grid:
MeshConvertowns the sidecar's converter column, and every GLB conversion can carry it. Production GLBs never got the translation repairs the WebXR preview showed —fbx_to_glbhas five callers (preview, both DCC Scene Exporters' GLB paths, tentacle's quick-export, the converter panel) and only the preview applied the sidecar, because the applier orchestration lived onPreviewDeliverer. The registry (SIDECAR_APPLIERS), schema version and the three ops —build_scene_sidecar(envelope),apply_scene_sidecar(dispatch sections + embed the envelope and its per-section outcome summary into the glTF rootextras, path or open-session),read_scene_sidecar(the consumer half) — now live onMeshConvertbeside the writers they dispatch to, andfbx_to_glb(sidecar=...)folds the whole apply+embed into the same post-conversion edit session as the alpha repair: one parameter, one file pass, and the artifact leaves self-describing with no side files.PreviewDelivererslims to build → convert → apply-for-the-summary → publish;PreviewBridge._attach_sidecardelegates envelope construction to the new schema owner.--user-propertiesis now a default FBX2glTF flag — measured against v0.13.1 + Maya 2025: the DataNodesdata_exportchannels deliberately embedded in the FBX arrive in per-node glTFextraswith it and are silently dropped without, so a GLB-only deliverable now carries the full channel set inside. Seven newTestSceneSidecartests (frozen envelope top level, apply+embed round-trip, session composition,Noneno-op, empty-sections embed, unknown-section skip, thefbx_to_glbparameter) plus an embed assertion on the published preview artifact; pythontk 2745 green. End-to-end verified live in both DCCs (standalone Maya 2025: lambert incandescence →emissiveFactor [1, .5, 0]+ embedded envelope; headless Blender 5.1.2: emission×2 → factor +KHR_materials_emissive_strength: 2.0). -
2026-08-07 — WebXR preview: the scene sidecar travels in a versioned envelope, owned in one place. The
.scene.jsonbeside the exported FBX (and the same dict onPayload.extras) used to be the bare section map — no version, no source DCC, no tie to the FBX it belongs to. Fine while the only reader wasPreviewDeliverer._apply_sidecarin this repo; frozen the moment an external tool parses it, which is the file's stated future (artist previews it, a dev pulls the same setup from FBX + sidecar alone). The top level is now{version, source, asset, color_space, sections}, built and written byPreviewBridge._attach_sidecar— both DCC producers call it rather than shaping the dict themselves, so the schema cannot fork between mayatk and blendertk (whose_producetails had duplicated the extras/json/log block verbatim, the drift the shared owner also removes). The deliverer consumes onlysections;SIDECAR_VERSIONbumps on top-level reshapes, never for a new section (sections are the extension point, and a reader skips unknown ones). Fixed with it, a pre-existing misreport: the panel's three-way outcome (off/ on-but-nothing-to-carry / on-but-nothing-matched) was unreachable — producers attached the extras key only when non-empty, sosidecar_requested(key presence) read False for an unlit scene and the summary told a user whose checkbox was ON "Scene sidecar off - showing what the FBX carried"; thedeliver()log ternary was also inverted (the off case logged "produced for this export")._attach_sidecarnow attaches (and writes) the envelope whenever the producer runs — empty sections included, an explicit"sections": {}being itself information — restoring key-presence ⇔ requested; both pinned by a test that fails on the old code. Explicitly not a second channel registry beside the DCCs'DataNodes: tool-authored semantics already ride inside the FBX ondata_export, so the sidecar carries only FBX-translation repairs, derived scene-read-only at push time — the boundary is documented in_attach_sidecarand in mayatk'sdocs/data_nodes.md. Deliverer fixtures now build through the envelope helper, plus four new tests: the frozen top-level key set (round-tripped through the written file), the empty-sections envelope still attaching, the requested-but-empty push reporting "nothing to carry" rather than "off" (fails on the old code), and the producer→deliverer pair end to end through the real_attach_sidecar. pythontk 2737 green; producers verified live in standalone Maya 2025 and headless Blender 5.1.2 (identical envelopes).