Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
a7bd443
node groups draft
pragma37 Sep 9, 2022
872d5a9
retrieve group parameters
pragma37 Sep 11, 2022
4f9df9d
fix group synchronization, improve node pass synchronization
pragma37 Sep 11, 2022
a022291
improve group function signature formatting
pragma37 Sep 12, 2022
945fc45
Move the group graph generation to the Bridge API
pragma37 Sep 12, 2022
5cc8d65
fixes and improvements
pragma37 Sep 12, 2022
458e610
Rename group to (Group)
pragma37 Sep 12, 2022
5fdb9d4
Merge branch 'node-ux' into node-groups
pragma37 Sep 12, 2022
d84e33c
Merge branch 'node-ux' into node-groups
pragma37 Sep 13, 2022
e4351b7
Merge branch 'Development' into node-groups
pragma37 Sep 24, 2022
cff1867
fix #396
pragma37 Sep 24, 2022
b4b085c
simplify and fix custom io references
pragma37 Sep 26, 2022
ddc47f3
fix #402
pragma37 Sep 27, 2022
7a7a90f
hide edit button when there's no group selected (fix #405)
pragma37 Sep 28, 2022
d1dbbbc
allow reordering custom IO parameters
pragma37 Sep 28, 2022
9fab4a1
reorder group io types
pragma37 Sep 28, 2022
cc96798
Merge branch 'Development' into node-groups
pragma37 Sep 28, 2022
37a4081
Fix graph reloading
pragma37 Sep 29, 2022
68b3c8a
Skip includes for node groups (fix #409)
pragma37 Oct 1, 2022
16ea0ee
Merge branch 'Development' into node-groups
pragma37 Oct 1, 2022
ad1b1e0
Fix node group linking (fix #412)
pragma37 Oct 2, 2022
cbb4dc0
add new UI options to create node groups
Kolupsy Oct 8, 2022
1eb3c57
fix node copy in malt_nodes_to_group
pragma37 Oct 8, 2022
cb72e50
don't copy IONodes in malt_nodes_to_group
pragma37 Oct 8, 2022
aa5405f
Merge branch 'Development' into node-groups
pragma37 Oct 9, 2022
633fa53
fix node groups overrides (#434)
pragma37 Nov 1, 2022
fe236d2
Merge branch 'Development' into node-groups
pragma37 Nov 9, 2022
075a95b
Fix OT_MaltNodesToGroup (fix #442)
pragma37 Nov 9, 2022
f4fa086
Merge branch 'Development' into node-groups
pragma37 Nov 9, 2022
e2522b6
Merge branch 'Development' into node-groups
pragma37 Nov 9, 2022
9176cf3
Merge branch 'Development' into node-groups
pragma37 Nov 13, 2022
ed2e9da
Merge branch 'Development' into node-groups
pragma37 Nov 18, 2022
2e9eb9d
Fix nested node group uniforms (fix #450)
pragma37 Nov 26, 2022
2d829a2
Merge branch 'Development' into node-groups
pragma37 Nov 26, 2022
e93dab6
Merge branch 'Development' into node-groups
pragma37 Dec 10, 2022
361ce2e
Merge branch 'Development' into node-groups
pragma37 Dec 10, 2022
5382620
Merge branch 'Development' into node-groups
pragma37 Dec 17, 2022
9a682b5
Merge branch 'Development' into node-groups
pragma37 Jan 5, 2023
7c53e17
Merge branch 'Development' into node-groups
pragma37 Feb 2, 2023
b3befca
Merge branch 'Development' into node-groups
pragma37 Feb 8, 2023
446bbef
Merge branch 'Development' into node-groups
pragma37 Feb 14, 2023
8a63aff
Merge branch 'Development' into node-groups
pragma37 Feb 25, 2023
16bb981
Merge branch 'Development' into node-groups
pragma37 Feb 25, 2023
d17bfaf
Merge branch 'Development' into node-groups
pragma37 Feb 26, 2023
0df31c3
Merge branch 'Development' into node-groups
pragma37 Feb 27, 2023
0395cd4
Merge branch 'Release' into node-groups
pragma37 Mar 30, 2023
c07b158
Merge branch 'Development' into node-groups
pragma37 Apr 2, 2023
00fa722
Merge branch 'Development' into node-groups
pragma37 Jun 9, 2023
722483d
Merge branch 'Development' into node-groups
pragma37 Jun 22, 2023
1c078ad
Merge branch 'Development' into node-groups
pragma37 Jul 1, 2023
c79be5d
Merge branch 'Development' into node-groups
pragma37 Jul 2, 2023
5ed963f
Merge branch 'Development' into node-groups
pragma37 Oct 22, 2023
78ed204
Merge branch 'Development' into node-groups
pragma37 Oct 22, 2023
c1e1a08
Merge branch 'Development' into node-groups
pragma37 Oct 29, 2023
f9a05cd
Merge branch 'Development' into node-groups
pragma37 Nov 9, 2023
2c9d228
Merge branch 'Development' into node-groups
pragma37 Nov 15, 2023
3b4fceb
Merge branch 'Development' into node-groups
pragma37 Nov 16, 2023
88f0a1e
Merge branch 'Development' into node-groups
pragma37 Nov 16, 2023
5394d82
Merge branch 'Development' into node-groups
pragma37 Nov 27, 2023
8b862f2
Merge branch 'Development' into node-groups
pragma37 Nov 28, 2023
7f8a405
Merge branch 'Development' into node-groups
pragma37 Dec 9, 2023
540a606
Merge branch 'Development' into node-groups
pragma37 Jan 2, 2024
95d44e2
Merge branch 'Development' into node-groups
pragma37 Mar 29, 2024
fd35ae5
Fix blf.size dpi
pragma37 Mar 29, 2024
c350132
Merge branch 'Development' into node-groups
pragma37 Jun 20, 2024
7dd08ef
Merge branch 'Development' into node-groups
pragma37 Jul 6, 2024
00d4608
Merge branch 'Development' into node-groups
pragma37 Jul 6, 2024
4c63ad1
Merge branch 'Development' into node-groups
pragma37 Jul 13, 2024
c88fa73
Merge branch 'Development' into node-groups
pragma37 Jul 20, 2024
31e2a21
Merge branch 'Development' into node-groups
pragma37 Sep 14, 2024
6325227
Merge branch 'Development' into node-groups
pragma37 Sep 14, 2024
a6f9650
Merge branch 'Development' into node-groups
pragma37 Sep 21, 2024
beb92ef
Merge branch 'Development' into node-groups
pragma37 Sep 24, 2024
fdea517
Merge branch 'Development' into node-groups
pragma37 Jan 13, 2025
2e421ca
Merge branch 'Development' into node-groups
pragma37 Mar 22, 2025
c8538a3
Merge branch 'Development' into node-groups
pragma37 Jun 11, 2025
08bcaf7
Merge branch 'Development' into node-groups
pragma37 Oct 18, 2025
300ad0d
Merge branch 'Development' into node-groups
pragma37 Dec 14, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions BlenderMalt/MaltMaterial.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ def get_source_path(self):
else:
return ''

def get_parameters(self, overrides, proxys):
parameters = self.parameters.get_parameters(overrides, proxys)
if self.shader_nodes:
parameters.update(self.shader_nodes.get_group_parameters(overrides, proxys))
return parameters

def draw_ui(self, layout, extension, material_parameters):
layout.active = self.id_data.library is None #only local data can be edited
layout.prop_search(self, 'material_type', bpy.context.scene.world.malt, 'material_types')
Expand Down
110 changes: 107 additions & 3 deletions BlenderMalt/MaltNodes/MaltNodeTree.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def poll_material(self, material):
return material.malt.shader_nodes is self

def update_graph_type(self, context):
self.is_group_type = self.graph_type.endswith(' (Group)')
graph = self.get_pipeline_graph()
if graph and graph.default_graph_path and len(self.nodes) == 0:
blend_path, tree_name = graph.default_graph_path
Expand All @@ -59,9 +60,14 @@ def update_graph_type(self, context):
self.user_remap(copy)
bpy.data.node_groups.remove(self)
copy.name = name
else:
self.reload_nodes()
self.update_ext(force_update=True)

graph_type: bpy.props.StringProperty(name='Type', update=update_graph_type,
options={'LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})
is_group_type : bpy.props.BoolProperty(default=False,
options={'SKIP_SAVE','LIBRARY_EDITABLE'}, override={'LIBRARY_OVERRIDABLE'})

#deprecated
library_source : bpy.props.StringProperty(name="Local Library", subtype='FILE_PATH',
Expand All @@ -83,6 +89,55 @@ def update_graph_type(self, context):

def is_active(self):
return self.get_pipeline_graph() is not None

def is_group(self):
return self.is_group_type

def get_group_source_name(self):
name = self.get_transpiler().get_source_name(self.name, prefix='').upper()
return name

def get_group_function(self, force_update=False):
if force_update == False and self.get('group_function'):
return self['group_function']
parameters = []
for node in self.nodes:
if node.bl_idname == 'MaltIONode':
sockets = node.inputs if node.is_output else node.outputs
for socket in sockets:
parameters.append({
'meta': {
'label': socket.name,
},
'name': socket.get_source_reference(),
'type': socket.data_type,
'size': socket.array_size,
'io': 'out' if node.is_output else 'in'
})
parameter_signature = ', '.join([f"{p['io']} {p['type']} {p['name']}" for p in parameters])
signature = f'void {self.get_group_source_name()}({parameter_signature})'
self['group_function'] = {
'meta': {},
'name': self.get_group_source_name(),
'type': 'void',
'parameters': parameters,
'signature': signature,
}
return self['group_function']

def get_group_parameters(self, overrides, proxys):
groups = []
parameters = {}
def get_groups(tree):
for node in tree.nodes:
if node.bl_idname == 'MaltGroupNode' and node.group is not None:
if node.group not in groups:
groups.append(node.group)
get_groups(node.group)
get_groups(self)
for group in groups:
parameters.update(group.malt_parameters.get_parameters(overrides, proxys))
return parameters

def get_source_language(self):
return self.get_pipeline_graph().language
Expand Down Expand Up @@ -242,7 +297,13 @@ def get_source(output):
for node in linked_nodes:
if hasattr(node, 'get_source_global_parameters'):
shader['GLOBAL'] += node.get_source_global_parameters(transpiler)
self['source'] = pipeline_graph.generate_source(shader)
if self.is_group():
shader['SKIP INCLUDES'] = True
shader['INCLUDE GUARD'] = self.get_group_source_name() + '_GLSL'
source = pipeline_graph.generate_source(shader)
if self.is_group():
source = source.replace('void NODE_GROUP_FUNCTION()', self.get_group_function()['signature'])
self['source'] = source
return self['source']

def reload_nodes(self):
Expand All @@ -254,10 +315,21 @@ def reload_nodes(self):
for node in self.nodes:
if hasattr(node, 'update'):
node.update()
self.get_group_function(force_update=True)
except:
import traceback
traceback.print_exc()
self.disable_updates = False

def on_name_change(self, old_src_name):
if self.is_group():
new_src_name = self.get_group_source_name()
for key in list(self.malt_parameters.get_rna().keys()):
if old_src_name in key:
self.malt_parameters.rename_property(key, key.replace(old_src_name, new_src_name))
bpy.msgbus.clear_by_owner(self)
self.subscribed = False
self.update_ext(force_update=True)

def update(self):
if self.is_active():
Expand All @@ -272,7 +344,7 @@ def update_ext(self, force_track_shader_changes=True, force_update=False):

if self.subscribed == False:
bpy.msgbus.subscribe_rna(key=self.path_resolve('name', False),
owner=self, args=(None,), notify=lambda _ : self.update_ext(force_update=True))
owner=self, args=(self.get_group_source_name(),), notify=lambda arg : self.on_name_change(arg))
self.subscribed = True

links_str = ''
Expand Down Expand Up @@ -310,7 +382,26 @@ def update_ext(self, force_track_shader_changes=True, force_update=False):
pathlib.Path(source_dir).mkdir(parents=True, exist_ok=True)
with open(source_path,'w') as f:
f.write(source)

if force_track_shader_changes:
if self.is_group():
from pathlib import Path
visited_trees = set()
def recompile_users(updated_tree):
visited_trees.add(updated_tree)
if updated_tree.is_group():
for tree in bpy.data.node_groups:
if tree.bl_idname == 'MaltTree' and tree not in visited_trees:
for node in tree.nodes:
if node.bl_idname == 'MaltGroupNode' and node.group is updated_tree:
recompile_users(tree)
break
else:
#Touch the file to force a recompilation
Path(updated_tree.get_generated_source_path()).touch()
return
recompile_users(self)

from BlenderMalt import MaltMaterial
MaltMaterial.track_shader_changes()
except:
Expand All @@ -334,7 +425,10 @@ def setup_node_trees():
for tree in bpy.data.node_groups:
if tree.bl_idname == 'MaltTree' and tree.is_active():
tree.reload_nodes()
for tree in bpy.data.node_groups:
if tree.bl_idname == 'MaltTree' and tree.is_active():
tree.update_ext(force_track_shader_changes=False, force_update=True)

from BlenderMalt import MaltMaterial
MaltMaterial.track_shader_changes()

Expand Down Expand Up @@ -370,6 +464,8 @@ def track_library_changes(force_update=False, is_initial_setup=False):
updated_graphs = []
if is_initial_setup == False:
for name, graph in graphs.items():
if '(Group)' in name:
continue
if graph.needs_reload():
updated_graphs.append(name)
if len(updated_graphs) > 0:
Expand Down Expand Up @@ -486,7 +582,7 @@ def draw_nothing(self, layout, _context):
super().__init__(nodetype, category, label=label, settings=settings, poll=poll, draw=draw_nothing)


category_id = f'BLENDERMALT_{graph.name.upper()}'
category_id = f'MALT{graph.name.upper()}'

try:
unregister_node_categories(category_id) # you could also check the hidden <nodeitems_utils._node_categories>
Expand Down Expand Up @@ -527,6 +623,9 @@ def draw_nothing(self, layout, _context):
categories['Other'].append(NodeItem('MaltArrayIndexNode', label='Array Element', settings={
'name' : repr('Array Element')
}))
categories['Other'].append(NodeItem('MaltGroupNode', label='Node Group', settings={
'name' : repr('Node Group')
}))

subcategories = set()

Expand Down Expand Up @@ -635,6 +734,7 @@ def duplicate():
context.space_data.node_tree = node_tree.get_copy()
self.layout.operator('wm.malt_callback', text='', icon='DUPLICATE').callback.set(duplicate, 'Duplicate')
def recompile():
node_tree.reload_nodes()
node_tree.update_ext(force_update=True)
self.layout.operator("wm.malt_callback", text='', icon='FILE_REFRESH').callback.set(recompile, 'Recompile')
self.layout.prop_search(node_tree, 'graph_type', context.scene.world.malt, 'graph_types',text='')
Expand Down Expand Up @@ -666,7 +766,11 @@ def set_node_tree(context, node_tree, node = None):
locked_spaces[0].node_tree = node_tree


SKIP_MATERIAL_UPDATE = False
def active_material_update(dummy=None):
global SKIP_MATERIAL_UPDATE
if SKIP_MATERIAL_UPDATE:
return
try:
material = bpy.context.object.active_material
node_tree = material.malt.shader_nodes
Expand Down
81 changes: 79 additions & 2 deletions BlenderMalt/MaltNodes/MaltNodeUITools.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ def execute( self, context ):
node = context.active_node
space_path = context.space_data.path
node_tree = None
if node and hasattr(node, 'get_pass_node_tree'):
node_tree = node.get_pass_node_tree()
if node and hasattr(node, 'get_linked_node_tree'):
node_tree = node.get_linked_node_tree()
if node_tree:
space_path.append(node_tree, node = node)
else:
Expand Down Expand Up @@ -343,13 +343,89 @@ def execute(self, context: bpy.types.Context):
bpy.ops.wm.malt_cycle_sub_categories('INVOKE_DEFAULT')
return{'FINISHED'}

class OT_MaltAddNodeGroup(bpy.types.Operator):
bl_idname = 'wm.malt_add_node_group'
bl_label = 'Add Node Group'
bl_description = 'Add a new node group to a group node'
bl_options = {'UNDO'}

@classmethod
def poll(cls, context: bpy.types.Context):
from BlenderMalt.MaltNodes.Nodes import MaltGroupNode
return (
is_malt_tree_context(context)
and isinstance(context.active_node, MaltGroupNode.MaltGroupNode)
)

def execute(self, context: bpy.types.Context):
from BlenderMalt.MaltNodes.Nodes import MaltGroupNode
node: MaltGroupNode.MaltGroupNode = context.active_node
node.create_new_group()
return {'FINISHED'}

@classmethod
def draw_ui(cls, layout: bpy.types.UILayout, node: bpy.types.Node, **layout_args):
layout.context_pointer_set('active_node', node)
layout.operator(cls.bl_idname, **layout_args)

class OT_MaltNodesToGroup(bpy.types.Operator):
bl_idname = 'wm.malt_nodes_to_group'
bl_label = 'Convert Nodes to Group'
bl_description = 'Copies selected nodes and converts them into a new node group'

@classmethod
def poll(cls, context: bpy.types.Context):
return (
is_malt_tree_context(context)
and len(context.selected_nodes) > 0
)

def execute(self, context: bpy.types.Context):
from BlenderMalt.MaltNodes.Nodes.MaltGroupNode import MaltGroupNode
from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree
from BlenderMalt.MaltNodes import MaltNodeTree
from mathutils import Vector
MaltNodeTree.SKIP_MATERIAL_UPDATE = True
sd: bpy.types.SpaceNodeEditor = context.space_data
initial_tree = sd.edit_tree
for n in context.selected_nodes:
if n.bl_idname == 'MaltIONode':
n.select = False
selected: list[bpy.types.Node] = context.selected_nodes
avg_loc = Vector((0.0, 0.0))
for node in selected:
avg_loc += Vector(get_absolute_node_position(node))
avg_loc /= len(selected)

bpy.ops.node.clipboard_copy()
new_group_node: MaltGroupNode = sd.edit_tree.nodes.new(MaltGroupNode.bl_idname)
new_nt = new_group_node.create_new_group()
new_group_node.location = avg_loc
sd.edit_tree.active = new_group_node
for n in sd.edit_tree.nodes:
n.select = n == new_group_node
set_node_tree(context, new_nt, new_group_node)
bpy.ops.node.clipboard_paste()
for n in new_nt.nodes.values():
n.location = Vector(n.location) - avg_loc
for n in selected:
initial_tree.nodes.remove(n)
MaltNodeTree.SKIP_MATERIAL_UPDATE = False
return {'FINISHED'}

def get_absolute_node_position(node: bpy.types.Node):
if node.parent is None:
return node.location
return get_absolute_node_position(node.parent) + node.location

classes = [
NodeTreePreview,
OT_MaltEditNodeTree,
OT_MaltSetTreePreview,
OT_MaltConnectTreePreview,
OT_MaltCycleSubCategories,
OT_MaltAddNodeGroup,
OT_MaltNodesToGroup,
NODE_OT_add_malt_subcategory_node,
]

Expand Down Expand Up @@ -445,6 +521,7 @@ def add_shortcut(keymaps: list, operator: bpy.types.Operator, *, type: str, valu
add_shortcut(keymaps, OT_MaltEditNodeTree, type='TAB', value='PRESS')
add_shortcut(keymaps, OT_MaltSetTreePreview, type='LEFTMOUSE', value='PRESS', shift=True, alt=True)
add_shortcut(keymaps, OT_MaltConnectTreePreview, type='LEFTMOUSE', value='PRESS', shift=True, ctrl=True)
add_shortcut(keymaps, OT_MaltNodesToGroup, type='G', value='PRESS', ctrl=True)

def register():
for _class in classes: bpy.utils.register_class(_class)
Expand Down
2 changes: 2 additions & 0 deletions BlenderMalt/MaltNodes/MaltSocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ def get_source_global_reference(self):
assert(self.active)
transpiler = self.id_data.get_transpiler()
result = transpiler.global_reference(self.node.get_source_name(), self.name)
if self.id_data.is_group():
result = result.replace('U_0_',f'_U_0_{self.id_data.get_group_source_name()}_')
if len(result) > 63:
#Blender dictionary keys are limited to 63 characters
import xxhash
Expand Down
3 changes: 2 additions & 1 deletion BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def get_pass_type(self):
return pass_type
return ''

def get_pass_node_tree(self):
def get_linked_node_tree(self):
if self.pass_graph_type != '':
graph = self.id_data.get_pipeline_graph(self.pass_graph_type)
if graph.graph_type == graph.GLOBAL_GRAPH:
Expand Down Expand Up @@ -179,6 +179,7 @@ def get_source_socket_reference(self, socket):
return transpiler.parameter_reference(self.get_source_name(), socket.name, 'out' if socket.is_output else 'in')
else:
source = self.get_source_code(transpiler)
#TODO: Assumes return type, support out and inout parameters
return source.splitlines()[-1].split('=')[-1].split(';')[0]

def get_source_code(self, transpiler):
Expand Down
Loading
Loading