Animated parametric surface

Hello ! This is my first post here.

I’m quite new to blender, and I’m using it to create 3D math animations for educational purposes.

I’m using a lot of parametric surfaces (which is a 2d array of points where all three coordonnates are controlled by two variables u and v), and there are plenty of ressource to create these into blender (addons, python scripts) that works well.

But I would like to have a parametric surface where the coordinates of its points depends on u, v and the current frame also. And while jumping to a new frame, its automatically recalculates all the vertices.

Any idea how to achieve this ? Thank you for the answers !

Interesting topic!
Perhaps too simple, but you can’t just use the frame number as input? Depending on how the function looks it could work.

I tried it but this didn’t worked.

Using the “Math Function” mesh of the addon “Add Mesh: Extra Objects”, it provoked an error :

Traceback (most recent call last):
  File "...", line 318, in xyz_function_surface_faces
    float(eval(*expr_args_x)),
TypeError: 'NoneType' object is not subscriptable

So i think this addon is not designed for this purpose, or there is another way to use the frame variable in the coordinates functions.

I’m also creating parametrized objects via python scripts (here is an example, but with a parametrized curve), but I havn’t found a way to modify the object according to the current frame.

import bpy
import math

# mesh arrays
verts = []
edges = []

# mesh variables
numX = 100

# variance and scale variables
variance = .35
scale = 4


# fill verts array
for i in range (0, numX):
    
    # nomalize range
    u = 8*(i/numX-1/2)

    s = variance
    x = math.sinh(u)*8*math.sqrt(3)/3
    y = 0
    z = -math.cosh(u)*(32/3)+8/3

    vert = (x,y,z)
    verts.append(vert)

# fill edges array
count = 0
for i in range (0, numX-1):
        A = i
        B = i+1
        edge = (A,B)
        edges.append(edge)

# create mesh and object
mesh = bpy.data.meshes.new("Hyperbola2")
object = bpy.data.objects.new("Hyperbola2",mesh)

# set mesh location
object.location = bpy.context.scene.cursor.location
bpy.context.collection.objects.link(object)

# create mesh from python data
mesh.from_pydata(verts,edges,[])
mesh.update(calc_edges=True)

You could do it by coding a new modifier, but I think it’s currently not available via Python. (only C++)
For example, the wave modifier is using current frame info :


That said, I had the same issue and I managed to solve it that way :

  • Create one object per frame (using frame number as a param for geometry generation)
  • Animate visibility keyframes on object mesh (so that each object is visible on only one frame)

It can quickly become heavy in term of RAM, but it does the job :stuck_out_tongue:

See you :slight_smile: ++
Tricotou

The second option vill be very difficult, as animations will have up to 1000 frames.

For the first, I will try to learn how to create a new modifier.

But I have two ideas that can make the point. The first would be to create the surface using nodes, like on this thread (https://blender.stackexchange.com/questions/135933/how-to-create-explicit-parametric-functions-in-animation-node), but I don’t know how to create such nodes.

The second would be to add on the python code I put earlier lines to create drivers to every vertex, to control their position using frame value. But I havn’t found good explaination about how to create a driver on this code, and about how to assign those drivers to vertex after the mesh was created with the mesh.from_pydata function.

And the code I put earlier was not the right one, so I edited it.

If you are using Python, creating 1 object or 1000 is quite the same “difficulty” …
I know that Blender can produce laggy behaviour when there are a lot of objects, but it happens for higher amounts of objects … Any way it will be simpler that coding new addon + recompiling Blender :stuck_out_tongue:

I will give it a try. But is there a way to set to which frame the object will become visible then invisible using a python function ? And then a function to assign a material to an object, or to assign a bevelling shape to a curve ?

I also had another idea, that could be less RAM consuming, but I don’t know about its feasibility. During my searches on the internet, I have discovered (if I am not misunderstanding, this was explained here : https://blender.stackexchange.com/questions/27465/running-a-script-for-each-frame) that there is a function which is executed at each frame changing. Is it possible to edit this function to make it delete the parametric surface, get the current frame and recreate it with the new frame value ?

I also tried to install AnimatedNodes, but there was an error during installation.

Finally ! I succeed in my quest for building animated parametric object.

Here is my code for the curves for those who may be interested :

import bpy
import math
import pdb
from mathutils import Vector

def my_handler(scene):
    # Deselect all
    bpy.ops.object.select_all(action='DESELECT')

    # Select the object
    if bpy.data.objects.get("Hyperbola") is not None:
        bpy.data.objects['Hyperbola'].select_set(True)

    bpy.ops.object.delete() 

    # Initialize coordinates calculation
    coords = []
    numX = 100
    frame = bpy.context.scene.frame_current

    # Coordinate calculations
    for i in range (0, numX):
    
        # nomalize range
        u = 8*(i/numX-1/2)

        x = math.sinh(u)*8*math.sqrt(3)/3*frame
        y = 0
        z = -math.cosh(u)*(32/3)+8/3

        vert = (x,y,z)
        coords.append(vert)


    # create the Curve Datablock
    curveData = bpy.data.curves.new('Hyperbola', type='CURVE')
    curveData.dimensions = '3D'
    curveData.resolution_u = 2

    # map coords to spline
    polyline = curveData.splines.new('POLY')
    polyline.points.add(len(coords)-1)
    for i, coord in enumerate(coords):
        x,y,z = coord
        polyline.points[i].co = (x, y, z, 1)

    # create Object
    curveOB = bpy.data.objects.new('Hyperbola', curveData)
    curveData.bevel_depth = 0.125

    # attach to scene and validate context
    scn = bpy.context.scene
    bpy.context.collection.objects.link(curveOB)

    # assign a material to the newly created curve
    mat = bpy.data.materials.get("White&Shiny")
    curveOB.data.materials.append(mat)

# Add the handler to handlers
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(my_handler)

And here is the code for parametric surfaces v:

import bpy
import math

def my_handler(scene):
    # Deselect all
    bpy.ops.object.select_all(action='DESELECT')

    # Select the object
    if bpy.data.objects.get("Hyperboloid") is not None:
        bpy.data.objects['Hyperboloid'].select_set(True)

    bpy.ops.object.delete()
    
    # mesh arrays
    verts = []
    faces = []

    # mesh variables
    numX = 50
    numY = 50
    
    # get frame value
    frame = bpy.context.scene.frame_current

    # fill verts array
    for i in range (0, numX):
        for j in range(0,numY):
            # nomalize range
            u = 8*(i/numX-1/2)
            v = 2*math.pi*(j/(numY-1)-1/2)

            x = 2*math.sqrt(1+u*u)*math.cos(v)*(frame/12)
            y = 2*math.sqrt(1+u*u)*math.sin(v)*(frame/12)
            z = 12*u/(frame/12+0.01)

            vert = (x,y,z)
            verts.append(vert)

    # fill faces array
    count = 0
    for i in range (0, numY *(numX-1)):
        if count < numY-1:
            A = i
            B = i+1
            C = (i+numY)+1
            D = (i+numY)
            face = (A,B,C,D)
            faces.append(face)
            count = count + 1
        else:
            count = 0

    # create mesh and object
    mesh = bpy.data.meshes.new("Hyperboloid")
    object = bpy.data.objects.new("Hyperboloid",mesh)

    # set mesh location
    object.location = bpy.context.scene.cursor.location
    bpy.context.collection.objects.link(object)

    # create mesh from python data
    mesh.from_pydata(verts,[],faces)
    mesh.update(calc_edges=True)

    # assign a material to the newly created curve
    mat = bpy.data.materials.get("Material")
    object.data.materials.append(mat)
    
    # active smooth shading to object
    bpy.data.objects['Hyperboloid'].select_set(True)    
    bpy.ops.object.shade_smooth()
    bpy.ops.object.select_all(action='DESELECT')

# Add the handler to handlers
bpy.app.handlers.frame_change_pre.clear()
bpy.app.handlers.frame_change_pre.append(my_handler)