layers info json exporter
0.9.3 - feat: export a json with layers info for compo. Masks, opacity, blend modemain
parent
89abd181e2
commit
5cca446fc0
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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": "",
|
||||
|
|
5
fn.py
5
fn.py
|
@ -728,7 +728,7 @@ def normalize(text):
|
|||
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
|
||||
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}')
|
||||
|
|
3
ui.py
3
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()
|
||||
|
|
Loading…
Reference in New Issue