diff --git a/CHANGELOG.md b/CHANGELOG.md index 89810e3..52f3303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ Activate / deactivate layer opaticty according to prefix Activate / deactivate all masks using MA layers --> +0.9.3 + +- feat: export a json with layers info for compo. Masks, opacity, blend mode 0.9.2 diff --git a/OP_setup_layers.py b/OP_setup_layers.py index 1922463..23d3a59 100644 --- a/OP_setup_layers.py +++ b/OP_setup_layers.py @@ -7,9 +7,138 @@ from bpy.props import (FloatProperty, from . import fn import math import re +import json +from pathlib import Path ## TODO : export json info to re-setup layers in AE automagically +def check_outname(ob, l): + vl_name = l.viewlayer_render + if vl_name in {'exclude', 'View Layer'}: + return + + ng_name = f'NG_{ob.name}' + ## check in wich node tree this exists + for scn in bpy.data.scenes: + if scn.name == 'Scene': + continue + ng = scn.node_tree.nodes.get(ng_name) + if ng: + break + print(scn.name) + if not ng: + print(f'Skip {vl_name}: Not found nodegroup {ng_name}' ) + return + ng_socket = ng.outputs.get(vl_name) + if not ng_socket: + print(f'Skip {vl_name}: Not found in nodegroup {ng_name} sockets') + return + if not len(ng_socket.links): + print(f' socket is disconnected in {ng_name} nodegroup') + return + fo_node = ng_socket.links[0].to_node + fo_socket = ng_socket.links[0].to_socket + if fo_node.type != 'OUTPUT_FILE': + print(f'Skip {vl_name}: node is not an output_file {fo_node.name}') + return + + # fo_socket.name isn't right, have to iterate in paths + idx = [i for i in fo_node.inputs].index(fo_socket) + subpath = fo_node.file_slots[idx].path + # fp = Path(fo_node.base_path.rstrip('/')) / subpath + # fp = Path(bpy.path.abspath(str(fp)).rstrip("/")) # abspath on disk + outname = subpath.split('/')[0] # folder name on disk + + return outname + +class GPEXP_OT_export_infos_for_compo(bpy.types.Operator): + bl_idname = "gp.export_infos_for_compo" + bl_label = "Export Infos For Compo" + bl_description = "Export informations for compositing, including layers with masks, fusion mode, opacity" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return context.object and context.object.type == 'GPENCIL' + + def invoke(self, context, event): + + self.l_infos = Path(bpy.data.filepath).parent / 'render' / 'infos.json' + if self.l_infos.exists(): + return context.window_manager.invoke_props_dialog(self) + return self.execute(context) + + def draw(self, context): + layout = self.layout + layout.label(text='An infos Json already exists', icon = 'ERROR') + layout.label(text='Do you want to overwrite ?') + layout.label(text='Note: Must export before "Check Layers" step', icon='INFO') + + def execute(self, context): + dic = {} + pool = [o for o in context.scene.objects if o.type == 'GPENCIL'] + for o in pool: + # if not o.visible_get(): + # continue + for l in o.data.layers: + # skip non rendered layers + if l.hide: + continue + + if l.info.startswith('MA_'): + # No point in storing information of masking layers... + continue + + ## Can't check viewlayers and final fileout name if Render scene not even created... + """ if not l.viewlayer_render or l.viewlayer_render == 'exclude': + continue + + fo_name = check_outname(o, l) # get name used for output file folder (same in AE) + if not fo_name: + print(f'! Could not found fileout name for {o.name} > {l.info}') + continue + """ + + ldic = {} + ## Check opacity, blend mode + if l.opacity < 1.0: + ldic['opacity'] = l.opacity + + if l.blend_mode != 'REGULAR': + ldic['blend_mode'] = l.blend_mode + + if l.use_mask_layer: + + multi_mask = {} + for i, ml in enumerate(l.mask_layers): + mask = {} + if ml.hide: + continue + mask['name'] = ml.name + if ml.invert: # create key get only if inverted + mask['invert'] = ml.invert + # multi_mask[ml.name] = mask + multi_mask[i] = mask + + if multi_mask: + ldic['masks'] = multi_mask + + ## add to full dic + if ldic: + # add source object ? might be usefull to pin point layer + ldic['object'] = o.name + dic[fn.normalize_layer_name(l, get_only=True)] = ldic + + if dic: + with self.l_infos.open('w') as fd: + json.dump(dic, fd, indent='\t') + self.report({'INFO'}, f'Exported json at: {self.l_infos.as_posix()}') + else: + self.report({'WARNING'}, f'No custom data to write with those objects/layers') + return {"CANCELLED"} + + return {"FINISHED"} + class GPEXP_OT_layers_state(bpy.types.Operator): bl_idname = "gp.layers_state" bl_label = "Set Layers State" @@ -380,9 +509,10 @@ class GPEXP_OT_check_masks(bpy.types.Operator): return {"FINISHED"} classes=( -GPEXP_OT_layers_state, -GPEXP_OT_lower_layers_name, GPEXP_OT_auto_number_object, +GPEXP_OT_lower_layers_name, +GPEXP_OT_export_infos_for_compo, +GPEXP_OT_layers_state, GPEXP_OT_check_masks, ) diff --git a/__init__.py b/__init__.py index f9a06cd..044c4cb 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, 9, 2), + "version": (0, 9, 3), "blender": (2, 93, 0), "location": "View3D", "warning": "", diff --git a/fn.py b/fn.py index 0f5d95f..267ba0e 100644 --- a/fn.py +++ b/fn.py @@ -728,7 +728,7 @@ def normalize(text): return text.lower().replace('-', '_') PATTERN = r'^(?P-\s)?(?P[A-Z]{2}_)?(?P.*?)(?P_[A-Z]{2})?(?P\.\d{3})?$' # numering -def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_to_underscore=True): +def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_to_underscore=True, get_only=False): '''GET a layer and argument to build and assign name''' import re @@ -772,7 +772,8 @@ def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_ name = name.replace('-', '_') new = f'{grp}{tag}{name}{sfix}' # lower suffix ? - + if get_only: + return new if new != layer.info: old = layer.info print(f'{old} >> {new}') diff --git a/ui.py b/ui.py index d159972..63c0f9a 100644 --- a/ui.py +++ b/ui.py @@ -211,8 +211,9 @@ class GPEXP_PT_gp_dopesheet_ui(Panel): row = col.row(align=True) row.operator('gp.auto_number_object', icon='OBJECT_DATAMODE', text='Renumber Objects') row.operator('gp.auto_number_object', icon='X', text='').delete = True - col.operator('gp.layers_state', icon='CHECKMARK', text='Check layers') col.operator('gp.lower_layers_name', icon='SYNTAX_OFF', text='Rename Lowercase') + col.operator('gp.export_infos_for_compo', icon='FILE', text='Export Layers Infos') + col.operator('gp.layers_state', icon='CHECKMARK', text='Check layers') col.operator('gp.check_masks', icon='MOD_MASK', text='Has Masks') # row = layout.row()