Code: Creating Blender Objects Programmatically with Python

Below is my addon_add_brick.py script. Once ran from Blender’s scripting interface it allows simple programatically generated lego meshes to be added through the menus: Add > Mesh > Add Lego.

Background information can be found in the companion post

bl_info = {
    "name": "Lego Object",
    "author": "Martin Ellis",
    "version": (1, 0),
    "blender": (2, 75, 0),
    "location": "View3D > Add > Mesh > l brick",
    "description": "Adds a new Mesh Object",
    "warning": "",
    "wiki_url": "",
    "category": "Add Mesh",
    }


import bpy
from bpy.types import Operator
from bpy.props import FloatVectorProperty
from bpy_extras.object_utils import AddObjectHelper, object_data_add
from mathutils import Vector
import math

def add_object(self, context):
    
    width_per_stud = self.width_of_1x1 
    
    plate_height = width_per_stud*1.2/3
    layers_to_do = self.plates_high+1
    stud_protrusion = 1.7/7.8 * width_per_stud
    stud_rad = 2.4/7.8 * width_per_stud
    stud_segs = self.verts_per_circle
    
    if self.plate_verts == False:
        layers_to_do = 2
        plate_height = plate_height * self.plates_high
        
    
    #create off-sets to ensure brick is cenered around origin
    offs_x = ((self.studs_x)*width_per_stud)/-2
    offs_y = ((self.studs_y)*width_per_stud)/-2
    
    #declare empty lists to append to...
    verts = []
    faces = []
    #I have no edges whcih are not part of a face, keep empty list.
    edges = []
    
    verts_per_layer = (self.studs_y + self.studs_x) *2
    
    #add verts
    for lyr_n in range(0, layers_to_do):
        #walk the perimiter
        for stu_y in range(0, self.studs_y):
            verts.append(Vector((
            (offs_x),
            (offs_y+stu_y*width_per_stud), 
            (lyr_n*plate_height))))
            
        for stu_x in range(0, self.studs_x):
            verts.append(Vector((
            (offs_x+stu_x*width_per_stud), 
            (offs_y+width_per_stud*self.studs_y), 
            (lyr_n*plate_height))))
                    
        for stu_y in reversed(range(0, self.studs_y)):
            verts.append(Vector((
            (offs_x+width_per_stud*self.studs_x), 
            (offs_y+(stu_y+1)*width_per_stud), 
            (lyr_n*plate_height))))
            
        for stu_x in reversed(range(0, self.studs_x)):
            verts.append(Vector((
            (offs_x+(stu_x+1)*width_per_stud), 
            #(offs_y+width_per_stud*self.studs_y), 
            (offs_y), 
            (lyr_n*plate_height))))
                
               
    #add faces
    #if this is the 2nd or higher layer, add faces to layer below
    for lyr_n in range(1, layers_to_do):
        for v_n in range(0, verts_per_layer):
            tl = lyr_n*verts_per_layer+v_n
            tr = tl+1
            bl = (lyr_n-1)*verts_per_layer+v_n
            br = bl+1
            
            #if this is the end of the layer, need to adjust to wrap round
            if (v_n+1) % verts_per_layer == 0:
                tr = tr - verts_per_layer
                br = br - verts_per_layer 
            faces.append([tl, tr, br, bl])    
        

    #add top face
    faces.append(range(len(verts)-verts_per_layer, len(verts)))

    verts_so_far = len(verts)


    #draw studs
    if (self.tile == False):
        for stu_x in range(0, self.studs_x):
        #for stu_x in range(0, 1):
            for stu_y in range(0, self.studs_y):
            #for stu_y in range(0, 1):
                c_x = (offs_x+stu_x*width_per_stud)+(width_per_stud/2)
                c_y = (offs_y+stu_y*width_per_stud)+(width_per_stud/2)
                    
                #verts, bottom of cylinder
                for seg in range(0, stud_segs):
                    ang = float(seg)/float(stud_segs)*math.pi*2
                    verts.append(Vector((
                    c_x + math.sin(ang)*stud_rad,
                    c_y + math.cos(ang)*stud_rad,
                    plate_height*(layers_to_do-1)+self.stud_base_offset)))
                #verts, top of cylinder
                for seg in range(0, stud_segs):
                    ang = float(seg)/float(stud_segs)*math.pi*2
                    verts.append(Vector((
                    c_x + math.sin(ang)*stud_rad,
                    c_y + math.cos(ang)*stud_rad,
                    plate_height*(layers_to_do-1)+stud_protrusion)))
                
                #add faces
                verts_so_far = len(verts)
                for v_n in range(0, stud_segs):
                    tl = verts_so_far - stud_segs+v_n
                    tr = tl+1
                    bl = tl - stud_segs
                    br = bl+1
            
                    #if this is the end of the layer, need to adjust to wrap round
                    if (v_n+1) % stud_segs == 0:
                        tr = tr - stud_segs
                        br = br - stud_segs 
                    faces.append([tl, tr, br, bl])  
                
                #add stud top face
                faces.append(range(len(verts)-stud_segs, len(verts)))
                
    mesh = bpy.data.meshes.new(name="New Object Mesh")
    mesh.from_pydata(verts, edges, faces)
    object_data_add(context, mesh, operator=self)
        

class OBJECT_OT_add_object(Operator, AddObjectHelper):
    """Create a new Mesh Object"""
    bl_idname = "mesh.add_object"
    bl_label = "Add Mesh Object"
    bl_options = {'REGISTER', 'UNDO'}


    studs_x = bpy.props.IntProperty(default=3)
    studs_y = bpy.props.IntProperty(default=2)
    plates_high = bpy.props.IntProperty(default=3)
    plate_verts = bpy.props.BoolProperty(default=False)
    tile = bpy.props.BoolProperty(default=False)
    
    width_of_1x1 = bpy.props.FloatProperty(default=1.0)
    verts_per_circle = bpy.props.IntProperty(default=32)
    stud_base_offset= bpy.props.FloatProperty(default=0.01)

    def execute(self, context):

        add_object(self, context)

        return {'FINISHED'}


# Registration

def add_object_button(self, context):
    self.layout.operator(
        OBJECT_OT_add_object.bl_idname,
        text="Add Lego",
        icon='PLUGIN')


# This allows you to right click on a button and link to the manual
def add_object_manual_map():
    url_manual_prefix = "https://docs.blender.org/manual/en/dev/"
    url_manual_mapping = (
        ("bpy.ops.mesh.add_object", "editors/3dview/object"),
        )
    return url_manual_prefix, url_manual_mapping


def register():
    bpy.utils.register_class(OBJECT_OT_add_object)
    bpy.utils.register_manual_map(add_object_manual_map)
    bpy.types.INFO_MT_mesh_add.append(add_object_button)


def unregister():
    bpy.utils.unregister_class(OBJECT_OT_add_object)
    bpy.utils.unregister_manual_map(add_object_manual_map)
    bpy.types.INFO_MT_mesh_add.remove(add_object_button)


if __name__ == "__main__":
    register()