{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "2e253605",
   "metadata": {},
   "source": [
    "# Serial chain\n",
    "\n",
    "This is an introductory example of creating a serial chain system along with starting up the GUI for introspection."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c9d789a",
   "metadata": {},
   "source": [
    "<img src=../resources/nb_images/serial_chain.png width=400>"
   ]
  },
  {
   "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 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 a simulation and populate the multibody using a DataStruct (DS) model file. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7cebe712-47c0-4c92-9312-6bdb8a5e5f37",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[WebUI] Web server is running on port 44553\n",
      "You may be able to connect in your browser at:\n",
      "\t\u001b[1mhttp://newton:44553\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\" / \"2_link_pendulum\" / \"2_link_pendulum_no_gravity.h5\"\n",
    "sim = SimDS.fromFile(sim_ds_path).toSim()\n",
    "\n",
    "# reset internal coordinates and state to zero values\n",
    "sim.mb.resetData()\n",
    "\n",
    "# position the 3D viewing pose\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",
    "sim.mb.getScene().update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f720711b-ccc3-4932-b9c5-e36e9cb2479d",
   "metadata": {},
   "source": [
    "A `Karana Viewer` gui should have started up. You can use the printed URL to open it up in a browser tab as well to explore the created model. The gui includes panes to view the model in 3D graphics, a pane to see the bodies and their connection topology, a pane with a tree view of the bodies. You can use the mouse to select objects from any of these panes, and more information and options for the selected object will be displayed in the _Info_ pane in the bottom right."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3eee0da8-3bf7-47db-96ff-6f442d5e2327",
   "metadata": {},
   "source": [
    "You can also examine the model structure from the command line as follows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "0e7c4972-598f-482f-bec4-d007b540f855",
   "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",
      "                                            ____          \n",
      "                                            2             \n",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.displayModel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "466df05f-e771-4a9f-b41c-e3f5ad6d30d6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " Nodes\n",
      " ---\n",
      "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",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.dumpTree()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e83f05af",
   "metadata": {},
   "source": [
    "## Set joint values\n",
    "Now use the Python API to set the joint values to 0.5 and 1 radians."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "112e755e",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Set the first body's hinge to 0.5 radians\n",
    "bd1 = sim.mb.getBody(\"bd1\")\n",
    "bd1.parentHinge().subhinge(0).setQ(0.5)\n",
    "\n",
    "# Set the second body's hinge to 1.0 radians\n",
    "sim.mb.getBody(\"bd2\").parentHinge().subhinge(0).setQ(1.0)\n",
    "\n",
    "# Update the scene to show the new positions in the GUI\n",
    "sim.mb.getScene().update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4282061a-0f5f-4ec4-974b-a23aae77139b",
   "metadata": {},
   "source": [
    "## Inspection\n",
    "A graph visualization of the all the Frames in the simulation thus far is shown below.\n",
    "\n",
    "<img src=../resources/nb_images/serial_chain_frames.png align=\"center\" width=400>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9cfee851-5e17-40f6-8cd0-d7de9d40aefc",
   "metadata": {},
   "source": [
    "Lets examine the structure of the multibody system that has been created."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3765e7e0-c440-4908-9640-8654fced89e8",
   "metadata": {},
   "source": [
    "Let's use the API to get the location of 'bd2' with respect to the inertial frame by making use of the Frames layer."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "f9030d9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-0.2397127693021015 0.0 -0.4387912809451864] meter\n"
     ]
    }
   ],
   "source": [
    "f_to_f = sim.mb.virtualRoot().frameToFrame(bd1)\n",
    "print(f_to_f.relTransform().getTranslation())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58678fbd",
   "metadata": {},
   "source": [
    "If we change bd1's subhinge value, then the location of bd2 will change."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1e3a7701",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-0.0 -0.0 -0.5] meter\n"
     ]
    }
   ],
   "source": [
    "bd1.parentHinge().subhinge(0).setQ(0.0)\n",
    "print(f_to_f.relTransform().getTranslation())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13ea9f94",
   "metadata": {},
   "source": [
    "Now, let's use the API to get the system's center of mass."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "7eb71f4a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-0.42073549240394825 0.0 -1.0201511529340699] meter\n"
     ]
    }
   ],
   "source": [
    "f_to_f = sim.mb.virtualRoot().frameToFrame(sim.mb.cmFrame())\n",
    "print(f_to_f.relTransform().getTranslation())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "09186d74",
   "metadata": {},
   "source": [
    "If we change the mass properties of one of the bodies, then this should change the cm location."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "1505bfa5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[-0.7012258206732471 0.0 -1.3669185882234498] meter\n"
     ]
    }
   ],
   "source": [
    "si_orig = bd1.getSpatialInertia()\n",
    "bd1.parentHinge().subhinge(0).setQ(0.0)\n",
    "sim.mb.getBody(\"bd2\").setSpatialInertia(\n",
    "    km.SpatialInertia(10.0, si_orig.bodyToCm(), si_orig.inertia())\n",
    ")\n",
    "print(f_to_f.relTransform().getTranslation())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc8739df",
   "metadata": {},
   "source": [
    "## Cloning the system\n",
    "There are many ways to clone the 2-link pendulum. Here, we will use a `DataStruct` to do so. Let's clone the system and add it onto the serial chain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "fd7685af",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create a DataStruct of the current system\n",
    "sg_ds = sim.mb.toDS()\n",
    "\n",
    "# Now, let's rename the bodies to clearly differential them from the old system\n",
    "sg_ds.renameBodies(\"pendulum_2_\")\n",
    "\n",
    "# Finally, let's add these cloned bodies to the multibody\n",
    "new_sg = sg_ds.toSubGraph(sim.mb, sim.mb.getBody(\"bd2\"))\n",
    "\n",
    "# We'll again reset and redraw the scene. The GUI will now reflect these changes.\n",
    "sim.mb.ensureHealthy()\n",
    "sim.mb.resetData()\n",
    "sim.mb.getScene().update()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3d7129d7",
   "metadata": {},
   "source": [
    "Let's show the new multibody tree."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ba11cc0e",
   "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] pendulum_2_bd1\n",
      "            |-[REVOLUTE] pendulum_2_bd2\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.dumpTree()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80b28389",
   "metadata": {},
   "source": [
    "## Python script\n",
    "Let's use the simulation we already have to create a standalone Python script that can be used to regenerate the system we have created. This will dump the simulation information into the file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "2e644dac",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1 file reformatted\n"
     ]
    }
   ],
   "source": [
    "tf = NamedTemporaryFile(suffix=\".py\")\n",
    "sim.mb.toDS().toFile(tf.name)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ad920f2",
   "metadata": {},
   "source": [
    "This call will list the generated file within the Jupyter notebook. Note that the file contains repeated segments defining the kinematics and mass properties for each body and its parent hinge, along with directives to create the body and hinge objects themselves. The parameters in the file can be changed, more bodies can be added following the pattern to create a new simulation instance. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "b4322d2c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "```python\n",
       "# This is an auto-generated file for the Multibody\n",
       "# This file was generated on 2026-06-30 09:36:45.112844\n",
       "import Karana.Math as km\n",
       "import numpy as np\n",
       "import atexit\n",
       "from Karana.Math.Kquantities import ureg\n",
       "from Karana.KUtils.Sim import Sim\n",
       "import Karana.Scene as ks\n",
       "import Karana.Dynamics as kd\n",
       "\n",
       "\n",
       "def populateMbody(mb: kd.Multibody):\n",
       "    \"\"\"Create, add, and connect physical bodies via hinges.\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 method 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",
       "    Parameters\n",
       "    ----------\n",
       "    mb : kd.Multibody\n",
       "        The Multibody to populate with physical bodies.\n",
       "    \"\"\"\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_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_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_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,\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 = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\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_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,\n",
       "        material=physical_material_1,\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([0.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_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 '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_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",
       "    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_2,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd2_end' mesh parameters for 'bd2' 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_3_info = ks.PhysicalMaterialInfo()\n",
       "    physical_material_3_info.color = ks.Color.fromRGBA(1.0, 0.843137264251709, 0.0, 1.0)\n",
       "    physical_material_3_info.roughness = 1.0\n",
       "    physical_material_3 = ks.PhysicalMaterial(physical_material_3_info)\n",
       "    bd2_end = ks.ScenePartSpec(\n",
       "        name=\"bd2_end\",\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_1,\n",
       "        material=physical_material_3,\n",
       "        scale=np.array([2.0, 2.0, 2.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'obstacle_part' mesh parameters for 'bd2' body\n",
       "    # Define the part shape (geometry)\n",
       "    box_2 = ks.BoxGeometry(0.25, 0.25, 0.25)\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.49803921580314636, 1.0, 0.8313725590705872, 1.0\n",
       "    )\n",
       "    physical_material_4_info.roughness = 1.0\n",
       "    physical_material_4 = ks.PhysicalMaterial(physical_material_4_info)\n",
       "    obstacle_part = ks.ScenePartSpec(\n",
       "        name=\"obstacle_part\",\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_2,\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",
       "            10.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=[bd2_body, bd2_end, obstacle_part],\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 'pendulum_2_bd1' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Start defining meshes for 'pendulum_2_bd1' body\"\n",
       "\n",
       "    # Create one or more meshes attached to the 'pendulum_2_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_body' mesh parameters for 'pendulum_2_bd1' body\n",
       "    # Define the part shape (geometry)\n",
       "    box = ks.BoxGeometry(0.05000000074505806, 0.05000000074505806, 1.0)\n",
       "\n",
       "    bd1_body_1 = 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,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd1_pivot' mesh parameters for 'pendulum_2_bd1' body\n",
       "    # Define the part shape (geometry)\n",
       "    cylinder = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\n",
       "\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.5]) * ureg.meter,\n",
       "        ),\n",
       "        geometry=cylinder,\n",
       "        material=physical_material_1,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # End defining meshes for 'pendulum_2_bd1' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Assemble overall parameters needed to define the 'pendulum_2_bd1' body\n",
       "    # including mass and kinematics properties.\n",
       "    pendulum_2_bd1_params = kd.PhysicalBodyParams(\n",
       "        # 'pendulum_2_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 'pendulum_2_bd1' parent hinge\n",
       "        # relative to the 'pendulum_2_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 'pendulum_2_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([0.0, 0.0, 0.0]) * ureg.meter,\n",
       "        ),\n",
       "        # define the 'pendulum_2_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 'pendulum_2_bd1' scene parts defined above\n",
       "        scene_part_specs=[bd1_body_1, bd1_pivot_1],\n",
       "    )\n",
       "\n",
       "    # Create the 'pendulum_2_bd1' body\n",
       "    pendulum_2_bd1 = kd.PhysicalBody(\"pendulum_2_bd1\", mb)\n",
       "\n",
       "    # Connect the 'pendulum_2_bd1' body to its parent via a hinge\n",
       "    kd.PhysicalHinge(bd2, pendulum_2_bd1, kd.HingeType.REVOLUTE)\n",
       "\n",
       "    # Set the parameters for the 'pendulum_2_bd1' body\n",
       "    pendulum_2_bd1.setParams(pendulum_2_bd1_params)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Create 'pendulum_2_bd2' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Start defining meshes for 'pendulum_2_bd2' body\"\n",
       "\n",
       "    # Create one or more meshes attached to the 'pendulum_2_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 'bd2_body' mesh parameters for 'pendulum_2_bd2' body\n",
       "    # Define the part shape (geometry)\n",
       "    box_1 = ks.BoxGeometry(0.05000000074505806, 0.05000000074505806, 1.0)\n",
       "\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_1,\n",
       "        material=physical_material_2,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'bd2_end' mesh parameters for 'pendulum_2_bd2' body\n",
       "    # Define the part shape (geometry)\n",
       "    cylinder_1 = ks.CylinderGeometry(0.10000000149011612, 0.10000000149011612)\n",
       "\n",
       "    bd2_end_1 = ks.ScenePartSpec(\n",
       "        name=\"bd2_end\",\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_1,\n",
       "        material=physical_material_3,\n",
       "        scale=np.array([2.0, 2.0, 2.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    # End defining meshes for 'pendulum_2_bd2' body\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Assemble overall parameters needed to define the 'pendulum_2_bd2' body\n",
       "    # including mass and kinematics properties.\n",
       "    pendulum_2_bd2_params = kd.PhysicalBodyParams(\n",
       "        # 'pendulum_2_bd2' mass properties\n",
       "        spI=km.SpatialInertia(\n",
       "            10.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 'pendulum_2_bd2' parent hinge\n",
       "        # relative to the 'pendulum_2_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 'pendulum_2_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 'pendulum_2_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 'pendulum_2_bd2' scene parts defined above\n",
       "        scene_part_specs=[bd2_body_1, bd2_end_1],\n",
       "    )\n",
       "\n",
       "    # Create the 'pendulum_2_bd2' body\n",
       "    pendulum_2_bd2 = kd.PhysicalBody(\"pendulum_2_bd2\", mb)\n",
       "\n",
       "    # Connect the 'pendulum_2_bd2' body to its parent via a hinge\n",
       "    kd.PhysicalHinge(pendulum_2_bd1, pendulum_2_bd2, kd.HingeType.REVOLUTE)\n",
       "\n",
       "    # Set the parameters for the 'pendulum_2_bd2' body\n",
       "    pendulum_2_bd2.setParams(pendulum_2_bd2_params)\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Start defining parts attached to the virtual root\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Define the 'obstacle_part' mesh parameters for virtual root\n",
       "    # Define the part shape (geometry)\n",
       "    box_2 = ks.BoxGeometry(0.25, 0.25, 0.25)\n",
       "\n",
       "    obstacle_part_1 = ks.ScenePartSpec(\n",
       "        name=\"obstacle_part\",\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_2,\n",
       "        material=physical_material_4,\n",
       "        scale=np.array([1.0, 1.0, 1.0]),\n",
       "        layers=419430400,\n",
       "    )\n",
       "\n",
       "    vroot.addScenePartSpec(obstacle_part_1)\n",
       "\n",
       "    # End defining parts attached to the virtual root\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "    # Finished physical body additions\n",
       "    # ----------------------------------------------------------------------------------------------\n",
       "\n",
       "    # Finalize the multibody for the added bodies\n",
       "    mb.ensureHealthy()\n",
       "\n",
       "\n",
       "if __name__ == \"__main__\":\n",
       "    # Create a simulation\n",
       "    sim = Sim()\n",
       "\n",
       "    # Populate the Multibody\n",
       "    populateMbody(sim.mb)\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": 13,
     "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": "d419097a",
   "metadata": {},
   "source": [
    "Above, you'll see values populate for each body such as `inb_to_joint_transform`. The image below gives a visual for what all of these values mean.\n",
    "\n",
    "<img src=../resources/nb_images/body_connection.png width=600>"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b8884dcb",
   "metadata": {},
   "source": [
    "At this time, we will delete our current sim and create a new one from this generated file."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "2afbb4ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "del sim\n",
    "globals().update(runpy.run_path(tf.name, run_name=\"__main__\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38bded67-c416-4f44-90bd-ba21d016b60e",
   "metadata": {},
   "source": [
    "Let check the structure of the new model from the command line."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b87b56fa-851b-433b-900a-ed39b80b358e",
   "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. pendulum_2_bd1   bd2                   REVOLUTE  1/2           \n",
      "   4. pendulum_2_bd2   pendulum_2_bd1        REVOLUTE  1/3           \n",
      "                                                       ____          \n",
      "                                                       4             \n",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.displayModel()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "e9ead049-fde7-406e-b16c-cc4ae60eb44f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " Nodes\n",
      " ---\n",
      "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] pendulum_2_bd1\n",
      "            |-[REVOLUTE] pendulum_2_bd2\n",
      "\n"
     ]
    }
   ],
   "source": [
    "sim.mb.dumpTree()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0411196",
   "metadata": {},
   "source": [
    "Let's bring up the GUI again just to visually explore the new model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "3c1a2692",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[WebUI] Web server is running on port 42999\n",
      "You may be able to connect in your browser at:\n",
      "\t\u001b[1mhttp://newton:42999\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": "ad536c26-dd10-41dc-a031-555f0c0bf4ad",
   "metadata": {},
   "source": [
    "You can now make changes to the generated Python file to experiment and customize the model and explore more of the **kdFlex** API."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "550b3b2e",
   "metadata": {},
   "source": [
    "## Cleanup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "dbf76e3d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.cleanup()>"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def cleanup():\n",
    "    \"\"\"Clean up the simulation.\"\"\"\n",
    "    global bd1, sim, f_to_f, new_sg, sg_ds\n",
    "    del bd1, f_to_f, sim, new_sg, sg_ds\n",
    "\n",
    "\n",
    "atexit.register(cleanup)"
   ]
  }
 ],
 "metadata": {
  "docs": {
   "tags": [
    "dynamics",
    "pendulum",
    "gui",
    "beginner"
   ]
  },
  "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
}
