Mod Tool/Scripting: Difference between revisions

From OniGalore
Jump to navigation Jump to search
(xsi scripting is getting to much)
 
m (using Image: consistently to make it easier to find all image refs on a page)
 
(68 intermediate revisions by 3 users not shown)
Line 1: Line 1:
==Information for novices==
* '''open the Script Editor: [Alt] + [4]'''
* '''run the current code in the Script Editor window: [F5]'''
* clear the log: Edit > Clear History Log
* Actions from button embedded code will be logged.
* Actions from code files that are linked in a button won't be logged. (This results in a performance boost.)
* Logged stuff will be rewritten if you change the script language. (File > Preferences...)
* You can also change the language by right-click the white script box and click on "Set to JScript" or "Set to VBScript".
* Python can be added as script language if you install it on your PC.
* Right-clicking the white script box gives you also access to a few code piece, e.g. "Syntax Help" > "If..Else" or "Catch Error".
* Mark code you want to disable ("Comment Out") or enable ("Comment Remove").
* You can save your code to a file via "File" > "Save As..." or "Save Selection"
'''Links'''
* [http://web.archive.org/web/20160803061035/http://softimage.wiki.softimage.com/index.php?title=Scripting_Tips_and_Tricks_%28XSISDK%29 xsi wiki page about scripting]
* '''[http://web.archive.org/web/20170616035120/http://softimage.wiki.softimage.com/sdkdocs/scriptsdb/scriptsdb/scrdb_vbscript.htm many vbscript examples]'''
* '''[https://ss64.com/vb/ vbs commands]'''
* [https://web.archive.org/web/20070510173452/https://www.activexperts.com/activmonitor/windowsmanagement/adminscripts/filesfolders/files/ objFSO/objWSHShell: Scripts to manage Files] (replace "Wscript.Echo" with "logmessage")
* [https://web.archive.org/web/20150504221146/http://activexperts.com/activmonitor/windowsmanagement/adminscripts/other/textfiles/ objFSO/objWSHShell: Scripts to manage Text Files]
* [http://web.archive.org/web/20080905102848/http://www.kxcad.net/softimage_xsi/Softimage_XSI_Documentation/script_basics_IncludingExternalScripts.htm using external scripts]
* [https://download.autodesk.com/global/docs/softimage2013/en_us/sdkguide/index.html?url=si_om/XSIUIToolkit.html,topicNumber=si_om_XSIUIToolkit_html progress bar and open file dialog<!-- (hm, InitialDirectory code for quick save idea ?)-->]
<!--* [http://download.autodesk.com/global/docs/softimage2013/en_us/sdkguide/index.html?url=files/cus_ppg_FileBrowserWidget.htm,topicNumber=d30e11980,hash=WS34BA39B437A993419C80CAB58E3BEFA1-0059 text box]-->
==General stuff==
===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====
Variables inside a function or sub are local. A local variable can only be used of the sub or function where it was declared.
Any variable that gets defined outside a function or subroutine is a global variable. Global variables can be used by any sub or function in the file.
' global var 7
MyVar7 = 3 + 0.3
sub test
' local var 8
MyVar8 = 3.3
end sub
function test2
' local var 9 and 10
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 [https://download.autodesk.com/global/docs/softimage2014/en_us/sdkguide/si_cmds/SetGlobal.html 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"
' 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 [http://web.archive.org/web/20150707131602/http://www.w3schools.com/vbscript/vbscript_ref_functions.asp 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.
ReDim MyArr (1)
MyArr(0) = true
MyArr(1) = false
MyArr2  = Array("A","B")
ReDim Preserve MyArr (2)
ReDim Preserve MyArr2(2)
MyArr (2) = true
MyArr2(2) = "C"
[...]
===Events===
====OnStartup====
Loading objects on startup can fail in some aspects even though you use the same function for OnActivate.
For example a console might get correct position but wrong rotation.
In that case you might want to switch to another program and then back to XSI to use the OnActivate event. Or you do something else e.g. let the user click on a button. With that everything that happened OnStartup should had enough time to process.
====OnActivate====
function siOnActivateEventTest_OnEvent( in_ctxt )
Application.LogMessage "State: " + cstr(in_ctxt.GetAttribute("State"))
' TODO: Put your code here.
' Return value is ignored as this event can not be aborted.
siOnActivateEventTest_OnEvent = true
end function
'''State becomes "False" when XSI loses its focus.'''
'''State becomes "True" when XSI regains focus.'''
Exchanging data between XSI and other programs is rather difficult.
Instead, whenever "True" is detected XSI could look into an exchange folder or exchange file*.
: *OniSplit GUI could save a vbs script to file and then it gets executed by XSI
:: Application.ExecuteScript( FileName, [Language], [ProcName], [Params] )
Full example (FillFile would have to be done by the GUI)
Dim fso, wText, bInteractive, TestFile
TestFile="C:\Oni\AE\Tools\Simple_OniSplit_GUI\XSI_exchange\test.vbs"
Main()
'------------------------------------------------------------------------------
' NAME: FillFile
'
' INPUT:
'
' DESCRIPTION: Fill the test file
'------------------------------------------------------------------------------
sub FillFile (in_file)
  wText.WriteLine "Main()"
  wText.WriteLine ""
  wText.WriteLine "sub log_some_messages(an_int, a_string)"
  wText.WriteLine "logmessage ""the int : "" & an_int"
  wText.WriteLine "logmessage ""the string : "" & a_string"
  wText.WriteLine "end sub"
  wText.WriteLine ""
  wText.WriteLine "sub main()"
  wText.WriteLine "CreatePrim ""Sphere"", ""NurbsSurface"""
  wText.WriteLine "end sub"
  wText.Close
end sub
sub main()
  Set fso = CreateObject("Scripting.FileSystemObject")
  Set wText = fso.CreateTextFile( TestFile, 1)
  FillFile TestFile
  ExecuteScript TestFile
  dim aParams
  aParams = Array(123456789, "toto")
  ExecuteScript TestFile,,"log_some_messages", aParams
end sub
====OnValueChange====
' this event is fired 4 times (2 local, 2 global)
' everytime when created, translated, rotated, or scaled
' we only want one global
firstValue = false
function siOnValueChangeEventTest_OnEvent( in_ctxt )
if instr(cstr(in_ctxt.GetAttribute("Object")), ".kine.global") > 0 then
  if firstValue = false then
' add more exit conditions here
' e.g. selection mode
' selection count
' if obj is not an Oni obj or camera
   
   
    if selection.count > 0 and not replace(cstr(in_ctxt.GetAttribute("Object")),".kine.global", "") = "Camera_Interest" then
        logmessage "Object at: " & selection(0).posx.value & " " & selection(0).posy.value & " " & selection(0).posz.value
    end if
    if replace(cstr(in_ctxt.GetAttribute("Object")),".kine.global", "") = "Camera_Interest" then
        logmessage GetValue("Camera_Interest.kine.global.posx") & " " & _
          GetValue("Camera_Interest.kine.global.posy") & " " & _
          GetValue("Camera_Interest.kine.global.posz")
  logmessage "Obj is cam"
    end if
    firstValue = true
  else
    firstValue = false
  end if
end if
end function


' INFO : Object at: -22,0187049984705 6,63004918144234 5,08830153431981
' INFO : Obj is cam


This could be used to track camera and sound spheres positions. They values could be passed as command line argument to GUI.




===Suppress events in batch processing===
Events that get triggered by code inside functions don't delay the function for processing the event.


The events are precessed after the function finished.


This can pose a serious problem with batch processing where you might create and select each object several times.




====euler rotation <-> matrix====
====Negative-example====
  ' ### declare all functions first
function XSILoadPlugin( in_reg )
in_reg.Author = ""
in_reg.Name = "Sel Plug-in"
in_reg.Major = 1
in_reg.Minor = 0
in_reg.RegisterEvent "Selection",siOnSelectionChange
XSILoadPlugin = true
end function
function XSIUnloadPlugin( in_reg )
dim strPluginName
strPluginName = in_reg.Name
Application.LogMessage strPluginName & " has been unloaded.",siVerbose
XSIUnloadPlugin = true
end function
function Selection_OnEvent( in_ctxt )
' get select event, ignore unselect events (0)
if cstr(in_ctxt.GetAttribute("ChangeType")) = 1 then
exit function
end if
if XSIUtils.Environment("IgnoreMe") = "true" then
exit function
else
logmessage "hi !"
end if
Selection_OnEvent = true
end function
 
Code to be called from somewhere else.
 
XSIUtils.Environment.Setitem "IgnoreMe", "true"
SelectObj "cylinder", , True
XSIUtils.Environment.Setitem "IgnoreMe", "false"
 
The selection event outputs "hi !" despite "IgnoreMe" is set to "true" at the beginning.
 
That's because the event becomes processed after the function finished after "IgnoreMe" was set to "false".
 
 
====Positive-example====
function XSILoadPlugin( in_reg )
in_reg.Author = ""
in_reg.Name = "Sel2 Plug-in"
in_reg.Major = 1
in_reg.Minor = 0
in_reg.RegisterEvent "Selection2",siOnSelectionChange
XSILoadPlugin = true
end function
function XSIUnloadPlugin( in_reg )
dim strPluginName
strPluginName = in_reg.Name
Application.LogMessage strPluginName & " has been unloaded.",siVerbose
XSIUnloadPlugin = true
end function
function Selection2_OnEvent( in_ctxt )
if cstr(in_ctxt.GetAttribute("ChangeType")) = 1 then
exit function
end if
IgnoreCount = GetGlobal ("IgnoreThis")
if IgnoreCount > 0 then
SetGlobal "IgnoreThis", (IgnoreCount - 1)
else
logmessage "Hi 2 !"
end if
Selection2_OnEvent = true
end function
 
Code to be called from somewhere else.
 
Be aware of what can trigger the unwanted event and use a '''''global ignore variable'''''.
 
for i=1 to 5
SetGlobal "IgnoreThis", GetGlobal ("IgnoreThis") + 1
SelectObj "cylinder", , True
next
' Don't use that variable where you want to trigger the event intentionally.
SelectObj "cylinder", , True
 
 
===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
 
 
===Dealing with different decimal marks===
Decimal sign is either period or comma.
 
Detect the used sign:
logmessage Mid(FormatNumber(0.1, 1, true, false, -2), 2, 1)
 
OniSplit always uses the period sign but XSI uses the system used one, which is language-specific.
 
When xml files are loaded into XSI, the OniSplit formatted values need to be converted if necessary. Example:
 
if Mid(FormatNumber(0.1, 1, true, false, -2), 2, 1) = "," then
    posX = cdbl(replace(posX, ".", ","))
    posY = cdbl(replace(posY, ".", ","))
    posZ = cdbl(replace(posZ, ".", ","))
end if
 
Actually you only the replacement function because it will skip the operation if the sign to replace is not found.
 
When xml files are written, comma signs have to be replaced again.
 
posX = replace(posX, ",", ".")
posY = replace(posY, ",", ".")
posZ = replace(posZ, ",", ".")
 
 
===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===
[[Image:VbsEdit_for_scripting_and_compiling.png|thumb]]
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 bitness===
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"), "64") > 0 then
logmessage "64"
else
logmessage "32"
end if
 
 
===XSI/Softimage bitness, version and license===
There are three possibilities to detect the program's bitness:
 
logmessage Platform
logmessage XSIUtils.ResolvePath("$XSI_CPU/")
logmessage XSIUtils.Is64BitOS
' output for 32-bit installation
' INFO : Win32
' INFO : nt-x86\
' INFO : False
' output for 64-bit installation
' INFO : Win64
' INFO : nt-x86-64\
' INFO : True
 
 
For program's version:
logmessage version
' examples:
' INFO : 13.0.114.0
' INFO : 7.5.203.0
 
For program's license:
logmessage license
' examples:
' INFO : Softimage
' INFO : Mod Tool
 
DAE files saved with XSI/Softimage contain license information.
 
 
===Read registry===
This reads the registry with forced 64/32-bit path (RegType). In this example Oni's install location gets revealed.
 
Set WshShell = CreateObject("WScript.Shell")
if instr(WshShell.RegRead("HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\Identifier"), "64") > 0 then
OS_bitness = 64
else
OS_bitness = 32
end if
Const HKEY_LOCAL_MACHINE = &H80000002
sPath = ReadRegStr (HKEY_LOCAL_MACHINE, _
"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{B67333BB-1CF9-4EFD-A40B-E25B5CB4C8A7}}_is1", _
"InstallLocation", _
OS_bitness)
logmessage sPath
Function ReadRegStr (RootKey, Key, Value, RegType)
  Dim oCtx, oLocator, oReg, oInParams, oOutParams
  Set oCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
  oCtx.Add "__ProviderArchitecture", RegType
  Set oLocator = CreateObject("Wbemscripting.SWbemLocator")
  Set oReg = oLocator.ConnectServer("", "root\default", "", "", , , , oCtx).Get("StdRegProv")
  Set oInParams = oReg.Methods_("GetStringValue").InParameters
  oInParams.hDefKey = RootKey
  oInParams.sSubKeyName = Key
  oInParams.sValueName = Value
  Set oOutParams = oReg.ExecMethod_("GetStringValue", oInParams, , oCtx)
  ReadRegStr = oOutParams.sValue
End Function
 
 
===Run other programs===
'''via XSI System'''
 
'relative pathes don't seem to work with this method
OniSplitLocation = "C:\Softimage\Softimage_Mod_Tool_7.5\OniXSI_resources\OniSplit.exe"
inputFile =  "M3GMU_security_tv_wall_0.oni"
inputPath  = "C:\Softimage\Softimage_Mod_Tool_7.5\OniXSI_resources\test a" & "\" & inputFile
outputPath = "C:\Softimage\Softimage_Mod_Tool_7.5\OniXSI_resources\test a"
ApplicationParam  = "-extract:xml " & """" & outputPath & """" & " " & """" & inputPath & """"
appResult = System( OniSplitLocation & " " & ApplicationParam )
Select Case appResult
    Case 0 LogMessage "Ok."
    Case Else LogMessage "Error."
End Select
 
'''Via CMD'''
' relative path
' the "GameDataFolder" isn't inside the "install" folder
' so we will use ..\ to go one folder backwards
' additional quote signs tells the program where the
' paths strings start and end in case the path contains spaces
' if you are going to use the xml file right after its extraction (which is likely)
' then the "/wait" argument inside the onisplit_action string is important
' without it the code would continue and might try to read the not existing xml file and produce an error
onisplit_location = "F:\Program Files (x86)\Oni\Edition\install"
input_folder = """..\GameDataFolder\level19_Final\ONLVcompound.oni"""
output_folder = """..\GameDataFolder"""
onisplit_action = "cmd /C start /wait OniSplit.exe -extract:xml " & output_folder & " " & input_folder
logmessage "relative path: " & onisplit_action
' expected logmessage:
' INFO : relative path: cmd /C start OniSplit.exe -extract:xml "..\GameDataFolder" "..\GameDataFolder\level19_Final\ONLVcompound.oni"
XSIUtils.LaunchProcess onisplit_action, 1, onisplit_location
' absolute path
'adapt paths so it works on your computer
onisplit_location = "F:\Program Files (x86)\Oni\Edition\install"
input_folder = """F:\Program Files (x86)\Oni\Edition\GameDataFolder\level19_Final\ONLVcompound.oni"""
output_folder = """F:\Program Files (x86)\Oni\Edition\GameDataFolder"""
onisplit_action = "cmd /C start /wait OniSplit.exe -extract:xml " & output_folder & " " & input_folder
logmessage "absolute path: " & onisplit_action
' expected logmessage:
' <small>INFO : absolute path: cmd /C start OniSplit.exe -extract:xml "F:\Program Files (x86)\Oni\Edition\GameDataFolder" "F:\Program Files (x86)\Oni\Edition\GameDataFolder\level19_Final\ONLVcompound.oni"</small>
XSIUtils.LaunchProcess onisplit_action, 1, onisplit_location
' you can also lunch bat files
onibat = "cmd /C start run_wind.bat"
onilocation = "F:\Program Files (x86)\Oni\Edition"
XSIUtils.LaunchProcess onibat, 0, onilocation
 
 
'''Via winmgmts'''
' slightly modified code from [https://devblogs.microsoft.com/scripting/how-can-i-start-a-process-and-then-wait-for-the-process-to-end-before-terminating-the-script/ that site]
' ''logmessage "onisplit finished."'' will be executed after the conversion finished, there should be also an delay of 3 seconds to support very slow computers
' if you are going to use this method consider to extent the code to check if input file and output directory exist
osp_loca = "C:\OniAE\Edition\install\OniSplit.exe"
osp_action = "-extract:xml"
osp_output = """C:\OniAE\Edition\GameDataFolder"""
osp_input = """C:\OniAE\Edition\GameDataFolder\level1_Final\AKEVEnvWarehouse.oni"""
osp_total = osp_loca & " " & osp_action & " " & osp_output & " " & osp_input
logmessage osp_total
strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")
objWMIService.Create osp_total, null, null, intProcessID
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
' wait 3 second to find event - should be enough time for single actions on a slow computer
Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _
    ("Select * From __InstanceDeletionEvent Within 3 Where TargetInstance ISA 'Win32_Process'")
Do Until i = 1
    Set objLatestProcess = colMonitoredProcesses.NextEvent
    If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
        i = 1
    End If
Loop
logmessage "onisplit finished."
' now you can work with the extracted xml file
 
 
===Detect a running program===
detectProgram = "Simple_OniSplit_GUI.exe"
programIsActive = false
sComputerName = "."
Set objWMIService = GetObject("winmgmts:\\" & sComputerName & "\root\cimv2")
sQuery = "SELECT * FROM Win32_Process"
Set objItems = objWMIService.ExecQuery(sQuery)
For Each objItem In objItems
    if objItem.name = detectProgram then
        programIsActive = true
        exit for
    end if
Next
logmessage programIsActive
 
outputs either True or False
 
The code above triggers a bug. When Mod Tool gets minimized you can't bring it back to front.
 
ExecuteScript doesn't help.
 
===Taking a viewport screenshot===
SetDisplayMode "Camera", "texturedecal"
DeselectAll
cf = ActiveProject.Properties.Item("Play Control").Parameters.Item("Current").Value
set oViewportCapture = Dictionary.GetObject("ViewportCapture")
oViewportCapture.NestedObjects.Item("Start Frame").Value = cf
oViewportCapture.NestedObjects.Item("End Frame").Value = cf
oViewportCapture.NestedObjects.Item("OpenGL Anti-Aliasing").Value = 4
oViewportCapture.NestedObjects.Item("File Name").Value = "C:\Oni\AE\Tools\Simple_OniSplit_GUI\OutputFolder\test.jpg"
CaptureViewport  2, false
 
command-line access via:
C:\Softimage\Softimage_Mod_Tool_7.5\Application\bin\flip.exe
 
For CMD options use "true" and Help > Command Line Options
CaptureViewport  2, true
 
It might be possible to further automate html creation with this.
 
When screenshots are big enough the info text doesn't overlay object and can be cut away in further image processing.
 
==Write file==
===Export DAE===
Before you go crazy, yes, the command "CreateExportCrosswalkOptions" doesn't get logged in the script history.
 
set oProps = ActiveProject.ActiveScene.Root.Properties
if typename (oProps.find("ExportCrosswalkOptions")) = "Nothing" then
CreateExportCrosswalkOptions , "ExportCrosswalkOptions"
end if
' sets the extension to dae
SetValue "ExportCrosswalkOptions.Format", 1
' set export path and file name
SetValue "ExportCrosswalkOptions.Filename", CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\export_test.dae"
' selection only
SetValue "ExportCrosswalkOptions.ExportSelectionOnly", True
' export
ExportCrosswalk "ExportCrosswalkOptions"
 
 
===Export FBX===
FBXExportLights (false)
FBXExportSelection (true)
' mark FBXExport and hit F1 to get more options
FBXExport (CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\export_test.fbx" )
 
 
===Write text file===
txt_location = "C:\Softimage\Softimage_Mod_Tool_7.5\test.txt"
Set fso = CreateObject ("Scripting.FileSystemObject")
Set wText = fso.CreateTextFile (txt_location, 1)
wText.WriteLine "I'm a test file."
wText.WriteLine "Yo!"
wText.Close
 
 
==Read file==
===Import DAE===
CopyPaste, filePath, parentObject, 2
 
'use scene object if you don't want to group the imported object under a parent, e.g.:
CopyPaste, filePath, ActiveProject.ActiveScene, 2
 
 
===Import DAE (and get name)===
Sometimes you want to get the name of the object you just imported.
 
In that case you use "ImportModel" instead of "CopyPaste"
 
ImportModel filePath, [parentObject], , , "0"
logmessage selection(0).parent
logmessage selection(0) 'this gets root object (file name)
logmessage selection(0).children(0) 'this gets first object of file
 
If you want to remove the null object and select the new parent you add these lines:
set nullObject = selection(0)
set newParent = selection(0).children(0)
CutObj newParent
DeleteObj nullObject
SelectObj newParent
 
 
===Read text file===
Set objFileToRead = CreateObject("Scripting.FileSystemObject").OpenTextFile("C:\Softimage\Softimage_Mod_Tool_7.5\test.txt", 1)
do while not objFileToRead.AtEndOfStream
    strLine = objFileToRead.ReadLine()
    logmessage strLine
loop
' INFO : I'm a test file.
' INFO : Yo!
objFileToRead.Close
Set objFileToRead = Nothing
 
 
===Read binary file===
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
 
 
====AKEV AGQG====
read_AGQG_binary
sub read_AGQG_binary
OniInputFile =  "C:\Oni\AE\GameDataFolder\L3\AKEVlab.oni"
' ##############################################
   
Set OniInputFileStream = CreateObject("ADODB.Stream")
OniInputFileStream.Type = 1
OniInputFileStream.Open
OniInputFileStream.LoadFromFile OniInputFile
data_table_offset = "&H20"
AGQG_table_offset = "&H94"
AGQG_table_size  = "&H9C"
ByteNum = 4
OniInputFileStream.Position = clng(data_table_offset)
BArr0 = OniInputFileStream.Read(ByteNum)
ByteNum = 4
OniInputFileStream.Position = clng(AGQG_table_offset)
BArr1 = OniInputFileStream.Read(ByteNum)
ByteNum = 4
OniInputFileStream.Position = clng(AGQG_table_size)
BArr2 = OniInputFileStream.Read(ByteNum)
newhex = ""
data_table_offset_hex = SimpleBinaryToString(BArr0)
for i = ubound(data_table_offset_hex) - 1 to 0 step -1
h = hex(Asc(data_table_offset_hex(i)))
if len(h) = 1 then
h = "0" & h
end if
newhex = newhex & h
next
logmessage newhex
logmessage "data table offset: " & cLng("&H" & newhex)
data_table_offset_int = cLng("&H" & newhex)
newhex = ""
AGQG_offset_hex = SimpleBinaryToString(BArr1)
for i = ubound(AGQG_offset_hex) - 1 to 0 step -1
h = hex(Asc(AGQG_offset_hex(i)))
if len(h) = 1 then
h = "0" & h
end if
newhex = newhex & h
next
logmessage newhex
logmessage "AGQG table offset: " & cLng("&H" & newhex)
AGQG_offset_int = cLng("&H" & newhex)
newhex = ""
AGQG_size_hex = SimpleBinaryToString(BArr2)
for i = ubound(AGQG_size_hex) - 1 to 0 step -1
h = hex(Asc(AGQG_size_hex(i)))
if len(h) = 1 then
h = "0" & h
end if
newhex = newhex & h
next
logmessage newhex
logmessage "AGQG table size: " & cLng("&H" & newhex)
AGQG_size_int = cLng("&H" & newhex)
  logmessage "------------------------------"
  AGQG_start = data_table_offset_int + AGQG_offset_int - 8
  AGQG_end  = data_table_offset_int + AGQG_offset_int + AGQG_size_int - 8
  logmessage "AGQG_start: " & AGQG_start
 
' AGQG array size
  ByteNum = 4
OniInputFileStream.Position = AGQG_start + 28
BArr3 = OniInputFileStream.Read(ByteNum)
TContent = SimpleBinaryToString(BArr3)
  logmessage "------------------------------"
newhex = 0
for i = ubound(TContent) to 0 step -1
h = hex(Asc(TContent(i)))
if len(h) = 1 then
h = "0" & h
end if
newhex = newhex & h
next
AGQG_array_size = clng("&H" & newhex)
logmessage "AGQG array size: " & clng("&H" & newhex)
' reduce number by header space
ByteNum = AGQG_size_int - 31
' skip bytes used by header
OniInputFileStream.Position = AGQG_start + 32
BArr4 = OniInputFileStream.Read(ByteNum)
TContent2 = SimpleBinaryToString(BArr4)
c = -1
loop_count = 1
h = ""
newhex = ""
color = 0
logmessage "----------"
logmessage "element: 1"
for i = 0 to ubound(TContent2) - 1
c = c + 1
if c = 56 then
c = 0
loop_count = loop_count + 1
logmessage "----------"
logmessage "element: " & loop_count
end if
h = hex(Asc(TContent2(i)))
if len(h) = 1 then
h = "0" & h
end if
newhex = newhex & h
if i mod 56 = 32 then
color = 1
end if
if i mod 56 = 48 then
color = 0
end if
if i mod 56 = 52 then
objid = 1
end if
if i mod 56 = 0 then
objid = 0
end if
if len(newhex) = 8 then
if color = 1 then
logmessage newhex & " (color: " & clng(("&H" & left(newhex, 2))) _
& " " & clng("&H" & mid(newhex, 3, 2)) _
& " " & clng("&H" & mid(newhex, 5, 2)) _
& " " & clng("&H" & mid(newhex, 7, 2)) & ")"
elseif objid = 1 then
if newhex = "FFFFFFFF" then
logmessage newhex & " (object id: -1)"
else
logmessage newhex
end if
else
logmessage newhex
end if
newhex = ""
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
'logmessage "hex = " & hex(AscB(S))
Next
SimpleBinaryToString = tmpArr
End Function
 
 
==Modify file==
===Binary===
[[Image:Before_and_after_binary_patching_with_vbs.png|thumb|400px|Before and after patching.]]
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, https://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)
  function ToEuler(M00, M10, M20, M21, M22)
Line 41: Line 1,177:
   
   
      end if
      end if
             Z = -Atan2(s, c)
             Z = -Atan2(s, c)
             Y = Atan2(M20, r)
             Y = Atan2(M20, r)
Line 53: Line 1,189:
  end function
  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)
==Check selection mode==
checkfilter
'use sub to make use of the exit command
sub checkfilter
Select Case Selection.Filter.Name
'caution: case-sensitive
Case "object"
logmessage "object mode"
Case "Edge"
logmessage "edge mode"
Case "Vertex"
logmessage "point mode"
Case "Polygon"
logmessage "polygon mode"
Case Else
logmessage "unknown mode"
exit sub
  End Select
end sub
==3D mesh==
===General mesh information===
logmessage selection.count
logmessage selection(0).Name
logmessage selection(0).Materials(0).Name
logmessage selection(0).Materials(0).Library.name
logmessage selection(0).Materials(0).shaders(0).name
logmessage selection(0).Materials(0).CurrentImageClip.source.filename.value
logmessage selection(0).Materials(0).CurrentImageClip.source.Parameters("XRes").Value
logmessage selection(0).Material.CurrentImageClip.source.Parameters("YRes").Value
logmessage selection(0).Material.CurrentUV.name
logmessage selection(0).activeprimitive.geometry.clusters(0).name
' look for UV cluster names
' xsi-generated: "Texture_Coordinates_AUTO"
' onisplit-generated: "NodeProperties"
logmessage selection(0).sclx.value
logmessage selection(0).scly.value
logmessage selection(0).sclz.value
   
   
  Function Atan2(y, x)
  logmessage selection(0).rotx.value
  If x > 0 Then
logmessage selection(0).roty.value
    Atan2 = Atn(y / x)
logmessage selection(0).rotz.value
  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
   
   
logmessage selection(0).posx.value
logmessage selection(0).posy.value
logmessage selection(0).posz.value
   
   
  function cosn (n)
  logmessage selection(0).rotorder.value
  cosn = cos(XSIMath.DegreesToRadians(n))
 
  end function
 
===Materials and textures===
====Get all targets of an image clip====
set imgClip = GetValue("Clips._marker_blackness_tga")
set imgClipTargets = imgClip.GetShaderParameterTargets
 
logmessage imgClipTargets.count
for each t in imgClipTargets
  logmessage t
next
 
 
====Get all material libraries and materials====
for each ml in Application.ActiveProject.ActiveScene.MaterialLibraries
logmessage ml
for each m in ml.items
logmessage m.name ' (material)
next
logmessage "--------------------------"
  next
   
   
' INFO : Sources.Materials.DefaultLib
' INFO : Scene_Material
' INFO : sosMatBarrier
' INFO : sosMatBlackness
' INFO : sosMatDanger
' INFO : sosMatGhost
' INFO : sosMatImpassable
' INFO : sosMatStairs
' INFO : --------------------------
' INFO : Sources.Materials.MaterialLibrary
' INFO : AIR_STAIRWALL_LOB1
' INFO : AIR_WAITSEAT3
' INFO : AIR_WAITSEAT2
' INFO : COLLISION
' INFO : --------------------------
====Check an object's main material for TwoSided-ness====
' test and toggles an object's main material for TwoSided-ness
' this is also a prerequired test for transparency
' the difficulty is to get the TextureObject (often named Image)
' the magic happens at FindShaders, I often fail to find such trivial stuff
' imo the xsi is terrible incomplete/unintuitive
' e.g. look at "Find (ShaderCollection)" in the help
' it will give you information about meshes such as cubes ...
   
   
  function sinn (n)
  matLib = selection(0).Materials(0).Library.name
sinn = sin(XSIMath.DegreesToRadians(n))
end function
   
   
  ' #########################################################################################
  set mat = selection(0).Material
  ' convert XYZ rotation to matrix
  materialName = mat.name
   
   
  logmessage "input"
  ' let us see if there is an Image TextureObject
  x = 60 : logmessage x
  set shaders = mat.FindShaders(siShaderFilter)
  y = 60 : logmessage y
  textureObj = "Image"
z = 60 : logmessage z
   
   
  logmessage "##################"
  'if typename(shaders(textureObj)) = "Texture" then ' if not it is Nothing
  logmessage "converted"
  ' logmessage "material has texture object ""Image"""
'end if
   
   
  set RotMatX = XSIMath.CreateMatrix3(1, 0, 0, 0, cosn(x), sinn(x), 0, -sinn(x), cosn(x))
  Set list = CreateObject("System.Collections.ArrayList")
  set RotMatY = XSIMath.CreateMatrix3(cosn(y), 0, -sinn(y), 0, 1, 0, sinn(y), 0, cosn(y))
  for each n in shaders
  set RotMatZ = XSIMath.CreateMatrix3(cosn(z), sinn(z), 0, -sinn(z), cosn(z), 0, 0, 0, 1)
list.Add n.name
  next
   
   
  RotMatZ.MulInPlace RotMatY
  foundShaderParameterTransparency = false
RotMatZ.MulInPlace RotMatX
'foundUniqueShaderName = false
shaderName = ""
if list.Contains(textureObj) = true then
set oColorShareShader = GetValue("Sources.Materials." & matLib & "." & mat.name & "." & textureObj)
set oTargets = oColorShareShader.GetShaderParameterTargets("")
   
   
for i=0 to 2
scriptObjArray = split(oTargets(0), ".")
  for j=0 to 2
  'logmessage scriptObjArray(0) ' Sources (fixed name? Could be considered a folder.)
logmessage RotMatZ (i, j)
'logmessage scriptObjArray(1) ' Materials (fixed name? Could be considered a folder.)
'logmessage scriptObjArray(2) ' MaterialsLib (usually each object has its own MaterialsLib)
'logmessage scriptObjArray(3) ' Material
'logmessage scriptObjArray(4) ' Shader e.g. Phong
shaderName = scriptObjArray(4)
for each t in oTargets
logmessage t
if t.name = "transparency" then
foundShaderParameterTransparency = true
exit for
end if
  next
  next
end if
if foundShaderParameterTransparency = false then
logmessage "material is not TwoSided, lets reverse now"
SIConnectShaderToCnxPoint "Sources.Materials." & matLib & "." & materialName & ".Image", "Sources.Materials." & matLib & "." & materialName & "." & shaderName & ".transparency", False
else
logmessage "material is TwoSided, lets reverse now"
RemoveAllShadersFromCnxPoint "Sources.Materials." & matLib & "." & materialName & "." & shaderName & ".transparency", siShaderCnxPointBasePorts
end if
Output example:
' INFO : Sources.Materials.DefaultLib.Material.Phong.diffuse
' INFO : material is not TwoSided, lets reverse now
SIConnectShaderToCnxPoint "Sources.Materials.DefaultLib.Material.Image", "Sources.Materials.DefaultLib.Material.Phong.transparency", False
' INFO : Sources.Materials.DefaultLib.Material.Phong.diffuse
' INFO : Sources.Materials.DefaultLib.Material.Phong.transparency
' INFO : material is TwoSided, lets reverse now
RemoveAllShadersFromCnxPoint "Sources.Materials.DefaultLib.Material.Phong.transparency", siShaderCnxPointBasePorts
===Clusters===
'does a certain cluster type exist ?
'set cls = selection(0).activeprimitive.geometry.clusters.find( siPolygonCluster )
 
' more interesting is how many of that type exist
for each n in selection(0).activeprimitive.geometry.clusters
    logmessage "Cluster " & n.name & " is of type " & n.type
  next
  next
  logmessage "##################"
' "poly" = polygon cluster
' "sample" = UV cluster
 
Output example:
' INFO : Cluster Polygon4 is of type poly
' INFO : Cluster Polygon1 is of type poly
' INFO : Cluster Texture_Coordinates_AUTO is of type sample
 
 
====Bounding box values====
' this could be useful to create a bounding box for [[XML:OFGA|OFGA files]]
' let's get the bounding box of a simple cylinder
' the output values will be absolute positions
CreatePrim "Cylinder", "MeshSurface"
dim xmin, ymin, zmin, xmax, ymax, zmax
dim list
'if you use SelectionList the objects will be treated as one single object
'set list = GetValue( "SelectionList" )
set list = GetValue( selection(0) )
GetBBox list, TRUE, xmin, ymin, zmin, xmax, ymax, zmax
LogMessage "Lower Bound: " & xmin & " / " & ymin & " / " & zmin
LogMessage "Upper Bound: " & xmax & " / " & ymax & " / " & zmax
' expected output:
' INFO : Lower Bound: -1 / -2 / -1
' INFO : Upper Bound: 1 / 2 / 1
 
====Get the scaling, rotation and position of selected objects====
logmessage "mesh name: " & selection(0)
logmessage selection(0).sclx.value
logmessage selection(0).scly.value
logmessage selection(0).sclz.value
logmessage selection(0).rotx.value
logmessage selection(0).roty.value
logmessage selection(0).rotz.value
logmessage selection(0).posx.value
logmessage selection(0).posy.value
logmessage selection(0).posz.value
 
 
====Get scaling, rotation, and position of not selected objects====
' GetValue("NAME.kine.global.rotx")
' NAME must be the exact mesh name
' let's say you want the data of one character's pelvis
logmessage GetValue("pelvis.kine.global.sclx")
logmessage GetValue("pelvis.kine.global.scly")
logmessage GetValue("pelvis.kine.global.sclz")
logmessage GetValue("pelvis.kine.global.rotx")
logmessage GetValue("pelvis.kine.global.roty")
logmessage GetValue("pelvis.kine.global.rotz")
logmessage GetValue("pelvis.kine.global.posx")
logmessage GetValue("pelvis.kine.global.posy")
logmessage GetValue("pelvis.kine.global.posz")
 
 
===Points===
====Get position of points (with selection mode point)====
' a point must be selected
' gets xyz position of first selected point of the first selected object
logmessage Selection(0).SubComponent.ComponentCollection(0).position.x
logmessage Selection(0).SubComponent.ComponentCollection(0).position.y
logmessage Selection(0).SubComponent.ComponentCollection(0).position.z
 
====Get position of points (with selection mode object)====
' an object must be selected
' gets xyz position of point 0 of the first selected object
logmessage selection(0).activeprimitive.geometry.Points(0).Position.x
logmessage selection(0).activeprimitive.geometry.Points(0).Position.y
logmessage selection(0).activeprimitive.geometry.Points(0).Position.z
 
====Get and set position of points (without selection) right after object creation====
' point positions are relative to the object's center
' to get the absolute point positions add center to point
' to set the absolute point positions subtract center from point
set oRoot = application.activeproject.activescene.root
set oObj = oRoot.addgeometry( "Cube", "MeshSurface", "test" )
' to test our code move center to somewhere else
Translate oObj, 9, 11, 13, siRelative, siGlobal, siCtr, siXYZ, , , , , , , , , , 0
SaveKey oObj & ".kine.local.posx," & oObj & ".kine.local.posy," & oObj & ".kine.local.posz", 1, , , , True
FreezeObj oObj
set oGeometry = oObj.activeprimitive.geometry
aPositions = oGeometry.Points.PositionArray
' get old position
'                                        (xyz, point)
logmessage "old point 0 posx: " & aPositions(0, 0) + GetValue(oObj & ".kine.global.posx")
logmessage "old point 0 posy: " & aPositions(1, 0) + GetValue(oObj & ".kine.global.posy")
  logmessage "old point 0 posz: " & aPositions(2, 0) + GetValue(oObj & ".kine.global.posz")
' set new position
aPositions(0, 0) = -7 - GetValue(oObj & ".kine.global.posx")
aPositions(1, 0) = -7 - GetValue(oObj & ".kine.global.posy")
aPositions(2, 0) = -7 - GetValue(oObj & ".kine.global.posz")
' update the array
oGeometry.Points.PositionArray = aPositions
' get new position
logmessage "new point 0 posx: " & aPositions(0, 0) + GetValue(oObj & ".kine.global.posx")
logmessage "new point 0 posy: " & aPositions(1, 0) + GetValue(oObj & ".kine.global.posy")
logmessage "new point 0 posz: " & aPositions(2, 0) + GetValue(oObj & ".kine.global.posz")
' INFO : old point 0 posx: -4
' INFO : old point 0 posy: -4
' INFO : old point 0 posz: -4
' INFO : new point 0 posx: -7
' INFO : new point 0 posy: -7
' INFO : new point 0 posz: -7
 
 
====Get global point position====
set oObj = selection(0)
set oTrans = oObj.Kinematics.Local.Transform
set oPoint0 = oObj.ActivePrimitive.Geometry.Points(0)
set oPoint7 = oObj.ActivePrimitive.Geometry.Points(7)
set oPos0 = oPoint0.Position
set oPos7 = oPoint7.Position
' scaling must be frozen to 1 before we can calculate the size from local values
ResetTransform selection(0), siCtr, siScl, siXYZ
logmessage "local p0: "& oPos0.X & " " & oPos0.Y & " " & oPos0.Z
set oGlobalPos0 = XSIMath.MapObjectPositionToWorldSpace( oTrans, oPos0)
logmessage "global p0: "& oGlobalPos0.X & " " & oGlobalPos0.Y & " " & oGlobalPos0.Z
logmessage "local p7: "& oPos7.X & " " & oPos7.Y & " " & oPos7.Z
set oGlobalPos7 = XSIMath.MapObjectPositionToWorldSpace( oTrans, oPos7)
logmessage "global p7: "& oGlobalPos7.X & " " & oGlobalPos7.Y & " " & oGlobalPos7.Z
 
logmessage "size: " & oPos7.X - oPos0.X & " " & _
oPos7.Y - oPos0.Y & " " & _
oPos7.Z - oPos0.Z
' with a rotation of: -3,8792 16,4039 -13,5017
' INFO : local p0: -4 -4 -4
' INFO : local p7: 4 4 4
' INFO : global p0: -5,74764582364017 -3,00250371537919 -2,43916767056426 ' TRGV start point
' INFO : global p7: 5,74764582364017 3,00250371537919 2,43916767056426
' INFO : size: 8 8 8
 
 
===Edges===
...
===Polygons===
...
 
 
 
==Layers==
===Check if object is member of layer===
logmessage isMemberOfLayer(selection(0), "layerNameToTest")
function isMemberOfLayer(obj, layerName)
dim list
set list = selectMembers ("Layers." & layerName, 0) ' 0 = for not changing the current selection
for each o in list
if o = obj then
isMemberOfLayer = "yes"
exit for
end if
next
end function
 
 
===Get layer name of an object===
logmessage RecursiveEnum (selection(0), false, false)
function RecursiveEnum( in_Comp, in_Type, in_FirstParentOnly )
  dim list, elem, layerNameToCheck
  set list = EnumElements( in_Comp, in_Type )
  if TypeName(list) <> "Nothing" then
      for each elem in list
          if instr(elem, "Layers") = 1 and instr(elem, ".Members") > 1 then
          layerNameToCheck = replace(replace(elem,"Layers.", ""),".Members", "")
      if not layerNameToCheck = "" then
RecursiveEnum = layerNameToCheck
end if
      exit for
          end if
      next
  end if
end function
' INFO : Layer_Default
 
 
==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===
====Button====
If you need quote signs in the Logic section you write two signs: "" (Not shown in example.)
 
oPPGLayout.AddButton("btnFuncName", "click here to trigger afunction").setAttribute siUICX, 200
oPPGLayout.Logic = " sub btnFuncName_OnClicked" & vbCrlf & _
" btnFuncNameReal" & vbCrlf & _
" end sub"
   
   
  ' #########################################################################################
function btnFuncNameReal
  ' convert matrix to XYZ rotation
logmessage "hello world"
end function
 
New lines in Logic need
& vbCrlf & _
on the earlier line.
 
 
====Text input====
oPSet.AddParameter3 "ParamName", siString
oPPGLayout.AddItem "ParamName", "Caption"
 
 
====Integer input====
oPSet.AddParameter3 "ParamName", siInt2, , , , false, 0
oPPGLayout.AddItem "ParamName", "Caption"
 
If you try to set a value that is below the defined minimum, you will get this error text:
' ERROR : 2006-EDIT-SetValue - Unexpected failure.
  ' Syntax: PPGname.data, value
  ' SetValue "Turret.int1", "0"
' ERROR : Überlauf: 'setvalue' - [line ''N'']
German devs? I expected "overflow", not "Überlauf".
 
 
====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 or 0
' CustomProperty.AddParameter3( ScriptName, ValueType, [DefaultValue], [Min], [Max], [Animatable], [ReadOnly] )
oPSet.AddParameter3 "Check1", siBool, , , , 0, 0
oPPGLayout.AddItem "Check1", "Checkbox_caption"
 
 
====Spacer====
' AddSpacer( [Width], [Height] )
oPPGLayout.AddSpacer 25
 
 
==Softimage ICE==
===Show a simple value in the viewport===
[[Image:XSI_ModTool_ICE_workaround_displaying_a_flag.png|thumb|400px]]
 
CreatePrim "Cube", "MeshSurface"
   
   
  ' store ouput values to array
  ' create ice tree
  ReXYZ = ToEuler(RotMatZ(0,0), RotMatZ(1,0), RotMatZ(2,0), RotMatZ(2,1), RotMatZ(2,2))
  ApplyOp "ICETree", cstr(selection(0)), siNode, , , 0
' create set data node
AddICECompoundNode "Set Data", ".polymsh.ICETree"
' connect set data node to ice tree root
ConnectICENodes ".polymsh.ICETree.port1", ".polymsh.ICETree.Set_Data.Execute"
' create integer node (e.g. for FLAG id)
AddICENode "$XSI_DSPRESETS\ICENodes\IntegerNode.Preset", ".polymsh.ICETree"
' connect integer with set data node
ConnectICENodes ".polymsh.ICETree.Set_Data.Value", ".polymsh.ICETree.IntegerNode.result"
' set data in object hosting (hence "self.") the ice tree
SetValue ".polymsh.ICETree.Set_Data.Reference", "self.tmp"
' show integer in viewport !
DisplayPortValues ".polymsh.ICETree.Set_Data.Value", True, 0, True, , 0, 0, 0, 1, False, True, 0.62, 1, 0.62, 1, _
False, 0, 10000, 1, False, False, 0, 10, False, True, False, 100
   
   
  logmessage "reconverted"
  ' set new integer value
logmessage ReXYZ(0)
SetValue ".polymsh.ICETree.IntegerNode.value", 14
logmessage ReXYZ(1)
 
logmessage ReXYZ(2)


logmessages:
[[Category:Modding tutorials]]
' 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
' INFO : ##################
' INFO : reconverted
' INFO : 60
' INFO : 60
' INFO : 60

Latest revision as of 17:38, 21 February 2022

Information for novices

  • open the Script Editor: [Alt] + [4]
  • run the current code in the Script Editor window: [F5]
  • clear the log: Edit > Clear History Log
  • Actions from button embedded code will be logged.
  • Actions from code files that are linked in a button won't be logged. (This results in a performance boost.)
  • Logged stuff will be rewritten if you change the script language. (File > Preferences...)
  • You can also change the language by right-click the white script box and click on "Set to JScript" or "Set to VBScript".
  • Python can be added as script language if you install it on your PC.
  • Right-clicking the white script box gives you also access to a few code piece, e.g. "Syntax Help" > "If..Else" or "Catch Error".
  • Mark code you want to disable ("Comment Out") or enable ("Comment Remove").
  • You can save your code to a file via "File" > "Save As..." or "Save Selection"


Links


General stuff

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

Variables inside a function or sub are local. A local variable can only be used of the sub or function where it was declared.

Any variable that gets defined outside a function or subroutine is a global variable. Global variables can be used by any sub or function in the file.


' global var 7
MyVar7 = 3 + 0.3
sub test
	' local var 8
	MyVar8 = 3.3
end sub
function test2
	' local var 9 and 10
	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"
' 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.

ReDim MyArr (1)
MyArr(0) = true
MyArr(1) = false

MyArr2   = Array("A","B")


ReDim Preserve MyArr (2)
ReDim Preserve MyArr2(2)


MyArr (2) = true
MyArr2(2) = "C"

[...]


Events

OnStartup

Loading objects on startup can fail in some aspects even though you use the same function for OnActivate. For example a console might get correct position but wrong rotation.

In that case you might want to switch to another program and then back to XSI to use the OnActivate event. Or you do something else e.g. let the user click on a button. With that everything that happened OnStartup should had enough time to process.


OnActivate

function siOnActivateEventTest_OnEvent( in_ctxt )
	Application.LogMessage "State: " + cstr(in_ctxt.GetAttribute("State"))
' 	TODO: Put your code here.
' 	Return value is ignored as this event can not be aborted.
	siOnActivateEventTest_OnEvent = true
end function

State becomes "False" when XSI loses its focus.

State becomes "True" when XSI regains focus.

Exchanging data between XSI and other programs is rather difficult.

Instead, whenever "True" is detected XSI could look into an exchange folder or exchange file*.

*OniSplit GUI could save a vbs script to file and then it gets executed by XSI
Application.ExecuteScript( FileName, [Language], [ProcName], [Params] )


Full example (FillFile would have to be done by the GUI)

Dim fso, wText, bInteractive, TestFile
TestFile="C:\Oni\AE\Tools\Simple_OniSplit_GUI\XSI_exchange\test.vbs"

Main()

'------------------------------------------------------------------------------
' NAME: FillFile
'
' INPUT:
'
' DESCRIPTION: Fill the test file
'------------------------------------------------------------------------------
sub FillFile (in_file)
  wText.WriteLine "Main()"
  wText.WriteLine ""

  wText.WriteLine "sub log_some_messages(an_int, a_string)"
  wText.WriteLine "logmessage ""the int : "" & an_int"
  wText.WriteLine "logmessage ""the string : "" & a_string"
  wText.WriteLine "end sub"
  wText.WriteLine ""

  wText.WriteLine "sub main()"
  wText.WriteLine "CreatePrim ""Sphere"", ""NurbsSurface"""
  wText.WriteLine "end sub"
  wText.Close
end sub

sub main()
  Set fso = CreateObject("Scripting.FileSystemObject")
  Set wText = fso.CreateTextFile( TestFile, 1)

  FillFile TestFile

  ExecuteScript TestFile

  dim aParams
  aParams = Array(123456789, "toto")
  ExecuteScript TestFile,,"log_some_messages", aParams
end sub

OnValueChange

' this event is fired 4 times (2 local, 2 global)
' everytime when created, translated, rotated, or scaled
' we only want one global

firstValue = false
function siOnValueChangeEventTest_OnEvent( in_ctxt )

if instr(cstr(in_ctxt.GetAttribute("Object")), ".kine.global") > 0 then
  if firstValue = false then

' add more exit conditions here
' e.g. selection mode
' selection count
' if obj is not an Oni obj or camera


    if selection.count > 0 and not replace(cstr(in_ctxt.GetAttribute("Object")),".kine.global", "") = "Camera_Interest" then
       logmessage "Object at: " & selection(0).posx.value & " " & selection(0).posy.value & " " & selection(0).posz.value
    end if
    if replace(cstr(in_ctxt.GetAttribute("Object")),".kine.global", "") = "Camera_Interest" then
       logmessage GetValue("Camera_Interest.kine.global.posx") & " " & _
         GetValue("Camera_Interest.kine.global.posy") & " " & _
         GetValue("Camera_Interest.kine.global.posz")
  	logmessage "Obj is cam"
    end if
    firstValue = true
  else
    firstValue = false
  end if
end if
end function
' INFO : Object at: -22,0187049984705 6,63004918144234 5,08830153431981
' INFO : Obj is cam

This could be used to track camera and sound spheres positions. They values could be passed as command line argument to GUI.


Suppress events in batch processing

Events that get triggered by code inside functions don't delay the function for processing the event.

The events are precessed after the function finished.

This can pose a serious problem with batch processing where you might create and select each object several times.


Negative-example

function XSILoadPlugin( in_reg )
	in_reg.Author = ""
	in_reg.Name = "Sel Plug-in"
	in_reg.Major = 1
	in_reg.Minor = 0
	in_reg.RegisterEvent "Selection",siOnSelectionChange
	XSILoadPlugin = true
end function
function XSIUnloadPlugin( in_reg )
	dim strPluginName
	strPluginName = in_reg.Name
	Application.LogMessage strPluginName & " has been unloaded.",siVerbose
	XSIUnloadPlugin = true
end function
function Selection_OnEvent( in_ctxt )
	' get select event, ignore unselect events (0)
	if cstr(in_ctxt.GetAttribute("ChangeType")) = 1 then
		exit function
	end if
	if XSIUtils.Environment("IgnoreMe") = "true" then
		exit function
	else
		logmessage "hi !"
	end if
	Selection_OnEvent = true
end function

Code to be called from somewhere else.

XSIUtils.Environment.Setitem "IgnoreMe", "true"
SelectObj "cylinder", , True
XSIUtils.Environment.Setitem "IgnoreMe", "false"

The selection event outputs "hi !" despite "IgnoreMe" is set to "true" at the beginning.

That's because the event becomes processed after the function finished after "IgnoreMe" was set to "false".


Positive-example

function XSILoadPlugin( in_reg )
	in_reg.Author = ""
	in_reg.Name = "Sel2 Plug-in"
	in_reg.Major = 1
	in_reg.Minor = 0
	in_reg.RegisterEvent "Selection2",siOnSelectionChange
	XSILoadPlugin = true
end function
function XSIUnloadPlugin( in_reg )
	dim strPluginName
	strPluginName = in_reg.Name
	Application.LogMessage strPluginName & " has been unloaded.",siVerbose
	XSIUnloadPlugin = true
end function
function Selection2_OnEvent( in_ctxt )
	if cstr(in_ctxt.GetAttribute("ChangeType")) = 1 then
		exit function
	end if
	IgnoreCount = GetGlobal ("IgnoreThis")
	if IgnoreCount > 0 then
		SetGlobal "IgnoreThis", (IgnoreCount - 1)
	else
		logmessage "Hi 2 !"
	end if
	Selection2_OnEvent = true
end function

Code to be called from somewhere else.

Be aware of what can trigger the unwanted event and use a global ignore variable.

for i=1 to 5
	SetGlobal "IgnoreThis", GetGlobal ("IgnoreThis") + 1
	SelectObj "cylinder", , True
next

' Don't use that variable where you want to trigger the event intentionally.
SelectObj "cylinder", , True


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


Dealing with different decimal marks

Decimal sign is either period or comma.

Detect the used sign:

logmessage Mid(FormatNumber(0.1, 1, true, false, -2), 2, 1)

OniSplit always uses the period sign but XSI uses the system used one, which is language-specific.

When xml files are loaded into XSI, the OniSplit formatted values need to be converted if necessary. Example:

if Mid(FormatNumber(0.1, 1, true, false, -2), 2, 1) = "," then
   posX = cdbl(replace(posX, ".", ","))
   posY = cdbl(replace(posY, ".", ","))
   posZ = cdbl(replace(posZ, ".", ","))
end if

Actually you only the replacement function because it will skip the operation if the sign to replace is not found.

When xml files are written, comma signs have to be replaced again.

posX = replace(posX, ",", ".")
posY = replace(posY, ",", ".")
posZ = replace(posZ, ",", ".")


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

VbsEdit for scripting and compiling.png

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 bitness

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"), "64") > 0 then
	logmessage "64"
else
	logmessage "32"
end if


XSI/Softimage bitness, version and license

There are three possibilities to detect the program's bitness:

logmessage Platform
logmessage XSIUtils.ResolvePath("$XSI_CPU/")
logmessage XSIUtils.Is64BitOS

' output for 32-bit installation
' INFO : Win32
' INFO : nt-x86\
' INFO : False

' output for 64-bit installation
' INFO : Win64
' INFO : nt-x86-64\
' INFO : True


For program's version:

logmessage version
' examples:
' INFO : 13.0.114.0
' INFO : 7.5.203.0

For program's license:

logmessage license
' examples:
' INFO : Softimage
' INFO : Mod Tool

DAE files saved with XSI/Softimage contain license information.


Read registry

This reads the registry with forced 64/32-bit path (RegType). In this example Oni's install location gets revealed.

Set WshShell = CreateObject("WScript.Shell")
if instr(WshShell.RegRead("HKLM\HARDWARE\DESCRIPTION\System\CentralProcessor\0\Identifier"), "64") > 0 then
	OS_bitness = 64
else
	OS_bitness = 32
end if

Const HKEY_LOCAL_MACHINE = &H80000002

sPath = ReadRegStr (HKEY_LOCAL_MACHINE, _
	"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{B67333BB-1CF9-4EFD-A40B-E25B5CB4C8A7}}_is1", _
	"InstallLocation", _
	OS_bitness)
	
logmessage sPath



Function ReadRegStr (RootKey, Key, Value, RegType)
  Dim oCtx, oLocator, oReg, oInParams, oOutParams

  Set oCtx = CreateObject("WbemScripting.SWbemNamedValueSet")
  oCtx.Add "__ProviderArchitecture", RegType

  Set oLocator = CreateObject("Wbemscripting.SWbemLocator")
  Set oReg = oLocator.ConnectServer("", "root\default", "", "", , , , oCtx).Get("StdRegProv")

  Set oInParams = oReg.Methods_("GetStringValue").InParameters
  oInParams.hDefKey = RootKey
  oInParams.sSubKeyName = Key
  oInParams.sValueName = Value

  Set oOutParams = oReg.ExecMethod_("GetStringValue", oInParams, , oCtx)

  ReadRegStr = oOutParams.sValue
End Function


Run other programs

via XSI System

'relative pathes don't seem to work with this method
OniSplitLocation = "C:\Softimage\Softimage_Mod_Tool_7.5\OniXSI_resources\OniSplit.exe"
inputFile =  "M3GMU_security_tv_wall_0.oni"
inputPath  = "C:\Softimage\Softimage_Mod_Tool_7.5\OniXSI_resources\test a" & "\" & inputFile
outputPath = "C:\Softimage\Softimage_Mod_Tool_7.5\OniXSI_resources\test a"
ApplicationParam  = "-extract:xml " & """" & outputPath & """" & " " & """" & inputPath & """"
appResult = System( OniSplitLocation & " " & ApplicationParam )
Select Case appResult
   Case 0 LogMessage "Ok."
   Case Else LogMessage "Error."
End Select

Via CMD

' relative path

' the "GameDataFolder" isn't inside the "install" folder
' so we will use ..\ to go one folder backwards

' additional quote signs tells the program where the 
' paths strings start and end in case the path contains spaces

' if you are going to use the xml file right after its extraction (which is likely)
' then the "/wait" argument inside the onisplit_action string is important
' without it the code would continue and might try to read the not existing xml file and produce an error

onisplit_location = "F:\Program Files (x86)\Oni\Edition\install"
input_folder = """..\GameDataFolder\level19_Final\ONLVcompound.oni"""
output_folder = """..\GameDataFolder"""
onisplit_action = "cmd /C start /wait OniSplit.exe -extract:xml " & output_folder & " " & input_folder
logmessage "relative path: " & onisplit_action
' expected logmessage:
' INFO : relative path: cmd /C start OniSplit.exe -extract:xml "..\GameDataFolder" "..\GameDataFolder\level19_Final\ONLVcompound.oni"
XSIUtils.LaunchProcess onisplit_action, 1, onisplit_location


' absolute path

'adapt paths so it works on your computer
onisplit_location = "F:\Program Files (x86)\Oni\Edition\install"
input_folder = """F:\Program Files (x86)\Oni\Edition\GameDataFolder\level19_Final\ONLVcompound.oni"""
output_folder = """F:\Program Files (x86)\Oni\Edition\GameDataFolder"""
onisplit_action = "cmd /C start /wait OniSplit.exe -extract:xml " & output_folder & " " & input_folder
logmessage "absolute path: " & onisplit_action
' expected logmessage:
' INFO : absolute path: cmd /C start OniSplit.exe -extract:xml "F:\Program Files (x86)\Oni\Edition\GameDataFolder" "F:\Program Files (x86)\Oni\Edition\GameDataFolder\level19_Final\ONLVcompound.oni"
XSIUtils.LaunchProcess onisplit_action, 1, onisplit_location


' you can also lunch bat files

onibat = "cmd /C start run_wind.bat"
onilocation = "F:\Program Files (x86)\Oni\Edition"
XSIUtils.LaunchProcess onibat, 0, onilocation


Via winmgmts

' slightly modified code from that site
' logmessage "onisplit finished." will be executed after the conversion finished, there should be also an delay of 3 seconds to support very slow computers 
' if you are going to use this method consider to extent the code to check if input file and output directory exist
osp_loca = "C:\OniAE\Edition\install\OniSplit.exe"
osp_action = "-extract:xml"
osp_output = """C:\OniAE\Edition\GameDataFolder"""
osp_input = """C:\OniAE\Edition\GameDataFolder\level1_Final\AKEVEnvWarehouse.oni"""
osp_total = osp_loca & " " & osp_action & " " & osp_output & " " & osp_input
logmessage osp_total

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2:Win32_Process")
objWMIService.Create osp_total, null, null, intProcessID
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

' wait 3 second to find event - should be enough time for single actions on a slow computer
Set colMonitoredProcesses = objWMIService.ExecNotificationQuery _
    ("Select * From __InstanceDeletionEvent Within 3 Where TargetInstance ISA 'Win32_Process'")

Do Until i = 1
    Set objLatestProcess = colMonitoredProcesses.NextEvent
    If objLatestProcess.TargetInstance.ProcessID = intProcessID Then
        i = 1
    End If
Loop
logmessage "onisplit finished."
' now you can work with the extracted xml file


Detect a running program

detectProgram = "Simple_OniSplit_GUI.exe"
programIsActive = false
sComputerName = "."
Set objWMIService = GetObject("winmgmts:\\" & sComputerName & "\root\cimv2")
sQuery = "SELECT * FROM Win32_Process"
Set objItems = objWMIService.ExecQuery(sQuery)
For Each objItem In objItems
    if objItem.name = detectProgram then
       programIsActive = true
       exit for
    end if
Next
logmessage programIsActive

outputs either True or False

The code above triggers a bug. When Mod Tool gets minimized you can't bring it back to front.

ExecuteScript doesn't help.

Taking a viewport screenshot

SetDisplayMode "Camera", "texturedecal"
DeselectAll
cf = ActiveProject.Properties.Item("Play Control").Parameters.Item("Current").Value
set oViewportCapture = Dictionary.GetObject("ViewportCapture")
oViewportCapture.NestedObjects.Item("Start Frame").Value = cf
oViewportCapture.NestedObjects.Item("End Frame").Value = cf
oViewportCapture.NestedObjects.Item("OpenGL Anti-Aliasing").Value = 4
oViewportCapture.NestedObjects.Item("File Name").Value = "C:\Oni\AE\Tools\Simple_OniSplit_GUI\OutputFolder\test.jpg"
CaptureViewport  2, false

command-line access via:

C:\Softimage\Softimage_Mod_Tool_7.5\Application\bin\flip.exe

For CMD options use "true" and Help > Command Line Options

CaptureViewport  2, true

It might be possible to further automate html creation with this.

When screenshots are big enough the info text doesn't overlay object and can be cut away in further image processing.

Write file

Export DAE

Before you go crazy, yes, the command "CreateExportCrosswalkOptions" doesn't get logged in the script history.

set oProps = ActiveProject.ActiveScene.Root.Properties
if typename (oProps.find("ExportCrosswalkOptions")) = "Nothing" then
	CreateExportCrosswalkOptions , "ExportCrosswalkOptions"
end if

' sets the extension to dae
SetValue "ExportCrosswalkOptions.Format", 1
' set export path and file name
SetValue "ExportCrosswalkOptions.Filename", CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\export_test.dae"
' selection only
SetValue "ExportCrosswalkOptions.ExportSelectionOnly", True
' export
ExportCrosswalk "ExportCrosswalkOptions"


Export FBX

FBXExportLights (false)
FBXExportSelection (true)
' mark FBXExport and hit F1 to get more options
FBXExport (CreateObject("WScript.Shell").SpecialFolders("Desktop") & "\export_test.fbx" )


Write text file

txt_location = "C:\Softimage\Softimage_Mod_Tool_7.5\test.txt"
Set fso = CreateObject ("Scripting.FileSystemObject")
Set wText = fso.CreateTextFile (txt_location, 1)
wText.WriteLine "I'm a test file."
wText.WriteLine "Yo!"
wText.Close


Read file

Import DAE

CopyPaste, filePath, parentObject, 2

'use scene object if you don't want to group the imported object under a parent, e.g.:

CopyPaste, filePath, ActiveProject.ActiveScene, 2


Import DAE (and get name)

Sometimes you want to get the name of the object you just imported.

In that case you use "ImportModel" instead of "CopyPaste"

ImportModel filePath, [parentObject], , , "0"
logmessage selection(0).parent
logmessage selection(0) 'this gets root object (file name)
logmessage selection(0).children(0) 'this gets first object of file

If you want to remove the null object and select the new parent you add these lines:

set nullObject = selection(0)
set newParent = selection(0).children(0)
CutObj newParent
DeleteObj nullObject
SelectObj newParent


Read text file

Set objFileToRead = CreateObject("Scripting.FileSystemObject").OpenTextFile("C:\Softimage\Softimage_Mod_Tool_7.5\test.txt", 1)

do while not objFileToRead.AtEndOfStream
    strLine = objFileToRead.ReadLine()
    logmessage strLine
loop
' INFO : I'm a test file.
' INFO : Yo!

objFileToRead.Close
Set objFileToRead = Nothing


Read binary file

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


AKEV AGQG

read_AGQG_binary

sub read_AGQG_binary
	OniInputFile =  "C:\Oni\AE\GameDataFolder\L3\AKEVlab.oni"
	' ##############################################
    
	Set OniInputFileStream = CreateObject("ADODB.Stream")
	OniInputFileStream.Type = 1
	OniInputFileStream.Open
	OniInputFileStream.LoadFromFile OniInputFile

	data_table_offset = "&H20"
	AGQG_table_offset = "&H94"
	AGQG_table_size   = "&H9C"


	ByteNum = 4
	OniInputFileStream.Position = clng(data_table_offset)
	BArr0 = OniInputFileStream.Read(ByteNum)

	ByteNum = 4
	OniInputFileStream.Position = clng(AGQG_table_offset)
	BArr1 = OniInputFileStream.Read(ByteNum)

	ByteNum = 4
	OniInputFileStream.Position = clng(AGQG_table_size)
	BArr2 = OniInputFileStream.Read(ByteNum)
		

	newhex = ""
	data_table_offset_hex = SimpleBinaryToString(BArr0)
	for i = ubound(data_table_offset_hex) - 1 to 0 step -1
		h = hex(Asc(data_table_offset_hex(i)))
		if len(h) = 1 then
			h = "0" & h
		end if
		newhex = newhex & h
	next
	logmessage newhex
	logmessage "data table offset: " & cLng("&H" & newhex)
	data_table_offset_int = cLng("&H" & newhex)

	newhex = ""
	AGQG_offset_hex = SimpleBinaryToString(BArr1)
	for i = ubound(AGQG_offset_hex) - 1 to 0 step -1
		h = hex(Asc(AGQG_offset_hex(i)))
		if len(h) = 1 then
			h = "0" & h
		end if
		newhex = newhex & h	
	next
	logmessage newhex
	logmessage "AGQG table offset: " & cLng("&H" & newhex)
	AGQG_offset_int = cLng("&H" & newhex)
	
	newhex = ""
	AGQG_size_hex = SimpleBinaryToString(BArr2)
	for i = ubound(AGQG_size_hex) - 1 to 0 step -1
		h = hex(Asc(AGQG_size_hex(i)))
		if len(h) = 1 then
			h = "0" & h
		end if
		newhex = newhex & h	
	next
	logmessage newhex
	logmessage "AGQG table size: " & cLng("&H" & newhex)
	AGQG_size_int = cLng("&H" & newhex) 
	
  	logmessage "------------------------------"
  	AGQG_start = data_table_offset_int + AGQG_offset_int - 8
  	AGQG_end   = data_table_offset_int + AGQG_offset_int + AGQG_size_int - 8
  	logmessage "AGQG_start: " & AGQG_start
  	
	' AGQG array size
 	ByteNum = 4
	OniInputFileStream.Position = AGQG_start + 28
	BArr3 = OniInputFileStream.Read(ByteNum)
	TContent = SimpleBinaryToString(BArr3)
  	logmessage "------------------------------"
	
	newhex = 0
	for i = ubound(TContent) to 0 step -1
		h = hex(Asc(TContent(i)))
		if len(h) = 1 then
			h = "0" & h
		end if
		newhex = newhex & h	
	next
	AGQG_array_size = clng("&H" & newhex) 
	logmessage "AGQG array size: " & clng("&H" & newhex)
	
	' reduce number by header space
	ByteNum = AGQG_size_int - 31
	' skip bytes used by header
	OniInputFileStream.Position = AGQG_start + 32
	BArr4 = OniInputFileStream.Read(ByteNum)
	TContent2 = SimpleBinaryToString(BArr4)
	
	c = -1
	loop_count = 1
	h = ""
	newhex = ""
	color = 0
	logmessage "----------"
	logmessage "element: 1"
	for i = 0 to ubound(TContent2) - 1
		c = c + 1
		if c = 56 then
			c = 0
			loop_count = loop_count + 1
			logmessage "----------"
			logmessage "element: " & loop_count
		end if
	
		h = hex(Asc(TContent2(i)))
		if len(h) = 1 then
			h = "0" & h
		end if
		newhex = newhex & h

		if i mod 56 = 32 then
			color = 1
		end if
		if i mod 56 = 48 then
			color = 0
		end if
		if i mod 56 = 52 then
			objid = 1
		end if
		if i mod 56 = 0 then
			objid = 0
		end if

		if len(newhex) = 8 then
			if color = 1 then
				logmessage newhex & " (color: " & clng(("&H" & left(newhex, 2))) _
				& " " & clng("&H" & mid(newhex, 3, 2)) _
				& " " & clng("&H" & mid(newhex, 5, 2)) _
				& " " & clng("&H" & mid(newhex, 7, 2)) & ")"
			elseif objid = 1 then
				if newhex = "FFFFFFFF" then
					logmessage newhex & " (object id: -1)"
				else
					logmessage newhex
				end if
			else
				logmessage newhex
			end if
			newhex = ""
		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
		'logmessage "hex = " & hex(AscB(S))
	Next
	SimpleBinaryToString = tmpArr
End Function


Modify file

Binary

Before and after patching.

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, https://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)


Check selection mode

checkfilter
'use sub to make use of the exit command

sub checkfilter
	Select Case Selection.Filter.Name
	'caution: case-sensitive

	Case "object"
		logmessage "object mode"
	Case "Edge"
		logmessage "edge mode"
	Case "Vertex"
		logmessage "point mode"
	Case "Polygon"
		logmessage "polygon mode" 
	Case Else
		logmessage "unknown mode"
		exit sub
 	End Select
end sub

3D mesh

General mesh information

logmessage selection.count

logmessage selection(0).Name

logmessage selection(0).Materials(0).Name 
logmessage selection(0).Materials(0).Library.name
logmessage selection(0).Materials(0).shaders(0).name 
logmessage selection(0).Materials(0).CurrentImageClip.source.filename.value
logmessage selection(0).Materials(0).CurrentImageClip.source.Parameters("XRes").Value
logmessage selection(0).Material.CurrentImageClip.source.Parameters("YRes").Value

logmessage selection(0).Material.CurrentUV.name

logmessage selection(0).activeprimitive.geometry.clusters(0).name
' look for UV cluster names
' xsi-generated: "Texture_Coordinates_AUTO"
' onisplit-generated: "NodeProperties"

logmessage selection(0).sclx.value
logmessage selection(0).scly.value
logmessage selection(0).sclz.value

logmessage selection(0).rotx.value
logmessage selection(0).roty.value
logmessage selection(0).rotz.value

logmessage selection(0).posx.value
logmessage selection(0).posy.value
logmessage selection(0).posz.value

logmessage selection(0).rotorder.value


Materials and textures

Get all targets of an image clip

set imgClip = GetValue("Clips._marker_blackness_tga")
set imgClipTargets = imgClip.GetShaderParameterTargets
logmessage imgClipTargets.count
for each t in imgClipTargets
	logmessage t
next


Get all material libraries and materials

for each ml in Application.ActiveProject.ActiveScene.MaterialLibraries
	logmessage ml
	for each m in ml.items
		logmessage m.name ' (material)
	next
	logmessage "--------------------------"
next

' INFO : Sources.Materials.DefaultLib
' INFO : Scene_Material
' INFO : sosMatBarrier
' INFO : sosMatBlackness
' INFO : sosMatDanger
' INFO : sosMatGhost
' INFO : sosMatImpassable
' INFO : sosMatStairs
' INFO : --------------------------
' INFO : Sources.Materials.MaterialLibrary
' INFO : AIR_STAIRWALL_LOB1
' INFO : AIR_WAITSEAT3
' INFO : AIR_WAITSEAT2
' INFO : COLLISION
' INFO : --------------------------


Check an object's main material for TwoSided-ness

' test and toggles an object's main material for TwoSided-ness
' this is also a prerequired test for transparency
' the difficulty is to get the TextureObject (often named Image)
' the magic happens at FindShaders, I often fail to find such trivial stuff
' imo the xsi is terrible incomplete/unintuitive
' e.g. look at "Find (ShaderCollection)" in the help
' it will give you information about meshes such as cubes ...

matLib = selection(0).Materials(0).Library.name

set mat = selection(0).Material
materialName = mat.name

' let us see if there is an Image TextureObject
set shaders = mat.FindShaders(siShaderFilter)
textureObj = "Image"

'if typename(shaders(textureObj)) = "Texture" then ' if not it is Nothing
'	logmessage "material has texture object ""Image"""
'end if

Set list = CreateObject("System.Collections.ArrayList")
for each n in shaders
	list.Add n.name
next

foundShaderParameterTransparency = false
'foundUniqueShaderName = false
shaderName = ""
if list.Contains(textureObj) = true then
	set oColorShareShader = GetValue("Sources.Materials." & matLib & "." & mat.name & "." & textureObj)
	set oTargets = oColorShareShader.GetShaderParameterTargets("")

	scriptObjArray = split(oTargets(0), ".")
	'logmessage scriptObjArray(0) ' Sources (fixed name? Could be considered a folder.)
	'logmessage scriptObjArray(1) ' Materials (fixed name? Could be considered a folder.)
	'logmessage scriptObjArray(2) ' MaterialsLib (usually each object has its own MaterialsLib)
	'logmessage scriptObjArray(3) ' Material
	'logmessage scriptObjArray(4) ' Shader e.g. Phong
	shaderName = scriptObjArray(4)
	
	for each t in oTargets
		logmessage t
		if t.name = "transparency" then
			foundShaderParameterTransparency = true
			exit for
		end if
	next
end if

if foundShaderParameterTransparency = false then
	logmessage "material is not TwoSided, lets reverse now"
	SIConnectShaderToCnxPoint "Sources.Materials." & matLib & "." & materialName & ".Image", "Sources.Materials." & matLib & "." & materialName & "." & shaderName & ".transparency", False
else
	logmessage "material is TwoSided, lets reverse now"
	RemoveAllShadersFromCnxPoint "Sources.Materials." & matLib & "." & materialName & "." & shaderName & ".transparency", siShaderCnxPointBasePorts
end if

Output example:

' INFO : Sources.Materials.DefaultLib.Material.Phong.diffuse
' INFO : material is not TwoSided, lets reverse now
SIConnectShaderToCnxPoint "Sources.Materials.DefaultLib.Material.Image", "Sources.Materials.DefaultLib.Material.Phong.transparency", False

' INFO : Sources.Materials.DefaultLib.Material.Phong.diffuse
' INFO : Sources.Materials.DefaultLib.Material.Phong.transparency
' INFO : material is TwoSided, lets reverse now
RemoveAllShadersFromCnxPoint "Sources.Materials.DefaultLib.Material.Phong.transparency", siShaderCnxPointBasePorts


Clusters

'does a certain cluster type exist ?
'set cls = selection(0).activeprimitive.geometry.clusters.find( siPolygonCluster )
  
' more interesting is how many of that type exist
for each n in selection(0).activeprimitive.geometry.clusters
   logmessage "Cluster " & n.name & " is of type " & n.type
next
' "poly" = polygon cluster
' "sample" = UV cluster

Output example:

' INFO : Cluster Polygon4 is of type poly
' INFO : Cluster Polygon1 is of type poly
' INFO : Cluster Texture_Coordinates_AUTO is of type sample


Bounding box values

' this could be useful to create a bounding box for OFGA files

' let's get the bounding box of a simple cylinder
' the output values will be absolute positions
CreatePrim "Cylinder", "MeshSurface"

dim xmin, ymin, zmin, xmax, ymax, zmax
dim list
'if you use SelectionList the objects will be treated as one single object
'set list = GetValue( "SelectionList" )
set list = GetValue( selection(0) )
GetBBox list, TRUE, xmin, ymin, zmin, xmax, ymax, zmax
LogMessage "Lower Bound: " & xmin & " / " & ymin & " / " & zmin
LogMessage "Upper Bound: " & xmax & " / " & ymax & " / " & zmax

' expected output:
' INFO : Lower Bound: -1 / -2 / -1
' INFO : Upper Bound: 1 / 2 / 1

Get the scaling, rotation and position of selected objects

logmessage "mesh name: " & selection(0)
logmessage selection(0).sclx.value
logmessage selection(0).scly.value
logmessage selection(0).sclz.value
logmessage selection(0).rotx.value
logmessage selection(0).roty.value
logmessage selection(0).rotz.value
logmessage selection(0).posx.value
logmessage selection(0).posy.value
logmessage selection(0).posz.value


Get scaling, rotation, and position of not selected objects

' GetValue("NAME.kine.global.rotx")
' NAME must be the exact mesh name
' let's say you want the data of one character's pelvis
logmessage GetValue("pelvis.kine.global.sclx")
logmessage GetValue("pelvis.kine.global.scly")
logmessage GetValue("pelvis.kine.global.sclz")
logmessage GetValue("pelvis.kine.global.rotx")
logmessage GetValue("pelvis.kine.global.roty")
logmessage GetValue("pelvis.kine.global.rotz")
logmessage GetValue("pelvis.kine.global.posx")
logmessage GetValue("pelvis.kine.global.posy")
logmessage GetValue("pelvis.kine.global.posz")


Points

Get position of points (with selection mode point)

' a point must be selected
' gets xyz position of first selected point of the first selected object
logmessage Selection(0).SubComponent.ComponentCollection(0).position.x
logmessage Selection(0).SubComponent.ComponentCollection(0).position.y
logmessage Selection(0).SubComponent.ComponentCollection(0).position.z

Get position of points (with selection mode object)

' an object must be selected
' gets xyz position of point 0 of the first selected object
logmessage selection(0).activeprimitive.geometry.Points(0).Position.x
logmessage selection(0).activeprimitive.geometry.Points(0).Position.y
logmessage selection(0).activeprimitive.geometry.Points(0).Position.z

Get and set position of points (without selection) right after object creation

' point positions are relative to the object's center
' to get the absolute point positions add center to point
' to set the absolute point positions subtract center from point

set oRoot = application.activeproject.activescene.root
set oObj = oRoot.addgeometry( "Cube", "MeshSurface", "test" )

' to test our code move center to somewhere else
Translate oObj, 9, 11, 13, siRelative, siGlobal, siCtr, siXYZ, , , , , , , , , , 0
SaveKey oObj & ".kine.local.posx," & oObj & ".kine.local.posy," & oObj & ".kine.local.posz", 1, , , , True

FreezeObj oObj
set oGeometry = oObj.activeprimitive.geometry
aPositions = oGeometry.Points.PositionArray
' get old position
'                                         (xyz, point)
logmessage "old point 0 posx: " & aPositions(0, 0) + GetValue(oObj & ".kine.global.posx")
logmessage "old point 0 posy: " & aPositions(1, 0) + GetValue(oObj & ".kine.global.posy")
logmessage "old point 0 posz: " & aPositions(2, 0) + GetValue(oObj & ".kine.global.posz")

' set new position
aPositions(0, 0) = -7 - GetValue(oObj & ".kine.global.posx")
aPositions(1, 0) = -7 - GetValue(oObj & ".kine.global.posy")
aPositions(2, 0) = -7 - GetValue(oObj & ".kine.global.posz")

' update the array
oGeometry.Points.PositionArray = aPositions
' get new position
logmessage "new point 0 posx: " & aPositions(0, 0) + GetValue(oObj & ".kine.global.posx")
logmessage "new point 0 posy: " & aPositions(1, 0) + GetValue(oObj & ".kine.global.posy")
logmessage "new point 0 posz: " & aPositions(2, 0) + GetValue(oObj & ".kine.global.posz")

' INFO : old point 0 posx: -4
' INFO : old point 0 posy: -4
' INFO : old point 0 posz: -4

' INFO : new point 0 posx: -7
' INFO : new point 0 posy: -7
' INFO : new point 0 posz: -7


Get global point position

set oObj = selection(0)
set oTrans = oObj.Kinematics.Local.Transform 
set oPoint0 = oObj.ActivePrimitive.Geometry.Points(0)
set oPoint7 = oObj.ActivePrimitive.Geometry.Points(7)
set oPos0 = oPoint0.Position
set oPos7 = oPoint7.Position

' scaling must be frozen to 1 before we can calculate the size from local values
ResetTransform selection(0), siCtr, siScl, siXYZ

logmessage "local p0: "& oPos0.X & " " & oPos0.Y & " " & oPos0.Z
set oGlobalPos0 = XSIMath.MapObjectPositionToWorldSpace( oTrans, oPos0)
logmessage "global p0: "& oGlobalPos0.X & " " & oGlobalPos0.Y & " " & oGlobalPos0.Z

logmessage "local p7: "& oPos7.X & " " & oPos7.Y & " " & oPos7.Z
set oGlobalPos7 = XSIMath.MapObjectPositionToWorldSpace( oTrans, oPos7)
logmessage "global p7: "& oGlobalPos7.X & " " & oGlobalPos7.Y & " " & oGlobalPos7.Z
 
logmessage "size: " &		oPos7.X - oPos0.X & " " & _
							oPos7.Y - oPos0.Y & " " & _
							oPos7.Z - oPos0.Z
' with a rotation of: -3,8792 16,4039 -13,5017
' INFO : local p0: -4 -4 -4
' INFO : local p7: 4 4 4
' INFO : global p0: -5,74764582364017 -3,00250371537919 -2,43916767056426 ' TRGV start point
' INFO : global p7: 5,74764582364017 3,00250371537919 2,43916767056426
' INFO : size: 8 8 8


Edges

...

Polygons

...


Layers

Check if object is member of layer

logmessage isMemberOfLayer(selection(0), "layerNameToTest")

function isMemberOfLayer(obj, layerName)
	dim list
	set list = selectMembers ("Layers." & layerName, 0) ' 0 = for not changing the current selection

	for each o in list
		if o = obj then
			isMemberOfLayer = "yes"
			exit for
		end if
	next
end function


Get layer name of an object

logmessage RecursiveEnum (selection(0), false, false)

function RecursiveEnum( in_Comp, in_Type, in_FirstParentOnly )
  dim list, elem, layerNameToCheck
  set list = EnumElements( in_Comp, in_Type )
  if TypeName(list) <> "Nothing" then
      for each elem in list
         if instr(elem, "Layers") = 1 and instr(elem, ".Members") > 1 then
         	layerNameToCheck = replace(replace(elem,"Layers.", ""),".Members", "")
    	  	if not layerNameToCheck = "" then
				RecursiveEnum = layerNameToCheck
			end if
    	  	exit for
         end if
      next
  end if
end function

' INFO : Layer_Default


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

Button

If you need quote signs in the Logic section you write two signs: "" (Not shown in example.)

oPPGLayout.AddButton("btnFuncName", "click here to trigger afunction").setAttribute siUICX, 200

oPPGLayout.Logic = "	sub btnFuncName_OnClicked" & vbCrlf & _
"		btnFuncNameReal" & vbCrlf & _
"	end sub"

function btnFuncNameReal
	logmessage "hello world"
end function

New lines in Logic need

& vbCrlf & _

on the earlier line.


Text input

oPSet.AddParameter3 "ParamName", siString
oPPGLayout.AddItem "ParamName", "Caption"


Integer input

oPSet.AddParameter3 "ParamName", siInt2, , , , false, 0
oPPGLayout.AddItem "ParamName", "Caption"

If you try to set a value that is below the defined minimum, you will get this error text:

' ERROR : 2006-EDIT-SetValue - Unexpected failure.
' Syntax: PPGname.data, value
' SetValue "Turret.int1", "0"
' ERROR : Überlauf: 'setvalue' - [line N]

German devs? I expected "overflow", not "Überlauf".


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 or 0
' CustomProperty.AddParameter3( ScriptName, ValueType, [DefaultValue], [Min], [Max], [Animatable], [ReadOnly] )
oPSet.AddParameter3 "Check1", siBool, , , , 0, 0
oPPGLayout.AddItem "Check1", "Checkbox_caption"


Spacer

' AddSpacer( [Width], [Height] )
oPPGLayout.AddSpacer 25


Softimage ICE

Show a simple value in the viewport

XSI ModTool ICE workaround displaying a flag.png
CreatePrim "Cube", "MeshSurface"

' create ice tree
ApplyOp "ICETree", cstr(selection(0)), siNode, , , 0
' create set data node
AddICECompoundNode "Set Data", ".polymsh.ICETree"
' connect set data node to ice tree root
ConnectICENodes ".polymsh.ICETree.port1", ".polymsh.ICETree.Set_Data.Execute"
' create integer node (e.g. for FLAG id)
AddICENode "$XSI_DSPRESETS\ICENodes\IntegerNode.Preset", ".polymsh.ICETree"
' connect integer with set data node
ConnectICENodes ".polymsh.ICETree.Set_Data.Value", ".polymsh.ICETree.IntegerNode.result"
' set data in object hosting (hence "self.") the ice tree
SetValue ".polymsh.ICETree.Set_Data.Reference", "self.tmp"
' show integer in viewport !
DisplayPortValues ".polymsh.ICETree.Set_Data.Value", True, 0, True, , 0, 0, 0, 1, False, True, 0.62, 1, 0.62, 1, _
			False, 0, 10000, 1, False, False, 0, 10, False, True, False, 100

' set new integer value
SetValue ".polymsh.ICETree.IntegerNode.value", 14