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)
This commit is contained in:
		
							parent
							
								
									280a575631
								
							
						
					
					
						commit
						053a9d7f7b
					
				| @ -1,5 +1,10 @@ | ||||
| # 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 | ||||
| 
 | ||||
| - 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 | ||||
| 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 | ||||
|     view_mat = rv3d.view_matrix.inverted() | ||||
|     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 | ||||
| import numpy as np | ||||
| from time import time | ||||
| 
 | ||||
| from . import utils | ||||
| from mathutils.geometry import intersect_line_plane | ||||
| 
 | ||||
| def get_scale_matrix(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: | ||||
|         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 | ||||
| 
 | ||||
|     # 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') | ||||
| 
 | ||||
| 
 | ||||
|     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: | ||||
|         bpy.context.scene.frame_current = fnum | ||||
|         bpy.ops.gpencil.select_all(action='SELECT') | ||||
|         bpy.ops.gpencil.reproject(type=proj_type) # 'INVOKE_DEFAULT' | ||||
|         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 | ||||
|     if all_strokes: | ||||
|         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 | ||||
| 
 | ||||
|     bpy.ops.object.mode_set(mode=omode) | ||||
|     ''' | ||||
|      | ||||
|     if restore_frame: | ||||
|         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') | ||||
|   | ||||
|     type: bpy.props.EnumProperty(name='Type', | ||||
|         items=(('FRONT', "Front", ""), | ||||
|         items=(('CURRENT', "Current", ""), | ||||
|                ('FRONT', "Front", ""), | ||||
|                ('SIDE', "Side", ""), | ||||
|                ('TOP', "Top", ""), | ||||
|                ('VIEW', "View", ""), | ||||
|                ('SURFACE', "Surface", ""), | ||||
|                ('CURSOR', "Cursor", ""), | ||||
|                ), | ||||
|         default='FRONT') | ||||
|         default='CURRENT') | ||||
| 
 | ||||
|     def invoke(self, context, event): | ||||
|         if context.object.data.use_multiedit: | ||||
| @ -373,15 +389,19 @@ class GPTB_OT_batch_reproject_all_frames(bpy.types.Operator): | ||||
|     def draw(self, context): | ||||
|         layout = self.layout | ||||
|         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, "type") | ||||
|          | ||||
| 
 | ||||
|     def execute(self, context): | ||||
|         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)' ) | ||||
| 
 | ||||
|  | ||||
| @ -4,7 +4,7 @@ bl_info = { | ||||
| "name": "GP toolbox", | ||||
| "description": "Tool set for Grease Pencil in animation production", | ||||
| "author": "Samuel Bernou, Christophe Seux", | ||||
| "version": (2, 3, 4), | ||||
| "version": (2, 4, 0), | ||||
| "blender": (3, 0, 0), | ||||
| "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", | ||||
| "warning": "", | ||||
|  | ||||
							
								
								
									
										50
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								utils.py
									
									
									
									
									
								
							| @ -263,10 +263,12 @@ def remapping(value, leftMin, leftMax, rightMin, rightMax): | ||||
| ### 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 | ||||
|     of the curent drawing accordign to geometry''' | ||||
| 
 | ||||
|     context = bpy.context | ||||
|     settings = context.scene.tool_settings | ||||
|     orient = settings.gpencil_sculpt.lock_axis #'VIEW', 'AXIS_Y', 'AXIS_X', 'AXIS_Z', 'CURSOR' | ||||
|     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) | ||||
|      | ||||
|     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): | ||||
|     '''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 | ||||
| 
 | ||||
|     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)) | ||||
| 
 | ||||
|     angle = math.degrees(view_direction.angle(plane_no)) | ||||
| @ -498,6 +540,10 @@ from mathutils import Vector | ||||
| ### 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): | ||||
|     return sqrt((v[0] * v[0]) + (v[1] * v[1]) + (v[2] * v[2])) | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user