Phone: 905 409-1589
Email: info@penproductions.ca
RSS LinkedIn Twitter Twitter
XML Writing
Navigate:

Down Load: Source Code
Down Load: PENsceneToXml.ms This script was an initial test to see how to go about storing the whole Max scene in an XML formatted file.

XML Format:
XML has become a standard for storing ascii data in a hierarchy based file format. Max uses it for the Save/Load Animation feature where all the animate of objects are stored and then applied back to another character. XML has an open format with only a few rules that need to be followed, this allow for any sort of data to be stored in just about any way that you would like to. I use it for many different tools including a new version of PEN Attribute Holder that is being developed, a file tracking and project management system and most recently the PEN Helper where I store all the information about objects in an XML file for recreating those objects when needed. Here is what the file format looks like for the PEN Helper, it has been truncated since we just need to illustrate the format.

 

Code:
<penObjects>
  <Preset Name="Two Way Arrow">
    <Verts data="#([-0.252935,-0.40128,0],[-0.401212,-0.40128,0],[0,-1,0])" />
    <Faces data="#([8,9,10],[6,7,8],[6,8,10])" />
    <EdgeVis data="#([1,1,0],[1,1,0],[0,0,0])" />
    <SmoothGroup data="#(0,0,0,0)" />
    <Renderable data="false" />
    <WireColor data="(color 90 200 225)" />
    <Size data="10.0" />
    <Position data="[0,0,0]" />
    <Rotation data="[0,0,0]" />
    <Scale data="[1,1,1]" />
  </Preset>
  <Preset Name="Character Root">
    <Verts data="#([1,0,0],[0.86336,0.504498,0],[0.504498,0.86336,0])" />
    <Faces data="#([1,2,14],[1,14,13],[2,3,15])" />
    <EdgeVis data="#([1,1,0],[0,1,1],[1,1,0])" />
    <SmoothGroup data="#(0,0,0,0,0,0)" />
    <Renderable data="false" />
    <WireColor data="(color 90 200 225)" />
    <Size data="10.0" />
    <Position data="[0,0,0]" />
    <Rotation data="[0,0,0]" />
    <Scale data="[1,1,1]" />
  </Preset>
</penObjects>
								  

The format has some simple rules.

  1. Each XML file has to have one root element, in the example this is <penObjects>
  2. Each element needs to have a closing element, in the example <penObjects> is closed with </penObjects>
  3. If and element doesn't have a children elements it can be closed without a closing element as in this element in the example <Renderable data="false" />
  4. The name of an element has to include only alphanumeric characters with no spaces or other characters as separators.
  5. Each element can have any number of properties.
  6. Property names need to follow the same formatting rules as element names.
  7. Property values start and end with a double quote " and can be anything that you want.

Getting Started:
The first thing that we need to do with dotNet XML is to load the assembly. Most of what you are usually going to use in dotNet like UI controls are already loaded, XML requires that you load this manually so that we can create the XML objects that will be needed.

 

Code:
dotNet.loadAssembly "system.xml"

For this example we are going to use the xmlDocument object to build the XML formatted file. We will apply the xmlDocument object to the variable xmlDoc.

 

Code:
xmlDoc=dotNetObject "system.xml.xmlDocument"

Next lets format the properties and methods for xmlDoc to the listener to see what we have to work with.

 

Code:
--Format properties and methods to the listener.
clearListener()
format "Properties\n"
showProperties xmlDoc
format "\nMethods\n"
showMethods xmlDoc
								

Creating Elements:

When we formatted the methods to the listener there are two that we need to use to build the XML tree of data. First we need to create a root node for the xmlDoc. In this case we will call it Root but we could call it anything that we want as long as it is following the rules stated above.

Once we have created the element we need to append it to the xmlDoc. Each element that is created needs to be appended to the parent element in the tree that it will be associated with. Use showMethods on the Root element to see what we can do with it.

 

Code:
--Create a root element for the xml doc and add it to the xmlDocument.
root=xmlDoc.createElement "Root"
xmlDoc.appendChild root

--show the properties for the new element.
showMethods root
								

Adding Child Elements:

For this example we will need several objects in the scene. Link objects together into a hierarchy with at least two trees of objects. This way we can see what the XML formatted file will look like.

We will now loop through all the objects in the scene and use createElement and build a new element called "node" for each object in the scene. For each new element we will also use the method setAttribute. setAttribute allows us to add named attributes to the element. It first needs a name of the attribute passed and then the value as a string. You can add as many attributes to an element as you need. Another way to handle lots of data that you might want to associate with an element is to add children element to it that store those values like I do in PEN Helper.

once the new element has been created we will need to append it to the root element that we created to start with. Each element has an appendChild method so you can add child nodes and build a tree.

 

Code:
--Names for elements can't include anything but alpha characters.
for x in objects do
(
	--Create a new element for the object.
	newElement=xmlDoc.createElement "node"
	
	--Set attributes on the new elements for the name of the object and the class of it.
	newElement.setAttribute "name" x.name
	newElement.setAttribute "class" (classOf x as string)
	
	--Append the new element to the root element. 
	root.appendChild newElement
)
								

 

Result:
<Root>
  <node name="Sphere01" class="Sphere" />
  <node name="Sphere03" class="Sphere" />
  <node name="Sphere05" class="Sphere" />
  <node name="Sphere06" class="Sphere" />
  <node name="Teapot01" class="Teapot" />
  <node name="Teapot02" class="Teapot" />
  <node name="Torus Knot02" class="Torus_Knot" />
  <node name="Gengon01" class="Gengon" />
  <node name="Gengon02" class="Gengon" />
  <node name="Gengon03" class="Gengon" />
  <node name="Torus Knot01" class="Torus_Knot" />
  <node name="Sphere04" class="Sphere" />
  <node name="Teapot03" class="Teapot" />
  <node name="Teapot04" class="Teapot" />
  <node name="Sphere02" class="Sphere" />
  <node name="Box01" class="Box" />
  <node name="Control Object01" class="penControlObject" />
</Root>								
								

At this point we don't have a tree just a list of objects. What we will need will be similar to what we used to create the tree in treeview.

Building XML Tree:

To be able to build the tree we will change the For Loop that added all the objects. In the treeView script we wrote a function to find all the root objects in the scene. We will simplify that and as we loop through all the object we will only add elements for the ones that don't have a parent. This is probably faster as it doesn't take a second step. How ever if you ever need to do anything with the root objects of the scene again you will not have an array of them already defined.

Once the root objects are added we will pass that object to a recursive function to add the tree.

 

Code:
--Names for elements can't include anything but alpha characters.
for x in objects do
(
	if x.parent==undefined then
	(
		--Create a new element for the object.
		newElement=xmlDoc.createElement "node"
		--Set attributes on the new elements for the name of the object and the class of it.
		newElement.setAttribute "name" x.name
		newElement.setAttribute "class" (classOf x as string)
		
		--Append the new element to the root element. 
		root.appendChild newElement
		
		--Call the recursive function and pass the object and element to it.
		recurseHierarchy x newElement	 
	)
)
								

This is the recursive function from the treeView script but changed to add xml elements instead of treeView nodes. For each child object a new element is created and the attributes are set for it. Then then element is appended to the previous and the recurseHierarchy function is called again for the children of that object.

 

Code:
--Recurse hierarchy and add xml elements
fn recurseHierarchy obj ele =
(
	for i = 1 to obj.children.count do		--Loop through each of the children
	(
		--Create a new element for the object.
		newElement=xmlDoc.createElement "node"
		--Set attributes on the new elements for the name of the object and the class of it.
		newElement.setAttribute "name" obj.children[i].name
		newElement.setAttribute "class" (classOf obj.children[i] as string)
		
		--Append the new element to the root element. 
		ele.appendChild newElement
		
		recurseHierarchy obj.children[i] newElement
	)
)
								

Now we have a true tree of data that has been stored. We could write out any data that we like with this method. I have even written a test script that writes out all the controllers to the XML files with all the animation keys. This is similar to what the Save/Load Animation tool does.

 

Result:
<Root>
  <node name="Sphere01" class="Sphere">
    <node name="Sphere03" class="Sphere">
      <node name="Sphere05" class="Sphere">
        <node name="Sphere06" class="Sphere" />
        <node name="Teapot01" class="Teapot" />
        <node name="Teapot02" class="Teapot">
          <node name="Torus Knot02" class="Torus_Knot">
            <node name="Gengon01" class="Gengon" />
            <node name="Gengon02" class="Gengon" />
            <node name="Gengon03" class="Gengon" />
          </node>
          <node name="Torus Knot01" class="Torus_Knot" />
        </node>
      </node>
      <node name="Sphere04" class="Sphere">
        <node name="Teapot03" class="Teapot" />
      </node>
      <node name="Teapot04" class="Teapot" />
    </node>
    <node name="Sphere02" class="Sphere" />
  </node>
  <node name="Box01" class="Box" />
  <node name="Control Object01" class="penControlObject" />
</Root>