5 Simulation Control

5.1 Control Panels

A control panel is an editing panel that allows for the interactive adjustment of component properties.

It is always possible to adjust component properties through the GUI by selecting one or more components and then choosing Edit properties ... in the right-click context menu. However, it may be tedious to repeatedly select the required components, and the resulting panels present the user with all properties common to the selection. A control panel allows an application to provide a customized editing panel for selected properties.

If an application wishes to adjust an attribute that is not exported as a property of some ArtiSynth component, it is often possible to create a custom property for the attribute in question. Custom properties are described in Section 5.2.

5.1.1 General principles

Control panels are implemented by the ControlPanel model component. They can be set up within a model’s build() method by creating an instance of ControlPanel, populating it with widgets for editing the desired properties, and then adding it to the root model using the latter’s addControlPanel() method. A typical code sequence looks like this:

 ControlPanel panel = new ControlPanel ("controls");
 ... add widgets ...
 addControlPanel (panel);

There are various addWidget() methods available for adding widgets and components to a control panel. Two of the most commonly used are:

 addWidget (HasProperties host, String propPath)
 addWidget (HasProperties host, String propPath, double min, double max)

The first method creates a widget to control the property located by propPath with respect to the property’s host (which is usually a model component or a composite property). Property paths are discussed in Section 1.4.2, and can consist of a simple property name, a composite property name, or, for properties located in descendant components, a component path followed by a colon ‘:’ and then a simple or compound property name.

The second method creates a slider widget to control a property whose value is a single number, with an initial numeric range given by max and min.

The first method will also create a slider widget for a property whose value is a number, if the property has a default range and slider widgets are not disabled in the property’s declaration. However, the second method allows the slider range to be explicitly specified.

Both methods also return the widget component itself, which is an instance of LabeledComponentBase, and assign the widget a text label that is the same as the property’s name. In some situations, it is useful to assign a different text label (such as when creating two widgets to control the same property in two different components). For those cases, the methods

  addWidget (
     String label, HasProperties host, String propPath)
  addWidget (
     String label, HasProperties host, String propPath, double min, double max)

allow the widget’s text label to be explicitly specified.

Sometimes, it is desirable to create a widget that controls that same property across two or more host components (as illustrated in Section 5.1.3 below). For that, the methods

 addWidget (String propPath, HasProperties... hosts)
 addWidget (String propPath, double min, double max, HasProperties... hosts)
 addWidget (
    String label, String propPath, HasProperties... hosts)
 addWidget (
    String label, String propPath, double min, double max, HasProperties... hosts)

allow multiple hosts to be specified using the variable length argument list hosts.

Other flavors of addWidget() also exist, as described in the API documentation for ControlPanel. In particular, any type of Swing or awt component can be added using the method

   addWidget (Component comp)

Control panels can also be created interactively using the GUI; see the section “Control Panels” in the ArtiSynth User Interface Guide.

5.1.2 Example: Creating a simple control panel

An application model showing a control panel is defined in

  artisynth.demos.tutorial.SimpleMuscleWithPanel

This model simply extends SimpleMuscle (Section 4.5.2) to provide a control panel for adjusting gravity, the mass and color of the box, and the muscle excitation. The class definition, excluding import statements, is shown below:

1 public class SimpleMuscleWithPanel extends SimpleMuscle {
2    ControlPanel panel;
3
4    public void build (String[] args) throws IOException {
5
6       super.build (args);
7
8       // add control panel for gravity, rigid body mass and color, and excitation
9       panel = new ControlPanel("controls");
10       panel.addWidget (mech, "gravity");
11       panel.addWidget (mech, "rigidBodies/box:mass");
12       panel.addWidget (mech, "rigidBodies/box:renderProps.faceColor");
13       panel.addWidget (new JSeparator());
14       panel.addWidget (muscle, "excitation");
15
16       addControlPanel (panel);
17    }
18 }

The build() method calls super.build() to create the model used by SimpleMuscle. It then proceeds to create a ControlPanel, populate it with widgets, and add it to the root model (lines 8-15). The panel is given the name "controls" in the constructor (line 8); this is its component name and is also used as the title for the panel’s window frame. A control panel does not need to be named, but if it is, then that name must be unique among the control panels.

Lines 9-11 create widgets for three properties located relative to the MechModel referenced by mech. The first is the MechModel’s gravity. The second is the mass of the box, which is a component located relative to mech by the path name (Section 1.1.3) "rigidBodies/box". The third is the box’s face color, which is the sub-property faceColor of the box’s renderProps property.

Line 12 adds a JSeparator to the panel, using the addWidget() method that accepts general components, and line 13 adds a widget to control the excitation property for muscle.

It should be noted that there are different ways to specify target properties in addWidget(). First, component paths may contain numbers instead of names, and so the box’s mass property could be specified using "rigidBodies/0:mass" instead of "rigidBodies/box:mass" since the box’s number is 0. Second, if a reference to a subcomponent is available, one can specify properties directly with respect to that, instead of indicating the subcomponent in the property path. For example, if the box was referenced by a variable body, then one could use the construction

   panel.addWidget (body, "mass");

in place of

   panel.addWidget (mech, "rigidBodies/box:mass");

To run this example in ArtiSynth, select All demos > tutorial > SimpleMuscleWithPanel from the Models menu. The demo will appear with the control panel shown in Figure 5.1, allowing the displayed properties to be adjusted interactively by the user while the model is either stationary or running.

Figure 5.1: Control panel created by the model SimpleMuscleWithPanel. Each of the property widgets consists of a text label followed by a field displaying the property’s value. A and B identify the icons for the inheritable properties gravity and faceColor, indicating whether their values have has been explicitly set (A) or inherited from an ancestor component (B).

As described in Section 1.4.3, some properties are inheritable, meaning that their values can either be set explicitly within their host component, or inherited from the equivalent property in an ancestor component. Widgets for inheritable properties include an icon in the left margin indicating whether the value is explicitly set (square icon) or inherited from an ancestor (triangular icon) (Figure 5.1). These settings can be toggled by clicking on the icon. Changing an explicit setting to inherited will cause the property’s value to be changed to that of the nearest ancestor component, or to the property’s default value if no ancestor component contains an equivalent property.

5.1.3 Example: Controlling properties in multiple components

Figure 5.2: Model and control panel created by ControlPanelDemo.

It is sometimes useful to create a property widget that adjusts the same property across several different components at the same time. This can be done using the addWidget() methods that accept multiple hosts. An application model demonstrating this is defined in

  artisynth.demos.tutorial.ControlPanelDemo

and shown in Figure 5.2. The model creates a simple arrangement of three spheres, connected by point-to-point muscles and with collisions enabled (Chapter 8), whose dynamic behavior can be adjusted using the control panel. Selected rendering properties can also be changed using the panel. The class definition, excluding import statements, is shown below:

1 public class ControlPanelDemo extends RootModel {
2
3    double myStiffness = 10.0; // default spring stiffness
4    double myMaxForce = 100.0; // excitation force multiplier
5    double DTOR = Math.PI/180; // degrees to radians
6
7    // Create and attach a simple muscle with default parameters between p0 and p1
8    Muscle attachMuscle (String name, MechModel mech, Point p0, Point p1) {
9       Muscle mus = new Muscle (name);
10       mus.setMaterial (
11          new SimpleAxialMuscle (myStiffness, /*damping=*/0, myMaxForce));
12       mech.attachAxialSpring (p0, p1, mus);
13       return mus;
14    }
15
16    public void build (String[] args) {
17       // create a mech model with zero gravity
18       MechModel mech = new MechModel ("mech");
19       addModel (mech);
20       mech.setGravity (0, 0, 0);
21       mech.setInertialDamping (0.1); // add some damping
22
23       double density = 100.0;
24       double particleMass = 50.0;
25
26       // create three spheres, each with a different color, along with a marker
27       // to attach a spring to, and arrange them roughly around the origin
28       RigidBody sphere0 = RigidBody.createIcosahedralSphere (
29          "sphere0", 0.5, density, /*ndivs=*/2);
30       FrameMarker mkr0 = mech.addFrameMarker (sphere0, new Point3d(0, 0, 0.5));
31       sphere0.setPose (new RigidTransform3d (1, 0, -0.5,  0, -DTOR*60, 0));
32       RenderProps.setFaceColor (sphere0, new Color(0f, 0.4f, 0.8f));
33       mech.addRigidBody (sphere0);
34
35       RigidBody sphere1 = RigidBody.createIcosahedralSphere (
36          "sphere1", 0.5, 1.5*density, /*ndivs=*/2);
37       FrameMarker mkr1 = mech.addFrameMarker (sphere1, new Point3d(0, 0, 0.5));
38       sphere1.setPose (new RigidTransform3d (-1, 0, -0.5,  0, DTOR*60, 0));
39       RenderProps.setFaceColor (sphere1, new Color(0f, 0.8f, 0.4f));
40       mech.addRigidBody (sphere1);
41
42       RigidBody sphere2 = RigidBody.createIcosahedralSphere (
43          "sphere2", 0.5, 1.5*density, /*ndivs=*/2);
44       FrameMarker mkr2 = mech.addFrameMarker (sphere2, new Point3d(0, 0, 0.5));
45       sphere2.setPose (new RigidTransform3d (0, 0, 1.1,  0, -DTOR*180, 0));
46       RenderProps.setFaceColor (sphere2, new Color(0f, 0.8f, 0.8f));
47       mech.addRigidBody (sphere2);
48
49       // create three muscles to connect the bodies via their markers
50       Muscle muscle0 = attachMuscle ("muscle0", mech, mkr1, mkr0);
51       Muscle muscle1 = attachMuscle ("muscle1", mech, mkr0, mkr2);
52       Muscle muscle2 = attachMuscle ("muscle2", mech, mkr1, mkr2);
53
54       // enable collisions between the spheres
55       mech.setDefaultCollisionBehavior (true, /*mu=*/0);
56
57       // render muscles as red spindles
58       RenderProps.setSpindleLines (mech, 0.05, Color.RED);
59       // render markers as white spheres. Note: unlike rigid bodies, markers
60       // normally have null render properties, and so we need to explicitly set
61       // their render properties for use in the control panel
62       RenderProps.setSphericalPoints (mkr0, 0.1, Color.WHITE);
63       RenderProps.setSphericalPoints (mkr1, 0.1, Color.WHITE);
64       RenderProps.setSphericalPoints (mkr2, 0.1, Color.WHITE);
65
66       // create a control panel to collectively set muscle excitation and
67       // stiffness, inertial damping, sphere visibility and color, and marker
68       // color. Muscle excitations and sphere colors can also be set
69       // individually.
70       ControlPanel panel = new ControlPanel();
71       panel.addWidget ("excitation", muscle0, muscle1, muscle2);
72       panel.addWidget ("excitation 0", "excitation", muscle0);
73       panel.addWidget ("excitation 1", "excitation", muscle1);
74       panel.addWidget ("excitation 2", "excitation", muscle2);
75       panel.addWidget (
76          "stiffness", "material.stiffness", muscle0, muscle1, muscle2);
77       panel.addWidget ("inertialDamping", sphere0, sphere1, sphere2);
78       panel.addWidget (
79          "spheres visible", "renderProps.visible", sphere0, sphere1, sphere2);
80       panel.addWidget (
81          "spheres color", "renderProps.faceColor", sphere0, sphere1, sphere2);
82       panel.addWidget ("sphere 0 color", "renderProps.faceColor", sphere0);
83       panel.addWidget ("sphere 1 color", "renderProps.faceColor", sphere1);
84       panel.addWidget ("sphere 2 color", "renderProps.faceColor", sphere2);
85       panel.addWidget (
86          "marker color", "renderProps.pointColor", mkr0, mkr1, mkr2);
87       addControlPanel (panel);
88    }
89 }

First, a MechModel is created with zero gravity and a default inertialDamping of 0.1 (lines 18-21). Next, three spheres are created, each with a different color and a frame marker attached to the top. These are positioned around the world coordinate origin, and oriented with their tops pointing toward the origin (lines 26-47), allowing them to be connected, via their markers, with three simple muscles (lines 49-52) created using the support method attachMuscle() (lines 8-14). Collisions (Chapter 8) are then enabled between all spheres (line 55).

At lines 62-64, we explicitly set the render properties for each marker; this is done because marker render properties are null by default and hence need to be explicitly set to enable widgets to be created for them, as discussed further below.

Finally, a control panel is created for various dynamic and rendering properties. These include the excitation and material stiffness for all muscles (lines 71 and 75); the inertialDamping, rendering visibility and faceColor for all spheres (lines 77, 79, and 81); and the rendering pointColor for all markers (lines 85). The panel also allows muscle excitations and sphere face colors to be set individually (lines 72-74 and 82-84). Some of the addWidget() calls explicitly set the label text for their widgets. For example, those controlling individual muscle excitations are labeled as “excitation 0”, “excitation 1”, and “excitation 2” to denote their associated muscle.

Some widgets are created for subproperties of their components (e.g., material.stiffness and renderProps.faceColor). The following caveats apply in these cases:

  1. 1.

    The parent property (e.g., renderProps for renderProps.faceColor) must be present in the component. While this will generally be true, in some instances the parent property may have a default value of null, and must be explicitly set to a non-null value before the widget is created (otherwise the subproperty will not be found and the widget creation will fail). This most commonly occurs for the renderProps property of smaller components, like particles and markers; in the example, render properties are explicitly assigned to the markers at lines 62-64.

  2. 2.

    If the parent property is changed for a particular host, then the widget will no longer be able to access the subproperty. For instance, in the example, if the material property for a muscle is changed (via either code or the GUI), then the widget controlling material.stiffness will no longer be able to access the stiffness subproperty for that muscle.

To run this example in ArtiSynth, select All demos > tutorial > ControlPanelDemo from the Models menu. When the model is run, the spheres will start to be drawn together by the muscles’ intrinsic stiffness. Setting non-zero excitation values in the control panel will increase the attraction, while setting stiffness values will likewise affect the dynamics. The panel can also be used to make all the spheres invisible, or to change their colors, either separately or collectively.

When a single widget is used to control a property across multiple host components, the property’s value may not be the same for those components. When this occurs, the widget’s value field displays either blank space (for numeric, string and enum values), a “?” (for boolean values), or a checked pattern (for color values). This is illustrated in Figure 5.2 for the “excitation” and “spheres color” widgets, since their individual values differ.

5.1.4 Coordinate panels

Figure 5.3: Coordinate panel added to the model MultiJointedArm.

A special subclass of ControlPanel is a CoordinatePanel, which supports the addition of widgets for controlling joint coordinate values. A CoordinatePanel is associated with the MechModel containing the coordinates’ joints. When a coordinate value changes, the MechModel is used to update other model components that may be affected, such as the wrap paths for MultiPointSprings and the locations of attached components.

At present, CoordinatePanels does not take into account couplings between coordinate values that may be induced by kinematic loops with the model. This will be remedied in the future.

A CoordinatePanel may be created with the constructor

CoordinatePanel (String name, MechModel mech)

which assigns it a name and MechModel. Coordinate widgets may then be added using a variety of methods:

CoordinateWidget addCoordinateWidget (JointBase joint, String cname)
CoordinateWidget addCoordinateWidget (JointBase joint, int cidx)
CoordinateWidget addCoordinateWidget (String label, JointBase joint, int cidx)
CoordinateWidget addCoordinateWidget (JointCoordinateHandle handle)
CoordinateWidget addCoordinateWidget (String label, JointCoordinateHandle handle)
CoordinateWidget addCoordinateWidget (JointCoordinateHandle handle, double min, double max)
CoordinateWidget addCoordinateWidget (String label, JointCoordinateHandle handle, double min, double max)
CoordinateWidget addCoordinateWidgets (JointBase joint)

The first three methods create a coordinate widget for a given joint, with the coordinate itself specified by either its name (cname) or index (cidx). For the third method, the widget’s label can be set explicitly; if label is null, or unspecified as in the other methods, then a label is generated from the coordinate’s name. The next four methods specify the coordinate using a JointCoordinateHandle, which encapsulates both a coordinate’s joint and index information and can be constructed easily as follows:

   JointCoordinateHandle handle = new JointCoordinateHandle (joint, cidx);

Some of these methods also allow the widget’s value range to be explicitly set via the arguments min and max. The final method, addCoordinateWidgets() adds widgets for every coordinate within the specified joint.

These methods return CoordinateWidget, which is a subclass of DoubleFieldSlider. Other types of widgets may be added to a CoordinatePanel using the addWidget() methods inherited from ControlPanel.

CoordinatePanel widgets represent their values for revolute coordinates in degrees.

When added to the example MultiJointedArm (Section 3.5.16), the following code fragment creates the coordinate panel shown in Figure 5.3:

import artisynth.core.gui.CoordinatePanel;
...
      CoordinatePanel panel = new CoordinatePanel ("coordinates", myMech);
      panel.addCoordinateWidgets (ujoint);
      panel.addCoordinateWidgets (hinge);
      addControlPanel (panel);

5.1.4.1 Panel settings

Figure 5.4: Coordinate panel with a settings panel added at the bottom.

Once a coordinate panel is created, an application can use the method addSettingsPanel() to add a subpanel that controls the panel’s behavior:

      CoordinatePanel panel = new CoordinatePanel ("coordinates", myMech);
      ... add coordinate widgets ...
      panel.addSettingsPanel();
      addControlPanel (panel);

The panel, shown in Figure 5.4, controls the following behavior settings:

  • Degrees: Sets whether values for rotary coordinates are expressed in degrees. The default setting is true.

  • Multiset: If true, changing the widget values does not change the underlying coordinates until the panel’s Set button is clicked, at which point all change requests are applied at once. This allows multiple coordinate values to be set simultaneously.

The degrees setting can also be controlled in code using the methods

boolean getUseDegrees()

Queries use of degrees for rotary coordinates.

void setUseDegrees (boolean enable)

Sets use of degrees for rotary coordinates.

5.1.4.2 Coordinate setting methods

For convenience, CoordinatePanel offers access to its underlying CoordinateSetter, together with wrapper methods to set or get coordinate values based on the widget label:

CoordinateSetter getCoordinateSetter()

Get the underlying coordinate setter.

double getCoordinate (String label)

Get value by widget label (radians for rotary coords).

SetStatus setCoordinate (String label, double val)

Set value by widget label (radians for rotary coords).

double getCoordinateDeg (String label)

Get value by widget label (degrees for rotary coords).

SetStatus setCoordinateDeg (String label, double val)

Set value by widget label (degrees for rotary coords).

void setRequest (String label, double val)

Request set by widget label (radians for rotary coords).

void setRequest (String label, double val)

Request set by widget label (degrees for rotary coords).

SetStatus setCoordinates ()

Apply all set requests.

For more information on setting coordinate values and the meaning of the return value SetStatus, see Section 3.6.