-
2026-08-02 — Hand-off launches can sanitize the child env:
ScriptLaunchSpec.launch_env+AppLauncher.handoff_env. A DCC bridge launches app B from inside app A, so B inherits A's whole environment — including anOCIOvar pointing inside A's own install tree (Blender's bundledocio_profile_version: 2.5config), which B's OpenColorIO runtime may be unable to load: Maya 2025 (OCIO 2.3) failed color-management init on every bridge launch.ScriptLaunchSpecgains an optionallaunch_envcallable (not a dict — the env must reflect launch-time state, and specs are built at import time;launch_argsprecedent), threaded throughScriptLaunchDeliverer.deliverintoAppLauncher.launch(env=...). The policy half isAppLauncher.handoff_env(source_root): copy of this process's env withoutOCIOwhen — and only when — its path lies under source_root (the running app's install tree; nobody points a studio pipeline config into a versioned DCC install dir, so a config outside it is deliberate cross-app state and inherits untouched). App-agnostic: the DCC bridges supply their own roots (blendertk frombpy.app.binary_path, mayatk from$MAYA_LOCATION). The deliverer treats the hook as best-effort: a raising hook logs a warning and launches with the inherited env rather than costing the send.test_app_launcher.py+3 (private stripped with the rest of the env intact; foreign inherited; no-OCIO/no-root inherit);test_bridge.py+1 (hook env reaches the launcher; raising hook degrades, send still succeeds). -
2026-08-02 —
Workspace.promote+Workspace.for_path: the two workspace primitives both DCC adapters had each hand-rolled.promote(root, scene_exts, rules=None)is the inverse ofcreate— instead of imposing a layout it describes the one already on disk (scene files loose in the root ⇒ scene rule.; an existingtextures/with nosourceimages/⇒ that is thesourceImagesrule) — and was duplicated verbatim inmtk.promote_workspaceandbtk.promote_workspace, differing only in the scene extensions. A divergence there would have made the two DCCs describe the same folder differently, which is precisely what the shared marker format exists to prevent.for_path(path)is the resolver tail bothcurrent_workspaceimplementations shared: nearest marked ancestor, else the path's own folder as an unmarked workspace. Also new: module constantSCENE_RULES— one tuple soscene_dir(reader) andpromote(writer) can't disagree about which rules mean "scene".promoteonly writes the per-format aliases (mayaAscii/mayaBinary) a caller's rule set already carries, so a custom template never silently grows rules it didn't ask for.test_workspace.py+9 → 39. -
2026-08-02 —
WorkspaceTemplates: named file-rule sets are now a SHARED, unnamespaced store, not a Blender-only one. Aworkspace.melis deliberately a shared Maya/Blender project — but the templates that say how a new project is built lived underPresetStore("workspace_templates", package="blendertk"), so a studio layout saved from Blender's Workspace Editor was invisible to Maya, and mayatk's newcreate_workspacetwin would have had to grow a second, divergent store.ptk.WorkspaceTemplates(infile_utils/workspace.py, beside theWorkspacemodel it serves) is that one store:user_config_root()/workspace_templates, no owning package, withlist/rules/save/deleteclassmethods.rules(name=None)resolves the active (last-saved) template, strips uitkPresetManager's_metabookkeeping block, and never returns an empty dict — an empty template would otherwise build ruleless projects.store()is rebuilt per call rather than cached, so a$UITK_PRESETS_ROOTsandbox set after first use still takes effect (tests rely on this), and it migrates a pre-existingblendertk/workspace_templatesdirectory on first access — those are user data.test_workspace.py+8 → 30.