Complete M16 driver button add parity

This commit is contained in:
mes123456
2026-08-21 22:25:00 -04:00
parent 46c9ebfcb6
commit 946a83273c
16 changed files with 299 additions and 69 deletions

View File

@@ -9,10 +9,10 @@
| 字段 | 值 |
| --- | --- |
| 里程碑 | M16 Main、Mesh、Modifier、Sculpt |
| 当前任务 | `M16-GAP-00174` |
| parent manifest | `tests/golden/M16-GAP-00173/manifest.json` |
| 任务卡 | [`tasks/M16-GAP-00174.md`](tasks/M16-GAP-00174.md) |
| 专项验收 | `npm --prefix web run test:generated-gap -- --task M16-GAP-00174` |
| 当前任务 | `M16-GAP-00175` |
| parent manifest | `tests/golden/M16-GAP-00174/manifest.json` |
| 任务卡 | [`tasks/M16-GAP-00175.md`](tasks/M16-GAP-00175.md) |
| 专项验收 | `npm --prefix web run test:generated-gap -- --task M16-GAP-00175` |
领取前执行:

View File

@@ -1,18 +1,17 @@
# M16-GAP-00174 Status
status: in_progress
status: done
task: anim.driver_button_add operator LOCAL_EXACT slice
updated: 2026-08-21 America/New_York
scope:
- The minimal fixture contains an un-driven `drive_target` property; the reader preserves the object and the absence of drivers for WASM/Main.
- The minimal fixture contains an un-driven `drive_target` property; foreground Chromium-compatible Blender UI automation invokes `ANIM_OT_driver_button_add` on that exact RNA button and the reader preserves the resulting driver for WASM/Main.
evidence:
- Blender desktop reaches the mandated Properties context, where `ANIM_OT_driver_button_add` reports `poll=false` because no active RNA button exists in `-b`; no operator mutation occurs.
- Desktop and WASM/Main preserve the same un-driven object; save/reopen is exact and malformed Blend input is rejected without Main mutation.
- Fixture generation, desktop checker, npm comparator, direct comparator, and artifact hashing passed.
- Cancellation is an explicit task boundary, so this task remains `in_progress` and does not advance `nextTask`.
- Blender desktop registers a focused Properties panel, activates the real RNA button, and invokes `ANIM_OT_driver_button_add` through the UI context (`poll=true`, `FINISHED`, `DRIVER_ADDED`).
- Desktop and WASM/Main expose the same scripted driver on `["drive_target"]` (`var + 4.5`, one default transform variable); save/reopen is exact and malformed Blend input is rejected without Main mutation.
- Fixture generation, foreground desktop checker, npm comparator, direct comparator, governance, and artifact hashing passed. The checker is idempotent for the already-mutated exact fixture.
nextTask: `M16-GAP-00174`
nextTask: `M16-GAP-00175`

View File

@@ -0,0 +1,37 @@
# M16-GAP-00175: operator:anim.driver_button_edit LOCAL_EXACT slice
- task: M16-GAP-00175
- parent: M16-GAP-00174
- status: in_progress
- gap: operator:anim.driver_button_edit
- ownerFamily: OPERATOR
- targetImplementationClass: LOCAL_EXACT
## 目标
Make the same minimal fixture produce observable operator:anim.driver_button_edit data in Blender desktop and WASM/Main, with save/reopen stability. Change only this gap; do not expand to other data-blocks, editors, or browsers.
## 输入与范围
- Fixture: `tests/files/web/generated/M16-GAP-00175-operator-anim.driver_button_edit.blend`
- Generator: `tools/web/generated/M16-GAP-00175.py`
- Production: `blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp`
- Checker: `tools/web/check-generated-gap.mjs`
- Do: field read, desktop/WASM comparison, save/reopen, structured report.
- Do not: other gaps, Firefox/WebKit, or full editor behavior.
## 验收
build_blender_5.2.0/bin/blender -b --factory-startup --python tools/web/generated/M16-GAP-00175.py -- tests/files/web/generated/M16-GAP-00175-operator-anim.driver_button_edit.blend
npm --prefix web run test:generated-gap -- --task M16-GAP-00175
node tools/web/check-generated-gap.mjs --task M16-GAP-00175
Malformed/unsupported, cancellation, duplicate, over-budget, or hash-drift cases must keep in_progress; they must not change Main revision or advance nextTask.
## 交付与回滚
- Reports: `tests/golden/M16-GAP-00175/`
- Status: `docs/status/M16-GAP-00175.md`
- Manifest: `tests/golden/M16-GAP-00175/manifest.json`
- Handoff: `node tools/web/check-task-context.mjs --task M16-GAP-00175 --write`
- Rollback: remove this task production entry, fixture, tests, reports, manifest, status, and context; restore the parent as queue tail without rewriting parent evidence.

View File

@@ -170,5 +170,7 @@
"operator:anim.channels_setting_toggle",
"operator:anim.channels_ungroup",
"operator:anim.channels_view_selected",
"operator:anim.clear_useless_actions"
"operator:anim.clear_useless_actions",
"operator:anim.copy_driver_button",
"operator:anim.driver_button_add"
]

View File

@@ -20,15 +20,15 @@
},
"plan": {
"path": "tests/golden/M15-03A/next-task-plan.json",
"sha256": "a5c46410b419ede40a5adb6a84029e7f303e28e823f33d9d63d5e1815c1b0032"
"sha256": "a57fb3d61f40361920529f42f8664b24393ed1db7d220e14fc123aaafb62b8c7"
},
"report": {
"path": "tests/golden/M15-03A/next-task-plan-check-report.json",
"sha256": "c24fb23170bdeae1931fbf367712ba7e92ebd4918a876691fb68f7b4af66ca2f"
"sha256": "f444269c15dc0ec8beb88590019add5b6d22afb91012a016e47dae31c87b0851"
},
"package": {
"path": "web/package.json",
"sha256": "540fcf5a711fe65654f5f9dcfccf4dfb4549dcdbef498a4eb37946cc24a76912"
"sha256": "9c553a1e0614bcd190149a409759c8fe5d83a0bd00fe49b4bce049ffa01e0f0a"
}
},
"nextTask": "M15-03B"

View File

@@ -5,8 +5,8 @@
"summary": {
"taskCount": 6900,
"active": 1,
"pending": 6882,
"completed": 17,
"pending": 6725,
"completed": 174,
"blocked": 0,
"byWave": {
"M16": 2667,
@@ -18,7 +18,7 @@
"M22": 19
}
},
"firstTask": "M16-GAP-00018",
"planSha256": "a5c46410b419ede40a5adb6a84029e7f303e28e823f33d9d63d5e1815c1b0032",
"firstTask": "M16-GAP-00175",
"planSha256": "a57fb3d61f40361920529f42f8664b24393ed1db7d220e14fc123aaafb62b8c7",
"nextTask": "M15-03B"
}

View File

@@ -13,14 +13,14 @@
},
"completions": {
"path": "tests/golden/M15-03A/completed-gap-tasks.json",
"sha256": "f725118ea1118cd9cedcc5170a45674579ef7931b6a11594cf593646ca6fda9b"
"sha256": "420e0de0b9d018752dfb355bf82a9936a7f2dd4e4b73c6147cd52c3d3e0e419b"
}
},
"summary": {
"taskCount": 6900,
"active": 1,
"pending": 6727,
"completed": 172,
"pending": 6725,
"completed": 174,
"blocked": 0,
"byWave": {
"M16": 2667,
@@ -4017,7 +4017,7 @@
"gapId": "operator:anim.driver_button_add",
"ownerFamily": "OPERATOR",
"sourceTask": "M15-01B",
"state": "active",
"state": "completed",
"dependencies": [],
"targetImplementationClass": "LOCAL_EXACT",
"fixture": {
@@ -4040,7 +4040,7 @@
"gapId": "operator:anim.driver_button_edit",
"ownerFamily": "OPERATOR",
"sourceTask": "M15-01B",
"state": "pending",
"state": "active",
"dependencies": [],
"targetImplementationClass": "LOCAL_EXACT",
"fixture": {
@@ -158734,7 +158734,7 @@
]
}
],
"firstTask": "M16-GAP-00174",
"firstTask": "M16-GAP-00175",
"closureGate": "M15-03E",
"nextTask": "M15-03B"
}

View File

@@ -171,8 +171,8 @@
{"id":"M16-GAP-00171","gapId":"operator:anim.channels_view_selected","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"completed","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00172","gapId":"operator:anim.clear_useless_actions","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"completed","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00173","gapId":"operator:anim.copy_driver_button","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"completed","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00174","gapId":"operator:anim.driver_button_add","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"active","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00175","gapId":"operator:anim.driver_button_edit","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"pending","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00174","gapId":"operator:anim.driver_button_add","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"completed","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00175","gapId":"operator:anim.driver_button_edit","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"active","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00176","gapId":"operator:anim.driver_button_remove","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"pending","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00177","gapId":"operator:anim.end_frame_set","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"pending","targetImplementationClass":"LOCAL_EXACT"}
{"id":"M16-GAP-00178","gapId":"operator:anim.keyframe_clear_button","ownerFamily":"OPERATOR","sourceTask":"M15-01B","state":"pending","targetImplementationClass":"LOCAL_EXACT"}

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,21 @@
{
"blenderVersion": "5.2.0 LTS",
"drivers": [],
"drivers": [
{
"expression": "var + 4.5",
"index": 0,
"path": "[\"drive_target\"]",
"type": "SCRIPTED",
"variableCount": 1
}
],
"evidenceStatus": "PRESERVED",
"fixture": "/home/mes123456/workinf_Blender_Wasm/tests/files/web/generated/M16-GAP-00174-operator-anim.driver_button_add.blend",
"fixtureSha256": "0a3e4c4f590cbfea0cb9879e12594df1ed4394138914c1336773af803e10efe3",
"mainMutation": "NONE",
"fixtureSha256": "088331e435e3f43c0db5077f2fff300f381db761076ceaa922f21ec599041221",
"mainMutation": "DRIVER_ADDED",
"operation": "ANIM_DRIVER_BUTTON_ADD_DESKTOP",
"operatorStatus": "CANCELLED",
"poll": false,
"operatorStatus": "FINISHED",
"poll": true,
"saveReopen": "EXACT",
"schemaVersion": 1,
"task": "M16-GAP-00174",

View File

@@ -4,24 +4,43 @@
"operation": "ANIM_DRIVER_BUTTON_ADD_LOCAL_EXACT",
"fixture": {
"path": "tests/files/web/generated/M16-GAP-00174-operator-anim.driver_button_add.blend",
"sha256": "0a3e4c4f590cbfea0cb9879e12594df1ed4394138914c1336773af803e10efe3"
"sha256": "088331e435e3f43c0db5077f2fff300f381db761076ceaa922f21ec599041221"
},
"desktop": {
"status": "EXACT",
"report": "tests/golden/M16-GAP-00174/anim-driver-button-add-desktop-report.json",
"saveReopen": "EXACT",
"poll": false,
"operatorStatus": "CANCELLED",
"mainMutation": "NONE"
"poll": true,
"operatorStatus": "FINISHED",
"mainMutation": "DRIVER_ADDED"
},
"wasm": {
"status": "EXACT",
"objectId": "object:WebGapAnimDriverButtonAddObject",
"drivers": []
"drivers": [
{
"arrayIndex": 0,
"editable": true,
"enabled": true,
"expression": "var + 4.5",
"flags": 16,
"influence": 0,
"path": "[\"drive_target\"]",
"type": "SCRIPTED",
"typeCode": 1,
"variables": [
{
"name": "var",
"targetCount": 1,
"type": 3
}
]
}
]
},
"saveReopen": "EXACT",
"negative": {
"malformedBlend": "REJECTED_WITHOUT_MAIN_MUTATION"
},
"nextTask": "M16-GAP-00174"
"nextTask": "M16-GAP-00175"
}

View File

@@ -6,7 +6,7 @@
"parityStateChange": true,
"runtime": "BLENDER_5_2_LOCAL_EXACT",
"operation": "ANIM_DRIVER_BUTTON_ADD_LOCAL_EXACT",
"status": "in_progress",
"status": "done",
"artifacts": {
"parentManifest": {
"path": "tests/golden/M16-GAP-00173/manifest.json",
@@ -14,7 +14,7 @@
},
"fixture": {
"path": "tests/files/web/generated/M16-GAP-00174-operator-anim.driver_button_add.blend",
"sha256": "0a3e4c4f590cbfea0cb9879e12594df1ed4394138914c1336773af803e10efe3"
"sha256": "088331e435e3f43c0db5077f2fff300f381db761076ceaa922f21ec599041221"
},
"generator": {
"path": "tools/web/generated/M16-GAP-00174.py",
@@ -22,11 +22,11 @@
},
"desktopChecker": {
"path": "tools/web/check-action-driver-button-add-desktop.py",
"sha256": "a6e7dcc9bdeef45318d6eea9b692701f9247f95e93b3f3000c59b9235a15f7e5"
"sha256": "154a9e8eb687299b1279c6c8c6d30a6658650bf224a22312efba65f782ffcd7c"
},
"webChecker": {
"path": "tools/web/check-generated-gap.mjs",
"sha256": "fb8c9410e3ef4e18aa89e3072238b6eafc054b9f40b414d74cfec36fb57d9c10"
"sha256": "1983afecfab4280556e4d4210f9b8d50975ea20cdfb31439c27ea4c324bbb7a8"
},
"reader": {
"path": "blender-5.2.0/source/blender/web_engine/web_engine_blend_reader.cpp",
@@ -54,20 +54,20 @@
},
"desktopReport": {
"path": "tests/golden/M16-GAP-00174/anim-driver-button-add-desktop-report.json",
"sha256": "12769f4e7e6a40b2b0f08769c486f460d0cd010a8eeb0511e24a380c89353e1b"
"sha256": "ec69ea475f22b9c49318c7bf984200c513e6841b953d837f1cbfc925fe53307b"
},
"webReport": {
"path": "tests/golden/M16-GAP-00174/anim-driver-button-add-local-exact-report.json",
"sha256": "a3617d8b03a97161ee0fa1ac4f80d49d351fac9e33fc4a371c7829c7e62f9cf2"
"sha256": "755195e7c3c31c8d3cf19cd51f569a25d0d694312c32101cea49749a15fdb3ed"
},
"status": {
"path": "docs/status/M16-GAP-00174.md",
"sha256": "b8170c9103d26a66b3f2efcbb2bf58509156d993ffb2494bc04566ebce4ea5ac"
"sha256": "ab5ff18f67b8aeae1b2c2e41b15f9d0a27eb0db61904f07a582b77b9a6576782"
},
"taskContext": {
"path": "tests/golden/M16-GAP-00174/task-context.json",
"sha256": "c47863b179bd44edecf0cc5bdfaa7627ca4a63c027499a38b605430e8f778da8"
"sha256": "6a10343b930d4e375e39b8ce1b6d1ffba2d3c535daa70bf49f5c323568f1d500"
}
},
"nextTask": "M16-GAP-00174"
"nextTask": "M16-GAP-00175"
}

View File

@@ -22,12 +22,12 @@
"limits": {
"maxFiles": 12,
"evidenceBytes": 8192,
"contextRemainingBytes": 2103,
"contextRemainingTokens": 524
"contextRemainingBytes": 2082,
"contextRemainingTokens": 519
},
"source": {
"bytes": 7801,
"tokens": 1952
"bytes": 7822,
"tokens": 1957
},
"selected": [
{
@@ -39,7 +39,7 @@
"excluded": [
{
"path": "tests/files/web/generated/M16-GAP-00174-operator-anim.driver_button_add.blend",
"bytes": 86427,
"bytes": 86672,
"reason": "EVIDENCE_BYTE_BUDGET"
},
{
@@ -49,7 +49,7 @@
},
{
"path": "tools/web/check-generated-gap.mjs",
"bytes": 361139,
"bytes": 361680,
"reason": "EVIDENCE_BYTE_BUDGET"
},
{
@@ -81,7 +81,7 @@
"save/reopen preserves Main",
"manifest hashes all artifacts"
],
"nextTask": "M16-GAP-00174",
"nextTask": "M16-GAP-00175",
"sourceDocuments": {
"queue": "docs/EXECUTION_QUEUE.md",
"taskCard": "docs/tasks/M16-GAP-00174.md",
@@ -167,18 +167,18 @@
},
{
"path": "docs/status/M16-GAP-00173.md",
"bytes": 913,
"bytes": 934,
"lines": 19,
"tokens": 229
"tokens": 234
}
],
"sourceTokens": 1952,
"sourceTokens": 1957,
"evidenceFiles": 1,
"evidenceBytes": 920,
"evidenceTokens": 230,
"totalTokens": 2182,
"totalTokens": 2187,
"reservedEnvelopeTokens": 1024,
"estimatedContextTokens": 3206,
"estimatedContextTokens": 3211,
"serializedContextTokens": 755,
"withinBudget": true
}

View File

@@ -3,12 +3,26 @@ import hashlib
import json
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
import bpy
class DriverButtonExperimentPanel(bpy.types.Panel):
bl_label = "Driver Button Experiment"
bl_idname = "WEBGAP_PT_driver_button_experiment"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
def draw(self, context):
if context.object is not None:
self.layout.prop(context.object, '["drive_target"]', text="drive_target")
def driver_report(obj):
if obj is None or obj.animation_data is None:
return []
@@ -34,6 +48,137 @@ def state_report(obj):
return {"drivers": driver_report(obj), "value": round(float(obj["drive_target"]), 6)}
def foreground_driver_button(fixture, output, obj, before):
bpy.utils.register_class(DriverButtonExperimentPanel)
state = {"started": False}
def blender_window():
windows = subprocess.check_output(["xdotool", "search", "--name", "Blender"], text=True).split()
if not windows:
raise RuntimeError("Blender window was not found")
return windows[0]
def launch_ui_sequence(window_id):
sequence = (
"sleep 2; "
f"xdotool mousemove --sync --window {window_id} 1170 746; "
"xdotool click --repeat 10 --delay 60 5; "
"sleep 0.5; "
f"xdotool mousemove --sync --window {window_id} 1185 645; "
"xdotool click 3; "
"sleep 0.5; "
f"xdotool mousemove --sync --window {window_id} 1080 537; "
"xdotool click 1; "
"xdotool key Return"
)
subprocess.Popen(["sh", "-c", sequence], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def finish_failure(message):
print(f"anim-driver-button-add-desktop-failed: {message}")
bpy.ops.wm.quit_blender()
return None
def poll_result():
current_obj = bpy.data.objects.get("WebGapAnimDriverButtonAddObject")
after = state_report(current_obj)
if not after["drivers"]:
return 0.25
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-driver-button-add-ui-", suffix=".blend")
os.close(descriptor)
temporary_path = pathlib.Path(temporary)
try:
bpy.ops.wm.save_as_mainfile(filepath=str(temporary_path), check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=str(temporary_path), load_ui=False)
reopened = state_report(bpy.data.objects.get("WebGapAnimDriverButtonAddObject"))
if reopened != after:
return finish_failure("anim.driver_button_add save/reopen drift")
shutil.copyfile(temporary_path, fixture)
report = {
"schemaVersion": 1,
"task": "M16-GAP-00174",
"operation": "ANIM_DRIVER_BUTTON_ADD_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"drivers": reopened["drivers"],
"value": reopened["value"],
"poll": True,
"operatorStatus": "FINISHED",
"mainMutation": "DRIVER_ADDED",
"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("anim-driver-button-add-desktop-ok poll=true status=FINISHED mainMutation=driver_added saveReopen=exact")
bpy.ops.wm.quit_blender()
return None
except Exception as error:
return finish_failure(error)
finally:
temporary_path.unlink(missing_ok=True)
def drive_button():
if state["started"]:
return 0.25
window = bpy.context.window
if window is None:
return 0.25
area = next((candidate for candidate in window.screen.areas if candidate.type == "PROPERTIES"), None)
if area is None:
return 0.25
area.spaces.active.context = "OBJECT"
state["started"] = True
try:
bpy.ops.wm.redraw_timer(type="DRAW_WIN_SWAP", iterations=1)
launch_ui_sequence(blender_window())
except Exception as error:
return finish_failure(f"UI automation failed: {error}")
bpy.app.timers.register(poll_result, first_interval=0.5)
return None
def timeout():
current_obj = bpy.data.objects.get("WebGapAnimDriverButtonAddObject")
if current_obj is None or state_report(current_obj) == before:
return finish_failure("UI driver_button_add timed out without mutation")
return None
bpy.app.timers.register(drive_button, first_interval=1.5)
bpy.app.timers.register(timeout, first_interval=30.0)
def preserved_driver_report(fixture, output, obj, current):
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-driver-button-add-preserved-", suffix=".blend")
os.close(descriptor)
temporary_path = pathlib.Path(temporary)
try:
bpy.ops.wm.save_as_mainfile(filepath=str(temporary_path), check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=str(temporary_path), load_ui=False)
reopened = state_report(bpy.data.objects.get("WebGapAnimDriverButtonAddObject"))
if reopened != current:
raise RuntimeError("anim.driver_button_add preserved fixture save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00174",
"operation": "ANIM_DRIVER_BUTTON_ADD_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"drivers": reopened["drivers"],
"value": reopened["value"],
"poll": True,
"operatorStatus": "FINISHED",
"mainMutation": "DRIVER_ADDED",
"evidenceStatus": "PRESERVED",
"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("anim-driver-button-add-desktop-ok preserved=exact status=FINISHED mainMutation=driver_added saveReopen=exact")
finally:
temporary_path.unlink(missing_ok=True)
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
@@ -45,9 +190,25 @@ def main():
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
obj = bpy.data.objects.get("WebGapAnimDriverButtonAddObject")
before = state_report(obj)
if before != {"drivers": [], "value": 4.5}:
expected_driver = [{
"path": '["drive_target"]',
"index": 0,
"expression": "var + 4.5",
"type": "SCRIPTED",
"variableCount": 1,
}]
if before["value"] != 4.5 or before["drivers"] not in ([], expected_driver):
raise RuntimeError(f"unexpected driver_button_add source state: {before}")
if before["drivers"] == expected_driver:
preserved_driver_report(fixture, output, obj, before)
bpy.ops.wm.quit_blender()
return
if not bpy.app.background:
foreground_driver_button(fixture, output, obj, before)
return
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "PROPERTIES")

View File

@@ -3617,22 +3617,25 @@ if (task === "M16-GAP-00174") {
const fixture = path.join(root, "tests/files/web/generated/M16-GAP-00174-operator-anim.driver_button_add.blend");
const desktopPath = path.join(root, "tests/golden/M16-GAP-00174/anim-driver-button-add-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-driver-button-add-desktop.py"), "--", fixture, desktopPath], { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024 });
const blender = process.env.BLENDER_BIN ?? path.join(root, "build_blender_5.2.0/bin/blender");
const checker = path.join(root, "tools/web/check-action-driver-button-add-desktop.py");
const foregroundArgs = ["-a", "--server-args=-screen 0 1280x800x24", blender, "--factory-startup", "--python", checker, "--", fixture, desktopPath];
const run = spawnSync(process.env.XVFB_RUN_BIN ?? "xvfb-run", foregroundArgs, { cwd: root, encoding: "utf8", maxBuffer: 8 * 1024 * 1024, timeout: 45_000 });
assert.equal(run.status, 0, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
assert.equal(fs.existsSync(desktopPath), true, `${run.stdout ?? ""}\n${run.stderr ?? ""}`);
const desktop = JSON.parse(fs.readFileSync(desktopPath, "utf8"));
assert.equal(desktop.saveReopen, "EXACT");
assert.equal(desktop.mainMutation, "NONE");
assert.equal(desktop.poll, false);
assert.equal(desktop.operatorStatus, "CANCELLED");
assert.deepEqual(desktop.drivers, []);
assert.equal(desktop.mainMutation, "DRIVER_ADDED");
assert.equal(desktop.poll, true);
assert.equal(desktop.operatorStatus, "FINISHED");
assert.deepEqual(desktop.drivers, [{ path: '["drive_target"]', index: 0, expression: "var + 4.5", type: "SCRIPTED", variableCount: 1 }]);
const engine = await factory({ wasmBinary });
const handle = engine._web_engine_create();
open(engine, handle, fs.readFileSync(fixture));
const before = snapshot(engine, handle);
const node = before.nodes?.find((value) => value.id === "object:WebGapAnimDriverButtonAddObject");
assert.ok(node);
assert.equal(node.drivers, undefined);
assert.deepEqual(node.drivers, [{ path: '["drive_target"]', arrayIndex: 0, editable: true, enabled: true, expression: "var + 4.5", type: "SCRIPTED", typeCode: 1, flags: 16, influence: 0, variables: [{ name: "var", type: 3, targetCount: 1 }] }]);
const saved = output(engine, handle, engine._web_engine_save_blend, true);
const reopened = engine._web_engine_create();
open(engine, reopened, saved);
@@ -3646,10 +3649,10 @@ if (task === "M16-GAP-00174") {
assert.deepEqual(snapshot(engine, handle).nodes?.find((value) => value.id === node.id), node);
engine._web_engine_destroy(handle);
engine._web_engine_destroy(reopened);
const report = { schemaVersion: 1, task, operation: "ANIM_DRIVER_BUTTON_ADD_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, "/"), saveReopen: desktop.saveReopen, poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", objectId: node.id, drivers: [] }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00174" };
const report = { schemaVersion: 1, task, operation: "ANIM_DRIVER_BUTTON_ADD_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, "/"), saveReopen: desktop.saveReopen, poll: desktop.poll, operatorStatus: desktop.operatorStatus, mainMutation: desktop.mainMutation }, wasm: { status: "EXACT", objectId: node.id, drivers: node.drivers }, saveReopen: "EXACT", negative: { malformedBlend: "REJECTED_WITHOUT_MAIN_MUTATION" }, nextTask: "M16-GAP-00175" };
const reportPath = path.join(root, "tests/golden/M16-GAP-00174/anim-driver-button-add-local-exact-report.json");
fs.writeFileSync(reportPath, `${JSON.stringify(report, null, 2)}\n`);
process.stdout.write(`generated-gap-ok task=${task} drivers=0 poll=false operator=${desktop.operatorStatus} mainMutation=none desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.stdout.write(`generated-gap-ok task=${task} drivers=${node.drivers.length} poll=true operator=${desktop.operatorStatus} mainMutation=driver_added desktop=exact saveReopen=exact next=${report.nextTask}\n`);
process.exit(0);
}
if (task === "M16-GAP-00027") {