On the torus wiki page there is an animation of a punctured torus. How would one go about creating same in blender?

enter image description here

In case someone wants to elaborate on @MutantBob shapekey sript answer here are the Parametric equations of a torus.

class Torus():
    def __init__(self, R, r):
        self.R = R
        self.r = r

    def point(self, theta, zi):
        r = self.r
        R = self.R
        def x(theta, zi):
            return (R + r * cos(zi)) * cos(theta)
        def y(theta, zi):
            return (R + r * cos(zi)) * sin(theta)
        def z(theta, zi):
            return r * sin(zi)

        return x(zi, theta), y(zi, theta), z(zi, theta)

Example usage

torus = Torus(2, 1)
SEGS = 10
bm = bmesh.new()
# outer circle
matrix = []
angles = [radians(a * 360.0 / SEGS) for a in range(SEGS)]
for theta in angles:
    # inner circles.
    ring = [bm.verts.new(torus.point(zi, theta)) for zi in angles]

for i in range(SEGS):
    I = (i + 1) % SEGS
    for j in range(SEGS):
        J = (j + 1) % SEGS

mesh = bpy.context.object.data      

And we can use the matrix transpose to get the Final shapekey where the loops and rings are swapped.

sk = obj.shape_key_add(name="Start")
sk = obj.shape_key_add(name="End")
for i in range(SEGS):
    for j in range(SEGS):
        sk.data[matrix[i][j].index].co = matrix[j][i].co

Changing coords on end shapekey and some subsurf.

sk.data[matrix[i][j].index].co = matrix[(j+6) % SEGS][i].co.zxy

enter image description here

Useful links.




  • $\begingroup$ a video youtube.com/watch?v=P5RLa3g-nwQ $\endgroup$
    – lemon
    Jul 3, 2016 at 10:52
  • $\begingroup$ You are very close to the result. Compared to what I did first to last frame, the point is you need an intermediate transition. This intermediate transition is here to stretch the puncture around the other parts of the mesh. As shape keys are linear, you'll need to use (at least) two SK. $\endgroup$
    – lemon
    Jul 5, 2016 at 17:51
  • $\begingroup$ related en.wikipedia.org/wiki/Surface_(topology) $\endgroup$
    – lemon
    Jul 5, 2016 at 17:57
  • $\begingroup$ Yep,, my gut feel is the Villarceau circles have something to do with it. Also if the angles to create torus above were in 60 degree increments starting from 30, then would have a middle strip more like the wiki anim. The add primitive always has verts on the "equator". $\endgroup$
    – batFINGER
    Jul 5, 2016 at 20:14
  • $\begingroup$ The equation of a torus I happened to already know. What I haven't fully imagined yet is the equation describing the deformation of the torus over time as it mutates from one shape to the other. Lemon's answer is a good starting point, it just is missing the circular form of the puncture perimeter at time=0.5 . $\endgroup$
    – Mutant Bob
    Jul 5, 2016 at 20:51

4 Answers 4


This question is a nice example for showing how to morph one mesh into another. Lets take a general approach that will work on multiple shapes and lets make the morphing nice (where non-linear morphing is needed).

First we will need the start and end meshes. In this example, the diameter of the hole of the start torus is the same like thickness of the end torus. Because the diameter of subdivision surfaces is different than the control cage, I chose to work with real geometry. This method shouldn't depend on vertex count anyway, so let's have some nicely detailed toruses (subdiv surfaces don't produce perfectly circular shapes).

I obtained these by spinning touching circles around 3d cursor locations:

enter image description here

There are 32 rings with 32 vertices each on both toruses. Now we could map the start vertices to the end vertices (by fiddling with their id, or based on vertex paint, or UV unwrap data,..) and make appropriate shape-key (by scripting), but this is not the intention, because such morphing will result in linear ugly motion, which could cause self-intersecting and other non-desired results.

We will convert edges of the meridians of the start torus into bone chains, and rotate them into the final positions as equators of the end torus. Then we will skin a mesh to this armature and render it.

As the bones will come from Skin modifier, we need to mark the root vertices and also split the toruses for chain ends first (seen as 2 hard lines in shading - because of duplicite vertices, you can split vertices with V when the ring is selected). Unwanted edges were removed, to leave only the chains:

enter image description here

Let's put Skin modifiers on, adjust the thickness and set the root vertices. Setting the roots location and where the chains end will shape the look of animation (roots highlighted and ends visible by shading):

enter image description here

Both toruses are still separate objects, so we'll get 2 armatures. Delete unwanted bones, you'll end up with 2 bone chains per ring:

enter image description here

Similarly we can map any corresponding topologies onto each other to morph them.

The corresponding start and end bones have to have a corresponding name inside those armatures. To fix it, here is a script to name the root bones based on shared location and direction and the chain bones based on chain distance (parent count) from roots. The bones also get a start/end prefix, because we will merge them into single armature later (to have a clean scene). Name the start armature Start, and the end one End:

import bpy

start = bpy.data.objects["Start"]
end = bpy.data.objects["End"]

i = 0
for pboneA in start.pose.bones:
    for pboneB in end.pose.bones:
        if ((pboneB.head - pboneA.head).length_squared < 0.001 and
            ((pboneA.tail - pboneA.head).dot(pboneB.tail - pboneB.head) > 0)):

            pboneA.name = "Start_Chain_" + str(i) + "_Bone_0"
            pboneB.name = "End_Chain_" + str(i) + "_Bone_0"
            i += 1

for object in [start, end]:
    for pbone in object.pose.bones:
        parents = pbone.parent_recursive
        if len(parents):
            pbone.name = parents[-1].name[:-1] + str(len(parents))

for pbone in start.data.bones:
    pbone.use_inherit_rotation = False
    pbone.use_inherit_scale = False

for pbone in end.data.bones:
    pbone.use_deform = False

You can now join the armatures together Ctrl+J. The deformation on end bones was also deactivated by the script.

The next script will create a custom property on the armature to drive the blending:

import bpy

def add_driver(expression, item, property, property_index = -1):

    driver = item.driver_add(property, property_index)
    driver.driver.type = 'SCRIPTED'
    driver.driver.expression = expression
    var = driver.driver.variables.get("blend")
    var = driver.driver.variables.new()
    var.name = "blend"
    var.targets[0].id = bpy.context.active_object
    var.type = 'SINGLE_PROP'
    var.targets[0].data_path = 'data["blend"]'

bpy.context.active_object.data["blend"] = 0.0
bpy.context.active_object.data["_RNA_UI"] = {"blend": {"min":0.0,

pbones = bpy.context.active_object.pose.bones

for pboneA in [pbone for pbone in pbones if "Start" in pbone.name]:

    pboneB = pbones[pboneA.name.replace("Start","End")]

    constraint = pboneA.constraints.new('COPY_ROTATION')
    constraint.target = bpy.context.active_object
    constraint.subtarget = pboneB.name
    add_driver("blend", constraint, "influence")

    l_A = (pboneA.head - pboneA.tail).length
    l_B = (pboneB.head - pboneB.tail).length
    add_driver("(1-blend) + blend * " + str(l_B/l_A), pboneA, "scale", 1)

This is the result with skinned mesh:

enter image description here

Lastly we will unwrap the torus mesh into a single rectangle UV with the point where the hole will be in the middle. The intention is to animate a material mask to create the transparent hole. In the picture are marked seams and the UV map - I used UV Squares addon to have such nice regular UV map:

enter image description here

In the material lets use a procedural gradient texture set to Quadratic Sphere, offset into the center of UV space, with an animated threshold (the Color Ramp node is animated):

enter image description here

The only problem is that the texture does not warp with the hole. Good news is the procedural spherical gradient to drive the texture warping effect is already in place:

enter image description here

There is the displacement mask on the left, UV coordinates in the middle with progressively blended displacement from 0 to 100% influence and the warped texture on the right.

This is the final result:

enter image description here

And a finished .blend to peak into:

  • 1
    $\begingroup$ Of the ones posted so far, this is my favorite. It still doesn't capture the geometry of the original animation (notice that the border of the hole appears to be a circle at the halfway point in the original), but it is better than the ones that look like a plus in the early stages. $\endgroup$
    – Mutant Bob
    Jul 10, 2016 at 19:59
  • $\begingroup$ @MutantBob yeah I am aware of the circular opening, you are right. Unfortunately to do that it requires more complicated rigging - stretchy IK chains. I just covered basics like generating control armature, controlling the bones with code and dealing with surface stretching and it ended up longer than I wanted:). A different shape might not have this problem with the hole mid-way though. The original animation looks like it came from some math model. $\endgroup$ Jul 10, 2016 at 20:13
  • $\begingroup$ @Jerryno Here is a looping version of your gif. Took me a long time to get it under the 2mb limit. Hope you like it. Awesome answer! The result looks great. $\endgroup$
    – David
    Jul 16, 2016 at 19:27
  • $\begingroup$ @David with what tool did you managed to do that? I tried 2 programs and several online converters and it was always >2MB looped..I'll put the looped gif into the answer, thanks! $\endgroup$ Jul 17, 2016 at 9:56
  • $\begingroup$ @Jerryno I tried everything till one worked :) combination of ScreenToGif, then fireworks compressed it. $\endgroup$
    – David
    Jul 17, 2016 at 15:15

Two steps answer here : initial post with more bones (so longer to achieve) and the new one (with explanations) less bones but also less precision in the texture (should be fixed / ameliorated).

The two parts are on the same principle : using bones and animating.

First answer

enter image description here

Not perfect, really... this is just a rig. So no math and no simple deform modifiers... only bones placement.

Here is the blend file :

I hope someone will give a more elegant solution.

Second answer (with explanations)

The blend file below contains 7 layers. Each correspond to a step of the job, with textual indications.

  • Making the torus

Use a plane divided, with two simple deform modifiers. This approach is not really needed, but this was the 'origin' of this question. Using this modifier also allows to have a flat starting surface for the UV map. The simple deform (bending) configuration is -360 then 360, with driving empties respectively rotated to (90, 0 ,0) and (90, 0, 90).

The plane is subdivided (but less than in the first answer above). This subdivisions are chosen so that we have summits along the axis. Also, the amount of subdivisions is the same in X and Y : the reversion principle (longitude becomes latitude) drives to this choice (not mandatory but easier).

enter image description here

  • The initial (blue) and target (green) positions

The torus need to be reverted from the puncture. In this process, we have to keep in mind that longitude of the first will become latitude of the second.

enter image description here

  • Symmetries

As we can see below, the shapes are symmetrical in X and Z. It is not in Y because of the puncture itself (symbolized by a sphere empty on the picture). Here the two shapes are shifted in Y, but this is just a choice and not mandatory.

enter image description here

Finally the symmetry allows us to use a mirror modifier, with X and Z. This is important because we'll later have to manipulate "many bones" (so working by quarter is better).

enter image description here

  • Guidelines

Finally, we have this two shapes : in blue the shape that will be animated and in green its target. We'll see in the next steps that the green mesh will help to locate the vertices final positions.

enter image description here

  • Puncture

Add the puncture. To do that, triangulate the face Ctrl T, subdivide the 3 edges that converge two the puncture center, drive the newly created vertices near this center, and remove the center itself. We notice that had an impact on the UV map (on the proportionality of the surfaces) : that will be to correct later.

enter image description here

  • Creating the armature

We want a bone per vertex. To do it quickly, add an armature, set our mesh as parent for this armature, go to the duplication options of the mesh : choose 'verts', and 'rotation'. Now go to edit mode of the initial armature and rotate it to Y in order to have all dupli bones along the vertices normals (not mandatory, but easier to see the bones after that).

enter image description here

Now, go to the object menu of the mesh, then 'apply' and 'make duplicates real' (or Shift Ctrl A).

We now have all the bones separated but they are not single user (they share the same data). So (still having all the bones selected) go to 'object' again, 'make single user' then 'object and data'.

Now you can join all the (ex)dupli bones together in a single armature : Ctrl J.

  • Parenting the mesh to the armature

Select the mesh and select the armature, use Ctrl P then the envelope options. The envelope will 'guaranty' here that no vertices misses a weighting (if we were using automatic weights we may miss some vertices). The only point to fix manually : the 3 puncture vertices that are to close to be weighted individually.

  • Animation

We'll animate from initial position to target and initial again. I have chosen here to do it at frames 1, 120 and 240. There will be intermediate steps at frames 60 and 180.

Enter to the pose mode of the armature, select all the bones and add this as initial position in the pose library. Then insert a keyframe for the frame 1 in the timeline (I then 'location').

Now we want to go to the target position (go in frame 120 of the timeline).

To help for that, activate the snap options and set it to 'vertex'.enter image description here

Doing that we can use the green target torus as target for the snapping (select the bone, move the mouse to the target location, then G and click) :

enter image description here

We have to do that for each bone... "just" follow the longitude vertices of the mesh and grab them progressively to the corresponding latitudes of the target. At the end, you will obtain the torus reversion final position (with inverted normals as expected) :

Add this as 'final' pose in the pose library.

enter image description here

But from frame 1 to 120, the bones are going straight to their final positions :

enter image description here

So we have to drive the puncture in the outer of the mesh.

Note the names of the 3 concerned bones so that you can identify them in frame 60. Go to frame 60. Select the top left one and drive it along Z. Select the bottom right one and drive it along X. And for the last one, drive it along X/Z. Insert the key frames for this frame 60.

The 3 bones positions at frame 60 : enter image description here

Add these positions to a new pose in the library (named 'intermediate').

Last steps : Go to frame 180, get the intermediate pose in the library and insert key frames. Then go to frame 240, and do the same with the initial pose.

Here is the blend file : it contains 7 layers with explanations of each step.


The first version looks better, except for the puncture. I wont have time to redo it, but I think the 7x7 vertices of the first version are needed (the second version is 5x5), especially for the textures.

Also, the intermediate step of the second version is too enhance a bit, concerning the other vertices around the puncture (I wont have time to do it).

Probably, it is possible to avoid the mirror modifier at some step... and to mirror the bones (X and Z ?)... I don't know.

  • 1
    $\begingroup$ please add more detail to your answer. Looking at at blend file does not imply that the users are going to understand what you did and what the logic is for whatever path you took to get there. $\endgroup$
    – user1853
    Jul 2, 2016 at 19:53
  • 3
    $\begingroup$ Thought I agree with you that the answer doesn't provide the explanation on how to do it I don't think that it deserves a downvote since @lemon 's effort here is worth appreciating. $\endgroup$
    – Paul Gonet
    Jul 2, 2016 at 20:11
  • $\begingroup$ @cegaton I will provide some explanatios, and probably some enhancements, too. $\endgroup$
    – lemon
    Jul 3, 2016 at 5:44
  • $\begingroup$ @batFINGER, I am currently working on a new version (with less bones), but the same principle. I felt bones were more easy to manipulate than shape keys suggested by Mutant Bob. A revision of the answer soon... $\endgroup$
    – lemon
    Jul 3, 2016 at 8:16
  • $\begingroup$ @batFINGER yes great ! thanks... I did not think about that $\endgroup$
    – lemon
    Jul 3, 2016 at 10:48

Python approach

  • Definition of slices and rings

I use ring to indicate a cut along the small circle, and slices to indicate a cut along the large circle.

Slices have a angle for 0 inner (in blue) to 180° outer (in red) : left part of the picture below. Rings have a angle for 0 right (in blue) to 180° left counterclockwise (in red) : right part of the picture below.

enter image description here

  • Simplest torus

If we look at the simplest torus quarter, a quarter of torus with 3 vertices in each direction, we can see :

  • 180° slice moves to its "opposite", turning 180° around z (top flat cone of the picture below)
  • 90° slice moves to the center, turning 90° (left cone, not flat as stretched to x)
  • 0° slice translate to its "opposite" (turning 0° ; bottom flat cone)

So we have here the principle of the movement we need.

enter image description here

  • Animation (or almost)

Here is the result : this is not a 1 torus animation, as I do not know how to do it from there (not skilled enough in Blender and Python), but we can see each step of the animation (so surely, some will know how to do that).

enter image description here

  • Python code

Note : I know how to code, but I am very new in Python and Blender API. So, surely many things are to be enhanced here.

The code is commented, so just some precision about the key point : what is driving the vertices movements.

It is all in the function called "mySlerp".

This function is in fact a mix between a slerp and a translation. Each vertex knows its starting point (normalized) and end point (normalized also).

The most inner slices are mainly translating from start to end. And outer slices are mainly slerped from start to end.

So these two interpolated vectors are calculated and mixed depending of their slice angle value.

The movement is not really perfect : it gets bad if two many vertices, so I used few and compensated by a subsurface modifier.

Note : I did not used Vector.slerp as it has some unwanted limitations when angles are opposite.

import bmesh
import bpy

from math import *
from mathutils import Vector
from mathutils import Euler

rad0 = radians( 0.0 )
rad1 = radians( 1.0 )
rad45 = radians( 45.0 )
rad90 = radians( 90.0 )
rad180 = radians( 180.0 )

#Makes faces
def Faces( bm, r1, r2, segs, open ):
    for i in range(1, segs) :
        bm.faces.new( [r1[i-1], r1[i], r2[i], r2[i-1]] )
    if not open :
        bm.faces.new( [r1[segs-1], r1[0], r2[0], r2[segs-1]] )

#Makes faces from a matrix of rings
def FacesFromRings( bm, mat, segs, open, puncture = False ) :
    prevRing = mat[0]
    for i in range(1, segs) :
        ring = mat[i]
        Faces( bm, prevRing, ring, segs, open )    
        prevRing = ring
    if not open :
        if puncture:
            Faces( bm, prevRing, mat[0], segs-1, open )
            Faces( bm, prevRing, mat[0], segs, open )

#Makes faces from a matrix of slices
def FacesFromSlices( bm, mat, segs, open ) :
    prevSlice = mat[0][1]
    for i in range(1, segs) :
        slice = mat[i][1]
        Faces( bm, prevSlice, slice, segs, open )    
        prevSlice = slice
    if not open :
        Faces( bm, prevSlice, mat[0][1], segs, open )

def MakeScale( v, t, dim ):
    if v == 0 :
        return 1
        return (v - (t * (v - dim))) / v

def TFactor( t ):
    return -2.0 *(abs(t - 0.5) - 0.5)

def Norm( v, start, end ):
    if start == end:
        return 1
        return (v - start) / (end - start)

class Vertex():
    def __init__( self, vector, ringAngle, sliceAngle ):
        #A vertex knows :
        self.vector = vector #The initial point location
        self.vectorNorm = sqrt(vector.dot(vector)) #vertex size from the origin
        self.vectorN = Vector( vector )
        self.vectorN.normalize() #Normilzed vector for the location

        self.ringAngle = ringAngle #Position of the point along the "torus rings"
        self.sliceAngle = sliceAngle #Position of the point along the "torus slices"
        self.t100 = {}
        self.t100N = {}

    def SetT100( self, t100 ):
        #Attaches data at the target point
        self.t100 = t100
        self.t100Norm = sqrt(t100.vector.dot(t100.vector))
        self.t100N = Vector( t100.vector )

class Torus():
    def __init__(self, R, r, segs, turns ):
        self.R = R
        self.r = r
        self.Segs = segs
        self.Turns = turns
        self.Rings = []
        self.Open = turns != 360.0

    def point(self, ringAngle, sliceAngle):
        '''parametric_equations''' #batFINGER code for parametric torus (few renaming)
        r = self.r
        R = self.R
        def x(ringAngle, sliceAngle):
            return r * sin(sliceAngle)
        def y(ringAngle, sliceAngle):
            return (R + r * cos(sliceAngle)) * cos(ringAngle)
        def z(ringAngle, sliceAngle):
            return (R + r * cos(sliceAngle)) * sin(ringAngle)

        return x(ringAngle, sliceAngle), y(ringAngle, sliceAngle),  z(ringAngle, sliceAngle)

    def Initialize( self ):
        #Torus initialisation

        #Calculate the iteration amounts
        effectiveSegs = self.Segs
        if self.Open:
            effectiveSegs = self.Segs - 1

        angles = [radians(a * turns / effectiveSegs) for a in range(self.Segs)]
        reversedAngles = [a for a in reversed(angles)]

        #Makes the initial geometry
        self.Rings = []
        for ringAngle in angles:
            ring = [Vertex( Vector( self.point(ringAngle, sliceAngle) ), ringAngle, rad180 - sliceAngle ) for sliceAngle in angles]

        #Attach final geometry
        for ring in self.Rings:
            for vertex in ring:
                vertex.SetT100( self.MakeSliceVertexAtT100( vertex ) )

    def RingMatrixToBMesh( self, bm ):
        #Creates the geometry for the torus matrix
        self.RingMatrixToBMeshFromExt( bm, self.Rings )

    def RingMatrixToBMeshFromExt( self, bm, matrix, puncture = False ):
        #Creates the geometry for the given matrix
        mat = []
        for ring in matrix:
            matPart = [bm.verts.new( [v.vector.x, v.vector.y, v.vector.z] ) for v in ring]
            mat.append( matPart )

        FacesFromRings( bm, mat, self.Segs, self.Open, True )

    def FindVertexIndex( self, mat, ringAngle, sliceAngle ):
        partIndex = 0
        for part in mat:
            vIndex = 0
            for v in part:
                if v.ringAngle == ringAngle and v.sliceAngle == sliceAngle:
                    return partIndex, vIndex
                vIndex += 1
            partIndex +=1
        return -1, -1

    def ToT100( self ):
        #Calculation for t = 100%        
        result = []
        for ring in self.Rings:
            resultPart = [self.MakeSliceVertexAtT100( v ) for v in ring]
            result.append( resultPart )

        return result 

    def MakeSliceVertexAtT100( self, vertex ) :
        #Slice circles are rotate following the slice angle
        #and scaled following the ratio given by R and r

        R = self.R
        r = self.r 

        #Initial values
        x = vertex.vector.x
        y = vertex.vector.y
        z = vertex.vector.z

        scale1 = MakeScale( x, 1, r )
        scale2 = MakeScale( sqrt( (y * y) + (z * z) ), 1, r )

        #Scaled values
        sx = x * scale1
        sy = (y * scale2) - R
        sz = z * scale2

        angle = vertex.sliceAngle
        cosAZ = cos(angle)
        sinAZ = sin(angle)

        #Rotated values
        rx = - (sy * sinAZ)
        ry = + (sy * cosAZ) #+ smallDim
        rz = sz

        return Vertex( Vector( [rx, ry, rz] ), 0, 0 )

    def ToT( self, t, offset ):

        result = []
        for ring in self.Rings:
            resultPart = [self.MakeSliceVertexAtT( v, t, offset ) for v in ring]
            result.append( resultPart )

        return result 

    def mySlerp( self, start, end, percent, sliceAngle, ringAngle, t ):
        #'fake' slerp : a mix between simple interpolation and real slerp depending on the ringAngle
        # - the lower ring angles are interpolated
        # - the bigger are slerped

        slerp1 = (1 - t) * start + t * end

        dot = start.dot(end)
        theta = acos(dot) * percent

        relativeVec = (end - (dot * start))

        slerp2 = (cos(theta) * start) + (sin(theta) * relativeVec)

        slerp = ( (rad180 - ringAngle) * slerp1 + (ringAngle) * slerp2 ) / rad180

        return slerp

    def MakeSliceVertexAtT( self, vertex, t, offset ) :

        n1 = vertex.vectorNorm
        n2 = vertex.t100Norm
        fact = n1 + (t * (n2 - n1)) #Scale of the vertex at this time

        #some tests about time (not used)
#        tFact = TFactor( t )
#        tFact = (tFact * tFact) * (vertex.ringAngle * vertex.sliceAngle) / 8.0
        tFact = 0

        slerp = self.mySlerp( vertex.vectorN, vertex.t100N, t, vertex.sliceAngle, vertex.ringAngle, t )
        slerp *= (fact + tFact)

        return Vertex( Vector( [slerp.x + offset[0], slerp.y + offset[1], slerp.z + offset[2]] ), 0, 0 )

bigDim = 2.0
smallDim = 1.0
SEGS = 5
turns = 180.0

torus = Torus(bigDim, smallDim, SEGS, turns)


bm = bmesh.new()

#t100 = torus.ToT100()
#torus.RingMatrixToBMeshFromExt( bm, t100 )

torus.RingMatrixToBMesh( bm )

#for ring in torus.Rings:
#    for v in ring:
#        vect = v.vector #.t100N
#        print( str(vect.x) + ";" + str(vect.y) + ";" + str(vect.z) + ";" + str(v.sliceAngle) + ";" + str(v.ringAngle) )

t = 0.1
for t in range(1, 11):
    offset = [0, (t+1)*6, 0]
    ringsAtT = torus.ToT( t / 10.0, offset )
    torus.RingMatrixToBMeshFromExt( bm, ringsAtT )

mesh = bpy.context.object.data      

The blend file :

  • The mesh animated, thanks to Jerryno

enter image description here


My recommendation would be shape keys. I have source code that animates a "waving fin" at http://web.purplefrog.com/~thoth/blender/python-cookbook/shape-key-fin.html

Here's an excerpt:

import bpy
import bmesh
import math

def vert1For(u, t):
    return [ 0, u, 0]

def vert2For(u, t, dTheta, z1):
    theta1 = dTheta * math.sin(t+u*0.4)
    return [ math.sin(theta1)*z1, u, math.cos(theta1)*z1]

def vert3For(u, t, dTheta, z2, thetaLag):
    theta2 = dTheta * math.sin(t+u*0.4-thetaLag)
    return [ math.sin(theta2)*z2, u, math.cos(theta2)*z2]

def makeMesh(name, nSegs, z1, z2, dTheta, thetaLag):
    mesh = bpy.data.meshes.new(name)
    verts = []
    faces = []
    for u in range(0,nSegs+1):
        verts.append( vert1For(u,0) )
        verts.append( vert2For(u,0,dTheta, z1) )
        verts.append( vert3For(u,0,dTheta, z2, thetaLag) )
        if (u>0):
            v1 = v4-3
            v2 = v1+1
            v3 = v1+2
            v5 = v1+4
            v6 = v1+5
            faces.append( [ v1, v4, v5, v2] )
            faces.append( [ v2, v5, v6, v3] )
    mesh.from_pydata(verts, [], faces)
    mesh.show_normal_face = True

    return mesh

def addShapeKey(obj, i, nKeys, z1, z2, dTheta, thetaLag):
    kn = "phase %d"%i
    sk = obj.shape_key_add(kn)
#    sk.value = 0
#    sk.frame = i/nKeys
#    sk.frame = i*i/(nKeys*nKeys) # crazy version
    bm = bmesh.new()
    sl = bm.verts.layers.shape.get(kn)

    for u in range( math.floor(len(bm.verts) / 3)):
        t = math.pi*2*i/nKeys
        bm.verts[u*3][sl] = vert1For(u, t)
        bm.verts[u*3+1][sl] = vert2For(u, t, dTheta, z1)
        bm.verts[u*3+2][sl] = vert3For(u, t, dTheta, z2, thetaLag)


dTheta = 0.8
thetaLag = 0.2
z1 = 2
z2 = 3
mesh = makeMesh("fin", 40, z1, z2, dTheta, thetaLag)

obj = bpy.data.objects.new("fin", mesh)

sk0 = obj.shape_key_add("Basis")
sk0 = obj.data.shape_keys
sk0.use_relative = False
nKeys = 11;
for i in range(1,nKeys):
    addShapeKey(obj, i, nKeys, z1, z2, dTheta, thetaLag)

You will have to modify it to calculate vertex coordinates for a torus, and you will have to build faces from those vertices appropriately.

  • $\begingroup$ Added the parametric eq of torus to q. $\endgroup$
    – batFINGER
    Jul 5, 2016 at 13:32

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .