{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "406b8c77-d398-4b67-b8d3-ee1ee8402f31",
   "metadata": {},
   "source": [
    "# 4-bar system simulation\n",
    "This notebook builds upon the system-level simulation showcased in the [system-level notebook](../example_system_level/notebook.ipynb). If you are not already familiar with the concepts described in that notebook, please go through it before this one."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2e253605",
   "metadata": {},
   "source": [
    "<img src=../resources/nb_images/4_bar.png align=center width=300>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "22ae75ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import Karana.Dynamics as kd\n",
    "import Karana.Dynamics.SOADyn_types as kdt\n",
    "from Karana.KUtils.Sim import SimDS\n",
    "import Karana.Models as kmdl\n",
    "import Karana.Integrators as ki\n",
    "import Karana.KUtils as ku\n",
    "import Karana.Core as kc\n",
    "import atexit\n",
    "import Karana.Math as km\n",
    "from Karana.Math.Kquantities import ureg\n",
    "from pathlib import Path\n",
    "import runpy\n",
    "from tempfile import NamedTemporaryFile"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfd3d9dc",
   "metadata": {},
   "source": [
    "Create the same serial chain simulation and populate the multibody using a DataStruct (DS) file just as before."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "68fd102c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[WebUI] Web server is running on port 36019\n",
      "You may be able to connect in your browser at:\n",
      "\t\u001b[1mhttp://newton:36019\u001b[0m\n",
      "[WebUI] Karana Viewer is running on port 29534\n",
      "Open this URL to connect:\n",
      "\t\u001b[1mhttp://newton:29534\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "        <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"http://newton:29534\">\n",
       "            <b>Click to open Karana Viewer in a new tab</b>\n",
       "        </a>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "sim_ds_path = Path(\"..\") / \"resources\" / \"4_bar\" / \"4_bar.h5\"\n",
    "sim = SimDS.fromFile(sim_ds_path).toSim()\n",
    "sim.mb.resetData()\n",
    "sim.mb.getScene().update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d674f2f9-bf30-4409-b11e-2a3b3610c3bc",
   "metadata": {},
   "source": [
    "While you can examine the new model in the gui, we also do so via the command line below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "3c76b625-74ad-45dc-9db4-bd78fc5c0c16",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "LEGEND: <body number> <body name> <parent body> <hinge type> [prescribed subhinges] <U dofs/offset> [flex dofs/offset]\n",
      "\n",
      "      Body  Parent                 Hinge    Dofs          \n",
      "      ____  ______                 _____    ____          \n",
      "   1. bd1   multibody_MBVROOT_    REVOLUTE  1/0           \n",
      "   2. bd2   bd1                   REVOLUTE  1/1           \n",
      "   3. bd3   bd2                   REVOLUTE  1/2           \n",
      "                                            ____          \n",
      "                                            3             \n",
      "\n",
      " Nodes\n",
      " ---\n",
      " cn <ConstraintNode>  body=bd3\n",
      "\n",
      " Enabled cutjoint loop constraints <1, residuals=5, error=6>\n",
      " ---------------\n",
      " constraint <REVOLUTE>  source=bd3/cn   target=ROOT/multibody_MBVROOT_\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.displayModel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "19a66d3d-1382-4053-b5bd-c6ea79eb8b5e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "LEGEND: [<hinge type[/dofs]> <prescribed subhinges>] <body name>[/num embedded bodies > 0] <flex dofs > 0>\n",
      "|-multibody_MBVROOT_\n",
      "   |-[REVOLUTE] bd1\n",
      "      |-[REVOLUTE] bd2\n",
      "         |-[REVOLUTE] bd3    [---> lc/REVOLUTE ---> ROOT]\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.dumpTree()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ac0456b",
   "metadata": {},
   "source": [
    "## Initial state\n",
    "Similar to before, we'll use the Python API to set a legal initial position and velocity for the system that satisfies the constraints. We begin by assigning initial coordinate values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "bee1af4b-856f-4e32-b44c-0d0f9bf3bee2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.mb.getBody(\"bd1\").parentHinge().subhinge(0).setQ(0.25)\n",
    "sim.mb.getBody(\"bd1\").parentHinge().subhinge(0).setU(1.0)\n",
    "sim.mb.getBody(\"bd2\").parentHinge().subhinge(0).setQ(1.25)\n",
    "sim.mb.getBody(\"bd3\").parentHinge().subhinge(0).setQ(1.25)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "be979a79-f115-438d-ba91-3b783f096ead",
   "metadata": {},
   "source": [
    "Not surprisingly, these initial coordinate values do not satisfy the constraint as revealed by the following error checks which return non-zero error values."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "ad3ec458-720f-4b35-ac28-4f33aa2eb289",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-0.        , -2.75      , -0.        , -0.62315438, -0.        ,\n",
       "        0.13251775])"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.mb.poseError()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "68293aca-2fc6-4dad-ad43-52ae54bdb8de",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([ 0.        , -1.        ,  0.        ,  0.11534724,  0.        ,\n",
       "       -1.62655994])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.mb.velError()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e28927c-01b0-4b74-a486-04e2fcb6752d",
   "metadata": {},
   "source": [
    "To remedy this situation, we use the built in _constraint kinematics solver (CKS)_ to solve for coordinates and states that do satisfy the constraints."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "9b57541b-6bd5-4023-9f84-1916731b2b50",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert sim.mb.cks().solveQ() < 1e-8\n",
    "assert sim.mb.cks().solveU() < 1e-8\n",
    "sim.mb.getScene().viewAroundFrame(\n",
    "    sim.mb.virtualRoot(), np.array([0.0, 5.0, 0.0]), np.zeros(3), np.array([0.0, 0.0, 1.0])\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "20dd8c24",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Update the Integrator with these values\n",
    "sim.sp.ensureHealthy()\n",
    "sim.sp.hardReset()\n",
    "sim.sp.setState(sim.sp.assembleState())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f99f7e2",
   "metadata": {},
   "source": [
    "## Register a Timed Event\n",
    "\n",
    "To stop the simulation and print information every 0.1 seconds, we create a timed event and register it with our state propagator here. See [timed events](timed_events_sec) for more information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ee0a5819",
   "metadata": {},
   "outputs": [],
   "source": [
    "sp = sim.sp\n",
    "\n",
    "\n",
    "def fn(t):\n",
    "    \"\"\"Print out the time and state.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    t : float\n",
    "        The current time.\n",
    "    \"\"\"\n",
    "    print(f\"t = {float(sp.getTime()) / 1e9}s; x = {np.round(sp.getIntegrator().getX(), 6)}\")\n",
    "\n",
    "\n",
    "h = np.timedelta64(100, \"ms\")\n",
    "t = kd.TimedEvent(\"hop_size\", h, fn, False)\n",
    "t.period = h\n",
    "\n",
    "# register the timed event\n",
    "sim.sp.registerTimedEvent(t)\n",
    "del t"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3133f7d",
   "metadata": {},
   "source": [
    "## Add plots\n",
    "Use the `addPlot` helper method on the `sim` to add some plots to the GUI."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "9a779cce",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Dash app started at http://newton:45265\n"
     ]
    }
   ],
   "source": [
    "import platform\n",
    "\n",
    "if platform.system() != \"Darwin\":\n",
    "    # In the Jupyter notebook, the live plotting does not work on macOS due to multiprocessing issues.\n",
    "    # This works just fine from a regular Python script if guarded by if __name__ == \"__main__\"\n",
    "    sim.addPlots(\n",
    "        [\n",
    "            ku.SinglePlotData(\n",
    "                \"Energy\",\n",
    "                sim.sp.getVars().time,\n",
    "                [\n",
    "                    (\n",
    "                        \"Energy\",\n",
    "                        lambda: np.array(\n",
    "                            [\n",
    "                                kd.Algorithms.evalKineticEnergy(sim.mb)\n",
    "                                + kd.Algorithms.evalGravitationalPotentialEnergy(sim.mb)\n",
    "                            ]\n",
    "                        ),\n",
    "                        1,\n",
    "                    )\n",
    "                ],\n",
    "            )\n",
    "        ],\n",
    "        0.1,\n",
    "        title=\"Time domain plots\",\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dc7f01d",
   "metadata": {},
   "source": [
    "## Run the simulation\n",
    "Now, let's run the simulation. `advanceTo` will advance to the desired time."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "de32a7c2",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "t = 0.1s; x = [-0.295796  1.866593  1.275    -2.845796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.2s; x = [-0.270796  1.841593  1.3      -2.870796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.3s; x = [-0.245796  1.816593  1.325    -2.895796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.4s; x = [-0.220796  1.791593  1.35     -2.920796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.5s; x = [-0.195796  1.766593  1.375    -2.945796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.6s; x = [-0.170796  1.741593  1.4      -2.970796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.7s; x = [-0.145796  1.716593  1.425    -2.995796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.8s; x = [-0.120796  1.691593  1.45     -3.020796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 0.9s; x = [-0.095796  1.666593  1.475    -3.045796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.0s; x = [-0.070796  1.641593  1.5      -3.070796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.1s; x = [-0.045796  1.616593  1.525    -3.095796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.2s; x = [-0.020796  1.591593  1.55     -3.120796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.3s; x = [ 0.004204  1.566593  1.575    -3.145796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.4s; x = [ 0.029204  1.541593  1.6      -3.170796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.5s; x = [ 0.054204  1.516593  1.625    -3.195796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.6s; x = [ 0.079204  1.491593  1.65     -3.220796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.7s; x = [ 0.104204  1.466593  1.675    -3.245796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.8s; x = [ 0.129204  1.441593  1.7      -3.270796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 1.9s; x = [ 0.154204  1.416593  1.725    -3.295796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.0s; x = [ 0.179204  1.391593  1.75     -3.320796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.1s; x = [ 0.204204  1.366593  1.775    -3.345796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.2s; x = [ 0.229204  1.341593  1.8      -3.370796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.3s; x = [ 0.254204  1.316593  1.825    -3.395796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.4s; x = [ 0.279204  1.291593  1.85     -3.420796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.5s; x = [ 0.304204  1.266593  1.875    -3.445796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.6s; x = [ 0.329204  1.241593  1.9      -3.470796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.7s; x = [ 0.354204  1.216593  1.925    -3.495796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.8s; x = [ 0.379204  1.191593  1.95     -3.520796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 2.9s; x = [ 0.404204  1.166593  1.975    -3.545796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.0s; x = [ 0.429204  1.141593  2.       -3.570796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.1s; x = [ 0.454204  1.116593  2.025    -3.595796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.2s; x = [ 0.479204  1.091593  2.05     -3.620796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.3s; x = [ 0.504204  1.066593  2.075    -3.645796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.4s; x = [ 0.529204  1.041593  2.1      -3.670796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.5s; x = [ 0.554204  1.016593  2.125    -3.695796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.6s; x = [ 0.579204  0.991593  2.15     -3.720796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.7s; x = [ 0.604204  0.966593  2.175    -3.745796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.8s; x = [ 0.629204  0.941593  2.2      -3.770796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 3.9s; x = [ 0.654204  0.916593  2.225    -3.795796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.0s; x = [ 0.679204  0.891593  2.25     -3.820796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.1s; x = [ 0.704204  0.866593  2.275    -3.845796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.2s; x = [ 0.729204  0.841593  2.3      -3.870796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.3s; x = [ 0.754204  0.816593  2.325    -3.895796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.4s; x = [ 0.779204  0.791593  2.35     -3.920796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.5s; x = [ 0.804204  0.766593  2.375    -3.945796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.6s; x = [ 0.829204  0.741593  2.4      -3.970796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.7s; x = [ 0.854204  0.716593  2.425    -3.995796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.8s; x = [ 0.879204  0.691593  2.45     -4.020796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 4.9s; x = [ 0.904204  0.666593  2.475    -4.045796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.0s; x = [ 0.929204  0.641593  2.5      -4.070796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.1s; x = [ 0.954204  0.616593  2.525    -4.095796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.2s; x = [ 0.979204  0.591593  2.55     -4.120796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.3s; x = [ 1.004204  0.566593  2.575    -4.145796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.4s; x = [ 1.029204  0.541593  2.6      -4.170796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.5s; x = [ 1.054204  0.516593  2.625    -4.195796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.6s; x = [ 1.079204  0.491593  2.65     -4.220796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.7s; x = [ 1.104204  0.466593  2.675    -4.245796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.8s; x = [ 1.129204  0.441593  2.7      -4.270796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 5.9s; x = [ 1.154204  0.416593  2.725    -4.295796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.0s; x = [ 1.179204  0.391593  2.75     -4.320796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.1s; x = [ 1.204204  0.366593  2.775    -4.345796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.2s; x = [ 1.229204  0.341593  2.8      -4.370796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.3s; x = [ 1.254204  0.316593  2.825    -4.395796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.4s; x = [ 1.279204  0.291593  2.85     -4.420796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.5s; x = [ 1.304204  0.266593  2.875    -4.445796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.6s; x = [ 1.329204  0.241593  2.9      -4.470796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.7s; x = [ 1.354204  0.216593  2.925    -4.495796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.8s; x = [ 1.379204  0.191593  2.95     -4.520796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 6.9s; x = [ 1.404204  0.166593  2.975    -4.545796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.0s; x = [ 1.429204  0.141593  3.       -4.570796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.1s; x = [ 1.454204  0.116593  3.025    -4.595796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.2s; x = [ 1.479204  0.091593  3.05     -4.620796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.3s; x = [ 1.504204  0.066593  3.075    -4.645796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.4s; x = [ 1.529204  0.041593  3.1      -4.670796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.5s; x = [ 1.554204  0.016593  3.125    -4.695796  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.6s; x = [ 1.579204 -0.008407  3.15     -4.720796  0.249997 -0.249993  0.249997\n",
      " -0.250002]\n",
      "t = 7.7s; x = [ 1.604203 -0.033407  3.175    -4.745797  0.25     -0.25      0.25\n",
      " -0.25    ]\n",
      "t = 7.8s; x = [ 1.629203 -0.058406  3.199999 -4.770797  0.250005 -0.250013  0.250005\n",
      " -0.249997]\n",
      "t = 7.9s; x = [ 1.654203 -0.083406  3.224999 -4.795797  0.250005 -0.250013  0.250005\n",
      " -0.249997]\n",
      "t = 8.0s; x = [ 1.679203 -0.108406  3.25     -4.820797  0.250004 -0.250009  0.250004\n",
      " -0.249998]\n",
      "t = 8.1s; x = [ 1.704204 -0.133407  3.275    -4.845796  0.250003 -0.250007  0.250003\n",
      " -0.249998]\n",
      "t = 8.2s; x = [ 1.729204 -0.158408  3.3      -4.870796  0.250002 -0.250005  0.250002\n",
      " -0.249999]\n",
      "t = 8.3s; x = [ 1.754204 -0.183408  3.325    -4.895796  0.250001 -0.250004  0.250001\n",
      " -0.249999]\n",
      "t = 8.4s; x = [ 1.779204 -0.208408  3.35     -4.920796  0.250001 -0.250003  0.250001\n",
      " -0.249999]\n",
      "t = 8.5s; x = [ 1.804204 -0.233408  3.375    -4.945796  0.250001 -0.250003  0.250001\n",
      " -0.249999]\n",
      "t = 8.6s; x = [ 1.829204 -0.258409  3.400001 -4.970796  0.250001 -0.250002  0.250001\n",
      " -0.25    ]\n",
      "t = 8.7s; x = [ 1.854204 -0.283409  3.425001 -4.995796  0.250001 -0.250002  0.250001\n",
      " -0.25    ]\n",
      "t = 8.8s; x = [ 1.879204 -0.308409  3.450001 -5.020796  0.250001 -0.250002  0.250001\n",
      " -0.25    ]\n",
      "t = 8.9s; x = [ 1.904204 -0.333409  3.475001 -5.045796  0.250001 -0.250001  0.250001\n",
      " -0.25    ]\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "t = 9.0s; x = [ 1.929204 -0.358409  3.500001 -5.070796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.1s; x = [ 1.954204 -0.383409  3.525001 -5.095796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.2s; x = [ 1.979204 -0.40841   3.550001 -5.120796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.3s; x = [ 2.004205 -0.43341   3.575001 -5.145796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.4s; x = [ 2.029205 -0.45841   3.600001 -5.170796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.5s; x = [ 2.054205 -0.48341   3.625001 -5.195796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.6s; x = [ 2.079205 -0.50841   3.650001 -5.220796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.7s; x = [ 2.104205 -0.53341   3.675001 -5.245796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.8s; x = [ 2.129205 -0.55841   3.700001 -5.270796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 9.9s; x = [ 2.154205 -0.58341   3.725001 -5.295796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n",
      "t = 10.0s; x = [ 2.179205 -0.60841   3.750001 -5.320796  0.25     -0.250001  0.250001\n",
      " -0.25    ]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<SpStatusEnum.REACHED_END_TIME: 1>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.sp.advanceTo(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80b28389",
   "metadata": {},
   "source": [
    "## Python script\n",
    "Let's use the simulation we already have to create a Python script. This will dump just the simulation information."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "a08ea98b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[warning] Model auto_time_display (TimeDisplay) does not have a \"toDS\" method, so it cannot be added to the StatePropagatorDS.\n",
      "[warning] Model data_plotter (DataPlotter) does not have a \"toDS\" method, so it cannot be added to the StatePropagatorDS.\n",
      "1 file reformatted\n"
     ]
    }
   ],
   "source": [
    "tf = NamedTemporaryFile(suffix=\".py\")\n",
    "sim.toDS().toFile(tf.name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ad920f2",
   "metadata": {},
   "source": [
    "This call will show the file in the Jupyter notebook"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "7734eca9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "```python\n",
       "# This is an auto-generated file for the Sim\n",
       "# This file was generated on 2026-06-30 10:02:56.207202\n",
       "import atexit\n",
       "import Karana.Dynamics as kd\n",
       "import Karana.Models as kmdl\n",
       "from typing import cast\n",
       "from Karana.Math.Kquantities import ureg\n",
       "import Karana.Scene as ks\n",
       "import numpy as np\n",
       "import Karana.Integrators as ki\n",
       "from Karana.KUtils.Sim import Sim\n",
       "import Karana.Math as km\n",
       "\n",
       "\n",
       "def populateMultibodyAndStatePropagator(mb: kd.Multibody, sp: kd.StatePropagator):\n",
       "    \"\"\"Populate and configure the provided Multibody and StatePropagator.\n",
       "\n",
       "    Parameters\n",
       "    ----------\n",
       "    mb : kd.Multibody\n",
       "        The Multibody to populate with physical bodies.\n",
       "    sp : kd.StatePropagator\n",
       "        The StatePropagator to populate and configure.\n",
       "    \"\"\"\n",
       "    # This creates, adds, and connects physical bodies via hinges,\n",
       "    # also referred to as joints, to the provided Multibody instance.\n",
       "    # Often, the input `Multibody' instance is empty and contains just a\n",
       "    # virtual root body.\n",
       "    #\n",
       "    # The code below consists of repeated blocks - one per body. Each block\n",
       "    # defines the mass properties for the body, the parent hinge type and\n",
       "    # location on the body and its parent body, and the mesh geometries attached.\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Look up the unique virtual root body for the multibody system that\n",
       "    # will serve as an anchor for the new physical bodies.\n",
       "    vroot = mb.virtualRoot()\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Create 'bd1' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Start defining meshes for 'bd1' body\"\n",
       "\n",
       "    # Create one or more meshes attached to the 'bd1' body. Each mesh\n",
       "    # consists of the shape and color/texture definition for its visual appearance.\n",
       "    # The shapes can be parameterized shapes (e.g., spheres, boxes, etc., or be\n",
       "    # defined in external files such as '.glb', '.obj', etc. formats.\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd1_beginning' mesh parameters for 'bd1' body\n",
       "    # Define the part shape (geometry)\n",
       "    cylinder = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_info.roughness = 1.0\n",
       "    physical_material = ks.PhysicalMaterial(physical_material_info)\n",
       "    bd1_beginning = ks.ScenePartSpec(\n",
       "        name=\"bd1_beginning\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.5]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=cylinder,\n",
       "        material=physical_material,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd1_body' mesh parameters for 'bd1' body\n",
       "    # Define the part shape (geometry)\n",
       "    box = ks.BoxGeometry(0.05000000074505806, 0.05000000074505806, 1.0)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_1_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_1_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_1_info.roughness = 1.0\n",
       "    physical_material_1 = ks.PhysicalMaterial(physical_material_1_info)\n",
       "    bd1_body = ks.ScenePartSpec(\n",
       "        name=\"bd1_body\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=box,\n",
       "        material=physical_material_1,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd1_pivot' mesh parameters for 'bd1' body\n",
       "    # Define the part shape (geometry)\n",
       "    cylinder_1 = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_2_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_2_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_2_info.roughness = 1.0\n",
       "    physical_material_2 = ks.PhysicalMaterial(physical_material_2_info)\n",
       "    bd1_pivot = ks.ScenePartSpec(\n",
       "        name=\"bd1_pivot\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, -0.5]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=cylinder_1,\n",
       "        material=physical_material_2,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # End defining meshes for 'bd1' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Assemble overall parameters needed to define the 'bd1' body\n",
       "    # including mass and kinematics properties.\n",
       "    bd1_params = kd.PhysicalBodyParams(\n",
       "        # 'bd1' mass properties\n",
       "        spI=km.SpatialInertia(\n",
       "            2.0 * ureg.kilogram,\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "            np.array([[3.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 1.0]])\n",
       "            * ureg.kilogram\n",
       "            * ureg.meter**2,\n",
       "        ),\n",
       "        # Define the location and orientation of the 'bd1' parent hinge\n",
       "        # relative to the 'bd1' body frame\n",
       "        body_to_joint_transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([-0.0, -0.0, 0.5]) * ureg.meter,\n",
       "        ),\n",
       "        # Define the location and orientation of the 'bd1' parent hinge\n",
       "        # relative to the parent body's frame\n",
       "        inb_to_joint_transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([1.0, 0.0, 0.0]) * ureg.meter,\n",
       "        ),\n",
       "        # define the 'bd1' parent hinge type and parameters\n",
       "        hinge_params=kd.PhysicalHingeParams(\n",
       "            hinge_type=kd.HingeType.REVOLUTE,\n",
       "            subhinge_params=[\n",
       "                kd.PinSubhingeParams(\n",
       "                    unit_axis=np.array([0.0, 1.0, 0.0]),\n",
       "                    prescribed=False,\n",
       "                ),\n",
       "            ],\n",
       "        ),\n",
       "        # Add 'bd1' scene parts defined above\n",
       "        scene_part_specs=[bd1_beginning, bd1_body, bd1_pivot],\n",
       "    )\n",
       "\n",
       "    # Create the 'bd1' body\n",
       "    bd1 = kd.PhysicalBody(\"bd1\", mb)\n",
       "\n",
       "    # Connect the 'bd1' body to its parent via a hinge\n",
       "    kd.PhysicalHinge(vroot, bd1, kd.HingeType.REVOLUTE)\n",
       "\n",
       "    # Set the parameters for the 'bd1' body\n",
       "    bd1.setParams(bd1_params)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Create 'bd2' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Start defining meshes for 'bd2' body\"\n",
       "\n",
       "    # Create one or more meshes attached to the 'bd2' body. Each mesh\n",
       "    # consists of the shape and color/texture definition for its visual appearance.\n",
       "    # The shapes can be parameterized shapes (e.g., spheres, boxes, etc., or be\n",
       "    # defined in external files such as '.glb', '.obj', etc. formats.\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd1_pivot' mesh parameters for 'bd2' body\n",
       "    # Define the part shape (geometry)\n",
       "    cylinder_2 = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_3_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_3_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_3_info.roughness = 1.0\n",
       "    physical_material_3 = ks.PhysicalMaterial(physical_material_3_info)\n",
       "    bd1_pivot_1 = ks.ScenePartSpec(\n",
       "        name=\"bd1_pivot\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=cylinder_2,\n",
       "        material=physical_material_3,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd2_body' mesh parameters for 'bd2' body\n",
       "    # Define the part shape (geometry)\n",
       "    box_1 = ks.BoxGeometry(0.05000000074505806, 0.05000000074505806, 1.0)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_4_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_4_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_4_info.roughness = 1.0\n",
       "    physical_material_4 = ks.PhysicalMaterial(physical_material_4_info)\n",
       "    bd2_body = ks.ScenePartSpec(\n",
       "        name=\"bd2_body\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.5]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=box_1,\n",
       "        material=physical_material_4,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # End defining meshes for 'bd2' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Assemble overall parameters needed to define the 'bd2' body\n",
       "    # including mass and kinematics properties.\n",
       "    bd2_params = kd.PhysicalBodyParams(\n",
       "        # 'bd2' mass properties\n",
       "        spI=km.SpatialInertia(\n",
       "            2.0 * ureg.kilogram,\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "            np.array([[3.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 1.0]])\n",
       "            * ureg.kilogram\n",
       "            * ureg.meter**2,\n",
       "        ),\n",
       "        # Define the location and orientation of the 'bd2' parent hinge\n",
       "        # relative to the 'bd2' body frame\n",
       "        body_to_joint_transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([-0.0, -0.0, 1.0]) * ureg.meter,\n",
       "        ),\n",
       "        # Define the location and orientation of the 'bd2' parent hinge\n",
       "        # relative to the parent body's frame\n",
       "        inb_to_joint_transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, -0.5]) * ureg.meter,\n",
       "        ),\n",
       "        # define the 'bd2' parent hinge type and parameters\n",
       "        hinge_params=kd.PhysicalHingeParams(\n",
       "            hinge_type=kd.HingeType.REVOLUTE,\n",
       "            subhinge_params=[\n",
       "                kd.PinSubhingeParams(\n",
       "                    unit_axis=np.array([0.0, 1.0, 0.0]),\n",
       "                    prescribed=False,\n",
       "                ),\n",
       "            ],\n",
       "        ),\n",
       "        # Add 'bd2' scene parts defined above\n",
       "        scene_part_specs=[bd1_pivot_1, bd2_body],\n",
       "    )\n",
       "\n",
       "    # Create the 'bd2' body\n",
       "    bd2 = kd.PhysicalBody(\"bd2\", mb)\n",
       "\n",
       "    # Connect the 'bd2' body to its parent via a hinge\n",
       "    kd.PhysicalHinge(bd1, bd2, kd.HingeType.REVOLUTE)\n",
       "\n",
       "    # Set the parameters for the 'bd2' body\n",
       "    bd2.setParams(bd2_params)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Create 'bd3' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Start defining meshes for 'bd3' body\"\n",
       "\n",
       "    # Create one or more meshes attached to the 'bd3' body. Each mesh\n",
       "    # consists of the shape and color/texture definition for its visual appearance.\n",
       "    # The shapes can be parameterized shapes (e.g., spheres, boxes, etc., or be\n",
       "    # defined in external files such as '.glb', '.obj', etc. formats.\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd1_pivot' mesh parameters for 'bd3' body\n",
       "    # Define the part shape (geometry)\n",
       "    cylinder_3 = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_5_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_5_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_5_info.roughness = 1.0\n",
       "    physical_material_5 = ks.PhysicalMaterial(physical_material_5_info)\n",
       "    bd1_pivot_2 = ks.ScenePartSpec(\n",
       "        name=\"bd1_pivot\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=cylinder_3,\n",
       "        material=physical_material_5,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd2_body' mesh parameters for 'bd3' body\n",
       "    # Define the part shape (geometry)\n",
       "    box_2 = ks.BoxGeometry(0.05000000074505806, 0.05000000074505806, 1.0)\n",
       "\n",
       "    # Define the part color/texture material\n",
       "    physical_material_6_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_6_info.color = ks.Color.fromRGBA(\n",
       "        0.6980392336845398, 0.13333334028720856, 0.13333334028720856, 1.0\n",
       "    )\n",
       "    physical_material_6_info.roughness = 1.0\n",
       "    physical_material_6 = ks.PhysicalMaterial(physical_material_6_info)\n",
       "    bd2_body_1 = ks.ScenePartSpec(\n",
       "        name=\"bd2_body\",\n",
       "        transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.5]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=box_2,\n",
       "        material=physical_material_6,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # End defining meshes for 'bd3' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Assemble overall parameters needed to define the 'bd3' body\n",
       "    # including mass and kinematics properties.\n",
       "    bd3_params = kd.PhysicalBodyParams(\n",
       "        # 'bd3' mass properties\n",
       "        spI=km.SpatialInertia(\n",
       "            2.0 * ureg.kilogram,\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "            np.array([[3.0, 0.0, 0.0], [0.0, 2.0, 0.0], [0.0, 0.0, 1.0]])\n",
       "            * ureg.kilogram\n",
       "            * ureg.meter**2,\n",
       "        ),\n",
       "        # Define the location and orientation of the 'bd3' parent hinge\n",
       "        # relative to the 'bd3' body frame\n",
       "        body_to_joint_transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([-0.0, -0.0, 1.0]) * ureg.meter,\n",
       "        ),\n",
       "        # Define the location and orientation of the 'bd3' parent hinge\n",
       "        # relative to the parent body's frame\n",
       "        inb_to_joint_transform=km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "        ),\n",
       "        # define the 'bd3' parent hinge type and parameters\n",
       "        hinge_params=kd.PhysicalHingeParams(\n",
       "            hinge_type=kd.HingeType.REVOLUTE,\n",
       "            subhinge_params=[\n",
       "                kd.PinSubhingeParams(\n",
       "                    unit_axis=np.array([0.0, 1.0, 0.0]),\n",
       "                    prescribed=False,\n",
       "                ),\n",
       "            ],\n",
       "        ),\n",
       "        # Add 'bd3' scene parts defined above\n",
       "        scene_part_specs=[bd1_pivot_2, bd2_body_1],\n",
       "    )\n",
       "\n",
       "    # Create the 'bd3' body\n",
       "    bd3 = kd.PhysicalBody(\"bd3\", mb)\n",
       "\n",
       "    # Connect the 'bd3' body to its parent via a hinge\n",
       "    kd.PhysicalHinge(bd2, bd3, kd.HingeType.REVOLUTE)\n",
       "\n",
       "    # Set the parameters for the 'bd3' body\n",
       "    bd3.setParams(bd3_params)\n",
       "\n",
       "    # Constraint node cn\n",
       "    cn = kd.ConstraintNode.lookupOrCreate(\"cn\", bd3)\n",
       "    cn.setBodyToNodeTransform(\n",
       "        km.HomTran(\n",
       "            km.UnitQuaternion(np.array([0.0, 0.0, 0.0, 1.0])),\n",
       "            np.array([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "        )\n",
       "    )\n",
       "\n",
       "    # constraint kd.LoopConstraintCutJoint\n",
       "    lc = kd.LoopConstraintCutJoint(\n",
       "        \"constraint\",\n",
       "        mb,\n",
       "        cn.frameToFrame(vroot),\n",
       "        kd.HingeType.REVOLUTE,\n",
       "    )\n",
       "    sh = cast(kd.PinSubhinge, lc.hinge().subhinge(0))\n",
       "    sh.setUnitAxis(np.array([0.0, 1.0, 0.0]))\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Finished physical body additions\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Finalize the multibody for the added bodies\n",
       "    mb.ensureHealthy()\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Populate and configure the StatePropagator. This will involve\n",
       "    # defining the type of solver to use for the multibody equations of\n",
       "    # motion, adding an integrator to use for state propagation,\n",
       "    # addition of KModels for interacting with the multibody dynamics,\n",
       "    # and timed events for execution during time advancement.\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Set the StatePropagator's solver type. 'FORWARD_DYNAMICS' is the\n",
       "    # typical choice for setting up the propagator to solve the\n",
       "    # equations of motion for time domain simulations\n",
       "    sp.setSolverType(kd.MMSolverType.FORWARD_DYNAMICS)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define integrator options\n",
       "    cvode_opts = ki.CVodeIntegratorOptions()\n",
       "    cvode_opts.rtol = 0.0001\n",
       "    cvode_opts.atol = 1e-08\n",
       "    cvode_opts.max_num_steps = 1000000\n",
       "    cvode_opts.min_step_size = 1e-12\n",
       "    cvode_opts.anderson_damping = 1.0\n",
       "    cvode_opts.anderson_length = 0\n",
       "    cvode_opts.max_nl_iters = 3\n",
       "\n",
       "    # Set up the integrator\n",
       "    sp.setIntegrator(ki.IntegratorType.CVODE, cvode_opts)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Set the StatePropagator options\n",
       "\n",
       "    # Call with True to make an extra dynamics derivative call at the\n",
       "    # end of a simulation step to update the acceleration values.\n",
       "    sp.setUpdateStateDerivativesHopEnd(False)\n",
       "\n",
       "    # Set a limit on the maximum integration time step size.\n",
       "    # A value of 0.0 means there is no maximum limit.\n",
       "    sp.setMaxStepSize(0.0)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Add KModels. KModels are parameterized models for\n",
       "    # component actuator, sensors, environmental, etc. that interact\n",
       "    # with the dynamics.\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Add the sync_real_time KModel\n",
       "    sync_real_time = kmdl.SyncRealTime(\"sync_real_time\", sp, 1.0)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Add the update_proxy_scene KModel\n",
       "    multibody_scene = mb.getScene()\n",
       "    if multibody_scene is not None:\n",
       "        update_proxy_scene = kmdl.UpdateProxyScene(\"update_proxy_scene\", sp, multibody_scene)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Add the gil_release KModel\n",
       "    gil_release = kmdl.GilRelease(\"gil_release\", sp)\n",
       "\n",
       "    # Finalize the StatePropagator for the updated multibody system, the\n",
       "    # KModels and the integrator\n",
       "    sp.hardReset()\n",
       "    sp.ensureHealthy()\n",
       "\n",
       "    # End setting up StatePropagator and KModels\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "\n",
       "if __name__ == \"__main__\":\n",
       "    # Create a simulation\n",
       "    sim = Sim()\n",
       "\n",
       "    # Populate the Multibody and StatePropagator for the Sim\n",
       "    populateMultibodyAndStatePropagator(sim.mb, sim.sp)\n",
       "\n",
       "    # Code to cleanup upon exit\n",
       "    def cleanup():\n",
       "        global sim\n",
       "        del sim\n",
       "\n",
       "    atexit.register(cleanup)\n",
       "\n",
       "```"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from IPython.display import Markdown\n",
    "from pathlib import Path\n",
    "\n",
    "Markdown(f\"```python\\n{Path(tf.name).read_text()}\\n```\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "073230ce",
   "metadata": {},
   "source": [
    "Above, you'll see the multibody code similar to the [time domain notebook](../example_time_domain/notebook.ipynb), as well as code to add the `Gravity` `KModel`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "6a7e04fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "del sim\n",
    "globals().update(runpy.run_path(tf.name, run_name=\"__main__\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b60265b",
   "metadata": {},
   "source": [
    "Let's bring up the GUI again just to show we captured everything"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "85b280dd",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[WebUI] Web server is running on port 43427\n",
      "You may be able to connect in your browser at:\n",
      "\t\u001b[1mhttp://newton:43427\u001b[0m\n",
      "[WebUI] Karana Viewer is running on port 29534\n",
      "Open this URL to connect:\n",
      "\t\u001b[1mhttp://newton:29534\u001b[0m\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "\n",
       "        <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"http://newton:29534\">\n",
       "            <b>Click to open Karana Viewer in a new tab</b>\n",
       "        </a>\n",
       "        "
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Create the GUI\n",
    "sim.setupGui()\n",
    "\n",
    "# Update the camera view\n",
    "sim.mb.resetData()\n",
    "sim.mb.getScene().viewAroundFrame(\n",
    "    sim.mb.virtualRoot(), np.array([0.0, 5.0, 0.0]), np.zeros(3), np.array([0.0, 0.0, 1.0])\n",
    ")\n",
    "\n",
    "# Redraw the scene\n",
    "sim.mb.getScene().update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "550b3b2e",
   "metadata": {},
   "source": [
    "## Cleanup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "dbf76e3d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.cleanup()>"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def cleanup():\n",
    "    \"\"\"Clean up the simulation.\"\"\"\n",
    "    global sim, sp\n",
    "    del sim, sp\n",
    "\n",
    "\n",
    "atexit.register(cleanup)"
   ]
  }
 ],
 "metadata": {
  "docs": {
   "tags": [
    "dynamics",
    "cutjoint",
    "constraints",
    "TA model",
    "beginner",
    "time domain",
    "planar 4-bar"
   ]
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
