gp_render/OP_render_pdf.py

278 lines
9.8 KiB
Python
Raw Normal View History

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
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
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):
# 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
### store
## dict all visible objects as key with value : sub dict {layer : hide_bool}
2023-01-18 14:28:27 +01: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
## 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}:'
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
## 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]}'
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'
print(mess)
messages.append(mess)
continue
2023-01-18 14:28:27 +01: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}'
print(mess)
messages.append(mess)
continue
2023-01-18 14:28:27 +01:00
ng_socket = ng.outputs.get(vl_name)
2023-01-18 14:28:27 +01: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'
print(mess)
messages.append(mess)
continue
2023-01-18 14:28:27 +01:00
if not len(ng_socket.links):
2023-01-18 14:28:27 +01:00
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
2023-01-18 14:28:27 +01: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
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("/"))
2023-01-18 14:28:27 +01: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]
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
for o in selection:
o.select_set(True)
if act:
context.view_layer.objects.active = act
2023-01-18 14:28:27 +01:00
# 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,
)
2023-01-18 14:28:27 +01:00
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)