From 77e3049d5dc2badebf4108f9d29320e1c0f5557d Mon Sep 17 00:00:00 2001 From: pullusb Date: Thu, 7 Dec 2023 12:16:18 +0100 Subject: [PATCH] animation mode to create all keys at once --- __init__.py | 2 +- interpolate_strokes/operators.py | 112 +++++++++++++++++++----------- interpolate_strokes/properties.py | 5 ++ ui.py | 1 + 4 files changed, 77 insertions(+), 43 deletions(-) diff --git a/__init__.py b/__init__.py index e236694..5e24ae2 100755 --- a/__init__.py +++ b/__init__.py @@ -1,7 +1,7 @@ bl_info = { "name": "gp interpolate", "author": "Christophe Seux, Samuel Bernou", - "version": (0, 1, 2), + "version": (0, 2, 0), "blender": (3, 6, 0), "location": "Sidebar > Gpencil Tab > Interpolate", "description": "Interpolate Grease pencil strokes over 3D", diff --git a/interpolate_strokes/operators.py b/interpolate_strokes/operators.py index 08b5d29..7b77866 100644 --- a/interpolate_strokes/operators.py +++ b/interpolate_strokes/operators.py @@ -20,13 +20,26 @@ from mathutils.geometry import (barycentric_transform, tessellate_polygon) -def following_key(forward=True): +def following_keys(forward=True, all_keys=True) -> list:# -> list[int] | list | None: + '''return a lsit of int or an empty list''' direction = 1 if forward else -1 cur_frame = bpy.context.scene.frame_current settings = bpy.context.scene.gp_interpo_settings if settings.mode == 'FRAME': - return cur_frame + (settings.padding * direction) + if all_keys: + scn = bpy.context.scene + if forward: + limit = scn.frame_preview_end if scn.use_preview_range else scn.frame_end + else: + limit = scn.frame_preview_start if scn.use_preview_range else scn.frame_start + + limit += direction # offset by one for limit to be in range + + return list(range(cur_frame, limit, settings.padding * direction)) + + else: + return [cur_frame + (settings.padding * direction)] elif settings.mode == 'GPKEY': layers = bpy.context.object.data.layers @@ -42,18 +55,26 @@ def following_key(forward=True): frames = [k.co.x for fc in arm.animation_data.action.fcurves for k in fc.keyframe_points] if not frames: - return + return [] + + # Sort frames (invert if looking backward) + frames.sort(reversed=not forward) + + if all_keys: + frames = list(set(frames)) + if forward: + frame_list = [int(f) for f in frames if f > cur_frame] + else: + frame_list = [int(f) for f in frames if f < cur_frame] + return frame_list - frames.sort() if forward: new = next((f for f in frames if f > cur_frame), None) else: - below = [f for f in frames if f < cur_frame] - if not below: - return - new = below[-1] - - return int(new) + new = next((f for f in frames if f < cur_frame), None) + if new is None: + return [] + return [int(new)] ## TODO: add bake animation to empty for later GP layer parenting @@ -88,9 +109,9 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): # auto_key_status = context.tool_settings.use_keyframe_insert_auto # context.tool_settings.use_keyframe_insert_auto = True - ## Determine on what key to jump - frame_to_jump = following_key(forward=self.next) - if frame_to_jump is None: + ## Determine on what key/keys to jump + frames_to_jump = following_keys(forward=self.next, all_keys=settings.use_animation) + if not len(frames_to_jump): self.report({'WARNING'}, 'No keyframe available in this direction') return {'CANCELLED'} @@ -214,38 +235,45 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): # Copy stroke selection, jump frame and paste bpy.ops.gpencil.copy() - scn.frame_set(frame_to_jump) - plan_co, plane_no = get_gp_draw_plane(gp) - bpy.ops.gpencil.paste() - if settings.method == 'BONE': - bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone), - arm=settings.target_rig, - set_rotation=settings.use_bone_rotation, - mesh=True) - - dg = bpy.context.evaluated_depsgraph_get() - matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted() - new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):] + wm = bpy.context.window_manager # Pgs - for new_stroke, stroke_data in zip(new_strokes, strokes_data): - world_co_3d = [] # np.array(len()dtype='float64')#np. - for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data: - eval_ob = object_hit.evaluated_get(dg) - tri_b = [eval_ob.data.vertices[i].co for i in tri_indices] - tri_b = matrix_transform(tri_b, eval_ob.matrix_world) + wm.progress_begin(frames_to_jump[0], frames_to_jump[-1]) # Pgs + for f in frames_to_jump: + wm.progress_update(f) # Pgs + scn.frame_set(f) + plan_co, plane_no = get_gp_draw_plane(gp) + bpy.ops.gpencil.paste() + + if settings.method == 'BONE': + bone_plane = plane_on_bone(settings.target_rig.pose.bones.get(settings.target_bone), + arm=settings.target_rig, + set_rotation=settings.use_bone_rotation, + mesh=True) - new_loc = barycentric_transform(hit_location, *tri_a, *tri_b) - world_co_3d.append(new_loc) + dg = bpy.context.evaluated_depsgraph_get() + matrix_inv = np.array(gp.matrix_world.inverted(), dtype='float64')#.inverted() + new_strokes = gp.data.layers.active.active_frame.strokes[-len(strokes_data):] - # Reproject on plane - new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d] - new_local_co_3d = matrix_transform(new_world_co_3d, matrix_inv) + for new_stroke, stroke_data in zip(new_strokes, strokes_data): + world_co_3d = [] # np.array(len()dtype='float64')#np. + for stroke, point_co, object_hit, hit_location, tri_a, tri_indices in stroke_data: + eval_ob = object_hit.evaluated_get(dg) + tri_b = [eval_ob.data.vertices[i].co for i in tri_indices] + tri_b = matrix_transform(tri_b, eval_ob.matrix_world) + + new_loc = barycentric_transform(hit_location, *tri_a, *tri_b) + world_co_3d.append(new_loc) - nb_points = len(new_stroke.points) - new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3)) - new_stroke.points.update() + # Reproject on plane + new_world_co_3d = [intersect_line_plane(origin, p, plan_co, plane_no) for p in world_co_3d] + new_local_co_3d = matrix_transform(new_world_co_3d, matrix_inv) + nb_points = len(new_stroke.points) + new_stroke.points.foreach_set('co', new_local_co_3d.reshape(nb_points*3)) + new_stroke.points.update() + + wm.progress_end() # Pgs ## Reset autokey status # context.tool_settings.use_keyframe_insert_auto = auto_key_status # (Done in context manager) @@ -284,8 +312,8 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): context.tool_settings.use_keyframe_insert_auto = True ## Determine on what key to jump - frame_to_jump = following_key(forward=self.next) - if frame_to_jump is None: + frames_to_jump = following_keys(forward=self.next) + if frames_to_jump is None: self.report({'WARNING'}, 'No keyframe available in this direction') return {'CANCELLED'} @@ -376,7 +404,7 @@ class GP_OT_interpolate_stroke(bpy.types.Operator): bpy.ops.gpencil.copy() - scn.frame_set(frame_to_jump) + scn.frame_set(frames_to_jump) plan_co, plane_no = get_gp_draw_plane(gp) diff --git a/interpolate_strokes/properties.py b/interpolate_strokes/properties.py index d2488c7..24c15b5 100644 --- a/interpolate_strokes/properties.py +++ b/interpolate_strokes/properties.py @@ -24,6 +24,11 @@ class GP_PG_interpolate_settings(PropertyGroup): description='Select method for interpolating strokes' ) + use_animation : BoolProperty( + name='Animatation', + default=True, + description='Apply the interpolation on the remaining range') + search_range : FloatProperty( name="Search Range", description="Search range size when points are out of mesh", diff --git a/ui.py b/ui.py index 9a7e24e..6b773f2 100755 --- a/ui.py +++ b/ui.py @@ -30,6 +30,7 @@ class GP_PT_interpolate(bpy.types.Panel): col.prop(settings, 'method', text='Method') + col.prop(settings, 'use_animation', text='Animation') if settings.method == 'BONE': col = layout.column(align=True)