1
$\begingroup$

I can update a text curve object when I change the frame and it doesn't create multiple objects but how can I do the same with a mesh object.

Code:

import bpy

scene = bpy.context.scene

font_curve = bpy.data.curves.new(type="FONT",name="Font Curve")
font_curve.body = 'Current Frame: ' + str(scene.frame_current)
font_obj = bpy.data.objects.new("Font Object", font_curve)
scene.collection.objects.link(font_obj)
  
def recalculate_obj(scene):
    font_curve.body = 'Current Frame: ' + str(scene.frame_current)         
     
def register():
    bpy.app.handlers.frame_change_post.append(recalculate_obj)

def unregister():
    bpy.app.handlers.frame_change_post.remove(recalculate_obj)

register()

Frame animation

It doesn't create multiple Font objects (this is what I want it just replaces the last one)

Font

But when I try and do it with a mesh object I run into problems due to the "_add"

import bpy

scene = bpy.context.scene

font_curve = bpy.data.curves.new(type="FONT",name="Font Curve")
font_curve.body = 'Current Frame: ' + str(scene.frame_current)
font_obj = bpy.data.objects.new("Font Object", font_curve)
scene.collection.objects.link(font_obj)

cyl_obj = bpy.ops.mesh.primitive_cylinder_add(radius = 1) 

def recalculate_obj(scene):
    font_curve.body = 'Current Frame: ' + str(scene.frame_current) 
    cyl_obj = bpy.ops.mesh.primitive_cylinder_add(radius = scene.frame_current)
          
def register():
    bpy.app.handlers.frame_change_post.append(recalculate_obj)

def unregister():
    bpy.app.handlers.frame_change_post.remove(recalculate_obj)

register()

Mesh

mesh multi

My goal is to replace the line bpy.ops.mesh.primitive_cylinder_add command with the python line below but have the a, b, c and d variables in the x_eq, y_eq, z_eq animated with out creating multiple objects.

cyl_obj = bpy.ops.mesh.primitive_xyz_function_surface(x_eq = "a+v", y_eq = "b*sin(2*pi*u+c)", z_eq = "cos(2*pi*u)/d")
$\endgroup$
2
$\begingroup$

Swap out the mesh

enter image description here

Just like the font change handler is swapping out the body of the font, ie the data part of the text object, can do same with meshes.

Proof of concept

Use the create surface operator to create an object stepping along v changing the steps and the point to step to, max.

After each call, save the mesh, remove the object.

Script to create the meshes via operator

import bpy
from addon_utils import enable
from math import pi

from bpy import context

enable("add_mesh_extra_objects") # make sure addon is enabled

# default 4pi in 128 steps
step_value = pi / 32
steps = 10
for step in range(1, 129, steps):
    bpy.ops.mesh.primitive_xyz_function_surface(
        range_v_max=step * step_value,
        range_v_step=step,
        )
    me = context.object.data
    me.name = f"XXXX{step}"
    me.use_fake_user = True
    bpy.data.objects.remove(context.object)
        

After running this script have a number of "XXXX" meshes

>>> D.meshes['XXXX
                  1']
                  101']
                  11']
                  111']
                  121']
                  21']
                  31']
                  41']
                  51']
                  61']
                  71']
                  81']
                  91']

Now simply a case of writing a handler to use these.

Handler to swap in the meshes created

import bpy

def swap_ob_mesh(ob_name, mesh_base_name):
    
    def handler(scene, depsgraph):
        ob = scene.objects.get(ob_name)
        f = scene.frame_current
        me = bpy.data.meshes.get(f"{mesh_base_name}{f}")
        if ob and me:
            ob.data = me
            
    return handler

# while testing
bpy.app.handlers.frame_change_post.clear()
        
# test call

bpy.app.handlers.frame_change_post.append(
        swap_ob_mesh("Cube", "XXXX")
        )

Notes: IMO never mix context and handlers. When it comes to render most likely wont work.

Consider shape keys. If instead simply ran the operator only changing v max (not steps) if all the meshes produced have the same number of verts / edges / faces simply join them as shapes and animate the shape key.

To demonstrate the "stepping" nature of swapping the mesh have used 10 frame steps. Reducing this to 1 will be as smooth as it gets.

On a side note this is a "proof of concept" scrubbing the timeline may produce unexpected results since the mesh is only swapped when exists.

| improve this answer | |
$\endgroup$
  • $\begingroup$ I hear you. if not done "correctly" and you mix context and handlers incorrectly when scrubbing with the timeline it will cause the memory to increase every time you scub the time line (and the memory usage will NOT go down) also some other strange things happen when switching between edit mode and object mode. I found some useful python code and connected it to Animation Nodes and will post it as a workaround / one of the correct ways to do this. $\endgroup$ – Rick T Jul 23 at 2:47
1
$\begingroup$

Another way you can do this is by just using Python. (Don't use this way, unless you have to. This will increase your memory usage every-time you scrub the time-line and the memory WILL NOT GO DOWN!!!)

Notice the memory usage!: Memory usage

Python code to run:

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)
| improve this answer | |
$\endgroup$
0
$\begingroup$

To avoid mixing context and handlers together as @batFINGER mentioned a workaround is below.

Combine some Python code with the Animation Nodes plugin.

Animation nodes node

Python Code Used in eq_2:

import bpy
import math

def parametricfunc(val1):

    # mesh arrays
    verts = []
    faces = []

    # mesh variables
    numX = 50
    numY = 50
       
    # 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)*(val1/12)
            y = 2*math.sqrt(1+u*u)*math.sin(v)*(val1/12)
            z = 12*u/(val1/12+0.01)

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

    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
            
        
        edges = AN.algorithms.mesh_generation.grid.quadEdges(numX, numY)
        polys = AN.algorithms.mesh_generation.grid.quadPolygons(numX, numY)
    return x, y, z, verts, A, B, C, D, faces, edges, polys
            
x, y, z, verts, A, B, C, D, faces, edges, polys  = parametricfunc(val1)     
   
    
| improve this answer | |
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.