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
#! /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)