Scene Manager: Load Publish and numbered instances
Load Publish (Loaded panel) searches every asset of the project, then loads any of its published .blend files at any version, cast in the shot or not. The file the casting would load and the latest version on this site are preselected; a version not on this site is requested instead. Built on the existing wtd_list_assets / wtd_get_asset_files / wtd_get_revisions commands. New instances are numbered (COL_bob_01, tree_01...), counting on from the highest number in the file (SCENE_MANAGER_INSTANCE_NAME). README: Pick Shot only lists shots that exist in Libreflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6ec2b228ae
commit
fbc7ac18e4
9 files changed
+307
-32
No files matched your search
@@ -3,7 +3,8 @@
|
||||
python dev/fake_libreflow.py <workdir>
|
||||
|
||||
Serves a project with one shot casting bob (main_character, x2), tree (bg_set), rock (main_props),
|
||||
lamp (main_props, nothing published), car (vehicles, v001 not on this site) and ghost (not in the flow). The asset files come
|
||||
lamp (main_props, nothing published), car (vehicles, v001 not on this site) and ghost (not in the flow);
|
||||
chair (set_props) is not cast, for Load Publish. 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).
|
||||
|
||||
@@ -36,6 +37,11 @@ ASSETS = { # asset oid -> (type, {file_oid: (label, [revisions oldest first])})
|
||||
f"{PROJECT}/asset_types/main_props/assets/lamp": ("main_props", {}),
|
||||
f"{PROJECT}/asset_types/vehicles/assets/car": ("vehicles", {f"{PROJECT}/asset_types/vehicles/assets/car/tasks/rigging/files/car_blend": ("rigging/car.blend", [
|
||||
rev("v001", f"{ROOT}/lib/car/v001/car.blend", available=False)])}),
|
||||
f"{PROJECT}/asset_types/set_props/assets/chair": ("set_props", {
|
||||
f"{PROJECT}/asset_types/set_props/assets/chair/tasks/shading/files/chair_shading_blend": ("shading/chair_shading.blend", [
|
||||
rev("v001", f"{ROOT}/lib/chair/v001/chair.blend")]),
|
||||
f"{PROJECT}/asset_types/set_props/assets/chair/tasks/modeling/files/chair_blend": ("modeling/chair.blend", [
|
||||
rev("v001", f"{ROOT}/lib/chair/v001/chair.blend"), rev("v002", f"{ROOT}/lib/chair/v001/chair.blend", available=False)])}),
|
||||
}
|
||||
CASTING = [("bob", "main_character", 2), ("tree", "bg_set", 1), ("rock", "main_props", 1), ("lamp", "main_props", 1), ("car", "vehicles", 1), ("ghost", "main_character", 1)]
|
||||
CALLS = []
|
||||
@@ -100,6 +106,8 @@ def cmd(name, kw):
|
||||
a["error"] = "No published revision available on this site"
|
||||
out.append(a)
|
||||
return {"shot_oid": SHOT, "assets": out}
|
||||
if name == "wtd_get_asset_files":
|
||||
return [file_data(f) for f in ASSETS[kw["oid"]][1]]
|
||||
if name == "wtd_get_latest_revisions":
|
||||
res = {}
|
||||
for f in kw["file_oids"]:
|
||||
|
||||
@@ -6,6 +6,7 @@ bob: a rig (COL_bob: armature + meshes), v001 has 1 mesh, v002 has 2.
|
||||
tree: COL_tree holding only LOD collections (COL_tree_LOD0, COL_tree_LOD1), plus an
|
||||
unrelated top-level collection. v002 adds a mesh outside the LODs, v003 has no LOD.
|
||||
car: a rig, COL_car: armature + named LODs COL_car_LODlow, _LODhigh, _LODmid.
|
||||
chair: COL_chair (not cast, loaded with Load Publish).
|
||||
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).
|
||||
"""
|
||||
@@ -67,4 +68,5 @@ write(os.path.join(lib, "tree", "v002", "model.blend"), "COL_tree", meshes=1, ex
|
||||
write(os.path.join(lib, "tree", "v003", "model.blend"), "COL_tree", meshes=1, extra=True)
|
||||
write(os.path.join(lib, "rock", "v001", "rock.blend"), "rock_geo", node_group=True)
|
||||
write(os.path.join(lib, "car", "v001", "car.blend"), "COL_car", meshes=0, rig=True, lods=["low", "high", "mid"])
|
||||
write(os.path.join(lib, "chair", "v001", "chair.blend"), "COL_chair")
|
||||
print("Test assets written in", lib)
|
||||
+40
-11
@@ -100,14 +100,14 @@ print("Load")
|
||||
bpy.ops.wtd_scene.load()
|
||||
pump()
|
||||
state = loaded()
|
||||
check("bob: 2 instances of v002", state.get("bob") == ("v002", ["COL_bob", "COL_bob.001"]), str(state.get("bob")))
|
||||
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("bob: 2 numbered instances of v002", state.get("bob") == ("v002", ["COL_bob_01", "COL_bob_02"]), str(state.get("bob")))
|
||||
check("tree: 1 numbered collection instance", state.get("tree") == ("v001", ["tree_01"]), str(state.get("tree")))
|
||||
check("rock: single top-level collection fallback", state.get("rock") == ("v001", ["rock_geo_01"]))
|
||||
check("lamp and ghost reported", "lamp" in message() and "ghost" in message(), message())
|
||||
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),
|
||||
all(ob.override_library for ob in bpy.data.collections["COL_bob_01"].all_objects),
|
||||
)
|
||||
check("shot manager groups bob as character", "Characters" in shot_viewer.rig_groups(bpy.context.view_layer))
|
||||
bpy.ops.wtd_scene.load()
|
||||
@@ -149,7 +149,7 @@ for ob in bpy.context.view_layer.objects:
|
||||
ob.select_set(ob in (tree, bpy.data.objects["RIG_COL_car"]))
|
||||
check("LOD of selected", bpy.ops.wtd_scene.set_lod(lod="1", selected=True) == {"FINISHED"})
|
||||
check("only the selected instances having it switched", (lib.current_lod(tree_lib, tree), lib.current_lod(car_lib, car)) == ("1", "low"))
|
||||
check("missing LOD refused", bpy.ops.wtd_scene.set_lod(lod="5", library="tree", instance="tree") == {"CANCELLED"})
|
||||
check("missing LOD refused", bpy.ops.wtd_scene.set_lod(lod="5", library="tree", instance="tree_01") == {"CANCELLED"})
|
||||
check("LOD order: list, numbers, names", sorted(["b", "10", "low", "2", "a", "high"], key=lib.lod_order) == ["high", "low", "2", "10", "a", "b"])
|
||||
second = lib.add_instance(scene, bpy.context.view_layer, car_lib)
|
||||
check(
|
||||
@@ -161,7 +161,7 @@ lib.remove_instance(second)
|
||||
lib.update("tree", {"name": "v002", "path": os.path.join(WORKDIR, "lib", "tree", "v002", "model.blend")})
|
||||
tree = lib.instances(bpy.data.libraries["tree"])[0]
|
||||
check("LOD kept by an update", lib.current_lod(bpy.data.libraries["tree"], tree) == "1")
|
||||
check("content outside the LODs: instance refused", bpy.ops.wtd_scene.set_lod(lod="0", library="tree", instance="tree") == {"CANCELLED"})
|
||||
check("content outside the LODs: instance refused", bpy.ops.wtd_scene.set_lod(lod="0", library="tree", instance="tree_01") == {"CANCELLED"})
|
||||
lib.update("tree", {"name": "v003", "path": os.path.join(WORKDIR, "lib", "tree", "v003", "model.blend")})
|
||||
tree = lib.instances(bpy.data.libraries["tree"])[0]
|
||||
check("LODs gone: whole collection instanced", tree.instance_collection.name == "COL_tree" and not lib.lods(bpy.data.libraries["tree"], tree))
|
||||
@@ -207,11 +207,11 @@ check("picking a version not on this site requests it", message() == "Requested
|
||||
check("still on v002", loaded()["bob"][0] == "v002")
|
||||
|
||||
print("Remove")
|
||||
bpy.ops.wtd_scene.remove(instance="COL_bob.001")
|
||||
check("one instance removed", loaded()["bob"][1] == ["COL_bob"])
|
||||
bpy.ops.wtd_scene.remove(instance="COL_bob_02")
|
||||
check("one instance removed", loaded()["bob"][1] == ["COL_bob_01"])
|
||||
for ob in bpy.context.view_layer.objects:
|
||||
ob.select_set(False)
|
||||
bpy.data.objects["tree"].select_set(True)
|
||||
bpy.data.objects["tree_01"].select_set(True)
|
||||
bpy.ops.wtd_scene.remove(selected=True)
|
||||
check("selected instance removed", loaded()["tree"][1] == [])
|
||||
bpy.ops.wtd_scene.remove("EXEC_DEFAULT", library="rock")
|
||||
@@ -220,6 +220,35 @@ check("its indirect library removed too", set(bpy.data.libraries.keys()) == {"bo
|
||||
bpy.ops.wtd_scene.load()
|
||||
pump()
|
||||
check("load restores what is missing", {k: len(v[1]) for k, v in loaded().items()} == {"bob": 2, "car": 1, "rock": 1, "tree": 1})
|
||||
check("numbers go on after the highest one", loaded()["bob"][1] == ["COL_bob_01", "COL_bob_02"], str(loaded()["bob"][1]))
|
||||
|
||||
print("Load publish")
|
||||
CHAIR = "/melvile/asset_types/set_props/assets/chair"
|
||||
check("project of the context", sm.project_oid(scene) == "/melvile")
|
||||
sm.STATE.assets = ipc.call("wtd_list_assets", None, oid=sm.project_oid(scene))
|
||||
check("any asset listed, cast or not", CHAIR in [a["oid"] for a in sm.STATE.assets])
|
||||
sm.browse_asset(next(a for a in sm.STATE.assets if a["oid"] == CHAIR))
|
||||
chair_file = sm.default_file("set_props", sm.STATE.browse["files"])
|
||||
check("default file follows the file patterns", chair_file["label"] == "modeling/chair.blend", chair_file["label"])
|
||||
check("versions newest first", [r["name"] for r in chair_file["revisions"]] == ["v002", "v001"])
|
||||
check("default version: latest on this site", sm.default_revision(chair_file)["name"] == "v001")
|
||||
result = bpy.ops.wtd_scene.load_publish("EXEC_DEFAULT", file=chair_file["file_oid"], revision="v002")
|
||||
pump()
|
||||
check(
|
||||
"version not on this site: requested, not loaded",
|
||||
result == {"CANCELLED"} and "chair" not in loaded()
|
||||
and ["wtd_request_revisions", {"revision_oids": [chair_file["file_oid"] + "/history/revisions/v002"]}] in fake("/_test/calls", {}),
|
||||
)
|
||||
bpy.ops.wtd_scene.load_publish("EXEC_DEFAULT", file=chair_file["file_oid"], revision="v001", count=2)
|
||||
check("loaded twice, numbered", loaded().get("chair") == ("v001", ["chair_01", "chair_02"]), str(loaded().get("chair")))
|
||||
check("its versions followed in Loaded", sm.version_status(bpy.data.libraries["chair"])[0] == "PENDING")
|
||||
sm.browse_asset(next(a for a in sm.STATE.assets if a["asset"] == "bob"))
|
||||
check(
|
||||
"another version of a loaded file refused",
|
||||
bpy.ops.wtd_scene.load_publish("EXEC_DEFAULT", file=BOB_FILE, revision="v004") == {"CANCELLED"} and loaded()["bob"][0] == "v002",
|
||||
)
|
||||
bpy.ops.wtd_scene.load_publish("EXEC_DEFAULT", file=BOB_FILE, revision="v002")
|
||||
check("same version: one more numbered instance", loaded()["bob"][1] == ["COL_bob_01", "COL_bob_02", "COL_bob_03"], str(loaded()["bob"][1]))
|
||||
|
||||
print("Ambiguous file")
|
||||
try:
|
||||
@@ -243,7 +272,7 @@ bpy.ops.wtd_scene.assetize(
|
||||
)
|
||||
pump()
|
||||
check("published and replaced", "replaced" in message(), message())
|
||||
check("lamp linked", loaded().get("lamp") == ("v001", ["COL_lamp"]), str(loaded().get("lamp")))
|
||||
check("lamp linked", loaded().get("lamp") == ("v001", ["COL_lamp_01"]), str(loaded().get("lamp")))
|
||||
check("local collection kept, renamed", "COL_lamp_local" in bpy.data.collections)
|
||||
bad = bpy.data.collections.new("COL_bad")
|
||||
scene.collection.children.link(bad)
|
||||
@@ -256,7 +285,7 @@ path = os.path.join(WORKDIR, "scene_manager_test.blend")
|
||||
bpy.ops.wm.save_as_mainfile(filepath=path)
|
||||
bpy.ops.wm.open_mainfile(filepath=path)
|
||||
sm._on_load_post()
|
||||
check("everything found after reopening", set(loaded()) == {"bob", "car", "lamp", "rock", "tree"})
|
||||
check("everything found after reopening", set(loaded()) == {"bob", "car", "chair", "lamp", "rock", "tree"})
|
||||
car_lib = bpy.data.libraries["car"]
|
||||
check("LOD kept after reopening", lib.current_lod(car_lib, lib.instances(car_lib)[0]) == "low")
|
||||
sm.connect(bpy.context.scene)
|
||||
|
||||
Reference in new issue
Block a user