import bpy from . import fn class GPEXP_OT_reconnect_render_layer(bpy.types.Operator): bl_idname = "gp.reconnect_render_layer" bl_label = "Reconnect Render Layer" bl_description = "Reconnect 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): node_tree = context.scene.node_tree nodes = node_tree.nodes changed = [] for n in nodes: if not n.select or not n.type == 'R_LAYERS': continue if not ' / ' in n.layer: continue if n.outputs[0].is_linked: # already connected continue # get namme obname = n.layer.split()[0] grp_name = f'NG_{obname}' # get nodegroup grp = nodes.get(grp_name) if not grp: print(f'{n.name} Node group not found : {n.layer} !-> {grp_name}') continue inp = grp.inputs.get(n.layer) if not inp: print(f'{n.name} no inputs name "{n.layer}" in group {grp_name}') continue # reconnect node_tree.links.new(n.outputs[0], inp) changed.append(f'{n.name} ({n.layer}) to {grp_name}') if changed: self.report({'INFO'}, f'{len(changed)} nodes reconnected') else: self.report({'WARNING'}, f'Could not reconnect, see console') 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 def execute(self, context): ## Compositing scene where nodes lives compo_scene = context.scene ## render scene where viewlayer lives rd_scn = fn.get_render_scene(create=False) if not rd_scn: self.report({'ERROR'}, 'No render scene found') return {'CANCELLED'} nodes = compo_scene.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 if bpy.app.version < (4,0,0): 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 else: g_inputs = [s for s in ngroup.interface.items_tree if s.in_out == 'INPUT'] g_outputs = [s for s in ngroup.interface.items_tree if s.in_out == 'OUTPUT'] for i in range(len(grp.inputs))[::-1]: if grp.inputs[i].name == sockin.name: ngroup.interface.remove(g_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.interface.remove(g_outputs[i]) break for sub_n in reversed(inside_nodes): ngroup.nodes.remove(sub_n) # Remove render_layer node nodes.remove(n) return {"FINISHED"} class GPEXP_OT_set_active_fileout_to_compout(bpy.types.Operator): bl_idname = "gp.set_active_fileout_to_compout" bl_label = "Set Active File Output To Composite" bl_description = "Use active slot of active file output node to set scene output settings (swap connection)" bl_options = {"REGISTER"} @classmethod def poll(cls, context): return context.scene.use_nodes\ and context.scene.node_tree\ and context.scene.node_tree.nodes.active\ and context.scene.node_tree.nodes.active.type == 'OUTPUT_FILE' relink_composite : bpy.props.BoolProperty( name='Relink Composite', default=True, description='In case file slot is linked, swap link to Composite file', options={'SKIP_SAVE'}, ) def invoke(self, context, event): self.fo = context.scene.node_tree.nodes.active if not len(self.fo.file_slots): self.report({'ERROR'}, 'no slots in active file output') return {'CANCELLED'} # check if active slot has a source if not self.fo.inputs[self.fo.active_input_index].is_linked: return self.execute(context) # check if composite linked out = context.scene.node_tree.nodes.get('Composite') if not out or not out.inputs[0].is_linked: self.compo_out_from_link = '' return self.execute(context) # compo linked, pop panel to choose replace or not self.compo_out_from_link = out.inputs[0].links[0].from_node.name return context.window_manager.invoke_props_dialog(self) def draw(self, context): layout = self.layout col = layout.column() col.label(text=f'Composite node connected to: {self.compo_out_from_link}') col.label(text=f'Would you like to replace by file output slot source ?') layout.prop(self, 'relink_composite') def execute(self, context): # if comp fn.set_scene_output_from_active_fileout_item() idx = self.fo.active_input_index sl = self.fo.file_slots[idx] sk = self.fo.inputs[idx] if not sk.is_linked: self.report({'INFO'}, f'Outut changed to match {sl.path} (slot was not linked)') return {'FINISHED'} ## If linked replace links to Composite node if not self.relink_composite: return {'FINISHED'} ntree = context.scene.node_tree links = context.scene.node_tree.links nodes = context.scene.node_tree.nodes out = nodes.get('Composite') if not out: out = fn.create_node('COMPOSITE', tree=ntree) fo_loc = fn.real_loc(self.fo) out.location = (fo_loc.x, fo_loc.y + 160) # if out.inputs[0].is_linked: # self.report({'WARNING'}, f'Outut changed to match {sl.path} (Composite node already linked)') lnk = sk.links[0] from_sk = sk.links[0].from_socket links.remove(lnk) links.new(from_sk, out.inputs[0]) return {"FINISHED"} classes=( GPEXP_OT_reconnect_render_layer, GPEXP_OT_delete_render_layer, GPEXP_OT_set_active_fileout_to_compout, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)