OverviewFilesCommitsIssuesDocsReleasesPeers

proc-nodes commits / b47f2a7

graph: a node remembers the id the authoring tool gave it
GraphImporter built a json-id -> graph-index map for connections and then threw it away, so a consumer holding an authoring id had no way to reach the node. blender-bridge's parameter deltas carry exactly that id and were passing it straight to get_node() as though it were an index -- correct only while nothing is skipped. Measured on the bridged voronoi mountain: 43 nodes serialized, SetMaterial at id 38 unmapped, 42 imported. So ids 39..42 are indices 38..41, and id 42 is past the end entirely: source id 37 -> index 37 Compare source id 38 -> index -1 (skipped, and now says so) source id 39 -> index 38 SeparateXYZ source id 42 -> index 41 CombineXYZ Before this, id 42 reached get_node(42) on a 42-node graph, and every delta after id 38 wrote the WRONG node in silence -- which this repo already has a commit about (681a3b1, "a parameter delta addressed the right node index and wrote the wrong node"). That one fixed a symptom; the id was never carried. source_id is serialized, so a graph saved as a .tres can still be addressed by the ids of the tree it came from. Baselines: op parity 770/770, golden 31/32, batch 7/8, density on the real .blend 100/100, bounded generator 10/10. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
b47f2a722523d37c7416e48302be9965f5d3ba9b
by plankatron <fleeky@gmail.com> · 2026-09-08T15:18:46.000Z

Changed files (6) +53 -0

demo/srcid_probe.gd +12 -0
……@@ -0,0 +1,12 @@
1extends SceneTree
2func _init() -> void:
3 var a := OS.get_cmdline_user_args()
4 var g: ProcNodeGraph = GraphImporter.import_from_json(FileAccess.get_file_as_string(a[0]))
5 print("nodes=", g.get_node_count())
6 for sid in [37, 38, 39, 40, 41, 42, 99]:
7 var idx: int = g.find_node_by_source_id(sid)
8 var t := "-"
9 if idx >= 0:
10 t = str(g.get_node(idx).get_node_type())
11 print(" source id %2d -> index %2d %s" % [sid, idx, t])
12 quit()
src/graph/graph_importer.cpp +4 -0
……@@ -905,6 +905,10 @@ Ref<ProcNodeGraph> GraphImporter::import_from_json(const String &p_json) {
905905 _apply_defaults(node, defaults);
906906 }
907907
908 // Keep the authoring id ON the node, not just in a local map that dies
909 // with this function. A consumer holding an id -- blender-bridge's
910 // parameter deltas do -- needs to reach the node afterwards.
911 node->set_source_id(json_id);
908912 int graph_idx = graph->add_node(node);
909913 id_to_idx[json_id] = graph_idx;
910914 }
src/graph/proc_node.cpp +5 -0
……@@ -19,6 +19,8 @@ void ProcNode::_bind_methods() {
1919 ClassDB::bind_method(D_METHOD("get_output_count"), &ProcNode::get_output_count);
2020 ClassDB::bind_method(D_METHOD("get_graph_position"), &ProcNode::get_graph_position);
2121 ClassDB::bind_method(D_METHOD("set_graph_position", "position"), &ProcNode::set_graph_position);
22 ClassDB::bind_method(D_METHOD("get_source_id"), &ProcNode::get_source_id);
23 ClassDB::bind_method(D_METHOD("set_source_id", "id"), &ProcNode::set_source_id);
2224 ClassDB::bind_method(D_METHOD("get_input_default", "index"), &ProcNode::get_input_default);
2325 ClassDB::bind_method(D_METHOD("get_input_name", "index"), &ProcNode::get_input_name);
2426 ClassDB::bind_method(D_METHOD("get_input_type", "index"), &ProcNode::get_input_type);
……@@ -28,6 +30,9 @@ void ProcNode::_bind_methods() {
2830 ClassDB::bind_method(D_METHOD("is_input_default_modified", "index"), &ProcNode::is_input_default_modified);
2931
3032 ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_position"), "set_graph_position", "get_graph_position");
33 // Serialized, so a graph saved as a .tres can still be addressed by the ids
34 // the tree it came from used.
35 ADD_PROPERTY(PropertyInfo(Variant::INT, "source_id"), "set_source_id", "get_source_id");
3136}
3237
3338} // namespace godot
src/graph/proc_node.h +13 -0
……@@ -20,6 +20,17 @@ protected:
2020 Vector<SocketDef> input_defs;
2121 Vector<SocketDef> output_defs;
2222 Vector2 graph_position;
23
24 // THE ID THE AUTHORING TOOL GAVE THIS NODE, -1 when it has none.
25 //
26 // GraphImporter builds a json-id -> graph-index map for connections and then
27 // THREW IT AWAY, so a consumer holding an authoring id had no way to reach
28 // the node. blender-bridge's parameter deltas carry exactly that id and were
29 // passing it straight to get_node() as though it were an index -- which is
30 // only correct while nothing is skipped. One unmapped node (SetMaterial)
31 // shifts every index after it, so a delta wrote the WRONG node, and an id
32 // past the end took the editor down with SIGSEGV.
33 int source_id = -1;
2334 Vector<Variant> _constructor_defaults; // Captured lazily on first set_input_default
2435
2536 void _capture_constructor_defaults() {
……@@ -71,6 +82,8 @@ public:
7182 // Graph editor position
7283 Vector2 get_graph_position() const { return graph_position; }
7384 void set_graph_position(const Vector2 &p_pos) { graph_position = p_pos; }
85 int get_source_id() const { return source_id; }
86 void set_source_id(int p_id) { source_id = p_id; }
7487
7588 // Set default value for an input socket (used by GraphImporter and serialization)
7689 void set_input_default(int p_idx, const Variant &p_value) {
src/graph/proc_node_graph.cpp +14 -0
……@@ -128,6 +128,7 @@ void ProcNodeGraph::_bind_methods() {
128128 ClassDB::bind_method(D_METHOD("evaluate_socket", "node_idx", "socket_idx"), &ProcNodeGraph::evaluate_socket);
129129 ClassDB::bind_method(D_METHOD("evaluate_density_batch", "positions"), &ProcNodeGraph::evaluate_density_batch);
130130 ClassDB::bind_method(D_METHOD("get_density_bounds"), &ProcNodeGraph::get_density_bounds);
131 ClassDB::bind_method(D_METHOD("find_node_by_source_id", "source_id"), &ProcNodeGraph::find_node_by_source_id);
131132 ClassDB::bind_method(D_METHOD("evaluate_density_at", "pos"), &ProcNodeGraph::evaluate_density_at);
132133 ClassDB::bind_static_method("ProcNodeGraph",
133134 D_METHOD("debug_phacelle_noise", "p", "dir", "freq", "offset", "normalization"),
……@@ -568,6 +569,19 @@ bool ProcNodeGraph::find_density_output(int &r_node, int &r_socket) const {
568569 return false;
569570}
570571
572int ProcNodeGraph::find_node_by_source_id(int p_source_id) const {
573 if (p_source_id < 0) {
574 return -1;
575 }
576 for (int i = 0; i < get_node_count(); i++) {
577 Ref<ProcNode> n = get_node(i);
578 if (n.is_valid() && n->get_source_id() == p_source_id) {
579 return i;
580 }
581 }
582 return -1;
583}
584
571585Dictionary ProcNodeGraph::get_density_bounds() const {
572586 Dictionary out;
573587 // The same terminal the density comes from: the graph's Group Output. Read
src/graph/proc_node_graph.h +5 -0
……@@ -185,6 +185,11 @@ public:
185185 // (hyper-games #65 NEXT 5).
186186 bool find_density_output(int &r_node, int &r_socket) const;
187187
188 // The graph index of the node the authoring tool called p_source_id, or -1.
189 // An id is NOT an index: the importer skips nodes it cannot map, so every
190 // index after a skipped one is shifted.
191 int find_node_by_source_id(int p_source_id) const;
192
188193 // THE GRAPH'S OWN EXTENT, declared beside its density.
189194 //
190195 // A Blender group interface takes any number of named, typed outputs, so a