Not on Windows? In your AGX Dynamics installation, navigate to data/openplx/tutorials/t02-Introduction-to-automatic-assembly/

Tutorial 02 — Introduction to Automatic Assembly

This tutorial will guide you through the basics of modeling mechanical systems using the built-in SNAP feature in the Physics3D bundle of OpenPLX. Using two building blocks—an axle and a link with several holes—we will demonstrate how to use SNAP to assemble mechanical systems.

The links_and_axles.openplx OpenPLX file contains the full tutorial content.

About SNAP

SNAP is designed to manage hierarchical tree structures of physical systems. At its core, a Physics3D.Interactions.Mate has two MateConnectors, which define the interaction. Using SNAP, OpenPLX attempts to find a local_transform for the owner or its parent, aligning the MateConnectors relative to their common ancestor, Physics3D.System.

Key behaviors of SNAP:

To debug ambiguities, run agxViewer with --enableOpenPlxDebugLogs trace and look for Forcing in the trace logs.


Step 1: Import the Link

Let's model the Link as a RigidBody with an ExternalTriMeshGeometry for both the visual object and the collision geometry. Create a file called links_and_axles.openplx with the following content.

Link is Physics3D.Bodies.RigidBody:
    scale is Real: 1.0
    geometry is Physics3D.Geometries.ExternalTriMeshGeometry:
        path: @"link_with_holes.obj"
        scale: {scale, scale, scale}
    visual is Visuals.Geometries.ExternalTriMeshGeometry:
        path: geometry.path
        scale: geometry.scale

Before we can load it, ensure that link_with_holes.obj is in the same folder as the openplx file you created, it is available under data/openplx/tutorial/t02-Introduction-to-automatic-assembly in your AGX installation. Test the model with agxViewer:

agxViewer links_and_axles.openplx --modelName Link -p

Step 2: Model the Link holes

The link_with_holes.obj defines the geometry. Key details:

Next, create HoleConnector model to represent a generic hole. To accommodate scaling, a scale attribute adjusts the connector’s position.

HoleConnector is Physics3D.Interactions.MateConnector:
    scale is Real: 1.0
    local_position is Real
    main_axis: Math.Vec3.Z_AXIS()
    position: Math.Vec3.from_xyz(0.013 * scale + local_position, 0.014 * scale, -0.00225 * scale)

The -0.00225 Z-offset positions the hole at the center plane of the link. Now, define a LinkWithHoles model and assign six holes, forwarding the scale attribute from the Link to each HoleConnector:

LinkWithHoles is Link:
    hole_separation is Real: 0.032 * scale
    hole_A is HoleConnector:
        scale: scale
        local_position: hole_separation * 0
    hole_B is HoleConnector:
        scale: scale
        local_position: hole_separation * 1
    hole_C is HoleConnector:
        scale: scale
        local_position: hole_separation * 2
    hole_D is HoleConnector:
        scale: scale
        local_position: hole_separation * 3
    hole_E is HoleConnector:
        scale: scale
        local_position: hole_separation * 4
    hole_F is HoleConnector:
        scale: scale
        local_position: hole_separation * 5

Test the model with agxViewer:

agxViewer links_and_axles.openplx --modelName LinkWithHoles -p

Two hinged links


Step 3: Connect two links with a hinge

Connect two instances of LinkWithHoles at specific holes using a Physics3D.Interactions.Hinge.

#--- Previous definitions ---

TwoHingedLinks is Physics3D.System:
    link_1 is LinkWithHoles
    link_2 is LinkWithHoles
    link_hinge is Physics3D.Interactions.Hinge:
        connectors: [link_1.hole_A, link_2.hole_F]

Run this model in agxViewer to see SNAP align hole_A of link_1 with hole_F of link_2.

agxViewer links_and_axles.openplx --modelName TwoHingedLinks -p --openplxDebugRenderFrames

Note that by specifying the --openplxDebugRenderFrames command line argument, you may see an rendered axis for each used MateConnector. To render an axis on the unused MateConnectors, you may add an annotation to them .agx_debug_render_frame: true and they will be visualized no matter if the command line argument is used or not.

The two links are initialized in a overlapping state, which can be seen in the image below. Two hinged links

Because the MateConnector is positioned at the link's center, and SNAP aligns the MateConnector axes, the links initially overlap geometrically. To resolve this, we can reposition the MateConnectors (or holes) to the link's surface instead of at the center plane. Additionally, introducing a small clearance—by offsetting each connector by an extra half millimeter—ensures the link surfaces don't make contact.

The position of each MateConnector influences the local moments on the links, which can introduce asymmetry into the model.

#--- Previous definitions --

TwoNonCollidingHingedLinks is TwoHingedLinks:
    link_1.hole_A.position.z: 0.0005
    link_2.hole_F.position.z: -0.0045 - 0.0005

Next, we want to stop the links from falling to infinity. Do this by creating a new MateConnector that is attached to the world, and a new hinge that connects the world_connector to one of the holes on the links:

TwoNonCollidingHingedLinksWorld is TwoNonCollidingHingedLinks:
    world_connector is Physics3D.Interactions.MateConnector:
        main_axis: Math.Vec3.Y_AXIS()
    world_hinge is Physics3D.Interactions.Hinge:
        connectors: [link_1.hole_F, world_connector]

Load the TwoNonCollidingHingedLinksWorld model with agxViewer to see a double pendulum swing!

agxViewer links_and_axles.openplx --modelName TwoNonCollidingHingedLinksWorld -p --openplxDebugRenderFrames

The double pendulum can be seen below: Two hinged links world


Step 4: Non planar linked systems

MateConnectors establish local frames of reference for computing forces and moments. When the links' gravitational forces act in different planes—due to small displacements (as we explored earlier) or scenarios like one link sliding along the hinge axis—the simulation results can vary significantly based on the order of MateConnectors within the connectors array of the Mate.

Later in this tutorial, we'll explore modeling such systems more realistically using axles and examine how SNAP facilitates their automatic assembly.

Below is a model that connects two links using a Cylindrical mate and offsets one link by 10 cm. The rotational stiffness is reduced to visually emphasize the effect.

CylindricalDoublePendulum is Physics3D.System:
    world_connector is Physics3D.Interactions.MateConnector:
        main_axis: Math.Vec3.Y_AXIS()
    world_hinge is Physics3D.Interactions.Hinge:
        connectors: [world_connector, link_1.hole_F]
        initial_angle: Math.PI*0.5
    link_1 is LinkWithHoles:
        hole_A.position.z: 0.0005
    link_2 is LinkWithHoles:
        hole_F.position.z: -0.0045 - 0.0005
    link_cylindrical is Physics3D.Interactions.Cylindrical:
        initial_position: 0.1
        connectors: [link_1.hole_A, link_2.hole_F]
        flexibility becomes Physics3D.Interactions.Flexibility.LinearElasticCylindricalFlexibility:
            around_normal.stiffness: 0.1
            around_cross.stiffness: 0.1
    linear_spring is Physics3D.Interactions.LinearSpring:
        connectors: link_cylindrical.connectors
        position: link_cylindrical.initial_position

Try load this model with agxViewer:

agxViewer links_and_axles.openplx --modelName CylindricalDoublePendulum -p --openplxDebugRenderFrames

In the cylindrical mate configuration, link_1's mate connector is the first entry in the connectors array. This setup centers the mate relative to link_1, causing link_2 to generate a moment around this central point. The yellow sphere represents the force and moment computation center located at hole_A of link_1, where the gravitational force from link_2 creates a moment.

By pressing g in the 3D view, the AGX debug rendering is activated, where the cylindrical joint axis can be compared with the main_axis of the MateConnector in blue.

If we swap the order of the MateConnector of the Cylindrical, the system looks completely different in a relaxed state.

CylindricalDoublePendulumSwappedOrder is CylindricalDoublePendulum:
    linear_spring.position: -link_cylindrical.initial_position
    link_cylindrical.connectors: [link_2.hole_F, link_1.hole_A]

Try load this model with agxViewer:

agxViewer links_and_axles.openplx --modelName CylindricalDoublePendulumSwappedOrder -p --openplxDebugRenderFrames

In this cylindrical mate configuration, link_2's mate connector is positioned as the first entry in the connectors array, centering the mate relative to link_2. Notably, there is no moment around the mate center because the sole acting force is link_2's gravitational force.

Here both main_axis of the MateConnectors align with the AGX debug rendering of the Cylindrical axis. Note that in the later image the lower link is now on the opposite side of the upper link, due to the sign of initial_position is relative the first MateConnector in the connectors array.


Step 5: Model and use the axle

For models with asymmetries, as described in Step 3, or when dealing with linked systems involving more than two links along a single axis, Hinges alone may not suffice. To achieve higher fidelity, we introduce an Axle equipped with a MateConnector for connecting the link holes. We begin by defining the AxleNoGeometry, which lacks physical geometry to exclude contact constraints with other components, relying solely on the Mate for link-axle interaction.

AxleNoGeometry is Physics3D.Bodies.RigidBody:
    inertia.mass: 0.01
    inertia.tensor: Physics3D.Bodies.Inertia.symmetric_tensor({0.001,0.01,0.001}, 0, 0, 0)
    visual is Visuals.Geometries.Cylinder:
        radius: 0.0025
        height: 0.3
    connector is Physics3D.Interactions.MateConnector:
        main_axis: {0,1,0}

Let's connect two links to an Axle. When the link mate connectors are listed as the first connector in the Mate's connectors arrays, they establish the frame of reference for force and moment calculations. Set an initial position for each link along the axle, and keep the axle static by setting is_dynamic to false, which disables the dynamics keeping its position fixed and unaffected by gravity and other forces. For demonstration purposes, we'll connect hole_B of link_1 and hole_D of link_2.


TwoLinksOneAxle is Physics3D.System:
    link_1 is LinkWithHoles
    link_2 is LinkWithHoles
    axle is AxleNoGeometry:
        is_dynamic: false
    link_1_mate is Physics3D.Interactions.Cylindrical:
        connectors: [link_1.hole_B, axle.connector]
        initial_position: 0.05
        initial_angle: 0
    link_2_mate is Physics3D.Interactions.Cylindrical:
        connectors: [link_2.hole_D, axle.connector]
        initial_position: -0.05
        initial_angle: 0

By specifying the initial_angle for each mate, SNAP constrains the rotation of the axle relative to the links. However, this constraint is not enforced by the physical simulation.

Load the model with :

agxViewer links_and_axles.openplx --modelName TwoLinksOneAxle -p --openplxDebugRenderFrames

Step 6: Create a looped connection

Let's add another axle to create a closed-loop system

TwoLinksTwoAxles is TwoLinksOneAxle:
    axle_2 is AxleNoGeometry
    link_1_mate_2 is Physics3D.Interactions.Cylindrical:
        connectors: [link_1.hole_D, axle_2.connector]
        initial_position: 0.05
    link_2_mate_2 is Physics3D.Interactions.Cylindrical:
        connectors: [link_2.hole_F, axle_2.connector]
        initial_position: -0.05

Load the model using:

agxViewer links_and_axles.openplx --modelName TwoLinksTwoAxles -p --openplxDebugRenderFrames

Step 7: Use SNAP for initial states of systems in contact

Even when the Mates are disabled, SNAP will still assemble the system in the same manner. This feature is useful for setting up initial states for systems where geometric contacts are anticipated. Extend AxleNoGeometry by adding a cylinder as its collision geometry.

CollidingAxle is AxleNoGeometry:
    geometry is Physics3D.Geometries.Cylinder:
        radius: visual.radius
        height: visual.height

Extend the TwoLinksTwoAxles model and update the axles to the CollidingAxle type:

TwoLinksTwoAxlesCollision is TwoLinksTwoAxles:
    axle becomes CollidingAxle
    axle_2 becomes CollidingAxle
    link_1_mate.enabled: false
    link_2_mate.enabled: false
    link_1_mate_2.enabled: false
    link_2_mate_2.enabled: false

Lets run this simulation with a finer time discretization, at 200Hz, by adding --timeStep 0.005 to the list of arguments.

agxViewer links_and_axles.openplx --modelName TwoLinksTwoAxlesCollision -p --openplxDebugRenderFrames --timeStep 0.005