Mod Tool/Scripting
General
logmessage license logmessage version ' examples: ' INFO : Softimage ' INFO : 13.0.114.0 ' INFO : Mod Tool ' INFO : 7.5.203.0
Variables
Declaration
In VBS, any new variable is of type "variant".
dim MyVar
A variant's subtype (boolean, string, integer, ...) gets declared automatically when the variable is used the first time.
MyVar = 100
Variants can be declared in bulk, separated by comma.
dim MyVar, MyVar2, MyVar3
Variables can be used without declaring them. That's convenient for short scripts.
MyVar4 = 100 MyVar5 = "text"
As longer a script becomes as more likely typos can appear including in variables.
option explicit will force you to declare every variable but that will also make sure no misspelled variable can appear. You would see the error immediately.
option explicit dim MyVar6 MyVar6 = true
Global variables
Any variable that gets defined outside a function or subroutine is a global variable.
Variables inside a function or sub are local.
A local variable can only be used of the sub or function where it was declared.
Global variables can be used by any sub or function in the file.
' global var MyVar7 = 3 + 0.3
sub test ' local var MyVar8 = 3.3 end sub
function test2 ' local var MyVar9 = 3 MyVar10 = 4 + MyVar7 end function
In Softimage Public variable declaration doesn't work. It means variables can be global inside a script but not between multiple scripts.
At same time Softimage's substitute for these missing "public" variables are two commands: SetGlobal and GetGlobal.
SetGlobal "MyGloVar", "variable_value"
logmessage GetGlobal ("MyGloVar")
Further information are found over HERE.
Theoretically it should be possible to place all code on one script file but that screws the overview.
Other possibilities to pass values from one script to another:
- a) a command (containing a function with at least one argument), needs many lines just for the setup
- b) user data blob, preferably attached to the scene root, needs annoying checks to see if they already exist, however an advantage is that UDB can be saved inside *.xsi files
- c) a Softimage environment item, the information can only be stored as a string
' set value XSIUtils.Environment.Setitem "MyVar", "true" ' caution: it can't contain boolean values no matter if true and false were set in quotes or not
' get value
logmessage XSIUtils.Environment("MyVar")
As the information is a string you need to convert it back to what it was meant originally e.g. with cBool and cInt. For more conversion see HERE
Arrays
An array is a variable that can contain multiple values, also named elements. VBS arrays are 0-based. For example MyArr(1) has 2 elements (one at index 0 and one at index 1).
' static array Dim MyArr(1) MyArr (0) = true MyArr (1) = false
To create dynamic arrays (where the amount of elements can by changed) use redim at all times or Array declaration at the beginning. ReDim clears an array. Use preserve to keep the old values.
MyArr = Array(true,false)
MyArr2 = Array("A","B")
ReDim Preserve MyArr (2)
ReDim Preserve MyArr2(2)
MyArr(2) = true
MyArr(2) = "C"
[...]
Directories
logmessage XSIUtils.ResolvePath("$XSI_USERHOME/")
' directory to addons and exported resources
logmessage XSIUtils.ResolvePath("$XSI_HOME/")
' directory to a few imported core files that must be modified (Model.vbs, ModStartup.js, ...)
' example:
' INFO : C:\Users\Paradox-01\Autodesk\Softimage_2015\
' INFO : C:\Program Files\Autodesk\Softimage 2015\
' INFO : C:\Users\Paradox-01\Autodesk\Softimage_Mod_Tool_7.5\
' INFO : C:\Softimage\Softimage_Mod_Tool_7.5\
' this can be useful for default locations like when selecting a folder
DesktopPath = CreateObject("WScript.Shell").SpecialFolders("Desktop")
logmessage DesktopPath
Message box
' okay-only msgbox "message", 0, "title"
' okay and cancel
MyVar = msgbox ("message", 1, "title")
if MyVar = 1 then
logmessage "OK button clicked"
logmessage MyVar ' = 1
else
logmessage "Cancel button clicked"
logmessage MyVar ' = 2
end if
Input box
MyVar = inputbox ("message", "title" , "pre-entered content")
if MyVar = false then
logmessage "Cancel button clicked"
else
logmessage "OK button clicked"
logmessage MyVar
end if
Check executable version
' taking OniSplit as example
Set objFSO = CreateObject("Scripting.FileSystemObject")
logmessage objFSO.GetFileVersion("F:\Program Files (x86)\Oni\Edition\install\onisplit.exe")
' result looks like this:
' INFO : 0.9.59.0
Build an vbs executable
Executable, app(lication), program. Whatever you call it, sometimes it might be necessary to compile the script into an actual program.
Even though vbs is a script language and not a programming language, it can be done.
VbsEdit is an editor to fulfill such task with ease.
- Just goto File > Convert into Executable. Choose output path, 32/64-bit version and hit OK.
OS bit version
if GetObject("winmgmts:root\cimv2:Win32_Processor='cpu0'").AddressWidth = 64 then
logmessage "64"
else
logmessage "32"
end if
faster
Set WshShell = CreateObject("WScript.Shell")
if instr(WshShell.RegRead("HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\Identifier"), "x86") = 1 then
logmessage "32"
else
logmessage "64"
end if
XSI/Softimage bit version
logmessage XSIUtils.ResolvePath("$XSI_CPU/")
' shows programm bit version
' example:
' for 64-bit
' INFO : nt-x86-64\
' for 32-bit
' INFO : nt-x86\
Read registry
Mod Tool (because 32-bit) fails to execute following code properly on 64-bit operation systems.
So the code must be build as 64-bit program on 64-bit OS and 32-bit program on 32-bit OS.
Set WshShell = CreateObject("WScript.Shell")
AE_path = WshShell.RegRead("HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{B67333BB-1CF9-4EFD-A40B-E25B5CB4C8A7}}_is1\InstallLocation")
MsgBox AE_path
Dim fso
Set fso = CreateObject ("Scripting.FileSystemObject")
txt_location = fso.GetAbsolutePathName(".") & "\AE_path.txt"
Set wText = fso.CreateTextFile (txt_location, 1)
wText.WriteLine AE_path
wText.Close
Read file
Binary
scan_AKEV_file_table
sub scan_AKEV_file_table
' ##############################################
OniInputFile = "H:\Oni\AE\GameDataFolder\level1_Final\AKEVEnvWarehouse.oni"
' ##############################################
Set OniInputFileStream = CreateObject("ADODB.Stream")
OniInputFileStream.Type = 1
OniInputFileStream.Open
OniInputFileStream.LoadFromFile OniInputFile
' ### read AKEV textures table offset and size
ByteNum = 4
' ##############################################
TOffset = cLng("&H" & "28")
' ##############################################
OniInputFileStream.Position = TOffset
BArr1 = OniInputFileStream.Read(ByteNum)
ByteNum = 4
' ##############################################
TSize = cLng ("&H" & "2C")
' ##############################################
OniInputFileStream.Position = TSize
BArr2 = OniInputFileStream.Read(ByteNum)
' ### get AKEV textures table offset and size
TOffsetHex = SimpleBinaryToString(BArr1)
for i = ubound(TOffsetHex ) - 1 to 0 step -1
newhex = newhex & hex(Asc(TOffsetHex(i)))
next
logmessage newhex
logmessage "name table offset: " & cLng("&H" & newhex)
TOffsetInt = cLng("&H" & newhex)
newhex = ""
TSizeHex = SimpleBinaryToString(BArr2)
for i = ubound(TSizeHex) - 1 to 0 step -1
newhex = newhex & hex(Asc(TSizeHex(i)))
next
logmessage newhex
logmessage "name table size: " & cLng("&H" & newhex)
TSizeInt = cLng("&H" & newhex)
logmessage "------------------------------"
' ### read table content
ByteNum = TSizeInt
OniInputFileStream.Position = TOffsetInt
BArr3 = OniInputFileStream.Read(ByteNum)
TContent = SimpleBinaryToString(BArr3)
' ### name grapper
NG = ""
for each n in TContent
if not Asc(n) = 0 then
NG = NG & n
else
'if instr(NG, "TXMP") = 1 then
' write TXMP to array ?
logmessage NG
'end if
NG = ""
end if
next
end sub
Function SimpleBinaryToString(Binary)
ReDim tmpArr(LenB(Binary) - 1)
For I = 1 To LenB(Binary)
S = Chr(AscB(MidB(Binary, I, 1)))
tmpArr(I - 1) = S
Next
SimpleBinaryToString = tmpArr
End Function
Output:
' INFO : 0E40 ' INFO : name table offset: 3648 ' INFO : 0A4A ' INFO : name table size: 2634 ' INFO : ------------------------------ ' INFO : AKEVEnvWarehouse ' INFO : AGDBEnvWarehouse ' INFO : TXMP_DOOR_FRAME ' INFO : TXMPNONE ' INFO : TXMPSUMI_1 ' INFO : TXMPTC_CONTROL_01 ' [...] ' INFO : TXMPWH_DCTRBND
Modify file
Binary
Change FilePath and in case of binary patching use function "StringToByteArray".
FilePath = "C:\path_to\AKEVEnvWarehouse.oni"
' ### create stream objects
Set InputStream = CreateObject("ADODB.Stream")
InputStream.Type = 1
InputStream.Open
Set OutputStream = CreateObject("ADODB.Stream")
OutputStream.Type = 1
OutputStream.Open
' ### load input stream from file
InputStream.LoadFromFile FilePath
' ### copy first 16 signs of input stream to output stream
OutputStream.Write InputStream.Read(16)
' ### apply patch
' # ASCII patching
patch_data = "ABCD"
patch_data_length = len(patch_data)
' patch_data_length = 4
InputStream.Position = InputStream.Position + patch_data_length
OutputStream.Write CreateObject("System.Text.ASCIIEncoding").GetBytes_4(patch_data)
' # binary patching
'OutputStream.Write StringToByteArray("41424344")
' ### re-add data that was cut off
OutputStream.Write InputStream.Read
' ### unloader
InputStream.Close
Set InputStream = Nothing
' ### modes: 2 = overwrite; 1 = dontoverwrite
' test: save to new file
' FilePath2 = "C:\path_to\AKEVEnvWarehouseB.oni"
OutputStream.SaveToFile FilePath, 2
OutputStream.Close
Set OutputStream = Nothing
Conversions
String -> byte array
function StringToByteArray(ThisString)
for i = 1 To Len(ThisString) Step 2
str = str & Chr("&h" & Mid(ThisString, i, 2))
next
Set stream = CreateObject("ADODB.Stream")
With stream
.Open
.CharSet = "Windows-1252"
.Type = 2
' ### 2 = text
.WriteText str
.Position = 0
.Type = 1
' ### 1 = binary
StringToByteArray = .Read
.Close
End With
end function
' ### usage
ByteArray = StringToByteArray(ThisString)
Byte array -> string
Function ByteArrayToString(Binary) 'Antonin Foller, http://www.motobit.com 'Optimized version of a simple BinaryToString algorithm. Dim cl1, cl2, cl3, pl1, pl2, pl3 Dim L cl1 = 1 cl2 = 1 cl3 = 1 L = LenB(Binary) Do While cl1<=L pl3 = pl3 & Chr(AscB(MidB(Binary,cl1,1))) cl1 = cl1 + 1 cl3 = cl3 + 1 If cl3>300 Then pl2 = pl2 & pl3 pl3 = "" cl3 = 1 cl2 = cl2 + 1 If cl2>200 Then pl1 = pl1 & pl2 pl2 = "" cl2 = 1 End If End If Loop BinaryToString = pl1 & pl2 & pl3 End Function ' ### usage MyString = ByteArrayToString(ByteArray)
Math
Euler rotation -> matrix
function cosn (n) cosn = cos(XSIMath.DegreesToRadians(n)) end function function sinn (n) sinn = sin(XSIMath.DegreesToRadians(n)) end function ' ################ logmessage "input" x = 60 : logmessage x y = 60 : logmessage y z = 60 : logmessage z logmessage "##################" logmessage "converted" set RotMatX = XSIMath.CreateMatrix3(1, 0, 0, 0, cosn(x), sinn(x), 0, -sinn(x), cosn(x)) set RotMatY = XSIMath.CreateMatrix3(cosn(y), 0, -sinn(y), 0, 1, 0, sinn(y), 0, cosn(y)) set RotMatZ = XSIMath.CreateMatrix3(cosn(z), sinn(z), 0, -sinn(z), cosn(z), 0, 0, 0, 1) RotMatZ.MulInPlace RotMatY RotMatZ.MulInPlace RotMatX for i=0 to 2 for j=0 to 2 logmessage RotMatZ (i, j) next next ' INFO : input ' INFO : 60 ' INFO : 60 ' INFO : 60 ' INFO : ################## ' INFO : converted ' INFO : 0,25 ' INFO : 0,808012701892219 ' INFO : 0,53349364905389 ' INFO : -0,433012701892219 ' INFO : -0,399519052838329 ' INFO : 0,808012701892219 ' INFO : 0,866025403784439 ' INFO : -0,433012701892219 ' INFO : 0,25
Matrix -> Euler rotation
Function Atan2(y, x)
If x > 0 Then
Atan2 = Atn(y / x)
ElseIf x < 0 Then
Atan2 = Sgn(y) * (XSIMath.PI - Atn(Abs(y / x)))
ElseIf y = 0 Then
Atan2 = 0
Else
Atan2 = Sgn(y) * XSIMath.PI / 2
End If
End Function
function ToEuler(M00, M10, M20, M21, M22)
a = M00
b = M10
dim c, s, r
if b = 0 then
c = Sgn(a)
s = 0
r = Abs(a)
elseif a = 0 then
c = 0
s = Sgn(b)
r = Abs(b)
elseif Abs(b) > Abs(a) then
t = a / b
u = Sgn(b) * Sqr(1 + t * t)
s = 1 / u
c = s * t
r = b * u
else
t = b / a
u = Sgn(a) * Sqr(1 + t * t)
c = 1 / u
s = c * t
r = a * u
end if
Z = -Atan2(s, c)
Y = Atan2(M20, r)
X = -Atan2(M21, M22)
X = XSIMath.RadiansToDegrees(X)
Y = XSIMath.RadiansToDegrees(Y)
Z = XSIMath.RadiansToDegrees(Z)
ToEuler = array(X, Y, Z)
end function
' ################################
set RotMat = XSIMath.CreateMatrix3( _
0.25, 0.808012701892219, 0.53349364905389, _
-0.433012701892219, -0.399519052838329, 0.808012701892219, _
0.866025403784439, -0.433012701892219, 0.25 )
' convert matrix to euler rotation and store values to array
ReXYZ = ToEuler(RotMat(0,0), RotMat(1,0), RotMat(2,0), RotMat(2,1), RotMat(2,2))
logmessage "reconverted"
logmessage ReXYZ(0)
logmessage ReXYZ(1)
logmessage ReXYZ(2)
' INFO : 60
' INFO : 60
' INFO : 60
Euler rotation -> quaternion
dim x, y, z, dRotation, qRotation x = 90 y = 0 z = 0 set dRotation = XSIMath.CreateRotation(XSIMath.DegreesToRadians(x), XSIMath.DegreesToRadians(y), XSIMath.DegreesToRadians(z)) set qRotation = XSIMath.CreateQuaternion() dRotation.GetQuaternion (qRotation) LogMessage qRotation.W LogMessage qRotation.X LogMessage qRotation.Y LogMessage qRotation.Z ' INFO : 0,707106781186548 ' INFO : 0,707106781186547 ' INFO : 0 ' INFO : 0 ' to calculate oni quaternions from euler rotations use this setup: ' LogMessage qRotation.X ' LogMessage qRotation.Y ' LogMessage qRotation.Z ' LogMessage qRotation.W * -1
Quaternion -> Euler rotation
dim qW, qX, qY, qZ, qRotation, x, y, z qW = 0.707106781186548 qX = 0.707106781186547 qY = 0 qZ = 0 set qRotation = XSIMath.CreateQuaternion (qW, qX , qY, qZ) qRotation.GetXYZAngleValues x, y, z logmessage XSIMath.RadiansToDegrees(x) logmessage XSIMath.RadiansToDegrees(y) logmessage XSIMath.RadiansToDegrees(z) ' INFO : 89,9999999999999 ' INFO : 0 ' INFO : 0 ' to calculate euler rotations from oni quaternions use this setup: 'qX = ... 'qY = ... 'qZ = ... 'qW = ... * -1 'set qRotation = XSIMath.CreateQuaternion (qW, qX, qY, qZ)
Property Page
Detect a PPG
' this check is used to decide to either build or open a PPG
set oRoot = ActiveProject.ActiveScene.Root
if typename(oRoot.Properties("my_PPG")) = "Nothing" then
logmessage "couldn't find my_PPG"
else
logmessage "found my_PPG"
end if
Disable PPG popups
' let's say a big amount of objects will be created and each object will open a PPG ' in that case for user convenience those PPG popups should be disabled
' disable PPG popup Preferences.SetPreferenceValue "Interaction.autoinspect", false ' creates the cube mesh but no PPG will show up CreatePrim "Cube", "MeshSurface" ' enable PPG popup again Preferences.SetPreferenceValue "Interaction.autoinspect", true
Build a PPG
' general PPG setup
set oPSet = ActiveSceneRoot.AddProperty("CustomProperty", false, "my_PPG")
set oPPGLayout = oPSet.PPGLayout
' PPG content
' [...]
' open PPG
InspectObj oPSet
PPG content
Text input
oPSet.AddParameter3 "ParamName", siString oPPGLayout.AddItem "ParamName", "Caption"
Integer input
oPSet.AddParameter3 "ParamName", siInt2, , , , false, 0 oPPGLayout.AddItem "ParamName", "Caption"
Droplist
oPSet.AddParameter3 "Team", siString, 0 aListTeams = Array( "Konoko", 0, _ "TCTF", 1, _ "Syndicate", 2, _ "Neutral", 3, _ "SecurityGuard", 4, _ "RogueKonoko", 5, _ "Switzerland (is melee-immune)", 6, _ "SyndicateAccessory", 7 ) oPPGLayout.AddEnumControl "Team", aListTeams, "", siControlCombo
Radio options
oPSet.AddParameter3 "Team", siString, 0 aListTeams = Array( "Konoko", 0, _ "TCTF", 1, _ "Syndicate", 2, _ "Neutral", 3, _ "SecurityGuard", 4, _ "RogueKonoko", 5, _ "Switzerland (is melee-immune)", 6, _ "SyndicateAccessory", 7 ) oPPGLayout.AddEnumControl "Team", aListTeams, "", siControlRadio
Checkbox
' create checkbox and remove key symboles by setting parameter "Animatable" to false (after siBool) oPSet.AddParameter3 "Check1", siBool, 0, , , false oPPGLayout.AddItem "Check1", "Checkbox_caption"
Spacer
' AddSpacer( [Width], [Height] ) oPPGLayout.AddSpacer 25