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
|
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…
Reference in New Issue