trailing whitespaces cleanup

main
pullusb 2023-01-18 14:28:27 +01:00
parent f3646e37df
commit 12cce98e41
23 changed files with 361 additions and 362 deletions

View File

@ -17,7 +17,7 @@ Activate / deactivate all masks using MA layers
1.2.1
- added: bundle app_template to load "GR Render" workspace from it
- added: ui button in dopesheet to load GP render workspace if not loaded
- added: ui button in dopesheet to load GP render workspace
1.2.0
@ -91,7 +91,7 @@ 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: `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.
@ -121,7 +121,7 @@ Activate / deactivate all masks using MA layers
- feat: Select a file output node. Set active file slot path and settings to main Scene output.
- Button in GP render panel with `Advanced` options active.
- Or search operator label `Set Active File Output To Composite`
- if Composite is already linked, pop-up ask if link should be replaced
- if Composite is already linked, pop-up ask if link should be replaced
0.7.0

View File

@ -19,7 +19,7 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator):
if not layer:
self.report({'ERROR'}, 'No active layer')
return {"CANCELLED"}
ct = 0
# send scene ?
hided = 0
@ -30,7 +30,7 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator):
l.viewlayer_render = fn.get_view_layer('exclude').name
continue
gen_vlayer.get_set_viewlayer_from_gp(ob, l)
if l.hide:
hided += 1
ct += 1
@ -64,7 +64,7 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator):
else:
scn = context.scene
excludes = [] # ['MA', 'IN'] # Get list dynamically
if self.mode == 'SELECTED':
gen_vlayer.export_gp_objects([o for o in context.selected_objects if o.type == 'GPENCIL'], exclude_list=excludes, scene=scn)
@ -106,7 +106,7 @@ GPEXP_OT_add_objects_to_render,
GPEXP_OT_split_to_scene,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -25,7 +25,7 @@ def batch_setup_render_scene(context=None, render_scn=None):
for fo in render_scn.node_tree.nodes:
if fo.type == 'OUTPUT_FILE':
fn.renumber_keep_existing(fo)
## Swap to bg_cam (if any)
# if render_scn.objects.get('bg_cam') and (not render_scn.camera or render_scn.camera.name != 'bg_cam'):
# print('Swap to bg cam')
@ -66,42 +66,42 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
# timer : bpy.props.FloatProperty(default=0.1, options={'SKIP_SAVE'})
excluded_prefix : bpy.props.StringProperty(
name='Excluded Layer By Prefix', default='GP, RG, PO, MA',
description='Exclude layer to send to render by prefix (comma separated list)')
clean_name_and_visibility : bpy.props.BoolProperty(name='Clean Name And Visibility', default=True,
clean_name_and_visibility : bpy.props.BoolProperty(name='Clean Name And Visibility', default=True,
description='Add object name to layer name when there is only prefix (ex: "CO_")\
\nEnable visibility for layer with prefix included in Prefix Filter')
clean_material_duplication : bpy.props.BoolProperty(name='Clean Material Duplication', default=True,
clean_material_duplication : bpy.props.BoolProperty(name='Clean Material Duplication', default=True,
description='Clean material stack. i.e: Replace "mat.001" in material stack if "mat" exists and has same color')
prefix_filter : bpy.props.StringProperty(name='Prefix Filter', default='CO, CU, FX, TO', # , MA # exclude MA if mask are applied
description='Comma separated prefix to render. Set the other prefix and non-prefixed layer to exluded viewlayer')
set_layers_colors : bpy.props.BoolProperty(name='Set Layers Colors', default=True,
set_layers_colors : bpy.props.BoolProperty(name='Set Layers Colors', default=True,
description='')
trigger_rename_lowercase : bpy.props.BoolProperty(name='Trigger Rename Lowercase', default=True,
trigger_rename_lowercase : bpy.props.BoolProperty(name='Trigger Rename Lowercase', default=True,
description='')
trigger_renumber_by_distance : bpy.props.BoolProperty(name='Trigger Renumber By Distance', default=True,
trigger_renumber_by_distance : bpy.props.BoolProperty(name='Trigger Renumber By Distance', default=True,
description='')
export_layer_infos : bpy.props.BoolProperty(name='Export Layer Infos', default=True,
export_layer_infos : bpy.props.BoolProperty(name='Export Layer Infos', default=True,
description='')
group_all_adjacent_layer_type : bpy.props.BoolProperty(name='Group All Adjacent Layer Type', default=True,
group_all_adjacent_layer_type : bpy.props.BoolProperty(name='Group All Adjacent Layer Type', default=True,
description='')
change_to_gp_workspace : bpy.props.BoolProperty(name='Change To Gp Workspace', default=True,
change_to_gp_workspace : bpy.props.BoolProperty(name='Change To Gp Workspace', default=True,
description='')
batch_setup_render_scene : bpy.props.BoolProperty(name='Batch Setup Render Scene', default=True,
batch_setup_render_scene : bpy.props.BoolProperty(name='Batch Setup Render Scene', default=True,
description='')
@ -116,7 +116,7 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
row = col.row()
row.prop(self, 'prefix_filter')
row.active = self.clean_name_and_visibility
col.prop(self, 'clean_material_duplication')
col.prop(self, 'set_layers_colors')
@ -132,7 +132,7 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
col.prop(self, 'batch_setup_render_scene')
# layout.prop(self, 'clear_unused_view_layers')
def execute(self, context):
print('-- Auto-build Render scene --\n')
@ -146,9 +146,9 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
if render_scn:
self.report({'ERROR'}, 'A "Render" scene already exists')
return {'CANCELLED'}
all_gp_objects = [o for o in context.scene.objects if o.type == 'GPENCIL']
## clean name and visibility
if self.clean_name_and_visibility:
for o in all_gp_objects:
@ -159,7 +159,7 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
## Clean name when layer has no name after prefix
if re.match(r'^[A-Z]{2}_$', l.info):
l.info = l.info + o.name.lower()
## Make used prefix visible ?? (maybe some layer were intentionally hidden...)
if (res := re.search(r'^([A-Z]{2})_', l.info)):
if res.group(1) in prefix_to_render and l.hide == True and not 'invisible' in l.info:
@ -202,7 +202,7 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
print('Trigger renumber by distance')
bpy.ops.gp.auto_number_object('EXEC_DEFAULT')
# bpy.ops.gp.auto_number_object('INVOKE_DEFAULT')
## Export layer infos ? (skip if json already exists)
if self.export_layer_infos:
print('Export layer infos (skip if json already exists)')
@ -221,7 +221,7 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
return {'CANCELLED'}
context.window.scene = render_scn
## Group all adjacent layer type
if self.group_all_adjacent_layer_type:
print('Group all adjacent layer type')
@ -252,7 +252,7 @@ class GPEXP_OT_render_auto_build(bpy.types.Operator):
## Batch setup render scene
batch_setup_render_scene(render_scn=render_scn)
## No need for timer anymore !
# if batch_setup_render_scene:
# if self.timer > 0:
@ -296,7 +296,7 @@ GPEXP_OT_render_auto_build,
GPEXP_OT_render_scene_setup,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -32,8 +32,8 @@ def check_layer_state(pool=None, reports=None):
# # all masks disable
# pass
## just list masks
# state = '' if l.use_mask_layer else ' (disabled)'
## 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 ''
@ -60,7 +60,7 @@ def check_file_output_numbering(reports=None):
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:
@ -84,7 +84,7 @@ class GPEXP_OT_check_render_scene(bpy.types.Operator):
bl_description = "Auto check render scene"
bl_options = {"REGISTER"} # , "UNDO"
# clear_unused_view_layers : bpy.props.BoolProperty(name="Clear unused view layers",
# 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)
@ -100,7 +100,7 @@ class GPEXP_OT_check_render_scene(bpy.types.Operator):
layout = self.layout
# layout.prop(self, 'clear_unused_view_layers')
def execute(self, context):
def execute(self, context):
reports = []
# check gp modifiers
broken_mods = check_broken_modifier_target()
@ -108,7 +108,7 @@ class GPEXP_OT_check_render_scene(bpy.types.Operator):
reports.append('GP modifiers targets:')
reports += broken_mods
# check layers
# check layers
layer_state = check_layer_state()
if layer_state:
if reports: reports.append('')
@ -137,7 +137,7 @@ classes=(
GPEXP_OT_check_render_scene,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -15,12 +15,12 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
# mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
def execute(self, context):
def execute(self, context):
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
print('re-arranging frames')
fn.rearrange_frames(render.node_tree)
@ -39,7 +39,7 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
break
if out:
fn.reorder_fileout(out, ng=n)
## clear disconnected fileout ??...
# for fo in render.node_tree.nodes:
@ -57,31 +57,31 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
# Internal prop (use when launching from python)
use_render_scene : bpy.props.BoolProperty(name="Use Render Scene",
use_render_scene : bpy.props.BoolProperty(name="Use Render Scene",
description="Force the clean on scene named Render, abort if not exists (not exposed)",
default=False, options={'SKIP_SAVE'})
clear_unused_view_layers : bpy.props.BoolProperty(name="Clear unused view layers",
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)
arrange_rl_nodes : bpy.props.BoolProperty(name="Arrange Render Node In Frames",
arrange_rl_nodes : bpy.props.BoolProperty(name="Arrange Render Node In Frames",
description="Re-arrange Render Layer Nodes Y positions within each existing frames" ,
default=True)
arrange_frames : bpy.props.BoolProperty(name="Arrange Frames",
arrange_frames : bpy.props.BoolProperty(name="Arrange Frames",
description="Re-arrange all frames Y positions" ,
default=True)
reorder_inputs : bpy.props.BoolProperty(name="Reorder I/O Sockets",
reorder_inputs : bpy.props.BoolProperty(name="Reorder I/O Sockets",
description="Reorder inputs/outputs of all 'NG_' nodegroup and their connected file output",
default=True)
clear_isolated_node_in_groups : bpy.props.BoolProperty(name="Clear Isolated Node In Groups",
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",
default=False)
@ -104,7 +104,7 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
layout.prop(self, 'fo_clear_disconnected')
if self.fo_clear_disconnected:
layout.label(text='Disconnected inputs are not exported', icon='INFO')
# box = layout.box()
# box.prop(self, 'arrange_frames')
# box.prop(self, 'reorder_inputs')
@ -152,17 +152,17 @@ class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
break
if out:
fn.reorder_fileout(out, ng=n)
# Clear input that do not exists
fn.clean_nodegroup_inputs(n, skip_existing_pass=True)
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)
fn.clear_nodegroup_content_if_disconnected(n.node_tree)
if self.fo_clear_disconnected:
for fo in nodes:
@ -179,7 +179,7 @@ classes=(
GPEXP_OT_clean_compo_tree,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -14,20 +14,20 @@ class GPEXP_OT_clear_render_tree(bpy.types.Operator):
mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
def execute(self, context):
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
# clear all nodes in frames
if render.use_nodes:
for i in range(len(render.node_tree.nodes))[::-1]:
# skip frames to delete later
if render.node_tree.nodes[i].type == 'FRAME':
continue
# skip unparented nodes
if not render.node_tree.nodes[i].parent:
continue
@ -59,7 +59,7 @@ classes=(
GPEXP_OT_clear_render_tree,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -14,10 +14,10 @@ class GPEXP_OT_reconnect_render_layer(bpy.types.Operator):
# mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
def execute(self, context):
def execute(self, context):
node_tree = context.scene.node_tree
nodes = node_tree.nodes
changed = []
for n in nodes:
if not n.select or not n.type == 'R_LAYERS':
@ -25,10 +25,10 @@ class GPEXP_OT_reconnect_render_layer(bpy.types.Operator):
if not ' / ' in n.layer:
continue
if n.outputs[0].is_linked: # already connected
continue
# get namme
obname = n.layer.split()[0]
grp_name = f'NG_{obname}'
@ -39,7 +39,7 @@ class GPEXP_OT_reconnect_render_layer(bpy.types.Operator):
if not grp:
print(f'{n.name} Node group not found : {n.layer} !-> {grp_name}')
continue
inp = grp.inputs.get(n.layer)
if not inp:
print(f'{n.name} no inputs name "{n.layer}" in group {grp_name}')
@ -48,7 +48,7 @@ class GPEXP_OT_reconnect_render_layer(bpy.types.Operator):
# reconnect
node_tree.links.new(n.outputs[0], inp)
changed.append(f'{n.name} ({n.layer}) to {grp_name}')
if changed:
self.report({'INFO'}, f'{len(changed)} nodes reconnected')
else:
@ -68,8 +68,8 @@ class GPEXP_OT_delete_render_layer(bpy.types.Operator):
# mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
def execute(self, context):
def execute(self, context):
rd_scn = bpy.data.scenes.get('Render')
if not rd_scn:
self.report({'ERROR'}, 'Viewlayers needs to be generated first!')
@ -81,7 +81,7 @@ class GPEXP_OT_delete_render_layer(bpy.types.Operator):
# if l.viewlayer_render and l.viewlayer_render != act.viewlayer_render and rd_scn.view_layers.get(l.viewlayer_render)]
rlayers_nodes = [n for n in nodes if n.select and n.type == 'R_LAYERS']
vls = [rd_scn.view_layers.get(n.layer) for n in rlayers_nodes if rd_scn.view_layers.get(n.layer)]
vl_names = [v.name for v in vls]
@ -115,7 +115,7 @@ class GPEXP_OT_delete_render_layer(bpy.types.Operator):
for i in range(len(grp.inputs))[::-1]:
if grp.inputs[i].name == sockin.name:
ngroup.inputs.remove(ngroup.inputs[i])
gp_in_socket = ngroup.nodes['Group Input'].outputs[i]
for lnk in gp_in_socket.links:
inside_nodes += fn.all_connected_forward(lnk.to_node)
@ -125,7 +125,7 @@ class GPEXP_OT_delete_render_layer(bpy.types.Operator):
if grp.outputs[i].name == sockout.name:
ngroup.outputs.remove(ngroup.outputs[i])
break
for sub_n in reversed(inside_nodes):
ngroup.nodes.remove(sub_n)
@ -160,7 +160,7 @@ class GPEXP_OT_set_active_fileout_to_compout(bpy.types.Operator):
if not len(self.fo.file_slots):
self.report({'ERROR'}, 'no slots in active file output')
return {'CANCELLED'}
# check if active slot has a source
if not self.fo.inputs[self.fo.active_input_index].is_linked:
return self.execute(context)
@ -170,11 +170,11 @@ class GPEXP_OT_set_active_fileout_to_compout(bpy.types.Operator):
if not out or not out.inputs[0].is_linked:
self.compo_out_from_link = ''
return self.execute(context)
# compo linked, pop panel to choose replace or not
self.compo_out_from_link = out.inputs[0].links[0].from_node.name
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
@ -189,11 +189,11 @@ class GPEXP_OT_set_active_fileout_to_compout(bpy.types.Operator):
idx = self.fo.active_input_index
sl = self.fo.file_slots[idx]
sk = self.fo.inputs[idx]
if not sk.is_linked:
self.report({'INFO'}, f'Outut changed to match {sl.path} (slot was not linked)')
return {'FINISHED'}
## If linked replace links to Composite node
if not self.relink_composite:
return {'FINISHED'}
@ -201,16 +201,16 @@ class GPEXP_OT_set_active_fileout_to_compout(bpy.types.Operator):
ntree = context.scene.node_tree
links = context.scene.node_tree.links
nodes = context.scene.node_tree.nodes
out = nodes.get('Composite')
if not out:
out = fn.create_node('COMPOSITE', tree=ntree)
fo_loc = fn.real_loc(self.fo)
out.location = (fo_loc.x, fo_loc.y + 160)
# if out.inputs[0].is_linked:
# self.report({'WARNING'}, f'Outut changed to match {sl.path} (Composite node already linked)')
lnk = sk.links[0]
from_sk = sk.links[0].from_socket
links.remove(lnk)
@ -226,7 +226,7 @@ GPEXP_OT_delete_render_layer,
GPEXP_OT_set_active_fileout_to_compout,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -46,7 +46,7 @@ GPEXP_OT_set_crop_from_selection,
GPEXP_OT_export_crop_coord_to_json,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -14,7 +14,7 @@ def Export_AE_2d_position_json_data():
if not cam:
print('Active camera not "anim_cam"')
cam = scn.camera
rx = scn.render
rx, ry = rd.resolution_x, rd.resolution_y
@ -49,7 +49,7 @@ def export_AE_objects_position_keys():
result = {}
print(f'Exporting 2d position (scene range: {scn.frame_start} - {scn.frame_end})')
for fr in range(scn.frame_start,scn.frame_end + 1):
print(f'frame: {fr}')
scn.frame_set(fr)
@ -57,17 +57,17 @@ def export_AE_objects_position_keys():
if not result.get(o.name):
result[o.name] = []
proj2d = world_to_camera_view(scn, scn.camera, o.matrix_world.to_translation()) # + Vector((.5,.5,0))
# proj2d = correct_shift(proj2d, scn.camera) # needed ?
x = (proj2d[0]) * scn.render.resolution_x
y = -(proj2d[1]) * scn.render.resolution_y + scn.render.resolution_y
result[o.name].append((fr,x,y))
for name,value in result.items():
txt = fn.get_ae_keyframe_clipboard_header(scn)
for v in value:
txt += '\t%s\t%s\t%s\t0\t\n'%(v[0],v[1],v[2]) # add 0 for Z (probably not needed)
@ -76,12 +76,12 @@ def export_AE_objects_position_keys():
blend = Path(bpy.data.filepath)
keyfile = blend.parent / 'render' / f'pos_{name}.txt'
keyfile.parent.mkdir(parents=False, exist_ok=True)
print(f'exporting keys for {name} at {keyfile}')
## save forcing CRLF terminator (DOS style, damn windows)
## in case it's exported from linux
with open(keyfile, 'w', newline='\r\n') as fd:
with open(keyfile, 'w', newline='\r\n') as fd:
fd.write(txt)
@ -178,7 +178,7 @@ class GPEXP_OT_fix_overscan_shift(bpy.types.Operator):
def draw(self, context):
layout = self.layout
if self.use_selection:
col = layout.column()
col.label(text=f'Camera "{self.cam_ob.name}" selected', icon='INFO')
@ -196,7 +196,7 @@ class GPEXP_OT_fix_overscan_shift(bpy.types.Operator):
def execute(self, context):
cam = self.cam_ob.data
ratio_x = self.init_rx / context.scene.render.resolution_x
ratio_y = self.init_ry / context.scene.render.resolution_y
@ -212,7 +212,7 @@ class GPEXP_OT_fix_overscan_shift(bpy.types.Operator):
else:
if cam.shift_x != 1:
cam.shift_x = cam.shift_x * ratio_x
if ratio_y != 1:
if fn.has_keyframe(cam, 'shift_y'):
fcu = cam.animation_data.action.fcurves.find('shift_y')
@ -260,7 +260,7 @@ GPEXP_OT_fix_overscan_shift,
GPEXP_PT_extra_gprender_func
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)
@ -274,4 +274,3 @@ def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -34,7 +34,7 @@ class GPEXP_OT_number_outputs(bpy.types.Operator):
return True
mode : bpy.props.StringProperty(default='SELECTED', options={'SKIP_SAVE'})
# ctrl : bpy.props.StringProperty(default=False, options={'SKIP_SAVE'}) # no need
# ctrl : bpy.props.StringProperty(default=False, options={'SKIP_SAVE'}) # no need
def invoke(self, context, event):
self.ctrl = event.ctrl
@ -45,7 +45,7 @@ class GPEXP_OT_number_outputs(bpy.types.Operator):
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
ct = 0
nodes = render.node_tree.nodes
for fo in nodes:
@ -95,7 +95,7 @@ class GPEXP_OT_set_output_node_format(bpy.types.Operator):
for n in nodes:
if n.type != 'OUTPUT_FILE' or n == ref or not n.select:
continue
for attr in dir(ref.format):
if attr.startswith('__') or attr in {'rna_type','bl_rna', 'view_settings', 'display_settings','stereo_3d_format'}: # views_format
continue
@ -104,10 +104,10 @@ class GPEXP_OT_set_output_node_format(bpy.types.Operator):
except Exception as e:
print(f"can't set attribute : {attr}")
# n.format.file_format = file_format
# n.format.file_format = file_format
# n.format.color_mode = color_mode
# n.format.color_depth = color_depth
# n.format.compression = compression
# n.format.color_depth = color_depth
# n.format.compression = compression
ct += 1
@ -157,7 +157,7 @@ class GPEXP_OT_normalize_outnames(bpy.types.Operator):
fp_l = reslash.split(fp)
for i, part in enumerate(fp_l):
fp_l[1] = re.sub(r'(^\d{3}_)?([A-Z]{2}_)?(.*?)(_[A-Z]{2})?(_)?', out_norm, part)
fs.path = '/'.join(fp_l)
ct += 1
@ -183,7 +183,7 @@ class GPEXP_OT_enable_all_viewlayers(bpy.types.Operator):
vl_list = [vl for vl in scn.view_layers if not vl.use and vl.name not in {'View Layer', 'exclude'}]
for v in vl_list:
v.use = True
self.report({"INFO"}, f'{len(vl_list)} ViewLayers Reactivated')
return {"FINISHED"}
@ -206,7 +206,7 @@ class GPEXP_OT_activate_only_selected_layers(bpy.types.Operator):
vls = [scn.view_layers.get(n.layer) for n in rlayers_nodes if scn.view_layers.get(n.layer)]
for v in scn.view_layers:
v.use = v in vls
self.report({"INFO"}, f'Now only {len(vls)} viewlayer active (/{len(scn.view_layers)})')
return {"FINISHED"}
@ -224,25 +224,25 @@ class GPEXP_OT_reset_render_settings(bpy.types.Operator):
if scn.name == 'Scene':
# don't touch original scene
continue
# set a unique preview output
# - avoid possible write/sync overlap (point to tmp on linux ?)
# - allow to monitor output of a scene and possibly use Overwrite
if scn.render.filepath.startswith('//render/preview/'):
scn.render.filepath = f'//render/preview/{bpy.path.clean_name(scn.name.lower())}/preview_'
print(f'Scene {scn.name}: change output to {scn.render.filepath}')
if not scn.use_nodes:
continue
# set the settings depending on merges node presences
use_native_aa = True
for n in scn.node_tree.nodes:
if n.name.startswith('merge_NG_'):
use_native_aa = False
break
if scn.use_aa != use_native_aa:
print(f'Scene {scn.name}: changed scene AA settings, native AA = {use_native_aa}')
fn.scene_aa(scene=scn, toggle=use_native_aa)
@ -262,7 +262,7 @@ GPEXP_OT_reset_render_settings,
# GPEXP_OT_normalize_outnames,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -19,7 +19,7 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
# get node group
# ng = rlayers[0].outputs[0].links[0].to_node
# sort RL descending
# sort RL descending
rlayers.sort(key=lambda n: fn.real_loc(n).y, reverse=True)
node_tree = rlayers[0].id_data
@ -41,11 +41,11 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
obname = lname = bpy.path.clean_name(vl_name)
base_path = f'//render/'
slot_name = f'{lname}/{lname}_'
# change colors of those nodes
disconnected_groups = []
if not color:
if not color:
color = fn.random_color()
for n in rlayers:
n.use_custom_color = True
@ -57,7 +57,7 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
if lnk.to_node.name.startswith('NG_'):
disconnected_groups.append(lnk.to_node)
links.remove(lnk)
disconnected_groups = list(set(disconnected_groups))
ng_name = f'merge_NG_{obname}' # only object name
@ -72,7 +72,7 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
while bpy.data.node_groups.get(ng_name): # nodes.get(ng_name)
if not re.search(r'(\d+)$', ng_name):
ng_name += '_02' # if not ending with a number add _02
ng_name = re.sub(r'(\d+)(?!.*\d)', lambda x: str(int(x.group(1))+1).zfill(len(x.group(1))), ng_name)
ng_name = re.sub(r'(\d+)(?!.*\d)', lambda x: str(int(x.group(1))+1).zfill(len(x.group(1))), ng_name)
# print(f'create merge nodegroup {ng_name}')
ngroup = bpy.data.node_groups.new(ng_name, 'CompositorNodeTree')
@ -101,7 +101,7 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
out.base_path = base_path
out.file_slots.new(slot_name)
links.new(ng.outputs[0], out.inputs[-1])
fn.clear_disconnected(out)
out.update()
@ -139,7 +139,7 @@ class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator):
# if not act.viewlayer_render:
# self.report({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned')
# return {'CANCELLED'}
ret = fn.merge_gplayer_viewlayers(ob, act=act, layers=layers)
if isinstance(ret, tuple):
self.report(*ret)
@ -159,9 +159,9 @@ class GPEXP_OT_auto_merge_adjacent_prefix(bpy.types.Operator):
excluded_prefix : bpy.props.StringProperty(
name='Excluded Prefix', default='GP,RG,PO',
description='Exclude comma separated prefix from merging viewlayer')
first_name : bpy.props.BoolProperty(name='Merge On Bottom Layer',
default=True,
first_name : bpy.props.BoolProperty(name='Merge On Bottom Layer',
default=True,
description='Keep the viewlayer of the bottom layer in groups, else upper layer')
def invoke(self, context, event):
@ -209,7 +209,7 @@ class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
render = bpy.data.scenes.get('Render')
if render:
nodes = render.node_tree.nodes
clean_ob_name = bpy.path.clean_name(ob.name)
rlayers = []
for l in layers:
@ -226,16 +226,16 @@ class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
if not rlayer:
# send to function to generate the rlayer and connect
_vl, rl = gen_vlayer.get_set_viewlayer_from_gp(ob, l)
else:
rlayer.sort(key=lambda n: n.location.y, reverse=True)
rl = rlayer[0]
if act == l:
nodes.active = rl # make it active so the merge use this one
rlayers.append(rl)
color = None
if fn.has_channel_color(act): # and bpy.context.preferences.edit.use_anim_channel_group_colors
color = act.channel_color
@ -287,7 +287,7 @@ GPEXP_OT_merge_selected_dopesheet_layers,# unused
GPEXP_OT_merge_selected_viewlayer_nodes,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -42,15 +42,15 @@ def renumber_sequence_on_disk_from_file_slots(apply=True, active_scene_only=Fals
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
@ -67,20 +67,20 @@ def renumber_sequence_on_disk_from_file_slots(apply=True, active_scene_only=Fals
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):
@ -91,7 +91,7 @@ def renumber_sequence_on_disk_from_file_slots(apply=True, active_scene_only=Fals
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}')
@ -120,11 +120,11 @@ class GPEXP_OT_renumber_files_on_disk(bpy.types.Operator):
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")')
@ -158,7 +158,7 @@ classes=(
GPEXP_OT_renumber_files_on_disk,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -37,7 +37,7 @@ def export_all_selected_frame_as_svg():
if ob.type != 'GPENCIL':
continue
frames += [f.frame_number for l in ob.data.layers if not l.hide for f in l.frames if len(f.strokes)]
if frames:
frames = sorted(list(set(frames)))
else:
@ -59,7 +59,7 @@ def export_all_selected_frame_as_svg():
if fp.exists():
print(f' already exists: {fp}')
continue
bpy.context.scene.frame_current = fnum
bpy.ops.wm.gpencil_export_svg(filepath=str(fp),
check_existing=True,
@ -103,16 +103,16 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
def poll(cls, context):
return True
def execute(self, context):
def execute(self, context):
# rd_scn = bpy.data.scenes.get('Render')
# if not rd_scn:
# self.report({'ERROR'}, 'Viewlayers needs to be generated first!')
# return {'CANCELLED'}
### store
## dict all visible objects as key with value : sub dict {layer : hide_bool}
# obj_vis = [[o, o.hide_viewport, o.hide_render] for o in context.scene.objects if o.type == 'GPENCIL' and not (o.hide_get() or o.hide_viewport)]
t0 = time()
@ -122,7 +122,7 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
selection = [o for o in context.selected_objects]
messages = []
## adaptative resampling on all concerned objects
for ob in store.keys():
mod = ob.grease_pencil_modifiers.get('resample')
@ -136,7 +136,7 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
if ob.type != 'GPENCIL':
continue
mess = f'--- {ob.name}:'
mess = f'--- {ob.name}:'
print(mess)
messages.append(mess)
@ -149,14 +149,14 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
# for o in context.scene.objects:
# o.hide_viewport = True
# ob.hide_viewport = False
## manage layers
gpl = ob.data.layers
vl_dicts = {vl_name: list(layer_grp) for vl_name, layer_grp in groupby(gpl, lambda x: x.viewlayer_render)}
for vl_name, layer_list in vl_dicts.items():
vl = context.scene.view_layers.get(vl_name)
if not vl:
mess = f'/!\ {vl_name} viewlayer not exists : skipped {[l.info for l in layer_list]}'
mess = f'/!\ {vl_name} viewlayer not exists : skipped {[l.info for l in layer_list]}'
print(mess)
messages.append(mess)
continue
@ -164,11 +164,11 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
continue
if not vl.use:
mess = f'{vl_name} viewlayer disabled'
mess = f'{vl_name} viewlayer disabled'
print(mess)
messages.append(mess)
continue
# Case of initially masked layer !
hide_ct = 0
@ -191,34 +191,34 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
ng_name = f'NG_{ob.name}'
ng = context.scene.node_tree.nodes.get(ng_name)
if not ng:
mess = f'Skip {vl_name}: Not found nodegroup {ng_name}'
mess = f'Skip {vl_name}: Not found nodegroup {ng_name}'
print(mess)
messages.append(mess)
continue
ng_socket = ng.outputs.get(vl_name)
if not ng_socket:
mess = f'Skip {vl_name}: Not found in nodegroup {ng_name} sockets'
mess = f'Skip {vl_name}: Not found in nodegroup {ng_name} sockets'
print(mess)
messages.append(mess)
continue
if not len(ng_socket.links):
mess = f' socket is disconnected in {ng_name} nodegroup'
mess = f' socket is disconnected in {ng_name} nodegroup'
print(mess)
messages.append(mess)
continue
fo_node = ng_socket.links[0].to_node
fo_socket = ng_socket.links[0].to_socket
if fo_node.type != 'OUTPUT_FILE':
mess = f'Skip {vl_name}: node is not an output_file {fo_node.name}'
print(mess)
messages.append(mess)
continue
if fo_node.mute:
mess = f'Skip {vl_name}: output is muted {fo_node.name}'
print(mess)
@ -231,12 +231,12 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
subpath = fo_node.file_slots[idx].path
fp = Path(fo_node.base_path.rstrip('/')) / subpath
fp = Path(bpy.path.abspath(str(fp)).rstrip("/"))
print(f'render {total} layers at: {fp.parent}') #Dbg
# hide all layer that are: not associated with VL (not in layer_list) or hided initially (store[ob][l])
for l in gpl:
l.hide = l not in layer_list or store[ob][l]
l.hide = l not in layer_list or store[ob][l]
for l in gpl:
if not l.hide:
@ -250,12 +250,12 @@ class GPEXP_OT_export_as_pdf(bpy.types.Operator):
# ob.hide_viewport = False # no need
for l, h in layer_dic.items():
l.hide = h
for o in selection:
o.select_set(True)
if act:
context.view_layer.objects.active = act
# for oviz in obj_vis:
# oviz[0].hide_viewport = oviz[1]
@ -269,7 +269,7 @@ classes=(
GPEXP_OT_export_as_pdf,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -34,7 +34,7 @@ class GPEXP_OT_render_all_scenes(bpy.types.Operator):
if all(x.mute for x in outfiles):
print(f'\n -!-> Skip {scn.name}, All output file are muted')
continue
print(f'\n --> Rendering {scn.name}')
# bpy.context.window.scene = scn
bpy.ops.render.render(animation=True, scene=scn.name)
@ -61,13 +61,13 @@ def scene_render_popup_ui(self, context):
scn = bpy.data.scenes.get(si.name)
# compare to existing Rlayers (overkill ?)
# vls = [scn.view_layers.get(n.layer) for n in rlayers_nodes if scn.view_layers.get(n.layer)]
vls = [vl for vl in scn.view_layers if vl.name != 'View Layer']
if vls:
exclude_count = len([vl for vl in vls if not vl.use])
if exclude_count:
row.label(text=f'{exclude_count}/{len(vls)} excluded viewlayers', icon='ERROR')
row.label(text=f'{exclude_count}/{len(vls)} excluded viewlayers', icon='ERROR')
if not scn.use_nodes:
row.label(text='use_node deactivated', icon='ERROR')
@ -77,13 +77,13 @@ def scene_render_popup_ui(self, context):
if not outfiles:
row.label(text='No output files nodes', icon='ERROR')
continue
outnum = len(outfiles)
muted = len([x for x in outfiles if x.mute])
if muted == outnum:
row.label(text='All output file are muted', icon='ERROR')
continue
elif muted:
row.label(text=f'{muted}/{outnum} output file muted', icon='ERROR')
continue
@ -174,7 +174,7 @@ class GPEXP_OT_bg_render_script_selected_scene(bpy.types.Operator):
def draw(self, context):
scene_render_popup_ui(self, context)
def execute(self, context):
d = fn.export_crop_to_json()
@ -182,7 +182,7 @@ class GPEXP_OT_bg_render_script_selected_scene(bpy.types.Operator):
print('No crop to export, border disabled in all scenes')
platform = sys.platform
blend = Path(bpy.data.filepath)
scn_to_render = [si.name for si in context.scene.scenes_list if si.select]
@ -210,11 +210,11 @@ class GPEXP_OT_bg_render_script_selected_scene(bpy.types.Operator):
else: # Unix : point same for each user
cmd = f'"{bin_path}" -b "{bpy.data.filepath}" -S "{scn_name}" -a'
script_text.append(cmd)
script_text.append('echo --- END BATCH ---')
script_text.append('pause')
with batch_file.open('w') as fd:
fd.write('\n'.join(script_text))
@ -230,7 +230,7 @@ GPEXP_OT_render_all_scenes,
GPEXP_OT_bg_render_script_selected_scene,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.scenes_list = bpy.props.CollectionProperty(type=GPEXP_scene_select_prop)
@ -238,5 +238,5 @@ def register():
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)
del bpy.types.Scene.scenes_list

View File

@ -18,21 +18,21 @@ class GPEXP_OT_render_scene_switch(bpy.types.Operator):
if len(scenes) < 2:
self.report({'ERROR'},'No other scene to go to')
return {"CANCELLED"}
if context.scene.name == 'Render':
scn = scenes.get('Scene')
if not scn: # get the next available scene
self.report({'WARNING'},'No scene named "Scene"')
slist = [s.name for s in scenes]
scn = scenes[(slist.index(bpy.context.scene.name) + 1) % len(scenes)]
else:
scn = scenes.get('Render')
if not scn:
self.report({'ERROR'},'No "Render" scene yet')
return {"CANCELLED"}
self.report({'INFO'},f'Switched to scene "{scn.name}"')
bpy.context.window.scene = scn
return {"FINISHED"}
@ -50,7 +50,7 @@ class GPEXP_OT_swap_render_cams(bpy.types.Operator):
if not anim_cam or not bg_cam:
self.report({'ERROR'}, 'anim_cam or bg_cam is missing')
return {"CANCELLED"}
cam = context.scene.camera
if not cam:
context.scene.camera = anim_cam
@ -105,7 +105,7 @@ GPEXP_OT_swap_render_cams,
GPEXP_OT_set_gp_render_workspace,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -41,14 +41,14 @@ def check_outname(ob, l):
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):
@ -93,7 +93,7 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
# skip non rendered layers
if l.hide:
continue
if l.info.startswith('MA_'):
# No point in storing information of masking layers...
continue
@ -101,7 +101,7 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
## 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}')
@ -112,7 +112,7 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
## Check opacity, blend mode
if l.opacity < 1.0:
ldic['opacity'] = l.opacity
if l.blend_mode != 'REGULAR':
ldic['blend_mode'] = l.blend_mode
@ -143,7 +143,7 @@ class GPEXP_OT_export_infos_for_compo(bpy.types.Operator):
if multi_mask:
ldic['masks'] = multi_mask
## add to full dic
if ldic:
# add source object ? might be usefull to pin point layer
@ -167,25 +167,25 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
bl_description = "Display state of layer that migh need adjustement"
bl_options = {"REGISTER"} # , "UNDO"
# clear_unused_view_layers :BoolProperty(name="Clear unused view layers",
# clear_unused_view_layers :BoolProperty(name="Clear unused view layers",
# description="Delete view layer that aren't used in the nodetree anymore",
# default=True)
all_objects : BoolProperty(name='On All Object',
default=True, description='On All object, else use selected objects') # , options={'SKIP_SAVE'}
set_full_opacity : BoolProperty(name='Set Full Opacity',
default=True, description='Check/Set full opacity') # , options={'SKIP_SAVE'}
set_use_lights : BoolProperty(name='Disable Use Light',
default=True, description='Check/Set use lights disabling') # , options={'SKIP_SAVE'}
set_blend_mode : BoolProperty(name='Set Regular Blend Mode',
default=True, description='Check/Set blend mode to regular') # , options={'SKIP_SAVE'}
clear_frame_out_of_range : BoolProperty(name='Clear Frames Out Of Scene Range',
default=False, description='Delete frames that before scene start and after scene end range\nWith a tolerance of one frame to avoid problem\nAffect all layers)') # , options={'SKIP_SAVE'}
opacity_exclude_list : StringProperty(name='Skip',
default='MA', description='Skip prefixes from this list when changing opacity\nSeparate multiple value with a comma (ex: MA,IN)') # , options={'SKIP_SAVE'}
@ -208,7 +208,7 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
layout = self.layout
layout.prop(self, 'all_objects')
total = len([o for o in context.scene.objects if o.type == 'GPENCIL'])
target_num = total if self.all_objects else len([o for o in context.selected_objects if o.type == 'GPENCIL'])
layout.label(text=f'{target_num}/{total} targeted GP')
@ -224,7 +224,7 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
layout.prop(self, 'set_blend_mode')
# layout.prop(self, 'clear_unused_view_layers')
def execute(self, context):
def execute(self, context):
if self.all_objects:
pool = [o for o in context.scene.objects if o.type == 'GPENCIL' and fn.is_valid_name(o.name)]
else:
@ -235,7 +235,7 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
for ob in pool:
changes.append(f'>> {ob.name}')
layers = ob.data.layers
if self.clear_frame_out_of_range:
ct = fn.clear_frame_out_of_range(ob, verbose=False)
if ct:
@ -243,11 +243,11 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
for l in layers:
used = False
## mask check
# if l.mask_layers:
# print(f'-> masks')
# state = '' if l.use_mask_layer else ' (disabled)'
# state = '' if l.use_mask_layer else ' (disabled)'
# print(f'{ob.name} > {l.info}{state}:')
# used = True
# for ml in l.mask_layers:
@ -263,7 +263,7 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
print(f'Skipped layer : {l.info}')
else:
full_opacity_state = '' if self.set_full_opacity else ' (check only)'
mess = f'{l.info} : opacity {l.opacity:.2f} >> 1.0{full_opacity_state}'
mess = f'{l.info} : opacity {l.opacity:.2f} >> 1.0{full_opacity_state}'
print(mess)
changes.append(mess)
if self.set_full_opacity:
@ -319,7 +319,7 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
# if not render:
# print('SKIP, no Render scene')
# return {"CANCELLED"}
return {"FINISHED"}
class GPEXP_OT_lower_layers_name(bpy.types.Operator):
@ -334,13 +334,13 @@ class GPEXP_OT_lower_layers_name(bpy.types.Operator):
all_objects : BoolProperty(name='On All Object',
default=True, description='On All object, else use selected objects') # , options={'SKIP_SAVE'}
object_name : BoolProperty(name='Normalize Object Name',
default=True, description='Make the object name lowercase') # , options={'SKIP_SAVE'}
layer_name : BoolProperty(name='Normalize Layers Names',
default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'}
# dash_to_undescore : BoolProperty(name='Dash To Underscore',
# default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'}
@ -359,7 +359,7 @@ class GPEXP_OT_lower_layers_name(bpy.types.Operator):
gp_ct = len([o for o in context.scene.objects if o.type == 'GPENCIL' and fn.is_valid_name(o.name)])
else:
gp_ct = len([o for o in context.selected_objects if o.type == 'GPENCIL'])
layout.label(text=f'{gp_ct} to lower-case')
layout.separator()
layout.label(text=f'Choose what to rename:')
@ -407,10 +407,10 @@ class GPEXP_OT_auto_number_object(bpy.types.Operator):
all_objects : BoolProperty(name='On All GP Object',
default=True, description='On All object, else use selected Grease Pencil objects') # , options={'SKIP_SAVE'}
rename_data : BoolProperty(name='Rename Gpencil Data',
default=True, description='Rename Also the Grease Pencil data using same name as object') # , options={'SKIP_SAVE'}
delete : BoolProperty(default=False, options={'SKIP_SAVE'})
def invoke(self, context, event):
@ -425,7 +425,7 @@ class GPEXP_OT_auto_number_object(bpy.types.Operator):
o.name = o.name[4:]
ct += 1
self.report({'INFO'}, f'{ct}/{len(gps)} number prefix removed from object names')
return {"FINISHED"}
return context.window_manager.invoke_props_dialog(self)
@ -437,7 +437,7 @@ class GPEXP_OT_auto_number_object(bpy.types.Operator):
gp_ct = len([o for o in context.scene.objects if o.type == 'GPENCIL' and fn.is_valid_name(o.name)])
else:
gp_ct = len([o for o in context.selected_objects if o.type == 'GPENCIL'])
layout.prop(self, 'rename_data')
layout.label(text=f'{gp_ct} objects to renumber')
if not gp_ct:
@ -459,8 +459,8 @@ class GPEXP_OT_auto_number_object(bpy.types.Operator):
for i, o in reversed_enumerate(pool):
if o.show_in_front:
fronts.append(pool.pop(i))
fronts.append(pool.pop(i))
cam_loc = context.scene.camera.matrix_world.to_translation()
# filter by distance to camera object (considering origins)
@ -473,15 +473,15 @@ class GPEXP_OT_auto_number_object(bpy.types.Operator):
regex_num = re.compile(r'^(\d{3})_')
for o in pool:
renum = regex_num.search(o.name)
if not renum:
o.name = f'{str(ct).zfill(3)}_{o.name}'
else:
## either replace or leave untouched
# continue
o.name = f'{str(ct).zfill(3)}_{o.name[4:]}'
ct += 10
if self.rename_data and o.name != o.data.name:
o.data.name = o.name
@ -513,17 +513,17 @@ class GPEXP_OT_check_masks(bpy.types.Operator):
if not obj_stat in changes:
changes.append(obj_stat)
print(obj_stat)
hide_state = ' (hided)' if l.hide else ''
text = f' {l.info}{hide_state}:' # :masks:
text = f' {l.info}{hide_state}:' # :masks:
changes.append(text)
print(text)
has_masks = False
for ml in l.mask_layers:
# 'hide', 'invert', 'name'
h = ' hided' if ml.hide else ''
i = ' (inverted)' if ml.invert else ''
i = ' (inverted)' if ml.invert else ''
text = f' - {ml.name}{h}{i}'
changes.append(text)
print(text)
@ -532,7 +532,7 @@ class GPEXP_OT_check_masks(bpy.types.Operator):
if not has_masks:
text = 'No masks!'
changes.append(text)
print(text)
print(text)
changes.append('')
if changes:
@ -601,10 +601,10 @@ class GPEXP_OT_select_layer_in_comp(bpy.types.Operator):
print(f'{l.info} -> Select node {n.name}')
selected.append(n.name)
n.select = True
if not infos and not selected:
self.report({'ERROR'}, 'Nothing selected')
return {"CANCELLED"}
return {"CANCELLED"}
infos = infos + [f'-- Selected {len(selected)} nodes --'] + selected
fn.show_message_box(_message=infos, _title="Selected viewlayer in compo", _icon='INFO')
@ -620,7 +620,7 @@ GPEXP_OT_check_masks,
GPEXP_OT_select_layer_in_comp,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)

View File

@ -6,8 +6,8 @@ bl_info = {
"blender": (2, 93, 0),
"location": "View3D",
"warning": "",
"doc_url": "https://gitlab.com/autour-de-minuit/blender/gp_render",
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/gp_render/-/issues",
"doc_url": "https://gitlab.com/autour-de-minuit/blender/gp_render",
"tracker_url": "https://gitlab.com/autour-de-minuit/blender/gp_render/-/issues",
"category": "Object" }
@ -52,7 +52,7 @@ bl_modules = (
)
def update_scene_aa(context, scene):
scene_aa(toggle=bpy.context.scene.use_aa)
scene_aa(toggle=bpy.context.scene.use_aa)
import bpy
@ -65,12 +65,12 @@ def register():
# bpy.types.Scene.pgroup_name = bpy.props.PointerProperty(type = PROJ_PGT_settings)
bpy.types.Scene.use_aa = bpy.props.BoolProperty(
name='Use Native Anti Aliasing',
default=True,
name='Use Native Anti Aliasing',
default=True,
description='\
Should be Off only if tree contains a merge_NG or alpha-over-combined renderlayers.\n\
Auto-set to Off when using node merge button\n\
Toggle: AA settings of and muting AA nested-nodegroup',
Toggle: AA settings of and muting AA nested-nodegroup',
update=update_scene_aa)
def unregister():

View File

@ -14,7 +14,7 @@ def setup_gp_render_workspace():
for space in area.spaces:
if space.type == 'VIEW_3D':
space.region_3d.view_perspective = 'CAMERA'
def register():
bpy.app.handlers.load_post.append(setup_gp_render_workspace)

174
fn.py
View File

@ -20,11 +20,11 @@ def is_valid_name(name):
if name.startswith('.'):
return False
## FIXME: /!\ "note" as an exclude word is not good practice, temporary fix
if name.lower() == 'note':
return False
return True
### -- node basic
@ -38,7 +38,7 @@ def create_node(type, tree=None, **kargs):
node = tree.nodes.new(type)
for k,v in kargs.items():
setattr(node, k, v)
return node
def new_aa_node(tree, **kargs):
@ -62,7 +62,7 @@ def create_aa_nodegroup(tree):
sep = create_node('CompositorNodeSepRGBA', tree=ngroup, location=(-150,0))
comb = create_node('CompositorNodeCombRGBA', tree=ngroup, location=(350,25))
# in AA
# ngroup.links.new(comb.outputs[0], ng_out.inputs[0]) # <- connect without out AA
aa = new_aa_node(ngroup, location=(-400, 0))
@ -73,24 +73,24 @@ def create_aa_nodegroup(tree):
# ngroup.links.new(ng_in.outputs[0], sep.inputs[0])
for i in range(3):
ngroup.links.new(sep.outputs[i], comb.inputs[i])
# alpha AA
alpha_aa = new_aa_node(ngroup, location=(100,-150))
ngroup.links.new(sep.outputs[3], alpha_aa.inputs[0])
ngroup.links.new(alpha_aa.outputs[0], comb.inputs[3])
ngroup.links.new(comb.outputs[0], ng_out.inputs[0])
ng = create_node('CompositorNodeGroup', tree=tree)
ng.node_tree = ngroup
ng.name = ngroup.name
ng.hide=True
return ng
## -- object and scene settings
## -- object and scene settings
def activate_workspace(name='', context=None):
if not name:
@ -105,11 +105,11 @@ def activate_workspace(name='', context=None):
if (wkspace := bpy.data.workspaces.get(name)):
context.window.workspace = wkspace
return True
# Same name with spaces as underscore
dir_name = name.replace(' ', '_')
filepath = Path(__file__).parent / 'app_templates' / dir_name / 'startup.blend'
ret = bpy.ops.workspace.append_activate(idname=name, filepath=str(filepath))
if ret != {'FINISHED'}:
print(f'Could not found {name} at {filepath}')
@ -119,7 +119,7 @@ def activate_workspace(name='', context=None):
def copy_settings(obj_a, obj_b):
exclusion = ['bl_rna', 'id_data', 'identifier','name_property','rna_type','properties', 'stamp_note_text','use_stamp_note',
'settingsFilePath', 'settingsStamp', 'select', 'matrix_local', 'matrix_parent_inverse',
'settingsFilePath', 'settingsStamp', 'select', 'matrix_local', 'matrix_parent_inverse',
'matrix_basis','location','rotation_euler', 'rotation_quaternion', 'rotation_axis_angle', 'scale']
for attr in dir(obj_a):
@ -169,7 +169,7 @@ def set_settings(scene=None, aa=True):
'''aa == using native AA, else disable scene AA'''
if not scene:
scene = bpy.context.scene
# specify scene settings for these kind of render
set_scene_aa_settings(scene=scene, aa=aa)
@ -177,7 +177,7 @@ def set_settings(scene=None, aa=True):
scene.render.use_compositing = True
scene.render.use_sequencer = False
scene.view_settings.view_transform = 'Standard'
scene.render.resolution_percentage = 100
# output (fast write settings since this is just to delete afterwards...)
@ -192,7 +192,7 @@ def scene_aa(scene=None, toggle=True):
scene=bpy.context.scene
# enable/disable native anti-alias on active scene
set_scene_aa_settings(scene=scene, aa=toggle)
set_scene_aa_settings(scene=scene, aa=toggle)
# mute/unmute AA nodegroups
for n in scene.node_tree.nodes:
if n.type == 'GROUP' and n.name.startswith('NG_'):
@ -216,7 +216,7 @@ def new_scene_from(name, src_scn=None, regen=True, crop=True, link_cam=True, lin
for attr in ['frame_start', 'frame_end', 'frame_current', 'camera', 'world']:
setattr(scn, attr, getattr(src_scn, attr))
copy_settings(src_scn.render, scn.render)
## link cameras (and lights ?)
for ob in src_scn.objects:
if link_cam and ob.type == 'CAMERA':
@ -226,7 +226,7 @@ def new_scene_from(name, src_scn=None, regen=True, crop=True, link_cam=True, lin
# set adapted render settings
set_settings(scn)
if crop:
scn.render.use_border = True
scn.render.use_crop_to_border = True
@ -239,7 +239,7 @@ def get_render_scene():
if render_scn:
return render_scn
## -- Create render scene
## -- Create render scene
current = bpy.context.scene
## With data
@ -256,14 +256,14 @@ def get_render_scene():
for attr in ['frame_start', 'frame_end', 'frame_current', 'camera', 'world']:
setattr(render_scn, attr, getattr(current, attr))
copy_settings(current.render, render_scn.render)
## link cameras (and lights ?)
for ob in current.objects:
if ob.type in ('CAMERA', 'LIGHT'):
render_scn.collection.objects.link(ob)
render_scn.use_nodes = True
## Clear node tree (initial view layer stuff)
render_scn.node_tree.nodes.clear()
# for n in reversed(render_scn.node_tree.nodes):
@ -326,15 +326,15 @@ def get_frame_transform(f, node_tree=None):
childs = [n for n in node_tree.nodes if n.parent == f]
# real_locs = [f.location + n.location for n in childs]
xs = [n.location.x for n in childs] + [n.location.x + n.dimensions.x for n in childs]
ys = [n.location.y for n in childs] + [n.location.y - n.dimensions.y for n in childs]
xs.sort(key=lambda loc: loc) # x val : ascending
xs.sort(key=lambda loc: loc) # x val : ascending
ys.sort(key=lambda loc: loc) # ascending # , reversed=True) # y val : descending
loc = Vector((min(xs), max(ys)))
dim = Vector((max(xs) - min(xs) + 60, max(ys) - min(ys) + 60))
return loc, dim
@ -359,7 +359,7 @@ def bbox(f, frames):
ys += [loc.y, loc.y - n.dimensions.y] # - (n.dimensions.y/get_dpi_factor())
# margin ~= 30
# margin ~= 30
# return xs and ys
return [min(xs)-30, max(xs)+30], [min(ys)-30, max(ys)+30]
@ -376,15 +376,15 @@ def get_frames_bbox(node_tree):
continue
# also contains frames
frames[n.parent].append(n)
# Dic for bbox coord
for f, nodes in frames.items():
if f.parent:
continue
xs, ys = bbox(f, frames)
# xs, ys = bbox(nodes, frames)
## returning: list of corner coords
# coords = [
# Vector((xs[0], ys[1])),
@ -411,7 +411,7 @@ def merge_gplayer_viewlayers(ob, act=None, layers=None):
rd_scn = bpy.data.scenes.get('Render')
if not rd_scn:
return ({'ERROR'}, 'Viewlayers needs to be generated first!')
if not act.viewlayer_render:
return ({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned')
@ -433,14 +433,14 @@ def merge_gplayer_viewlayers(ob, act=None, layers=None):
sockout = grp.outputs.get(sockin.name)
if not sockout:
continue
for grplink in sockout.links:
if grplink.to_node.type != 'OUTPUT_FILE':
continue
fo_socket = grplink.to_socket
fo = grplink.to_node
fo.file_slots.remove(fo_socket)
# remove input and output from group
# grp.inputs.remove(sockin) # do not clear inside !!
# grp.outputs.remove(sockout) # do not clear inside !!
@ -453,19 +453,19 @@ def merge_gplayer_viewlayers(ob, act=None, layers=None):
if grp.outputs[i].name == sockout.name:
ngroup.outputs.remove(ngroup.outputs[i])
break
# remove render_layer node
rd_scn.node_tree.nodes.remove(n)
# assign view layer from active to selected
for l in layers:
l.viewlayer_render = act.viewlayer_render
## delete unused_vl
# used_vl_name = [n.layer for n in rd_scn.node_tree.nodes if n.type == 'R_LAYERS' and n.layer]
# used_vl_name = [n.layer for n in rd_scn.node_tree.nodes if n.type == 'R_LAYERS' and n.layer]
for vl in vls:
rd_scn.view_layers.remove(vl)
rd_scn.view_layers.remove(vl)
# if not vl.name in used_vl_name:
# rd_scn.view_layers.remove(vl)
@ -478,13 +478,13 @@ def group_adjacent_layer_prefix_rlayer(ob, excluded_prefix=[], first_name=True):
from itertools import groupby
re_prefix = re.compile(r'^([A-Z]{2})_')
if isinstance(excluded_prefix, str):
excluded_prefix = [p.strip() for p in excluded_prefix.split(',')]
## Create adjacent grp list: [('CO', [layer1, layer2]), ('LN', [layer3, layer4])]
## Create adjacent grp list: [('CO', [layer1, layer2]), ('LN', [layer3, layer4])]
adjacent_prefix_groups = [
(g[0], list(g[1])) for g in
(g[0], list(g[1])) for g in
groupby([l for l in ob.data.layers],
key=lambda l: re_prefix.search(l.info).group(1) if re_prefix.search(l.info) else '')
]
@ -559,7 +559,7 @@ def rearrange_frames(node_tree):
## order the dict by frame.y location
frame_d = {key: value for key, value in sorted(frame_d.items(), key=lambda pair: pair[1][0].y - pair[1][1].y, reverse=True)}
frames = [[f, v[0], v[1].y] for f, v in frame_d.items()] # [frame_node, real_loc, real dimensions]
top = frames[0][1].y # upper node location.y
# top = 0 #always start a 0
offset = 0
@ -571,7 +571,7 @@ def rearrange_frames(node_tree):
f[0].location.y = (f[1].y - f[0].location.y) - offset # avoid offset when recalculating from 0 top
# f[0].location.y = f[1].y - top - offset
offset += f[2] + 200 # gap
f[0].update()
def reorder_inputs(ng):
@ -657,7 +657,7 @@ def all_connected_forward(n, nlist=[]):
else:
return nlist
else:
nlist = all_connected_forward(lnk.to_node, nlist)
nlist = all_connected_forward(lnk.to_node, nlist)
if n in nlist:
return nlist
return nlist + [n]
@ -692,7 +692,7 @@ def reorder_nodegroup_content(ngroup):
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])
@ -718,7 +718,7 @@ def clear_nodegroup_content_if_disconnected(ngroup):
continue
if not connect_to_group_input(n) and not connect_to_group_output(n): # is disconnected from both side
ngroup.nodes.remove(n)
reorder_nodegroup_content(ngroup)
def clean_nodegroup_inputs(ng, skip_existing_pass=True):
@ -756,7 +756,7 @@ def bridge_reconnect_nodegroup(ng, socket_name=None):
ngroup.links.new(sockin, aa.inputs[0])
ngroup.links.new(aa.outputs[0], sockout)
print(f'{ng.name}: Bridged {sockin.name}')
def random_color(alpha=False):
import random
@ -789,7 +789,7 @@ def nodegroup_merge_inputs(ngroup):
ao = create_node('CompositorNodeAlphaOver', tree=ngroup, location=(x,y), hide=True)
ngroup.links.new(prev.outputs[0], ao.inputs[1])
ngroup.links.new(inp, ao.inputs[2])
x += offset_x
y += offset_y
prev = ao
@ -798,7 +798,7 @@ def nodegroup_merge_inputs(ngroup):
aa = create_aa_nodegroup(ngroup) # new_aa_node(ngroup)
aa.location = (ao.location.x + 200, ao.location.y)
ngroup.links.new(ao.outputs[0], aa.inputs[0]) # node_tree
# create one input and link
out = ngroup.outputs.new('NodeSocketColor', ngroup.inputs[0].name)
ngroup.links.new(aa.outputs[0], ng_out.inputs[0])
@ -852,7 +852,7 @@ def delete_numbering(fo): # padding=3
elems = fs.path.split('/')
for i, e in enumerate(elems):
elems[i] = re.sub(r'^\d{3}_', '', e)
new = '/'.join(elems)
fs.path = new
@ -868,7 +868,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
if fo.type != 'OUTPUT_FILE': return
ct = 10
if invert:
reverse_fileout_inputs(fo)
@ -931,7 +931,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
add_fileslot_number(fs, prev_num + offset)
else:
add_fileslot_number(fs, ct)
else:
if prev_num is not None:
# iterate rename
@ -968,7 +968,7 @@ def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_
import re
name = layer.info
pattern = PATTERN
sep = '_'
res = re.search(pattern, name.strip())
@ -993,7 +993,7 @@ def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_
# tag2 = prefix2.upper().strip() + sep
if desc:
name = desc
if suffix:
if suffix == 'suffixkillcode':
sfix = ''
@ -1013,7 +1013,7 @@ def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_
old = layer.info
print(f'{old} >> {new}')
layer.info = new
# Also change name string in modifier target !
for ob in [o for o in bpy.data.objects if o.type == 'GPENCIL' and o.data == layer.id_data]:
for m in ob.grease_pencil_modifiers:
@ -1037,7 +1037,7 @@ def build_layers_targets_from_dopesheet(context):
gpl = context.object.data.layers
act = gpl.active
dopeset = context.space_data.dopesheet
if dopeset.show_only_selected:
pool = [o for o in context.selected_objects if o.type == 'GPENCIL']
@ -1048,7 +1048,7 @@ def build_layers_targets_from_dopesheet(context):
layer_pool = [l for o in pool for l in o.data.layers]
layer_pool = list(set(layer_pool)) # remove dupli-layers from same data source with
# apply search filter
if dopeset.filter_text:
layer_pool = [l for l in layer_pool if (dopeset.filter_text.lower() in l.info.lower()) ^ dopeset.use_filter_invert]
@ -1067,7 +1067,7 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
self.layout.label(text=l)
else:
self.layout.label(text=l[0], icon=l[1])
if isinstance(_message, str):
_message = [_message]
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
@ -1092,7 +1092,7 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
elif len(l) == 3: # ops
self.layout.operator_context = "INVOKE_DEFAULT"
self.layout.operator(l[0], text=l[1], icon=l[2], emboss=False) # <- highligh the entry
if isinstance(_message, str):
_message = [_message]
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
@ -1122,15 +1122,15 @@ def is_render_included(o, scn):
def get_crop_pixel_coord(scn):
# width height probably not needed. might need
# width height probably not needed. might need
px_width = (scn.render.border_max_x - scn.render.border_min_x) * scn.render.resolution_x
px_height = (scn.render.border_max_y - scn.render.border_min_y) * scn.render.resolution_y
pos_x = (scn.render.border_min_x + ((scn.render.border_max_x - scn.render.border_min_x) / 2)) * scn.render.resolution_x
## coord y > image center coord from bottom-left (Blender)
# pos_y = (scn.render.border_min_y + ((scn.render.border_max_y - scn.render.border_min_y) / 2)) * scn.render.resolution_y,
## image center coord from top-left (AE)
pos_y = ((1 - scn.render.border_max_y) + ((scn.render.border_max_y - scn.render.border_min_y) / 2)) * scn.render.resolution_y
@ -1145,15 +1145,15 @@ def get_crop_pixel_coord(scn):
def export_crop_to_json():
'''Export crop to json coords for AE
'''
blend = Path(bpy.data.filepath)
json_path = blend.parent / 'render' / f'{blend.stem}.json' #f'{ob.name}.json'
## per scene : json_path = Path(bpy.data.filepath).parent / 'render' / f'{scn.name}.json'
# json_path = Path(bpy.data.filepath).parent / 'render' / f'{scn.name}.json' #f'{ob.name}.json'
coord_dic = {}
for scn in bpy.data.scenes:
# if scn.name in {'Scene', 'Render'}:
# if scn.name == 'Scene':
@ -1178,7 +1178,7 @@ def export_crop_to_json():
# save bbox
with json_path.open('w') as fd:
json.dump(coord_dic, fd, indent='\t')
print(f'Coords saved at: {json_path}')
return coord_dic
@ -1189,13 +1189,13 @@ def set_border_region_from_coord(coords, scn=None, margin=30, export_json=True):
'''
scn = scn or bpy.context.scene
coords2d_x = sorted([c[0] for c in coords])
coords2d_y = sorted([c[1] for c in coords])
margin_width = margin / scn.render.resolution_x
margin_height = margin / scn.render.resolution_y
# set crop
scn.render.border_min_x = coords2d_x[0] - margin_width
scn.render.border_max_x = coords2d_x[-1] + margin_width
@ -1223,17 +1223,17 @@ def set_border_region_from_coord(coords, scn=None, margin=30, export_json=True):
def get_gp_box_all_frame(ob, cam=None):
'''set crop to object bounding box considering whole animation. Cam should not be animated (render in bg_cam)
return 2d bbox in pixels
return 2d bbox in pixels
'''
from bpy_extras.object_utils import world_to_camera_view
coords_cam_list = []
scn = bpy.context.scene
cam = cam or scn.camera
start = time()
if ob.animation_data and ob.animation_data.action: # use frame set on all frames
print(f'{ob.name} has anim')
# frame_nums = sorted(list(set([f.frame_number for l in ob.data.layers if len(l.frames) for f in l.frames if len(f.strokes) and scn.frame_start <= f.frame_number <= scn.frame_end])))
# frame_nums = sorted(list(set([f.frame_number for l in ob.data.layers if len(l.frames) for f in l.frames if len(f.strokes) and scn.frame_start <= f.frame_number <= scn.frame_end])))
for num in range(scn.frame_start, scn.frame_end+1):
scn.frame_set(num)
for l in ob.data.layers:
@ -1257,7 +1257,7 @@ def get_gp_box_all_frame(ob, cam=None):
if len(s.points) == 1: # skip isolated points
continue
coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points]
print(time() - start) # Dbg-time
return coords_cam_list
@ -1276,7 +1276,7 @@ def has_keyframe(ob, attr):
def get_gp_box_all_frame_selection(oblist=None, scn=None, cam=None, timeout=40):
'''
get points of all selection
return 2d bbox in pixels
return 2d bbox in pixels
return None if timeout (too long to process, better to do it visually)
'''
@ -1289,7 +1289,7 @@ def get_gp_box_all_frame_selection(oblist=None, scn=None, cam=None, timeout=40):
cam = cam or scn.camera
start = time()
if any(has_anim(ob) for ob in oblist):
print(f'at least one is animated: {oblist}')
for num in range(scn.frame_start, scn.frame_end+1):
@ -1304,7 +1304,7 @@ def get_gp_box_all_frame_selection(oblist=None, scn=None, cam=None, timeout=40):
if len(s.points) == 1: # skip isolated points
continue
coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points]
if time() - t0 > timeout:
print(f'timeout (more than {timeout}s to calculate) evaluating frame position of objects {oblist}')
return
@ -1325,16 +1325,16 @@ def get_gp_box_all_frame_selection(oblist=None, scn=None, cam=None, timeout=40):
if len(s.points) == 1: # skip isolated points
continue
coords_cam_list += [world_to_camera_view(scn, cam, ob.matrix_world @ p.co) for p in s.points]
print(f'{len(coords_cam_list)} gp points listed {time() - start:.1f}s')
return coords_cam_list
def get_bbox_2d(ob, cam=None):
from bpy_extras.object_utils import world_to_camera_view
scn = bpy.context.scene
scn = bpy.context.scene
cam = cam or scn.camera
coords2d = [world_to_camera_view(scn, cam, p) for p in get_bbox_3d(ob)]
coords2d = [world_to_camera_view(scn, cam, p) for p in get_bbox_3d(ob)]
coords2d_x = sorted([c[0] for c in coords2d])
coords2d_y = sorted([c[1] for c in coords2d])
@ -1348,9 +1348,9 @@ def get_bbox_2d(ob, cam=None):
return [Vector(b) for b in bbox2d_coords]
def set_box_from_selected_objects(scn=None, cam=None, export_json=False):
scn = scn or bpy.context.scene
scn = scn or bpy.context.scene
cam = cam or scn.camera
selection = [o for o in scn.objects if o.select_get()] # selected_objects
coords = get_gp_box_all_frame_selection(oblist=selection, scn=scn, cam=cam)
if not coords:
@ -1445,10 +1445,10 @@ def get_collection_childs_recursive(col, cols=[], include_root=True):
cols.append(sub)
if len(sub.children):
cols = get_collection_childs_recursive(sub, cols)
if include_root and col not in cols: # add root col
cols.append(col)
return cols
def unlink_objects_from_scene(oblist, scn):
@ -1473,7 +1473,7 @@ def split_object_to_scene():
active = bpy.context.object
scene_name = active.name
objs = [o for o in bpy.context.selected_objects]
if bpy.data.scenes.get(scene_name):
print(f'Scene "{scene_name}" Already Exists')
raise Exception(f'Scene "{scene_name}" Already Exists')
@ -1497,7 +1497,7 @@ def split_object_to_scene():
continue
if sob not in objs:
col.objects.unlink(sob)
frame_names = [n.label for n in new.node_tree.nodes if n.type == 'FRAME' if new.objects.get(n.label)]
remove_scene_nodes_by_obj_names(new, frame_names, negative=True)
@ -1511,7 +1511,7 @@ def split_object_to_scene():
## remove asset from original scene
#src_frame_names = [n.label for n in src.node_tree.nodes if n.type == 'FRAME' and n.label in [o.name for o in objs]]
#remove_scene_nodes_by_obj_names(src, src_frame_names)
#remove_scene_nodes_by_obj_names(src, src_frame_names)
remove_scene_nodes_by_obj_names(src, frame_names, negative=False)
# unlink objects ?
@ -1522,7 +1522,7 @@ def split_object_to_scene():
coords = get_gp_box_all_frame_selection(oblist=gp_objs, scn=new, cam=new.camera)
if not coords:
return f'Scene "{scene_name}" created. But Border was not set (Timeout during GP analysis), should be done by hand if needed then use export crop to json'
set_border_region_from_coord(coords, margin=30, scn=new, export_json=True)
export_crop_to_json()
@ -1548,7 +1548,7 @@ def clear_frame_out_of_range(o, verbose=False):
print(f'del: obj {o.name} > layer {l.info} > frame {f.frame_number}')
l.frames.remove(f)
ct += 1
# before
elif f.frame_number < scn.frame_start - 1:
if first:
@ -1596,7 +1596,7 @@ def set_scene_output_from_active_fileout_item():
excluded = ['file_format', 'color_mode', 'color_depth',
'view_settings', 'views_format']
''' ## all attrs
# 'cineon_black', 'cineon_gamma', 'cineon_white',
# 'color_depth', 'color_mode', 'compression', 'display_settings',
@ -1641,7 +1641,7 @@ def set_layer_colors(skip_if_colored=False):
continue
print(l.info, '->', color)
l.channel_color = color
bpy.context.preferences.edit.use_anim_channel_group_colors = True

View File

@ -12,10 +12,10 @@ def add_rlayer(layer_name, scene=None, location=None, color=None, node_name=None
node_name = layer_name # 'RL_' +
if not scene:
scene=bpy.context.scene
scene=bpy.context.scene
nodes = scene.node_tree.nodes
comp = nodes.get(node_name)
if comp:
if comp.layer == node_name:
@ -33,7 +33,7 @@ def add_rlayer(layer_name, scene=None, location=None, color=None, node_name=None
comp.location = location
if color:
comp.color = color
if width:
comp.width = width
comp.show_preview = False
@ -51,14 +51,14 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
vl_name = rlayer.layer
if not vl_name or vl_name == 'View Layer':
print(f'Bad layer for node {rlayer.name}')
if not ' / ' in vl_name:
print(f'no slash (" / ") separator in vl_name {vl_name}, should be "obj.name / layer_name"')
return
obname, lname = vl_name.split(' / ')
lname = bpy.path.clean_name(lname)
if not frame:
if rlayer.parent:
frame=rlayer.parent
@ -96,7 +96,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
ng_in = fn.create_node('NodeGroupInput', tree=ngroup, location=(-600,0))
ng_out = fn.create_node('NodeGroupOutput', tree=ngroup, location=(600,0))
else:
ngroup = ng.node_tree
ng_in = ngroup.nodes.get('Group Input')
@ -113,20 +113,20 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
## get nodes from frame
# rl_nodes = [n for n in nodes if n.type == 'R_LAYERS' and n.layer != 'View Layer' and n.parent == frame]
# auto clean : if an input exists but is not linked and name not exists in rlayers of current frame
for s in reversed(ng.inputs):
if not s.is_linked: # and not any(x.layer == s.name for x in rl_nodes)
print(f'removing grp unlinked input {s.name}')
ng.inputs.remove(s)
## get nodes from linked NG inputs ??? maybe more clear...
# rl_nodes = [s.links[0].from_node for s in ng.inputs if s.links and s.links[0].from_node and s.links[0].from_node.type == 'R_LAYERS']
## reorder
fn.reorder_inputs(ng)
ng.update()
# CREATE NG outsocket (individual, without taking merge)
connected = False
@ -136,7 +136,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
if socket:
connected = True
groupout = ng.outputs.get(socket.name)
ng.update()
if not connected:
@ -151,7 +151,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
# ng_in.outputs[vl_name]
ngroup.links.new(ng_in.outputs[vl_name], aa.inputs[0]) # node_tree
ngroup.links.new(aa.outputs[0], ng_out.inputs[vl_name]) # node_tree
aa.mute = scene.use_aa # mute if native AA is used
@ -166,7 +166,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
ng.update()
# reorder output to match inputs
fn.reorder_outputs(ng)
ng.update()
# Clear : delete orphan nodes that are not connected from ng_in
@ -180,7 +180,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
if groupout.links and groupout.links[0].to_node.type == 'OUTPUT_FILE':
# if already connected to outfile just skip cause user might have customised the name
return
slot_name = f'{lname}/{lname}_'
out_name = f'OUT_{obname}' # or get output from frame
if not out:
@ -194,7 +194,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
out.base_path = f'//render/{bpy.path.clean_name(obname)}'
## out_input = out.inputs.get(slot_name) # ok for non-numbered outputs
out_input = None
out_input = fn.get_numbered_output(out, slot_name)
@ -235,9 +235,9 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
# ob.data = ob.data.copy() # create duplicate (this will also affect the one in original scene !!!)
scene.collection.objects.link(ob)
ob.hide_viewport = ob.hide_render = False
## set object active in default viewlayer
# if (avl := scene.view_layers.get('ViewLayer')):
# # This select the object in source scene
# avl.objects.active = ob
@ -269,7 +269,7 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
if rlayer_list: # rlayer exists
print(f'{len(rlayer_list)} nodes using {vl_name}')
# affect only the one within an object frame
framed_rl = [n for n in rlayer_list if n.parent and n.parent.label == ob.name]
if framed_rl:
@ -282,7 +282,7 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
cp.select = True # select so the user see that it existed
return vl, cp
# Returned if existed and OK
# Returned if existed and OK
if not ob.name in frame_dic.keys(): # and len(frame_dic[ob.name])
print(f'\n{ob.name} -> {l.info} (first generation)')
@ -303,7 +303,7 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
frame.label = ob.name
frame.label_size = 50
frame.location = (loc[0], loc[1] + 20)
cp = add_rlayer(vl_name, scene=scene, location=loc)
cp.parent = frame
# use same color as layer
@ -326,7 +326,7 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
if cp.layer != vl_name:
print(f'problem with {cp}: {cp.layer} != {vl_name}')
return
if fn.has_channel_color(l):
cp.use_custom_color = True
cp.color = l.channel_color
@ -339,16 +339,16 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
top_loc = fn.real_loc(rl_nodes[0])
else:
top_loc = fn.get_frame_transform(frame[1], node_tree) - 60
# cp.location = (top_loc[0], top_loc[1] + 100) # temp location to adjust x loc
# list of layer names in nodes order
rl_names = [n.layer.split(' / ')[1] for n in rl_nodes] # get True layer name from rl
# names with the right order WITH the new layer included
names = [lay.info for lay in ob.data.layers if lay.info in rl_names or lay == l]
rl_nodes.append(cp)
# filter by getting index(layer_name)
cp.parent = frame
rl_nodes.sort(key=lambda x : names.index(x.layer.split(' / ')[1])) # Sort True layer name from rl
@ -356,13 +356,13 @@ def get_set_viewlayer_from_gp(ob, l, scene=None):
offset = 0
# print(f'number of nodes in frame: {len(rl_nodes)}')
ref_node = rl_nodes[0]
# print('ref_node: ', ref_node.name, ref_node.location)
for n in rl_nodes:
# set x loc from first node in list (maybe use leftmost ?)
n.location = Vector((fn.real_loc(ref_node)[0], top_loc[1] - offset)) - n.parent.location
offset += 180
n.update()
# reorder render layers nodes within frame
@ -379,12 +379,12 @@ def export_gp_objects(oblist, exclude_list=[], scene=None):
oblist = [oblist]
if isinstance(exclude_list, str):
exclude_list = [p.strip() for p in exclude_list.split(',')]
# print('exclude_list: ', exclude_list)
for ob in oblist:
for l in ob.data.layers:
# if l.hide:
# if l.hide:
# continue
if l.hide or any(x + '_' in l.info for x in exclude_list): # exclude hided ?
print(f'Exclude export: {ob.name} : {l.info}')

View File

@ -7,7 +7,7 @@ class gp_render_prefs(bpy.types.AddonPreferences):
# name='Resample on the fly',
# description="Allow smoother stroke when using pinch\nnote that stroke using textured materials will not be resampled",
# default=True)
advanced : bpy.props.BoolProperty(
name='Advanced Options', # Reproject On Guessed Plane
description="Display advanced options",
@ -22,7 +22,7 @@ def get_addon_prefs():
function to read current addon preferences properties
access with : get_addon_prefs().super_special_option
'''
import os
import os
addon_name = os.path.splitext(__name__)[0]
preferences = bpy.context.preferences
addon_prefs = preferences.addons[addon_name].preferences

View File

@ -23,7 +23,7 @@ scn = bpy.context.scene
# - Import Fx3D (or render from Fx3D file... maybe easier consifering the number)
## tried to make color that fit in White theme
## tried to make color that fit in White theme
## (difficult for readability since this text color is not the same)
@ -54,7 +54,7 @@ def set_layer_colors():
continue
print(l.info, '->', color)
l.channel_color = color
C.preferences.edit.use_anim_channel_group_colors = True

48
ui.py
View File

@ -17,26 +17,26 @@ class GPEXP_PT_gp_node_ui(Panel):
advanced = prefs.advanced
layout = self.layout
layout.operator('gp.render_scene_switch', icon='SCENE_DATA', text='Switch Scene')
scn = context.scene
## Camera swapping
row = layout.row()
cam = scn.camera
if cam:
text = f'{cam.name} : {scn.render.resolution_x}x{scn.render.resolution_y}' # Cam:
text = f'{cam.name} : {scn.render.resolution_x}x{scn.render.resolution_y}' # Cam:
else:
text = f'None' # Cam:
text = f'None' # Cam:
# if cam and cam_name == 'draw_cam':
# cam_name = f'{cam.parent.name} > {cam_name}'
row.operator("gp.swap_render_cams", text=text, icon='OUTLINER_OB_CAMERA')
# Live checks
if scn.render.resolution_percentage != 100:
layout.label(text='Res Percentage not 100%', icon='ERROR')
layout.prop(scn.render, 'resolution_percentage')
exclude_count = len([vl for vl in scn.view_layers if not vl.use and vl.name not in {'View Layer', 'exclude'}])
if exclude_count:
# layout.label(text=f'{exclude_count} Excluded View Layers !')
@ -45,21 +45,21 @@ class GPEXP_PT_gp_node_ui(Panel):
if not scn.use_nodes or not scn.node_tree:
return
disabled_output = [n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.mute]
if disabled_output:
output_ct = len([n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE'])
layout.label(text=f'{len(disabled_output)}/{output_ct} Output Muted', icon='INFO')
layout.separator()
layout.label(text='View layers:')
ct = len([n for n in context.scene.node_tree.nodes if n.type == 'R_LAYERS' and n.select])
# col = layout.column(align=True)
# row=col.row(align=True)
row=layout.row(align=True)
row1 = row.row(align=True)
row1.operator('gp.activate_only_selected_layers', text=f'Activate Only {ct} Layer Nodes')
row1.enabled = ct > 0
@ -76,7 +76,7 @@ class GPEXP_PT_gp_node_ui(Panel):
col.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text=txt).disconnect = True
col.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text='Merge (keep connect)').disconnect = False
col.enabled = ct > 1
layout.separator()
col = layout.column()
subcol = col.column()
@ -87,18 +87,18 @@ class GPEXP_PT_gp_node_ui(Panel):
subcol.enabled = False
subcol.operator('gp.reconnect_render_layer', icon='ANIM', text=f'Reconnect {ct} Layer Node')
col.operator('gp.delete_render_layer', icon='TRACKING_CLEAR_FORWARDS', text=f'Delete {ct} Layer Node')
layout.separator()
layout.label(text='All Outputs:')
row=layout.row()
row.operator('gp.mute_toggle_output_nodes', icon='NODE_INSERT_ON', text='Mute').mute = True
row.operator('gp.mute_toggle_output_nodes', icon='NODE_INSERT_OFF', text='Unmute').mute = False
layout.separator()
col=layout.column()
col.label(text='Clean and updates:')
@ -119,7 +119,7 @@ class GPEXP_PT_gp_node_ui(Panel):
subcol.operator('gp.number_outputs', icon='LINENUMBERS_ON', text=txt).mode = 'SELECTED'
# subcol.operator('gp.normalize_outnames', icon='SYNTAX_OFF', text=f'Normalize Paths {ct} Selected Ouptut') # not ready
# col.operator('gp.number_outputs', icon='LINENUMBERS_ON', text='Renumber all outputs').mode = 'ALL'
if advanced:
subcol.operator('gp.set_output_node_format', icon='OUTPUT', text='Copy Active Output Format')
subcol.operator('gp.set_active_fileout_to_compout', icon='OUTPUT', text='Active Slot to Composite')
@ -204,7 +204,7 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
# merge layers from dopesheet
row.operator('gp.merge_viewlayers_to_active', text=txt, icon='SELECT_EXTEND')
row.enabled= ct > 1
col.operator('gpexp.auto_merge_adjacent_prefix', icon='SELECT_EXTEND')
## all and objects
@ -227,17 +227,17 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
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.check_masks', icon='MOD_MASK', text='Has Masks')
# row = layout.row()
layout.prop(bpy.context.preferences.edit, 'use_anim_channel_group_colors')
layout.separator()
row = layout.row()
row.operator('gp.export_as_pdf', icon='RENDER_STILL', text='Render All to PDF Sequences')
if bpy.app.version < (3,0,0):
if bpy.app.version < (3,0,0):
row.label(text='Not Blender 3.0.0+')
## Append GP Render workspace (usefull for user with disabled 'load_UI')
if not bpy.data.workspaces.get('GP Render'):
layout.operator('gp.set_gp_render_workspace')
@ -274,7 +274,7 @@ def viewlayer_layout(layout, scn):
# bl_label = "View Layers"
# def draw(self, context):
# layout = self.layout
# viewlayer_layout(layout, context)
# viewlayer_layout(layout, context)
class GPEXP_PT_viewlayers_ui(Panel):
bl_space_type = "NODE_EDITOR"
@ -361,7 +361,7 @@ GPEXP_PT_gp_node_ui,
GPEXP_PT_gp_dopesheet_ui,
)
def register():
def register():
for cls in classes:
bpy.utils.register_class(cls)
# bpy.types.DATA_PT_gpencil_layers.prepend(manager_ui)