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-value
main
Pullusb 2022-01-26 16:32:33 +01:00
parent 5cca446fc0
commit 64efb7e395
9 changed files with 376 additions and 137 deletions

View File

@ -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

View File

@ -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)

View File

@ -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,
) )

View File

@ -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':

167
OP_post_render.py Normal file
View 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)

View File

@ -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)

View File

@ -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
View File

@ -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
View File

@ -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')