From 7fa914e4388dc3fb406cfa67294ac93771bb3f93 Mon Sep 17 00:00:00 2001 From: pullusb Date: Mon, 22 Apr 2024 11:59:30 +0200 Subject: [PATCH] Add templates options for fileoutput 1.7.3 - added: Optional string templates for fileoutputs - add `base_path, file_slot, layer_slot` arguments to operators - possible keywords are as follow: - '{object}' : Set object name - '{gplayer}' : Set Gp layer name - Default template when not passed: base_path = `//render/{object}` (for multilayer exr, default to `//render/{object}/{object}_`) file_slot = `{gplayer}/{gplayer}_` layer_slot = `{gplayer}` --- CHANGELOG.md | 13 ++++++ OP_add_layer.py | 78 ++++++++---------------------------- OP_auto_build.py | 8 +++- __init__.py | 2 +- gen_vlayer.py | 102 ++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 128 insertions(+), 75 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1309bc6..b993ea3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,19 @@ Activate / deactivate layer opacity according to prefix Activate / deactivate all masks using MA layers --> +1.7.3 + +- added: Optional string templates for fileoutputs + - add `base_path, file_slot, layer_slot` arguments to operators + - possible keywords are as follow: + - '{object}' : Set object name + - '{gplayer}' : Set Gp layer name + - Default template when not passed: + base_path = `//render/{object}` + (for multilayer exr, default to `//render/{object}/{object}_`) + file_slot = `{gplayer}/{gplayer}_` + layer_slot = `{gplayer}` + 1.7.2 - added: selectable output popup in `connect to file output` operator diff --git a/OP_add_layer.py b/OP_add_layer.py index 0224c00..f73194d 100644 --- a/OP_add_layer.py +++ b/OP_add_layer.py @@ -2,39 +2,6 @@ import bpy from . import gen_vlayer, fn -def add_layer_to_render(ob, node_scene=None): - '''Send GP object to render layer - return a tuple with report message''' - - # ob = ob or bpy.context.object - layer = ob.data.layers.active - if not layer: - return ('ERROR', 'No active layer') - - node_scene = fn.get_compo_scene(scene_name=node_scene, create=True) - - ct = 0 - # send scene ? - hidden = 0 - for l in ob.data.layers: - if not l.select: - if not l.viewlayer_render: - # 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, node_scene=node_scene) - - if l.hide: - hidden += 1 - ct += 1 - - if hidden: - return ('WARNING', f'{hidden}/{ct} layers are hidden!') - - else: - return ('INFO', f'{ct} layer(s) added') - class GPEXP_OT_add_layer_to_render(bpy.types.Operator): bl_idname = "gp.add_layer_to_render" bl_label = "Add Gp Layer as render nodes" @@ -49,39 +16,20 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator): node_scene : bpy.props.StringProperty(default='', description='Scene where to add nodes, Abort if not found', options={'SKIP_SAVE'}) + # File output templates + base_path : bpy.props.StringProperty(name='Base Path', default='', options={'SKIP_SAVE'}) + file_slot : bpy.props.StringProperty(name='File Slot', default='', options={'SKIP_SAVE'}) + layer_slot : bpy.props.StringProperty(name='Layer Slot', default='', options={'SKIP_SAVE'}) + def execute(self, context): - ret = add_layer_to_render(context.object, node_scene=self.node_scene) + ret = gen_vlayer.add_layer_to_render(context.object, node_scene=self.node_scene, + base_path=self.base_path, file_slot=self.file_slot, layer_slot=self.layer_slot) if isinstance(ret, tuple): self.report({ret[0]}, ret[1]) if ret[0] == 'ERROR': return {'CANCELLED'} return {"FINISHED"} - -def add_object_to_render(mode='ALL', scene='', node_scene=''): - context = bpy.context - - if scene: - scn = fn.get_render_scene(scene) - else: - scn = fn.get_render_scene() - - if node_scene: - node_scn = fn.get_compo_scene(scene_name=node_scene, create=True) - if not node_scn: - return ('ERROR', f'/!\ Node Scene "{node_scene}" not found ! Abort "Add object to Render" !') - else: - # if not passed add in render scene - node_scn = scn - - excludes = [] # ['MA', 'IN'] # Get list dynamically - if mode == 'SELECTED': - gen_vlayer.export_gp_objects([o for o in context.selected_objects if o.type == 'GPENCIL'], exclude_list=excludes, scene=scn, node_scene=node_scn) - - elif mode == 'ALL': - gen_vlayer.export_gp_objects([o for o in context.scene.objects if o.type == 'GPENCIL' and not o.hide_get() and fn.is_valid_name(o.name)], exclude_list=excludes, scene=scn, node_scene=node_scn) - - ## send operator with mode ALL or SELECTED to batch build class GPEXP_OT_add_objects_to_render(bpy.types.Operator): bl_idname = "gp.add_object_to_render" @@ -96,8 +44,8 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator): mode : bpy.props.EnumProperty( items=( - ('ALL', 'All', 'All objects', 0), - ('SELECTED', 'Selected', 'Selected objects', 0), + ('ALL', 'All', 'All objects'), + ('SELECTED', 'Selected', 'Selected objects'), ), default='ALL', options={'SKIP_SAVE'}, description='Choice to send all or only selected objects') @@ -108,8 +56,14 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator): node_scene : bpy.props.StringProperty(default='', description='Scene where to add nodes, Abort if not found', options={'SKIP_SAVE'}) + # File output templates + base_path : bpy.props.StringProperty(name='Base Path', default='', options={'SKIP_SAVE'}) + file_slot : bpy.props.StringProperty(name='File Slot', default='', options={'SKIP_SAVE'}) + layer_slot : bpy.props.StringProperty(name='Layer Slot', default='', options={'SKIP_SAVE'}) + def execute(self, context): - ret = add_object_to_render(mode=self.mode, scene=self.scene, node_scene=self.node_scene) + ret = gen_vlayer.add_object_to_render(mode=self.mode, scene=self.scene, node_scene=self.node_scene, + base_path=self.base_path, file_slot=self.file_slot, layer_slot=self.layer_slot) if isinstance(ret, tuple): self.report({ret[0]}, ret[1]) if ret[0] == 'ERROR': diff --git a/OP_auto_build.py b/OP_auto_build.py index 7dd83c7..a8a48da 100644 --- a/OP_auto_build.py +++ b/OP_auto_build.py @@ -118,6 +118,11 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator): add_preview : BoolProperty(name='Add Preview', default=True, description='Create preview with stacked alpha over on render layers') + # File output templates + base_path : bpy.props.StringProperty(name='Base Path', default='', options={'SKIP_SAVE'}) + file_slot : bpy.props.StringProperty(name='File Slot', default='', options={'SKIP_SAVE'}) + layer_slot : bpy.props.StringProperty(name='Layer Slot', default='', options={'SKIP_SAVE'}) + def invoke(self, context, event): # return self.execute(context) return context.window_manager.invoke_props_dialog(self) @@ -241,7 +246,8 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator): ## Send all GP to render scene print('Send all GP to render scene (Create render scene if needed)') # bpy.ops.gp.add_object_to_render(mode="ALL") # Ops to send all - gen_vlayer.export_gp_objects(ob_list, exclude_list=self.excluded_prefix, scene=render_scn, node_scene=node_scene) # Create render scene OTF + gen_vlayer.export_gp_objects(ob_list, exclude_list=self.excluded_prefix, scene=render_scn, node_scene=node_scene, + base_path=self.base_path, file_slot=self.file_slot, layer_slot=self.layer_slot) # Create render scene OTF ## Switch to new Render Scene print('Switch to new Render Scene') diff --git a/__init__.py b/__init__.py index 0cd4303..0aef64b 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, 7, 2), + "version": (1, 7, 3), "blender": (3, 0, 0), "location": "View3D", "warning": "", diff --git a/gen_vlayer.py b/gen_vlayer.py index 28a1496..d34eb3b 100644 --- a/gen_vlayer.py +++ b/gen_vlayer.py @@ -53,8 +53,7 @@ def add_rlayer(layer_name, scene=None, node_scene=None, location=None, color=Non # FIXME (Maybe just use "base_path") def connect_render_layer(rlayer, ng=None, out=None, frame=None, - base_path='//render/{object}', file_slot='{gplayer}/{gplayer}_', - multi_base_path='//render/{object}/{object}_', layer_slot='{gplayer}' + base_path=None, file_slot=None, layer_slot=None ): '''Connect a render layer node to a fileoutput Return existing or created nodegroup and file output nodes @@ -67,14 +66,21 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None, base_path (str, optional): Template for base path when used with EXR file_slot (str, optional): Template for slots when used with EXR - multi_base_path (str, optional): Template for base path when used with Multilayer EXR layer_slot (str, optional): Template for slots when used with Multilayer EXR - Available keyword : {object} {gplayer} + File output template strings keyword : + {object} : Set object name + {gplayer} : Set Gp layer name Return: tuple(node, node) Nodegroup node, file_output node ''' + multi_base_path = base_path or '//render/{object}/{object}_' + base_path = base_path or '//render/{object}' + file_slot = file_slot or '{gplayer}/{gplayer}_' + layer_slot = layer_slot or '{gplayer}' + + node_tree = rlayer.id_data # get node_tree from rlayer nodes = node_tree.nodes @@ -311,10 +317,19 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None, return ng, out -def get_set_viewlayer_from_gp(ob, l, scene=None, node_scene=None): +def get_set_viewlayer_from_gp(ob, l, scene=None, node_scene=None, + base_path=None, file_slot=None, layer_slot=None): '''setup ouptut from passed gp obj > layer - scene: scene to set viewlayer - node_scene: where to add compo node (use scene if not passed)''' + scene: scene to set viewlayer + node_scene: where to add compo node (use scene if not passed) + + base_path (str, optional) : File output Base Path template + file_slot (str, optional) : File output slot template for individual files + layer_slot (str, optional) : File output slot for multilayer EXR + + Return: + viewlayer, render-layer node + ''' scene = scene or fn.get_render_scene() node_scene = node_scene or fn.get_compo_scene() or scene @@ -420,7 +435,8 @@ def get_set_viewlayer_from_gp(ob, l, scene=None, node_scene=None): cp.use_custom_color = True cp.color = l.channel_color - connect_render_layer(cp, frame=frame) + connect_render_layer(cp, frame=frame, + base_path=base_path, file_slot=file_slot, layer_slot=layer_slot) fn.rearrange_frames(node_tree) @@ -475,14 +491,15 @@ def get_set_viewlayer_from_gp(ob, l, scene=None, node_scene=None): n.update() # reorder render layers nodes within frame - connect_render_layer(cp, frame=frame) + connect_render_layer(cp, frame=frame, + base_path=base_path, file_slot=file_slot, layer_slot=layer_slot) # re-arrange all frames (since the offset probably overlapped) fn.rearrange_frames(node_tree) return vl, cp -def export_gp_objects(oblist, exclude_list=[], scene=None, node_scene=None): +def export_gp_objects(oblist, exclude_list=[], scene=None, node_scene=None, base_path=None, file_slot=None, layer_slot=None): # Skip layer containing element in exclude list if not isinstance(oblist, list): oblist = [oblist] @@ -500,4 +517,67 @@ def export_gp_objects(oblist, exclude_list=[], scene=None, node_scene=None): l.viewlayer_render = fn.get_view_layer('exclude', scene=scene).name # assign "exclude" continue - _vl, _cp = get_set_viewlayer_from_gp(ob, l, scene=scene, node_scene=node_scene) # scene=fn.get_render_scene()) \ No newline at end of file + get_set_viewlayer_from_gp(ob, l, scene=scene, node_scene=node_scene, + base_path=base_path, file_slot=file_slot, layer_slot=layer_slot) + +def add_layer_to_render(ob, node_scene=None, base_path=None, file_slot=None, layer_slot=None): + '''Send GP object to render layer + + return a tuple with report message''' + + # ob = ob or bpy.context.object + layer = ob.data.layers.active + if not layer: + return ('ERROR', 'No active layer') + + node_scene = fn.get_compo_scene(scene_name=node_scene, create=True) + + ct = 0 + # send scene ? + hidden = 0 + for l in ob.data.layers: + if not l.select: + if not l.viewlayer_render: + # 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 + + get_set_viewlayer_from_gp(ob, l, node_scene=node_scene, base_path=base_path, file_slot=file_slot, layer_slot=layer_slot) + + if l.hide: + hidden += 1 + ct += 1 + + if hidden: + return ('WARNING', f'{hidden}/{ct} layers are hidden!') + + else: + return ('INFO', f'{ct} layer(s) added') + + +def add_object_to_render(mode='ALL', scene='', node_scene='', base_path=None, file_slot=None, layer_slot=None): + context = bpy.context + + if scene: + scn = fn.get_render_scene(scene) + else: + scn = fn.get_render_scene() + + if node_scene: + node_scn = fn.get_compo_scene(scene_name=node_scene, create=True) + if not node_scn: + return ('ERROR', f'/!\ Node Scene "{node_scene}" not found ! Abort "Add object to Render" !') + else: + # if not passed add in render scene + node_scn = scn + + excludes = [] # ['MA', 'IN'] # Get list dynamically + if mode == 'SELECTED': + export_gp_objects([o for o in context.selected_objects if o.type == 'GPENCIL'], + exclude_list=excludes, scene=scn, node_scene=node_scn, + base_path=base_path, file_slot=file_slot, layer_slot=layer_slot) + + elif mode == 'ALL': + export_gp_objects([o for o in context.scene.objects if o.type == 'GPENCIL' and not o.hide_get() and fn.is_valid_name(o.name)], + exclude_list=excludes, scene=scn, node_scene=node_scn, + base_path=base_path, file_slot=file_slot, layer_slot=layer_slot)