I want to use Blender and bpy to create sinusoidal struts. I have been using Python with VTK in the past, but it is causing some trouble and I feel Blender might be the better solution.

An example of what I want to create is depicted below. It is planar in a sense that only one axis is modified with a sin (or in this case cos) function. It is also tilted by 45°, but that's not the big hurdle.


Since I am very new to Blender, I googled around a bit and found this: http://wiki.theprovingground.org/blender-py-mathmesh

It contains the following code that creates a surface that is modified with sin:

import bpy
import math

# mesh arrays
verts = []
faces = []

# mesh variables
numX = 10
numY = 10

# wave variables
freq = 1
amp = 1
scale = 1

#fill verts array
for i in range (0, numX):
    for j in range(0,numY):

        x = scale * i
        y = scale * j
        z = scale*((amp*math.cos(i*freq))+(amp*math.sin(j*freq)))

        vert = (x,y,z) 

#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)
        count = count + 1
        count = 0

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

#set mesh location
object.location = bpy.context.scene.cursor_location

#create mesh from python data

I just can't rewrite it to just creating a strut, though. Originally, I created the points using the following approach with numpy + vtk, but when I try this in Blender I get error messages ('Unsupported operand type(s) for /: "tuple" and "int"' on the line where I define y in the function).

def strut(x, node_dist=5, amp=1):
    """Takes 1D array x, creates shape and normalizes it to fit the length of the cosine function."""
    x = x * 2 / node_dist * np.pi  # normalize length to fit [-4pi; 4pi]
    y = amp / 2 * np.cos(x) + amp / 2  # adjusting position and amplitude
    return y

a = np.linspace(-2 * node_dist, 2 * node_dist, points_per_line)
b = strut(a, node_dist, amp) - node_dist / 2.
c = strut(a, node_dist, amp) + node_dist / 2.

for i in range(0, a.shape[0], 1):
    if strut_number == 1:
        x = a[i]
        y = b[i]
        z = c[i]

If I could just get some help with being able to define how many points on the line I want(-> smoothness) and how to define the shape (ideally quadratic) around the strut I could figure the rest out myself. I hope my question is specific enough and not too odd. Thanks for any help!

  • $\begingroup$ It seems that you have a bug in the face-generating logic. The way you allocate verts to faces, you assign indices that do not exist (beyond the max index). Also, the result of the first piece of code is a surface that looks like a wave, not a strut. When I ran the strut code function, I did get something that looks more like a strut but it was only a line of verts rather than a complete 3D surface. $\endgroup$ – TLousky Jul 12 '17 at 14:10
  • $\begingroup$ @TLousky Yeah I it is faulty at this state. I just thought the surface is a more complex version of what I want and I can "dumb it down" to a line/strut. How did you get my function to work with bpy? Did you not get the error I got? $\endgroup$ – Ian Jul 12 '17 at 14:33

Got it to work with Bmesh (not necessary but easier to work with IMHO). Had to add several chunks of code that will generate 4 waves as the main longitudinal edge loops, and add faces to the mesh.

enter image description here

This can be fairly easily expanded to generate other shapes, or non-quad based struts. Here you can control the width (thickness) of the 3D strut via the width global and zw / xw variables within the loop.

I also changed the strut function a tiny bit. Instead of normalizing between -4 to +4 X pi, I just use the Y values provided. In this example I'm providing the minimum and maximum Y values as multiples of pi directly, so I removed the normalization to open up the possibility to create a wave in whichever length and number of peaks you want.

Here's the code:

import bpy, bmesh, math
from mathutils import Vector
import numpy as np

numPoints  = 100
yMin       = -3 * np.pi 
yMax       = 1 * np.pi
width      = 0.5

def strut( x, node_dist = 5, amp = 1 ):
    """ Takes 1D array x, creates shape y values """
    return amp / 2 * np.cos(x) + amp / 2  # adjusting position and amplitude

bm    = bmesh.new() # Generate bmesh object
loops = []          # Strut major longitudinal loops array

# Calculate vertex coordinates
# Essentially generate 4 longitudinal loops that represent the top, bottom. left and right edge loops of the strut
y = np.linspace( yMin, yMax, numPoints )
for zw, xw in zip( [1, 0, -1, 0], [0, -1, 0, 1] ):
    z = strut( y, amp = 2 ) + zw * width
    x = strut( y, amp = 0 ) + xw * width

    loop = [ 
        bm.verts.new( Vector(( xi, yi, zi )) ) 
        for xi, yi, zi in zip( x, y, z ) 

    loops.append( loop )

# Add faces
for i in range( len( loops ) ):
    for j in range( len( loops[i] ) - 1 ):
        verts = []
        if i == len( loops ) - 1:
            verts.append( loops[0][j] )
            verts.append( loops[0][j+1] )
            verts.append( loops[i][j + 1] )
            verts.append( loops[i][j] )
            verts.append( loops[i][j] )
            verts.append( loops[i][j + 1] )
            verts.append( loops[i+1][j+1] )
            verts.append( loops[i+1][j] )


# Add caps faces
for i in [0, len( loops[0] ) - 1 ]:
    bm.faces.new([ loops[j][i] for j in range(len(loops)) ])

# Generate mesh data and object, and link to scene
m = bpy.data.meshes.new('strut')

o = bpy.data.objects.new('strut', m)
bpy.context.scene.objects.link( o )

enter image description here

  • 1
    $\begingroup$ This is amazing, thank you so much! I wish I could upvote more than once :) $\endgroup$ – Ian Jul 20 '17 at 12:57

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.