Mod Tool/Scripting: Difference between revisions
Paradox-01 (talk | contribs) (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. | |||
==== | ====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=== | |||
[[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 | |||
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 " | ' 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 | 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 | ||
' | 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" | |||
' | ' 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 | |||
[[Category:Modding tutorials]] | |||
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
- xsi wiki page about scripting
- many vbscript examples
- vbs commands
- objFSO/objWSHShell: Scripts to manage Files (replace "Wscript.Echo" with "logmessage")
- objFSO/objWSHShell: Scripts to manage Text Files
- using external scripts
- progress bar and open file dialog
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
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
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
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