Faster and better batch reproject
2.4.0 - changed: Batch reproject consider camera movement and is almost 8x faster - added: Batch reproject have "Current" mode (using current tool setting)gpv2
parent
280a575631
commit
053a9d7f7b
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
2.4.0
|
||||||
|
|
||||||
|
- changed: Batch reproject consider camera movement and is almost 8x faster
|
||||||
|
- added: Batch reproject have "Current" mode (using current tool setting)
|
||||||
|
|
||||||
2.3.4
|
2.3.4
|
||||||
|
|
||||||
- fixed: bug when exporting json palettes containing empty material slots
|
- fixed: bug when exporting json palettes containing empty material slots
|
||||||
|
|
|
@ -8,7 +8,7 @@ from .utils import get_gp_draw_plane, location_to_region, region_to_location
|
||||||
|
|
||||||
### passing by 2D projection
|
### passing by 2D projection
|
||||||
def get_3d_coord_on_drawing_plane_from_2d(context, co):
|
def get_3d_coord_on_drawing_plane_from_2d(context, co):
|
||||||
plane_co, plane_no = get_gp_draw_plane(context)
|
plane_co, plane_no = get_gp_draw_plane()
|
||||||
rv3d = context.region_data
|
rv3d = context.region_data
|
||||||
view_mat = rv3d.view_matrix.inverted()
|
view_mat = rv3d.view_matrix.inverted()
|
||||||
if not plane_no:
|
if not plane_no:
|
||||||
|
|
112
OP_realign.py
112
OP_realign.py
|
@ -4,7 +4,8 @@ from mathutils import Matrix, Vector
|
||||||
from math import pi
|
from math import pi
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from time import time
|
from time import time
|
||||||
|
from . import utils
|
||||||
|
from mathutils.geometry import intersect_line_plane
|
||||||
|
|
||||||
def get_scale_matrix(scale):
|
def get_scale_matrix(scale):
|
||||||
# recreate a neutral mat scale
|
# recreate a neutral mat scale
|
||||||
|
@ -21,64 +22,77 @@ def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False
|
||||||
|
|
||||||
if restore_frame:
|
if restore_frame:
|
||||||
oframe = bpy.context.scene.frame_current
|
oframe = bpy.context.scene.frame_current
|
||||||
|
|
||||||
|
plan_co, plane_no = utils.get_gp_draw_plane(obj, orient=proj_type)
|
||||||
|
|
||||||
|
frame_list = [f.frame_number for l in obj.data.layers for f in l.frames if len(f.strokes)]
|
||||||
|
frame_list = list(set(frame_list))
|
||||||
|
frame_list.sort()
|
||||||
|
|
||||||
|
scn = bpy.context.scene
|
||||||
|
for i in frame_list:
|
||||||
|
scn.frame_set(i) # refresh scene
|
||||||
|
# scn.frame_current = i # no refresh
|
||||||
|
|
||||||
|
origin = scn.camera.matrix_world.to_translation()
|
||||||
|
matrix_inv = obj.matrix_world.inverted()
|
||||||
|
# origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
|
||||||
|
# matrix = np.array(obj.matrix_world, dtype='float64')
|
||||||
|
# matrix_inv = np.array(obj.matrix_world.inverted(), dtype='float64')
|
||||||
|
#mat = src.matrix_world
|
||||||
|
for l in obj.data.layers:
|
||||||
|
if not all_strokes:
|
||||||
|
if not l.select:
|
||||||
|
continue
|
||||||
|
if l.hide or l.lock:
|
||||||
|
continue
|
||||||
|
f = next((f for f in l.frames if f.frame_number == i), None)
|
||||||
|
if f is None:
|
||||||
|
continue
|
||||||
|
for s in f.strokes:
|
||||||
|
## Batch matrix apply (Here is slower than list comprehension).
|
||||||
|
# nb_points = len(s.points)
|
||||||
|
# coords = np.empty(nb_points * 3, dtype='float64')
|
||||||
|
# s.points.foreach_get('co', coords)
|
||||||
|
# world_co_3d = utils.matrix_transform(coords.reshape((nb_points, 3)), matrix)
|
||||||
|
|
||||||
|
## list comprehension method
|
||||||
|
world_co_3d = [obj.matrix_world @ p.co for p in s.points]
|
||||||
|
|
||||||
|
new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d]
|
||||||
|
|
||||||
|
## Basic method (Slower than foreach_set)
|
||||||
|
# for i, p in enumerate(s.points):
|
||||||
|
# p.co = obj.matrix_world.inverted() @ new_world_co_3d[i]
|
||||||
|
|
||||||
|
## Ravel new coordinate on the fly
|
||||||
|
new_local_coords = [axis for p in new_world_co_3d for axis in matrix_inv @ p]
|
||||||
|
|
||||||
|
## Set points in obj local space (apply matrix slower)
|
||||||
|
# new_local_coords = utils.matrix_transform(new_world_co_3d, matrix_inv).ravel()
|
||||||
|
s.points.foreach_set('co', new_local_coords)
|
||||||
|
|
||||||
|
bpy.context.area.tag_redraw()
|
||||||
|
|
||||||
|
'''
|
||||||
|
## Old method using Operators:
|
||||||
omode = bpy.context.mode
|
omode = bpy.context.mode
|
||||||
|
|
||||||
# FIXME : if all_stroke is False, might be better to still store>set>restore "lock_frame"
|
|
||||||
if all_strokes:
|
if all_strokes:
|
||||||
layers_state = [[l, l.hide, l.lock, l.lock_frame] for l in obj.data.layers]
|
layers_state = [[l, l.hide, l.lock, l.lock_frame] for l in obj.data.layers]
|
||||||
for l in obj.data.layers:
|
for l in obj.data.layers:
|
||||||
l.hide = False
|
l.hide = False
|
||||||
l.lock = False
|
l.lock = False
|
||||||
l.lock_frame = False
|
l.lock_frame = False
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
||||||
|
|
||||||
|
|
||||||
frame_list = [f.frame_number for l in obj.data.layers for f in l.frames if len(f.strokes)]
|
|
||||||
frame_list = list(set(frame_list))
|
|
||||||
frame_list.sort()
|
|
||||||
for fnum in frame_list:
|
for fnum in frame_list:
|
||||||
bpy.context.scene.frame_current = fnum
|
bpy.context.scene.frame_current = fnum
|
||||||
bpy.ops.gpencil.select_all(action='SELECT')
|
bpy.ops.gpencil.select_all(action='SELECT')
|
||||||
bpy.ops.gpencil.reproject(type=proj_type) # 'INVOKE_DEFAULT'
|
bpy.ops.gpencil.reproject(type=proj_type) # 'INVOKE_DEFAULT'
|
||||||
bpy.ops.gpencil.select_all(action='DESELECT')
|
bpy.ops.gpencil.select_all(action='DESELECT')
|
||||||
|
|
||||||
|
|
||||||
#print('fnum: ', fnum)
|
|
||||||
|
|
||||||
# bpy.context.scene.frame_set(fnum)
|
|
||||||
# bpy.context.scene.frame_current = fnum
|
|
||||||
# bpy.ops.gpencil.select_all(action='SELECT')
|
|
||||||
# bpy.ops.gpencil.reproject(type=proj_type) # default is VIEW
|
|
||||||
# # bpy.ops.gpencil.select_all(action='DESELECT')
|
|
||||||
# bpy.ops.object.mode_set(mode='OBJECT')
|
|
||||||
# bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
|
||||||
# bpy.context.view_layer.update()
|
|
||||||
|
|
||||||
"""
|
|
||||||
for l in obj.data.layers:
|
|
||||||
for f in l.frames:
|
|
||||||
if not len(f.strokes):
|
|
||||||
continue
|
|
||||||
bpy.context.scene.frame_set(f.frame_number)
|
|
||||||
# bpy.context.scene.frame_current = f.frame_number
|
|
||||||
|
|
||||||
## / attempt update trigger for failing reproject surface mode
|
|
||||||
# bpy.ops.object.mode_set(mode='OBJECT')
|
|
||||||
# bpy.ops.object.mode_set(mode='EDIT_GPENCIL')
|
|
||||||
# bpy.context.view_layer.update()
|
|
||||||
# for a in bpy.context.screen.areas:
|
|
||||||
# a.tag_redraw()
|
|
||||||
# dg = bpy.context.evaluated_depsgraph_get()
|
|
||||||
# obj.evaluated_get(dg)
|
|
||||||
## /
|
|
||||||
|
|
||||||
# switch to edit to reproject through ops
|
|
||||||
bpy.ops.gpencil.select_all(action='SELECT')
|
|
||||||
bpy.ops.gpencil.reproject(type=proj_type) # default is VIEW
|
|
||||||
bpy.ops.gpencil.select_all(action='DESELECT')
|
|
||||||
"""
|
|
||||||
|
|
||||||
# restore
|
# restore
|
||||||
if all_strokes:
|
if all_strokes:
|
||||||
for layer, hide, lock, lock_frame in layers_state:
|
for layer, hide, lock, lock_frame in layers_state:
|
||||||
|
@ -87,6 +101,7 @@ def batch_reproject(obj, proj_type='VIEW', all_strokes=True, restore_frame=False
|
||||||
layer.lock_frame = lock_frame
|
layer.lock_frame = lock_frame
|
||||||
|
|
||||||
bpy.ops.object.mode_set(mode=omode)
|
bpy.ops.object.mode_set(mode=omode)
|
||||||
|
'''
|
||||||
|
|
||||||
if restore_frame:
|
if restore_frame:
|
||||||
bpy.context.scene.frame_current = oframe
|
bpy.context.scene.frame_current = oframe
|
||||||
|
@ -355,14 +370,15 @@ class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
|
||||||
description='Hided and locked layer will also be reprojected')
|
description='Hided and locked layer will also be reprojected')
|
||||||
|
|
||||||
type: bpy.props.EnumProperty(name='Type',
|
type: bpy.props.EnumProperty(name='Type',
|
||||||
items=(('FRONT', "Front", ""),
|
items=(('CURRENT', "Current", ""),
|
||||||
|
('FRONT', "Front", ""),
|
||||||
('SIDE', "Side", ""),
|
('SIDE', "Side", ""),
|
||||||
('TOP', "Top", ""),
|
('TOP', "Top", ""),
|
||||||
('VIEW', "View", ""),
|
('VIEW', "View", ""),
|
||||||
('SURFACE', "Surface", ""),
|
('SURFACE', "Surface", ""),
|
||||||
('CURSOR', "Cursor", ""),
|
('CURSOR', "Cursor", ""),
|
||||||
),
|
),
|
||||||
default='FRONT')
|
default='CURRENT')
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
if context.object.data.use_multiedit:
|
if context.object.data.use_multiedit:
|
||||||
|
@ -373,15 +389,19 @@ class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator):
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
if not context.region_data.view_perspective == 'CAMERA':
|
if not context.region_data.view_perspective == 'CAMERA':
|
||||||
layout.label(text='Not in camera ! (reprojection is made from view)', icon='ERROR')
|
# layout.label(text='Not in camera ! (reprojection is made from view)', icon='ERROR')
|
||||||
|
layout.label(text='Reprojection is made from camera, not current view', icon='ERROR')
|
||||||
layout.prop(self, "all_strokes")
|
layout.prop(self, "all_strokes")
|
||||||
layout.prop(self, "type")
|
layout.prop(self, "type")
|
||||||
|
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
t0 = time()
|
t0 = time()
|
||||||
|
orient = self.type
|
||||||
|
if self.type == 'CURRENT':
|
||||||
|
orient = None
|
||||||
|
|
||||||
batch_reproject(context.object, proj_type=self.type, all_strokes=self.all_strokes, restore_frame=True)
|
batch_reproject(context.object, proj_type=orient, all_strokes=self.all_strokes, restore_frame=True)
|
||||||
|
|
||||||
self.report({'INFO'}, f'Reprojected in ({time()-t0:.2f}s)' )
|
self.report({'INFO'}, f'Reprojected in ({time()-t0:.2f}s)' )
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ bl_info = {
|
||||||
"name": "GP toolbox",
|
"name": "GP toolbox",
|
||||||
"description": "Tool set for Grease Pencil in animation production",
|
"description": "Tool set for Grease Pencil in animation production",
|
||||||
"author": "Samuel Bernou, Christophe Seux",
|
"author": "Samuel Bernou, Christophe Seux",
|
||||||
"version": (2, 3, 4),
|
"version": (2, 4, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (3, 0, 0),
|
||||||
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
|
|
50
utils.py
50
utils.py
|
@ -263,10 +263,12 @@ def remapping(value, leftMin, leftMax, rightMin, rightMax):
|
||||||
### GP funcs
|
### GP funcs
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|
||||||
def get_gp_draw_plane(context, obj=None):
|
""" V1
|
||||||
|
def get_gp_draw_plane(obj=None):
|
||||||
''' return tuple with plane coordinate and normal
|
''' return tuple with plane coordinate and normal
|
||||||
of the curent drawing accordign to geometry'''
|
of the curent drawing accordign to geometry'''
|
||||||
|
|
||||||
|
context = bpy.context
|
||||||
settings = context.scene.tool_settings
|
settings = context.scene.tool_settings
|
||||||
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
|
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
|
||||||
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
|
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
|
||||||
|
@ -307,6 +309,46 @@ def get_gp_draw_plane(context, obj=None):
|
||||||
plane_no.rotate(context.scene.cursor.matrix)
|
plane_no.rotate(context.scene.cursor.matrix)
|
||||||
|
|
||||||
return plane_co, plane_no
|
return plane_co, plane_no
|
||||||
|
"""
|
||||||
|
|
||||||
|
## V2
|
||||||
|
def get_gp_draw_plane(obj=None, orient=None):
|
||||||
|
''' return tuple with plane coordinate and normal
|
||||||
|
of the curent drawing according to geometry'''
|
||||||
|
|
||||||
|
if obj is None:
|
||||||
|
obj = bpy.context.object
|
||||||
|
|
||||||
|
settings = bpy.context.scene.tool_settings
|
||||||
|
if orient is None:
|
||||||
|
orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR'
|
||||||
|
loc = settings.gpencil_stroke_placement_view3d #'ORIGIN', 'CURSOR', 'SURFACE', 'STROKE'
|
||||||
|
|
||||||
|
mat = obj.matrix_world
|
||||||
|
plane_no = Vector((0.0, 0.0, 1.0))
|
||||||
|
plane_co = mat.to_translation()
|
||||||
|
|
||||||
|
# -> orientation
|
||||||
|
if orient == 'VIEW':
|
||||||
|
mat = bpy.context.scene.camera.matrix_world
|
||||||
|
# -> placement
|
||||||
|
if loc == "CURSOR":
|
||||||
|
plane_co = bpy.context.scene.cursor.location
|
||||||
|
mat = bpy.context.scene.cursor.matrix
|
||||||
|
|
||||||
|
elif orient == 'AXIS_Y':#front (X-Z)
|
||||||
|
plane_no = Vector((0,1,0))
|
||||||
|
|
||||||
|
elif orient == 'AXIS_X':#side (Y-Z)
|
||||||
|
plane_no = Vector((1,0,0))
|
||||||
|
|
||||||
|
elif orient == 'AXIS_Z':#top (X-Y)
|
||||||
|
plane_no = Vector((0,0,1))
|
||||||
|
|
||||||
|
plane_no.rotate(mat)
|
||||||
|
|
||||||
|
return plane_co, plane_no
|
||||||
|
|
||||||
|
|
||||||
def check_angle_from_view(obj=None, plane_no=None, context=None):
|
def check_angle_from_view(obj=None, plane_no=None, context=None):
|
||||||
'''Return angle to obj according to chosen drawing axis'''
|
'''Return angle to obj according to chosen drawing axis'''
|
||||||
|
@ -317,7 +359,7 @@ def check_angle_from_view(obj=None, plane_no=None, context=None):
|
||||||
context = bpy.context
|
context = bpy.context
|
||||||
|
|
||||||
if not plane_no:
|
if not plane_no:
|
||||||
_plane_co, plane_no = get_gp_draw_plane(context, obj=obj)
|
_plane_co, plane_no = get_gp_draw_plane(obj=obj)
|
||||||
view_direction = view3d_utils.region_2d_to_vector_3d(context.region, context.region_data, (context.region.width/2.0, context.region.height/2.0))
|
view_direction = view3d_utils.region_2d_to_vector_3d(context.region, context.region_data, (context.region.width/2.0, context.region.height/2.0))
|
||||||
|
|
||||||
angle = math.degrees(view_direction.angle(plane_no))
|
angle = math.degrees(view_direction.angle(plane_no))
|
||||||
|
@ -498,6 +540,10 @@ from mathutils import Vector
|
||||||
### Vector utils 3d
|
### Vector utils 3d
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|
||||||
|
def matrix_transform(coords, matrix):
|
||||||
|
coords_4d = np.column_stack((coords, np.ones(len(coords), dtype='float64')))
|
||||||
|
return np.einsum('ij,aj->ai', matrix, coords_4d)[:, :-1]
|
||||||
|
|
||||||
def single_vector_length(v):
|
def single_vector_length(v):
|
||||||
return sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2]))
|
return sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2]))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue