From a0a1647bf943c9af6a94c9243778cc532490feab Mon Sep 17 00:00:00 2001 From: Pullusb Date: Thu, 23 Sep 2021 19:14:48 +0200 Subject: [PATCH] viewlayer management and fixes 0.3.7 - fix: set render scene res at 100% at creation - fix: exclude VL assignation - feat: delete a render_layer (and add concerned gp layers to exclude) - feat: reactivate all viewlayers - feat: activate only selected viewlayer for fast re-render - ui: rearrange + new buttons --- CHANGELOG.md | 21 ++++++++---- OP_add_layer.py | 4 +-- OP_connect_toggle.py | 80 ++++++++++++++++++++++++++++++++++++++++++++ OP_manage_outputs.py | 43 ++++++++++++++++++++++++ __init__.py | 2 +- fn.py | 19 +++++++++++ ui.py | 43 +++++++++++++++++++----- 7 files changed, 194 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fbb56..90f970d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,23 +14,32 @@ Activate / deactivate layer opaticty according to prefix Activate / deactivate all masks using MA layers --> +0.3.7 + +- fix: set render scene res at 100% at creation +- fix: exclude VL assignation +- feat: delete a render_layer (and add concerned gp layers to exclude) +- feat: reactivate all viewlayers +- feat: activate only selected viewlayer for fast re-render +- ui: rearrange + new buttons + 0.3.6 -change: output settings switch from PNG to EXR -fix: set render scene output (preview) to jpeg fast to write -fix: correct copy output format ops +- change: output settings switch from PNG to EXR +- fix: set render scene output (preview) to jpeg fast to write +- fix: correct copy output format ops 0.3.5: -feat: set full opacity -> skip chosen prefix (MA by default) +- feat: set full opacity -> skip chosen prefix (MA by default) 0.3.4: -feat: swap cams button, code copied from `bg_plane_manager` +- feat: swap cams button, code copied from `bg_plane_manager` 0.3.3: -fix: norm name : lowercase first (else bad naming break prefix) +- fix: norm name : lowercase first (else bad naming break prefix) 0.3.2 diff --git a/OP_add_layer.py b/OP_add_layer.py index 24c3b73..3440d15 100644 --- a/OP_add_layer.py +++ b/OP_add_layer.py @@ -26,8 +26,8 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator): for l in ob.data.layers: if not l.select: if not l.viewlayer_render: - # TODO : need to link, can reaise error if object is not linked in Render scene yet - l.viewlayer_render == fn.get_view_layer('exclude').name + # TODO : need to link, can raise error if object is not linked in Render scene yet + l.viewlayer_render = fn.get_view_layer('exclude').name continue gen_vlayer.get_set_viewlayer_from_gp(ob, l) diff --git a/OP_connect_toggle.py b/OP_connect_toggle.py index 8874fd0..fd2542c 100644 --- a/OP_connect_toggle.py +++ b/OP_connect_toggle.py @@ -56,8 +56,88 @@ class GPEXP_OT_reconnect_render_layer(bpy.types.Operator): return {"FINISHED"} +class GPEXP_OT_delete_render_layer(bpy.types.Operator): + bl_idname = "gp.delete_render_layer" + bl_label = "Delete Render Layer" + bl_description = "Delete selected render layers" + bl_options = {"REGISTER"} + + @classmethod + def poll(cls, context): + return True + + # mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'}) + + def execute(self, context): + + rd_scn = bpy.data.scenes.get('Render') + if not rd_scn: + self.report({'ERROR'}, 'Viewlayers needs to be generated first!') + return {'CANCELLED'} + + nodes = rd_scn.node_tree.nodes + # 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)] + + rlayers_nodes = [n for n in nodes if n.select and n.type == 'R_LAYERS'] + + vls = [rd_scn.view_layers.get(n.layer) for n in rlayers_nodes if rd_scn.view_layers.get(n.layer)] + + vl_names = [v.name for v in vls] + ## disable layers using those VL + for ob in [o for o in rd_scn.objects if o.type == 'GPENCIL']: + for l in ob.data.layers: + if l.viewlayer_render in vl_names: + l.viewlayer_render = fn.get_view_layer('exclude').name + + for n in reversed(rlayers_nodes): + # Disconnect linked + for lnk in n.outputs[0].links: + grp = lnk.to_node + if grp.type != 'GROUP': + continue + if not grp.name.startswith('NG'): + continue + sockin = lnk.to_socket + sockout = grp.outputs.get(sockin.name) + if not sockout: + continue + for grplink in sockout.links: + if grplink.to_node.type != 'OUTPUT_FILE': + continue + fo_socket = grplink.to_socket + fo = grplink.to_node + fo.file_slots.remove(fo_socket) + + inside_nodes = [] + ngroup = grp.node_tree + for i in range(len(grp.inputs))[::-1]: + if grp.inputs[i].name == sockin.name: + ngroup.inputs.remove(ngroup.inputs[i]) + + gp_in_socket = ngroup.nodes['Group Input'].outputs[i] + for lnk in gp_in_socket.links: + inside_nodes += fn.all_connected_forward(lnk.to_node) + list(set(inside_nodes)) + break + for i in range(len(grp.outputs))[::-1]: + if grp.outputs[i].name == sockout.name: + ngroup.outputs.remove(ngroup.outputs[i]) + break + + for sub_n in reversed(inside_nodes): + ngroup.nodes.remove(sub_n) + + # remove render_layer node + rd_scn.node_tree.nodes.remove(n) + + return {"FINISHED"} + + classes=( GPEXP_OT_reconnect_render_layer, +GPEXP_OT_delete_render_layer, ) def register(): diff --git a/OP_manage_outputs.py b/OP_manage_outputs.py index 6cc80c5..b3acee6 100644 --- a/OP_manage_outputs.py +++ b/OP_manage_outputs.py @@ -167,11 +167,54 @@ class GPEXP_OT_normalize_outnames(bpy.types.Operator): return {"FINISHED"} +class GPEXP_OT_enable_all_viewlayers(bpy.types.Operator): + bl_idname = "gp.enable_all_viewlayers" + bl_label = "Enable All Viewlayers" + bl_description = "Enable all View layers except those named 'exclude' 'View Layer'" + bl_options = {"REGISTER"} + + def execute(self, context): + rd_scn = bpy.data.scenes.get('Render') + if not rd_scn: + print('SKIP, no Render scene') + return {"CANCELLED"} + + vl_list = [vl for vl in rd_scn.view_layers if not vl.use and vl.name not in {'View Layer', 'exclude'}] + for v in vl_list: + v.use = True + + self.report({"INFO"}, f'{len(vl_list)} ViewLayers Reactivated') + return {"FINISHED"} + +class GPEXP_OT_activate_only_selected_layers(bpy.types.Operator): + bl_idname = "gp.activate_only_selected_layers" + bl_label = "Activate Only Selected Layers" + bl_description = "Activate only selected node view layer , excluding all others" + bl_options = {"REGISTER"} + + def execute(self, context): + rd_scn = bpy.data.scenes.get('Render') + if not rd_scn: + print('SKIP, no Render scene') + return {"CANCELLED"} + + nodes = rd_scn.node_tree.nodes + + rlayers_nodes = [n for n in nodes if n.select and n.type == 'R_LAYERS'] + vls = [rd_scn.view_layers.get(n.layer) for n in rlayers_nodes if rd_scn.view_layers.get(n.layer)] + for v in rd_scn.view_layers: + v.use = v in vls + + self.report({"INFO"}, f'Now only {len(vls)} viewlayer active (/{len(rd_scn.view_layers)})') + return {"FINISHED"} + classes=( GPEXP_OT_mute_toggle_output_nodes, GPEXP_OT_set_output_node_format, GPEXP_OT_number_outputs, +GPEXP_OT_enable_all_viewlayers, +GPEXP_OT_activate_only_selected_layers, # GPEXP_OT_normalize_outnames, ) diff --git a/__init__.py b/__init__.py index 11eab30..6fad1fa 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": (0, 3, 6), + "version": (0, 3, 7), "blender": (2, 93, 0), "location": "View3D", "warning": "", diff --git a/fn.py b/fn.py index 4e2bad9..c497f54 100644 --- a/fn.py +++ b/fn.py @@ -113,6 +113,8 @@ def set_settings(scene=None): scene.grease_pencil_settings.antialias_threshold = 0 scene.render.film_transparent = True scene.view_settings.view_transform = 'Standard' + + scene.render.resolution_percentage = 100 # output (fast write settings since this is just to delete afterwards...) scene.render.filepath = '//render/preview/preview_' @@ -370,6 +372,23 @@ def connect_to_group_input(n): return val return False +def all_connected_forward(n, nlist=[]): + '''return list of all forward connected nodes recursively (include passed nodes)''' + for o in n.outputs: + if o.is_linked: + for lnk in o.links: + if lnk.to_node.type == 'GROUP_OUTPUT': + if n not in nlist: + return nlist + [n] + else: + return nlist + else: + nlist = all_connected_forward(lnk.to_node, nlist) + if n in nlist: + return nlist + return nlist + [n] + + def clear_nodegroup_content_if_disconnected(ngroup): '''Get a nodegroup.node_tree delete orphan nodes that are not connected from group input node diff --git a/ui.py b/ui.py index cb8a76b..0ec52e9 100644 --- a/ui.py +++ b/ui.py @@ -2,7 +2,6 @@ import bpy from bpy.types import Panel # from .preferences import get_addon_prefs - # Node view panel class GPEXP_PT_gp_node_ui(Panel): bl_space_type = "NODE_EDITOR" @@ -28,18 +27,50 @@ class GPEXP_PT_gp_node_ui(Panel): # if cam and cam_name == 'draw_cam': # cam_name = f'{cam.parent.name} > {cam_name}' row.operator("gp.swap_render_cams", text=text, icon='OUTLINER_OB_CAMERA') + + # Live checks + if scn.render.resolution_percentage != 100: + layout.label(text='Res Percentage not 100%', icon='ERROR') + layout.prop(scn.render, 'resolution_percentage') + + exclude_count = len([vl for vl in scn.view_layers if not vl.use and vl.name not in {'View Layer', 'exclude'}]) + if exclude_count: + # layout.label(text=f'{exclude_count} Excluded View Layers !') + layout.operator('gp.enable_all_viewlayers', text=f'Reactivate {exclude_count} Excluded View Layers') + if not scn.use_nodes or not scn.node_tree: return layout.separator() # TODO : add advanced bool checkbox to hide some options from the user - col = layout.column(align=True) + + layout.label(text='View layers:') ct = len([n for n in context.scene.node_tree.nodes if n.type == 'R_LAYERS' and n.select]) + col = layout.column(align=True) + + col.operator('gp.activate_only_selected_layers', text=f'Activate Only {ct} Layer Nodes') + col.enabled = ct > 0 + + col = layout.column(align=True) + txt = f'Merge {ct} Layer Nodes' col.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text=txt).disconnect = True col.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text='Merge (keep connect)').disconnect = False col.enabled = ct > 1 + + layout.separator() + col = layout.column() + subcol = col.column() + n = context.scene.node_tree.nodes.active + if n: + subcol.enabled = n and n.type == 'R_LAYERS' and not n.outputs[0].is_linked + else: + subcol.enabled = False + + subcol.operator('gp.reconnect_render_layer', icon='ANIM', text=f'Reconnect {ct} Layer Node') + + col.operator('gp.delete_render_layer', icon='TRACKING_CLEAR_FORWARDS', text=f'Delete {ct} Layer Node') layout.separator() @@ -53,14 +84,8 @@ class GPEXP_PT_gp_node_ui(Panel): col=layout.column() col.label(text='Clean and updates:') - row = col.row() - n = context.scene.node_tree.nodes.active - if n: - row.enabled = n and n.type == 'R_LAYERS' and not n.outputs[0].is_linked - else: - row.enabled = False - row.operator('gp.reconnect_render_layer', icon='ANIM', text='Reconnect') + col.separator() col.operator('gp.clean_compo_tree', icon='BRUSHES_ALL', text='Clean Nodes') # NODE_CORNER