So I was able to clean everything up and get it implemented into the new UI along with the ability for users to quickly and easily add their own custom pipes to the system. Lastly I just had to add the method of acknowledging occupied space in order to eliminate self intersections which has seemingly been already figured out earlier in this thread by Denis. So I’ll be looking ti implement that here next. As well as a few controls for easy manipulation of systems created.
For those who have been sitting around waiting for the new release, I appreciate your patience. I’m in the process of uploading a new video and script as of right now. So bear with me here, in about 30 minutes there will be an update released.
John
Here is the latest update.
https://vimeo.com/54404186
I’m aware of a few redundant bits of code as far as optimization goes. So if any feels like help me optimize some of the code a good place would be to condense these three functions into one…
getEnds
getOpenEnds
fnSelectOpenEnds
Aside from that I’ve added the controls for building custom rig templates as well as layer managing and point helper size controls. Check out the video and see it in action.
Everything works so far. I have yet to add the collision detection. It’s getting there.
Before running the code unzip the attached file and place the JokerMartini folder in the local Max Scripts directory folder, otherwise just comment out the icon images in the script below.
/*
Things To add:
- Denis collision methods
- Option for collision rotation exit
- Quick rotate buttons
- Select open ends button
- Error check for delete scene node/update list
*/
try(destroyDialog rlPipeGen)catch()
rollout rlPipeGen "Pipes Generator"
(
local grpRig = 25
local grpPipes = 150
local grpTools = 320
local pipeList = #()
local iconsFile = #("$scripts/JokerMartini/PipeIconStrip.bmp","$scripts/JokerMartini/PipeIconStripAlpha.bmp")
local rots = #(-90,-45,0,45,90,180)
/* RIG TEMPLATE FUNCITONS
-----------------------------------------------------------------------------------------*/
fn fnObjectsToLayer str nodes: = (--Places supplied nodes to given layer
if nodes == unsupplied do nodes = #()
layers = for i = 1 to LayerManager.count - 1 collect (LayerManager.getLayer i).name
theLayer = if findItem layers str != 0 then (LayerManager.getLayerFromName str)else(layermanager.newLayerFromName str)
for obj in nodes do (theLayer.addNode obj)
)
fn fnGetParents nodes: = ( --get top most parent node
if nodes == unsupplied do nodes = #()
local parentNodes = #()
for i = 1 to nodes.count do (
o = nodes[i]
if isvalidnode o do (--Check if the node passed to the function is a valid node.
while o.parent != undefined do o = o.parent --Loop through the hierarchy until the current node's parent is undefined (i.e. rootnode)
append parentNodes o --Return the rootnode
)
)
makeUniqueArray parentNodes
parentNodes
)
fn fnEndCtrl parent pos = (ctrl = point name:(uniqueName "pipeEnd_") cross:true box:false centerMarker:false axisTripod:true wirecolor:yellow size:(rlPipeGen.spCtrlSize.value/2) parent:parent pos:pos)
fn setNodeWorldRotation theNode theRot = (in coordsys (transmatrix theNode.transform.pos) theNode.rotation = theRot)--Sets rotation of objects in world space
fn setNodeLocalRotation theNode theRot = (in coordsys local rotate theNode theRot)
fn fnCreatePipeRig type:1 layerName: = (
local rigCtrls = #()
ctrlSize = rlPipeGen.spCtrlSize.value
pStart = if selection.count >=1 then [selection.center.x,selection.center.y,selection.min.z] else [0,0,0]
pEnd = if selection.count >=1 then [selection.center.x,selection.center.y,selection.max.z] else [0,0,ctrlSize*3]
tmMax = if selection.count >=1 then [selection.max.x,selection.max.y,selection.max.z] else [ctrlSize*3,ctrlSize*3,ctrlSize*3]
tmMin = if selection.count >=1 then [selection.min.x,selection.min.y,selection.min.z] else -[ctrlSize*3,ctrlSize*3,ctrlSize*3]
/* Template Rigs = 1. Straight , 2. T Shaped 90° , 3. T Shaped , 4. Cross , 5. 90° Curve Corner , 6. 45° Curve Corner , 7. Fork , 8. Y Shaped , 9. End Cap */
pName = #("Straight","TShaped","TShaped","Cross","90Curve","45Curve","Fork","YShaped","EndCap")
append rigCtrls (ctrlStart = point name:(uniqueName ("pipe" + pName[type] +"_Start_")) cross:true box:true centerMarker:false axisTripod:false wirecolor:green size:ctrlSize pos:pStart) --required minimum helper
if type == 1 do ( --Straight
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart pEnd)
)
if type == 2 do ( --T Shaped 90°
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart pEnd)
append rigCtrls (ctrlEndB = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z/2])
setNodeWorldRotation ctrlEndB (eulerangles 0 -90 0)
)
if type == 3 do ( --T Shaped
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z])
append rigCtrls (ctrlEndB = fnEndCtrl ctrlStart [tmMin.x,pStart.y,pEnd.z])
setNodeWorldRotation ctrlEndA (eulerangles 0 -90 0)
setNodeWorldRotation ctrlEndB (eulerangles 0 90 0)
)
if type == 4 do ( --Cross
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart pEnd)
append rigCtrls (ctrlEndB = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z/2])
append rigCtrls (ctrlEndC = fnEndCtrl ctrlStart [tmMin.x,pStart.y,pEnd.z/2])
setNodeWorldRotation ctrlEndB (eulerangles 0 -90 0)
setNodeWorldRotation ctrlEndC (eulerangles 0 90 0)
)
if type == 5 do ( --90° Curve Corner
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z])
setNodeWorldRotation ctrlEndA (eulerangles 0 -90 0)
)
if type == 6 do ( --45° Curve Corner
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z])
setNodeWorldRotation ctrlEndA (eulerangles 0 -45 0)
)
if type == 7 do ( --Fork
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart pEnd)
append rigCtrls (ctrlEndB = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z])
setNodeWorldRotation ctrlEndB (eulerangles 0 -45 0)
)
if type == 8 do ( --Y Shaped
append rigCtrls (ctrlEndA = fnEndCtrl ctrlStart [tmMax.x,pStart.y,pEnd.z])
append rigCtrls (ctrlEndB = fnEndCtrl ctrlStart [tmMin.x,pStart.y,pEnd.z])
setNodeWorldRotation ctrlEndA (eulerangles 0 -45 0)
setNodeWorldRotation ctrlEndB (eulerangles 0 45 0)
)
if type == 9 do () -- End Cap
--Make sure to link all parent nodes in selection to the pipe start control
for o in (fnGetParents nodes:(selection as array)) do o.parent = ctrlStart
--Add all pipe rig controls to desired layer
fnObjectsToLayer layerName nodes:rigCtrls
)
/*PIPE LIST CONTROLS
-----------------------------------------------------------------------------------------*/
fn checkStart node = (if classof node == Point AND findString node.name "Start" != undefined then true else false)
fn fnAddItmsLst lst arr = ( -- add Objects to list if they meet requirements
curSel = getCurrentSelection() as array
tmpArr = for p in curSel where (checkStart p) collect p
if tmpArr.count >= 1 do
(
for o in tmpArr do (appendIfUnique arr o)
lst.items = for i in arr collect i.name
)
)
fn fnRemoveItmsLst lst arr = ( -- remove objects from list
local currSel = lst.selection
for i = lst.items.count to 1 by -1 where currSel[i] do (deleteItem arr i)
lst.items = for i in arr collect i.name
lst.selection = #{}
)
/* PIPE LIST CONTROLS
-----------------------------------------------------------------------------------------*/
fn getEnds arr = (
local tmpArr = #()
tmpArr = if arr.count >= 1 then (for o in arr where classof o == Point AND findString o.name "End" != undefined AND findString o.name "Start" == undefined collect o) else #()
tmpArr
)
fn fnGeneratePipes = (
if (rlPipeGen.lstPipes.selection as array).count >= 1 then (
newEnds = #()
pts = if selection.count >= 1 then (for o in selection collect o) else #(circle name:(uniqueName "PipeSystem_") size:(rlPipeGen.spCtrlSize.value*2) wirecolor:yellow) --use selected objects transforms else use the origin
for o in pts do (
--Randomly choose a pipe based on the list selection
items = rlPipeGen.lstPipes.selection as array
idx = items[random 1 items.count]
_pipe = pipeList[idx]
maxOps.cloneNodes _pipe expandHierarchy:true cloneType:#copy actualNodeList:&refPipes newNodes:&newPipes
pipeCtrl = newPipes[1]
pipeCtrl.transform = copy (prerotatez o.transform rots[random 1 rots.count])
for p in newPipes where (checkStart p) do p.parent = o --link children
join newEnds (getEnds newPipes)
)
clearselection()
select newEnds
)
else messagebox "Select at least one or more items in the list in order to generate objects."
)
/* TOOLS
-----------------------------------------------------------------------------------------*/
fn getOpenEnds node &arr:#() =
(
for c in node.children do
(
append arr c
getOpenEnds c arr:arr
)
arr
)
fn fnSelectOpenEnds = (
local endCaps = #()
for o in selection do (for c in (getOpenEnds o arr:(selection as array)) where c.children.count == 0 AND classof c == point AND findString c.name "End" != undefined do append endCaps c)
select endCaps
)
groupbox gpPipeRigCtrls "Pipe Rig Templates:" width:200 height:140 pos:[10,grpRig-20]
label lbCtrlSize "Size:" pos:[20,grpRig]
spinner spCtrlSize "" width:90 pos:[110,grpRig] range:[.1,9999999,(units.decodevalue "0.5m")] type:#worldUnits
label lbAddToLayer "Add to layer:" pos:[20,grpRig+20]
edittext etLayerName "" width:95 pos:[106,grpRig+20] text:"Pipe_ctrls"
button btnPipeA "St8" height:30 width:30 pos:[20,grpRig+45] images:#(iconsFile[1], iconsFile[2], 9,1,1,1,1 ) tooltip:"STRAIGHT: pipe includes 1 open end"
button btnPipeB "T 90°" height:30 width:30 pos:[57,grpRig+45] images:#(iconsFile[1], iconsFile[2], 9,3,3,1,1 ) tooltip:"T: shaped pipe rotated 90° includes 2 open ends"
button btnPipeC "T" height:30 width:30 pos:[94,grpRig+45] images:#(iconsFile[1], iconsFile[2], 9,2,2,1,1 ) tooltip:"T: shaped pipe includes 2 open ends"
button btnPipeD "X" height:30 width:30 pos:[131,grpRig+45] images:#(iconsFile[1], iconsFile[2], 9,4,4,1,1 ) tooltip:"CROSS: shaped pipe includes 3 open ends"
button btnPipeE "90°" height:30 width:30 pos:[168,grpRig+45] images:#(iconsFile[1], iconsFile[2], 9,5,5,1,1 ) tooltip:"CURVE 90°: angle inlcudes 1 open end"
button btnPipeF "45°" height:30 width:30 pos:[20,grpRig+82] images:#(iconsFile[1], iconsFile[2], 9,6,6,1,1 ) tooltip:"CURVE 45°: angle includes 1 open end"
button btnPipeG "Fork" height:30 width:30 pos:[57,grpRig+82] images:#(iconsFile[1], iconsFile[2], 9,7,7,1,1 ) tooltip:"FORK: pipe includes 2 open ends"
button btnPipeH "Y" height:30 width:30 pos:[94,grpRig+82] images:#(iconsFile[1], iconsFile[2], 9,8,8,1,1 ) tooltip:"Y: shaped pipe includes 2 open ends"
button btnPipeI "End" height:30 width:30 pos:[131,grpRig+82] images:#(iconsFile[1], iconsFile[2], 9,9,9,1,1 ) tooltip:"END CAP: gets used as the object to close any pipe"
groupbox gpPipes "Pipes:" width:200 height:165 pos:[10,grpPipes]
multilistbox lstPipes "" items:#() width:130 height:10 pos:[20,grpPipes+20]
button btnAdd "+" height:68 width:50 pos:[150,grpPipes+20]
button btnRemove "-" height:67 width:50 pos:[150,grpPipes+88]
groupbox gpTools "Tools:" width:200 height:90 pos:[10,grpTools]
button btnSelectEnds "Select Open Ends" width:180 height:25 pos:[20,grpTools+20]
label lbQuickRotate "Quick Rotate:" pos:[20,grpTools+60]
button btnRotA "15°" width:30 height:30 pos:[94,grpTools+52]
button btnRotB "45°" width:30 height:30 pos:[132,grpTools+52]
button btnRotC "90°" width:30 height:30 pos:[170,grpTools+52]
button btnInstructions "?" width:30 height:30 pos:[10,420]
button btnGenPipes "Generate Pipes" width:170 height:30 pos:[40,420]
on spCtrlSize changed val do (for o in selection where classof o == Point do o.size = val)
on btnPipeA pressed do (fnCreatePipeRig type:1 layerName:etLayerName.text)
on btnPipeB pressed do (fnCreatePipeRig type:2 layerName:etLayerName.text)
on btnPipeC pressed do (fnCreatePipeRig type:3 layerName:etLayerName.text)
on btnPipeD pressed do (fnCreatePipeRig type:4 layerName:etLayerName.text)
on btnPipeE pressed do (fnCreatePipeRig type:5 layerName:etLayerName.text)
on btnPipeF pressed do (fnCreatePipeRig type:6 layerName:etLayerName.text)
on btnPipeG pressed do (fnCreatePipeRig type:7 layerName:etLayerName.text)
on btnPipeH pressed do (fnCreatePipeRig type:8 layerName:etLayerName.text)
on btnPipeI pressed do (fnCreatePipeRig type:9 layerName:etLayerName.text)
on btnAdd pressed do (fnAddItmsLst lstPipes pipeList)
on btnRemove pressed do (fnRemoveItmsLst lstPipes pipeList)
on btnSelectEnds pressed do (fnSelectOpenEnds())
on btnRotA pressed do (for o in selection do setNodeLocalRotation o (eulerangles 0 0 15))
on btnRotA rightclick do (for o in selection do setNodeLocalRotation o (eulerangles 0 0 -15))
on btnRotB pressed do (for o in selection do setNodeLocalRotation o (eulerangles 0 0 45))
on btnRotB rightclick do (for o in selection do setNodeLocalRotation o (eulerangles 0 0 -45))
on btnRotC pressed do (for o in selection do setNodeLocalRotation o (eulerangles 0 0 90))
on btnRotC rightclick do (for o in selection do setNodeLocalRotation o (eulerangles 0 0 -90))
on btnGenPipes pressed do (fnGeneratePipes())
)
createDialog rlPipeGen 220 460 style:#(#style_SysMenu, #style_ToolWindow)
How can I optimize this snippet of code into a smaller nugget?
The comment part of the script just creates the scene for testings. Once you run that you can comment it back out. Then just run what is below the dotted line. From there you can select any point helper in the scene and run the script. It will change the color of only the ‘green’ point helpers.
This function is made to return the last end control in the selected hierarchy/hieararchies.
-- delete objects
-- s = 20
-- c = circle wirecolor:red
-- fn fnGenSegment = (
-- local a = point pos:[0,0,0]wirecolor:yellow size:(s*2) name:(uniqueName "start_") cross:true box:true
-- local b = box parent:a height:(s*4) width:s length:s wirecolor:blue
-- local c = point parent: b pos:[0,0,b.max.z]wirecolor:yellow size:(s*2) name:(uniqueName "end_") cross:true box:false
-- #(a,c)
-- )
-- a = fnGenSegment()
-- a[1].parent = c
-- b = fnGenSegment()
-- b[1].parent = a[2]
-- b[1].pos = [0,s*2,s*4]
-- c = fnGenSegment()
-- c[1].parent = a[2]
-- c[1].pos = [0,-s*2,s*4]
-- b[2].wirecolor = c[2].wirecolor = green
------------------------------------------------------------- The Script
fn getEnds node &arr:#() =
(
for c in node.children do
(
append arr c
getEnds c arr:arr
)
arr
)
endCaps = #()
for o in selection do (for c in (getEnds o arr:(selection as array)) where c.children.count == 0 AND classof c == point AND findString c.name "End" != undefined do append endCaps c)
endCaps.wirecolor = random black white
This reminds me a lot of a race game editor I wrote in 3ds max for a mobile game company.
Too bad I lost the script
Keep up the good work!
Has anyone been able to figure out a better way of getting all of the last children in a hierarchy.
I just need to figure out a good way to collect the children of a hierarchy going from the selected objects down through their children and selecting the last child but only if that last child is a point helper. The objects leading up to it can be whatever they want.
mapped fn getEndChildren node ends:#() root:on = if not isvalidnode node then ends else
(
if root or node.children.count > 0 then for child in node.children do getEndChildren child ends:ends root:off
else if iskindof node Point do appendifunique ends node
ends
)
(
a = #()
getEndChildren selection ends:a
a
)
Thank you Denis for the help. I figured there was a better way of writing it.
I’m going to at some rotation controls into the build and I’m looking to do something like this possibly.
Here the user can define the intervals by an angle. So if they say I want to rotate my object randomly from 0-360 on intervals of 90 = 0,90,180,270,360. It still needs some tweaking but figured I would share the idea.
(
range = [0,360]
intervals = 45
fn fnRandomRotation = (
rotations = for a = 0 to 360 by intervals collect a
rot = rotations[random 1 rotations.count]
)
clearlistener()
fnRandomRotation()
)