Double-Wishbone#
This notebook walks through loading a DARTS Model file of a Double Wishbone multibody. Additionally, it demonstrates how to enforce loop constraints in the graph multibody.
Requirements:
In this tutorial we will:
Import the multibody
Setup constraint kinematics
Setup the kdFlex Scene
Run the simulation with loop constraints
Run the simulation without constraints
Clean up the simulation
For a more in-depth descriptions of kdflex concepts see usage.
import atexit
import Karana.Core as kc
import Karana.Frame as kf
import Karana.Dynamics as kd
import Karana.Dynamics.SOADyn_types as kdt
Import the Multibody#
The double wishbone model has already been prepared as a SOADyn_types.SubGraphDS and saved to an H5 file. We can use this file to populate our Multibody. We then perform basic initialization and log the model to verify.
# load our DARTS model into a multibody
fc = kf.FrameContainer("root")
mbody_ds = kdt.SubGraphDS.fromFile("../resources/double_wishbone/rigid_double_wishbone.h5")
mb = mbody_ds.toMultibody(fc)
del mbody_ds
# finalize and verify our multibody
mb.ensureHealthy()
mb.resetData()
assert kc.allReady()
# dump tree structure
mb.dumpTree()
LEGEND: [<hinge type> <prescribed subhinges>] <body name>[/num embedded bodies > 0] <flex dofs > 0>
|-mb_MBVROOT_
|-[LOCKED] chassis [<--- lc/BALL <--- upper_control_arm], [---> lc/BALL ---> tie_rod], [---> lc/REVOLUTE ---> shock_absorber_upper]
|-[REVOLUTE (P)] lower_control_arm
|-[REVOLUTE] shock_absorber_lower
|-[SLIDER] shock_absorber_upper [<--- lc/REVOLUTE <--- chassis]
|-[REVOLUTE] spindle
|-[REVOLUTE] spindleAlt
|-[UJOINT] tie_rod [<--- lc/BALL <--- chassis]
|-[REVOLUTE] wheel
|-[BALL] upper_control_arm [---> lc/BALL ---> chassis]
# show model details
mb.displayModel()
LEGEND: <body number> <body name> <parent body> <hinge type> [prescribed subhinges] <U dofs/offset> [flex dofs/offset]
Body Parent Hinge Dofs
____ ______ _____ ____
1. chassis mb_MBVROOT_ LOCKED 0/0
2. lower_control_arm chassis REVOLUTE 1/0 P
3. shock_absorber_lower lower_control_arm REVOLUTE 1/1
4. shock_absorber_upper shock_absorber_lower SLIDER 1/2
5. spindle lower_control_arm REVOLUTE 1/3
6. spindleAlt spindle REVOLUTE 1/4
7. tie_rod spindleAlt UJOINT 2/5
8. wheel spindleAlt REVOLUTE 1/7
9. upper_control_arm spindle BALL 3/8
____
11
Nodes
---
spindleAlt_ik_ref_node <Node> body=spindleAlt
WheelContactNode <Node> body=wheel force=true
chassis_shock_absorber_constraint_node <ConstraintNode> body=chassis
chassis_tie_rod_constraint_node <ConstraintNode> body=chassis
chassis_upper_control_arm_constraint_node <ConstraintNode> body=chassis
shock_absorber_chassis_constraint_node <ConstraintNode> body=shock_absorber_upper
tie_rod_chassis_constraint_node <ConstraintNode> body=tie_rod
upper_control_arm_chassis_constraint_node <ConstraintNode> body=upper_control_arm
Enabled cutjoint loop constraints <3, residuals=11, error=18>
---------------
chassis_shock_absorber_constraint <REVOLUTE> source=chassis/chassis_shock_absorber_constraint_node target=shock_absorber_upper/shock_absorber_chassis_constraint_node
chassis_tie_rod_constraint <BALL> source=chassis/chassis_tie_rod_constraint_node target=tie_rod/tie_rod_chassis_constraint_node
chassis_upper_control_arm_constraint <BALL> source=upper_control_arm/upper_control_arm_chassis_constraint_node target=chassis/chassis_upper_control_arm_constraint_node
Setup Constraint Kinematics#
To use constraint kinematics, we use Karana.Dynamics.Multibody.cks() to get an instance of Karana.Dynamics.ConstraintKinematicsSolver. Then, Karana.Dynamics.ConstraintKinematicsSolver.solveQ() can solve for the coordinates to satisfy loop constraints and return the residual error.
solver = mb.cks()
error = solver.solveQ()
assert error < 1e-10
Setup the kdFlex Scene#
Next we setup kdflex’s graphics by calling the setupGraphics helper method on the multibody. This method takes care of setting up the graphics environment. We use Karana.Dynamics.Multibody.createStickParts() to add automatically add visual geometries.
See Visualization and Scene Layer for more information relating to this section.
# setup graphics and add visual stick parts
cleanup_graphics, web_scene = mb.setupGraphics(port=0, axes=0.5)
mb.createStickParts()
Run the Simulation With Loop Constraints#
We can now articulate through the lower control arm’s subhinge and demonstrate how loop constraints are enforced on the subhinge.
# grab the lower arm subhinge and enforce the loop constraints in it
lca = mb.getBody("lower_control_arm")
lca_shg = lca.parentHinge().subhinge(0)
mb.articulateSubhinge(lca_shg, 0)
Run the Simulation Without Constraints#
Compare this to when we articulate the bodies without enforcing loop constraints.
# this will not enforce the loop constraints
mb.articulateBodies()
Clean Up the Simulation#
Finally, we cleanup by deleting local variables, discarding our containers, multibodies, and cleaning up the scene.
# Cleanup
def cleanup():
"""Cleanup the simulation."""
import gc
global web_scene, solver, lca, lca_shg
del solver, lca, lca_shg, web_scene
gc.collect()
cleanup_graphics()
kc.discard(mb)
kc.discard(fc)
atexit.register(cleanup)
<function __main__.cleanup()>
Summary#
By importing a model with built-in constraints, we can setup a ConstraintKinematicsSolver to enforce these constraints. Then, we compare the outputs from running the simulation with and without constraints.
Further Readings#
Load a mars 2020 rover urdf
Load a robotic arm urdf
Create and use constraints in a slider-crank model
Drive an ATRVjr rover