add step select gp frames feature

2.5.0

- added: Animation manager new button `Frame Select Step` (sort of a checker deselect, but in GP dopesheet)
gpv2 v2.5.0
pullusb 2024-01-24 17:54:14 +01:00
parent 429179d936
commit 4fadf35163
4 changed files with 138 additions and 19 deletions

View File

@ -1,5 +1,9 @@
# Changelog # Changelog
2.5.0
- added: Animation manager new button `Frame Select Step` (sort of a checker deselect, but in GP dopesheet)
2.4.0 2.4.0
- changed: Batch reproject consider camera movement and is almost 8x faster - changed: Batch reproject consider camera movement and is almost 8x faster

View File

@ -1,15 +1,17 @@
from time import ctime
import bpy import bpy
import mathutils import mathutils
from mathutils import Vector#, Matrix
from pathlib import Path
import math import math
from time import ctime
from mathutils import Vector #, Matrix
from pathlib import Path
from math import radians from math import radians
from bpy.types import Operator
from .view3d_utils import View3D from .view3d_utils import View3D
from . import utils from . import utils
class GPTB_OT_copy_text(Operator):
class GPTB_OT_copy_text(bpy.types.Operator):
bl_idname = "wm.copytext" bl_idname = "wm.copytext"
bl_label = "Copy To Clipboard" bl_label = "Copy To Clipboard"
bl_description = "Insert passed text to clipboard" bl_description = "Insert passed text to clipboard"
@ -23,7 +25,7 @@ class GPTB_OT_copy_text(bpy.types.Operator):
self.report({'INFO'}, mess) self.report({'INFO'}, mess)
return {"FINISHED"} return {"FINISHED"}
class GPTB_OT_flipx_view(bpy.types.Operator): class GPTB_OT_flipx_view(Operator):
bl_idname = "view3d.camera_mirror_flipx" bl_idname = "view3d.camera_mirror_flipx"
bl_label = "Cam Mirror Flipx" bl_label = "Cam Mirror Flipx"
bl_description = "Invert X scale on camera to flip image horizontally" bl_description = "Invert X scale on camera to flip image horizontally"
@ -38,7 +40,7 @@ class GPTB_OT_flipx_view(bpy.types.Operator):
context.scene.camera.scale.x *= -1 context.scene.camera.scale.x *= -1
return {"FINISHED"} return {"FINISHED"}
class GPTB_OT_view_camera_frame_fit(bpy.types.Operator): class GPTB_OT_view_camera_frame_fit(Operator):
bl_idname = "view3d.view_camera_frame_fit" bl_idname = "view3d.view_camera_frame_fit"
bl_label = "View Fit" bl_label = "View Fit"
bl_description = "Fit the camera in view (view 1:1)" bl_description = "Fit the camera in view (view 1:1)"
@ -67,7 +69,7 @@ class GPTB_OT_view_camera_frame_fit(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class GPTB_OT_rename_data_from_obj(bpy.types.Operator): class GPTB_OT_rename_data_from_obj(Operator):
bl_idname = "gp.rename_data_from_obj" bl_idname = "gp.rename_data_from_obj"
bl_label = "Rename GP From Object" bl_label = "Rename GP From Object"
bl_description = "Rename the GP datablock with the same name as the object" bl_description = "Rename the GP datablock with the same name as the object"
@ -148,7 +150,7 @@ def get_gp_alignement_vector(context):
elif orient == 'CURSOR': elif orient == 'CURSOR':
return Vector((0,0,1))#.rotate(context.scene.cursor.matrix) return Vector((0,0,1))#.rotate(context.scene.cursor.matrix)
class GPTB_OT_draw_cam(bpy.types.Operator): class GPTB_OT_draw_cam(Operator):
bl_idname = "gp.draw_cam_switch" bl_idname = "gp.draw_cam_switch"
bl_label = "Draw Cam Switch" bl_label = "Draw Cam Switch"
bl_description = "switch between main camera and draw (manipulate) camera" bl_description = "switch between main camera and draw (manipulate) camera"
@ -276,7 +278,7 @@ class GPTB_OT_draw_cam(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class GPTB_OT_set_view_as_cam(bpy.types.Operator): class GPTB_OT_set_view_as_cam(Operator):
bl_idname = "gp.set_view_as_cam" bl_idname = "gp.set_view_as_cam"
bl_label = "Cam At View" bl_label = "Cam At View"
bl_description = "Place the active camera at current viewpoint, parent to active object. (need to be out of camera)" bl_description = "Place the active camera at current viewpoint, parent to active object. (need to be out of camera)"
@ -318,7 +320,7 @@ class GPTB_OT_set_view_as_cam(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
class GPTB_OT_reset_cam_rot(bpy.types.Operator): class GPTB_OT_reset_cam_rot(Operator):
bl_idname = "gp.reset_cam_rot" bl_idname = "gp.reset_cam_rot"
bl_label = "Reset Rotation" bl_label = "Reset Rotation"
bl_description = "Reset rotation of the draw manipulation camera" bl_description = "Reset rotation of the draw manipulation camera"
@ -396,7 +398,7 @@ class GPTB_OT_reset_cam_rot(bpy.types.Operator):
context.space_data.region_3d.view_camera_offset = new_cam_offset context.space_data.region_3d.view_camera_offset = new_cam_offset
return {"FINISHED"} return {"FINISHED"}
class GPTB_OT_toggle_mute_animation(bpy.types.Operator): class GPTB_OT_toggle_mute_animation(Operator):
bl_idname = "gp.toggle_mute_animation" bl_idname = "gp.toggle_mute_animation"
bl_label = "Toggle Animation Mute" bl_label = "Toggle Animation Mute"
bl_description = "Enable/Disable animation evaluation\n(shift+clic to affect selection only)" bl_description = "Enable/Disable animation evaluation\n(shift+clic to affect selection only)"
@ -451,7 +453,7 @@ class GPTB_OT_toggle_mute_animation(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class GPTB_OT_toggle_hide_gp_modifier(bpy.types.Operator): class GPTB_OT_toggle_hide_gp_modifier(Operator):
bl_idname = "gp.toggle_hide_gp_modifier" bl_idname = "gp.toggle_hide_gp_modifier"
bl_label = "Toggle Modifier Hide" bl_label = "Toggle Modifier Hide"
bl_description = "Show/Hide viewport on GP objects modifier\ bl_description = "Show/Hide viewport on GP objects modifier\
@ -481,7 +483,7 @@ class GPTB_OT_toggle_hide_gp_modifier(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class GPTB_OT_list_disabled_anims(bpy.types.Operator): class GPTB_OT_list_disabled_anims(Operator):
bl_idname = "gp.list_disabled_anims" bl_idname = "gp.list_disabled_anims"
bl_label = "List Disabled Anims" bl_label = "List Disabled Anims"
bl_description = "List disabled animations channels in scene. (shit+clic to list only on seleciton)" bl_description = "List disabled animations channels in scene. (shit+clic to list only on seleciton)"
@ -546,7 +548,7 @@ class GPTB_OT_list_disabled_anims(bpy.types.Operator):
## TODO presets are still not used... need to make a custom preset save/remove/quickload manager to be efficient (UIlist ?) ## TODO presets are still not used... need to make a custom preset save/remove/quickload manager to be efficient (UIlist ?)
class GPTB_OT_overlay_presets(bpy.types.Operator): class GPTB_OT_overlay_presets(Operator):
bl_idname = "gp.overlay_presets" bl_idname = "gp.overlay_presets"
bl_label = "Overlay presets" bl_label = "Overlay presets"
bl_description = "Overlay save/load presets for showing only whats needed" bl_description = "Overlay save/load presets for showing only whats needed"
@ -601,7 +603,7 @@ class GPTB_OT_overlay_presets(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class GPTB_OT_clear_active_frame(bpy.types.Operator): class GPTB_OT_clear_active_frame(Operator):
bl_idname = "gp.clear_active_frame" bl_idname = "gp.clear_active_frame"
bl_label = "Clear Active Frame" bl_label = "Clear Active Frame"
bl_description = "Delete all strokes in active frames" bl_description = "Delete all strokes in active frames"
@ -633,7 +635,7 @@ class GPTB_OT_clear_active_frame(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
class GPTB_OT_check_canvas_alignement(bpy.types.Operator): class GPTB_OT_check_canvas_alignement(Operator):
bl_idname = "gp.check_canvas_alignement" bl_idname = "gp.check_canvas_alignement"
bl_label = "Check Canvas Alignement" bl_label = "Check Canvas Alignement"
bl_description = "Check if view is aligned to canvas\nWarn if the drawing angle to surface is too high\nThere can be some error margin" bl_description = "Check if view is aligned to canvas\nWarn if the drawing angle to surface is too high\nThere can be some error margin"
@ -663,7 +665,116 @@ class GPTB_OT_check_canvas_alignement(bpy.types.Operator):
# self.report({ret}, message) # self.report({ret}, message)
return {'FINISHED'} return {'FINISHED'}
class GPTB_OT_open_addon_prefs(bpy.types.Operator): class GPTB_OT_step_select_frames(Operator):
bl_idname = "gptb.step_select_frames"
bl_label = "Step Select Frame"
bl_description = "Select frames by a step frame value"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
start : bpy.props.IntProperty(name='Start Frame',
description='Start frame of the step animation',
default=100)
step : bpy.props.IntProperty(name='Step',
description='Step of the frame, value of 2 select one frame on two',
default=2,
min=2)
strict : bpy.props.BoolProperty(name='Strict',
description='Strictly select step frame from start to scene end range\
\nElse reset step when a gap exsits already',
default=False)
# TODO: add option to start at cursor (True default)
def invoke(self, context, execute):
## list frame to keep
return context.window_manager.invoke_props_dialog(self, width=450)
# return self.execute(context)
def draw(self, context):
layout = self.layout
col = layout.column()
col.prop(self, 'step')
col.prop(self, 'strict')
if self.strict:
col.prop(self, 'start')
## helper (need more work)
# col.separator()
# range_string = f"{', '.join(numbers[:3])} ... {', '.join(numbers[-3:])}"
# col.label(text=f'Will keep {range_string}')
if not self.strict:
col.label(text=f'Each gap will be considered a new step start', icon='INFO')
def execute(self, context):
numbers = [i for i in range(self.start, context.scene.frame_end + 1, self.step)]
self.to_select = numbers
## Negative switch : list frames to remove
self.to_select = [i for i in range(self.start, context.scene.frame_end + 1) if i not in numbers]
gp = context.object.data
## Get frame summary (reset start after each existing gaps)
key_summary = list(set([f.frame_number for l in gp.layers for f in l.frames]))
key_summary.sort()
print('key summary: ', key_summary)
start = key_summary[0]
if self.strict:
to_select = self.to_select
else:
to_select = []
prev = None
for k in key_summary:
print(k, prev)
if prev is not None and k != prev + 1:
## this is a gap ! new start
prev = start = k
# print('new start', start)
continue
new_range = [i for i in range(start, key_summary[-1] + 1, self.step)]
# print('new_range: ', new_range)
if k not in new_range:
to_select.append(k)
prev = k
## deselect all
for l in gp.layers:
for f in l.frames:
f.select = False
print('To select:', to_select)
gct = 0
for i in to_select:
ct = 0
for l in gp.layers:
frame = next((f for f in l.frames if f.frame_number == i), None)
if not frame:
continue
## Select instead of remove
frame.select = True
## Optionnally remove frames ?
# l.frames.remove(frame)
ct += 1
# print(f'{i}: Selected {ct} frame(s)')
gct += ct
self.report({'INFO'}, f'Selected {gct} frames')
return {"FINISHED"}
class GPTB_OT_open_addon_prefs(Operator):
bl_idname = "gptb.open_addon_prefs" bl_idname = "gptb.open_addon_prefs"
bl_label = "Open Addon Prefs" bl_label = "Open Addon Prefs"
bl_description = "Open user preferences window in addon tab and prefill the search with addon name" bl_description = "Open user preferences window in addon tab and prefill the search with addon name"
@ -686,6 +797,7 @@ GPTB_OT_toggle_hide_gp_modifier,
GPTB_OT_list_disabled_anims, GPTB_OT_list_disabled_anims,
GPTB_OT_clear_active_frame, GPTB_OT_clear_active_frame,
GPTB_OT_check_canvas_alignement, GPTB_OT_check_canvas_alignement,
GPTB_OT_step_select_frames,
GPTB_OT_open_addon_prefs, GPTB_OT_open_addon_prefs,
) )

View File

@ -242,6 +242,9 @@ class GPTB_PT_anim_manager(Panel):
row.operator('gp.toggle_hide_gp_modifier', text='ON', icon=on_icon).show = True row.operator('gp.toggle_hide_gp_modifier', text='ON', icon=on_icon).show = True
row.operator('gp.toggle_hide_gp_modifier', text='OFF', icon=off_icon).show = False row.operator('gp.toggle_hide_gp_modifier', text='OFF', icon=off_icon).show = False
## Step Select Frames
col.operator('gptb.step_select_frames')
## Follow curve path ## Follow curve path
col = col.column() col = col.column()
row = col.row(align=True) row = col.row(align=True)

View File

@ -4,7 +4,7 @@ bl_info = {
"name": "GP toolbox", "name": "GP toolbox",
"description": "Tool set for Grease Pencil in animation production", "description": "Tool set for Grease Pencil in animation production",
"author": "Samuel Bernou, Christophe Seux", "author": "Samuel Bernou, Christophe Seux",
"version": (2, 4, 0), "version": (2, 5, 0),
"blender": (3, 0, 0), "blender": (3, 0, 0),
"location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties", "location": "Sidebar (N menu) > Gpencil > Toolbox / Gpencil properties",
"warning": "", "warning": "",