?
Solved

MaxScript not working (bake)

Posted on 2009-07-14
4
Medium Priority
?
4,692 Views
Last Modified: 2013-12-21
Hello.
This is related to this question (http://www.experts-exchange.com/Programming/Game/3D_Prog./Q_24529221.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
0
Comment
Question by:juhoru
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 3
4 Comments
 
LVL 5

Accepted Solution

by:
bham3dman earned 2000 total points
ID: 24851882
Hi juhoru,

Sorry that I didn't see your reply in the other topic yesterday - I was unavailable all day.

I think that I have a solution for you that will resolve both errors:

1. The "Cannot assign to read-only variable: toUpper" error I believe is due to the use of the reserved keyword toUpper.  Here's my fix:

a. In Max, edit the jbFunctions.ms script (Maxscript menu - Open Script).
b. Press Ctrl+F to open the Find utility and type toUpper in the Find what drop down list - then click Find Next.
c. For each "toUpper" in the script, change to "toUpper2".  Since "toUpper2" is not a reserved keyword, there should be no conflict.
d. Save the script.
e. Restart Max.

2. No "getPixels" function for undefined - This error is due to the fact that the script cannot locate the BFDtools-Bake_Buttons.bmp image that contains the graphics for the Out-of-Range selections (most likely due to out-of-date script, version changes, etc.)  To fix, do the following:

a. In Windows, navigate to the C:\Program Files\Autodesk\3ds Max 2009\ui\macroscripts directory.  
b. Create a new directory "Bake"
c. Place the BFDtools-Bake.mcr and BFDtools-Bake_Buttons.bmp files from the downloaded .zip file into this new Bake folder.
d. In Max, edit the BFDtools-Bake.mcr macroscript that you just placed into the Bake folder (Maxscript menu - Open Script).
e. Change line 63 to:
local OORBitmap = try (openBitmap (bakePath + "\\Bake\\BFDtools-Bake_Buttons.bmp")) catch (bitmap 168 18 color:green)
f. Delete lines 57-62 (the if statement related to the Max directory)
g. Save the macroscript.
h. Restart Max and try the macroscript.

Both of these fixes worked for me and the Bake script worked successfully.  
I will provide the final scripts below.

Good luck!
0
 
LVL 5

Expert Comment

by:bham3dman
ID: 24851918
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

0
 
LVL 5

Expert Comment

by:bham3dman
ID: 24851931
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

0
 

Author Closing Comment

by:juhoru
ID: 31603176
Thanks you!
This solved all our problems.
0

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Recently, in one of the tech-blogs I usually read, I saw a post about the best-selling video games through history. The first place in the list is for the classic, extremely addictive Tetris. Well, a long time ago, in a galaxy far far away, I was…
Performance in games development is paramount: every microsecond counts to be able to do everything in less than 33ms (aiming at 16ms). C# foreach statement is one of the worst performance killers, and here I explain why.
Add bar graphs to Access queries using Unicode block characters. Graphs appear on every record in the color you want. Give life to numbers. Hopes this gives you ideas on visualizing your data in new ways ~ Create a calculated field in a query: …
Sometimes it takes a new vantage point, apart from our everyday security practices, to truly see our Active Directory (AD) vulnerabilities. We get used to implementing the same techniques and checking the same areas for a breach. This pattern can re…
Suggested Courses

764 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question