Complete M16 driver button add parity
This commit is contained in:
@@ -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` |
|
||||
|
||||
领取前执行:
|
||||
|
||||
|
||||
@@ -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`
|
||||
|
||||
37
docs/tasks/M16-GAP-00175.md
Normal file
37
docs/tasks/M16-GAP-00175.md
Normal 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.
|
||||
Binary file not shown.
@@ -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"
|
||||
]
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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") {
|
||||
|
||||
Reference in New Issue
Block a user