Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer, web workflow engine and protocol, tooling, docs and task records. Large generated evidence, user-testing archives, build snapshots and imported test assets remain untracked. Co-Authored-By: Claude Code <noreply@anthropic.com>
107 lines
4.0 KiB
Python
107 lines
4.0 KiB
Python
import json
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
import bpy
|
|
|
|
|
|
def curves_of(action):
|
|
curves = []
|
|
if hasattr(action, "fcurves"):
|
|
curves.extend(action.fcurves)
|
|
for layer in action.layers:
|
|
for strip in layer.strips:
|
|
for bag in getattr(strip, "channelbags", []):
|
|
curves.extend(bag.fcurves)
|
|
return curves
|
|
|
|
|
|
def curve_data(curve):
|
|
driver = curve.driver
|
|
return {
|
|
"path": curve.data_path,
|
|
"index": curve.array_index,
|
|
"group": curve.group.name if curve.group else None,
|
|
"driver": None if driver is None else {
|
|
"type": driver.type,
|
|
"expression": driver.expression,
|
|
"variables": [variable.name for variable in driver.variables],
|
|
},
|
|
"keys": [{
|
|
"co": list(key.co),
|
|
"left": list(key.handle_left),
|
|
"right": list(key.handle_right),
|
|
"leftType": key.handle_left_type,
|
|
"rightType": key.handle_right_type,
|
|
"interpolation": key.interpolation,
|
|
} for key in curve.keyframe_points],
|
|
}
|
|
|
|
|
|
def snapshot():
|
|
result = {
|
|
"actions": [{
|
|
"name": action.name,
|
|
"curves": sorted([curve_data(curve) for curve in curves_of(action)],
|
|
key=lambda curve: (curve["path"], curve["index"])),
|
|
"legacyGroups": [group.name for group in getattr(action, "groups", [])],
|
|
"slotGroups": [{
|
|
"slot": bag.slot_handle,
|
|
"groups": [group.name for group in bag.groups],
|
|
} for layer in action.layers for strip in layer.strips
|
|
for bag in getattr(strip, "channelbags", [])],
|
|
} for action in sorted(bpy.data.actions, key=lambda item: item.name)],
|
|
"objects": [{
|
|
"name": obj.name,
|
|
"action": obj.animation_data.action.name
|
|
if obj.animation_data and obj.animation_data.action else None,
|
|
"drivers": [curve_data(curve) for curve in obj.animation_data.drivers]
|
|
if obj.animation_data else [],
|
|
"location": list(obj.location),
|
|
} for obj in sorted(bpy.data.objects, key=lambda item: item.name)],
|
|
"scenes": [{
|
|
"name": scene.name,
|
|
"frame": scene.frame_current,
|
|
"activeKeyingSet": scene.keying_sets.active.bl_label
|
|
if scene.keying_sets.active else None,
|
|
"keyingSets": [{
|
|
"name": keying_set.bl_label,
|
|
"idname": keying_set.bl_idname,
|
|
"isPathAbsolute": keying_set.is_path_absolute,
|
|
"paths": [{
|
|
"path": item.data_path,
|
|
"arrayIndex": item.array_index,
|
|
"id": item.id.name if item.id else None,
|
|
"idType": item.id_type,
|
|
} for item in keying_set.paths],
|
|
} for keying_set in scene.keying_sets],
|
|
} for scene in sorted(bpy.data.scenes, key=lambda item: item.name)],
|
|
}
|
|
frames = []
|
|
scene = bpy.context.scene
|
|
original_frame = scene.frame_current
|
|
for frame in [1, 3, 5]:
|
|
scene.frame_set(frame)
|
|
evaluated = bpy.context.evaluated_depsgraph_get()
|
|
frames.append({
|
|
"frame": frame,
|
|
"objects": [{
|
|
"name": obj.name,
|
|
"location": list(obj.evaluated_get(evaluated).location),
|
|
"rotation": list(obj.evaluated_get(evaluated).rotation_euler),
|
|
"scale": list(obj.evaluated_get(evaluated).scale),
|
|
} for obj in sorted(bpy.data.objects, key=lambda item: item.name)],
|
|
})
|
|
scene.frame_set(original_frame)
|
|
result["evaluatedSamples"] = frames
|
|
return result
|
|
|
|
|
|
request_path = Path(sys.argv[sys.argv.index("--") + 1])
|
|
request = json.loads(request_path.read_text())
|
|
result = {}
|
|
for label, filename in request["files"].items():
|
|
bpy.ops.wm.open_mainfile(filepath=str(Path(filename).resolve()))
|
|
result[label] = snapshot()
|
|
Path(request["output"]).write_text(json.dumps(result, sort_keys=True, indent=2) + "\n")
|