Scene Manager: request publishes missing on this site
- Request (globe) on casting rows and loaded assets whose latest publish is not on this site, and Request Missing for all of them, through the new wtdcmds command wtd_request_revisions. Requested publishes show "requested, waiting for the download". - The version picker requests a version that is not on this site. - A 404 explains that the session is gone or wtdcmds is too old. - Fake server: car (vehicles) with a publish not on this site, wtd_request_revisions and /_test/download; 45 checks. - Tested live: car_paul modeling.blend v001 -> Requested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
9ee7710479
commit
9fe38ce58a
8 files changed
+197
-19
No files matched your search
+29
-3
@@ -3,11 +3,12 @@
|
||||
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) 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). 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).
|
||||
|
||||
Test-only endpoints: POST /_test/publish {file_oid, name, available, path},
|
||||
POST /_test/download {revision_oid} (a requested revision reaches this site),
|
||||
POST /_test/sessions {uid: session}, POST /_test/calls (commands received).
|
||||
State lives in memory: restart the server for a clean state.
|
||||
"""
|
||||
@@ -33,10 +34,22 @@ ASSETS = { # asset oid -> (type, {file_oid: (label, [revisions oldest first])})
|
||||
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/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)])}),
|
||||
}
|
||||
CASTING = [("bob", "main_character", 2), ("tree", "bg_set", 1), ("rock", "main_props", 1), ("lamp", "main_props", 1), ("ghost", "main_character", 1)]
|
||||
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 = []
|
||||
|
||||
def set_oids():
|
||||
for _a, (_t, files) in ASSETS.items():
|
||||
for f, (_l, revs) in files.items():
|
||||
for r in revs: r["oid"] = f"{f}/history/revisions/{r['name']}"
|
||||
|
||||
def find_rev(oid):
|
||||
return next((r for _a, (_t, files) in ASSETS.items() for _l, revs in files.values() for r in revs if r["oid"] == oid), None)
|
||||
|
||||
set_oids()
|
||||
|
||||
def file_data(file_oid):
|
||||
for asset_oid, (atype, files) in ASSETS.items():
|
||||
if file_oid in files:
|
||||
@@ -98,6 +111,15 @@ def cmd(name, kw):
|
||||
if kw["file_oid"] in files:
|
||||
return list(reversed(files[kw["file_oid"]][1]))
|
||||
raise ValueError("No file")
|
||||
if name == "wtd_request_revisions":
|
||||
res = {}
|
||||
for oid in kw["revision_oids"]:
|
||||
r = find_rev(oid)
|
||||
if r is None:
|
||||
res[oid] = {"oid": oid, "error": f"Could not find {oid}"}; continue
|
||||
if not r["available"]: r["sync_status"] = "Requested"
|
||||
res[oid] = r
|
||||
return res
|
||||
if name == "wtd_assetize":
|
||||
src = kw["source_path"]
|
||||
if not os.path.isfile(src): raise ValueError(f"Source file {src} does not exist")
|
||||
@@ -115,7 +137,7 @@ def cmd(name, kw):
|
||||
label, revs = files.setdefault(file_oid, (f"{task}/{fname}.blend", []))
|
||||
name = f"v{len(revs) + 1:03d}"
|
||||
dest = f"{ROOT}/lib/{aname}/{name}/{fname}.blend"; os.makedirs(os.path.dirname(dest), exist_ok=True); shutil.copy(src, dest)
|
||||
revs.append(rev(name, dest, comment=kw.get("comment", "")))
|
||||
revs.append(rev(name, dest, comment=kw.get("comment", ""))); set_oids()
|
||||
return {"asset_oid": asset_oid, "asset_created": created, **file_data(file_oid)}
|
||||
raise KeyError(name)
|
||||
|
||||
@@ -139,6 +161,10 @@ class Handler(BaseHTTPRequestHandler):
|
||||
f = body["file_oid"]
|
||||
for _a, (_t, files) in ASSETS.items():
|
||||
if f in files: files[f][1].append(rev(body["name"], body.get("path", "/nowhere/" + body["name"]), body.get("available", True)))
|
||||
set_oids()
|
||||
return self.reply(200, {"result": True})
|
||||
if url.path == "/_test/download":
|
||||
r = find_rev(body["revision_oid"]); r["available"] = True; r["sync_status"] = "Available"
|
||||
return self.reply(200, {"result": True})
|
||||
if url.path == "/_test/sessions":
|
||||
SESSIONS.clear(); SESSIONS.update(body); return self.reply(200, {"result": True})
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
bob: a rig (COL_bob: armature + meshes), v001 has 1 mesh, v002 has 2.
|
||||
tree: COL_tree, plus an unrelated top-level collection.
|
||||
car: COL_car.
|
||||
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).
|
||||
"""
|
||||
@@ -56,4 +57,5 @@ write(os.path.join(lib, "bob", "v001", "rig.blend"), "COL_bob", meshes=1, rig=Tr
|
||||
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", node_group=True)
|
||||
write(os.path.join(lib, "car", "v001", "car.blend"), "COL_car")
|
||||
print("Test assets written in", lib)
|
||||
@@ -33,6 +33,8 @@ 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/main_character/assets/bob/tasks/rigging/files/rig_blend"
|
||||
CAR = "/melvile/asset_types/vehicles/assets/car"
|
||||
CAR_FILE = CAR + "/tasks/rigging/files/car_blend"
|
||||
FAILED = []
|
||||
|
||||
|
||||
@@ -92,7 +94,7 @@ scene.wtd_scene_manager.context_oid = SHOT
|
||||
pump()
|
||||
check("shot context validated", sm.context_label(SHOT) == ("shot: f01 / s010 / p010", True))
|
||||
errors = {a["name"]: a["error"] for a in sm.STATE.casting.get(SHOT, {}).get("assets", [])}
|
||||
check("casting read", set(errors) == {"bob", "tree", "rock", "lamp", "ghost"}, str(errors))
|
||||
check("casting read", set(errors) == {"bob", "tree", "rock", "lamp", "car", "ghost"}, str(errors))
|
||||
|
||||
print("Load")
|
||||
bpy.ops.wtd_scene.load()
|
||||
@@ -112,12 +114,31 @@ bpy.ops.wtd_scene.load()
|
||||
pump()
|
||||
check("load again adds nothing", message().startswith("Nothing new to load"), message())
|
||||
|
||||
print("Request")
|
||||
check("car can be requested", sm.missing_files(scene) == [CAR_FILE], str(sm.missing_files(scene)))
|
||||
bpy.ops.wtd_scene.request()
|
||||
pump()
|
||||
check("car v001 requested", message() == "Requested car v001", message())
|
||||
check("nothing left to request", sm.missing_files(scene) == [])
|
||||
calls = fake("/_test/calls", {})
|
||||
check("request sent to Libreflow", ["wtd_request_revisions", {"revision_oids": [CAR_FILE + "/history/revisions/v001"]}] in calls)
|
||||
fake("/_test/download", {"revision_oid": CAR_FILE + "/history/revisions/v001"})
|
||||
bpy.ops.wtd_scene.refresh()
|
||||
pump()
|
||||
bpy.ops.wtd_scene.load(asset_oid=CAR)
|
||||
pump()
|
||||
check("car loaded once downloaded", loaded().get("car", (None,))[0] == "v001", message())
|
||||
|
||||
print("Versions")
|
||||
check("bob is the latest", sm.version_status(bpy.data.libraries["bob"])[0] == "LATEST")
|
||||
fake("/_test/publish", {"file_oid": BOB_FILE, "name": "v003", "available": False})
|
||||
sm.refresh_versions()
|
||||
pump()
|
||||
check("v003 not on this site: pending", sm.version_status(bpy.data.libraries["bob"])[0] == "PENDING")
|
||||
bpy.ops.wtd_scene.request(file_oid=BOB_FILE)
|
||||
pump()
|
||||
status = sm.version_status(bpy.data.libraries["bob"])
|
||||
check("v003 requested", status == ("PENDING", "v003 requested, waiting for the download"), str(status))
|
||||
fake(
|
||||
"/_test/publish",
|
||||
{"file_oid": BOB_FILE, "name": "v004", "available": True, "path": os.path.join(WORKDIR, "lib", "bob", "v001", "rig.blend")},
|
||||
@@ -137,6 +158,13 @@ sm._REVISION_ITEMS[:] = [(r["name"], r["name"], "", "NONE", i) for i, r in enume
|
||||
bpy.ops.wtd_scene.pick_revision("EXEC_DEFAULT", library="bob", revision="v002")
|
||||
check("rolled back to v002", loaded()["bob"][0] == "v002")
|
||||
check("unavailable v003 refused", bpy.ops.wtd_scene.pick_revision("EXEC_DEFAULT", library="bob", revision="v003") == {"CANCELLED"})
|
||||
fake("/_test/publish", {"file_oid": BOB_FILE, "name": "v005", "available": False})
|
||||
sm.STATE.revisions[BOB_FILE] = ipc.call("wtd_get_revisions", None, file_oid=BOB_FILE)
|
||||
sm._REVISION_ITEMS[:] = [(r["name"], r["name"], "", "NONE", i) for i, r in enumerate(sm.STATE.revisions[BOB_FILE])]
|
||||
bpy.ops.wtd_scene.pick_revision("EXEC_DEFAULT", library="bob", revision="v005")
|
||||
pump()
|
||||
check("picking a version not on this site requests it", message() == "Requested bob v005", message())
|
||||
check("still on v002", loaded()["bob"][0] == "v002")
|
||||
|
||||
print("Remove")
|
||||
bpy.ops.wtd_scene.remove(instance="COL_bob.001")
|
||||
@@ -148,10 +176,10 @@ 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()))
|
||||
check("its indirect library removed too", set(bpy.data.libraries.keys()) == {"bob", "car", "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})
|
||||
check("load restores what is missing", {k: len(v[1]) for k, v in loaded().items()} == {"bob": 2, "car": 1, "rock": 1, "tree": 1})
|
||||
|
||||
print("Ambiguous file")
|
||||
try:
|
||||
@@ -188,7 +216,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", "lamp", "rock", "tree"})
|
||||
check("everything found after reopening", set(loaded()) == {"bob", "car", "lamp", "rock", "tree"})
|
||||
sm.connect(bpy.context.scene)
|
||||
pump()
|
||||
check("versions checked again", sm.version_status(bpy.data.libraries["bob"])[0] == "UPDATE")
|
||||
|
||||
Reference in new issue
Block a user