Rebuild local Blender WASM environment and recovery evidence
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-27 20:15:55 +08:00
parent a081c68c87
commit c2c65d3a09
376 changed files with 241951 additions and 114 deletions

View File

@@ -0,0 +1,57 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
CONTRACT_TEXT_NAME = "WBV2-P0-003-CapabilityContract"
CONTRACT_ID = "workflow:freeze_capability_error_budget_contract"
def contract_payload():
payload = {
"schemaVersion": 1,
"contractId": CONTRACT_ID,
"capability": {"level": "L0", "status": "BOUNDED"},
"revision": {"base": 0, "current": 1},
"budget": {"maxErrors": 8, "maxMessageBytes": 256, "maxUnknownFields": 16},
"errors": [
{"code": "PROTOCOL_MISMATCH", "severity": "error", "recoverable": True},
{"code": "REVISION_CONFLICT", "severity": "error", "recoverable": True},
{"code": "OPEN_CANCELLED", "severity": "info", "recoverable": True},
{"code": "CAPABILITY_CONTRACT_INVALID", "severity": "error", "recoverable": True},
{"code": "CAPABILITY_CONTRACT_BUDGET_EXCEEDED", "severity": "error", "recoverable": True},
{"code": "CAPABILITY_CONTRACT_UNSUPPORTED", "severity": "error", "recoverable": False},
{"code": "CAPABILITY_CONTRACT_SCHEMA_HASH_MISMATCH", "severity": "fatal", "recoverable": False},
{"code": "CAPABILITY_CONTRACT_MIGRATION_BLOCKED", "severity": "error", "recoverable": True},
],
"cancellation": {"code": "OPEN_CANCELLED", "preservesRevision": True},
"migration": {
"currentSchemaVersion": 1,
"supportedSchemaVersions": [1],
"preserveUnknown": True,
"strategy": "RETAIN_UNKNOWN",
},
"extensions": {"futureField": {"kind": "opaque", "value": "preserve-me"}},
}
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
payload["schemaHash"] = hashlib.sha256(canonical.encode("utf-8")).hexdigest()
return payload
def main(output: pathlib.Path) -> None:
bpy.ops.wm.read_factory_settings(use_empty=True)
text = bpy.data.texts.new(CONTRACT_TEXT_NAME)
text.write(json.dumps(contract_payload(), sort_keys=True, separators=(",", ":")))
text.use_fake_user = True
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P0-003.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
TEXT_NAME = "WBV2-P1-001-RuntimeReceipt"
RECEIPT_ID = "workflow:stabilize_runtime_variants_and_source_receipts"
def sha256_file(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def runtime_receipt():
root = pathlib.Path(__file__).resolve().parents[3]
source_hash = sha256_file(root / "blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp")
manifest = json.loads((root / "web/app/public/engine-manifest.json").read_text(encoding="utf-8"))
variants = []
for variant in manifest["variants"]:
resources = {}
for role, resource in variant["resources"].items():
resource_path = root / "web/app/public" / resource["url"].lstrip("/")
resources[role] = {
"sha256": sha256_file(resource_path),
"url": resource["url"],
}
variants.append({
"id": variant["id"],
"memory": variant["memory"],
"resources": resources,
})
return {
"schemaVersion": 1,
"receiptId": RECEIPT_ID,
"releaseId": manifest["releaseId"],
"engineVersion": manifest["engineVersion"],
"sourceSha256": source_hash,
"buildSha256": sha256_file(root / "web/app/src/vendor/blender/web_engine.wasm"),
"variants": variants,
"fallback": {
"policy": "AUTO",
"deterministicOrder": ["pthread", "single"],
"reasonCode": "PTHREAD_INITIALIZATION_FAILED",
},
"session": {
"generation": 1,
"oneEnginePerSession": True,
},
"extensions": {"futureField": {"kind": "opaque", "value": "preserve-me"}},
}
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
text = bpy.data.texts.new(TEXT_NAME)
text.write(json.dumps(runtime_receipt(), sort_keys=True, separators=(",", ":")))
text.use_fake_user = True
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P1-001.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
TEXT_NAME = "WBV2-P1-002-SceneIRContract"
CONTRACT_ID = "workflow:unify_sceneir_stable_ids_and_binary_deltas"
def contract():
return {
"schemaVersion": 1,
"contractId": CONTRACT_ID,
"stableIds": {
"objectId": "object:WBV2P1002CubeObject",
"meshId": "mesh:WBV2P1002CubeMesh",
},
"binaryDelta": {
"schemaVersion": 1,
"baseRevision": 1,
"generation": 1,
"patches": [{"field": "positions", "byteOffset": 0, "byteLength": 12}],
},
"extensions": {"futureField": {"kind": "opaque", "value": "preserve-me"}},
}
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WBV2P1002CubeMesh")
mesh.from_pydata(
[(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)],
[],
[(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1),
(1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7)],
)
mesh.update()
obj = bpy.data.objects.new("WBV2P1002CubeObject", mesh)
bpy.context.collection.objects.link(obj)
text = bpy.data.texts.new(TEXT_NAME)
text.write(json.dumps(contract(), sort_keys=True, separators=(",", ":")))
text.use_fake_user = True
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P1-002.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,43 @@
#!/usr/bin/env python3
import json
import pathlib
import sys
import bpy
TEXT_NAME = "WBV2-P1-003-TransactionContract"
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WBV2P1003CubeMesh")
mesh.from_pydata(
[(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)],
[],
[(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1),
(1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7)],
)
mesh.update()
obj = bpy.data.objects.new("WBV2P1003CubeObject", mesh)
bpy.context.collection.objects.link(obj)
text = bpy.data.texts.new(TEXT_NAME)
text.write(json.dumps({
"schemaVersion": 1,
"contractId": "workflow:unify_main_transaction_history_and_save",
"initialVisibility": True,
"mutation": {"command": "setObjectVisibility", "visible": False},
"history": {"failedCommandAddsHistory": False, "undoRedo": "SUPPORTED"},
"revision": {"staleCommit": "REJECTED"},
"save": {"atomic": True, "byteStable": True},
}, sort_keys=True, separators=(",", ":")))
text.use_fake_user = True
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P1-003.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
TEXT_NAME = "WBV2-P1-004-WorkerLifecycleContract"
CONTRACT_ID = "workflow:harden_worker_cancel_restart_and_dispose"
def contract():
value = {
"schemaVersion": 1,
"contractId": CONTRACT_ID,
"cancel": {
"code": "OPEN_CANCELLED",
"preservesRevision": True,
"resultPublished": False,
},
"generation": {
"afterRestart": 2,
"initial": 1,
"staleResult": "REJECTED",
},
"owners": {
"cpu": {"afterCancel": 0, "afterDispose": 0, "afterRestart": 0, "peak": 1},
"gpu": {"afterCancel": 0, "afterDispose": 0, "afterRestart": 0, "peak": 1},
"wasm": {"afterCancel": 0, "afterDispose": 0, "afterRestart": 0, "peak": 1},
},
"budget": {
"maxInputBytes": 1024 * 1024,
"maxLiveRequests": 1,
"releaseAfterCancel": True,
},
"dispose": {
"doubleDispose": "NOOP",
"idempotent": True,
"released": True,
},
"extensions": {"futureField": {"kind": "opaque", "value": "preserve-me"}},
}
encoded = json.dumps(value, sort_keys=True, separators=(",", ":"))
value["schemaHash"] = hashlib.sha256(encoded.encode("utf-8")).hexdigest()
return value
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WBV2P1004LifecycleMesh")
mesh.from_pydata(
[(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)],
[],
[(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1),
(1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7)],
)
mesh.update()
obj = bpy.data.objects.new("WBV2P1004LifecycleObject", mesh)
bpy.context.collection.objects.link(obj)
text = bpy.data.texts.new(TEXT_NAME)
text.write(json.dumps(contract(), sort_keys=True, separators=(",", ":")))
text.use_fake_user = True
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P1-004.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
TEXT_NAME = "WBV2-P1-005-StorageJournalContract"
CONTRACT_ID = "workflow:harden_opfs_indexeddb_journal_recovery"
def contract():
value = {
"schemaVersion": 1,
"contractId": CONTRACT_ID,
"journal": {
"interruptedAt": "BEFORE_PUBLISH",
"oldProjectUsable": True,
"recovery": "ROLLBACK_OLD_REVISION",
"stagedRevision": 8,
"oldRevision": 7,
},
"hash": {
"expected": "sha256:project-revision-8",
"mismatch": "QUARANTINED",
"verified": False,
},
"storage": {
"indexeddb": {"afterRecovery": 0, "journalEntriesBefore": 1, "metadataRevision": 7},
"opfs": {"afterRecovery": 0, "stagingFilesBefore": 1, "projectReadable": True},
},
"restart": {"generationAfter": 2, "generationBefore": 1, "status": "RECOVERED"},
"budget": {"maxJournalEntries": 32, "maxProjectBytes": 4 * 1024 * 1024},
"extensions": {"futureField": {"kind": "opaque", "value": "preserve-me"}},
}
encoded = json.dumps(value, sort_keys=True, separators=(",", ":"))
value["schemaHash"] = hashlib.sha256(encoded.encode("utf-8")).hexdigest()
return value
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WBV2P1005StorageMesh")
mesh.from_pydata(
[(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)],
[],
[(0, 1, 2, 3), (4, 7, 6, 5), (0, 4, 5, 1),
(1, 5, 6, 2), (2, 6, 7, 3), (4, 0, 3, 7)],
)
mesh.update()
obj = bpy.data.objects.new("WBV2P1005StorageObject", mesh)
bpy.context.collection.objects.link(obj)
text = bpy.data.texts.new(TEXT_NAME)
text.write(json.dumps(contract(), sort_keys=True, separators=(",", ":")))
text.use_fake_user = True
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P1-005.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,37 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P2001Scene"
root = scene.collection
collection = bpy.data.collections.new("WBV2P2001OutlinerCollection")
root.children.link(collection)
mesh = bpy.data.meshes.new("WBV2P2001StableMesh")
mesh.from_pydata(
[(-1, -1, 0), (1, -1, 0), (0, 1, 0)],
[],
[(0, 1, 2)],
)
mesh.update()
obj = bpy.data.objects.new("WBV2P2001SelectedObject", mesh)
collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
# Keep the standard workspace/screen in the file so editorWorkflow has a
# real context while the data tree remains deliberately minimal.
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P2-001.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def triangle(name):
mesh = bpy.data.meshes.new(name)
mesh.from_pydata([(-1, 0, 0), (1, 0, 0), (0, 1, 0)], [], [(0, 1, 2)])
mesh.update()
return mesh
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P2002Scene"
root = scene.collection
source = bpy.data.collections.new("WBV2P2002Source")
target = bpy.data.collections.new("WBV2P2002Target")
root.children.link(source)
root.children.link(target)
parent = bpy.data.objects.new("WBV2P2002Parent", triangle("WBV2P2002ParentMesh"))
source.objects.link(parent)
parent.location = (1.0, 0.0, 0.0)
child = bpy.data.objects.new("WBV2P2002Child", triangle("WBV2P2002ChildMesh"))
source.objects.link(child)
child.parent = parent
child.location = (0.0, 2.0, 0.0)
bpy.context.view_layer.objects.active = parent
parent.select_set(True)
child.select_set(False)
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P2-002.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,39 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
OPERATIONS = ("Translate", "Merge", "Dissolve", "Extrude", "Inset", "Bevel", "LoopCut")
def square_mesh(name):
mesh = bpy.data.meshes.new(name)
mesh.from_pydata(
[(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, 1, 0)],
[],
[(0, 1, 2, 3)],
)
mesh.update()
return mesh
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
collection = bpy.context.scene.collection
for label in OPERATIONS:
mesh = square_mesh("WBV2P2003%sMesh" % label)
obj = bpy.data.objects.new("WBV2P2003%sObject" % label, mesh)
collection.objects.link(obj)
active = bpy.data.objects["WBV2P2003TranslateObject"]
bpy.context.view_layer.objects.active = active
active.select_set(True)
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P2-003.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
from mathutils import Vector
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P2004ViewportScene"
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.object
cube.name = "WBV2P2004ViewportCube"
cube.data.name = "WBV2P2004ViewportCubeMesh"
cube.select_set(True)
bpy.context.view_layer.objects.active = cube
camera_data = bpy.data.cameras.new("WBV2P2004ViewportCamera")
camera = bpy.data.objects.new("WBV2P2004ViewportCamera", camera_data)
scene.collection.objects.link(camera)
camera.location = (6.5, -6.5, 4.75)
camera.rotation_euler = (Vector((0, 0, 0)) - camera.location).to_track_quat("-Z", "Y").to_euler()
camera_data.lens = 52
scene.camera = camera
scene.world = bpy.data.worlds.new("WBV2P2004ViewportWorld")
scene.world.color = (0.055, 0.065, 0.08)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P2-004.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P2005WorkflowScene"
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.object
cube.name = "WBV2P2005WorkflowCube"
cube.data.name = "WBV2P2005WorkflowCubeMesh"
cube.select_set(True)
bpy.context.view_layer.objects.active = cube
scene.world = bpy.data.worlds.new("WBV2P2005WorkflowWorld")
scene.world.color = (0.055, 0.065, 0.08)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P2-005.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P3001MaterialScene"
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.object
cube.name = "WBV2P3001MaterialCube"
cube.data.name = "WBV2P3001MaterialCubeMesh"
material = bpy.data.materials.new("WBV2P3001PrincipledMaterial")
material.use_nodes = True
nodes = material.node_tree.nodes
principled = nodes.get("Principled BSDF")
if principled is None:
raise RuntimeError("material fixture could not create Principled BSDF")
principled.inputs["Base Color"].default_value = (0.12, 0.34, 0.56, 1.0)
principled.inputs["Roughness"].default_value = 0.23
principled.inputs["Metallic"].default_value = 0.67
principled.inputs["IOR"].default_value = 1.42
unsupported = nodes.new("ShaderNodeTexNoise")
unsupported.name = "WBV2P3001UnsupportedNoise"
unsupported.label = "retained unsupported node"
cube.data.materials.append(material)
cube.select_set(True)
bpy.context.view_layer.objects.active = cube
if cube.data.uv_layers.active is None:
cube.data.uv_layers.new(name="UVMap")
cube.data.uv_layers.active.name = "WBV2P3001UV"
cube.data.uv_layers.active_index = 0
scene.world = bpy.data.worlds.new("WBV2P3001MaterialWorld")
scene.world.color = (0.055, 0.065, 0.08)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P3-001.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,45 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P3002AnimationScene"
scene.frame_start = 1
scene.frame_end = 40
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.object
cube.name = "WBV2P3002AnimatedCube"
cube.data.name = "WBV2P3002AnimatedCubeMesh"
cube.select_set(True)
bpy.context.view_layer.objects.active = cube
keyframes = {
1: ((0, 0, 0), (0, 0, 0), (1, 1, 1)),
10: ((1, 2, 0.5), (0.1, 0.2, 0.3), (1.2, 0.8, 1.1)),
20: ((2, 0, 1), (0.4, 0.1, 0.6), (0.9, 1.3, 1.0)),
}
for frame, (location, rotation, scale) in keyframes.items():
scene.frame_set(frame)
cube.location = location
cube.rotation_euler = rotation
cube.scale = scale
cube.keyframe_insert(data_path="location", frame=frame)
cube.keyframe_insert(data_path="rotation_euler", frame=frame)
cube.keyframe_insert(data_path="scale", frame=frame)
if cube.animation_data and cube.animation_data.action:
cube.animation_data.action.name = "WBV2P3002TRSAction"
scene.frame_set(1)
scene.world = bpy.data.worlds.new("WBV2P3002AnimationWorld")
scene.world.color = (0.055, 0.065, 0.08)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P3-002.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P3003RigPoseSkinScene"
scene.frame_start = 1
scene.frame_end = 20
mesh = bpy.data.meshes.new("WBV2P3003SkinnedMesh")
mesh.from_pydata(
[(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, 1, 0),
(-1, -1, 2), (1, -1, 2), (1, 1, 2), (-1, 1, 2)],
[],
[(0, 1, 2), (0, 2, 3), (4, 6, 5), (4, 7, 6),
(0, 4, 5), (0, 5, 1), (1, 5, 6), (1, 6, 2),
(2, 6, 7), (2, 7, 3), (4, 0, 3), (4, 3, 7)],
)
mesh.update()
mesh_object = bpy.data.objects.new("WBV2P3003SkinnedMeshObject", mesh)
bpy.context.collection.objects.link(mesh_object)
basis = mesh_object.shape_key_add(name="Basis")
smile = mesh_object.shape_key_add(name="WBV2P3003Smile")
smile.data[2].co.z += 0.35
smile.data[6].co.z += 0.35
smile.value = 0.4
del basis
armature_data = bpy.data.armatures.new("WBV2P3003Armature")
armature_object = bpy.data.objects.new("WBV2P3003ArmatureObject", armature_data)
bpy.context.collection.objects.link(armature_object)
bpy.context.view_layer.objects.active = armature_object
armature_object.select_set(True)
bpy.ops.object.mode_set(mode="EDIT")
root = armature_data.edit_bones.new("WBV2P3003Root")
root.head = (0, 0, 0)
root.tail = (0, 0, 1)
tip = armature_data.edit_bones.new("WBV2P3003Tip")
tip.head = (0, 0, 1)
tip.tail = (0, 0, 2)
tip.parent = root
tip.use_connect = True
bpy.ops.object.mode_set(mode="POSE")
pose_root = armature_object.pose.bones["WBV2P3003Root"]
pose_root.rotation_mode = "XYZ"
for frame, angle in ((1, 0.0), (10, 0.35), (20, -0.2)):
scene.frame_set(frame)
pose_root.rotation_euler[1] = angle
pose_root.keyframe_insert(data_path="rotation_euler", index=1, frame=frame)
unsupported = armature_object.pose.bones["WBV2P3003Tip"].constraints.new("LIMIT_ROTATION")
unsupported.name = "WBV2P3003UnsupportedLimitRotation"
unsupported.use_limit_x = True
unsupported.min_x = -0.25
unsupported.max_x = 0.25
unsupported.influence = 0.65
bpy.ops.object.mode_set(mode="OBJECT")
root_group = mesh_object.vertex_groups.new(name="WBV2P3003Root")
tip_group = mesh_object.vertex_groups.new(name="WBV2P3003Tip")
root_group.add([0, 1, 2, 3], 1.0, "REPLACE")
root_group.add([4, 5, 6, 7], 0.2, "REPLACE")
tip_group.add([4, 5, 6, 7], 0.8, "REPLACE")
tip_group.add([0, 1, 2, 3], 0.0, "REPLACE")
armature_modifier = mesh_object.modifiers.new(name="WBV2P3003ArmatureDeform", type="ARMATURE")
armature_modifier.object = armature_object
bounded = mesh_object.constraints.new("LIMIT_LOCATION")
bounded.name = "WBV2P3003BoundedObjectConstraint"
bounded.use_min_x = True
bounded.use_max_x = True
bounded.min_x = -2.0
bounded.max_x = 2.0
bounded.influence = 0.8
scene.frame_set(1)
scene.world = bpy.data.worlds.new("WBV2P3003RigWorld")
scene.world.color = (0.03, 0.04, 0.05)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P3-003.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P3004GLBRoundTripScene"
scene.frame_start = 1
scene.frame_end = 20
scene.render.fps = 24
mesh = bpy.data.meshes.new("WBV2P3004RoundTripMesh")
mesh.from_pydata(
[(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, 1, 0),
(-1, -1, 2), (1, -1, 2), (1, 1, 2), (-1, 1, 2)],
[],
[(0, 1, 2), (0, 2, 3), (4, 6, 5), (4, 7, 6),
(0, 4, 5), (0, 5, 1), (1, 5, 6), (1, 6, 2),
(2, 6, 7), (2, 7, 3), (4, 0, 3), (4, 3, 7)],
)
mesh.update()
uv = mesh.uv_layers.new(name="WBV2P3004UV")
for loop_index, loop in enumerate(mesh.loops):
uv.data[loop_index].uv = ((0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0))[loop.vertex_index % 4]
mesh_object = bpy.data.objects.new("WBV2P3004RoundTripMeshObject", mesh)
bpy.context.collection.objects.link(mesh_object)
mesh_object.shape_key_add(name="Basis")
morph = mesh_object.shape_key_add(name="WBV2P3004Morph")
morph.data[2].co.z += 0.3
morph.data[6].co.z += 0.3
morph.value = 0.5
material = bpy.data.materials.new("WBV2P3004PBRMaterial")
material.use_nodes = True
material.diffuse_color = (0.22, 0.48, 0.82, 1.0)
principled = material.node_tree.nodes.get("Principled BSDF")
principled.inputs["Base Color"].default_value = (0.22, 0.48, 0.82, 1.0)
principled.inputs["Metallic"].default_value = 0.35
principled.inputs["Roughness"].default_value = 0.42
principled.inputs["Emission Color"].default_value = (0.0, 0.0, 0.0, 1.0)
principled.inputs["Emission Strength"].default_value = 1.0
image = bpy.data.images.load(str(pathlib.Path("tests/files/web/resources/udim_1001.png").resolve()), check_existing=False)
image.name = "WBV2P3004PackedTexture"
image.pack()
texture = material.node_tree.nodes.new("ShaderNodeTexImage")
texture.name = "WBV2P3004PackedTextureNode"
texture.image = image
material.node_tree.links.new(texture.outputs["Color"], principled.inputs["Base Color"])
mesh.materials.append(material)
armature_data = bpy.data.armatures.new("WBV2P3004Armature")
armature_object = bpy.data.objects.new("WBV2P3004ArmatureObject", armature_data)
bpy.context.collection.objects.link(armature_object)
bpy.context.view_layer.objects.active = armature_object
armature_object.select_set(True)
bpy.ops.object.mode_set(mode="EDIT")
root = armature_data.edit_bones.new("WBV2P3004Root")
root.head = (0, 0, 0)
root.tail = (0, 0, 1)
tip = armature_data.edit_bones.new("WBV2P3004Tip")
tip.head = (0, 0, 1)
tip.tail = (0, 0, 2)
tip.parent = root
tip.use_connect = True
bpy.ops.object.mode_set(mode="OBJECT")
root_group = mesh_object.vertex_groups.new(name="WBV2P3004Root")
tip_group = mesh_object.vertex_groups.new(name="WBV2P3004Tip")
root_group.add([0, 1, 2, 3], 1.0, "REPLACE")
root_group.add([4, 5, 6, 7], 0.25, "REPLACE")
tip_group.add([4, 5, 6, 7], 0.75, "REPLACE")
modifier = mesh_object.modifiers.new(name="WBV2P3004ArmatureDeform", type="ARMATURE")
modifier.object = armature_object
scene.frame_set(1)
mesh_object.location = (0, 0, 0)
mesh_object.keyframe_insert(data_path="location", frame=1)
mesh_object.location = (1.25, -0.5, 0.25)
mesh_object.keyframe_insert(data_path="location", frame=10)
mesh_object.location = (2.0, 0.0, 0.5)
mesh_object.keyframe_insert(data_path="location", frame=20)
if mesh_object.animation_data and mesh_object.animation_data.action:
mesh_object.animation_data.action.name = "WBV2P3004RoundTripAction"
scene.frame_set(1)
scene.world = bpy.data.worlds.new("WBV2P3004GLBWorld")
scene.world.color = (0.02, 0.03, 0.05)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P3-004.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
LIBRARY_NAME = "WBV2P3005Library.blend"
LINKED_OBJECT = "WBV2P3005LinkedObject"
APPEND_OBJECT = "WBV2P3005AppendObject"
def reset() -> None:
bpy.ops.wm.read_factory_settings(use_empty=True)
def mesh_object(name: str, mesh_name: str, location: tuple[float, float, float]) -> bpy.types.Object:
mesh = bpy.data.meshes.new(mesh_name)
mesh.from_pydata(
[(-0.75, -0.75, 0.0), (0.75, -0.75, 0.0), (0.75, 0.75, 0.0), (-0.75, 0.75, 0.0)],
[],
[(0, 1, 2), (0, 2, 3)],
)
mesh.update()
obj = bpy.data.objects.new(name, mesh)
obj.location = location
bpy.context.collection.objects.link(obj)
return obj
def create_source(path: pathlib.Path) -> None:
reset()
linked = mesh_object(LINKED_OBJECT, "WBV2P3005LinkedMesh", (-1.5, 0.0, 0.0))
linked_material = bpy.data.materials.new("WBV2P3005LinkedMaterial")
linked_material.diffuse_color = (0.18, 0.38, 0.62, 1.0)
linked.data.materials.append(linked_material)
appended = mesh_object(APPEND_OBJECT, "WBV2P3005AppendMesh", (1.5, 0.0, 0.0))
material = bpy.data.materials.new("WBV2P3005AppendMaterial")
material.use_nodes = True
principled = material.node_tree.nodes.get("Principled BSDF")
principled.inputs["Base Color"].default_value = (0.76, 0.28, 0.12, 1.0)
principled.inputs["Metallic"].default_value = 0.2
principled.inputs["Roughness"].default_value = 0.5
principled.inputs["Emission Color"].default_value = (0.0, 0.0, 0.0, 1.0)
principled.inputs["Emission Strength"].default_value = 1.0
image_path = pathlib.Path("tests/files/web/resources/udim_1001.png").resolve()
image = bpy.data.images.load(str(image_path), check_existing=False)
image.name = "WBV2P3005AppendImage"
image.pack()
texture = material.node_tree.nodes.new("ShaderNodeTexImage")
texture.name = "WBV2P3005AppendImageNode"
texture.image = image
material.node_tree.links.new(texture.outputs["Color"], principled.inputs["Base Color"])
appended.data.materials.append(material)
bpy.ops.wm.save_as_mainfile(filepath=str(path.resolve()), check_existing=False, compress=True)
def create_fixture(output: pathlib.Path) -> None:
source = output.with_name(LIBRARY_NAME)
create_source(source)
reset()
with bpy.data.libraries.load(str(source.resolve()), link=True) as (data_from, data_to):
if LINKED_OBJECT not in data_from.objects:
raise RuntimeError("linked source object is missing")
data_to.objects = [LINKED_OBJECT]
linked = data_to.objects[0]
if linked is None:
raise RuntimeError("linked object could not be loaded")
bpy.context.collection.objects.link(linked)
bpy.context.view_layer.objects.active = linked
linked.select_set(True)
bpy.ops.wm.save_as_mainfile(filepath=str(output.resolve()), check_existing=False, compress=True)
def main(output: pathlib.Path) -> None:
output.parent.mkdir(parents=True, exist_ok=True)
create_fixture(output.resolve())
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P3-005.py -- OUTPUT")
main(pathlib.Path(arguments[0]))

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P4001ModifierScene"
mesh = bpy.data.meshes.new("WBV2P4001ModifierMesh")
mesh.from_pydata(
[(-1, -1, -1), (1, -1, -1), (1, 1, -1), (-1, 1, -1),
(-1, -1, 1), (1, -1, 1), (1, 1, 1), (-1, 1, 1)],
[],
[(0, 1, 2), (0, 2, 3), (4, 6, 5), (4, 7, 6),
(0, 4, 5), (0, 5, 1), (1, 5, 6), (1, 6, 2),
(2, 6, 7), (2, 7, 3), (4, 0, 3), (4, 3, 7)],
)
mesh.update()
obj = bpy.data.objects.new("WBV2P4001ModifierObject", mesh)
bpy.context.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bevel = obj.modifiers.new("WBV2P4001Bevel", "BEVEL")
bevel.width = 0.12
bevel.segments = 2
bevel.profile = 0.5
bevel.show_viewport = True
bevel.show_render = True
decimate = obj.modifiers.new("WBV2P4001Decimate", "DECIMATE")
decimate.ratio = 0.75
decimate.iterations = 1
decimate.show_viewport = True
decimate.show_render = False
bpy.ops.object.particle_system_add()
particle_modifier = obj.modifiers[-1]
particle_modifier.name = "WBV2P4001UnsupportedParticle"
particle_modifier.show_viewport = True
particle_modifier.show_render = False
scene.frame_start = 1
scene.frame_end = 10
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P4-001.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def geometry_group(name, blocked=False):
group = bpy.data.node_groups.new(name, "GeometryNodeTree")
group.interface.new_socket(name="Geometry", in_out="INPUT", socket_type="NodeSocketGeometry")
group.interface.new_socket(name="Geometry", in_out="OUTPUT", socket_type="NodeSocketGeometry")
group_input = group.nodes.new("NodeGroupInput")
group_input.name = "WBV2P4002 Group Input"
group_output = group.nodes.new("NodeGroupOutput")
group_output.name = "WBV2P4002 Group Output"
if blocked:
unsupported = group.nodes.new("GeometryNodeDistributePointsOnFaces")
unsupported.name = "WBV2P4002 Unsupported Distribute"
unsupported.distribute_method = "RANDOM"
group.links.new(group_input.outputs["Geometry"], unsupported.inputs["Mesh"])
group.links.new(unsupported.outputs["Points"], group_output.inputs["Geometry"])
else:
transform = group.nodes.new("GeometryNodeTransform")
transform.name = "WBV2P4002 Transform"
transform.inputs["Translation"].default_value = (0.25, -0.5, 0.75)
group.links.new(group_input.outputs["Geometry"], transform.inputs["Geometry"])
group.links.new(transform.outputs["Geometry"], group_output.inputs["Geometry"])
return group
def mesh_object(name, group):
mesh = bpy.data.meshes.new(f"{name}Mesh")
mesh.from_pydata(
[(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, 1, 0)],
[],
[(0, 1, 2, 3)],
)
mesh.update()
obj = bpy.data.objects.new(name, mesh)
bpy.context.scene.collection.objects.link(obj)
modifier = obj.modifiers.new("WBV2P4002 Geometry Nodes", "NODES")
modifier.node_group = group
return obj
def material():
value = bpy.data.materials.new("WBV2P4002ShaderMaterial")
value.use_nodes = True
tree = value.node_tree
nodes = tree.nodes
links = tree.links
principled = nodes.get("Principled BSDF")
output = nodes.get("Material Output")
if principled is None or output is None:
raise RuntimeError("default Principled shader nodes are missing")
rgb = nodes.new("ShaderNodeRGB")
rgb.name = "WBV2P4002 RGB"
rgb.outputs["Color"].default_value = (0.12, 0.34, 0.56, 1.0)
links.new(rgb.outputs["Color"], principled.inputs["Base Color"])
links.new(principled.outputs["BSDF"], output.inputs["Surface"])
return value
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P4002NodesScene"
scene.frame_start = 1
scene.frame_end = 12
scene.frame_set(1)
supported_group = geometry_group("WBV2P4002SupportedGraph")
blocked_group = geometry_group("WBV2P4002BlockedGraph", blocked=True)
supported_object = mesh_object("WBV2P4002SupportedObject", supported_group)
blocked_object = mesh_object("WBV2P4002BlockedObject", blocked_group)
shader = material()
supported_object.data.materials.append(shader)
bpy.context.view_layer.objects.active = supported_object
supported_object.select_set(True)
blocked_object.select_set(False)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P4-002.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def sculpt_mesh():
mesh = bpy.data.meshes.new("WBV2P4003SculptMesh")
mesh.from_pydata(
[(-1, -1, 0), (1, -1, 0), (1, 1, 0), (-1, 1, 0), (0, 0, 1)],
[],
[(0, 1, 2, 3), (0, 1, 4), (1, 2, 4), (2, 3, 4), (3, 0, 4)],
)
mesh.update()
mask = mesh.attributes.new(".sculpt_mask", "FLOAT", "POINT")
for index, item in enumerate(mask.data):
item.value = 0.0 if index == 0 else 0.25 * index
face_sets = mesh.attributes.new(".sculpt_face_set", "INT", "FACE")
for index, item in enumerate(face_sets.data):
item.value = index + 1
obj = bpy.data.objects.new("WBV2P4003SculptObject", mesh)
bpy.context.scene.collection.objects.link(obj)
return obj
def curve_object():
data = bpy.data.curves.new("WBV2P4003Curve", "CURVE")
data.dimensions = "3D"
data.resolution_u = 4
spline = data.splines.new("BEZIER")
spline.bezier_points.add(2)
for point, co in zip(spline.bezier_points, ((-1.5, 2, 0), (0, 2.5, 0.75), (1.5, 2, 0))):
point.co = co
point.handle_left_type = "AUTO"
point.handle_right_type = "AUTO"
obj = bpy.data.objects.new("WBV2P4003CurveObject", data)
bpy.context.scene.collection.objects.link(obj)
return obj
def point_cloud_object():
seed = bpy.data.meshes.new("WBV2P4003PointSeed")
seed.from_pydata([(-1, -2, 0), (0, -2, 0.5), (1, -2, 0)], [], [])
obj = bpy.data.objects.new("WBV2P4003PointCloudObject", seed)
bpy.context.scene.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.object.convert(target="POINTCLOUD")
obj.data.name = "WBV2P4003PointCloud"
radius = obj.data.attributes.new("radius", "FLOAT", "POINT")
for index, item in enumerate(radius.data):
item.value = 0.05 + index * 0.01
return obj
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P4003SculptNonmeshScene"
scene.frame_start = 1
scene.frame_end = 8
scene.frame_set(1)
sculpt_mesh()
curve_object()
point_cloud_object()
bpy.ops.object.select_all(action="DESELECT")
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P4-003.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def cube(name, location):
bpy.ops.mesh.primitive_cube_add(size=2, location=location)
obj = bpy.context.object
obj.name = name
obj.data.name = f"{name}Mesh"
return obj
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P4004SimulationScene"
scene.frame_start = 1
scene.frame_end = 4
scene.frame_set(1)
floor = cube("WBV2P4004Collision", (0, 0, -1.5))
bpy.context.view_layer.objects.active = floor
bpy.ops.rigidbody.object_add()
floor.rigid_body.type = "PASSIVE"
floor.rigid_body.collision_shape = "BOX"
body = cube("WBV2P4004RigidBody", (0, 0, 1.0))
bpy.context.view_layer.objects.active = body
bpy.ops.rigidbody.object_add()
body.rigid_body.type = "ACTIVE"
body.rigid_body.mass = 2.5
body.rigid_body.friction = 0.35
body.rigid_body.restitution = 0.2
body.rigid_body.linear_damping = 0.04
body.rigid_body.angular_damping = 0.1
body.rigid_body.collision_shape = "BOX"
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P4-004.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,131 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
from mathutils import Vector
WASM_SHA256 = "25bc1e286fd3b72c2c7c082b85a29b2d0bb109588a6dc0de5be94fcdc37cc88c"
def digest(value):
if isinstance(value, bytes):
payload = value
else:
payload = value.encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def settings():
return {
"backend": "WEBGL2",
"complexity": "BOUNDED",
"renderEngine": "BLENDER_EEVEE",
"resolutionPercentage": 100,
"resolutionX": 64,
"resolutionY": 64,
"samples": 1,
}
def route(name, target, status, capability, reason, code=None):
result = {
"name": name,
"target": target,
"status": status,
"capability": capability,
"reason": reason,
}
if code:
result["code"] = code
return result
def receipt():
render_settings = settings()
return {
"schemaVersion": 1,
"receiptId": "workflow:render_capability_routing",
"source": {
"kind": "blend",
"identifier": "WBV2-P5-001-RENDER.ROUTING",
"sha256": digest("blend:WBV2-P5-001-RENDER.ROUTING"),
},
"settings": {
**render_settings,
"sha256": digest(json.dumps(render_settings, sort_keys=True, separators=(",", ":"))),
},
"build": {"version": "5.2.0", "buildSha256": WASM_SHA256},
"routes": [
route("boundedEevee", "WEB_LOCAL_BOUNDED", "READY", "WEB_REALTIME_BOUNDED", "BOUNDED_EEVEE"),
route("cycles", "SERVER_JOB", "BLOCKED", "CYCLES_SERVER_RENDER", "CYCLES_REQUIRES_SERVER", "SERVER_JOB_UNAVAILABLE"),
route("complexEevee", "SERVER_JOB", "BLOCKED", "COMPLEX_EEVEE_SERVER_RENDER", "COMPLEX_EEVEE_REQUIRES_SERVER", "SERVER_JOB_UNAVAILABLE"),
route("hardware", "SERVER_JOB", "BLOCKED", "HARDWARE_SERVER_RENDER", "HARDWARE_BACKEND_REQUIRES_SERVER", "SERVER_JOB_UNAVAILABLE"),
route("webgpu", "WEB_LOCAL_BOUNDED", "BLOCKED", "WEB_REALTIME_BOUNDED", "WEBGPU_UNAVAILABLE", "WEBGPU_RENDERER_UNAVAILABLE"),
route("unknownEngine", "SERVER_JOB", "BLOCKED", "UNSUPPORTED_RENDER_ENGINE", "UNSUPPORTED_ENGINE", "PLATFORM_CAPABILITY_UNAVAILABLE"),
],
"recovery": {
"cancellation": "OPEN_CANCELLED",
"deviceLoss": "RELEASED",
"temporaryResourcesAfter": 0,
"publishedResults": 0,
},
}
def make_fixture(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P5001RenderRoutingScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 64
scene.render.resolution_y = 64
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = "PNG"
scene.render.image_settings.color_mode = "RGBA"
scene.render.image_settings.color_depth = "8"
scene.render.film_transparent = False
scene.render.dither_intensity = 0
scene.eevee.taa_render_samples = 1
scene.view_settings.look = "None"
scene.view_settings.exposure = 0
scene.view_settings.gamma = 1
world = bpy.data.worlds.new("WBV2P5001RenderWorld")
world.use_nodes = True
world.node_tree.nodes["Background"].inputs["Color"].default_value = (0.03, 0.04, 0.06, 1)
world.node_tree.nodes["Background"].inputs["Strength"].default_value = 0.5
scene.world = world
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.object
cube.name = "WBV2P5001RenderCube"
cube.data.name = "WBV2P5001RenderMesh"
material = bpy.data.materials.new("WBV2P5001RenderMaterial")
material.use_nodes = True
principled = material.node_tree.nodes.get("Principled BSDF")
principled.inputs["Base Color"].default_value = (0.12, 0.42, 0.8, 1)
principled.inputs["Roughness"].default_value = 0.6
cube.data.materials.append(material)
camera_data = bpy.data.cameras.new("WBV2P5001RenderCamera")
camera = bpy.data.objects.new("WBV2P5001RenderCamera", camera_data)
scene.collection.objects.link(camera)
camera.location = (4.5, -4.5, 3.2)
camera.rotation_euler = (Vector((0, 0, 0)) - camera.location).to_track_quat("-Z", "Y").to_euler()
camera_data.lens = 52
scene.camera = camera
text = bpy.data.texts.new("WBV2-P5-001-RenderRoutingReceipt")
text.write(json.dumps(receipt(), sort_keys=True, separators=(",", ":")))
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P5-001.py -- OUTPUT")
make_fixture(arguments[0])

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import pathlib
import sys
import wave
import bpy
def write_silence(path):
path.parent.mkdir(parents=True, exist_ok=True)
with wave.open(str(path), "wb") as stream:
stream.setnchannels(1)
stream.setsampwidth(2)
stream.setframerate(8000)
stream.writeframes(b"\0\0" * 8000)
def main(output_path):
output = pathlib.Path(output_path).resolve()
media = output.parent / "media"
audio_path = media / "WBV2P5002-silence.wav"
image_path = media / "WBV2P5002-frame.png"
write_silence(audio_path)
bpy.ops.wm.read_factory_settings(use_empty=True)
image = bpy.data.images.new("WBV2P5002Frame", width=8, height=8, alpha=True)
image.generated_color = (0.12, 0.35, 0.8, 1.0)
image.filepath_raw = str(image_path)
image.file_format = "PNG"
image.save()
bpy.data.images.remove(image)
scene = bpy.context.scene
scene.name = "WBV2P5002MediaScene"
scene.frame_start = 1
scene.frame_end = 48
scene.frame_set(1)
scene.render.fps = 24
scene.render.fps_base = 1.0
tree = bpy.data.node_groups.new("WBV2P5002CompositorTree", "CompositorNodeTree")
scene.compositing_node_group = tree
tree.interface.new_socket(name="Image", in_out="OUTPUT", socket_type="NodeSocketColor")
color = tree.nodes.new("CompositorNodeRGB")
color.name = "WBV2P5002Constant"
color.outputs["Color"].default_value = (0.1, 0.2, 0.4, 1.0)
exposure = tree.nodes.new("CompositorNodeExposure")
exposure.name = "WBV2P5002Exposure"
exposure.inputs["Exposure"].default_value = 0.5
invert = tree.nodes.new("CompositorNodeInvert")
invert.name = "WBV2P5002Invert"
composite = tree.nodes.new("NodeGroupOutput")
composite.name = "WBV2P5002Composite"
tree.links.new(color.outputs["Color"], exposure.inputs["Image"])
tree.links.new(exposure.outputs["Image"], invert.inputs["Color"])
tree.links.new(invert.outputs["Color"], composite.inputs["Image"])
editor = scene.sequence_editor_create()
sound = editor.strips.new_sound("WBV2P5002Sound", str(audio_path), channel=1, frame_start=1)
sound.sound.filepath = "//media/WBV2P5002-silence.wav"
sound.mute = True
image_strip = editor.strips.new_image("WBV2P5002Image", str(image_path), channel=2, frame_start=1)
image_strip.directory = "//media/"
image_strip.elements[0].filename = "WBV2P5002-frame.png"
image_strip.frame_final_duration = 24
second = editor.strips.new_image("WBV2P5002ImageB", str(image_path), channel=3, frame_start=1)
second.directory = "//media/"
second.elements[0].filename = "WBV2P5002-frame.png"
second.frame_final_duration = 24
cross = editor.strips.new_effect("WBV2P5002Cross", "CROSS", channel=4, frame_start=1, length=24, input1=image_strip, input2=second)
cross.mute = True
output.parent.mkdir(parents=True, exist_ok=True)
bpy.ops.wm.save_as_mainfile(filepath=str(output), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P5-002.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,59 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
WASM_SHA256 = "25bc1e286fd3b72c2c7c082b85a29b2d0bb109588a6dc0de5be94fcdc37cc88c"
def digest(value):
return hashlib.sha256(value.encode("utf-8") if isinstance(value, str) else value).hexdigest()
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P5003ServerJobScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = "PNG"
scene.render.image_settings.color_mode = "RGBA"
scene.render.image_settings.color_depth = "8"
bpy.ops.mesh.primitive_cube_add(size=2, location=(0, 0, 0))
cube = bpy.context.object
cube.name = "WBV2P5003ServerCube"
cube.data.name = "WBV2P5003ServerMesh"
material = bpy.data.materials.new("WBV2P5003ServerMaterial")
material.diffuse_color = (0.2, 0.55, 0.9, 1)
cube.data.materials.append(material)
settings = {"renderEngine": "BLENDER_EEVEE", "frameStart": 1, "frameEnd": 1, "resolutionX": 32, "resolutionY": 32, "resolutionPercentage": 100, "samples": 1, "outputMime": "image/png", "transparent": False}
request_hash = digest("request:WBV2-P5-003")
result_hash = digest("result:WBV2-P5-003")
receipt = {
"schemaVersion": 1,
"receiptId": "workflow:server_job_result_binding",
"source": {"kind": "blend", "identifier": "WBV2-P5-003-SERVER.JOB_BINDING", "sha256": digest("source:WBV2-P5-003")},
"settings": {**settings, "settingsSha256": digest(json.dumps(settings, sort_keys=True, separators=(",", ":")))},
"build": {"version": "5.2.0", "buildSha256": WASM_SHA256},
"request": {"jobId": "wbv2-p5-003", "sourceRevision": 1, "requestSha256": request_hash},
"result": {"status": "SUCCEEDED", "outputMime": "image/png", "outputByteLength": 512, "outputSha256": digest("output:WBV2-P5-003"), "resultSha256": result_hash},
"lifecycle": {"duplicate": "REUSED", "stale": "BLOCKED", "cancel": "CANCELLED", "timeout": "TIMEOUT", "oom": "OOM", "publishedOnFailure": False},
}
text = bpy.data.texts.new("WBV2-P5-003-ServerJobBindingReceipt")
text.write(json.dumps(receipt, sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P5-003.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
def digest(value):
payload = value.encode("utf-8") if isinstance(value, str) else value
return hashlib.sha256(payload).hexdigest()
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P6001ScriptingSecurityScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
source = "import os\nimport socket\nopen('../escape.txt', 'w')\nprint('secret-token')"
malicious = bpy.data.texts.new("WBV2-P6-001-MaliciousScript")
malicious.write(source)
malicious.use_module = True
policy = {
"schemaVersion": 1,
"policyId": "workflow:scripting_default_deny_audit",
"defaults": {
"autorun": False,
"network": False,
"addonInstall": False,
"driverExpressions": False,
"module": False,
},
"scope": {
"dom": False,
"hostWorker": False,
"opfs": False,
"indexedDB": False,
"network": False,
},
"sandbox": {
"execution": "DISABLED",
"cancellation": "SCRIPT_SANDBOX_CANCELLED",
"dispose": "RELEASED",
"mainRevisionUnchanged": True,
"publishedResults": 0,
"temporaryBytes": 0,
},
"audit": {
"decision": "DENY",
"approvedKey": False,
"reason": "SCRIPT_SIGNATURE_INVALID",
"requestId": "script-audit:WBV2-P6-001",
},
"boundaries": {
"secretAccess": "BLOCKED",
"filesystemEscape": "BLOCKED",
"network": "BLOCKED",
},
"budget": {
"maxSourceBytes": 1024 * 1024,
"maxScripts": 64,
"maxAuditEntries": 256,
},
}
policy["policyHash"] = digest(json.dumps(policy, sort_keys=True, separators=(",", ":")))
receipt = bpy.data.texts.new("WBV2-P6-001-ScriptingPolicyReceipt")
receipt.write(json.dumps(policy, sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P6-001.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
def digest(value):
payload = value.encode("utf-8") if isinstance(value, str) else value
return hashlib.sha256(payload).hexdigest()
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P6002PerformanceFaultScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
bpy.ops.mesh.primitive_cube_add(size=2)
cube = bpy.context.object
cube.name = "WBV2P6002RecoveryCube"
cube.data.name = "WBV2P6002RecoveryMesh"
thresholds = [
{"id": "geometry-100k", "domain": "geometry", "limit": 100000, "unit": "triangles", "status": "PASS"},
{"id": "geometry-1m", "domain": "geometry", "limit": 1000000, "unit": "triangles", "status": "PASS"},
{"id": "geometry-10m", "domain": "geometry", "limit": 10000000, "unit": "triangles", "status": "PASS"},
{"id": "texture-4k", "domain": "texture", "limit": 4096, "unit": "pixels", "status": "PASS"},
{"id": "texture-8k", "domain": "texture", "limit": 8192, "unit": "pixels", "status": "PASS"},
{"id": "long-media", "domain": "media", "limit": 600, "unit": "seconds", "status": "PASS"},
{"id": "simulation-cache", "domain": "simulation", "limit": 17179869184, "unit": "bytes", "status": "PASS"},
]
faults = {
"oom": {"status": "BLOCKED", "code": "WASM_OUT_OF_MEMORY", "publishedResults": 0},
"quota": {"status": "BLOCKED", "code": "STORAGE_QUOTA", "publishedResults": 0},
"deviceLoss": {"status": "BLOCKED", "code": "GPU_DEVICE_LOSS_BLOCKED", "publishedResults": 0},
"networkInterrupt": {"status": "BLOCKED", "code": "NETWORK_INTERRUPT_BLOCKED", "publishedResults": 0},
}
matrix = {
"schemaVersion": 1,
"receiptId": "workflow:performance_fault_recovery_matrix",
"thresholds": thresholds,
"faults": faults,
"recovery": {"oldRevisionUsable": True, "revisionBefore": 1, "revisionAfter": 1, "resourcesAfter": 0, "cache": "QUARANTINED", "workerRestart": "RECOVERED"},
"budget": {"maxGeometryTriangles": 10000000, "maxTextureBytes": 536870912, "maxMediaBytes": 536870912, "maxCacheBytes": 17179869184},
}
matrix["matrixHash"] = digest(json.dumps(matrix, sort_keys=True, separators=(",", ":")))
text = bpy.data.texts.new("WBV2-P6-002-PerformanceFaultMatrixReceipt")
text.write(json.dumps(matrix, sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P6-002.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
ROOT = pathlib.Path(__file__).resolve().parents[3]
def digest_file(relative):
return hashlib.sha256((ROOT / relative).read_bytes()).hexdigest()
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P6003OfflineReleaseScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
bpy.ops.mesh.primitive_cube_add(size=2)
cube = bpy.context.object
cube.name = "WBV2P6003ReleaseReceiptCube"
cube.data.name = "WBV2P6003ReleaseReceiptMesh"
package = json.loads((ROOT / "web/package.json").read_text(encoding="utf-8"))
engine = json.loads((ROOT / "web/app/public/engine-manifest.json").read_text(encoding="utf-8"))
quick = json.loads((ROOT / "release/ci-reports/quick.json").read_text(encoding="utf-8"))
if quick.get("status") != "READY":
raise RuntimeError("quick CI report must be READY before creating the release receipt")
receipt = {
"schemaVersion": 1,
"receiptId": "workflow:offline_ci_sbom_and_rollback_release",
"releaseId": engine["releaseId"],
"ci": {
"quick": {"status": quick["status"], "reportSha256": digest_file("release/ci-reports/quick.json")},
},
"gates": {
"offlineReproducibility": {"status": "READY", "binarySha256": digest_file("release/blender-web-offline.tar.gz"), "sourceSha256": digest_file("release/blender-web-corresponding-source.tar.gz")},
"sbom": {"status": "READY", "sha256": digest_file("docs/web/sbom.spdx.json")},
"rollback": {"status": "READY", "reportSha256": digest_file("release/operations-reports/rollback.json")},
},
"archives": {
"binary": {"path": "release/blender-web-offline.tar.gz", "bytes": (ROOT / "release/blender-web-offline.tar.gz").stat().st_size, "sha256": digest_file("release/blender-web-offline.tar.gz")},
"source": {"path": "release/blender-web-corresponding-source.tar.gz", "bytes": (ROOT / "release/blender-web-corresponding-source.tar.gz").stat().st_size, "sha256": digest_file("release/blender-web-corresponding-source.tar.gz")},
},
"sbom": {"path": "docs/web/sbom.spdx.json", "sha256": digest_file("docs/web/sbom.spdx.json")},
"rollback": {"runbookPath": "docs/web/RELEASE_RECOVERY.md", "runbookSha256": digest_file("docs/web/RELEASE_RECOVERY.md"), "reportPath": "release/operations-reports/rollback.json"},
"bindings": {"packageJsonSha256": digest_file("web/package.json"), "packageLockSha256": digest_file("web/package-lock.json"), "rcManifestSha256": digest_file("release/RC_MANIFEST.json"), "version": package["version"]},
}
text = bpy.data.texts.new("WBV2-P6-003-OfflineCiSbomRollbackReleaseReceipt")
text.write(json.dumps(receipt, sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P6-003.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,117 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
ROOT = pathlib.Path(__file__).resolve().parents[3]
PARITY_MAP = ROOT / "tests/golden/WBV2-P0-002/parity-map.json"
def digest_file(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def risk_score(capability):
levels = {"L0": 0, "L1": 1, "L2": 2, "L3": 3}
return (capability["legacyPending"] * 100 + capability["legacyActive"] * 50 +
capability["entryCount"] * 10 + levels[capability["targetParityLevel"]] * 1000)
def build_receipt():
parity = json.loads(PARITY_MAP.read_text(encoding="utf-8"))
if parity.get("schemaVersion") != 1 or parity.get("operation") != "BLENDER_52_CAPABILITY_PARITY_MAP":
raise RuntimeError("P0-002 parity map schema is not supported")
if parity.get("summary", {}).get("featureParityClaims") != 0 or parity["summary"].get("unknownEntries") != 0:
raise RuntimeError("P0-002 parity map contains an unsafe claim")
entries_by_family = {}
for entry in parity["entries"]:
entries_by_family.setdefault(entry["family"], []).append(entry)
clusters = []
for capability in sorted(parity["capabilities"], key=lambda value: value["capabilityId"]):
members = sorted(entries_by_family.get(capability["family"], []), key=lambda value: value["id"])
score = risk_score(capability)
band = "CRITICAL" if score >= 300000 else "HIGH" if score >= 50000 else "MEDIUM" if score >= 10000 else "LOW"
clusters.append({
"capabilityId": capability["capabilityId"],
"family": capability["family"],
"layer": capability["layer"],
"entryCount": capability["entryCount"],
"legacyCompleted": capability["legacyCompleted"],
"legacyPending": capability["legacyPending"],
"legacyActive": capability["legacyActive"],
"targetParityLevel": capability["targetParityLevel"],
"currentParityLevel": "L0",
"completionClass": "METADATA_ONLY",
"evidenceStatus": "NOT_RUN",
"webStatus": "GAP",
"riskScore": score,
"riskBand": band,
"representativeIds": [entry["id"] for entry in members[:5]],
"blockedReasons": [
"no_desktop_positive_and_wasm_mutation_evidence",
"save_reopen_parity_not_run",
],
})
summary = {
"inventoryEntries": parity["summary"]["inventoryEntries"],
"clusterCount": len(clusters),
"unknownEntries": parity["summary"]["unknownEntries"],
"completedLegacyEntries": parity["summary"]["completedLegacyEntries"],
"pendingLegacyEntries": parity["summary"]["pendingLegacyEntries"],
"activeLegacyEntries": parity["summary"]["activeLegacyEntries"],
"webGapEntries": sum(cluster["entryCount"] for cluster in clusters if cluster["currentParityLevel"] == "L0"),
"currentParityClaims": 0,
"currentParityLevel": "L0",
"byLayer": parity["summary"]["byLayer"],
}
risk_ranking = [cluster["capabilityId"] for cluster in sorted(clusters, key=lambda value: (-value["riskScore"], value["capabilityId"]))]
return {
"schemaVersion": 1,
"receiptId": "workflow:cluster_full_blender_gap_inventory",
"runtime": "BLENDER_5_2_INVENTORY",
"inventorySource": {
"path": "tests/golden/WBV2-P0-002/parity-map.json",
"sha256": digest_file(PARITY_MAP),
"entryCount": parity["summary"]["inventoryEntries"],
},
"classificationPolicy": {
"featureParityRequires": ["desktopPositive", "wasmMutation", "saveReopenExact"],
"negativeOnlyClasses": ["NEGATIVE_BOUNDARY", "UNSUPPORTED_DECLARED", "BLOCKED"],
"unprovenClass": "METADATA_ONLY",
"parityLevel": "L0",
},
"clusters": clusters,
"summary": summary,
"riskRanking": risk_ranking,
"nextTask": "WBV2-P7-002",
}
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P7001GapInventoryScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
bpy.ops.mesh.primitive_cube_add(size=2)
cube = bpy.context.object
cube.name = "WBV2P7001GapInventoryCube"
cube.data.name = "WBV2P7001GapInventoryMesh"
receipt = build_receipt()
text = bpy.data.texts.new("WBV2-P7-001-ClusterFullBlenderGapInventoryReceipt")
text.write(json.dumps(receipt, sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P7-001.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
ROOT = pathlib.Path(__file__).resolve().parents[3]
PARENT_MANIFEST = ROOT / "tests/golden/WBV2-P7-001/manifest.json"
PARENT_REPORT = ROOT / "tests/golden/WBV2-P7-001/gap-inventory-desktop-report.json"
BEHAVIORS = ("desktopPositive", "wasmMutation", "saveReopenExact")
def digest(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def task_id(capability_id, behavior):
return "WBV2-P7-002-" + capability_id.removeprefix("CAP-") + "-" + behavior.upper()
def build_receipt():
parent_manifest = json.loads(PARENT_MANIFEST.read_text(encoding="utf-8"))
parent_report = json.loads(PARENT_REPORT.read_text(encoding="utf-8"))
inventory = parent_report["state"]["receipt"]
if inventory.get("receiptId") != "workflow:cluster_full_blender_gap_inventory":
raise RuntimeError("P7-001 parent receipt is missing")
tasks = []
for cluster in sorted(inventory["clusters"], key=lambda value: value["capabilityId"]):
for behavior in BEHAVIORS:
tasks.append({
"id": task_id(cluster["capabilityId"], behavior),
"parityId": cluster["capabilityId"],
"capabilityId": cluster["capabilityId"],
"family": cluster["family"],
"behavior": behavior,
"implementationClass": "LOCAL_BOUNDED",
"parentTask": "WBV2-P7-001",
"parentReceiptSha256": digest(PARENT_REPORT),
"state": "active" if not tasks else "pending",
"ready": not tasks,
})
return {
"schemaVersion": 1,
"receiptId": "workflow:generate_atomic_family_gap_queue",
"runtime": "BLENDER_5_2_INVENTORY",
"parent": {
"task": "WBV2-P7-001",
"manifestPath": "tests/golden/WBV2-P7-001/manifest.json",
"manifestSha256": digest(PARENT_MANIFEST),
"receiptPath": "tests/golden/WBV2-P7-001/gap-inventory-desktop-report.json",
"receiptSha256": digest(PARENT_REPORT),
},
"queue": {
"activeTaskId": tasks[0]["id"],
"activeCount": 1,
"plannedCount": len(tasks) - 1,
"ordering": "capabilityId_asc_then_behavior_order",
"behaviors": list(BEHAVIORS),
"implementationClasses": ["LOCAL_BOUNDED"],
},
"tasks": tasks,
"nextTask": "WBV2-P7-003",
}
def main(output):
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P7002AtomicGapQueueScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
bpy.ops.mesh.primitive_cube_add(size=2)
cube = bpy.context.object
cube.name = "WBV2P7002AtomicGapQueueCube"
cube.data.name = "WBV2P7002AtomicGapQueueMesh"
text = bpy.data.texts.new("WBV2-P7-002-AtomicFamilyGapQueueReceipt")
text.write(json.dumps(build_receipt(), sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(pathlib.Path(output).resolve()), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P7-002.py -- OUTPUT")
main(arguments[0])

View File

@@ -0,0 +1,95 @@
#!/usr/bin/env python3
import hashlib
import json
import pathlib
import sys
import bpy
ROOT = pathlib.Path(__file__).resolve().parents[3]
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def build_receipt():
inventory = json.loads((ROOT / "tests/golden/WBV2-P7-001/gap-inventory-report.json").read_text(encoding="utf-8"))
queue = json.loads((ROOT / "tests/golden/WBV2-P7-002/atomic-queue-report.json").read_text(encoding="utf-8"))
quick = json.loads((ROOT / "release/ci-reports/quick.json").read_text(encoding="utf-8"))
drift = json.loads((ROOT / "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json").read_text(encoding="utf-8"))
if quick.get("status") != "READY" or inventory["wasm"]["status"] != "EXACT" or queue["wasm"]["status"] != "EXACT":
raise RuntimeError("release gate inputs are not ready")
return {
"schemaVersion": 1,
"receiptId": "workflow:full_parity_candidate_release_gate",
"runtime": "BLENDER_DESKTOP_WASM_MAIN",
"inputs": {
"gapInventoryReport": {"path": "tests/golden/WBV2-P7-001/gap-inventory-report.json", "sha256": sha(ROOT / "tests/golden/WBV2-P7-001/gap-inventory-report.json")},
"atomicQueueReport": {"path": "tests/golden/WBV2-P7-002/atomic-queue-report.json", "sha256": sha(ROOT / "tests/golden/WBV2-P7-002/atomic-queue-report.json")},
"quickCiReport": {"path": "release/ci-reports/quick.json", "sha256": sha(ROOT / "release/ci-reports/quick.json")},
"m12DriftEvidence": {"path": "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json", "sha256": sha(ROOT / "tests/golden/corrective/rehydration/M12-manifest-sha-drift.json")},
},
"parityClosure": {
"status": "BLOCKED",
"inventoryEntries": inventory["desktop"]["entries"],
"clusterCount": inventory["desktop"]["clusters"],
"atomicTaskCount": queue["desktop"]["tasks"],
"currentParityLevel": "L0",
"featureParityClaims": 0,
"reasonCodes": ["ATOMIC_QUEUE_PENDING", "NO_WASM_MUTATION_EVIDENCE"],
},
"evidenceHealth": {
"status": "BLOCKED",
"historicalEvidencePreserved": drift.get("historicalEvidencePreserved") is True,
"m12ProvenanceState": drift.get("provenanceState"),
"m12Entries": len(drift.get("entries", [])),
"reasonCodes": ["M12_SOURCE_LOST_SHA_DRIFT"],
},
"recoveryHealth": {
"status": "READY",
"quickCi": quick["status"],
"inventorySaveReopen": inventory["desktop"]["saveReopen"],
"queueSaveReopen": queue["desktop"]["saveReopen"],
"malformedBlend": "REJECTED_WITHOUT_MAIN_MUTATION",
},
"releaseDecision": {
"status": "BLOCKED",
"reasonCodes": ["PARITY_CLOSURE_BLOCKED", "HISTORICAL_EVIDENCE_PROVENANCE_BLOCKED"],
"nextTask": None,
},
}
def main(output):
output_path = pathlib.Path(output).resolve()
# Blender serializes process-local pointers into .blend files; never
# overwrite an existing evidence fixture during a verification rerun.
if output_path.exists():
if output_path.stat().st_size == 0:
raise RuntimeError(f"existing fixture is empty: {output_path}")
print(f"fixture-preserved path={output_path} bytes={output_path.stat().st_size} sha256={sha(output_path)}")
return
bpy.ops.wm.read_factory_settings(use_empty=True)
scene = bpy.context.scene
scene.name = "WBV2P7003ParityReleaseGateScene"
scene.render.engine = "BLENDER_EEVEE"
scene.render.resolution_x = 32
scene.render.resolution_y = 32
scene.render.resolution_percentage = 100
bpy.ops.mesh.primitive_cube_add(size=2)
cube = bpy.context.object
cube.name = "WBV2P7003ParityReleaseGateCube"
cube.data.name = "WBV2P7003ParityReleaseGateMesh"
text = bpy.data.texts.new("WBV2-P7-003-FullParityCandidateReleaseGateReceipt")
text.write(json.dumps(build_receipt(), sort_keys=True, separators=(",", ":")))
scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(filepath=str(output_path), check_existing=False, compress=True)
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python WBV2-P7-003.py -- OUTPUT")
main(arguments[0])