17.6. Customizing a Dragger (Advanced)

This section describes how to modify the appearance of a dragger. This customization is performed either by changing the default geometry for a part or by changing the part after an instance of a dragger has been built. Although the look and feel of a dragger can be changed or removed in this manner, no new functionality can be added.

Using the SoTrackballDragger SoTrackballDragger SoTrackballDragger as an example, this section describes the parts of a dragger and how they combine to make the whole dragger. It explains how the geometry for each part can be changed or removed, and how that can affect the functionality of the dragger. Example 17.5, “ Changing Parts after Building a Dragger illustrates how parts of an SoTranslate1Dragger SoTranslate1Dragger SoTranslate1Dragger can be changed after it has been built.

Parts of a Dragger

Every dragger is a node kit that is constructed out of parts. A part is simply a piece of the dragger that has some task associated with it. Often, two parts act as a pair. One part is displayed when it is in use (or active), and the other is displayed when that part is not in use (or inactive). For example, for the

trackball's inactive “XRotator” part, a white stripe is displayed, and for its active “XRotatorActive” part, a yellow stripe is displayed.

Each dragger has a resource file associated with it that contains an Inventor scene graph describing the default geometry for each part. By creating a new resource file, you can override the default and give the part a new shape or new properties such as color or drawing style. In the resource file, scene graphs are labeled with their unique resource names.

Many classes of draggers use the same part names. For example, the trackball, rotate-disc, rotate-cylindrical, and rotate-spherical draggers each have a part named “rotator.” Since the default parts are stored in the global dictionary, each part in each class must have a unique resource name. In all cases, the class name (without the “So” or “Dragger”) is prepended to the part name. Table 17.2, “Selected Parts of the SoTrackballDragger shows how the resource names and part names relate.

For example, the SoTrackballDragger SoTrackballDragger SoTrackballDragger has twelve parts. Table 17.2, “Selected Parts of the SoTrackballDragger lists the resource and part names of eight of these parts (for brevity, the “userRotator” and “userAxis” parts are omitted). When you interact with a trackball dragger, you are actually interacting with its parts. For example, if the mouse goes down over the trackball's “XRotator” part, a rotation about the x-axis is initiated.

Table 17.2. Selected Parts of the SoTrackballDragger

Resource Names

Part Names

Task

trackballRotator

trackball RotatorActive

rotator rotatorActive

Free rotation

trackballXRotator

trackballXRotatorActive

XRotator XRotatorActive

Rotation about x-axis

trackballYRotator

trackballYRotatorActive

YRotator YRotatorActive

Rotation about y-axis

trackballZRotator

trackballZRotatorActive

ZRotator ZRotatorActive

Rotation about z-axis

Changing a Part after Building the Dragger

To change the part of a dragger that has already been built, use the setPart() or setPartAsPath() method provided by SoInteractionKit SoInteractionKit SoInteractionKit . The setPart() method takes the root of a scene graph as a parameter, while setPartAsPath() accepts an SoPath SoPath SoPath .

For example, to change the “rotator” part of myDragger:

myDragger->setPart("rotator", myNewRotatorSceneGraph);
    
myDragger.SetPart("rotator", myNewRotatorSceneGraph);
    
myDragger.setPart("rotator", myNewRotatorSceneGraph);
    

To change the “rotator” part of a dragger within a manipulator:

myManip->getDragger()->setPart("rotator",
                             myNewRotatorSceneGraph);
    
myManip.GetDragger().SetPart("rotator",
                               myNewRotatorSceneGraph);
    
myManip.getDragger().setPart("rotator",
        myNewRotatorSceneGraph);

You can also provide setPartAsPath() with the path to a particular instance of an object in the scene graph. The dragger then uses that object for the part. For example, if you have an arrow used as a weather vane mounted on a post, you could provide the path to the arrow and rotate the arrow itself. (Note the difference here between specifying a node and specifying a path. If you specify the arrow node using setPart(), a new instance of that node is created and two copies of the same geometry appear on the screen. If you specify the path to the arrow using setPartAsPath(), the dragger actually uses the existing arrow node and waits for the user to press the mouse on the same weather vane that is sitting on the barn.)

          myRotateManip->getDragger()->setPartAsPath("rotator", pathToMyWeatherVaneArrow);
        
          myRotateManip.GetDragger().SetPartAsPath("rotator", pathToMyWeatherVaneArrow);
        
          myRotateManip.getDragger().setPartAsPath("rotator", pathToMyWeatherVaneArrow);
        

Example 17.5, “ Changing Parts after Building a Dragger shows how to change the geometry of the draggers in Example 17.2, “ Using Multiple Draggers ”. The “translator” and “translatorActive” parts are now cubes instead of arrows. The setPart() method is used to replace the default parts with the new scene graphs specified here. Figure 17.11, “ Changing the Dragger Parts to Cubes shows the new dragger geometry.

Changing the Dragger Parts to Cubes

Figure 17.11.  Changing the Dragger Parts to Cubes



Example 17.5.  Changing Parts after Building a Dragger

// Create myTranslator and myTranslatorActive.
// These are custom geometry for the draggers.
SoSeparator *myTranslator = new SoSeparator;
SoSeparator *myTranslatorActive = new SoSeparator;
myTranslator->ref();
myTranslatorActive->ref();
   
// Materials for the dragger in regular and active states
SoMaterial *myMtl = new SoMaterial;
SoMaterial *myActiveMtl = new SoMaterial;
myMtl->diffuseColor.setValue(1,1,1);
myActiveMtl->diffuseColor.setValue(1,1,0);
myTranslator->addChild(myMtl);
myTranslatorActive->addChild(myActiveMtl);
   
// Same shape for both versions.
SoCube *myCube = new SoCube;
myCube->set("width 3 height .4 depth .4");
myTranslator->addChild(myCube);
myTranslatorActive->addChild(myCube);

// Now, customize the draggers with the pieces we created.
xDragger->setPart("translator",myTranslator);
xDragger->setPart("translatorActive",myTranslatorActive);
yDragger->setPart("translator",myTranslator);
yDragger->setPart("translatorActive",myTranslatorActive);
zDragger->setPart("translator",myTranslator);
zDragger->setPart("translatorActive",myTranslatorActive);
      
// Create myTranslator and myTranslatorActive.
// These are custom geometry for the draggers.
SoSeparator myTranslator = new SoSeparator();
SoSeparator myTranslatorActive = new SoSeparator();
// Materials for the dragger in regular and active states
SoMaterial myMtl = new SoMaterial();
SoMaterial myActiveMtl = new SoMaterial();
myMtl.diffuseColor.SetValue(1, 1, 1);
myActiveMtl.diffuseColor.SetValue(1, 1, 0);
myTranslator.AddChild(myMtl);
myTranslatorActive.AddChild(myActiveMtl);
// Same shape for both versions.
SoCube myCube = new SoCube();
myCube.Set("width 3 height .4 depth .4");
myTranslator.AddChild(myCube);
myTranslatorActive.AddChild(myCube);

// Now, customize the draggers with the pieces we created.
xDragger.SetPart("translator", myTranslator);
xDragger.SetPart("translatorActive", myTranslatorActive);
yDragger.SetPart("translator", myTranslator);
yDragger.SetPart("translatorActive", myTranslatorActive);
zDragger.SetPart("translator", myTranslator);
zDragger.SetPart("translatorActive", myTranslatorActive);
      
// Create myTranslator and myTranslatorActive.
// These are custom geometry for the draggers.
SoSeparator myTranslator = new SoSeparator();
SoSeparator myTranslatorActive = new SoSeparator();
// Materials for the dragger in regular and active states
SoMaterial myMtl = new SoMaterial();
SoMaterial myActiveMtl = new SoMaterial();
myMtl.diffuseColor.setValue(1, 1, 1);
myActiveMtl.diffuseColor.setValue(1, 1, 0);
myTranslator.addChild(myMtl);
myTranslatorActive.addChild(myActiveMtl);
// Same shape for both versions.
SoCube myCube = new SoCube();
myCube.set("width 3 height .4 depth .4");
myTranslator.addChild(myCube);
myTranslatorActive.addChild(myCube);

// Now, customize the draggers with the pieces we created.
xDragger.setPart("translator", myTranslator);
xDragger.setPart("translatorActive", myTranslatorActive);
yDragger.setPart("translator", myTranslator);
yDragger.setPart("translatorActive", myTranslatorActive);
zDragger.setPart("translator", myTranslator);
zDragger.setPart("translatorActive", myTranslatorActive);
      


Changing the Default Geometry for a Part

Every class of dragger has a resource file associated with it that contains Inventor scene graphs defining default geometry for that class. The default geometry for a given class is also compiled in, so that if the dragger resource files are lost, the dragger will still operate.

Where a Dragger Looks for Defaults

When a dragger is constructed, it checks whether a resource file for overriding the defaults has been created. When reading from this file (if found), if the dragger encounters a second definition of a particular geometry, the new geometry replaces any previously defined geometry of the same name.

Inventor will look for files only if the environment variable SO_DRAGGER_DIR has been set. If it has, Inventor will look in that directory.

In all cases, a given dragger class will read only files of its same name: the SoTranslate1Dragger SoTranslate1Dragger SoTranslate1Dragger class reads only resource files named translate1Dragger.iv, the SoTrackballDragger SoTrackballDragger SoTrackballDragger class reads only files named trackballDragger.iv.

Changing the default geometry of a dragger part is a simple matter of creating a new file that contains a scene graph defining the new resource. When encountered, this new definition overrides the default definitions of that resource. The next two sections show how to do this using the SoTrackballDragger SoTrackballDragger SoTrackballDragger as a foundation.

How to Edit Your File

To change the default geometry of a part, you merely redefine the scene graph for the geometry used by the part. When creating new resource files, it is necessary to define only the geometry that you wish to change. Other geometry will use the default values.

Keep in mind that you should never edit the resource files in Inventor/resources. If you want your application to use alternate resources, put your files in a convenient place and set the SO_DRAGGER_DIR environment variable to point there.

As an example, let's replace the trackballRotator resource of the trackball with a cube. (For more information on the Inventor file format, see Chapter 12, Importing data.) Looking at the default geometry file for the trackball, we see that the trackballRotator resource is defined by this scene graph:

# default geometry for SoTrackballDragger's "rotator" part (inactive)
DEF trackballRotator Separator {
   DrawStyle { style INVISIBLE }
   Sphere {}
}
# default geometry for SoTrackballDragger's "rotatorActive" part
DEF trackballRotatorActive Separator {
   DrawStyle { style INVISIBLE }
   Sphere {}
}

Note that, in the case of the trackball, the resources specify that the rotator and rotatorActive parts have the same geometry, an invisible sphere. Although this is common, some draggers may have completely different geometry for when they are inactive and active (and most manipulators have more complicated scene graphs than just a sphere).

To change the trackballRotator and trackballRotatorActive resources from an invisible sphere to a visible cube, you simply replace the sphere with a cube in both scene graphs:

# default geometry for the SoTrackballDragger's "rotator" part
DEF trackballRotator Separator {
   BaseColor {
      rgb 1. 1. 1.        #white
   }
   Cube {}
}
# default geometry for the SoTrackballDragger's "rotatorActive" part
DEF trackballRotatorActive Separator {
   BaseColor {
      rgb .5 .5  0.        #yellow
   }
   Cube {}
}

Using this mechanism, you can not only change the geometry of a given part, but also remove the functionality of that part entirely. For example, to disable the trackball's "rotator" part but still leave the cube visible, you can make the cube unpickable:

# default geometry for the SoTrackballDragger's "rotator" part
DEF trackballRotator Separator {
   BaseColor {
      rgb 1. 1. 1.        #white
   }
   PickStyle {
      style UNPICKABLE
   }
   Cube {}
}
# default geometry for the SoTrackballDragger's "rotatorActive" part
DEF trackballRotatorActive Separator {
   BaseColor {
      rgb .5 .5  0.        #yellow
   }
   PickStyle {
      style UNPICKABLE
   }
   Cube {}
}

To remove the trackball's rotator part altogether, leaving a trackball that can only rotate about its x, y, and z axes, you could redefine its geometry to be an empty scene graph:

# default geometry for SoTrackballDragger's "rotator" part
DEF trackballRotator Separator {
}
# default geometry for SoTrackballDragger's "rotatorActive" part
DEF trackballRotatorActive Separator {
}

You can also read the geometry from a file instead of defining it inline:

DEF trackballRotator Separator {
   File { name "myCustomRotator.iv" }
}
DEF trackballRotatorActive Separator {
   File { name "myCustomRotatorActive.iv" }
}

Note: Never set a dragger part to NULL. Internal methods require a node to be present, even if it's simply an empty separator as shown in the previous example. (Often, the dragger parts are the children of a switch node. Changing a node to NULL could result in an incorrect ordering of the switch node's children.)