Script to create stairs *with* railings

In searching around I’ve found a few scripts to create stairs, but none that also create nice railings. So, some time ago, I decided to write my own script to do just that.

You can set which components of the stairs to create (e.g. treads, stringer, posts, railings) and modify their dimensions.

You can set how many treads and railing posts you want and the script will figure out where to place the posts and how long they need to be to rest on the treads. It will also create a nice intersection between the railing posts and the railing.

I hope some of you here find it useful!

Cheers,
Nick

http://van.moelicious.be/stairs-railings-2b.png


#! /usr/bin/env python

'''
Stairs and railing creator script for blender 2.49
Author: Nick van Adium
Date: 2010 08 09

Creates a straight-run staircase with railings and stringer
All components are optional and can be turned on and off by setting e.g. makeTreads=True or makeTreads=False
No GUI for the script, all parameters must be defined below
Current values assume 1 blender unit = 1 metre

Stringer will rest on lower landing and hang from upper landing
Railings start on the lowest step and end on the upper landing

NOTE: You must have numpy installed for this script to work!
      numpy is used to easily perform the necessary algebra
      numpy can be found at http://www.scipy.org/Download

Note: I'm not sure how to use recalcNormals so not all normals points ouwards.
      Perhaps someone else can contribute this.
'''

#---------------------------------------------------------------------
'''define all parameters here'''

global rise, run
rise=0.20 #single tread rise, including the tread height
run=0.30 #single tread run, not including the nosing

#for treads
makeTreads=True
wT=1.2 #tread width
hT=0.04 #tread height
tT=0.03 #tread toe, a.k.a. nosing
nT=10 #number of treads

#for posts
makePosts=True
dP=0.04 #post depth
wP=0.04 #post width
nP=5 #number of posts

#for railings
makeRailings=True
wR=0.12 #rail width
tR=0.03 #rail thickness
hR=0.90 #rail height

#for retainers
makeRetainers=True
wRR=0.01 #retainer width
hRR=0.01 #retainer height
nRR=3 #number of retainers

#for stringer
makeStringer=True
wS=0.15 #stringer width, set equal to tread width for fully enclosed riser


#---------------------------------------------------------------------
'''import modules'''
from Blender import *
import bpy
import math
from numpy import *

#---------------------------------------------------------------------
'''classes for mesh creation'''

class General:
    
    '''common data, methods needed for other objects'''

    def __init__(self,rise,run,N):
        self.start=zeros((3))
        self.stop=float(N)*array([run,0,rise])
        self.slope=rise/run
        self.angle=arctan(self.slope)
        #identical quads for all objects except stringer
        self.faces=[[0,1,3,2],[0,1,5,4],[0,2,6,4],[4,5,7,6],[2,3,7,6],[1,3,7,5]]

    def Make_mesh(self,coords,faces,meshname,objname):
        '''make a mesh given the vertex coordinates, faces, and names for mesh and object'''
        me = bpy.data.meshes.new(meshname)
        me.verts.extend(coords.tolist())
        me.faces.extend(faces)
        scn = bpy.data.scenes.active
        ob = scn.objects.new(me, objname)

class Treads:

    '''class for creating treads'''

    def __init__(self,w,h,d,r,toe,N):
        self.w=w #tread width
        self.h=h #tread height
        self.d=d #tread run
        self.r=r #tread rise
        self.t=toe #tread nosing
        self.N=N #number of treads
        self.Create()

    def Create(self):
        for i in range(self.N):
            coords=zeros((8,3))
            coords[0,:]=[-self.t,0,0]
            coords[1,:]=[self.d,0,0]
            coords[2,:]=[-self.t,self.w,0]
            coords[3,:]=[self.d,self.w,0]
            coords[4:,:]=coords[0:4,:]+array([0,0,-self.h])
            coords=coords+array([i*self.d,0,i*self.r])
            G.Make_mesh(coords,G.faces,'tMesh','tObj')

class Posts:

    '''class to create posts'''
    
    def __init__(self,d,w,wT,nP,hR,tR):
        self.x1=G.start+array([0,0,hR-tR]) #rail start
        self.x2=G.stop+array([0,0,hR-tR]) #rail stop
        self.d=d #post depth
        self.w=w #post width
        self.wT=wT #tread width
        self.nP=nP #number of posts 
        self.sp=array([(self.x2[0]-self.x1[0])/float(nP+1),0,0]) #spacing between posts
        self.Create()

    def Intersect(self,i,d):
        '''find intersection point, x, for rail and post'''
        x3=self.x1+i*self.sp+d
        x4=x3+array([0,0,self.x2[-1]])
        a=self.x2-self.x1
        b=x4-x3
        c=x3-self.x1
        cr_ab=cross(a,b)
        mag_cr_ab=(cr_ab**2).sum()
        return self.x1+a*(dot(cross(c,b),cr_ab)/mag_cr_ab)

    def Create(self):
        for i in range(0,self.nP+2,1):
            coords=zeros((8,3))
            #intersections with rail
            coords[0]=self.Intersect(i,0.0)
            coords[1]=self.Intersect(i,self.d)
            #intersections with tread
            coords[2]=array([self.x1[0]+i*self.sp[0],0,int(coords[0,0]/run)*rise])
            coords[3]=coords[2]+array([self.d,0,0])
            #inner face
            coords[4:,:]=coords[0:4,:]+array([0,self.w,0])
            G.Make_mesh(coords,G.faces,'pMesh','pObj')
            #make post on other side of steps as well
            coords=coords+array([0,self.wT-self.w,0])
            G.Make_mesh(coords,G.faces,'pMesh','pObj')
            
class Rails:

    '''class for creating railings'''

    def __init__(self,w,t,h,tT,wP,dP,wT):
        self.w=w #rail width
        self.t=t #rail thickness
        self.h=h #rail height
        self.start=G.start+array([0,0,self.h-self.t]) #rail start
        self.stop=G.stop+array([0,0,self.h-self.t]) #rail stop
        self.tT=tT #tread toe
        self.wP=wP #post width
        self.dP=dP #post depth
        self.wT=wT #tread width
        self.Create()

    def Create(self):
        #determine offset to include railing toe
        offset=array([self.tT,0,self.tT*tan(G.angle)])
        coords=zeros((8,3))
        coords[0]=self.start-offset
        coords[1]=self.stop+offset+array([self.dP,0,self.dP*tan(G.angle)])
        coords[2]=self.start-offset+array([0,self.w,0])
        coords[3]=self.stop+offset+array([self.dP,self.w,self.dP*tan(G.angle)])
        coords[4:,:]=coords[0:4,:]+array([0,0,self.t])
        #centre over posts
        coords=coords+array([0,0.5*(-self.w+self.wP),0])
        G.Make_mesh(coords,G.faces,'rMesh','rObj')
        #make rail on other side
        coords=coords+array([0,self.wT-self.wP,0])
        G.Make_mesh(coords,G.faces,'rMesh','rObj')
        
class Retainers:

    '''class for creating retainers'''

    def __init__(self,w,h,wP,wT,nR):
        self.w=w #retainer width
        self.h=h #retainer height
        self.wP=wP #post width
        self.wT=wT #tread width
        self.nR=nR #number of retainers
        self.sp=hR/float(nR+1) #retainer spacing
        self.Create()

    def Create(self):
        for i in range(self.nR):
            coords=zeros((8,3))
            offset=(i+1)*array([0,0,self.sp])
            coords[0]=G.start+offset
            coords[1]=G.stop+offset
            coords[2]=G.start+offset+array([0,self.w,0])
            coords[3]=G.stop+offset+array([0,self.w,0])
            coords[4:,:]=coords[0:4,:]+array([0,0,self.h])
            #centre in posts
            coords=coords+array([0,0.5*(self.wP-self.w),0])
            G.Make_mesh(coords,G.faces,'rrMesh','rrObj')
            #make retainer on other side
            coords=coords+array([0,self.wT-self.wP,0])
            G.Make_mesh(coords,G.faces,'rrMesh','rrObj')

class Stringer:
    
    '''class for creating stringer'''

    def  __init__(self,w,nT,hT,wT):
        self.w=w #stringer width
        self.nT=nT #number of treads
        self.hT=hT #tread height
        self.wT=wT #tread width
        self.faces=[[0,1,3,2],[1,5,3],[3,5,4],[6,7,9,8],[7,11,9],[9,11,10],[0,2,8,6],[0,1,7,6],[1,5,11,7]]
        self.Create()
        
    def Create(self):
        for i in range(self.nT):
            coords=zeros((12,3))
            coords[0]=G.start+array([0,0,-rise])
            coords[1]=G.start+array([run,0,-rise])
            coords[2]=G.start+array([run*0,0,-self.hT])
            coords[3]=G.start+array([run*1,0,-self.hT])
            coords[4]=G.start+array([run*1,0,0])
            coords[5]=G.start+array([run*2,0,0])
            coords[6:]=coords[0:6]+array([0,self.w,0])
            coords=coords+i*array([run,0,rise])
            #centre below tread
            coords=coords+array([0,0.5*(self.wT-self.w),0])
            G.Make_mesh(coords,self.faces,'sMesh','sObj')
        
#-----------------------------------------------------------
'''create meshes'''

editmode = Window.EditMode()    # are we in edit mode?  If so ...
if editmode: Window.EditMode(0) # leave edit mode before getting the mesh

global G
G=General(rise,run,nT)
if makeTreads: Treads(wT,hT,run,rise,tT,nT)
if makePosts: Posts(dP,wP,wT,nP,hR,tR)
if makeRailings: Rails(wR,tR,hR,tT,wP,dP,wT)
if makeRetainers: Retainers(wRR,hRR,wP,wT,nRR)
if makeStringer: Stringer(wS,nT,hT,wT)

if editmode: Window.EditMode(1)  


Hello! And welcome to BA! :slight_smile: What version of Blender is this for?

Thanks for the welcome staxxer. It’s for version 2.49. When I wrote the script last August the python API for 2.5x was in a state of flux.

wow, quite a nice effect.
I’ve had requests recently for stair creators in 2.5.
are you interested in developing a script for stairs, maybe including several different stair types for 2.5?

Thanks Meta-Androcto. I would be interested but unfortunately I don’t have the time to dedicate to such a project.

Hmm… does not work for me. Line #66 no numpy.

That is because it is using the NumPy Python module which is not distributed with stock Blender. You’ll have to download it (http://numpy.scipy.org/) and install it (you’ll probably have to build it) yourself.

@Atom: As BrikBot mentioned you’ll need to install numpy. As far as I know, no need to build it from source: you can use the .exe installer that matches the version of python packaged with blender2.49.

I have ported the basic script to B2.5x with a slight addition (you can generate multiple stringers). I’ll put it up later this evening once I am on a machine that can access my MediaFire account. It still requires NumPy. Unfortunately this means Mac users are out for now (I’m still trying to get a build that will work with Blender on Mac :mad:), though Windows users can definitely use it and to my knowledge Linux users should also be able to use it. Using NumPy with Blender requires getting a build Blender will play nice with and coping it into one of Blender’s “scripts” sub-folders such as “modules” or “addons_extern”. Windows builds I have done that I know work can be found here for x64 and here for x86.

Depending on how things play out, if you don’t mind Nick, I may over the next couple days add support for a couple more features I could see myself wanting, such as the ability to generate just the left or right posts/railings/retainers in addition to cleaning up the port some more.

Attachments


Script is up: http://www.mediafire.com/file/af1p7zcec49c09c/add_mesh_stairs.zip

Thank you. Seriously, thanks. Will check it out.

Hi BrikBot, Nick van Adium,

Cool script@Nick_van_Adium Nick van Adium! Just looking through the code quickly, does it really need numpy? It seems that it is mostly used for vector operations which can now be done internally with Blender using mathutils.Vector like this:

import mathutils
vec1 = mathutils.Vector((1, 2, 3))
vec2 = mathutils.Vector((-1, 0, 0))
newvec = vec1.cross(vec2)

Just for interest anyway.

Cheers,
Truman

@BrikBot: I’m glad that you are interested enough to make a port. Feel free to add any new features you like!
@TrumanBlending: You are likely right that things can be done with mathutils. I haven’t learned how to use mathutils, but I do know numpy so that was what I used.

I looked over the script and everything can be done with just Vector and some slight modifications to some of the array operations. I have started changing things over from NumPy to it, and have almost finished. However it seems I inadvertently changed the math for the posts a little since it now has the last post on the top step instead of the landing.

@Nick: Awesome! Will do.

Ok, the add-on no longer uses NumPy: everything is done with either math or mathutils. Also I have added a couple different stair types. I am working on adding circular/spiral as the GUI reflects, though it does nothing right now. If anyone else wants any other type of stairs/functions, you’ll need to mention it because beyond circular/spiral I have no further plans for development beyond figuring out whats up with some of the posts going through the treads they should be landing on.

Link to download: http://www.mediafire.com/file/ld37ml0cubvdwdd/add_mesh_stairs.zip
Legacy versions will be kept here: http://www.mediafire.com/?8faw845qh4jn8

Below are samples from the three type of stairs that are currently generated. You can see the clipping in the box staircase caused by the post error.

Attachments




really cool!
You can make Stadium Bleacher/grandstands with this too. (just make the treads wider)

hey could you give a couple options for the top of the stair surface?
here are some samples industrial style., grip plate, Grate, expanded metal, corrugated metal.

Also give an option for the stringer, C-Channel
(some industrial stairs are made out of that, i know i fabricated alot that way :slight_smile: )
the C- Face out…


or maybe you could combine this script with the “beam Builder” script (that makes really nice C-Channel and I-beam’s and gives great control of what kind you want)

Very interesting!!! I read the thread but haven’t downloaded it yet. This is sort of why I wrote the BeamBuilder add-on, I wanted to generate mesh primitives for an industrial setting. With the BeamBuilder I could create stairs if I needed to, but it would take more time than using this. I’m interested in seeing where this goes!!

Randy

I’m interested in seeing where this goes!!

me too +1
i’ll include it in my next build

Curvacious Galore is another cool addon that gives good profile curves and setting adjustments.