diff --git a/BlenderMalt/MaltMaterial.py b/BlenderMalt/MaltMaterial.py index e5a40f5f..c671b306 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 99503461..7e2247a9 100644 --- a/BlenderMalt/MaltNodes/MaltNodeTree.py +++ b/BlenderMalt/MaltNodes/MaltNodeTree.py @@ -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 @@ -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', @@ -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 @@ -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): @@ -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(): @@ -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 = '' @@ -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: @@ -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() @@ -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: @@ -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 @@ -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() @@ -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='') @@ -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 diff --git a/BlenderMalt/MaltNodes/MaltNodeUITools.py b/BlenderMalt/MaltNodes/MaltNodeUITools.py index b52abd75..1f73c9f2 100644 --- a/BlenderMalt/MaltNodes/MaltNodeUITools.py +++ b/BlenderMalt/MaltNodes/MaltNodeUITools.py @@ -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: @@ -343,6 +343,80 @@ 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, @@ -350,6 +424,8 @@ def execute(self, context: bpy.types.Context): OT_MaltSetTreePreview, OT_MaltConnectTreePreview, OT_MaltCycleSubCategories, + OT_MaltAddNodeGroup, + OT_MaltNodesToGroup, NODE_OT_add_malt_subcategory_node, ] @@ -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) diff --git a/BlenderMalt/MaltNodes/MaltSocket.py b/BlenderMalt/MaltNodes/MaltSocket.py index e1acb918..b3c2b2d8 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 6d7301ce..38693645 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: @@ -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): diff --git a/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py new file mode 100644 index 00000000..36d2876d --- /dev/null +++ b/BlenderMalt/MaltNodes/Nodes/MaltGroupNode.py @@ -0,0 +1,91 @@ +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: 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 + + 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): + from BlenderMalt.MaltNodes.MaltNodeTree import set_node_tree + from BlenderMalt.MaltNodes.MaltNodeUITools import OT_MaltAddNodeGroup + row = layout.row(align=True) + 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 / 72.0)) + + 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, +] + +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 e3539aab..0418a8fa 100644 --- a/BlenderMalt/MaltNodes/Nodes/MaltIONode.py +++ b/BlenderMalt/MaltNodes/Nodes/MaltIONode.py @@ -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] index = graph_io.custom_output_start_index @@ -172,11 +174,16 @@ def remove(): def draw_buttons_ext(self, context, layout): if self.allow_custom_parameters: def refresh(): - #TODO: Overkill + self.id_data.reload_nodes() 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: + 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): row = layout.row() @@ -206,6 +213,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: diff --git a/BlenderMalt/MaltPipeline.py b/BlenderMalt/MaltPipeline.py index 74ac7e66..4ad098c1 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/BlenderMalt/MaltProperties.py b/BlenderMalt/MaltProperties.py index 5d0f0ac1..d9be469f 100644 --- a/BlenderMalt/MaltProperties.py +++ b/BlenderMalt/MaltProperties.py @@ -454,7 +454,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() @@ -541,7 +541,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) @@ -557,6 +557,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 25fcdbb0..90e0f226 100644 --- a/BlenderMalt/MaltRenderEngine.py +++ b/BlenderMalt/MaltRenderEngine.py @@ -151,7 +151,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) diff --git a/Bridge/Client_API.py b/Bridge/Client_API.py index ddff8f0e..64939ef7 100644 --- a/Bridge/Client_API.py +++ b/Bridge/Client_API.py @@ -113,6 +113,46 @@ 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 '(Group)' in name: + continue + 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 = [ + 'float','vec2','vec3','vec4', + 'int','ivec2','ivec3','ivec4', + 'uint','uvec2','uvec3','uvec4', + 'bool','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']: @@ -182,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): diff --git a/Malt/PipelineGraph.py b/Malt/PipelineGraph.py index 39518289..a8ab16b4 100644 --- a/Malt/PipelineGraph.py +++ b/Malt/PipelineGraph.py @@ -145,18 +145,25 @@ 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) 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(): 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 0d1be953..6eba6c6f 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