12.1. Open Inventor file format

This section describes the Inventor ASCII file format. Whenever you apply a write action to a node, path, or path list, the output file is written in this format. You can read files that use this format into the Inventor scene database by using the read method on the database. The file format is also used for transferring 3D copy and paste data between processes.

As described in Chapter 8, Applying Actions, you can apply a write action to a node, path, or path list. When the write action is applied to a node, it writes the entire subgraph rooted at that node.


C++
SoWriteAction 	writeAction;

writeAction.apply(root); //writes the entire scene graph to stdout

.NET
SoWriteAction 	writeAction = new SoWriteAction();

writeAction.Apply(root); //writes the entire scene graph

Java
SoWriteAction 	writeAction = new SoWriteAction();

writeAction.apply(root); //writes the entire scene graph

See also the section "Writing an Open Inventor fileSection 13.3, “Writing an Open Inventor file”"

You can read a scene graph from a file into the scene database using the readAll() method on the Inventor database. This example reads a file with the given filename and returns a separator containing the file. It returns NULL if there is an error reading the file.


C++
SoSeparator *
readFile(const char *filename)
{
   // Open the input file
   SoInput mySceneInput;
   if (!mySceneInput.openFile(filename)) {
      fprintf(stderr, "Cannot open file %s\n", filename);
      return NULL;
   }

   // Read the whole file into the database
   SoSeparator *myGraph = SoDB::readAll(&mySceneInput);
   if (myGraph == NULL) {
      fprintf(stderr, "Problem reading file\n");
      return NULL;
   } 
   mySceneInput.closeFile();
   return myGraph;
}
  

.NET
SoSeparator readFile(String filename)
{
    // Open the input file
    SoInput mySceneInput = new SoInput();
    if (!mySceneInput.OpenFile(filename))
    {
        Console.WriteLine("Cannot open file " + filename);
        return null;
    }

    // Read the whole file into the database
    SoSeparator myGraph = SoDB.ReadAll(mySceneInput);
    if (myGraph == null)
    {
        Console.WriteLine("Problem reading file");
        return null;
    }

    mySceneInput.CloseFile();
    return myGraph;
}
  

Java
SoSeparator readFile(String filename)
{
    // Open the input file
    SoInput mySceneInput = new SoInput();
    if (!mySceneInput.openFile(filename))
    {
        System.err.println("Cannot open file " + filename);
        return null;
    }

    // Read the whole file into the database
    SoSeparator myGraph = SoDB.readAll(mySceneInput);
    if (myGraph == null)
    {
        System.err.println("Problem reading file");
        return null;
    }

    mySceneInput.closeFile();
    return myGraph;
}
  

There are two read() methods. One method reads a graph rooted by a node, returning a pointer to that node. The other reads a graph defined by a path. You must call the correct method, based on the contents of the input. When you read in a model, you usually read a node. If you are cutting and pasting with paths, you will need to read a path.

SoDB( C++ | Java | .NET ) uses the class when reading Inventor data files. This class can also be used to read from a buffer in memory. By default, SoInput( C++ | Java | .NET ) looks for a specified file in the current directory (unless the specification begins with /). You can add directories to the search path with the addDirectory- First() and addDirectoryLast() methods (see the Open Inventor C++ Reference Manual on SoInput( C++ | Java | .NET )). Use the clearDirectories() method to clear the directory list.

You can also add a list of directories that is specified as the value of an environment variable. Use the following methods on SoInput( C++ | Java | .NET ):

addEnvDirectoriesFirst() addEnvDirectoriesLast()

The following sections outline the syntax for the Inventor ASCII file format. In this file format, extra white space created by spaces, tabs, and new lines is ignored. Comments begin with a number sign (#) anywhere on a line and continue to the end of the line:

# this is a comment in the Inventor file format

For simplicity, this discussion focuses on writing a scene graph to a file. This same format applies to files you create that will be read into the Inventor database.

See the Open Inventor C++ Reference Manual for descriptions of the file format for each Inventor class.

Fields within a node are written as the name of the field, followed by the value or values contained in the field. If the field value has not been changed from its default value, that field is not written out. Fields within a node can be written in any order. An example of writing field values is as follows:

Transform {
	   translation	     0 -4 0.2
}

LightModel {
   model	           BASE_COLOR
}

Material {
   ambientColor   .3 .1 .1
   diffuseColor  [.8 .7 .2,
                   1 .2 .2,
                  .2  1 .2,
                  .2 .2  1]
   specularColor  .4 .3 .1
   emissiveColor  .1  0 .1
}

Brackets surround multiple-value fields, with commas separating the values, as shown for the diffuseColor field in the preceding example. It's all right to have a comma after the last value as well:

[value1, value2, value3,]

Single-value () fields do not contain any brackets or commas. Multiple-value () fields usually have brackets, but they are not necessary if only one value is present:

specularColor    .4 .3 .1

or

specularColor   [.4 .3 .1]

The value that is written depends on the type of the field, as described in the following list.

Type of Field

Acceptable Formats

longs, shorts, unsigned shorts

integers, in decimal, hexadecimal, or octal; For example:

255
0xff
0177

floats

integer or floating point number. For example:

		13
	13.0
	13.123
	1.3e-2

names, strings

double quotation marks ( " " ) around the name if it is more than one word, or just the name (with no white space) if it is a single word (quotation marks are optional). For example:

label   " front left leg " 
label   car

You can have any ASCII character in the string, including newlines and backslashes, except for double quotation marks. To include a double quotation mark in the string, precede it with a backslash (\").

enums

either the mnemonic form of the enum or the integer form. (The mnemonic form is recommended, both for portability and readability of code.) For example:

MaterialBinding {
   value   PER_FACE
	}

bit mask

one or more mnemonic flags, separated by a vertical bar (|) if there are multiple flags. When more than one flag is used, parentheses are required:

	Cylinder {
	   parts   SIDES
	}

	Cylinder {
	   parts   (SIDES | TOP)
	}

vectors

n floats separated by white space: (SbVecnf, where n is the number of

PerspectiveCamera {
components of the	   position   0 0 9.5
vector)	}

colors

3 floats (RGB) separated by white space:

	BaseColor {
	   rgb   0.3 0.2 0.6
	}

rotation

a 3-vector for the axis, followed by a float for the angle (in radians), separated by white space:

	Transform {
	   rotation   0 1 0 1.5708
	   # y axis ... pi\xb9 /2 radians
	}

matrix

16 floats, separated by white space

path

an SFPath has one value, a pointer to a path. To write this value, write the path (see the section called “Writing a Path”). An MFPath has multiple values, which are all pointers to paths. To write this value, enclose the path list in brackets, and use commas to separate each path:

[first_path, second_path, ... nth_path]

node

an SFNode has one value, a pointer to a node. To write this value, write the node. An MFNode has multiple values, which are all pointers to nodes. To write this value, enclose the node list in brackets, and use commas to separate each node:

[node1, node2, ... noden]

Boolean	TRUE, FALSE or 0, 1:
	SoFile {
	   isWriteBack   FALSE
	}

The Ignore flag for a field (see Chapter 3, Nodes and Groups) is written as a tilde ( ~ ), either after or in place of the field value or values. For example:

transparency [ .9, .1 ] ~

or

transparency ~

The first case preserves the values even though the field is ignored. The second case uses the default value but ignores the field.

[Tip]

Tip: The Ignore flag applies only to properties. It is not used for cameras, lights, and shapes.

A path (see Chapter 3, Nodes and Groups) is written with the following elements:

  • The word Path

  • Open brace ({ )

  • The entire subgraph that is rooted on the head node for the path

  • Number of indices in the rest of the path chain

  • The indices themselves

  • Close brace (})

When Inventor encounters separator groups within the subgraph, it ignores them if they do not affect the nodes in the path chain. Written indices for the children within a group are adjusted to account for the skipped separator groups. For example, in Figure 12.1, “ Adjusting Path Indices to Account for Separator Groups, node N is counted as child index 1 when written, since the two previous children are separator groups that do not affect this node at all. (The indices in the path itself remain unchanged.)


[Note]

Note: If a path contains connections to nodes that are not part of the path, these nodes are not written out. For example, if a path contains an engine connected to a node outside the path, the engine will be written, but the node will not be.

Example 12.1, “ Writing a Path illustrates the process of writing a path to a file. First, here is the file for the scene graph, which creates chartreuse, rust, and violet spheres.

Separator {
   PerspectiveCamera {
      position 0 0 9.53374
      aspectRatio 1.09446
      nearDistance 0.0953375
      farDistance 19.0675
      focalDistance 9.53374
   }

   DirectionalLight {
   }

   Transform {
      rotation -0.189479 0.981839 -0.00950093 0.102051
      center 0 0 0 
   }

   DrawStyle {
   }

   Separator {
      LightModel {
         model   BASE_COLOR
      }
      Separator {
         Transform {
            translation -2.2 0 0
         }
         BaseColor {
            rgb .2 .6 .3   # chartreuse
         }
         Sphere { }
      }
      Separator {
         BaseColor {
            rgb .6 .3 .2   # rust
         }
         Sphere { }
      }
      Separator {
         Transform {
            translation 2.2 0 0 
         }
         BaseColor {
            rgb .3 .2 .6   # violet
         }
         Sphere { }
      }
}

Figure 12.2, “ Scene Graph for a Scene with Three Spheres shows the scene graph for this file.


If you pick the third sphere (the violet one), the pick path could be written to a file as shown in Example 12.1, “ Writing a Path. First, the subgraph under the root node is written. This description is followed by the number of indices in the path (3), and the indices themselves (4, 1, 2), as shown in Figure 12.3, “ Pick Path for Violet Sphere.



In the file format, the keyword DEF introduces a named instance of a node, path, or engine. It can be referenced later with the USE keyword. For example:

// This example shows keeping a cone between two cubes using an
// InterpolateVec3f engine.

Separator {
   DEF A Translation { translation -4 0 0 }
   Cube { }
}
Separator {
   DEF B Translation { translation 4 5 6 }
   Cube { }
}
Separator {
   Translation { translation 0 0 0 =
                 InterpolateVec3f {
                    input0 0 0 0 = USE A.translation
                    input1 0 0 0 = USE B.translation
                    alpha 0.5
                 } . output
               }
   Cone { }
}

The name can be any valid SbName( C++ ). In certain cases, Inventor adds some extra characters to the name when the file is written out. For example, consider the somewhat unusual scene graph shown in Figure 12.4, “ Shared Instances of Nodes. To indicate which instance of the beachball node is used by node B, the scene graph is written out as follows:

Separator{
   Separator{
      DEF beachball+0
      DEF beachball+1
   }
   Separator{
      USE beachball+0
      USE beachball+1
   }
}

When the scene graph is read back in, the original names are preserved, but the +n notations are discarded.


When a node kit is written, it includes one field for each part. For example:

AppearanceKit {
   lightModel			    LightModel { model PHONG }
   drawStyle			     DrawStyle  { style LINES }
   material			      Material   { diffuseColor .5 .5 .5 }
   complexity	    Complexity { value .5 }
}

In this format, the name of the field (lightModel) is followed by the name of the node (LightModel), and then the node's fields and values (each part is contained in an SoSFNode( C++ | Java | .NET ) field). If the part has not been created, or if it is NULL, it is not written out. However, if a part is created by default (such as the shape part in the SoShapeKit( C++ | Java | .NET )), and if the part is explicitly set to NULL, it is written out.

This example shows nesting node kits. Here, the appearance kit is the value for the appearance field. The appearance kit, in turn, has a material field.

SeparatorKit {
   appearance			    AppearanceKit {
      material   				Material { diffuseColor	   1 1 1 }
   }
}

When Inventor writes out a node kit, it writes out the intermediate parts. When you enter the information yourself, you can use a shorthand method and omit the intermediate parts. For example, if you omit the AppearanceKit, the SeparatorKit knows to add an AppearanceKit and put the Material node inside. So, you could simply enter this:

SeparatorKit {
   material   				Material { diffuseColor	   1 1 1 }
}

The file format for list parts within node kits is a bit more specialized. Each list part has three standard fields:

For example, here is the childList part of an instance of SoSeparatorKit( C++ | Java | .NET ):

SeparatorKit {
   childList			    NodeKitListPart {
      containerTypeName	   "Separator"
      childTypeNames	      "SeparatorKit"
      containerNode	        			Separator {
         SeparatorKit {
            transform	      Transform { translation 	1 0 0 }
         }
         SeparatorKit {
            transform	      Transform { translation 	0 1 0 }
         }
         SeparatorKit {
            transform	      Transform { translation 	0 0 1 }
         }
      }
   }
}

By default, Inventor does not write out the internal parts, such as separators and groups, or fields with default values. But if the node kit is in a path, everything is written out, as the following example shows. Generally, it writes out the parts in the reverse order they are defined in the catalog, with the leaf nodes first:

#Inventor V2.0 ascii

SeparatorKit {
   appearance	    DEF +0 AppearanceKit {
      material   		DEF +1 Material {
         diffuseColor	   1 0 1
      }
   }
   childList	    DEF +2 NodeKitListPart {
      containerTypeName   "Separator"
      childTypeNames	       "SeparatorKit"
      containerNode	   	     DEF +3 Separator {
         ShapeKit {
            appearance	   		  DEF +4 AppearanceKit {
               material			    DEF +5 Material {}
		            }
            transform			   DEF +6 Transform {}
            shape			   DEF +7 Cube {}
		            topSeparator			  Separator {
               USE +4
               USE +6
               DEF +8 Separator {
                  USE +7
               }
            }

            shapeSeparator  			USE +8
        }
   }
}
topSeparator	    Separator {
   USE +0
   USE +2
   }
}

To include a file within another file, use an SoFile( C++ | Java | .NET ) node. This node is useful when you are building scene graphs that include many objects. The objects can reside in their own files, and SoFile( C++ | Java | .NET ) nodes can be used to refer to them without copying them directly into the new file. The SoFile( C++ | Java | .NET ) node is written as

File {
   name "myFile.iv"
}

where the name field is the name of the file to be included. On read, the contents of the file myFile.iv are added as hidden children of SoFile( C++ | Java | .NET ). On write, Inventor just writes the filename (but not the children).

The objects within an SoFile( C++ | Java | .NET ) node are not editable. You can copy the contents of an SoFile( C++ | Java | .NET ) node using the method

SoFile::copyChildren()

or you can modify the name field of the SoFile( C++ | Java | .NET ) node. Whenever the value of the name field changes, the new file is read in. If the name is not an absolute path name, the list of directories set on SoInput( C++ | Java | .NET ) is used to search for the file (see the section called “Reading a File into the Database”). automatically adds the directory of the file being read to 's list of directories to search.

For example, suppose you have myFile.iv, which contains windmill.iv.

Contents of /usr/tmp/myFile.iv:

#Inventor V2.0 ascii
File { name "myObjects/windmill.iv" }

Contents of /usr/tmp/myObjects/windmill.iv:

#Inventor V2.0 ascii
//format to make the windmill

When /usr/tmp/myFile.iv is read in, /usr/tmp is added to the directory search list. When the SoFile( C++ | Java | .NET ) node in myFile.iv calls SoDB::read, SoInput( C++ | Java | .NET ) will find /usr/tmp/myObjects/windmill.iv, and it will be read (the directory /usr/tmp/myObjects will also be added to the list of search directories). When reading finishes, /usr/tmp/myObjects and /usr/tmp will be removed from the search directories list.

The SoOutput( C++ | Java | .NET ) object in an SoWriteAction( C++ | Java | .NET ) has a setBinary() method, which sets whether the output should be in ASCII (default) or binary format (see Chapter 8, Applying Actions). The getOutput() method returns a pointer to the SoOutput( C++ | Java | .NET ). When a file is written, Inventor inserts a header line that indicates whether the file is in ASCII or binary format, as well as the Inventor version number (see the section called “File Header”).

As described in The Inventor Toolmaker, developers can create their own nodes or engines and use them in new applications. This section describes what happens if you read in a file with references to extender nodes and engines whose code may or may not be accessible to your program. (In most cases, nodes and engines are interchangeable, so the discussion refers only to nodes for simplicity. In cases where engines differ slightly from nodes, those differences are called out explicitly.)

When an Inventor node is read from a file, Inventor first checks to see if the node's type has been registered. If the name is found, an instance of that node is created and the fields for the node are read in. This situation occurs if your program is linked with the code for the new node.

However, if your program is not linked with the code for the new node, things become slightly more interesting. If your system supports dynamic loading of compiled objects, Inventor can find the compiled object and recognize the new node. In this case, the author of the new node supplies the compiled object for the node and places it in a directory that can be found by the system. (Check your release notes for information on whether your system supports dynamic loading of shared objects and how it implements searching directories for the objects.)

the section called “Reading a File into the Database” showed you how to read from a file. Example 12.2, “ Reading from a String shows how you can read from a string stored in memory. Note that when a graph is read from a buffer, you do not need the file-header string. This example creates a dodecahedron from an indexed face set.

Example 12.2.  Reading from a String


C++
// Reads a dodecahedron from the following string: 
// (Note: ANSI compilers automatically concatenate 
// adjacent string literals together, so the compiler sees 
// this as one big string)

static char *dodecahedron =
   "Separator { "
      "   Material { "
   "      diffuseColor [ "
   "         1  0  0,   0 1  0,   0  0 1,   0  1  1, "
   "         1  0  1,  .5 1  0,  .5  0 1,  .5  1  1, "
   "         1 .3 .7,  .3 1 .7,  .3 .7 1,  .5 .5 .8 "
   "      ] "
   "   } "
   "   MaterialBinding { value PER_FACE } "
   "   Coordinate3 { "
   "      point [ "
   "         1.7265 0 0.618,    1 1 1, "
   "         0 0.618 1.7265,    0 -0.618 1.7265, "
   "         1 -1 1,    -1 -1 1, "
   "         -0.618 -1.7265 0,    0.618 -1.7265 0, "
   "         1 -1 -1,    1.7265 0 -0.618, "
   "         1 1 -1,    0.618 1.7265 0, "
   "         -0.618 1.7265 0,    -1 1 1, "
   "         -1.7265 0 0.618,    -1.7265 0 -0.618, "
   "         -1 -1 -1,    0 -0.618 -1.7265, "
   "         0 0.618 -1.7265,    -1 1 -1 "
   "      ] "
   "   } "
   "   IndexedFaceSet { "
   "      coordIndex [ "
   "         1, 2, 3, 4, 0, -1,  0, 9, 10, 11, 1, -1, "
   "         4, 7, 8, 9, 0, -1,  3, 5, 6, 7, 4, -1, "
   "         2, 13, 14, 5, 3, -1,  1, 11, 12, 13, 2, -1, "
   "         10, 18, 19, 12, 11, -1,  19, 15, 14, 13, 12, -1, "
   "         15, 16, 6, 5, 14, -1,  8, 7, 6, 16, 17, -1, "
   "         9, 8, 17, 18, 10, -1,  18, 17, 16, 15, 19, -1, "
   "      ] "
   "   } "
   "}";

// Routine to create a scene graph representing a dodecahedron
SoNode *
makeDodecahedron()
{
   // Read from the string.
   SoInput in;
   in.setBuffer(dodecahedron, strlen(dodecahedron));

   SoNode *result;
   SoDB::read(&in, result);

   return result;
}
  

.NET
// Reads a dodecahedron from the following string: 
// (Note: ANSI compilers automatically concatenate 
// adjacent string literals together, so the compiler sees 
// this as one big string)

        String dodecahedron =
"Separator {" +
"   Normal {" +
"      vector [" +
"         0.553341 0 0.832955, 0.832955 0.553341 0," +
"         0.832955 -0.553341 0, 0 -0.832955 0.553341," +
"         -0.553341 0 0.832955, 0 0.832955 0.553341," +
"         0 0.832955 -0.553341, -0.832955 0.553341 0," +
"         -0.832955 -0.553341 0, 0 -0.832955 -0.553341," +
"         0.553341 0 -0.832955, -0.553341 0 -0.832955," +
"      ]" +
"   }" +
"   NormalBinding { value PER_FACE }" +
"   Material {" +
"      diffuseColor [" +
"         1  0  0,   0 1  0,   0  0 1,   0  1  1," +
"         1  0  1,  .5 1  0,  .5  0 1,  .5  1  1," +
"         1 .3 .7,  .3 1 .7,  .3 .7 1,  .5 .5 .8" +
"      ]" +
"   }" +
"   MaterialBinding { value PER_FACE }" +
"   Coordinate3 {" +
"      point [" +
"         1.7265 0 0.618,    1 1 1," +
"         0 0.618 1.7265,    0 -0.618 1.7265," +
"         1 -1 1,    -1 -1 1," +
"         -0.618 -1.7265 0,    0.618 -1.7265 0," +
"         1 -1 -1,    1.7265 0 -0.618," +
"         1 1 -1,    0.618 1.7265 0," +
"         -0.618 1.7265 0,    -1 1 1," +
"         -1.7265 0 0.618,    -1.7265 0 -0.618," +
"         -1 -1 -1,    0 -0.618 -1.7265," +
"         0 0.618 -1.7265,    -1 1 -1" +
"      ]" +
"   }" +
"   IndexedFaceSet {" +
"      coordIndex [" +
"         1, 2, 3, 4, 0, -1,  0, 9, 10, 11, 1, -1," +
"         4, 7, 8, 9, 0, -1,  3, 5, 6, 7, 4, -1," +
"         2, 13, 14, 5, 3, -1,  1, 11, 12, 13, 2, -1," +
"         10, 18, 19, 12, 11, -1,  19, 15, 14, 13, 12, -1," +
"         15, 16, 6, 5, 14, -1,  8, 7, 6, 16, 17, -1," +
"         9, 8, 17, 18, 10, -1,  18, 17, 16, 15, 19, -1," +
"      ]" +
"   }" +
"}";

// Routine to create a scene graph representing a dodecahedron
SoNode MakeDodecahedron()
{
    // Read from the string.
    SoInput input = new SoInput();
    input.SetBuffer(dodecahedron);
    SoNode result;
    SoDB.Read(input, out result);

    return result;
}
  

Java
       String dodecahedron =
"Separator {" +
"   Normal {" +
"      vector [" +
"         0.553341 0 0.832955, 0.832955 0.553341 0," +
"         0.832955 -0.553341 0, 0 -0.832955 0.553341," +
"         -0.553341 0 0.832955, 0 0.832955 0.553341," +
"         0 0.832955 -0.553341, -0.832955 0.553341 0," +
"         -0.832955 -0.553341 0, 0 -0.832955 -0.553341," +
"         0.553341 0 -0.832955, -0.553341 0 -0.832955," +
"      ]" +
"   }" +
"   NormalBinding { value PER_FACE }" +
"   Material {" +
"      diffuseColor [" +
"         1  0  0,   0 1  0,   0  0 1,   0  1  1," +
"         1  0  1,  .5 1  0,  .5  0 1,  .5  1  1," +
"         1 .3 .7,  .3 1 .7,  .3 .7 1,  .5 .5 .8" +
"      ]" +
"   }" +
"   MaterialBinding { value PER_FACE }" +
"   Coordinate3 {" +
"      point [" +
"         1.7265 0 0.618,    1 1 1," +
"         0 0.618 1.7265,    0 -0.618 1.7265," +
"         1 -1 1,    -1 -1 1," +
"         -0.618 -1.7265 0,    0.618 -1.7265 0," +
"         1 -1 -1,    1.7265 0 -0.618," +
"         1 1 -1,    0.618 1.7265 0," +
"         -0.618 1.7265 0,    -1 1 1," +
"         -1.7265 0 0.618,    -1.7265 0 -0.618," +
"         -1 -1 -1,    0 -0.618 -1.7265," +
"         0 0.618 -1.7265,    -1 1 -1" +
"      ]" +
"   }" +
"   IndexedFaceSet {" +
"      coordIndex [" +
"         1, 2, 3, 4, 0, -1,  0, 9, 10, 11, 1, -1," +
"         4, 7, 8, 9, 0, -1,  3, 5, 6, 7, 4, -1," +
"         2, 13, 14, 5, 3, -1,  1, 11, 12, 13, 2, -1," +
"         10, 18, 19, 12, 11, -1,  19, 15, 14, 13, 12, -1," +
"         15, 16, 6, 5, 14, -1,  8, 7, 6, 16, 17, -1," +
"         9, 8, 17, 18, 10, -1,  18, 17, 16, 15, 19, -1," +
"      ]" +
"   }" +
"}";

// Routine to create a scene graph representing a dodecahedron
SoNode makeDodecahedron()
{
  // Read from the string.
  SoInput input = new SoInput();
  input.setBuffer(dodecahedron, dodecahedron.length());
  SoNode result = SoDB.read(input);

  return result;
}