diff --git a/Scripts/Animation/MotionCapHelper/README.md b/Scripts/Animation/MotionCapHelper/README.md new file mode 100644 index 0000000..54bd292 --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/README.md @@ -0,0 +1,17 @@ +# MotionCapHelper + maya simple plugin for animator + +# main feature:主要功能 +animation curve smooth:平滑动画曲线 +frame align:在参考物体的帧时间k选择物体的帧,并删除其他帧。(对齐帧) +regular expression:根据正则表达式过滤物体名,并创建(多个)显示层 +animation rebuild:删除范围内外帧,偏移选中物体帧,手动提取关键帧,快速烘焙动画到另一个物体,fk转换ik,钉住世界位置,快速烘焙父子约束效果。 + +都是非常简单的功能,其中平滑曲线的代码来自https://blog.csdn.net/lulongfei172006/article/details/51493273/ + +# support maya version:支持版本 +maya 2018+ + + +![preview](./preview.PNG) +![preview](./preview1.PNG) \ No newline at end of file diff --git a/Scripts/Animation/MotionCapHelper/Thumbs.db b/Scripts/Animation/MotionCapHelper/Thumbs.db new file mode 100644 index 0000000..c2e8f12 Binary files /dev/null and b/Scripts/Animation/MotionCapHelper/Thumbs.db differ diff --git a/Scripts/Animation/MotionCapHelper/extra_scripts/trace back(追踪对象或层).py b/Scripts/Animation/MotionCapHelper/extra_scripts/trace back(追踪对象或层).py new file mode 100644 index 0000000..f55967e --- /dev/null +++ b/Scripts/Animation/MotionCapHelper/extra_scripts/trace back(追踪对象或层).py @@ -0,0 +1,48 @@ +import maya.cmds as cmds +objs = cmds.ls(selection = True,type = "dagNode",long = True) + +selectobjflag = True +selectmultiobjflag = False +if len(objs) == 0: + selectobjflag = False +else: + if len(objs) != 1: + selectmultiobjflag = True + + + +if objs == None: + selectobjflag = False + +curves = cmds.keyframe(query = True , selected = True , name = True) +print(type(curves)) +selectkeyflag = True +if curves == None: + selectkeyflag = False + +if selectobjflag: + if selectkeyflag: + nodes = set() + for cv in curves: + node = cmds.listConnections(cv)[0] + nodes.add(node) + cmds.select(clear = True) + cmds.select(nodes) + print("TRACE BACK OBJS FROM KEY: " + str(nodes)) + else: + obj = cmds.ls(objs[0])[0] + + layer = cmds.ls(cmds.listConnections(obj,t = "displayLayer")) + if len(layer) == 0: + print("this obj has no layer,or you have selected multiple objs.") + elif cmds.editDisplayLayerGlobals(cdl = True,q = True) == "defaultLayer": + print("no display layer selected,trace back won\'t work.") + else: + cmds.editDisplayLayerGlobals(cdl = layer[0]) + + if selectmultiobjflag: + print("TRACE BACK LAYER FROM OBJ: " + layer[0] + "BUT WITH MULTIPLE OBJS SELECTED,SO RESULE IS UNEXPECTED.") + else: + print("TRACE BACK LAYER FROM OBJ: " + layer[0] ) +else : + print("no objs selected!") diff --git a/Scripts/Animation/MotionCapHelper/preview.PNG b/Scripts/Animation/MotionCapHelper/preview.PNG new file mode 100644 index 0000000..2595c3a Binary files /dev/null and b/Scripts/Animation/MotionCapHelper/preview.PNG differ diff --git a/Scripts/Animation/MotionCapHelper/preview1.PNG b/Scripts/Animation/MotionCapHelper/preview1.PNG new file mode 100644 index 0000000..c11daa8 Binary files /dev/null and b/Scripts/Animation/MotionCapHelper/preview1.PNG differ diff --git a/Scripts/Animation/springmagic/HowToCompile.txt b/Scripts/Animation/springmagic/HowToCompile.txt new file mode 100644 index 0000000..be04d81 --- /dev/null +++ b/Scripts/Animation/springmagic/HowToCompile.txt @@ -0,0 +1,5 @@ +If you changed the source code, you have to recompile SpringMagic project + +Uncomment this line in file springMagic.py +# Recompile SpringMagic module if modification has been made +dev.refresh(springmagic) diff --git a/Scripts/Animation/springmagic/HowToInstall.txt b/Scripts/Animation/springmagic/HowToInstall.txt new file mode 100644 index 0000000..d05d370 --- /dev/null +++ b/Scripts/Animation/springmagic/HowToInstall.txt @@ -0,0 +1,30 @@ +1. Unzip all files into same folder + +2. Run Maya + +3. Run command below in command bar with Python way + execfile(r'the folder path\springMagic.py') + example: + execfile(r'C:\Users\Chris\Scripts\springMagic.py') + + keep the 'r' in front of your path to avoid IO Error may cause + +Tested under Maya 2011 and above, you may need to install Pymel for Maya 2010 or elder build by yourself + +Known Issue: +1. Error: Syntax error + Please check if you run the command in Python mode, you can click "mel" word to toggle between mel and python + +2. # Error: ImportError: No module named pymel.core # + That means you are using old version of MAYA and have no Pymel installed, please download and install Pymel follow this page: + http://download.autodesk.com/us/maya/2011help/PyMel/install.html + +3. Error: IOError: file line 1: 2 + It mean your path has some error when you run the execfile command, please double check what you typed + + +for more details visit my site +www.animbai.com + +Yanbin +2017.10 \ No newline at end of file diff --git a/Scripts/Animation/springmagic/Howto.txt b/Scripts/Animation/springmagic/Howto.txt new file mode 100644 index 0000000..d5d161c --- /dev/null +++ b/Scripts/Animation/springmagic/Howto.txt @@ -0,0 +1,2 @@ +import springmagic +springmagic.main() \ No newline at end of file diff --git a/Scripts/Animation/springmagic/__init__.py b/Scripts/Animation/springmagic/__init__.py new file mode 100644 index 0000000..ed8e5a2 --- /dev/null +++ b/Scripts/Animation/springmagic/__init__.py @@ -0,0 +1,12 @@ +# from springmagic.main import main + +__version__ = "3.5a" + + +def version(): + """ + Return the current version of the Spring Magic + + :rtype: str + """ + return __version__ diff --git a/Scripts/Animation/springmagic/core.py b/Scripts/Animation/springmagic/core.py new file mode 100644 index 0000000..31341a3 --- /dev/null +++ b/Scripts/Animation/springmagic/core.py @@ -0,0 +1,961 @@ +# - * - coding: utf - 8 - * - +# PEP8 formatting + +##################################################################################### +# +# Spring Magic for Maya +# +# Calculate bone chain animation by settings, support collisions and wind force +# Can work with rigging controller as well +# +# Need pringMagic.ui file to work with +# This script need also icon file support, which should be put in same folder +# +# feel free to mail me redtank@outlook.com for any bug or issue +# +# Yanbin Bai +# 2021.02 +# +##################################################################################### + +import math +import logging +# import copy + +import pymel.core as pm +import pymel.core.datatypes as dt +import maya.cmds as cmds + +import Animation.springmagic.decorators as decorators +import Animation.springmagic.springMath as springMath + +from Animation.springmagic.utility import * + +from itertools import cycle +from itertools import chain +from weakref import WeakValueDictionary + +from collections import OrderedDict + +################################### +# spring magic +#################################### + +kWindObjectName = 'spring_wind' +kSpringProxySuffix = '_SpringProxy' +kCollisionPlaneSuffix = '_SpringColPlane' +kCapsuleNameSuffix = '_collision_capsule' +kNullSuffix = '_SpringNull' +# kTwistNullSuffix = '_SpringTwistNull' + + +class Spring: + + def __init__(self, ratio=0.5, twistRatio=0.0, tension=0.0, extend=0.0, inertia=0.0): + + self.ratio = ratio + self.twist_ratio = twistRatio + self.tension = tension + self.extend = extend + self.inertia = inertia + +class SpringMagic: + + def __init__(self, startFrame, endFrame, subDiv=1.0, isLoop=False, isPoseMatch=False, isCollision=False, isFastMove=False, wipeSubframe=True): + + self.start_frame = startFrame + self.end_frame = endFrame + + self.sub_div = subDiv + self.is_loop = isLoop + self.is_pose_match = isPoseMatch + self.is_fast_move = isFastMove + self.wipe_subframe = wipeSubframe + + self.is_collision = isCollision + self.collision_planes_list = None + + self.wind = None + +class SpringData: + + cur_position_locator = None + prev_position_locator = None + prev_grand_child_position_locator = None + + _instances = WeakValueDictionary() + + @property + def Count(self): + return len(self._instances) + + def __init__(self, springMagic, spring, transform, child, grand_child, grand_parent): + # self.current_child_position + + self._instances[id(self)] = self + + self.springMagic = springMagic + self.spring = spring + + self.parent = transform + self.child = child + self.grand_child = grand_child + self.grand_parent = grand_parent + + self.child_position = get_translation(child) + self.grand_child_position = get_translation(grand_child) if grand_child else None + + self.previous_child_position = self.child_position + + self.rotation = get_rotation(transform) + self.up_vector = get_matrix(transform)[4:7] + + transform_pos = get_translation(transform) + self.bone_length = springMath.distance(transform_pos, self.child_position) + + self.has_child_collide = False + self.has_plane_collide = False + + # create temporary locators use for aim constraint + if not SpringData.cur_position_locator: + SpringData.cur_position_locator = pm.spaceLocator(name='cur_position_locator') + + if not SpringData.prev_position_locator: + SpringData.prev_position_locator = pm.spaceLocator(name='prev_position_locator') + + if not SpringData.prev_grand_child_position_locator: + SpringData.prev_grand_child_position_locator = pm.spaceLocator(name='prev_grand_child_position_locator') + + # Weight attribute to de/activate the aim constraint in pose match mode + self.pairblend_weight_attribute = None + self.aim_constraint = None + + self.__create_child_proxy() + self.__prepare_animation_key() + self.__create_aim_constraint() + self.__init_pairblend_weight() + + def __del__(self): + + if self.Count == 0: + # print('Last Counter object deleted') + + # delete temporary locators (useful, it's delete constraints at the same time) + pm.delete(SpringData.cur_position_locator, SpringData.prev_position_locator, SpringData.prev_grand_child_position_locator) + + SpringData.cur_position_locator = None + SpringData.prev_position_locator = None + SpringData.prev_grand_child_position_locator = None + + # remove all spring nulls, add recursive incase name spaces + # pm.delete(pm.ls('*' + kNullSuffix + '*', recursive=1)) + else: + # print(self.Count, 'Counter objects remaining') + pass + + if self.child_proxy: + # remove spring nulls, add recursive incase name spaces + pm.delete(pm.ls('*' + self.child_proxy + '*', recursive=1)) + + def update(self, has_collision, has_hit_plane, child_pos_corrected): + # Update current transform with the new values + self.child_position = get_translation(self.child) + self.grand_child_position = get_translation(self.grand_child) if self.grand_child else None + self.previous_child_position = child_pos_corrected + + self.rotation = get_rotation(self.parent) + self.up_vector = get_matrix(self.parent)[4:7] + + self.has_child_collide = has_collision + self.has_plane_collide = has_hit_plane + + def __create_child_proxy(self): + # create a null at child pos, then parent to obj parent for calculation + child_proxy_locator_name = self.parent.name() + kNullSuffix + child_proxy_list = pm.ls(child_proxy_locator_name) + + if not child_proxy_list: + self.child_proxy = pm.spaceLocator(name=child_proxy_locator_name) + else: + self.child_proxy = child_proxy_list[0] + + self.child_proxy.getShape().setAttr('visibility', False) + + pm.parent(self.child_proxy, self.parent.getParent()) + # pm.parent(child_proxy, self.grand_parent) + + if not self.springMagic.is_pose_match: + self.child_proxy.setTranslation(self.child.getTranslation(space='world'), space='world') + self.child_proxy.setRotation(self.child.getRotation(space='world'), space='world') + + def __prepare_animation_key(self): + if not self.springMagic.is_pose_match: + # remove exists keys + pm.cutKey(self.parent, time=(self.springMagic.start_frame, self.springMagic.end_frame + 0.99999)) + pm.cutKey(self.child, time=(self.springMagic.start_frame, self.springMagic.end_frame + 0.99999)) + + # set key + pm.setKeyframe(self.parent, attribute='rotate') + + if self.spring.extend != 0.0: + pm.setKeyframe(self.child, attribute='tx') + + def __create_aim_constraint(self): + # Create a constraint per transform to speed up computation, not active yet (weight=0) + self.aim_constraint = pm.aimConstraint(SpringData.cur_position_locator, SpringData.prev_position_locator, SpringData.prev_grand_child_position_locator, self.parent, aimVector=[1, 0, 0], upVector=[0, 1, 0], maintainOffset=False, weight=0) + + def __init_pairblend_weight(self): + # if transform rotation has no animation, set a key at start frame to force the creation of a pairblend when the aim constraint is created + for rotation_input in ['rx', 'ry', 'rz']: + rotation_connection = pm.listConnections(self.parent + '.' + rotation_input, d=False, s=True) + + if not rotation_connection: + pm.setKeyframe(self.parent, attribute=rotation_input) + + pairblends = pm.listConnections(self.parent, type="pairBlend", destination=True, skipConversionNodes=True) + + # Find the pairblend connected to the aim constraint + for pairblend in pairblends: + + connected_constraint_list = cmds.listConnections(pairblend.name(), type='constraint', destination=False) + + if self.aim_constraint.name() in connected_constraint_list: + + # Get pairblend weight connected attribute + # return [u'joint2.blendAim1')] + weight_attribute_list = cmds.listConnections(pairblend + '.weight', d=False, s=True, p=True) + + if weight_attribute_list: + self.pairblend_weight_attribute = weight_attribute_list[0] + + def set_pairblend_weight(self, blend_value): + if self.pairblend_weight_attribute: + pm.setAttr(self.pairblend_weight_attribute, blend_value) + + def keyframe_child_proxy(self): + + if self.child_proxy: + # Deactivate pairblend weight + # Aim constraint weight set to 0 is not enough, it paratizes the process + self.set_pairblend_weight(0.0) + + self.child_proxy.setTranslation(self.child.getTranslation(space='world'), space='world') + pm.setKeyframe(self.child_proxy, attribute='translate') + self.child_proxy.setRotation(self.child.getRotation(space='world'), space='world') + pm.setKeyframe(self.child_proxy, attribute='rotate') + + self.set_pairblend_weight(1.0) + + def apply_inertia(self, currentChildPosition): + ratio = self.spring.ratio / self.springMagic.sub_div + inertia_offset = [0.0, 0.0, 0.0] + + if self.spring.inertia > 0.0: + bone_ref_loc_offset_dir = currentChildPosition - self.child_position + bone_ref_loc_offset_distance = ((bone_ref_loc_offset_dir) * (1 - ratio) * (1 - self.spring.inertia)).length() + + inertia_offset = bone_ref_loc_offset_dir.normal() * (bone_ref_loc_offset_distance / self.springMagic.sub_div) + + # apply mass + force_direction = self.child_position - self.previous_child_position + force_distance = force_direction.length() * self.spring.inertia + + # offset position + inertia_offset += force_direction.normal() * (force_distance / self.springMagic.sub_div) + + return inertia_offset + + def apply_wind(self, frame): + wind_offset = [0.0, 0.0, 0.0] + + if self.springMagic.wind: + wind_max_force = self.springMagic.wind.getAttr('MaxForce') + wind_min_force = self.springMagic.wind.getAttr('MinForce') + wind_frequency = self.springMagic.wind.getAttr('Frequency') + + mid_force = (wind_max_force + wind_min_force) / 2 + + # get source x - axis direction in world space + wind_direction = get_matrix(self.springMagic.wind)[:3] + # sDirection = sObj.getMatrix()[0][:3] + wind_direction = dt.Vector(wind_direction[0], wind_direction[1], wind_direction[2]).normal() + wind_distance = math.sin(frame * wind_frequency) * (wind_max_force - wind_min_force) + mid_force + + # offset position + wind_offset = wind_direction.normal() * wind_distance + + return wind_offset + + def detect_collision(self, new_obj_pos, new_child_pos, capsule_list): + col_pre = col_cur = None + + child_pos_corrected = self.child_position + + if self.springMagic.is_collision and capsule_list: + + if preCheckCollision(new_obj_pos, self.bone_length, capsule_list): + + # check collision from previous pos to cur pos + col_pre, col_body_pre, hitCylinder_pre = springMath.checkCollision(new_child_pos, self.child_position, capsule_list, True) + + # check collision from cur pos to previous pos + col_cur, col_body_cur, hitCylinder_cur = springMath.checkCollision(new_child_pos, self.child_position, capsule_list, False) + + if col_pre and (col_cur is None): + new_child_pos = col_pre + elif col_cur and (col_pre is None): + child_pos_corrected = col_cur + elif col_pre and col_cur: + + # move cur child pose to closest out point if both pre and cur pos are already inside of col body + # if distance(col_pre, new_child_pos) < distance(col_cur, new_child_pos): + mid_point = (self.child_position + new_child_pos) / 2 + + if springMath.distance(col_pre, mid_point) < springMath.distance(col_cur, mid_point): + new_child_pos = col_pre + else: + new_child_pos = col_cur + + if self.springMagic.is_fast_move: + child_pos_corrected = new_child_pos + + # # draw debug locator + # if col_pre and col_cur: + # locator1 = pm.spaceLocator(name=obj.name() + '_col_pre_locator_' + str(i)) + # locator1.setTranslation(col_pre) + # locator1 = pm.spaceLocator(name=obj.name() + '_col_cur_locator_' + str(i)) + # locator1.setTranslation(col_cur) + + return True if col_pre or col_cur else False, new_child_pos, child_pos_corrected + + def detect_plane_hit(self, new_obj_pos, new_child_pos, grand_parent_has_plane_collision): + has_hit_plane = False + + if self.springMagic.is_collision and self.springMagic.collision_planes_list[0]: + collision_plane = self.springMagic.collision_planes_list[0] + has_plane_collision = springMath.checkPlaneCollision(new_obj_pos, new_child_pos, collision_plane) + + if has_plane_collision or grand_parent_has_plane_collision: + new_child_pos = repeatMoveToPlane(self.parent, new_child_pos, self.child, collision_plane, 3) + has_hit_plane = True + + return has_hit_plane, new_child_pos + + # calculate upvector by interpolation y axis for twist + def compute_up_vector(self): + twist_ratio = self.spring.twist_ratio / self.springMagic.sub_div + + cur_obj_yAxis = get_matrix(self.child_proxy)[4:7] + prev_up_vector = dt.Vector(self.up_vector[0], self.up_vector[1], self.up_vector[2]).normal() + cur_up_vector = dt.Vector(cur_obj_yAxis[0], cur_obj_yAxis[1], cur_obj_yAxis[2]).normal() + + up_vector = (prev_up_vector * (1 - twist_ratio)) + (cur_up_vector * twist_ratio) + + return up_vector + + def aim_by_ratio(self, upVector, newChildPos, childPosCorrected): + ratio = self.spring.ratio / self.springMagic.sub_div + tension = self.spring.tension / (1.0 / (springMath.sigmoid(1 - self.springMagic.sub_div) + 0.5)) + + # print("obj: " + str(self.parent.name())) + # print("newChildPos: " + str(newChildPos)) + # print("childPosCorrected: " + str(childPosCorrected)) + # print("grand_child_position: " + str(self.grand_child_position)) + # print("upVector: " + str(upVector)) + # print("ratio: " + str(ratio)) + # print("tension: " + str(tension)) + + SpringData.cur_position_locator.setTranslation(newChildPos) + SpringData.prev_position_locator.setTranslation(childPosCorrected) + + pm.aimConstraint(self.parent, e=True, worldUpVector=upVector) + + pm.aimConstraint(SpringData.cur_position_locator, self.parent, e=True, w=ratio) + pm.aimConstraint(SpringData.prev_position_locator, self.parent, e=True, w=1 - ratio) + + if self.has_child_collide and self.grand_child_position and tension != 0: + SpringData.prev_grand_child_position_locator.setTranslation(self.grand_child_position) + pm.aimConstraint(SpringData.prev_grand_child_position_locator, self.parent, e=True, w=(1 - ratio) * tension) + + pm.setKeyframe(self.parent, attribute='rotate') + + pm.aimConstraint(SpringData.cur_position_locator, SpringData.prev_position_locator, SpringData.prev_grand_child_position_locator, self.parent, e=True, w=0.0) + + def extend_bone(self, childPosCorrected): + if self.spring.extend != 0.0: + child_translation = self.child.getTranslation() + # get length between bone pos and child pos + x2 = (childPosCorrected - get_translation(self.parent)).length() + x3 = (self.bone_length * (1 - self.spring.extend)) + (x2 * self.spring.extend) + self.child.setTranslation([x3, child_translation[1], child_translation[2]]) + pm.setKeyframe(self.child, attribute='tx') + # else: + # self.child.setTranslation([self.bone_length, child_translation[1], child_translation[2]]) + +def createCollisionPlane(): + + # remove exist plane + collision_plane = get_node('*' + kCollisionPlaneSuffix + '*') + + if collision_plane: + pm.delete(collision_plane) + + collision_plane = pm.polyPlane(name="the" + kCollisionPlaneSuffix, sx=1, sy=1, w=10, h=10, ch=1)[0] + + # one side display + pm.setAttr(collision_plane.doubleSided, False) + + # lock scale + pm.setAttr(collision_plane.sx, lock=True) + pm.setAttr(collision_plane.sy, lock=True) + pm.setAttr(collision_plane.sz, lock=True) + + pm.select(collision_plane) + + +def removeBody(clear=False): + cylinder_list = getCapsule(clear) + + pm.delete(cylinder_list) + + collision_plane = get_node('*' + kCollisionPlaneSuffix + '*') + + if collision_plane: + pm.delete(collision_plane) + + +def addWindObj(): + windCone = pm.cone(name=kWindObjectName)[0] + + windCone.setScale([5, 5, 5]) + + pm.delete(windCone, constructionHistory=1) + + # add wind attr + pm.addAttr(windCone, longName='MaxForce', attributeType='float') + pm.setAttr(windCone.name() + '.MaxForce', 1, e=1, keyable=1) + pm.addAttr(windCone, longName='MinForce', attributeType='float') + pm.setAttr(windCone.name() + '.MinForce', 0.5, e=1, keyable=1) + pm.addAttr(windCone, longName='Frequency', attributeType='float') + pm.setAttr(windCone.name() + '.Frequency', 1, e=1, keyable=1) + # pm.addAttr(windCone, longName='Wave', attributeType='float') + # pm.setAttr(windCone.name() + '.Wave', 0.5, e=1, keyable=1) + + setWireShading(windCone, False) + + pm.makeIdentity(apply=True) + windCone.setRotation([0, 0, 90]) + + +def bindControls(linked_chains=False): + selected_ctrls = pm.ls(sl=True) + pm.select(clear=True) + + # The chains are linked, we can sort them + if linked_chains: + # Create list for every ctrls chains + # ie [[ctrl1, ctrl1.1, ctrl1.2], [ctrl2, ctrl2.1, ctrl2.2, ctrl2.3]] + all_ctrls_descendants_list = pm.listRelatives(selected_ctrls, allDescendents=True) + top_hierarchy_ctrls_list = [x for x in selected_ctrls if x not in all_ctrls_descendants_list] + + ctrls_chains_list = map(lambda x: [x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in selected_ctrls][::-1], top_hierarchy_ctrls_list) + # No sorting possible because the controlers have no lineage + else: + ctrls_chains_list = [selected_ctrls] + + proxy_joint_chain_list = [] + + for ctrls_list in ctrls_chains_list: + + proxy_joint_list = [] + + for ctrl in ctrls_list: + # create proxy joint in ctrl world position + ctrl_position = pm.xform(ctrl, worldSpace=1, rp=1, q=1) + + proxyJoint = pm.joint(name=ctrl.name() + kSpringProxySuffix, position=ctrl_position, radius=0.2, roo='xyz') + proxy_joint_list.append(proxyJoint) + + for joint in proxy_joint_list: + # set joint orientation + pm.joint(joint, edit=1, orientJoint='xyz', zeroScaleOrient=True) + + # Straight bones alignment + joint.setRotation([0, 0, 0]) + joint.setAttr('rotateAxis', [0, 0, 0]) + joint.setAttr('jointOrient', [0, 0, 0]) + + # Free rotation (move rotation values to joint orient values) + # pm.makeIdentity(proxy_joint_list[idx], apply=True, t=False, r=True, s=False, pn=True) + + if proxy_joint_list: + # parent root proxy joint to control parent + pm.parent(proxy_joint_list[0], ctrls_list[0].getParent()) + + # Necessary to start a new joint chain + pm.select(clear=True) + + proxy_joint_chain_list += [proxy_joint_list] + + for idx, joint in enumerate(proxy_joint_list[:-1]): + # orient joint chain + cns = pm.aimConstraint(ctrls_list[idx + 1], proxy_joint_list[idx], aimVector=[1, 0, 0], upVector=[0, 0, 0], worldUpVector=[0, 1, 0], skip='x') + pm.delete(cns) + + for idx, joint in enumerate(proxy_joint_list): + pm.parentConstraint(proxy_joint_list[idx], ctrls_list[idx], maintainOffset=True) + + pm.select(proxy_joint_chain_list) + + +def clearBind(startFrame, endFrame): + proxyJointLst = pm.ls(sl=True) + pm.select(d=True) + + ctrlList = [] + + for bone in proxyJointLst: + ctrl = pm.ls(bone.name().split(kSpringProxySuffix)[0])[0] + ctrlList.append(ctrl) + + if ctrlList: + pm.bakeResults(*ctrlList, t=(startFrame, endFrame)) + + pm.delete(proxyJointLst) + + +def bindPose(): + pm.runtime.GoToBindPose() + + +# Prepare all information to call SpringMagicMaya function +def startCompute(spring, springMagic, progression_callback=None): + + autokeyframe_state = cmds.autoKeyframe(query=True, state=True) + cmds.autoKeyframe(state=False) + + # get selection obj + objs = pm.ls(sl=True) + + # check objects validity + for obj in objs: + # has duplicate name obj + nameCntErr = (len(pm.ls(obj.name())) > 1) + + # is a duplicate obj + nameValidErr = (obj.name().find('|') > 0) + + if nameCntErr or nameValidErr: + raise ValueError(obj.name() + ' has duplicate name object! Stopped!') + + obj_translation = obj.getTranslation() + + if (obj_translation[0] < 0 or abs(obj_translation[1]) > 0.001 or abs(obj_translation[2]) > 0.001) and obj.getParent() and (obj.getParent() in objs): + pm.warning(obj.getParent().name() + "'s X axis not point to child! May get broken result!") + + # Search for collision objects + if springMagic.is_collision: + springMagic.collision_planes_list = [get_node('*' + kCollisionPlaneSuffix + '*')] + + # Search for a wind object + if pm.ls(kWindObjectName): + springMagic.wind = pm.ls(kWindObjectName)[0] + + SpringMagicMaya(objs, spring, springMagic, progression_callback) + + cmds.autoKeyframe(state=autokeyframe_state) + + +# @decorators.viewportOff +@decorators.gShowProgress(status="SpringMagic does his magic") +def SpringMagicMaya(objs, spring, springMagic, progression_callback=None): + # on each frame go through all objs and do: + # 1. make a vectorA from current obj position to previous child position + # 2. make a vectorB from current obj position to current child position + # 3. calculate the angle between two vectors + # 4. rotate the obj towards vectorA base on spring value + + start_frame = springMagic.start_frame + end_frame = springMagic.end_frame + sub_div = springMagic.sub_div + + # remove all spring nulls, add recursive incase name spaces + pm.delete(pm.ls('*' + kNullSuffix + '*', recursive=True)) + + # get all capsules in scene + capsule_list = getCapsule(True) if springMagic.is_collision else None + + if progression_callback: + progression_callback(0) + + # Save object previous frame information in a ordered dict + spring_data_dict = OrderedDict() + + # Initialize data on the first frame + pm.currentTime(start_frame, edit=True) + + # Create a list of objects chains + # ie [[nt.Joint(u'joint1'), nt.Joint(u'joint2'), nt.Joint(u'joint4')], [nt.Joint(u'joint7'), nt.Joint(u'joint8'), nt.Joint(u'joint10')]] + all_joints_descendants_list = pm.listRelatives(objs, allDescendents=True, type='transform') + top_hierarchy_joints_list = [x for x in objs if x not in all_joints_descendants_list] + + # transforms_chains_list = map(lambda x: [x] + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1], top_hierarchy_joints_list) + + # Deal with the specific case of root bone with no parent. + # The root bone is considered the driver, so we remove it from the calculation. + transforms_chains_list = map(lambda x: ([x] if x.getParent() else []) + [y for y in pm.listRelatives(x, allDescendents=True) if y in objs][::-1], top_hierarchy_joints_list) + + # Remove empty lists + transforms_chains_list = [x for x in transforms_chains_list if x != []] + + # Create progression bar generator values + number_of_progession_step = 0 + + if springMagic.is_pose_match: + number_of_progession_step += end_frame - start_frame + 1 + + if springMagic.is_loop: + # Doesn't process the first frame on the first loop + number_of_progession_step += ((end_frame - start_frame) * 2 + 1) * sub_div + else: + # Doesn't process the first frame + number_of_progession_step += (end_frame - start_frame) * sub_div + + progression_increment = 100.0 / number_of_progession_step + progression_generator = frange(progression_increment, 100.0 + progression_increment, progression_increment) + + # Create spring data for each transforms at start frame + for transforms_chain in transforms_chains_list: + + if SpringMagicMaya.isInterrupted(): + break + + transforms_cycle = cycle(transforms_chain) + + # Prime the pump + parent = first_transform = next(transforms_cycle) + grand_parent = parent.getParent() + child = next(transforms_cycle) + grand_child = next(transforms_cycle) + + # skip end bone + for transform in transforms_chain[:-1]: + + if SpringMagicMaya.isInterrupted(): + break + + # End of cycle iteration + if grand_child == first_transform: + grand_child = None + + spring_data_dict[parent.name()] = SpringData(springMagic, spring, parent, child, grand_child, grand_parent) + + grand_parent, parent, child, grand_child = parent, child, grand_child, next(transforms_cycle) + + # Save joints position over timeline + # Parse timeline just one time + if springMagic.is_pose_match: + for frame in range(0, end_frame - start_frame + 1): + + if SpringMagicMaya.isInterrupted(): + break + + pm.currentTime(start_frame + frame, edit=True) + + for spring_data in spring_data_dict.values(): + + if not SpringMagicMaya.isInterrupted(): + spring_data.keyframe_child_proxy() + + progression = progression_generator.next() + progression = clamp(progression, 0, 100) + + if progression_callback: + progression_callback(progression) + + SpringMagicMaya.progress(progression) + + # Generate frame index + # Skip first frame on first calculation pass + frame_increment = 1.0 / sub_div + frame_generator = frange(frame_increment, end_frame - start_frame + frame_increment, frame_increment) + + # On second calculation pass compute first frame + if springMagic.is_loop: + frame_generator = chain(frame_generator, frange(0, end_frame - start_frame + frame_increment, frame_increment)) + + for frame in frame_generator: + + # print('Frame: ' + str(frame)) + + if SpringMagicMaya.isInterrupted(): + break + + pm.currentTime(start_frame + frame, edit=True) + + for previous_frame_spring_data in spring_data_dict.values(): + + if SpringMagicMaya.isInterrupted(): + break + + grand_parent_spring_data = None + if previous_frame_spring_data.grand_parent and previous_frame_spring_data.grand_parent.name() in spring_data_dict.keys(): + grand_parent_spring_data = spring_data_dict[previous_frame_spring_data.grand_parent.name()] + + # get current position of parent and child + parent_pos = get_translation(previous_frame_spring_data.parent) + + # print("obj: " + str(previous_frame_spring_data.parent.name())) + + new_child_pos = get_translation(previous_frame_spring_data.child_proxy) + + # Apply inertia + new_child_pos += previous_frame_spring_data.apply_inertia(new_child_pos) + + # apply wind + new_child_pos += previous_frame_spring_data.apply_wind(start_frame + frame) + + # detect collision + has_collision, new_child_pos, child_pos_corrected = previous_frame_spring_data.detect_collision(parent_pos, new_child_pos, capsule_list) + + # detect plane collision + grand_parent_has_plane_collision = False + if grand_parent_spring_data: + grand_parent_has_plane_collision = grand_parent_spring_data.has_plane_collide + + has_hit_plane, new_child_pos = previous_frame_spring_data.detect_plane_hit(parent_pos, new_child_pos, grand_parent_has_plane_collision) + + # calculate upvector by interpolation y axis for twist + up_vector = previous_frame_spring_data.compute_up_vector() + + # apply aim constraint to do actual rotation + previous_frame_spring_data.aim_by_ratio(up_vector, new_child_pos, child_pos_corrected) + + # Extend bone if needed (update child translation) + previous_frame_spring_data.extend_bone(child_pos_corrected) + + # Update current transform with the new values + previous_frame_spring_data.update(has_collision, has_hit_plane, child_pos_corrected) + + # Update the grand parent has_child_collide value + if grand_parent_spring_data: + grand_parent_spring_data.has_child_collide = has_collision + + progression = progression_generator.next() + progression = clamp(progression, 0, 100) + + if progression_callback: + progression_callback(progression) + + SpringMagicMaya.progress(progression) + + # bake result on frame + if springMagic.wipe_subframe and not SpringMagicMaya.isInterrupted(): + transform_to_bake_list = [spring_data.parent for spring_data in spring_data_dict.values()] + + # Deactivate all pairblend otherwise bake doesn't work with animation layers + for spring_data in spring_data_dict.values(): + spring_data.set_pairblend_weight(0.0) + + bakeAnim(transform_to_bake_list, start_frame, end_frame) + + +def bakeAnim(objList, startFrame, endFrame): + pm.bakeResults( + objList, + t=(startFrame, endFrame), + sampleBy=1, + disableImplicitControl=False, + preserveOutsideKeys=True, + sparseAnimCurveBake=False, + removeBakedAttributeFromLayer=False, + bakeOnOverrideLayer=False, + minimizeRotation=True, + shape=False, + simulation=False) + + +SM_boneTransformDict = {} + + +def copyBonePose(): + global SM_boneTransformDict + + for obj in pm.ls(sl=True): + SM_boneTransformDict[obj] = [obj.getTranslation(), obj.getRotation()] + + +def pasteBonePose(): + global SM_boneTransformDict + + for obj in pm.ls(sl=True): + if obj in SM_boneTransformDict.keys(): + + logging.debug(SM_boneTransformDict[obj][0]) + + obj.setTranslation(SM_boneTransformDict[obj][0]) + obj.setRotation(SM_boneTransformDict[obj][1]) + + +def preCheckCollision(objPos, objLength, capsuleList): + + # print('objPos:' + str(objPos)) + # print('objLength:' + str(objLength)) + + # pre check bone length compare with collision body radius + # will improve performance if bone is far from capsule + for capsule in capsuleList: + capsule_children_list = pm.listRelatives(capsule, children=1, type='transform') + + p = capsule_children_list[0].getTranslation(space='world') + q = capsule_children_list[1].getTranslation(space='world') + r = capsule.getAttr('scaleZ') + + bone_to_capsule_distance = springMath.dist_to_line(p, q, objPos) + + # print('p:' + str(p)) + # print('q:' + str(q)) + # print('r:' + str(r)) + # print('boneToCapsuleDistance:' + str(bone_to_capsule_distance)) + + # means close enough to have a hit change + if bone_to_capsule_distance < objLength + r: + return True + + return False + + +def repeatMoveToPlane(obj, objPos, objTarget, colPlane, times): + # Y axis direction of plane + n = dt.Vector(get_matrix(colPlane)[4:7]) + q = get_translation(colPlane) + d = n.dot(q) + + # for i in range(times): + # pt = objPos + # obj.setTranslation(proj_pt_to_plane(pt, n, d), space='world') + # if (i + 1) != times: + # obj.setTranslation(get_translation(objTarget), space='world') + pt = objPos + outPos = springMath.proj_pt_to_plane(pt, n, d) + + return outPos + + +def setWireShading(obj, tmp): + obj.getShape().overrideEnabled.set(True) + obj.getShape().overrideShading.set(False) + + if tmp: + obj.getShape().overrideDisplayType.set(1) + + +def addCapsuleSphereConstraint(sphereObj): + # create a locator and make sphere follow it + locator = pm.spaceLocator(name=sphereObj.name() + '_locator' + kCapsuleNameSuffix) + + locator.setTranslation(sphereObj.getTranslation()) + locator.setRotation(sphereObj.getRotation()) + locator.getShape().setAttr('visibility', False) + + pm.parentConstraint(locator, sphereObj) + + return locator + + +def createCapsuleGeometry(size): + # create geometry + cylinder, cylinder_history = pm.cylinder(radius=size, sections=8, heightRatio=3) + pm.rename(cylinder.name(), cylinder.name() + kCapsuleNameSuffix) + + sphereA, sphereA_history = pm.sphere(radius=size, endSweep=180, sections=4) + pm.rename(sphereA.name(), sphereA.name() + kCapsuleNameSuffix) + + sphereB, sphereB_history = pm.sphere(radius=size, endSweep=180, sections=4) + pm.rename(sphereB.name(), sphereB.name() + kCapsuleNameSuffix) + + # set to wireframe shader + setWireShading(cylinder, False) + setWireShading(sphereA, True) + setWireShading(sphereB, True) + + # build a capsule with geometry + cylinder.setAttr('rotateZ', 90) + sphereA.setAttr('translateY', -1.5 * size) + sphereB.setAttr('rotateZ', 180) + sphereB.setAttr('translateY', 1.5 * size) + + # add constrain + locatorA = addCapsuleSphereConstraint(sphereA) + locatorB = addCapsuleSphereConstraint(sphereB) + + pm.parent(locatorA, cylinder) + pm.parent(locatorB, cylinder) + + pm.parent(sphereA, cylinder) + pm.parent(sphereB, cylinder) + + sphereA.setAttr('inheritsTransform', False) + sphereB.setAttr('inheritsTransform', False) + + pm.connectAttr(cylinder.scaleY, (sphereA_history.name() + '.radius')) + pm.connectAttr(cylinder.scaleY, (sphereB_history.name() + '.radius')) + pm.connectAttr(cylinder.scaleY, cylinder.scaleZ) + + return cylinder + + +def getCapsule(getAll): + if getAll: + nurbsTransLst = pm.ls(type='transform') + else: + nurbsTransLst = pm.ls(sl=True) + + nurbsSurfaceLst = [] + for obj in nurbsTransLst: + if obj.getShape() and (pm.nodeType(obj.getShape()) == 'nurbsSurface'): + nurbsSurfaceLst.append(obj) + + cylinderLst = [] + for obj in nurbsTransLst: + if 'ylinder' in obj.name() and kCapsuleNameSuffix in obj.name(): + cylinderLst.append(obj) + + return cylinderLst + + +def addCapsuleBody(): + # create capsule body for collision + # place capsule at ori point of nothing selected in scene + # place capsule match with object position and rotation if select scene object + collisionBoneList = [] + objs = pm.ls(sl=True) + + for obj in objs: + children = pm.listRelatives(obj, children=1) + + # only add capsule to the obj which has child + if children: + collisionBoneList.append([obj, children[0]]) + + if collisionBoneList: + for couple in collisionBoneList: + baseBone = couple[0] + endBone = couple[1] + capsule = createCapsuleGeometry(1) + + pm.parent(capsule, baseBone) + # match capsule to bone + endBoneTrans = endBone.getTranslation() + capsule.setTranslation(endBoneTrans * 0.5) + capsule.setAttr('scaleX', endBoneTrans[0] / 3) + capsule.setAttr('scaleY', endBoneTrans[0] / 3) + cns = pm.aimConstraint(endBone, capsule, aimVector=[1, 0, 0]) + pm.delete(cns) + + else: + capsule = createCapsuleGeometry(1) + capsule.setAttr('scaleX', 10) + capsule.setAttr('scaleY', 10) + pm.select(clear=1) + diff --git a/Scripts/Animation/springmagic/decorators.py b/Scripts/Animation/springmagic/decorators.py new file mode 100644 index 0000000..143d23b --- /dev/null +++ b/Scripts/Animation/springmagic/decorators.py @@ -0,0 +1,121 @@ +import maya.mel as mel +import maya.cmds as cmds + +from functools import wraps + + +# ----------------------------------------------------------------------------- +# Decorators +# ----------------------------------------------------------------------------- +def viewportOff(func): + """ + Decorator - turn off Maya display while func is running. + if func will fail, the error will be raised after. + """ + @wraps(func) + def wrap(*args, **kwargs): + + # Turn $gMainPane Off: + mel.eval("paneLayout -e -manage false $gMainPane") + + # Decorator will try/except running the function. + # But it will always turn on the viewport at the end. + # In case the function failed, it will prevent leaving maya viewport off. + try: + return func(*args, **kwargs) + except Exception: + raise # will raise original error + finally: + mel.eval("paneLayout -e -manage true $gMainPane") + + return wrap + + +class gShowProgress(object): + """ + Function decorator to show the user (progress) feedback. + @usage + + import time + @gShowProgress(end=10) + def createCubes(): + for i in range(10): + time.sleep(1) + if createCubes.isInterrupted(): break + iCube = cmds.polyCube(w=1,h=1,d=1) + cmds.move(i,i*.2,0,iCube) + createCubes.step() + """ + + def __init__(self, status='Busy...', start=0, end=100, interruptable=True): + import maya.mel + + self.mStartValue = start + self.mEndValue = end + self.mStatus = status + self.mInterruptable = interruptable + self.mMainProgressBar = maya.mel.eval('$tmp = $gMainProgressBar') + + def step(self, inValue=1): + """Increase step + @param inValue (int) Step value""" + cmds.progressBar(self.mMainProgressBar, edit=True, step=inValue) + + def progress(self, inValue): + """Set progression value + @param inValue (int) Progress value""" + cmds.progressBar(self.mMainProgressBar, edit=True, progress=inValue) + + def isInterrupted(self): + """Check if the user has interrupted the progress + @return (boolean)""" + return cmds.progressBar(self.mMainProgressBar, query=True, isCancelled=True) + + def start(self): + """Start progress""" + cmds.waitCursor(state=True) + cmds.progressBar( + self.mMainProgressBar, + edit=True, + beginProgress=True, + isInterruptable=self.mInterruptable, + status=self.mStatus, + minValue=self.mStartValue, + maxValue=self.mEndValue) + cmds.refresh() + + def end(self): + """Mark the progress as ended""" + cmds.progressBar(self.mMainProgressBar, edit=True, endProgress=True) + cmds.waitCursor(state=False) + + def __call__(self, inFunction): + """ + Override call method + @param inFunction (function) Original function + @return (function) Wrapped function + @description + If there are decorator arguments, __call__() is only called once, + as part of the decoration process! You can only give it a single argument, + which is the function object. + """ + def wrapped_f(*args, **kwargs): + # Start progress + self.start() + # Call original function + inFunction(*args, **kwargs) + # End progress + self.end() + + # Add special methods to the wrapped function + wrapped_f.step = self.step + wrapped_f.progress = self.progress + wrapped_f.isInterrupted = self.isInterrupted + + # Copy over attributes + wrapped_f.__doc__ = inFunction.__doc__ + wrapped_f.__name__ = inFunction.__name__ + wrapped_f.__module__ = inFunction.__module__ + + # Return wrapped function + return wrapped_f diff --git a/Scripts/Animation/springmagic/history.txt b/Scripts/Animation/springmagic/history.txt new file mode 100644 index 0000000..74c3ccf --- /dev/null +++ b/Scripts/Animation/springmagic/history.txt @@ -0,0 +1,75 @@ +##################################################################################### +# +# Spring Magic for Maya +# +# Calculate bone chain animation by settings, support collisions and wind force +# Can work with rigging controller as well +# +# Need pringMagic.ui file to work with +# This script need also icon file support, which should be put in same folder +# +# feel free to mail me redtank@outlook.com for any bug or issue +# +# Yanbin Bai +# 2021.02 +# +##################################################################################### + +3.5a +- Fix bug tension calculation introduced in the 3.5 (Benoit Degand) +- Fix bug inertia calculation introduced in the 3.5 (Benoit Degand) +- Clarify code splitting source code in class and methods (Benoit Degand) + +3.5 +- Apply PEP8 coding format (Benoit Degand) +- Add possiblity to cancel the operation (Esc) (Benoit Degand) +- Increase speed (x2), avoiding locators and aim constraints intensive creation/deletion (Benoit Degand) +- Fragment source code in several files (Benoit Degand) +- Pose Match default off + +3.4b +- fix collision bug + +3.4a +- fix wind bug + +3.4 +- add plane collision +- add pose match + +3.3 +- add inertia effect + +3.2 +- fix wind effect cannot set key issue + +3.1 +- add bind controller +- add wind +- add flex setting +- improve performance +- fix twist bug +- add capsule icon +- seperate skinTools to spring magic and skin magic + +3.0 +- re-write spring magic to improve performance +- add capsule collision for spring magic +- add donate page + +2.7.8 +- fix script stop working issue cause by highend3d.com changed their web page + +2.7.7 +- add time out for update checking in case of network issue + +2.7.6 +- fix spring magic calculation issue on MAYA 2016 +- update UI for MAYA 2016 +Thanks for all the help from Nobuyuki Kobayashi nobuyuki@unity3d.com + +2.7.5 +- add floor collision to spring magic + +2.7 +- Add spring magic diff --git a/Scripts/Animation/springmagic/icons/China Flag.png b/Scripts/Animation/springmagic/icons/China Flag.png new file mode 100644 index 0000000..15e088b Binary files /dev/null and b/Scripts/Animation/springmagic/icons/China Flag.png differ diff --git a/Scripts/Animation/springmagic/icons/addCapsule.png b/Scripts/Animation/springmagic/icons/addCapsule.png new file mode 100644 index 0000000..e4ce639 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/addCapsule.png differ diff --git a/Scripts/Animation/springmagic/icons/addPlane.png b/Scripts/Animation/springmagic/icons/addPlane.png new file mode 100644 index 0000000..af46dfa Binary files /dev/null and b/Scripts/Animation/springmagic/icons/addPlane.png differ diff --git a/Scripts/Animation/springmagic/icons/ali_pay.png b/Scripts/Animation/springmagic/icons/ali_pay.png new file mode 100644 index 0000000..2777058 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/ali_pay.png differ diff --git a/Scripts/Animation/springmagic/icons/bilibili.png b/Scripts/Animation/springmagic/icons/bilibili.png new file mode 100644 index 0000000..b1c1a9f Binary files /dev/null and b/Scripts/Animation/springmagic/icons/bilibili.png differ diff --git a/Scripts/Animation/springmagic/icons/bitcoin.png b/Scripts/Animation/springmagic/icons/bitcoin.png new file mode 100644 index 0000000..6185e08 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/bitcoin.png differ diff --git a/Scripts/Animation/springmagic/icons/clearCapsule.png b/Scripts/Animation/springmagic/icons/clearCapsule.png new file mode 100644 index 0000000..507c483 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/clearCapsule.png differ diff --git a/Scripts/Animation/springmagic/icons/ctrl_bake.png b/Scripts/Animation/springmagic/icons/ctrl_bake.png new file mode 100644 index 0000000..443ccc3 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/ctrl_bake.png differ diff --git a/Scripts/Animation/springmagic/icons/ctrl_bind.png b/Scripts/Animation/springmagic/icons/ctrl_bind.png new file mode 100644 index 0000000..7f343f2 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/ctrl_bind.png differ diff --git a/Scripts/Animation/springmagic/icons/donut.png b/Scripts/Animation/springmagic/icons/donut.png new file mode 100644 index 0000000..d7acafe Binary files /dev/null and b/Scripts/Animation/springmagic/icons/donut.png differ diff --git a/Scripts/Animation/springmagic/icons/english.png b/Scripts/Animation/springmagic/icons/english.png new file mode 100644 index 0000000..2bf1f65 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/english.png differ diff --git a/Scripts/Animation/springmagic/icons/info.png b/Scripts/Animation/springmagic/icons/info.png new file mode 100644 index 0000000..8ded416 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/info.png differ diff --git a/Scripts/Animation/springmagic/icons/japanese.png b/Scripts/Animation/springmagic/icons/japanese.png new file mode 100644 index 0000000..7967692 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/japanese.png differ diff --git a/Scripts/Animation/springmagic/icons/language.png b/Scripts/Animation/springmagic/icons/language.png new file mode 100644 index 0000000..755a11d Binary files /dev/null and b/Scripts/Animation/springmagic/icons/language.png differ diff --git a/Scripts/Animation/springmagic/icons/linkedin.png b/Scripts/Animation/springmagic/icons/linkedin.png new file mode 100644 index 0000000..cae1963 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/linkedin.png differ diff --git a/Scripts/Animation/springmagic/icons/paypal.png b/Scripts/Animation/springmagic/icons/paypal.png new file mode 100644 index 0000000..d446316 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/paypal.png differ diff --git a/Scripts/Animation/springmagic/icons/redo.png b/Scripts/Animation/springmagic/icons/redo.png new file mode 100644 index 0000000..a75ef63 Binary files /dev/null and b/Scripts/Animation/springmagic/icons/redo.png differ