589 lines
23 KiB
Python
589 lines
23 KiB
Python
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
bl_info = {
|
|
"name": "AnimAll",
|
|
"author": "Daniel Salazar <zanqdo@gmail.com>",
|
|
"version": (0, 8, 3),
|
|
"blender": (2, 80, 0),
|
|
"location": "3D View > Toolbox > Animation tab > AnimAll",
|
|
"description": "Allows animation of mesh, lattice, curve and surface data",
|
|
"warning": "",
|
|
"doc_url": "{BLENDER_MANUAL_URL}/addons/animation/animall.html",
|
|
"category": "Animation",
|
|
}
|
|
|
|
"""
|
|
Thanks to Campbell Barton and Joshua Leung for hes API additions and fixes
|
|
Daniel 'ZanQdo' Salazar
|
|
"""
|
|
|
|
import bpy
|
|
from bpy.types import (
|
|
Operator,
|
|
Panel,
|
|
AddonPreferences,
|
|
)
|
|
from bpy.props import (
|
|
BoolProperty,
|
|
StringProperty,
|
|
)
|
|
from bpy.app.handlers import persistent
|
|
|
|
|
|
# Property Definitions
|
|
class AnimallProperties(bpy.types.PropertyGroup):
|
|
key_selected: BoolProperty(
|
|
name="Selected Only",
|
|
description="Insert keyframes only on selected elements",
|
|
default=True
|
|
)
|
|
key_shape: BoolProperty(
|
|
name="Shape",
|
|
description="Insert keyframes on active Shape Key layer",
|
|
default=False
|
|
)
|
|
key_uvs: BoolProperty(
|
|
name="UVs",
|
|
description="Insert keyframes on active UV coordinates",
|
|
default=False
|
|
)
|
|
key_ebevel: BoolProperty(
|
|
name="E-Bevel",
|
|
description="Insert keyframes on edge bevel weight",
|
|
default=False
|
|
)
|
|
key_vbevel: BoolProperty(
|
|
name="V-Bevel",
|
|
description="Insert keyframes on vertex bevel weight",
|
|
default=False
|
|
)
|
|
key_crease: BoolProperty(
|
|
name="Crease",
|
|
description="Insert keyframes on edge creases",
|
|
default=False
|
|
)
|
|
key_vcols: BoolProperty(
|
|
name="V-Cols",
|
|
description="Insert keyframes on active Vertex Color values",
|
|
default=False
|
|
)
|
|
key_vgroups: BoolProperty(
|
|
name="V-groups",
|
|
description="Insert keyframes on active Vertex group values",
|
|
default=False
|
|
)
|
|
key_points: BoolProperty(
|
|
name="Points",
|
|
description="Insert keyframes on point locations",
|
|
default=False
|
|
)
|
|
key_radius: BoolProperty(
|
|
name="Radius",
|
|
description="Insert keyframes on point radius (Shrink/Fatten)",
|
|
default=False
|
|
)
|
|
key_tilt: BoolProperty(
|
|
name="Tilt",
|
|
description="Insert keyframes on point tilt",
|
|
default=False
|
|
)
|
|
|
|
|
|
# Utility functions
|
|
|
|
def refresh_ui_keyframes():
|
|
try:
|
|
for area in bpy.context.screen.areas:
|
|
if area.type in ('TIMELINE', 'GRAPH_EDITOR', 'DOPESHEET_EDITOR'):
|
|
area.tag_redraw()
|
|
except:
|
|
pass
|
|
|
|
|
|
def insert_key(data, key, group=None):
|
|
try:
|
|
if group is not None:
|
|
data.keyframe_insert(key, group=group)
|
|
else:
|
|
data.keyframe_insert(key)
|
|
except:
|
|
pass
|
|
|
|
|
|
def delete_key(data, key):
|
|
try:
|
|
data.keyframe_delete(key)
|
|
except:
|
|
pass
|
|
|
|
|
|
# GUI (Panel)
|
|
|
|
class VIEW3D_PT_animall(Panel):
|
|
bl_space_type = 'VIEW_3D'
|
|
bl_region_type = 'UI'
|
|
bl_category = "Animate"
|
|
bl_label = 'AnimAll'
|
|
bl_options = {'DEFAULT_CLOSED'}
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
return context.active_object and context.active_object.type in {'MESH', 'LATTICE', 'CURVE', 'SURFACE'}
|
|
|
|
def draw(self, context):
|
|
obj = context.active_object
|
|
animall_properties = context.window_manager.animall_properties
|
|
|
|
layout = self.layout
|
|
col = layout.column(align=True)
|
|
row = col.row()
|
|
row.prop(animall_properties, "key_selected")
|
|
col.separator()
|
|
|
|
row = col.row()
|
|
|
|
if obj.type == 'LATTICE':
|
|
row.prop(animall_properties, "key_points")
|
|
row.prop(animall_properties, "key_shape")
|
|
|
|
elif obj.type == 'MESH':
|
|
row.prop(animall_properties, "key_points")
|
|
row.prop(animall_properties, "key_shape")
|
|
row = col.row()
|
|
row.prop(animall_properties, "key_ebevel")
|
|
row.prop(animall_properties, "key_vbevel")
|
|
row = col.row()
|
|
row.prop(animall_properties, "key_crease")
|
|
row.prop(animall_properties, "key_uvs")
|
|
row = col.row()
|
|
row.prop(animall_properties, "key_vcols")
|
|
row.prop(animall_properties, "key_vgroups")
|
|
|
|
elif obj.type == 'CURVE':
|
|
row.prop(animall_properties, "key_points")
|
|
row.prop(animall_properties, "key_shape")
|
|
row = col.row()
|
|
row.prop(animall_properties, "key_radius")
|
|
row.prop(animall_properties, "key_tilt")
|
|
|
|
elif obj.type == 'SURFACE':
|
|
row.prop(animall_properties, "key_points")
|
|
row.prop(animall_properties, "key_shape")
|
|
row = col.row()
|
|
row.prop(animall_properties, "key_radius")
|
|
row.prop(animall_properties, "key_tilt")
|
|
|
|
layout.separator()
|
|
row = layout.row(align=True)
|
|
row.operator("anim.insert_keyframe_animall", icon="KEY_HLT")
|
|
row.operator("anim.delete_keyframe_animall", icon="KEY_DEHLT")
|
|
row = layout.row()
|
|
row.operator("anim.clear_animation_animall", icon="X")
|
|
|
|
if animall_properties.key_shape:
|
|
shape_key = obj.active_shape_key
|
|
shape_key_index = obj.active_shape_key_index
|
|
|
|
split = layout.split()
|
|
row = split.row()
|
|
|
|
if shape_key_index > 0:
|
|
row.label(text=shape_key.name, icon="SHAPEKEY_DATA")
|
|
row.prop(shape_key, "value", text="")
|
|
row.prop(obj, "show_only_shape_key", text="")
|
|
if shape_key.value < 1:
|
|
row = layout.row()
|
|
row.label(text='Maybe set "%s" to 1.0?' % shape_key.name, icon="INFO")
|
|
elif shape_key:
|
|
row.label(text="Cannot key on Basis Shape", icon="ERROR")
|
|
else:
|
|
row.label(text="No active Shape Key", icon="ERROR")
|
|
|
|
if animall_properties.key_points and animall_properties.key_shape:
|
|
row = layout.row()
|
|
row.label(text='"Points" and "Shape" are redundant?', icon="INFO")
|
|
|
|
|
|
class ANIM_OT_insert_keyframe_animall(Operator):
|
|
bl_label = "Insert"
|
|
bl_idname = "anim.insert_keyframe_animall"
|
|
bl_description = "Insert a Keyframe"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def execute(op, context):
|
|
animall_properties = context.window_manager.animall_properties
|
|
|
|
if context.mode == 'OBJECT':
|
|
objects = context.selected_objects
|
|
else:
|
|
objects = context.objects_in_mode_unique_data[:]
|
|
|
|
mode = context.object.mode
|
|
|
|
# Separate loop for lattices, curves and surfaces, since keyframe insertion
|
|
# has to happen in Edit Mode, otherwise points move back upon mode switch...
|
|
# (except for curve shape keys)
|
|
for obj in [o for o in objects if o.type in {'CURVE', 'SURFACE', 'LATTICE'}]:
|
|
data = obj.data
|
|
|
|
if obj.type == 'LATTICE':
|
|
if animall_properties.key_shape:
|
|
if obj.active_shape_key_index > 0:
|
|
sk_name = obj.active_shape_key.name
|
|
for p_i, point in enumerate(obj.active_shape_key.data):
|
|
if not animall_properties.key_selected or data.points[p_i].select:
|
|
insert_key(point, 'co', group="%s Point %s" % (sk_name, p_i))
|
|
|
|
if animall_properties.key_points:
|
|
for p_i, point in enumerate(data.points):
|
|
if not animall_properties.key_selected or point.select:
|
|
insert_key(point, 'co_deform', group="Point %s" % p_i)
|
|
|
|
else:
|
|
for s_i, spline in enumerate(data.splines):
|
|
if spline.type == 'BEZIER':
|
|
for v_i, CV in enumerate(spline.bezier_points):
|
|
if (not animall_properties.key_selected
|
|
or CV.select_control_point
|
|
or CV.select_left_handle
|
|
or CV.select_right_handle):
|
|
if animall_properties.key_points:
|
|
insert_key(CV, 'co', group="Spline %s CV %s" % (s_i, v_i))
|
|
insert_key(CV, 'handle_left', group="Spline %s CV %s" % (s_i, v_i))
|
|
insert_key(CV, 'handle_right', group="Spline %s CV %s" % (s_i, v_i))
|
|
|
|
if animall_properties.key_radius:
|
|
insert_key(CV, 'radius', group="Spline %s CV %s" % (s_i, v_i))
|
|
|
|
if animall_properties.key_tilt:
|
|
insert_key(CV, 'tilt', group="Spline %s CV %s" % (s_i, v_i))
|
|
|
|
elif spline.type in ('POLY', 'NURBS'):
|
|
for v_i, CV in enumerate(spline.points):
|
|
if not animall_properties.key_selected or CV.select:
|
|
if animall_properties.key_points:
|
|
insert_key(CV, 'co', group="Spline %s CV %s" % (s_i, v_i))
|
|
|
|
if animall_properties.key_radius:
|
|
insert_key(CV, 'radius', group="Spline %s CV %s" % (s_i, v_i))
|
|
|
|
if animall_properties.key_tilt:
|
|
insert_key(CV, 'tilt', group="Spline %s CV %s" % (s_i, v_i))
|
|
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
|
|
for obj in [o for o in objects if o.type in {'MESH', 'CURVE', 'SURFACE'}]:
|
|
data = obj.data
|
|
if obj.type == 'MESH':
|
|
if animall_properties.key_points:
|
|
for v_i, vert in enumerate(data.vertices):
|
|
if not animall_properties.key_selected or vert.select:
|
|
insert_key(vert, 'co', group="Vertex %s" % v_i)
|
|
|
|
if animall_properties.key_vbevel:
|
|
for v_i, vert in enumerate(data.vertices):
|
|
if not animall_properties.key_selected or vert.select:
|
|
insert_key(vert, 'bevel_weight', group="Vertex %s" % v_i)
|
|
|
|
if animall_properties.key_vgroups:
|
|
for v_i, vert in enumerate(data.vertices):
|
|
if not animall_properties.key_selected or vert.select:
|
|
for group in vert.groups:
|
|
insert_key(group, 'weight', group="Vertex %s" % v_i)
|
|
|
|
if animall_properties.key_ebevel:
|
|
for e_i, edge in enumerate(data.edges):
|
|
if not animall_properties.key_selected or edge.select:
|
|
insert_key(edge, 'bevel_weight', group="Edge %s" % e_i)
|
|
|
|
if animall_properties.key_crease:
|
|
for e_i, edge in enumerate(data.edges):
|
|
if not animall_properties.key_selected or edge.select:
|
|
insert_key(edge, 'crease', group="Edge %s" % e_i)
|
|
|
|
if animall_properties.key_shape:
|
|
if obj.active_shape_key_index > 0:
|
|
sk_name = obj.active_shape_key.name
|
|
for v_i, vert in enumerate(obj.active_shape_key.data):
|
|
if not animall_properties.key_selected or data.vertices[v_i].select:
|
|
insert_key(vert, 'co', group="%s Vertex %s" % (sk_name, v_i))
|
|
|
|
if animall_properties.key_uvs:
|
|
if data.uv_layers.active is not None:
|
|
for uv_i, uv in enumerate(data.uv_layers.active.data):
|
|
if not animall_properties.key_selected or uv.select:
|
|
insert_key(uv, 'uv', group="UV layer %s" % uv_i)
|
|
|
|
if animall_properties.key_vcols:
|
|
for v_col_layer in data.vertex_colors:
|
|
if v_col_layer.active: # only insert in active VCol layer
|
|
for v_i, data in enumerate(v_col_layer.data):
|
|
insert_key(data, 'color', group="Loop %s" % v_i)
|
|
|
|
elif obj.type in {'CURVE', 'SURFACE'}:
|
|
# Shape key keys have to be inserted in object mode for curves...
|
|
if animall_properties.key_shape:
|
|
sk_name = obj.active_shape_key.name
|
|
global_spline_index = 0 # numbering for shape keys, which have flattened indices
|
|
for s_i, spline in enumerate(data.splines):
|
|
if spline.type == 'BEZIER':
|
|
for v_i, CV in enumerate(spline.bezier_points):
|
|
if (not animall_properties.key_selected
|
|
or CV.select_control_point
|
|
or CV.select_left_handle
|
|
or CV.select_right_handle):
|
|
if obj.active_shape_key_index > 0:
|
|
CV = obj.active_shape_key.data[global_spline_index]
|
|
insert_key(CV, 'co', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
insert_key(CV, 'handle_left', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
insert_key(CV, 'handle_right', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
insert_key(CV, 'radius', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
insert_key(CV, 'tilt', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
global_spline_index += 1
|
|
|
|
elif spline.type in ('POLY', 'NURBS'):
|
|
for v_i, CV in enumerate(spline.points):
|
|
if not animall_properties.key_selected or CV.select:
|
|
if obj.active_shape_key_index > 0:
|
|
CV = obj.active_shape_key.data[global_spline_index]
|
|
insert_key(CV, 'co', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
insert_key(CV, 'radius', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
insert_key(CV, 'tilt', group="%s Spline %s CV %s" % (sk_name, s_i, v_i))
|
|
global_spline_index += 1
|
|
|
|
bpy.ops.object.mode_set(mode=mode)
|
|
refresh_ui_keyframes()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ANIM_OT_delete_keyframe_animall(Operator):
|
|
bl_label = "Delete"
|
|
bl_idname = "anim.delete_keyframe_animall"
|
|
bl_description = "Delete a Keyframe"
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
|
|
def execute(op, context):
|
|
animall_properties = context.window_manager.animall_properties
|
|
|
|
if context.mode == 'OBJECT':
|
|
objects = context.selected_objects
|
|
else:
|
|
objects = context.objects_in_mode_unique_data[:]
|
|
|
|
mode = context.object.mode
|
|
|
|
for obj in objects:
|
|
data = obj.data
|
|
if obj.type == 'MESH':
|
|
if animall_properties.key_points:
|
|
for vert in data.vertices:
|
|
if not animall_properties.key_selected or vert.select:
|
|
delete_key(vert, 'co')
|
|
|
|
if animall_properties.key_vbevel:
|
|
for vert in data.vertices:
|
|
if not animall_properties.key_selected or vert.select:
|
|
delete_key(vert, 'bevel_weight')
|
|
|
|
if animall_properties.key_vgroups:
|
|
for vert in data.vertices:
|
|
if not animall_properties.key_selected or vert.select:
|
|
for group in vert.groups:
|
|
delete_key(group, 'weight')
|
|
|
|
if animall_properties.key_ebevel:
|
|
for edge in data.edges:
|
|
if not animall_properties.key_selected or edge.select:
|
|
delete_key(edge, 'bevel_weight')
|
|
|
|
if animall_properties.key_crease:
|
|
for edge in data.edges:
|
|
if not animall_properties.key_selected or vert.select:
|
|
delete_key(edge, 'crease')
|
|
|
|
if animall_properties.key_shape:
|
|
if obj.active_shape_key:
|
|
for v_i, vert in enumerate(obj.active_shape_key.data):
|
|
if not animall_properties.key_selected or data.vertices[v_i].select:
|
|
delete_key(vert, 'co')
|
|
|
|
if animall_properties.key_uvs:
|
|
if data.uv_layers.active is not None:
|
|
for uv in data.uv_layers.active.data:
|
|
if not animall_properties.key_selected or uv.select:
|
|
delete_key(uv, 'uv')
|
|
|
|
if animall_properties.key_vcols:
|
|
for v_col_layer in data.vertex_colors:
|
|
if v_col_layer.active: # only delete in active VCol layer
|
|
for data in v_col_layer.data:
|
|
delete_key(data, 'color')
|
|
|
|
elif obj.type == 'LATTICE':
|
|
if animall_properties.key_shape:
|
|
if obj.active_shape_key:
|
|
for point in obj.active_shape_key.data:
|
|
delete_key(point, 'co')
|
|
|
|
if animall_properties.key_points:
|
|
for point in data.points:
|
|
if not animall_properties.key_selected or point.select:
|
|
delete_key(point, 'co_deform')
|
|
|
|
elif obj.type in {'CURVE', 'SURFACE'}:
|
|
# run this outside the splines loop (only once)
|
|
if animall_properties.key_shape:
|
|
if obj.active_shape_key_index > 0:
|
|
for CV in obj.active_shape_key.data:
|
|
delete_key(CV, 'co')
|
|
delete_key(CV, 'handle_left')
|
|
delete_key(CV, 'handle_right')
|
|
|
|
for spline in data.splines:
|
|
if spline.type == 'BEZIER':
|
|
for CV in spline.bezier_points:
|
|
if (not animall_properties.key_selected
|
|
or CV.select_control_point
|
|
or CV.select_left_handle
|
|
or CV.select_right_handle):
|
|
if animall_properties.key_points:
|
|
delete_key(CV, 'co')
|
|
delete_key(CV, 'handle_left')
|
|
delete_key(CV, 'handle_right')
|
|
if animall_properties.key_radius:
|
|
delete_key(CV, 'radius')
|
|
if animall_properties.key_tilt:
|
|
delete_key(CV, 'tilt')
|
|
|
|
elif spline.type in ('POLY', 'NURBS'):
|
|
for CV in spline.points:
|
|
if not animall_properties.key_selected or CV.select:
|
|
if animall_properties.key_points:
|
|
delete_key(CV, 'co')
|
|
if animall_properties.key_radius:
|
|
delete_key(CV, 'radius')
|
|
if animall_properties.key_tilt:
|
|
delete_key(CV, 'tilt')
|
|
|
|
refresh_ui_keyframes()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
class ANIM_OT_clear_animation_animall(Operator):
|
|
bl_label = "Clear Animation"
|
|
bl_idname = "anim.clear_animation_animall"
|
|
bl_description = ("Delete all keyframes for this object\n"
|
|
"If in a specific case it doesn't work\n"
|
|
"try to delete the keys manually")
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
def invoke(self, context, event):
|
|
wm = context.window_manager
|
|
return wm.invoke_confirm(self, event)
|
|
|
|
def execute(self, context):
|
|
if context.mode == 'OBJECT':
|
|
objects = context.selected_objects
|
|
else:
|
|
objects = context.objects_in_mode_unique_data
|
|
|
|
for obj in objects:
|
|
try:
|
|
data = obj.data
|
|
data.animation_data_clear()
|
|
except:
|
|
self.report({'WARNING'}, "Clear Animation could not be performed")
|
|
return {'CANCELLED'}
|
|
|
|
refresh_ui_keyframes()
|
|
|
|
return {'FINISHED'}
|
|
|
|
|
|
# Add-ons Preferences Update Panel
|
|
|
|
# Define Panel classes for updating
|
|
panels = [
|
|
VIEW3D_PT_animall
|
|
]
|
|
|
|
|
|
def update_panel(self, context):
|
|
message = "AnimAll: Updating Panel locations has failed"
|
|
try:
|
|
for panel in panels:
|
|
if "bl_rna" in panel.__dict__:
|
|
bpy.utils.unregister_class(panel)
|
|
|
|
for panel in panels:
|
|
panel.bl_category = context.preferences.addons[__name__].preferences.category
|
|
bpy.utils.register_class(panel)
|
|
|
|
except Exception as e:
|
|
print("\n[{}]\n{}\n\nError:\n{}".format(__name__, message, e))
|
|
pass
|
|
|
|
|
|
class AnimallAddonPreferences(AddonPreferences):
|
|
# this must match the addon name, use '__package__'
|
|
# when defining this in a submodule of a python package.
|
|
bl_idname = __name__
|
|
|
|
category: StringProperty(
|
|
name="Tab Category",
|
|
description="Choose a name for the category of the panel",
|
|
default="Animate",
|
|
update=update_panel
|
|
)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
row = layout.row()
|
|
col = row.column()
|
|
|
|
col.label(text="Tab Category:")
|
|
col.prop(self, "category", text="")
|
|
|
|
|
|
def register():
|
|
bpy.utils.register_class(AnimallProperties)
|
|
bpy.types.WindowManager.animall_properties = bpy.props.PointerProperty(type=AnimallProperties)
|
|
bpy.utils.register_class(VIEW3D_PT_animall)
|
|
bpy.utils.register_class(ANIM_OT_insert_keyframe_animall)
|
|
bpy.utils.register_class(ANIM_OT_delete_keyframe_animall)
|
|
bpy.utils.register_class(ANIM_OT_clear_animation_animall)
|
|
bpy.utils.register_class(AnimallAddonPreferences)
|
|
update_panel(None, bpy.context)
|
|
|
|
|
|
def unregister():
|
|
del bpy.types.WindowManager.animall_properties
|
|
bpy.utils.unregister_class(AnimallProperties)
|
|
bpy.utils.unregister_class(VIEW3D_PT_animall)
|
|
bpy.utils.unregister_class(ANIM_OT_insert_keyframe_animall)
|
|
bpy.utils.unregister_class(ANIM_OT_delete_keyframe_animall)
|
|
bpy.utils.unregister_class(ANIM_OT_clear_animation_animall)
|
|
bpy.utils.unregister_class(AnimallAddonPreferences)
|
|
|
|
if __name__ == "__main__":
|
|
register()
|