12 Importing OpenSim Models

12.5 Model editing

Once an OpenSim model has been imported into ArtiSynth and tuned, users will typically want to modify it. This entails locating various OpenSim components, which are then removed, modified or connected to other ArtiSynth components, such as FEM models.

12.5.1 Finding components

Locating the OpenSim components can done in several ways. First, as with all ArtiSynth models, any component that is a descendant of a container component can be found by specifying its path name with respect to the container, using findComponent() method. For example, the MultiPointMuscle TRIlong seen Figure 12.3 could be obtained using

import artisynth.core.mechmodel.MultiPointMuscle;
...
   MultiPointMuscle muscle = (MultiPointMuscle)mech.findComponent (
      "forceset/TRIlong/TRIlong");

where we know a priori that the sought component is a MultiPointMuscle and so are comfortable casting it to such. Likewise, the immediate children of any composite component can be accessed using get(idx) or get(name).

Second, most ArtiSynth container classes implement the java.util interfaces Collection<C> and Iterable<C> with respect to their component type C, making list iteration easy:

import artisynth.core.modelbase.RenderableComponentList;
import artisynth.core.mechmodels.RigidBody;
...
   // search the body set for a rigid body:
   RenderableComponentList<RigidBody> bodyset =
      (RenderableComponentList)mech.findComponent ("bodyset");
   for (RigidBody body : bodyset) {
      if (body.getName().equals ("r_humerus")) {
         System.out.println ("Found it!");
      }
   }

Finally, OpenSimParser provides a number of methods for finding components after the model has been created:

RigidBody getGround()

Returns ground, if present.

RenderableComponentList<RigidBody> getBodySet()

Returns the body set.

RigidBody findBody (String name)

Finds a body by name.

ArrayList<WrapComponent> getWrapObjects()

Returns a list of all wrap objects.

WrapComponent findWrapObject (String name)

Finds a wrap object by name.

ArrayList<WrapComponent> getBodyWrapObjects (RigidBody body)

Returns all wrap objects for a body.

List<WrapComponent> getMuscleWrapObjects ( MPointSpringBase muscle)

Returns all wrap objects for a muscle.

RenderableComponentList<JointBase> getJointSet()

Returns the joint set.

ArrayList<JointBase> getJoints()

Returns a list of all the joints.

JointBase findJoint (String name)

Finds a joint by name.

ArrayList<JointCoordinateHandle> getCoordinates()

Returns a list of all coordinates.

JointCoordinateHandle findCoordinate (String name)

Finds a coordinate by name.

RenderableComponentList<ModelComponent> getForceSet()

Returns the force set.

ArrayList<ForceComponent> getForceComponents()

Returns all force components.

ArrayList<PointSpringBase> getMusclesAndSprings()

Returns all muscles and axial springs.

ArrayList<MuscleComponent> getMuscles()

Returns all muscles.

ForceComponent findForceComponent (String name)

Finds a force component by name.

PointSpringBase findMuscleOrSpring (String name)

Finds muscle or axial spring by name.

MuscleComponent findMuscle (String name)

Finds muscle by name.

RenderableComponentList<ConstrainerBase> getConstraintSet()

Returns the constraint set.

ConstrainerBase findConstraint (String name)

Finds a constraint by name.

PointList<Marker> getMarkerSet()

Returns the marker set.

Marker findMarker (String name)

Finds a marker by name.

The following should be noted about these methods:

  • For OpenSim 3 models, getGround() and getJointSet() will both return null. To obtain a list of all joints in this case, one can use getJoints(), which collects all joints regardless of where they are located within the hierarchy.

  • Access to joint coordinates is provided by means of a JointCoordinateHandle, which encapsulates the information about both the coordinate and the joint to which it belongs.

  • The muscles and axial spring components returned by getMusclesAndSprings() are a subset of the components returned by getForceComponents().

12.5.2 Removing components

For component removal, most container classes supply remove() methods that allow this to be done directly. However, care must be be taken that other model components either do not have a hard dependency on those being removed, or that the dependent components are be removed as well. For example, any muscle component has a hard dependency on the frame markers that serve as its origin and insertion points, so removing either of these markers necessitates removing the muscle as well. The easiest way to ensure the removal of one or more components and their dependencies is to use the utility methods

void ComponentUtils.deleteComponentAndDependencies (ModelComponent comp)
void ComponentUtils.deleteComponentsAndDependencies (Collection<? extends ModelComponent> comps)

For example, to remove a frame marker, one may call

import artisynth.core.modelbase.ComponentUtils;
import artisynth.core.mechmodels.Marker;
...
   Marker mkr;
   ... locate the marker to remove ...
   ComponentUtils.deleteComponentAndDependencies (mkr);

In addition to removing components with hard dependencies, these methods also take care of soft dependencies, by which some components adjust themselves if certain components are deleted. For example, a MultiPointMuscle has a soft dependency on any wrap object that it references: if the wrap object is removed, the muscle deletes its internal reference to the object and update its wrap path.

Use of these methods is equivalent to invoking the Delete operation from the context menu in the ArtiSynth GUI, which also looks for dependent components and queries the user for confirmation if any are found (Figure 12.5).

Figure 12.5: Using the GUI to delete a marker that serves as a muscle origin triggers a warning that other components (in this case the muscle) will also be deleted.

There are situations where one may wish to remove a component but not the components that depend on it. For example, if one wishes to replace a rigid body with a different rigid body, or an FEM model of the same structure, one may wish to transfer to the new body the joints and markers that depend on the original body. This is described more in the following section.

12.5.3 Attaching and transferring to ArtiSynth components

New ArtiSynth components can be attached to OpenSim components through the general point and frame attachment mechanisms described in Sections 3.8, 6.4, and 6.6.

Contact behaviors (Chapter 8) can also be set between OpenSim and/or ArtiSynth components. When working with the former, it may be desirable to replace body surface meshes (using setSurfaceMesh()), or add additional meshes for collision purposes (using addMesh()).

In some instances, one may wish to transfer OpenSim joints, path points, markers or wrap objects to new ArtiSynth components. For joints, one can use the various setBodies() or setBodyA/B() methods described in Section 3.4.3 to reassign one or both bodies for an existing joint.

OpenSim wrap objects are attached to their parent bodies using a frame attachment. Attaching to a different body involves replacing this attachment. However, when doing so, the existing frame attachment must be removed and the existing wrap object should probably be removed from its parent body and placed elsewhere in the component hierarchy. To make this easier, OpenSimParser supplies the method removeWrapObject() that disconnects a wrap object from its parent body and removes its attachment; the application can then move the wrap object elsewhere and reattach it:

  MechModel mech;       // mech model containing everything
  OpenSimParser parser; // parser that imported the model
  WrapComponent wobj;   // wrap object to be reassigned
  RigidBody newBody;    // new body to connect wobj to
  ...
  parser.removeWrapObject(wobj);     // detach wobj and remove its attachment
  RigidBody wbody = (RigidBody)wobj; // wrap objects are also rigid bodies
  mech.addRigidBody (wbody);         // move wobj to standard body list
  mech.attachFrame (wbody, newBody); // attach to new body

For transferring markers and path points to other bodies, OpenSimParser supplies the methods

Marker replacePathPoint (PointSpringBase muscle, Marker pnt, PointAttachable body)
Marker replaceMarker (Marker mkr, PointAttachable body)

replacePathPoint() replaces the path point pnt of the specified muscle with a new point (also implemented as a Marker, though not necessarily a FrameMarker) attached to the specified body. The new point is placed in the same location of the muscle as the original. Similarly, replaceMarker() replaces the marker mkr within the marker set with a new marker attached to the specified body.

12.5.4 Example: replacing the elbow joint with a contact FEM model

Figure 12.6: Arm26FemElbow model, showing a closeup of the toy FEM model used to emulate the elbow, with the humerus displayed as a wireframe for greater visibility.

The model

  artisynth.demos.opensim.Arm26FemElbow

presents a modified version of OpenSimArm26 in which the elbow joint has been removed and replaced with a (very simple) contact model involving two nested FEM cylinders simulating cartilage layers, with the outer layer attached to the lower arm and the inner one attached to the humerus (Figure 12.6). Additional stability is supplied by a FrameSpring, located where the elbow joint was, that prevents sliding and limits rotation along and about the joint axis. The class definition, excluding import statements, is shown below:

1 public class Arm26FemElbow extends OpenSimArm26 {
2    /**
3     * Helper method to set material and render properties fo a FEM.
4     */
5    private void initializeFem (FemModel3d fem, String name) {
6       // Mooney Rivlin value c01 of 2e6 is sometimes reported for cartilage
7       MooneyRivlinMaterial mrmat = new MooneyRivlinMaterial (
8          /*c10*/2000000, /*c01*/10000, 0, 0, 0, /*kappa*/1e8);
9       fem.setMaterial (mrmat);
10       fem.setParticleDamping (1.0);
11       fem.setDensity (1000);
12       fem.setName (name);
13       // enable surface rending, turn off element rendering, change color
14       fem.setSurfaceRendering (SurfaceRender.Shaded);
15       RenderProps.setVisible (fem.getElements(), false);
16       RenderProps.setDrawEdges (fem, true);
17       RenderProps.setFaceColor (fem, new Color (0.8f, 0.8f, 1f));
18       RenderProps.setLineColor (fem, new Color (0.6f, 0.6f, 1f));
19    }
20
21    public void build (String[] args) throws IOException {
22       super.build (args); // create OpenSimArm26
23
24       // geometry dir is "geometry" relative to source for this model
25       String geodir = getSourceRelativePath ("geometry/");
26
27       // locate the humerus, lower arm, and elbow joint
28       RigidBody humerus = myParser.findBody ("r_humerus");
29       RigidBody lowerArm = myParser.findBody ("r_ulna_radius_hand");
30       JointBase elbow = myParser.findJoint ("r_elbow");
31
32       // use the current TDW for the elbow joint to determine the transform to
33       // locate the FEM meshes
34       RigidTransform3d TDW = new RigidTransform3d();
35       TDW.set (elbow.getCurrentTDW());
36
37       // read FEM models for the inner and outer cartilage layers from
38       // predefined Gmsh files, initialize their other properties, transform
39       // them to the location of the elbow joint, and add them to the MechModel
40       FemModel3d innerCart = GmshReader.read (geodir+"innerCart.gmsh");
41       initializeFem (innerCart, "inner_cartilage");
42       innerCart.transformGeometry (TDW);
43       myMech.addModel (innerCart);
44
45       FemModel3d outerCart = GmshReader.read (geodir+"outerCart.gmsh");
46       initializeFem (outerCart, "outer_cartilage");
47       outerCart.transformGeometry (TDW);
48       myMech.addModel (outerCart);
49
50       // read the nodes that need to be attached for each model, and
51       // attach them to the humerus or lower arm
52       ArrayList<FemNode3d> attach;
53       attach = NodeNumberReader.read (geodir+"innerCartAttach.txt", innerCart);
54       for (FemNode3d n : attach) {
55          myMech.attachPoint (n, humerus);
56       }
57       attach = NodeNumberReader.read (geodir+"outerCartAttach.txt", outerCart);
58       for (FemNode3d n : attach) {
59          myMech.attachPoint (n, lowerArm);
60       }
61
62       // Remove the elbow joint and replace with a FrameSpring to provide
63       // motion limits.
64       ComponentUtils.deleteComponentAndDependencies (elbow);
65       PowerFrameMaterial fmat = new PowerFrameMaterial();
66       // set stiffnesses to limit sliding and rotation along/about z.
67       fmat.setStiffness (0, 0, 10000);
68       fmat.setRotaryStiffness (0, 0, 3000);
69       // use a rotary deadband to provide a z rotation range of [0,PI]
70       fmat.setUpperRotaryDeadband (0, 0, Math.PI); //
71       FrameSpring fspring = new FrameSpring ("elbow_spring", fmat);
72       fspring.setUseTransformDC (false); // use same transform as for joints
73       fspring.setFrames (lowerArm, humerus, TDW);
74       myMech.addFrameSpring (fspring);
75
76       // enable collsions between the inner and outer cartilage
77       CollisionBehavior cb = myMech.setCollisionBehavior (
78          innerCart, outerCart, true);
79       // to improve stability, make the contact compliant and reduce step size
80       double c = 1e-6;
81       cb.setCompliance (c);
82       cb.setDamping (2*Math.sqrt(/*mass*/5/c)); // aim for critical damping
83       setMaxStepSize (0.005);
84
85       // With the elbow joint removed, some muscles exert initial forces that
86       // are too large. Reduce this by reseting their tendon slack length.
87       for (String muscleName : new String[] { "BIClong", "BRA" }) {
88          MuscleComponent muscle = myParser.findMuscle (muscleName);
89          Thelen2003AxialMuscle tmat = (Thelen2003AxialMuscle)muscle.getMaterial();
90          tmat.setTendonSlackLength (muscle.getLength()-tmat.getOptFibreLength());
91       }
92       // remove initial muscle excitations
93       myParser.zeroMuscleExcitations();
94
95       // make the TRI wrap object invisible
96       RenderProps.setVisible (myParser.findWrapObject ("TRI"), false);
97    }
98 }

In the build() method, super.build() is first called to create the OpenSimArm26 model which this example extends. Then the geometry folder is located relative to the model’s source folder (line 25), and the humerus, lower arm, and elbow components are located using the parser’s findBody() and findJoint() methods (lines 25-30).

The FEM models used to simulate the inner and outer cartilage layers are defined with respect to the elbow’s D frame, which is located in world coordinates using its getCurrentTDW() method (line 35). The models themselves, which are defined as Gmsh files in the geometry folder, are read using GmshReader (Section 6.2.2), initialized with the helper method initializeFem() which sets their material and rendering properties (lines 5-19), transformed to the D frame, and added to the MechModel (lines 40-48). The inner and outer layers are then connected to the humerus and lower arm, respectively, by attaching nodes whose numbers are specified by node number files (Section 6.4.6) also located in the geometry folder (lines 52-60).

With the cartilage models connected, the elbow joint is removed (line 64). It is replaced by a FrameSpring (lines 65-74) to supply the stabilization effected by other tissues surrounding the joint. The spring is centered on the D frame, and uses a PowerFrameMaterial (Section 3.6.2.1) with all stiffnesses set to 0 except for a translational stiffness along z to limit sliding along the joint axis, and a rotational stiffness (with a deadband of \pi) about z to limit joint axis rotation to the approximate range [0,\pi].

Note the use of setUseTransformDC(false) when setting up the FrameSpring. As described in Section 3.6.1, this makes the spring compute its restoring forces using {\bf T}_{CD}, which is the same displacement used by joints, instead of its inverse {\bf T}_{DC}. This in turn means that translations and rotations will have the same directionality in the spring as in the original joint.

Frictionless contact is enabled between the cartilage models (lines 77-78), and to improve stability this contact is made compliant (Section 8.7.1, lines 80-82) and the model’s step size is decreased to 0.005 sec (line 83).

Removal of the elbow joint revealed that the quiescent forces in the muscles "BIClong" and "BRA" were large enough to cause a large upward impulse at the start of the simulation. To eliminate this, the tendon slack length for these muscles is reset to equal the difference between the total muscle length and the optimal fibre length (lines 87-91; this calculation uses the fact that the optimal pennation angle is 0). To further reduce initial forces, the bias excitations present in the OpenSim model are also removed (line 93). Finally, to enable the FEM joint to be seen, the wrap object "TRI" is made invisible (line 96).

To run this example in ArtiSynth, select All demos > opensim > Arm26FemElbow from the Models menu.

It is possible to make this model go unstable, usually with an “inverted elements” error, if sufficiently large or abrupt forces are exerted on it via the pull controller or the excitation panel.

12.5.5 Example: replacing the humerus with a FEM model

Figure 12.7: Arm26FemHumerus model, showing the FEM humerus and the three coordinate frames embedded into its top, middle and bottom.

The model

  artisynth.demos.opensim.Arm26FemHumerus

presents a modified version of OpenSimArm26 in which the humerus has been replaced with an FEM model, with the joints, wrap objects, and muscle path points transferred from the humerus body to the FEM model (Figure 12.7). Most of this transfer is based on three Frame objects that are attached to the top, middle and center of the FEM model, using the methods described in Section 6.6. These frames serve as anchors for attaching the shoulder and elbow joints, the wrap objects, and the muscle path points that are far enough from the FEM that direct point-to-FEM connections are problematic. Forces applied to wrap objects and path points are transmitted to the FEM model via the frames, which are given a large number of supporting nodes so that the resulting stresses are distributed across a reasonable number of elements.

The build() method for Arm26FemHumerus makes use of the following helper methods:

131     * Find the frame whose origin is nearest to a given point or vector.
132     */
133    Frame nearestFrame (Frame[] frames, Vector3d pos) {
134       Frame nearest = null;
135       double minDist = Double.POSITIVE_INFINITY;
136       for (Frame frame : frames) {
137          double dist = frame.getPose().p.distance(pos);
138          if (dist < minDist) {
139             nearest = frame;
140             minDist = dist;
141          }
142       }
143       return nearest;
144    }
145
146    /**
147     * Create a frame attachment inside a FEM model. For this application, we
148     * support the attachment using the FEM nodes that are within
149     * ’frameAttachDist’ of the frame’s z axis.
150     */
151    FrameAttachment createFrameAttachment (
152       Frame frame, FemModel3d fem, RigidTransform3d TFW) {
153       ArrayList<FemNode3d> nodes = new ArrayList<>();
154       Vector3d u = new Vector3d();
155       TFW.R.getColumn (2, u);
156       Line line = new Line (new Point3d(TFW.p), u);
157       for (FemNode3d n : fem.getNodes()) {
158          if (line.distance(n.getPosition()) <= frameAttachDist) {
159             nodes.add (n);
160          }
161       }
162       FrameFem3dAttachment attachment = new FrameFem3dAttachment (frame);
163       attachment.setFromNodes (TFW, nodes.toArray(new FemNode3d[0]));
164       return attachment;
165    }
166
167    /**
168     * Create a frame with a given name, and attach it to the FEM at
169     * the pose specified by TFW.
170     */
171    private Frame addFemFrame (
172       MechModel mech, FemModel3d fem, String name, RigidTransform3d TFW) {
173       Frame frame = new Frame ();
174       frame.setName (name);
175       frame.setAxisLength (0.05); // make the axes of the frame visible
176       mech.addFrame (frame);
177       mech.addAttachment (createFrameAttachment (frame, fem, TFW));
178       return frame;
179    }
180 }

nearestFrame() finds the frame whose origin is nearest to a given position vector. createFrameAttachment() creates a FrameFem3dAttacment (Section 6.6) for a frame located at the pose described by TFW and supported by a set of nodes that are within a prescribed distance of the frame’s z axis. This choice of supporting nodes is based on the fact that much of the force in this model involves moments about the z axis, but other choices are possible. The critical point is to choose enough nodes so that stresses induced frame forces are distributed reasonably well within the model. The method addFemFrame() creates a Frame object initially located at TFW, gives it a name, and attaches it to a FEM model using an attachment created by createFrameAttachment().

The build() method itself is listed below:

40    public void build (String[] args) throws IOException {
41       // create OpenSimArm26
42       super.build (args);
43
44       // find the original humerus bone
45       RigidBody humerus = myParser.findBody ("r_humerus");
46
47       // create a FEM model for the humerus, based on a TetGen construction of
48       // a higher resolution surface mesh
49       PolygonalMesh mesh = new PolygonalMesh (
50          getSourceRelativePath ("geometry/humerus_rv_highres.obj"));
51       FemModel3d fem = FemFactory.createFromMesh (null, mesh, /*quality*/2);
52       fem.setMaterial (new LinearMaterial (1e9, 0.49));
53       fem.setParticleDamping (1.0);
54       // match the density to the original humerus
55       fem.setDensity (humerus.getDensity());
56       fem.setName ("humerus");
57       // transform the model to match current humerus pose
58       fem.transformGeometry (humerus.getPose());
59       // enable surface rending, turn off element rendering, set colors
60       fem.setSurfaceRendering (SurfaceRender.Shaded);
61       RenderProps.setVisible (fem.getElements(), false);
62       RenderProps.setDrawEdges (fem, true);
63       RenderProps.setFaceColor (fem, new Color (0.8f, 0.8f, 1f));
64       RenderProps.setLineColor (fem, new Color (0.6f, 0.6f, 1f));
65       myMech.addModel (fem);
66
67       // replace humerus in the shoulder and elbow joints
68       JointBase shoulder = myParser.findJoint ("r_shoulder");
69       JointBase elbow = myParser.findJoint ("r_elbow");
70       shoulder.setBodyA (
71          fem, createFrameAttachment (null, fem, shoulder.getCurrentTCW()));
72       elbow.setBodyB (
73          fem, createFrameAttachment (null, fem, elbow.getCurrentTDW()));
74       // add range limits for the elbow
75       elbow.setCoordinateRange (0, new DoubleInterval(0, Math.PI));
76
77       // Attach three frames to top, middle, and bottom of the humerus FEM.
78       // These will serve as anchor frames for the wrap objects, or for
79       // humerus-based muscle points that are not origins or insertions.
80       Frame[] anchorFrames = new Frame[3];
81       // use shoulder joint frame for the top frame
82       RigidTransform3d TFW = new RigidTransform3d (shoulder.getCurrentTDW());
83       anchorFrames[0] = addFemFrame (myMech, fem, "top", TFW);
84       TFW.mulXyz (0, -0.14, -0.005);
85       anchorFrames[1] = addFemFrame (myMech, fem, "mid", TFW);
86       // use elbow joint frame for the bottom frame
87       anchorFrames[2] = addFemFrame (myMech, fem, "bot", elbow.getCurrentTDW());
88
89       // reattach each humerus wrap object to the nearest FEM frame
90       for (WrapComponent wobj : myParser.getWrapObjects()) {
91          // remove object and it’s attachment from OpenSim hierarchy
92          myParser.removeWrapObject(wobj);
93          RigidBody wbody = (RigidBody)wobj; // wobj is also a RigidBody
94          // place it in the default ’rigidBodies’ container, and create an
95          // attachment for it
96          myMech.addRigidBody (wbody);
97          Frame frame = nearestFrame (anchorFrames, wbody.getPose().p);
98          myMech.addAttachment (new FrameFrameAttachment (wbody, frame));
99       }
100
101       // replace the muscle points that are attached to the humerus with ones
102       // that are attached to the FEM
103       for (PointSpringBase muscle : myParser.getMusclesAndSprings()) {
104          for (int i=0; i<muscle.numPoints(); i++) {
105             // can assume default OpenSim muscle points are FrameMarkers
106             FrameMarker mkr = (FrameMarker)muscle.getPoint(i);
107             if (mkr.getFrame() == humerus) {
108                // marker is attached to the humerus
109                PointAttachable attachBody;
110                if (i == 0 || i == muscle.numPoints()-1) {
111                   // origin or insertion - attach directly to FEM
112                   attachBody = fem;
113                }
114                else {
115                   // attach to the nearest frame
116                   attachBody = nearestFrame (anchorFrames, mkr.getPosition());
117                }
118                // replace the old point with a new one attached to ’attachBody’
119                myParser.replacePathPoint (muscle, mkr, attachBody);
120             }
121          }
122       }
123       // reassign epicondyle marker to the lowest FEM frame
124       Marker mkr = myParser.findMarker ("r_humerus_epicondyle");
125       myParser.replaceMarker (mkr, anchorFrames[2]);
126       // remove the humerus, which is now attached to nothing
127       myParser.getBodySet().remove (humerus);
128    }

First, super.build() is called to create the OpenSimArm26 model which this example extends. The parser’s findBody() method is then used to locate the humerus body that is being replaced (line 45).

The FEM model replacing the humerus body is created and added to the MechModel at lines (47-66). A tetrahedral element mesh is created from a surface mesh (read from a geometry directory) by FemFactory.createFromMesh(). Material properties are assigned, with the FEM model given the same density as that of the humerus body (which is higher than bone density to account for the mass of surrounding tissue). Since the defining surface mesh uses the same coordinate system as the original humerus mesh, the model geometry can be transformed into the correct position using the pose of the humerus body (line 59).

The remainder of the build() method transfers the joints, wrap objects, muscle path points and markers from the humerus body to the FEM model. Joints are transferred at lines 60-76. The humerus corresponds to body A of the shoulder joint and body B of the elbow joint, and so setBodyA() and setBodyB() are used, respectively, to reconnect these to the FEM model at the shoulder’s C frame and the elbow’s D frame. Attachments are generated by createFrameAttachment(), using the world locations of the C and D frames returned by the joint methods getCurrentTCW() and getCurrentTDW().

At lines 81-88, addFemFrame() is used to create and attach three anchoring frames to the top, middle and bottom of the FEM model. The top and bottom frames are located the C and D frames of the shoulder and elbow joints, while the middle frame is placed at an offset from the C frame. All wrap objects connected to the humerus body are reconnected to one of these frames (lines 91-100). For each wrap object, the parser’s removeWrapObject() removes it from the OpenSim component hierarchy and deletes its attachment, the object is cast to a RigidBody and added to the MechModel’s default rigid body list (other locations could be used), and a new attachment is created between the wrap object and the nearest anchor frame.

The muscle path points are transferred at lines 104-123. The code iterates through all the points of all the muscles. Since OpenSimParser implements all path points as FrameMarkers, each point can be cast to a FrameMarker and inspected to see if its frame is the original humerus body. If it is, the point must be replaced with a new marker attached to the FEM model. If the point is either the first or last path point, then it is an origin or insertion and so is near enough to the FEM model than it can be attached directly. Otherwise, it is attached to the nearest anchor frame. Point replacement is performed by the parser’s replacePathPoint() method.

Lastly, the "r_humerus_epicondyle" marker is transferred to the bottom anchor frame, using the parser’s replaceMarker() method, and the humerus body is removed from the body set. There is no need to do this removal using the ComponentUtils method deleteComponentAndDependencies() since all dependencies have been removed by the preceding steps.

To run this example in ArtiSynth, select All demos > opensim > Arm26FemHumerus from the Models menu.