From 12cce98e41a9c64daaa92e2601a8a23fc3473252 Mon Sep 17 00:00:00 2001 From: pullusb Date: Wed, 18 Jan 2023 14:28:27 +0100 Subject: [PATCH] trailing whitespaces cleanup --- CHANGELOG.md | 6 +- OP_add_layer.py | 8 +- OP_auto_build.py | 42 +++---- OP_check_scene.py | 14 +-- OP_clean.py | 32 ++--- OP_clear.py | 10 +- OP_connect_toggle.py | 40 +++---- OP_crop_to_object.py | 2 +- OP_export_to_ae.py | 25 ++-- OP_manage_outputs.py | 30 ++--- OP_merge_layers.py | 30 ++--- OP_post_render.py | 22 ++-- OP_render_pdf.py | 48 ++++---- OP_render_scenes.py | 22 ++-- OP_scene_switch.py | 10 +- OP_setup_layers.py | 80 ++++++------- __init__.py | 12 +- app_templates/GP_Render/__init__.py | 2 +- fn.py | 174 ++++++++++++++-------------- gen_vlayer.py | 58 +++++----- prefs.py | 4 +- setup_elements.py | 4 +- ui.py | 48 ++++---- 23 files changed, 361 insertions(+), 362 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03968c8..339b5a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/OP_add_layer.py b/OP_add_layer.py index 716487b..ff7f164 100644 --- a/OP_add_layer.py +++ b/OP_add_layer.py @@ -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) diff --git a/OP_auto_build.py b/OP_auto_build.py index b218ddf..d152290 100644 --- a/OP_auto_build.py +++ b/OP_auto_build.py @@ -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) diff --git a/OP_check_scene.py b/OP_check_scene.py index c28e29f..486eb3b 100644 --- a/OP_check_scene.py +++ b/OP_check_scene.py @@ -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) diff --git a/OP_clean.py b/OP_clean.py index 05185ee..22b3420 100644 --- a/OP_clean.py +++ b/OP_clean.py @@ -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) diff --git a/OP_clear.py b/OP_clear.py index 251fe2c..11d5e59 100644 --- a/OP_clear.py +++ b/OP_clear.py @@ -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) diff --git a/OP_connect_toggle.py b/OP_connect_toggle.py index b2dcfde..455f7a7 100644 --- a/OP_connect_toggle.py +++ b/OP_connect_toggle.py @@ -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) diff --git a/OP_crop_to_object.py b/OP_crop_to_object.py index 0fc4bf0..97b6ea3 100644 --- a/OP_crop_to_object.py +++ b/OP_crop_to_object.py @@ -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) diff --git a/OP_export_to_ae.py b/OP_export_to_ae.py index 34d41e3..881feea 100644 --- a/OP_export_to_ae.py +++ b/OP_export_to_ae.py @@ -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) - \ No newline at end of file diff --git a/OP_manage_outputs.py b/OP_manage_outputs.py index 7b292f9..1dc3606 100644 --- a/OP_manage_outputs.py +++ b/OP_manage_outputs.py @@ -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) diff --git a/OP_merge_layers.py b/OP_merge_layers.py index ae3bb9a..d59193c 100644 --- a/OP_merge_layers.py +++ b/OP_merge_layers.py @@ -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) diff --git a/OP_post_render.py b/OP_post_render.py index 1575e17..5099b59 100644 --- a/OP_post_render.py +++ b/OP_post_render.py @@ -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) diff --git a/OP_render_pdf.py b/OP_render_pdf.py index 49addfc..01704e2 100644 --- a/OP_render_pdf.py +++ b/OP_render_pdf.py @@ -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) diff --git a/OP_render_scenes.py b/OP_render_scenes.py index e9cce1b..830f0c9 100644 --- a/OP_render_scenes.py +++ b/OP_render_scenes.py @@ -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 \ No newline at end of file diff --git a/OP_scene_switch.py b/OP_scene_switch.py index 1f096e2..cc32725 100644 --- a/OP_scene_switch.py +++ b/OP_scene_switch.py @@ -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) diff --git a/OP_setup_layers.py b/OP_setup_layers.py index e19bde6..a1d4ca1 100644 --- a/OP_setup_layers.py +++ b/OP_setup_layers.py @@ -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) diff --git a/__init__.py b/__init__.py index 9858036..a1908a1 100644 --- a/__init__.py +++ b/__init__.py @@ -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(): diff --git a/app_templates/GP_Render/__init__.py b/app_templates/GP_Render/__init__.py index 6bf597c..ec5c966 100644 --- a/app_templates/GP_Render/__init__.py +++ b/app_templates/GP_Render/__init__.py @@ -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) diff --git a/fn.py b/fn.py index b119f92..d051750 100644 --- a/fn.py +++ b/fn.py @@ -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 diff --git a/gen_vlayer.py b/gen_vlayer.py index 098adef..c3b0f20 100644 --- a/gen_vlayer.py +++ b/gen_vlayer.py @@ -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}') diff --git a/prefs.py b/prefs.py index d15cf5d..8edc7ca 100644 --- a/prefs.py +++ b/prefs.py @@ -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 diff --git a/setup_elements.py b/setup_elements.py index c080e24..c2657b1 100644 --- a/setup_elements.py +++ b/setup_elements.py @@ -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 diff --git a/ui.py b/ui.py index 5a1cb5c..c7945f2 100644 --- a/ui.py +++ b/ui.py @@ -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)