[Closed] Create Stairs from Plane/Edges?
Whats up guys, have an interesting one for you.
So I’m working on a lot of architectural models lately to be used inside a simulation engine. To move our agents down stairs, we just used an angled plane instead of the stairs themselves.
So I just create my stairs (and landings) with some standard box modeling techniques.
Recently there’s been some talk about adding the actual stairs for aesthetic purposes. Now even with the Stair object in Max, this is quite a tedious task. So my boss and I had some discussion about whether or not a script could make this task much easier…problem is I’m not a programmer ;).
So here’s the idea:
Could we somehow use the top and bottom edges of the existing angled plane, and have a script that copies and distributes an array of boxes along that plane. I figure the length & height would be constant, and the width could either be derived directly from the plane, or just a value in a textbox.
I’d imagine this is pretty complex, so even just a few hints to get me started would be much appreciated.
Thanks guys,
ryan
Here is my idea, though there are probably better or more efficient ones. I’m not sure if all these commands are scriptable, I haven’t checked, but it might give you some ideas.
- manually assign material id 1 to the entire object.
- manually assign material id 2 just to the angled part faces.
- manually assign material id 3 just to the TOP FACING face of the angled parts.
- script: select all faces with material id 2
- script: convert selection to edges
- script: find the longest of edge of the selected edges (to ensure we’re not picking one of the wrong edges)
- script: select ring of selected edge
- script: find out how many stairs are needed for the current edge (length of edge divided by your fixed stair length)
- script: edge connect command with iterations as the number of steps needed.
- script: select all faces with material id 3
- script: for each of these faces, individually, extrude by a very small amount (0.001) and move upward on the z axis by an amount equal to your fixed stair height. Then align extruded face to world z axis.
i’m bored and i’ve taken your problem as a challenge. i’m rather new to maxscripting and need a good problem to solve. I had some questions to ask regarding how you would prefer your end product to be. In your example you used copies of a box to make the stairs. is this how you would prefer? or would you rather the object be modified to have the steps modeled in.(far more complex but doable)
i'm intending to do the method you showed with the individual boxes. i personalty liked that option because not all stairs are the same, some are solid steps, some are individual platforms mounted to a frame underneath(like how the default stairs are)
I’ll post whatever i come up with an hopefully it will be of some use to you. i just wanted a goal to work toward
so far so good, here is my script so far. it only works with edges that are in line with world axis(x or y)
i'll be doing more but it's break time and it's working so. feel free to try it out. just execute the script and you'll get the diolog. pick 2 edges and select the correct axis. i'm thinking of modifying it so it creates the items at the origin and moves/rotates them to the right coords after all the steps are created. i'll probably add some flags so that it will attach them together too
[img] http://dl.dropbox.com/u/900056/Images/Webhost/SS-2010-05-16_17.17.20.jpg [/img]
See Below 3 Posts
and yes, i know that could be 1000 times better. like using such a huge function in my button event isn't the best way. i'm new to this whole scripting thing though :P i would say that's the first functional little thing i've ever made. excluding all the modifications i do to my ui and workflow
[b]Edit: Added auto setting and set it to default. still only works in X and Y. Can now change Thickness in realtime also[/b]. [b]just now thought of a name for the script too! i could call it labyrinth![/b]
msCookie, that is just too awesome, I really can’t thank you enough for your time.
The script works really nice, just like I had envisioned. If we could get that local orientation working I think we’d be all set.
For now that’s not too big of an issue, because I can just build my stairs aligned to XYZ, then just rotate everything later.
This brings me to an interesting question. When I can, I use the angle snap function, so I can always re-rotate an object back to a perfect XYZ. However, if I rotate the object without any snap, say 39.142 degrees, do I have to rotate it back to exactly -39.142 degrees? So does it have to be perfect, “exact” XYZ orientation for the script? (this issue being that unless I jot down that number I’ll never get it perfect without some manual tweaking in the Edit Poly panel.)
Thanks again man, great stuff. (Love the name too!)
i’m pretty new to scripting so i’m still pretty lost when it comes to coordinate systems. there is a ton of trig and vector/matrix math involved when trying to orient something correctly in 3d space
that’s why it only works in x and y. As i get better with it i’ll see about adding that functionality. i have a concept in my head that i think could work but i haven’t tried to do it yet.
as for your question. as of now it is simply creating boxes in the correct location, the only setting i’m adjusting are pos [x,y,z] and the width length height. no rotation at all. the auto function just checks to see what direction the offset is in. you could probably get away with 1 or 2 degrees of rotation. but the result wont be as good as if it where straight. and i’m not sure the auto function will work either.
I’m going to start digging through the maxsript help and see about learning coordinate systems. i’m sure there are some really good functions for handling that type of stuff but i wanted to keep it simple for starters.
Here is a little update, i added an option for setting a static step lenght. i’m sure you would prefer that setting over the step count one.
Next update i plan to have a mouse tool, it will be setup so that when you click “make” without anything selected you can start picking edges and it will create a new set of stairs for every 2 edges you select. With an abort on esc or right mouse click. Then i’ll start on the coordinate system.
I’ll probably go through and reformat the entire script too. it’s rather sloppy at its current state.
cool project though:D thanks for the idea! i’m learning lots
Ver 2 below
i wasn’t too happy with the last version of the script. here is version 2. new features
-Nearly completely rewritten
-Realtime update for last stairs
-Works in all 360 degrees
-Saved settings, per 3ds max session
-all steps in one staircase are instances so changing one will effect all. Nice for when you want to add a chamfer, Extrude out a ledge, ect. Can really do anything with them pretty quickly
could add a bend, skew or FFD modifier to all of them change the shape.
...or symmetry modifier at 45 degrees for a quick corner. I love max :P
To use: It's a macroscript. Found in category "Custom" in customize user interface.
you can make a button, bind a hotkey or run macros.run "Custom" "StairBuilder"
1: Select 2 parallel edges of an Editable/Edit poly that you would like to build stairs between
2: Click Make
3: Adjust Settings
4: Add Edit Poly modifier to one of the stairs and go crazy :)
5: Rince lather repeat
Next I plan to tackle what I want to call"Speed mode". For every 2 edges selected it will build a set between them without having to click the make button.
Let me know if you find any bugs,
Dont try and drag spinners if you do an undo, it will try and set setting for stairs that dont exist. not sure how to fix that one yet...wait...duh. [b]Edit: fixed[/b] [b]
Edit2: couple other bug fixes v.21[/b]
------------------------------------------------------------------------------------------------
------------------------------------------Stair Builder--------------------------------------
--To use:
--1 - Select 2 parallel edges of an Editable/Edit poly that you would like to build stairs between
--2 - Click "Make"
--3- Adjust Settings
--4 - Add Edit Poly modifier to one of the stairs and go crazy :)
--5 - Rince lather repeat
--
--The steps are instances of each-other. Per stairset.
----------------------------------------------V 0.21--------------------------------------------
------------------------------------------------------------------------------------------------
macroScript StairBuilder
category:"Custom"
buttonText:"Stair builder"
(
try(closerolloutfloater rofSmr)catch()
global rofSmr
global rofWidth = 230
global rofHeigth = 130
--UI state globals
global rofSaved --saved settings flag
global rofPos --rollout floater position
global rofThick --Thickness state
global rofSSize --Step Size state
global rofRBS --radio buttons state
global aNewSteps
if rofSaved == undefined then --set defaults if no settings are stored
(
rofThick = 100
rofRBS = 1
rofSSize = 10
rofPos = [((getMAXWindowSize())[1]- rofWidth - 300),300]
)
rollout StepMakerRollout "Step Maker"
(
--------------------------------------------------------------
--UI controls
--------------------------------------------------------------
label scaleByLbl "Scale By:" align:#Left across:2
radiobuttons SizeByRadio labels:#("Length","Count(int)") columns:2 offset:[-10,0] default:rofRBS
spinner stepSizeSpn "Step Length " align:#Left type:#float range:[.5,100,rofSSize]
spinner stepThicknessSpn "Step Thickness (%)" align:#Left type:#integer range:[0,100,rofThick]
button makeBtn "Build Steps" align:#Left across:2
button deleteBtn "Delete last" align:#Left
label angleLbl "Angle: Unknown" align: #Left
-- button testBtn "test" align:#Left
--------------------------------------------------------------
--local Varialbles
--------------------------------------------------------------
local selObj = undefined
local c2cVector = undefined
local aSelectedEdges = #()
local sHeight = undefined
local count = undefined
local Edge1 = undefined
local Edge2 = undefined
local Edge1Center = undefined
local Edge2Center = undefined
local Edge1Angle = undefined
local Edge1Length = undefined
--------------------------------------------------------------
--functions
--------------------------------------------------------------
fn getSelectionWithValidCheck =
(
--Switch to modify panel
max modify mode
--reset selection variables to nil
selObj = undefined
aSelectedEdges = #()
-- Check that object of class Geometry is selected
if classof $ == Editable_poly or classof $ == PolyMeshObject then
(
--Checking Modify for valid classof edit/editable poly and setting rollout scope variables
local valObj = false
if classof(modpanel.getcurrentobject()) == Edit_poly then
(
print "Edit_poly = true"
selObj = $.modifiers[#Edit_Poly]
valObj = true
)
else if classof(modpanel.getcurrentobject()) == Editable_poly then
(
print "Editable_poly = true"
selObj = $
valObj = true
)
-- if valid obj, check edge selection to be 2
if valObj and ((selObj.getselection #Edge)as array).count == 2 then
(
print "2 edges count pass"
aSelectedEdges = ((selObj.GetSelection #Edge) as array)
True
)
else (format "Need 2 edges selected. You have % selected. Returning false.
" (((selObj.getselection #Edge)as array).count) ; false)
)
else
(
print "Needs Edit/Editable_poly"
false
) --end if GeometryClass check
) -- end getSelectionWithValidCheck
--average xyz of 2 points to find center
fn getEdgeCenter myEdge =
(
([(myEdge[1].x+myEdge[2].x),(myEdge[1].y+myEdge[2].y),(myEdge[1].z+myEdge[2].z)]/2)
)
--return array of verts locations in
fn getEdgeVerts myEdge =
(
vert1 = selObj.GetEdgeVertex myEdge 1
vert2 = selObj.GetEdgeVertex myEdge 2.
case classof selObj of
(
Edit_poly:
(
--print "case:Edit_poly"
return #(selObj.Getvertex vert1, selObj.Getvertex vert2)
)
Editable_poly:
(
--print "case:Editable_poly"
return #(polyop.Getvert selObj vert1, polyop.Getvert selObj vert2)
)
)
) --end getEdgeVerts
fn getCount val =
(
local c
case SizeByRadio.state of
(
1: c = floor ((length [c2cVector.x, c2cVector.y, 0])/val)
2: c = floor val
)
c
)
fn collectEgdeInfo = -- set top edge, Get Verts, angles, offset and vectors
(
Edge1 = getEdgeVerts aSelectedEdges[1]
Edge2 = getEdgeVerts aSelectedEdges[2]
if Edge2[1].z > Edge1[1].z then swap Edge1 Edge2
Edge1Center = getEdgeCenter Edge1
Edge2Center = getEdgeCenter Edge2
c2cVector = Edge2Center - Edge1Center
-- print Edge1Center
-- print Edge2Center
-- print center2CenterVector
--Set Upper edge to Edge 1 if not already
--Set Edge2[1] nearest to Edge1[1]
if sqrt(dot (Edge1[1] - Edge2[1]) (Edge1[1] - Edge2[1])) > sqrt(dot (Edge1[1] - Edge2[2])(Edge1[1] - Edge2[2])) then
swap Edge2[1] Edge2[2]
--Square root of the dot product of edgevector^2 == length
Edge1Length = length (Edge1[1] - Edge1[2]) -- hypotenuse
Edge2Length = length (Edge2[1] - Edge2[2])
adjacent = Edge1[1].x - Edge1[2].x -- adjacent
-- soh ((cah)) toa. cah = cos = adjasent/hypotenuse. inverse to get angle
Edge1Angle = 180 - acos (adjacent/Edge1Length)
if Edge1[1].y < Edge1[2].y then
Edge1Angle *= -1
angleLbl.caption = ("Angle: " + Edge1Angle as string)
count = getCount StepSizeSpn.value
c2cVector = (Edge2Center - [0,0,(c2cVector.z/(count+1))]) - Edge1Center
--print (Edge1Angle)
-- print ("Edge1 vector = " +(Edge1[1] - Edge1[2]) as string)
-- print ("Edge2 vector = " +(Edge2[1] - Edge2[2]) as string)
-- print ("Edge1 Length = " + (sqrt(dot (Edge1[1] - Edge1[2])(Edge1[1] - Edge1[2]))) as string)
-- print ("Edge2 Length = " + (sqrt(dot (Edge2[1] - Edge2[2])(Edge2[1] - Edge2[2]))) as string)
-- print ("Edge 1 Vertex 1: " + Edge1[1] as string)
-- print ("Edge 1 Vertex 2: " + Edge1[2] as string)
-- print ("Edge 2 Vertex 1: " + Edge2[1] as string)
-- print ("Edge 2 Vertex 2: " + Edge2[2] as string)
) -- end collectEgdeInfo
--------------------------------------------------------------
--UI event Handelers
--------------------------------------------------------------
--Set Caption of Step Size Spinner based on radio button selection
on SizeByRadio changed val do
(
case val of
(
1: StepSizeSpn.caption = "Step Lenght"
2: StepSizeSpn.caption = "Step Count"
)
)
fn buildSteps =
(
(
sHeight = -length ([0,0,c2cVector.z]/count)
local sWidth = Edge1Length
local sLength = length [c2cVector.x,c2cVector.y,0] / count
local initPos = (Edge1Center + (normalize ([c2cVector.x,c2cVector.y,0])*(sLength/2))+[0,0,sHeight])--+[0,0,sHeight]
aNewSteps = #()
--local step
for i = 0 to count-1 do
(
local step
if aNewSteps.count == 0 then
(
step = Box name:"stair" length:sLength width:sWidth height:(sHeight*(stepThicknessSpn.value*.01)) mapcoords:on pos:initPos
rotate Step (angleaxis -Edge1Angle [0,0,1])
)
else
(
step = instance aNewSteps[i]
move step ((c2cVector)/count)
)
append aNewSteps step
)
--print aNewSteps
completeRedraw()
)
)
on makeBtn pressed do
(
if getSelectionWithValidCheck() then
(
undo off
(
collectEgdeInfo()
buildSteps()
)
)
)
on stepThicknessSpn changed val do
(
if aNewSteps.count != 0 and IsValidNode aNewSteps[1] then
aNewSteps[1].height = sHeight*(val*.01)
)
on StepSizeSpn changed val do
(
if aNewSteps[1] != 0 and IsValidNode aNewSteps[1] then
(
local newCount = getCount val
if count != newCount then
(
count = newCount
--print "count change"
delete aNewSteps
c2cVector = Edge2Center - Edge1Center
c2cVector = (Edge2Center - [0,0,(c2cVector.z/(count+1))]) - Edge1Center
buildSteps ()
)
)
)
on SizeByRadio changed val do
(
if aNewSteps[1] != 0 and IsValidNode aNewSteps[1] then
(
count = getCount stepSizeSpn.value
--print "count change"
delete aNewSteps
c2cVector = Edge2Center - Edge1Center
c2cVector = (Edge2Center - [0,0,(c2cVector.z/(count+1))]) - Edge1Center
buildSteps ()
--delete aNewSteps
)
)
on deleteBtn pressed do
(
delete aNewSteps
aNewSteps = #()
gc light:true
)
on StepMakerRollout close do
(
rofPos = rofSmr.pos
rofThick = stepThicknessSpn.value
rofRBS = SizeByRadio.state
rofSSize = stepSizeSpn.value
rofSaved = true
)
)
rofSmr = newrolloutfloater "StepMakerRollout" rofWidth rofHeigth rofPos.x rofPos.y
addrollout StepMakerRollout rofSmr border:false
)
Cool stuff.
I could see this being good for quick prototypes, or maybe certain games, but for normal stairs, I think defining the 2 end edges, and then cutting in/raising the new edges would be more realistic.
Right now if you wanted to make this into a solid staircase, alot of welding/cutting would be involved.
Basically u’d have to take the 2 edges, leave those heights, then set the rise/run for the other edges as per input or something. But this would be more like what max already has I think.