70 lines
3.7 KiB
Python
70 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate a native Blender GeometryNodeTree fixture for one WBV2 batch."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
NATIVE_BLENDER = ROOT / "build_blender_native/bin/blender"
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("--output", required=True)
|
|
parser.add_argument("--node", action="append", required=True)
|
|
args = parser.parse_args()
|
|
output = Path(args.output)
|
|
if output.is_absolute() or ".." in output.parts or output.suffix != ".blend" or not str(output).startswith("tests/files/web/"):
|
|
parser.error("--output must be a repository-relative .blend path under tests/files/web/")
|
|
nodes = list(dict.fromkeys(args.node))
|
|
if not nodes or any(not (node.startswith("GeometryNode") or node.startswith("FunctionNode")) or not node.replace("_", "").isalnum() for node in nodes):
|
|
parser.error("every --node must be a Blender GeometryNode or FunctionNode identifier")
|
|
if not NATIVE_BLENDER.is_file():
|
|
raise SystemExit(f"native Blender fixture generator is unavailable: {NATIVE_BLENDER.relative_to(ROOT)}")
|
|
destination = ROOT / output
|
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
script = (
|
|
"import bpy\n"
|
|
f"destination = {str(destination)!r}\n"
|
|
f"node_types = {nodes!r}\n"
|
|
"mesh = bpy.data.meshes.new('WBV2 Geometry Mesh')\n"
|
|
"mesh.from_pydata([(-1,-1,0),(1,-1,0),(0,1,0)], [], [(0,1,2)])\n"
|
|
"obj = bpy.data.objects.new('WBV2 Geometry Object', mesh)\n"
|
|
"bpy.context.collection.objects.link(obj)\n"
|
|
"modifier = obj.modifiers.new('WBV2 Geometry Nodes', 'NODES')\n"
|
|
"tree = bpy.data.node_groups.new('WBV2 Geometry Batch', 'GeometryNodeTree')\n"
|
|
"modifier.node_group = tree\n"
|
|
"tree.interface.new_socket(name='Geometry', in_out='INPUT', socket_type='NodeSocketGeometry')\n"
|
|
"tree.interface.new_socket(name='Geometry', in_out='OUTPUT', socket_type='NodeSocketGeometry')\n"
|
|
"input_node = tree.nodes.new('NodeGroupInput')\n"
|
|
"output_node = tree.nodes.new('NodeGroupOutput')\n"
|
|
"previous = input_node\n"
|
|
"for node_type in node_types:\n"
|
|
" node = tree.nodes.new(node_type)\n"
|
|
" node.name = 'WBV2 ' + node_type\n"
|
|
" geometry_input = next((socket for socket in node.inputs if socket.type == 'GEOMETRY'), None)\n"
|
|
" geometry_output = next((socket for socket in node.outputs if socket.type == 'GEOMETRY'), None)\n"
|
|
" previous_output = next((socket for socket in previous.outputs if socket.type == 'GEOMETRY'), None)\n"
|
|
" if geometry_input and previous_output: tree.links.new(previous_output, geometry_input)\n"
|
|
" previous = node\n"
|
|
"previous_output = next((socket for socket in previous.outputs if socket.type == 'GEOMETRY'), None)\n"
|
|
"if previous_output: tree.links.new(previous_output, output_node.inputs['Geometry'])\n"
|
|
"bpy.context.view_layer.objects.active = obj\n"
|
|
"obj.select_set(True)\n"
|
|
"bpy.ops.wm.save_as_mainfile(filepath=destination)\n"
|
|
)
|
|
result = subprocess.run([str(NATIVE_BLENDER), "--background", "--factory-startup", "--python-expr", script], cwd=ROOT, text=True, capture_output=True, check=False)
|
|
if result.returncode or not destination.is_file():
|
|
raise SystemExit(f"native geometry fixture generation failed ({result.returncode}):\n{result.stdout}{result.stderr}")
|
|
print(json.dumps({"status": "PASS", "fixture": output.as_posix(), "nodes": nodes, "bytes": destination.stat().st_size}))
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|