Notifications
Clear all

[Closed] CgTalk Maxscript Challenge 020: "Build a Better Tool"

Well, here’s my first challenge entry ever. So i’m likely to overdo it a bit. this one renames an object by doubleclicking on an item in a list. I’ve prefilled the list with dutch construction-elements. you also get a wirecolor (optionally).
A cool thing i learned is using a callback to trigger the script to pop up.
Use:
Execute the script
Select a single object
Enter a questionmark in the name of the object
Script pops up
Doubleclick on a name in the script

This is easily also implemented as macro. Well here it is

--the main body
 (
 	--put an item in one of these arrays to associate a color with it
 	local redWire = #("gevel","straat")
 	local greenWire = #("gras","raam","boom")
 	local blueWire = #("kozijn","water","rand","dakrand")
 	local cyanWire = #("glas")
 	local magentaWire = #("vloer","dorpel")
 	local yellowWire = #("rollaag")
 	local blackWire = #("trasraam","plafond")
 	local whiteWire = #("Script by Klaas","stoeprand","lamp")
 	local defaultWire = #("wand","binnenwand","balkon","stoep",
 		"dak","boeibord","waterslag","hek","spijl","beschoeiing",
 		"deur")
 		
 	--the list of names, will be sorted alphabetically later on
 	local nameListArray = #()
 	local wireAssignArrays = #(redWire,greenWire,blueWire,cyanWire,magentaWire,yellowWire,blackWire,whiteWire,defaultWire)
 	for arr in wireAssignArrays do join nameListArray arr --combine the different arrays with names to a single one
 	
 	local defaultColor = color 180 180 158 --use this color if the name is put in the defaultWire array
 		
 	--associate a name with a wirecolor
 	function fn_getWireColor myName =
 	(
 		theRightColor = undefined
 		for arr in wireAssignArrays do
 		(
 			found = findItem arr myName
 			if found != 0 do
 			(
 				case arr of
 				(
 					redWire:theRightColor = red
 					greenWire:theRightColor = green
 					blueWire:theRightColor = blue
 					cyanWire:theRightColor = color 0 255 255
 					magentaWire:theRightColor = color 255 0 255
 					yellowWire:theRightColor = yellow
 					blackWire:theRightColor = black
 					whiteWire:theRightColor = white
 					defaultWire:theRightColor = defaultColor
 				)
 			)
 		)
 		theRightColor
 	)
 	
 	--initialize the listView
 	function fn_initListview lv =
 	(
 		lv.columns.add "" 16 --the color column
 		lv.columns.add "" 80 --the name column
 		lv.View = (dotNetClass "System.Windows.Forms.View").Details
 		lv.HeaderStyle = lv.Headerstyle.none --i don't want to see the columnheader
 		lv.fullRowSelect = true
 		lv.multiselect = false
 	)
 	
 	--populate the listview
 	function fn_populateListview names lv doColor =
 	(
 		lv.items.clear()
 		--sort the names alphabetically
 		sort names
 		local theItemArray = #()
 		for o in names do
 		(
 			--the first item is the color
 			theItem = dotNetObject "listViewItem"
 			theItem.text = ""
 			theItem.UseItemStyleForSubItems = false--don't use colors of the main item in the subitem
 			theItem.subItems.add o --the subItem hold the actual name
 			
 			--if the user wants to see the wires: change the color of the text of the items in the list
 			if doColor == true do
 			(
 				theColor = fn_getWireColor o
 				theItem.backColor = (dotNetClass "System.Drawing.Color").fromARGB theColor.r theColor.g theColor.b
 			)
 			append theItemArray theItem
 		)
 		lv.items.addrange theItemArray
 	)
 
 	--define the rollout
 	rollout theWhatsMyNameRollout "whatsmyname"
 	(
 		label theLabel "Doubleclick to apply" align:#center --pos:[2,2]
 		label theLabel1 "the objectname." align:#center --pos:[2,18]
 		checkBox chkDefaultWire "Use wirecolor" checked:true offset:[-11,0]
 		checkBox chkCloseOnRename "Close on rename" offset:[-11,0] checked:true
 		dotNetControl lvNameList "System.Windows.Forms.ListView" width:120 height:450 offset:[-11,0]--pos:[2,35]
 		
 		--init and populate the list when the rollout opens
 		on theWhatsMyNameRollout open do 
 		(
 			fn_initListview lvNameList
 			fn_populateListview nameListArray lvNameList (chkDefaultWire.checked)
 		)
 		
 		--reflect the use of color in the UI
 		on chkDefaultWire changed state do
 		(
 			case state of
 			(
 				true:fn_populateListview nameListArray lvNameList true
 				false:fn_populateListview nameListArray lvNameList false
 			)
 		)
 		
 		--apply name and wirecolor when an item is doubleclicked
 		on lvNameList doubleclick control eventArgs do
 		(
 			--get the name of the clicked item
 			theName =  control.selectedItems.item[0].subItems.item[1].text
 			--get the right wirecolor for a name and paint it on an object
 			if chkDefaultWire.checked == true do 
 			(
 				theColor = fn_getWireColor theName
 				for o in selection where isProperty o #Wirecolor do o.wirecolor = theColor
 			)
 			
 			--apply the doubleclicked name to every selected item
 			for o = 1 to selection.count do
 			(
 				--create a three-digit number with leading zeros
 				theNumber = o as string
 				if theNumber.count < 3 do
 				(
 					leadingZero = ""
 					for n = 1 to 3 - theNumber.count do append leadingZero "0"
 					theNumber = leadingZero + theNumber
 				)
 				--apply the name
 				selection[o].name = (theName + "_" + theNumber)
 			)
 			if chkCloseOnRename.checked == true do destroyDialog theWhatsMyNameRollout
 		)
 	)--end rollout
 )--end body
 
 
 function fn_popItUp = 
 (
 	local theParam = callbacks.notificationParam()
 	local triggerCharacter = "?"
 	triggered = findString theParam[2] triggerCharacter
 	if triggered != undefined do 
 	(
 		thePos = mouse.screenpos
 		thePos += [-200,0]
 		if thePos.x < 0 do thePos.x = 0
 		if thePos.y > 600 do thePos.y = 600
 			
 		global theWhatsMyNameRollout
 		try(destroyDialog theWhatsMyNameRollout)catch()
 		createDialog theWhatsMyNameRollout 125 535 pos:thePos modal:false
 	)
 )
 --callback mechanism
 callbacks.removeScripts id:#klaasRenamer --remove the callback
 callbacks.addScript #nodeNameSet "fn_popItUp()" id:#klaasRenamer --add the callback
 

Klaas

Here is my small contribution for this challenge.
I call it the ‘Distance tool’ or ‘Position scale tool’.


 (
	tool disTool prompt:"Distance Tool started (Alt - Change center object, Ctrl - Average center)" numPoints:2
	(
		local objs, center, objsPos, mp, centerObj
		local xRayBitArr = #{}
		
		fn getCenter ctrlMode:true =
		(
			if not ctrlMode then (
				center = centerObj.pos
			) else (
				center = [0,0,0]
				for o in objs do (
					center += o.pos
				)
				center *= 1.0 / objs.count
			)
		)
		
		fn moveObj o p1 p2 ctrlMode:true  =
		(
			v = center - objsPos[o]
			objs[o].pos = objsPos[o] - v * (p1.y - p2.y) / 100
		)
		
		fn getCenterByRay lastObj =
		(
			local mRay = mapScreenToWorldRay mouse.pos
			local lastDist = 9999999
			for o in objs where o != lastObj do (
				local newInt = intersectRay o mRay
				if newInt != undefined do (
					local newDist = distance mRay.pos newInt.pos
					if newDist < lastDist do (
						lastDist = newDist
						lastObj = o
					)
				)
			)
			lastObj
		)
		
		fn init =
		(
			objs = selection as array
			if objs.count > 1 then (
				for g = 1 to geometry.count do (
					xRayBitArr[g] = geometry[g].xRay
					geometry[g].xRay = if not geometry[g].isSelected then true else false
				)
				deselect objects
				centerObj = objs[1]
				objsPos = #()
				for o in objs do (
					append objsPos o.pos
				)
			) else (
				pushPrompt "Distance Tool stoped because less then two objects are selected."
				#stop
			)
		)
		
		fn done =
		(
			for g = 1 to geometry.count do (
			geometry[g].xRay = xRayBitArr[g]
			)
			select objs
			gc light:true
		)
		
		on start do init()
		
		on freeMove do (
			if not ctrlKey and altKey then (
				if not mouse.buttonStates[1] and not mouse.buttonStates[1] and not mouse.buttonStates[1] then (
					centerObj = getCenterByRay centerObj
					flashNodes #(centerObj)
				)
			)
		)
		
		on mousePoint n do (
			getCenter ctrlMode:ctrlKey
			mp = viewPoint
		)
		
		on mouseMove n do (
			if n == 2 then (
				for o = 1 to objs.count do ( 
					moveObj o mp viewPoint ctrlMode:ctrlKey
				)
			)
		)
		
		on mouseAbort aborted do (
			for o = 1 to objs.count do (
				objs[o].pos = objsPos[o]
			)
		)
		
		on stop do done()
	)
	
	startTool disTool
)
    
select a couple of objects and evaluate the tool.
press left mouse button, hold and drag the mouse up and down to move
the selected object closer or further away from the first selected object.
use Alt to change the center object by ray hitting before you press the left mouse button.
use Ctrl while you press and hold left mouse button to move the selected objects relative to their average position.

Wow !!! this is a nice tool, very well done :applause:

rollout rollout1 "Selection Batch Renamer" width:243 height:44
(
	editText edt1 "" pos:[7,8] width:140 height:27
	button btn1 "Rename" pos:[158,9] width:74 height:25
	on edt1 entered text do
(
	
	)
	on btn1 pressed  do
(
		for i = 1 to selection.count do

		(
		--objname = Execute (edt1 as String)

		Selection[i].name=uniquename edt1.text

		

		)
	)
)
rollout about_ro "About..." (
	label l0 "Written by Dave Wortley"
	label l1 "Renames selected objects based on" align:#left
	label l2 "selection order. Numbering will" align:#left
	label l3 "automatically start from the first" align:#left
	label l35 "free incrimental." align:#left

)


Renamer = newRolloutFloater "Renamer v 1.2" 250 95
addRollout rollout1 Renamer rolledUp:false
addRollout about_ro Renamer rolledUp:true

Simplicity and speed, key Functionality, the Renaming tool in max is slow, this is quick, doesn’t do anything fancy but works much better.

rollout QRender_rollout "Draft Renderer" width:150 height:20
(
	--button btn_store "Store" pos: [5,5] width:40 height:17 enabled:false
	button btn_Render "Render" pos:[5,5] width:70 height:17 enabled:true
	editText edt1 "" pos:[75,4] width:60 height:17 text:"500"
	--button btn_restore "Restore" pos:[155,4] width:56 height:17 enabled:false
	

	fn store = 
	(
		renderSceneDialog.close()
		--renderers.current = RendererClass.classes[4]()
		global i = renderers.current
		global m = i.gi_on
		global a = i.imagesampler_type
		global b = i.filter_on
		global c = i.gi_primary_type
		global d = i.gi_irradmap_preset
		global l = i.gi_irradmap_subdivs
		global f = i.gi_irradmap_showCalcPhase
		global g = i.gi_secondary_type
		global h = i.lightcache_subdivs
		global j = i.lightcache_sampleSize
		global k = i.lightcache_showCalcPhase
		global n = i.qmc_earlyTermination_threshold
		global p = i.system_frameStamp_on
		
		--btn_store.enabled = false
		--btn_render.enabled = true
			

	)
	
	fn restore =
	(
			i = renderers.current
		i.gi_on = m
		i.imagesampler_type = a
		i.filter_on = b
		i.gi_primary_type = c
		i.gi_irradmap_preset = d
		i.gi_irradmap_subdivs = l
		i.gi_irradmap_showCalcPhase = f
		i.gi_secondary_type = g
		i.lightcache_subdivs = h
		i.lightcache_sampleSize = j
		i.lightcache_showCalcPhase = k
		i.qmc_earlyTermination_threshold = n
		i.system_frameStamp_on = false
		
		--btn_store.enabled = true
		--btn_render.enabled = false
		--btn_restore.enabled = false
		--renderSceneDialog.open()
		gc()
	)
	
	on btn_Render pressed  do
	(
		store()
		renderSceneDialog.cancel()
		renderSceneDialog.close()
		

		
		i = renderers.current
		
		

		--set
		i.gi_on = true
		i.imagesampler_type = 0
		i.filter_on = false
		i.gi_primary_type = 0
		i.gi_irradmap_preset = 3
		i.gi_irradmap_subdivs = 30
		i.gi_irradmap_showCalcPhase = true
		i.gi_secondary_type = 3
		i.lightcache_subdivs = 100
		i.lightcache_sampleSize = 0.01
		i.lightcache_showCalcPhase = 1
		i.qmc_earlyTermination_threshold = 1.0
		--to stop errors
		i.options_defaultLights = false
		i.system_frameStamp_on = true
		i.system_frameStamp_string = "file: %filename | render time: %rendertime | %date"
		RA = getRendImageAspect()
		Rw = edt1.text as integer
		Rh = Rw / RA


		render outputwidth: Rw outputheight: Rh vfb: true progressbar: true --renderType: #region
		--btn_restore.enabled = true
		restore()
		
	)
	on edt1 entered text do
	(
		Rw = edt1.text
	)
	on btn_restore pressed  do
	(

	
	)
)

Vray_Quick_render = newRolloutFloater "Vray Quick Render" 160 60
addRollout qrender_rollout Vray_quick_render rolledUp:false

Vray Quick Render

Quickly adjust Vray Render settings to low GI settings, antialiasing off, sampling low etc. Then restores settings after render. Ability to specify render size as well.

Great stuff guys, really good to see the brains ticking into new uses for max tools.

TzMtN: Nice catch on the offset fix. I think I was originally moving the objects after the cloning, then got lazy and pasted it into the offset. I like the tool interaction of your script, but I couldn’t get the alt and ctrl keys to work for the objects. Perhaps i was doing it wrong!

Dave: Nice and succinct! I can’t test it unfortunately (no Vray), but I love clean readable code! Good one on the renamer too… you’re right about the built in tool. You’d think with all the other speed improvements in max a simple object namer would be faster.

martroyx: Really clean interface! I like! I like the fact that you’ve made the randomness easy to understand and organise.

grabjacket: Very cool idea! I’d love to see it implemented with a double click on the object, but the concept of a predefined tagger is great!

I have a couple of tools i created at work so i know it’s not from scratch but i thought this would still be a good place to share them.

First one:

Spline Aligner
Draw a spline and create a bunch of objects.
Select the objects.
Run the script to create the dialog.
pick the Spline and the selected objects should be spaced evenly along the spline
play with the other options to re-position the objects along the spline
NOTE: if you check the ‘Follow’ checkbox, the objects will become oriented to the spline but they will not be reset to their original rotations if you uncheck it. Maybe something for the future…

(
   	global splineAligner
   	
   	try(destroyDialog splineAligner)catch()
   	
   	local dWidth = 250
   	local al = #Center
   	local os = [3,0]
   	
   	local sel
   	
   	fn splineFilter s = 
   	(
   		if classof s == SplineShape OR s.category == #Splines then true else false
   	)
   	
   	rollout splineAligner "Spline Aligner"
   	(
   		Group "Alignment"
   		(
   		pickButton pic_spline "Pick Spline Object" align:al across:2 width:(dWidth/2 - 8) \
   			offset:-os autoDisplay:true filter:splineFilter
   		button btn_align "Align" align:al width:(dWidth/2 - 10) offset:os enabled:false
   		radioButtons rdo_side "" labels:#("Left","Right","Center") align:#Right offset:[-10,3]
   		label lab_rdo "Align to:" align:#Left offset:[20,-20]
   		spinner spn_space "Spacing %: " fieldwidth:50 type:#float align:#Right across:2 range:[0,100,100] offset:[0,2]
   		spinner spn_offset "Offset: " fieldwidth:50 type:#float align:#Right offset:((os*2)+[0,2]) range:[-100,100,0] 
   		checkbox chk_orient "Follow" across:2 checked:false 
   		checkbox chk_flip "Reverse Order"
   		radioButtons rdo_orientAxis "" labels:#("X", "Y", "Z") align:#Left across:2 
   		checkbox chk_flipAxis "Flip" 
   		)
   		
   		fn orientTo o tangent orientAxis flipAxis startDeg:0 endDeg:0 =
   		(
   			o.dir = tangent
   			case orientAxis of
   			(
   				1:
   				(
   					rotate o -90 o.transform[2]
   					rotate o -90 o.transform[1]
   					--in coordsys o rotate o -90 y_axis
   					if flipAxis then rotate o 180 o.transform[3]
   				)
   				2:
   				(
   					rotate o -90 o.transform[1]
   					if NOT flipAxis then rotate o 180 o.transform[3]
   				)
   				3:
   				(
   					if flipAxis then rotate o 180 o.transform[1]
   				)
   			)
   		)
   		
   		fn fixFraction f =
   		(
   			if f > 1.0 then 1.0
   			else if f < 0.0 then 0
   			else f
   		)
   		
   		fn alignToSpline arr s spc side flip ofs orient:false orientAxis:3 flipAxis:false startDeg:0 endDeg:0 =
   		(
   			spc /= 100.0
   			ofs /= 100.0
   			cnt = (arr.count - 1) as float
   			rotateOffset = eulerangles 0 0 0
   			if flip then
   				arr = for i = arr.count to 1 by -1 collect arr[i]
   
   			for a = 0 to cnt where isValidNode arr[a+1] do
   			(
   				case side of
   				(
   					1: -- #Left
   					(
   						frac = ((a / cnt) * spc)
   						arr[a+1].pos = interpCurve3D s 1 (fixFraction (frac + ofs))
   						if orient then
   						(
   							orientTo arr[a+1] (tangentCurve3D s 1 (fixFraction (frac + ofs))) orientAxis flipAxis
   						)
   					)
   					2: -- #Right
   					(
   						frac = ((a / cnt) * spc) + (1.0 - spc)
   						arr[a+1].pos = interpCurve3D s 1 (fixFraction (frac + ofs))
   						if orient then
   						(
   							orientTo arr[a+1] (tangentCurve3D s 1 (fixFraction (frac + ofs))) orientAxis flipAxis
   						)
   					)
   					3: -- #Center
   					(
   						frac = ((a / cnt) * spc) + ((1.0 - spc) / 2)
   						arr[a+1].pos = interpCurve3D s 1 (fixFraction (frac + ofs))
   						if orient then
   						(
   							orientTo arr[a+1] (tangentCurve3D s 1 (fixFraction (frac + ofs))) orientAxis flipAxis
   						)
   					)
   				)
   			)
   		)
   		
   		on btn_align pressed do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state chk_flip.checked spn_offset.value \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   		
   		on spn_space changed val do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object val rdo_side.state chk_flip.checked spn_offset.value \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   		
   		on spn_offset changed val do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state chk_flip.checked val \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   		
   		on rdo_side changed state do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value state chk_flip.checked spn_offset.value \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   		
   		on chk_orient changed state do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state chk_flip.checked spn_offset.value \
   					orient:state orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   		
   		on chk_flip changed state do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state state spn_offset.value \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   		
   		on chk_flipAxis changed state do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state chk_flip.checked spn_offset.value \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:state
   		)
   		
   		on rdo_orientAxis changed state do
   		(
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 AND isValidNode pic_spline.object then
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state chk_flip.checked spn_offset.value \
   					orient:chk_orient.checked orientAxis:state flipAxis:chk_flipAxis.checked
   		)
   		
   		on pic_spline picked arg do
   		(
   			btn_align.enabled = true
   			sel = for s in selection where s != pic_spline.object collect s
   			if sel.count > 0 then 
   				alignToSpline sel pic_spline.object spn_space.value rdo_side.state chk_flip.checked spn_offset.value \
   					orient:chk_orient.checked orientAxis:rdo_orientAxis.state flipAxis:chk_flipAxis.checked
   		)
   	)
   	
   	createDialog splineAligner width:dWidth
   )

EDIT: there seems to be some redundent arguments in the alignToSpline function. I guess i intended to add more functionality but never got around to it…

Second one:

Scale Objects Positions
Allows you to ‘scale’ the positions of selected objects about the selection center or average pivot position, in any combination of axes without affecting the actual scale of the objects.
TzMtN has already posted something similar but i think this one is still fairly different as you have a floating dialog to work with as needed.
Run the script select a bunch of objects and play with the spinners!

 (
 	-- Globals
 	
 	global scaleObjectPositions
 	
 	try(destroyDialog scaleObjectPositions)catch()
 	
 	-- Private Globals
 	
 	local scriptName = "Scale Object Positions"
 	local version = 1.00
 	
 	local dWidth = 175
 	local bw = (dWidth - 20) / 2.08
 	local fw = 45
 	local al = #center
 	local os = [-2,0]
 	local nowhere = [0,0,0]
 	local noMove = 100
 	
 	local ext = 99999999
 	
 	-- Private Global Functions
 	
 	fn constructRolloutName =
 	(
 		ver = version as string
 		if ver.count < 4 then ver += "0"
 		" " + scriptName + " " + ver
 	)
 	
 	-- Rollouts
 	
 rollout scaleObjectPositions (constructRolloutName())
 (
 	local sel
 	local mid
 	local vecs
 	local starts
 	local goFlag = false
 	local count
 	local allSpinners
 	
 	Group "Scale Group"
 	(
 	spinner spn_x "X:" range:[-ext,ext,100] type:#float fieldwidth:fw across:2 align:al offset:os
 	spinner spn_xy "XY:" range:[-ext,ext,100] type:#float fieldwidth:fw align:al
 	spinner spn_y "Y:" range:[-ext,ext,100] type:#float fieldwidth:fw across:2 align:al offset:os
 	spinner spn_yz "YZ:" range:[-ext,ext,100] type:#float fieldwidth:fw align:al
 	spinner spn_z "Z:" range:[-ext,ext,100] type:#float fieldwidth:fw across:2 align:al offset:os
 	spinner spn_zx "XZ:" range:[-ext,ext,100] type:#float fieldwidth:fw align:al
 	label lab_spacer "" across:2
 	spinner spn_xyz "XYZ:" range:[-ext,ext,100] type:#float fieldwidth:fw align:al offset:[-4,0]
 	
 	radiobuttons rdo_mid "Middle Point:" labels:#("Selection Center","Average Pivot Position") align:#Left offset:[0,-20]
 	
 	button btn_undo "Undo" width:bw align:al across:2
 	button btn_redo "Redo" width:bw align:al
 	)
 	
 	-- Local Rollout Functions
 	
 	fn resetMove =
 	(
 		for i = 1 to count do sel[i].pos = starts[i]
 	)-- end resetMove function
 	
 	fn sortLinkedObjs arr = -- needed for when the spinners are zeroed out
 	(
 		--collect all unlinked objects
 		tempArr = for a in arr where a.children.count == 0 AND a.parent == undefined collect a
 		--collect all objects with parents but no children and join with the array 'tempArr'
 		join tempArr (for a in arr where a.children.count == 0 AND a.parent != undefined collect a)
 		--go through all items in tempArr and append their parents to tempArr. Should work recursively.
 		for t in tempArr where ((findItem arr t.parent) > 0) do append tempArr t.parent
 		--remove duplicate objects
 		for i = tempArr.count to 1 by -1 where (index = findItem tempArr tempArr[i]) > 0 AND
 			index != i do deleteItem tempArr index
 		--reverse the array order
 		sortedArray = for i = tempArr.count to 1 by -1 collect tempArr[i]
 		
 		sortedArray
 	)-- end sortLinkedObjs function
 	
 	fn getCenterPoint =
 	(
 		if rdo_mid.state == 1 then selection.center
 		else
 		(
 			mid = [0,0,0]
 			for s in selection do mid += s.pos
 			mid / selection.count
 		)
 	)-- end getCenterPoint function
 	
 	fn initValues =
 	(
 		sel = sortLinkedObjs (selection as array)
 		mid = getCenterPoint()
 		vecs = for s in sel collect /*normalize*/ (s.pos - mid)
 		starts = for s in sel collect s.pos
 		goFlag = true
 		count = sel.count
 		flagForeground sel true
 	)-- end initValues function
 	
 	fn scaleMoveObjects factor =
 	(
 		factor /= 100.0
 		for i = 1 to count do sel[i].pos = vecs[i] * factor + mid
 	)-- end scaleMoveObjects function
 	
 	mapped fn resetSpinners s = s.value = 100 -- end scaleMoveObjects function
 	
 	fn btn_up rcFlag =
 	(
 		if goFlag AND rcFlag then max undo
 		goFlag = false
 		resetSpinners allSpinners
 	)-- end btn_up function
 	
 	fn btn_Down = 
 	(
 		if selection.count > 1 then
 		(
 			-- Fake the undo buffer by 'moving' to the same positions
 			undo "Scale Object Positions" on move selection nowhere
 			initValues()
 		)
 		else goFlag = false
 	)-- end btn_Down function
 	
 	-- Rollout Event Handlers
 	
 	on spn_x changed val do if goFlag do scaleMoveObjects [val,noMove,noMove]
 	on spn_xy changed val do if goFlag do scaleMoveObjects [val,val,noMove]
 	on spn_y changed val do if goFlag do scaleMoveObjects [noMove,val,noMove]
 	on spn_yz changed val do if goFlag do scaleMoveObjects [noMove,val,val]
 	on spn_z changed val do if goFlag do scaleMoveObjects [noMove,noMove,val]
 	on spn_zx changed val do if goFlag do scaleMoveObjects [val,noMove,val]
 	on spn_xyz changed val do if goFlag do scaleMoveObjects [val,val,val]
 	
 	on spn_x buttondown do btn_Down()
 	on spn_xy buttondown do btn_Down()
 	on spn_y buttondown do btn_Down()
 	on spn_yz buttondown do btn_Down()
 	on spn_z buttondown do btn_Down()
 	on spn_zx buttondown do btn_Down()
 	on spn_xyz buttondown do btn_Down()
 	
 	on spn_x buttonup rightClickFlag do btn_up rightClickFlag
 	on spn_xy buttonup rightClickFlag do btn_up rightClickFlag
 	on spn_y buttonup rightClickFlag do btn_up rightClickFlag
 	on spn_yz buttonup rightClickFlag do btn_up rightClickFlag
 	on spn_z buttonup rightClickFlag do btn_up rightClickFlag
 	on spn_zx buttonup rightClickFlag do btn_up rightClickFlag
 	on spn_xyz buttonup rightClickFlag do btn_up rightClickFlag
 	
 	on btn_undo pressed do
 	(
 		max undo
 	)
 	on btn_redo pressed do
 	(
 		max redo
 	)
 	
 	on scaleObjectPositions open do
 	(
 		allSpinners = for c in scaleObjectPositions.controls where classof c == SpinnerControl collect c
 	)
 )-- end scaleObjectPositions rollout definition
 	
 	createDialog scaleObjectPositions width:dWidth style:#(#style_toolwindow, #style_border, #style_sysmenu)
 )
Page 2 / 2