diff --git a/CHANGELOG.md b/CHANGELOG.md index af405f1..b2888c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ +1.1.0 + +- added: `autobuild` button (partial auto-buildfor now) +- added: make sent object selected + 1.0.3 - fixed: Send to render layer compatibility with blender 3.4 diff --git a/OP_auto_build.py b/OP_auto_build.py index c247f3f..f5da6cd 100644 --- a/OP_auto_build.py +++ b/OP_auto_build.py @@ -75,17 +75,19 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator): if (render_wkspace := bpy.data.workspaces.get('GP Render')): context.window.workspace = render_wkspace else: - render_wkspace_filepath = Path(bpy.utils.user_resource('SCRIPTS'), 'startup', 'GP', 'startup.blend') + render_wkspace_filepath = Path(bpy.utils.user_resource('SCRIPTS'), 'startup', 'bl_app_templates_user', 'GP', 'startup.blend') + print('render workspace', render_wkspace_filepath.exists()) ret = bpy.ops.workspace.append_activate(idname='GP Render', filepath=str(render_wkspace_filepath)) + print('ret: ', ret) if ret != {'FINISHED'}: # Fallback to workspace template shipped with addon (TODO : add template blend file in addon) render_wkspace_filepath = Path(__file__).parent / 'workspaces' / 'startup.blend' ret = bpy.ops.workspace.append_activate(idname='GP Render', filepath=str(render_wkspace_filepath)) - + if ret != {'FINISHED'}: + print('No GP render workspace available') ## Group all adjacent layer type - ## Renumber File outputs diff --git a/OP_merge_layers.py b/OP_merge_layers.py index 2a2726c..fe44d5d 100644 --- a/OP_merge_layers.py +++ b/OP_merge_layers.py @@ -114,6 +114,73 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None) return ng, out +def merge_gplayer_viewlayers(ob, act=None, layers=None): + 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] + + 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)] + + vl_names = [v.name for v in vls] + + for n in reversed(rd_scn.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 + 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) + + # remove input and output from group + # grp.inputs.remove(sockin) # do not clear inside !! + # grp.outputs.remove(sockout) # do not clear inside !! + 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]) + break + for i in range(len(grp.outputs))[::-1]: + if grp.outputs[i].name == sockout.name: + ngroup.outputs.remove(ngroup.outputs[i]) + break + + # remove render_layer node + rd_scn.node_tree.nodes.remove(n) + + # assign view layer from active to selected + for l in layers: + l.viewlayer_render = act.viewlayer_render + + ## delete unused_vl + + # 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) + class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator): bl_idname = "gp.merge_viewlayers_to_active" bl_label = "Merge selected layers view_layers" @@ -130,69 +197,19 @@ class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator): act = ob.data.layers.active layers = [l for l in ob.data.layers if l.select and l != act] - if not act.viewlayer_render: - self.report({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned') - return {'CANCELLED'} + ## Tested in func + # rd_scn = bpy.data.scenes.get('Render') + # if not rd_scn: + # self.report({'ERROR'}, 'Viewlayers needs to be generated first!') + # return {'CANCELLED'} - rd_scn = bpy.data.scenes.get('Render') - if not rd_scn: - self.report({'ERROR'}, 'Viewlayers needs to be generated first!') - return {'CANCELLED'} - - # 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): - if n.type == 'R_LAYERS' and n.layer in vl_names: - 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) - - # remove input and output from group - # grp.inputs.remove(sockin) # do not clear inside !! - # grp.outputs.remove(sockout) # do not clear inside !! - 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]) - break - for i in range(len(grp.outputs))[::-1]: - if grp.outputs[i].name == sockout.name: - ngroup.outputs.remove(ngroup.outputs[i]) - break - - # remove render_layer node - rd_scn.node_tree.nodes.remove(n) + # if not act.viewlayer_render: + # self.report({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned') + # return {'CANCELLED'} - # assign view layer from active to selected - for l in layers: - l.viewlayer_render = act.viewlayer_render - - ## delete unused_vl - - # 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) - + ret = merge_gplayer_viewlayers(ob, act=act, layers=layers) + if isinstance(ret, tuple): + self.report(*ret) return {"FINISHED"} class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator): diff --git a/__init__.py b/__init__.py index a8d033b..60a9aa4 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, 0, 2), + "version": (1, 1, 0), "blender": (2, 93, 0), "location": "View3D", "warning": "", diff --git a/gen_vlayer.py b/gen_vlayer.py index 2c14823..12e20a1 100644 --- a/gen_vlayer.py +++ b/gen_vlayer.py @@ -234,7 +234,17 @@ def get_set_viewlayer_from_gp(ob, l, scene=None): # ob.data = ob.data.copy() # create duplicate (this will also affect the one in original scene !!!) scene.collection.objects.link(ob) ob.hide_viewport = ob.hide_render = False - + + ## set object active in default viewlayer + + # if (avl := scene.view_layers.get('ViewLayer')): + # # This select the object in source scene + # avl.objects.active = ob + # # avl.objects.active.select_set(True) + nob = scene.collection.objects.get(ob.name) + if nob: + nob.select_set(True) + # create viewlayer vl_name = f'{ob.name} / {l.info}' vl = fn.get_view_layer(vl_name, scene=scene) diff --git a/ui.py b/ui.py index 369709a..4f46c66 100644 --- a/ui.py +++ b/ui.py @@ -174,8 +174,7 @@ class GPEXP_PT_gp_dopesheet_ui(Panel): def draw(self, context): layout = self.layout - ## TODO: add auto-build - # layout.operator('gp_export.render_auto_build') + layout.operator('gp_export.render_auto_build') if context.object: layout.label(text=f'Object: {context.object.name}') if context.object.data.users > 1: