batch reproject ops

1.0.9:

- feat: Reproject all frames operator
gpv2
Pullusb 2021-05-04 23:17:19 +02:00
parent 07a44190c9
commit 492095d333
4 changed files with 97 additions and 30 deletions

View File

@ -5,7 +5,7 @@ from math import pi
import numpy as np import numpy as np
from time import time from time import time
def batch_reproject(obj, project_type='VIEW', all_strokes=True, restore_frame=False): def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False):
'''Reproject - ops method '''Reproject - ops method
:all_stroke: affect hided, locked layers :all_stroke: affect hided, locked layers
''' '''
@ -30,7 +30,7 @@ def batch_reproject(obj, project_type='VIEW', all_strokes=True, restore_frame=Fa
bpy.context.scene.frame_set(f.frame_number) bpy.context.scene.frame_set(f.frame_number)
# switch to edit to reproject through ops # switch to edit to reproject through ops
bpy.ops.gpencil.select_all(action='SELECT') bpy.ops.gpencil.select_all(action='SELECT')
bpy.ops.gpencil.reproject(type=project_type) # default is VIEW bpy.ops.gpencil.reproject(type=proj_type) # default is VIEW
bpy.ops.gpencil.select_all(action='DESELECT') bpy.ops.gpencil.select_all(action='DESELECT')
# restore # restore
@ -46,7 +46,7 @@ def batch_reproject(obj, project_type='VIEW', all_strokes=True, restore_frame=Fa
bpy.context.scene.frame_current = oframe bpy.context.scene.frame_current = oframe
def align_global(reproject=True, ref=None): def align_global(reproject=True, ref=None, all_strokes=True):
if not ref: if not ref:
ref = bpy.context.scene.camera ref = bpy.context.scene.camera
@ -101,10 +101,10 @@ def align_global(reproject=True, ref=None):
o.matrix_basis = new_mat o.matrix_basis = new_mat
if reproject: if reproject:
batch_reproject(o, project_type='FRONT') batch_reproject(o, proj_type='FRONT', all_strokes=all_strokes)
def align_all_frames(reproject=True, ref=None): def align_all_frames(reproject=True, ref=None, all_strokes=True):
print('aligning all frames...') print('aligning all frames...')
@ -128,9 +128,9 @@ def align_all_frames(reproject=True, ref=None):
rot_keys = list(set(rot_keys)) rot_keys = list(set(rot_keys))
# TODO # TOTHINK # TODO # TOTHINK
# for now the rotation of the object is adjusted at every check.... # for now the rotation of the object is adjusted at every frame....
# might be better to check camera rotation of the current frame only stored as copy. # might be better to check camera rotation of the current frame only (stored as copy).
# else the object rotate following the camera... # else the object rotate following the cameraview vector (not constant)...
mat_90 = Matrix.Rotation(-pi/2, 4, 'X') mat_90 = Matrix.Rotation(-pi/2, 4, 'X')
@ -191,7 +191,7 @@ def align_all_frames(reproject=True, ref=None):
if reproject: if reproject:
batch_reproject(o, project_type='FRONT') batch_reproject(o, proj_type='FRONT', all_strokes=all_strokes)
return return
@ -199,21 +199,60 @@ def align_all_frames(reproject=True, ref=None):
class GPTB_OT_realign(bpy.types.Operator): class GPTB_OT_realign(bpy.types.Operator):
bl_idname = "gp.realign" bl_idname = "gp.realign"
bl_label = "Realign GP" bl_label = "Realign GP"
bl_description = "Realign the grease pencil on camera" bl_description = "Realign the grease pencil front axis with active camera"
bl_options = {"REGISTER"} bl_options = {"REGISTER"}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.object and context.object.type == 'GPENCIL' return context.object and context.object.type == 'GPENCIL'
reproject : bpy.props.BoolProperty(name='Reproject', default=True) reproject : bpy.props.BoolProperty(
name='Reproject', default=True,
description='Reproject stroke on the new alignment')
all_strokes : bpy.props.BoolProperty(
name='All Strokes', default=False,
description='Hided and locked layer will also be reprojected')
## add option to bake strokes if rotation anim is not constant ? might generate too many Keyframes
def invoke(self, context, event):
if context.object.data.use_multiedit:
self.report({'ERROR'}, 'Does not work in Multi-edit')
return {"CANCELLED"}
self.alert = ''
o = context.object
if o.animation_data and o.animation_data.action:
act = o.animation_data.action
for chan in ('rotation_euler', 'rotation_quaternion'):
if act.fcurves.find(chan):
self.alert = 'Animated Rotation (CONSTANT interpolation)'
interpos = [p for fcu in act.fcurves if fcu.data_path == chan for p in fcu.keyframe_points if p.interpolation != 'CONSTANT']
if interpos:
self.alert = f'Animated Rotation ! ({len(interpos)} key not constant)'
break
return context.window_manager.invoke_props_dialog(self, width=450)
def draw(self, context):
layout = self.layout
if self.alert:
layout.label(text=self.alert, icon='INFO')
layout.label(text='(rotations key will be overwritten to face camera)')
layout.prop(self, "reproject")
layout.label(text='Realign the GP front axis with active camera')
if self.reproject:
if not context.region_data.view_perspective == 'CAMERA':
layout.label(text='Not in camera ! (reprojection is made from view)', icon='ERROR')
layout.prop(self, "all_strokes")
def execute(self, context): def execute(self, context):
t0 = time() t0 = time()
oframe = context.scene.frame_current oframe = context.scene.frame_current
o = bpy.context.object
o = bpy.context.object
if o.animation_data and o.animation_data.action: if o.animation_data and o.animation_data.action:
if o.animation_data.action.fcurves.find('rotation_euler') or o.animation_data.action.fcurves.find('rotation_quaternion'): if o.animation_data.action.fcurves.find('rotation_euler') or o.animation_data.action.fcurves.find('rotation_quaternion'):
align_all_frames(reproject=self.reproject) align_all_frames(reproject=self.reproject)
@ -225,6 +264,7 @@ class GPTB_OT_realign(bpy.types.Operator):
align_global(reproject=self.reproject) align_global(reproject=self.reproject)
context.scene.frame_current = oframe context.scene.frame_current = oframe
print(f'\nGlobal Realign ({time()-t0:.2f}s)') print(f'\nGlobal Realign ({time()-t0:.2f}s)')
return {"FINISHED"} return {"FINISHED"}
@ -232,39 +272,57 @@ class GPTB_OT_realign(bpy.types.Operator):
class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator): class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
bl_idname = "gp.batch_reproject_all_frames" bl_idname = "gp.batch_reproject_all_frames"
bl_label = "Reproject All Frame" bl_label = "Reproject All Frames"
bl_description = "Reproject all frame of active object." bl_description = "Reproject all frames of active object."
bl_options = {"REGISTER"} bl_options = {"REGISTER"}
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
return context.object and context.object.type == 'GPENCIL' return context.object and context.object.type == 'GPENCIL'
reproject : bpy.props.BoolProperty(name='Reproject', default=True) # reproject : bpy.props.BoolProperty(name='Reproject', default=True)
# TODO witch side ? all_strokes : bpy.props.BoolProperty(
name='All Strokes', default=False,
description='Hided and locked layer will also be reprojected')
type: bpy.props.EnumProperty(name='Type',
items=(('FRONT', "Front", ""),
('SIDE', "Side", ""),
('TOP', "Top", ""),
('VIEW', "View", ""),
('SURFACE', "Surface", ""),
('CURSOR', "Cursor", ""),
),
default='FRONT')
# def invoke(self, context, event): def invoke(self, context, event):
# return context.window_manager.invoke_props_dialog(self) if context.object.data.use_multiedit:
# return self.execute(context) self.report({'ERROR'}, 'Does not work in Multi-edit')
return {"CANCELLED"}
return context.window_manager.invoke_props_dialog(self)
def draw(self, context): def draw(self, context):
self.layout.prop(self, "toggle", text="Toggle to redraw") layout = self.layout
for i in range(20): if not context.region_data.view_perspective == 'CAMERA':
self.layout.label(str(i)) layout.label(text='Not in camera ! (reprojection is made from view)', icon='ERROR')
layout.prop(self, "all_strokes")
layout.prop(self, "type")
def execute(self, context): def execute(self, context):
t0 = time() t0 = time()
batch_reproject(bpy.context.object, all_strokes=True, restore_frame=True) # all_strokes=True, restore_frame=False)
self.report({'INFO'}, f'\nReprojected in ({time()-t0:.2f}s)' ) batch_reproject(context.object, proj_type=self.type, all_strokes=self.all_strokes, restore_frame=True)
self.report({'INFO'}, f'Reprojected in ({time()-t0:.2f}s)' )
return {"FINISHED"} return {"FINISHED"}
classes = ( classes = (
GPTB_OT_realign, # GPTB_OT_realign,
GPTB_OT_batch_reproject_all_frames GPTB_OT_batch_reproject_all_frames,
) )
def register(): def register():

View File

@ -112,6 +112,10 @@ Panel in sidebar : 3D view > sidebar 'N' > Gpencil
## Changelog: ## Changelog:
1.0.9:
- feat: Reproject all frames operator
1.0.8: 1.0.8:
- feat: Keyframe jump filter added in UI to change general behavior. Keymap own jump filter can override this new global settings if specified - feat: Keyframe jump filter added in UI to change general behavior. Keymap own jump filter can override this new global settings if specified

View File

@ -160,15 +160,17 @@ class GPTB_PT_sidebar_panel(bpy.types.Panel):
box = layout.box() box = layout.box()
box.label(text='Missing base material setup', icon='INFO') box.label(text='Missing base material setup', icon='INFO')
box.operator('gp.load_default_palette') box.operator('gp.load_default_palette')
else: else:
layout.label(text='No GP object selected') layout.label(text='No GP object selected')
layout.prop(context.scene.gptoolprops, 'edit_lines_opacity') layout.prop(context.scene.gptoolprops, 'edit_lines_opacity')
# layout.operator('gp.realign')
layout.operator('gp.batch_reproject_all_frames') # text=Batch Reproject
## Create empty frame on layer (ops stored under GP_colorize... might be best to separate in another panel ) ## Create empty frame on layer (ops stored under GP_colorize... might be best to separate in another panel )
layout.operator('gp.create_empty_frames', icon = 'DECORATE_KEYFRAME') layout.operator('gp.create_empty_frames', icon='DECORATE_KEYFRAME')
## File checker ## File checker
row = layout.row(align=True) row = layout.row(align=True)

View File

@ -15,7 +15,7 @@ bl_info = {
"name": "GP toolbox", "name": "GP toolbox",
"description": "Set of tools for Grease Pencil in animation production", "description": "Set of tools for Grease Pencil in animation production",
"author": "Samuel Bernou", "author": "Samuel Bernou",
"version": (1, 0, 8), "version": (1, 0, 9),
"blender": (2, 91, 0), "blender": (2, 91, 0),
"location": "sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "location": "sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
"warning": "", "warning": "",
@ -44,6 +44,7 @@ from . import OP_palettes
from . import OP_file_checker from . import OP_file_checker
from . import OP_render from . import OP_render
from . import OP_copy_paste from . import OP_copy_paste
from . import OP_realign
from . import keymaps from . import keymaps
from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers from .OP_pseudo_tint import GPT_OT_auto_tint_gp_layers
@ -439,6 +440,7 @@ def register():
OP_cursor_snap_canvas.register() OP_cursor_snap_canvas.register()
OP_render.register() OP_render.register()
OP_copy_paste.register() OP_copy_paste.register()
OP_realign.register()
UI_tools.register() UI_tools.register()
keymaps.register() keymaps.register()
bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings) bpy.types.Scene.gptoolprops = bpy.props.PointerProperty(type = GP_PG_ToolsSettings)
@ -460,6 +462,7 @@ def unregister():
for cls in reversed(classes): for cls in reversed(classes):
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
UI_tools.unregister() UI_tools.unregister()
OP_realign.unregister()
OP_copy_paste.unregister() OP_copy_paste.unregister()
OP_render.unregister() OP_render.unregister()
OP_cursor_snap_canvas.unregister() OP_cursor_snap_canvas.unregister()