Last active
August 29, 2015 14:01
-
-
Save Pentan/f6722930908b740f4a48 to your computer and use it in GitHub Desktop.
Strange shapekey utility W.I.P.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
bl_info = { | |
"name": "Nakawari Shapekey Utilities", | |
"author": "Pentan", | |
"version": (0, 1), | |
"blender": (2, 70, 0), | |
"location": "Object > Nakawari", | |
"description": "This addon makes Nakawari shapekeys for 2D hand drawn like motions.", | |
"warning": "experimental", | |
"wiki_url": "", | |
"category": "Animation"} | |
import bpy | |
from mathutils import Vector | |
##### | |
class GengaData(bpy.types.PropertyGroup): | |
tsume_enum = [ | |
('NONE', 'None', ''), | |
('SAKI', 'Saki', ''), | |
('ATO', 'Ato', ''), | |
('RYOU', 'Ryou', '')] | |
frame = bpy.props.IntProperty(name="Genga Frame") | |
#tsume_type = bpy.props.IntProperty(name="Tume Type") | |
tsume_type = bpy.props.EnumProperty(items=tsume_enum, default="NONE", name="Tume Type") | |
##### | |
class GENGA_UL_List(bpy.types.UIList): | |
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index, flt_flag): | |
#print("draw list") | |
if self.layout_type in {'DEFAULT', 'COMPACT'}: | |
#layout.label("[{}] frame:{} tsume:{}".format(index, item.frame, item.tsume_type)) | |
row = layout.row() | |
row.label("f:{:06}".format(item.frame)) | |
#row.label("tsume:{}".format(item.tsume_type)) | |
row.prop(data=item, text="", property="tsume_type") | |
elif self.layout_type in {'GRID'}: | |
layout.alignment = 'CENTER' | |
layout.label("{}:{}".format(item.frame, item.tsume_type)) | |
class NakawariUIPanel(bpy.types.Panel): | |
"""Nakawari propertiess""" | |
bl_label = "Nakawari" | |
bl_idname = "OBJECT_PT_nkwr_properties" | |
bl_space_type = 'PROPERTIES' | |
bl_region_type = 'WINDOW' | |
bl_context = 'object' | |
def draw(self, context): | |
layout = self.layout | |
obj = context.object | |
layout.template_list("GENGA_UL_List", "", obj, "nakawari_genga_frames", obj, "nakawari_active_genga_index") | |
layout.operator("object.nkwr_add_genga") | |
layout.operator("object.nkwr_remove_genga") | |
layout.separator() | |
layout.operator("object.nkwr_generate_mesh_object") | |
layout.prop(data=obj, text="Choose basis shape", property="nakawari_use_basis_frame") | |
layout.prop(data=obj, text="basis frame", property="nakawari_basis_frame") | |
##### | |
class AddGengaFrameOperator(bpy.types.Operator): | |
"""Add Genga Frame""" | |
bl_idname = "object.nkwr_add_genga" | |
bl_label = "Add Genga" | |
bl_options = {'REGISTER', 'UNDO'} | |
@classmethod | |
def poll(cls, context): | |
return len(context.selected_objects) > 0 | |
def execute(self, context): | |
curframe = context.scene.frame_current | |
for obj in context.selected_objects: | |
genga_frames = obj.nakawari_genga_frames | |
frame_found = False | |
gf = None | |
for f in genga_frames: | |
if f.frame == curframe: | |
f.frame = curframe | |
frame_found = True | |
gf = f | |
break | |
if frame_found == False: | |
# add new | |
f = genga_frames.add() | |
f.frame = curframe | |
i = len(genga_frames) - 2 | |
while i >= 0: | |
if curframe > genga_frames[i].frame: | |
break | |
genga_frames.move(i, i + 1) | |
i -= 1 | |
obj.nakawari_active_genga_index = max(0, i+1) | |
gf = f | |
#print("{} genga added: {}: ".format(obj.name, gf.frame)) | |
return {'FINISHED'} | |
class RemoveGengaFrameOperator(bpy.types.Operator): | |
"""Remove Genga Frame""" | |
bl_idname = "object.nkwr_remove_genga" | |
bl_label = "Remove Genga" | |
bl_options = {'UNDO'} | |
@classmethod | |
def poll(cls, context): | |
return len(context.selected_objects) > 0 | |
def execute(self, context): | |
for obj in context.selected_objects: | |
genga_frames = obj.nakawari_genga_frames | |
active_id = obj.nakawari_active_genga_index | |
if active_id >= 0 and active_id < len(genga_frames): | |
print("{} removed {}".format(obj.name, active_id)) | |
genga_frames.remove(active_id) | |
active_genga_index_changed(self, context) | |
return {'FINISHED'} | |
class GenerateNkawariMeshObjOperator(bpy.types.Operator): | |
"""Generate Nakawari Mesh Object""" | |
bl_idname = "object.nkwr_generate_mesh_object" | |
bl_label = "Generate Object" | |
bl_options = {'UNDO'} | |
def apply_transforms(sel, dstverts, srcverts, camera, obj): | |
cm = camera.matrix_world.inverted() | |
om = obj.matrix_world | |
m = cm * om | |
for (i, v) in enumerate(srcverts): | |
dstverts[i].co = m * v.co | |
def set_tsume_curve(self, kp0, kp1, tsume_type): | |
#print("set_tsume_curve:{}".format(tsume_type)) | |
kp0.handle_left_type = 'FREE' | |
kp0.handle_right_type = 'FREE' | |
kp1.handle_left_type = 'FREE' | |
kp1.handle_right_type = 'FREE' | |
if tsume_type == 'NONE': | |
kp0.interpolation = 'LINEAR' | |
else: | |
kp0.interpolation = 'BEZIER' | |
if tsume_type == 'SAKI': | |
vx0 = 0.3333333 | |
vy0 = 0.0 | |
vx1 = -0.3333333 | |
vy1 = -0.6666667 | |
elif tsume_type == 'ATO': | |
vx0 = 0.3333333 | |
vy0 = 0.6666667 | |
vx1 = -0.3333333 | |
vy1 = 0.0 | |
else: | |
vx0 = 0.3904115 # Blender default value | |
vy0 = 0.0 | |
vx1 = -0.3904115 | |
vy1 = 0.0 | |
kpw = kp1.co.x - kp0.co.x | |
kph = kp1.co.y - kp0.co.y | |
vx0 *= kpw | |
vy0 *= kph | |
vx1 *= kpw | |
vy1 *= kph | |
kp0.handle_right.x = kp0.co.x + vx0 | |
kp0.handle_right.y = kp0.co.y + vy0 | |
kp1.handle_left.x = kp1.co.x + vx1 | |
kp1.handle_left.y = kp1.co.y + vy1 | |
def set_fcurve_ipo(self, obj, shapekey, genga_frames, genga_id): | |
curgenga = genga_frames[genga_id] | |
#print("set_fcurve_ipo:{}({})".format(genga_id, curgenga.frame)) | |
fcurves = obj.data.shape_keys.animation_data.action.fcurves | |
data_path = 'key_blocks["' + shapekey.name + '"].value' | |
#print("fcurves;{},path:{}".format(len(fcurves), data_path)) | |
for crv in fcurves: | |
#print(crv.data_path) | |
if crv.data_path == data_path: | |
#print("{} found".format(data_path)) | |
for i, kp in enumerate(crv.keyframe_points): | |
#print("kp[{}]:{}".format(i, kp.co.x)) | |
if kp.co.x == curgenga.frame: | |
if i > 0: | |
prekp = crv.keyframe_points[i - 1] | |
self.set_tsume_curve(prekp, kp, curgenga.tsume_type) | |
if i < len(crv.keyframe_points) - 1: | |
nxtkp = crv.keyframe_points[i + 1] | |
nxtgenga = genga_frames[genga_id + 1] | |
self.set_tsume_curve(kp, nxtkp, nxtgenga.tsume_type) | |
break | |
@classmethod | |
def poll(cls, context): | |
return len(context.selected_objects) > 0 | |
def execute(self, context): | |
sc = bpy.context.scene | |
camera = sc.camera | |
first_frame = sc.frame_current | |
frame_start = sc.frame_start | |
tomesh_settings = 'PREVIEW' # modifier apply quality | |
for obj in context.selected_objects: | |
#print(obj.name) ### | |
# create object | |
#sc.frame_set(frame_start) | |
if obj.nakawari_use_basis_frame: | |
# use basis shape frame | |
sc.frame_set(obj.nakawari_basis_frame) | |
mesh = obj.to_mesh(sc, True, tomesh_settings) | |
self.apply_transforms(mesh.vertices, mesh.vertices, camera, obj) | |
else: | |
# basis shape is not choosed. use undeformed mesh. | |
apply_ignore = {'ARMATURE'} | |
modf_settings = {} | |
for modf in obj.modifiers: | |
if modf.type in apply_ignore: | |
modf_settings[modf.name] = (modf.show_render, modf.show_viewport) | |
modf.show_render = False | |
modf.show_viewport = False | |
mesh = obj.to_mesh(sc, True, tomesh_settings) | |
# recover modifier settings | |
for modf in obj.modifiers: | |
if modf.type in apply_ignore: | |
modf.show_render = modf_settings[modf.name][0] | |
modf.show_viewport = modf_settings[modf.name][1] | |
mesh.update() | |
newobj = bpy.data.objects.new("{}_NAKAWARI".format(obj.name), mesh) | |
# base mesh | |
newobj.shape_key_add("Basis") | |
newobj.data.update() | |
genga_frames = obj.nakawari_genga_frames | |
# make nakawari meshes | |
for i, genga in enumerate(genga_frames): | |
#print("{},{}".format(genga.frame, genga.tsume_type)) | |
# get genga_frame's mesh | |
sc.frame_set(genga.frame) | |
mesh = obj.to_mesh(sc, True, tomesh_settings) | |
# add that mesh as shapekey | |
shapekey = newobj.shape_key_add("{}_{:04}".format(obj.name, genga.frame)) | |
shape_key_index = len(newobj.data.shape_keys.key_blocks) - 1 | |
newobj.active_shape_key_index = shape_key_index | |
self.apply_transforms(shapekey.data, mesh.vertices, camera, obj) | |
### reflesh shapekey object to insert key frame | |
shapekey = newobj.data.shape_keys.key_blocks[shape_key_index] | |
# previous | |
f = genga_frames[i-1].frame if i > 0 else frame_start | |
shapekey.value = 0.0 | |
shapekey.keyframe_insert("value", frame=f) | |
# current | |
shapekey.value = 1.0 | |
shapekey.keyframe_insert("value", frame=genga.frame) | |
# next | |
if i < len(genga_frames)-1: | |
f = genga_frames[i + 1].frame | |
shapekey.value = 0.0 | |
shapekey.keyframe_insert("value", frame=f) | |
self.set_fcurve_ipo(newobj, shapekey, genga_frames, i) | |
newobj.data.update() | |
# cleaning | |
bpy.data.meshes.remove(mesh) | |
sc.objects.link(newobj) | |
sc.frame_set(first_frame) | |
return {'FINISHED'} | |
def active_genga_index_changed(self, context): | |
obj = context.object | |
genga_frames = obj.nakawari_genga_frames | |
active_index = obj.nakawari_active_genga_index | |
if active_index >= 0 and active_index < len(genga_frames): | |
context.scene.frame_current = genga_frames[active_index].frame | |
""" | |
def use_basis_shape_frame_changed(self, context): | |
print("use basis frame:{}".format(self.nakawari_use_basis_frame)) | |
""" | |
##### | |
class CurveSegment: | |
def __init__(self, lp, rp): | |
self.left_point = lp | |
self.right_point = rp | |
#print("new segment({},{})".format(lp.co.x, rp.co.x)) | |
def is_overlap(self, lp, rp): | |
return (lp.co.x == self.left_point.co.x) and (rp.co.x == self.right_point.co.x) | |
def left_handle_vector(self): | |
return Vector(( | |
self.left_point.handle_right.x - self.left_point.co.x, | |
self.left_point.handle_right.y - self.left_point.co.y | |
)) | |
def right_handle_vector(self): | |
return Vector(( | |
self.right_point.handle_left.x - self.right_point.co.x, | |
self.right_point.handle_left.y - self.right_point.co.y | |
)) | |
class NormalizeTumeFCurveOperator(bpy.types.Operator): | |
"""Normalize Tume FCurve""" | |
bl_idname = "graph.nakawari_normalize_tsume_fcurve" | |
bl_label = "Normalize Tume FCurve" | |
bl_options = {'UNDO'} | |
@classmethod | |
def poll(cls, context): | |
obj = context.active_object | |
fcurves = None | |
if obj: | |
if obj.type == 'MESH': | |
shape_keys = obj.data.shape_keys | |
if shape_keys: | |
if shape_keys.animation_data: | |
act = shape_keys.animation_data.action | |
if act: | |
fcurves = act.fcurves | |
return (obj and fcurves) | |
def execute(self, context): | |
# find selected fcurve from active object | |
obj = context.active_object | |
mesh = obj.data | |
fcurves = mesh.shape_keys.animation_data.action.fcurves | |
# find selected | |
selcurve = None | |
for crv in fcurves: | |
if crv.select: | |
if selcurve: | |
self.report({'ERROR'}, "Please select 1 sorce curve") | |
return {'FINISHED'} | |
else: | |
selcurve = crv | |
# find interval | |
# all segments | |
frame_cur = context.scene.frame_current | |
keyframe_points = selcurve.keyframe_points | |
segments = [] | |
for i, p in enumerate(keyframe_points[:-1]): | |
#print("keyframe_points[{}].co.x:{}".format(i, p.co.x)) | |
segments.append(CurveSegment(p, keyframe_points[i+1])) | |
# or nearest? | |
# or selected? | |
# normalize | |
for crv in fcurves: | |
if crv != selcurve: | |
kf_points = crv.keyframe_points | |
for i, p in enumerate(kf_points[:-1]): | |
lp = p | |
rp = kf_points[i + 1] | |
for seg in segments: | |
if seg.is_overlap(lp, rp): | |
seg_lv = seg.left_handle_vector() | |
seg_rv = seg.right_handle_vector() | |
lp.interpolation = seg.left_point.interpolation | |
lp.co.y = 1.0 - seg.left_point.co.y | |
lp.handle_right.x = lp.co.x + seg_lv.x | |
lp.handle_right.y = lp.co.y - seg_lv.y | |
rp.interpolation = seg.right_point.interpolation | |
rp.co.y = 1.0 - seg.right_point.co.y | |
rp.handle_left.x = rp.co.x + seg_rv.x | |
rp.handle_left.y = rp.co.y - seg_rv.y | |
#print("normalized({},{})".format(lp.co.x, rp.co.x)) | |
#else: | |
# print("skiped({},{})".format(lp.co.x, rp.co.x)) | |
return {'FINISHED'} | |
##### | |
def register(): | |
bpy.utils.register_class(AddGengaFrameOperator) | |
bpy.utils.register_class(RemoveGengaFrameOperator) | |
bpy.utils.register_class(GenerateNkawariMeshObjOperator) | |
bpy.utils.register_class(GENGA_UL_List) | |
bpy.utils.register_class(NakawariUIPanel) | |
bpy.utils.register_class(GengaData) | |
bpy.types.Object.nakawari_genga_frames = bpy.props.CollectionProperty(type=GengaData) | |
bpy.types.Object.nakawari_active_genga_index = bpy.props.IntProperty(name="Active Genga Frame", update=active_genga_index_changed) | |
bpy.types.Object.nakawari_use_basis_frame = bpy.props.BoolProperty(name="Use Basis Frame", default=False) #, update=use_basis_shape_frame_changed) | |
bpy.types.Object.nakawari_basis_frame = bpy.props.IntProperty(name="Basis Frame", default=1) | |
bpy.utils.register_class(NormalizeTumeFCurveOperator) | |
def unregister(): | |
bpy.utils.unregister_class(AddGengaFrameOperator) | |
bpy.utils.unregister_class(RemoveGengaFrameOperator) | |
bpy.utils.unregister_class(GenerateNkawariMeshObjOperator) | |
bpy.utils.unregister_class(GENGA_UL_List) | |
bpy.utils.unregister_class(NakawariUIPanel) | |
bpy.utils.unregister_class(GengaData) | |
del bpy.types.Object.nakawari_genga_frames | |
del bpy.types.Object.nakawari_active_genga_index | |
del bpy.types.Object.nakawari_use_basis_frame | |
del bpy.types.Object.nakawari_basis_frame | |
bpy.utils.unregister_class(NormalizeTumeFCurveOperator) | |
if __name__ == "__main__": | |
register() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment