diff --git a/CHANGELOG.md b/CHANGELOG.md index 991f792..7f03a9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,11 @@ Activate / deactivate layer opacity according to prefix Activate / deactivate all masks using MA layers --> +1.3.0 + +- added: Preview with a combined alpha over of all render layer available +- added: button to clear preview nodes + 1.2.3 - added: autobuild set each gp objects data to single user diff --git a/OP_merge_layers.py b/OP_merge_layers.py index d59193c..7ed4d3e 100644 --- a/OP_merge_layers.py +++ b/OP_merge_layers.py @@ -1,6 +1,7 @@ import bpy import re from math import isclose +from itertools import groupby from . import fn from . import gen_vlayer @@ -114,6 +115,126 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None) return ng, out +def merge_compositor_preview(context=None, clear=False): + '''Merge all active render layer with alpha over.location[0]+450, ng.location[1]+50), width=600) + Create a dedicated node group and connect to compositor output + return tuple(merge nodegroup, compositor out) + ''' + + context = context or bpy.context + node_tree = context.scene.node_tree + nodes = node_tree.nodes + links = node_tree.links + + ## identify all duplicated render layer and remove them, then recreate the preview + for n in reversed(nodes): + if n.type in ('R_LAYERS', 'GROUP') and n.get('is_preview'): + nodes.remove(n) + + ng_name = f'merge_NG_preview' + ## clear unused nodes groups duplication + fn.clear_nodegroup(ng_name, full_clear=True) + + if clear: + ## Restore to jpg out ? + # im_settings = context.scene.render.image_settings + # im_settings.file_format = 'JPEG' + # im_settings.color_mode = 'RGB' + # im_settings.quality = 0 + return + + ## Get all RL node per object block (node frames), sort object by name (should a) + + all_rlayers = [n for n in nodes if n.type == 'R_LAYERS'] + all_rlayers.sort(key=lambda x: x.label, reverse=True) + + ## ! All at once doe not work, need to separate by individual object first + # all_rlayers.sort(key=lambda x: (x.label, -fn.real_loc(x).y)) + + ## Sort all render layer by object (either by start name or by frames) + ## sort order within by order in layer stack (check -n.location.y or associated gp object) + + grps = groupby(all_rlayers, key=lambda x : x.label.split(' /')[0]) + + rlayers_groups = {k : sorted(list(grp), key=lambda x: x.location.y, reverse=True) for k, grp in grps if k} + + # Debug prints + # for ob_key_name, rl_group in rlayers_groups.items(): + # print(ob_key_name) + # for n in rl_group: + # print(f'- {n.label}') + # print() + # print('Done') + + ## Recreate the render layer nodes duplicated at the side of the frames + + pos_x = 2400 + pos_y = 30 + offset_y = 180 + comp_list = [] + for k, rl_group in rlayers_groups.items(): + for rl in rl_group: + comp = nodes.new('CompositorNodeRLayers') + comp['is_preview'] = 1 + comp.label = rl.label # f'.{rl.label}' + comp.name = f'.{rl.name}' + comp.scene = rl.scene + comp.layer = rl.layer + comp.color = rl.color + comp.use_custom_color = True + comp.width = rl.width + comp.show_preview = False + comp.location = (pos_x, pos_y) + comp_list.append(comp) + pos_y -= offset_y + + + ### Create the nodegroup for clean alpha over merge + + ## Need a unique nodegroup name, increment name while nodegroup exists + # while bpy.data.node_groups.get(ng_name): # nodes.get(ng_name) + # if not re.search(r'(\d+)$', ng_name): + # ng_name += '_02' # if not ending with a number add _02 + # ng_name = re.sub(r'(\d+)(?!.*\d)', lambda x: str(int(x.group(1))+1).zfill(len(x.group(1))), ng_name) + + print(f'Create preview merge nodegroup {ng_name}') + + ngroup = bpy.data.node_groups.new(ng_name, 'CompositorNodeTree') + ng = fn.create_node('CompositorNodeGroup', tree=node_tree, location=(3000, 0), width=400) + ng.node_tree = ngroup + ng.name = ngroup.name + ng['is_preview'] = 1 + fn.create_node('NodeGroupInput', tree=ngroup, location=(-600,0)) + fn.create_node('NodeGroupOutput', tree=ngroup, location=(1000,0)) + + # Create inputs and links to node_group + for rln in comp_list: + rln.outputs['Image'] + sockin = ng.inputs.new('NodeSocketColor', rln.layer) + sockin = ng.inputs[-1] + links.new(rln.outputs['Image'], sockin) + + fn.nodegroup_merge_inputs(ng.node_tree) + ng.update() + + # Create composite out (if needed) and connect + composite_out = next((n for n in nodes if n.type == 'COMPOSITE'), None) + if not composite_out: + composite_out = fn.create_node('CompositorNodeComposite', tree=node_tree, location=(ng.location[0]+450, ng.location[1]+50), width=140) + composite_out.use_alpha = True + links.new(ng.outputs[0], composite_out.inputs[0]) + + im_settings = context.scene.render.image_settings + # im_settings.file_format = 'JPEG' + # im_settings.color_mode = 'RGB' + # im_settings.quality = 0 + im_settings.file_format = 'OPEN_EXR' + im_settings.color_mode = 'RGBA' + im_settings.color_depth = '16' + im_settings.exr_codec = 'ZIP' + + return ng, composite_out + class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator): bl_idname = "gp.merge_viewlayers_to_active" bl_label = "Merge selected layers view_layers" @@ -145,6 +266,21 @@ class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator): self.report(*ret) return {"FINISHED"} +class GPEXP_OT_merge_preview_ouput(bpy.types.Operator): + bl_idname = "gp.merge_preview_ouput" + bl_label = "Merge Preview Output" + bl_description = "Merge all active render layers to an output" + bl_options = {"REGISTER"} + + # @classmethod + # def poll(cls, context): + # return True + + clear : bpy.props.BoolProperty(default=False, options={'SKIP_SAVE'}) + + def execute(self, context): + merge_compositor_preview(context=context, clear=self.clear) + return {"FINISHED"} class GPEXP_OT_auto_merge_adjacent_prefix(bpy.types.Operator): bl_idname = "gpexp.auto_merge_adjacent_prefix" @@ -285,6 +421,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_merge_preview_ouput, ) def register(): diff --git a/__init__.py b/__init__.py index aecbdf0..f21e445 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, 2, 3), + "version": (1, 3, 0), "blender": (2, 93, 0), "location": "View3D", "warning": "", diff --git a/fn.py b/fn.py index d051750..85514d5 100644 --- a/fn.py +++ b/fn.py @@ -182,9 +182,16 @@ def set_settings(scene=None, aa=True): # output (fast write settings since this is just to delete afterwards...) scene.render.filepath = f'//render/preview/{scene.name}/preview_' - scene.render.image_settings.file_format = 'JPEG' - scene.render.image_settings.color_mode = 'RGB' - scene.render.image_settings.quality = 0 + im_settings = scene.render.image_settings + # im_settings.file_format = 'JPEG' + # im_settings.color_mode = 'RGB' + # im_settings.quality = 0 + + ## Same as output nodes + im_settings.file_format = 'OPEN_EXR' + im_settings.color_mode = 'RGBA' + im_settings.color_depth = '16' + im_settings.exr_codec = 'ZIP' def scene_aa(scene=None, toggle=True): '''Change scene AA settings and commute AA nodes according to toggle''' @@ -1702,4 +1709,4 @@ def clean_mats_duplication(ob, skip_different_materials=True): if diff_ct: print(f'{diff_ct} mat skipped >> same name but different color settings!') - # return ('INFO', f'{diff_ct} mat skipped >> same name but different color settings!') \ No newline at end of file + # return ('INFO', f'{diff_ct} mat skipped >> same name but different color settings!') diff --git a/ui.py b/ui.py index c7945f2..10ec309 100644 --- a/ui.py +++ b/ui.py @@ -117,6 +117,10 @@ class GPEXP_PT_gp_node_ui(Panel): subcol = col.column() subcol.enabled = bool(ct) subcol.operator('gp.number_outputs', icon='LINENUMBERS_ON', text=txt).mode = 'SELECTED' + + row=layout.row(align=True) + row.operator('gp.merge_preview_ouput', icon='NODETREE', text='Set Preview') + row.operator('gp.merge_preview_ouput', icon='X', text='').clear = True # subcol.operator('gp.normalize_outnames', icon='SYNTAX_OFF', text=f'Normalize Paths {ct} Selected Ouptut') # not ready # col.operator('gp.number_outputs', icon='LINENUMBERS_ON', text='Renumber all outputs').mode = 'ALL'