## Import a Blender-bridged graph JSON and read one node's OUTPUT SOCKETS.
##
## `import_json_probe.gd` bakes a mesh and `parity_driver.gd` builds a graph
## from a spec written by hand. Neither can answer "did this node's DATA survive
## the crossing?" -- the first only sees the result as geometry, and the second
## never goes through the serializer at all, so it would pass with the bridge
## carrying nothing.
##
## Nodes are found by TYPE, not by index: the importer assigns its own indices
## and SKIPS anything unmapped, so a Blender node id is not a proc node index.
##
## Values are flattened the same way parity_driver does, so the Python side
## compares numbers without knowing Godot's types.
##
## Usage: godot --headless --path demo --script res://import_socket_probe.gd \
## -- <graph.json> <out.json> <NodeType>
extends SceneTree
func _flatten(v: Variant) -> Array:
match typeof(v):
TYPE_QUATERNION:
return [v.x, v.y, v.z, v.w]
TYPE_VECTOR3:
return [v.x, v.y, v.z]
TYPE_COLOR:
return [v.r, v.g, v.b, v.a]
TYPE_BOOL:
return [1.0 if v else 0.0]
TYPE_INT, TYPE_FLOAT:
return [float(v)]
return []
func _init() -> void:
var a := OS.get_cmdline_user_args()
if a.size() < 3:
printerr("need <graph.json> <out.json> <NodeType>")
quit(1)
return
var g: ProcNodeGraph = GraphImporter.import_from_json(
FileAccess.get_file_as_string(a[0]))
if g == null:
printerr("import returned null")
quit(1)
return
var want := a[2]
var found := -1
for i in range(g.get_node_count()):
var n: ProcNode = g.get_node(i)
if n != null and String(n.get_node_type()) == want:
found = i
break
var result := {}
if found < 0:
# Reported rather than thrown: "the node did not survive the crossing"
# is a legitimate answer and the caller should see it as data.
result["error"] = "no %s node in the imported graph" % want
var types := []
for i in range(g.get_node_count()):
var n: ProcNode = g.get_node(i)
types.append("null" if n == null else String(n.get_node_type()))
result["types"] = types
else:
var node: ProcNode = g.get_node(found)
var vals := []
for s in range(node.get_output_count()):
vals.append_array(_flatten(g.evaluate_socket(found, s)))
result["values"] = vals
result["sockets"] = node.get_output_count()
# The node's INPUT socket types, and which of them arrived connected.
# A node whose socket TYPE depends on a property (Switch, Store Named
# Attribute) cannot be checked any other way: the link is either
# refused or silently narrowed, and both look like a value bug.
var conns: PackedInt32Array = g.get_connections_flat()
var linked_sockets := {}
for c in range(0, conns.size(), 4):
if conns[c + 2] == found:
linked_sockets[conns[c + 3]] = true
var in_types := []
var in_names := []
var in_linked := []
for s in range(node.get_input_count()):
in_types.append(node.get_input_type(s))
in_names.append(node.get_input_name(s))
in_linked.append(linked_sockets.has(s))
result["input_types"] = in_types
result["input_names"] = in_names
result["input_linked"] = in_linked
# The data as the node holds it AFTER import, so a mismatch can be told
# apart from a mis-evaluation of data that did arrive.
var props := {}
for p in node.get_property_list():
if p["usage"] & PROPERTY_USAGE_STORAGE:
var val = node.get(p["name"])
if typeof(val) == TYPE_PACKED_FLOAT32_ARRAY:
props[p["name"]] = Array(val)
elif typeof(val) == TYPE_INT or typeof(val) == TYPE_FLOAT:
props[p["name"]] = val
result["props"] = props
var f := FileAccess.open(a[1], FileAccess.WRITE)
f.store_string(JSON.stringify(result))
f.close()
quit()