2021-09-24 18:36:58 +02:00
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 ) ]
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
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
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
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
2023-01-18 14:28:27 +01:00
def execute ( self , context ) :
2021-09-24 18:36:58 +02:00
# rd_scn = bpy.data.scenes.get('Render')
# if not rd_scn:
# self.report({'ERROR'}, 'Viewlayers needs to be generated first!')
# return {'CANCELLED'}
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
### store
## dict all visible objects as key with value : sub dict {layer : hide_bool}
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
# 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 = [ ]
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
## 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
2023-01-18 14:28:27 +01:00
mess = f ' --- { ob . name } : '
2021-09-24 18:36:58 +02:00
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
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
## 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 :
2023-01-18 14:28:27 +01:00
mess = f ' /! \ { vl_name } viewlayer not exists : skipped { [ l . info for l in layer_list ] } '
2021-09-24 18:36:58 +02:00
print ( mess )
messages . append ( mess )
continue
if vl_name in { ' exclude ' , ' View Layer ' } :
continue
if not vl . use :
2023-01-18 14:28:27 +01:00
mess = f ' { vl_name } viewlayer disabled '
2021-09-24 18:36:58 +02:00
print ( mess )
messages . append ( mess )
continue
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
# 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 :
2023-01-18 14:28:27 +01:00
mess = f ' Skip { vl_name } : Not found nodegroup { ng_name } '
2021-09-24 18:36:58 +02:00
print ( mess )
messages . append ( mess )
continue
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
ng_socket = ng . outputs . get ( vl_name )
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
if not ng_socket :
2023-01-18 14:28:27 +01:00
mess = f ' Skip { vl_name } : Not found in nodegroup { ng_name } sockets '
2021-09-24 18:36:58 +02:00
print ( mess )
messages . append ( mess )
continue
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
if not len ( ng_socket . links ) :
2023-01-18 14:28:27 +01:00
mess = f ' socket is disconnected in { ng_name } nodegroup '
2021-09-24 18:36:58 +02:00
print ( mess )
messages . append ( mess )
continue
fo_node = ng_socket . links [ 0 ] . to_node
fo_socket = ng_socket . links [ 0 ] . to_socket
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
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
2023-01-18 14:28:27 +01:00
2021-09-28 11:01:49 +02:00
if fo_node . mute :
mess = f ' Skip { vl_name } : output is muted { fo_node . name } '
print ( mess )
messages . append ( mess )
continue
2021-09-24 18:36:58 +02:00
# 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 ( " / " ) )
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
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 :
2023-01-18 14:28:27 +01:00
l . hide = l not in layer_list or store [ ob ] [ l ]
2021-09-24 18:36:58 +02:00
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
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
for o in selection :
o . select_set ( True )
if act :
context . view_layer . objects . active = act
2023-01-18 14:28:27 +01:00
2021-09-24 18:36:58 +02:00
# for oviz in obj_vis:
# oviz[0].hide_viewport = oviz[1]
2021-09-28 11:01:49 +02:00
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) ' )
2021-09-24 18:36:58 +02:00
return { " FINISHED " }
classes = (
GPEXP_OT_export_as_pdf ,
)
2023-01-18 14:28:27 +01:00
def register ( ) :
2021-09-24 18:36:58 +02:00
for cls in classes :
bpy . utils . register_class ( cls )
def unregister ( ) :
for cls in reversed ( classes ) :
bpy . utils . unregister_class ( cls )