Files
workinf_Blender_Wasm/tools/web/check-action-view-curve-in-graph-editor-desktop.py
mes123456 9f43244982
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
Continue Blender Web parity task handoff
2026-08-23 05:47:21 -04:00

158 lines
8.0 KiB
Python

#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
OBJECT_NAME = "WebGapAnimViewCurveGraphEditorObject"
ACTION_NAME = "WebGapAnimViewCurveGraphEditorAction"
PROPERTY_NAME = "curve_target"
def view_report(area):
region = next((candidate for candidate in area.regions if candidate.type == "WINDOW"), None)
if region is None:
raise RuntimeError("Graph Editor window region is missing")
view = region.view2d
xmin, ymin = view.region_to_view(0, 0)
xmax, ymax = view.region_to_view(region.width - 1, region.height - 1)
return {
"areaType": area.type,
"mode": area.spaces.active.mode,
"view2d": {
"cur": {"xmin": round(float(xmin), 6), "xmax": round(float(xmax), 6), "ymin": round(float(ymin), 6), "ymax": round(float(ymax), 6)},
"mask": {"xmin": 0, "xmax": int(region.width - 1), "ymin": 0, "ymax": int(region.height - 1)},
},
}
def action_report(obj):
action = obj.animation_data.action if obj.animation_data else None
if action is None:
raise RuntimeError("Graph Editor Action is missing")
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({"path": curve.data_path, "index": int(curve.array_index), "frames": [float(key.co.x) for key in curve.keyframe_points], "values": [float(key.co.y) for key in curve.keyframe_points], "selected": bool(curve.select)})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def state_report():
obj = bpy.data.objects.get(OBJECT_NAME)
if obj is None or obj.animation_data is None:
raise RuntimeError("view_curve_in_graph_editor fixture object is missing")
area = next((candidate for screen in bpy.data.screens for candidate in screen.areas if candidate.type == "GRAPH_EDITOR"), None)
if area is None:
raise RuntimeError("Graph Editor area is missing")
return {"selected": bool(obj.select_get()), "active": bpy.context.view_layer.objects.active == obj, "value": round(float(obj[PROPERTY_NAME]), 6), "action": action_report(obj), "view": view_report(area)}
class ViewCurvePanel(bpy.types.Panel):
bl_label = "View Curve Fixture"
bl_idname = "WEBGAP_PT_view_curve_in_graph_editor"
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, f'["{PROPERTY_NAME}"]', text=PROPERTY_NAME)
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender --factory-startup --python check-action-view-curve-in-graph-editor-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=True)
before = state_report()
expected_channel = {"path": f'["{PROPERTY_NAME}"]', "index": 0, "frames": [1.0, 10.0], "values": [-3.0, 7.0], "selected": True}
if before["action"]["name"] != ACTION_NAME or before["action"]["channels"] != [expected_channel]:
raise RuntimeError(f"unexpected source action: {before}")
def finish_report(before_state, after_state, poll, operator_status, main_mutation):
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-view-curve-graph-reopen-", 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=True)
reopened = state_report()
if reopened != after_state:
raise RuntimeError(f"view_curve_in_graph_editor save/reopen drift: {after_state} != {reopened}")
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps({"schemaVersion": 1, "task": "M16-GAP-00214", "operation": "ANIM_VIEW_CURVE_IN_GRAPH_EDITOR_DESKTOP", "fixture": str(fixture), "fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(), "before": before_state, "after": reopened, "poll": poll, "operatorStatus": operator_status, "mainMutation": main_mutation, "saveReopen": "EXACT", "blenderVersion": bpy.app.version_string}, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(f"anim-view-curve-in-graph-editor-desktop-ok poll={str(poll).lower()} status={operator_status} mainMutation={main_mutation.lower()} saveReopen=exact")
finally:
temporary_path.unlink(missing_ok=True)
bpy.utils.register_class(ViewCurvePanel)
if bpy.app.background:
try:
screen = bpy.context.screen
properties_area = next(candidate for candidate in screen.areas if candidate.type == "PROPERTIES")
properties_region = next(candidate for candidate in properties_area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=bpy.context.window, screen=screen, area=properties_area, region=properties_region):
poll = bool(bpy.ops.anim.view_curve_in_graph_editor.poll())
result = bpy.ops.anim.view_curve_in_graph_editor(all=False, isolate=False) if poll else set()
operator_status = "FINISHED" if "FINISHED" in result else "CANCELLED"
finish_report(before, state_report(), poll, operator_status, "GRAPH_VIEW_FRAMED" if operator_status == "FINISHED" else "NONE")
finally:
bpy.utils.unregister_class(ViewCurvePanel)
return
state = {"started": False, "done": False}
def blender_window():
import subprocess
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 activate_property_button(window_id):
import subprocess
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 1")
subprocess.Popen(["sh", "-c", sequence], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def poll_result():
if state["done"]:
return None
screen = bpy.context.screen
properties_area = next((candidate for candidate in screen.areas if candidate.type == "PROPERTIES"), None)
if properties_area is None:
return 0.25
properties_region = next(candidate for candidate in properties_area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=bpy.context.window, screen=screen, area=properties_area, region=properties_region):
poll = bool(bpy.ops.anim.view_curve_in_graph_editor.poll())
result = bpy.ops.anim.view_curve_in_graph_editor(all=False, isolate=False) if poll else set()
if "FINISHED" not in result:
return 0.25
state["done"] = True
after = state_report()
finish_report(before, after, poll, "FINISHED", "GRAPH_VIEW_FRAMED")
bpy.utils.unregister_class(ViewCurvePanel)
bpy.ops.wm.quit_blender()
return None
def drive():
if state["started"]:
return 0.25
state["started"] = True
activate_property_button(blender_window())
bpy.app.timers.register(poll_result, first_interval=2.5)
return None
bpy.app.timers.register(drive, first_interval=1.5)
bpy.app.timers.register(lambda: None if state["done"] else (_ for _ in ()).throw(RuntimeError("UI Graph Editor operator timed out")), first_interval=30.0)
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"anim-view-curve-in-graph-editor-desktop-failed: {error}")
raise SystemExit(1)