From 26b317eec8f6e668f043d5c8c39645b7d644c22e Mon Sep 17 00:00:00 2001 From: RodZill4 Date: Tue, 24 Jul 2018 08:04:25 +0200 Subject: [PATCH] Refactoring + added preview for selected node Also started replacing LineEdits with Spinboxes --- addons/procedural_material/graph_edit.gd | 25 ++- addons/procedural_material/node_base.gd | 9 +- addons/procedural_material/nodes/bricks.tscn | 124 +++++++------ addons/procedural_material/nodes/colorize.gd | 4 +- addons/procedural_material/nodes/material.gd | 17 +- addons/procedural_material/pm_editor.gd | 22 +-- addons/procedural_material/pm_editor.tscn | 177 +++++++++++++++++-- 7 files changed, 272 insertions(+), 106 deletions(-) diff --git a/addons/procedural_material/graph_edit.gd b/addons/procedural_material/graph_edit.gd index 69294f4..062825f 100644 --- a/addons/procedural_material/graph_edit.gd +++ b/addons/procedural_material/graph_edit.gd @@ -12,4 +12,27 @@ func get_source(node, port): func remove_node(node): for c in get_connection_list(): if c.from == node or c.to == node: - disconnect_node(c.from, c.from_port, c.to, c.to_port) \ No newline at end of file + disconnect_node(c.from, c.from_port, c.to, c.to_port) + +func generate_shader(node): + var code = "" + var file = File.new() + file.open("res://addons/procedural_material/shader_header.txt", File.READ) + code += file.get_as_text() + code += "\n" + for c in get_children(): + if c is GraphNode: + c.generated = false + c.generated_variants = [] + var src_code = node.get_shader_code("UV") + var shader_code = src_code.defs + shader_code += "void fragment() {\n" + shader_code += src_code.code + if src_code.has("rgb"): + shader_code += "COLOR = vec4("+src_code.rgb+", 1.0);\n" + else: + shader_code += "COLOR = vec4(vec3("+src_code.f+"), 1.0);\n" + shader_code += "}\n" + print("GENERATED SHADER:\n"+shader_code) + code += shader_code + return code diff --git a/addons/procedural_material/node_base.gd b/addons/procedural_material/node_base.gd index 0a456d9..3f67a84 100644 --- a/addons/procedural_material/node_base.gd +++ b/addons/procedural_material/node_base.gd @@ -15,6 +15,9 @@ func initialize_properties(object_list): if o is LineEdit: set(o.name, float(o.text)) o.connect("text_changed", self, "_on_text_changed", [ o.name ]) + elif o is SpinBox: + set(o.name, o.value) + o.connect("value_changed", self, "_on_value_changed", [ o.name ]) elif o is ColorPickerButton: set(o.name, o.color) o.connect("color_changed", self, "_on_color_changed", [ o.name ]) @@ -30,6 +33,10 @@ func _on_text_changed(new_text, variable): set(variable, float(new_text)) get_parent().get_parent().generate_shader() +func _on_value_changed(new_value, variable): + set(variable, new_value) + get_parent().get_parent().generate_shader() + func _on_color_changed(new_color, variable): set(variable, new_color) get_parent().get_parent().generate_shader() @@ -44,7 +51,7 @@ func get_source(index = 0): func get_source_f(source): var rv if source.has("rgb"): - rv = source.rgb+".r" + rv = "dot("+source.rgb+", vec3(1.0, 1.0, 1.0))" elif source.has("f"): rv = source.f else: diff --git a/addons/procedural_material/nodes/bricks.tscn b/addons/procedural_material/nodes/bricks.tscn index 32f676e..d364d2b 100644 --- a/addons/procedural_material/nodes/bricks.tscn +++ b/addons/procedural_material/nodes/bricks.tscn @@ -12,7 +12,7 @@ anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 1.0 -margin_right = 155.0 +margin_right = 171.0 margin_bottom = 165.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false @@ -45,7 +45,7 @@ anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 16.0 margin_top = 24.0 -margin_right = 138.0 +margin_right = 154.0 margin_bottom = 160.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false @@ -75,29 +75,31 @@ percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="rows" type="LineEdit" parent="GridContainer" index="1"] +[node name="rows" type="SpinBox" parent="GridContainer" index="1"] anchor_left = 0.0 anchor_top = 0.0 anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 64.0 -margin_right = 122.0 +margin_right = 138.0 margin_bottom = 24.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false -focus_mode = 2 mouse_filter = 0 -mouse_default_cursor_shape = 1 +mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -text = "6" -focus_mode = 2 -context_menu_enabled = true -placeholder_alpha = 0.6 -caret_blink = false -caret_blink_speed = 0.65 -caret_position = 0 +min_value = 1.0 +max_value = 64.0 +step = 1.0 +page = 0.0 +value = 6.0 +exp_edit = false +rounded = false +editable = true +prefix = "" +suffix = "" _sections_unfolded = [ "Caret", "Placeholder" ] [node name="Label2" type="Label" parent="GridContainer" index="2"] @@ -120,7 +122,7 @@ percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="columns" type="LineEdit" parent="GridContainer" index="3"] +[node name="columns" type="SpinBox" parent="GridContainer" index="3"] anchor_left = 0.0 anchor_top = 0.0 @@ -128,22 +130,24 @@ anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 64.0 margin_top = 28.0 -margin_right = 122.0 +margin_right = 138.0 margin_bottom = 52.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false -focus_mode = 2 mouse_filter = 0 -mouse_default_cursor_shape = 1 +mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -text = "3" -focus_mode = 2 -context_menu_enabled = true -placeholder_alpha = 0.6 -caret_blink = false -caret_blink_speed = 0.65 -caret_position = 0 +min_value = 1.0 +max_value = 64.0 +step = 1.0 +page = 0.0 +value = 3.0 +exp_edit = false +rounded = false +editable = true +prefix = "" +suffix = "" _sections_unfolded = [ "Caret", "Placeholder" ] [node name="Label3" type="Label" parent="GridContainer" index="4"] @@ -166,7 +170,7 @@ percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="row_offset" type="LineEdit" parent="GridContainer" index="5"] +[node name="row_offset" type="SpinBox" parent="GridContainer" index="5"] anchor_left = 0.0 anchor_top = 0.0 @@ -174,22 +178,24 @@ anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 64.0 margin_top = 56.0 -margin_right = 122.0 +margin_right = 138.0 margin_bottom = 80.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false -focus_mode = 2 mouse_filter = 0 -mouse_default_cursor_shape = 1 +mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -text = "0.5" -focus_mode = 2 -context_menu_enabled = true -placeholder_alpha = 0.6 -caret_blink = false -caret_blink_speed = 0.65 -caret_position = 0 +min_value = 0.0 +max_value = 1.0 +step = 0.05 +page = 0.0 +value = 0.5 +exp_edit = false +rounded = false +editable = true +prefix = "" +suffix = "" _sections_unfolded = [ "Caret", "Placeholder" ] [node name="Label4" type="Label" parent="GridContainer" index="6"] @@ -212,7 +218,7 @@ percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="mortar" type="LineEdit" parent="GridContainer" index="7"] +[node name="mortar" type="SpinBox" parent="GridContainer" index="7"] anchor_left = 0.0 anchor_top = 0.0 @@ -220,22 +226,24 @@ anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 64.0 margin_top = 84.0 -margin_right = 122.0 +margin_right = 138.0 margin_bottom = 108.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false -focus_mode = 2 mouse_filter = 0 -mouse_default_cursor_shape = 1 +mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -text = "0.1" -focus_mode = 2 -context_menu_enabled = true -placeholder_alpha = 0.6 -caret_blink = false -caret_blink_speed = 0.65 -caret_position = 0 +min_value = 0.0 +max_value = 1.0 +step = 0.05 +page = 0.0 +value = 0.1 +exp_edit = false +rounded = false +editable = true +prefix = "" +suffix = "" _sections_unfolded = [ "Caret", "Placeholder" ] [node name="Label5" type="Label" parent="GridContainer" index="8"] @@ -258,7 +266,7 @@ percent_visible = 1.0 lines_skipped = 0 max_lines_visible = -1 -[node name="bevel" type="LineEdit" parent="GridContainer" index="9"] +[node name="bevel" type="SpinBox" parent="GridContainer" index="9"] anchor_left = 0.0 anchor_top = 0.0 @@ -266,22 +274,24 @@ anchor_right = 0.0 anchor_bottom = 0.0 margin_left = 64.0 margin_top = 112.0 -margin_right = 122.0 +margin_right = 138.0 margin_bottom = 136.0 rect_pivot_offset = Vector2( 0, 0 ) rect_clip_content = false -focus_mode = 2 mouse_filter = 0 -mouse_default_cursor_shape = 1 +mouse_default_cursor_shape = 0 size_flags_horizontal = 1 size_flags_vertical = 1 -text = "0.1" -focus_mode = 2 -context_menu_enabled = true -placeholder_alpha = 0.6 -caret_blink = false -caret_blink_speed = 0.65 -caret_position = 0 +min_value = 0.0 +max_value = 1.0 +step = 0.05 +page = 0.0 +value = 0.1 +exp_edit = false +rounded = false +editable = true +prefix = "" +suffix = "" _sections_unfolded = [ "Caret", "Placeholder" ] [connection signal="close_request" from="." to="." method="queue_free"] diff --git a/addons/procedural_material/nodes/colorize.gd b/addons/procedural_material/nodes/colorize.gd index 745a1e3..dc2df8b 100644 --- a/addons/procedural_material/nodes/colorize.gd +++ b/addons/procedural_material/nodes/colorize.gd @@ -23,8 +23,8 @@ func get_shader_code(uv): if variant_index == -1: variant_index = generated_variants.size() generated_variants.append(uv) - rv.code = src_code.code+"vec3 "+name+"_"+str(variant_index)+"_rgb = mix("+color_to_string(color0)+", "+color_to_string(color1)+", "+src_code.f+");\n" - rv.f = src_code.f + rv.code = src_code.code+"vec3 "+name+"_"+str(variant_index)+"_rgb = mix("+color_to_string(color0)+", "+color_to_string(color1)+", "+get_source_f(src_code)+");\n" + rv.f = get_source_f(src_code) rv.rgb = name+"_"+str(variant_index)+"_rgb" return rv diff --git a/addons/procedural_material/nodes/material.gd b/addons/procedural_material/nodes/material.gd index 3e92909..393652e 100644 --- a/addons/procedural_material/nodes/material.gd +++ b/addons/procedural_material/nodes/material.gd @@ -5,19 +5,12 @@ func _ready(): set_slot(0, true, 0, Color(0.5, 0.5, 1), false, 0, Color(0.5, 0.5, 1)) func get_shader_code(uv): - var rv = { defs="", code="" } + var rv = { defs="", code="", rgb="vec3(0.0, 0.0, 0.0)" } var src = get_source() - if src == null: - rv.code += "void fragment() {\n" - rv.code += "COLOR = vec4(0.0, 0.0, 0.0, 1.0);" - else: - var src_code = src.get_shader_code("UV") - rv.code += src_code.defs - rv.code += "void fragment() {\n" - rv.code += src_code.code - rv.code += "vec3 "+name+"_rgb = "+get_source_rgb(src_code)+";\n" - rv.code += "COLOR = vec4("+name+"_rgb, 1.0);\n" - rv.code += "}\n" + if src != null: + rv = src.get_shader_code(uv) + if !rv.has("rgb"): + rv.rgb = get_source_rgb(rv) return rv func _get_state_variables(): diff --git a/addons/procedural_material/pm_editor.gd b/addons/procedural_material/pm_editor.gd index 900c89c..f04d9a9 100644 --- a/addons/procedural_material/pm_editor.gd +++ b/addons/procedural_material/pm_editor.gd @@ -2,6 +2,7 @@ tool extends Container var popup_position = Vector2(0, 0) +var selected_node = null const MENU = [ { command="load_texture", description="Load texture" }, @@ -99,18 +100,11 @@ func save_file(filename): file.close() func generate_shader(): - var code = "" - var file = File.new() - file.open("res://addons/procedural_material/shader_header.txt", File.READ) - while !file.eof_reached(): - code += file.get_line() - code += "\n" - for c in $GraphEdit.get_children(): - if c is GraphNode: - c.generated = false - c.generated_variants = [] - var shader_code = $GraphEdit/Material.get_shader_code("UV") - print("GENERATED SHADER:\n"+shader_code.code) - code += shader_code.code - $Preview.material.shader.set_code(code) + $TexturePreview.material.shader.set_code($GraphEdit.generate_shader($GraphEdit/Material)) + if selected_node != null: + $TexturePreview/SelectedPreview.material.shader.set_code($GraphEdit.generate_shader(selected_node)) +func _on_GraphEdit_node_selected(node): + print("selected "+str(node)) + selected_node = node + $TexturePreview/SelectedPreview.material.shader.set_code($GraphEdit.generate_shader(selected_node)) diff --git a/addons/procedural_material/pm_editor.tscn b/addons/procedural_material/pm_editor.tscn index 48e331c..b330f0b 100644 --- a/addons/procedural_material/pm_editor.tscn +++ b/addons/procedural_material/pm_editor.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=6 format=2] +[gd_scene load_steps=8 format=2] [ext_resource path="res://addons/procedural_material/pm_editor.gd" type="Script" id=1] [ext_resource path="res://addons/procedural_material/graph_edit.gd" type="Script" id=2] @@ -100,24 +100,23 @@ float perlin(vec2 uv, int iterations, float turbulence) { return f/m; } -float Bricks_f(vec2 uv) { return bricks(uv, vec2(3, 6), 0.5, 0.1, 0.1); } -float Perlin_f(vec2 uv) { return perlin(uv, 6, 0.8); } +float Perlin_f(vec2 uv) { return perlin(uv, 9, 0.8); } +float Perlin2_f(vec2 uv) { return perlin(uv, 6, 0.5); } +float Bricks_f(vec2 uv) { return bricks(uv, vec2(3, 6), 0.5, 0.05, 0.1); } void fragment() { -float Bricks_0_f = Bricks_f(UV+vec2(0.01, 0.0)); -float Perlin_0_f = Perlin_f(UV+vec2(0.01, 0.0)); -vec3 Blend_0_rgb = mix(vec3(Bricks_0_f), vec3(Perlin_0_f), 0.5); -float Bricks_1_f = Bricks_f(UV-vec2(0.01, 0.0)); -float Perlin_1_f = Perlin_f(UV-vec2(0.01, 0.0)); -vec3 Blend_1_rgb = mix(vec3(Bricks_1_f), vec3(Perlin_1_f), 0.5); -float Bricks_2_f = Bricks_f(UV+vec2(0.0, 0.01)); -float Perlin_2_f = Perlin_f(UV+vec2(0.0, 0.01)); -vec3 Blend_2_rgb = mix(vec3(Bricks_2_f), vec3(Perlin_2_f), 0.5); -float Bricks_3_f = Bricks_f(UV-vec2(0.0, 0.01)); -float Perlin_3_f = Perlin_f(UV-vec2(0.0, 0.01)); -vec3 Blend_3_rgb = mix(vec3(Bricks_3_f), vec3(Perlin_3_f), 0.5); -vec3 NormalMap_0_rgb = vec3(0.5, 0.5, 0.5) + 0.5*normalize(3.1*vec3(Blend_0_rgb.r-Blend_1_rgb.r, Blend_2_rgb.r-Blend_3_rgb.r, 0.0) + vec3(0.0, 0.0, 1.0)); -vec3 Material_rgb = NormalMap_0_rgb; -COLOR = vec4(Material_rgb, 1.0); +float Perlin_0_f = Perlin_f(UV); +vec3 Colorize_0_rgb = mix(vec3(0,0,0), vec3(1,1,1), Perlin_0_f); +vec3 Colorize2_0_rgb = mix(vec3(1,0,0), vec3(0.875,0.461907,0.061523), Perlin_0_f); +float Perlin2_0_f = Perlin2_f(UV+vec2(0.01, 0.0)); +float Perlin2_1_f = Perlin2_f(UV-vec2(0.01, 0.0)); +float Perlin2_2_f = Perlin2_f(UV+vec2(0.0, 0.01)); +float Perlin2_3_f = Perlin2_f(UV-vec2(0.0, 0.01)); +vec2 Warp_0_uv = UV+0.5*vec2(Perlin2_0_f-Perlin2_1_f, Perlin2_2_f-Perlin2_3_f); +float Bricks_0_f = Bricks_f(Warp_0_uv); +vec3 Warp_0_rgb = vec3(Bricks_0_f); +float Warp_0_f = Bricks_0_f; +vec3 Blend_0_rgb = mix(Colorize_0_rgb, Colorize2_0_rgb, Warp_0_f); +COLOR = vec4(Blend_0_rgb, 1.0); } " _sections_unfolded = [ "Resource" ] @@ -127,6 +126,123 @@ _sections_unfolded = [ "Resource" ] render_priority = 0 shader = SubResource( 1 ) +[sub_resource type="Shader" id=3] + +code = "shader_type canvas_item; + +float hash1(vec2 p) { + float q = dot(p,vec2(127.1,311.7)); + return fract(sin(q)*43758.5453); +} + +vec2 hash2(vec2 p) { + vec2 q = vec2( dot(p,vec2(127.1,311.7)), + dot(p,vec2(269.5,183.3)) ); + return fract(sin(q)*43758.5453); +} + +vec3 hash3(vec2 p) { + vec3 q = vec3( dot(p,vec2(127.1,311.7)), + dot(p,vec2(269.5,183.3)), + dot(p,vec2(419.2,371.9)) ); + return fract(sin(q)*43758.5453); +} + +float sine(vec2 uv, float count, float sharpness) { + return max(0.0, min(1.0, (0.5+sharpness*0.5*sin(count*3.1415928*2.0*uv.x)))); +} + +vec2 rotate(vec2 uv, float angle) { + vec2 rv; + rv.x = cos(angle)*uv.x + sin(angle)*uv.y; + rv.y = -sin(angle)*uv.x + cos(angle)*uv.y; + return rv; +} + +float bricks(vec2 uv, vec2 count, float offset, float mortar, float bevel) { + mortar /= max(count.x, count.y); + bevel /= max(count.x, count.y); + float fract_x = fract(uv.x*count.x+offset*step(0.5, fract(uv.y*count.y*0.5))); + float slope_x = 1.0/(bevel*count.x); + float off = 0.5*mortar/bevel; + float f1 = fract_x*slope_x-off; + float f2 = (1.0-fract_x)*slope_x-off; + float fract_y = fract(uv.y*count.y); + float slope_y = 1.0/(bevel*count.y); + float f3 = fract_y*slope_y-off; + float f4 = (1.0-fract_y)*slope_y-off; + return max(0.0, min(1.0, min(min(f1, f2), min(f3, f4)))); +} + +float colored_bricks(vec2 uv, vec2 count, float offset) { + float x = floor(uv.x*count.x+offset*step(0.5, fract(uv.y*count.y*0.5))); + float y = floor(uv.y*count.y); + return fract(x/3.0+y/7.0); +} + +float iqnoise(vec2 uv, float s, float u, float v) { + uv *= s; + vec2 p = floor(uv); + vec2 f = fract(uv); + + float k = 1.0+63.0*pow(1.0-v,4.0); + + float va = 0.0; + float wt = 0.0; + for( int j=-2; j<=2; j++ ) + for( int i=-2; i<=2; i++ ) + { + vec2 g = vec2( float(i),float(j) ); + vec3 o = hash3( p + g )*vec3(u,u,1.0); + vec2 r = g - f + o.xy; + float d = dot(r,r); + float ww = pow( 1.0-smoothstep(0.0,1.414,sqrt(d)), k ); + va += o.z*ww; + wt += ww; + } + + return va/wt; +} + +float perlin(vec2 uv, int iterations, float turbulence) { + float f = 0.0; + float c = 1.0; + float s = 2.0; + float m = 0.0; + for(int i = 0; i < iterations; i++) { + vec2 uv2 = float(s) * uv; + vec2 uv2_floor = floor(uv2); + vec2 uv2_fract = fract(uv2); + f += c * ( (1.0 - uv2_fract.x) * ((1.0 - uv2_fract.y) * hash1(uv2_floor) + uv2_fract.y * hash1(uv2_floor+vec2(0.0, 1.0))) + + uv2_fract.x * ((1.0 - uv2_fract.y) * hash1(uv2_floor+vec2(1.0, 0.0)) + uv2_fract.y * hash1(uv2_floor+vec2(1.0, 1.0)))); + m += c; + s *= 2.0; + c *= turbulence; + } + return f/m; +} + +float Perlin2_f(vec2 uv) { return perlin(uv, 6, 0.5); } +float Bricks_f(vec2 uv) { return bricks(uv, vec2(3, 6), 0.5, 0.05, 0.1); } +void fragment() { +float Perlin2_0_f = Perlin2_f(UV+vec2(0.01, 0.0)); +float Perlin2_1_f = Perlin2_f(UV-vec2(0.01, 0.0)); +float Perlin2_2_f = Perlin2_f(UV+vec2(0.0, 0.01)); +float Perlin2_3_f = Perlin2_f(UV-vec2(0.0, 0.01)); +vec2 Warp_0_uv = UV+0.5*vec2(Perlin2_0_f-Perlin2_1_f, Perlin2_2_f-Perlin2_3_f); +float Bricks_0_f = Bricks_f(Warp_0_uv); +vec3 Warp_0_rgb = vec3(Bricks_0_f); +float Warp_0_f = Bricks_0_f; +COLOR = vec4(Warp_0_rgb, 1.0); +} +" +_sections_unfolded = [ "Resource" ] + +[sub_resource type="ShaderMaterial" id=4] + +render_priority = 0 +shader = SubResource( 3 ) + [node name="ProceduralMaterialEditor" type="MarginContainer" index="0"] anchor_left = 0.0 @@ -193,7 +309,7 @@ items = [ "Load texture", null, 0, false, false, 0, 0, null, "", false, "Save te hide_on_state_item_selection = false _sections_unfolded = [ "Rect" ] -[node name="Preview" type="ColorRect" parent="." index="1"] +[node name="TexturePreview" type="ColorRect" parent="." index="1"] material = SubResource( 2 ) anchor_left = 0.0 @@ -213,8 +329,31 @@ size_flags_vertical = 0 color = Color( 1, 1, 1, 1 ) _sections_unfolded = [ "Material", "Mouse", "Rect" ] +[node name="SelectedPreview" type="ColorRect" parent="TexturePreview" index="0"] + +material = SubResource( 4 ) +anchor_left = 0.0 +anchor_top = 0.0 +anchor_right = 0.0 +anchor_bottom = 0.0 +margin_left = 3.0 +margin_top = 189.0 +margin_right = 67.0 +margin_bottom = 253.0 +rect_min_size = Vector2( 64, 64 ) +rect_pivot_offset = Vector2( 0, 0 ) +rect_clip_content = false +mouse_filter = 2 +mouse_default_cursor_shape = 0 +size_flags_horizontal = 8 +size_flags_vertical = 0 +color = Color( 1, 1, 1, 1 ) +_sections_unfolded = [ "Material", "Mouse", "Rect" ] + [connection signal="connection_request" from="GraphEdit" to="." method="_on_GraphEdit_connection_request"] +[connection signal="node_selected" from="GraphEdit" to="." method="_on_GraphEdit_node_selected"] + [connection signal="popup_request" from="GraphEdit" to="." method="_on_GraphEdit_popup_request"] [connection signal="id_pressed" from="GraphEdit/PopupMenu" to="." method="_on_PopupMenu_id_pressed"]