Scene Manager: fixes from the first run against a real Libreflow
- Update / version switch: Blender 5.2 replaces the library on reload (renamed after its file, custom properties gone); find it by path and restore its name and tags. - Remove the libraries an asset linked indirectly when it is removed or fails to load (remove_unused_libraries), never a managed one. - Config: real Kitsu asset types for link modes and Shot Viewer groups, setdress_low files for bg_location, "environments" collection fallback. - Dev fake server and test use the real asset types; rock links a node group from the essentials to cover the indirect library cleanup. - README and CLAUDE.md updated with the live findings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
017bdfc2fd
commit
9ee7710479
7 files changed
+109
-35
No files matched your search
@@ -2,8 +2,8 @@
|
||||
|
||||
python dev/fake_libreflow.py <workdir>
|
||||
|
||||
Serves a project with one shot casting bob (chars, x2), tree (sets), rock (props),
|
||||
lamp (props, nothing published) and ghost (not in the flow). The asset files come
|
||||
Serves a project with one shot casting bob (main_character, x2), tree (bg_set), rock (main_props),
|
||||
lamp (main_props, nothing published) and ghost (not in the flow). The asset files come
|
||||
from dev/make_test_assets.py (<workdir>/lib). Writes <workdir>/kabaret/server.json:
|
||||
point config LIBREFLOW_SERVER_FILE to it (see dev/test_scene_manager.py).
|
||||
|
||||
@@ -26,15 +26,15 @@ def rev(name, path, available=True, comment=""):
|
||||
"available": available, "date": time.time(), "user": "nero", "comment": comment}
|
||||
|
||||
ASSETS = { # asset oid -> (type, {file_oid: (label, [revisions oldest first])})
|
||||
f"{PROJECT}/asset_types/chars/assets/bob": ("chars", {f"{PROJECT}/asset_types/chars/assets/bob/tasks/rigging/files/rig_blend": ("rigging/rig.blend", [
|
||||
f"{PROJECT}/asset_types/main_character/assets/bob": ("main_character", {f"{PROJECT}/asset_types/main_character/assets/bob/tasks/rigging/files/rig_blend": ("rigging/rig.blend", [
|
||||
rev("v001", f"{ROOT}/lib/bob/v001/rig.blend"), rev("v002", f"{ROOT}/lib/bob/v002/rig.blend", comment="two meshes")])}),
|
||||
f"{PROJECT}/asset_types/sets/assets/tree": ("sets", {f"{PROJECT}/asset_types/sets/assets/tree/tasks/modeling/files/model_blend": ("modeling/model.blend", [
|
||||
f"{PROJECT}/asset_types/bg_set/assets/tree": ("bg_set", {f"{PROJECT}/asset_types/bg_set/assets/tree/tasks/modeling/files/model_blend": ("modeling/model.blend", [
|
||||
rev("v001", f"{ROOT}/lib/tree/v001/model.blend")])}),
|
||||
f"{PROJECT}/asset_types/props/assets/rock": ("props", {f"{PROJECT}/asset_types/props/assets/rock/tasks/modeling/files/rock_blend": ("modeling/rock.blend", [
|
||||
f"{PROJECT}/asset_types/main_props/assets/rock": ("main_props", {f"{PROJECT}/asset_types/main_props/assets/rock/tasks/modeling/files/rock_blend": ("modeling/rock.blend", [
|
||||
rev("v001", f"{ROOT}/lib/rock/v001/rock.blend")])}),
|
||||
f"{PROJECT}/asset_types/props/assets/lamp": ("props", {}),
|
||||
f"{PROJECT}/asset_types/main_props/assets/lamp": ("main_props", {}),
|
||||
}
|
||||
CASTING = [("bob", "chars", 2), ("tree", "sets", 1), ("rock", "props", 1), ("lamp", "props", 1), ("ghost", "chars", 1)]
|
||||
CASTING = [("bob", "main_character", 2), ("tree", "bg_set", 1), ("rock", "main_props", 1), ("lamp", "main_props", 1), ("ghost", "main_character", 1)]
|
||||
CALLS = []
|
||||
|
||||
def file_data(file_oid):
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
bob: a rig (COL_bob: armature + meshes), v001 has 1 mesh, v002 has 2.
|
||||
tree: COL_tree, plus an unrelated top-level collection.
|
||||
rock: a single top-level collection not named after the asset (fallback case).
|
||||
rock: a single top-level collection not named after the asset (fallback case), its mesh
|
||||
using a node group linked from Blender's essentials (an indirect library).
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -21,7 +22,7 @@ def mesh_object(name):
|
||||
return bpy.data.objects.new(name, mesh)
|
||||
|
||||
|
||||
def write(path, collection_name, meshes=1, rig=False, extra=False):
|
||||
def write(path, collection_name, meshes=1, rig=False, extra=False, node_group=False):
|
||||
bpy.ops.wm.read_homefile(use_empty=True)
|
||||
scene = bpy.context.scene
|
||||
collection = bpy.data.collections.new(collection_name)
|
||||
@@ -39,6 +40,11 @@ def write(path, collection_name, meshes=1, rig=False, extra=False):
|
||||
ob = mesh_object(f"GEO_{collection_name}_{index}")
|
||||
ob.parent = parent
|
||||
collection.objects.link(ob)
|
||||
if node_group:
|
||||
essentials = os.path.join(bpy.utils.system_resource("DATAFILES"), "assets", "nodes", "geometry_nodes_essentials.blend")
|
||||
with bpy.data.libraries.load(essentials, link=True) as (_data_from, data_to):
|
||||
data_to.node_groups = ["Smooth by Angle"]
|
||||
ob.modifiers.new("Smooth", "NODES").node_group = data_to.node_groups[0]
|
||||
if extra:
|
||||
scene.collection.children.link(bpy.data.collections.new("WIP_other"))
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
@@ -49,5 +55,5 @@ lib = os.path.join(WORKDIR, "lib")
|
||||
write(os.path.join(lib, "bob", "v001", "rig.blend"), "COL_bob", meshes=1, rig=True, extra=True)
|
||||
write(os.path.join(lib, "bob", "v002", "rig.blend"), "COL_bob", meshes=2, rig=True, extra=True)
|
||||
write(os.path.join(lib, "tree", "v001", "model.blend"), "COL_tree", extra=True)
|
||||
write(os.path.join(lib, "rock", "v001", "rock.blend"), "rock_geo")
|
||||
write(os.path.join(lib, "rock", "v001", "rock.blend"), "rock_geo", node_group=True)
|
||||
print("Test assets written in", lib)
|
||||
@@ -32,7 +32,7 @@ shot_viewer = sys.modules[PACKAGE + ".shot_viewer"]
|
||||
SERVER_FILE = os.path.join(WORKDIR, "kabaret", "server.json")
|
||||
config.LIBREFLOW_SERVER_FILE = SERVER_FILE
|
||||
SHOT = "/melvile/films/f01/sequences/s010/shots/p010"
|
||||
BOB_FILE = "/melvile/asset_types/chars/assets/bob/tasks/rigging/files/rig_blend"
|
||||
BOB_FILE = "/melvile/asset_types/main_character/assets/bob/tasks/rigging/files/rig_blend"
|
||||
FAILED = []
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ check("bob: 2 instances of v002", state.get("bob") == ("v002", ["COL_bob", "COL_
|
||||
check("tree: 1 collection instance", state.get("tree") == ("v001", ["tree"]), str(state.get("tree")))
|
||||
check("rock: single top-level collection fallback", state.get("rock") == ("v001", ["rock_geo"]))
|
||||
check("lamp and ghost reported", "lamp" in message() and "ghost" in message(), message())
|
||||
check("type collections", {"COL_chars", "COL_sets", "COL_props"} <= set(scene.collection.children.keys()))
|
||||
check("type collections", {"COL_main_character", "COL_bg_set", "COL_main_props"} <= set(scene.collection.children.keys()))
|
||||
check(
|
||||
"chars are posable overrides",
|
||||
all(ob.override_library for ob in bpy.data.collections["COL_bob"].all_objects),
|
||||
@@ -148,6 +148,7 @@ bpy.ops.wtd_scene.remove(selected=True)
|
||||
check("selected instance removed", loaded()["tree"][1] == [])
|
||||
bpy.ops.wtd_scene.remove("EXEC_DEFAULT", library="rock")
|
||||
check("whole asset removed", "rock" not in bpy.data.libraries)
|
||||
check("its indirect library removed too", set(bpy.data.libraries.keys()) == {"bob", "tree"}, str(bpy.data.libraries.keys()))
|
||||
bpy.ops.wtd_scene.load()
|
||||
pump()
|
||||
check("load restores what is missing", {k: len(v[1]) for k, v in loaded().items()} == {"bob": 2, "rock": 1, "tree": 1})
|
||||
@@ -155,7 +156,7 @@ check("load restores what is missing", {k: len(v[1]) for k, v in loaded().items(
|
||||
print("Ambiguous file")
|
||||
try:
|
||||
lib.link_asset(
|
||||
{"name": "zzz", "oid": "x", "asset_type": "props", "file": {"file_oid": "f"}},
|
||||
{"name": "zzz", "oid": "x", "asset_type": "main_props", "file": {"file_oid": "f"}},
|
||||
{"name": "v1", "path": os.path.join(WORKDIR, "lib", "bob", "v001", "rig.blend")},
|
||||
SHOT,
|
||||
)
|
||||
@@ -170,7 +171,7 @@ local.objects.link(bpy.data.objects.new("GEO_lamp", bpy.data.meshes.new("GEO_lam
|
||||
check("only free local collections offered", [c.name for c in lib.assetize_candidates()] == ["COL_lamp"])
|
||||
sm.STATE.assets = ipc.call("wtd_list_assets", None, oid=SHOT)
|
||||
bpy.ops.wtd_scene.assetize(
|
||||
"EXEC_DEFAULT", collection="COL_lamp", asset_type="props", asset_name="lamp", task="modeling", comment="test"
|
||||
"EXEC_DEFAULT", collection="COL_lamp", asset_type="main_props", asset_name="lamp", task="modeling", comment="test"
|
||||
)
|
||||
pump()
|
||||
check("published and replaced", "replaced" in message(), message())
|
||||
@@ -178,7 +179,7 @@ check("lamp linked", loaded().get("lamp") == ("v001", ["COL_lamp"]), str(loaded(
|
||||
check("local collection kept, renamed", "COL_lamp_local" in bpy.data.collections)
|
||||
bad = bpy.data.collections.new("COL_bad")
|
||||
scene.collection.children.link(bad)
|
||||
bpy.ops.wtd_scene.assetize("EXEC_DEFAULT", collection="COL_bad", asset_type="props", asset_name="bad", task="layout")
|
||||
bpy.ops.wtd_scene.assetize("EXEC_DEFAULT", collection="COL_bad", asset_type="main_props", asset_name="bad", task="layout")
|
||||
pump()
|
||||
check("unknown task reported", "available: modeling, rigging" in message(), message())
|
||||
|
||||
|
||||
Reference in new issue
Block a user