OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes / demo / import_socket_probe.gd

109 lines · gd ref:
## 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()