276 lines
8.4 KiB
Python
276 lines
8.4 KiB
Python
|
import bpy
|
||
|
import mathutils
|
||
|
from mathutils import Matrix, Vector
|
||
|
from math import pi
|
||
|
import numpy as np
|
||
|
from time import time
|
||
|
|
||
|
def batch_reproject(obj, project_type='VIEW', all_strokes=True, restore_frame=False):
|
||
|
'''Reproject - ops method
|
||
|
:all_stroke: affect hided, locked layers
|
||
|
'''
|
||
|
|
||
|
if restore_frame:
|
||
|
oframe = bpy.context.scene.frame_current
|
||
|
omode = bpy.context.mode
|
||
|
|
||
|
# FIXME : if all_stroke is False, might be better to still store>set>restore "lock_frame"
|
||
|
if all_strokes:
|
||
|
layers_state = [[l, l.hide, l.lock, l.lock_frame] for l in obj.data.layers]
|
||
|
for l in obj.data.layers:
|
||
|
l.hide = False
|
||
|
l.lock = False
|
||
|
l.lock_frame = False
|
||
|
|
||
|
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
||
|
# TODO : need to toggle visible and unlock all layers before attacking, and store restore.
|
||
|
|
||
|
for l in obj.data.layers:
|
||
|
for f in l.frames:
|
||
|
bpy.context.scene.frame_set(f.frame_number)
|
||
|
# switch to edit to reproject through ops
|
||
|
bpy.ops.gpencil.select_all(action='SELECT')
|
||
|
bpy.ops.gpencil.reproject(type=project_type) # default is VIEW
|
||
|
bpy.ops.gpencil.select_all(action='DESELECT')
|
||
|
|
||
|
# restore
|
||
|
if all_strokes:
|
||
|
for layer, hide, lock, lock_frame in layers_state:
|
||
|
layer.hide = hide
|
||
|
layer.lock = lock
|
||
|
layer.lock_frame = lock_frame
|
||
|
|
||
|
bpy.ops.object.mode_set(mode=omode)
|
||
|
|
||
|
if restore_frame:
|
||
|
bpy.context.scene.frame_current = oframe
|
||
|
|
||
|
|
||
|
def align_global(reproject=True, ref=None):
|
||
|
|
||
|
if not ref:
|
||
|
ref = bpy.context.scene.camera
|
||
|
|
||
|
o = bpy.context.object
|
||
|
# if o.matrix_basis != o.matrix_world and not o.parent:
|
||
|
|
||
|
ref = bpy.context.scene.camera
|
||
|
ref_mat = ref.matrix_world
|
||
|
ref_loc, ref_rot, ref_scale = ref_mat.decompose()
|
||
|
|
||
|
if o.parent:
|
||
|
mat = o.matrix_world
|
||
|
else:
|
||
|
mat = o.matrix_basis
|
||
|
|
||
|
o_loc, o_rot, o_scale = mat.decompose()
|
||
|
|
||
|
mat_90 = Matrix.Rotation(-pi/2, 4, 'X')
|
||
|
|
||
|
loc_mat = Matrix.Translation(o_loc)
|
||
|
rot_mat = ref_rot.to_matrix().to_4x4() @ mat_90
|
||
|
scale_mat = get_scale_matrix(o_scale)
|
||
|
|
||
|
new_mat = loc_mat @ rot_mat @ scale_mat
|
||
|
|
||
|
# world_coords = []
|
||
|
for l in o.data.layers:
|
||
|
for f in l.frames:
|
||
|
for s in f.strokes:
|
||
|
## foreach
|
||
|
coords = [p.co @ mat.inverted() @ new_mat for p in s.points]
|
||
|
# print('coords: ', coords)
|
||
|
# print([co for v in coords for co in v])
|
||
|
s.points.foreach_set('co', [co for v in coords for co in v])
|
||
|
# s.points.update() # seem to works # but adding/deleting a point is "safer"
|
||
|
## force update
|
||
|
s.points.add(1)
|
||
|
s.points.pop()
|
||
|
|
||
|
# for p in s.points:
|
||
|
## GOOD :
|
||
|
# world_co = mat @ p.co
|
||
|
# p.co = new_mat.inverted() @ world_co
|
||
|
|
||
|
## GOOD :
|
||
|
# p.co = p.co @ mat.inverted() @ new_mat
|
||
|
|
||
|
if o.parent:
|
||
|
o.matrix_world = new_mat
|
||
|
else:
|
||
|
o.matrix_basis = new_mat
|
||
|
|
||
|
if reproject:
|
||
|
batch_reproject(o, project_type='FRONT')
|
||
|
|
||
|
|
||
|
def align_all_frames(reproject=True, ref=None):
|
||
|
|
||
|
print('aligning all frames...')
|
||
|
|
||
|
o = bpy.context.object
|
||
|
if not ref:
|
||
|
ref = bpy.context.scene.camera
|
||
|
|
||
|
# get all rot
|
||
|
chanel = 'rotation_quaternion' if o.rotation_mode == 'QUATERNION' else 'rotation_euler'
|
||
|
|
||
|
## double list keys
|
||
|
rot_keys = [k.co.x for fcu in o.animation_data.action.fcurves for k in fcu.keyframe_points if fcu.data_path == chanel]
|
||
|
|
||
|
## normal iter
|
||
|
# for fcu in o.animation_data.action.fcurves:
|
||
|
# if fcu.data_path != chanel :
|
||
|
# continue
|
||
|
# for k in fcu.keyframe_points():
|
||
|
# rot_keys.append(k.co.x)
|
||
|
|
||
|
rot_keys = list(set(rot_keys))
|
||
|
|
||
|
# TODO # TOTHINK
|
||
|
# for now the rotation of the object is adjusted at every check....
|
||
|
# might be better to check camera rotation of the current frame only stored as copy.
|
||
|
# else the object rotate following the camera...
|
||
|
|
||
|
mat_90 = Matrix.Rotation(-pi/2, 4, 'X')
|
||
|
|
||
|
for l in o.data.layers:
|
||
|
for f in l.frames:
|
||
|
# set the frame to dedicated
|
||
|
bpy.context.scene.frame_set(f.frame_number)
|
||
|
|
||
|
ref_mat = ref.matrix_world
|
||
|
ref_loc, ref_rot, ref_scale = ref_mat.decompose()
|
||
|
|
||
|
if o.parent:
|
||
|
mat = o.matrix_world
|
||
|
else:
|
||
|
mat = o.matrix_basis
|
||
|
|
||
|
o_loc, o_rot, o_scale = mat.decompose()
|
||
|
loc_mat = Matrix.Translation(o_loc)
|
||
|
rot_mat = ref_rot.to_matrix().to_4x4() @ mat_90
|
||
|
scale_mat = get_scale_matrix(o_scale)
|
||
|
new_mat = loc_mat @ rot_mat @ scale_mat
|
||
|
|
||
|
for s in f.strokes:
|
||
|
## foreach
|
||
|
coords = [p.co @ mat.inverted() @ new_mat for p in s.points]
|
||
|
# print('coords: ', coords)
|
||
|
# print([co for v in coords for co in v])
|
||
|
s.points.foreach_set('co', [co for v in coords for co in v])
|
||
|
# s.points.update() # seem to works
|
||
|
## force update
|
||
|
s.points.add(1)
|
||
|
s.points.pop()
|
||
|
|
||
|
for fnum in rot_keys:
|
||
|
bpy.context.scene.frame_set(fnum)
|
||
|
#/update calculation block
|
||
|
ref_mat = ref.matrix_world
|
||
|
ref_loc, ref_rot, ref_scale = ref_mat.decompose()
|
||
|
|
||
|
if o.parent:
|
||
|
mat = o.matrix_world
|
||
|
else:
|
||
|
mat = o.matrix_basis
|
||
|
|
||
|
o_loc, o_rot, o_scale = mat.decompose()
|
||
|
loc_mat = Matrix.Translation(o_loc)
|
||
|
rot_mat = ref_rot.to_matrix().to_4x4() @ mat_90
|
||
|
scale_mat = get_scale_matrix(o_scale)
|
||
|
new_mat = loc_mat @ rot_mat @ scale_mat
|
||
|
# update calculation block/
|
||
|
|
||
|
if o.parent:
|
||
|
o.matrix_world = new_mat
|
||
|
else:
|
||
|
o.matrix_basis = new_mat
|
||
|
|
||
|
o.keyframe_insert(chanel, index=-1, frame=bpy.context.scene.frame_current, options={'INSERTKEY_AVAILABLE'})
|
||
|
|
||
|
|
||
|
if reproject:
|
||
|
batch_reproject(o, project_type='FRONT')
|
||
|
|
||
|
return
|
||
|
|
||
|
|
||
|
class GPTB_OT_realign(bpy.types.Operator):
|
||
|
bl_idname = "gp.realign"
|
||
|
bl_label = "Realign GP"
|
||
|
bl_description = "Realign the grease pencil on camera"
|
||
|
bl_options = {"REGISTER"}
|
||
|
|
||
|
@classmethod
|
||
|
def poll(cls, context):
|
||
|
return context.object and context.object.type == 'GPENCIL'
|
||
|
|
||
|
reproject : bpy.props.BoolProperty(name='Reproject', default=True)
|
||
|
|
||
|
def execute(self, context):
|
||
|
t0 = time()
|
||
|
oframe = context.scene.frame_current
|
||
|
|
||
|
o = bpy.context.object
|
||
|
|
||
|
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'):
|
||
|
align_all_frames(reproject=self.reproject)
|
||
|
|
||
|
bpy.context.scene.frame_current = oframe
|
||
|
print(f'\nAnim realign ({time()-t0:.2f}s)')
|
||
|
return
|
||
|
|
||
|
align_global(reproject=self.reproject)
|
||
|
|
||
|
context.scene.frame_current = oframe
|
||
|
print(f'\nGlobal Realign ({time()-t0:.2f}s)')
|
||
|
|
||
|
return {"FINISHED"}
|
||
|
|
||
|
|
||
|
class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
|
||
|
bl_idname = "gp.batch_reproject_all_frames"
|
||
|
bl_label = "Reproject All Frame"
|
||
|
bl_description = "Reproject all frame of active object."
|
||
|
bl_options = {"REGISTER"}
|
||
|
|
||
|
@classmethod
|
||
|
def poll(cls, context):
|
||
|
return context.object and context.object.type == 'GPENCIL'
|
||
|
|
||
|
reproject : bpy.props.BoolProperty(name='Reproject', default=True)
|
||
|
|
||
|
# TODO witch side ?
|
||
|
|
||
|
# def invoke(self, context, event):
|
||
|
# return context.window_manager.invoke_props_dialog(self)
|
||
|
# return self.execute(context)
|
||
|
|
||
|
def draw(self, context):
|
||
|
self.layout.prop(self, "toggle", text="Toggle to redraw")
|
||
|
for i in range(20):
|
||
|
self.layout.label(str(i))
|
||
|
|
||
|
|
||
|
def execute(self, context):
|
||
|
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)' )
|
||
|
|
||
|
return {"FINISHED"}
|
||
|
|
||
|
|
||
|
classes = (
|
||
|
GPTB_OT_realign,
|
||
|
GPTB_OT_batch_reproject_all_frames
|
||
|
)
|
||
|
|
||
|
def register():
|
||
|
for cl in classes:
|
||
|
bpy.utils.register_class(cl)
|
||
|
|
||
|
def unregister():
|
||
|
for cl in reversed(classes):
|
||
|
bpy.utils.unregister_class(cl)
|