Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1025 lines (766 sloc) 37.3 KB
#
# This Blender add-on creates writing animation for the selected Bezier curves
# Supported Blender Version: 2.79b
#
# License: GPL (https://github.com/Shriinivas/writinganimation/blob/master/LICENSE)
#
# Not yet pep8 compliant
import bpy
import bmesh
from bpy.props import IntProperty, FloatProperty, BoolProperty
from bpy.props import EnumProperty, PointerProperty
from mathutils import Vector, Euler, Quaternion
from math import radians, floor, ceil
from enum import Enum
DEF_ERR_MARGIN = 0.0001
DEFAULT_DEPTH = 0.001
OBJTYPE_MODIFIER = 'MODIFIER'
OBJTYPE_NONMODIFIER = 'NONMODIFIER'
NEW_DATA_PREFIX = 'WritingAnim_'
def floatCmpWithMargin(float1, float2, margin = DEF_ERR_MARGIN):
return abs(float1 - float2) < margin
def vectCmpWithMargin(vect1, vect2, margin = DEF_ERR_MARGIN):
return all(abs(vect2[i] - vect1[i]) < margin for i in range(0, len(vect1)))
def isBezier(bObj):
return bObj.type == 'CURVE' and len(bObj.data.splines) > 0 \
and bObj.data.splines[0].type == 'BEZIER'
class DrawableCurve:
def __init__(self, curveObj, objType = OBJTYPE_NONMODIFIER):
self.bCurveObj = curveObj
self.scale = curveObj.scale[:]
curveCopyData = curveObj.data.copy()
#Non-zero values of the following attributes impacts length
curveCopyData.bevel_depth = 0
curveCopyData.extrude = 0
curveCopyData.offset = 0
curveCopyObj = curveObj.copy()
curveCopyObj.data = curveCopyData
apply_modifiers = (objType == OBJTYPE_MODIFIER)
self.curveMeshData = curveCopyObj.to_mesh(bpy.context.scene, \
apply_modifiers = apply_modifiers, settings = 'PREVIEW')
#Convert co to world space and calculate length and approximate normal
tmpBM = bmesh.new()
self.curveLength = 0
self.mw = self.bCurveObj.matrix_world
for i in range(0, len(self.curveMeshData.vertices)):
self.curveMeshData.vertices[i].co = \
self.mw * self.curveMeshData.vertices[i].co
if(i > 0):
segLen = (self.curveMeshData.vertices[i].co - \
self.curveMeshData.vertices[i-1].co).length
self.curveLength += segLen
tmpBM.verts.new(self.curveMeshData.vertices[i].co)
self.startCo = self.curveMeshData.vertices[0].co
self.endCo = self.curveMeshData.vertices[-1].co
tmpFace = tmpBM.faces.new(tmpBM.verts)
tmpBM.faces.ensure_lookup_table()
#Normal of co-linear verts is not zero every time, so check area
if(floatCmpWithMargin(tmpFace.calc_area(), 0)):
self.curveNormal = Vector((0,0,0))
else:
tmpFace.normal_update()
self.curveNormal = tmpFace.normal.copy()
tmpBM.free()
#TODO: copying the object also copies the old bbox;
#find a way to force recalculation
# ~ worldBBox = []
# ~ for val in self.bCurveObj.bound_box:
# ~ worldBBox.append(mw * Vector((val[0], val[1], val[2])))
#leftBot_rgtTop
# ~ self.bbox = [min(b[0] for b in worldBBox),
# ~ min(b[1] for b in worldBBox),
# ~ max(b[0] for b in worldBBox),
# ~ max(b[1] for b in worldBBox)]
def copySrcObjProps(copyObjData, newCurveData):
#Copying just a few attributes
newCurveData.dimensions = copyObjData.dimensions
newCurveData.resolution_u = copyObjData.resolution_u
newCurveData.render_resolution_u = copyObjData.render_resolution_u
newCurveData.fill_mode = copyObjData.fill_mode
newCurveData.use_fill_deform = copyObjData.use_fill_deform
newCurveData.use_radius = copyObjData.use_radius
newCurveData.use_stretch = copyObjData.use_stretch
newCurveData.use_deform_bounds = copyObjData.use_deform_bounds
newCurveData.twist_smooth = copyObjData.twist_smooth
newCurveData.twist_mode = copyObjData.twist_mode
newCurveData.offset = copyObjData.offset
newCurveData.extrude = copyObjData.extrude
newCurveData.bevel_depth = copyObjData.bevel_depth
newCurveData.bevel_resolution = copyObjData.bevel_resolution
for material in copyObjData.materials:
newCurveData.materials.append(material)
#static method
def copyBezierPt(src, target):
target.co = src.co
target.handle_left = src.handle_left
target.handle_left_type = 'FREE'
target.handle_right = src.handle_right
target.handle_right_type = 'FREE'
#static method
def createNoncyclicSpline(curveData, srcSpline, forceNoncyclic):
spline = curveData.splines.new('BEZIER')
spline.bezier_points.add(len(srcSpline.bezier_points)-1)
if(forceNoncyclic):
spline.use_cyclic_u = False
else:
spline.use_cyclic_u = srcSpline.use_cyclic_u
for i in range(0, len(srcSpline.bezier_points)):
DrawableCurve.copyBezierPt(srcSpline.bezier_points[i],
spline.bezier_points[i])
if(forceNoncyclic == True and srcSpline.use_cyclic_u == True):
spline.bezier_points.add(1)
DrawableCurve.copyBezierPt(srcSpline.bezier_points[0],
spline.bezier_points[-1])
#static method
def getDCObjsForSpline(curveObj, objType, defaultDepth, nameStartIdx,
group = None, copyPropObj = None):
#Nurbs curve excuded for now
if(not isBezier(curveObj)):
return []
activeIdx = None #Needed, because active_index is object (not data) attribute
copyData = bpy.data.curves.new(NEW_DATA_PREFIX+'tmp', 'CURVE')
if(copyPropObj != None):
#If object is bezier curve copy curve properties and material
if(isBezier(copyPropObj)):
DrawableCurve.copySrcObjProps(copyPropObj.data, copyData)
#If not a curve copy only material
else:
DrawableCurve.copySrcObjProps(curveObj.data, copyData)
if(len(copyPropObj.data.materials) > 0):
copyMatIdx = copyPropObj.active_material_index
mat = copyPropObj.data.materials[copyMatIdx]
if(len(copyData.materials) == 0 or
mat.name not in copyData.materials):
copyData.materials.append(mat)
activeIdx = -1 #Last
else:
activeIdx = copyData.materials.find(mat.name)
else:
DrawableCurve.copySrcObjProps(curveObj.data, copyData)
dcObjs = []
idSuffix = 0
for i, spline in enumerate(curveObj.data.splines):
dataCopy = copyData.copy()
dataCopy.splines.clear()
DrawableCurve.createNoncyclicSpline(dataCopy, spline, forceNoncyclic = True)
dataCopy.animation_data_clear()
#Default settings
dataCopy.bevel_factor_mapping_end = 'SPLINE'
if(dataCopy.bevel_depth == 0):
dataCopy.bevel_depth = defaultDepth
dataCopy.offset = -defaultDepth / 2
objCopy = curveObj.copy()
objCopy.name = curveObj.name + str(idSuffix).zfill(2)
objCopy.data = dataCopy
if(dataCopy.shape_keys != None):
for i in range(0, len(dataCopy.shape_keys.key_blocks)):
objCopy.shape_key_remove(dataCopy.shape_keys.key_blocks[0])
objCopy.animation_data_clear()
bpy.context.scene.objects.link(objCopy)
if(objType == OBJTYPE_MODIFIER):
dcObj = ModifierDrawableCurve(objCopy)
else:
dcObj = DrawableCurve(objCopy)
if(activeIdx != None):
dcObj.active_material_index = activeIdx
dcObjs.append(dcObj)
if(group != None):
group.objects.link(dcObj.bCurveObj)
bpy.data.curves.remove(copyData)
return dcObjs
class ModifierDrawableCurve(DrawableCurve):
def __init__(self, curveObj):
DrawableCurve.__init__(self, curveObj, OBJTYPE_MODIFIER)
#Given the count, return the intgerpolated coordinates of the equally spaced vertices
#numPts is numSegs + 1 (first and last verts are included)
def getInterpolatedVertsCo(self, numPts):
totalLength = self.curveLength
if(floatCmpWithMargin(totalLength, 0)):
return [self.curveMeshData.vertices[0].co] * numPts
segLen = totalLength / (numPts-1)
vertCos = [self.curveMeshData.vertices[0].co]
actualLen = 0
vertIdx = 0
for i in range(1, numPts - 1):
co = None
targetLen = i * segLen
while(not floatCmpWithMargin(actualLen, targetLen)
and actualLen < targetLen):
vert = self.curveMeshData.vertices[vertIdx]
vertIdx += 1
nextVert = self.curveMeshData.vertices[vertIdx]
actualLen += (nextVert.co - vert.co).length
if(floatCmpWithMargin(actualLen, targetLen)):
co = self.curveMeshData.vertices[vertIdx].co
else: #interpolate
diff = actualLen - targetLen
co = (nextVert.co - (nextVert.co - vert.co) * \
(diff/(nextVert.co - vert.co).length))
#Revert to last pt
vertIdx -= 1
actualLen -= (nextVert.co - vert.co).length
vertCos.append(co)
vertCos.append(self.curveMeshData.vertices[-1].co)
return vertCos
def insertKF(obj, dataPath, frame):
obj.keyframe_insert(data_path = dataPath, frame = frame)
CreateWritingAnimOp.keyframeCnt += 1
#Needed in follow path constraint based animation
def setInterpolationLinear(empty):
fcs = empty.animation_data.action.fcurves
for fc in fcs:
if(fc.data_path.endswith('offset') or fc.data_path.endswith('location')):
for k in fc.keyframe_points:
k.interpolation = 'LINEAR'
def createEmptyWithInitKF(name, startFrame, initCo, initDirection, parentObjs, hide, group):
empty = bpy.data.objects.new(name, None)
empty.empty_draw_type = 'PLAIN_AXES'
bpy.context.scene.objects.link(empty)
bpy.context.scene.update()
empty.hide = hide
if(initDirection != None):
empty.rotation_mode = 'QUATERNION'
empty.rotation_quaternion = initDirection.to_track_quat('Z','Y')
insertKF(obj = empty, dataPath = 'rotation_quaternion', frame = startFrame)
if( len(parentObjs) > 0 ):
for parentObj in parentObjs:
const = empty.constraints.new(type='FOLLOW_PATH')
const.target = parentObj
const.name = parentObj.name
const.forward_axis = 'FORWARD_Y'
const.influence = 0
const.offset = 0
empty.location = initCo
insertKF(obj = empty, dataPath = 'location', frame = startFrame)
if(group != None):
group.objects.link(empty)
return empty
def addCustomWriterKFs(customWriter, empty, startFrame, endFrame, resetLocation):
if(resetLocation == True):
insertKF(obj = customWriter, dataPath = 'location', frame = (startFrame - 1))
customWriter.location = (0,0,0)
insertKF(obj = customWriter, dataPath = 'location', frame = startFrame)
insertKF(obj = customWriter, dataPath = 'location', frame = endFrame)
const = customWriter.constraints.new(type='CHILD_OF')
const.target = empty
const.name = NEW_DATA_PREFIX + 'Constraint'
const.influence = 0
insertKF(obj = const, dataPath = 'influence', frame = (startFrame - 1))
const.influence = 1
insertKF(obj = const, dataPath = 'influence', frame = startFrame)
insertKF(obj = const, dataPath = 'influence', frame = endFrame)
const.influence = 0
insertKF(obj = const, dataPath = 'influence', frame = (endFrame + 1))
def createPencil(name, co, segs = 12, tipDepth = .1, height = 1, diameter = .033,
endTipPerc = 15, endTipDia = 0.0033, sharpCutOffset = 0.01,
tilt = 45, group = None):
def createMaterialNode(obj, colorVal, matName):
if(bpy.data.materials.get(matName)== None):
mat = bpy.data.materials.new(matName)
if(bpy.context.scene.render.engine == 'CYCLES'):
mat.use_nodes = True
defNode = mat.node_tree.nodes.get("Diffuse BSDF")
defNode.inputs["Color"].default_value = colorVal
elif(bpy.context.scene.render.engine == 'BLENDER_RENDER'):
mat.diffuse_color = colorVal[:-1]
else:
mat = bpy.data.materials[matName]
obj.data.materials.append(mat)
return mat
bm = bmesh.new()
bmesh.ops.create_cone(bm, cap_ends = True, segments = segs,
depth = tipDepth * (100-endTipPerc)/100, diameter1 = endTipDia,
diameter2 = diameter)
up = Vector((0, 0, 1))
bm.normal_update()
topFace = [f for f in bm.faces if f.normal.angle(up) < radians(3)][0]
oldFaces = [f for f in bm.faces]
info = bmesh.ops.extrude_face_region(bm, geom=[topFace])
bmesh.ops.translate(bm, vec = Vector((0, 0, height)),
verts=[v for v in info["geom"] if isinstance(v, bmesh.types.BMVert)])
for f in bm.faces:
if (f not in oldFaces):
f.material_index = 0
else:
f.material_index = 1
oldFaces = [f for f in bm.faces]
offsetVerts = [v for i, v in enumerate(topFace.verts) if i % 2 == 0]
bmesh.ops.translate(bm, vec = (0, 0, sharpCutOffset), verts = offsetVerts)
info = bmesh.ops.create_cone(bm, cap_ends = True, segments = segs * 2,
depth = tipDepth * endTipPerc/100, diameter1 = 0, diameter2 = endTipDia)
bmesh.ops.translate(bm, vec = (0, 0, -tipDepth * .5), verts = info['verts'])
for f in bm.faces:
if (f not in oldFaces):
f.material_index = 2
mesh = bpy.data.meshes.new(name)
pObj = bpy.data.objects.new(name, mesh)
bpy.context.scene.objects.link(pObj)
bpy.context.scene.update()
bm.to_mesh(mesh)
bodyColor = [0.00, 0.35, 0.44, 1.00]
sharpenedTipColor = [0.80, 0.55, 0.09, 1.00]
endTipColor = [0.00, 0.00, 0.00, 1.00]
createMaterialNode(pObj, bodyColor, NEW_DATA_PREFIX+'BodyMat')
createMaterialNode(pObj, sharpenedTipColor, NEW_DATA_PREFIX + 'Tip1Mat')
createMaterialNode(pObj, endTipColor, NEW_DATA_PREFIX + 'Tip2Mat')
pObj.data.uv_textures.new()
for vert in mesh.vertices:
vert.co.z += (tipDepth + tipDepth * endTipPerc/100) / 2
pObj.rotation_euler = Euler((radians(tilt), 0,0), 'XYZ')
if(group != None):
group.objects.link(pObj)
return pObj
def createKfs(empty, dcObj, startFrame, curveFrameCnt, alignToVert, objType):
curveObj = dcObj.bCurveObj
curveObj.data.bevel_factor_end = 0
insertKF(obj = curveObj.data, dataPath = 'bevel_factor_end', frame = startFrame)
currFrame = startFrame
if(objType == OBJTYPE_NONMODIFIER):
oldRot = empty.rotation_euler.copy()
if(curveFrameCnt > 1):
if(alignToVert):
empty.rotation_euler = dcObj.mw.to_euler()
insertKF(obj = empty, dataPath = 'rotation_euler', frame = startFrame)
insertKF(obj = empty, dataPath = 'location', frame = startFrame)
if(not alignToVert):
empty.rotation_euler = dcObj.mw.inverted().to_euler()
else:
empty.rotation_euler = empty.matrix_world.inverted().to_euler()
insertKF(obj = empty, dataPath = 'rotation_euler', frame = (startFrame + 1))
empty.location = [0,0,0]
insertKF(obj = empty, dataPath = 'location', frame = (startFrame + 1))
con = empty.constraints[curveObj.name]
con.influence = 0
insertKF(obj = con, dataPath = 'influence', frame = startFrame)
con.influence = 1
insertKF(obj = con, dataPath = 'influence', frame = (startFrame + 1))
con.offset = 0
insertKF(obj = empty, dataPath = 'constraints["'+curveObj.name+'"].offset', \
frame = startFrame)
con.offset = -100
insertKF(obj = empty, dataPath = 'constraints["'+curveObj.name+'"].offset', \
frame = (startFrame + curveFrameCnt))
con.influence = 1
insertKF(obj = con, dataPath = 'influence', \
frame = (startFrame + curveFrameCnt - 1))
con.influence = 0
insertKF(obj = con, dataPath = 'influence', \
frame = (startFrame + curveFrameCnt))
insertKF(obj = empty, dataPath = 'location', \
frame = (startFrame + curveFrameCnt - 1))
insertKF(obj = empty, dataPath = 'rotation_euler', \
frame = (startFrame + curveFrameCnt - 1))
empty.location = dcObj.endCo
insertKF(obj = empty, dataPath = 'location', frame = (startFrame + curveFrameCnt))
if(not alignToVert):
empty.rotation_euler = oldRot
else:
empty.rotation_euler = dcObj.mw.to_euler()
insertKF(obj = empty, dataPath = 'rotation_euler', \
frame = (startFrame + curveFrameCnt))
else:
objCos = dcObj.getInterpolatedVertsCo(curveFrameCnt + 1)
if(alignToVert):
oldRot = empty.rotation_quaternion
rotate = dcObj.curveNormal.rotation_difference(empty.matrix_world.to_euler())
empty.rotation_quaternion = rotate
insertKF(obj = empty, dataPath = 'rotation_quaternion', frame = startFrame)
if(curveFrameCnt > 1):
insertKF(obj = empty, dataPath = 'rotation_quaternion', \
frame = (startFrame + curveFrameCnt - 1))
#Start from the second, since first is already covered by lift
for i in range(1, len(objCos)):
currFrame += 1
empty.location = objCos[i]
insertKF(obj = empty, dataPath = 'location', frame = currFrame)
curveObj.data.bevel_factor_end = 1
insertKF(obj = curveObj.data, dataPath = 'bevel_factor_end', \
frame = (startFrame + curveFrameCnt))
fc = curveObj.data.animation_data.action.fcurves.find('bevel_factor_end')
for kfp in fc.keyframe_points:
kfp.interpolation = 'LINEAR'
def getLiftMidPtCo(objStart, objEnd, liftAxis, lift, reverseLift):
startCo = objStart.endCo
endCo = objEnd.startCo
if(all(co==0 for co in objStart.curveNormal)):
moveAxis = liftAxis
else:
moveAxis = [i for i in range(0, len(objStart.curveNormal)) \
if(abs(objStart.curveNormal[i]) == abs(max(objStart.curveNormal, key=abs)))][0]
midPtCo = startCo + (endCo - startCo) / 2.
midPtCo[moveAxis] = max(startCo[moveAxis], endCo[moveAxis], key=abs)
dirn = 1
if(endCo[moveAxis] < startCo[moveAxis]):
dirn = -1
if(reverseLift):
dirn *= -1
midPtCo[moveAxis] += dirn * lift
return midPtCo
def addLiftKeyFrames(empty, objStart, objEnd, liftAxis, lift, currFrame, \
liftFrameCnt, reverseLift):
empty.location = getLiftMidPtCo(objStart, objEnd, liftAxis, lift, reverseLift)
insertKF(obj = empty, dataPath = 'location', \
frame = round(currFrame + liftFrameCnt / 2))
empty.location = objEnd.startCo
insertKF(obj = empty, dataPath = 'location', frame = (currFrame + liftFrameCnt))
def getTransitionInfo(allDcObjs, liftAxis, maxLift, transitionSpeed,
proportionalLift, reverseLift):
transitionLengths = []
transitionLifts = []
if(len(allDcObjs) <= 1):
return transitionLengths, transitionLifts
flatTransitionLengths = [(allDcObjs[i].startCo - \
allDcObjs[i-1].endCo).length for i in range(1, len(allDcObjs))]
maxFlatLength = max(flatTransitionLengths)
for i, fl in enumerate(flatTransitionLengths):
transitionLength = 0
lift = 0
startObj = allDcObjs[i]
endObj = allDcObjs[i+1]
if(maxFlatLength > 0):
if(proportionalLift):
lift = maxLift * (fl / maxFlatLength)
else:
lift = maxLift
midPtCo = getLiftMidPtCo(startObj, endObj, liftAxis, lift, reverseLift)
transitionLength = ((midPtCo - startObj.endCo).length + \
(endObj.startCo - midPtCo).length) / transitionSpeed
transitionLengths.append(transitionLength)
transitionLifts.append(lift)
return transitionLengths, transitionLifts
def getCurveDCObjs(selObjs, objType, defaultDepth, retain, copyPropObj, group = None):
curveDCObjs = []
idx = 0 #Only for naming the new objects
for obj in selObjs:
dcObjs = DrawableCurve.getDCObjsForSpline(obj, objType,
defaultDepth, idx, group, copyPropObj)
if(len(dcObjs) == 0 ):
continue
idx += len(dcObjs)
if(retain == 'Copy'):
obj.hide = True
obj.hide_render = True
curveDCObjs.append(dcObjs)
return curveDCObjs
def showOrigCurve(zeroFrameCurveDCObjs, zeroFrameOrigCurves, currFrame, retain):
for i, origCurve in enumerate(zeroFrameOrigCurves):
origCurve.scale = [0,0,0]
insertKF(obj = origCurve, dataPath = 'scale', frame = (currFrame-1))
origCurve.scale = zeroFrameCurveDCObjs[i][0].scale[:]
insertKF(obj = origCurve, dataPath = 'scale', frame = (currFrame))
if(retain != 'Both'):
for dcObj in zeroFrameCurveDCObjs[i]:
dcObj.bCurveObj.scale = dcObj.scale[:]
insertKF(obj = dcObj.bCurveObj, dataPath = 'scale', frame = (currFrame-1))
dcObj.bCurveObj.scale = [0,0,0]
insertKF(obj = dcObj.bCurveObj, dataPath = 'scale', frame = (currFrame))
#TODO: Find a better way to calculate frame count proportional to the length
def getFrameCntForLength(totalFrames, totalLength, remainingLength,
remainingFrames, elemLength):
cnt1 = remainingFrames * elemLength / remainingLength
return floor(cnt1)
def main(retain, defaultDepth, startFrame, totalFrames,
liftAxis, maxLift, transitionSpeed, alignToVert, proportionalLift, objType,
copyPropObj, customWriter, reverseLift, resetLocation):
selObjs = [o for o in bpy.data.objects if o in bpy.context.selected_objects
and o != customWriter and isBezier(o)]
# ~ selObjs = bpy.context.selected_objects[:] #sequence incorrect
group = bpy.data.groups.new(NEW_DATA_PREFIX+'Group')
curveDCObjs = getCurveDCObjs(selObjs, objType, defaultDepth,
retain, copyPropObj, group)
currFrame = -1
if(len(curveDCObjs) == 0):
return currFrame
initCo = curveDCObjs[0][0].startCo
initTangent = None
if(alignToVert and objType == OBJTYPE_MODIFIER):
initTangent = curveDCObjs[0][0].curveNormal
allDcObjs = [d for c in curveDCObjs for d in c]
parentObjs = []
if(objType == OBJTYPE_NONMODIFIER):
parentObjs = [o.bCurveObj for o in allDcObjs]
empty = createEmptyWithInitKF(NEW_DATA_PREFIX + 'Guide', startFrame, initCo,
initTangent, parentObjs = parentObjs, hide = False, group = group)
if(customWriter == None):
pObj = createPencil(NEW_DATA_PREFIX + 'Writer', initCo, tilt = 45, group = group)
pObj.parent = empty
transitionLengths, transitionLifts = \
getTransitionInfo(allDcObjs, liftAxis, maxLift, transitionSpeed, \
proportionalLift, reverseLift)
totalLength = sum(o.curveLength for o in allDcObjs) + sum(transitionLengths)
remainingLength = totalLength
currFrame = startFrame
zeroFrameCurves = []
oldOrigCurveIdx = 0
lastButOneFrame = False
i = 0
#inline method, makes changes to local variables
def _createPendingKfs(empty, alignToVert):
nonlocal zeroFrameCurves
nonlocal currFrame
nonlocal remainingLength
length = 0
for dcObj in zeroFrameCurves:
createKfs(empty, dcObj, currFrame, curveFrameCnt = 1,
alignToVert = alignToVert, objType = objType)
length += dcObj.curveLength
zeroFrameCurves = []
currFrame += 1
remainingLength -= length
for j, curveDcObj in enumerate(curveDCObjs):
for k, dcObj in enumerate(curveDcObj):
if(lastButOneFrame):
zeroFrameCurves.append(dcObj)
continue
if(i > 0):
remainingFrames = totalFrames - currFrame + startFrame
liftFrameCnt = getFrameCntForLength(totalFrames, totalLength,
remainingLength, remainingFrames, transitionLengths[i-1])
if(liftFrameCnt == remainingFrames and
i < len(allDcObjs)-1):
lastButOneFrame = True
zeroFrameCurves.append(dcObj)
continue
if(liftFrameCnt > 0):
if(len(zeroFrameCurves) > 0):
addedLength = \
_createPendingKfs(empty, alignToVert)
liftFrameCnt -= 1
if(liftFrameCnt > 0):
addLiftKeyFrames(empty, prevDcObj, dcObj, liftAxis,
transitionLifts[i-1], currFrame, liftFrameCnt, reverseLift)
currFrame += liftFrameCnt
remainingLength -= transitionLengths[i-1]
remainingFrames = totalFrames - currFrame + startFrame
if(i == len(allDcObjs)-1):
curveFrameCnt = remainingFrames
else:
curveFrameCnt = getFrameCntForLength(totalFrames, totalLength,
remainingLength, remainingFrames, dcObj.curveLength)
#Condition will be rare, but should be taken care of
if(curveFrameCnt >= remainingFrames and i < len(allDcObjs)-1):
lastButOneFrame = True
curveFrameCnt -= 1
if(curveFrameCnt == 0):
zeroFrameCurves.append(dcObj)
totalEndingFrameCnt = round(remainingFrames * \
(sum(o.curveLength for o in zeroFrameCurves) / remainingLength))
if(totalEndingFrameCnt > 0 and
not lastButOneFrame and remainingFrames > 1):
_createPendingKfs(empty, alignToVert)
else:
if(curveFrameCnt == 1):
zeroFrameCurves.append(dcObj)
_createPendingKfs(empty, alignToVert)
else:
if(len(zeroFrameCurves) > 0):
_createPendingKfs(empty, alignToVert)
curveFrameCnt -= 1
createKfs(empty, dcObj, currFrame, curveFrameCnt,
alignToVert, objType)
currFrame += curveFrameCnt
remainingLength -= dcObj.curveLength
prevDcObj = dcObj
i += 1
if(retain != 'Copy'):
totalProcessed = sum(len(c) for x, c in enumerate(curveDCObjs) if x <= j)
totalProcessed -= len(zeroFrameCurves)
newOrigCurveIdx = 0
processed = 0
while(processed < totalProcessed):
processed += len(curveDCObjs[newOrigCurveIdx])
newOrigCurveIdx += 1
if(processed > totalProcessed):
newOrigCurveIdx -= 1
showOrigCurve(curveDCObjs[oldOrigCurveIdx:newOrigCurveIdx],
selObjs[oldOrigCurveIdx:newOrigCurveIdx], currFrame, retain)
oldOrigCurveIdx = newOrigCurveIdx
if(len(zeroFrameCurves) > 0):
addedLength = _createPendingKfs(empty, alignToVert)
if(retain != 'Copy'):
showOrigCurve(curveDCObjs[oldOrigCurveIdx:], selObjs[oldOrigCurveIdx:],
currFrame, retain)
setInterpolationLinear(empty)
if(customWriter != None):
addCustomWriterKFs(customWriter, empty, startFrame, currFrame, resetLocation)
if(bpy.context.scene.frame_end < currFrame):
bpy.context.scene.frame_end = currFrame
bpy.context.scene.frame_current = startFrame
return currFrame
class CreateWritingAnimParams(bpy.types.PropertyGroup):
retain = EnumProperty(name="Retain",
items = [('Both', 'Both', ""), ('Original', 'Original', ""),
('Copy', 'Copy', "")],
default = 'Copy',
description='What to Retain After Finishing Animation')
startFrame = IntProperty(
name = "Start",
description = "Start Frame of Animation",
min = 1,
default = 1)
totalFrames = IntProperty(
name = "Length",
description = "Total Animation Frames",
min = 1,
default = 1000)
transitionSpeed = FloatProperty(
name = "Speed",
description = "Speed of Writer During Lift (X Times Normal Speed)",
min = 0.1,
default = 1.5)
maxLift = FloatProperty(
name = "Max Lift",
description = "Maximum Upward Distance to Traverse During Transition",
precision = 3,
default = 0.3)
proportionalLift = BoolProperty(
name = "Proportional Lift",
description = "Should Lift Height Be Proportional to Transition Distance",
default = True)
liftAxis = EnumProperty(name="Axis",
items = [('0', 'X', ""), ('1', 'Y', ""), ('2', 'Z', "")],
default = '2',
description='Axis Along Which Writer Is to Be Lifted (If Curve Is Linear)')
reverseLift = BoolProperty(
name = "Reverse",
description = "Reverse Direction of Lift",
default = False)
alignToVert = BoolProperty(
name = "Aligned (Experimental)",
description = "Align Writer to Curve Normal (Works Differently Based on Anim Types)",
default = False)
resetLocation = BoolProperty(
name = "Reset Location",
description = "Reset Location of Custom Writer to Origin For Anim Duration",
default = True)
animType = EnumProperty(name = "Type",
description='Follow Path Or Location Based Animation.',
items = [(OBJTYPE_NONMODIFIER, 'Follow Path', ""),
(OBJTYPE_MODIFIER, 'Location', "")],
default = OBJTYPE_MODIFIER)
copyPropertiesCurve = PointerProperty(
name = 'Properties of',
description = "Copy Properties (Material, Bevel Depth etc.) of Object",
type = bpy.types.Object)
customWriter = PointerProperty(
name = 'Custom Writer',
description = "Custom Object To Be Used As Writer",
type = bpy.types.Object)
class CreateWritingAnimOp(bpy.types.Operator):
bl_idname = "object.create_writing_anim"
bl_label = "Create Writing Animation"
bl_options = {'REGISTER', 'UNDO'}
keyframeCnt = 0
def execute(self, context):
CreateWritingAnimOp.keyframeCnt = 0
retain = context.window_manager.createWritingAnimParams.retain
startFrame = context.window_manager.createWritingAnimParams.startFrame
totalFrames = context.window_manager.createWritingAnimParams.totalFrames
transitionSpeed = context.window_manager.createWritingAnimParams.transitionSpeed
liftAxis = int(context.window_manager.createWritingAnimParams.liftAxis)
maxLift = context.window_manager.createWritingAnimParams.maxLift
alignToVert = context.window_manager.createWritingAnimParams.alignToVert
proportionalLift = context.window_manager.createWritingAnimParams.proportionalLift
reverseLift = context.window_manager.createWritingAnimParams.reverseLift
animType = context.window_manager.createWritingAnimParams.animType
copyPropObj = context.window_manager.createWritingAnimParams.copyPropertiesCurve
customWriter = context.window_manager.createWritingAnimParams.customWriter
resetLocation = context.window_manager.createWritingAnimParams.resetLocation
if(copyPropObj == None or not hasattr(copyPropObj, 'type') or \
copyPropObj.type not in(['CURVE','MESH'])):
copyPropObj = None
if(customWriter == None or not hasattr(customWriter, 'type') or \
customWriter.type not in(['CURVE','MESH'])):
customWriter = None
endFrame = main(retain, DEFAULT_DEPTH, startFrame, totalFrames,
liftAxis, maxLift, transitionSpeed, alignToVert, proportionalLift, animType,
copyPropObj, customWriter, reverseLift, resetLocation)
if(endFrame < 0):
self.report({'WARNING'}, "No Curve Objects Selected to Create Animation")
else:
self.report({'INFO'}, "Created "+
str(CreateWritingAnimOp.keyframeCnt)+ " new keyframes")
return {'FINISHED'}
class SeparateSplinesObjsOp(bpy.types.Operator):
bl_idname = "object.separate_splines"
bl_label = "Separate Bezier Splines"
bl_options = {'REGISTER', 'UNDO'}
def execute(self, context):
selObjs = bpy.context.selected_objects
changeCnt = 0
splineCnt = 0
if(len(selObjs) == 0):
self.report({'WARNING'}, "No Curve Objects Selected")
return {'FINISHED'}
for obj in selObjs:
if(not isBezier(obj) or len(obj.data.splines) <= 1):
continue
for i, spline in enumerate(obj.data.splines):
objCopy = obj.copy()
objCopy.name = obj.name+"_"+ str(i).zfill(4)
dataCopy = obj.data.copy()
dataCopy.splines.clear()
objCopy.data = dataCopy
DrawableCurve.createNoncyclicSpline(dataCopy,
spline, forceNoncyclic = False)
bpy.context.scene.objects.link(objCopy)
bpy.context.scene.objects.unlink(obj)
bpy.data.curves.remove(obj.data)
bpy.data.objects.remove(obj)
changeCnt += 1
splineCnt += (i + 1)
self.report({'INFO'}, "Separated "+ str(changeCnt) + " curve object" + \
("s" if(changeCnt > 1) else "") + " into " +str(splineCnt) + " new ones")
return {'FINISHED'}
bl_info = {
"name": "Create Writing Animation",
"category": "Animation",
}
class CreateWritingAnimPanel(bpy.types.Panel):
bl_label = "Writing Animation"
bl_idname = "object.writinganim"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_category = "Writing Animation"
bl_context = "objectmode"
def draw(self, context):
layout = self.layout
obj = context.object
col = layout.column()
col.operator("object.separate_splines")
col.separator()
col.label(text="Animation", icon="ANIM")
col.prop(context.window_manager.createWritingAnimParams, "animType")
col.prop(context.window_manager.createWritingAnimParams, "startFrame")
col.prop(context.window_manager.createWritingAnimParams, "totalFrames")
col.prop(context.window_manager.createWritingAnimParams, "retain")
col.prop(context.window_manager.createWritingAnimParams, "copyPropertiesCurve")
col.separator()
col.label(text="Transition", icon="MAN_TRANS")
col.prop(context.window_manager.createWritingAnimParams, "transitionSpeed")
col.prop(context.window_manager.createWritingAnimParams, "maxLift")
col.prop(context.window_manager.createWritingAnimParams, "liftAxis")
col.prop(context.window_manager.createWritingAnimParams, "proportionalLift")
col.prop(context.window_manager.createWritingAnimParams, "reverseLift")
col.separator()
col.label(text="Writer", icon="LINE_DATA")
col.prop(context.window_manager.createWritingAnimParams, "alignToVert")
col.prop(context.window_manager.createWritingAnimParams, "customWriter")
col.prop(context.window_manager.createWritingAnimParams, "resetLocation")
col.separator()
col.operator("object.create_writing_anim")
def register():
bpy.utils.register_class(CreateWritingAnimPanel)
bpy.utils.register_class(CreateWritingAnimOp)
bpy.utils.register_class(SeparateSplinesObjsOp)
bpy.utils.register_class(CreateWritingAnimParams)
bpy.types.WindowManager.createWritingAnimParams = \
bpy.props.PointerProperty(type=CreateWritingAnimParams)
def unregister():
bpy.utils.unregister_class(CreateWritingAnimPanel)
bpy.utils.unregister_class(CreateWritingAnimOp)
bpy.utils.unregister_class(SeparateSplinesObjsOp)
del bpy.types.WindowManager.createWritingAnimParams
bpy.utils.unregister_class(CreateWritingAnimParams)
if __name__ == "__main__":
register()
You can’t perform that action at this time.