-- ----------------------------------------------------------------------- -- MAXScript Procedural Array -- "r7_procedural_array.ms" -- ----------------------------------------------------------------------- -- AUTHOR: Markus Boos -- COMPANY: project|gemini -- CONTACT: relief7@projectgemini.net -- COPYRIGHT: 2007-2010. Markus Boos. All Rights Reserved -- CREATION DATE: 2009-03-01 (YYYY-MM-DD) -- MAX Version: 2008-2010 -- ----------------------------------------------------------------------- /* -------------------------------------------------------------------------- INSTRUCTIONS -------------------------------------------------------------------------- This script allows you to create procedural arrays which stay editable even after you close and reopen the file. The array objects are linked to a parent helper which you can transform to your liking while still keeping array qualities. Running the script: Drag & Drop the script into MAX's viewport or start it via MAXScript -> Run Script Usage: 1. A dialog opens. Click and select the source object in your scene (currently only instances are supported). 2. The master array node (yellow point helper) and one instance will be created at [0,0,0]. 3. Move, rotate and scale the master array node into place (instances are linked to it). 4. Change number of instances in each axis (x,y,z) and distance between objects to your liking. 5. Check "auto-distance" if you want the script to place one object right next to each other (without any gaps). This is based on the bounding box on the object. Does not work that well if source object is transformed (scaled, rotated). 6. "Center around master" means all instances will be placed around the master array node, instead of starting to place elements there and continue in one direction. 7. The scale values allow you to locally scale the instances without affecting the source object. 8. Similar to 7, just changing the rotation values. 9. After you are done. Close the tool window and continue working. If you want to change an existing array: 1. Reopen the tool. 2. Click and click the master array node. 3. The last settings are read in from the master node. 3. If you want to change anything, you need to have an instance selected as well. But with every change the array needs to be rebuilt. So you need to make a copy of an existing array element and use that as your geo node: . 4. Then change to your liking and array will be rebuilt. Attention: Do not delete the master array node or its modifier. Otherwise the array will lose its ability to be changed. After that it will be treated as any other collapsed max array. The auto-distance option only works in MAX 2008 and higher. -------------------------------------------------------------------------- VERSION HISTORY -------------------------------------------------------------------------- Version 0.01 2007-03-01 - Markus Boos - relief7@projectgemini.net - initial script created -------------------------------------------------------------------------- BUGS / KNOWN ISSUES -------------------------------------------------------------------------- - -------------------------------------------------------------------------- TODO ITEMS / WISHLIST -------------------------------------------------------------------------- - */ ( -- --------------------------------------------------- -- SETUP -- --------------------------------------------------- -- --------------------------------------------------- -- GLOBAL VARIABLES -- --------------------------------------------------- -- global rollout floater variable global rofTool -- --------------------------------------------------- -- CUSTOM ATTRIBUTE DEFINITIONS -- --------------------------------------------------- CADefArrayControl = attributes caArrayControl attribId:#(0x72dcf469, 0xc133030) ( parameters main -- rollout: roHeliControl ( fCountX type:#integer default: 1 fCountY type:#integer default: 1 fCountZ type:#integer default: 1 fDistX type:#float default: 10.0 fDistY type:#float default: 10.0 fDistZ type:#float default: 10.0 fScaleX type:#float default: 10.0 fScaleY type:#float default: 10.0 fScaleZ type:#float default: 10.0 bCenter type:#boolean default: false bAutoDistance type:#boolean default: false ) ) -- --------------------------------------------------- -- FUNCTION DEFINITIONS -- --------------------------------------------------- -- --------------------------------------------------- -- ROLLOUT DEFINITIONS -- --------------------------------------------------- function filterGeo obj = ( superclassOf obj == GeometryClass ) Rollout roProceduralArray "Procedural Array" ( -- --------------------------------------------------- -- ROLLOUT FUNCTIONS -- --------------------------------------------------- -- --------------------------------------------------- -- ROLLOUT UI ELEMENTS -- --------------------------------------------------- label lblGeoNode "Array element:" pos: [10, 6] label lblMasterNode "Array master:" pos: [10, 30] pickbutton pbGeoNode "" message: "pick geometry node" filter: filterGeo autoDisplay:true width: 100 pos: [90, 2] pickbutton pbRoot "" message: "pick root node" autoDisplay:true width: 100 pos: [90, 26] spinner spCountX "count X" type: #integer range: [1, 99999, 1] fieldwidth: 45 pos: [10, 54] spinner spCountY "count Y" type: #integer range: [1, 99999, 1] fieldwidth: 45 pos: [10, 74] spinner spCountZ "count Z" type: #integer range: [1, 99999, 1] fieldwidth: 45 pos: [10, 94] spinner spDistX "dist X" type: #worldunits range: [0, 99999, 10] fieldwidth: 45 pos: [110, 54] spinner spDistY "dist Y" type: #worldunits range: [0, 99999, 10] fieldwidth: 45 pos: [110, 74] spinner spDistZ "dist Z" type: #worldunits range: [0, 99999, 10] fieldwidth: 45 pos: [110, 94] checkbox cbAutoDistance "auto distance (by BBox)" pos: [50, 114] checkbox cbCenter "center around master" pos: [50, 134] spinner spScaleX "scale X" type: #float range: [0, 99999, 1] fieldwidth: 45 pos: [10, 154] spinner spScaleY "scale Y" type: #float range: [0, 99999, 1] fieldwidth: 45 pos: [10, 174] spinner spScaleZ "scale Z" type: #float range: [0, 99999, 1] fieldwidth: 45 pos: [10, 194] spinner spRotateX "rotate X" type: #float range: [-99999, 99999, 0] fieldwidth: 45 pos: [110, 154] spinner spRotateY "rotate Y" type: #float range: [-99999, 99999, 0] fieldwidth: 45 pos: [110, 174] spinner spRotateZ "rotate Z" type: #float range: [-99999, 99999, 0] fieldwidth: 45 pos: [110, 194] -- --------------------------------------------------- -- EVENT HANDLER(S) -- --------------------------------------------------- function doUpdate = ( -- start x, y, z position, distance and scale values local fStartX = 0.0 local fStartY = 0.0 local fStartZ = 0.0 local fDistX = spDistX.value local fDistY = spDistY.value local fDistZ = spDistZ.value local fScaleX = spScaleX.value local fScaleY = spScaleY.value local fScaleZ = spScaleZ.value -- check if auto distance is checked if ( cbAutoDistance.checked ) then ( local bbGeo = undefined if pbGeoNode.object != undefined then ( -- calculate bounding box of geo object bbGeo = nodeGetBoundingBox pbGeoNode.object ( matrix3 1 ) fDistX = ( bbGeo [2].x - bbGeo[1].x ) * fScaleX fDistY = ( bbGeo [2].y - bbGeo[1].y ) * fScaleY fDistZ = ( bbGeo [2].z - bbGeo[1].z ) * fScaleZ ) ) -- check if center is checked if ( cbCenter.checked ) then ( fStartX = -( spCountX.value - 1 ) * fDistX / 2.0 fStartY = -( spCountY.value - 1 ) * fDistY / 2.0 fStartZ = -( spCountZ.value - 1 ) * fDistZ / 2.0 ) -- save old root transform local mRoot = matrix3 1 if pbRoot.object != undefined then ( mRoot = pbRoot.object.transform for obj in pbRoot.object.children do ( delete obj ) delete pbRoot.object pbRoot.object = undefined ) -- create root helper objHlp = Point cross: true box: true wirecolor: yellow transform: mRoot size: 10 name: ( uniqueName "Master_Array_Node" ) addModifier objHlp ( EmptyModifier () ) objHlp.modifiers[#Attribute_Holder].name = "ArrayControl" -- add custom attribute custAttributes.add objHlp.modifiers[#ArrayControl] CADefArrayControl objHlp.modifiers[#ArrayControl].caArrayControl.fCountX = spCountX.value objHlp.modifiers[#ArrayControl].caArrayControl.fCountY = spCountY.value objHlp.modifiers[#ArrayControl].caArrayControl.fCountZ = spCountZ.value objHlp.modifiers[#ArrayControl].caArrayControl.fDistX = fDistX objHlp.modifiers[#ArrayControl].caArrayControl.fDistY = fDistY objHlp.modifiers[#ArrayControl].caArrayControl.fDistZ = fDistZ objHlp.modifiers[#ArrayControl].caArrayControl.fScaleX = fScaleX objHlp.modifiers[#ArrayControl].caArrayControl.fScaleY = fScaleY objHlp.modifiers[#ArrayControl].caArrayControl.fScaleZ = fScaleZ objHlp.modifiers[#ArrayControl].caArrayControl.bCenter = cbCenter.checked objHlp.modifiers[#ArrayControl].caArrayControl.bAutoDistance = cbAutoDistance.checked -- parent array elements to helper pbRoot.object = objHlp if pbGeoNode.object != undefined then ( for x = 0 to spCountX.value - 1 do ( for y = 0 to spCountY.value - 1 do ( for z = 0 to spCountZ.value - 1 do ( local obj = instance pbGeoNode.object wirecolor: ( color 100 100 100 ) obj.transform = matrix3 1 obj.pos = [ fStartX + x * fDistX, fStartY + y * fDistY, fStartZ + z * fDistZ ] -- apply rotation rot = eulerangles spRotateX.value 0 0 rotate obj rot rot = eulerangles 0 spRotateY.value 0 rotate obj rot rot = eulerangles 0 0 spRotateZ.value rotate obj rot obj.scale = [ fScaleX, fScaleY, fScaleZ ] obj.transform *= mRoot obj.parent = objHlp ) ) ) ) ) on pbGeoNode picked objRoot do ( doUpdate() ) on pbRoot picked objRoot do ( -- try to read values from helper spCountX.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fCountX spCountY.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fCountY spCountZ.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fCountZ spDistX.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fDistX spDistY.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fDistY spDistZ.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fDistZ spScaleX.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fScaleX spScaleY.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fScaleY spScaleZ.value = pbRoot.object.modifiers[#ArrayControl].caArrayControl.fScaleZ cbCenter.checked = pbRoot.object.modifiers[#ArrayControl].caArrayControl.bCenter cbAutoDistance.checked = pbRoot.object.modifiers[#ArrayControl].caArrayControl.bAutoDistance ) on btnCreate pressed do ( doUpdate() ) on spCountX changed val do ( doUpdate() ) on spCountY changed val do ( doUpdate() ) on spCountZ changed val do ( doUpdate() ) on spDistX changed val do ( doUpdate() ) on spDistY changed val do ( doUpdate() ) on spDistZ changed val do ( doUpdate() ) on spScaleX changed val do ( doUpdate() ) on spScaleY changed val do ( doUpdate() ) on spScaleZ changed val do ( doUpdate() ) on spRotateX changed val do ( doUpdate() ) on spRotateY changed val do ( doUpdate() ) on spRotateZ changed val do ( doUpdate() ) on cbCenter changed val do ( doUpdate() ) on cbAutoDistance changed val do ( if val == true then ( spDistX.enabled = false spDistY.enabled = false spDistZ.enabled = false ) else ( spDistX.enabled = true spDistY.enabled = true spDistZ.enabled = true ) doUpdate() ) ) Rollout roAbout "About" ( label lblAbout00 "relief7 Tools" align: #left offset: [ 22, 0 ] label lblAbout01 "Version:" align: #left offset: [ 22, 0 ] label lblAbout02 "Author: Markus Boos" align: #left offset: [ 22, 0 ] label lblAbout03 "(c) 2007-2010 project|gemini" align: #left offset: [ 22, 0 ] label lblAbout04 "All rights reserved." align: #left offset: [ 22, 0 ] label lblAbout05 "" align: #left offset: [ 22, 0 ] --label lblAbout06 \"relief7@projectgemini.net\" align: #left offset: [ 22, -10 ] hyperLink lblAbout06 "relief7@projectgemini.net" color: white address: "mailto:relief7@projectgemini.net" align: #left offset: [ 22, -10 ] hyperLink lblAbout07 "http://www.projectgemini.net" color: white address: "http://www.projectgemini.net" align: #left offset: [ 22, -5 ] on roAbout open do ( lblAbout00.text = ( "Procedural Array" ) lblAbout01.text = ( "Version: 0.01" ) ) ) -- --------------------------------------------------- -- ROLLOUT FLOATER -- --------------------------------------------------- -- try closing the dialog if it already exists try ( closeRolloutFloater rofTool ) catch ( ) -- create rollout floater rofTool = newRolloutFloater "Procedural Array 0.01" 220 266 -- add rollouts addRollout roProceduralArray rofTool addRollout roAbout rofTool rolledUp:true )