From a7bd44366c3c7e010718546269000f38d43ba49f Mon Sep 17 00:00:00 2001 From: pragma37 Date: Sat, 10 Sep 2022 00:57:08 +0200 Subject: [PATCH 01/23] node groups draft --- BlenderMalt/MaltNodes/MaltNodeTree.py | 45 ++++++++++++++++++- BlenderMalt/MaltNodes/MaltSocket.py | 2 + .../MaltNodes/Nodes/MaltFunctionNode.py | 1 + BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py | 40 +++++++++++++++++ BlenderMalt/MaltNodes/Nodes/MaltIONode.py | 4 +- Malt/Pipeline.py | 40 +++++++++++++++++ Malt/PipelineGraph.py | 6 +++ Malt/SourceTranspiler.py | 8 +++- 8 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index ea9dcc62..51bd03d4 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -70,6 +70,38 @@ def update_graph_type(self, context): def is_active(self): return self.get_pipeline_graph() is not None + + def is_group(self): + return self.graph_type.endswith(' group') + + def get_group_source_name(self): + name = self.get_transpiler().get_source_name(self.name_full, prefix='').upper() + return name + + def get_group_function(self): + 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})' + return { + 'meta': {}, + 'name': self.get_group_source_name(), + 'type': 'void', + 'parameters': parameters, + 'signature': signature, + } def get_source_language(self): return self.get_pipeline_graph().language @@ -228,7 +260,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['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']) + source = source.replace('NODE_GROUP_FUNCTION', self.get_group_source_name()) + self['source'] = source return self['source'] def reload_nodes(self): @@ -472,7 +510,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 @@ -513,6 +551,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() diff --git a/BlenderMalt/MaltNodes/MaltSocket.py b/BlenderMalt/MaltNodes/MaltSocket.py index 2fc268b4..8cd4df0f 100644 --- a/BlenderMalt/MaltNodes/MaltSocket.py +++ b/BlenderMalt/MaltNodes/MaltSocket.py @@ -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 diff --git a/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py b/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py index 00c5ed87..93eef293 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py @@ -177,6 +177,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): diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py new file mode 100644 index 00000000..a3c6dfaf --- /dev/null +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -0,0 +1,40 @@ +from Malt.PipelineParameters import Parameter, Type +import bpy +from BlenderMalt.MaltNodes.Nodes.MaltFunctionNode import MaltFunctionNodeBase + + +class MaltGroupNode(bpy.types.Node, MaltFunctionNodeBase): + + bl_label = "Group Node" + + def poll_group(self, tree): + group_type = self.id_data.graph_type + if group_type.endswith(' group') == False: + group_type += ' group' + return tree.bl_idname == 'MaltTree' and tree.graph_type == group_type + + def update_group(self, context): + self.setup(context) + + group : bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_group, update=update_group) + + def get_function(self, skip_overrides=True, find_replacement=False): + return self.group.get_group_function() + + def get_source_global_parameters(self, transpiler): + src = f'#include "{self.group.get_generated_source_path()}"\n\n' + return src + super().get_source_global_parameters(transpiler) + + def draw_buttons(self, context, layout): + layout.prop(self, 'group', text='') + +classes = [ + MaltGroupNode, +] + +def register(): + for _class in classes: bpy.utils.register_class(_class) + +def unregister(): + for _class in reversed(classes): bpy.utils.unregister_class(_class) + diff --git a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py index bf507c27..3b8560c4 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -106,7 +106,7 @@ def is_custom_socket(self, socket): def get_source_socket_reference(self, socket): transpiler = self.id_data.get_transpiler() io = 'out' if self.is_output else 'in' - if self.is_custom_socket(socket): + if self.is_custom_socket(socket) and self.id_data.is_group() == False: return transpiler.custom_io_reference(io, self.io_type, socket.name) else: return transpiler.io_parameter_reference(socket.name, io) @@ -139,6 +139,8 @@ def get_source_code(self, transpiler): def get_source_global_parameters(self, transpiler): src = MaltNode.get_source_global_parameters(self, transpiler) + if self.id_data.is_group(): + return src custom_outputs = '' graph_io = self.id_data.get_pipeline_graph().graph_io[self.io_type] try: index = graph_io.custom_output_start_index diff --git a/Malt/Pipeline.py b/Malt/Pipeline.py index 4909af26..49d919a1 100644 --- a/Malt/Pipeline.py +++ b/Malt/Pipeline.py @@ -85,6 +85,46 @@ def get_graphs(self): result = {} for name, graph in self.graphs.items(): result[name] = graph.get_serializable_copy() + + import copy + group_graphs = {} + for name, graph in result.items(): + if graph.language == 'GLSL': + group_graphs[f'{name} group'] = copy.deepcopy(graph) + + for name, graph in group_graphs.items(): + graph.name = name + from Malt.PipelineGraph import GLSLGraphIO + io_types = [ + 'bool','float','int','uint', + 'vec2','vec3','vec4', + 'ivec2','ivec3','ivec4', + 'uvec2','uvec3','uvec4', + 'mat4', + 'sampler1D','sampler2D', + ] + graph.graph_io={ + 'NODE_GROUP_FUNCTION': GLSLGraphIO('NODE_GROUP_FUNCTION', + dynamic_input_types=io_types, + dynamic_output_types=io_types + ) + } + io = graph.graph_io['NODE_GROUP_FUNCTION'] + io.function = { + 'meta': {}, + 'name': 'NODE_GROUP_FUNCTION', + 'type': 'void', + 'parameters': [], + } + io.signature = 'void NODE_GROUP_FUNCTION()' + graph.default_graph_path = None + graph.default_global_scope = "" + graph.default_shader_src = "" + + graph.graph_type = graph.GLOBAL_GRAPH + + result.update(group_graphs) + return result def setup_resources(self): diff --git a/Malt/PipelineGraph.py b/Malt/PipelineGraph.py index 38956e82..d78b4d87 100644 --- a/Malt/PipelineGraph.py +++ b/Malt/PipelineGraph.py @@ -144,6 +144,10 @@ def generate_source(self, parameters): import textwrap from Malt.SourceTranspiler import GLSLTranspiler code = '' + include_guard = parameters.get('INCLUDE GUARD') + if include_guard: + code += f'#ifndef {include_guard}\n' + code += f'#define {include_guard}\n\n' for graph_io in self.graph_io.values(): if graph_io.name in parameters.keys() and graph_io.define: code += '#define {}\n'.format(graph_io.define) @@ -156,6 +160,8 @@ def generate_source(self, parameters): code += GLSLTranspiler.preprocessor_wrap(graph_io.shader_type, '{}\n{{\n{}\n}}'.format(graph_io.signature, textwrap.indent(parameters[graph_io.name],'\t'))) code += '\n\n' + if include_guard: + code += '#endif\n\n' return code def compile_material(self, source, include_paths=[]): diff --git a/Malt/SourceTranspiler.py b/Malt/SourceTranspiler.py index 2b14e1f1..3140ca35 100644 --- a/Malt/SourceTranspiler.py +++ b/Malt/SourceTranspiler.py @@ -4,9 +4,9 @@ class SourceTranspiler(): @classmethod - def get_source_name(self, name): + def get_source_name(self, name, prefix='_'): name = name.replace('.','_').replace(' ', '_') - name = '_' + ''.join(char for char in name if char.isalnum() or char == '_') + name = prefix + ''.join(char for char in name if char.isalnum() or char == '_') while '__' in name: name = name.replace('__','_') return name @@ -87,6 +87,10 @@ def global_declaration(self, type, size, name, initialization=None): def custom_io_reference(self, io, graph_io_type, name): return f"{io.upper()}_{graph_io_type.upper()}_{''.join(char.upper() for char in name if char.isalnum())}" + @classmethod + def io_parameter_reference(self, parameter_name, io_type): + return self.get_source_name(parameter_name, '') + @classmethod def preprocessor_wrap(self, define, content): if define is None: From 872d5a9cadf7035ba79c0552f7fc91d88c24a8c2 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Sun, 11 Sep 2022 17:03:42 +0200 Subject: [PATCH 02/23] retrieve group parameters --- BlenderMalt/MaltMaterial.py | 6 ++++++ BlenderMalt/MaltNodes/MaltNodeTree.py | 14 ++++++++++++++ BlenderMalt/MaltProperties.py | 3 ++- BlenderMalt/MaltRenderEngine.py | 2 +- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/BlenderMalt/MaltMaterial.py b/BlenderMalt/MaltMaterial.py index 6ee8255c..93b9ab38 100644 --- a/BlenderMalt/MaltMaterial.py +++ b/BlenderMalt/MaltMaterial.py @@ -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') diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 51bd03d4..6faf511d 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -102,6 +102,20 @@ def get_group_function(self): 'parameters': parameters, 'signature': signature, } + + 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 diff --git a/BlenderMalt/MaltProperties.py b/BlenderMalt/MaltProperties.py index 00017380..c75bf7d0 100644 --- a/BlenderMalt/MaltProperties.py +++ b/BlenderMalt/MaltProperties.py @@ -536,7 +536,7 @@ def get_parameter(self, key, overrides, proxys, retrieve_blender_type=False, rna material_key = ('material', material.name_full) if material_key not in proxys.keys(): path = material.malt.get_source_path() - shader_parameters = material.malt.parameters.get_parameters(overrides, proxys) + shader_parameters = material.malt.get_parameters(overrides, proxys) material_parameters = material.malt_parameters.get_parameters(overrides, proxys) from Bridge.Proxys import MaterialProxy proxys[material_key] = MaterialProxy(path, shader_parameters, material_parameters) @@ -552,6 +552,7 @@ def get_parameter(self, key, overrides, proxys, retrieve_blender_type=False, rna result = {} result['source'] = graph.get_generated_source() result['parameters'] = {} + #TODO: Optimize. Retrieve all parameters from the tree, and pass them to node.get_parameters for node in graph.nodes: if hasattr(node, 'get_source_name'): result['parameters'][node.get_source_name()] = node.get_parameters(overrides, proxys) diff --git a/BlenderMalt/MaltRenderEngine.py b/BlenderMalt/MaltRenderEngine.py index 63ae97d5..16d0d7f4 100644 --- a/BlenderMalt/MaltRenderEngine.py +++ b/BlenderMalt/MaltRenderEngine.py @@ -141,7 +141,7 @@ def add_object(obj, matrix, id): material_key = ('material',material_name) if material_key not in scene.proxys.keys(): path = slot.material.malt.get_source_path() - shader_parameters = slot.material.malt.parameters.get_parameters(overrides, scene.proxys) + shader_parameters = slot.material.malt.get_parameters(overrides, scene.proxys) material_parameters = slot.material.malt_parameters.get_parameters(overrides, scene.proxys) from Bridge.Proxys import MaterialProxy scene.proxys[material_key] = MaterialProxy(path, shader_parameters, material_parameters) From 4f9df9de2b763aa4e36145784b4562dc4750d3fc Mon Sep 17 00:00:00 2001 From: pragma37 Date: Mon, 12 Sep 2022 00:00:23 +0200 Subject: [PATCH 03/23] fix group synchronization, improve node pass synchronization --- BlenderMalt/MaltNodes/MaltNodeTree.py | 11 +++++++++++ BlenderMalt/MaltNodes/MaltNodeUITools.py | 4 ++-- BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py | 2 +- BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py | 10 +++++++++- BlenderMalt/MaltNodes/Nodes/MaltIONode.py | 14 ++++++++++---- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 6faf511d..7ec36685 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -348,6 +348,17 @@ 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 self.is_group(): + from pathlib import Path + for tree in bpy.data.node_groups: + if tree.bl_idname == 'MaltTree' and tree is not self: + for node in tree.nodes: + if node.bl_idname == 'MaltGroupNode' and node.group is self: + #Touch the file to force a recompilation + Path(tree.get_generated_source_path()).touch() + break + if force_track_shader_changes: from BlenderMalt import MaltMaterial MaltMaterial.track_shader_changes() diff --git a/BlenderMalt/MaltNodes/MaltNodeUITools.py b/BlenderMalt/MaltNodes/MaltNodeUITools.py index 6168baeb..335a788f 100644 --- a/BlenderMalt/MaltNodes/MaltNodeUITools.py +++ b/BlenderMalt/MaltNodes/MaltNodeUITools.py @@ -116,8 +116,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: diff --git a/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py b/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py index 93eef293..50b3dacb 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltFunctionNode.py @@ -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: diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py index a3c6dfaf..7f0f0022 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -18,6 +18,9 @@ def update_group(self, context): group : bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_group, update=update_group) + def get_linked_node_tree(self): + return self.group + def get_function(self, skip_overrides=True, find_replacement=False): return self.group.get_group_function() @@ -26,7 +29,12 @@ def get_source_global_parameters(self, transpiler): return src + super().get_source_global_parameters(transpiler) def draw_buttons(self, context, layout): - layout.prop(self, 'group', text='') + from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree + row = layout.row(align=True) + row.prop(self, 'group', text='') + row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set( + lambda: set_node_tree(context, self.group, self) + ) classes = [ MaltGroupNode, diff --git a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py index 3b8560c4..df9d5464 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -175,11 +175,17 @@ def remove(): def draw_buttons_ext(self, context, layout): if self.allow_custom_parameters: def refresh(): - #TODO: Overkill + def update_tree(tree): + tree.reload_nodes() + tree.update_ext(force_update=True) + update_tree(self.id_data) for tree in bpy.data.node_groups: - if tree.bl_idname == 'MaltTree': - tree.reload_nodes() - tree.update_ext(force_update=True) + if tree.bl_idname == 'MaltTree' and tree is not self.id_data: + for node in tree.nodes: + if hasattr(node, 'get_linked_node_tree') and node.get_linked_node_tree() is self.id_data: + update_tree(tree) + break + layout.operator("wm.malt_callback", text='Reload', icon='FILE_REFRESH').callback.set(refresh, 'Reload') def draw_parameters_list(owner, parameters_key): row = layout.row() From a02229102b049383409cbcce7400a6e4e133bc01 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Mon, 12 Sep 2022 15:45:38 +0200 Subject: [PATCH 04/23] improve group function signature formatting --- BlenderMalt/MaltNodes/MaltNodeTree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 7ec36685..4ee548ba 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -93,7 +93,7 @@ def get_group_function(self): '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]) + parameter_signature = ', '.join([f"{p['io']} {p['type']} {p['name']}" for p in parameters]) signature = f'void {self.get_group_source_name()}({parameter_signature})' return { 'meta': {}, From 945fc45c59da42e2a5e4a0d7a954d953fef43e02 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Mon, 12 Sep 2022 16:19:57 +0200 Subject: [PATCH 05/23] Move the group graph generation to the Bridge API --- BlenderMalt/MaltPipeline.py | 1 + Bridge/Client_API.py | 39 ++++++++++++++++++++++++++++++++++++ Malt/Pipeline.py | 40 ------------------------------------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/BlenderMalt/MaltPipeline.py b/BlenderMalt/MaltPipeline.py index 167ca4f8..4fe6c007 100644 --- a/BlenderMalt/MaltPipeline.py +++ b/BlenderMalt/MaltPipeline.py @@ -79,6 +79,7 @@ def update_pipeline(self, context): bridge = Bridge.Client_API.Bridge(path, int(self.viewport_bit_depth), debug_mode, renderdoc_path, plugin_dirs, docs_path) from Malt.Utils import LOG LOG.info('Blender {} {} {}'.format(bpy.app.version_string, bpy.app.build_branch, bpy.app.build_hash)) + bridge.generate_group_graphs() params = bridge.get_parameters() global _BRIDGE, _PIPELINE_PARAMETERS diff --git a/Bridge/Client_API.py b/Bridge/Client_API.py index 135d926b..cf1ff0c5 100644 --- a/Bridge/Client_API.py +++ b/Bridge/Client_API.py @@ -113,6 +113,45 @@ def __del__(self): def get_parameters(self): return self.parameters + def generate_group_graphs(self): + import copy + group_graphs = {} + for name, graph in self.graphs.items(): + if graph.language == 'GLSL': + group_graphs[f'{name} group'] = copy.deepcopy(graph) + + for name, graph in group_graphs.items(): + graph.name = name + from Malt.PipelineGraph import GLSLGraphIO + output_types = [ + 'bool','float','int','uint', + 'vec2','vec3','vec4', + 'ivec2','ivec3','ivec4', + 'uvec2','uvec3','uvec4', + 'mat4', + ] + input_types = [*output_types] + ['sampler1D','sampler2D'] + graph.graph_io={ + 'NODE_GROUP_FUNCTION': GLSLGraphIO('NODE_GROUP_FUNCTION', + dynamic_input_types=input_types, + dynamic_output_types=output_types + ) + } + io = graph.graph_io['NODE_GROUP_FUNCTION'] + io.function = { + 'meta': {}, + 'name': 'NODE_GROUP_FUNCTION', + 'type': 'void', + 'parameters': [], + } + io.signature = 'void NODE_GROUP_FUNCTION()' + graph.default_graph_path = None + graph.default_global_scope = "" + graph.default_shader_src = "" + graph.graph_type = graph.GLOBAL_GRAPH + + self.graphs.update(group_graphs) + @bridge_method def get_stats(self): if 'STATS' in self.shared_dict and self.shared_dict['STATS']: diff --git a/Malt/Pipeline.py b/Malt/Pipeline.py index 49d919a1..4909af26 100644 --- a/Malt/Pipeline.py +++ b/Malt/Pipeline.py @@ -85,46 +85,6 @@ def get_graphs(self): result = {} for name, graph in self.graphs.items(): result[name] = graph.get_serializable_copy() - - import copy - group_graphs = {} - for name, graph in result.items(): - if graph.language == 'GLSL': - group_graphs[f'{name} group'] = copy.deepcopy(graph) - - for name, graph in group_graphs.items(): - graph.name = name - from Malt.PipelineGraph import GLSLGraphIO - io_types = [ - 'bool','float','int','uint', - 'vec2','vec3','vec4', - 'ivec2','ivec3','ivec4', - 'uvec2','uvec3','uvec4', - 'mat4', - 'sampler1D','sampler2D', - ] - graph.graph_io={ - 'NODE_GROUP_FUNCTION': GLSLGraphIO('NODE_GROUP_FUNCTION', - dynamic_input_types=io_types, - dynamic_output_types=io_types - ) - } - io = graph.graph_io['NODE_GROUP_FUNCTION'] - io.function = { - 'meta': {}, - 'name': 'NODE_GROUP_FUNCTION', - 'type': 'void', - 'parameters': [], - } - io.signature = 'void NODE_GROUP_FUNCTION()' - graph.default_graph_path = None - graph.default_global_scope = "" - graph.default_shader_src = "" - - graph.graph_type = graph.GLOBAL_GRAPH - - result.update(group_graphs) - return result def setup_resources(self): From 5cc8d650bb5642d7652305b17c8d9aeb4e98a1a7 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Mon, 12 Sep 2022 18:55:28 +0200 Subject: [PATCH 06/23] fixes and improvements --- BlenderMalt/MaltNodes/MaltNodeTree.py | 53 +++++++++++++++++------ BlenderMalt/MaltNodes/MaltSocket.py | 3 +- BlenderMalt/MaltNodes/Nodes/MaltIONode.py | 9 ++-- Malt/SourceTranspiler.py | 2 +- 4 files changed, 46 insertions(+), 21 deletions(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 4ee548ba..62dedc14 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -28,6 +28,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 @@ -46,9 +47,14 @@ def update_graph_type(self, context): bpy.data.node_groups.remove(self) copy.name = name copy.update_ext(force_update=True)#Compile + 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', @@ -72,13 +78,15 @@ def is_active(self): return self.get_pipeline_graph() is not None def is_group(self): - return self.graph_type.endswith(' group') + return self.is_group_type def get_group_source_name(self): name = self.get_transpiler().get_source_name(self.name_full, prefix='').upper() return name - def get_group_function(self): + 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': @@ -95,13 +103,14 @@ def get_group_function(self): }) parameter_signature = ', '.join([f"{p['io']} {p['type']} {p['name']}" for p in parameters]) signature = f'void {self.get_group_source_name()}({parameter_signature})' - return { + 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 = [] @@ -292,6 +301,7 @@ 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() @@ -349,17 +359,25 @@ def update_ext(self, force_track_shader_changes=True, force_update=False): with open(source_path,'w') as f: f.write(source) - if self.is_group(): - from pathlib import Path - for tree in bpy.data.node_groups: - if tree.bl_idname == 'MaltTree' and tree is not self: - for node in tree.nodes: - if node.bl_idname == 'MaltGroupNode' and node.group is self: - #Touch the file to force a recompilation - Path(tree.get_generated_source_path()).touch() - break - 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: @@ -383,7 +401,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() @@ -684,9 +705,13 @@ def node_header_ui(self, context): if context.space_data.tree_type != 'MaltTree' or node_tree is None: return def duplicate(): - context.space_data.node_tree = node_tree.copy() + tree = node_tree.copy() + tree.reload_nodes() + tree.update_ext(force_update=True) + context.space_data.node_tree = tree 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='') diff --git a/BlenderMalt/MaltNodes/MaltSocket.py b/BlenderMalt/MaltNodes/MaltSocket.py index 8cd4df0f..8036cc94 100644 --- a/BlenderMalt/MaltNodes/MaltSocket.py +++ b/BlenderMalt/MaltNodes/MaltSocket.py @@ -112,7 +112,8 @@ def get_linked_internal(socket): if socket.is_linked == False: return None else: - link = socket.links[0] + try: link = socket.links[0] + except: return None #socket.links can be empty even if is_linked is true!?!?! if ignore_muted and link.is_muted: return None linked = link.to_socket if socket.is_output else link.from_socket diff --git a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py index df9d5464..ff188cf5 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -175,16 +175,15 @@ def remove(): def draw_buttons_ext(self, context, layout): if self.allow_custom_parameters: def refresh(): - def update_tree(tree): - tree.reload_nodes() - tree.update_ext(force_update=True) - update_tree(self.id_data) + self.id_data.reload_nodes() for tree in bpy.data.node_groups: if tree.bl_idname == 'MaltTree' and tree is not self.id_data: for node in tree.nodes: if hasattr(node, 'get_linked_node_tree') and node.get_linked_node_tree() is self.id_data: - update_tree(tree) + tree.reload_nodes() + tree.update_ext(force_update=True, force_track_shader_changes=False) break + self.id_data.update_ext(force_update=True) layout.operator("wm.malt_callback", text='Reload', icon='FILE_REFRESH').callback.set(refresh, 'Reload') def draw_parameters_list(owner, parameters_key): diff --git a/Malt/SourceTranspiler.py b/Malt/SourceTranspiler.py index 3140ca35..d419bce3 100644 --- a/Malt/SourceTranspiler.py +++ b/Malt/SourceTranspiler.py @@ -45,7 +45,7 @@ def parameter_reference(self, node_name, parameter_name, io_type): @classmethod def io_parameter_reference(self, parameter_name, io_type): - return parameter_name + pass @classmethod def is_instantiable_type(self, type): From 458e6105482f4dda8a91cf1635c0f09dc1296b82 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Mon, 12 Sep 2022 19:03:39 +0200 Subject: [PATCH 07/23] Rename group to (Group) --- BlenderMalt/MaltNodes/MaltNodeTree.py | 2 +- BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py | 4 ++-- Bridge/Client_API.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 62dedc14..a16d72b4 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -28,7 +28,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') + 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 diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py index 7f0f0022..0078de40 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -9,8 +9,8 @@ class MaltGroupNode(bpy.types.Node, MaltFunctionNodeBase): def poll_group(self, tree): group_type = self.id_data.graph_type - if group_type.endswith(' group') == False: - group_type += ' group' + if group_type.endswith(' (Group)') == False: + group_type += ' (Group)' return tree.bl_idname == 'MaltTree' and tree.graph_type == group_type def update_group(self, context): diff --git a/Bridge/Client_API.py b/Bridge/Client_API.py index cf1ff0c5..511ba133 100644 --- a/Bridge/Client_API.py +++ b/Bridge/Client_API.py @@ -118,7 +118,7 @@ def generate_group_graphs(self): group_graphs = {} for name, graph in self.graphs.items(): if graph.language == 'GLSL': - group_graphs[f'{name} group'] = copy.deepcopy(graph) + group_graphs[f'{name} (Group)'] = copy.deepcopy(graph) for name, graph in group_graphs.items(): graph.name = name From cff1867dfa6b6b67c40313f2cc4d5ab7f82e70f5 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Sat, 24 Sep 2022 22:39:31 +0200 Subject: [PATCH 08/23] fix #396 --- BlenderMalt/MaltNodes/Nodes/MaltIONode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py index ff188cf5..447e8fcc 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -109,7 +109,10 @@ def get_source_socket_reference(self, socket): if self.is_custom_socket(socket) and self.id_data.is_group() == False: return transpiler.custom_io_reference(io, self.io_type, socket.name) else: - return transpiler.io_parameter_reference(socket.name, io) + if socket.is_struct_member(): + return socket.name #Keep the dot. Should be OK for GLSL (hackish) + else: + return transpiler.io_parameter_reference(socket.name, io) def get_source_code(self, transpiler): code = '' From b4b085c392408454d02789343cc72c3a115c4972 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Mon, 26 Sep 2022 23:08:50 +0200 Subject: [PATCH 09/23] simplify and fix custom io references --- BlenderMalt/MaltNodes/Nodes/MaltIONode.py | 7 ++----- Malt/SourceTranspiler.py | 6 +----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py index 447e8fcc..2736fca6 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -106,13 +106,10 @@ def is_custom_socket(self, socket): def get_source_socket_reference(self, socket): transpiler = self.id_data.get_transpiler() io = 'out' if self.is_output else 'in' - if self.is_custom_socket(socket) and self.id_data.is_group() == False: + if self.is_custom_socket(socket): return transpiler.custom_io_reference(io, self.io_type, socket.name) else: - if socket.is_struct_member(): - return socket.name #Keep the dot. Should be OK for GLSL (hackish) - else: - return transpiler.io_parameter_reference(socket.name, io) + return transpiler.io_parameter_reference(socket.name, io) def get_source_code(self, transpiler): code = '' diff --git a/Malt/SourceTranspiler.py b/Malt/SourceTranspiler.py index d419bce3..7216f8d7 100644 --- a/Malt/SourceTranspiler.py +++ b/Malt/SourceTranspiler.py @@ -45,7 +45,7 @@ def parameter_reference(self, node_name, parameter_name, io_type): @classmethod def io_parameter_reference(self, parameter_name, io_type): - pass + return parameter_name @classmethod def is_instantiable_type(self, type): @@ -87,10 +87,6 @@ def global_declaration(self, type, size, name, initialization=None): def custom_io_reference(self, io, graph_io_type, name): return f"{io.upper()}_{graph_io_type.upper()}_{''.join(char.upper() for char in name if char.isalnum())}" - @classmethod - def io_parameter_reference(self, parameter_name, io_type): - return self.get_source_name(parameter_name, '') - @classmethod def preprocessor_wrap(self, define, content): if define is None: From ddc47f37cb2f1a4291e29429dd7d04f0c70dcbad Mon Sep 17 00:00:00 2001 From: pragma37 Date: Tue, 27 Sep 2022 20:44:49 +0200 Subject: [PATCH 10/23] fix #402 --- BlenderMalt/MaltNodes/MaltNodeTree.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 6dbf38ee..97407e6c 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -312,6 +312,16 @@ def reload_nodes(self): 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(): @@ -326,7 +336,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 = '' From 7a7a90f6531ac7e618a29492e4832e680890d696 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Wed, 28 Sep 2022 22:28:30 +0200 Subject: [PATCH 11/23] hide edit button when there's no group selected (fix #405) --- BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py index 0078de40..c80ad684 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -32,9 +32,10 @@ def draw_buttons(self, context, layout): from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree row = layout.row(align=True) row.prop(self, 'group', text='') - row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set( - lambda: set_node_tree(context, self.group, self) - ) + if self.group is not None: + row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set( + lambda: set_node_tree(context, self.group, self) + ) classes = [ MaltGroupNode, From d1dbbbc3e1033f6065e61de0ca1fcf1108134d79 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Wed, 28 Sep 2022 22:29:08 +0200 Subject: [PATCH 12/23] allow reordering custom IO parameters --- BlenderMalt/MaltNodes/Nodes/MaltIONode.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py index 2736fca6..aaa5773c 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -208,6 +208,17 @@ def add_custom_socket(): def remove_custom_socket(): parameters.remove(index) col.operator("wm.malt_callback", text='', icon='REMOVE').callback.set(remove_custom_socket, 'Remove') + def move_up(): + if index > 0: + parameters.move(index, index - 1) + setattr(owner, index_key, index - 1) + col.operator("wm.malt_callback", text='', icon='TRIA_UP').callback.set(move_up, 'Move Up') + def move_down(): + if index < len(parameters) - 1: + parameters.move(index, index + 1) + setattr(owner, index_key, index + 1) + col.operator("wm.malt_callback", text='', icon='TRIA_DOWN').callback.set(move_down, 'Move Down') + if self.allow_custom_pass: draw_parameters_list(self.get_custom_pass_io(), 'outputs' if self.is_output else 'inputs') else: From 9fab4a1b0d4d8f20d6758b28f15cdbfc16169eb0 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Wed, 28 Sep 2022 22:29:50 +0200 Subject: [PATCH 13/23] reorder group io types --- Bridge/Client_API.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Bridge/Client_API.py b/Bridge/Client_API.py index 3c13a102..bea66f4c 100644 --- a/Bridge/Client_API.py +++ b/Bridge/Client_API.py @@ -124,11 +124,10 @@ def generate_group_graphs(self): graph.name = name from Malt.PipelineGraph import GLSLGraphIO output_types = [ - 'bool','float','int','uint', - 'vec2','vec3','vec4', - 'ivec2','ivec3','ivec4', - 'uvec2','uvec3','uvec4', - 'mat4', + 'float','vec2','vec3','vec4', + 'int','ivec2','ivec3','ivec4', + 'uint','uvec2','uvec3','uvec4', + 'bool','mat4', ] input_types = [*output_types] + ['sampler1D','sampler2D'] graph.graph_io={ From 37a40816dfdc0d1d16feabc2f19a869b7e6404ff Mon Sep 17 00:00:00 2001 From: pragma37 Date: Thu, 29 Sep 2022 23:08:55 +0200 Subject: [PATCH 14/23] Fix graph reloading --- BlenderMalt/MaltNodes/MaltNodeTree.py | 2 ++ Bridge/Client_API.py | 3 +++ 2 files changed, 5 insertions(+) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 97407e6c..c65b3026 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -456,6 +456,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: diff --git a/Bridge/Client_API.py b/Bridge/Client_API.py index bea66f4c..91587ffc 100644 --- a/Bridge/Client_API.py +++ b/Bridge/Client_API.py @@ -117,6 +117,8 @@ def generate_group_graphs(self): import copy group_graphs = {} for name, graph in self.graphs.items(): + if '(Group)' in name: + continue if graph.language == 'GLSL': group_graphs[f'{name} (Group)'] = copy.deepcopy(graph) @@ -220,6 +222,7 @@ def reload_graphs(self, graph_types): 'graph_types': graph_types }) self.graphs.update(self.connections['REFLECTION'].recv()) + self.generate_group_graphs() @bridge_method def get_shared_buffer(self, ctype, size): From 68b3c8a39f7fd321a150b36f11882955bda2c191 Mon Sep 17 00:00:00 2001 From: pragma37 Date: Sat, 1 Oct 2022 16:36:31 +0200 Subject: [PATCH 15/23] Skip includes for node groups (fix #409) --- BlenderMalt/MaltNodes/MaltNodeTree.py | 1 + Malt/PipelineGraph.py | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index c65b3026..71ca8802 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -290,6 +290,7 @@ def get_source(output): if hasattr(node, 'get_source_global_parameters'): shader['GLOBAL'] += node.get_source_global_parameters(transpiler) 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(): diff --git a/Malt/PipelineGraph.py b/Malt/PipelineGraph.py index d78b4d87..9025572e 100644 --- a/Malt/PipelineGraph.py +++ b/Malt/PipelineGraph.py @@ -152,8 +152,9 @@ def generate_source(self, parameters): if graph_io.name in parameters.keys() and graph_io.define: code += '#define {}\n'.format(graph_io.define) code += '\n\n' + self.default_global_scope + '\n\n' - for file in self.lib_files: - code += f'#include "{file}"\n' + if parameters.get('SKIP INCLUDES', False) == False: + for file in self.lib_files: + code += f'#include "{file}"\n' code += '\n\n' + parameters['GLOBAL'] + '\n\n' for graph_io in self.graph_io.values(): if graph_io.name in parameters.keys(): From ad1b1e0bf34fac277a3c44acbecb2a0ba2df047a Mon Sep 17 00:00:00 2001 From: pragma37 Date: Sun, 2 Oct 2022 22:43:52 +0200 Subject: [PATCH 16/23] Fix node group linking (fix #412) --- BlenderMalt/MaltNodes/MaltNodeTree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 71ca8802..4dc0ebb5 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -87,7 +87,7 @@ def is_group(self): return self.is_group_type def get_group_source_name(self): - name = self.get_transpiler().get_source_name(self.name_full, prefix='').upper() + name = self.get_transpiler().get_source_name(self.name, prefix='').upper() return name def get_group_function(self, force_update=False): From cbb4dc0101814eb89643e240565c38c06d9c9307 Mon Sep 17 00:00:00 2001 From: Kolupsy Date: Sat, 8 Oct 2022 17:01:41 +0200 Subject: [PATCH 17/23] add new UI options to create node groups --- BlenderMalt/MaltNodes/MaltNodeUITools.py | 72 ++++++++++++++++++++ BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py | 56 +++++++++++++-- 2 files changed, 121 insertions(+), 7 deletions(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeUITools.py b/BlenderMalt/MaltNodes/MaltNodeUITools.py index 633067ec..8abb6926 100644 --- a/BlenderMalt/MaltNodes/MaltNodeUITools.py +++ b/BlenderMalt/MaltNodes/MaltNodeUITools.py @@ -342,12 +342,83 @@ 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 mathutils import Vector + sd: bpy.types.SpaceNodeEditor = context.space_data + 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() + for n in selected: + sd.edit_tree.nodes.remove(n) + + 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 + 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, ] @@ -443,6 +514,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) diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py index c80ad684..2b172874 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -1,23 +1,52 @@ from Malt.PipelineParameters import Parameter, Type import bpy from BlenderMalt.MaltNodes.Nodes.MaltFunctionNode import MaltFunctionNodeBase +from BlenderMalt.MaltNodes.MaltNodeTree import MaltTree class MaltGroupNode(bpy.types.Node, MaltFunctionNodeBase): - + + bl_idname = 'MaltGroupNode' bl_label = "Group Node" - def poll_group(self, tree): - group_type = self.id_data.graph_type - if group_type.endswith(' (Group)') == False: - group_type += ' (Group)' - return tree.bl_idname == 'MaltTree' and tree.graph_type == group_type + def poll_group(self, tree: MaltTree): + group_type = self.get_graph_type_from_parent(self.id_data) + + def is_target_nested_in_source(target: MaltTree, source: MaltTree): + if source is None: + return False + for node in (n for n in source.nodes.values() if isinstance(n, MaltGroupNode)): + if node.group is target or is_target_nested_in_source(node.group, target): + return True + return False + + return ( + tree.bl_idname == 'MaltTree' + and tree.graph_type == group_type + and not tree == self.id_data + and not is_target_nested_in_source(self.id_data, tree) + ) + + @staticmethod + def get_graph_type_from_parent(malt_tree: MaltTree) -> str: + graph_type: str = malt_tree.graph_type + suffix = ' (Group)' + if not graph_type.endswith(suffix): + graph_type += suffix + return graph_type def update_group(self, context): self.setup(context) + self.setup_width() #has to be called explicitely because 'setup' will only call 'setup_width' in some cases group : bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll_group, update=update_group) + def create_new_group(self) -> MaltTree: + nt = bpy.data.node_groups.new('Malt Node Tree', 'MaltTree') + nt.graph_type = self.get_graph_type_from_parent(self.id_data) + self.group = nt + return nt + def get_linked_node_tree(self): return self.group @@ -30,13 +59,26 @@ def get_source_global_parameters(self, transpiler): def draw_buttons(self, context, layout): from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree + from BlenderMalt.MaltNodes.MaltNodeUITools import OT_MaltAddNodeGroup row = layout.row(align=True) - row.prop(self, 'group', text='') + row.template_ID(self, 'group', new=OT_MaltAddNodeGroup.bl_idname) if self.group is not None: row.operator('wm.malt_callback', text = '', icon = 'GREASEPENCIL').callback.set( lambda: set_node_tree(context, self.group, self) ) + def calc_node_width(self, point_size, dpi) -> float: + import blf + blf.size(0, point_size, dpi) + + button_padding = 150 #Magic number to account for the other buttons on the node UI + if getattr(self.group, 'users', 0) > 1: + button_padding += 30 + + width = super().calc_node_width(point_size, dpi) + width = max(width, blf.dimensions(0, getattr(self.group, 'name', ''))[0] + button_padding) + return width + classes = [ MaltGroupNode, ] From 1eb3c57291ffbfc8d33a15e757e4c16df35d62b3 Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Sat, 8 Oct 2022 22:37:09 +0200 Subject: [PATCH 18/23] fix node copy in malt_nodes_to_group --- BlenderMalt/MaltNodes/MaltNodeUITools.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeUITools.py b/BlenderMalt/MaltNodes/MaltNodeUITools.py index 8abb6926..ee3e8fe2 100644 --- a/BlenderMalt/MaltNodes/MaltNodeUITools.py +++ b/BlenderMalt/MaltNodes/MaltNodeUITools.py @@ -384,6 +384,7 @@ def execute(self, context: bpy.types.Context): from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree from mathutils import Vector sd: bpy.types.SpaceNodeEditor = context.space_data + initial_tree = sd.edit_tree selected: list[bpy.types.Node] = context.selected_nodes avg_loc = Vector((0.0, 0.0)) for node in selected: @@ -391,9 +392,6 @@ def execute(self, context: bpy.types.Context): avg_loc /= len(selected) bpy.ops.node.clipboard_copy() - for n in selected: - sd.edit_tree.nodes.remove(n) - 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 @@ -404,6 +402,8 @@ def execute(self, context: bpy.types.Context): 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) return {'FINISHED'} def get_absolute_node_position(node: bpy.types.Node): From cb72e50d1afc1d019cfcdd75a730de0ea4b6b52e Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Sat, 8 Oct 2022 22:37:41 +0200 Subject: [PATCH 19/23] don't copy IONodes in malt_nodes_to_group --- BlenderMalt/MaltNodes/MaltNodeUITools.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/BlenderMalt/MaltNodes/MaltNodeUITools.py b/BlenderMalt/MaltNodes/MaltNodeUITools.py index ee3e8fe2..6f4cc155 100644 --- a/BlenderMalt/MaltNodes/MaltNodeUITools.py +++ b/BlenderMalt/MaltNodes/MaltNodeUITools.py @@ -385,6 +385,9 @@ def execute(self, context: bpy.types.Context): from mathutils import Vector 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: From 633fa53786acf2f1948ef2c30b1a2199f5843770 Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Tue, 1 Nov 2022 19:58:54 +0100 Subject: [PATCH 20/23] fix node groups overrides (#434) --- BlenderMalt/MaltProperties.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BlenderMalt/MaltProperties.py b/BlenderMalt/MaltProperties.py index 1eae78ac..198ae15d 100644 --- a/BlenderMalt/MaltProperties.py +++ b/BlenderMalt/MaltProperties.py @@ -456,7 +456,7 @@ def add_override(self, property_name, override_name): parameter.min = main_prop.get('min') parameter.max = main_prop.get('max') property[new_name] = parameter - self.setup(property, replace_parameters= False) + self.setup(property, replace_parameters= False, skip_private=False) def remove_override(self, property): rna = self.get_rna() From 075a95bfca3f0ed1bcc47d80149b00a934ad8957 Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Wed, 9 Nov 2022 17:27:29 +0100 Subject: [PATCH 21/23] Fix OT_MaltNodesToGroup (fix #442) --- BlenderMalt/MaltNodes/MaltNodeTree.py | 4 ++++ BlenderMalt/MaltNodes/MaltNodeUITools.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index 51a89d3b..be52813e 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -760,7 +760,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 diff --git a/BlenderMalt/MaltNodes/MaltNodeUITools.py b/BlenderMalt/MaltNodes/MaltNodeUITools.py index 794f8d87..8b2dccdc 100644 --- a/BlenderMalt/MaltNodes/MaltNodeUITools.py +++ b/BlenderMalt/MaltNodes/MaltNodeUITools.py @@ -382,7 +382,9 @@ def poll(cls, context: bpy.types.Context): 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: @@ -407,6 +409,7 @@ def execute(self, context: bpy.types.Context): 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): From 2e9eb9df41113b9952f4b716aede0ec0a4208bfa Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Sat, 26 Nov 2022 19:00:14 +0100 Subject: [PATCH 22/23] Fix nested node group uniforms (fix #450) --- BlenderMalt/MaltNodes/MaltNodeTree.py | 1 - 1 file changed, 1 deletion(-) diff --git a/BlenderMalt/MaltNodes/MaltNodeTree.py b/BlenderMalt/MaltNodes/MaltNodeTree.py index be52813e..81e598d9 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -296,7 +296,6 @@ def get_source(output): source = pipeline_graph.generate_source(shader) if self.is_group(): source = source.replace('void NODE_GROUP_FUNCTION()', self.get_group_function()['signature']) - source = source.replace('NODE_GROUP_FUNCTION', self.get_group_source_name()) self['source'] = source return self['source'] From fd35ae502aeaf331313a8708eb5138fed6f19106 Mon Sep 17 00:00:00 2001 From: Miguel Pozo Date: Fri, 29 Mar 2024 16:53:01 +0100 Subject: [PATCH 23/23] Fix blf.size dpi --- BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py index 2b172874..36d2876d 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -69,7 +69,7 @@ def draw_buttons(self, context, layout): def calc_node_width(self, point_size, dpi) -> float: import blf - blf.size(0, point_size, dpi) + blf.size(0, point_size * (dpi / 72.0)) button_padding = 150 #Magic number to account for the other buttons on the node UI if getattr(self.group, 'users', 0) > 1: