velocity based interpolate, multi layers
This commit is contained in:
		
							parent
							
								
									c16e8a7731
								
							
						
					
					
						commit
						d11d1e0435
					
				
							
								
								
									
										4
									
								
								constants.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								constants.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RESOURCES_DIR = Path(__file__).parent /'resources'
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
from gp_interpolate.interpolate_strokes import (properties,
 | 
					from gp_interpolate.interpolate_strokes import (properties,
 | 
				
			||||||
                                                operators,
 | 
					                                                operators,
 | 
				
			||||||
                                                operators_triangle,
 | 
					                                                operators_triangle,
 | 
				
			||||||
 | 
					                                                operators_velocity,
 | 
				
			||||||
                                                debug,
 | 
					                                                debug,
 | 
				
			||||||
                                                bind_points,
 | 
					                                                bind_points,
 | 
				
			||||||
                                                )
 | 
					                                                )
 | 
				
			||||||
@ -9,6 +10,7 @@ modules = (
 | 
				
			|||||||
    properties,
 | 
					    properties,
 | 
				
			||||||
    operators,
 | 
					    operators,
 | 
				
			||||||
    operators_triangle,
 | 
					    operators_triangle,
 | 
				
			||||||
 | 
					    operators_velocity,
 | 
				
			||||||
    debug,
 | 
					    debug,
 | 
				
			||||||
    bind_points,
 | 
					    bind_points,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
@ -88,20 +88,21 @@ class GP_OT_interpolate_stroke_base(bpy.types.Operator):
 | 
				
			|||||||
            return {'FINISHED'}
 | 
					            return {'FINISHED'}
 | 
				
			||||||
        return {'CANCELLED'}
 | 
					        return {'CANCELLED'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_stroke_to_interpolate(self, context):
 | 
					    # def get_stroke_to_interpolate(self, context):
 | 
				
			||||||
        ## Get strokes to interpolate
 | 
					    #     ## Get strokes to interpolate
 | 
				
			||||||
        tgt_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
 | 
					    #     #tgt_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
 | 
				
			||||||
 | 
					    #     tgt_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ## If nothing selected in sculpt/paint, Select all before triggering
 | 
					    #     ## If nothing selected in sculpt/paint, Select all before triggering
 | 
				
			||||||
        if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
 | 
					    #     if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
 | 
				
			||||||
            for s in self.gp.data.layers.active.active_frame.strokes:
 | 
					    #         for s in self.gp.data.layers.active.active_frame.strokes:
 | 
				
			||||||
                s.select = True
 | 
					    #             s.select = True
 | 
				
			||||||
            tgt_strokes = self.gp.data.layers.active.active_frame.strokes
 | 
					    #         tgt_strokes = self.gp.data.layers.active.active_frame.strokes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if tgt_strokes:            
 | 
					    #     if tgt_strokes:            
 | 
				
			||||||
            return tgt_strokes
 | 
					    #         return tgt_strokes
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return self.exit(context, status='ERROR', text='No stroke selected!')
 | 
					    #     return self.exit(context, status='ERROR', text='No stroke selected!')
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ## Added to operators owns invoke with uper().invoke(context, event)
 | 
					    ## Added to operators owns invoke with uper().invoke(context, event)
 | 
				
			||||||
@ -128,19 +129,28 @@ class GP_OT_interpolate_stroke_base(bpy.types.Operator):
 | 
				
			|||||||
        if interp_col := bpy.data.collections.get('interpolation_tool'):
 | 
					        if interp_col := bpy.data.collections.get('interpolation_tool'):
 | 
				
			||||||
            bpy.data.collections.remove(interp_col)
 | 
					            bpy.data.collections.remove(interp_col)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if context.mode != 'EDIT_GPENCIL':
 | 
				
			||||||
 | 
					            self.report({"ERROR"}, "Mode need to be Edit Grease Pencil")
 | 
				
			||||||
 | 
					            return {"CANCELLED"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ## Change active layer if strokes are selected only on this layer
 | 
					        ## Change active layer if strokes are selected only on this layer
 | 
				
			||||||
        layers = [l for l in self.gp.data.layers 
 | 
					        self.layers = [l for l in self.gp.data.layers 
 | 
				
			||||||
                  if (not l.lock and l.active_frame)
 | 
					                  if (not l.lock and l.active_frame and not l.hide)
 | 
				
			||||||
                  and next((s for s in l.active_frame.strokes if s.select), None)]
 | 
					                  and next((s for s in l.active_frame.strokes if s.select), None)]
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if not layers:
 | 
					        self.strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
            return self.exit(context, status='ERROR', text='No stroke selected!')
 | 
					        if not self.strokes:
 | 
				
			||||||
 | 
					            self.report({"ERROR"}, "No strokes selected")
 | 
				
			||||||
 | 
					            return {"CANCELLED"}
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #if not self.layers:
 | 
				
			||||||
 | 
					        #    return self.exit(context, status='ERROR', text='No stroke selected!')
 | 
				
			||||||
            
 | 
					            
 | 
				
			||||||
        elif len(layers) > 1:
 | 
					        #elif len(layers) > 1:
 | 
				
			||||||
            return self.exit(context, status='ERROR', text='Strokes selected accross multiple layers!')
 | 
					        #    return self.exit(context, status='ERROR', text='Strokes selected accross multiple layers!')
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        ## Set active layer
 | 
					        ## Set active layer
 | 
				
			||||||
        self.gp.data.layers.active = layers[0]
 | 
					        #self.gp.data.layers.active = layers[0]
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        if self.interactive:
 | 
					        if self.interactive:
 | 
				
			||||||
            self.frames_to_jump = following_keys(forward=True, animation=True)
 | 
					            self.frames_to_jump = following_keys(forward=True, animation=True)
 | 
				
			||||||
@ -269,9 +279,10 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        origin = scn.camera.matrix_world.to_translation()
 | 
					        origin = scn.camera.matrix_world.to_translation()
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        tgt_strokes = self.get_stroke_to_interpolate(context)
 | 
					        strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
        if isinstance(tgt_strokes, set):
 | 
					        if not strokes:
 | 
				
			||||||
            return tgt_strokes
 | 
					            self.report({"ERROR"}, "No strokes selected")
 | 
				
			||||||
 | 
					            return {"CANCELLED"}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        col = self.settings.target_collection
 | 
					        col = self.settings.target_collection
 | 
				
			||||||
        if not col:
 | 
					        if not col:
 | 
				
			||||||
@ -350,7 +361,7 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
        dg = bpy.context.evaluated_depsgraph_get()
 | 
					        dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
        self.strokes_data = []
 | 
					        self.strokes_data = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for stroke_index, stroke in enumerate(tgt_strokes):
 | 
					        for stroke_index, stroke in enumerate(strokes):
 | 
				
			||||||
            stroke_data = []
 | 
					            stroke_data = []
 | 
				
			||||||
            for point_index, point in enumerate(stroke.points):
 | 
					            for point_index, point in enumerate(stroke.points):
 | 
				
			||||||
                point_co_world = self.gp.matrix_world @ point.co
 | 
					                point_co_world = self.gp.matrix_world @ point.co
 | 
				
			||||||
@ -393,7 +404,7 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
        origin = scn.camera.matrix_world.to_translation()
 | 
					        origin = scn.camera.matrix_world.to_translation()
 | 
				
			||||||
        plane_co, plane_no = get_gp_draw_plane(self.gp)
 | 
					        plane_co, plane_no = get_gp_draw_plane(self.gp)
 | 
				
			||||||
        bpy.ops.gpencil.select_all(action='DESELECT')
 | 
					        bpy.ops.gpencil.select_all(action='DESELECT')
 | 
				
			||||||
        bpy.ops.gpencil.paste()
 | 
					        bpy.ops.gpencil.paste(type='LAYER')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.settings.method == 'BONE':
 | 
					        if self.settings.method == 'BONE':
 | 
				
			||||||
            ## Set plane on the bone
 | 
					            ## Set plane on the bone
 | 
				
			||||||
@ -405,10 +416,13 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
        dg = bpy.context.evaluated_depsgraph_get()
 | 
					        dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        ## Get pasted stroke
 | 
					        ## Get pasted stroke
 | 
				
			||||||
        new_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
 | 
					        #new_strokes = [s for s in self.gp.data.layers.active.active_frame.strokes if s.select]
 | 
				
			||||||
 | 
					        new_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
        ## Keep reference to all accessible other strokes (in all accessible layer)
 | 
					        ## Keep reference to all accessible other strokes (in all accessible layer)
 | 
				
			||||||
        other_strokes = [s for l in self.gp.data.layers if l.active_frame and not l.lock for s in l.active_frame.strokes if not s.select]
 | 
					        other_strokes = [s for l in self.gp.data.layers if l.active_frame and not l.lock for s in l.active_frame.strokes if not s.select]
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
 | 
					        smooth_level = self.settings.smooth_level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        occluded_points = []
 | 
					        occluded_points = []
 | 
				
			||||||
        for new_stroke, stroke_data in zip(list(new_strokes), list(self.strokes_data)):
 | 
					        for new_stroke, stroke_data in zip(list(new_strokes), list(self.strokes_data)):
 | 
				
			||||||
            world_co_3d = []
 | 
					            world_co_3d = []
 | 
				
			||||||
@ -416,9 +430,25 @@ class GP_OT_interpolate_stroke(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
                eval_ob = object_hit.evaluated_get(dg)
 | 
					                eval_ob = object_hit.evaluated_get(dg)
 | 
				
			||||||
                tri_b = [eval_ob.matrix_world @ eval_ob.data.vertices[i].co for i in tri_indices]
 | 
					                tri_b = [eval_ob.matrix_world @ eval_ob.data.vertices[i].co for i in tri_indices]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                new_loc = barycentric_transform(hit_location, *tri_a, *tri_b)                        
 | 
					                new_loc = barycentric_transform(hit_location, *tri_a, *tri_b) 
 | 
				
			||||||
                world_co_3d.append(new_loc)
 | 
					                world_co_3d.append(new_loc)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # Smooth points
 | 
				
			||||||
 | 
					            if smooth_level:
 | 
				
			||||||
 | 
					                old_co_3d = [s[1] for s in stroke_data]
 | 
				
			||||||
 | 
					                points_velocity = [b-a for a, b in zip(old_co_3d, world_co_3d)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                # Average of points
 | 
				
			||||||
 | 
					                for i in range(smooth_level + 1):
 | 
				
			||||||
 | 
					                    points_velocity = [
 | 
				
			||||||
 | 
					                        (points_velocity[i] + points_velocity[i + 1]) / 2 if i == 0 else
 | 
				
			||||||
 | 
					                        (points_velocity[i] + points_velocity[i - 1]) / 2 if i == len(points_velocity) - 1 else
 | 
				
			||||||
 | 
					                        (points_velocity[i - 1] + points_velocity[i] + points_velocity[i + 1]) / 3
 | 
				
			||||||
 | 
					                        for i in range(len(points_velocity))
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                world_co_3d = [a+b for a, b in zip(old_co_3d, points_velocity)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ## Reproject on plane
 | 
					            ## Reproject on plane
 | 
				
			||||||
            new_world_co_3d = [intersect_line_plane(origin, p, plane_co, plane_no) for p in world_co_3d]        
 | 
					            new_world_co_3d = [intersect_line_plane(origin, p, plane_co, plane_no) for p in world_co_3d]        
 | 
				
			||||||
            new_local_co_3d = [co for coord in new_world_co_3d for co in self.gp.matrix_world.inverted() @ coord]
 | 
					            new_local_co_3d = [co for coord in new_world_co_3d for co in self.gp.matrix_world.inverted() @ coord]
 | 
				
			||||||
 | 
				
			|||||||
@ -78,7 +78,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # print('----')
 | 
					        # print('----')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tgt_strokes = [s for s in gp.data.layers.active.active_frame.strokes if s.select]
 | 
					        tgt_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        ## If nothing selected in sculpt/paint, Select all before triggering
 | 
					        ## If nothing selected in sculpt/paint, Select all before triggering
 | 
				
			||||||
        if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
 | 
					        if not tgt_strokes and context.mode in ('SCULPT_GPENCIL', 'PAINT_GPENCIL'):
 | 
				
			||||||
@ -247,7 +247,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
 | 
				
			|||||||
                origin = scn.camera.matrix_world.to_translation()
 | 
					                origin = scn.camera.matrix_world.to_translation()
 | 
				
			||||||
                # origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
 | 
					                # origin = np.array(scn.camera.matrix_world.to_translation(), 'float64')
 | 
				
			||||||
                plan_co, plane_no = get_gp_draw_plane(gp)
 | 
					                plan_co, plane_no = get_gp_draw_plane(gp)
 | 
				
			||||||
                bpy.ops.gpencil.paste()
 | 
					                bpy.ops.gpencil.paste(type="LAYER")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if settings.method == 'BONE':
 | 
					                if settings.method == 'BONE':
 | 
				
			||||||
                    bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone),
 | 
					                    bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone),
 | 
				
			||||||
@ -257,10 +257,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
 | 
				
			|||||||
                    
 | 
					                    
 | 
				
			||||||
                dg = bpy.context.evaluated_depsgraph_get()
 | 
					                dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
                matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
 | 
					                matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted()
 | 
				
			||||||
                new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):]
 | 
					                new_strokes = [(l, s) for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                # for new_stroke, stroke_data in zip(new_strokes, strokes_data):
 | 
					                # for new_stroke, stroke_data in zip(new_strokes, strokes_data):
 | 
				
			||||||
                for new_stroke, stroke_data in zip(reversed(new_strokes), reversed(strokes_data)):
 | 
					                for (layer, new_stroke), stroke_data in zip(reversed(new_strokes), reversed(strokes_data)):
 | 
				
			||||||
                    world_co_3d = []
 | 
					                    world_co_3d = []
 | 
				
			||||||
                    for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data:
 | 
					                    for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data:
 | 
				
			||||||
                        eval_ob = object_hit.evaluated_get(dg)
 | 
					                        eval_ob = object_hit.evaluated_get(dg)
 | 
				
			||||||
@ -316,7 +316,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
 | 
				
			|||||||
                                if len(sublist) == 1:
 | 
					                                if len(sublist) == 1:
 | 
				
			||||||
                                    continue
 | 
					                                    continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                ns = gp.data.layers.active.active_frame.strokes.new()
 | 
					                                ns = layer.active_frame.strokes.new()
 | 
				
			||||||
                                for elem in ('hardness', 'material_index', 'line_width'):
 | 
					                                for elem in ('hardness', 'material_index', 'line_width'):
 | 
				
			||||||
                                    setattr(ns, elem, getattr(new_stroke, elem))
 | 
					                                    setattr(ns, elem, getattr(new_stroke, elem))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -326,7 +326,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator):
 | 
				
			|||||||
                                        setattr(ns.points[i], elem, getattr(new_stroke.points[point_index], elem))
 | 
					                                        setattr(ns.points[i], elem, getattr(new_stroke.points[point_index], elem))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        ## Delete original stroke
 | 
					                        ## Delete original stroke
 | 
				
			||||||
                        gp.data.layers.active.active_frame.strokes.remove(new_stroke)
 | 
					                        layer.active_frame.strokes.remove(new_stroke)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            wm.progress_end() # Pgs
 | 
					            wm.progress_end() # Pgs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -27,10 +27,6 @@ class GP_OT_interpolate_stroke_tri(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        origin = scn.camera.matrix_world.to_translation()
 | 
					        origin = scn.camera.matrix_world.to_translation()
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
        tgt_strokes = self.get_stroke_to_interpolate(context)
 | 
					 | 
				
			||||||
        if isinstance(tgt_strokes, set):
 | 
					 | 
				
			||||||
            return tgt_strokes
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ## Prepare context manager
 | 
					        ## Prepare context manager
 | 
				
			||||||
        attrs = [
 | 
					        attrs = [
 | 
				
			||||||
            # (context.view_layer.objects, 'active', self.gp),
 | 
					            # (context.view_layer.objects, 'active', self.gp),
 | 
				
			||||||
@ -55,7 +51,7 @@ class GP_OT_interpolate_stroke_tri(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        self.strokes_data = []
 | 
					        self.strokes_data = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for stroke in tgt_strokes:
 | 
					        for stroke in self.strokes:
 | 
				
			||||||
            stroke_data = []
 | 
					            stroke_data = []
 | 
				
			||||||
            for point in stroke.points:
 | 
					            for point in stroke.points:
 | 
				
			||||||
                point_co_world = self.gp.matrix_world @ point.co
 | 
					                point_co_world = self.gp.matrix_world @ point.co
 | 
				
			||||||
@ -91,12 +87,13 @@ class GP_OT_interpolate_stroke_tri(GP_OT_interpolate_stroke_base):
 | 
				
			|||||||
        scn = context.scene
 | 
					        scn = context.scene
 | 
				
			||||||
        origin = scn.camera.matrix_world.to_translation()
 | 
					        origin = scn.camera.matrix_world.to_translation()
 | 
				
			||||||
        plane_co, plane_no = get_gp_draw_plane(self.gp)
 | 
					        plane_co, plane_no = get_gp_draw_plane(self.gp)
 | 
				
			||||||
        bpy.ops.gpencil.paste()
 | 
					        bpy.ops.gpencil.paste(type='LAYER')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dg = bpy.context.evaluated_depsgraph_get()
 | 
					        dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        ## List of newly pasted strokes (using range)
 | 
					        ## List of newly pasted strokes (using range)
 | 
				
			||||||
        new_strokes = self.gp.data.layers.active.active_frame.strokes[-len(self.strokes_data):]
 | 
					        new_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
 | 
					        #new_strokes = self.gp.data.layers.active.active_frame.strokes[-len(self.strokes_data):]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ## Get user triangle position at current frame
 | 
					        ## Get user triangle position at current frame
 | 
				
			||||||
        tri_b = []
 | 
					        tri_b = []
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										275
									
								
								interpolate_strokes/operators_velocity.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								interpolate_strokes/operators_velocity.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,275 @@
 | 
				
			|||||||
 | 
					import bpy
 | 
				
			||||||
 | 
					from time import time
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import bpy
 | 
				
			||||||
 | 
					#from bpy_extras.object_utils import world_to_camera_view
 | 
				
			||||||
 | 
					from mathutils import Vector
 | 
				
			||||||
 | 
					from mathutils.kdtree import KDTree
 | 
				
			||||||
 | 
					from math import tan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from mathutils.geometry import (barycentric_transform,
 | 
				
			||||||
 | 
					                                intersect_line_plane)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..utils import (triangle_normal,
 | 
				
			||||||
 | 
					                    get_gp_draw_plane, load_datablock)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from ..constants import RESOURCES_DIR
 | 
				
			||||||
 | 
					from .operators import GP_OT_interpolate_stroke_base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def world_to_camera_view(scene, obj, coord):
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    Returns the camera space coords for a 3d point.
 | 
				
			||||||
 | 
					    (also known as: normalized device coordinates - NDC).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Where (0, 0) is the bottom left and (1, 1)
 | 
				
			||||||
 | 
					    is the top right of the camera frame.
 | 
				
			||||||
 | 
					    values outside 0-1 are also supported.
 | 
				
			||||||
 | 
					    A negative 'z' value means the point is behind the camera.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Takes shift-x/y, lens angle and sensor size into account
 | 
				
			||||||
 | 
					    as well as perspective/ortho projections.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    :arg scene: Scene to use for frame size.
 | 
				
			||||||
 | 
					    :type scene: :class:`bpy.types.Scene`
 | 
				
			||||||
 | 
					    :arg obj: Camera object.
 | 
				
			||||||
 | 
					    :type obj: :class:`bpy.types.Object`
 | 
				
			||||||
 | 
					    :arg coord: World space location.
 | 
				
			||||||
 | 
					    :type coord: :class:`mathutils.Vector`
 | 
				
			||||||
 | 
					    :return: a vector where X and Y map to the view plane and
 | 
				
			||||||
 | 
					       Z is the depth on the view axis.
 | 
				
			||||||
 | 
					    :rtype: :class:`mathutils.Vector`
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    from mathutils import Vector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    co_local = obj.matrix_world.normalized().inverted() @ coord
 | 
				
			||||||
 | 
					    z = -co_local.z
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    camera = obj.data
 | 
				
			||||||
 | 
					    frame = [v for v in camera.view_frame(scene=scene)[:3]]
 | 
				
			||||||
 | 
					    if camera.type != 'ORTHO':
 | 
				
			||||||
 | 
					        if z == 0.0:
 | 
				
			||||||
 | 
					            return Vector((0.5, 0.5, 0.0))
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            frame = [-(v / (v.z / z)) for v in frame]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    min_x, max_x = frame[2].x, frame[1].x
 | 
				
			||||||
 | 
					    min_y, max_y = frame[1].y, frame[0].y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    x = (co_local.x - min_x) / (max_x - min_x)
 | 
				
			||||||
 | 
					    y = (co_local.y - min_y) / (max_y - min_y)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Vector((x, y, z))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def camera_view_to_world(scene, obj, coord):
 | 
				
			||||||
 | 
					    """Reverse function of world_to_camera_view"""
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    frame = [obj.matrix_world @ co for co in obj.data.view_frame(scene=scene)]
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    x, y, z = coord
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    right_interp = frame[1] + y * (frame[0] - frame[1])
 | 
				
			||||||
 | 
					    # Interpolate along x-axis (left side)
 | 
				
			||||||
 | 
					    left_interp = frame[2] + y * (frame[3] - frame[2])
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    # Interpolate along y-axis
 | 
				
			||||||
 | 
					    return  Vector(left_interp + x * (right_interp - left_interp))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class GP_OT_interpolate_stroke_velocity(GP_OT_interpolate_stroke_base):
 | 
				
			||||||
 | 
					    bl_idname = "gp.interpolate_stroke_velocity"
 | 
				
			||||||
 | 
					    bl_label = "Interpolate Stroke"
 | 
				
			||||||
 | 
					    bl_description = 'Interpolate Stroke based on velocity'
 | 
				
			||||||
 | 
					    bl_options = {'REGISTER', 'UNDO'} 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def invoke(self, context, event):
 | 
				
			||||||
 | 
					        if state := super().invoke(context, event):
 | 
				
			||||||
 | 
					            return state
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        scn = bpy.context.scene
 | 
				
			||||||
 | 
					        settings = context.scene.gp_interpo_settings
 | 
				
			||||||
 | 
					        col = settings.target_collection
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ## Prepare context manager
 | 
				
			||||||
 | 
					        attrs = [
 | 
				
			||||||
 | 
					            # (context.view_layer.objects, 'active', self.gp),
 | 
				
			||||||
 | 
					            (context.tool_settings, 'use_keyframe_insert_auto', True),
 | 
				
			||||||
 | 
					            # (bpy.context.scene.render, 'simplify_subdivision', 0),
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        self.apply_and_store(attrs)
 | 
				
			||||||
 | 
					                        
 | 
				
			||||||
 | 
					        velocity_mesh = bpy.data.meshes.new('interpolate_velocity')
 | 
				
			||||||
 | 
					        velocity_ob = bpy.data.objects.new('interpolate_velocity', velocity_mesh)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.velocity_node_group = load_datablock(RESOURCES_DIR/'nodes.blend', 'Velocity Grid', type='node_groups', link=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        instance_col_mod = velocity_ob.modifiers.new('IngestCollection', 'NODES')
 | 
				
			||||||
 | 
					        ingest_node_group = load_datablock(RESOURCES_DIR/'nodes.blend', 'Ingest Collection', type='node_groups', link=False)
 | 
				
			||||||
 | 
					        instance_col_mod.node_group = ingest_node_group
 | 
				
			||||||
 | 
					        instance_col_mod["Socket_2"] = col
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        scn.collection.objects.link(velocity_ob)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Apply instance collection modifier
 | 
				
			||||||
 | 
					        dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
 | 
					        eval_ob = velocity_ob.evaluated_get(dg)
 | 
				
			||||||
 | 
					        eval_data = eval_ob.data.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        velocity_ob.modifiers.remove(instance_col_mod)
 | 
				
			||||||
 | 
					        velocity_ob.data = eval_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bpy.data.node_groups.remove(ingest_node_group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.velocity_ob = velocity_ob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if self.debug:
 | 
				
			||||||
 | 
					            self.scan_time = time()-self.start
 | 
				
			||||||
 | 
					            print(f'Scan time {self.scan_time:.4f}s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Baking Camera
 | 
				
			||||||
 | 
					        self.camera = scn.camera.copy()
 | 
				
			||||||
 | 
					        self.camera.data = self.camera.data.copy()
 | 
				
			||||||
 | 
					        self.camera.animation_data_clear()
 | 
				
			||||||
 | 
					        self.camera.data.animation_data_clear()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        cam_mat = self.camera.matrix_world.copy()
 | 
				
			||||||
 | 
					        self.camera.animation_data_clear()
 | 
				
			||||||
 | 
					        self.camera.parent = None
 | 
				
			||||||
 | 
					        self.camera.matrix_world = cam_mat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Store curent gp matrix
 | 
				
			||||||
 | 
					        self.gp_matrix = self.gp.matrix_world.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Ensure whole stroke are selected before copy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bpy.ops.gpencil.select_linked()
 | 
				
			||||||
 | 
					        # Copy stroke selection
 | 
				
			||||||
 | 
					        bpy.ops.gpencil.copy()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Jump frame and paste
 | 
				
			||||||
 | 
					        # if self.report_progress:
 | 
				
			||||||
 | 
					        #     context.window_manager.progress_begin(self.frames_to_jump[0], self.frames_to_jump[-1]) # Pgs
 | 
				
			||||||
 | 
					        # context.area.header_text_set('Starting interpolation | Esc: Cancel')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        context.window_manager.modal_handler_add(self)
 | 
				
			||||||
 | 
					        return {'RUNNING_MODAL'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def interpolate_frame(self, context):
 | 
				
			||||||
 | 
					        scn = context.scene
 | 
				
			||||||
 | 
					        cam = scn.camera
 | 
				
			||||||
 | 
					        #dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
 | 
					        col = self.settings.target_collection
 | 
				
			||||||
 | 
					        smooth_level = self.settings.smooth_level
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        origin = scn.camera.matrix_world.to_translation()
 | 
				
			||||||
 | 
					        plane_co, plane_no = get_gp_draw_plane(self.gp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #print("interpolate_frame")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        print(self.gp_matrix)
 | 
				
			||||||
 | 
					        print(self.gp.matrix_world)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        velocity_ob = self.velocity_ob
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        velocity_ob.hide_set(True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        grid_velocity_mod = velocity_ob.modifiers.new('VelocityGrid', 'NODES')
 | 
				
			||||||
 | 
					        grid_velocity_mod.node_group = self.velocity_node_group
 | 
				
			||||||
 | 
					        grid_velocity_mod["Socket_2"] = col
 | 
				
			||||||
 | 
					        grid_velocity_mod["Socket_4"] = self.camera
 | 
				
			||||||
 | 
					        grid_velocity_mod["Socket_5"] = self.camera.data.angle
 | 
				
			||||||
 | 
					        grid_velocity_mod["Socket_6"] = self.camera.data.shift_x
 | 
				
			||||||
 | 
					        grid_velocity_mod["Socket_7"] = self.camera.data.shift_y
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #raise Exception()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Apply velocity grid modifier
 | 
				
			||||||
 | 
					        dg = bpy.context.evaluated_depsgraph_get()
 | 
				
			||||||
 | 
					        eval_ob = velocity_ob.evaluated_get(dg)
 | 
				
			||||||
 | 
					        #eval_data = eval_ob.data.copy()
 | 
				
			||||||
 | 
					        grid_ob = bpy.data.objects.new('Velocity Grid Object', eval_ob.data.copy())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #copy_ob = velocity_ob.copy()
 | 
				
			||||||
 | 
					        #scn.collection.objects.link(copy_ob)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        velocity_ob.modifiers.remove(grid_velocity_mod)
 | 
				
			||||||
 | 
					        #velocity_ob.data = eval_data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #Create kd tree for finding nearest points
 | 
				
			||||||
 | 
					        kd = KDTree(len(grid_ob.data.vertices))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        points = [0, 0, 0] * len(grid_ob.data.vertices) 
 | 
				
			||||||
 | 
					        grid_ob.data.vertices.foreach_get('co', points)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for i in range(0, len(points), 3):
 | 
				
			||||||
 | 
					            kd.insert(points[i:i+3], int(i/3))    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        kd.balance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        #nb_strokes = len(self.gp.data.layers.active.active_frame.strokes)
 | 
				
			||||||
 | 
					        bpy.ops.gpencil.paste(type='LAYER')
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        ## List of newly pasted strokes (using range)
 | 
				
			||||||
 | 
					        new_strokes = [s for l in self.layers for s in l.active_frame.strokes if s.select]
 | 
				
			||||||
 | 
					        #new_strokes = self.gp.data.layers.active.active_frame.strokes[-nb_strokes:]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        velocity_attr = grid_ob.data.attributes["velocity"].data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for stroke in new_strokes:
 | 
				
			||||||
 | 
					            points = [0, 0, 0] * len(stroke.points) 
 | 
				
			||||||
 | 
					            stroke.points.foreach_get('co', points)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            points_2d = [world_to_camera_view(scn, self.camera, self.gp_matrix @ Vector(points[i:i+3])) for i in range(0, len(points), 3)]
 | 
				
			||||||
 | 
					            points_2d = [Vector((p.x, p.y, 0)) for p in points_2d] # Remove Z component
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            points_velocity = [velocity_attr[kd.find(p)[1]].vector for p in points_2d]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if smooth_level:
 | 
				
			||||||
 | 
					                # Average of points
 | 
				
			||||||
 | 
					                for i in range(smooth_level + 1):
 | 
				
			||||||
 | 
					                    points_velocity = [
 | 
				
			||||||
 | 
					                        (points_velocity[i] + points_velocity[i + 1]) / 2 if i == 0 else
 | 
				
			||||||
 | 
					                        (points_velocity[i] + points_velocity[i - 1]) / 2 if i == len(points_velocity) - 1 else
 | 
				
			||||||
 | 
					                        (points_velocity[i - 1] + points_velocity[i] + points_velocity[i + 1]) / 3
 | 
				
			||||||
 | 
					                        for i in range(len(points_velocity))
 | 
				
			||||||
 | 
					                    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            new_points_3d = [camera_view_to_world(scn, cam, p+vel) for p, vel in zip(points_2d, points_velocity)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ## Reproject on plane
 | 
				
			||||||
 | 
					            new_points_3d = [intersect_line_plane(origin, p, plane_co, plane_no) for p in new_points_3d]    
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					            stroke.points.foreach_set('co', [v for p in new_points_3d for v in self.gp.matrix_world.inverted() @p])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            #stroke.points.foreach_set('co', [v for p in points_2d for v in self.gp.matrix_world.inverted() @p])
 | 
				
			||||||
 | 
					            stroke.points.update()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        #velocity_ob.modifiers.remove(grid_velocity_mod)
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        bpy.data.meshes.remove(grid_ob.data)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    def exit(self, context, status='INFO', text=None, cancelled=False):
 | 
				
			||||||
 | 
					        out = super().exit(context, status='INFO', text=None, cancelled=False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bpy.data.node_groups.remove(self.velocity_node_group)
 | 
				
			||||||
 | 
					        bpy.data.meshes.remove(self.velocity_ob.data)
 | 
				
			||||||
 | 
					        bpy.data.cameras.remove(self.camera.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return out
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					classes = (
 | 
				
			||||||
 | 
					    GP_OT_interpolate_stroke_velocity,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def register():
 | 
				
			||||||
 | 
					    for c in classes:
 | 
				
			||||||
 | 
					        bpy.utils.register_class(c)
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					def unregister():
 | 
				
			||||||
 | 
					    for c in reversed(classes):
 | 
				
			||||||
 | 
					        bpy.utils.unregister_class(c)
 | 
				
			||||||
@ -21,6 +21,7 @@ class GP_PG_interpolate_settings(PropertyGroup):
 | 
				
			|||||||
            ('OBJECT', 'Object Geometry', 'Same as Geometry mode, but target only a specific object, even if occluded (ignore all the others)', 1),
 | 
					            ('OBJECT', 'Object Geometry', 'Same as Geometry mode, but target only a specific object, even if occluded (ignore all the others)', 1),
 | 
				
			||||||
            ('BONE', 'Bone', 'Pick an armature bone and follow it', 2),
 | 
					            ('BONE', 'Bone', 'Pick an armature bone and follow it', 2),
 | 
				
			||||||
            ('TRI', 'Triangle', 'Interpolate based on triangle traced manually over geometry', 3),
 | 
					            ('TRI', 'Triangle', 'Interpolate based on triangle traced manually over geometry', 3),
 | 
				
			||||||
 | 
					            ('VELOCITY', 'Velocity', 'Interpolate based on velocity, works well for point outside geometry', 4)
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
        default='GEOMETRY',
 | 
					        default='GEOMETRY',
 | 
				
			||||||
        description='Select method for interpolating strokes'
 | 
					        description='Select method for interpolating strokes'
 | 
				
			||||||
@ -83,6 +84,11 @@ class GP_PG_interpolate_settings(PropertyGroup):
 | 
				
			|||||||
        default=True,
 | 
					        default=True,
 | 
				
			||||||
        description='Apply rotation of the bone') # Bone
 | 
					        description='Apply rotation of the bone') # Bone
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #selection: EnumProperty(default='SELECTED', items=[("SELECTED", "Selected", ""), ("ALL", "All", "")], 
 | 
				
			||||||
 | 
					    #                        description="Stroke to interpolate")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    smooth_level: IntProperty(default=2, min=0, max=10, name='Smooth Level')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
classes = (
 | 
					classes = (
 | 
				
			||||||
    GP_PG_interpolate_settings,
 | 
					    GP_PG_interpolate_settings,
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								resources/nodes.blend
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								resources/nodes.blend
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										15
									
								
								ui.py
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								ui.py
									
									
									
									
									
								
							@ -36,7 +36,13 @@ class GP_PT_interpolate(bpy.types.Panel):
 | 
				
			|||||||
        row.scale_y = 1.2
 | 
					        row.scale_y = 1.2
 | 
				
			||||||
        direction_button_row = row.row(align=True)
 | 
					        direction_button_row = row.row(align=True)
 | 
				
			||||||
        direction_button_row.scale_x = 3
 | 
					        direction_button_row.scale_x = 3
 | 
				
			||||||
        ops_id = "gp.interpolate_stroke_tri" if settings.method == 'TRI' else "gp.interpolate_stroke"
 | 
					        ops_id = "gp.interpolate_stroke"
 | 
				
			||||||
 | 
					        if settings.method == 'TRI':
 | 
				
			||||||
 | 
					            ops_id = "gp.interpolate_stroke_tri"
 | 
				
			||||||
 | 
					        elif settings.method == 'VELOCITY':
 | 
				
			||||||
 | 
					            ops_id = "gp.interpolate_stroke_velocity"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        direction_button_row.operator(ops_id, text=prev_text, icon=prev_icon).next = False
 | 
					        direction_button_row.operator(ops_id, text=prev_text, icon=prev_icon).next = False
 | 
				
			||||||
        direction_button_row.operator(ops_id, text=next_text, icon=next_icon).next = True
 | 
					        direction_button_row.operator(ops_id, text=next_text, icon=next_icon).next = True
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
@ -59,10 +65,17 @@ class GP_PT_interpolate(bpy.types.Panel):
 | 
				
			|||||||
        elif settings.method == 'GEOMETRY':
 | 
					        elif settings.method == 'GEOMETRY':
 | 
				
			||||||
            col.prop(settings, 'search_range')
 | 
					            col.prop(settings, 'search_range')
 | 
				
			||||||
            col.prop(settings, 'remove_occluded')
 | 
					            col.prop(settings, 'remove_occluded')
 | 
				
			||||||
 | 
					            col.prop(settings, 'smooth_level', text='Smooth')
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        elif settings.method == 'OBJECT':
 | 
					        elif settings.method == 'OBJECT':
 | 
				
			||||||
            col.prop(settings, 'search_range')
 | 
					            col.prop(settings, 'search_range')
 | 
				
			||||||
            col.prop(settings, 'target_object', text='Object')
 | 
					            col.prop(settings, 'target_object', text='Object')
 | 
				
			||||||
 | 
					            col.prop(settings, 'smooth_level', text='Smooth')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elif settings.method == 'VELOCITY':
 | 
				
			||||||
 | 
					            col.prop(settings, 'target_collection', text='Collection')
 | 
				
			||||||
 | 
					            col.prop(settings, 'target_object', text='Object')
 | 
				
			||||||
 | 
					            col.prop(settings, 'smooth_level', text='Smooth')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        col.separator()
 | 
					        col.separator()
 | 
				
			||||||
        col = layout.column(align=True)
 | 
					        col = layout.column(align=True)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										60
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								utils.py
									
									
									
									
									
								
							@ -2,6 +2,9 @@ import bpy
 | 
				
			|||||||
import math
 | 
					import math
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import numpy as np
 | 
					import numpy as np
 | 
				
			||||||
 | 
					import fnmatch
 | 
				
			||||||
 | 
					import os
 | 
				
			||||||
 | 
					from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from math import tan
 | 
					from math import tan
 | 
				
			||||||
from mathutils import Vector, Matrix
 | 
					from mathutils import Vector, Matrix
 | 
				
			||||||
@ -35,6 +38,63 @@ class attr_set():
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# --- Vector
 | 
					# --- Vector
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def load_datablock(filepath, *names, type='objects', link=True, expr=None, assets_only=False, 
 | 
				
			||||||
 | 
					        relative_to=None):
 | 
				
			||||||
 | 
					    """link or append elements from another blender scene
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Args:
 | 
				
			||||||
 | 
					        filepath (str): filepath of the scene to import objects from
 | 
				
			||||||
 | 
					        names (list[str]): names of datablocks to import.
 | 
				
			||||||
 | 
					        type (str, optional): type of data to import.
 | 
				
			||||||
 | 
					            Defaults to 'objects'.
 | 
				
			||||||
 | 
					        link (bool, optional): true if we want to import as link, else append.
 | 
				
			||||||
 | 
					            Defaults to True.
 | 
				
			||||||
 | 
					        expr (str, optional): pattern of names to import.
 | 
				
			||||||
 | 
					            Defaults to None.
 | 
				
			||||||
 | 
					        assets_only (bool, optional):  If true, import only data-blocks marked as assets.
 | 
				
			||||||
 | 
					            Defaults to False.
 | 
				
			||||||
 | 
					        relative_to (str|Path|bool, optionnal): If str or Path and link make path relative to it
 | 
				
			||||||
 | 
					        if False make path absolute, if None use preferences
 | 
				
			||||||
 | 
					            Defaults to None.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Returns:
 | 
				
			||||||
 | 
					        list|bpy.types.Object: datablocks imported
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # convert names from tuple to list to get the correct datablock type (blender tricks)
 | 
				
			||||||
 | 
					    names = list(names)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if isinstance(expr, str):
 | 
				
			||||||
 | 
					        pattern = expr
 | 
				
			||||||
 | 
					        expr = lambda x: fnmatch(x, pattern)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with bpy.data.libraries.load(str(filepath), link=link, assets_only=assets_only) as (data_from, data_to):
 | 
				
			||||||
 | 
					        datablocks = getattr(data_from, type)
 | 
				
			||||||
 | 
					        if expr:
 | 
				
			||||||
 | 
					            names += [i for i in datablocks if expr(i)]
 | 
				
			||||||
 | 
					        elif not names:
 | 
				
			||||||
 | 
					            names = datablocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setattr(data_to, type, names)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        datablocks = getattr(data_to, type)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if link and datablocks:
 | 
				
			||||||
 | 
					        lib = datablocks[0].library
 | 
				
			||||||
 | 
					        lib_path = os.path.abspath(bpy.path.abspath(lib.filepath))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if relative_to is False:
 | 
				
			||||||
 | 
					            lib.filepath = lib_path
 | 
				
			||||||
 | 
					        elif isinstance(relative_to, (str, Path)):
 | 
				
			||||||
 | 
					            lib.filepath = bpy.path.relpath(lib_path, start=str(relative_to))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if len(names) > 1:
 | 
				
			||||||
 | 
					        return datablocks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if datablocks:
 | 
				
			||||||
 | 
					        return datablocks[0]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def triangle_normal(p1, p2, p3):
 | 
					def triangle_normal(p1, p2, p3):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Calculate the normal of a triangle given its three vertices.
 | 
					    Calculate the normal of a triangle given its three vertices.
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user