Mod Tool
General information
Various modders of the community use "Autodesk Softimage Mod Tool". Usually we just call it "Mod Tool" or "XSI" (old name of the program).
Beside the free version, there's also the retail and student version "Autodesk Softimage [year]"
- Those versions are also available as 64-bit versions.
- If you are a student you can get Autodesk Software for free if your school is a partner of Autodesk.
- In that case you can register with your email address given by your school.
But in the very most cases "Mod Tool" isn't really in disadvantage towards the retail version when it comes to modding Oni.
There's also the possibility to get files that extent Mod Tool's range of functions.
- Those are scripts (.vbs, .js), toolbars (.xsitb) and addons (.xsiaddon).
- They can be create on one's own and also shared with other users.
Wanted knowledge:
- How to setup particle in Mod Tool? We still need an Oni particle editor!
- How to rig models and then extract the animation data?
Extensions
links
- "Roadkill" UV Tool
- drag and drop support for OBJ files
- OBJ exporter
- OBJ + OFGA exporter for Oni
- might need the regular OBJ exporter to be installed first
- adjusting existing fw throws (instructions)
- put more links here
how to bind scripts to buttons (embedded code or .vbs / .js)
- You can enable/disable the Main Shelf over "View" > "Optional Panels" > "Main Shelf". Then click the "Custom" tab.
- A) Right-click the free gray space, click on "Customize Toolbar...". Select "Toolbar Widgets" > "Script Button".
- Enter your code and make sure you selected the correct Scripting Language. Click okay when you are done.
- B) You can also drag'n'drop your script file directly onto the free gray space.
- A new window pops up, check the settings and click okay when done.
- Right-click the Main Shelf and use the "Save" option. ("Save As" let you save the Main Shelf as toolbar file.)
how to install toolbars (.xsitb)
- creation: goto "View" > "New custom toolbar"
- Now you can add buttons like in Main Shelf. Remember to save your changes. (Right-click, "Save As".)
- installation: Drag'n'drop the xsitb file onto the viewport (it's simply the window where you can see 3D objects).
- access: goto "View" > "Toolbars"
- deletion: goto "View" > "Manage..." Check a toolbar and click Delete button.
- The file will remain with a new file suffix ".bak" You can restore this backup if you remove the ".bak" from the name.
how to install add-ons (.xsiaddon)
- A) drag'n'drop the file into the so-called viewport (it's simply the window where you can see 3D objects), then restart Mod Tool
- B) if drag and drop doesn't work, goto "File" > "Add-On" > "Install"
- Then click on the "..." button to search for the file. When you found it, click the "Install" button.
- Building an add-on file needs more effort than the other 'extensions' but for the end user it's best because it can hold all other files: toolbars, plugins (scripts), etc.
- The user can install a newer version of the addon by repeating the installation with the new file: it's unnecessary to remove the old version.
- File > Add-On > Package... doesn't work with ModTool "Add-Ons cannot be created with Softimage Demo Version."
- Images and any other files can be put into any folder of the add-on.
- Let's say you have an addon called "my_stuff", a subfolder "images" and an image called "test.png". The image can be accessed via:
- XSIUtils.ResolvePath("$XSI_USERHOME/") & "Addons\my_stuff\images\test.png"
- The problem with embedded code: commands like CreatePrim can call an property page (PPG).
- Those popups can be disabled. Search for "disabling PPG popups" on this page.
- Putting scripts simply into folders doesn't work, they must be "plugins", the file extension can be the same e.g. "vbs".
- A script function can run on another computer if it is found as command, and plugin register new commands.
- E.g. you can use those commands in your toolbar's script buttons.
- Example of turning a function into a command:
function with arguments as it is
' this function can only be used inside the script where it was defined function simple_math (a, b, operator) if operator = "+" then simple_math = a + b elseif operator = "-" then simple_math = a - b elseif operator = "/" then simple_math = a / b elseif operator = "x" then simple_math = a * b else logmessage "The used operator is not allowed. Choose one of the following:" & vbCrlf & "+ - / x" end if end function ' example of usage result = simple_math(5, 3, "+") logmessage result
the same function as command
' the command simple_math can now be used in any script function XSILoadPlugin( in_reg ) in_reg.Author = "your_name" in_reg.Name = "simple_mathPlugin" in_reg.Email = "" in_reg.URL = "" in_reg.Major = 1 in_reg.Minor = 0 in_reg.RegisterCommand "simple_math","simple_math" 'RegistrationInsertionPoint - do not remove this line 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 simple_math_Init( in_ctxt ) dim oCmd set oCmd = in_ctxt.Source oCmd.Description = "" oCmd.ReturnValue = true dim oArgs set oArgs = oCmd.Arguments oArgs.Add "a",siArgumentInput,"0" oArgs.Add "b",siArgumentInput,"0" oArgs.Add "operator",siArgumentInput,"0" simple_math_Init = true end function function simple_math_Execute( a, b, operator ) ' convert to double a = cdbl(a) b = cdbl(b) if operator = "+" then simple_math_Execute = a + b elseif operator = "-" then simple_math_Execute = a - b elseif operator = "/" then simple_math_Execute = a / b elseif operator = "x" then simple_math_Execute = a * b else logmessage "The used operator is not allowed. Choose one of the following:" & vbCrlf & "+ - / x" end if end function
Selected wisdom
We've also a tutorial thread on OCF.
Visibility
- show/hide grid: [G]
- display polygon normals: click the eye symbol, select "Polygon Normals" (now you know the polygons up and down)
- display triangle count: click the eye symbole, select "Selection Info"
- reset view: [R]
- zoom camera: [Scrolling]
- move camera: [S] + [LClick] + [MoveMouse]
- rotate camera: [S] + [RClick] + [MoveMouse]
- pan camera: [S] + [MClick] + [MoveMouse]
- move camera mode: [Z]
- rotate camera mode: [O]
- pan camera mode: [P]
- use camera mode move/rotate/pan with: [LClick] + [MoveMouse]
Mesh
- scale mode: [X]
- rotate mode: [C]
- translate mode [V]
- manipulate tool: [B] (a gray box appears with gray balls at the corners)
- drag inside the object to move it
- drag an edge to rotate it
- drag a ball to scale it
- object selection mode: [Space]
- extracting a polygon from an object: select polygon, right-click, "Extract Polygons (Delete)"
- copying an polygon from an object: select polygon, right-click, "Extract Polygons (Keep)"
- merging two objects: select object A, then goto Model > Modify > Poly. Mesh > Boolean > Union, select object B
- (this makes a copy of object B and attaches it to A, the merged objects (A) must be freezed before the original object B can be removed)
- filling a hole: select all edges surrounding the hole and choose one of the following methods
- A) goto Model > Modify > Poly. Mesh > Weld Boundary Points/Edges
- B) goto Model > Modify > Poly. Mesh > Bridge Boundary Points/Edges (if blue lines appear try to disable the checkbox "Angle > 90")
- merging two objects at blue lines: merge the objects at first, then proceed with the point above "filling a hole"
- merging two objects with UVs: Create > Poly. Mesh > Merge
- extracted Oni characters have their materials and hence textures directly grouped under the mesh (see Explorer)
- when adding (merging) a new part to the existing mesh, the materials/textures will be grouped under (Explorer again) mesh > Polygon Mesh > Clusters > ...
- Samer reported that saving the file via fbx solves a problem when trying to convert TRBS
- one would need to move the content by hand in the explorer to bypass the fbx
- hiding some objects/polygons: select object/polygon and hit [H], use [Control] + [H] to unhide all
- mirroring an object: select object, Modify > Poly. Mesh > Symmetrize Polygons
- working with symmetry mode (look below at "Low poly modeling" section for other possibilities with symmetry)
- select object, Property > Symmetry Map (YZ/XZ/XY) (press [W] to preview symmetry plane)
- click on Sym buttom (in Transform area) to enable/disable symmetry mode
- changing the Symmetry plane: Explorer [8] > unfold object > Polygon Mesh > Cluster > delete SymmetryMapCls; then add a new symmetry plane
Low poly modeling
- select edge loop: while being in selection mode Edge, press [Alt] + [MClick]
- modify point/edge/polygon: [M]
- press left mouse button and hold it, move mouse to translate point/edge/polygon freely
- if axes symbol is active you can left-click a component and the translate axes pop up
- if magnet symbol is active the translation will be limited by the surrounded components
- if point symbol is active then only points can be selected, moving one point onto another will weld them
- By the way, combining edge loop with component selection [M] and active magnet gives an interesting effect: a line of edges will mimic the shape of their neighbor edges as closer the selected edges comes to the neighbors.
- getting images into Mod Tool that serve as modeling reference:
- click where you can set the view mode (e.g. to "Wireframe"), select "Rotoscope"
- in the appearing window at "Image Placement" choose "Fixed" so that the image will be zoomed when you zoom on the object
- choose an image by clicking the button "New"
symmetry in modeling:
You could make A) only one half of an object or B) selected polygons and then mirror and merge the parts.
- A) select object, [Alt] + [RClick] object, Symmetrize Polygons
- B) select polygons, [RClick] one of them, Symmetrize Polygons
Or you start with a half object and create a mirrored clone. You can then work on the original mesh. (In Schematics the cloned mesh will have a red "Cl" and a trapezoid shape as symbol.)
- select object then goto Edit > Duplicate/Instantiate > Clone Single
- scale X or one of the other axes to -1
- make any modification on the original mesh, the clone will adept them
- eventually merge cloned and original mesh, freeze them to make changes final
Exchange meshes in hierarchies
Let's say you loaded an Oni character with his 19 body parts into the scene, something is wrong and you want to fix it.
To destroy the hierarchy you must be sure the model is not animated. If there are keyframes the model will be one big mess after breaking the hierarchical links.
So, save the model with the settings "Selection Only [X]" and "Animation [ ]". Then reload the model.
Next, disable the red "Auto" button under "Animation" (if enabled). Keyframes affects also the creation of hierarchies, that's why we don't want new keyframes.
Now you can safely exchange, move and rotate meshes and their centers, and eventually rebuild the hierarchy.
Material and texture
- objects use the scene material by default, objects need their own material if they should have individual textures
- selecting a material and surface: Model > Material > Phong (simply close the windows that pops up)
- connecting the texture with the material:
- [7] to open Render Tree
- in first column: "Texture"
- in second column: drag'n'drop "Image" into the free space
- click and hold the red point of "Image", then move it to "Phong" and release the mouse click
- choose "diffuse" as illumination mode (for instance "ambient" won't work - the texture would be visible in Mod Tool but Onisplit can't use it)
- now "Image" can be double-clicked (the Texture Editor will pop up), in the section "Image" click on "New" to choose an image
Projection and UV
getting started
- in section "Texture Projection" click on "New", choose a projection similar to the mesh shape (planar / cylindrical / cubic / spherical)
- select the object
- Texture editor: [Alt]+[7] (open it while being in selection mode "Object" or "Polygon", otherwise the UVs aren't shown)
modify single/all UVs
- rotate 90°: [R]
- rotate: [C]
- scale: [X]
- scale height only: [X], then [Shift] + [MClick] + [MoveMouse]
- scale length only: [X], then [Shift] + [RClick] + [MoveMouse]
- move: [V]
- tearing single UV parts: [Control] + [T], then move [V]
- heal (re-connect) selected UV parts: click on the plus symbol (the parts should be very near to each other, best if the points are on same spot)
- snap function for move tool: Snap > Enable Snapping; check "Points" (if not already active)
- relax (smooth warped UVs): [Control] + [R] (usually pressing several times that combination)
- a new projection can also be applied to currently selected UV parts
- freeze object to bake projections (green lines in the 3D view will disappear)
- getting a picture of the UV: apply first a black texture then enter the Texture Editor [Alt] + [7], Edit > Stamp UV Mesh [Shift] + [S], save the texture
Animating
- preparing ModTool to save animations Oni can use: File > Preferences... > Preferences > Tools > Output Format > Frame Step: 1; Frame Format: Custom framerate; Frame Rate: 60
- opening the Animation Editor : [0]
- opening the DopeSheet: from any other Animation Editor, Editor > DopeSheet
- making an animation shorter or longer: in DopeSheet click Region button (arrow with rectangle), click and hold to select keyframes, at selection edge a) [shift] plus left-click and move mouse or b) middle-click and move mouse
Scripting
- 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
- 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
Notes about polygon extraction
- a grid of 9 polygons (ID 0 to 8)
- you extract the middle polygon (ID 4)
- grid's polygon of ID 8 will change to ID 4
- if you now extract 5th, the 7th gets the ID
- now the ID order is: 0, 1, 2, 3, 6, 5, 4
- this means that an automatic extraction of several polygons requires two changes in the code
- 1) the ID array of selected polygons must be sorted
- 2) the extraction must start with the last ID to avoid ID shift during the process (see code pieces)
Some basics of VBScript
- In VBS you declare variables only as a variant e.g. "dim PolygonCount". Mod Tool decides what type it becomes when it uses the variable the first time.
- You can put "option explicit" at the start of your script. Then you will be forced to declare all variables with "dim". That looks quite unnecessary but help to find mistakes for example if you mistyped a variable somewhere in a long script.
- The commands are not case-sensitive, e.g. you can write "LogMessage" or "logmessage".
- Vbs functions can be found HERE.
' while it is possible to use + in strings, ' you should actually use & only for strings ' and + only for numbers logmessage "How about ... " + "a riddle?" logmessage "Where can you find following phrase in Oni? " & "The day is mine !!" logmessage "Did you know that Bungie likes to hide sevens? The digits of the year 2032 add up to " & cstr(2 + 0 + 3 + 2) & "." ' INFO : How about ... a riddle? ' INFO : Where can you find following phrase in Oni? The day is mine !! ' INFO : Did you know that Bungie likes to hide sevens? The digits of the year 2032 add up to 7.
More tips
- You can replace each "logmessage" with a variable. In that case don't forget the = sign.
- Selection(0) means that from all selected objects the first one is taken.
- Use a loop to get all objects.
for each n in selection logmessage n next
- In order to shorten the code you might want to set script objects.
- The command to create a standard cube is CreatePrim "Cube", "MeshSurface"
- If you use set name = the cube or other object will be available under that name.
- Then you can use any valid method or property with that name.
- Example:
set c = CreatePrim ("Cube", "MeshSurface") logmessage selection(0).name logmessage selection(0).length.value logmessage c.name logmessage c.length.value ' INFO : cube ' INFO : 8 ' INFO : cube ' INFO : 8
Some ModTool commands in vbs language that might be interesting for Oni related stuff
LogMessage [string | var | object] | A command to log information. It requires a string or a variable. Example:
|
logmessage selection.count | Logs the number of selected objects. |
logmessage selection(0).Name | Logs the object name. |
logmessage selection(0).Material.Name | Logs the material name. |
logmessage selection(0).Material.CurrentImageClip.source.filename.value | Logs absolute path of the texture. Output example:
|
logmessage selection(0).Material.CurrentImageClip.source.Parameters("XRes").Value logmessage selection(0).Material.CurrentImageClip.source.Parameters("YRes").Value |
Logs the horizontal (X) and vertical (Y) pixel length of the texture. Output example:
|
logmessage selection(0).sclx.value logmessage selection(0).scly.value logmessage selection(0).sclz.value |
Logs object's scaling (X, Y, Z). |
logmessage selection(0).rotx.value logmessage selection(0).roty.value logmessage selection(0).rotz.value |
Logs object's rotation (X, Y, Z). |
logmessage selection(0).posx.value logmessage selection(0).posy.value logmessage selection(0).posz.value |
Logs object's position (X, Y, Z). |
logmessage selection(0).rotorder.value | Logs object's rotation order. Oni characters have ZYX as rotation order.
|
SelectObj [string | var] | select one or more objects. Comma serves as separator. Example:
|
ToggleSelection [string | var] | Adds one or more objects from current selection. If the objects are already selected then they become subtracted from the selection. Example:
|
SelectAllUsingFilter [SelFilter], [CheckComponentVisibility], [AffectSelectionList], [CheckObjectSelectability] | Selects everything that matches the filter and other options. Example:
|
DeselectAll | deselects everything |
DeleteObj [string | var] | Deletes one or more objects. Comma used as separator. Example:
|
FreezeObj | Makes all changes final. But saving those changes is still necessary. (Click on key symbol so it gets red.) |
Translate [InputObjs], [X], [Y], [Z], [Delta], [RefMode], [Center], [AxesFilter], [Snap], [SnapReference], [SnapFilter], [SplitLocalComponents], [PropTagOnly], [Pivot], [PivotX], [PivotY], [PivotZ], [ConstructionMode], [SlideComponents] | see link |
Rotate [InputObjs], [X], [Y], [Z], [Delta], [RefMode], [Center], [AxesFilter], [Reference], [SplitLocalComponents], [PropTagOnly], [Pivot], [PivotX], [PivotY], [PivotZ], [ConstructionMode], [SlideComponents] | see link |
Code pieces (VB Script)
Remember that you can use the search function of your web browser.
If you see absolute paths adapt them so that the code works on your system too.
[1] creating and extending an array | [2] check selection mode | [3] getting the IDs of selected polygons plus sorting them |
[4] converting euler rotations (in degrees) to quaternions | [5] converting quaternions to euler rotations (in degrees) | [6] get and set values of the playcontrol (timeline) |
[7] get keyframes | [8] message box | [9] input box |
[10] getting the desktop path | [11] selecting a folder | [12] selecting a file of a specific type |
[13] reading out environment variables | [14] check if file exist | [15] check if folder exist + create folder |
[16] write xml file | [17] create txt file + write lines | [18] read txt file |
[19] check onisplit version | [20] call CMD, e.g. to use onisplit | [21] building forms in ModTool |
[22] UserDataBlob | [23] getting the position of points (with selection mode point) | [24] getting the position of points (with selection mode object) |
[25] getting and setting the position of points (without selection) | [26] getting the rotation and position of selected objects | [27] getting the rotation and position of not selected objects |
[28] decrypting merged integer flags | [29] disabling PPG popups | [30] disabling logmessages |
[31] getting bounding box values | [32] open an explorer window | [33] working with hierarchies |
[34] drag and drop (DnD) support for specific oni files |
' [1] creating and extending an array ' creating an array ' () sets the number of array items redim nums (3) for i = 0 to 3 nums(i) = (i) next logmessage "output all items" for each n in nums logmessage n next logmessage "first array item holds: " & lbound(nums) logmessage "last array item holds: " & ubound(nums) logmessage "-------------------------------------" ' extending an array ' use "preserve" to keep the old content ' find next higher value plus_one = ubound(nums) + 1 redim preserve nums (plus_one) nums(plus_one) = 4 for each n in nums logmessage n next logmessage "first array item holds: " & lbound(nums) logmessage "last array item holds: " & ubound(nums) ' INFO : output all items ' INFO : 0 ' INFO : 1 ' INFO : 2 ' INFO : 3 ' INFO : first array item holds: 0 ' INFO : last array item holds: 3 ' INFO : ------------------------------------- ' INFO : 0 ' INFO : 1 ' INFO : 2 ' INFO : 3 ' INFO : 4 ' INFO : first array item holds: 0 ' INFO : last array item holds: 4
' [2] 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
' [3] getting the IDs of selected polygons plus sorting them poly_count = Selection(0).SubComponent.ComponentCollection.count logmessage "poly_count: " & poly_count redim selected (poly_count) logmessage "Show ID as it is." for i=0 to poly_count - 1 ' build ID array selected(i) = Selection(0).SubComponent.ComponentCollection(i).index logmessage selected(i) next ' change sort order by replacing ">" with "<" For i = 0 To poly_count - 1 For j = 0 To poly_count - 1 If selected(i) > selected(j) Then tmp = selected(i) selected(i) = selected(j) selected(j) = tmp End If Next Next logmessage "Show sorted ID." for i=0 to poly_count - 1 logmessage selected(i) next
' [4] converting euler rotations (in degrees) to quaternions 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
' [5] converting quaternions to euler rotations (in degrees) 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
' [6] get and set values of the playcontrol (timeline) ' timeline variables (tl) dim tlStart, tlEnd, tlCurrent ' get values tlStart = GetValue ("PlayControl.In") tlEnd = GetValue ("PlayControl.Out") tlCurrent = GetValue ("PlayControl.Current") ' set values SetValue ("PlayControl.In"), 7 SetValue ("PlayControl.Out"), 70 SetValue ("PlayControl.Current"), 14
' [7] get keyframes ' keyframe counting code like "selection(0).rotx.Source.Keys.count" will produce an error if no keys exist ' that's why we need to catch possible errors for each counting on error resume next logmessage "--------------------------------------------------" logmessage "X rotation keys: " & selection(0).rotx.Source.Keys.count for each k in selection(0).rotx.Source.Keys logmessage "index: " & k.index & " // time: " & k.time & " // value: " & k.value next if err <> 0 then logmessage "no X rotation keys" end if on error goto 0 on error resume next logmessage "--------------------------------------------------" logmessage "Y rotation keys: " & selection(0).roty.Source.Keys.count for each k in selection(0).roty.Source.Keys logmessage "index: " & k.index & " // time: " & k.time & " // value: " & k.value next if err <> 0 then logmessage "no Y rotation keys" end if on error goto 0 on error resume next logmessage "--------------------------------------------------" logmessage "Z rotation keys: " & selection(0).rotz.Source.Keys.count for each k in selection(0).rotz.Source.Keys logmessage "index: " & k.index & " // time: " & k.time & " // value: " & k.value next if err <> 0 then logmessage "no Z rotation keys" end if on error goto 0 on error resume next logmessage "--------------------------------------------------" logmessage "X position keys: " & selection(0).posx.Source.Keys.count for each k in selection(0).posx.Source.Keys logmessage "index: " & k.index & " // time: " & k.time & " // value: " & k.value next if err <> 0 then logmessage "no X position keys" end if on error goto 0 on error resume next logmessage "--------------------------------------------------" logmessage "Y position keys: " & selection(0).posy.Source.Keys.count for each k in selection(0).posy.Source.Keys logmessage "index: " & k.index & " // time: " & k.time & " // value: " & k.value next if err <> 0 then logmessage "no Y position keys" end if on error goto 0 on error resume next logmessage "--------------------------------------------------" logmessage "Z position keys: " & selection(0).posz.Source.Keys.count for each k in selection(0).posz.Source.Keys logmessage "index: " & k.index & " // time: " & k.time & " // value: " & k.value next if err <> 0 then logmessage "no Z position keys" end if on error goto 0 ' output example (0 rot keys, 2 pos keys): ' INFO : -------------------------------------------------- ' INFO : no X rotation keys ' INFO : -------------------------------------------------- ' INFO : no Y rotation keys ' INFO : -------------------------------------------------- ' INFO : no Z rotation keys ' INFO : -------------------------------------------------- ' INFO : X position keys: 2 ' INFO : index: 0 // time: 2 // value: 5,14534318031942 ' INFO : index: 1 // time: 6 // value: 8,2411504340802 ' INFO : -------------------------------------------------- ' INFO : Y position keys: 2 ' INFO : index: 0 // time: 2 // value: 0,365325291829147 ' INFO : index: 1 // time: 6 // value: 1,11923927115289 ' INFO : -------------------------------------------------- ' INFO : Z position keys: 2 ' INFO : index: 0 // time: 2 // value: 1,96103417177471 ' INFO : index: 1 // time: 6 // value: 1,88564277384233
' [8] message box msgbox "message", , "title"
' [9] input box logmessage inputbox ("message", "title" , "pre-entered content")
' [10] getting the desktop path ' this can be useful for default locations like when selecting a folder DesktopPath = CreateObject("WScript.Shell").SpecialFolders("Desktop") logmessage DesktopPath
' [11] selecting a folder ' the default path is in this case my desktop ' to make it work on other systems, you would need the code of "getting the desktop path" section and replace my path with the DesktopPath logmessage XSIUIToolkit.PickFolder("C:\Users\RMM\Desktop\", "title" )
' [12] selecting a file of a specific type set oFileBrowser = XSIUIToolkit.FileBrowser oFileBrowser.DialogTitle = "Select an image file (png/tga/jpg)" ' (default folder) oFileBrowser.InitialDirectory = "c:\" oFileBrowser.Filter = "JPEG (*.jpg)|*.jpg|PNG (*.png)|*.png| Targa (*.tga)|*.tga||" oFileBrowser.ShowOpen If oFileBrowser.FilePathName <> "" Then logmessage "User selected " & oFileBrowser.FilePathName Else logmessage "User pressed cancel" End If
' [13] reading out environment variables ' some infos about env vars: (1), (2), (3) ' those variables are stored inside the setenv.bat, two disadvantages: ' adding or editing those vars appears to not work in vbs or I just did it wrong ' anyway, new vars can only be read out after app restart ' therefore let's concentrate on reading out existing ones and then how to create our own via txt files (example: Oni_env_vars.txt) ' code for reading out: logmessage XSIUtils.ResolvePath("$SI_HOME/")
' [14] check if file exist Set objFSO = CreateObject("Scripting.FileSystemObject") If objFSO.FileExists ("C:\folder\file.txt") then logmessage "File exists." else logmessage "File doesn't exist." end if
' [15] check if folder exist + create folder dim strDirectory strDirectory = "C:\test" Set objFSO = CreateObject("Scripting.FileSystemObject") If objFSO.FolderExists(strDirectory) Then Set objFolder = objFSO.GetFolder(strDirectory) logmessage strDirectory & " already exists" Else Set objFolder = objFSO.CreateFolder(strDirectory) logmessage "created folder " & strDirectory End If
' [16] write xml file FolderName = CreateObject("WScript.Shell").SpecialFolders("Desktop") FileName = "test" FilePath = FolderName & "\OBAN" & FileName & ".xml" Set oFS = CreateObject("Scripting.FileSystemObject") Set objXMLFile = oFS.OpenTextFile(FilePath, 2, True, 0) ' quote sign in a string needs two quote signs objXMLFile.WriteLine "<?xml version=""1.0"" encoding=""utf-8""?>" objXMLFile.WriteLine "<Oni>" objXMLFile.WriteLine "..."
' [17] create txt file + write lines 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
' [18] read txt 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
' [19] check onisplit version 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
' [20] call CMD, e.g. to lunch onisplit or the game ' 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
' alternative to cmd: call onisplit 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
' [21] building a form (PPG) dim oPSet, oPPGLayout, oItem, PPG_exist ' set check value to 0 PPG_exist = 0 ' check if PPG exists for each prop in ActiveProject.ActiveScene.Root.Properties logmessage prop.name if instr(1, prop.name, "my_new_PPG") > 0 then PPG_exist = 1 logmessage "found " & """" & prop.name & """" end if next ' create PPG if it doesn't exist if PPG_exist = 0 then set oPSet = ActiveSceneRoot.AddProperty("CustomProperty", false, "my_new_PPG") set oPPGLayout = oPSet.PPGLayout ' setup PPG parameters ' ################################################################################### ' create checkbox and remove key symboles by setting parameter "Animatable" to false oPSet.AddParameter3 "Check1", siBool, 0, , , false oPSet.AddParameter3 "Check2", siBool, 0, , , false oPSet.AddParameter3 "Check3", siBool, 1, , , false oPSet.AddParameter3 "Check4", siBool, 1, , , false oPSet.AddParameter3 "text1", siString ' last parameter of "int1" is set to ReadOnly oPSet.AddParameter3 "int1", siInt2, , , , false, 1 oPSet.AddParameter3 "int2", siInt2, , , , false, 0 ' add PPG items ' ################################################################################### oPPGLayout.AddItem "text1", "Hi there!" oPPGLayout.AddItem "int1", "number 1" oPPGLayout.AddItem "int2", "number 2" oPPGLayout.AddGroup "4 checkboxes", true oPPGLayout.AddRow oPPGLayout.AddItem "Check1", "C1" oPPGLayout.AddItem "Check3", "C3" oPPGLayout.EndRow oPPGLayout.AddRow oPPGLayout.AddItem "Check2", "C2" oPPGLayout.AddItem "Check4", "C4" oPPGLayout.EndRow oPPGLayout.EndGroup oPPGLayout.AddButton("log_values", "log PPG values").setAttribute siUICX, 120 oPPGLayout.Logic = "sub log_values_OnClicked" & vbCrlf & _ " logmessage ""text 1 = "" & getvalue(""my_new_PPG.text1"")" & vbCrlf & _ " logmessage ""number 1 = "" & getvalue(""my_new_PPG.int1"")" & vbCrlf & _ " logmessage ""C1 = "" & getvalue(""my_new_PPG.Check1"")" & vbCrlf & _ " end sub" oPPGLayout.Language = "VBScript" 'Optional because this is the default ' open PPG InspectObj oPSet else ' open that PPG if it already exist InspectObj "my_new_PPG" end if
' set values from outside the PPG setvalue "my_new_PPG.text1", "any text could stand here" setvalue "my_new_PPG.int1", "42" setvalue "my_new_PPG.check1", true ' get values from outside the PPG logmessage getvalue("my_new_PPG.text1") logmessage getvalue("my_new_PPG.int1") logmessage getvalue("my_new_PPG.check1")
' [22] UserDataBlob ' you can attach data to objects, e.g. new properties for the scene root or Oni Trigger Volumes ' the so-called "UserDataBlob" can't be saved in *.dae, so save the whole scene to keep your progress ' check if my property exist found_my_prop = 0 set oProps = ActiveProject.ActiveScene.Root logmessage "root name: " & ActiveProject.ActiveScene.Root for each prop in oProps.Properties ' remove apostroph in line beneath to log all root properties 'LogMessage prop.Name if instr(1, prop.Name, "my_prop") = 1 then found_my_prop = 1 end if next ' prop doesn't exist, create it if found_my_prop = 0 then ' create property (must also have content, in this case "yes") oProps.AddProperty( "UserDataBlob", , "my_prop" ).value = "yes" logmessage "created my new prop" end if ' prop exists, change/read its value if found_my_prop = 1 then ' change property oProps.Properties( "my_prop" ).value = "no" ' read property logmessage oProps.Properties( "my_prop" ).value ' if you want to read a property from another script be sure that it was already created ' reuse code under "check if my property exist" end if
' [23] getting the 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
' [24] getting the 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
' [25] getting and setting the 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
' [26] getting 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
' [27] getting the 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")
' [28] decrypting merged integer flags ' for example the BINACJBOTrigger Volume.xml file has flags as strings for the <Flags> tag ' and integer values for the <Teams> tag whereby the values are merged to one value ' so if <Teams> tag holds "101" we have to think what flags it holds, the flags can be broken down with following code ' x: hypothetical value of <Teams> x=101 ' 101 = 64 + 32 + 4 + 1 x_255 sub x_255 if x > 255 then msgbox "Flags bigger than 255 are not allowed.", ,"error" exit sub end if if x >= 128 and x <= 255 then logmessage "128" x = x - 128 end if if x >= 64 and x < 128 then logmessage "64" x = x - 64 end if if x >= 32 and x < 64 then logmessage "32" x = x - 32 end if if x >= 16 and x < 32 then logmessage "16" x = x - 16 end if if x >= 8 and x < 16 then logmessage "8" x = x - 8 end if if x >= 4 and x < 8 then logmessage "4" x = x - 4 end if if x >= 2 and x < 4 then logmessage "2" x = x - 2 end if if x = 2 then logmessage "2" x = x - 2 end if if x = 1 then logmessage "1" x = x - 1 end if if x = 0 then logmessage "finished check" end if end sub ' INFO : 64 ' INFO : 32 ' INFO : 4 ' INFO : 1 ' INFO : finished check
' [29] disabling PPG popups ' for example if code of a script button creates numerous objects in one go, the same number of property pages (PPG) can appear ' for user convenience those PPG popups can 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
' [30] disabling logmessages ' msglogverbos is probably already set to false by default ' could be set at the start of a final script to increase performance setvalue "preferences.scripting.msglogverbose", false setvalue "preferences.scripting.msglog", false
' [31] getting 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
' [32] open an explorer window ' this could be used open an output folder ... strPath = "explorer.exe /e," & XSIUtils.ResolvePath("$SI_HOME/") Set objShell = CreateObject("Wscript.Shell") objShell.Run strPath ' to select a generated file inside the folder: strPath = "explorer.exe /select," & "C:\folder\test.txt"
SelectNeighborObj obj 5 |
' [33] working with hierarchies ' let's be sure we have an object selected if selection.count > 0 and typename(selection(0)) = "X3DObject" then ' the object itself logmessage selection(0) ' the parent object logmessage selection(0).parent ' the children for each n in selection(0).FindChildren( , , , 0 ) logmessage n next ' the 4the parameter of FindChildren can be set to 0 or 1 ' with 0 you get all direct children ' with 1 you get everything located under that object no matter how deep the level ' count all objects logmessage "children: " & selection(0).FindChildren( , , , 1).count ' caution: if the 3rd parameter is set to siMeshFamily ' then the selected object will be also counted ' find the root object of the hierarchy, any object could be selected SelectNeighborObj selection(0), 4 ' go through the hierarchy in different directions ' SelectNeighborObj [parameter1], [parameter2], [parameter3], [parameter4] ' options of the second parameters ' 0 go one level up ' 1 go one level down ' 2 go to next silbing ' 3 go to previous silbing ' 4 go to highest level ' 5 go to deepest level of current selected obj ' 5: if there are more than one children, the path goes always for the first (left) child (see image) ' select the hierarchy as a whole SelectObj "left_thigh", "TREE", true ' first parameter could be any object of the hierarchy ' after using this the selection of an Oni character will be "B:pelvis" else msgbox "no object was selected" end if
[34] drag and drop support for specific oni files
function XSILoadPlugin( in_reg ) in_reg.Author = "" in_reg.Name = "Oni_drag_and_drop" in_reg.Email = "" in_reg.URL = "" in_reg.Major = 1 in_reg.Minor = 0 in_reg.RegisterEvent "support_oni_DnD",siOnDragAndDrop 'RegistrationInsertionPoint - do not remove this line 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 support_oni_DnD_Init( in_ctxt ) dim action set action = in_ctxt.GetAttribute( "DragAndDropAction" ) end function ' Callback for the support_oni_DnD event. function support_oni_DnD_OnEvent( in_ctxt ) filename = in_ctxt.GetAttribute("DragSource") ' isolate file extension from full name FileExt = right(filename, len(filename) - instrrev(filename, ".")) ' use LCase to recognize extension that might be in upper case, e.g. rewrites "ONI" to "oni" if len(FileExt) = 3 and instr(LCase(FileExt), "oni") = 1 then 'enable drop if file extension is "oni" in_ctxt.SetAttribute "DragSourceSupported", true end if ' catch file drop event if in_ctxt.GetAttribute("DragAndDropAction") = 1 then ' code that handels the imported file ' there could be a subtype recognition e.g. to let onisplit convert AKEV to dae files ' [...] end if ' Return value is ignored as this event can not be aborted. support_oni_DnD_OnEvent = true end function
Script ideas
- addon to make easily use of onisplit within Mod Tool [done]
- supports drag and drop of onisplit
- onisplit's location will be saved and resaved in a txt
- updates scene property after txt changes
- loads txt on app start as scene property
- use backup onisplit from addon if user is to lazy for DnD
- import/export of flag collection
- import/export of patrol path collection
- import/export of weapons collection
- import/export of powerup collection
- [...]
- autoheal discontinuities (blue lines) of overlaying edges (for repairing google sketchup objects that weren't yet textured)
- making loops from parts of animations
- copying animation data from one character to another
The following sections are about ideas that are checked right now for their possibility of realization or already in the making.
Oni tools addon
exported warehouse TVs |
- current content
- Trigger volumes: import / add / export
- camera OBAN: import (DnD) / export
Code needs improvement to ease integration of other file types and other code pieces.
Selection tracking camera
' enable automatic tracking set_auto_tracking ("yes") ' disable automatic tracking set_auto_tracking ("no")
There could be other buttons to jump to previous/next object grouped under their parent.
SelectNeighborObj , 2 SelectNeighborObj , 3
New camera animations
last frame of OBANOutroCam02 |
There's already an excel macro available but now I want to build cam anims without excel.
The last frame's rotation of level1_Final file OBANOutroCam02 is: -0.0158410165 0.854542 0.0261086561 -0.518483639.
Those quaternions are sorted in X Y Z -W order, so we've to watch out a bit when setting the input for converting them to euler rotations in degrees.
If a newly created object (in this case a cube) shall serve as camera placeholder then the X output has to be multiplied by -1 and the Y output has to be reduced by -180. Those changes must repeated again when exporting the object's rotation to xml.
Update: Sept. 29, 2012
If camera is a real one then the additional X and Y changes aren't necessary. (Primitive > Camera > any should do)
Positions and rotations keyframes can be applied to the camera root object.
Adding multiple textures to level geometry
Material and textures are normally stored right under the object. But AKEV geometry can have more than one texture. Here comes polygon clusters in to play.
An example:
set oCube = Application.ActiveProject.ActiveScene.Root.AddGeometry("Cube","MeshSurface") SelectObj "cube", , True SetSelFilter "Edge" SelectGeometryComponents "cube.poly[LAST]" CreateCluster AddToCluster "cube.polymsh.cls.Polygon, cube.poly[2-4]" RemoveFromCluster "cube.polymsh.cls.Polygon, cube.poly[3]"
The cube has now a polygon cluster. More can be added. Each cluster must get its own material before a texture can be applied to the desired polygons.
[...]
TRBS-fitting TRMA creation
With the following code ...
One more idea is to add support for reflective textures. |
hexhound hierarchy |
if selection.count > 0 then ' any part could be selected, let's find the root body part SelectNeighborObj selection(0), 4 ' get all members including the pelvis set bodyparts = selection(0).FindChildren( , , siMeshFamily) for each member in bodyparts logmessage "object name: " & member.name if not typename(member.Material.CurrentImageClip) = "Nothing" then logmessage "texture: " & member.Material.CurrentImageClip.source.filename.value 'logmessage "material: " & member.Material.name 'logmessage "shader: " & member.Material.shaders(0).name logmessage "X: " & member.Material.CurrentImageClip.source.Parameters("XRes").Value logmessage "Y: " & member.Material.CurrentImageClip.source.Parameters("YRes").Value end if logmessage "----------------------------------------------------------" next logmessage "counted body parts: " & bodyparts.count else logmessage "no object was selected" end if
' INFO : object name: A ' INFO : texture: C:\Users\RRM\Desktop\A.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: R ' INFO : texture: C:\Users\RRM\Desktop\R.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: S ' INFO : texture: C:\Users\RRM\Desktop\S.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: P ' INFO : texture: C:\Users\RRM\Desktop\P.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: Q ' INFO : texture: C:\Users\RRM\Desktop\Q.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: N ' INFO : texture: C:\Users\RRM\Desktop\N.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: O ' INFO : texture: C:\Users\RRM\Desktop\O.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: L ' INFO : texture: C:\Users\RRM\Desktop\L.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: M ' INFO : texture: C:\Users\RRM\Desktop\M.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: B ' INFO : texture: C:\Users\RRM\Desktop\B.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: C ' INFO : texture: C:\Users\RRM\Desktop\C.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: D ' INFO : texture: C:\Users\RRM\Desktop\D.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: E ' INFO : texture: C:\Users\RRM\Desktop\E.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: F ' INFO : texture: C:\Users\RRM\Desktop\F.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: G ' INFO : texture: C:\Users\RRM\Desktop\G.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: H ' INFO : texture: C:\Users\RRM\Desktop\H.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: I ' INFO : texture: C:\Users\RRM\Desktop\I.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: J ' INFO : texture: C:\Users\RRM\Desktop\J.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : object name: K ' INFO : texture: C:\Users\RRM\Desktop\K.tga ' INFO : X: 512 ' INFO : Y: 512 ' INFO : ---------------------------------------------------------- ' INFO : counted body parts: 19
New door animations
Final door animation depends on:
- BINACJBODOOR position and rotation
- OBAN positions and rotations
- ZAxisUp (Does this flag do something else?)
Theory of the (Oni to ModTool) import from scratch:
- import BINACJBODOOR
- get door classes
- extract M3GM from DOOR, etc.
- import M3GM
- move center to lower bounding box Z
- move object to Z = 0
- import OBAN
- apply rotations and positions to object
- create null object
- make null a parent of door object
- rotate null in X = -90 (to make Z axis pointing up)
- add BINACJBODOOR rotation and position to null object
The current version of onisplit (v0.9.68.0) imports the doors from AKEV without animations.
Those door have there centers at the lower bounding box Z, got corrected from their ZAxisUp, and have (BINACJBODOOR) rotation and position.
Writing an own combined import would not only take more time, also it wouldn't be so fast and efficient. So, I will go with the existing import but then do some changes on the doors (appling null object, custom properties, etc.)
The door objects can be identified by their naming. Each name contains the BINACJBODOOR ID, e.g. <DOOR Id="7294">.
There could be a loop scanning all objects for those numbers.
Hierarchy builder for characters
Based on old knowledge. See HERE for an easy manual way to change the meshes in hierarchies.
(old notes in the following)
hierarchy breaker beta |
(Automatic rigging.) Maybe null objects could stand for the centers of body parts. Then one click and the real centers gets positioned. The rotations are known and the names would lead to the correct hierarchy.
In order to make edits there is also a need of destroying the hierarchy again while preserving the rotation and position of the meshes.
code pieces
hierarchy breaker
- hierarchy_breaker_beta.txt (works only with strict correct names)
hierarchy builder
- delete_keyframes.txt (deletes keys, saves and reloads character)
- [...]
Keyframe affects hierarchy building
situation: "cube" and "cube1" were created
- case 1:
- apply 45° y rotation to cube
- make cube1 a child of cube
- cube1 will not inherit the 45°
- case 2:
- apply 45° y rotation to cube
- make rotation keyframe for cube1
- make cube1 a child of cube
- cube1 inherit the 45°
- case 3:
- apply 45° y rotation to cube
- make rotation keyframe for cube1
- remove that keyframe again
- make cube1 a child of cube
- cube1 inherit the 45°
- case 4:
- apply 45° y rotation to cube
- make rotation keyframe for cube1
- remove that keyframe again
- save both cube to a dae file
- make new scene and load dae
- make cube1 a child of cube
- cube1 will not inherit the 45°
Case 1 might be a bug (case 4 is a variant of it) but it simplify the hierarchy creation.
While doing those test you can observe red letters next to the meshes in the Schematic window.
- A means the presence of keyframes. Removing the keyframe doesn't remove the A. Object must be saved and reloaded.
- D means that mesh center was changed by rotation or translation (center mode) or by clicking Transform > Freeze(...). D can be removed by clicking the normal Freeze button. Yea, whatever...
- C can be seen next to camera objects, present when Camera_Interest is horizontal attached to it.