Link to home
Start Free TrialLog in
Avatar of juhoru
juhoruFlag for Finland

asked on

MaxScript not working (bake)

Hello.
This is related to this question (https://www.experts-exchange.com/questions/24529221/How-to-convert-to-keyframe-animation-in-3D-studio-max.html?sfQueryTermInfo=1+anim+bake).

John Burnett's Bake found in http://www.scriptspot.com, seems like exactly what we were looking for, but it doesn't seem to want to install.

I know a little about MaxScript, but it seems not enough.
It needs 3 other scripts to work (Avguard Extensions, BFDToolsCore  and jbFunctions) and I think I installed them all correctly, except avguard, but someone in scriptspot told me that its already implimented in Max 2008 and up. But If I try to make a button out of the bake script and push it, I get an error message saying "--No ""getPixels"" function for undefined".
Also, now Max is giving an error message saying "--Cannot assign to read-only variable: toUpper" every time it starts.

I'm running Windows XP 32bit and 3ds Max 2009 32bit.
error-01.jpg
error-02.jpg
ASKER CERTIFIED SOLUTION
Avatar of bham3dman
bham3dman

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of bham3dman
bham3dman

Final jbFunctions.ms script

Also, I forgot to mention above - you will need to also replace every instance of "toLower" to "toLower2". It doesn't matter if the text is "ToUpper" or "toUpper" - add a 2 to every find.


---------------------------------------------------------------------
-- jbFunctions.ms
--
-- copyright 1999-2001            John Burnett, foo@footools.com
---------------------------------------------------------------------
-- INSTALLATION:
-- Simply place this file in one of your Max plugin paths (i.e.
-- "c:\3dsmax4\plugins\") and restart Max.
--
-- This script only contains support functions for other scripts,
-- and does nothing useful in and of itself.
---------------------------------------------------------------------
-- REQUIRED PLUGINS:
-- avg_dlx.dlx
---------------------------------------------------------------------
-- DISCLAIMER AND DISTRIBUTION:
--
-- This script is provided as FREEWARE, and cannot be sold. This
-- script cannot be bundled with any commercial package without
-- express written permission from the author.  You MAY distribute
-- this script provided that it is complete with all files in the
-- original archive, and no profit is made from the distribution.
--
-- You MAY take portions of this script and include them in your
-- own script.  Credit to the original author would be appreciated.
--
-- While this script has been used heavily in a production environment,
-- and should be reasonably bug free, there may (read: ARE) still be
-- bugs present.  By using this product, you agree to exempt the author
-- from any responsibility for the damages your computer or your data
-- may incur through the use of this script.
--
-- This is an unsupported product so use it at your own risk!
--
-- Lastly, suggestions and bug reports are welcome.  Please use the
-- contact info above.
---------------------------------------------------------------------
 
---------------------------------------------------------------------
-- All available functions are declared here at the top.
---------------------------------------------------------------------
 
fn jbFunctionsCurrentVersion = (
	-- slight workaround for R4.0 bug
	(18 - 0)
)
 
-- Check that required extensions are installed.  Takes an array of arrays, where
-- each array has two elements: the extension name, and the required version.
-- Returns true if all required extensions are installed, otherwise false.
fn jbFunctionsVersionCheck vers =
(
--#("3dsmax 3.1",0) -- force 3.1, exclude 4.0
--#("jbFunctions",)
--#("jbLib",)
--#("avg_dlx",2.09)
--#("ctrlLib",2.2)
--#("mouseTrack",0)
--#("bind",0)
--#("ish_MorpherCtrl",0)
	local resAr = for ver in vers collect
	(
		case ver[1] of (
			"3dsmax 3.1": ( try (((MaxVersion())[1] == 3100)) catch (FALSE) )
			"jbFunctions": ( try (ver[2] <= jbFunctionsCurrentVersion()) catch (FALSE) )
			"jbLib": ( try (ver[2] <= jbLibVersion) catch (FALSE) )
			"avg_dlx": ( try (((MaxVersion())[1] >= 4000) OR ver[2] <= (avguard_dlx_ver as float)) catch (FALSE) )
			"ctrlLib": ( try (((MaxVersion())[1] >= 4000) OR ver[2] <= ctrllib.version) catch (FALSE) )
			"mouseTrack": ( try (mouseTrack != undefined) catch (FALSE) )
			"bind": ( try (bindOps != undefined) catch (FALSE) )
			"ish_MorpherCtrl": ( try (IsValidMorpherMod != undefined) catch (FALSE) )
			default: ( FALSE )
		)
	)
 
	local failed = FALSE
	local str = "The following required extensions are not installed or are out of date:\n\n"
	for i in 1 to resAr.count where NOT resAr[i] do
	(
		failed = TRUE
		str += (vers[i][1] + "\n")
	)
 
	if failed then (
		str += "\nYou can get the latest versions at http://www.footools.com/.\n\nWould you like to connect there now?"
		if (QueryBox str title:"Error") then ( try (ShellLaunch "http://www.footools.com/" "") catch () )
		return FALSE
	)
 
	TRUE
)
 
-- Array Declarations -----------------------------------------------
global \
BitFirstSet,
BitHasSet,
BitNumberSet,
DeleteItems,
GetArrayValue,
GetNormArrayValue,
InsertAfter,
ItemFound,
MaxValueIndex,
MinValueIndex,
ReverseArray,
ScaleArray,
ScrambleArray,
TrimDuplicates
 
-- Atmospherics And Effects Declarations ----------------------------
global \
PopAtmosphericNames,
PopEffectNames,
PopSpecialFXNames,
PushAtmosphericNames,
PushEffectNames,
PushSpecialFXNames
 
-- BitmapAndColor Declarations --------------------------------------
global \
ClampColor,
DrawCrosshair,
GetCroppedBitmap,
GetHue,
HSVtoRGB,
InvertColor,
Noise3bmp,
Noise4bmp,
OpenBitmapNoGamma
 
-- BFDtools Declarations --------------------------------------------
global BFDtool, _BFDman, BFDman
 
-- Controller Declarations ------------------------------------------
global \
GetAnimatedSubAnims,
GetControllers
 
-- File Declarations ------------------------------------------------
global \
FileExists,
FixFilename,
GetIFLfiles,
GetParentDir,
GetSequenceFilenameBase,
GetSequences,
GetSubDirs,
IsBitmapFile,
IsFileCreateNewer,
IsFileModNewer,
IsFileType,
IsSequenceFile,
IsUNCpath,
IsValidFilename,
MakeIFL,
SortINISection
 
-- FuncAlias Declarations -------------------------------------------
global \
co,
sco,
gpn,
sp,
spm,
spv,
spmv
 
-- Hierarchy Declarations -------------------------------------------
global \
CopyHierarchy,
DupHierarchy,
GetChildren,
GetDepth,
GetHierarchy,
GetHierarchyRoot,
GetParentChain,
InstanceHierarchy,
ReferenceHierarchy,
SafeDelete
 
-- Material Declarations --------------------------------------------
global \
DeleteUnusedSubMaterials,
GetBitmapTextures,
GetObjectMaterials,
GetObjectStandardMaterials,
GetStandardSubMaterials,
GetSubMaterials,
GetSubTextures,
GetUsedMatIDs,
ReduceSubMaterials,
RemapMeshMaterials
 
-- Math Declarations ------------------------------------------------
global \
Bias,
ClampPnt2,
fMin,
fMax,
fClamp,
Gain,
GetClosestPoints,
Round,
Smoothstep
 
-- Mesh Declarations ------------------------------------------------
global \
BuildQuad,
CleanIsoVerts,
DetachMeshFaces,
ExplodeMeshElements,
GetAllMeshElements,
GetAllPolygons,
GetAllPolygonsProgress,
GetClosestVert,
GetFaceAsArray,
GetFacesByNormal,
GetMeshElement,
GetVertFaceCache,
MeshOKtoModify
 
-- Miscellaneous Declarations ---------------------------------------
global \
GetClassTree,
PrintClassTree
 
-- Modifier Declarations --------------------------------------------
global \
GetAllModifiers,
GetModifiersOfClass
 
-- Object Declarations ----------------------------------------------
global \
CopyNodeProps,
DupNodeProps,
GetBipedObjects,
GetInstances,
InstanceNodeProps,
IsBipedObject,
IsInstance,
ObjectExists,
SelectAndShow,
TrimInvalidObjects
 
-- Spline Declarations ----------------------------------------------
global \
AverageShapes,
BuildSpline,
CreateSplineFromArray,
DetachSpline,
ExplodeShape,
GetRequiredSteps,
GetShapeRoots,
KnotLengthParam,
ShapeToRibbon,
ShapeToString,
SmoothShape
 
-- String Declarations ----------------------------------------------
global \
Capitalize,
ColumnFormat,
DateAsPoint3,
DateAsSeconds,
DoubleSlash,
FindString2,
GetPadNum,
GetTag,
GetValidFilename,
IsCharInt,
PrintColor,
ReplaceTags,
SearchReplace,
SnipString,
StringCount,
ToLower2,
ToUpper2,
Trunc
 
-- System Declarations ----------------------------------------------
global \
GetDriveFreeSpace,
GetProcessorName,
GetSysInfo,
GetSysInfoFromFile,
GetSysInfoPhysicalMemory,
GetSysInfoProcessors,
MakeSysInfoFile,
PrintMachineStats,
ServerInfo,
ServerStats
 
-- Time Declarations ------------------------------------------------
global \
GetCurrentMilitaryTime,
GetFormattedTime,
IsDateNewer,
LapTimer,
TimeIt
 
-- UI Declarations --------------------------------------------------
global \
IntersectPickPoint,
PopCommandPanelTaskMode,
PushCommandPanelTaskMode,
ValidListboxSel
 
 
---------------------------------------------------------------------
---------------------------------------------------------------------
---------------------------------------------------------------------
-- All functions are defined below.
---------------------------------------------------------------------
---------------------------------------------------------------------
---------------------------------------------------------------------
 
 
---------------------------------------------------------------------
-- Array Definitions
---------------------------------------------------------------------
 
fn ScrambleArray anArray passes:1 lowerBound:1 upperBound:-1 seedVal:1 = (
	-- make the bounds floats (workaround for silly random() bug)
	local lBound = lowerBound as float
	local uBound
	if (upperBound == -1) then (
		uBound = anArray.count as float
	) else (
		uBound = upperBound as float
	)
 
	seed seedVal
 
	local targIdx
	for i in 1 to passes do (
		for srcIdx in lBound to uBound do (
			do (targIdx = random lBound uBound) while (targIdx == srcIdx)
			swap anArray[srcIdx] anArray[targIdx]
		)
	)
 
	anArray
)
 
fn InsertAfter anArray item idx = (
	if (idx >= 1) AND (idx <= anArray.count) then (
		append anArray item
		for i in anArray.count to (idx+2) by -1 do (
			swap anArray[i] anArray[i-1]
		)
		true
	)
	false
)
 
fn ReverseArray anArray = (
	for i in 1 to (anArray.count/2) do (
		swap anArray[i] anArray[(anArray.count-(i-1))]
	)
 
	anArray
)
 
fn TrimDuplicates anArray = (
	local idx
	for i in anArray.count to 1 by -1 do (
		idx = findItem anArray anArray[i]
		if (idx != 0) AND (idx != i) do deleteItem anArray i
	)
 
	anArray
)
 
fn ItemFound anArray anItem = (
	((findItem anArray anItem) != 0)
)
 
fn DeleteItems anArray ba = (
	if ba.count != anArray.count then (
		false
	) else (
		for i in ba.count to 1 do (
			deleteItem anArray i
		)
		true
	)
)
 
fn GetArrayValue anArray f = (
	if (f <= 1) then (
		anArray[1]
	) else if (f >= anArray.count) then (
		anArray[anArray.count]
	) else (
		local perc = f - (f as integer)
		if (0.0 == perc) then (
			anArray[f]
		) else (
			local a = anArray[f]
			local b = anArray[f+1]
			((1-perc)*a) + (perc*b)
		)
	)
)
 
fn GetNormArrayValue anArray nf = (
	GetArrayValue anArray (nf * (anArray.count-1) + 1)
)
 
fn ScaleArray anArray newCount = (
	local newArray = #()
	newArray.count = newCount
 
	for i in 1 to newCount do (
		newArray[i] = GetNormArrayValue anArray ((i-1.0)/(newCount-1))
	)
 
	newArray
)
 
fn MinValueIndex anArray = (
	local minVal = anArray[1]
	local idx = 1
	for i in 2 to anArray.count do (
		if anArray[i] < minVal then (
			minVal = anArray[i]
			idx = i
		)
	)
	idx
)
 
fn MaxValueIndex anArray = (
	local maxVal = anArray[1]
	local idx = 1
	for i in 2 to anArray.count do (
		if anArray[i] > maxVal then (
			maxVal = anArray[i]
			idx = i
		)
	)
	idx
)
 
fn BitNumberSet bitAr = (
	local cnt = 0
	for i in bitAr do cnt += 1
	cnt
)
 
fn BitFirstSet bitAr = (
	for i in bitAr do return i
	0
)
 
fn BitHasSet bitar = (
	for i in 1 to bitar.count do if bitar[i] do return TRUE
	FALSE
)
 
---------------------------------------------------------------------
-- Atmospherics And Effects Definitions
---------------------------------------------------------------------
 
-- Don't use the following two functions directly -------------------
fn _PushSpecialFXNames GetSFX sfxCount sfxStack =
(
	try (
		local nameArray = for i in 1 to sfxCount collect (GetSFX i).name
		Append sfxStack nameArray
 
		TRUE
	) catch ( FALSE )
)
 
fn _PopSpecialFXNames GetSFX sfxCount sfxStack =
(
	try (
		local curIdx = sfxStack.count
		if (curIdx == 0) then return FALSE
 
		local savedCnt = sfxStack[curIdx].count
		local maxCnt = if (sfxCount > savedCnt) then savedCnt else sfxCount
 
		for	i in 1 to maxCnt do (
			(GetSFX i).name = sfxStack[curIdx][i]
		)
		DeleteItem sfxStack curIdx
 
		TRUE
	) catch ( FALSE )
)
-- Don't use the above two functions directly -----------------------
 
global jbSavedAtmosphericNameStack = #()
global jbSavedEffectsNameStack = #()
 
fn SaveAtmosphericNames =
(
	_PushSpecialFXNames GetAtmospheric numAtmospherics jbSavedAtmosphericNameStack
)
fn RestoreAtmosphericNames =
(
	_PopSpecialFXNames GetAtmospheric numAtmospherics jbSavedAtmosphericNameStack
)
 
fn SaveEffectNames =
(
	_PushSpecialFXNames GetEffect numEffects jbSavedEffectsNameStack
)
fn RestoreEffectNames =
(
	_PopSpecialFXNames GetEffect numEffects jbSavedEffectsNameStack
)
 
fn SaveSpecialFXNames =
(
	(SaveAtmosphericNames() AND SaveEffectNames())
)
fn RestoreSpecialFXNames =
(
	(RestoreAtmosphericNames() AND RestoreEffectNames())
)
 
---------------------------------------------------------------------
-- BitmapAndColor Definitions
---------------------------------------------------------------------
 
fn Noise3bmp resX resY scale =
(
	local bmp = Bitmap resX resY
	local s = 1./scale
	local dx = s / resX
	local dy = s / resY
	local row = 0
	local buf
	for y in dy to s by dy do
	(
		buf = for x in dx to s by dx collect (
			white * (((Noise3 [x,y,0]) + 1.0) * 0.5)
		)
 
		SetPixels bmp [0,row] buf
		row += 1
	)
 
	bmp
)
 
fn Noise4bmp resX resY scale phase =
(
	local bmp = Bitmap resX resY
	local s = 1./scale
	local dx = s / resX
	local dy = s / resY
	local row = 0
	local buf
	for y in dy to s by dy do
	(
		buf = for x in dx to s by dx collect (
			white * (((Noise4 [x,y,0] phase) + 1.0) * 0.5)
		)
 
		SetPixels bmp [0,row] buf
		row += 1
	)
 
	bmp
)
 
mapped fn ClampColor col = (
	col.r = if col.r > 255 then 255 else if col.r < 0 then 0 else col.r
	col.g = if col.g > 255 then 255 else if col.g < 0 then 0 else col.g
	col.b = if col.b > 255 then 255 else if col.b < 0 then 0 else col.b
	col.a = if col.a > 255 then 255 else if col.a < 0 then 0 else col.a
 
	col
)
 
-- backwards compatibility
ClampCol = ClampColor
 
fn InvertColor col = (
	color (abs (col.r-255)) (abs (col.g-255)) (abs (col.b-255))
)
 
fn OpenBitmapNoGamma str = (
	local bmp, bmpNoGamma, row
	try (
		bmp = openBitmap str
		bmpNoGamma = bitmap bmp.width bmp.height	\
							filename:bmp.filename	\
							gamma:1.0				\
							pixelAspect:bmp.aspect
		for y = 0 to (bmp.height-1) do (
			row = getPixels bmp [0,y] bmp.width
			setPixels bmpNoGamma [0,y] row
		)
		return bmpNoGamma
	) catch (
		return undefined
	)
)
 
fn GetHue hue = (
	hue = if (hue < 0) then 0. else if (hue > 1) then 1. else (hue as float)
	local col = case of (
		(hue <= 0.166667): (
			[1, (hue/0.166667), 0]
		)
		(hue <= 0.333333): (
			[(1.-((hue-0.166667)/0.166667)), 1, 0]
		)
		(hue <= 0.5): (
			[0, 1, ((hue-0.333333)/0.166667)]
		)
		(hue <= 0.666667): (
			[0, (1.-((hue-0.5)/0.166667)), 1]
		)
		(hue <= 0.833333): (
			[((hue-0.666667)/0.166667), 0, 1]
		)
		(hue <= (1.)): (
			[1, 0, (1.-((hue-0.833333)/0.166667))]
		)
	)
	col *= 255
	col = clampColor (col as color)
	col.alpha = 255
 
	col
)
 
fn HSVtoRGB hue sat val = (
	local col = getHue hue
	col *= val
	col = (col * (1.-sat) + (col.value * sat))
	col.alpha = 255
 
	col
)
 
fn DrawCrosshair bmp nCrossPos crossSize:10 = (
 
	crossPos = [(bmp.width-1),(bmp.height-1)] * nCrossPos
 
	local halfCross = crossSize/2
	hLine = [crossPos.x-halfCross,crossPos.x+halfCross]
	vLine = [crossPos.y-halfCross,crossPos.y+halfCross]
	hLine = ClampPnt2 hLine 0 (bmp.width-1)
	vLine = ClampPnt2 vLine 0 (bmp.height-1)
 
	local crossColor
	-- draw top horizontal line
	if (vLine.x >= 0) AND (vLine.x < bmp.height) then (
		for x in hLine.x to hLine.y do (
			crossColor = InvertColor (getPixels bmp [x,vLine.x] 1)[1]
			setPixels bmp [x,vLine.x] #(crossColor)
		)
	)
	-- draw middle horizontal line
	if (crossPos.y >= 0) AND (crossPos.y < bmp.height) then (
		for x in hLine.x to hLine.y do (
			crossColor = InvertColor (getPixels bmp [x,crossPos.y] 1)[1]
			setPixels bmp [x,crossPos.y] #(crossColor)
		)
	)
	-- draw lower horizontal line
	if (vLine.y >= 0) AND (vLine.y < bmp.height) then (
		for x in hLine.x to hLine.y do (
			crossColor = InvertColor (getPixels bmp [x,vLine.y] 1)[1]
			setPixels bmp [x,vLine.y] #(crossColor)
		)
	)
	-- draw left vertical line
	if (hLine.x >= 0) AND (hLine.x < bmp.width) then (
		for y in vLine.x to vLine.y do (
			crossColor = InvertColor (getPixels bmp [hLine.x,y] 1)[1]
			setPixels bmp [hLine.x,y] #(crossColor)
		)
	)
	-- draw middle vertical line
	if (crossPos.x >= 0) AND (crossPos.x < bmp.width) then (
		for y in vLine.x to vLine.y do (
			crossColor = InvertColor (getPixels bmp [crossPos.x,y] 1)[1]
			setPixels bmp [crossPos.x,y] #(crossColor)
		)
	)
	-- draw right vertical line
	if (hLine.y >= 0) AND (hLine.y < bmp.width) then (
		for y in vLine.x to vLine.y do (
			crossColor = InvertColor (getPixels bmp [hLine.y,y] 1)[1]
			setPixels bmp [hLine.y,y] #(crossColor)
		)
	)
 
	bmp
)
 
fn GetCroppedBitmap bmp tl br =
(
	if (tl.x >= 0 AND tl.x <= br.x AND
		tl.y >= 0 AND tl.y <= br.y AND
		br.x < bmp.width AND br.y < bmp.height) then
	(
		local w = br.x - tl.x + 1
		local h = br.y - tl.y + 1
		local newBmp = bitmap w h
		local thisY = 0
		for y in tl.y to br.y do
		(
			local row = GetPixels bmp [tl.x, y] w
			SetPixels newBmp [0,thisY] row
			thisY += 1
		)
		return newBmp
	) else (
		return undefined
	)
)
 
---------------------------------------------------------------------
-- BFDtools Definitions
---------------------------------------------------------------------
 
------------------------------------------------------------------------------------
-- Contents:
--		struct BFDtool - A struct containing the definition of any BFDtool
--		struct _BFDman - A struct containing the definition for the global
--						"BFDman" instance, which manages any BFDtools
--
-- History:
--		11.1.1999 - Created (or there abouts)
--		4.12.2000 - If version is increased, floater geometry isn't loaded from INI
------------------------------------------------------------------------------------
 
------------------------------------------------------------------------------------
--BFDtool structure
------------------------------------------------------------------------------------
--BFDtool Variables:
--	listed in the form:
--	variableName (variable type, default value)
------------------------------------------------------------------------------------
--	toolName (string, undefined)
--		This is the only variable you MUST define for each tool.  It should be a
--		unique, identifying name, since it's used to both identify the tool
--		internally, and as the tool's floater name.  It should be short	and simple,
--		and remain constant between different versions of the tool.
--		(i.e. use "particleTrack", and NOT "The Particle Tracker, v1.53")
--	author (string, "unsupplied")
--		Name of the tool's author
--	createDate (point3, [yyyy,mm,dd])
--		Date the tool was initially created, in [month,day,year] format.
--		Note: Use full four digit year (1999 instead of 99)
--	modifyDate (point3, [yyyy,mm,dd])
--		Date the tool was last modified.  Update every time you change the version.
--	version (integer, 1)
--		Version of the tool.  Increase by 1 EVERY time you change something.
--	defFloaterSize (point2, [250,250])
--		Default size of the floater
--	defFloaterPos (point2, undefined)
--		Default position of the floater.  If undefined, the floater will be centered.
--	autoLoadRolloutStates (bool, true)
--		Whether to automatically save/load the rollout states on open and close
--	autoLoadFloaterSize (bool, true)
--		Whether to automatically save/load the rollout size on open and close
--	ops (array, #())
--		Array of script variables
------------------------------------------------------------------------------------
--BFDtool Functions:
------------------------------------------------------------------------------------
--	getFloater
--		returns the tool's floater, undefined if it doesn't exist right now
--	getINIFilename
--		INI file to save info to, defaults to ".\plugcfg\BFDtools.ini"
--	setINIFilename
--		Sets the INI file to save info to
--	addRoll r rolledUp:false
--		Add rollout r to list of rollouts to be added to floater
--		r can be an array of rollouts or just a single rollout
--		The optional rolledUp parameter controls the state of the rollout
--		when it's added.  It can be a single boolean or an array as well.
--	delRoll i
--		Remove rollout at index i from list of rollouts
--		If i == 0, the all rollouts are removed
--	getRoll i
--		Returns the i'th rollout
--	numRolls
--		Returns the number of current rollouts
--	openTool
--		Create floater of default size, add rollouts, restore saved geometry
--	closeTool
--		Close floater, save geometry
--	saveFloaterGeom/loadFloaterGeom
--		Save/Load floater geometry to current INI file
--		This is done automatically on openTool/closeTool
--	saveRolloutStates/loadRolloutStates
--		Save/Load the rollout position and open/close states of the current
--		rollouts to INI file
--		if autoLoadRolloutStates == true then this is done automatically on
--		openTool/closeTool
--	updateUI rollIdx
--		Calls a function named "updateUI" in rollout index rollIdx
--		Passing 0 is equivalent to calling updateAllUI()
--	updateAllUI
--		Calls "updateUI" in all rollouts
--	getFloaterSize / setFloaterSize point2
--		Gets and sets the size of the floater
------------------------------------------------------------------------------------
struct BFDtool (
	-- Public
	toolName = undefined,
	author = "unsupplied",
	createDate = [0,0,0],
	modifyDate = [0,0,0],
	version = 1,
	defFloaterSize = [250,250],
	defFloaterPos = undefined,
	version = 1,
	autoLoadRolloutStates = true,
	autoLoadFloaterSize = true,
	ops = #(),
	------------------------------------------------------------------------------------------
	-- Start Private stuff, subject to change at any time.  Don't rely on this.
	------------------------------------------------------------------------------------------
	floater = undefined,
	rollouts = #(),
	rolloutRolledUp = #(),
	-- floaterClosing is used in case closeTool is called multiple times while a
	-- floater is closing (which happens if it's in the close handler of multiple rollouts
	-- ...prevents nasty recursive infinite loops.
	floaterClosing = false,
	-- Should be undefined, unless it's over-riden by a call to setINIFilename
	INIFilename = undefined,
	------------------------------------------------------------------------------------------
	-- End Private stuff
	------------------------------------------------------------------------------------------
	fn updateUI rollIdx = (
		if rollIdx == 0 then (
			for i in 1 to rollouts.count do try (rollouts[i].updateUI()) catch ()
		) else (
			if (rollIdx >= 1) AND (rollIdx <= rollouts.count) then (
				try (rollouts[rollIdx].updateUI()) catch ()
			)
		)
	),
	fn updateAllUI = (
		updateUI 0
	),
	fn getFloater = (
		return floater
	),
	fn getINIFilename = (
		if INIFilename == undefined then (
			return (GetDir #plugcfg + "\\BFDtools.ini")
		) else (
			return INIFilename
		)
	),
	fn setINIFilename str = (
		-- TODO: Should do some more checking here
		INIFilename = str
		return true
	),
	fn addRoll r rolledUp:false = (
		try (
			if toolName == undefined then return false
 
			if classOf r != array then ( r = #(r) )
			if classOf rolledUp != array then ( rolledUp = #(rolledUp) )
 
			if rolledUp.count < r.count then (
				for i in (rolledUp.count+1) to r.count do (
					rolledUp[i] = rolledUp[i-1]
				)
			)
 
			for i in 1 to r.count do (
				if (findItem rollouts r[i]) == 0 then (
					append rollouts r[i]
					append rolloutRolledUp rolledUp[i]
 
					if floater != undefined then (
						addRollout r[i] floater rolledUp:rolledUp[i]
					)
				)
			)
		) catch ( return false )
	),
	fn delRoll idx = (
		try (
			local s, e
			if (idx == 0) then (
				s = rollouts.count
				e = 1
			) else (
				s = e = idx
			)
 
			for i in s to e by -1 do (
				removeRollout rollouts[i] floater
				deleteItem rollouts i
				deleteItem rolloutRolledUp i
			)
			return true
		) catch ( return false )
	),
	fn numRolls = (
		return rollouts.count
	),
	fn getRoll i = (
		if (i >= 1) AND (i <= rollouts.count) then return rollouts[i] else return undefined
	),
	fn saveToolVersion = (
		try (
			local res = setINISetting (getINIFilename()) toolName "Version" (version as string)
			return res
		) catch ( return false )
	),
	fn loadToolVersion = (
		try (
			local v = (getINISetting (getINIFilename()) toolname "Version") as integer
			if v != 0 then return v else return undefined
		) catch ( return undefined )
	),
	fn saveFloaterGeom = (
		if	(floater == undefined) OR (classOf floater != RolloutFloater) OR
			(toolName == undefined) OR (classOf toolName != String) then return false
 
		local res = TRUE
 
		res = res AND (setINISetting (getINIFilename()) toolName "Pos" (floater.pos as string))
		if autoLoadFloaterSize then (
			res = res AND (setINISetting (getINIFilename()) toolName "Size" (floater.size as string))
		)
 
		return res
	),
	fn loadFloaterGeom = (
		if	(floater == undefined) OR (classOf floater != RolloutFloater) OR
			(toolName == undefined) OR (classOf toolName != String) then return false
 
		local res = FALSE
 
		local tmp = execute (getINISetting (getINIFilename()) toolName "Pos")
		if (classOf tmp) == point2 then ( floater.pos = tmp ) else ( res = FALSE )
		if autoLoadFloaterSize AND (version == loadToolVersion()) then (
			tmp = execute (getINISetting (getINIFilename()) toolName "Size")
			if (classOf tmp) == point2 then ( floater.size = tmp ) else ( res = FALSE )
		)
 
		return res
	),
	fn saveRolloutStates = (
		try (
			local rollStates = #()
			for r in rollouts do append rollStates (NOT r.open)
			local sp = try ( rollouts[1].scrollPos ) catch ( 0 )
			if sp == undefined then sp = 0
 
			local res = ( setINISetting (getINIFilename()) toolName "RolloutRolledUp" (rollStates as string) ) AND
						( setINISetting (getINIFilename()) toolName "ScrollPos" (sp as string) )
			return res
		) catch ( return false )
	),
	fn loadRolloutStates = (
		try (
			local rollStates = execute (getINISetting (getINIFilename()) toolName "RolloutRolledUp")
			if rollStates == OK then (
				rollStates = rolloutRolledUp
				for i in 1 to rollStates do rollStates[i] = rollStates[i]
			)
			for i in 1 to rollouts.count do (
				local tmp = rollStates[i]
				if tmp == undefined then tmp = false
				rollouts[i].open = NOT tmp
			)
			rollouts[1].scrollPos = (getINISetting (getINIFilename()) toolName "ScrollPos") as integer
 
			return true
		) catch ( return false )
	),
	fn openTool this = (
		try (
			-- Maxscript Bug: copy of toolName must be made
			local str = toolName
			if str == undefined then return false
 
			if (BFDman.toolInUse str) then (
				BFDman.closeTool str
				return false
			) else (
				BFDman.addTool this
 
				if (defFloaterPos == undefined) OR (classOf defFloaterPos != point2) then (
					floater = newRolloutFloater toolName defFloaterSize.x defFloaterSize.y
				) else (
					floater = newRolloutFloater toolName defFloaterSize.x defFloaterSize.y defFloaterPos.x defFloaterPos.y
				)
 
				loadFloaterGeom()
				for i in 1 to rollouts.count do (
					addRollout rollouts[i] floater rolledUp:rolloutRolledUp[i]
				)
				if autoLoadRolloutStates then loadRolloutStates()
				floaterClosing = false
 
				return true
			)
		) catch ( return false )
	),
	fn closeTool = (
		try (
			if NOT floaterClosing then (
				-- Maxscript Bug: copy of toolName must be made
				local str = toolName
				BFDman.delTool str
 
				saveFloaterGeom()
				saveToolVersion()
				if autoLoadRolloutStates then saveRolloutStates()
				floaterClosing = true
				try ( closeRolloutFloater floater ) catch ()
			)
 
			return true
		) catch ( return false )
	),
	fn getFloaterSize = (
		local f = getFloater()
		if f != undefined then (
			return f.size
		) else (
			return undefined
		)
	),
	fn setFloaterSize fSize = (
		local f = getFloater()
		if f != undefined then (
			try ( f.size = fSize ) catch ( return false )
			return true
		) else (
			return false
		)
	)
--	fn saveSettings = (
--		saveFloaterGeom()
--
--		setINISetting INIFile toolName "VERSION" (version as string)
--		for i in 1 to ops.count do (
--			--Do a massive case here to convert values to strings
--			try (
--				setINISetting INIFile toolName (i as string) (ops[i] as string)
--			) catch ()
--		)
--	),
--	fn loadSettings = (
--		loadFloaterGeom()
--	)
)
 
------------------------------------------------------------------------------------
--global _BFDman structure
------------------------------------------------------------------------------------
--Variables:
------------------------------------------------------------------------------------
--	version (integer, 2)
--		current version of BFDtools core
--		version 1: initial version
--		version 2: usage tracking added
--	usageFile (string, "")
--		file to record usage information on tools as they are opened
--		if this a filename is supplied here, each time a tool is opened,
--		its name is recorded along with the number of times it has been opened.
------------------------------------------------------------------------------------
--Functions:
------------------------------------------------------------------------------------
--	getRoot
--		returns the current root of BFDtools
--	addTool aTool
--		add aTool to list of current tools
--	getTool param
--		get an active tool
--		param can be either a tool name or tool index
--	closeTool param
--		call the closeTool function on specified tool
--		param can be either a tool name or tool index
--	closeAllTools
--		call the closeTool function on all open tools
--	delTool param
--		remove tool from the list of active tools, without closing
--		param can be either a tool name or tool index
--	numTools
--		see how many tools are currently active
--	toolInUse toolName
--		check if a tool with the name "toolName" is currently active
--	getToolIndex param
--		returns the specified tool's index
--		param must be the tool's name
------------------------------------------------------------------------------------
struct _BFDman (
	--------------------------------------------------------------------------------
	-- Start Private stuff, subject to change at any time.  Don't rely on this.
	--------------------------------------------------------------------------------
	activeTools = #(),
	--------------------------------------------------------------------------------
	-- End Private stuff, subject to change at any time.  Don't rely on this.
	--------------------------------------------------------------------------------
	version = 2,
	usageFile = "",
	fn getToolIndex param = (
		case (classOf param) of (
			string: (
				local toolName = param
				for i in activeTools.count to 1 by -1 do (
					if activeTools[i].toolName == toolName then return i
				)
				return undefined
			)
		)
	),
	fn getTool param = (
		case (classOf param) of (
			integer: (
				local i = param
				if (i >= 1) AND (i <= activeTools.count) then (
					return activeTools[i]
				)
			)
			string: (
				local toolName = param
				local i = getToolIndex toolName
				if (i != undefined) then return activeTools[i]
			)
		)
		return undefined
	),
	fn toolInUse toolName = (
		if ((getTool toolName) == undefined) then return false else return true
	),
	fn addTool aTool = (
		if NOT (toolInUse aTool.toolName) then (
			append activeTools aTool
			if (usageFile != "") then (
				try (
					local cnt = try ((GetINISetting usageFile "Usage" aTool.toolName) as integer) catch (0)
					cnt += 1
					SetINISetting usageFile "Usage" aTool.toolName (cnt as string)
				) catch ()
			)
			return true
		) else ( return false )
	),
	fn delTool param = (
		case (classOf param) of (
			integer: (
				local i = param
				if (i <= activeTools.count) AND (i >= 1) then (
					deleteItem activeTools i
					return true
				)
			)
			string: (
				local toolName = param
				local idx = getToolIndex toolName
				if (delTool idx) then return true
			)
		)
		return false
	),
	fn numTools = (
		return activeTools.count
	),
	fn closeTool param = (
		local t = getTool param
		if t != undefined then (
			try (t.closeTool()) catch (return false)
			return true
		) else (
			return false
		)
	),
	fn closeAllTools = (
		for i in numTools() to 1 by -1 do closeTool i
	)
)
 
if (masterMaxDir != undefined) then
(
	BFDman = _BFDman usageFile:(masterMaxDir + "\\Logs\\BFDtoolsUsage.ini")
) else (
	BFDman = _BFDman()
)
 
---------------------------------------------------------------------
-- Controller Definitions
---------------------------------------------------------------------
 
fn GetControllers anim depth:-1 ctrlArray:undefined = (
	if ctrlArray == undefined then ctrlArray = #()
	if depth == 0 then return() else depth -= 1
 
	for i in 1 to anim.numSubs do (
		if	(anim[i].controller != undefined) AND
			(ClassOf anim[i].controller != undefined) then
		(
			append ctrlArray anim[i].controller
--			for i in depth to -3 do format "\t"
--			format "% : %\n" anim[i] anim[i].controller
		)
		GetControllers anim[i] depth:depth ctrlArray:ctrlArray
	)
 
	ctrlArray
)
 
fn GetAnimatedSubAnims anim depth:-1 saArray:undefined = (
	if saArray == undefined then saArray = #()
	if depth == 0 then return() else depth -= 1
 
	for i in 1 to anim.numSubs do (
		if	(anim[i].controller != undefined) AND
			(ClassOf anim[i].controller != undefined) then
		(
			append saArray anim[i]
--			for i in depth to -3 do format "\t"
--			format "%\n" anim[i]
		)
		GetAnimatedSubAnims anim[i] depth:depth saArray:saArray
	)
 
	saArray
)
 
---------------------------------------------------------------------
-- File Definitions
---------------------------------------------------------------------
 
fn FileExists fName = (
	(getFiles fName).count != 0
)
 
fn FixFilename fName = (
	local strArray = filterString fName "\\"
	local fixedName = if (isUNCpath fName) then "\\\\" else ""
	for i in 1 to (strArray.count-1) do fixedName += (strArray[i] + "\\")
	fixedName += strArray[strArray.count]
	return fixedName
)
 
fn GetIFLfiles IFLfile = (
	if (fileExists IFLfile) then (
		local f = openfile IFLfile
		local IFLpath = getFilenamePath IFLfile
		local files = #()
 
		while (true) do (
			if (eof f) then exit()
 
			local str = readLine f
			str = filenameFromPath str
			str = IFLpath + str
 
			if (fileExists str) do append files str
		)
 
		close f
		return files
	)
 
	return undefined
)
 
fn GetParentDir fName = (
	if (filenameFromPath fName) != "" do fName = getFilenamePath fName
	local strArray = filterString fName "\\"
	local pDir = if (isUNCpath fName) then "\\\\" else ""
	for i in 1 to (strArray.count-1) do pDir += (strArray[i] + "\\")
	return pDir
)
 
fn GetSequences fDir = (
	local seqArray = #()
	local fDirWild = fixFilename (fDir + "\\*.*")
	local fNames = getFiles fDirWild
 
	-- go through all files in dir
	while (fNames.count != 0) do (
		local fName = fNames[1]
		-- if file is part of sequence
		if (isSequenceFile fName) then (
			-- grab the sequence base name
			local seqBase =	(getFilenamePath fName) +
							(getSequenceFilenameBase fName) + "*" +
							(getFilenameType fName)
			-- get all files that are part of sequence (potential bug here)
			local seq = getFiles seqBase
			if (seq.count >= 2) then (
				sort seq
				-- add sequence to array of sequences
				append seqArray seq
 
				-- remove all files in found sequence from pool of names to check
				for seqName in seq do (
					local idx = findItem fNames seqName
					if (idx != 0) then deleteItem fNames idx
				)
			) else (
				deleteItem fNames 1
			)
		) else (
			-- remove fName from list of files to check
			deleteItem fNames 1
		)
	)
 
	return seqArray
)
 
fn GetSequenceFilenameBase fName = (
	local ints = #("0","1","2","3","4","5","6","7","8","9")
	local str = ""
	local baseName = fName
	fName = getFilenameFile fName
 
	local endIdx = fName.count
	local foundAlpha = false
 
	while (endIdx >= 2) AND NOT foundAlpha do (
		if ((findItem ints fName[endIdx]) != 0) then
			endIdx -= 1
		else
			foundAlpha = true
	)
 
	if	(endIdx >= 1) AND
		(endIdx != fName.count) do (
		baseName = subString fName 1 endIdx
	)
 
	return baseName
)
 
fn GetSubDirs fDir depth:-1 subDirs:undefined = (
	if subDirs == undefined then subDirs = #()
	if depth == 0 then return() else depth -= 1
 
	local dirs = getDirectories (fDir + "\\*")
	for i in 1 to dirs.count do dirs[i] = fixFilename dirs[i]
 
	for subDir in dirs do (
		append subDirs subDir
		getSubDirs subDir subDirs:subDirs depth:depth
	)
	return subDirs
)
 
fn IsBitmapFile fName = (
	try (
		close (openBitmap fName)
		return TRUE
	) catch (
		return FALSE
	)
)
 
fn IsFileType fName fType = (
	local n = (getFilenameType fName) as name
	n == (("." + fType) as name)
)
 
fn IsSequenceFile fName = (
	return NOT (fName == getSequenceFilenameBase fName)
)
 
fn IsUNCpath fPath = (
	if	(classOf fPath == string) AND
		(fPath.count >= 2) AND
		(fPath[1] == "\\") AND
		(fPath[2] == "\\") then TRUE else FALSE
)
 
fn MakeIFL fArray filename:undefined includePath:false createInParent:false = (
	sort fArray
 
	if createInParent then (
		if (filename == undefined) then (
			filename =	(getParentDir (getFilenamePath fArray[1])) +
						(getFilenameFile fArray[1]) + ".IFL"
		) else (
			filename =	(getParentDir fArray[1]) +
						(getFilenameFile filename) + ".IFL"
		)
	) else (
		if filename == undefined do (
			filename =	(getFilenamePath fArray[1]) +
						(getFilenameFile fArray[1]) + ".IFL"
		)
	)
	local iflFile = createFile filename
	if iflFile == undefined then return undefined
 
	if includePath then (
		for f in fArray do format "%\n" f to:iflFile
	) else (
		if createInParent then (
			local rPath = getFilenamePath fArray[1]
			rPath = filterString rPath "\\"
			rPath = ".\\" + rPath[rPath.count] + "\\"
			for f in fArray do format "%\n" (rPath + (filenameFromPath f)) to:iflFile
		) else (
			for f in fArray do format "%\n" (filenameFromPath f) to:iflFile
		)
	)
 
	close iflFile
	return filename
)
 
fn IsFileModNewer fPathA fPathB =
(
	local dateStrA = try (GetFileModDate fPathA) catch (return undefined)
	local dateStrB = try (GetFileModDate fPathB) catch (return undefined)
 
	IsDateNewer dateStrA dateStrB
)
 
fn IsFileCreateNewer fPathA fPathB =
(
	local dateStrA = try (GetFileCreateDate fPathA) catch (return undefined)
	local dateStrB = try (GetFileCreateDate fPathB) catch (return undefined)
 
	IsDateNewer dateStrA dateStrB
)
 
fn IsValidFilename filename = (
	local badChars = #("\\","/",":","*","?","\"","<",">","|")
	local str = GetFilenameFile filename
 
	local res = true
	res = res AND (str.count != 0)
	res = res AND (str.count <= 215)
	for i in 1 to str.count do res = res AND ((findItem badChars str[i]) == 0)
	res
)
 
fn SortINISection INIfile sectionName keyNameBase count =
(
	if (FileExists INIfile) then (
		keyValues = for i in 1 to count collect (
			GetINISetting INIfile sectionName (keyNameBase+(i as string))
		)
		Sort keyValues
		for i in 1 to count do (
			SetINISetting INIfile sectionName (keyNameBase+(i as string)) keyValues[i]
		)
		TRUE
	) else (
		FALSE
	)
)
 
fn UniqueFileName fName padSize:4 =
(
	local newName = ""
	if (FileExists fName) then
	(
		local fBase = ""
		if (IsSequenceFile fName) then
		(
			fBase = (GetFilenamePath fName) + (GetSequenceFilenameBase fName)
		) else (
			fBase = (GetFilenamePath fName) + (GetFilenameFile fName)
		)
		local fType = GetFilenameType fName
 
		local i = 0
		do (i += 1) while
		(
			newName = fBase + (GetPadNum i padSize) + fType
			FileExists newName
		)
	) else (
		newName = fName
	)
 
	newName
)
 
---------------------------------------------------------------------
-- FuncAlias Definitions
---------------------------------------------------------------------
 
co = ClassOf
sco = SuperClassOf
gpn = GetPropNames
pct = PrintClassTree
gct = GetClassTree
-- ShowProperties
if ((MaxVersion())[1] >= 4000) then
(
	mapped fn sp obj = (
		try (
			if (ClassOf obj == Interface OR ClassOf obj == MixinInterface) then
			(
				format "%\n" obj
				ShowInterface obj
			) else (
				format "%\n" obj
				ShowProperties obj
			)
		) catch ( undefined )
		OK
	)
) else (
	mapped fn sp obj = (
		try (
			format "%\n" obj
			ShowProperties obj
			format "\n"
		) catch ( undefined )
		OK
	)
)
-- ShowPropertiesModifier
mapped fn spm obj i = (
	try (
		format "$%.modifiers[%] -> %\n" obj.name i obj.modifiers[i]
		showproperties obj.modifiers[i]
		format "\n"
	) catch ( undefined )
)
-- ShowPropertiesValue
mapped fn spv obj = (
	try (
		format "%\n" obj
		for prop in (gpn obj) do (
			format "  .%: %\n" (prop as string) (getProperty obj prop)
		)
		format "\n"
	) catch ( undefined )
)
-- ShowPropertiesModifierValue
mapped fn spmv obj i = (
	try (
		format "$%.modifiers[%] -> %\n" obj.name i obj.modifiers[i]
		local m = obj.modifiers[i]
		for prop in (gpn m) do (
			format "  .%: %\n" (prop as string) (getProperty m prop)
		)
		format "\n"
	) catch ( undefined )
)
 
---------------------------------------------------------------------
-- Hierarchy Definitions
---------------------------------------------------------------------
 
fn GetChildren parent depth:-1 objArray:undefined = (
	if objArray == undefined then objArray = #()
	if depth == 0 then return() else depth -= 1
	for child in parent.children do (
		append objArray child
		getChildren child objArray:objArray depth:depth
	)
	return objArray
)
 
fn GetDepth obj cnt:0 = (
	if obj.parent == undefined then (
		return cnt
	) else (
		cnt+=1
		getDepth obj.parent cnt:cnt
	)
)
 
fn DupHierarchy parentObj dupFunc instanceAnimation:false = (
	-- Grab the children to copy
	local srcObjs = #(parentObj) + (getChildren parentObj)
 
	-- Unlink everything
	local srcObjsParents = for obj in srcObjs collect obj.parent
	for obj in srcObjs do obj.parent = undefined
 
	-- Duplicate the objects
	local dupObjs = for obj in srcObjs collect dupFunc obj
 
	-- Relink everything
	for i in 1 to srcObjs.count do srcObjs[i].parent = srcObjsParents[i]
 
	-- Sync up the parenting of the copies
	for i in 1 to dupObjs.count do (
		local parentIndex = findItem srcObjs srcObjs[i].parent
		if parentIndex != 0 then (
			dupObjs[i].parent = dupObjs[parentIndex]
		)
	)
 
	animate off at time 0 (
		if (dupFunc == instance) OR (dupFunc == reference) then (
			for i in 1 to dupObjs.count do instanceNodeProps srcObjs[i] dupObjs[i]
		) else (
			for i in 1 to dupObjs.count do copyNodeProps srcObjs[i] dupObjs[i]
		)
	)
 
	-- Instance the controllers
	if instanceAnimation then (
		for i in 2 to dupObjs.count do (
			--dupObjs[i].parent = srcObjs[i].parent
			dupObjs[i].pos.controller = srcObjs[i].pos.controller
			dupObjs[i].rotation.controller = srcObjs[i].rotation.controller
			dupObjs[i].scale.controller = srcObjs[i].scale.controller
		)
	)
 
	return dupObjs
)
 
fn CopyHierarchy parentObj = (
	return (dupHierarchy parentObj copy)
)
 
fn InstanceHierarchy parentObj instanceAnimation:false = (
	return (dupHierarchy parentObj instance instanceAnimation:instanceAnimation)
)
 
fn ReferenceHierarchy parentObj instanceAnimation:false = (
	return (dupHierarchy parentObj reference instanceAnimation:instanceAnimation)
)
 
-- Delete only objects that have no children, and
-- recurse until you can't delete any more.
fn SafeDelete objs lastSize:-1 = (
	if (classOf objs != Array) do return undefined
	if (lastSize == -1) do lastSize = objs.count
 
	for i in objs.count to 1 by -1 do (
		if (objs[i].children.count == 0) do (
			delete objs[i]
			deleteItem objs i
		)
	)
	if (lastSize == objs.count) then (
		return objs
	) else (
		safeDelete objs lastSize:(objs.count)
	)
)
 
fn GetHierarchyRoot obj = (
	local theParent = obj
	while theParent.parent != undefined do theParent = theParent.parent
 
	theParent
)
 
fn GetHierarchy obj = (
	local rootObj = getHierarchyRoot obj
	local kids = getChildren rootObj
 
	(#(rootObj) + kids)
)
 
fn GetParentChain obj = (
	local pChain = #(obj)
	local theParent = obj
	while theParent.parent != undefined do (
		theParent = theParent.parent
		append pChain theParent
	)
 
	(reverseArray pChain)
)
 
---------------------------------------------------------------------
-- Material Definitions
---------------------------------------------------------------------
 
fn DefaultMaterialFilter mat = ( true )
fn DefaultTextureFilter mat = ( true )
 
fn GetSubMaterials mat MaterialFilter:DefaultMaterialFilter = (
	-- all the submaterials we find at this level
	local matArray = #()
 
	if (mat == undefined) then return #()
 
	-- grab passed material, if it is indeed a material, and one that passes the filter
	if (SuperClassOf mat == material) AND (MaterialFilter mat) then ( append matArray mat )
 
	-- run through material's subAnims
	for i in 1 to mat.numSubs do
	(
		-- get subAnim
		local subAn = GetSubAnim mat i
		-- Different materials seem to expose their submaterials to maxscript differently.
		-- this case tries to see if "subAn" is really a material, or
		-- is a subAnim _holding_ a material, or simply something we're not interested in
--		local subMat = case of
--		(
--			(SuperClassOf subAn == material): subAn
--			((ClassOf subAn == subAnim) AND (SuperClassOf subAn.object == material)): subAn.object
--			default: undefined
--		)
		local subMat = if (ClassOf subAn == subAnim) then subAn.object else subAn
 
		-- if subMat turned out to be a material, recurse (which will return an array
		-- containing subMat and any subMaterials found in it)
		if (subMat != undefined) then matArray += GetSubMaterials subMat MaterialFilter:MaterialFilter
	)
 
	-- return array of all submaterials for given level
	matArray
)
 
fn GetStandardSubMaterials mat = (
	local StdFilter
	fn StdFilter mat = ( ClassOf mat == standardMaterial )
 
	GetSubMaterials mat MaterialFilter:StdFilter
)
 
fn GetObjectMaterials obj MaterialFilter:DefaultMaterialFilter = (
	if (obj.material == undefined) then (
		#()
	) else (
		GetSubMaterials obj.material MaterialFilter:MaterialFilter
	)
)
 
fn GetObjectStandardMaterials obj = (
	local StdFilter
	fn StdFilter mat = ( ClassOf mat == standardMaterial )
 
	if (obj.material == undefined) then (
		#()
	) else (
		GetSubMaterials obj.material MaterialFilter:StdFilter
	)
)
 
fn GetSubTextures mat TextureFilter:DefaultTextureFilter = (
	local texArray = #()
 
	if (mat == undefined) then return #()
 
	if (SuperClassOf mat == textureMap) AND (TextureFilter mat) do append texArray mat
 
	for i in 1 to mat.numSubs do
	(
		local subAn = GetSubAnim mat i
		local subTex = if ((ClassOf subAn == subAnim) AND (SuperClassOf subAn.object == textureMap)) then (
			subAn.object
		) else (
			subAn
		)
 
		if (subTex != undefined) then texArray += GetSubTextures subTex TextureFilter:TextureFilter
	)
 
	texArray
)
 
fn GetBitmapTextures mat TextureFilter:DefaultTextureFilter = (
	local BitmapFilter
	fn BitmapFilter mat = ( ClassOf mat == Bitmaptexture )
 
	GetSubTextures mat TextureFilter:BitmapFilter
)
 
fn ReduceSubMaterials mat compareFunc:undefined = (
	local newSO = MultiMaterial name:mat.name
	newSO.materialList.count = 0
 
	local oldArray = mat.materialList
	-- local newArray = newSO.materialList
	-- Collecting materials into an array first now since sometimes
	-- "FindItem" fails finding instances in the materialList property
	local newArray = #()
	local remapArray = for i in 1 to mat.materialList.count collect i
 
	for mIdx in 1 to oldArray.count do (
		local m = oldArray[mIdx]
 
		local fIdx = 0
		if (compareFunc == undefined) then (
			fIdx = findItem newArray m
		) else (
			for i in 1 to newArray.count do (
				if (compareFunc m newArray[i]) then (
					fIdx = i
					format "found %: %\n" m.name i
					break
				)
			)
		)
		if (fIdx == 0) then (
			local newIdx = newArray.count + 1
			newArray[newIdx] = m
			remapArray[mIdx] = newIdx
		) else (
			remapArray[mIdx] = fIdx
		)
	)
 
	-- now collect the new submaterials a stuff them in
	-- the new multi/SO
	newSO.materialList.count = newArray.count
	for i in 1 to newArray.count do newSO.materialList[i] = newArray[i]
 
	#(newSO,remapArray)
)
 
fn DeleteUnusedSubMaterials obj = (
	local oldSO = obj.material
	if (ClassOf oldSO != MultiMaterial) then return undefined
 
	local newSO = MultiMaterial name:oldSO.name
	newSO.materialList.count = 0
 
	local oldArray = oldSO.materialList
	local newArray = newSO.materialList
	local remapArray = for i in 1 to oldArray.count collect i
 
	local usedMatIDs = GetUsedMatIDs obj
 
	for mIdx in 1 to oldArray.count do (
		local fIdx = FindItem usedMatIDs mIdx
 
		if (fIdx != 0) then (
			local newIdx = newArray.count + 1
			newArray[newIdx] = oldArray[mIdx]
			remapArray[mIdx] = newIdx
		)
	)
 
	#(newSO,remapArray)
)
 
fn RemapMeshMaterials obj remapArray = (
	local raCnt = remapArray.count
	for fIdx in 1 to obj.numFaces do (
		local fID = GetFaceMatID obj fIdx
		fID = if (fID > raCnt) then (mod fID raCnt) else fID
		SetFaceMatID obj fIdx remapArray[fID]
	)
	Update obj
)
 
fn GetUsedMatIDs obj =
(
	local matIDs = #()
	local id
	for i in 1 to obj.mesh.numFaces do (
		id = GetFaceMatID obj.mesh i
		if (FindItem matIDs id)==0 then Append matIDs id
	)
	matIDs = sort matIDs
 
	matIDs
)
 
---------------------------------------------------------------------
-- Math Definitions
---------------------------------------------------------------------
 
fn fMin a b = ( if a > b then b else a )
 
fn fMax a b = ( if a > b then a else b )
 
fn fClamp x a b = (
	if x < a then a else
	if x > b then b else x
)
 
Clamp = fClamp
 
fn ClampPnt2 num minVal maxVal = (
	[(Clamp num.x minVal maxVal), (Clamp num.y minVal maxVal)]
)
 
fn ClampPnt3 num minVal maxVal = (
	[(Clamp num.x minVal maxVal), (Clamp num.y minVal maxVal), (Clamp num.z minVal maxVal)]
)
 
fn Bias x b = ( pow x (log b/log 0.5) )
 
fn Gain x g = (
	if x < 0.5 then ((bias (1-g) (2*x))/2) else
	(1 - ((bias (1-g) (2-2*x))/2))
)
 
fn Smoothstep x a b = (
	if x < a then return 0
	if x >= b then return 1
	x = (x-a)/(b-a)
	x*x*(3-2*x)
)
 
fn Round n prec = (
	case (classOf n) of (
		Integer: (
			(Round (n as float) prec) as integer
		)
		Float: (
			local mult = pow 10 prec
			n *= mult
			if n < 0 then n -= 0.5 else n += 0.5
			return ((n as integer) / mult)
		)
		Point2: (
			Point2 (round n.x prec) (round n.y prec)
		)
		Point3: (
			Point3 (round n.x prec) (round n.y prec) (round n.z prec)
		)
	)
)
 
fn GetClosestPoints thisP posArray = (
	--fn CompVal a b valArray: = (
	--	valArray[a]-valArray[b]
	--)
	--workaround for qsort not using CompVal return value as a float
	fn CompVal a b valArray: = (
		if valArray[a] < valArray[b] then -1
		else
		if valArray[a] > valArray[b] then 1
		else
			0
	)
 
	local dist = for p in posArray collect (distance thisP p)
	local distIdx = for i in 1 to dist.count collect i
	qsort distIdx CompVal valArray:dist
	#(distIdx, for i in 1 to posArray.count collect dist[distIdx[i]])
)
 
---------------------------------------------------------------------
-- Mesh Definitions
---------------------------------------------------------------------
 
fn GetVertFaceCache obj = (
	local vertFaces = for i in 1 to obj.numVerts collect #()
	for i in 1 to obj.numFaces do (
		local f = getFace obj i
		append vertFaces[f.x] i
		append vertFaces[f.y] i
		append vertFaces[f.z] i
	)
 
	return vertFaces
)
 
fn GetMeshElement obj faceIdx vertFaceCache = (
	local meshElement = #{}
	local faceVerts, faces, f, curFace
	-- put the passed face onto a stack
	local faceStack = #(faceIdx)
 
	-- while we still have faces to look through
	while faceStack.count != 0 do (
		-- grab the verts used by the face at the top of the stack
		curFace = faceStack[faceStack.count]
		faceVerts = getFace obj curFace
		-- note that we've visited current face, and remove it from the stack
		meshElement[curFace] = true
		deleteItem faceStack faceStack.count
 
		-- gather all the faces referenced by the current face's verts
		faces = vertFaceCache[faceVerts.x]
		faces += vertFaceCache[faceVerts.y]
		faces += vertFaceCache[faceVerts.z]
		-- for each face, if it's not already visited, put it on the stack
		for i in 1 to faces.count do (
			f = faces[i]
			if NOT meshElement[f] then (
				meshElement[f] = true
				append faceStack f
			)
		)
	)
 
	return meshElement
)
 
fn GetAllMeshElements obj vertFaceCache = (
	local usedFaces = #{}
	local meshElements = #()
 
	local i = 1
	while i <= obj.numFaces do (
		-- grab an element
		meshElement = (getMeshElement obj i vertFaceCache)
		-- note faces that are used
		usedFaces += meshElement
		-- save into the array of elements
		append meshElements meshElement
 
		-- find the next face that hasn't been flagged as
		-- part of an element yet
		while usedFaces[i] do i += 1
	)
 
	return meshElements
)
 
fn DetachMeshFaces obj faces = (
	local cObj = copy obj
	for i in obj.numFaces to 1 by -1 do (
		if faces[i] then
			deleteFace obj i
		else
			deleteFace cObj i
	)
	update obj
	update cObj
 
	return cObj
)
 
fn CleanIsoVerts obj = (
	local verts = #{1..obj.numVerts}
	for i in 1 to obj.numFaces do (
		local f = getFace obj i
		verts[f.x] = verts[f.y] = verts[f.z] = false
	)
	for i in verts.count to 1 by -1 do (
		if verts[i] do deleteVert obj i
	)
 
	OK
)
 
fn ExplodeMeshElements obj = (
	local newObjs = #()
 
	format "Building cache...\n"
	local elements = getAllMeshElements obj (getVertFaceCache obj)
	for eIdx in 1 to elements.count do (
		format "Detaching % of %\n" eIdx elements.count
		local mapVerts = #(); mapVerts[obj.numVerts] = undefined
		local verts = #()
		local faces = #()
		local edgeVis = #()
		local smoothGroup = #()
		local matID = #()
 
		-- loop through all faces
		for fIdx in 1 to elements[eIdx].count do (
			if elements[eIdx][fIdx] then (
				-- get the used face
				local f = getFace obj fIdx
				append faces f
				edgeVis += #((getEdgeVis obj fIdx 1),(getEdgeVis obj fIdx 2),(getEdgeVis obj fIdx 3))
				append smoothGroup (getFaceSmoothGroup obj fIdx)
				append matID (getFaceMatID obj fIdx)
				-- grab the position of each vertex, and note which
				-- vertex in old mesh maps to the vertex in the new mesh
				for v in 1 to 3 do (
					case v of (
						1: v = f.x
						2: v = f.y
						3: v = f.z
					)
					if mapVerts[v] == undefined then (
						append verts (getVert obj v)
						mapVerts[v] = verts.count
					)
				)
			)
		)
		local cObj = mesh numVerts:verts.count numFaces:faces.count
		cObj.name = uniqueName (obj.name + "_ELEMENT")
		cObj.transform = obj.transform
		for i in 1 to verts.count do setVert cObj i verts[i]
		for i in 1 to faces.count do (
			setFace cObj i mapVerts[(faces[i].x)] mapVerts[(faces[i].y)] mapVerts[(faces[i].z)]
		)
		local cnt = 0
		for i in 1 to faces.count do (
			setEdgeVis cObj i 1 edgeVis[cnt+=1]
			setEdgeVis cObj i 2 edgeVis[cnt+=1]
			setEdgeVis cObj i 3 edgeVis[cnt+=1]
			setFaceSmoothGroup cObj i smoothGroup[i]
			setFaceMatID cObj i matID[i]
		)
		update cObj
 
		append newObjs cObj
		gc()
	)
 
	return newObjs
)
 
fn GetFacesByNormal obj vec angThresh = (
	if	(classOf obj == editable_mesh) AND
		(obj.modifiers.count == 0) then (
			local selFaces = #{1..obj.numFaces}
			local n, d, ang
			vec = normalize (-vec)
			for i in 1 to obj.numFaces do (
				n = getFaceNormal obj i
				d = dot n vec
				ang = if (d > 0.0) then (acos d) else (90 + (acos d))
				selFaces[i] = (acos d) < angThresh
			)
			return selFaces
	) else (
		return undefined
	)
)
 
fn GetAllPolygons obj = (
	-- faces left to consider for polygons
	local faces = #{1..(obj.numFaces+1)}
	-- array of bitarrays, one for each poly
	local polys = #()
	local cnt = 1
	while (cnt < faces.count) do (
		-- grab poly from next face
		append polys (meshop.getPolysUsingFace obj cnt threshhold:180)
		-- remove faces used by poly from pool of remaining faces
		faces -= polys[polys.count]
		-- zip ahead to next unused face
		while NOT faces[cnt] do cnt += 1
	)
	return polys
)
 
fn GetAllPolygonsProgress obj = (
	local oldSOmode = subObjectLevel
	progressStart "Gathering Polygons..."
	-- faces left to consider for polygons
	local faces = #{1..(obj.numFaces+1)}
	-- array of bitarrays, one for each poly
	local polys = #()
	local cnt = 1
	while (cnt < faces.count) do (
		-- grab poly from next face
		append polys (meshop.getPolysUsingFace obj cnt threshhold:180)
		-- remove faces used by poly from pool of remaining faces
		faces -= polys[polys.count]
		-- zip ahead to next unused face
		while NOT faces[cnt] do cnt += 1
		progressUpdate (cnt as float / faces.count * 100.)
	)
	progressEnd()
	if oldSOmode != undefined do subObjectLevel = oldSOmode
	return polys
)
 
fn MeshOKtoModify obj = (
	if	(classOf obj == Editable_mesh) AND
		(obj.modifiers.count == 0) then true else false
)
 
fn GetClosestVert meshObj pnt vertMask:undefined =
(
	case of (
		(#selection == vertMask): (
			vertMask = GetVertSelection meshObj
		)
		(undefined == vertMask): (
			vertMask = #{1..meshObj.numVerts}
		)
	)
 
	local closest = 999999999
	local closestIdx
	local dist
 
	for i in vertMask do (
		dist = distance pnt (GetVert meshObj i)
		case of (
			(dist == 0.0): return i
			(dist < closest): (
				closest = dist
				closestIdx = i
			)
		)
	)
 
	return closestIdx
)
 
fn GetClosestVerts meshObj pnt vertMask:undefined =
(
	case of (
		(#selection == vertMask): (
			vertMask = GetVertSelection meshObj
		)
		(undefined == vertMask): (
			vertMask = #{1..meshObj.numVerts}
		)
	)
 
	local posArray = for i in vertMask collect (GetVert meshObj i)
	local origIndex = for i in vertMask collect i
 
	local res = GetClosestPoints pnt posArray
 
	local idxArray = for i in 1 to res[1].count collect origIndex[res[1][i]]
 
	return #(idxArray, res[2])
)
 
fn GetFaceAsArray meshObj faceIndex =
(
	local f = GetFace meshObj faceIndex
	#(f.x, f.y, f.z)
)
 
---------------------------------------------------------------------
-- Miscellaneous Definitions
---------------------------------------------------------------------
 
fn GetClassTree val =
(
	local c = ClassOf val
 
	local classAr = #(c)
	while (c != ClassOf c) do (c = ClassOf c; Append classAr c)
 
	ReverseArray classAr
)
 
fn PrintClassTree val =
(
	local classAr = GetClassTree val
 
	format "__%\n" classAr[1]
	local formatStr = "|__%\n"
	for i in 2 to classAr.count do
	(
		format formatStr classAr[i]
		formatStr = " " + formatStr
	)
 
	OK
)
 
---------------------------------------------------------------------
-- Modifier Definitions
---------------------------------------------------------------------
 
fn GetAllModifiers = (
	local ss = stringstream ""
	showclass "*:mod*" to:ss
 
	local sa = filterString (ss as string) ":\n"
 
	local classes = for i in 1 to sa.count by 2 collect (
		-- capitalize first letter and remove trailing space
		(toUpper2 sa[i][1]) + (subString sa[i] 2 (sa[i].count-2))
	)
	sort classes
 
	return classes
)
 
fn GetModifiersOfClass obj modClass =
(
	local objArray
	try(objArray = obj as array) catch (objArray = #(obj))
 
	local mods = #()
	for obj in objArray do (
		mods += (for m in obj.modifiers where (ClassOf m == modClass) collect m)
	)
	mods
)
 
---------------------------------------------------------------------
-- Object Definitions
---------------------------------------------------------------------
 
fn IsInstance objA objB = (
	(isKindOf objA node) AND
	(isKindOf objB node) AND
	(objA.baseObject == objB.baseObject)
)
 
fn GetInstances obj = (
	local objs
 
	local bObj = obj.baseObject
	local rObjs = refs.dependents bObj
	objs = for rObj in rObjs where ( (isKindOf rObj node) AND
									(bObj == rObj.baseObject) AND
									(obj != rObj) ) collect rObj
 
	return objs
)
 
fn DupNodeProps sourceObj targetObj instanceControllers:false = (
	animate off at time 0 (
		local props = #(#renderable,#castShadows,#ishidden,#boxmode,#alledges,#backfacecull,
			#receiveshadows,#gbufferchannel,#motionblur,#imageMotionBlurMultiplier,
			#showLinks,#showLinksOnly,#isfrozen,#showTrajectory,#showVertexColors,
			#vertexColorsShaded,#inheritVisibility,#xray,#ignoreExtents,#renderOccluded,
			#motionBlurOn,#rcvCaustics,#generateCaustics,#rcvGlobalIllum,#generateGlobalIllum,
			#primaryVisibility,#secondaryVisibility)
 
		for prop in props do (
			local val = getProperty sourceObj prop
			if val != undefined do setProperty targetObj prop val
		)
 
		try (
			if instanceControllers then (
				targetObj.imageMotionBlurMultiplier.controller = sourceObj.imageMotionBlurMultiplier.controller
			) else (
				targetObj.imageMotionBlurMultiplier.controller = copy sourceObj.imageMotionBlurMultiplier.controller
			)
		) catch ()
--		try (
--			targetObj.motionBlurOnController = copy sourceObj.motionBlurOnController
--		) catch ()
	)
)
 
fn InstanceNodeProps sourceObj targetObj = (
	try (
		dupNodeProps sourceObj targetObj instanceControllers:true
		true
	) catch ( false )
)
 
fn CopyNodeProps sourceObj targetObj = (
	try (
		dupNodeProps sourceObj targetObj
		true
	) catch ( false )
)
 
--fn getDependentObjects obj = (
--	for dep in (refs.dependents obj) where \
--		(isKindOf dep node) AND
--		(dep.baseObject != obj.baseObject) collect dep
--)
 
fn ObjectExists obj = (
	NOT (try ((isDeleted obj) OR (obj == undefined)) catch (true))
)
 
fn IsBipedObject obj = (
	(classOf obj == Biped_Object) OR
	(	(classOf obj == Dummy) AND
		(classOf obj.controller == BipSlave_Control)
	)
)
 
fn TrimInvalidObjects objArray = (
	for i in objArray.count to 1 by -1 do (
		if NOT (objectExists objArray[i]) do (deleteItem objArray i)
	)
	objArray
)
 
fn SelectAndShow objArray keepSel:FALSE prompt:TRUE =
(
	undo on (
		local badObjs = for obj in objArray where obj.isHidden OR obj.isFrozen collect obj
 
		if (prompt AND badObjs.count != 0) then (
			local str = "This selection contains hidden and/or frozen objects.
Do you want these objects to be unhidden and unfrozen?
(Choosing \"No\" means that the hidden/frozen objects will become selected!)"
 
			if (QueryBox str title:"3D Studio MAX") then
				for obj in badObjs do ( obj.isHidden = obj.isFrozen = false )
		)
 
		if (keepSel == true) then
			selectMore objArray
		else
			select objArray
	)
 
	OK
)
 
fn GetBipedObjects bipObj =
(
	if NOT (IsBipedObject bipObj) then return #()
	local hier = GetHierarchy bipObj
	for obj in hier where (IsBipedObject obj) collect obj
)
 
---------------------------------------------------------------------
-- Spline Definitions
---------------------------------------------------------------------
 
fn GetRequiredSteps shp splineIndex lengthParam =
(
	ResetLengthInterp()
	local len = CurveLength shp splineIndex
	local reqAccuracy = (len * lengthParam) * 0.05 --0.25 -- fudge to be a bit more accurate
	(len * (1.0 / reqAccuracy)) as integer
)
 
fn SmoothShape shp splineIndex
	knotCount:undefined
	segmentLength:undefined
	numSamples:9 =
(
	local numPnts
	if (knotCount != undefined) then (
		numPnts = knotCount
	) else if (segmentLength != undefined) then (
		local splLen = CurveLength shp splineIndex
		numPnts = (splLen / segLen) + 1
	) else (
		return undefined
	)
 
	local d = 1.0 / (numPnts-1)
 
	local tmpShp = copy shp
	ConvertToSplineShape tmpShp
 
	local sD = d / (numSamples-1)
 
	local smoothShp = SplineShape()
	smoothShp.name = shp.name + "_Smoothed"
	smoothShp.wireColor = shp.wireColor
	AddNewSpline smoothShp
	AddKnot smoothShp 1 #smooth #curve (GetKnotPoint tmpShp splineIndex 1)
 
	local steps = GetRequiredSteps shp splineIndex sD
	for iP in 2 to (numPnts-1) do (
		local curD = ((iP-1.0) / (numPnts-1.0)) - (d / 2.0)
 
		local p = [0,0,0]
		local thisNumSamples = numSamples
		for iS in 1 to numSamples do (
			if (curD < 0.0 OR curD > 1.0) then (thisNumSamples -= 1; continue())
			p +=	if ((curD-1.0) > -0.00001) then -- workaround for LengthInterp giving bad values at spline ends
						PathInterp tmpShp splineIndex curD steps:steps
					else
						LengthInterp tmpShp splineIndex curD steps:steps
			curD += sD
		)
		p /= numSamples
 
		AddKnot smoothShp 1 #smooth #curve p
	)
	AddKnot smoothShp 1 #smooth #curve (GetKnotPoint tmpShp splineIndex (NumKnots tmpShp splineIndex))
	if (IsClosed tmpShp splineIndex) then Close smoothShp 1
 
	delete tmpShp
 
	UpdateShape smoothShp
	smoothShp
)
 
fn ShapeToString shp splineIndex extraRootVertex:false = (
	local numVerts = NumKnots shp splineIndex
	local numFaces = NumSegments shp splineIndex
 
	if extraRootVertex then (
		numVerts += 1
		numFaces += 1
	)
 
	local msh = mesh numVerts:numVerts numFaces:numFaces
	msh.name = shp.name + "_MeshString"
	msh.wireColor = shp.wireColor
 
	if extraRootVertex then (
		local p1 = GetKnotPoint shp splineIndex 1
		local p2 = GetKnotPoint shp splineIndex 2
		local n = -(p2-p1)
		SetVert msh 1 (p1+n)
	)
 
	local kIdx = 0
	local startVIdx = if extraRootVertex then 2 else 1
	for vIdx in startVIdx to numVerts do (
		kIdx += 1
		SetVert msh vIdx (GetKnotPoint shp splineIndex kIdx)
	)
	for fIdx in 1 to numFaces do (
		local a = fIdx
		local b = if (fIdx < numVerts) then (fIdx+1) else 1
		SetFace msh fIdx [a,a,b]
 
		for eIdx in 1 to 3 do SetEdgeVis msh fIdx eIdx true
	)
 
	Update msh
	msh
)
 
fn ShapeToRibbon shp splineIndex segmentCount width:#auto widthScale:1.0 contourMesh:undefined center:false mapping:false = (
	fn BuildQuad msh a b c d = (
		SetFace msh a [a,d,b]
		SetFace msh (a+1) [a,c,d]
		SetEdgeVis msh a 2 true
		SetEdgeVis msh a 3 true
		SetEdgeVis msh (a+1) 1 true
		SetEdgeVis msh (a+1) 2 true
		OK
	)
 
	fn GetContourNormal p contourMesh = (
		local res = GetClosestVerts contourMesh p
 
		local n = [0,0,0]
		if (res[1].count >= 4) then (
			local weights = for i in 1 to 4 collect res[2][i]
			for i in 1 to 4 do weights[i] = 1.0 - (weights[i] / weights[4])
 
			for i in 1 to 3 do (
				n += (GetNormal contourMesh res[1][i]) * weights[i]
			)
			if (Length n == 0) then
				n = [0,0,1]
			else
				n = Normalize n
		)
 
		n
	)
 
	local numVerts = (segmentCount * 2) + 2
	local numFaces = if (IsClosed shp splineIndex) then numVerts else (numVerts-2)
	local d = 1.0 / (segmentCount as float)
 
	local steps = GetRequiredSteps shp splineIndex d
 
	if (width == #auto) then (
		width = (CurveLength shp splineIndex steps:steps) / (segmentCount as float) * widthScale
	) else if (width == #flip) then (
		width = -(CurveLength shp splineIndex steps:steps) / (segmentCount as float) * widthScale
	)
 
	local msh = mesh numVerts:numVerts numFaces:numFaces
	msh.name = shp.name + "_Ribbon"
	msh.wireColor = shp.wireColor
	if (mapping) then (
		SetNumTVerts msh numVerts
		BuildTVFaces msh
	)
 
	local curD = 0.0
	for vIdx in 1 to numVerts by 2 do (
		local p = LengthInterp shp splineIndex curD steps:steps
		local t = if ((curD-1.0) > -0.00001) then -- workaround for LengthTangent giving bad values at spline ends
					PathTangent shp splineIndex curD
				else
					LengthTangent shp splineIndex curD steps:steps
 
		local n
		if (contourMesh != undefined) then
			n = GetContourNormal p contourMesh
		else
			n = [0,0,1]
 
		local u = Normalize (Cross n t)
		local extrudeVec = u * width
 
--handy while debugging
--null pos:p nullshape:5 xscale:10 mixWireColor:true dir:t name:("T"+vIdx as string) wireColor:red
--null pos:p nullshape:5 xscale:10 mixWireColor:true dir:n name:("N"+vIdx as string) wireColor:green
--null pos:p nullshape:5 xscale:10 mixWireColor:true dir:u name:("U"+vIdx as string) wireColor:blue
 
		if (center) then p -= (extrudeVec/2.0)
 
		SetVert msh vIdx p
		SetVert msh (vIdx+1) (p+extrudeVec)
 
		if (mapping) then (
			SetTVert msh vIdx     [0.0,curD,0.0]
			SetTVert msh (vIdx+1) [1.0,curD,0.0]
		)
 
		curD += d
		curD = fClamp curD 0.0 1.0
	)
 
	for fIdx in 1 to numFaces by 2 do (
		local a = fIdx
		local b = fIdx + 1
		local c = if (fIdx+2 <= numVerts) then (fIdx+2) else 1
		local d = if (fIdx+3 <= numVerts) then (fIdx+3) else 2
		BuildQuad msh a b c d
 
		if (mapping) then (
			SetTVFace msh a [a,d,b]
			SetTVFace msh b [a,c,d]
		)
	)
 
	Update msh
	msh
)
 
--	local weights = #(); weights[splineCount] = undefined
--	if (basePoint != undefined) then (
--		local roots = GetShapeRoots shapeArray
--		local closestRes = GetClosestPoints basePoint roots
--		local idxArray = closestRes[1]
--		local distArray = closestRes[2]
--
--		local minDist = distArray[idxArray[1]]
--		local maxDist = distArray[idxArray[idxArray.count]]
--		local totDist = 0.0; for dist in distArray do totDist += dist
--		local center = totDist / distArray.count
--
--		for i in 1 to splineCount do (
--			local thisDist = distArray[i]
--
--			if (center - roots[i]) < thisDistw
--		)
--
--		local n = minDist / maxDist
--		local maxWeight = 1.0
--		for i in 1 to splineCount do (
--			local thisDist = distArray[i]
--			local nw = 1.0 - (thisDist - minDist) / maxDist
--			local w = 1.0 - ((distArray[i]-minDist) / maxDist) --) / splineCount
--			w *= maxWeight
--			weights[idxArray[i]] = w
--			maxWeight -= w
--		)
--		print weights
--	) else (
--		w = 1.0 / splineCount
--		for i in 1 to splineCount do weights[i] = w
--	)
fn AverageShapes shapeArray knotCount:0 whichSplines:undefined = (
	local splineCount = 0.0
	local splineArray = #()	-- contains a #(shape,splineIndex) for each spline in all the shapes,
							-- for easy iterating over
	for shp in shapeArray do (
		splineCount += NumSplines shp
		for si in 1 to (NumSplines shp) do (
			append splineArray #(shp,si)
		)
	)
 
	if (whichSplines == undefined) then (whichSplines = #{1..splineCount})
 
	local numUsedSplines = BitNumberSet whichSplines
	if (numUsedSplines == 0) then (return undefined)
 
	if (knotCount == 0) then (
		for shp in shapeArray do (
			for i in 1 to (NumSplines shp) do (
				local cnt = NumKnots shp i
				if (cnt > knotCount) then knotCount = cnt
			)
		)
	)
 
	local aveShp = SplineShape name:(UniqueName "AverageShape")
	AddNewSpline aveShp
	for i in 1 to knotCount do (
		local np = (i - 1) / (knotCount - 1.0)
 
		local p = [0,0,0]
 
		for i in whichSplines do (
			local shp = splineArray[i][1]
			local sIdx = splineArray[i][2]
			p += (LengthInterp shp sIdx np) --* weights[splIdx]
		)
		p /= numUsedSplines
 
		AddKnot aveShp 1 #smooth #curve p
	)
 
	UpdateShape aveShp
	aveShp
)
 
fn ExplodeShape shp origin:#world = (
	splArray = #()
	for si in 1 to (NumSplines shp) do (
		local spl = BuildSpline shp si
		append splArray spl
	)
	splArray
)
 
fn BuildSpline shp splineIndex origin:#world = (
	local spl = SplineShape name:(shp.name + "_Spline" + (GetPadNum splineIndex 2))
 
	case origin of
	(
		#world: (
			spl.transform = Matrix3 1
		)
		#original: (
			spl.transform = shp.transform
		)
		#spline: (
			spl.pos = GetKnotPoint shp splineIndex 1
			spl.dir = PathTangent shp splineIndex 0.0
		)
	)
 
	AddNewSpline spl
	for ki in 1 to (NumKnots shp splineIndex) do (
		AddKnot spl 1 #corner #line (GetKnotPoint shp splineIndex ki)
	)
	if (IsClosed shp splineIndex) then close spl 1
	for i in 1 to (NumSegments shp splineIndex) do (
		SetSegmentType spl 1 i (GetSegmentType shp splineIndex i)
	)
	for ki in 1 to (NumKnots shp splineIndex) do (
		SetKnotType spl 1 ki (GetKnotType shp splineIndex ki)
		SetInVec spl 1 ki (GetInVec shp splineIndex ki)
		SetOutVec spl 1 ki (GetOutVec shp splineIndex ki)
	)
	UpdateShape spl
	spl
)
 
fn DetachSpline shp splineIndex origin:#world = (
	local spl = BuildSpline shp splineIndex
	DeleteSpline shp splineIndex
	UpdateShape shp
	spl
)
 
-- Get the root positions of all the passed shapes
fn GetShapeRoots shapeArray = (
	local rootArray = #()
	for shp in shapeArray do (
		for i in 1 to (NumSplines shp) do (
			append rootArray (GetKnotPoint shp i 1)
		)
	)
	rootArray
)
 
fn KnotLengthParam shp splineIndex kIdx =
(
	PathToLengthParam shp splineIndex ((kIdx-1) / ((NumKnots shp splineIndex) - 1.0))
)
 
fn CreateShapeInstance shp =
(
	iShp = CreateInstance SplineShape
 
	for si in 1 to (NumSplines shp) do
	(
		AddNewSpline iShp
 
		for ki in 1 to (NumKnots shp si) do
		(
			AddKnot iShp si #smooth #curve (GetKnotPoint shp si ki)
			SetKnotType iShp si ki (GetKnotType shp si ki)
			SetInVec iShp si ki (GetInVec iShp si ki)
			SetOutVec iShp si ki (GetOutVec iShp si ki)
		)
 
		if (IsClosed shp) then Close iShp
 
		for i in 1 to (NumSegments shp si) do
		(
			SetSegmentType iShp si i (GetSegmentType shp si i)
		)
	)
 
	iShp
)
 
fn CreateSplineFromArray pointArray shapeObj:undefined knotType:#corner closed:FALSE =
(
	local shp
	if (shapeObj == undefined) then
	(
		shp = SplineShape()
		shp.name = UniqueName "Shape"
	) else
		shp = shapeObj
 
	-- don't set bezier types initially (avoid manually creating tangents)
	local kt = if (
					knotType == #bezier OR
					knotType == #beziercorner
				) then
					#smooth
				else
					knotType
 
	AddNewSpline shp
	local splineIndex = NumSplines shp
	for pnt in pointArray do (
		AddKnot shp splineIndex kt #curve pnt
	)
 
	-- go back and set bezier knot types, let max figure out the tangents
	if (knotType == #bezier OR
		knotType == #beziercorner) then
	(
		for i in 1 to (NumKnots shp splineIndex) do SetKnotType shp splineIndex i knotType
	)
 
	if (closed) then Close shp splineIndex
 
	UpdateShape shp
	shp
)
 
---------------------------------------------------------------------
-- String Definitions
---------------------------------------------------------------------
 
fn Truncate str n = (
	if n == 0 then return ""
	substring str 1 n
)
 
fn SnipString str n = (
	if n <= 1 then (return substring str 1 1)
	local ln = str.count as integer
	if ln <= n then (return str)
	else (
		local half = n / 2.0
		(substring str 1 half) + "~" + (substring str (ln-(half-2)) (half-0.5))
	)
)
 
-- Takes a string with single \'s and returns one with \\'s
fn DoubleSlash str = (
	for i = str.count to 1 by -1 do (
		if str[i] == "\\" then (
			str = replace str i 1 "\\\\"
		)
	)
	return str
)
 
fn ToUpper2 str = (
	try (
		str = copy str
		local lowerLetters = #("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")
		local upperLetters = #("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z")
		for i = 1 to str.count do (
			str[i] = try ( upperLetters[(findItem lowerLetters str[i])] ) catch ( str[i] )
		)
		str
	) catch ( undefined )
)
 
fn ToLower2 str = (
	try (
		str = copy str
		local lowerLetters = #("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z")
		local upperLetters = #("A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z")
		for i = 1 to str.count do (
			str[i] = try ( lowerLetters[(findItem upperLetters str[i])] ) catch ( str[i] )
		)
		str
	) catch ( undefined )
)
 
fn FindString2 str searchString fromIndex:1 lengthIndex:0 caseSensitive:false = (
	if fromIndex > str.count then return undefined
	if lengthIndex == 0 then lengthIndex = (str.count - fromIndex + 1)
	if not caseSensitive then (
		str = toUpper2 str
		searchString = toUpper2 searchString
	)
	for i in fromIndex to (fromIndex + lengthIndex) do
	(
		if str[i] == searchString[1] then
		(
			for j in 0 to (searchString.count-1) do
			(
				if str[i+j] != searchString[j+1] then
					exit
				else
				(
					if (j == (searchString.count-1)) then return i
				)
			)
		)
	)
 
	undefined
)
 
fn SearchReplace str searchText replaceText fromIndex:1 lengthIndex:0 caseSensitive:false =
(
	if str == "" then return str
	if fromIndex > str.count then return undefined
	if lengthIndex == 0 then lengthIndex = (str.count - fromIndex + 1)
	local newStr = copy str
	local x = fromIndex
	local y = lengthIndex
	while (idx = findString2 newStr searchText fromIndex:x lengthIndex:y caseSensitive:caseSensitive) != undefined do (
		newStr = replace newStr idx searchText.count replaceText
		x = idx + replaceText.count
		y -= (replaceText.count - searchText.count)
	)
	return newStr
)
 
fn DateAsPoint3 dateStr = (
	try (
		local ar = filterString dateStr "/ "
		local pnt3 = [0,0,0]
		if (classOf ar == array) then (
			pnt3.x = ar[3] as integer
			pnt3.y = ar[1] as integer
			pnt3.z = ar[2] as integer
 
			-- Workaround for NT/W2K difference in localTime string
			if (pnt3.x < 2000) then pnt3.x += 2000
 
			return pnt3
		) else (
			return undefined
		)
	) catch ( undefined )
)
 
fn DateAsSeconds dateStr = (
	try (
		local strAr = Filterstring dateStr " "
		local timeAr = FilterString strAr[2] ":"
 
		for i in 1 to timeAr.count do timeAr[i] = timeAr[i] as integer
 
		if strAr[strAr.count] == "PM" then timeAr[1] += 12
 
		( (timeAr[1] * 3600) + (timeAr[2] * 60) + (timeAr[3]) )
	) catch ( undefined )
)
 
fn GetTag str start = (
	ret = #(0,0,"")
	for i = start to str.count do (
		if (str[i] == "<") and (str[i+1] == "*") then (
			ret[1] = i
			for j = (i+2) to str.count do (
				if str[j] == "*" and str[j+1] == ">" then (
					ret[2] = j-i+2
					exit
				)
				ret[3] += str[j]
			)
		)
		if ret[1] != 0 then exit
	)
	if ret[1] == 0 then return undefined
	return ret
)
 
fn ReplaceTags str tags tagVals = (
	local lastIdx = 1
	local aTag, tagIdx, tagVal
	while ((aTag = getTag str lastIdx) != undefined) do (
		lastIdx = aTag[1]
		tagIdx = findItem tags (aTag[3] as name)
		tagVal = if tagIdx != 0 then (tagVals[tagIdx] as string) else "UNKNOWNTAG"
		str = replace str aTag[1] aTag[2] tagVal
	)
	return str
)
 
fn GetPadNum num minSize = (
	try (
		local pad = ""
		local numStr = num as string
		local diff = minSize - numStr.count
 
		if diff > 0 then (
			for i in 1 to diff do ( pad += "0" )
		)
 
		return ( pad + numStr )
	) catch ( return "" )
)
 
fn PrintColor str col = (
	local oldCol = outputTextColor
	outputTextColor = col
	print str
	outputTextColor = oldCol
	return str
)
 
fn IsCharInt char = (
	return ((findItem #("1","2","3","4","5","6","7","8","9","0") char) != 0)
)
 
fn Capitalize str =
(
	(ToUpper2 str[1]) + (SubString str 2 (str.count-1))
)
 
fn StringCount str findStr = (
	local cnt = 0
	local idx = 1
	while ((idx = FindString2 str findStr fromIndex:idx) != undefined) do (
		idx += 1
		cnt += 1
	)
	cnt
)
 
fn ColumnFormat fmtStr args argsLen = (
	-- convert all args to strings
	local sargs = for arg in args collect (arg as string)
 
	-- count wildcards in format string, fail if count doesn't match argument count
	local wildCnt = StringCount fmtStr "%"
	if (wildCnt != sargs.count) OR (wildCnt != argsLen.count) then throw "Argument arrays not the proper size\n"
 
	-- loop through wildcards, replacing with args[x] string (snipped to argsLen[x] length)
	local tmpStr = copy fmtStr
	for i in 1 to wildCnt do (
		local wildIdx = FindString tmpStr "%"
		argStr = SubString sargs[i] 1 argsLen[i]
		if (argStr.count < argsLen[i]) do (
			for i = 1 to (argsLen[i] - argStr.count) do argStr += " "
		)
		tmpStr = Replace tmpStr wildIdx 1 argStr
	)
	format "%" tmpStr
)
 
fn GetValidFilename str allowSpaces:FALSE replaceCharacter:"_" =
(
	local validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890-_{}[]`~,.+"
	if allowSpaces do validChars += " "
 
	local outStr = copy str
	for i in 1 to outStr.count do
		outStr[i] = if ((FindString validChars str[i]) == undefined) then
			replaceCharacter
		else
			str[i]
 
	outStr
)
 
---------------------------------------------------------------------
-- System Definitions
---------------------------------------------------------------------
 
fn GetDriveFreeSpace driveLetter = (
	local freeSpace = undefined
 
	local fName = driveLetter + ":\\DskCheck.tmp"
	dosCommand ("dir " + driveLetter + ":>" + fName)
	local f = openFile fName
	if (f != undefined) do (
		local sizeStr
		while not eof f do sizeStr = readLine f
		if (findString sizeStr "free") != undefined do (
			local strArray = filterString sizeStr ", "
			if (strArray.count < 2) do return undefined
			-- delete "X Dir(s)" part in Win2K
			if (strArray[2] == "Dir(s)") do (
				deleteItem strArray 1
				deleteItem strArray 1
			)
			if (strArray[1] as integer) != undefined do (
				local i = 1
				freeSpace = ""
				while (strArray[i] as integer) != undefined do (
					freeSpace += strArray[i]
					i += 1
				)
			)
		)
		close f
	)
	deleteFile fName
 
	return freeSpace
)
 
fn MakeSysInfoFile =
(
	local MAXTRIES = 60
	local BASEPATH = "R:\\MAX3master\\Util\\WinMSD\\"
 
	local tries = 0
	local si = SystemInfo()
 
	local WinMSD =
	local fileName = BASEPATH + (si.computerName) + ".TXT"
 
	local dateA = try (GetFileModDate fileName) catch (undefined)
	if (dateA == undefined) then dateA = "1/1/1970  1:23:45 AM"
 
	local dateB = undefined
 
	ShellLaunch (BASEPATH + "WinMSD.exe") "/s"
 
	while	((IsDateNewer dateB dateA) != true) AND
			(tries < MAXTRIES) do
	(
		sleep 1
		dateB = try (GetFileModDate fileName) catch (undefined)
		tries += 1
	)
 
	if (tries == MAXTRIES) then
	(
		return undefined
	) else (
		return fileName
	)
)
 
fn GetSysInfoProcessors f =
(
	try
	(
		local procs = #() -- array of processors
 
		seek f 0
		skipToString f "Processor list"
		skipToNextLine f
		local str = ""
 
		while ( (str = readLine f)[1] != "-" ) do
		(
			local strAr = filterString str " :~"
			local proc = #()
 
			--speed
			append proc (strAr[(findItem strAr "Mhz")-1] as integer)
			--family
			append proc (strAr[(findItem strAr "Family")+1] as integer)
			--model
			append proc (strAr[(findItem strAr "Model")+1] as integer)
			--stepping
			append proc (strAr[(findItem strAr "Stepping")+1] as integer)
 
			append procs proc
 
			procs
		)
	) catch ( #() )
)
 
fn GetSysInfoPhysicalMemory f =
(
	try
	(
		seek f 0
		skipToString f "Memory Report"
		skipToString f "Physical Memory"
		skipToNextLine f
 
		local str = readLine f
 
		local memAr = filterString str " Total:,"
 
		local mem = ""
		--for i in 1 to (memAr.count-1) do mem += memAr[i]
		for m in memAr do mem += m
 
		mem as integer
	) catch ( 0 )
)
 
fn GetSysInfoFromFile fPath =
(
	local f
	try
	(
		if (fPath != undefined) then
		(
			f = OpenFile fPath
 
			if (f != undefined) then
			(
				local sysInfo = #()
 
				append sysInfo ((SystemInfo()).computerName)
				append sysInfo (GetSysInfoProcessors f)
				append sysInfo (GetSysInfoPhysicalMemory f)
 
				close f
				return sysInfo
			)
		)
 
		return undefined
	) catch ( close f )
)
 
fn GetSysInfo =
(
	local sysInfoFile = MakeSysInfoFile()
 
	if (sysInfoFile != undefined) then
	(
		GetSysInfoFromFile sysInfoFile
	) else (
		undefined
	)
)
 
fn GetProcessorName procCount procFamily procModel =
(
	local procName = case procCount of (
		1: ""
		2: "Dual "
		4: "Quad "
		8: "8 Proc "
		16: "16 Proc "
		default: ""
	)
 
	-- Note: these names are very roughly chosen
	procName += case procFamily of (
		5: (
			"Pentium"
		)
		6: (
			case of (
				(procModel <= 1):		"Pentium Pro"
				(procModel <= 5):		"Pentium 2"
				(procModel == 6):		"Celeron"
				default:				"Pentium 3"
			)
		)
		15: (
			"Pentium 4"
		)
		default: "Unknown Processor"
	)
)
 
struct ServerInfo (
	serverName = "",
	infoVersion = 0,
	numProcessors = 0,
	processorSpeed = 0,
	processorFamily = 0,
	processorModel = 0,
	physicalMemory = 0,
	lockid = 0
)
 
-----------------------------------------------------------------------------------------------------
-- Non-cached ServerStats... original implementation
-----------------------------------------------------------------------------------------------------
struct ServerStats_NoCache (
	serverStatsFile = "R:\\MAX3master\\ServerStats.ini",
	fn GetMasterInfoVersion = (
		local ver = GetINISetting serverStatsFile "ServerStats" "MasterInfoVersion"
		ver as integer
	),
	fn GetServerCount = (
		local cnt = GetINISetting serverStatsFile "ServerStats" "ServerCount"
		cnt as integer
	),
	fn SetServerCount cnt = (
		SetINISetting serverStatsFile "ServerStats" "ServerCount" (cnt as string)
	),
	fn GetServerName idx = (
		local str = GetINISetting serverStatsFile "ServerStats" ("Server" + (idx as string))
		if (str == "") then undefined else str
	),
	fn AddServer serverName = (
		local serverCount = GetServerCount() + 1
		SetServerCount serverCount
		SetINISetting serverStatsFile "ServerStats" ("Server" +  (serverCount as string)) serverName
	),
	fn GetServerInfo serverName = (
		local si = ServerInfo()
 
		-- see if server is in the list
		local serverIdx = 0
		for i in 1 to (GetServerCount()) do (
			if ((GetServerName i) == serverName) then (serverIdx = i; exit)
		)
		-- if not, add it and return empty serverInfo struct
		if (serverIdx == 0) then (
			AddServer serverName
		) else (
		-- otherwise get its info
			si.serverName = serverName
			si.infoVersion = (GetINISetting serverStatsFile serverName "InfoVersion") as integer
			si.numProcessors = (GetINISetting serverStatsFile serverName "NumProcessors") as integer
			si.processorSpeed = (GetINISetting serverStatsFile serverName "ProcessorSpeed") as integer
			si.processorFamily = (GetINISetting serverStatsFile serverName "ProcessorFamily") as integer
			si.processorModel = (GetINISetting serverStatsFile serverName "ProcessorModel") as integer
			si.physicalMemory = (GetINISetting serverStatsFile serverName "PhysicalMemory") as integer
			si.lockid = (GetINISetting serverStatsFile serverName "LockID") as integer
		)
		si
	),
	fn UpdateServerInfo serverName = (
		local servInfo = GetServerInfo serverName
 
		if (servInfo.infoVersion < (GetMasterInfoVersion())) then (
			local sysInfo = GetSysInfo()
			if (sysInfo != undefined) then (
				SetINISetting serverStatsFile serverName "InfoVersion" (GetMasterInfoVersion() as string)
				SetINISetting serverStatsFile serverName "NumProcessors" (sysInfo[2].count as string)
				SetINISetting serverStatsFile serverName "ProcessorSpeed" (sysInfo[2][1][1] as string)
				SetINISetting serverStatsFile serverName "ProcessorFamily" (sysInfo[2][1][2] as string)
				SetINISetting serverStatsFile serverName "ProcessorModel" (sysInfo[2][1][3] as string)
				SetINISetting serverStatsFile serverName "PhysicalMemory" ((sysInfo[3]/1023) as string)
				SetINISetting serverStatsFile serverName "LockID" (hardwareLockID as string)
				SetINISetting serverStatsFile serverName "LastUpdateFailed" "0"
 
				return true
			) else (
				SetINISetting serverStatsFile serverName "LastUpdateFailed" "1"
			)
		)
 
		return false
	)
)
 
-----------------------------------------------------------------------------------------------------
-- Moderate cached ServerStats... just caches the server stats name list
-----------------------------------------------------------------------------------------------------
struct ServerStats_ModerateCache (
	serverStatsFile = "R:\\MAX3master\\ServerStats.ini",
	serverNameCache = #(),
	fn GetMasterInfoVersion = (
		local ver = GetINISetting serverStatsFile "ServerStats" "MasterInfoVersion"
		ver as integer
	),
	fn GetServerCount = (
		local cnt = GetINISetting serverStatsFile "ServerStats" "ServerCount"
		cnt as integer
	),
	fn SetServerCount cnt = (
		SetINISetting serverStatsFile "ServerStats" "ServerCount" (cnt as string)
	),
	fn GetServerName idx = (
		local str = GetINISetting serverStatsFile "ServerStats" ("Server" + (idx as string))
		if (str == "") then undefined else str
	),
	fn UpdateNameCache = (
		local serverCount = GetServerCount()
		serverNameCache = for i in 1 to serverCount collect (GetServerName i)
 
		OK
	),
	fn AddServer serverName = (
		local serverCount = GetServerCount() + 1
		SetServerCount serverCount
		SetINISetting serverStatsFile "ServerStats" ("Server" +  (serverCount as string)) serverName
	),
	fn GetServerInfo serverName = (
		local si = ServerInfo()
 
		if (serverNameCache.count == 0) then UpdateNameCache()
 
		-- see if server is in the list
		local serverIdx = 0
		local serverCount = GetServerCount()
		for i in 1 to serverCount do (
			if (serverNameCache[i] == serverName) then (serverIdx = i; exit)
		)
		-- if not, add it and return empty serverInfo struct
		if (serverIdx == 0) then (
			AddServer serverName
		) else (
		-- otherwise get its info
			si.serverName = serverName
			si.infoVersion = (GetINISetting serverStatsFile serverName "InfoVersion") as integer
			si.numProcessors = (GetINISetting serverStatsFile serverName "NumProcessors") as integer
			si.processorSpeed = (GetINISetting serverStatsFile serverName "ProcessorSpeed") as integer
			si.processorFamily = (GetINISetting serverStatsFile serverName "ProcessorFamily") as integer
			si.processorModel = (GetINISetting serverStatsFile serverName "ProcessorModel") as integer
			si.physicalMemory = (GetINISetting serverStatsFile serverName "PhysicalMemory") as integer
			si.lockid = (GetINISetting serverStatsFile serverName "LockID") as integer
		)
		si
	),
	fn UpdateServerInfo serverName = (
		local servInfo = GetServerInfo serverName
 
		if (servInfo.infoVersion < (GetMasterInfoVersion())) then (
			local sysInfo = GetSysInfo()
			if (sysInfo != undefined) then (
				SetINISetting serverStatsFile serverName "InfoVersion" (GetMasterInfoVersion() as string)
				SetINISetting serverStatsFile serverName "NumProcessors" (sysInfo[2].count as string)
				SetINISetting serverStatsFile serverName "ProcessorSpeed" (sysInfo[2][1][1] as string)
				SetINISetting serverStatsFile serverName "ProcessorFamily" (sysInfo[2][1][2] as string)
				SetINISetting serverStatsFile serverName "ProcessorModel" (sysInfo[2][1][3] as string)
				SetINISetting serverStatsFile serverName "PhysicalMemory" ((sysInfo[3]/1023) as string)
				SetINISetting serverStatsFile serverName "LockID" (hardwareLockID as string)
				SetINISetting serverStatsFile serverName "LastUpdateFailed" "0"
 
				return true
			) else (
				SetINISetting serverStatsFile serverName "LastUpdateFailed" "1"
			)
		)
 
		return false
	)
)
 
-----------------------------------------------------------------------------------------------------
-- Aggressive cached ServerStats... copies serverStatsFile local + server name cache
-----------------------------------------------------------------------------------------------------
struct ServerStats_HeavyCache (
	masterServerStatsFile = "R:\\MAX3master\\ServerStats.ini",
	localServerStatsFile = (GetDir #plugcfg) + "\\ServerStats.ini",
	serverNameCache = #(),
	fn IncrementUpdateNumber = (
		local ver = (GetINISetting masterServerStatsFile "UpdateNumber" "UpdateNumber") as integer
		ver += 1
		SetINISetting masterServerStatsFile "UpdateNumber" "UpdateNumber" (ver as string)
	),
	fn GetMasterInfoVersion = (
		local ver = GetINISetting masterServerStatsFile "ServerStats" "MasterInfoVersion"
		ver as integer
	),
	fn GetServerCount = (
		local cnt = GetINISetting masterServerStatsFile "ServerStats" "ServerCount"
		cnt as integer
	),
	fn SetServerCount cnt = (
		SetINISetting masterServerStatsFile "ServerStats" "ServerCount" (cnt as string)
		IncrementUpdateNumber()
	),
	fn GetServerName idx = (
		local str = GetINISetting localServerStatsFile "ServerStats" ("Server" + (idx as string))
		if (str == "") then undefined else str
	),
	fn UpdateNameCache = (
		local serverCount = GetServerCount()
		serverNameCache = for i in 1 to serverCount collect (GetServerName i)
 
		OK
	),
	fn AddServer serverName = (
		local serverCount = GetServerCount() + 1
		SetServerCount serverCount
		SetINISetting masterServerStatsFile "ServerStats" ("Server" +  (serverCount as string)) serverName
		IncrementUpdateNumber()
	),
	fn GetServerInfo serverName = (
		local si = ServerInfo()
 
		if (serverNameCache.count == 0) then UpdateNameCache()
 
		-- see if server is in the list
		local serverIdx = 0
		local serverCount = GetServerCount()
		for i in 1 to serverCount do (
			if (serverNameCache[i] == serverName) then (serverIdx = i; exit)
		)
		-- if not, add it and return empty serverInfo struct
		if (serverIdx == 0) then (
			AddServer serverName
		) else (
		-- otherwise get its info
			si.serverName = serverName
			si.infoVersion = (GetINISetting localServerStatsFile serverName "InfoVersion") as integer
			si.numProcessors = (GetINISetting localServerStatsFile serverName "NumProcessors") as integer
			si.processorSpeed = (GetINISetting localServerStatsFile serverName "ProcessorSpeed") as integer
			si.processorFamily = (GetINISetting localServerStatsFile serverName "ProcessorFamily") as integer
			si.processorModel = (GetINISetting localServerStatsFile serverName "ProcessorModel") as integer
			si.physicalMemory = (GetINISetting localServerStatsFile serverName "PhysicalMemory") as integer
			si.lockid = (GetINISetting localServerStatsFile serverName "LockID") as integer
		)
		si
	),
	fn SyncServerStatsFile = (
		local localVer = (GetINISetting localServerStatsFile "UpdateNumber" "UpdateNumber") as integer
		local masterVer = (GetINISetting masterServerStatsFile "UpdateNumber" "UpdateNumber") as integer
		if (localVer != masterVer) then
		(
			DeleteFile localServerStatsFile
			local res = CopyFile masterServerStatsFile localServerStatsFile
			if (NOT res) then
			(
				MessageBox "Something fucked up in Sync'ing local stats file.\n\nComplain to John B." title:"Error"
			)
		)
 
		OK
	),
	fn UpdateServerInfo serverName = (
		-- Sync cached stat file (GetServerInfo below uses it)
		SyncServerStatsFile()
 
		-- Check if stat info is out of date, and needs to be updated
		local servInfo = GetServerInfo serverName
 
		local res = TRUE
		if (servInfo.infoVersion < (GetMasterInfoVersion())) then
		(
			local sysInfo = GetSysInfo()
			res = if (sysInfo != undefined) then
			(
				SetINISetting masterServerStatsFile serverName "InfoVersion" (GetMasterInfoVersion() as string)
				SetINISetting masterServerStatsFile serverName "NumProcessors" (sysInfo[2].count as string)
				SetINISetting masterServerStatsFile serverName "ProcessorSpeed" (sysInfo[2][1][1] as string)
				SetINISetting masterServerStatsFile serverName "ProcessorFamily" (sysInfo[2][1][2] as string)
				SetINISetting masterServerStatsFile serverName "ProcessorModel" (sysInfo[2][1][3] as string)
				SetINISetting masterServerStatsFile serverName "PhysicalMemory" ((sysInfo[3]/1023) as string)
				SetINISetting masterServerStatsFile serverName "LockID" (hardwareLockID as string)
				SetINISetting masterServerStatsFile serverName "LastUpdateFailed" "0"
 
				TRUE
			) else (
				SetINISetting masterServerStatsFile serverName "LastUpdateFailed" "1"
				FALSE
			)
 
			-- We've written to the master, update the UpdateNumber
			IncrementUpdateNumber()
		)
 
		-- Re-sync cached stat file, in case something above changed
		SyncServerStatsFile()
 
		return res
	)
)
 
--ServerStats = ServerStats_NoCache
--ServerStats = ServerStats_ModerateCache
ServerStats = ServerStats_HeavyCache
 
fn PrintMachineStats_SortServerInfo a b sortKey: = (
	local ap = GetProperty a sortKey
	local bp = GetProperty b sortKey
	return (
		if (ap == bp) then 0
		else if (ap < bp) then -1
		else 1
	)
)
 
fn PrintMachineStats sortBy:#serverName = (
 
	local ss = ServerStats()
 
	local masterVersion = ss.GetMasterInfoVersion()
 
	local servers = for i in 1 to ss.GetServerCount() collect
	(
		local sName = ss.GetServerName i
		ss.GetServerInfo sName
	)
 
	qSort servers PrintMachineStats_SortServerInfo sortKey:sortBy
 
	Format " Name                Type                Speed  Memory    LockID    \n"
	for si in servers do (
		local fmtStr = if (si.infoVersion < masterVersion) then "*%%%%%\n" else " %%%%%\n"
		ColumnFormat fmtStr #(	si.serverName,
								(GetProcessorName si.numProcessors si.processorFamily si.processorModel),
								si.processorSpeed,
								si.physicalMemory,
								si.lockid) #(20,20,7,10,10)
	)
	OK
)
 
---------------------------------------------------------------------
-- Time Definitions
---------------------------------------------------------------------
 
fn GetFormattedTime totSec = (
	local tmp, h, m, s
 
	h = (totSec / 3600) as integer
	tmp = mod totSec 3600
	m = (tmp / 60) as integer
	s = (mod tmp 60) as integer
 
	return ( (h as string) + "h:" + (m as string) + "m:" + (s as string) + "s" )
)
 
struct LapTimer (
	lapTimes = #(),
	totTime = 0,
	days = 0,
	startTime = undefined,
	endTime = undefined,
 
	fn Reset = (
		lapTimes = #()
		totTime = 0
		days = 0
		startTime = undefined
		endTime = undefined
	),
 
	fn Start = (
		startTime = TimeStamp()
	),
 
	fn CleanStart = (
		gc()
		Start()
	),
 
	fn GetLastTime = (
		if (lapTimes.count >= 1) then (
			lapTimes[lapTimes.count]
		) else (
			-1
		)
	),
 
	fn GetLapTime n = (
		if (n >= 1) AND (n <= lapTimes.count) then (
			lapTimes[n]
		) else (
			-1
		)
	),
 
	fn GetAllTimes = (
		lapTimes
	),
 
	fn GetNumLaps = (
		lapTimes.count
	),
 
	fn GetTotalTime = (
		totTime
	),
 
	fn Lap = (
		if (startTime != undefined) then
		(
			endTime = TimeStamp()
 
			-- see if we've timed over midnight
			if (endTime < startTime) then (
				days += 1
				endTime += days * 86400000
			)
			local lastTime = ((endTime - startTime) / 1000.0) - totTime
			append lapTimes lastTime
			totTime += lastTime
 
			lastTime
		) else (
			0
		)
	),
 
	fn Stop = (
		Lap()
		startTime = undefined
		GetTotalTime()
	)
)
 
fn IsDateNewer dateStrA dateStrB =
(
	try
	(
		local dateA = DateAsPoint3 dateStrA
		local dateB = DateAsPoint3 dateStrB
 
		-- check for same date
		if (dateA == dateB) then
		(
			-- if same, compare seconds
			local secA = DateAsSeconds dateStrA
			local secB = DateAsSeconds dateStrB
 
			return (secA > secB)
		) else (
			-- if different, see which has the newer date
			if (dateA.x == dateA.x) then (
				if (dateA.y == dateA.y) then (
					return (dateA.z > dateB.z)
				) else (
					return (dateA.y > dateB.y)
				)
			) else (
				return (dateA.x > dateB.x)
			)
		)
	) catch ( undefined )
)
 
fn GetCurrentMilitaryTime =
(
	try (
		local ar = FilterString localTime " "
		local isPM = ar[3] == "PM"
		ar = FilterString ar[2] ":"
 
		for i in 1 to ar.count do ar[i] = ar[i] as integer
		if (isPM) then ar[1] += 12
 
		ar
	) catch (
		undefined
	)
)
 
fn TimeIt expr iter runs:3 =
(
	local lt = LapTimer()
	local runTimes = for ri in 1 to runs collect
	(
		lt.CleanStart()
		for i in 1 to iter do expr
		local t = lt.Stop()
		format "Run %: %\n" ri t
		t
	)
 
	local aveTime = 0.
	for t in runTimes do aveTime += t
	aveTime /= runs
	format "Average: %\n" aveTime
 
	runTimes
)
 
---------------------------------------------------------------------
-- UI Definitions
---------------------------------------------------------------------
 
fn ValidListboxSel lb = (
	(lb.selection >= 1) AND
	(lb.selection <= lb.items.count)
)
 
global jbCommandPanelTaskModeStack = #()
 
fn PushCommandPanelTaskMode panelMode =
(
	local panelState = GetCommandPanelTaskMode()
	append jbCommandPanelTaskModeStack panelState
	if (panelState != panelMode) then (
		SetCommandPanelTaskMode mode:panelMode
		return TRUE
	)
	FALSE
)
 
fn PopCommandPanelTaskMode =
(
	local stackCount = jbCommandPanelTaskModeStack.count
	if (stackCount != 0) then (
		local stackState = jbCommandPanelTaskModeStack[stackCount]
		local panelState = GetCommandPanelTaskMode()
		if (stackState != panelState) then
			SetCommandPanelTaskMode mode:stackState
		DeleteItem jbCommandPanelTaskModeStack stackCount
		return TRUE
	)
	FALSE
)
 
fn IntersectPickPoint obj pnt = (
	local r = Ray [0,0,0] [1,0,0]
 
	if (gw.IsPerspView()) then (
		local viewPos = (Inverse(getViewTM())).row4
		r.pos = viewPos
		r.dir = Normalize (pnt-viewPos)
	) else (
	   local coordSysTM = Inverse(getViewTM())
	   local viewDir = coordSysTM.row3
	   r.pos = pnt + (viewDir * 9999999)
	   r.dir = -viewDir
	)
 
	IntersectRay obj r
)
 
-- EOF ------------------------------------ Thanks For Visiting... --

Open in new window

Final BFDtools-Bake.mcr:
macroScript Bake
category:"BFDtools"
buttontext:"Bake"
tooltip:"Bake - Bake a selection of objects"
icon:#("BFDtools-Icons",2)
(
 
------------------------------------------------------------------------------------------
-- Contents:
--		Bake - Bakes a selection of object properties (mesh, transform, ...)
--
-- Requires:
--		Avg_dlx.dlx, v2.02
--		jbFunctions.ms
------------------------------------------------------------------------------------------
--To add a new bake type:
--	Define a new rollout immediately beneath the other bake type rollouts
--	Append the rolloutNameList and rolloutList arrays to register the bake type
--	Append the rolloutSizeList array with the vertical size of your rollout
--	In your rollout, define a function called bake that will actually do the baking
--		This function can reference the Bake local variables like start and end times, etc.
--		The bake function should obey all options in the main Bake rollout
--		It is passed two arguments:
--			An array of objects to bake
--			An empty array to be filled with the baked objects
--			The first array should be trimmed by the bake function to
--			remove any objects that were not baked for whatever reason.
--		It should RETURN true if success or false if it failed
------------------------------------------------------------------------------------------
-- TODO: step through with timeslider instead of sample at random time points,
--		for solutions that depend on solving from frame 0?
------------------------------------------------------------------------------------------
 
if (
	if (jbFunctionsCurrentVersion == undefined OR (jbFunctionsCurrentVersion() < 11)) then (
		local str = "This script requires jbFunctions to run properly.\n\nYou can get the latest version at http://www.johnburnett.com/.\n\nWould you like to connect there now?"
		if (QueryBox str title:"Error") then ( try (ShellLaunch "http://www.johnburnett.com/" "") catch () )
		FALSE
	) else (
		jbFunctionsVersionCheck #( #("jbFunctions",11), #("avg_dlx",2.02) )
	)
) then (
 
	-- Change this path to point to where the script is installed!
	local bakePath = (getDir #ui) + "\\macroscripts"
 
	local thisTool = BFDtool	toolName:"Bake" 			\
								author:"John Burnett"		\
								createDate:[11,11,1999]		\
								modifyDate:[5,21,2001]		\
								version:1					\
								defFloaterSize:[220,577]	\
								autoLoadRolloutStates:false	\
								autoLoadFloaterSize:false
								--defFloaterSize:[220,543]\
 
	
	local OORBitmap = try (openBitmap (bakePath + "\\Bake\\BFDtools-Bake_Buttons.bmp")) catch (bitmap 168 18 color:green)
	local OORTypeBitmaps = #()
	for btnIdx in 1 to 6 do (
		OORTypeBitmaps[btnIdx] = bitmap 28 18
		for y in 0 to 17 do (
			local row = getPixels OORBitmap [(btnIdx-1) * 28, y] 28
			setPixels OORTypeBitmaps[btnIdx] [0, y] row
		)
	)
 
 
	-- Local variables available to all bake types ------------------------------------
	local startTime = ((animationRange.start as float/ticksPerFrame) as integer)
	local endTime = ((animationRange.end as float/ticksPerFrame) as integer)
	local nthFrame = 1
	local delOrig = false
	local addSuffix = true
	local selBaked = true
	local bakeXRef = false
	local bakeSeparateFiles = true
	local useScenePath = true
	local bakeXRefPath = "" --maxFilePath
	local bakeType = 2
	local rolloutNameList, rolloutList, rolloutSizeList
	local InOORType = 1
	local OutOORType = 1
	local OORTypes = #("Constant","Cycle","Loop","PingPong","Linear","RelativeRepeat")
	-----------------------------------------------------------------------------------
 
	fn getValidMeshes objs = (
		local meshObjs = #()
		for obj in objs do (
			if (canConvertTo obj mesh) then append meshObjs obj
		)
		return meshObjs
	)
 
	fn defaultTransforms obj = (
		obj.transform.controller = prs()
		deleteKeys obj.transform.controller #allKeys
 
		obj.position.controller = bezier_position()
		deleteKeys obj.position.controller #allKeys
		setBeforeORT obj.position.controller #constant
		setAfterORT obj.position.controller #constant
 
		obj.rotation.controller = tcb_rotation()
		deleteKeys obj.rotation.controller #allKeys
		setBeforeORT obj.rotation.controller #constant
		setAfterORT obj.rotation.controller #constant
 
		obj.scale.controller = bezier_scale()
		deleteKeys obj.scale.controller #allKeys
		setBeforeORT obj.scale.controller #constant
		setAfterORT obj.scale.controller #constant
	)
 
	fn zeroObjTransforms obj = (
		defaultTransforms obj
		obj.objectOffsetScale = [1,1,1]
		obj.objectOffsetRot = quat 0 0 0 1
		obj.objectOffsetPos = [0,0,0]
		obj.transform = (matrix3 1)
	)
 
	-- Get an object snapshot in the specified coordSys space
	fn getBakeSnap obj snapSpace t = (
		local snap = copy obj
		snap.name = "Frame" + (getPadNum t 4) as string
		convertToMesh snap
		-- kill vertex animation on snap
		if (snap[4][1].keys.count != 0) do (DeleteKeys snap[4][1].controller #allKeys)
 
		case snapSpace of (
			#world: (
				snap.parent = undefined
				zeroObjTransforms snap
				snap.mesh = at time t snapshotAsMesh obj
			)
			#object: (
				snap.parent = obj.parent
				snap.mesh = at time t obj.mesh
			)
		)
 
		update snap
		return snap
	)
 
	fn killCloth objs = (
		local oldSel = selection as array
		local success = true
		local obj, m, failObj
		progressStart "Checking objects..."
		undo on (
			for i in 1 to objs.count do (
				if not (progressUpdate (i as float/objs.count*100)) then (
					max undo
					select oldSel
					return()
				)
				obj = objs[i]
				for j in obj.modifiers.count to 1 by -1 do (
					m = obj.modifiers[j]
					if (classOf m) == ClothReyes_3 then (
						try (
							deleteModifier obj j
						) catch (
							success = false
							failObj = obj
						)
						--format "obj: %, m:%, j:%, success:%\n" obj.name m j success
					)
				)
			)
		)
		progressEnd()
		select oldSel
		if success == false then (
			str = "Unexpected error occured while attempting to delete
cloth on object" + failObj.name + "
 
Without making any sudden movements, calmly go tell John."
			messageBox str title:"Oops"
		)
		return success
	)
 
	fn CollapsePointCache obj = (
		local idx = 0
		local pc = undefined
		for i in obj.modifiers.count to 1 by -1 do (
			local mc = ClassOf obj.modifiers[i]
			if (mc == Point_Cache_2 OR
				mc == Point_Cache_2SpacewarpModifier) then (
				idx = i
				pc = copy obj.modifiers[i]
				exit
			)
		)
 
		if (idx != 0) then (
			ConvertToMesh obj
			-- kill vertex animation on object
			if (obj[4][1].keys.count != 0) do (DeleteKeys obj[4][1].controller #allKeys)
 
			if (ClassOf pc == Point_Cache_2) then (
				AddModifier obj pc
			)
			return TRUE
		)
 
		return FALSE
	)
--------------------------------------------------------------------------------------------------
	rollout DLGmeshRollout "Mesh Animation" (
		local outputType
		local bakeSpace
		local bakeSpaces
		local subAnimList
 
		fn updateUI = (
			DLGmeshRollout.DLGbakeSpace.state = bakeSpace
			DLGmeshRollout.DLGoutputType.state = outputType
		)
 
		fn bake sourceObjs bakedObjs = (
			-- Grab valid bake objects
			local srcObjs = getValidMeshes sourceObjs
 
			-- Trim out any invalid bake objects
			for i in sourceObjs.count to 1 by -1 do (
				if (findItem srcObjs sourceObjs[i]) == 0 then deleteItem sourceObjs i
			)
 
			case outputType of (
				-- Morph Object
				1: (
					local morphObjs = #(); morphObjs.count = srcObjs.count
 
					-- Create initial morph objects
					for i in 1 to srcObjs.count do (
						local obj = srcObjs[i]
 
						morphObj = getBakeSnap obj bakeSpaces[bakeSpace] startTime
						morphObj.name = obj.name
						if addSuffix then morphObj.name += "_MESHBAKE"
 
						createMorphObject morphObj
						setMorphTargetName morphObj.morph 1 ("Frame" + (getPadNum startTime 4))
						addNewKey morphObj.morph startTime
 
						morphObjs[i] = morphObj
					)
 
					-- Add all snapshot targets to the base morph objects
					progressStart ("(Step 1/2) Gathering Snapshots")
					for t in (startTime + nthFrame) to endTime by nthFrame do (
						for i in 1 to srcObjs.count do (
							local snap = getBakeSnap srcObjs[i] bakeSpaces[bakeSpace] t
							addMorphTarget morphObjs[i].morph snap 3
							local numTargets = (getMKTargetNames morphObjs[i].morph).count
							setMorphTargetName morphObjs[i].morph numTargets ("Frame" + (getPadNum t 4))
						)
						progressUpdate ((t - startTime) as float/(endTime - startTime) * 100)
					)
					progressEnd()
 
					progressStart("(Step 2/2) Morphing")
					-- Get rid of morph keys made when the targets were added
					for obj in morphObjs do (
						deleteKeys obj.morph.controller #allKeys
					)
					-- Go through and add the final morph keys
					local targetIdx = 1
					for t in startTime to endTime by nthFrame do (
						for obj in morphObjs do (
							-- add a key and set the key value to the new target
							addNewKey obj.morph.controller t
							setMKWeight (getMKKey obj.morph.controller t) targetIdx 100 true
						)
						targetIdx += 1
						progressUpdate ((t - startTime) as float/(endTime - startTime) * 100)
					)
					for obj in morphObjs do (
						setBeforeORT obj.morph.controller (OORTypes[InOORType] as name)
						setAfterORT obj.morph.controller (OORTypes[OutOORType] as name)
					)
					progressEnd()
					bakedObjs.count = morphObjs.count
					for i in 1 to morphObjs.count do bakedObjs[i] = morphObjs[i]
				)
				-- Vertex Animation
				2: (
					for srcIdx in 1 to srcObjs.count do (
						local sourceObj = srcObjs[srcIdx]
 
						local targObj = snapshot sourceObj
						targObj.parent = sourceObj.parent
						targObj.transform.controller = sourceObj.transform.controller
 
						if addSuffix then targObj.name += "_VERTBAKE"
						animateVertex targObj #all
						local masterCtrl = targObj[4][1]	-- shortcut to vertex controllers
 
						-- assign OOR types
						for i in 1 to targObj.numVerts do (
							setBeforeORT masterCtrl[i].controller (OORTypes[InOORType] as name)
							setAfterORT masterCtrl[i].controller (OORTypes[OutOORType] as name)
						)
 
						progressStart ("Baking " + (srcIdx as string) + " of " + (srcObjs.count as string) + "...")
 
						for t in startTime to endTime by nthFrame do
						(
							local k								-- key added for each vert
 
							for i in 1 to targObj.numVerts do
							(
								k = addNewKey masterCtrl[i].controller t
								k.value = at time t in coordSys sourceObj getVert sourceObj i
							)
 
							local cont = progressUpdate ((t - startTime) as float/(endTime - startTime) * 100)
							if NOT cont then return true
						)
 
						progressEnd()
 
						append bakedObjs targObj
					)
				)
			)
			return true
		)
 
--			case outputType of (
--				-- Morph Object
--				1: (
--					-- loop through source objects and create base morph objects
--					local morphObjs = #()
--
--					for obj in srcObjs do (
--						-- Snapshot world state of the mesh
--						local msh = at time startTime snapshotAsMesh obj
--
--						-- Copy source to keep any extra props intact (materials, obj properties, etc)
--						local morphObj = copy obj
--						morphObj.name = if addSuffix then ( obj.name + "_MESHBAKE" ) else ( obj.name )
--						morphObj.parent = undefined
--						convertToMesh morphObj
--
--						-- Zero the transforms
--						zeroObjTransforms morphObj
--
--						-- Set the morph object's mesh to the world state mesh of the source
--						morphObj.mesh = msh
--						update morphObj
--
--						-- Convert to a morph object
--						createMorphObject morphObj
--						setMorphTargetName morphObj.morph 1 ("Frame" + (getPadNum startTime 4))
--						addNewKey morphObj.morph startTime
--
--						-- Add morph object to list of final morph objects
--						append morphObjs morphObj
--					)
--
--					progressStart ("(Step 1/2) Gathering Snapshots")
--					-- Add all snapshot targets to the base morph objects
--					local snap = mesh numVerts:0 numFaces:0 -- temp object for morph targets to fill with snapshot mesh
--					-- loop through time range and morph objects
--					for t in (startTime+nthFrame) to endTime by nthFrame do (
--						for i in 1 to srcObjs.count do (
--							-- grab mesh at time t
--							snap.mesh = at time t snapshotAsMesh srcObjs[i]
--							update snap
--							-- add it as a morph target, set the name
--							addMorphTarget morphObjs[i].morph snap 2
--							local numTargets = (getMKTargetNames morphObjs[i].morph).count
--							setMorphTargetName morphObjs[i].morph numTargets ("Frame" + (getPadNum t 4))
--						)
--						progressUpdate ((t - startTime) as float/(endTime - startTime) * 100)
--					)
--					delete snap
--					progressEnd()
--
--					progressStart("(Step 2/2) Morphing")
--					-- Get rid of morph keys made when the targets were added
--					for obj in morphObjs do (
--						deleteKeys obj.morph.controller #allKeys
--					)
--					-- Go through and add the final morph keys
--					local targetIdx = 1
--					for t in startTime to endTime by nthFrame do (
--						for obj in morphObjs do (
--							-- add a key and set the key value to the new target
--							addNewKey obj.morph.controller t
--							setMKWeight (getMKKey obj.morph.controller t) targetIdx 100 true
--						)
--						targetIdx += 1
--						progressUpdate ((t - startTime) as float/(endTime - startTime) * 100)
--					)
--					progressEnd()
--					bakedObjs = morphObjs
--				)
--				-- Separate objects
--				2: (
--					local numFrames = ((endTime - startTime) as float / nthFrame) as integer
--					progressStart ("Baking over " + numFrames as string + " frames...")
--					for t in startTime to endTime by nthFrame do (
--						for obj in srcObjs do (
--							progressUpdate ((t - startTime) as float / (endTime - startTime) * 100)
--							local snap = copy obj
--							convertToMesh snap
--							snap.parent = undefined
--							zeroObjTransforms snap
--							snap.mesh = at time t snapshotAsMesh obj
--							snap.name = obj.name + "_Frame" + (getPadNum t 4)
--							append bakedObjs snap
--						)
--					)
--					progressEnd()
--				)
--				-- Vertex List
--				3: (
--					--point pos:$.verts[1].pos
--					--point pos:((getVert $.mesh 1)*$.objectTransform)
--				)
--			)
--
--			if delOrig then delete srcObjs
--			return bakedObjs
--		)
 
		label DLGbakeSpaceLabel "Bake Space:" align:#left
		radiobuttons DLGbakeSpace labels:#("Object","World") columns:1 align:#left offset:[10,0] enabled:false
		label DLGoutputTypeLabel "Output To:" align:#left
		radiobuttons DLGoutputType labels:#("Morph Object","Vertex Animation") align:#left offset:[10,0]
 
		on DLGbakeSpace changed state do
		(
			bakeSpace = state
			updateUI()
		)
 
		on DLGoutputType changed state do
		(
			outputType = state
			-- temporary, since morphs only does world and vertex only does local for now
			bakeSpace = (3 - state)
 
			updateUI()
		)
 
		on DLGmeshRollout open do
		(
			outputType = 1
			bakeSpace = 2
			bakeSpaces = #(#object,#world)
			subAnimList = false
 
			updateUI()
		)
	)
 
--------------------------------------------------------------------------------------------------
	rollout DLGobjTransRollout "Object Transform" (
		local keyTypes
		local bakePos
		local bakeRot
		local bakeScale
		local posKeyType
		local rotKeyCont
		local scaleKeyType
		local unlink
 
		fn updateUI = (
			DLGobjTransRollout.DLGbakePos.checked = bakePos
			DLGobjTransRollout.DLGbakeRot.checked = bakeRot
			DLGobjTransRollout.DLGbakeScale.checked = bakeScale
			DLGobjTransRollout.DLGposKeyType.selection = posKeyType
			DLGobjTransRollout.DLGrotKeyCont.value = rotKeyCont
			DLGobjTransRollout.DLGscaleKeyType.selection = scaleKeyType
			DLGobjTransRollout.DLGunlink.checked = unlink
		)
 
		fn bake sourceObjs bakedObjs = (
			-- Trim any objects that are invalid for baking
			for i in sourceObjs.count to 1 by -1 do (
				local obj = sourceObjs[i]
				local trimIt = false
 
				--Biped objects and "finger dummies"
				trimIt = trimIt OR (classOf obj == Biped_Object)
				trimIt = trimIt OR ((classOf obj == Dummy) AND (obj.transform.controller == undefined))
				--Bone objects with IK controllers
				trimIt = trimIt OR ((classOf obj == Bone) AND (obj.transform.controller == IK_ControllerMatrix3Controller))
 
				if trimIt then deleteItem sourceObjs i
			)
 
			-- Create the bake objects
			bakedObjs[sourceObjs.count] = undefined
			for i in 1 to sourceObjs.count do (
				-- Set up initial baked objects
				bakedObjs[i] = copy sourceObjs[i]
				bakedObjs[i].name = sourceObjs[i].name
				if addSuffix then bakedObjs[i].name += "_TRANSBAKE"
 
				defaultTransforms bakedObjs[i]
			)
 
			-- Set up parenting
			for i in 1 to bakedObjs.count do (
				if unlink then (
					bakedObjs[i].parent = undefined
				) else (
					-- Get original parent
					local theParent = sourceObjs[i].parent
					-- See if parent is being baked as well
					local idx = findItem sourceObjs theParent
					-- If it is, then baked object should use the baked version as a parent
					if idx != 0 then theParent = bakedObjs[idx]
					bakedObjs[i].parent = theParent
				)
			)
 
			progressStart ("Baking Transforms...")
 
--			local lastRot = #()
--			for i in 1 to bakedObjs.count do lastRot[i] = quat 1
 
			for obj in bakedObjs do (
				addNewKey obj.position.controller startTime
				addNewKey obj.rotation.controller startTime
				addNewKey obj.scale.controller startTime
			)
 
			for t in startTime to endTime by nthFrame do (
				for i in 1 to bakedObjs.count do (
--					if bakeRot then (
----						local k = addNewKey bakedObjs[i].rotation.controller t
----						local aRot = at time t sourceObjs[i].rotation
----						local rRot = aRot - lastRot[i]
----						k.value = rRot as angleAxis
----						lastRot[i] = aRot
--					)
--					if bakePos then (
--						local k = addNewKey bakedObjs[i].pos.controller t
--						k.value = at time t sourceObjs[i].pos
--					)
--					if bakeScale then (
--						local k = addNewKey bakedObjs[i].scale.controller t
--						k.value = at time t sourceObjs[i].scale
--					)
					animate on at time t bakedObjs[i].transform = at time t sourceObjs[i].transform
				)
				progressUpdate ((t - startTime) as float/(endTime - startTime) * 100)
			)
			for obj in bakedObjs do (
				-- remove unwanted keys
				if NOT bakePos then (
					local savePos = at time startTime obj.pos
					deleteKeys obj.position.controller #allKeys
					at time startTime obj.pos = savePos
				)
				if NOT bakeRot then (
					local saveRot = at time startTime obj.rotation
					deleteKeys obj.rotation.controller #allKeys
					at time startTime obj.rotation = saveRot
				)
				if NOT bakeScale then (
					local saveScale = at time startTime obj.scale
					deleteKeys obj.scale.controller #allKeys
					at time startTime obj.scale = saveScale
				)
 
				-- Set Out Of Range types
				if bakePos then (
					setBeforeORT obj.position.controller (OORTypes[InOORType] as name)
					setAfterORT obj.position.controller (OORTypes[OutOORType] as name)
				)
				if bakeRot then (
					setBeforeORT obj.rotation.controller (OORTypes[InOORType] as name)
					setAfterORT obj.rotation.controller (OORTypes[OutOORType] as name)
				)
				if bakeScale then (
					setBeforeORT obj.scale.controller (OORTypes[InOORType] as name)
					setAfterORT obj.scale.controller (OORTypes[OutOORType] as name)
				)
 
				-- Set key tangents
				for k in obj.position.keys do (
					k.inTangentType = keyTypes[posKeyType] as name
					k.outTangentType = keyTypes[posKeyType] as name
				)
				for k in obj.rotation.keys do (
					--format "time: %, value: %, [t,c,b]: [%,%,%], in: %, out: %\n" k.time k.value k.tension k.continuity k.bias k.easeTo k.easeFrom
					--print k
					k.continuity = rotKeyCont
					--format "time: %, value: %, [t,c,b]: [%,%,%], in: %, out: %\n" k.time k.value k.tension k.continuity k.bias k.easeTo k.easeFrom
				)
				for k in obj.scale.keys do (
					k.inTangentType = keyTypes[scaleKeyType] as name
					k.outTangentType = keyTypes[scaleKeyType] as name
				)
			)
			progressEnd()
 
			return true
		)
 
		group "Bake" (
			checkbox DLGbakePos "Position" align:#left
			dropdownlist DLGposKeyType "" width:80 offset:[90,-24]
			checkbox DLGbakeRot "Rotation" align:#left
			spinner DLGrotKeyCont "Cont:" range:[0,50,25] type:#float width:70 offset:[22,-22]
			checkbox DLGbakeScale "Scale" align:#left offset:[0,5]
			dropdownlist DLGscaleKeyType "" width:80 offset:[90,-24]
		)
		group "Options" (
			checkbox DLGunlink "Unlink From Parent" align:#left
		)
		on DLGbakePos changed state do ( bakePos = state; updateUI() )
		on DLGposKeyType selected idx do ( posKeyType = idx; updateUI() )
		on DLGbakeRot changed state do ( bakeRot = state; updateUI() )
		on DLGbakeScale changed state do ( bakeScale = state; updateUI() )
		on DLGscaleKeyType selected idx do ( scaleKeyType = idx; updateUI() )
		on DLGunlink changed state do ( unlink = state; updateUI() )
 
		on DLGobjTransRollout open do (
			keyTypes = #("smooth","linear","step","fast","slow","custom")
			DLGposKeyType.items = keyTypes
			DLGscaleKeyType.items = keyTypes
			bakePos = true
			bakeRot = true
			bakeScale = false
			posKeyType = 2
			rotKeyCont = 0.0
			scaleKeyType = 2
			unlink = true
 
			updateUI()
		)
	)
 
--------------------------------------------------------------------------------------------------
	rollout DLGffdRollout "FFD Spacewarp Animation" (
		fn bake sourceObjs bakedObjs = (
			print "baking FFD"
		)
		label DLGnoneLabel "unfinished"
	)
 
--------------------------------------------------------------------------------------------------
	rollout DLGshapeRollout "Shape Animation" (
		fn bake sourceObjs bakedObjs = (
			local MASTER_IDX = if (MaxVersion())[1] >= 4000 then 4 else 1
 
			for i in sourceObjs.count to 1 by -1 do (
				local obj = sourceObjs[i]
				local trimIt = false
 
				--Can convert to a spline shape
				trimIt = trimIt OR NOT (canConvertTo obj splineShape)
 
				if trimIt then deleteItem sourceObjs i
			)
 
			for i in 1 to sourceObjs.count do (
				local shp = copy sourceObjs[i]
				shp.name = sourceObjs[i].name
				if addSuffix then shp.name += "_SHAPEBAKE"
				convertToSplineShape shp
				animateVertex shp #all
 
				local masterCtrl = shp[4][MASTER_IDX]
 
				-- assign OOR types
				for i in 1 to masterCtrl.numsubs do (
					setBeforeORT masterCtrl[i].controller (OORTypes[InOORType] as name)
					setAfterORT masterCtrl[i].controller (OORTypes[OutOORType] as name)
				)
 
				append bakedObjs shp
			)
 
			local numFrames = ((endTime-startTime)/NthFrame as float) as integer
			ProgressStart ("Baking " + (numFrames as string) + " Frames...")
 
			local totFrames = 0.0
 
			-- massive kludge for updating skin on object
			PushCommandPanelTaskMode #modify
			local oldSliderTime = sliderTime
 
			for t in startTime to endTime by NthFrame do (
				for i in 1 to bakedObjs.count do (
					local obj = bakedObjs[i]
 
					local tmpObj = copy sourceObjs[i]
					-- massive kludge for updating skin on object
					for m in tmpObj.modifiers do (
						if (ClassOf m == Skin) then modPanel.SetCurrentObject m
						sliderTime += 1
					)
					at time t collapseStack tmpObj
					animateVertex tmpObj #all
 
					for i in 1 to obj[4][MASTER_IDX].numsubs do (
						local k = addNewKey obj[4][MASTER_IDX][i].controller t
						k.value = tmpObj[4][MASTER_IDX][i].value
					)
 
					delete tmpObj
				)
				totFrames += 1
				if NOT (ProgressUpdate (totFrames/numFrames * 100)) then (
					if NOT (QueryBox "Keep What Been Baked So Far?" title:"Bake") then (
						delete bakedObjs
						for i in bakedObjs.count to 1 by -1 do ( deleteItem bakedObjs i )
					)
 
					for i in sourceObjs.count to 1 by -1 do ( deleteItem sourceObjs i )
					ProgressEnd()
 
					return false
				)
			)
 
			PopCommandPanelTaskMode()
			sliderTime = oldSliderTime
 
			ProgressEnd()
 
			true
		)
		label DLGnoneLabel "No Options"
	)
--------------------------------------------------------------------------------------------------
	rollout DLGpointCacheRollout "Point Cache" (
		local bakeSpace
		local doCollapse
		local outputPath
 
		fn updateUI = (
			DLGpointCacheRollout.DLGoutputPath.text = outputPath
			DLGpointCacheRollout.DLGdoCollapse.checked = doCollapse
			DLGpointCacheRollout.DLGbakeSpace.state = bakeSpace
		)
 
		fn bake sourceObjs bakedObjs = (
			-- kludge to remove trailing "\\"
			while (outputPath[outputPath.count] == "\\") do outputPath = SubString outputPath 1 (outputPath.count-1)
 
			if ((getDirectories outputPath).count != 0) then
			(
				local oldMode = GetCommandPanelTaskMode()
				if (oldMode != #modify) then SetCommandPanelTaskMode mode:#modify
 
				-- which modifier to use based on desired bake space
				local cacheMaster = if (bakeSpace == 1) then PointCache2 else PointCache2WSM
 
				-- strip out all but one instance of each object
				local tmpMod = cacheMaster()
				for srcObjIdx in 1 to sourceObjs.count do
				(
					local src = sourceObjs[srcObjIdx]
					--src might be undefined if array got shrunk from under it
					if (src != undefined) then (
						if (ValidModifier src tmpMod) then (
							for targObjIdx in sourceObjs.count to (srcObjIdx+1) by -1 do
							(
								local targ = sourceObjs[targObjIdx]
								if	(src == targ) OR
									(IsInstance src targ) then DeleteItem sourceObjs targObjIdx
							)
						) else (
							DeleteItem sourceObjs srcObjIdx
						)
					)
				)
				tmpMod = undefined
 
				local bail = false
				progressStart ("Caching " + (sourceObjs.count as string) + " Objects...")
				local cachesToDisable = #() -- caches that should have stacks disabled after baking
				for objIdx in 1 to sourceObjs.count do
				(
					local obj = sourceObjs[objIdx]
 
					local cache = cacheMaster()
					cache.cacheFile = outputPath + "\\" + obj.name + ".pc2"
					cache.recordStart = cache.playbackStart = startTime
					cache.recordEnd = cache.playbackEnd = endTime
					cache.sampleRate = nthFrame
 
					AddModifier obj cache
 
					if (obj.modifiers[1] == cache) then (
						-- if modifier is at top of stack, just select object
						select obj
					) else (
						-- otherwise try to SetCurrentObject
						local modIdx = modPanel.GetModifierIndex obj cache
						modPanel.SetCurrentObject obj.modifiers[modIdx]
					)
 
					-- Workaround for SetCurrentObject not working in some cases
					if (modPanel.GetCurrentObject() != cache) then (
 
						DeleteModifier obj modIdx
 
						local str = "Error:  The following object is causing problems with the currently baking object,\nand must probably be deleted before Bake can continue:\n\n"
 
						local col = (refs.dependents (modPanel.GetCurrentObject()))[1]
						if (IsKindOf col node) then
							str += col.name
						else
							str += "Unknown"
 
						MessageBox str title:"Bake"
						bail = true
					) else (
						cacheOps.RecordCache cache
 
						if doCollapse then (
							CollapsePointCache obj
						) else (
							append cachesToDisable #(obj,cache)
						)
					)
 
					local cont = ProgressUpdate(objIdx as float / sourceObjs.count * 100)
					if NOT cont OR bail then (
						local endIdx = objIdx + 1
						if (endIdx > sourceObjs.count) then endIdx = sourceObjs.count
						for i in sourceObjs.count to endIdx do DeleteItem sourceObjs i
						exit
					)
				)
 
				-- Disable stacks below baked caches
				for tmp in cachesToDisable do (
					local obj = tmp[1]
					local cache = tmp[2]
					if (obj.modifiers[1] == cache) then (
						select obj
						cacheOps.DisableBelow cache
					) else (
						local modIdx = modPanel.GetModifierIndex obj cache
						modPanel.SetCurrentObject obj.modifiers[modIdx]
						if (modPanel.GetCurrentObject() == obj.modifiers[modIdx]) then (
							cacheOps.DisableBelow cache
						)
					)
				)
 
				progressEnd()
 
				if (oldMode != #modify) then SetCommandPanelTaskMode mode:oldMode
 
				return true
			) else (
				messageBox "Please Select A Valid Output Path For Cache Files" title:"Point Cache"
			)
		)
 
		label DLGbakeSpaceLabel "Bake Space:" align:#left
		radiobuttons DLGbakeSpace labels:#("Object","World") columns:1 align:#left offset:[10,0]
		checkbox DLGdoCollapse "Collapse Stack" checked:false align:#left
		button DLGbrowsePath "Browse" align:#right height:18
		label DLGoutputPathLabel "Output Path:" align:#left offset:[0,-20]
		edittext DLGoutputPath ""
 
		on DLGbakeSpace changed state do
		(
			bakeSpace = state
			UpdateUI()
		)
 
		on DLGdoCollapse changed state do (
			doCollapse = state
			UpdateUI()
		)
 
		on DLGbrowsePath pressed do
		(
			local str = GetSavePath caption:"Pick Output Directory"
			outputPath = if (str == undefined) then "" else str
			UpdateUI()
		)
 
		on DLGoutputPath entered str do (
			if (str[str.count] != "\\") do str += "\\"
			outputPath = GetFilenamePath str
			UpdateUI()
		)
 
		on DLGpointCacheRollout open do (
			bakeSpace = 1
			doCollapse = false
			outputPath = ""
 
			UpdateUI()
		)
	)
--------------------------------------------------------------------------------------------------
 
	rolloutNameList = #(
		"Mesh Animation",
		"Object Transform",
		"Shape Animation",
		"Point Cache")
--		"FFD Spacewarp Animation")
 
	rolloutList = #(
		DLGmeshRollout,
		DLGobjTransRollout,
		DLGshapeRollout,
		DLGpointCacheRollout,
		DLGffdRollout )
 
	rolloutSizeList = #(
		138,
		172,
		50,
		148,
		46 )
 
	rollout DLGaboutRollout "About" (
		label DLGAbout01 ""
		label DLGAbout02 ""
		label DLGAbout03 ""
 
		on DLGaboutRollout open do (
			DLGabout01.text = thisTool.toolName
			DLGabout02.text = thisTool.author
			DLGabout03.text =	(thisTool.modifyDate.x as integer) as string + "." +
								(thisTool.modifyDate.y as integer) as string + "." +
								(thisTool.modifyDate.z as integer) as string
		)
 
		on DLGaboutRollout close do ( thisTool.closeTool() )
	)
 
	rollout DLGbakeUtilRollout "Bake Utilities" (
		button DLGmakeMorph "Make Morph From Selection" width:170 enabled:false
		button DLGcollapseToPointCache "Collapse To PointCache" width:170
		group "Remove ClothReyes From:" (
			button DLGremoveClothSel "All Objects" width:170
			button DLGremoveClothAll "Selected Objects" width:170 offset:[-1,0]
		)
 
		on DLGmakeMorph pressed do (
			MakeMorphFromObjs selection
		)
		on DLGcollapseToPointCache pressed do (
			local objs = selection as array
			for obj in objs do try (CollapsePointCache obj) catch ()
		)
		on DLGremoveClothAll pressed do ( killCloth objects )
		on DLGremoveClothSel pressed do ( killCloth selection )
	)
 
	rollout DLGbakeRollout "Bake" (
		fn updateUI = (
			DLGbakeRollout.DLGstartTime.value = startTime
			DLGbakeRollout.DLGendTime.value = endTime
			DLGbakeRollout.DLGnthFrame.value = nthFrame
			DLGbakeRollout.DLGdelOrig.checked = delOrig
			DLGbakeRollout.DLGaddSuffix.checked = addSuffix
			DLGbakeRollout.DLGselBaked.checked = selBaked
			DLGbakeRollout.DLGInOORType.selection = InOORType
			DLGbakeRollout.DLGOutOORType.selection = OutOORType
 
			DLGbakeRollout.DLGdelOrig.enabled =
				DLGbakeRollout.DLGaddSuffix.enabled =
				DLGbakeRollout.DLGselBaked.enabled = (bakeType != 4)
		)
 
		group "Time Sampling" (
			spinner DLGstartTime "Start" range:[-105214,105214,startTime] type:#integer fieldWidth:50 across:2
			spinner DLGendTime "End" range:[-105214,105214,endTime] type:#integer fieldWidth:50
			spinner DLGnthFrame "Every Nth Frame" range:[1,999999,nthFrame] type:#integer fieldWidth:50 offset:[-1,0]
		)
		group "Options" (
			label DLGbakeTypeLabel "Bake:" offset:[-69,2]
			dropdownlist DLGbakeType "" width:133 offset:[34,-22]
			checkbox DLGdelOrig "Delete Original" checked:delOrig
			checkbox DLGaddSuffix "Add Suffix" checked:addSuffix
			checkbox DLGselBaked "Select Baked Objects" checked:selBaked
		)
		group "Out Of Range" (
			label DLGInOORTypeLabel "In:" align:#left offset:[0,4]
			dropdownlist DLGInOORType items:OORTypes width:100 offset:[25,-22]
			bitmap DLGInOORTypeImage bitmap:OORTypeBitmaps[1] width:28 height:20 offset:[65,-26]
			label DLGOutOORTypeLabel "Out:" align:#left offset:[0,4]
			dropdownlist DLGOutOORType items:OORTypes width:100 offset:[25,-22]
			bitmap DLGOutOORTypeImage bitmap:OORTypeBitmaps[1] width:28 height:20 offset:[65,-26]
		)
--		group "External Bake Options" (
--			checkbox DLGbakeXRef "Bake To External File" checked:bakeXRef enabled:false
--			checkbox DLGbakeSeparateFiles "Separate File For Each Object" checked:bakeSeparateFiles enabled:bakeXRef
--			label DLGbakeXRefPathLabel "Base Path:" align:#left
--			checkbox DLGuseScenePath "Use Current Scene Path" checked:useScenePath enabled:bakeXRef
--			edittext DLGbakeXRefPath "Path" text:bakeXRefPath enabled:(bakeXRef AND NOT useScenePath)
--		)
		button DLGbake "BAKE" width:80 height:30
 
		on DLGstartTime changed val do (
			startTime = val
			if startTime >= endTime then ( endTime = startTime )
			DLGendTime.value = endTime
		)
		on DLGendTime changed val do (
			endTime = val
			if endTime <= startTime then ( startTime = endTime )
			DLGstartTime.value = startTime
		)
		on DLGnthFrame changed val do ( nthFrame = val )
 
		on DLGbakeType selected idx do (
			if idx != bakeType do (
				local f = thisTool.getFloater()
				if f != undefined then (
					local oldSize = rolloutSizeList[bakeType]
					local newSize = rolloutSizeList[idx]
					f.size.y += ( newSize - oldSize )
				)
				bakeType = idx
				thisTool.delRoll (thisTool.numRolls())
				thisTool.addRoll rolloutList[bakeType]
			)
			UpdateUI()
		)
		on DLGdelOrig changed state do ( delOrig = state; updateUI() )
		on DLGaddSuffix changed state do ( addSuffix = state; updateUI() )
		on DLGselBaked changed state do ( selBaked = state; updateUI() )
 
		on DLGInOORType selected idx do (
			DLGInOORTypeImage.bitmap = OORTypeBitmaps[idx]
			InOORType = idx
			updateUI()
		)
		on DLGOutOORType selected idx do (
			DLGOutOORTypeImage.bitmap = OORTypeBitmaps[idx]
			OutOORType = idx
			updateUI()
		)
 
		on DLGbakeXRef changed state do (
			bakeXRef =
				DLGbakeSeparateFiles.enabled =
				DLGuseScenePath.enabled = state
			DLGbakeXRefPath.enabled = bakeXRef AND NOT useScenePath
		)
		on DLGbakeSeparateFiles changed state do ( bakeSeparateFiles = state )
		on DLGuseScenePath changed state do (
			useScenePath = state
			DLGbakeXRefPath.enabled = bakeXRef AND NOT useScenePath
			DLGbakeXRefPath.text = if useScenePath then "" else maxFilePath
		)
		on DLGbakeXRefPath entered text do (
			--check for validity
		)
 
		on DLGbake pressed do (
			if selection.count != 0 then (
				local comMode = getCommandPanelTaskMode()
				if comMode == #modify then setCommandPanelTaskMode mode:#create
 
				local origSelection = selection as array
				local sourceObjs = selection as array
				local bakedObjs = #()
 
				try ( rolloutList[bakeType].bake sourceObjs bakedObjs ) catch (format "fail")
 
				if delOrig then undo on ( delete sourceObjs )
				if selBaked then (
					select bakedObjs
				) else (
					clearSelection()
					for obj in origSelection do if NOT isDeleted obj then selectMore obj
				)
 
				setCommandPanelTaskMode mode:comMode
			)
		)
 
		on DLGbakeRollout open do (
			DLGbakeType.items = rolloutNameList
			DLGbakeType.selection = bakeType
			UpdateUI()
		)
--		on DLGbakeRollout close do (
--			if bakeType != 1 then (
--				DLGbakeType.selected 1
--			)
--		)
	)
 
	thisTool.addRoll #(DLGaboutRollout, DLGbakeUtilRollout, DLGbakeRollout, rolloutList[bakeType]) rolledUp:#(true, true, false)
	thisTool.openTool thisTool
)
)

Open in new window

Avatar of juhoru

ASKER

Thanks you!
This solved all our problems.