gp_render/OP_render_pdf.py

272 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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
# 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]
self.report({'INFO'}, f'Done ({time()-t0:.1f}s)')
fn.show_message_box(_message=messages, _title='PDF render report')
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)