Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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
languagepy

We then define our array's basic parameters.

...

languagepy
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
languagepy
### 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
languagepy
### 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)

Image Added

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
languagepy
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
languagepy
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
languagepy
### 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.

...

languagepy

Image Removed

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.

...

languagepy

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.

...

languagepy

From here onwards, the vertices can be calculated by basic trigonometry, and the rest of the code follows the bar example closely.

...

languagepy

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
languagepy
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
languagepy
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)

...

PrismImage RemovedImage Added

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
languagepy
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
languagepy
bar1 = bar()
bar1.create(baseLocation = [-1, -1, 1])

...

Cylinder

A simple cylinder.

Code Block
languagepy
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
languagepy
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
languagepy
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
languagepy
eraser1 = eraser()
eraser1.delete_all_objects()
eraser1.delete_all_collections()
eraser1.delete_orphaned_meshes()

...

Prism

A multi-layer prism.

Code Block
languagepy
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
languagepy
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.