...
Let's build an array out of bars. It is convenient to take most of the code from the previous example and make a class out of it.
Code Block | ||
---|---|---|
|
We then define our array's basic parameters.
...
language | py |
---|
import bpy
import bmesh
from mathutils import Vector
class bar:
'''Create a bar'''
def create(self,
dimensions = (2, 1, 1), # Bar XYZ dimension
baseLocation = (0., 0., 0.), # Bar base XYZ position
name = 'Bar'): # Object name
'''Create bar mesh and object'''
### Calculate base corners
baseCorners = []
dx = dimensions[0]
dy = dimensions[1]
dz = dimensions[2]
baseCorners.append((-0.5*dx, -0.5*dy))
baseCorners.append((-0.5*dx, 0.5*dy))
baseCorners.append((0.5*dx, 0.5*dy))
baseCorners.append((0.5*dx, -0.5*dy))
### Calculate vertices and faces
vertexList = []
topCapVertexList = []
bottomCapVertexList = []
faceList = []
for c in baseCorners:
vertexList.append((c[0], c[1], 0))
vertexList.append((c[0], c[1], dz))
### Calculate faces
lVL = len(vertexList)
if lVL > 3:
f = (lVL - 1, lVL - 2, lVL - 4, lVL - 3)
faceList.append(f)
### Append vertex indices for the cap faces
topCapVertexList.append(lVL - 1)
bottomCapVertexList.append(lVL - 2)
### Append remaining faces
f = (lVL - 1, lVL - 2, 0, 1)
faceList.append(f)
f = tuple(bottomCapVertexList)
faceList.append(f)
f = tuple(topCapVertexList)
faceList.append(f)
### Create mesh
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.location = Vector((baseLocation[0], baseLocation[1], baseLocation[2]))
bpy.context.collection.objects.link(ob)
mesh.from_pydata(vertexList,[],faceList)
mesh.validate(verbose=True)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.translate(bm, vec = baseLocation, verts = bm.verts)
bm.to_mesh(mesh)
mesh.update()
### Origin to center of mesh
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') |
We then define our array's basic parameters.
Code Block | ||
---|---|---|
| ||
### Array definitions
N_X = 10 # Number of structures along x
N_Y = 10 # Number of structures along y
P_X = 2 # Array period along x
P_Y = 2 # Array period along y
W_X = 1 # Structure dimension along x
W_Y = 1.5 # Structure dimension along y
W_Z = 1 # Structure dimension along z |
Finally, we simply run the class in a loop to create the array. Because Blender objects must have an unique names, we assign one to each bar based of its position. If we didn't, Blender would append a number to each one, but this method makes them easier to distinguish.
Code Block | ||
---|---|---|
| ||
### Create array
for x in range(0, N_X):
for y in range(0, N_Y):
barName = 'Bar_{:04.0f}_{:04.0f}'.format(x, y)
bar1 = bar()
bar1.create(dimensions = (W_X, W_Y, W_Z),
baseLocation = [x * P_X, y * P_Y, 0],
name = barName) |
This method can be applied to arrays of structures of any kind.
Cylinder with custom angular resolution
A cylinder is another very commonly used shape. We will also build it vertex by vertex, for which we need the math
package.
Code Block | ||
---|---|---|
| ||
import bpy
import bmesh
import math
from mathutils import Vector |
Our definitions are similar to those for the bar, but we add the choice of the number of points into which to subdivide the circle. This follows the default Blender behavior. With very small structures, you don't need all that many vertices to describe a curve, although you may want to add a few to be on the safe side.
Code Block | ||
---|---|---|
| ||
baseLocation = (1., 3., 0.) # Cylinder base XYZ position
bottomRadius = 1 # Cylinder bottom radius
circlePoints = 36 # Circle subdivision points
height = 1 # Cylinder height
name = 'Cylinder' # Object name
topRadius = 1 # Cylinder top radius |
From here onwards, the vertices can be calculated by basic trigonometry, and the rest of the code follows the bar example closely.
Code Block | ||
---|---|---|
| ||
### Calculate circle subdivision angle
angle = (2 * math.pi / 360) * (360/circlePoints)
### Calculate vertices
vertexList = []
faceList = []
topCapVertexList = []
bottomCapVertexList = []
for u in range(0, int(circlePoints)):
### Outer lower vertex
xol = bottomRadius * math.cos(u * angle)
yol = bottomRadius * math.sin(u * angle)
vol = (xol, yol, 0)
vertexList.append(vol)
### Outer upper vertex
xou = topRadius * math.cos(u * angle)
you = topRadius * math.sin(u * angle)
vou = (xou, you, height)
vertexList.append(vou)
### Calculate faces
lVL = len(vertexList)
if lVL > 3:
f = (lVL - 1, lVL - 2, lVL - 4, lVL - 3)
faceList.append(f)
### Append vertex indices for the cap faces
topCapVertexList.append(lVL - 1)
bottomCapVertexList.append(lVL - 2)
### Append remaining faces
f = (lVL - 1, lVL - 2, 0, 1)
faceList.append(f)
f = tuple(bottomCapVertexList)
faceList.append(f)
f = tuple(topCapVertexList)
faceList.append(f)
### Create mesh
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.location = Vector((0., 0., 0.))
bpy.context.collection.objects.link(ob)
mesh.from_pydata(vertexList,[],faceList)
mesh.validate(verbose=True)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.translate(bm, vec = baseLocation, verts = bm.verts)
bm.to_mesh(mesh)
mesh.update()
### Origin to center of mesh
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') |
Finally, we simply run the class in a loop to create the array. Because Blender objects must have an unique names, we assign one to each bar based of its position. If we didn't, Blender would append a number to each one, but this method makes them easier to distinguish.
...
language | py |
---|
This method can be applied to arrays of structures of any kind.
Cylinder with custom angular resolution
A cylinder is another very commonly used shape. We will also build it vertex by vertex, for which we need the math
package.
...
language | py |
---|
Our definitions are similar to those for the bar, but we add the choice of the number of points into which to subdivide the circle. This follows the default Blender behavior. With very small structures, you don't need all that many vertices to describe a curve, although you may want to add a few to be on the safe side.
...
language | py |
---|
From here onwards, the vertices can be calculated by basic trigonometry, and the rest of the code follows the bar example closely.
...
language | py |
---|
More flexibility along z with prisms
...
This time, for the sake of variety, we will start by defining a class for our prism and creating object via class instances, just as with the bar array example.
Code Block | ||
---|---|---|
| ||
import bpy
import bmesh
from mathutils import Vector
class prism:
'''Create a prism with arbitrary base vertices and number of layers'''
def create(self,
vertices = [[(-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0)],
[(-.5, -.5, 1), (-.5, .5, 1), (.5, .5, 1), (.5, -.5, 1)]], # List of vertices, per layer
baseLocation = (0., 0., 0.), # Prism base XYZ position
name = 'Prism'): # Object name
'''Create prism mesh and object'''
### Calculate vertices
vertexList = []
faceList = []
topCapVertexList = []
bottomCapVertexList = []
for li, layer in enumerate(vertices):
for v in layer:
vertexList.append(v)
lVL = len(vertexList)
if lVL > li * len(layer) + 1:
f = (lVL - 1, lVL - 2, lVL - len(layer) - 2, lVL - len(layer) - 1)
faceList.append(f)
# Append vertex indices for the cap faces
if li == len(vertices) - 1:
topCapVertexList.append(lVL - 1)
elif li == 0:
bottomCapVertexList.append(lVL - 1)
### Append remaining faces
f = (lVL - 1, lVL - len(layer), lVL - 2 * len(layer), lVL - len(layer) - 1)
faceList.append(f)
f = tuple(bottomCapVertexList)
faceList.append(f)
f = tuple(topCapVertexList)
faceList.append(f)
### Create mesh
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.location = Vector((0., 0., 0.))
bpy.context.collection.objects.link(ob)
mesh.from_pydata(vertexList,[],faceList)
mesh.validate(verbose=True)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.translate(bm, vec = baseLocation, verts = bm.verts)
bm.to_mesh(mesh)
mesh.update()
### Origin to center of mesh
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') |
...
We test the class by creating an instance and feeding it a list of vertices. We use the class defaults, so specifying the vertices and the vertices
keyword is redundant, but it serves as one more usage example.
Code Block | ||
---|---|---|
| ||
prism1 = prism()
vertices = [[(-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0)],
[(-.8, -.8, .5), (-.8, .8, .5), (.8, .8, .5), (.8, -.8, .5)],
[(-.5, -.5, 1), (-.5, .5, 1), (.5, .5, 1), (.5, -.5, 1)]]
prism1.create(baseLocation = [-2, 1, 1], vertices = vertices) |
...
Useful classes
This section gathers code from the examples above in pre-made classes for ease of use. Also available is an "eraser" class to delete geometry programmatically. Feel free to copy them and paste them in your projects.
Bar
A simple bar.
Code Block | ||
---|---|---|
| ||
import bpy
import bmesh
from mathutils import Vector
class bar:
'''Create a bar'''
def create(self,
dimensions = (2, 1, 1), # Bar XYZ dimension
baseLocation = (0., 0., 0.), # Bar base XYZ position
name = 'Bar'): # Object name
'''Create bar mesh and object'''
### Calculate base corners
baseCorners = []
dx = dimensions[0]
dy = dimensions[1]
dz = dimensions[2]
baseCorners.append((-0.5*dx, -0.5*dy))
baseCorners.append((-0.5*dx, 0.5*dy))
baseCorners.append((0.5*dx, 0.5*dy))
baseCorners.append((0.5*dx, -0.5*dy))
### Calculate vertices and faces
vertexList = []
topCapVertexList = []
bottomCapVertexList = []
faceList = []
for c in baseCorners:
vertexList.append((c[0], c[1], 0))
vertexList.append((c[0], c[1], dz))
### Calculate faces
lVL = len(vertexList)
if lVL > 3:
f = (lVL - 1, lVL - 2, lVL - 4, lVL - 3)
faceList.append(f)
### Append vertex indices for the cap faces
topCapVertexList.append(lVL - 1)
bottomCapVertexList.append(lVL - 2)
### Append remaining faces
f = (lVL - 1, lVL - 2, 0, 1)
faceList.append(f)
f = tuple(bottomCapVertexList)
faceList.append(f)
f = tuple(topCapVertexList)
faceList.append(f)
### Create mesh
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.location = Vector((baseLocation[0], baseLocation[1], baseLocation[2]))
bpy.context.collection.objects.link(ob)
mesh.from_pydata(vertexList,[],faceList)
mesh.validate(verbose=True)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.translate(bm, vec = baseLocation, verts = bm.verts)
bm.to_mesh(mesh)
mesh.update()
### Origin to center of mesh
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') |
...
Example:
Code Block | ||
---|---|---|
| ||
bar1 = bar()
bar1.create(baseLocation = [-1, -1, 1]) |
...
Cylinder
A simple cylinder.
Code Block | ||
---|---|---|
| ||
class cylinder:
'''Create a cylinder or conic section'''
def create(self,
baseLocation = (0., 0., 0.), # Cylinder base XYZ position
bottomRadius = 1, # Cylinder bottom radius
circlePoints = 36, # Circle subdivision points
height = 1, # Cylinder height
name = 'Cylinder', # Object name
topRadius = 1): # Cylinder top radius
'''Create cylinder mesh and object'''
### Calculate circle subdivision angle
angle = (2 * math.pi / 360) * (360/circlePoints)
### Calculate vertices
vertexList = []
faceList = []
topCapVertexList = []
bottomCapVertexList = []
for u in range(0, int(circlePoints)):
### Outer lower vertex
xol = bottomRadius * math.cos(u * angle)
yol = bottomRadius * math.sin(u * angle)
vol = (xol, yol, 0)
vertexList.append(vol)
### Outer upper vertex
xou = topRadius * math.cos(u * angle)
you = topRadius * math.sin(u * angle)
vou = (xou, you, height)
vertexList.append(vou)
### Calculate faces
lVL = len(vertexList)
if lVL > 3:
f = (lVL - 1, lVL - 2, lVL - 4, lVL - 3)
faceList.append(f)
### Append vertex indices for the cap faces
topCapVertexList.append(lVL - 1)
bottomCapVertexList.append(lVL - 2)
### Append remaining faces
f = (lVL - 1, lVL - 2, 0, 1)
faceList.append(f)
f = tuple(bottomCapVertexList)
faceList.append(f)
f = tuple(topCapVertexList)
faceList.append(f)
### Create mesh
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.location = Vector((0., 0., 0.))
bpy.context.collection.objects.link(ob)
mesh.from_pydata(vertexList,[],faceList)
mesh.validate(verbose=True)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.translate(bm, vec = baseLocation, verts = bm.verts)
bm.to_mesh(mesh)
mesh.update()
### Origin to center of mesh
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') |
...
Example:
Code Block | ||
---|---|---|
| ||
cyl1 = cylinder()
cyl1.create(bottomRadius = 1, height = 3) |
...
Eraser
Should, in most instances, clean a .blend
file of all objects, collections and meshes.
Code Block | ||
---|---|---|
| ||
import bpy
class eraser:
'''Remove Blender objects'''
def delete_all_objects(self):
'''Delete all objects in the scene'''
for o in bpy.context.scene.objects:
o.select_set(True)
bpy.ops.object.delete()
def delete_all_collections(self):
'''Delete all collections'''
for c in bpy.data.collections:
collection = bpy.data.collections.get(c.name)
bpy.data.collections.remove(collection)
def delete_orphaned_meshes(self):
'''Delete all orphaned meshes'''
orphan_mesh = [m for m in bpy.data.meshes if not m.users]
while orphan_mesh:
bpy.data.meshes.remove(orphan_mesh.pop()) |
...
Example:
Code Block | ||
---|---|---|
| ||
eraser1 = eraser()
eraser1.delete_all_objects()
eraser1.delete_all_collections()
eraser1.delete_orphaned_meshes() |
...
Prism
A multi-layer prism.
Code Block | ||
---|---|---|
| ||
import bpy
import bmesh
from mathutils import Vector
class prism:
'''Create a prism with arbitrary base vertices and number of layers'''
def create(self,
vertices = [[(-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0)],
[(-.5, -.5, 1), (-.5, .5, 1), (.5, .5, 1), (.5, -.5, 1)]], # List of vertices, per layer
baseLocation = (0., 0., 0.), # Prism base XYZ position
name = 'Prism'): # Object name
'''Create prism mesh and object'''
### Calculate vertices
vertexList = []
faceList = []
topCapVertexList = []
bottomCapVertexList = []
for li, layer in enumerate(vertices):
for v in layer:
vertexList.append(v)
lVL = len(vertexList)
if lVL > li * len(layer) + 1:
f = (lVL - 1, lVL - 2, lVL - len(layer) - 2, lVL - len(layer) - 1)
faceList.append(f)
# Append vertex indices for the cap faces
if li == len(vertices) - 1:
topCapVertexList.append(lVL - 1)
elif li == 0:
bottomCapVertexList.append(lVL - 1)
### Append remaining faces
f = (lVL - 1, lVL - len(layer), lVL - 2 * len(layer), lVL - len(layer) - 1)
faceList.append(f)
f = tuple(bottomCapVertexList)
faceList.append(f)
f = tuple(topCapVertexList)
faceList.append(f)
### Create mesh
mesh = bpy.data.meshes.new(name)
ob = bpy.data.objects.new(name, mesh)
ob.location = Vector((0., 0., 0.))
bpy.context.collection.objects.link(ob)
mesh.from_pydata(vertexList,[],faceList)
mesh.validate(verbose=True)
bm = bmesh.new()
bm.from_mesh(mesh)
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
bmesh.ops.translate(bm, vec = baseLocation, verts = bm.verts)
bm.to_mesh(mesh)
mesh.update()
### Origin to center of mesh
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = ob
ob.select_set(True)
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN') |
...
Example:
Code Block | ||
---|---|---|
| ||
prism1 = prism()
vertices = [[(-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0)],
[(-.8, -.8, .5), (-.8, .8, .5), (.8, .8, .5), (.8, -.8, .5)],
[(-.5, -.5, 1), (-.5, .5, 1), (.5, .5, 1), (.5, -.5, 1)]]
prism1.create(baseLocation = [-2, 1, 1], vertices = vertices) |
...
Alternatives
Dedicated CAD software, if available, is always the best solution for creating precise geometries. Many can export to STL of DXF and may have scripting facilities, too. Alternatively, if your layout is scripted but in GDSII (for example by using our guide it can be converted to STL by using for example gds3extrude.