render checkers and codefix
0.9.4 - feat: `Renumber files on disk` option using number in file outputs (under advanced gp render options) - feat: new `Check for problems` button, check if problem in layer state, missing file out, broken gp modifier target and report - added: clean nodes now also rearrange inside nodegroup - changed: `Check layers` now trigger `export layer infos` automatically. - fix: `export layer infos`: - create render folder if necessary - masks list in json file use name as keys instead of sub-valuemain
parent
5cca446fc0
commit
64efb7e395
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -14,6 +14,16 @@ Activate / deactivate layer opaticty according to prefix
|
||||||
Activate / deactivate all masks using MA layers
|
Activate / deactivate all masks using MA layers
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
0.9.4
|
||||||
|
|
||||||
|
- feat: `Renumber files on disk` option using number in file outputs (under advanced gp render options)
|
||||||
|
- feat: new `Check for problems` button, check if problem in layer state, missing file out, broken gp modifier target and report
|
||||||
|
- added: clean nodes now also rearrange inside nodegroup
|
||||||
|
- changed: `Check layers` now trigger `export layer infos` automatically.
|
||||||
|
- fix: `export layer infos`:
|
||||||
|
- create render folder if necessary
|
||||||
|
- masks list in json file use name as keys instead of sub-value
|
||||||
|
|
||||||
0.9.3
|
0.9.3
|
||||||
|
|
||||||
- feat: export a json with layers info for compo. Masks, opacity, blend mode
|
- feat: export a json with layers info for compo. Masks, opacity, blend mode
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
import bpy
|
|
||||||
from . import fn
|
|
||||||
|
|
||||||
## not used, replaced by "setup_layers.py"
|
|
||||||
class GPEXP_OT_check_layers_state(bpy.types.Operator):
|
|
||||||
bl_idname = "gp.check_layers_state"
|
|
||||||
bl_label = "Check Layers State"
|
|
||||||
bl_description = "Display state of layer that migh need adjustement"
|
|
||||||
bl_options = {"REGISTER"} # , "UNDO"
|
|
||||||
|
|
||||||
# clear_unused_view_layers : bpy.props.BoolProperty(name="Clear unused view layers",
|
|
||||||
# description="Delete view layer that aren't used in the nodetree anymore",
|
|
||||||
# default=True)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def poll(cls, context):
|
|
||||||
return context.object and context.object.type == 'GPENCIL'
|
|
||||||
|
|
||||||
def invoke(self, context, event):
|
|
||||||
self.ctrl=event.ctrl
|
|
||||||
self.alt=event.alt
|
|
||||||
return self.execute(context)
|
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
|
||||||
|
|
||||||
def draw(self, context):
|
|
||||||
layout = self.layout
|
|
||||||
# layout.prop(self, 'clear_unused_view_layers')
|
|
||||||
|
|
||||||
def execute(self, context):
|
|
||||||
if self.alt:
|
|
||||||
pool = [o for o in context.selected_objects if o.type == 'GPENCIL']
|
|
||||||
else:
|
|
||||||
pool = [context.object]
|
|
||||||
|
|
||||||
# TODO create a list to disaply everything in a message box ?
|
|
||||||
|
|
||||||
for ob in pool:
|
|
||||||
layers = ob.data.layers
|
|
||||||
for l in layers:
|
|
||||||
used = False
|
|
||||||
if l.mask_layers:
|
|
||||||
print(f'-> masks')
|
|
||||||
state = '' if l.use_mask_layer else ' (disabled)'
|
|
||||||
print(f'{ob.name} > {l.info}{state}:')
|
|
||||||
used = True
|
|
||||||
for ml in l.mask_layers:
|
|
||||||
mlstate = ' (disabled)' if ml.hide else ''
|
|
||||||
mlinvert = ' <>' if ml.invert else ''
|
|
||||||
print(f' - {ml.info}{mlstate}{mlinvert}')
|
|
||||||
|
|
||||||
if l.opacity != 1:
|
|
||||||
print(f'-> opacity {l.opacity}')
|
|
||||||
used = True
|
|
||||||
|
|
||||||
if l.use_lights:
|
|
||||||
print(f'-> use lights !')
|
|
||||||
used = True
|
|
||||||
if l.blend_mode != 'REGULAR':
|
|
||||||
print(f'-> blend mode "{l.blend_mode}" !')
|
|
||||||
used = True
|
|
||||||
|
|
||||||
if used:
|
|
||||||
print()
|
|
||||||
|
|
||||||
# render = bpy.data.scenes.get('Render')
|
|
||||||
# if not render:
|
|
||||||
# print('SKIP, no Render scene')
|
|
||||||
# return {"CANCELLED"}
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
classes=(
|
|
||||||
GPEXP_OT_check_layers_state,
|
|
||||||
)
|
|
||||||
|
|
||||||
def register():
|
|
||||||
for cls in classes:
|
|
||||||
bpy.utils.register_class(cls)
|
|
||||||
|
|
||||||
def unregister():
|
|
||||||
for cls in reversed(classes):
|
|
||||||
bpy.utils.unregister_class(cls)
|
|
|
@ -1,7 +1,83 @@
|
||||||
import bpy
|
import bpy, re
|
||||||
from . import fn
|
from . import fn
|
||||||
|
|
||||||
## not used, replaced by "setup_layers.py"
|
def check_broken_modifier_target(pool=None, reports=None):
|
||||||
|
if not reports:
|
||||||
|
reports = []
|
||||||
|
if not pool:
|
||||||
|
pool = [o for o in bpy.context.scene.objects if o.type == 'GPENCIL']
|
||||||
|
|
||||||
|
for o in pool:
|
||||||
|
lay_name_list = [l.info for l in o.data.layers]
|
||||||
|
for m in o.grease_pencil_modifiers:
|
||||||
|
if not hasattr(m, 'layer'):
|
||||||
|
continue
|
||||||
|
if not m.layer in lay_name_list:
|
||||||
|
reports.append(f'Broken modifier target :{o.name} > {m.name} > {m.layer}')
|
||||||
|
# else:
|
||||||
|
# print(f'Modifier target :{o.name} > {m.name} > ok')
|
||||||
|
|
||||||
|
return reports
|
||||||
|
|
||||||
|
def check_layer_state(pool=None, reports=None):
|
||||||
|
if not reports:
|
||||||
|
reports = []
|
||||||
|
if not pool:
|
||||||
|
pool = [o for o in bpy.context.scene.objects if o.type == 'GPENCIL']
|
||||||
|
for ob in pool:
|
||||||
|
layers = ob.data.layers
|
||||||
|
for l in layers:
|
||||||
|
# if l.mask_layers:
|
||||||
|
# if not any(not x.hide for x in l.mask_layers):
|
||||||
|
# # all masks disable
|
||||||
|
# pass
|
||||||
|
|
||||||
|
## just list masks
|
||||||
|
# state = '' if l.use_mask_layer else ' (disabled)'
|
||||||
|
# reports.append(f'{ob.name} > {l.info} masks{state}:')
|
||||||
|
# for ml in l.mask_layers:
|
||||||
|
# mlstate = ' (disabled)' if ml.hide else ''
|
||||||
|
# mlinvert = ' <>' if ml.invert else ''
|
||||||
|
# reports.append(f' - {ml.name}{mlstate}{mlinvert}')
|
||||||
|
|
||||||
|
if l.opacity != 1:
|
||||||
|
reports.append(f'{ob.name} > {l.info} > opacity {l.opacity}')
|
||||||
|
|
||||||
|
# if l.use_lights:
|
||||||
|
# reports.append(f'-> use lights !')
|
||||||
|
|
||||||
|
if l.blend_mode != 'REGULAR':
|
||||||
|
reports.append(f'{ob.name} > {l.info} > blend mode "{l.blend_mode}" !')
|
||||||
|
|
||||||
|
return reports
|
||||||
|
|
||||||
|
def check_file_output_numbering(reports=None):
|
||||||
|
if not reports:
|
||||||
|
reports = []
|
||||||
|
prenum = re.compile(r'\d{3}_')
|
||||||
|
file_outs = []
|
||||||
|
for S in bpy.data.scenes:
|
||||||
|
if S.name == 'Scene' or not S.node_tree or not S.use_nodes:
|
||||||
|
continue
|
||||||
|
file_outs += [n for n in S.node_tree.nodes if n.type == 'OUTPUT_FILE']
|
||||||
|
|
||||||
|
used=False
|
||||||
|
|
||||||
|
if not file_outs:
|
||||||
|
reports.append('No file output nodes found')
|
||||||
|
return reports
|
||||||
|
|
||||||
|
for fo in file_outs:
|
||||||
|
if not prenum.match(fo.base_path.split('/')[-1]):
|
||||||
|
reports.append(f'No object numbering : node {fo.name}')
|
||||||
|
pct = 0
|
||||||
|
for fs in fo.file_slots:
|
||||||
|
if not prenum.match(fs.path.split('/')[0]):
|
||||||
|
pct += 1
|
||||||
|
if pct:
|
||||||
|
reports.append(f'{pct}/{len(fo.file_slots)} slots not numbered: node {fo.name}')
|
||||||
|
|
||||||
|
return reports
|
||||||
class GPEXP_OT_check_render_scene(bpy.types.Operator):
|
class GPEXP_OT_check_render_scene(bpy.types.Operator):
|
||||||
bl_idname = "gp.check_render_scene"
|
bl_idname = "gp.check_render_scene"
|
||||||
bl_label = "Check render scene"
|
bl_label = "Check render scene"
|
||||||
|
@ -25,48 +101,34 @@ class GPEXP_OT_check_render_scene(bpy.types.Operator):
|
||||||
# layout.prop(self, 'clear_unused_view_layers')
|
# layout.prop(self, 'clear_unused_view_layers')
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
gp_objs = [o for o in context.scene.objects if o.type == 'GPENCIL']
|
reports = []
|
||||||
|
# check gp modifiers
|
||||||
|
broken_mods = check_broken_modifier_target()
|
||||||
|
if broken_mods:
|
||||||
|
reports.append('GP modifiers targets:')
|
||||||
|
reports += broken_mods
|
||||||
|
|
||||||
|
# check layers
|
||||||
|
layer_state = check_layer_state()
|
||||||
|
if layer_state:
|
||||||
|
if reports: reports.append('')
|
||||||
|
reports.append('Layers State:')
|
||||||
|
reports += layer_state
|
||||||
|
|
||||||
# TODO create a list to disaply everything in a message box ?
|
# check file output numbering
|
||||||
|
numbering_problems = check_file_output_numbering()
|
||||||
|
if numbering_problems:
|
||||||
|
if reports: reports.append('')
|
||||||
|
reports.append('File output numbering:')
|
||||||
|
reports += numbering_problems
|
||||||
|
|
||||||
for ob in pool:
|
if not reports:
|
||||||
layers = ob.data.layers
|
self.report({'INFO'}, 'All OK !')
|
||||||
for l in layers:
|
else:
|
||||||
used = False
|
fn.show_message_box(_message=reports, _title='Potential Problems list')
|
||||||
if l.mask_layers:
|
|
||||||
print(f'-> masks')
|
|
||||||
state = '' if l.use_mask_layer else ' (disabled)'
|
|
||||||
print(f'{ob.name} > {l.info}{state}:')
|
|
||||||
used = True
|
|
||||||
for ml in l.mask_layers:
|
|
||||||
mlstate = ' (disabled)' if ml.hide else ''
|
|
||||||
mlinvert = ' <>' if ml.invert else ''
|
|
||||||
print(f' - {ml.info}{mlstate}{mlinvert}')
|
|
||||||
|
|
||||||
if l.opacity != 1:
|
|
||||||
print(f'-> opacity {l.opacity}')
|
|
||||||
used = True
|
|
||||||
|
|
||||||
if l.use_lights:
|
|
||||||
print(f'-> use lights !')
|
|
||||||
used = True
|
|
||||||
if l.blend_mode != 'REGULAR':
|
|
||||||
print(f'-> blend mode "{l.blend_mode}" !')
|
|
||||||
used = True
|
|
||||||
|
|
||||||
if used:
|
|
||||||
print()
|
|
||||||
|
|
||||||
# render = bpy.data.scenes.get('Render')
|
|
||||||
# if not render:
|
|
||||||
# print('SKIP, no Render scene')
|
|
||||||
# return {"CANCELLED"}
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
classes=(
|
classes=(
|
||||||
GPEXP_OT_check_render_scene,
|
GPEXP_OT_check_render_scene,
|
||||||
)
|
)
|
||||||
|
|
11
OP_clean.py
11
OP_clean.py
|
@ -73,6 +73,10 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
|
||||||
description="Reorder inputs/outputs of all 'NG_' nodegroup and their connected file output",
|
description="Reorder inputs/outputs of all 'NG_' nodegroup and their connected file output",
|
||||||
default=True)
|
default=True)
|
||||||
|
|
||||||
|
clear_isolated_node_in_groups : bpy.props.BoolProperty(name="Clear Isolated Node In Groups",
|
||||||
|
description="Clean content of 'NG_' nodegroup bpy deleting isolated nodes)",
|
||||||
|
default=True)
|
||||||
|
|
||||||
fo_clear_disconnected : bpy.props.BoolProperty(name="Remove Disconnected Export Inputs",
|
fo_clear_disconnected : bpy.props.BoolProperty(name="Remove Disconnected Export Inputs",
|
||||||
description="Clear any disconnected intput of every 'file output' node",
|
description="Clear any disconnected intput of every 'file output' node",
|
||||||
default=False)
|
default=False)
|
||||||
|
@ -91,6 +95,7 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
|
||||||
layout.prop(self, 'arrange_rl_nodes')
|
layout.prop(self, 'arrange_rl_nodes')
|
||||||
layout.prop(self, 'arrange_frames')
|
layout.prop(self, 'arrange_frames')
|
||||||
layout.prop(self, 'reorder_inputs')
|
layout.prop(self, 'reorder_inputs')
|
||||||
|
layout.prop(self, 'clear_isolated_node_in_groups')
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
layout.prop(self, 'fo_clear_disconnected')
|
layout.prop(self, 'fo_clear_disconnected')
|
||||||
|
@ -146,6 +151,12 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
|
||||||
|
|
||||||
fn.bridge_reconnect_nodegroup(n)
|
fn.bridge_reconnect_nodegroup(n)
|
||||||
|
|
||||||
|
if self.clear_isolated_node_in_groups:
|
||||||
|
for n in nodes:
|
||||||
|
if n.type != 'GROUP' or not n.name.startswith('NG_'):
|
||||||
|
continue
|
||||||
|
fn.clear_nodegroup_content_if_disconnected(n.node_tree)
|
||||||
|
|
||||||
if self.fo_clear_disconnected:
|
if self.fo_clear_disconnected:
|
||||||
for fo in nodes:
|
for fo in nodes:
|
||||||
if fo.type != 'OUTPUT_FILE':
|
if fo.type != 'OUTPUT_FILE':
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
from time import time
|
||||||
|
|
||||||
|
|
||||||
|
def renumber_sequence_on_disk_from_file_slots(apply=True, active_scene_only=False):
|
||||||
|
'''renumber sequence on disk from scenes file slots'''
|
||||||
|
|
||||||
|
scn = bpy.context.scene
|
||||||
|
blend = Path(bpy.data.filepath)
|
||||||
|
render = blend.parent / 'render'
|
||||||
|
|
||||||
|
prenum = re.compile(r'\d{3}_')
|
||||||
|
|
||||||
|
print('-- starting rename sequences numbers from fileslots number')
|
||||||
|
if not apply:
|
||||||
|
print('-- Dry run')
|
||||||
|
|
||||||
|
t0 = time()
|
||||||
|
ct = 0
|
||||||
|
|
||||||
|
if active_scene_only:
|
||||||
|
# Only on currrent scene
|
||||||
|
file_outs = [n for n in bpy.context.scene.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.name.startswith('OUT_') and not n.mute]
|
||||||
|
else:
|
||||||
|
# multi scene check:
|
||||||
|
file_outs = []
|
||||||
|
for S in bpy.data.scenes:
|
||||||
|
if S.name == 'Scene' or not S.node_tree or not S.use_nodes:
|
||||||
|
continue
|
||||||
|
file_outs += [n for n in S.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.name.startswith('OUT_') and not n.mute]
|
||||||
|
|
||||||
|
|
||||||
|
if not file_outs:
|
||||||
|
return 'No file output found (should be unmuted nodes with name starting with OUT_)', '_'
|
||||||
|
|
||||||
|
for fo in file_outs:
|
||||||
|
obj_full = fo.base_path.split('/')[-1]
|
||||||
|
obj = prenum.sub('', obj_full)
|
||||||
|
obj_num = prenum.search(obj_full)
|
||||||
|
if obj_num:
|
||||||
|
obj_num = obj_num.group(0)
|
||||||
|
|
||||||
|
## check if folder exists
|
||||||
|
folder_path = None
|
||||||
|
|
||||||
|
for d in os.scandir(render):
|
||||||
|
if d.is_dir() and prenum.sub('', d.name) == obj:
|
||||||
|
folder_path = render / d.name
|
||||||
|
break
|
||||||
|
|
||||||
|
if not folder_path:
|
||||||
|
print(f'Could not find obj folder for: {obj}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
# rename inside folder dirst so that root path isn't changed while iterating
|
||||||
|
for fs in fo.file_slots:
|
||||||
|
img_full = fs.path.split('/')[0]
|
||||||
|
img = prenum.sub('', img_full)
|
||||||
|
img_num = prenum.search(img_full)
|
||||||
|
if img_num:
|
||||||
|
img_num = img_num.group(0)
|
||||||
|
else:
|
||||||
|
print(f'! no num : {fo.base_path} : {img_full}')
|
||||||
|
continue # If no img_num no point in renaming sequences
|
||||||
|
|
||||||
|
img_dir_path = None
|
||||||
|
|
||||||
|
for img_dir in os.scandir(folder_path):
|
||||||
|
if img_dir.is_dir() and prenum.sub('', img_dir.name) == img:
|
||||||
|
img_dir_path = folder_path / img_dir.name
|
||||||
|
break
|
||||||
|
|
||||||
|
if not img_dir_path:
|
||||||
|
print(f'Could not find img folder for: {img}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
# if folder exists check if full name is ok
|
||||||
|
if img_full == img_dir_path.name:
|
||||||
|
continue # name already (maybe not in sequence but should be good)
|
||||||
|
|
||||||
|
|
||||||
|
# rename sequence and image folder
|
||||||
|
for frame in os.scandir(img_dir_path):
|
||||||
|
good = img_num + prenum.sub('', frame.name)
|
||||||
|
if frame.name != good:
|
||||||
|
print(f' img: {frame.name} > {good}')
|
||||||
|
ct += 1
|
||||||
|
if apply:
|
||||||
|
fp = Path(frame.path)
|
||||||
|
fp.rename(fp.parent / good)
|
||||||
|
|
||||||
|
# rename image folder
|
||||||
|
if img_dir_path.name != img_full:
|
||||||
|
print(f' dir:{img_dir_path.name} > {img_full}')
|
||||||
|
ct += 1
|
||||||
|
if apply:
|
||||||
|
img_dir_path.rename(img_dir_path.parent / img_full)
|
||||||
|
|
||||||
|
# rename object folder
|
||||||
|
if obj_num and folder_path.name != obj_full:
|
||||||
|
print(f'obj: {folder_path.name} > {obj_full}')
|
||||||
|
ct += 1
|
||||||
|
if apply:
|
||||||
|
folder_path.rename(folder_path.parent / obj_full)
|
||||||
|
|
||||||
|
elapsed = f'{time() - t0:.2f}s'
|
||||||
|
print(f'Eslapsed time: {elapsed}')
|
||||||
|
return ct, elapsed
|
||||||
|
|
||||||
|
|
||||||
|
class GPEXP_OT_renumber_files_on_disk(bpy.types.Operator):
|
||||||
|
bl_idname = "gp.renumber_files_on_disk"
|
||||||
|
bl_label = "Renumber Files On Disk"
|
||||||
|
bl_description = "Rename folder/files in render folder on disk according to unmuted file output numbering"
|
||||||
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
# return self.execute(context)
|
||||||
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
dry_run: bpy.props.BoolProperty(name='Dry-run (no actions, prints in console only)',
|
||||||
|
default=False,
|
||||||
|
description='Test mode. If checked, no action is actually performed')
|
||||||
|
|
||||||
|
active_scene_only: bpy.props.BoolProperty(name='Only Active Scene',
|
||||||
|
default=False,
|
||||||
|
description='use only file output of active scene instead of all scenes (skipping "Scene")')
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
layout.prop(self, 'dry_run')
|
||||||
|
layout.prop(self, 'active_scene_only')
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
ct, timing = renumber_sequence_on_disk_from_file_slots(apply = not self.dry_run, active_scene_only=self.active_scene_only)
|
||||||
|
if isinstance(ct, str):
|
||||||
|
self.report({'ERROR'}, ct)
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if not ct:
|
||||||
|
self.report({'WARNING'}, 'Already good or nothing to rename')
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
if self.dry_run:
|
||||||
|
mess = f'Dry run : {ct} items would have been renamed, see console'
|
||||||
|
else:
|
||||||
|
mess = f'{ct} items renamed in {timing}'
|
||||||
|
|
||||||
|
self.report({'INFO'}, mess)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
classes=(
|
||||||
|
GPEXP_OT_renumber_files_on_disk,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
|
@ -55,7 +55,7 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
|
||||||
bl_idname = "gp.export_infos_for_compo"
|
bl_idname = "gp.export_infos_for_compo"
|
||||||
bl_label = "Export Infos For Compo"
|
bl_label = "Export Infos For Compo"
|
||||||
bl_description = "Export informations for compositing, including layers with masks, fusion mode, opacity"
|
bl_description = "Export informations for compositing, including layers with masks, fusion mode, opacity"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER"}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
|
@ -110,15 +110,27 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
|
||||||
if l.use_mask_layer:
|
if l.use_mask_layer:
|
||||||
|
|
||||||
multi_mask = {}
|
multi_mask = {}
|
||||||
for i, ml in enumerate(l.mask_layers):
|
|
||||||
|
## dict key as number for masks
|
||||||
|
# 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
|
||||||
|
|
||||||
|
## dict key as mask name
|
||||||
|
for ml in l.mask_layers:
|
||||||
mask = {}
|
mask = {}
|
||||||
if ml.hide:
|
if ml.hide:
|
||||||
continue
|
continue
|
||||||
mask['name'] = ml.name
|
# mask['name'] = ml.name
|
||||||
if ml.invert: # create key get only if inverted
|
if ml.invert: # create key get only if inverted
|
||||||
mask['invert'] = ml.invert
|
mask['invert'] = ml.invert # ! no key if no invert
|
||||||
# multi_mask[ml.name] = mask
|
multi_mask[ml.name] = mask
|
||||||
multi_mask[i] = mask
|
|
||||||
|
|
||||||
if multi_mask:
|
if multi_mask:
|
||||||
ldic['masks'] = multi_mask
|
ldic['masks'] = multi_mask
|
||||||
|
@ -130,6 +142,7 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
|
||||||
dic[fn.normalize_layer_name(l, get_only=True)] = ldic
|
dic[fn.normalize_layer_name(l, get_only=True)] = ldic
|
||||||
|
|
||||||
if dic:
|
if dic:
|
||||||
|
self.l_infos.parent.mkdir(exist_ok=True) # create render folder if needed
|
||||||
with self.l_infos.open('w') as fd:
|
with self.l_infos.open('w') as fd:
|
||||||
json.dump(dic, fd, indent='\t')
|
json.dump(dic, fd, indent='\t')
|
||||||
self.report({'INFO'}, f'Exported json at: {self.l_infos.as_posix()}')
|
self.report({'INFO'}, f'Exported json at: {self.l_infos.as_posix()}')
|
||||||
|
@ -149,9 +162,6 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
|
||||||
# description="Delete view layer that aren't used in the nodetree anymore",
|
# description="Delete view layer that aren't used in the nodetree anymore",
|
||||||
# default=True)
|
# default=True)
|
||||||
|
|
||||||
# TODO : (optional) export layer opacity to json and/or text
|
|
||||||
# (that way compo artists can re-affect opacity quickly or at least have a reminder)
|
|
||||||
|
|
||||||
all_objects : BoolProperty(name='On All Object',
|
all_objects : BoolProperty(name='On All Object',
|
||||||
default=True, description='On All object, else use selected objects') # , options={'SKIP_SAVE'}
|
default=True, description='On All object, else use selected objects') # , options={'SKIP_SAVE'}
|
||||||
|
|
||||||
|
@ -176,10 +186,14 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
|
||||||
return context.object and context.object.type == 'GPENCIL'
|
return context.object and context.object.type == 'GPENCIL'
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
# self.ctrl=event.ctrl
|
|
||||||
# self.alt=event.alt
|
|
||||||
if event.alt:
|
if event.alt:
|
||||||
self.all_objects=True
|
self.all_objects=True
|
||||||
|
|
||||||
|
## if no existing infos.json generated, call ops
|
||||||
|
l_infos = Path(bpy.data.filepath).parent / 'render' / 'infos.json'
|
||||||
|
if not l_infos.exists(): # only if infos not created
|
||||||
|
bpy.ops.gp.export_infos_for_compo('INVOKE_DEFAULT')
|
||||||
|
|
||||||
# return self.execute(context)
|
# return self.execute(context)
|
||||||
return context.window_manager.invoke_props_dialog(self)
|
return context.window_manager.invoke_props_dialog(self)
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,8 @@ from . import OP_manage_outputs
|
||||||
from . import OP_scene_switch
|
from . import OP_scene_switch
|
||||||
from . import OP_crop_to_object
|
from . import OP_crop_to_object
|
||||||
from . import OP_render_scenes
|
from . import OP_render_scenes
|
||||||
# from . import OP_check_layer_status
|
from . import OP_check_scene
|
||||||
|
from . import OP_post_render
|
||||||
from . import OP_render_pdf
|
from . import OP_render_pdf
|
||||||
from . import OP_export_to_ae
|
from . import OP_export_to_ae
|
||||||
from . import prefs
|
from . import prefs
|
||||||
|
@ -47,7 +48,8 @@ def register():
|
||||||
OP_scene_switch.register()
|
OP_scene_switch.register()
|
||||||
OP_crop_to_object.register()
|
OP_crop_to_object.register()
|
||||||
OP_render_scenes.register()
|
OP_render_scenes.register()
|
||||||
# OP_check_layer_status.register()
|
OP_check_scene.register()
|
||||||
|
OP_post_render.register()
|
||||||
OP_render_pdf.register()
|
OP_render_pdf.register()
|
||||||
OP_export_to_ae.register()
|
OP_export_to_ae.register()
|
||||||
OP_setup_layers.register()
|
OP_setup_layers.register()
|
||||||
|
@ -68,7 +70,8 @@ def unregister():
|
||||||
|
|
||||||
ui.unregister()
|
ui.unregister()
|
||||||
OP_setup_layers.unregister()
|
OP_setup_layers.unregister()
|
||||||
# OP_check_layer_status.unregister()
|
OP_check_scene.unregister()
|
||||||
|
OP_post_render.unregister()
|
||||||
OP_export_to_ae.unregister()
|
OP_export_to_ae.unregister()
|
||||||
OP_render_pdf.unregister()
|
OP_render_pdf.unregister()
|
||||||
OP_render_scenes.unregister()
|
OP_render_scenes.unregister()
|
||||||
|
|
48
fn.py
48
fn.py
|
@ -474,17 +474,65 @@ def all_connected_forward(n, nlist=[]):
|
||||||
return nlist
|
return nlist
|
||||||
return nlist + [n]
|
return nlist + [n]
|
||||||
|
|
||||||
|
def all_connected_forward_from_socket(socket):
|
||||||
|
'''return a list of all nodes connected forward after socket'''
|
||||||
|
node_list = []
|
||||||
|
for ln in socket.links:
|
||||||
|
for n in all_connected_forward(ln.to_node):
|
||||||
|
if n not in node_list:
|
||||||
|
node_list.append(n)
|
||||||
|
# node_list = list(set(node_list))
|
||||||
|
return node_list
|
||||||
|
|
||||||
|
def node_height(n):
|
||||||
|
return n.height if not n.hide else 30
|
||||||
|
|
||||||
|
def reorder_nodegroup_content(ngroup):
|
||||||
|
if isinstance(ngroup, bpy.types.Node):
|
||||||
|
ngroup = ngroup.node_tree
|
||||||
|
|
||||||
|
grp_in = None
|
||||||
|
for n in ngroup.nodes:
|
||||||
|
if n.type == 'GROUP_INPUT':
|
||||||
|
grp_in = n
|
||||||
|
break
|
||||||
|
if not grp_in:
|
||||||
|
return
|
||||||
|
|
||||||
|
n_threads = []
|
||||||
|
for out in grp_in.outputs:
|
||||||
|
n_thread = all_connected_forward_from_socket(out)
|
||||||
|
if n_thread:
|
||||||
|
n_threads.append(n_thread)
|
||||||
|
|
||||||
|
level = grp_in.location.y
|
||||||
|
for thread in n_threads:
|
||||||
|
top = max([n.location.y for n in thread])
|
||||||
|
bottom = min([n.location.y - node_height(n) for n in thread])
|
||||||
|
thread_h = top - bottom
|
||||||
|
# move all nodes to adjust to level
|
||||||
|
diff_to_add = level - top
|
||||||
|
for n in thread:
|
||||||
|
n.location.y += diff_to_add
|
||||||
|
# move level to bottom
|
||||||
|
level -= thread_h + 2 # add a gap of two
|
||||||
|
|
||||||
def clear_nodegroup_content_if_disconnected(ngroup):
|
def clear_nodegroup_content_if_disconnected(ngroup):
|
||||||
'''Get a nodegroup.node_tree
|
'''Get a nodegroup.node_tree
|
||||||
delete orphan nodes that are not connected from group input node
|
delete orphan nodes that are not connected from group input node
|
||||||
'''
|
'''
|
||||||
|
if isinstance(ngroup, bpy.types.Node):
|
||||||
|
# case where a node is sent instead of the group
|
||||||
|
ngroup = ngroup.node_tree
|
||||||
|
|
||||||
for n in reversed(ngroup.nodes):
|
for n in reversed(ngroup.nodes):
|
||||||
if n.type in ('GROUP_INPUT', 'GROUP_OUTPUT'):
|
if n.type in ('GROUP_INPUT', 'GROUP_OUTPUT'):
|
||||||
continue
|
continue
|
||||||
if not connect_to_group_input(n) and not connect_to_group_output(n): # is disconnected from both side
|
if not connect_to_group_input(n) and not connect_to_group_output(n): # is disconnected from both side
|
||||||
ngroup.nodes.remove(n)
|
ngroup.nodes.remove(n)
|
||||||
|
|
||||||
|
reorder_nodegroup_content(ngroup)
|
||||||
|
|
||||||
def clean_nodegroup_inputs(ng, skip_existing_pass=True):
|
def clean_nodegroup_inputs(ng, skip_existing_pass=True):
|
||||||
'''Clear inputs to output of passed nodegroup if not connected'''
|
'''Clear inputs to output of passed nodegroup if not connected'''
|
||||||
ngroup = ng.node_tree
|
ngroup = ng.node_tree
|
||||||
|
|
10
ui.py
10
ui.py
|
@ -106,6 +106,7 @@ class GPEXP_PT_gp_node_ui(Panel):
|
||||||
|
|
||||||
col.operator('gp.clean_compo_tree', icon='BRUSHES_ALL', text='Clean Nodes') # NODE_CORNER
|
col.operator('gp.clean_compo_tree', icon='BRUSHES_ALL', text='Clean Nodes') # NODE_CORNER
|
||||||
col.operator('gp.reset_render_settings', icon='SCENE', text='Reset All Scenes Render Settings')
|
col.operator('gp.reset_render_settings', icon='SCENE', text='Reset All Scenes Render Settings')
|
||||||
|
col.operator('gp.check_render_scene', icon='PRESET', text='Check For Problems')
|
||||||
|
|
||||||
col.separator()
|
col.separator()
|
||||||
|
|
||||||
|
@ -123,6 +124,7 @@ class GPEXP_PT_gp_node_ui(Panel):
|
||||||
subcol.operator('gp.set_active_fileout_to_compout', icon='OUTPUT', text='Active Slot to Composite')
|
subcol.operator('gp.set_active_fileout_to_compout', icon='OUTPUT', text='Active Slot to Composite')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
layout.separator()
|
layout.separator()
|
||||||
|
|
||||||
col=layout.column()
|
col=layout.column()
|
||||||
|
@ -145,6 +147,12 @@ class GPEXP_PT_gp_node_ui(Panel):
|
||||||
row.operator('gp.bg_render_script_selected_scenes', icon='TEXT', text='Gen Batch')
|
row.operator('gp.bg_render_script_selected_scenes', icon='TEXT', text='Gen Batch')
|
||||||
# row.operator('gp.render_all_scenes', icon='RENDER_ANIMATION', text='Render All')
|
# row.operator('gp.render_all_scenes', icon='RENDER_ANIMATION', text='Render All')
|
||||||
|
|
||||||
|
if advanced:
|
||||||
|
layout.separator()
|
||||||
|
col = layout.column()
|
||||||
|
col.label(text='Post-Render:')
|
||||||
|
col.operator('gp.renumber_files_on_disk', icon='FILE', text='Renumber Files On Disk')
|
||||||
|
|
||||||
layout.prop(context.scene, 'use_aa', text='Use Native AA Settings')
|
layout.prop(context.scene, 'use_aa', text='Use Native AA Settings')
|
||||||
layout.prop(prefs, 'advanced', text='Show Advanced Options')
|
layout.prop(prefs, 'advanced', text='Show Advanced Options')
|
||||||
# layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL'
|
# layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL'
|
||||||
|
@ -212,7 +220,7 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
|
||||||
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.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.export_infos_for_compo', icon='FILE', text='Export Layers Infos') # Not really need, called in Check layers invoke
|
||||||
col.operator('gp.layers_state', icon='CHECKMARK', text='Check layers')
|
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')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue