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
parent
429179d936
commit
4fadf35163
|
@ -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
|
||||||
|
|
148
OP_helpers.py
148
OP_helpers.py
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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": "",
|
||||||
|
|
Loading…
Reference in New Issue