diff --git a/CHANGELOG.md b/CHANGELOG.md index d08ce76..caa7822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ Activate / deactivate layer opacity according to prefix Activate / deactivate all masks using MA layers --> +1.3.5 + +- added: button to exclude viewlayers and nodes by selection or by hided layers + 1.3.4 - added: multi object merge diff --git a/OP_manage_outputs.py b/OP_manage_outputs.py index 1dc3606..7472f01 100644 --- a/OP_manage_outputs.py +++ b/OP_manage_outputs.py @@ -26,7 +26,8 @@ class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator): class GPEXP_OT_number_outputs(bpy.types.Operator): bl_idname = "gp.number_outputs" bl_label = "Number Outputs" - bl_description = "(Re)Number the outputs to have ordered file by name in export directories\nCtrl+Clic : Delete numbering" + bl_description = "(Re)Number the outputs to have ordered file by name in export directories\ + \nCtrl+Clic : Delete numbering" bl_options = {"REGISTER"} @classmethod diff --git a/OP_merge_layers.py b/OP_merge_layers.py index 8f61b57..fb0f246 100644 --- a/OP_merge_layers.py +++ b/OP_merge_layers.py @@ -274,6 +274,85 @@ class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator): self.report(*ret) return {"FINISHED"} +class GPEXP_OT_remove_viewlayer_on_selected(bpy.types.Operator): + bl_idname = "gp.remove_viewlayer_on_selected" + bl_label = "Exclude Viewlayer" + bl_description = "Set exclude view layers on selected gp layers\ + \nRemove associated nodes in Render scene nodetree\ + \nCtrl + Click : Affect selected GP objects, not only active" + bl_options = {"REGISTER"} + + @classmethod + def poll(cls, context): + return context.object and context.object.type == 'GPENCIL' + + # multi_object : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}) + remove_all_hidden : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'}) + + @classmethod + def description(cls, context, properties) -> str: + if properties.remove_all_hidden: + return "Set HIDDEN gp layers to 'exclude' viewlayers\ + \nremoving associated nodes in Render scene nodetree\ + \nCtrl + Click : Affect selected GP objects, else only active" + else: + return "Set SELECTED gp layers to 'exclude' viewlayers\ + \nremoving associated nodes in Render scene nodetree\ + \nCtrl + Click : Affect selected GP objects, else only active" + + def invoke(self, context, event): + self.multi_object = event.ctrl + return self.execute(context) + + def execute(self, context): + ob = context.object + + ## Force use of render scene (?) + rd_scn = bpy.data.scenes.get('Render') + if not rd_scn: + self.report({'ERROR'}, 'No render scene found') + return {'CANCELLED'} + + if self.remove_all_hidden: + if self.multi_object: + layers = [l for ob in context.selected_objects if ob.type == 'GPENCIL' for l in ob.data.layers if l.hide] + else: + layers = [l for l in ob.data.layers if l.select] + + else: + if self.multi_object: + layers = [l for ob in context.selected_objects if ob.type == 'GPENCIL' for l in ob.data.layers if l.select] + else: + layers = [l for l in ob.data.layers if l.select] + + if not layers: + self.report({'ERROR'}, 'Some layers need to be selected to exclude render viewlayer') + return {'CANCELLED'} + + layers = list(set(layers)) + + ## Prepare report / prints in console + exclude_message = ['Layer list set to exclude:'] + print('\nLayer list to exclude:') + for l in layers: + vl_name = l.viewlayer_render if l.viewlayer_render else 'None' + mess = f'{l.id_data.name}: {l.info} (previous: {vl_name})' + print(mess) + exclude_message.append(mess) + + view_layers = [rd_scn.view_layers.get(l.viewlayer_render) for l in layers\ + if l.viewlayer_render and rd_scn.view_layers.get(l.viewlayer_render)] + + ## remove nodes associated with those viewlayers + fn.remove_nodes_by_viewlayer(view_layers, scene=rd_scn) + + ## Set selected those layer viewlayer exclude + for l in layers: + l.viewlayer_render = fn.get_view_layer('exclude').name + + fn.show_message_box(exclude_message) + return {"FINISHED"} + class GPEXP_OT_merge_preview_ouput(bpy.types.Operator): bl_idname = "gp.merge_preview_ouput" bl_label = "Merge Preview Output" @@ -429,6 +508,7 @@ GPEXP_OT_merge_viewlayers_to_active, GPEXP_OT_auto_merge_adjacent_prefix, GPEXP_OT_merge_selected_dopesheet_layers,# unused GPEXP_OT_merge_selected_viewlayer_nodes, +GPEXP_OT_remove_viewlayer_on_selected, GPEXP_OT_merge_preview_ouput, ) diff --git a/__init__.py b/__init__.py index 380f83e..e5ce3ff 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ bl_info = { "name": "GP Render", "description": "Organise export of gp layers through compositor output", "author": "Samuel Bernou", - "version": (1, 3, 4), + "version": (1, 3, 5), "blender": (2, 93, 0), "location": "View3D", "warning": "", diff --git a/app_templates/GP_Render/startup.blend b/app_templates/GP_Render/startup.blend index 042b184..ecf43ef 100644 Binary files a/app_templates/GP_Render/startup.blend and b/app_templates/GP_Render/startup.blend differ diff --git a/fn.py b/fn.py index 217ec14..b075625 100644 --- a/fn.py +++ b/fn.py @@ -410,32 +410,18 @@ def get_frames_bbox(node_tree): return frames_bbox - ## -- nodes helper functions -def merge_gplayer_viewlayers(ob=None, act=None, layers=None): - '''ob is not needed if active and layers are passed''' - if ob is None: - ob = bpy.context.object - if act is None: - act = ob.data.layers.active - if layers is None: - layers = [l for l in ob.data.layers if l.select and l != act] +def remove_nodes_by_viewlayer(viewlayer_list, scene=None): + '''Take a list of viewlayer and optionaly a scene to target nodetree + remove nodes related to this viewlayer in nodetree + ''' - rd_scn = bpy.data.scenes.get('Render') - if not rd_scn: - return ({'ERROR'}, 'Viewlayers needs to be generated first!') + scene = scene or bpy.context.scene - if not act.viewlayer_render: - return ({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned') + vl_names = [v.name for v in viewlayer_list] - # list layers and viewlayers - vls = [rd_scn.view_layers.get(l.viewlayer_render) for l in layers - if l.viewlayer_render and l.viewlayer_render != act.viewlayer_render and rd_scn.view_layers.get(l.viewlayer_render)] - - vl_names = [v.name for v in vls] - - for n in reversed(rd_scn.node_tree.nodes): + for n in reversed(scene.node_tree.nodes): if n.type == 'R_LAYERS' and n.layer in vl_names: for lnk in n.outputs[0].links: grp = lnk.to_node @@ -468,20 +454,49 @@ def merge_gplayer_viewlayers(ob=None, act=None, layers=None): ngroup.outputs.remove(ngroup.outputs[i]) break - # remove render_layer node - rd_scn.node_tree.nodes.remove(n) + # Remove render_layer node + scene.node_tree.nodes.remove(n) - # assign view layer from active to selected +def merge_gplayer_viewlayers(ob=None, act=None, layers=None): + '''ob is not needed if active and layers are passed''' + if ob is None: + ob = bpy.context.object + if act is None: + act = ob.data.layers.active + if layers is None: + layers = [l for l in ob.data.layers if l.select and l != act] + + if act is None: + return ({'ERROR'}, 'Active layer not found. Should be active layer on active object!') + + rd_scn = bpy.data.scenes.get('Render') + if not rd_scn: + return ({'ERROR'}, 'Viewlayers needs to be generated first!') + + if not act.viewlayer_render: + return ({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned') + + # list layers and viewlayers + vls = [rd_scn.view_layers.get(l.viewlayer_render) for l in layers + if l.viewlayer_render and l.viewlayer_render != act.viewlayer_render and rd_scn.view_layers.get(l.viewlayer_render)] + + # Remove viewlayer related nodes + remove_nodes_by_viewlayer(vls, rd_scn) + + # Assign view layer from active to selected for l in layers: l.viewlayer_render = act.viewlayer_render - ## delete unused_vl + ## Delete unused viewlayers () - # used_vl_name = [n.layer for n in rd_scn.node_tree.nodes if n.type == 'R_LAYERS' and n.layer] + used_vl_name = [n.layer for n in rd_scn.node_tree.nodes if n.type == 'R_LAYERS' and n.layer] for vl in vls: - rd_scn.view_layers.remove(vl) - # if not vl.name in used_vl_name: - # rd_scn.view_layers.remove(vl) + # rd_scn.view_layers.remove(vl) + if vl.name == 'exclude': + # keep exclude + continue + if not vl.name in used_vl_name: + rd_scn.view_layers.remove(vl) def group_adjacent_layer_prefix_rlayer(ob, excluded_prefix=[], first_name=True): '''Set viewlayer and renderlayers by Gp layer adjacent prefix diff --git a/ui.py b/ui.py index b612fc0..d3d9cc8 100644 --- a/ui.py +++ b/ui.py @@ -213,6 +213,9 @@ class GPEXP_PT_gp_dopesheet_ui(Panel): row.enabled= ct > 1 col.operator('gpexp.auto_merge_adjacent_prefix', icon='SELECT_EXTEND') + row = col.row(align=True) + row.operator('gp.remove_viewlayer_on_selected', text=f'Exclude {ct} layers', icon='X').remove_all_hidden = False + row.operator('gp.remove_viewlayer_on_selected', text='', icon='HIDE_ON').remove_all_hidden = True ## all and objects layout.separator()