# Creating sinusoidal struts using bpy

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)
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("wave")
object = bpy.data.objects.new("wave",mesh)

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

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


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, 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!

• 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. Jul 12, 2017 at 14:10
• @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?
– Ian
Jul 12, 2017 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. 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 )

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

bm.faces.new(verts)

for i in [0, len( loops ) - 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')
bm.to_mesh(m)

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