layers info json exporter
0.9.3 - feat: export a json with layers info for compo. Masks, opacity, blend mode
This commit is contained in:
		
							parent
							
								
									89abd181e2
								
							
						
					
					
						commit
						5cca446fc0
					
				@ -14,6 +14,9 @@ Activate / deactivate layer opaticty according to prefix
 | 
				
			|||||||
Activate / deactivate all masks using MA layers
 | 
					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
 | 
					0.9.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -7,9 +7,138 @@ from bpy.props import (FloatProperty,
 | 
				
			|||||||
from . import fn
 | 
					from . import fn
 | 
				
			||||||
import math
 | 
					import math
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					import json
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## TODO : export json info to re-setup layers in AE automagically
 | 
					## 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):
 | 
					class GPEXP_OT_layers_state(bpy.types.Operator):
 | 
				
			||||||
    bl_idname = "gp.layers_state"
 | 
					    bl_idname = "gp.layers_state"
 | 
				
			||||||
    bl_label = "Set Layers State"
 | 
					    bl_label = "Set Layers State"
 | 
				
			||||||
@ -380,9 +509,10 @@ class GPEXP_OT_check_masks(bpy.types.Operator):
 | 
				
			|||||||
        return {"FINISHED"}
 | 
					        return {"FINISHED"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
classes=(
 | 
					classes=(
 | 
				
			||||||
GPEXP_OT_layers_state,
 | 
					 | 
				
			||||||
GPEXP_OT_lower_layers_name,
 | 
					 | 
				
			||||||
GPEXP_OT_auto_number_object,
 | 
					GPEXP_OT_auto_number_object,
 | 
				
			||||||
 | 
					GPEXP_OT_lower_layers_name,
 | 
				
			||||||
 | 
					GPEXP_OT_export_infos_for_compo,
 | 
				
			||||||
 | 
					GPEXP_OT_layers_state,
 | 
				
			||||||
GPEXP_OT_check_masks,
 | 
					GPEXP_OT_check_masks,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -2,7 +2,7 @@ bl_info = {
 | 
				
			|||||||
    "name": "GP Render",
 | 
					    "name": "GP Render",
 | 
				
			||||||
    "description": "Organise export of gp layers through compositor output",
 | 
					    "description": "Organise export of gp layers through compositor output",
 | 
				
			||||||
    "author": "Samuel Bernou",
 | 
					    "author": "Samuel Bernou",
 | 
				
			||||||
    "version": (0, 9, 2),
 | 
					    "version": (0, 9, 3),
 | 
				
			||||||
    "blender": (2, 93, 0),
 | 
					    "blender": (2, 93, 0),
 | 
				
			||||||
    "location": "View3D",
 | 
					    "location": "View3D",
 | 
				
			||||||
    "warning": "",
 | 
					    "warning": "",
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										5
									
								
								fn.py
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								fn.py
									
									
									
									
									
								
							@ -728,7 +728,7 @@ def normalize(text):
 | 
				
			|||||||
    return text.lower().replace('-', '_')
 | 
					    return text.lower().replace('-', '_')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
 | 
					PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\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'''
 | 
					    '''GET a layer and argument to build and assign name'''
 | 
				
			||||||
    import re
 | 
					    import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -772,7 +772,8 @@ def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_
 | 
				
			|||||||
        name = name.replace('-', '_')
 | 
					        name = name.replace('-', '_')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    new = f'{grp}{tag}{name}{sfix}' # lower suffix ?
 | 
					    new = f'{grp}{tag}{name}{sfix}' # lower suffix ?
 | 
				
			||||||
 | 
					    if get_only:
 | 
				
			||||||
 | 
					        return new
 | 
				
			||||||
    if new != layer.info:
 | 
					    if new != layer.info:
 | 
				
			||||||
        old = layer.info
 | 
					        old = layer.info
 | 
				
			||||||
        print(f'{old} >> {new}')
 | 
					        print(f'{old} >> {new}')
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								ui.py
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								ui.py
									
									
									
									
									
								
							@ -211,8 +211,9 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
 | 
				
			|||||||
        row = col.row(align=True)
 | 
					        row = col.row(align=True)
 | 
				
			||||||
        row.operator('gp.auto_number_object', icon='OBJECT_DATAMODE', text='Renumber Objects')
 | 
					        row.operator('gp.auto_number_object', icon='OBJECT_DATAMODE', text='Renumber Objects')
 | 
				
			||||||
        row.operator('gp.auto_number_object', icon='X', text='').delete = True
 | 
					        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.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')
 | 
					        col.operator('gp.check_masks', icon='MOD_MASK', text='Has Masks')
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        # row = layout.row()
 | 
					        # row = layout.row()
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user