Add single object raycast method
This commit is contained in:
		
							parent
							
								
									5590753550
								
							
						
					
					
						commit
						b8180ea84f
					
				| @ -1,7 +1,7 @@ | |||||||
| bl_info = { | bl_info = { | ||||||
|     "name": "gp interpolate", |     "name": "gp interpolate", | ||||||
|     "author": "Christophe Seux, Samuel Bernou", |     "author": "Christophe Seux, Samuel Bernou", | ||||||
|     "version": (0, 4, 2), |     "version": (0, 5, 0), | ||||||
|     "blender": (3, 6, 0), |     "blender": (3, 6, 0), | ||||||
|     "location": "Sidebar > Gpencil Tab > Interpolate", |     "location": "Sidebar > Gpencil Tab > Interpolate", | ||||||
|     "description": "Interpolate Grease pencil strokes over 3D", |     "description": "Interpolate Grease pencil strokes over 3D", | ||||||
|  | |||||||
| @ -6,12 +6,14 @@ from mathutils import Vector, Matrix | |||||||
| from gp_interpolate.utils import (matrix_transform, | from gp_interpolate.utils import (matrix_transform, | ||||||
|                                   plane_on_bone, |                                   plane_on_bone, | ||||||
|                                   ray_cast_point, |                                   ray_cast_point, | ||||||
|  |                                   obj_ray_cast, | ||||||
|                                   intersect_with_tesselated_plane, |                                   intersect_with_tesselated_plane, | ||||||
|                                   triangle_normal, |                                   triangle_normal, | ||||||
|                                   search_square, |                                   search_square, | ||||||
|                                   get_gp_draw_plane, |                                   get_gp_draw_plane, | ||||||
|                                   create_plane, |                                   create_plane, | ||||||
|                                   following_keys, |                                   following_keys, | ||||||
|  |                                   index_list_from_bools, | ||||||
|                                   attr_set) |                                   attr_set) | ||||||
| 
 | 
 | ||||||
| from mathutils.geometry import (barycentric_transform, | from mathutils.geometry import (barycentric_transform, | ||||||
| @ -22,6 +24,7 @@ from mathutils.geometry import (barycentric_transform, | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| ## TODO: add bake animation to empty for later GP layer parenting | ## TODO: add bake animation to empty for later GP layer parenting | ||||||
|  | ## TODO: Occlusion management | ||||||
| 
 | 
 | ||||||
| class GP_OT_interpolate_stroke(bpy.types.Operator): | class GP_OT_interpolate_stroke(bpy.types.Operator): | ||||||
|     bl_idname = "gp.interpolate_stroke" |     bl_idname = "gp.interpolate_stroke" | ||||||
| @ -63,10 +66,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
|          |          | ||||||
|         gp = context.object |         gp = context.object | ||||||
| 
 | 
 | ||||||
|         # matrix = gp.matrix_world |         # matrix = np.array(gp.matrix_world, dtype='float64') | ||||||
|         # origin = scn.camera.matrix_world.to_translation() |         # origin = np.array(scn.camera.matrix_world.to_translation(), 'float64') | ||||||
|         matrix = np.array(gp.matrix_world, dtype='float64') |         matrix = gp.matrix_world | ||||||
|         origin = np.array(scn.camera.matrix_world.to_translation(), 'float64') |         origin = scn.camera.matrix_world.to_translation() | ||||||
|          |          | ||||||
|         col = settings.target_collection |         col = settings.target_collection | ||||||
|         if not col: |         if not col: | ||||||
| @ -87,10 +90,10 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
|             return {'CANCELLED'} |             return {'CANCELLED'} | ||||||
| 
 | 
 | ||||||
|         included_cols = [c.name for c in gp.users_collection] |         included_cols = [c.name for c in gp.users_collection] | ||||||
|  |         target_obj = None | ||||||
|         start = time() |         start = time() | ||||||
|          |          | ||||||
|         if settings.method == 'BONE': |         if settings.method == 'BONE': | ||||||
|             ## Follow Bone method (WIP) |  | ||||||
|             if not settings.target_rig or not settings.target_bone: |             if not settings.target_rig or not settings.target_bone: | ||||||
|                 self.report({'ERROR'}, 'No Bone selected') |                 self.report({'ERROR'}, 'No Bone selected') | ||||||
|                 return {'CANCELLED'} |                 return {'CANCELLED'} | ||||||
| @ -115,13 +118,23 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
| 
 | 
 | ||||||
|             if plane.name not in toolcol.objects: |             if plane.name not in toolcol.objects: | ||||||
|                 toolcol.objects.link(plane) |                 toolcol.objects.link(plane) | ||||||
|             ## TODO: Ensure the plane is not animated! |             target_obj = plane | ||||||
| 
 | 
 | ||||||
|         else: |         elif settings.method == 'GEOMETRY': | ||||||
|             # Geometry mode |  | ||||||
|             if col != context.scene.collection: |             if col != context.scene.collection: | ||||||
|                 included_cols.append(col.name) |                 included_cols.append(col.name) | ||||||
|             ## Maybe include a plane just behing geo ? probably bad idea |             ## Maybe include a plane just behind geo ? probably bad idea | ||||||
|  |          | ||||||
|  |         elif settings.method == 'OBJECT': | ||||||
|  |             if not settings.target_object: | ||||||
|  |                 self.report({'ERROR'}, 'No Object selected') | ||||||
|  |                 return {'CANCELLED'} | ||||||
|  |             col = scn.collection # Reset collection filter | ||||||
|  |             target_obj = settings.target_object | ||||||
|  |             if target_obj.library: | ||||||
|  |                 ## Look if an override exists in scene to use instead of default object | ||||||
|  |                 if (override := next((o for o in scn.objects if o.name == target_obj.name and o.override_library), None)): | ||||||
|  |                     target_obj = override | ||||||
| 
 | 
 | ||||||
|         ## Prepare context manager |         ## Prepare context manager | ||||||
|         store_list = [ |         store_list = [ | ||||||
| @ -130,8 +143,9 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
|             # (bpy.context.scene.render, 'simplify_subdivision', 0), |             # (bpy.context.scene.render, 'simplify_subdivision', 0), | ||||||
|             ] |             ] | ||||||
|          |          | ||||||
|         # TODO : Customize below filter to use in geo mode as well |         # TODO: for now, the collection filter is not used at all in GEOMETRY mode | ||||||
|         # so it does not exclude collections containing rig |         # it can be used to hide collection for faster animation mode | ||||||
|  | 
 | ||||||
|         if settings.method == 'BONE': |         if settings.method == 'BONE': | ||||||
|             ## TEST: Add collections containing rig (cannot be excluded) |             ## TEST: Add collections containing rig (cannot be excluded) | ||||||
|             # rig_parent_cols = [c.name for c in scn.collection.children_recursive if settings.target_rig.name in c.all_objects] |             # rig_parent_cols = [c.name for c in scn.collection.children_recursive if settings.target_rig.name in c.all_objects] | ||||||
| @ -173,38 +187,26 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
| 
 | 
 | ||||||
|                 stroke_data = [] |                 stroke_data = [] | ||||||
|                 for i, point in enumerate(stroke.points): |                 for i, point in enumerate(stroke.points): | ||||||
|                     # print(si, i)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |  | ||||||
|                     point_co_world = world_co_3d[i] |                     point_co_world = world_co_3d[i] | ||||||
|                      |                     if target_obj: | ||||||
|  |                         object_hit, hit_location, tri, tri_indices = obj_ray_cast(target_obj, Vector(point_co_world), origin, dg) | ||||||
|  |                     else: | ||||||
|  |                         # scene raycast | ||||||
|                         object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg) |                         object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg) | ||||||
|                     ## Try condition (not needed) |  | ||||||
|                     # try: |  | ||||||
|                     #     object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg) |  | ||||||
|                     # except Exception as e: |  | ||||||
|                     #     print(f'Error on first {si}:{i}') |  | ||||||
|                     #     self.report({'ERROR'}, f'Error on first {si}:{i}') |  | ||||||
|                     #     for p in stroke.points: |  | ||||||
|                     #         p.select = False |  | ||||||
|                     #     stroke.points[i].select = True |  | ||||||
|                     #     print(e) |  | ||||||
|                     #     return {'CANCELLED'} |  | ||||||
| 
 | 
 | ||||||
|                     ## with one simple extra search |                     ## Increasing search range | ||||||
|                     # if not object_hit or object_hit not in col.all_objects[:]: |                     if not object_hit: # or object_hit not in col.all_objects[:]: | ||||||
|                     #     for square_co in search_square(point_co_world, factor=settings.search_range): |  | ||||||
|                     #         object_hit, hit_location, tri, tri_indices = ray_cast_point(square_co, origin, dg) |  | ||||||
|                     #         if object_hit and object_hit in col.all_objects[:]: |  | ||||||
|                     #             hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri)) |  | ||||||
|                     #             break |  | ||||||
| 
 |  | ||||||
|                     ### with increasing search range |  | ||||||
|                     if not object_hit or object_hit not in col.all_objects[:]: |  | ||||||
|                         found = False |                         found = False | ||||||
|                         for iteration in range(1, 6): |                         for iteration in range(1, 6): | ||||||
|                             for square_co in search_square(point_co_world, factor=settings.search_range * iteration): |                             for square_co in search_square(point_co_world, factor=settings.search_range * iteration): | ||||||
| 
 | 
 | ||||||
|                                 object_hit, hit_location, tri, tri_indices = ray_cast_point(square_co, origin, dg) |                                 if target_obj: | ||||||
|                                 if object_hit and object_hit in col.all_objects[:]: |                                     object_hit, hit_location, tri, tri_indices = obj_ray_cast(target_obj, Vector(square_co), origin, dg) | ||||||
|  |                                 else: | ||||||
|  |                                     # scene raycast | ||||||
|  |                                     object_hit, hit_location, tri, tri_indices = ray_cast_point(point_co_world, origin, dg) | ||||||
|  | 
 | ||||||
|  |                                 if object_hit: # and object_hit in col.all_objects[:]: | ||||||
|                                     hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri)) |                                     hit_location = intersect_line_plane(origin, point_co_world, tri[0], triangle_normal(*tri)) | ||||||
|                                     found = True |                                     found = True | ||||||
|                                     # print(f'{si}:{i} iteration {iteration}') # Dbg |                                     # print(f'{si}:{i} iteration {iteration}') # Dbg | ||||||
| @ -241,8 +243,8 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
|             for f in frames_to_jump: |             for f in frames_to_jump: | ||||||
|                 wm.progress_update(f) # Pgs |                 wm.progress_update(f) # Pgs | ||||||
|                 scn.frame_set(f) |                 scn.frame_set(f) | ||||||
|                 # 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() | ||||||
| 
 | 
 | ||||||
| @ -256,7 +258,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
|                 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 = gp.data.layers.active.active_frame.strokes[-len(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)): | ||||||
|                     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) | ||||||
| @ -288,6 +290,45 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): | |||||||
|                     new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3)) |                     new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3)) | ||||||
|                     new_stroke.points.update() |                     new_stroke.points.update() | ||||||
| 
 | 
 | ||||||
|  |                      | ||||||
|  |                     ## TODO: Occlusion management | ||||||
|  |                     ## Tag occlusion on points for removal (need to create all substrokes from existing strokes) | ||||||
|  |                     # if settings.method == 'GEOMETRY': | ||||||
|  |                     #     ## WIP | ||||||
|  |                     #     occlusion_list = [False]*len(world_co_3d) | ||||||
|  |                     #     for i, nco in enumerate(world_co_3d): | ||||||
|  |                     #         vec_direction = nco - origin | ||||||
|  |                     #         ## Maybe distance need to be reduced by tiny segment... | ||||||
|  |                     #         n_hit, _hit_location, _normal, _n_face_index, n_object_hit, _matrix = scn.ray_cast(dg, origin, vec_direction, distance=vec_direction.length) | ||||||
|  |                     #         # if n_hit and n_object_hit != object_hit: # note: Arm could still hit from torso... | ||||||
|  |                     #         if n_hit: | ||||||
|  |                     #             # if there is a hit, it's occluded | ||||||
|  |                     #             # Occluded ! | ||||||
|  |                     #             occlusion_list[i] = True | ||||||
|  | 
 | ||||||
|  |                     #     if all(occlusion_list): | ||||||
|  |                     #         # all occluded, Just remove stroke (! Safer to reverse both list in zip iteration !) | ||||||
|  |                     #         gp.data.layers.active.active_frame.strokes.remove(new_stroke) | ||||||
|  | 
 | ||||||
|  |                     #     if any(occlusion_list): | ||||||
|  |                     #         # Create substroke according to indices in original stroke | ||||||
|  |                     #         for sublist in index_list_from_bools(occlusion_list): | ||||||
|  |                     #             ## Clear if only one isolated point ? | ||||||
|  |                     #             # if len(sublist) == 1: | ||||||
|  |                     #             #     continue     | ||||||
|  |                     #             ns = gp.data.layers.active.active_frame.strokes.new() | ||||||
|  |                     #             for elem in ('hardness', 'material_index', 'line_width'): | ||||||
|  |                     #                 setattr(ns, elem, getattr(new_strokes, elem)) | ||||||
|  | 
 | ||||||
|  |                     #             ns.points.add(len(sublist)) | ||||||
|  |                     #             for i, point_index in enumerate(sublist): | ||||||
|  |                     #                 for elem in ('uv_factor', 'uv_fill', 'uv_rotation', 'pressure', 'co', 'strength', 'vertex_color'): | ||||||
|  |                     #                     setattr(ns.points[i], elem, getattr(new_strokes.points[point_index], elem)) | ||||||
|  | 
 | ||||||
|  |                     #         ## Delete original stroke | ||||||
|  |                     #         gp.data.layers.active.active_frame.strokes.remove(new_stroke) | ||||||
|  |                              | ||||||
|  | 
 | ||||||
|             wm.progress_end() # Pgs |             wm.progress_end() # Pgs | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -19,6 +19,7 @@ class GP_PG_interpolate_settings(PropertyGroup): | |||||||
|         items= ( |         items= ( | ||||||
|             ('GEOMETRY', 'Geometry', 'Directly follow underlying geometry', 0), |             ('GEOMETRY', 'Geometry', 'Directly follow underlying geometry', 0), | ||||||
|             ('BONE', 'Bone', 'Pick an armature bone and follow it', 1), |             ('BONE', 'Bone', 'Pick an armature bone and follow it', 1), | ||||||
|  |             ('OBJECT', 'Object', 'Directly follow a specific object, even if occluded', 2), | ||||||
|         ), |         ), | ||||||
|         default='GEOMETRY', |         default='GEOMETRY', | ||||||
|         description='Select method for interpolating strokes' |         description='Select method for interpolating strokes' | ||||||
| @ -34,7 +35,6 @@ class GP_PG_interpolate_settings(PropertyGroup): | |||||||
|             \nThe value is as percentage of the camera width",  |             \nThe value is as percentage of the camera width",  | ||||||
|         default=0.05, precision=2, step=3, options={'HIDDEN'}) |         default=0.05, precision=2, step=3, options={'HIDDEN'}) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|     mode : EnumProperty( |     mode : EnumProperty( | ||||||
|         name='Mode', |         name='Mode', | ||||||
|         # Combined ?markers ? |         # Combined ?markers ? | ||||||
| @ -64,9 +64,10 @@ class GP_PG_interpolate_settings(PropertyGroup): | |||||||
|         description='Rig to use as target', |         description='Rig to use as target', | ||||||
|         type=bpy.types.Object) |         type=bpy.types.Object) | ||||||
|      |      | ||||||
|     # target_rig : StringProperty( |     target_object : PointerProperty( | ||||||
|     #     name='Rig', |         name='Object', | ||||||
|     #     description='Rig to use as target') |         description='Object to interpolate on', | ||||||
|  |         type=bpy.types.Object) | ||||||
|      |      | ||||||
|     target_bone : StringProperty( |     target_bone : StringProperty( | ||||||
|         name='Bone', |         name='Bone', | ||||||
|  | |||||||
							
								
								
									
										11
									
								
								ui.py
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								ui.py
									
									
									
									
									
								
							| @ -27,14 +27,6 @@ class GP_PT_interpolate(bpy.types.Panel): | |||||||
|         row.operator("gp.interpolate_stroke", text="", icon=prev_icon).next = False |         row.operator("gp.interpolate_stroke", text="", icon=prev_icon).next = False | ||||||
|         row.operator("gp.interpolate_stroke", text="", icon=next_icon).next = True |         row.operator("gp.interpolate_stroke", text="", icon=next_icon).next = True | ||||||
| 
 | 
 | ||||||
|         ## Old version to test (TODO: delete later) |  | ||||||
|         # col.label(text='Test Old Ops') |  | ||||||
|         # row = col.row(align=True) |  | ||||||
|         # row.scale_x = 3 |  | ||||||
|         # row.operator("gp.interpolate_stroke_simple", text="", icon=prev_icon).next = False |  | ||||||
|         # row.operator("gp.interpolate_stroke_simple", text="", icon=next_icon).next = True |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         col.prop(settings, 'use_animation', text='Animation') |         col.prop(settings, 'use_animation', text='Animation') | ||||||
|         col.prop(settings, 'method', text='Method') |         col.prop(settings, 'method', text='Method') | ||||||
| 
 | 
 | ||||||
| @ -51,6 +43,9 @@ class GP_PT_interpolate(bpy.types.Panel): | |||||||
|             col.prop(settings, 'target_collection', text='Collection') |             col.prop(settings, 'target_collection', text='Collection') | ||||||
|             col.prop(settings, 'search_range') |             col.prop(settings, 'search_range') | ||||||
|          |          | ||||||
|  |         elif settings.method == 'OBJECT': | ||||||
|  |             col.prop(settings, 'target_object', text='Object') | ||||||
|  | 
 | ||||||
|         col.separator() |         col.separator() | ||||||
|         col = layout.column(align=True) |         col = layout.column(align=True) | ||||||
|         row = col.row(align=True) |         row = col.row(align=True) | ||||||
|  | |||||||
							
								
								
									
										58
									
								
								utils.py
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								utils.py
									
									
									
									
									
								
							| @ -68,15 +68,8 @@ def search_square(point, factor=0.05, cam=None): | |||||||
|      |      | ||||||
|     return matrix_transform(plane, mat @ mat_scale) |     return matrix_transform(plane, mat @ mat_scale) | ||||||
| 
 | 
 | ||||||
| def ray_cast_point(point, origin, depsgraph): | def get_tri_from_face(hit_location, face_index, object_hit, depsgraph): | ||||||
|     ray = (point - origin)#.normalized() |  | ||||||
|     hit, hit_location, normal, face_index, object_hit, matrix = bpy.context.scene.ray_cast(depsgraph, origin, ray) |  | ||||||
|      |  | ||||||
|     if not hit: |  | ||||||
|         return None, None, None, None |  | ||||||
|      |  | ||||||
|     eval_ob = object_hit.evaluated_get(depsgraph) |     eval_ob = object_hit.evaluated_get(depsgraph) | ||||||
|      |  | ||||||
|     face = eval_ob.data.polygons[face_index] |     face = eval_ob.data.polygons[face_index] | ||||||
|     vertices = [eval_ob.data.vertices[i] for i in face.vertices] |     vertices = [eval_ob.data.vertices[i] for i in face.vertices] | ||||||
|     face_co = matrix_transform([v.co for v in vertices], eval_ob.matrix_world) |     face_co = matrix_transform([v.co for v in vertices], eval_ob.matrix_world) | ||||||
| @ -88,8 +81,37 @@ def ray_cast_point(point, origin, depsgraph): | |||||||
|         if intersect_point_tri(hit_location, *tri): |         if intersect_point_tri(hit_location, *tri): | ||||||
|             break |             break | ||||||
|      |      | ||||||
|  |     return tri, tri_indices | ||||||
|  | 
 | ||||||
|  | def ray_cast_point(point, origin, depsgraph): | ||||||
|  |     ray = (point - origin) | ||||||
|  |     hit, hit_location, normal, face_index, object_hit, matrix = bpy.context.scene.ray_cast(depsgraph, origin, ray) | ||||||
|  |      | ||||||
|  |     if not hit: | ||||||
|  |         return None, None, None, None | ||||||
|  |      | ||||||
|  |     tri, tri_indices = get_tri_from_face(hit_location, face_index, object_hit, depsgraph) | ||||||
|  | 
 | ||||||
|     return object_hit, np.array(hit_location), tri, tri_indices |     return object_hit, np.array(hit_location), tri, tri_indices | ||||||
| 
 | 
 | ||||||
|  | def obj_ray_cast(obj, point, origin, depsgraph): | ||||||
|  |     """Wrapper for ray casting that moves the ray into object space""" | ||||||
|  | 
 | ||||||
|  |     # get the ray relative to the object | ||||||
|  |     matrix_inv = obj.matrix_world.inverted() | ||||||
|  |     ray_origin_obj = matrix_inv @ origin # matrix_transform(origin, matrix_inv)  | ||||||
|  |     ray_target_obj = matrix_inv @ point # matrix_transform(point, matrix_inv)  | ||||||
|  |     ray_direction_obj = ray_target_obj - ray_origin_obj | ||||||
|  |     # cast the ray | ||||||
|  |     success, location, normal, face_index = obj.ray_cast(ray_origin_obj, ray_direction_obj, depsgraph=depsgraph) | ||||||
|  |     if not success: | ||||||
|  |         return None, None, None, None | ||||||
|  | 
 | ||||||
|  |     # Get hit location world_space | ||||||
|  |     hit_location = obj.matrix_world @ location | ||||||
|  |     tri, tri_indices = get_tri_from_face(hit_location, face_index, obj, depsgraph) | ||||||
|  |     return obj, np.array(hit_location), tri, tri_indices | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def empty_at(name='Empty', pos=(0,0,0), collection=None, type='PLAIN_AXES', size=1, show_name=False): | def empty_at(name='Empty', pos=(0,0,0), collection=None, type='PLAIN_AXES', size=1, show_name=False): | ||||||
|     ''' |     ''' | ||||||
| @ -425,6 +447,26 @@ def following_keys(forward=True, all_keys=False) -> list:# -> list[int] | list | | |||||||
|     return [int(new)] |     return [int(new)] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def index_list_from_bools(bool_list) -> list: | ||||||
|  |     '''Receive a list of boolean | ||||||
|  |     Return a list of sublists of indices where there is a continuity of True. | ||||||
|  |     e.g., [True, True, False, True] will return [[0,1][3]] | ||||||
|  |     ''' | ||||||
|  |     result = [] | ||||||
|  |     current_sublist = [] | ||||||
|  | 
 | ||||||
|  |     for i, value in enumerate(bool_list): | ||||||
|  |         if value: | ||||||
|  |             current_sublist.append(i) | ||||||
|  |         elif current_sublist: | ||||||
|  |             result.append(current_sublist) | ||||||
|  |             current_sublist = [] | ||||||
|  | 
 | ||||||
|  |     if current_sublist: | ||||||
|  |         result.append(current_sublist) | ||||||
|  | 
 | ||||||
|  |     return result | ||||||
|  | 
 | ||||||
| ## -- animation | ## -- animation | ||||||
| 
 | 
 | ||||||
| def is_animated(obj): | def is_animated(obj): | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user