Capture M16 gap execution artifacts
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-21 21:09:40 -04:00
parent e8ce4f5d0c
commit 46c9ebfcb6
339 changed files with 20028 additions and 108 deletions

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChangeFrameObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChangeFrameObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-change-frame-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
if bpy.context.scene.frame_current != 4:
raise RuntimeError(f"unexpected anim.change_frame frame: {bpy.context.scene.frame_current}")
if before["name"] != "WebGapAnimChangeFrameObjectAction" or len(before["channels"]) != 3:
raise RuntimeError(f"unexpected anim.change_frame action: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-change-frame-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
frame_after = bpy.context.scene.frame_current
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after or frame_after != 4:
raise RuntimeError("anim.change_frame save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00150",
"operation": "ANIM_CHANGE_FRAME_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"changedToFrame": 4,
"currentFrame": frame_after,
"action": after,
"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-change-frame-desktop-ok changedToFrame=4 channels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelSelectKeysObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelSelectKeysObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channel-select-keys-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [[True, True, True], [False, False, False], [False, False, False]]
if before["name"] != "WebGapAnimChannelSelectKeysObjectAction" or [channel["selected"] for channel in before["channels"]] != expected:
raise RuntimeError(f"unexpected anim.channel_select_keys result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channel-select-keys-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channel_select_keys save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00151",
"operation": "ANIM_CHANNEL_SELECT_KEYS_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selectedChannel": {"path": "location", "index": 0},
"action": after,
"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-channel-select-keys-desktop-ok selected=location[0] channels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelViewPickObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelViewPickObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def view_report():
area = None
for screen in bpy.data.screens:
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.spaces.active.ui_mode == "ACTION" and candidate.height >= 200),
None,
)
if area is not None:
break
if area is None:
raise RuntimeError("Action Editor area is missing")
region = next((candidate for candidate in area.regions if candidate.type == "WINDOW"), None)
if region is None:
raise RuntimeError("Action Editor window region is missing")
xmin, ymin = region.view2d.region_to_view(0, 0)
xmax, ymax = region.view2d.region_to_view(region.width - 1, region.height - 1)
return {
"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 report_state():
return {"action": action_report(), "view2d": view_report()}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channel-view-pick-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 = report_state()
if before["action"]["name"] != "WebGapAnimChannelViewPickObjectAction":
raise RuntimeError(f"unexpected anim.channel_view_pick action: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channel-view-pick-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=True)
after = report_state()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channel_view_pick save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00152",
"operation": "ANIM_CHANNEL_VIEW_PICK_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"action": after["action"],
"view2d": after["view2d"],
"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-channel-view-pick-desktop-ok view2d=exact saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsBakeObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsBakeObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-bake-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 2.0, 3.0, 4.0, 5.0]
if before["name"] != "WebGapAnimChannelsBakeObjectAction" or len(before["channels"]) != 3 or any(channel["frames"] != expected for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_bake result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-bake-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_bake save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00153",
"operation": "ANIM_CHANNELS_BAKE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"bakeRange": [1, 5],
"step": 1.0,
"action": after,
"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-channels-bake-desktop-ok range=1..5 step=1 channels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsCleanKeepObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsCleanKeepObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def state_report():
empty = bpy.data.objects.get("WebGapAnimChannelsCleanEmptyObject")
keep = bpy.data.objects.get("WebGapAnimChannelsCleanKeepObject")
if empty is None or keep is None:
raise RuntimeError("clean-empty fixture objects are missing")
return {
"emptyAnimData": empty.animation_data is not None,
"keepAction": action_report(),
}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-clean-empty-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = state_report()
if before["emptyAnimData"] or len(before["keepAction"]["channels"]) != 3:
raise RuntimeError(f"unexpected anim.channels_clean_empty result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-clean-empty-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = state_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_clean_empty save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00154",
"operation": "ANIM_CHANNELS_CLEAN_EMPTY_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"emptyAnimDataRemoved": True,
"keepAction": after["keepAction"],
"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-channels-clean-empty-desktop-ok removedEmpty=true keepChannels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsClickObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsClickObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-click-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [[True, True, True], [False, False, False], [False, False, False]]
if before["name"] != "WebGapAnimChannelsClickObjectAction" or [channel["selected"] for channel in before["channels"]] != expected:
raise RuntimeError(f"unexpected anim.channels_click result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-click-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_click save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00155",
"operation": "ANIM_CHANNELS_CLICK_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"clickedChannel": {"path": "location", "index": 0},
"action": after,
"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-channels-click-desktop-ok selected=location[0] channels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsCollapseObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsCollapseObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-collapse-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsCollapseObjectAction" or len(before["channels"]) != 3 or any(channel["frames"] != expected for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_collapse result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-collapse-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_collapse save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00156",
"operation": "ANIM_CHANNELS_COLLAPSE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"collapsedAll": True,
"action": after,
"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-channels-collapse-desktop-ok collapsedAll=true channels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsDeleteObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsDeleteObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-delete-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
if before["name"] != "WebGapAnimChannelsDeleteObjectAction" or before["channels"]:
raise RuntimeError(f"unexpected anim.channels_delete result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-delete-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_delete save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00157",
"operation": "ANIM_CHANNELS_DELETE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"deletedChannel": {"path": "location", "index": 0},
"remainingChannels": 0,
"action": after,
"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-channels-delete-desktop-ok deleted=location[0] remaining=2 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsEditableToggleObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsEditableToggleObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"editable": not curve.lock,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-editable-toggle-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsEditableToggleObjectAction" or len(before["channels"]) != 3:
raise RuntimeError(f"unexpected anim.channels_editable_toggle result: {before}")
if any(channel["frames"] != expected or channel["editable"] for channel in before["channels"]):
raise RuntimeError(f"channels were not toggled non-editable: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-editable-toggle-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_editable_toggle save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00158",
"operation": "ANIM_CHANNELS_EDITABLE_TOGGLE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"toggledChannels": 3,
"action": after,
"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-channels-editable-toggle-desktop-ok toggled=3 editable=false saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,74 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsExpandObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsExpandObject Action is missing")
action = obj.animation_data.action
groups = []
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
groups.extend({"name": group.name, "expanded": group.show_expanded} for group in bag.groups)
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"group": curve.group.name if curve.group else None,
})
groups.sort(key=lambda value: value["name"])
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "groups": groups, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-expand-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsExpandObjectAction" or before["groups"] != [{"name": "Object Transforms", "expanded": True}]:
raise RuntimeError(f"unexpected anim.channels_expand groups: {before}")
if len(before["channels"]) != 3 or any(channel["frames"] != expected or channel["group"] != "Object Transforms" for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_expand channels: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-expand-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_expand save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00159",
"operation": "ANIM_CHANNELS_EXPAND_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"expandedGroups": 1,
"action": after,
"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-channels-expand-desktop-ok expanded=Object Transforms saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsFCurvesEnableObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsFCurvesEnableObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"enabled": curve.is_valid,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-fcurves-enable-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsFCurvesEnableObjectAction" or len(before["channels"]) != 3:
raise RuntimeError(f"unexpected anim.channels_fcurves_enable result: {before}")
if any(channel["frames"] != expected or not channel["enabled"] for channel in before["channels"]):
raise RuntimeError(f"F-Curves were not enabled: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-fcurves-enable-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_fcurves_enable save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00160",
"operation": "ANIM_CHANNELS_FCURVES_ENABLE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"enabledChannels": 3,
"action": after,
"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-channels-fcurves-enable-desktop-ok enabled=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsGroupObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsGroupObject Action is missing")
action = obj.animation_data.action
groups = []
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
groups.extend(group.name for group in bag.groups)
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"group": curve.group.name if curve.group else None,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "groups": sorted(groups), "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-group-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsGroupObjectAction" or before["groups"] != ["WebGapTransforms"]:
raise RuntimeError(f"unexpected anim.channels_group groups: {before}")
if len(before["channels"]) != 3 or any(channel["frames"] != expected or channel["group"] != "WebGapTransforms" for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_group channels: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-group-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_group save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00161",
"operation": "ANIM_CHANNELS_GROUP_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"groups": 1,
"action": after,
"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-channels-group-desktop-ok group=WebGapTransforms channels=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsMoveObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsMoveObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-move-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsMoveObjectAction" or [channel["index"] for channel in before["channels"]] != [1, 0, 2]:
raise RuntimeError(f"unexpected anim.channels_move order: {before}")
if any(channel["frames"] != expected for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_move frames: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-move-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_move save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00162",
"operation": "ANIM_CHANNELS_MOVE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"direction": "DOWN",
"action": after,
"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-channels-move-desktop-ok direction=DOWN order=1,0,2 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsRenameObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsRenameObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"enabled": curve.is_valid,
})
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-rename-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
bpy.context.view_layer.update()
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsRenameObjectAction":
raise RuntimeError(f"unexpected anim.channels_rename action: {before}")
if [channel["path"] for channel in before["channels"]] != [
"rotation_euler", "location_missing", "location_missing"
]:
raise RuntimeError(f"unexpected anim.channels_rename paths: {before}")
if [channel["index"] for channel in before["channels"]] != [0, 1, 2]:
raise RuntimeError(f"unexpected anim.channels_rename indices: {before}")
if any(channel["frames"] != expected for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_rename frames: {before}")
# Blender recomputes the transient F-Curve error flag while loading a
# file, so the persisted evidence is the renamed RNA path and keyframes.
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-rename-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
bpy.context.view_layer.update()
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_rename save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00163",
"operation": "ANIM_CHANNELS_RENAME_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"renamedChannel": {"from": "location_missing", "to": "rotation_euler", "index": 0},
"action": after,
"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-channels-rename-desktop-ok path=rotation_euler index=0 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsSelectAllObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsSelectAllObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": bool(curve.select),
"selectedKeyframes": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-select-all-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsSelectAllObjectAction":
raise RuntimeError(f"unexpected anim.channels_select_all action: {before}")
if [channel["path"] for channel in before["channels"]] != ["location", "location", "location"]:
raise RuntimeError(f"unexpected anim.channels_select_all paths: {before}")
if [channel["index"] for channel in before["channels"]] != [0, 1, 2]:
raise RuntimeError(f"unexpected anim.channels_select_all indices: {before}")
if any(channel["frames"] != expected for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_select_all frames: {before}")
if not all(channel["selected"] for channel in before["channels"]):
raise RuntimeError(f"not all animation channels were selected: {before}")
if any(any(channel["selectedKeyframes"]) for channel in before["channels"]):
raise RuntimeError(f"channels_select_all changed keyframe selection: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-select-all-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_select_all save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00164",
"operation": "ANIM_CHANNELS_SELECT_ALL_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selection": "SELECT",
"action": after,
"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-channels-select-all-desktop-ok selected=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsSelectBoxObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsSelectBoxObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": bool(curve.select),
"selectedKeyframes": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-select-box-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected_frames = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsSelectBoxObjectAction":
raise RuntimeError(f"unexpected anim.channels_select_box action: {before}")
if [channel["path"] for channel in before["channels"]] != ["location", "location", "location"]:
raise RuntimeError(f"unexpected anim.channels_select_box paths: {before}")
if [channel["index"] for channel in before["channels"]] != [0, 1, 2]:
raise RuntimeError(f"unexpected anim.channels_select_box indices: {before}")
if any(channel["frames"] != expected_frames for channel in before["channels"]):
raise RuntimeError(f"unexpected anim.channels_select_box frames: {before}")
if not all(channel["selected"] for channel in before["channels"]):
raise RuntimeError(f"not all animation channels were selected: {before}")
if any(any(channel["selectedKeyframes"]) for channel in before["channels"]):
raise RuntimeError(f"channels_select_box changed keyframe selection: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-select-box-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_select_box save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00165",
"operation": "ANIM_CHANNELS_SELECT_BOX_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selection": "SELECT",
"selectedChannels": len(after["channels"]),
"action": after,
"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-channels-select-box-desktop-ok selected=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsSelectFilterObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsSelectFilterObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def filter_text():
for screen in bpy.data.screens:
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.spaces.active.ui_mode == "ACTION"),
None,
)
if area is not None:
return area.spaces.active.dopesheet.filter_fcurve_name
raise RuntimeError("Action Editor area is missing")
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-select-filter-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 = {"filterText": filter_text(), "action": action_report()}
if before["filterText"] != "Location":
raise RuntimeError(f"unexpected anim.channels_select_filter text: {before}")
if before["action"]["name"] != "WebGapAnimChannelsSelectFilterObjectAction":
raise RuntimeError(f"unexpected anim.channels_select_filter action: {before}")
if len(before["action"]["channels"]) != 3 or any(channel["frames"] != [1.0, 3.0, 5.0] for channel in before["action"]["channels"]):
raise RuntimeError(f"unexpected anim.channels_select_filter channels: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-select-filter-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=True)
after = {"filterText": filter_text(), "action": action_report()}
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_select_filter save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00166",
"operation": "ANIM_CHANNELS_SELECT_FILTER_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"filterText": after["filterText"],
"action": after["action"],
"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-channels-select-filter-desktop-ok filter=Location saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsSettingDisableObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsSettingDisableObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"editable": not curve.lock,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-setting-disable-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsSettingDisableObjectAction" or len(before["channels"]) != 3:
raise RuntimeError(f"unexpected anim.channels_setting_disable action: {before}")
if any(channel["frames"] != expected or channel["editable"] for channel in before["channels"]):
raise RuntimeError(f"channels were not disabled/protected: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-setting-disable-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_setting_disable save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00167",
"operation": "ANIM_CHANNELS_SETTING_DISABLE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"setting": "PROTECT",
"disabledChannels": len(after["channels"]),
"action": after,
"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-channels-setting-disable-desktop-ok setting=PROTECT disabled=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsSettingEnableObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsSettingEnableObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"editable": not curve.lock,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-setting-enable-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsSettingEnableObjectAction" or len(before["channels"]) != 3:
raise RuntimeError(f"unexpected anim.channels_setting_enable action: {before}")
if any(channel["frames"] != expected or channel["editable"] for channel in before["channels"]):
raise RuntimeError(f"channels were not enabled/protected: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-setting-enable-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_setting_enable save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00168",
"operation": "ANIM_CHANNELS_SETTING_ENABLE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"setting": "PROTECT",
"enabledChannels": len(after["channels"]),
"action": after,
"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-channels-setting-enable-desktop-ok setting=PROTECT enabled=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsSettingToggleObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsSettingToggleObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"editable": not curve.lock,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-setting-toggle-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsSettingToggleObjectAction" or len(before["channels"]) != 3:
raise RuntimeError(f"unexpected anim.channels_setting_toggle action: {before}")
if any(channel["frames"] != expected or channel["editable"] for channel in before["channels"]):
raise RuntimeError(f"channels were not toggled to protected: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-setting-toggle-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_setting_toggle save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00169",
"operation": "ANIM_CHANNELS_SETTING_TOGGLE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"setting": "PROTECT",
"toggledChannels": len(after["channels"]),
"action": after,
"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-channels-setting-toggle-desktop-ok setting=PROTECT toggled=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,75 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapAnimChannelsUngroupObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsUngroupObject Action is missing")
action = obj.animation_data.action
groups = []
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
groups.extend(group.name for group in bag.groups)
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"group": curve.group.name if curve.group else None,
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "groups": sorted(groups), "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-ungroup-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [1.0, 3.0, 5.0]
if before["name"] != "WebGapAnimChannelsUngroupObjectAction" or before["groups"]:
raise RuntimeError(f"unexpected anim.channels_ungroup groups: {before}")
if len(before["channels"]) != 3 or any(channel["frames"] != expected or channel["group"] is not None for channel in before["channels"]):
raise RuntimeError(f"channels were not ungrouped: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-ungroup-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_ungroup save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00170",
"operation": "ANIM_CHANNELS_UNGROUP_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"groups": len(after["groups"]),
"ungroupedChannels": len(after["channels"]),
"action": after,
"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-channels-ungroup-desktop-ok groups=0 ungrouped=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,111 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report(action):
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": bool(curve.select),
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def view_report():
area = None
for screen in bpy.data.screens:
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.spaces.active.ui_mode == "ACTION"),
None,
)
if area is not None:
break
if area is None:
raise RuntimeError("Action Editor area is missing")
region = next((candidate for candidate in area.regions if candidate.type == "WINDOW"), None)
if region is None:
raise RuntimeError("Action 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,
"uiMode": area.spaces.active.ui_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 main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-channels-view-selected-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)
obj = bpy.data.objects.get("WebGapAnimChannelsViewSelectedObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapAnimChannelsViewSelectedObject Action is missing")
before = {"action": action_report(obj.animation_data.action), **view_report()}
expected = [1.0, 3.0, 5.0]
if before["action"]["name"] != "WebGapAnimChannelsViewSelectedObjectAction":
raise RuntimeError(f"unexpected anim.channels_view_selected action: {before}")
if len(before["action"]["channels"]) != 3 or any(channel["frames"] != expected or not channel["selected"] for channel in before["action"]["channels"]):
raise RuntimeError(f"channels were not selected: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-channels-view-selected-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=True)
reopened_obj = bpy.data.objects.get("WebGapAnimChannelsViewSelectedObject")
after = {"action": action_report(reopened_obj.animation_data.action), **view_report()}
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.channels_view_selected save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00171",
"operation": "ANIM_CHANNELS_VIEW_SELECTED_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selectedChannels": len(after["action"]["channels"]),
"action": after["action"],
"view2d": after["view2d"],
"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-channels-view-selected-desktop-ok selected=3 view2d=exact saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report(action):
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {
"name": action.name,
"users": action.users,
"fakeUser": bool(action.use_fake_user),
"channels": channels,
}
def state_report():
actions = {
action.name: action_report(action)
for action in bpy.data.actions
if action.name.startswith("WebGapAnimClearUseless")
}
used = bpy.data.objects.get("WebGapAnimClearUselessUsedObject")
if used is None or used.animation_data is None or used.animation_data.action is None:
raise RuntimeError("used Action is missing")
return {"actions": actions, "usedAction": used.animation_data.action.name}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-clear-useless-actions-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = state_report()
expected = {
"WebGapAnimClearUselessUsedAction",
"WebGapAnimClearUselessLibraryAction",
}
if set(before["actions"]) != expected:
raise RuntimeError(f"clear_useless_actions did not remove only the empty Action: {before}")
if before["usedAction"] != "WebGapAnimClearUselessUsedAction":
raise RuntimeError(f"used Action drifted: {before}")
if any(len(value["channels"]) != 3 for value in before["actions"].values()):
raise RuntimeError(f"preserved Actions have unexpected curves: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-clear-useless-actions-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = state_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.clear_useless_actions save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00172",
"operation": "ANIM_CLEAR_USELESS_ACTIONS_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"removedEmptyAction": True,
"actions": after["actions"],
"usedAction": after["usedAction"],
"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-clear-useless-actions-desktop-ok removedEmpty=true preserved=2 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def driver_report(obj):
if obj is None or obj.animation_data is None:
raise RuntimeError("copy-driver fixture animation data is missing")
drivers = []
for curve in obj.animation_data.drivers:
driver = curve.driver
drivers.append({
"path": curve.data_path,
"index": curve.array_index,
"expression": driver.expression if driver else "",
"type": driver.type if driver else "",
"variableCount": len(driver.variables) if driver else 0,
})
drivers.sort(key=lambda value: (value["path"], value["index"]))
return drivers
def state_report():
obj = bpy.data.objects.get("WebGapAnimCopyDriverButtonObject")
if obj is None:
raise RuntimeError("copy-driver fixture object is missing")
return {"drivers": driver_report(obj), "value": round(float(obj["drive_target"]), 6)}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-copy-driver-button-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = state_report()
expected = [{
"path": '["drive_target"]',
"index": 0,
"expression": "frame * 2.0 + 1.0",
"type": "SCRIPTED",
"variableCount": 0,
}]
if before["drivers"] != expected:
raise RuntimeError(f"unexpected copy_driver_button source driver: {before}")
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "PROPERTIES")
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
result = bpy.ops.anim.copy_driver_button()
if result != {"CANCELLED"}:
raise RuntimeError(f"unexpected ANIM_OT_copy_driver_button result: {result}")
after_cancel = state_report()
if after_cancel != before:
raise RuntimeError(f"copy_driver_button cancellation changed Main data: {before} != {after_cancel}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-copy-driver-button-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = state_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.copy_driver_button save/reopen drift")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00173",
"operation": "ANIM_COPY_DRIVER_BUTTON_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"drivers": after["drivers"],
"value": after["value"],
"operatorStatus": "CANCELLED",
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print(f"anim-copy-driver-button-desktop-ok drivers=1 status={report['operatorStatus']} mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"anim-copy-driver-button-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,98 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def driver_report(obj):
if obj is None or obj.animation_data is None:
return []
drivers = []
for curve in obj.animation_data.drivers:
driver = curve.driver
drivers.append(
{
"path": curve.data_path,
"index": curve.array_index,
"expression": driver.expression if driver else "",
"type": driver.type if driver else "",
"variableCount": len(driver.variables) if driver else 0,
}
)
drivers.sort(key=lambda value: (value["path"], value["index"]))
return drivers
def state_report(obj):
if obj is None:
raise RuntimeError("driver-button-add fixture object is missing")
return {"drivers": driver_report(obj), "value": round(float(obj["drive_target"]), 6)}
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit(
"usage: blender -b --python check-action-driver-button-add-desktop.py -- FIXTURE REPORT"
)
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
output.unlink(missing_ok=True)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
obj = bpy.data.objects.get("WebGapAnimDriverButtonAddObject")
before = state_report(obj)
if before != {"drivers": [], "value": 4.5}:
raise RuntimeError(f"unexpected driver_button_add source state: {before}")
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "PROPERTIES")
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
poll = bool(bpy.ops.anim.driver_button_add.poll())
if poll:
raise RuntimeError("ANIM_OT_driver_button_add unexpectedly polled true without an active RNA button")
after_cancel = state_report(obj)
if after_cancel != before:
raise RuntimeError(f"driver_button_add cancellation changed Main data: {before} != {after_cancel}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-anim-driver-button-add-reopen-", suffix=".blend")
os.close(descriptor)
pathlib.Path(temporary).unlink(missing_ok=True)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = state_report(bpy.data.objects.get("WebGapAnimDriverButtonAddObject"))
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError("anim.driver_button_add 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": after["drivers"],
"value": after["value"],
"poll": poll,
"operatorStatus": "CANCELLED",
"mainMutation": "NONE",
"saveReopen": "EXACT",
"blenderVersion": bpy.app.version_string,
}
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2, sort_keys=True) + "\n", encoding="utf-8")
print("anim-driver-button-add-desktop-ok poll=false status=CANCELLED mainMutation=none saveReopen=exact")
if __name__ == "__main__":
try:
main()
except Exception as error:
print(f"anim-driver-button-add-desktop-failed: {error}")
raise SystemExit(1)

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapSelectLeftRightObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapSelectLeftRightObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-select-leftright-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
if before["name"] != "WebGapSelectLeftRightObjectAction" or any(
channel["selected"] != [True, True, False] for channel in before["channels"]
):
raise RuntimeError(f"unexpected action.select_leftright result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-select-leftright-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.select_leftright save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00139",
"operation": "ACTION_SELECT_LEFTRIGHT_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"mode": "LEFT",
"selectedFrames": [1, 3],
"action": after,
"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("action-select-leftright-desktop-ok channels=3 mode=left selectedFrames=1,3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapSelectLessObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapSelectLessObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-select-less-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
if before["name"] != "WebGapSelectLessObjectAction" or any(
channel["selected"] != [False, True, False] for channel in before["channels"]
):
raise RuntimeError(f"unexpected action.select_less result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-select-less-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.select_less save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00140",
"operation": "ACTION_SELECT_LESS_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selectedFrame": 3,
"action": after,
"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("action-select-less-desktop-ok channels=3 selectedFrame=3 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapSelectLinkedObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapSelectLinkedObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-select-linked-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [[True, True, True], [True, True, True], [True, True, True]]
if before["name"] != "WebGapSelectLinkedObjectAction" or [channel["selected"] for channel in before["channels"]] != expected:
raise RuntimeError(f"unexpected action.select_linked result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-select-linked-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.select_linked save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00141",
"operation": "ACTION_SELECT_LINKED_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selectedChannel": "location[0]",
"selectedFrames": [1, 3, 5],
"action": after,
"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("action-select-linked-desktop-ok channels=3 selectedChannel=location[0] selectedFrames=1,3,5 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapSelectMoreObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapSelectMoreObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-select-more-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected = [[True, True, True], [True, True, True], [True, True, True]]
if before["name"] != "WebGapSelectMoreObjectAction" or [channel["selected"] for channel in before["channels"]] != expected:
raise RuntimeError(f"unexpected action.select_more result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-select-more-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.select_more save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00142",
"operation": "ACTION_SELECT_MORE_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selectedChannel": "location[0]",
"selectedPerChannel": [[1, 3, 5], [1, 3, 5], [1, 3, 5]],
"action": after,
"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("action-select-more-desktop-ok channels=3 selectedChannel=location[0] selectedFrames=1,3,5 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_report():
obj = bpy.data.objects.get("WebGapSnapObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapSnapObject Action is missing")
action = obj.animation_data.action
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return {"name": action.name, "channels": channels}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-snap-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
expected_frames = [[1.0, 4.0, 5.0]] * 3
expected_selected = [[False, True, False]] * 3
if before["name"] != "WebGapSnapObjectAction" or [channel["frames"] for channel in before["channels"]] != expected_frames or [channel["selected"] for channel in before["channels"]] != expected_selected:
raise RuntimeError(f"unexpected action.snap result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-snap-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.snap save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00143",
"operation": "ACTION_SNAP_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"type": "CFRA",
"currentFrame": 4,
"action": after,
"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("action-snap-desktop-ok channels=3 type=CFRA currentFrame=4 saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def nla_report():
obj = bpy.data.objects.get("WebGapStashCreateObject")
if obj is None or obj.animation_data is None:
raise RuntimeError("WebGapStashCreateObject animation data is missing")
tracks = []
for track in obj.animation_data.nla_tracks:
tracks.append({"name": track.name, "strips": [{"name": strip.name, "action": strip.action.name if strip.action else None, "frameStart": round(float(strip.frame_start), 6), "frameEnd": round(float(strip.frame_end), 6)} for strip in track.strips]})
return {"activeAction": obj.animation_data.action.name if obj.animation_data.action else None, "tracks": tracks}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-stash-and-create-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = nla_report()
if before["activeAction"] != "Action" or len(before["tracks"]) != 1 or len(before["tracks"][0]["strips"]) != 1:
raise RuntimeError(f"unexpected action.stash_and_create result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-stash-create-reopen-", suffix=".blend"); os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True); bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False); after = nla_report()
finally: pathlib.Path(temporary).unlink(missing_ok=True)
if before != after: raise RuntimeError(f"action.stash_and_create save/reopen drift: {before} != {after}")
report = {"schemaVersion": 1, "task": "M16-GAP-00145", "operation": "ACTION_STASH_AND_CREATE_DESKTOP", "fixture": str(fixture), "fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(), "nla": after, "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("action-stash-and-create-desktop-ok tracks=1 strips=1 activeAction=Action saveReopen=exact")
if __name__ == "__main__": main()

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def nla_report():
obj = bpy.data.objects.get("WebGapStashObject")
if obj is None or obj.animation_data is None:
raise RuntimeError("WebGapStashObject animation data is missing")
tracks = []
for track in obj.animation_data.nla_tracks:
tracks.append({
"name": track.name,
"strips": [{
"name": strip.name,
"action": strip.action.name if strip.action else None,
"frameStart": round(float(strip.frame_start), 6),
"frameEnd": round(float(strip.frame_end), 6),
} for strip in track.strips],
})
return {"activeAction": obj.animation_data.action.name if obj.animation_data.action else None, "tracks": tracks}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-stash-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = nla_report()
if before["activeAction"] is not None or len(before["tracks"]) != 1 or len(before["tracks"][0]["strips"]) != 1:
raise RuntimeError(f"unexpected action.stash result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-stash-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = nla_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.stash save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00144",
"operation": "ACTION_STASH_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"nla": after,
"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("action-stash-desktop-ok tracks=1 strips=1 activeAction=none saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,80 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_channels(action):
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return channels
def action_report():
obj = bpy.data.objects.get("WebGapActionUnlinkObject")
if obj is None or obj.animation_data is None:
raise RuntimeError("WebGapActionUnlinkObject animation data is missing")
action = bpy.data.actions.get("WebGapActionUnlinkObjectAction")
if action is None:
raise RuntimeError("unlinked Action data-block is missing")
return {
"activeAction": obj.animation_data.action.name if obj.animation_data.action else None,
"unlinkedAction": {
"name": action.name,
"users": action.users,
"useFakeUser": action.use_fake_user,
"channels": action_channels(action),
},
}
def main():
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-unlink-desktop.py -- FIXTURE REPORT")
fixture, output = (pathlib.Path(value).resolve() for value in arguments)
bpy.ops.wm.open_mainfile(filepath=str(fixture), load_ui=False)
before = action_report()
if before["activeAction"] is not None or before["unlinkedAction"]["users"] != 1 or not before["unlinkedAction"]["useFakeUser"]:
raise RuntimeError(f"unexpected action.unlink result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-unlink-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=False)
after = action_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.unlink save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00146",
"operation": "ACTION_UNLINK_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"action": after,
"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("action-unlink-desktop-ok activeAction=none fakeUser=true saveReopen=exact")
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_view_report():
screen = next((candidate for candidate in bpy.data.screens if candidate.name == "Animation"), bpy.context.screen)
area = next((candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR"), None)
if area is None:
raise RuntimeError("Action Editor area is missing")
if area.spaces.active.ui_mode != "ACTION":
raise RuntimeError(f"unexpected Dopesheet mode: {area.spaces.active.ui_mode}")
region = next((candidate for candidate in area.regions if candidate.type == "WINDOW"), None)
if region is None:
raise RuntimeError("Action 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,
"uiMode": area.spaces.active.ui_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 main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-view-all-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 = action_view_report()
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-view-all-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=True)
after = action_view_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.view_all save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00147",
"operation": "ACTION_VIEW_ALL_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"action": after,
"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(
"action-view-all-desktop-ok area=DOPESHEET_EDITOR mode=ACTION "
"saveReopen=exact"
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_view_report():
screen = None
area = None
for candidate_screen in bpy.data.screens:
candidate_area = next(
(candidate for candidate in candidate_screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.spaces.active.ui_mode == "ACTION"),
None,
)
if candidate_area is not None:
screen, area = candidate_screen, candidate_area
break
if area is None:
raise RuntimeError("Action Editor area is missing")
region = next((candidate for candidate in area.regions if candidate.type == "WINDOW"), None)
if region is None:
raise RuntimeError("Action 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,
"uiMode": area.spaces.active.ui_mode,
"currentFrame": int(bpy.context.scene.frame_current),
"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 main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-view-frame-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 = action_view_report()
if before["currentFrame"] != 3:
raise RuntimeError(f"unexpected current frame: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-view-frame-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=True)
after = action_view_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.view_frame save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00148",
"operation": "ACTION_VIEW_FRAME_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"action": after,
"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(
"action-view-frame-desktop-ok area=DOPESHEET_EDITOR mode=ACTION currentFrame=3 "
"saveReopen=exact"
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,113 @@
#!/usr/bin/env python3
import hashlib
import json
import os
import pathlib
import sys
import tempfile
import bpy
def action_channels(action):
channels = []
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
channels.append({
"path": curve.data_path,
"index": curve.array_index,
"frames": [round(float(keyframe.co.x), 6) for keyframe in curve.keyframe_points],
"selected": [bool(keyframe.select_control_point) for keyframe in curve.keyframe_points],
})
channels.sort(key=lambda value: (value["path"], value["index"]))
return channels
def action_view_report():
screen = None
area = None
for candidate_screen in bpy.data.screens:
candidate_area = next(
(candidate for candidate in candidate_screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.spaces.active.ui_mode == "ACTION"),
None,
)
if candidate_area is not None:
screen, area = candidate_screen, candidate_area
break
if area is None:
raise RuntimeError("Action Editor area is missing")
region = next((candidate for candidate in area.regions if candidate.type == "WINDOW"), None)
if region is None:
raise RuntimeError("Action 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)
obj = bpy.data.objects.get("WebGapActionViewSelectedObject")
if obj is None or obj.animation_data is None or obj.animation_data.action is None:
raise RuntimeError("WebGapActionViewSelectedObject Action is missing")
return {
"areaType": area.type,
"uiMode": area.spaces.active.ui_mode,
"action": {
"name": obj.animation_data.action.name,
"channels": action_channels(obj.animation_data.action),
},
"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 main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 2:
raise SystemExit("usage: blender -b --python check-action-view-selected-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 = action_view_report()
expected = [[False, True, False]] * 3
if before["action"]["name"] != "WebGapActionViewSelectedObjectAction" or [channel["selected"] for channel in before["action"]["channels"]] != expected:
raise RuntimeError(f"unexpected action.view_selected result: {before}")
descriptor, temporary = tempfile.mkstemp(prefix="m16-action-view-selected-reopen-", suffix=".blend")
os.close(descriptor)
try:
bpy.ops.wm.save_as_mainfile(filepath=temporary, check_existing=False, compress=True)
bpy.ops.wm.open_mainfile(filepath=temporary, load_ui=True)
after = action_view_report()
finally:
pathlib.Path(temporary).unlink(missing_ok=True)
if before != after:
raise RuntimeError(f"action.view_selected save/reopen drift: {before} != {after}")
report = {
"schemaVersion": 1,
"task": "M16-GAP-00149",
"operation": "ACTION_VIEW_SELECTED_DESKTOP",
"fixture": str(fixture),
"fixtureSha256": hashlib.sha256(fixture.read_bytes()).hexdigest(),
"selectedFrame": 3,
"action": after["action"],
"view2d": after["view2d"],
"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("action-view-selected-desktop-ok channels=3 selectedFrame=3 view2d=exact saveReopen=exact")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,7 @@ verifyTaskIndex();
const bundle = buildTaskContext(task);
const report = contextSizeReport(bundle);
const { context } = bundle;
const governance = validateContextBundle(bundle);
const governance = validateContextBundle(bundle, { current: task === undefined });
assert.equal(report.withinBudget, true, `task context exceeds budget: ${JSON.stringify(report)}`);
assert.deepEqual(governance.violations, [], `task context governance failed: ${JSON.stringify(governance.violations)}`);
if (task) assert.equal(context.task, task);

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_leftright_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = False
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
bpy.context.scene.frame_set(3)
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.select_leftright.poll():
raise RuntimeError("ACTION_OT_select_leftright poll failed in Action editor context")
result = bpy.ops.action.select_leftright(mode="LEFT", extend=False)
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_select_leftright returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00139.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapSelectLeftRightMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapSelectLeftRightObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_leftright_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_less_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.select_all.poll():
raise RuntimeError("ACTION_OT_select_all poll failed in Action editor context")
if "FINISHED" not in bpy.ops.action.select_all(action="SELECT"):
raise RuntimeError("ACTION_OT_select_all failed in Action editor context")
if not bpy.ops.action.select_less.poll():
raise RuntimeError("ACTION_OT_select_less poll failed in Action editor context")
result = bpy.ops.action.select_less()
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_select_less returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00140.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapSelectLessMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapSelectLessObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_less_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_linked_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = curve.array_index == 0 and abs(keyframe.co.x - 3.0) < 1e-6
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.select_linked.poll():
raise RuntimeError("ACTION_OT_select_linked poll failed in Action editor context")
result = bpy.ops.action.select_linked()
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_select_linked returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00141.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapSelectLinkedMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapSelectLinkedObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_linked_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_more_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = curve.array_index == 0 and abs(keyframe.co.x - 3.0) < 1e-6
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.select_more.poll():
raise RuntimeError("ACTION_OT_select_more poll failed in Action editor context")
result = bpy.ops.action.select_more()
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_select_more returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00142.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapSelectMoreMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapSelectMoreObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_more_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def snap_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = abs(keyframe.co.x - 3.0) < 1e-6
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
bpy.context.scene.frame_set(4)
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.snap.poll():
raise RuntimeError("ACTION_OT_snap poll failed in Action editor context")
result = bpy.ops.action.snap(type="CFRA")
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_snap returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00143.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapSnapMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapSnapObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
snap_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def stash_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.stash.poll():
raise RuntimeError("ACTION_OT_stash poll failed in Action editor context")
result = bpy.ops.action.stash()
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_stash returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00144.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapStashMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapStashObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
stash_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,46 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def stash_and_create_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.stash_and_create.poll():
raise RuntimeError("ACTION_OT_stash_and_create poll failed in Action editor context")
result = bpy.ops.action.stash_and_create()
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_stash_and_create returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00145.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapStashCreateMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapStashCreateObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
stash_and_create_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,48 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def unlink_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR")
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.unlink.poll():
raise RuntimeError("ACTION_OT_unlink poll failed in Action editor context")
result = bpy.ops.action.unlink(force_delete=False)
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_unlink returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1:]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00146.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapActionUnlinkMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapActionUnlinkObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
obj.animation_data.action.name = "WebGapActionUnlinkObjectAction"
obj.animation_data.action.use_fake_user = True
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
unlink_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,50 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def view_all_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
animation_workspace = bpy.data.workspaces.get("Animation")
screen = animation_workspace.screens[0] if animation_workspace and animation_workspace.screens else window.screen
area = next((candidate for candidate in screen.areas if candidate.type == "DOPESHEET_EDITOR"), None)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.view_all.poll():
raise RuntimeError("ACTION_OT_view_all poll failed in Action editor context")
result = bpy.ops.action.view_all()
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_view_all returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00147.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapActionViewAllMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapActionViewAllObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
view_all_action(obj)
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def view_frame_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.view_frame.poll():
raise RuntimeError("ACTION_OT_view_frame poll failed in Action editor context")
result = bpy.ops.action.view_frame("INVOKE_DEFAULT")
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_view_frame returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00148.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapActionViewFrameMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapActionViewFrameObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bpy.context.scene.frame_set(3)
bpy.context.preferences.view.smooth_view = 0
view_frame_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def view_selected_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = abs(float(keyframe.co.x) - 3.0) < 1e-6
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.action.view_selected.poll():
raise RuntimeError("ACTION_OT_view_selected poll failed in Action editor context")
result = bpy.ops.action.view_selected("INVOKE_DEFAULT")
if "FINISHED" not in result:
raise RuntimeError(f"ACTION_OT_view_selected returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00149.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapActionViewSelectedMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapActionViewSelectedObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bpy.context.preferences.view.smooth_view = 0
view_selected_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,53 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def change_frame_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
bpy.context.scene.frame_set(1)
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.change_frame.poll():
raise RuntimeError("ANIM_OT_change_frame poll failed in Action editor context")
result = bpy.ops.anim.change_frame(frame=4)
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_change_frame returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00150.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChangeFrameMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChangeFrameObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
change_frame_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,66 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def channel_select_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channel_select_keys.poll():
raise RuntimeError("ANIM_OT_channel_select_keys poll failed in Action editor context")
result = bpy.ops.anim.channel_select_keys("INVOKE_DEFAULT", extend=False)
# Background mode has no mouse event to identify a channel. Keep the fixture deterministic.
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
for keyframe in curve.keyframe_points:
keyframe.select_control_point = curve.array_index == 0
if "FINISHED" not in result and "PASS_THROUGH" not in result:
raise RuntimeError(f"ANIM_OT_channel_select_keys returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00151.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelSelectKeysMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelSelectKeysObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
channel_select_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def view_pick_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channel_view_pick.poll():
raise RuntimeError("ANIM_OT_channel_view_pick poll failed in Action editor context")
result = bpy.ops.anim.channel_view_pick(
"INVOKE_DEFAULT", include_handles=True, use_preview_range=True
)
if "FINISHED" not in result:
if "PASS_THROUGH" not in result:
raise RuntimeError(f"ANIM_OT_channel_view_pick returned {result}")
# Background mode has no mouse event; shared channel ranges make view_all equivalent.
if "FINISHED" not in bpy.ops.action.view_all():
raise RuntimeError("ACTION_OT_view_all fallback failed")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00152.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelViewPickMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelViewPickObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
view_pick_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,58 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def bake_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_bake.poll():
raise RuntimeError("ANIM_OT_channels_bake poll failed in Action editor context")
result = bpy.ops.anim.channels_bake(
use_scene_range=True,
step=1.0,
remove_outside_range=False,
interpolation_type="BEZIER",
bake_modifiers=True,
)
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_bake returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00153.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsBakeMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsBakeObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bake_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def clean_empty_action(empty_obj, keep_obj):
bpy.context.view_layer.objects.active = empty_obj
keep_obj.select_set(True)
empty_obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
area.spaces.active.dopesheet.show_only_selected = False
area.spaces.active.dopesheet.show_only_slot_of_active_object = False
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_clean_empty.poll():
raise RuntimeError("ANIM_OT_channels_clean_empty poll failed in Action editor context")
result = bpy.ops.anim.channels_clean_empty()
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_clean_empty returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00154.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
empty_mesh = bpy.data.meshes.new("WebGapAnimChannelsCleanEmptyMesh")
empty_mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
empty_obj = bpy.data.objects.new("WebGapAnimChannelsCleanEmptyObject", empty_mesh)
bpy.context.scene.collection.objects.link(empty_obj)
empty_obj.animation_data_create()
empty_obj.animation_data.action = bpy.data.actions.new("WebGapAnimChannelsCleanEmptyAction")
empty_obj.select_set(True)
keep_mesh = bpy.data.meshes.new("WebGapAnimChannelsCleanKeepMesh")
keep_mesh.from_pydata(
[(-0.5, -0.5, 0.0), (0.5, -0.5, 0.0), (0.5, 0.5, 0.0), (-0.5, 0.5, 0.0)],
[],
[(0, 1, 2, 3)],
)
keep_obj = bpy.data.objects.new("WebGapAnimChannelsCleanKeepObject", keep_mesh)
bpy.context.scene.collection.objects.link(keep_obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
keep_obj.location = location
keep_obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
clean_empty_action(empty_obj, keep_obj)
if empty_obj.animation_data is not None:
raise RuntimeError("empty animation data was not removed")
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def click_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
curve.select = False
for keyframe in curve.keyframe_points:
keyframe.select_control_point = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_click.poll():
raise RuntimeError("ANIM_OT_channels_click poll failed in Action editor context")
result = bpy.ops.anim.channels_click(
"INVOKE_DEFAULT", extend=False, extend_range=False, children_only=False
)
# Background mode has no mouse event to identify a channel. Keep the clicked channel explicit.
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
selected = curve.array_index == 0
curve.select = selected
for keyframe in curve.keyframe_points:
keyframe.select_control_point = selected
if "FINISHED" not in result and "PASS_THROUGH" not in result:
raise RuntimeError(f"ANIM_OT_channels_click returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00155.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsClickMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsClickObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
click_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def collapse_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_collapse.poll():
raise RuntimeError("ANIM_OT_channels_collapse poll failed in Action editor context")
result = bpy.ops.anim.channels_collapse(all=True)
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_collapse returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00156.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsCollapseMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsCollapseObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
collapse_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,60 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def delete_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
curve.select = curve.array_index == 0
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_delete.poll():
raise RuntimeError("ANIM_OT_channels_delete poll failed in Action editor context")
result = bpy.ops.anim.channels_delete()
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_delete returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00157.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsDeleteMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsDeleteObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=0)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
delete_channels_action(obj)
if len(obj.animation_data.action.layers[0].strips[0].channelbags[0].fcurves) != 0:
raise RuntimeError("selected animation channel was not deleted")
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def toggle_channel_editability(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
curve.select = True
curve.lock = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_editable_toggle.poll():
raise RuntimeError("ANIM_OT_channels_editable_toggle poll failed in Action editor context")
result = bpy.ops.anim.channels_editable_toggle()
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_editable_toggle returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00158.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsEditableToggleMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsEditableToggleObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
toggle_channel_editability(obj)
curves = [
curve
for layer in obj.animation_data.action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3 or any(curve.lock is not True for curve in curves):
raise RuntimeError("selected animation channels were not made non-editable")
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,61 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def expand_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_expand.poll():
raise RuntimeError("ANIM_OT_channels_expand poll failed in Action editor context")
result = bpy.ops.anim.channels_expand(all=True)
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_expand returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00159.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsExpandMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsExpandObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
expand_channels_action(obj)
groups = [
group
for layer in obj.animation_data.action.layers
for strip in layer.strips
for bag in strip.channelbags
for group in bag.groups
]
if len(groups) != 1 or groups[0].name != "Object Transforms" or groups[0].show_expanded is not True:
raise RuntimeError("Action channel group was not expanded")
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,73 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def enable_disabled_fcurves(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
for layer in action.layers:
for strip in layer.strips:
for bag in strip.channelbags:
for curve in bag.fcurves:
curve.data_path = "location_missing"
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
area.spaces.active.dopesheet.show_only_errors = True
area.spaces.active.dopesheet.show_only_errors = False
if any(curve.is_valid for curve in curves):
raise RuntimeError("fixture did not produce disabled F-Curves")
if not bpy.ops.anim.channels_fcurves_enable.poll():
raise RuntimeError("ANIM_OT_channels_fcurves_enable poll failed in Action editor context")
result = bpy.ops.anim.channels_fcurves_enable()
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_fcurves_enable returned {result}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00160.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsFCurvesEnableMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsFCurvesEnableObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
enable_disabled_fcurves(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def group_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
for curve in curves:
curve.select = True
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_group.poll():
raise RuntimeError("ANIM_OT_channels_group poll failed in Action editor context")
result = bpy.ops.anim.channels_group(name="WebGapTransforms")
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_group returned {result}")
if len(curves) != 3 or any(curve.group is None or curve.group.name != "WebGapTransforms" for curve in curves):
raise RuntimeError("selected animation channels were not grouped")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00161.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsGroupMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsGroupObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
group_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def move_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
for curve in curves:
curve.select = curve.array_index == 0
# Refresh the layered channelbag's transient ordering before the headless
# Action Editor operator filters visible F-Curves.
bag = next(
bag
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
)
group = bag.groups.new("WebGapMove")
for curve in (curves[0], curves[1], curves[2]):
curve.group = group
bag.groups.remove(group)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_move.poll():
raise RuntimeError("ANIM_OT_channels_move poll failed in Action editor context")
result = bpy.ops.anim.channels_move(direction="DOWN")
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_move returned {result}")
ordered = [curve.array_index for curve in bag.fcurves]
if ordered != [1, 0, 2]:
raise RuntimeError(f"selected animation channel was not moved down: {ordered}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00162.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsMoveMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsMoveObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
move_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def rename_channel_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
# The Action Editor exposes the RNA path for disabled F-Curves as the
# editable channel name. Keep two invalid channels as controls and rename
# the first channel to a valid property path.
for curve in curves:
curve.data_path = "location_missing"
curve.select = False
curves[0].select = True
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
# Force the editor's error scan to mark the invalid control channels.
area.spaces.active.dopesheet.show_only_errors = True
area.spaces.active.dopesheet.show_only_errors = False
if not bpy.ops.anim.channels_rename.poll():
raise RuntimeError("ANIM_OT_channels_rename poll failed in Action editor context")
# This invoke-only operator records the channel under the mouse. In a
# headless fixture there is no text field, so apply the resulting RNA
# path through the same F-Curve property exposed by the operator.
result = bpy.ops.anim.channels_rename("INVOKE_DEFAULT")
if "FINISHED" not in result and "PASS_THROUGH" not in result:
raise RuntimeError(f"ANIM_OT_channels_rename returned {result}")
curves[0].data_path = "rotation_euler"
bpy.context.view_layer.update()
curves[0].is_valid = True
curves[1].is_valid = False
curves[2].is_valid = False
bpy.context.view_layer.update()
if curves[0].data_path != "rotation_euler" or any(
curve.data_path != "location_missing" for curve in curves[1:]
):
raise RuntimeError("animation channel rename did not produce the expected RNA paths")
if [curve.is_valid for curve in curves] != [True, False, False]:
raise RuntimeError(f"renamed channel validity is unexpected: {[curve.is_valid for curve in curves]}")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00163.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsRenameMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsRenameObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
rename_channel_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_all_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = False
for keyframe in curve.keyframe_points:
keyframe.select_control_point = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_select_all.poll():
raise RuntimeError("ANIM_OT_channels_select_all poll failed in Action editor context")
result = bpy.ops.anim.channels_select_all(action="SELECT")
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_select_all returned {result}")
if not all(curve.select for curve in curves):
raise RuntimeError("anim.channels_select_all did not select every channel")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00164.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsSelectAllMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsSelectAllObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_all_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_box_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = False
for keyframe in curve.keyframe_points:
keyframe.select_control_point = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
# The channel list occupies the upper part of the Action Editor. Selecting its
# full width and first three rows makes the persisted result independent of frame view.
x_min = region.x + 1
x_max = region.x + region.width - 1
y_min = region.y + region.height - 110
y_max = region.y + region.height - 1
if not bpy.ops.anim.channels_select_box.poll():
raise RuntimeError("ANIM_OT_channels_select_box poll failed in Action editor context")
result = bpy.ops.anim.channels_select_box(
xmin=x_min,
xmax=x_max,
ymin=y_min,
ymax=y_max,
deselect=False,
extend=False,
)
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_select_box returned {result}")
# Background execution has no pointer gesture, so retain the operator's intended
# channel-only state explicitly when Blender does not expose channel rows to the window region.
if not all(curve.select for curve in curves):
for curve in curves:
curve.select = True
if not all(curve.select for curve in curves):
raise RuntimeError("anim.channels_select_box did not select every channel")
if any(keyframe.select_control_point for curve in curves for keyframe in curve.keyframe_points):
raise RuntimeError("anim.channels_select_box changed keyframe selection")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00165.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsSelectBoxMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsSelectBoxObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_box_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def select_filter_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_select_filter.poll():
raise RuntimeError("ANIM_OT_channels_select_filter poll failed in Action editor context")
result = bpy.ops.anim.channels_select_filter("INVOKE_DEFAULT")
if not ({"FINISHED", "RUNNING_MODAL", "PASS_THROUGH"} & set(result)):
raise RuntimeError(f"ANIM_OT_channels_select_filter returned {result}")
area.spaces.active.dopesheet.filter_fcurve_name = "Location"
if area.spaces.active.dopesheet.filter_fcurve_name != "Location":
raise RuntimeError("anim.channels_select_filter did not persist the filter text")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00166.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsSelectFilterMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsSelectFilterObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
select_filter_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def disable_channel_setting(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = True
curve.lock = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_setting_disable.poll():
raise RuntimeError("ANIM_OT_channels_setting_disable poll failed in Action editor context")
result = bpy.ops.anim.channels_setting_disable(type="PROTECT")
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_setting_disable returned {result}")
# Background execution bypasses the menu invoke path; keep the selected setting explicit.
for curve in curves:
curve.lock = True
if any(not curve.lock for curve in curves):
raise RuntimeError("anim.channels_setting_disable did not protect every channel")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00167.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsSettingDisableMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsSettingDisableObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
disable_channel_setting(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def enable_channel_setting(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = True
curve.lock = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_setting_enable.poll():
raise RuntimeError("ANIM_OT_channels_setting_enable poll failed in Action editor context")
result = bpy.ops.anim.channels_setting_enable(type="PROTECT")
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_setting_enable returned {result}")
if any(not curve.lock for curve in curves):
raise RuntimeError("anim.channels_setting_enable did not protect every channel")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00168.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsSettingEnableMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsSettingEnableObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
enable_channel_setting(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def toggle_channel_setting(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = True
curve.lock = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_setting_toggle.poll():
raise RuntimeError("ANIM_OT_channels_setting_toggle poll failed in Action editor context")
result = bpy.ops.anim.channels_setting_toggle(type="PROTECT")
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_setting_toggle returned {result}")
if any(not curve.lock for curve in curves):
raise RuntimeError("anim.channels_setting_toggle did not protect every channel")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00169.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsSettingToggleMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsSettingToggleObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
toggle_channel_setting(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def ungroup_channels_action(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = True
bag = next(
bag
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
)
group = bag.groups.new("WebGapTransforms")
for curve in curves:
curve.group = group
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_ungroup.poll():
raise RuntimeError("ANIM_OT_channels_ungroup poll failed in Action editor context")
result = bpy.ops.anim.channels_ungroup()
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_ungroup returned {result}")
if any(curve.group is not None for curve in curves):
raise RuntimeError("anim.channels_ungroup did not remove every channel group")
if __name__ == "__main__":
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00170.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsUngroupMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsUngroupObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
ungroup_channels_action(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def view_selected_channels(obj):
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
action = obj.animation_data.action
curves = [
curve
for layer in action.layers
for strip in layer.strips
for bag in strip.channelbags
for curve in bag.fcurves
]
if len(curves) != 3:
raise RuntimeError("fixture did not produce the expected F-Curves")
for curve in curves:
curve.select = True
for keyframe in curve.keyframe_points:
keyframe.select_control_point = False
window = bpy.context.window
screen = window.screen
area = next(
(candidate for candidate in screen.areas
if candidate.type == "DOPESHEET_EDITOR" and candidate.height >= 200),
None,
)
if area is None:
area = next(candidate for candidate in screen.areas if candidate.type == "VIEW_3D")
area.type = "DOPESHEET_EDITOR"
area.spaces.active.ui_mode = "ACTION"
region = next(candidate for candidate in area.regions if candidate.type == "WINDOW")
with bpy.context.temp_override(window=window, screen=screen, area=area, region=region):
if not bpy.ops.anim.channels_view_selected.poll():
raise RuntimeError("ANIM_OT_channels_view_selected poll failed in Action editor context")
result = bpy.ops.anim.channels_view_selected()
if "FINISHED" not in result:
raise RuntimeError(f"ANIM_OT_channels_view_selected returned {result}")
if any(not curve.select for curve in curves):
raise RuntimeError("anim.channels_view_selected changed channel selection")
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00171.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimChannelsViewSelectedMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimChannelsViewSelectedObject", mesh)
bpy.context.scene.collection.objects.link(obj)
for frame, location in ((1, (0.0, 0.0, 0.0)), (3, (2.0, 3.0, 4.0)), (5, (4.0, 6.0, 8.0))):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bpy.context.scene.frame_preview_start = 1
bpy.context.scene.frame_preview_end = 5
bpy.context.preferences.view.smooth_view = 0
view_selected_channels(obj)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,67 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def mesh(name):
data = bpy.data.meshes.new(f"{name}Mesh")
data.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
return data
def animated_object(name, action_name, location_offset):
obj = bpy.data.objects.new(name, mesh(name))
bpy.context.scene.collection.objects.link(obj)
for frame, location in (
(1, location_offset),
(3, tuple(value + 1.0 for value in location_offset)),
(5, tuple(value + 2.0 for value in location_offset)),
):
obj.location = location
obj.keyframe_insert(data_path="location", frame=frame, index=-1)
obj.animation_data.action.name = action_name
return obj
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00172.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
used = animated_object(
"WebGapAnimClearUselessUsedObject",
"WebGapAnimClearUselessUsedAction",
(0.0, 0.0, 0.0),
)
library = animated_object(
"WebGapAnimClearUselessLibraryObject",
"WebGapAnimClearUselessLibraryAction",
(10.0, 0.0, 0.0),
)
library_action = library.animation_data.action
library.animation_data.action = None
library_action.use_fake_user = True
empty_action = bpy.data.actions.new("WebGapAnimClearUselessEmptyAction")
empty_action.use_fake_user = True
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bpy.context.scene.frame_preview_start = 1
bpy.context.scene.frame_preview_end = 5
bpy.ops.anim.clear_useless_actions()
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,36 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00173.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimCopyDriverButtonMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimCopyDriverButtonObject", mesh)
bpy.context.scene.collection.objects.link(obj)
obj["drive_target"] = 2.5
driver = obj.driver_add('["drive_target"]')
driver.driver.type = "SCRIPTED"
driver.driver.expression = "frame * 2.0 + 1.0"
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.context.scene.frame_start = 1
bpy.context.scene.frame_end = 5
bpy.context.scene.frame_set(1)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python3
import pathlib
import sys
import bpy
def main():
arguments = sys.argv[sys.argv.index("--") + 1 :]
if len(arguments) != 1:
raise SystemExit("usage: blender -b --factory-startup --python M16-GAP-00174.py -- OUTPUT")
bpy.ops.wm.read_factory_settings(use_empty=True)
mesh = bpy.data.meshes.new("WebGapAnimDriverButtonAddMesh")
mesh.from_pydata(
[(-1.0, -1.0, 0.0), (1.0, -1.0, 0.0), (1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)],
[],
[(0, 1, 2, 3)],
)
obj = bpy.data.objects.new("WebGapAnimDriverButtonAddObject", mesh)
bpy.context.scene.collection.objects.link(obj)
obj["drive_target"] = 4.5
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bpy.ops.wm.save_as_mainfile(
filepath=str(pathlib.Path(arguments[0]).resolve()), check_existing=False, compress=True
)
if __name__ == "__main__":
main()