import bpy from . import fn from pathlib import Path from itertools import groupby from pprint import pprint as pp from time import time, strftime def export_all_selected_frame_as_svg(): '''Export All frames (only where there is a frame) of selected layer as svg''' ### Export operator parameters description # use_fill (boolean, (optional)) – Fill, Export strokes with fill enabled # selected_object_type (enum in ['ACTIVE', 'SELECTED', 'VISIBLE'], (optional)) – # Object, Which objects to include in the export # ACTIVE Active, Include only the active object. # SELECTED Selected, Include selected objects. # VISIBLE Visible, Include all visible objects. # # stroke_sample (float in [0, 100], (optional)) – Sampling, Precision of stroke sampling. Low values mean a more precise result, and zero disables sampling # use_normalized_thickness (boolean, (optional)) – Normalize, Export strokes with constant thickness # use_clip_camera (boolean, (optional)) – Clip Camera, Clip drawings to camera size when export in camera view ## Write an ouput name (folder and image will use this name) ## if left empty, will use name of active object with 'svg_' prefix name = '' only_frames = 1 # put 0 to export whole frame range ## ---- o = bpy.context.object assert o.type == 'GPENCIL', 'Active object should be GP' if only_frames: frames = [] for ob in bpy.context.selected_objects: 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: frames = [f for f in range(bpy.context.scene.frame_start, bpy.context.scene.frame_end + 1)] print(len(frames), 'frames to export') pass_name = name if name else f'svg_{o.name}' blend = Path(bpy.data.filepath) for fnum in frames: out = f'{pass_name}_{fnum:04d}.svg' print(out) folder = blend.parent / 'render' / pass_name folder.mkdir(parents=True, exist_ok=True) fp = folder / out 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, use_fill=True, selected_object_type='SELECTED', # ACTIVE, VISIBLE stroke_sample=0.0, use_normalized_thickness=False, use_clip_camera=True) # False by defaut print('Done') def pdf_render(fp): scn = bpy.context.scene fp.parent.mkdir(parents=True, exist_ok=True) # mode=0o777 for fnum in range(scn.frame_start, scn.frame_end + 1): # print('fnum: ', fnum) scn.frame_current = fnum # bpy.ops.wm.gpencil_export_svg(filepath=str(fp), # check_existing=True, # use_fill=True, selected_object_type='SELECTED', # ACTIVE, VISIBLE # stroke_sample=0.0, # use_normalized_thickness=False, # use_clip_camera=True) # False by defaut # bpy.ops.wm.gpencil_export_pdf(filepath=f'{bpy.path.abspath(str(fp)).rstrip("/")}{fnum:04d}.pdf', bpy.ops.wm.gpencil_export_pdf(filepath=f'{fp}{fnum:04d}.pdf', check_existing=False, # True by default use_fill=True, selected_object_type='ACTIVE', # VISIBLE, SELECTED stroke_sample=0, use_normalized_thickness=False, frame_mode='ACTIVE') class GPEXP_OT_export_as_pdf(bpy.types.Operator): bl_idname = "gp.export_as_pdf" bl_label = "export_as_pdf" bl_description = "Export current layers as pdf" bl_options = {"REGISTER"} @classmethod def poll(cls, context): return True 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() store = {o: {l: l.hide for l in o.data.layers} for o in context.scene.objects if o.type == 'GPENCIL' and not (o.hide_get() or o.hide_viewport)} # pp(store) act = context.object if context.object else None 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') if not mod: mod = ob.grease_pencil_modifiers.new('resample', 'GP_SIMPLIFY') mod.mode = 'ADAPTIVE' mod.factor = 0.001 # for ob in context.scene.objects: for ob in store.keys(): if ob.type != 'GPENCIL': continue mess = f'--- {ob.name}:' print(mess) messages.append(mess) ## swap hide other GP object (or just swap select) # for so in store.keys(): # so.hide_viewport = True # ob.hide_viewport = False context.view_layer.objects.active = ob # render active only mode # 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]}' print(mess) messages.append(mess) continue if vl_name in {'exclude', 'View Layer'}: continue if not vl.use: mess = f'{vl_name} viewlayer disabled' print(mess) messages.append(mess) continue # Case of initially masked layer ! hide_ct = 0 total = len(layer_list) for l in layer_list: if store[ob][l]: # get original hide bool hide_ct += 1 if hide_ct == total: mess = f'/!\ Skip {vl_name}: {hide_ct}/{total} are hided' print(mess) messages.append(mess) continue elif hide_ct > 0: mess = f'Warning: {vl_name}: {hide_ct}/{total} are hided' print(mess) messages.append(mess) # check connections in compositor 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}' 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' print(mess) messages.append(mess) continue if not len(ng_socket.links): 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) messages.append(mess) continue # fo_socket.name isn't right 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("/")) 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] for l in gpl: if not l.hide: print(f'-> {l.info}') #Dbg pdf_render(fp) print() ### restore for ob, layer_dic in store.items(): # 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] print(f'Done ({time()-t0:.1f}s)') # self.report({'INFO'}, f'Done ({time()-t0:.1f}s)') fn.show_message_box(_message=messages, _title=f'PDF render report ({time()-t0:.1f}s)') return {"FINISHED"} classes=( GPEXP_OT_export_as_pdf, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)