New shortcuts and node groups in library

Updated library code to be able to store a group of nodes.
Added keyboard shortcuts:
- Del to delete selected nodes
- C to center the view
Modified paste action to paste at the center of the view.
New example (biohazard from webbzeug)
This commit is contained in:
Rodolphe Suescun 2018-08-22 07:33:50 +02:00
parent ba016797e3
commit eecc199427
9 changed files with 130 additions and 74 deletions

View File

@ -10,16 +10,27 @@ signal graph_changed
func _ready():
$SaveViewport/ColorRect.material = $SaveViewport/ColorRect.material.duplicate(true)
OS.low_processor_usage_mode = true
center_view()
func _gui_input(event):
if event is InputEventKey and event.pressed:
if event.scancode == KEY_C:
center_view()
elif event.scancode == KEY_DELETE:
remove_selection()
# Misc. useful functions
func get_source(node, port):
for c in get_connection_list():
if c.to == node && c.to_port == port:
return { node=c.from, slot=c.from_port }
func add_node(node, global_position = null):
func offset_from_global_position(global_position):
return (scroll_offset + global_position - rect_global_position) / zoom
func add_node(node):
add_child(node)
if global_position != null:
node.offset = (scroll_offset + global_position - rect_global_position) / zoom
node.connect("close_request", self, "remove_node", [ node ])
func connect_node(from, from_slot, to, to_slot):
@ -89,6 +100,7 @@ func new_material():
clear_material()
create_node({name="Material", type="material"})
set_save_path(null)
center_view()
func get_free_name(type):
var i = 0
@ -98,20 +110,32 @@ func get_free_name(type):
return node_name
i += 1
func create_node(data, global_position = null):
if !data.has("type"):
return null
var node_type = load("res://addons/procedural_material/nodes/"+data.type+".tscn")
if node_type != null:
var node = node_type.instance()
if data.has("name") && !has_node(data.name):
node.name = data.name
else:
node.name = get_free_name(data.type)
add_node(node, global_position)
node.deserialize(data)
send_changed_signal()
return node
func create_nodes(data, position = null):
if data.has("type"):
var node_type = load("res://addons/procedural_material/nodes/"+data.type+".tscn")
if node_type != null:
var node = node_type.instance()
if data.has("name") && !has_node(data.name):
node.name = data.name
else:
node.name = get_free_name(data.type)
add_node(node)
node.deserialize(data)
if position != null:
node.offset += position
send_changed_signal()
return node
else:
if typeof(data.nodes) == TYPE_ARRAY and typeof(data.connections) == TYPE_ARRAY:
var names = {}
for c in data.nodes:
var node = create_nodes(c, position)
if node != null:
names[c.name] = node.name
node.selected = true
for c in data.connections:
connect_node(names[c.from], c.from_port, names[c.to], c.to_port)
return null
func load_file():
var dialog = FileDialog.new()
@ -131,11 +155,12 @@ func do_load_file(filename):
file.close()
clear_material()
for n in data.nodes:
var node = create_node(n)
var node = create_nodes(n)
for c in data.connections:
connect_node(c.from, c.from_port, c.to, c.to_port)
set_save_path(filename)
set_need_save(false)
center_view()
func save_file():
if save_path != null:
@ -173,6 +198,66 @@ func export_textures(size = 512):
if c is GraphNode && c.has_method("export_textures"):
c.export_textures(prefix)
# Cut / copy / paste
func remove_selection():
for c in get_children():
if c is GraphNode and c.selected:
remove_node(c)
func serialize_selection():
var data = { nodes = [], connections = [] }
var nodes = []
for c in get_children():
if c is GraphNode and c.selected:
nodes.append(c)
if nodes.empty():
return null
var center = Vector2(0, 0)
for n in nodes:
center += n.offset+0.5*n.rect_size
center /= nodes.size()
for n in nodes:
var s = n.serialize()
var p = n.offset-center
s.node_position = { x=p.x, y=p.y }
data.nodes.append(s)
for c in get_connection_list():
var from = get_node(c.from)
var to = get_node(c.to)
if from != null and from.selected and to != null and to.selected:
data.connections.append(c)
return data
func cut():
copy()
remove_selection()
func copy():
OS.clipboard = to_json(serialize_selection())
func paste(pos = Vector2(0, 0)):
for c in get_children():
if c is GraphNode:
c.selected = false
var data = parse_json(OS.clipboard)
create_nodes(data, scroll_offset+0.5*rect_size)
# Center view
func center_view():
var center = Vector2(0, 0)
var node_count = 0
for c in get_children():
if c is GraphNode:
center += c.offset + 0.5*c.rect_size
node_count += 1
if node_count > 0:
center /= node_count
scroll_offset = center - 0.5*rect_size
# Delay after graph update
func send_changed_signal():
set_need_save(true)
$Timer.start()
@ -180,51 +265,13 @@ func send_changed_signal():
func do_send_changed_signal():
emit_signal("graph_changed")
func cut():
copy()
for c in get_children():
if c is GraphNode and c.selected:
remove_node(c)
func copy():
var data = { nodes = [], connections = [] }
for c in get_children():
if c is GraphNode and c.selected:
data.nodes.append(c.serialize())
for c in get_connection_list():
var from = get_node(c.from)
var to = get_node(c.to)
if from != null and from.selected and to != null and to.selected:
data.connections.append(c)
OS.clipboard = to_json(data)
func paste():
for c in get_children():
if c is GraphNode:
c.selected = false
var data = parse_json(OS.clipboard)
if data == null or typeof(data) != TYPE_DICTIONARY:
return
if !data.has("nodes") or !data.has("connections"):
return
if typeof(data.nodes) != TYPE_ARRAY or typeof(data.connections) != TYPE_ARRAY:
return
var names = {}
for c in data.nodes:
var node = create_node(c)
if node != null:
names[c.name] = node.name
node.selected = true
for c in data.connections:
connect_node(names[c.from], c.from_port, names[c.to], c.to_port)
# Drag and drop
func can_drop_data(position, data):
return typeof(data) == TYPE_DICTIONARY and data.has('type')
return typeof(data) == TYPE_DICTIONARY and (data.has('type') or (data.has('nodes') and data.has('connections')))
func drop_data(position, data):
var node = create_node(data, get_global_transform().xform(position))
create_nodes(data, offset_from_global_position(get_global_transform().xform(position)))
return true
# Save shader to image, create image texture

View File

@ -45,7 +45,7 @@ _sections_unfolded = [ "Material", "Mouse", "Visibility" ]
[node name="Material" parent="." index="0" instance=ExtResource( 2 )]
theme = SubResource( 1 )
_sections_unfolded = [ "Anchor", "Margin", "Mouse", "Theme", "slot/2", "slot/3", "slot/4", "slot/5" ]
_sections_unfolded = [ "Anchor", "Margin", "Mouse", "Theme", "slot", "slot/2", "slot/3", "slot/4", "slot/5" ]
[node name="Timer" type="Timer" parent="." index="1"]

View File

@ -64,7 +64,7 @@ func add_item(item, item_name, item_parent = null):
new_item = create_item(item_parent)
new_item.set_text(0, item_name)
new_item.collapsed = true
if item.has("type"):
if item.has("type") || item.has("nodes"):
new_item.set_metadata(0, item)
if item.has("collapsed"):
new_item.collapsed = item.collapsed

View File

@ -139,25 +139,32 @@ func edit_paste():
func add_to_user_library():
var graph_edit = $VBoxContainer/HBoxContainer/Projects.get_current_tab_control()
if graph_edit != null and graph_edit is GraphEdit:
var selected_nodes = []
for n in graph_edit.get_children():
if n is GraphNode and n.selected:
var dialog = preload("res://addons/procedural_material/widgets/line_dialog.tscn").instance()
add_child(dialog)
dialog.connect("ok", self, "do_add_to_user_library", [n])
dialog.popup_centered()
break
selected_nodes.append(n)
if !selected_nodes.empty():
var dialog = preload("res://addons/procedural_material/widgets/line_dialog.tscn").instance()
add_child(dialog)
dialog.connect("ok", self, "do_add_to_user_library", [ selected_nodes ])
dialog.popup_centered()
func do_add_to_user_library(name, node):
var data = node.serialize()
func do_add_to_user_library(name, nodes):
var data
if nodes.size() == 1:
data = nodes[0].serialize()
data.erase("node_position")
else:
var graph_edit = $VBoxContainer/HBoxContainer/Projects.get_current_tab_control()
data = graph_edit.serialize_selection()
var dir = Directory.new()
dir.make_dir("user://library")
dir.make_dir("user://library/user")
data.erase("node_position")
data.library = "user://library/user.json"
data.icon = name.right(name.rfind("/")+1).to_lower()
$VBoxContainer/HBoxContainer/VBoxContainer/Library.add_item(data, name)
var graph_edit = $VBoxContainer/HBoxContainer/Projects.get_current_tab_control()
graph_edit.export_texture(node, "user://library/user/"+data.icon+".png", 64)
graph_edit.export_texture(nodes[0], "user://library/user/"+data.icon+".png", 64)
func save_user_library():
print("Saving user library")

View File

@ -4,7 +4,7 @@
[ext_resource path="res://addons/procedural_material/library.gd" type="Script" id=2]
[ext_resource path="res://addons/procedural_material/preview.tscn" type="PackedScene" id=3]
[node name="MainWindow" type="Panel" index="0"]
[node name="MainWindow" type="Panel"]
anchor_left = 0.0
anchor_top = 0.0

View File

@ -45,8 +45,9 @@ func _set_state(s):
material_maker = s.material_maker
func open_material_maker():
material_maker = load("res://addons/procedural_material/window_dialog.tscn").instance()
add_child(material_maker)
if material_maker == null:
material_maker = load("res://addons/procedural_material/window_dialog.tscn").instance()
add_child(material_maker)
material_maker.popup_centered()
func handles(object):
@ -65,7 +66,6 @@ func make_visible(b):
remove_control_from_container(CONTAINER_SPATIAL_EDITOR_MENU, pt_button)
pt_button.queue_free()
pt_button = null
print(pt_button)
func paint():
paint_tool = preload("res://addons/procedural_material/paint_tool/paint_window.tscn").instance()

1
examples/biohazard.ptex Normal file

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"connections":[{"from":"pattern_0","from_port":0,"to":"colorize_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"blend_0","to_port":0},{"from":"transform_0","from_port":0,"to":"blend_0","to_port":1},{"from":"colorize_0","from_port":0,"to":"transform_0","to_port":0},{"from":"blend_0","from_port":0,"to":"transform_1","to_port":0},{"from":"transform_1","from_port":0,"to":"blend_1","to_port":1},{"from":"blend_0","from_port":0,"to":"blend_1","to_port":0},{"from":"blend_1","from_port":0,"to":"colorize_1","to_port":0},{"from":"colorize_1","from_port":0,"to":"normal_map_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"perlin_0","from_port":0,"to":"colorize_2","to_port":0},{"from":"colorize_2","from_port":0,"to":"blend_2","to_port":0},{"from":"blend_2","from_port":0,"to":"Material","to_port":0},{"from":"colorize_4","from_port":0,"to":"blend_2","to_port":2},{"from":"colorize_4","from_port":0,"to":"colorize_5","to_port":0},{"from":"colorize_5","from_port":0,"to":"Material","to_port":1},{"from":"colorize_4","from_port":0,"to":"colorize_6","to_port":0},{"from":"colorize_6","from_port":0,"to":"Material","to_port":2},{"from":"perlin_1","from_port":0,"to":"blend_3","to_port":1},{"from":"colorize_7","from_port":0,"to":"blend_3","to_port":0},{"from":"blend_1","from_port":0,"to":"colorize_7","to_port":0},{"from":"blend_3","from_port":0,"to":"colorize_4","to_port":0},{"from":"uniform_0","from_port":0,"to":"blend_2","to_port":1},{"from":"colorize_5","from_port":0,"to":"combine_0","to_port":0},{"from":"colorize_6","from_port":0,"to":"combine_0","to_port":1},{"from":"combine_0","from_port":0,"to":"export_0","to_port":0}],"nodes":[{"iterations":7,"name":"perlin_0","node_position":{"x":1,"y":-330},"persistence":0.85,"scale_x":4,"scale_y":4,"type":"perlin"},{"gradient":[{"b":0,"g":0,"pos":0.018182,"r":0},{"b":1,"g":1,"pos":0.045455,"r":1}],"name":"colorize_0","node_position":{"x":-828,"y":218},"type":"colorize"},{"gradient":[{"b":0,"g":0.152344,"pos":0,"r":0.270833},{"b":0,"g":0.191569,"pos":0.945455,"r":0.557292}],"name":"colorize_2","node_position":{"x":201,"y":-298},"type":"colorize"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":1,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":755,"y":-137},"normal_scale":1,"roughness":1,"type":"material"},{"amount":0.5,"blend_type":0,"name":"blend_2","node_position":{"x":407.094238,"y":-265.083313},"type":"blend"},{"gradient":[{"b":0,"g":0,"pos":0.445455,"r":0},{"b":1,"g":1,"pos":0.545455,"r":1}],"name":"colorize_4","node_position":{"x":258,"y":-136},"type":"colorize"},{"mix":5,"name":"pattern_0","node_position":{"x":-858,"y":120},"type":"pattern","x_scale":8,"x_wave":0,"y_scale":8,"y_wave":0},{"name":"transform_1","node_position":{"x":-647,"y":285},"repeat":true,"rotate":90,"scale_x":1,"scale_y":1,"translate_x":0.06,"translate_y":0,"type":"transform"},{"amount":1,"blend_type":2,"name":"blend_0","node_position":{"x":-633,"y":181},"type":"blend"},{"amount":1,"blend_type":2,"name":"blend_1","node_position":{"x":-632,"y":77},"type":"blend"},{"color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"name":"uniform_0","node_position":{"x":233,"y":-216},"type":"uniform"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":0.018182,"r":1}],"name":"colorize_7","node_position":{"x":-109.265503,"y":-175},"type":"colorize"},{"iterations":7,"name":"perlin_1","node_position":{"x":-161,"y":-86},"persistence":0.75,"scale_x":4,"scale_y":4,"type":"perlin"},{"gradient":[{"b":0.640625,"g":0.640625,"pos":0,"r":0.640625},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_6","node_position":{"x":444,"y":-66},"type":"colorize"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_1","node_position":{"x":85,"y":72},"type":"colorize"},{"name":"transform_0","node_position":{"x":-845,"y":279},"repeat":true,"rotate":0,"scale_x":1,"scale_y":1,"translate_x":0.06,"translate_y":0.06,"type":"transform"},{"gradient":[{"b":1,"g":1,"pos":0.609091,"r":1},{"b":0,"g":0,"pos":0.645455,"r":0}],"name":"colorize_5","node_position":{"x":446,"y":-150},"type":"colorize"},{"name":"combine_0","node_position":{"x":677,"y":-264},"type":"combine"},{"name":"export_0","node_position":{"x":828,"y":-268},"suffix":"mr","type":"export"},{"amount":0.6,"name":"normal_map_0","node_position":{"x":294,"y":76},"type":"normal_map"},{"amount":0.2,"blend_type":2,"name":"blend_3","node_position":{"x":72.734497,"y":-130},"type":"blend"}]}
{"connections":[{"from":"pattern_0","from_port":0,"to":"colorize_0","to_port":0},{"from":"colorize_0","from_port":0,"to":"blend_0","to_port":0},{"from":"transform_0","from_port":0,"to":"blend_0","to_port":1},{"from":"colorize_0","from_port":0,"to":"transform_0","to_port":0},{"from":"blend_0","from_port":0,"to":"transform_1","to_port":0},{"from":"transform_1","from_port":0,"to":"blend_1","to_port":1},{"from":"blend_0","from_port":0,"to":"blend_1","to_port":0},{"from":"blend_1","from_port":0,"to":"colorize_1","to_port":0},{"from":"colorize_1","from_port":0,"to":"normal_map_0","to_port":0},{"from":"normal_map_0","from_port":0,"to":"Material","to_port":4},{"from":"perlin_0","from_port":0,"to":"colorize_2","to_port":0},{"from":"colorize_2","from_port":0,"to":"blend_2","to_port":0},{"from":"blend_2","from_port":0,"to":"Material","to_port":0},{"from":"colorize_4","from_port":0,"to":"blend_2","to_port":2},{"from":"colorize_4","from_port":0,"to":"colorize_5","to_port":0},{"from":"colorize_5","from_port":0,"to":"Material","to_port":1},{"from":"colorize_4","from_port":0,"to":"colorize_6","to_port":0},{"from":"colorize_6","from_port":0,"to":"Material","to_port":2},{"from":"perlin_1","from_port":0,"to":"blend_3","to_port":1},{"from":"colorize_7","from_port":0,"to":"blend_3","to_port":0},{"from":"blend_1","from_port":0,"to":"colorize_7","to_port":0},{"from":"blend_3","from_port":0,"to":"colorize_4","to_port":0},{"from":"uniform_0","from_port":0,"to":"blend_2","to_port":1},{"from":"colorize_5","from_port":0,"to":"combine_0","to_port":0},{"from":"colorize_6","from_port":0,"to":"combine_0","to_port":1},{"from":"combine_0","from_port":0,"to":"export_0","to_port":0}],"nodes":[{"iterations":7,"name":"perlin_0","node_position":{"x":1,"y":-330},"persistence":0.85,"scale_x":4,"scale_y":4,"type":"perlin"},{"gradient":[{"b":0,"g":0.152344,"pos":0,"r":0.270833},{"b":0,"g":0.191569,"pos":0.945455,"r":0.557292}],"name":"colorize_2","node_position":{"x":201,"y":-298},"type":"colorize"},{"albedo_color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"ao_light_affect":1,"depth_scale":1,"emission_energy":1,"metallic":1,"name":"Material","node_position":{"x":755,"y":-137},"normal_scale":1,"roughness":1,"type":"material"},{"amount":0.5,"blend_type":0,"name":"blend_2","node_position":{"x":407.094238,"y":-265.083313},"type":"blend"},{"gradient":[{"b":0,"g":0,"pos":0.445455,"r":0},{"b":1,"g":1,"pos":0.545455,"r":1}],"name":"colorize_4","node_position":{"x":258,"y":-136},"type":"colorize"},{"color":{"a":1,"b":1,"g":1,"r":1,"type":"Color"},"name":"uniform_0","node_position":{"x":233,"y":-216},"type":"uniform"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":0.018182,"r":1}],"name":"colorize_7","node_position":{"x":-109.265503,"y":-175},"type":"colorize"},{"iterations":7,"name":"perlin_1","node_position":{"x":-161,"y":-86},"persistence":0.75,"scale_x":4,"scale_y":4,"type":"perlin"},{"gradient":[{"b":0.640625,"g":0.640625,"pos":0,"r":0.640625},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_6","node_position":{"x":444,"y":-66},"type":"colorize"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":1,"r":1}],"name":"colorize_1","node_position":{"x":85,"y":72},"type":"colorize"},{"gradient":[{"b":1,"g":1,"pos":0.609091,"r":1},{"b":0,"g":0,"pos":0.645455,"r":0}],"name":"colorize_5","node_position":{"x":446,"y":-150},"type":"colorize"},{"name":"combine_0","node_position":{"x":677,"y":-264},"type":"combine"},{"name":"export_0","node_position":{"x":828,"y":-268},"suffix":"mr","type":"export"},{"amount":0.6,"name":"normal_map_0","node_position":{"x":294,"y":76},"type":"normal_map"},{"amount":0.2,"blend_type":2,"name":"blend_3","node_position":{"x":72.734497,"y":-130},"type":"blend"},{"name":"transform_1","node_position":{"x":-647,"y":285},"repeat":true,"rotate":90,"scale_x":1,"scale_y":1,"translate_x":0.06,"translate_y":0,"type":"transform"},{"amount":1,"blend_type":2,"name":"blend_0","node_position":{"x":-633,"y":181},"type":"blend"},{"amount":1,"blend_type":2,"name":"blend_1","node_position":{"x":-632,"y":77},"type":"blend"},{"gradient":[{"b":0,"g":0,"pos":0,"r":0},{"b":1,"g":1,"pos":0.045455,"r":1}],"name":"colorize_0","node_position":{"x":-828,"y":218},"type":"colorize"},{"name":"transform_0","node_position":{"x":-844,"y":279},"repeat":true,"rotate":0,"scale_x":1,"scale_y":1,"translate_x":0.06,"translate_y":0.06,"type":"transform"},{"mix":5,"name":"pattern_0","node_position":{"x":-859,"y":120},"type":"pattern","x_scale":8,"x_wave":0,"y_scale":8,"y_wave":0}]}

View File

@ -7,6 +7,7 @@
; param=value ; assign values to parameters
config_version=3
[application]
config/name="Material Maker"