gp_toolbox/OP_flat_reproject.py

160 lines
6.0 KiB
Python

import bpy
import mathutils
from mathutils import Matrix, Vector
from mathutils.geometry import intersect_line_plane
from math import pi
import numpy as np
from time import time
from .utils import (location_to_region, region_to_location)
## DISABLED (in init, also in menu append, see register below)
"""
## Do not work on multiple object
def batch_flat_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False):
'''Reproject
:all_stroke: affect hidden, locked layers
'''
if restore_frame:
oframe = bpy.context.scene.frame_current
omode = bpy.context.mode
# frame_list = [ f.frame_number for l in obj.data.layers for f in l.frames if len(f.drawing.strokes)]
# frame_list = list(set(frame_list))
# frame_list.sort()
# for fnum in frame_list:
# bpy.context.scene.frame_current = fnum
t0 = time()
scn = bpy.context.scene
laynum = len(obj.data.layers)
for i, l in enumerate(obj.data.layers):
## \x1b[2K\r ?
fnum = len(l.frames)
zf = len(str(fnum))
for j, f in enumerate(reversed(l.frames)): # whynot...
print(f'{obj.name} : {i+1}/{laynum} : {l.name} : {str(j+1).zfill(zf)}/{fnum}{" "*30}', end='\r')
scn.frame_set(f.frame_number) # more chance to update the matrix
bpy.context.view_layer.update() # update the matrix ?
bpy.context.scene.camera.location = bpy.context.scene.camera.location
scn.frame_current = f.frame_number
for s in f.drawing.strokes:
for p in s.points:
p.position = obj.matrix_world.inverted() @ region_to_location(location_to_region(obj.matrix_world @ p.position), scn.cursor.location)
if restore_frame:
bpy.context.scene.frame_current = oframe
print(' '*50,end='\x1b[1K\r') # clear the line
print(f'{obj.name} ok ({time()-t0:.2f})')
"""
"""
def batch_flat_reproject(obj):
'''Reproject all strokes on 3D cursor for all existing frame of passed GP object'''
scn = bpy.context.scene
cam = scn.camera
for l in obj.data.layers:
for f in l.frames:
scn.frame_set(f.frame_number)
cam_mat = cam.matrix_local.copy()
origin = cam.matrix_world.to_translation()
mat_inv = obj.matrix_world.inverted()
plane_no = Vector((0,0,1))
plane_no.rotate(cam_mat)
plane_co = scn.cursor.location
for s in f.drawing.strokes:
points_co = [obj.matrix_world @ p.position for p in s.points]
points_co = [mat_inv @ intersect_line_plane(origin, p, plane_co, plane_no) for p in points_co]
points_co = [co for vector in points_co for co in vector]
s.points.foreach_set('co', points_co)
s.points.add(1) # update
s.points.pop() # update
#for p in s.points:
# loc_2d = location_to_region(obj.matrix_world @ p.position)
# p.position = obj.matrix_world.inverted() @ region_to_location(loc_2d, scn.cursor.location)
"""
def batch_flat_reproject(obj):
'''Reproject strokes of passed GP object on 3D cursor full scene range'''
scn = bpy.context.scene
cam = scn.camera
for i in range(scn.frame_start, scn.frame_end + 1):
scn.frame_set(i)
cam_mat = cam.matrix_local.copy()
origin = cam.matrix_world.to_translation()
mat_inv = obj.matrix_world.inverted()
plane_no = Vector((0,0,1))
plane_no.rotate(cam_mat)
plane_co = scn.cursor.location
for l in obj.data.layers:
f = l.current_frame()
if not f: # No active frame
continue
if f.frame_number != scn.frame_current:
f = l.frames.copy(f) # duplicate content of the previous frame
for s in f.drawing.strokes:
points_co = [obj.matrix_world @ p.position for p in s.points]
points_co = [mat_inv @ intersect_line_plane(origin, p, plane_co, plane_no) for p in points_co]
points_co = [co for vector in points_co for co in vector]
s.points.foreach_set('co', points_co)
s.points.add(1) # update
s.points.pop() # update
class GPTB_OT_batch_flat_reproject(bpy.types.Operator):
bl_idname = "gp.batch_flat_reproject"
bl_label = "Flat Reproject Selected On cursor"
bl_description = "Reproject all frames of all selected gp object on cursor"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GREASEPENCIL'
def execute(self, context):
for o in context.selected_objects:
if o.type != 'GREASEPENCIL' or not o.select_get():
continue
batch_flat_reproject(o)
return {"FINISHED"}
### -- MENU ENTRY --
def flat_reproject_clean_menu(self, context):
if context.mode == 'EDIT_GREASE_PENCIL':
self.layout.operator_context = 'INVOKE_REGION_WIN' # needed for popup (also works with 'INVOKE_DEFAULT')
self.layout.operator('gp.batch_flat_reproject', icon='KEYTYPE_JITTER_VEC')
def flat_reproject_context_menu(self, context):
if context.mode == 'EDIT_GREASE_PENCIL' and context.scene.tool_settings.gpencil_selectmode_edit == 'STROKE':
self.layout.operator_context = 'INVOKE_REGION_WIN' # needed for popup
self.layout.operator('gp.batch_flat_reproject', icon='KEYTYPE_JITTER_VEC')
classes = (
GPTB_OT_batch_flat_reproject,
)
def register():
for cl in classes:
bpy.utils.register_class(cl)
# bpy.types.VIEW3D_MT_grease_pencil_edit_context_menu.append(flat_reproject_context_menu)
# bpy.types.GPENCIL_MT_cleanup.append(flat_reproject_clean_menu)
def unregister():
# bpy.types.GPENCIL_MT_cleanup.remove(flat_reproject_clean_menu)
# bpy.types.VIEW3D_MT_grease_pencil_edit_context_menu.remove(flat_reproject_context_menu)
for cl in reversed(classes):
bpy.utils.unregister_class(cl)