Complete M16 asset operator parity chain
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-23 18:10:00 -04:00
parent c0d471da01
commit 5c8cfd1a8c
127 changed files with 5574 additions and 47 deletions

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
def library_state():
libraries = list(bpy.data.libraries)
if len(libraries) != 1:
raise RuntimeError(f"expected one asset-clear library, found {len(libraries)}")
library = libraries[0]
return {"name": library.name, "filepath": library.filepath, "packed": library.packed_file is not None}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --factory-startup --python check-action-asset-clear-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = library_state()
accepted_errors = (
"context is incorrect",
"Data-block is not marked as asset",
"No data-block selected that is marked as asset",
"No asset data-blocks selected",
)
try:
poll = bool(bpy.ops.asset.clear.poll())
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
poll = False
if poll:
raise RuntimeError("asset.clear unexpectedly polled true")
try:
bpy.ops.asset.clear(set_fake_user=False)
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.clear unexpectedly finished")
after_cancel = library_state()
if after_cancel != before:
raise RuntimeError(f"asset.clear cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-asset-clear-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = library_state()
if after != before:
raise RuntimeError(f"asset.clear save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00275",
"operation": "ASSET_CLEAR_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-clear-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-clear-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
IMAGE_NAME = "WebGapAssetDownloadImage"
def image_state():
image = bpy.data.images.get(IMAGE_NAME)
if image is None:
raise RuntimeError("asset download fixture image is missing")
return {
"name": image.name,
"filepath": image.filepath,
"source": image.source,
"packed": image.packed_file is not None,
"size": [int(image.generated_width), int(image.generated_height)],
}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-asset-download-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
bpy.context.preferences.system.use_online_access = True
before = image_state()
poll = bool(bpy.ops.asset.asset_download.poll())
try:
bpy.ops.asset.asset_download(relative_asset_identifier="WebGapAssetDownload")
except RuntimeError as error:
if "Asset could not be found" not in str(error):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.asset_download unexpectedly finished without a remote asset")
after_cancel = image_state()
if after_cancel != before:
raise RuntimeError(f"asset.asset_download cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-asset-download-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = image_state()
if after != before:
raise RuntimeError(f"asset.asset_download save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00264",
"operation": "ASSET_ASSET_DOWNLOAD_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-asset-download-desktop-ok status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-asset-download-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
IMAGE_NAME = "WebGapAssetsDownloadImage"
def image_state():
image = bpy.data.images.get(IMAGE_NAME)
if image is None:
raise RuntimeError("assets download fixture image is missing")
return {
"name": image.name,
"filepath": image.filepath,
"source": image.source,
"packed": image.packed_file is not None,
"size": [int(image.generated_width), int(image.generated_height)],
}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-assets-download-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
bpy.context.preferences.system.use_online_access = True
before = image_state()
poll = bool(bpy.ops.asset.assets_download.poll())
if poll:
raise RuntimeError("asset.assets_download unexpectedly polled true without selected asset")
try:
bpy.ops.asset.assets_download()
except RuntimeError as error:
if "No asset selected or active" not in str(error):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.assets_download unexpectedly finished without selected asset")
after_cancel = image_state()
if after_cancel != before:
raise RuntimeError(f"asset.assets_download cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-assets-download-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = image_state()
if after != before:
raise RuntimeError(f"asset.assets_download save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00265",
"operation": "ASSET_ASSETS_DOWNLOAD_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-assets-download-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-assets-download-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
ACTION_NAME = "WebGapAssignAction"
OBJECT_NAME = "WebGapAssignActionObject"
def action_state():
obj = bpy.data.objects.get(OBJECT_NAME)
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("assign_action fixture action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-assign-action-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_state()
try:
poll = bool(bpy.ops.asset.assign_action.poll())
except RuntimeError as error:
if "context is incorrect" not in str(error):
raise
poll = False
if poll:
raise RuntimeError("asset.assign_action unexpectedly polled true without selected asset")
try:
bpy.ops.asset.assign_action()
except RuntimeError as error:
if "No asset selected or active" not in str(error) and "context is incorrect" not in str(error):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.assign_action unexpectedly finished without selected asset")
after_cancel = action_state()
if after_cancel != before:
raise RuntimeError(f"asset.assign_action cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-assign-action-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = action_state()
if after != before:
raise RuntimeError(f"asset.assign_action save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00266",
"operation": "ASSET_ASSIGN_ACTION_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-assign-action-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-assign-action-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
def library_state():
libraries = list(bpy.data.libraries)
if len(libraries) != 1:
raise RuntimeError(f"expected one browse-containing library, found {len(libraries)}")
library = libraries[0]
return {"name": library.name, "filepath": library.filepath, "packed": library.packed_file is not None}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-browse-containing-blend-file-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = library_state()
try:
poll = bool(bpy.ops.asset.browse_containing_blend_file.poll())
except RuntimeError as error:
if "context is incorrect" not in str(error) and "No asset selected" not in str(error):
raise
poll = False
if poll:
raise RuntimeError("asset.browse_containing_blend_file unexpectedly polled true")
try:
bpy.ops.asset.browse_containing_blend_file()
except RuntimeError as error:
if "context is incorrect" not in str(error) and "No asset selected" not in str(error):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.browse_containing_blend_file unexpectedly finished")
after_cancel = library_state()
if after_cancel != before:
raise RuntimeError(f"browse-containing cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-browse-containing-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = library_state()
if after != before:
raise RuntimeError(f"browse-containing save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00267",
"operation": "ASSET_BROWSE_CONTAINING_BLEND_FILE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-browse-containing-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-browse-containing-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,76 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
def library_state():
libraries = list(bpy.data.libraries)
if len(libraries) != 1:
raise RuntimeError(f"expected one bundle-install library, found {len(libraries)}")
library = libraries[0]
return {"name": library.name, "filepath": library.filepath, "packed": library.packed_file is not None}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-bundle-install-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = library_state()
try:
poll = bool(bpy.ops.asset.bundle_install.poll())
except RuntimeError as error:
if not any(token in str(error) for token in ("context is incorrect", "No asset selected", "No asset library")):
raise
poll = False
if poll:
raise RuntimeError("asset.bundle_install unexpectedly polled true")
try:
bpy.ops.asset.bundle_install()
except RuntimeError as error:
if not any(token in str(error) for token in ("context is incorrect", "No asset selected", "No asset library")):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.bundle_install unexpectedly finished")
after_cancel = library_state()
if after_cancel != before:
raise RuntimeError(f"bundle-install cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-bundle-install-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = library_state()
if after != before:
raise RuntimeError(f"bundle-install save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00268",
"operation": "ASSET_BUNDLE_INSTALL_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-bundle-install-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-bundle-install-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
def library_state():
libraries = list(bpy.data.libraries)
if len(libraries) != 1:
raise RuntimeError(f"expected one catalog-delete library, found {len(libraries)}")
library = libraries[0]
return {"name": library.name, "filepath": library.filepath, "packed": library.packed_file is not None}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-catalog-delete-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = library_state()
accepted_errors = ("context is incorrect", "No asset selected", "No asset library", "Catalog")
try:
poll = bool(bpy.ops.asset.catalog_delete.poll())
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
poll = False
if poll:
raise RuntimeError("asset.catalog_delete unexpectedly polled true")
try:
bpy.ops.asset.catalog_delete()
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.catalog_delete unexpectedly finished")
after_cancel = library_state()
if after_cancel != before:
raise RuntimeError(f"catalog-delete cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-catalog-delete-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = library_state()
if after != before:
raise RuntimeError(f"catalog-delete save/reopen drift: {before} != {after}")
report = {"schemaVersion": 1, "task": "M16-GAP-00269", "operation": "ASSET_CATALOG_DELETE_DESKTOP", "fixture": str(fixture), "fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(), "before": before, "after": after, "poll": poll, "operatorStatus": operator_status, "mainMutation": "NONE", "saveReopen": "EXACT", "blenderVersion": bpy.app.version_string}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-catalog-delete-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-catalog-delete-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
def library_state():
libraries = list(bpy.data.libraries)
if len(libraries) != 1:
raise RuntimeError(f"expected one catalog-new library, found {len(libraries)}")
library = libraries[0]
return {"name": library.name, "filepath": library.filepath, "packed": library.packed_file is not None}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-catalog-new-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = library_state()
accepted_errors = ("context is incorrect", "No asset selected", "No asset library", "Catalog")
try:
poll = bool(bpy.ops.asset.catalog_new.poll())
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
poll = False
if poll:
raise RuntimeError("asset.catalog_new unexpectedly polled true")
try:
bpy.ops.asset.catalog_new()
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.catalog_new unexpectedly finished")
after_cancel = library_state()
if after_cancel != before:
raise RuntimeError(f"catalog-new cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-catalog-new-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = library_state()
if after != before:
raise RuntimeError(f"catalog-new save/reopen drift: {before} != {after}")
report = {"schemaVersion": 1, "task": "M16-GAP-00270", "operation": "ASSET_CATALOG_NEW_DESKTOP", "fixture": str(fixture), "fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(), "before": before, "after": after, "poll": poll, "operatorStatus": operator_status, "mainMutation": "NONE", "saveReopen": "EXACT", "blenderVersion": bpy.app.version_string}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-catalog-new-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-catalog-new-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,33 @@
#!/usr/bin/env python3
import hashlib, json, pathlib, sys, tempfile, bpy
def state():
if len(bpy.data.libraries) != 1: raise RuntimeError("catalog-redo library missing")
lib = bpy.data.libraries[0]; return {"name": lib.name, "filepath": lib.filepath, "packed": lib.packed_file is not None}
def main():
args = sys.argv[sys.argv.index("--") + 1:]
if len(args) != 2: raise SystemExit("usage")
fixture, output = (pathlib.Path(v).resolve() for v in args); bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False); before = state()
accepted = ("context is incorrect", "No asset selected", "No asset library", "Catalog")
try: poll = bool(bpy.ops.asset.catalog_redo.poll())
except RuntimeError as e:
if not any(x in str(e) for x in accepted): raise
poll = False
if poll: raise RuntimeError("catalog_redo poll unexpectedly true")
try: bpy.ops.asset.catalog_redo()
except RuntimeError as e:
if not any(x in str(e) for x in accepted): raise
status = "CANCELLED"
else: raise RuntimeError("catalog_redo unexpectedly finished")
if state() != before: raise RuntimeError("catalog_redo mutated Main")
with tempfile.NamedTemporaryFile(prefix="m16-catalog-redo-reopen-", suffix=".blend", dir=fixture.parent) as t:
bpy.ops.wm.save_as_mainfile(filepath=t.name, check_existing=False, compress=True); bpy.ops.wm.open_mainfile(filepath=t.name, load_ui=False); after = state()
if after != before: raise RuntimeError("catalog_redo save/reopen drift")
report = {"schemaVersion":1,"task":"M16-GAP-00271","operation":"ASSET_CATALOG_REDO_DESKTOP","fixture":str(fixture),"fixtureSha256":hashlib.sha256(fixture.read_bytes()).hexdigest(),"before":before,"after":after,"poll":poll,"operatorStatus":status,"mainMutation":"NONE","saveReopen":"EXACT","blenderVersion":bpy.app.version_string}
output.parent.mkdir(parents=True, exist_ok=True); output.write_text(json.dumps(report, indent=2, sort_keys=True)+"\n")
print("asset-catalog-redo-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try: main()
except Exception as e: print(f"asset-catalog-redo-desktop-failed: {e}"); raise SystemExit(1)

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import hashlib,json,pathlib,sys,tempfile,bpy
def state():
if len(bpy.data.libraries)!=1: raise RuntimeError("catalog-undo library missing")
lib=bpy.data.libraries[0]; return {"name":lib.name,"filepath":lib.filepath,"packed":lib.packed_file is not None}
def main():
args=sys.argv[sys.argv.index("--")+1:]
if len(args)!=2: raise SystemExit("usage")
fixture,output=(pathlib.Path(v).resolve() for v in args); bpy.ops.wm.open_mainfile(filepath=str(fixture),load_ui=False); before=state(); accepted=("context is incorrect","No asset selected","No asset library","Catalog")
try: poll=bool(bpy.ops.asset.catalog_undo.poll())
except RuntimeError as e:
if not any(x in str(e) for x in accepted): raise
poll=False
if poll: raise RuntimeError("catalog_undo poll unexpectedly true")
try: bpy.ops.asset.catalog_undo()
except RuntimeError as e:
if not any(x in str(e) for x in accepted): raise
status="CANCELLED"
else: raise RuntimeError("catalog_undo unexpectedly finished")
if state()!=before: raise RuntimeError("catalog_undo mutated Main")
with tempfile.NamedTemporaryFile(prefix="m16-catalog-undo-reopen-",suffix=".blend",dir=fixture.parent) as t:
bpy.ops.wm.save_as_mainfile(filepath=t.name,check_existing=False,compress=True); bpy.ops.wm.open_mainfile(filepath=t.name,load_ui=False); after=state()
if after!=before: raise RuntimeError("catalog_undo save/reopen drift")
report={"schemaVersion":1,"task":"M16-GAP-00272","operation":"ASSET_CATALOG_UNDO_DESKTOP","fixture":str(fixture),"fixtureSha256":hashlib.sha256(fixture.read_bytes()).hexdigest(),"before":before,"after":after,"poll":poll,"operatorStatus":status,"mainMutation":"NONE","saveReopen":"EXACT","blenderVersion":bpy.app.version_string}; output.parent.mkdir(parents=True,exist_ok=True); output.write_text(json.dumps(report,indent=2,sort_keys=True)+"\n"); print("asset-catalog-undo-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__=="__main__":
try: main()
except Exception as e: print(f"asset-catalog-undo-desktop-failed: {e}"); raise SystemExit(1)

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import hashlib,json,pathlib,sys,tempfile,bpy
def state():
if len(bpy.data.libraries)!=1: raise RuntimeError("catalog-undo-push library missing")
lib=bpy.data.libraries[0]; return {"name":lib.name,"filepath":lib.filepath,"packed":lib.packed_file is not None}
def main():
args=sys.argv[sys.argv.index("--")+1:]
if len(args)!=2: raise SystemExit("usage")
fixture,output=(pathlib.Path(v).resolve() for v in args); bpy.ops.wm.open_mainfile(filepath=str(fixture),load_ui=False); before=state(); accepted=("context is incorrect","No asset selected","No asset library","Catalog")
try: poll=bool(bpy.ops.asset.catalog_undo_push.poll())
except RuntimeError as e:
if not any(x in str(e) for x in accepted): raise
poll=False
if poll: raise RuntimeError("catalog_undo_push poll unexpectedly true")
try: bpy.ops.asset.catalog_undo_push()
except RuntimeError as e:
if not any(x in str(e) for x in accepted): raise
status="CANCELLED"
else: raise RuntimeError("catalog_undo_push unexpectedly finished")
if state()!=before: raise RuntimeError("catalog_undo_push mutated Main")
with tempfile.NamedTemporaryFile(prefix="m16-catalog-undo-push-reopen-",suffix=".blend",dir=fixture.parent) as t:
bpy.ops.wm.save_as_mainfile(filepath=t.name,check_existing=False,compress=True); bpy.ops.wm.open_mainfile(filepath=t.name,load_ui=False); after=state()
if after!=before: raise RuntimeError("catalog_undo_push save/reopen drift")
report={"schemaVersion":1,"task":"M16-GAP-00273","operation":"ASSET_CATALOG_UNDO_PUSH_DESKTOP","fixture":str(fixture),"fixtureSha256":hashlib.sha256(fixture.read_bytes()).hexdigest(),"before":before,"after":after,"poll":poll,"operatorStatus":status,"mainMutation":"NONE","saveReopen":"EXACT","blenderVersion":bpy.app.version_string}; output.parent.mkdir(parents=True,exist_ok=True); output.write_text(json.dumps(report,indent=2,sort_keys=True)+"\n"); print("asset-catalog-undo-push-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__=="__main__":
try: main()
except Exception as e: print(f"asset-catalog-undo-push-desktop-failed: {e}"); raise SystemExit(1)

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import tempfile
import bpy
def library_state():
libraries = list(bpy.data.libraries)
if len(libraries) != 1:
raise RuntimeError(f"expected one catalogs-save library, found {len(libraries)}")
library = libraries[0]
return {"name": library.name, "filepath": library.filepath, "packed": library.packed_file is not None}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --factory-startup --python check-action-catalogs-save-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = library_state()
accepted_errors = ("context is incorrect", "No asset selected", "No asset library", "Catalog")
try:
poll = bool(bpy.ops.asset.catalogs_save.poll())
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
poll = False
if poll:
raise RuntimeError("asset.catalogs_save unexpectedly polled true")
try:
bpy.ops.asset.catalogs_save()
except RuntimeError as error:
if not any(token in str(error) for token in accepted_errors):
raise
operator_status = "CANCELLED"
else:
raise RuntimeError("asset.catalogs_save unexpectedly finished")
after_cancel = library_state()
if after_cancel != before:
raise RuntimeError(f"catalogs-save cancellation changed Main data: {before} != {after_cancel}")
with tempfile.NamedTemporaryFile(prefix="m16-catalogs-save-reopen-", suffix=".blend", dir=fixture.parent) as temporary:
bpy.ops.wm.save_as_mainfile(filepath=temporary.name, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary.name, load_ui=False)
after = library_state()
if after != before:
raise RuntimeError(f"catalogs-save save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00274",
"operation": "ASSET_CATALOGS_SAVE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"before": before,
"after": after,
"poll": poll,
"operatorStatus": operator_status,
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("asset-catalogs-save-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"asset-catalogs-save-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -7815,6 +7815,387 @@ if (task === "M16-GAP-00263") {
process.stdout.write(`generated-gap-ok task=${task} armature=${armature.id} mirrored=1 direction=negative_x desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00264") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00264-operator-asset.asset_download.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00264/asset-download-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-asset-download-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, true);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
const image = before.images?.find((value) => value.id === "image:WebGapAssetDownloadImage");
assert.ok(image);
assert.equal(image.name, "WebGapAssetDownloadImage");
assert.equal(image.assetId, image.id);
assert.equal(image.sourcePath, "//assets/WebGapAssetDownload.png");
assert.equal(image.assetStatus, "EXTERNAL");
assert.equal(image.packed, false);
assert.deepEqual([image.width, image.height], [1, 1]);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
const after = snapshot(engine, reopened).images?.find((value) => value.id === image.id);
assert.deepEqual(after, image);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).images?.find((value) => value.id === image.id), image);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_ASSET_DOWNLOAD_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", imageId: image.id, sourcePath: image.sourcePath, assetStatus: image.assetStatus, packed: image.packed, dimensions: [image.width, image.height] }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00265" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00264/asset-download-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} image=${image.id} assetStatus=${image.assetStatus} operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00265") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00265-operator-asset.assets_download.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00265/assets-download-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-assets-download-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
const image = before.images?.find((value) => value.id === "image:WebGapAssetsDownloadImage");
assert.ok(image);
assert.equal(image.name, "WebGapAssetsDownloadImage");
assert.equal(image.assetId, image.id);
assert.equal(image.sourcePath, "//assets/WebGapAssetsDownload.png");
assert.equal(image.assetStatus, "EXTERNAL");
assert.equal(image.packed, false);
assert.deepEqual([image.width, image.height], [1, 1]);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
const after = snapshot(engine, reopened).images?.find((value) => value.id === image.id);
assert.deepEqual(after, image);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).images?.find((value) => value.id === image.id), image);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_ASSETS_DOWNLOAD_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", imageId: image.id, sourcePath: image.sourcePath, assetStatus: image.assetStatus, packed: image.packed, dimensions: [image.width, image.height] }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00266" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00265/assets-download-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} image=${image.id} assetStatus=${image.assetStatus} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00266") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00266-operator-asset.assign_action.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00266/assign-action-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-assign-action-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
const animation = before.animations?.find((value) => value.id === "action:WebGapAssignAction:object:WebGapAssignActionObject");
assert.ok(animation);
assert.equal(animation.channels.length, desktop.before.channels.length);
for (const [index, channel] of animation.channels.entries()) {
const expected = desktop.before.channels[index];
assert.equal(channel.path.replace(/\[\d+\]$/, ""), expected.path);
assert.deepEqual(channel.keyframes.map((value) => value.frame), expected.frames);
}
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
assert.deepEqual(snapshot(engine, reopened).animations, before.animations);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).animations, before.animations);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_ASSIGN_ACTION_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", actionId: animation.id, targetId: animation.targetId, channelPaths: animation.channels.map((channel) => channel.path), keyframesPerChannel: animation.channels.map((channel) => channel.keyframes.length) }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00267" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00266/assign-action-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} action=${animation.id} channels=${animation.channels.length} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00267") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00267-operator-asset.browse_containing_blend_file.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00267/browse-containing-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-browse-containing-blend-file-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
assert.equal(before.libraryStatus, "AVAILABLE");
assert.ok(Array.isArray(before.libraries));
assert.equal(before.libraries.length, 1);
const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`);
assert.equal(library.name, desktop.before.name);
assert.equal(library.sourcePath, "//WebGapBrowseContaining.blend");
assert.equal(library.packed, false);
assert.equal(library.status, "EXTERNAL_REQUIRED");
assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED");
assert.deepEqual(library.dependencyIds, []);
assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
const after = snapshot(engine, reopened);
assert.deepEqual(after.libraries, before.libraries);
assert.equal(after.libraryStatus, before.libraryStatus);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).libraries, before.libraries);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_BROWSE_CONTAINING_BLEND_FILE_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00268" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00267/browse-containing-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00268") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00268-operator-asset.bundle_install.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00268/bundle-install-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-bundle-install-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
assert.equal(before.libraryStatus, "AVAILABLE");
assert.ok(Array.isArray(before.libraries));
assert.equal(before.libraries.length, 1);
const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`);
assert.equal(library.name, desktop.before.name);
assert.equal(library.sourcePath, "//WebGapBundleInstall.blend");
assert.equal(library.packed, false);
assert.equal(library.status, "EXTERNAL_REQUIRED");
assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED");
assert.deepEqual(library.dependencyIds, []);
assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
const after = snapshot(engine, reopened);
assert.deepEqual(after.libraries, before.libraries);
assert.equal(after.libraryStatus, before.libraryStatus);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).libraries, before.libraries);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_BUNDLE_INSTALL_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00269" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00268/bundle-install-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00269") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00269-operator-asset.catalog_delete.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00269/catalog-delete-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-catalog-delete-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
assert.equal(before.libraryStatus, "AVAILABLE");
assert.ok(Array.isArray(before.libraries));
assert.equal(before.libraries.length, 1);
const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`);
assert.equal(library.name, desktop.before.name);
assert.equal(library.sourcePath, "//WebGapCatalogDelete.blend");
assert.equal(library.packed, false);
assert.equal(library.status, "EXTERNAL_REQUIRED");
assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED");
assert.deepEqual(library.dependencyIds, []);
assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
const after = snapshot(engine, reopened);
assert.deepEqual(after.libraries, before.libraries);
assert.equal(after.libraryStatus, before.libraryStatus);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).libraries, before.libraries);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CATALOG_DELETE_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00270" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00269/catalog-delete-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00270") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00270-operator-asset.catalog_new.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00270/catalog-new-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-catalog-new-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.equal(desktop.mainMutation, "NONE");
assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
assert.equal(before.libraryStatus, "AVAILABLE");
assert.ok(Array.isArray(before.libraries));
assert.equal(before.libraries.length, 1);
const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`);
assert.equal(library.name, desktop.before.name);
assert.equal(library.sourcePath, "//WebGapCatalogNew.blend");
assert.equal(library.packed, false);
assert.equal(library.status, "EXTERNAL_REQUIRED");
assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED");
assert.deepEqual(library.dependencyIds, []);
assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
const after = snapshot(engine, reopened);
assert.deepEqual(after.libraries, before.libraries);
assert.equal(after.libraryStatus, before.libraryStatus);
const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]);
const pointer = engine._malloc(malformed.byteLength);
let result;
try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); }
assert.notEqual(result, 0);
assert.deepEqual(snapshot(engine, handle).libraries, before.libraries);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CATALOG_NEW_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00271" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00270/catalog-new-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00271") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00271-operator-asset.catalog_redo.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00271/catalog-redo-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-catalog-redo-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT"); assert.equal(desktop.poll, false); assert.equal(desktop.operatorStatus, "CANCELLED"); assert.equal(desktop.mainMutation, "NONE"); assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); open(engine, handle, fs.readFileSync(fixture)); const before = snapshot(engine, handle);
assert.equal(before.libraryStatus, "AVAILABLE"); assert.equal(before.libraries.length, 1); const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`); assert.equal(library.name, desktop.before.name); assert.equal(library.sourcePath, "//WebGapCatalogRedo.blend"); assert.equal(library.packed, false); assert.equal(library.status, "EXTERNAL_REQUIRED"); assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED"); assert.deepEqual(library.dependencyIds, []); assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true); const reopened = engine._web_engine_create(); open(engine, reopened, saved); const after = snapshot(engine, reopened); assert.deepEqual(after.libraries, before.libraries); assert.equal(after.libraryStatus, before.libraryStatus);
const malformed = new Uint8Array([0x42,0x4c,0x45,0x4e,0x44,0x45,0x52]); const pointer = engine._malloc(malformed.byteLength); let result; try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); } assert.notEqual(result, 0); assert.deepEqual(snapshot(engine, handle).libraries, before.libraries); engine._web_engine_destroy(handle); engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CATALOG_REDO_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00272" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00271/catalog-redo-local-exact-report.json"); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`); process.exit(0);
}
if (task === "M16-GAP-00272") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00272-operator-asset.catalog_undo.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00272/catalog-undo-desktop-report.json");
fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-catalog-undo-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT"); assert.equal(desktop.poll, false); assert.equal(desktop.operatorStatus, "CANCELLED"); assert.equal(desktop.mainMutation, "NONE"); assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); open(engine, handle, fs.readFileSync(fixture)); const before = snapshot(engine, handle); assert.equal(before.libraryStatus, "AVAILABLE"); assert.equal(before.libraries.length, 1); const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`); assert.equal(library.name, desktop.before.name); assert.equal(library.sourcePath, "//WebGapCatalogUndo.blend"); assert.equal(library.packed, false); assert.equal(library.status, "EXTERNAL_REQUIRED"); assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED"); assert.deepEqual(library.dependencyIds, []); assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true); const reopened = engine._web_engine_create(); open(engine, reopened, saved); const after = snapshot(engine, reopened); assert.deepEqual(after.libraries, before.libraries); assert.equal(after.libraryStatus, before.libraryStatus);
const malformed = new Uint8Array([0x42,0x4c,0x45,0x4e,0x44,0x45,0x52]); const pointer = engine._malloc(malformed.byteLength); let result; try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); } assert.notEqual(result, 0); assert.deepEqual(snapshot(engine, handle).libraries, before.libraries); engine._web_engine_destroy(handle); engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CATALOG_UNDO_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00273" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00272/catalog-undo-local-exact-report.json"); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`); process.exit(0);
}
if (task === "M16-GAP-00273") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00273-operator-asset.catalog_undo_push.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00273/catalog-undo-push-desktop-report.json"); fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-catalog-undo-push-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }); assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8")); assert.equal(desktop.saveReopen, "EXACT"); assert.equal(desktop.poll, false); assert.equal(desktop.operatorStatus, "CANCELLED"); assert.equal(desktop.mainMutation, "NONE"); assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); open(engine, handle, fs.readFileSync(fixture)); const before = snapshot(engine, handle); assert.equal(before.libraryStatus, "AVAILABLE"); assert.equal(before.libraries.length, 1); const library = before.libraries[0]; assert.equal(library.id, `library:${desktop.before.name}`); assert.equal(library.name, desktop.before.name); assert.equal(library.sourcePath, "//WebGapCatalogUndoPush.blend"); assert.equal(library.packed, false); assert.equal(library.status, "EXTERNAL_REQUIRED"); assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED"); assert.deepEqual(library.dependencyIds, []); assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true); const reopened = engine._web_engine_create(); open(engine, reopened, saved); const after = snapshot(engine, reopened); assert.deepEqual(after.libraries, before.libraries); assert.equal(after.libraryStatus, before.libraryStatus); const malformed = new Uint8Array([0x42,0x4c,0x45,0x4e,0x44,0x45,0x52]); const pointer = engine._malloc(malformed.byteLength); let result; try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); } assert.notEqual(result, 0); assert.deepEqual(snapshot(engine, handle).libraries, before.libraries); engine._web_engine_destroy(handle); engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CATALOG_UNDO_PUSH_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00274" }; const reportPath = path.join(root, "tests/golden/M16-GAP-00273/catalog-undo-push-local-exact-report.json"); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`); process.exit(0);
}
if (task === "M16-GAP-00274") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00274-operator-asset.catalogs_save.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00274/catalogs-save-desktop-report.json"); fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-catalogs-save-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }); assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8")); assert.equal(desktop.saveReopen, "EXACT"); assert.equal(desktop.poll, false); assert.equal(desktop.operatorStatus, "CANCELLED"); assert.equal(desktop.mainMutation, "NONE"); assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); open(engine, handle, fs.readFileSync(fixture)); const before = snapshot(engine, handle); assert.equal(before.libraryStatus, "AVAILABLE"); assert.equal(before.libraries.length, 1); const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`); assert.equal(library.name, desktop.before.name); assert.equal(library.sourcePath, "//WebGapCatalogsSave.blend"); assert.equal(library.packed, false); assert.equal(library.status, "EXTERNAL_REQUIRED"); assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED"); assert.deepEqual(library.dependencyIds, []); assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true); const reopened = engine._web_engine_create(); open(engine, reopened, saved); const after = snapshot(engine, reopened); assert.deepEqual(after.libraries, before.libraries); assert.equal(after.libraryStatus, before.libraryStatus); const malformed = new Uint8Array([0x42,0x4c,0x45,0x4e,0x44,0x45,0x52]); const pointer = engine._malloc(malformed.byteLength); let result; try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); } assert.notEqual(result, 0); assert.deepEqual(snapshot(engine, handle).libraries, before.libraries); engine._web_engine_destroy(handle); engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CATALOGS_SAVE_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00275" }; const reportPath = path.join(root, "tests/golden/M16-GAP-00274/catalogs-save-local-exact-report.json"); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`); process.exit(0);
}
if (task === "M16-GAP-00275") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00275-operator-asset.clear.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00275/asset-clear-desktop-report.json"); fs.mkdirSync(path.dirname(desktopPath), { recursive: true });
const run = spawnSync(process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender"), ["-b", "--factory-startup", "--python", path.join(root, "tools/web/check-action-asset-clear-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 }); assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8")); assert.equal(desktop.saveReopen, "EXACT"); assert.equal(desktop.poll, false); assert.equal(desktop.operatorStatus, "CANCELLED"); assert.equal(desktop.mainMutation, "NONE"); assert.deepEqual(desktop.after, desktop.before);
const engine = await factory({ wasmBinary }); const handle = engine._web_engine_create(); open(engine, handle, fs.readFileSync(fixture)); const before = snapshot(engine, handle); assert.equal(before.libraryStatus, "AVAILABLE"); assert.equal(before.libraries.length, 1); const library = before.libraries[0];
assert.equal(library.id, `library:${desktop.before.name}`); assert.equal(library.name, desktop.before.name); assert.equal(library.sourcePath, "//WebGapAssetClear.blend"); assert.equal(library.packed, false); assert.equal(library.status, "EXTERNAL_REQUIRED"); assert.equal(library.errorCode, "LINKED_LIBRARY_RESOURCE_REQUIRED"); assert.deepEqual(library.dependencyIds, []); assert.equal(library.readOnly, true);
const saved = output(engine, handle, engine._web_engine_save_blend, true); const reopened = engine._web_engine_create(); open(engine, reopened, saved); const after = snapshot(engine, reopened); assert.deepEqual(after.libraries, before.libraries); assert.equal(after.libraryStatus, before.libraryStatus); const malformed = new Uint8Array([0x42, 0x4c, 0x45, 0x4e, 0x44, 0x45, 0x52]); const pointer = engine._malloc(malformed.byteLength); let result; try { engine.HEAPU8.set(malformed, pointer); result = engine._web_engine_open_blend(handle, pointer, malformed.byteLength); } finally { engine._free(pointer); } assert.notEqual(result, 0); assert.deepEqual(snapshot(engine, handle).libraries, before.libraries); engine._web_engine_destroy(handle); engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ASSET_CLEAR_LOCAL_EXACT", fixture: { path: path.relative(root, fixture).replaceAll(path.sep, "/"), sha256: desktop.fixtureSha256 }, desktop: { status: "EXACT", report: path.relative(root, desktopPath).replaceAll(path.sep, "/"), poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", libraryId: library.id, sourcePath: library.sourcePath, libraryStatus: library.status, dependencyIds: library.dependencyIds }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00276" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00275/asset-clear-local-exact-report.json"); fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`); process.stdout.write(`generated-gap-ok task=${task} library=${library.id} status=${library.status} poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`); process.exit(0);
}
if (task === "M16-GAP-00205") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00205-operator-anim.separate_slots.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00205/anim-separate-slots-desktop-report.json");

View File

@@ -5,11 +5,13 @@ import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const output = path.resolve(process.argv[2] ?? path.join(root, "tests/golden/M15-03A/next-task-plan.json"));
const completionArg = process.argv.indexOf("--completion-path");
const completionPathArg = completionArg >= 0 ? process.argv[completionArg + 1] : undefined;
const mapPath = path.join(root, "tests/golden/M15-02A/blender-parity-map.json");
const gapPath = path.join(root, "tests/golden/M15-02B/blender-gap-audit.json");
const map = JSON.parse(fs.readFileSync(mapPath, "utf8"));
const gaps = JSON.parse(fs.readFileSync(gapPath, "utf8"));
const completionPath = path.join(root, "tests/golden/M15-03A/completed-gap-tasks.json");
const completionPath = path.resolve(root, completionPathArg ?? "tests/golden/M15-03A/completed-gap-tasks.json");
const completedIds = fs.existsSync(completionPath) ? new Set(JSON.parse(fs.readFileSync(completionPath, "utf8"))) : new Set();
const byId = new Map(map.entries.map((entry) => [entry.id, entry]));
const waveFor = (owner) => {
@@ -58,7 +60,7 @@ const plan = {
schemaVersion: 1,
task: "M15-03A",
operation: "BLENDER_NEXT_TASK_PLAN",
sources: { parityMap: { path: "tests/golden/M15-02A/blender-parity-map.json", sha256: sha256File(mapPath) }, gapAudit: { path: "tests/golden/M15-02B/blender-gap-audit.json", sha256: sha256File(gapPath) }, completions: { path: "tests/golden/M15-03A/completed-gap-tasks.json", sha256: crypto.createHash("sha256").update(completionBytes).digest("hex") } },
sources: { parityMap: { path: "tests/golden/M15-02A/blender-parity-map.json", sha256: sha256File(mapPath) }, gapAudit: { path: "tests/golden/M15-02B/blender-gap-audit.json", sha256: sha256File(gapPath) }, completions: { path: path.relative(root, completionPath).replaceAll(path.sep, "/"), sha256: crypto.createHash("sha256").update(completionBytes).digest("hex") } },
summary: { taskCount: tasks.length, active: tasks.filter((task) => task.state === "active").length, pending: tasks.filter((task) => task.state === "pending").length, completed: tasks.filter((task) => task.state === "completed").length, blocked: tasks.filter((task) => task.state === "blocked").length, byWave: Object.fromEntries([...waveCounts.entries()]) },
tasks,
firstTask: tasks.find((task) => task.state === "active")?.id ?? null,

View File

@@ -4,8 +4,10 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const planPath = path.join(root, "tests/golden/M15-03A/next-task-plan.json");
const outputDir = path.join(root, "tests/golden/M15-03A");
const planArg = process.argv.indexOf("--plan-path");
const outputArg = process.argv.indexOf("--output-dir");
const planPath = path.resolve(root, planArg >= 0 ? process.argv[planArg + 1] : "tests/golden/M15-03A/next-task-plan.json");
const outputDir = path.resolve(root, outputArg >= 0 ? process.argv[outputArg + 1] : "tests/golden/M15-03A");
const catalogPath = path.join(outputDir, "task-catalog.jsonl");
const indexPath = path.join(outputDir, "task-index.json");
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
IMAGE_NAME = "WebGapAssetDownloadImage"
IMAGE_PATH = "//assets/WebGapAssetDownload.png"
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00264.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
image = bpy.data.images.new(IMAGE_NAME, width=1, height=1, alpha=True)
image.filepath = IMAGE_PATH
image.source = "FILE"
image.use_fake_user = True
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
IMAGE_NAME = "WebGapAssetsDownloadImage"
IMAGE_PATH = "//assets/WebGapAssetsDownload.png"
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00265.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
image = bpy.data.images.new(IMAGE_NAME, width=1, height=1, alpha=True)
image.filepath = IMAGE_PATH
image.source = "FILE"
image.use_fake_user = True
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
ACTION_NAME = "WebGapAssignAction"
OBJECT_NAME = "WebGapAssignActionObject"
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00266.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
obj = bpy.data.objects.new(OBJECT_NAME, None)
bpy.context.scene.collection.objects.link(obj)
for frame, x in ((1, 0.0), (5, 2.0)):
obj.location.x = x
obj.keyframe_insert(data_path="location", index=0, frame=frame)
action = obj.animation_data.action
action.name = ACTION_NAME
action.use_fake_user = True
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WebGapBrowseContaining.blend"
OBJECT_NAME = "WebGapBrowseContainingObject"
def reset():
bpy.ops.wm.read_factory_settings(use_empty=True)
def create_source(path):
reset()
mesh = bpy.data.meshes.new("WebGapBrowseContainingMesh")
mesh.from_pydata([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], [], [(0, 1, 2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh)
bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(path), check_existing=False, compress=True)
def create_fixture(output):
source = output.with_name(LIBRARY_NAME)
create_source(source)
reset()
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
if OBJECT_NAME not in data_from.objects:
raise RuntimeError("browse-containing source object is missing")
data_to.objects = [OBJECT_NAME]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("browse-containing linked object is missing")
bpy.context.scene.collection.objects.link(linked)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00267.py -- OUTPUT")
output = pathlib.Path(arguments[0]).resolve()
output.parent.mkdir(parents=True, exist_ok=True)
create_fixture(output)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WebGapBundleInstall.blend"
OBJECT_NAME = "WebGapBundleInstallObject"
def reset():
bpy.ops.wm.read_factory_settings(use_empty=True)
def create_source(path):
reset()
mesh = bpy.data.meshes.new("WebGapBundleInstallMesh")
mesh.from_pydata([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], [], [(0, 1, 2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh)
bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(path), check_existing=False, compress=True)
def create_fixture(output):
source = output.with_name(LIBRARY_NAME)
create_source(source)
reset()
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
if OBJECT_NAME not in data_from.objects:
raise RuntimeError("bundle-install source object is missing")
data_to.objects = [OBJECT_NAME]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("bundle-install linked object is missing")
bpy.context.scene.collection.objects.link(linked)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00268.py -- OUTPUT")
output = pathlib.Path(arguments[0]).resolve()
output.parent.mkdir(parents=True, exist_ok=True)
create_fixture(output)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WebGapCatalogDelete.blend"
OBJECT_NAME = "WebGapCatalogDeleteObject"
def reset():
bpy.ops.wm.read_factory_settings(use_empty=True)
def create_fixture(output):
source = output.with_name(LIBRARY_NAME)
reset()
mesh = bpy.data.meshes.new("WebGapCatalogDeleteMesh")
mesh.from_pydata([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], [], [(0, 1, 2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh)
bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(source), check_existing=False, compress=True)
reset()
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
if OBJECT_NAME not in data_from.objects:
raise RuntimeError("catalog-delete source object is missing")
data_to.objects = [OBJECT_NAME]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("catalog-delete linked object is missing")
bpy.context.scene.collection.objects.link(linked)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00269.py -- OUTPUT")
output = pathlib.Path(arguments[0]).resolve()
output.parent.mkdir(parents=True, exist_ok=True)
create_fixture(output)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WebGapCatalogNew.blend"
OBJECT_NAME = "WebGapCatalogNewObject"
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00270.py -- OUTPUT")
output = pathlib.Path(arguments[0]).resolve()
source = output.with_name(LIBRARY_NAME)
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapCatalogNewMesh")
mesh.from_pydata([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], [], [(0, 1, 2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh)
bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(source), check_existing=False, compress=True)
bpy.ops.wm.read_factory_settings(use_empty=True)
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
if OBJECT_NAME not in data_from.objects:
raise RuntimeError("catalog-new source object is missing")
data_to.objects = [OBJECT_NAME]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("catalog-new linked object is missing")
bpy.context.scene.collection.objects.link(linked)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,20 @@
#!/usr/bin/env python3
import pathlib, sys, bpy
LIBRARY_NAME = "WebGapCatalogRedo.blend"
OBJECT_NAME = "WebGapCatalogRedoObject"
def main():
args = sys.argv[sys.argv.index("--") + 1:]
if len(args) != 1: raise SystemExit("usage")
output = pathlib.Path(args[0]).resolve(); source = output.with_name(LIBRARY_NAME)
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapCatalogRedoMesh"); mesh.from_pydata([(-1,0,0),(1,0,0),(0,1,0)], [], [(0,1,2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh); bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(source), check_existing=False, compress=True)
bpy.ops.wm.read_factory_settings(use_empty=True)
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to): data_to.objects = [OBJECT_NAME]
bpy.context.scene.collection.objects.link(data_to.objects[0])
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
if __name__ == "__main__": main()

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python3
import pathlib, sys, bpy
LIBRARY_NAME="WebGapCatalogUndo.blend"; OBJECT_NAME="WebGapCatalogUndoObject"
def main():
args=sys.argv[sys.argv.index("--")+1:]
if len(args)!=1: raise SystemExit("usage")
output=pathlib.Path(args[0]).resolve(); source=output.with_name(LIBRARY_NAME); bpy.ops.wm.read_factory_settings(use_empty=True)
mesh=bpy.data.meshes.new("WebGapCatalogUndoMesh"); mesh.from_pydata([(-1,0,0),(1,0,0),(0,1,0)],[],[(0,1,2)]); obj=bpy.data.objects.new(OBJECT_NAME,mesh); bpy.context.scene.collection.objects.link(obj); bpy.ops.wm.save_as_mainfile(filepath=str(source),check_existing=False,compress=True); bpy.ops.wm.read_factory_settings(use_empty=True)
with bpy.data.libraries.load(str(source),link=True) as (data_from,data_to): data_to.objects=[OBJECT_NAME]
bpy.context.scene.collection.objects.link(data_to.objects[0]); bpy.ops.wm.save_as_mainfile(filepath=str(output),check_existing=False,compress=True)
if __name__=="__main__": main()

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python3
import pathlib, sys, bpy
LIBRARY_NAME="WebGapCatalogUndoPush.blend"; OBJECT_NAME="WebGapCatalogUndoPushObject"
def main():
args=sys.argv[sys.argv.index("--")+1:]
if len(args)!=1: raise SystemExit("usage")
output=pathlib.Path(args[0]).resolve(); source=output.with_name(LIBRARY_NAME); bpy.ops.wm.read_factory_settings(use_empty=True)
mesh=bpy.data.meshes.new("WebGapCatalogUndoPushMesh"); mesh.from_pydata([(-1,0,0),(1,0,0),(0,1,0)],[],[(0,1,2)]); obj=bpy.data.objects.new(OBJECT_NAME,mesh); bpy.context.scene.collection.objects.link(obj); bpy.ops.wm.save_as_mainfile(filepath=str(source),check_existing=False,compress=True); bpy.ops.wm.read_factory_settings(use_empty=True)
with bpy.data.libraries.load(str(source),link=True) as (data_from,data_to): data_to.objects=[OBJECT_NAME]
bpy.context.scene.collection.objects.link(data_to.objects[0]); bpy.ops.wm.save_as_mainfile(filepath=str(output),check_existing=False,compress=True)
if __name__=="__main__": main()

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WebGapCatalogsSave.blend"
OBJECT_NAME = "WebGapCatalogsSaveObject"
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00274.py -- OUTPUT")
output = pathlib.Path(arguments[0]).resolve()
source = output.with_name(LIBRARY_NAME)
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapCatalogsSaveMesh")
mesh.from_pydata([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], [], [(0, 1, 2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh)
bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(source), check_existing=False, compress=True)
bpy.ops.wm.read_factory_settings(use_empty=True)
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
if OBJECT_NAME not in data_from.objects:
raise RuntimeError("catalogs-save source object is missing")
data_to.objects = [OBJECT_NAME]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("catalogs-save linked object is missing")
bpy.context.scene.collection.objects.link(linked)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WebGapAssetClear.blend"
OBJECT_NAME = "WebGapAssetClearObject"
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00275.py -- OUTPUT")
output = pathlib.Path(arguments[0]).resolve()
source = output.with_name(LIBRARY_NAME)
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAssetClearMesh")
mesh.from_pydata([(-1.0, 0.0, 0.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)], [], [(0, 1, 2)])
obj = bpy.data.objects.new(OBJECT_NAME, mesh)
bpy.context.scene.collection.objects.link(obj)
bpy.ops.wm.save_as_mainfile(filepath=str(source), check_existing=False, compress=True)
bpy.ops.wm.read_factory_settings(use_empty=True)
with bpy.data.libraries.load(str(source), link=True) as (data_from, data_to):
if OBJECT_NAME not in data_from.objects:
raise RuntimeError("asset-clear source object is missing")
data_to.objects = [OBJECT_NAME]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("asset-clear linked object is missing")
bpy.context.scene.collection.objects.link(linked)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
if __name__ == "__main__":
main()