<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tendon-Driven Robotic Hand — A Vision-Teleoperated MuJoCo Digital Twin on Mulham Fetna</title>
    <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/</link>
    <description>Recent content in Tendon-Driven Robotic Hand — A Vision-Teleoperated MuJoCo Digital Twin on Mulham Fetna</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <managingEditor>contact@mulhamfetna.com (Mulham Fetna)</managingEditor>
    <webMaster>contact@mulhamfetna.com (Mulham Fetna)</webMaster>
    <copyright>© 2026 Mulham Fetna</copyright>
    <lastBuildDate>Wed, 16 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Calibrating hand tracking to your own hand — with real data from a live session</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/calibration/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/calibration/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The shipped thresholds were measured on one hand with one webcam. Here is how to measure yours — and&#xA;what a live recording revealed about how far off &amp;ldquo;good enough&amp;rdquo; can be while the twin still looks&#xA;perfect.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Symptoms that call for calibration&#xA;    &lt;div id=&#34;symptoms-that-call-for-calibration&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#symptoms-that-call-for-calibration&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;What you see in the published flexions&lt;/th&gt;&#xA;          &lt;th&gt;Cause&lt;/th&gt;&#xA;          &lt;th&gt;Change&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;A fist, but flexion stays below 1.0&lt;/td&gt;&#xA;          &lt;td&gt;your fist angle is &lt;strong&gt;above&lt;/strong&gt; the curled limit&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;raise&lt;/strong&gt; &lt;code&gt;*_CURLED_ANGLE&lt;/code&gt; to your fist reading&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Flexion hits 1.0 with the hand half closed&lt;/td&gt;&#xA;          &lt;td&gt;curled limit too high&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;lower&lt;/strong&gt; &lt;code&gt;*_CURLED_ANGLE&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Flexion above 0 with a relaxed open hand&lt;/td&gt;&#xA;          &lt;td&gt;your open angle is &lt;strong&gt;below&lt;/strong&gt; the straight limit&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;lower&lt;/strong&gt; &lt;code&gt;*_STRAIGHT_ANGLE&lt;/code&gt; to your open reading&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;The thumb flickers&lt;/td&gt;&#xA;          &lt;td&gt;thumb window too narrow for your jitter&lt;/td&gt;&#xA;          &lt;td&gt;widen it (&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/&#34; &gt;Part 8&lt;/a&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;  &#xA;  &#xA;  &#xA;  &#xA;&#xA;&#xA;&#xA;&lt;div&#xA;  &#xA;    class=&#34;flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900&#34;&#xA;  &#xA;  &gt;&#xA;  &lt;span&#xA;    &#xA;      class=&#34;text-primary-400 pe-3 flex items-center&#34;&#xA;    &#xA;    &gt;&#xA;    &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 384 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z&#34;/&gt;&lt;/svg&gt;&#xA;&lt;/span&gt;&#xA;  &lt;/span&gt;&#xA;&#xA;  &lt;span&#xA;    &#xA;      class=&#34;dark:text-neutral-300&#34;&#xA;    &#xA;    &gt;&lt;strong&gt;Calibrate against the numbers, not the render.&lt;/strong&gt; With the current force-control tuning, the&#xA;simulated finger snaps shut past flexion ≈ 0.51 (&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/&#34; &gt;Part 11&lt;/a&gt;),&#xA;so most calibration errors are invisible in the viewer. Watch &lt;code&gt;ros2 topic echo /hand/target_flexions&lt;/code&gt;.&lt;/span&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Real data: what a live session produced&#xA;    &lt;div id=&#34;real-data-what-a-live-session-produced&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#real-data-what-a-live-session-produced&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;MuJoCo&amp;rsquo;s viewer has a &lt;strong&gt;Control&lt;/strong&gt; panel showing the live force of every motor. Since the force is&#xA;\(F = 50 - 100 \cdot \text{flexion}\), every recorded frame gives back the exact flexion the tracker&#xA;published: &lt;strong&gt;flexion = (50 − F) / 100&lt;/strong&gt;.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/calibration/cover.png" />
    </item>
    
    <item>
      <title>Docker Compose architecture for ROS 2: dependency-only images and mounted code</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-compose-architecture/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-compose-architecture/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Both images hold dependencies and nothing else. The code and the robot model are mounted from your&#xA;checkout at runtime — so an edit is a five-second restart, not a five-minute rebuild.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The big picture&#xA;    &lt;div id=&#34;the-big-picture&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-big-picture&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    subgraph HOST[&#34;🐧 Linux host&#34;]&#xA;        CAMDEV[&#34;/dev/video0&#34;]&#xA;        GPU[&#34;/dev/dri · Intel iGPU&#34;]&#xA;        X11[&#34;/tmp/.X11-unix&lt;br&gt;XWayland :0&#34;]&#xA;        SHM[&#34;/dev/shm&lt;br&gt;Fast DDS segments&#34;]&#xA;        NET[&#34;host network&lt;br&gt;UDP multicast · domain 42&#34;]&#xA;        REPO[&#34;repository checkout&#34;]&#xA;        subgraph VT[&#34;🐳 vision_tracker · 2.9 GB image&#34;]&#xA;            VN[&#34;vision_tracker_node.py&lt;br&gt;mediapipe 0.10.14 · OpenCV&#34;]&#xA;        end&#xA;        subgraph MT[&#34;🐳 mujoco_twin · 1.7 GB image&#34;]&#xA;            MN[&#34;mujoco_twin_node.py&lt;br&gt;mujoco 3.13.0 · GLFW&#34;]&#xA;        end&#xA;    end&#xA;    CAMDEV --&gt; VN&#xA;    GPU --&gt; VN&#xA;    GPU --&gt; MN&#xA;    X11 &lt;--&gt; VN&#xA;    X11 &lt;--&gt; MN&#xA;    VN &lt;--&gt; SHM &lt;--&gt; MN&#xA;    VN &lt;--&gt; NET &lt;--&gt; MN&#xA;    REPO -. &#34;bind mount .:/workspace:ro&#34; .-&gt; VN&#xA;    REPO -. &#34;bind mount .:/workspace:ro&#34; .-&gt; MN&#xA;&lt;/pre&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Repository layout&#xA;    &lt;div id=&#34;repository-layout&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#repository-layout&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── docker-compose.yml          # both services, shared namespaces&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── setup_host.sh               # xhost + device checks, once per login&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── vision_tracker/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── Dockerfile              # ros:jazzy + mediapipe==0.10.14&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── .dockerignore           # src/ is mounted, so keep it out of the build context&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── src/vision_tracker_node.py&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── mujoco_twin/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── Dockerfile              # ros:jazzy + mujoco==3.13.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── .dockerignore           # src/ and model/ are mounted&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   ├── src/mujoco_twin_node.py&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;│   └── model/                  # scene.xml → robot.xml (+ tendons.xml), assets/, config.json&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;├── standalone/main.py          # the same pipeline, one process&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── docs/&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Each service folder is its own &lt;strong&gt;build context&lt;/strong&gt;: editing the vision Dockerfile never invalidates the&#xA;twin&amp;rsquo;s image cache, and neither build uploads the 13 MB of meshes it doesn&amp;rsquo;t need.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-compose-architecture/cover.png" />
    </item>
    
    <item>
      <title>Finger joint angles from three hand landmarks: the dot-product geometry</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Every knuckle angle in this project comes from three landmarks and one dot product. No learning,&#xA;no lookup table — just the definition of the angle between two vectors, plus two guards that keep a&#xA;single glitchy frame from sending NaN into a motor command.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Three points make an angle&#xA;    &lt;div id=&#34;three-points-make-an-angle&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#three-points-make-an-angle&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;An angle needs a vertex and two rays. A knuckle is the vertex; the two bones meeting there are the rays:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;a &lt;strong&gt;base point&lt;/strong&gt;, where the previous bone starts,&lt;/li&gt;&#xA;&lt;li&gt;the &lt;strong&gt;vertex&lt;/strong&gt; — the knuckle being measured,&lt;/li&gt;&#xA;&lt;li&gt;an &lt;strong&gt;end point&lt;/strong&gt;, where the next bone ends.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    P1((&#34;p1&lt;br&gt;base&#34;)) -- &#34;v1 = p1 − p2&#34; --- P2((&#34;p2&lt;br&gt;vertex&lt;br&gt;knuckle&#34;))&#xA;    P2 -- &#34;v2 = p3 − p2&#34; --- P3((&#34;p3&lt;br&gt;end&#34;))&#xA;&lt;/pre&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The triplet table&#xA;    &lt;div id=&#34;the-triplet-table&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-triplet-table&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger_triplets&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;thumb&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;3&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;4&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;index&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;5&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;6&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;7&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;8&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;middle&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;9&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;11&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;ring&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;   &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;13&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;14&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;15&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;16&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)],&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;s2&#34;&gt;&amp;#34;pinky&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;  &lt;span class=&#34;p&#34;&gt;[(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;17&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;18&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;19&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;20&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)]&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;MediaPipe landmark indices used to build the triplets&#34;&#xA;    width=&#34;2146&#34;&#xA;    height=&#34;744&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_788faf393ce06561.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_788faf393ce06561.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks_hu_8e6e6baf8bb39074.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/mediapipe-landmarks.png&#34;&gt;&lt;figcaption&gt;Landmark 0 — the wrist — starts every finger&amp;rsquo;s first triplet.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Finger&lt;/th&gt;&#xA;          &lt;th&gt;Triplet&lt;/th&gt;&#xA;          &lt;th&gt;Vertex&lt;/th&gt;&#xA;          &lt;th&gt;Joint measured&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(0, 5, 6)&lt;/td&gt;&#xA;          &lt;td&gt;5&lt;/td&gt;&#xA;          &lt;td&gt;MCP — joins finger to palm&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(5, 6, 7)&lt;/td&gt;&#xA;          &lt;td&gt;6&lt;/td&gt;&#xA;          &lt;td&gt;PIP — middle knuckle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Index&lt;/td&gt;&#xA;          &lt;td&gt;(6, 7, 8)&lt;/td&gt;&#xA;          &lt;td&gt;7&lt;/td&gt;&#xA;          &lt;td&gt;DIP — fingertip knuckle&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(0, 1, 2)&lt;/td&gt;&#xA;          &lt;td&gt;1&lt;/td&gt;&#xA;          &lt;td&gt;CMC — saddle joint at the wrist&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(1, 2, 3)&lt;/td&gt;&#xA;          &lt;td&gt;2&lt;/td&gt;&#xA;          &lt;td&gt;MCP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb&lt;/td&gt;&#xA;          &lt;td&gt;(2, 3, 4)&lt;/td&gt;&#xA;          &lt;td&gt;3&lt;/td&gt;&#xA;          &lt;td&gt;IP&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;strong&gt;Why every first triplet starts at 0.&lt;/strong&gt; The palm has no landmark of its own, so the wrist → knuckle&#xA;line stands in for the metacarpal bone. It isn&amp;rsquo;t exactly collinear with a straight finger — ring and&#xA;pinky metacarpals fan outward — so a relaxed straight finger rarely measures a full \(\pi\). That&amp;rsquo;s&#xA;part of why the calibrated &amp;ldquo;straight&amp;rdquo; threshold is 3.10 rad rather than 3.14.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/cover.png" />
    </item>
    
    <item>
      <title>From an Onshape assembly to a MuJoCo model with onshape-to-robot</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The simulated hand was never modelled by hand. It is an Onshape assembly — five SG90 servos, fifteen&#xA;knuckle mates, a palm full of tendon channels — pulled through the Onshape API and written out as&#xA;MuJoCo XML. Here is the design, and every setting that steers the export.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;a&#xA;  class=&#34;!rounded-md bg-primary-600 px-4 py-2 !text-neutral !no-underline hover:!bg-primary-500 dark:bg-primary-800 dark:hover:!bg-primary-700&#34;&#xA;  href=&#34;https://cad.onshape.com/documents/a2dbb5f16624f10f1aa22f02/w/693ffc0be3e83ae21f78f6ed/e/4d69727744037003575f4068?renderMode=0&#34;&#xA;  target=&#34;_blank&#34;&#xA;  &#xA;  role=&#34;button&#34;&gt;&#xA;  Open the Onshape assembly&#xA;&lt;/a&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The design&#xA;    &lt;div id=&#34;the-design&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-design&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;figure&gt;&#xA;  &lt;video&#xA;    class=&#34;w-full rounded-md aspect-video object-contain&#34;preload=&#34;metadata&#34; muted controls playsinline&gt;&#xA;    &lt;source src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/onshape_cad_tour_current_design.webm&#34;&gt;&#xA;    &lt;p&gt;Your browser cannot play this video. &lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/onshape_cad_tour_current_design.webm&#34;&gt;Download video&lt;/a&gt;.&lt;/p&gt;&#xA;  &lt;/video&gt;&#xA;&lt;/figure&gt;&#xA;&#xA;&#xA;&#xA;  &#xA;  &#xA;  &#xA;  &#xA;  &#xA;  &lt;div class=&#34;width-patch&#34;&gt;&lt;/div&gt;&#xA;&lt;div id=&#34;gallery-7acbdbda855d714d7a7934961dc8bbc7&#34; class=&#34;gallery&#34;&gt;&#xA;  &#xA;  &lt;img src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_assembly_iso.png&#34; class=&#34;grid-w50&#34; alt=&#34;Current assembly, isometric, with mate connectors visible&#34; /&gt;&#xA;  &lt;img src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_front_tendon_channels.png&#34; class=&#34;grid-w50&#34; alt=&#34;Front view: one tendon channel per finger down the palm&#34; /&gt;&#xA;  &lt;img src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_servo_block.png&#34; class=&#34;grid-w50&#34; alt=&#34;Five SG90 servos staggered in the base block&#34; /&gt;&#xA;  &lt;img src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_servo_layout.png&#34; class=&#34;grid-w50&#34; alt=&#34;Servo layout seen from below the palm&#34; /&gt;&#xA;  &lt;img src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_fingers_flexing.png&#34; class=&#34;grid-w50&#34; alt=&#34;Finger and thumb mates driven to flexion in Onshape&#34; /&gt;&#xA;  &lt;img src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_side_profile_flexed.png&#34; class=&#34;grid-w50&#34; alt=&#34;Side profile of the flexed finger and thumb chains&#34; /&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Palm and fingers:&lt;/strong&gt; four three-phalanx fingers and a three-segment thumb, every knuckle a&#xA;&lt;strong&gt;revolute mate&lt;/strong&gt; with limits. The RGB triads in the views are mate connectors.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Tendon channels:&lt;/strong&gt; one per finger, running down the palm into the base.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Servo block:&lt;/strong&gt; five SG90-class servos, staggered so each horn sits under a tendon exit.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The design has a history&#xA;    &lt;div id=&#34;the-design-has-a-history&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-design-has-a-history&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;ol class=&#34;border-s-2 border-primary-500 dark:border-primary-300 list-none&#34;&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;li&gt;&#xA;  &lt;div class=&#34;flex&#34;&gt;&#xA;    &lt;div&#xA;      class=&#34;bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full ltr:-ml-12 rtl:-mr-[79px] mt-5&#34;&gt;&#xA;      &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 512 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M421.7 220.3L188.5 453.4L154.6 419.5L158.1 416H112C103.2 416 96 408.8 96 400V353.9L92.51 357.4C87.78 362.2 84.31 368 82.42 374.4L59.44 452.6L137.6 429.6C143.1 427.7 149.8 424.2 154.6 419.5L188.5 453.4C178.1 463.8 165.2 471.5 151.1 475.6L30.77 511C22.35 513.5 13.24 511.2 7.03 504.1C.8198 498.8-1.502 489.7 .976 481.2L36.37 360.9C40.53 346.8 48.16 333.9 58.57 323.5L291.7 90.34L421.7 220.3zM492.7 58.75C517.7 83.74 517.7 124.3 492.7 149.3L444.3 197.7L314.3 67.72L362.7 19.32C387.7-5.678 428.3-5.678 453.3 19.32L492.7 58.75z&#34;/&gt;&lt;/svg&gt;&#xA;&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;block p-6 rounded-lg shadow-2xl flex-1 ms-6 mb-10 break-words&#34;&gt;&#xA;      &lt;div class=&#34;flex justify-between&#34;&gt;&#xA;        &#xA;          &lt;h2 class=&#34;mt-0&#34;&gt;Start&lt;/h2&gt;&#xA;        &#xA;        &#xA;      &lt;/div&gt;&#xA;      &#xA;        &lt;h4 class=&#34;mt-0&#34;&gt;&#xA;          2026-09-02&#xA;        &lt;/h4&gt;&#xA;      &#xA;      &lt;div class=&#34;mb-6&#34;&gt;&#xA;        The first version in the history.&#xA;      &lt;/div&gt;&#xA;    &lt;/div&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;li&gt;&#xA;  &lt;div class=&#34;flex&#34;&gt;&#xA;    &lt;div&#xA;      class=&#34;bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full ltr:-ml-12 rtl:-mr-[79px] mt-5&#34;&gt;&#xA;      &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 576 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M287.9 0C297.1 0 305.5 5.25 309.5 13.52L378.1 154.8L531.4 177.5C540.4 178.8 547.8 185.1 550.7 193.7C553.5 202.4 551.2 211.9 544.8 218.2L433.6 328.4L459.9 483.9C461.4 492.9 457.7 502.1 450.2 507.4C442.8 512.7 432.1 513.4 424.9 509.1L287.9 435.9L150.1 509.1C142.9 513.4 133.1 512.7 125.6 507.4C118.2 502.1 114.5 492.9 115.1 483.9L142.2 328.4L31.11 218.2C24.65 211.9 22.36 202.4 25.2 193.7C28.03 185.1 35.5 178.8 44.49 177.5L197.7 154.8L266.3 13.52C270.4 5.249 278.7 0 287.9 0L287.9 0zM287.9 78.95L235.4 187.2C231.9 194.3 225.1 199.3 217.3 200.5L98.98 217.9L184.9 303C190.4 308.5 192.9 316.4 191.6 324.1L171.4 443.7L276.6 387.5C283.7 383.7 292.2 383.7 299.2 387.5L404.4 443.7L384.2 324.1C382.9 316.4 385.5 308.5 391 303L476.9 217.9L358.6 200.5C350.7 199.3 343.9 194.3 340.5 187.2L287.9 78.95z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;block p-6 rounded-lg shadow-2xl flex-1 ms-6 mb-10 break-words&#34;&gt;&#xA;      &lt;div class=&#34;flex justify-between&#34;&gt;&#xA;        &#xA;          &lt;h2 class=&#34;mt-0&#34;&gt;v1.0.0 — MediaPipe&lt;/h2&gt;&#xA;        &#xA;        &#xA;      &lt;/div&gt;&#xA;      &#xA;        &lt;h4 class=&#34;mt-0&#34;&gt;&#xA;          2026-09-06&#xA;        &lt;/h4&gt;&#xA;      &#xA;      &lt;div class=&#34;mb-6&#34;&gt;&#xA;        The joint-angle-driven hand behind the &lt;a href=&#34;https://mulhamfetna.com/projects/ros2-mediapipe-robotic-hand-digital-twin-vision-teleoperation/&#34; &gt;RViz predecessor project&lt;/a&gt;.&#xA;      &lt;/div&gt;&#xA;    &lt;/div&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;li&gt;&#xA;  &lt;div class=&#34;flex&#34;&gt;&#xA;    &lt;div&#xA;      class=&#34;bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full ltr:-ml-12 rtl:-mr-[79px] mt-5&#34;&gt;&#xA;      &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; height=&#34;1em&#34; viewBox=&#34;0 0 512 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M441 58.9L453.1 71c9.4 9.4 9.4 24.6 0 33.9L424 134.1 377.9 88 407 58.9c9.4-9.4 24.6-9.4 33.9 0zM209.8 256.2L344 121.9 390.1 168 255.8 302.2c-2.9 2.9-6.5 5-10.4 6.1l-58.5 16.7 16.7-58.5c1.1-3.9 3.2-7.5 6.1-10.4zM373.1 25L175.8 222.2c-8.7 8.7-15 19.4-18.3 31.1l-28.6 100c-2.4 8.4-.1 17.4 6.1 23.6s15.2 8.5 23.6 6.1l100-28.6c11.8-3.4 22.5-9.7 31.1-18.3L487 138.9c28.1-28.1 28.1-73.7 0-101.8L474.9 25C446.8-3.1 401.2-3.1 373.1 25zM88 64C39.4 64 0 103.4 0 152V424c0 48.6 39.4 88 88 88H360c48.6 0 88-39.4 88-88V312c0-13.3-10.7-24-24-24s-24 10.7-24 24V424c0 22.1-17.9 40-40 40H88c-22.1 0-40-17.9-40-40V152c0-22.1 17.9-40 40-40H200c13.3 0 24-10.7 24-24s-10.7-24-24-24H88z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;block p-6 rounded-lg shadow-2xl flex-1 ms-6 mb-10 break-words&#34;&gt;&#xA;      &lt;div class=&#34;flex justify-between&#34;&gt;&#xA;        &#xA;          &lt;h2 class=&#34;mt-0&#34;&gt;v1.0.1 → Main&lt;/h2&gt;&#xA;        &#xA;        &#xA;      &lt;/div&gt;&#xA;      &#xA;        &lt;h4 class=&#34;mt-0&#34;&gt;&#xA;          2026-09-08&#xA;        &lt;/h4&gt;&#xA;      &#xA;      &lt;div class=&#34;mb-6&#34;&gt;&#xA;        Point release and the main line the later work branches from.&#xA;      &lt;/div&gt;&#xA;    &lt;/div&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;li&gt;&#xA;  &lt;div class=&#34;flex&#34;&gt;&#xA;    &lt;div&#xA;      class=&#34;bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full ltr:-ml-12 rtl:-mr-[79px] mt-5&#34;&gt;&#xA;      &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 448 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M80 104c13.3 0 24-10.7 24-24s-10.7-24-24-24S56 66.7 56 80s10.7 24 24 24zm80-24c0 32.8-19.7 61-48 73.3V192c0 17.7 14.3 32 32 32H304c17.7 0 32-14.3 32-32V153.3C307.7 141 288 112.8 288 80c0-44.2 35.8-80 80-80s80 35.8 80 80c0 32.8-19.7 61-48 73.3V192c0 53-43 96-96 96H256v70.7c28.3 12.3 48 40.5 48 73.3c0 44.2-35.8 80-80 80s-80-35.8-80-80c0-32.8 19.7-61 48-73.3V288H144c-53 0-96-43-96-96V153.3C19.7 141 0 112.8 0 80C0 35.8 35.8 0 80 0s80 35.8 80 80zm208 24c13.3 0 24-10.7 24-24s-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24zM248 432c0-13.3-10.7-24-24-24s-24 10.7-24 24s10.7 24 24 24s24-10.7 24-24z&#34;/&gt;&lt;/svg&gt;&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;block p-6 rounded-lg shadow-2xl flex-1 ms-6 mb-10 break-words&#34;&gt;&#xA;      &lt;div class=&#34;flex justify-between&#34;&gt;&#xA;        &#xA;          &lt;h2 class=&#34;mt-0&#34;&gt;V3 → Mujoco branch&lt;/h2&gt;&#xA;        &#xA;        &#xA;      &lt;/div&gt;&#xA;      &#xA;        &lt;h4 class=&#34;mt-0&#34;&gt;&#xA;          2026-09-16&#xA;        &lt;/h4&gt;&#xA;      &#xA;      &lt;div class=&#34;mb-6&#34;&gt;&#xA;        The current design used by this twin — the version with the servo base block shown above.&#xA;      &lt;/div&gt;&#xA;    &lt;/div&gt;&#xA;  &lt;/div&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Onshape version history&lt;/th&gt;&#xA;          &lt;th&gt;Mate features&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Onshape version graph: Start, v1.0.0 MediaPipe, v1.0.1, Main, V3, Mujoco branch&#34;&#xA;    width=&#34;413&#34;&#xA;    height=&#34;475&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_version_history.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_version_history.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_version_history.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_version_history.png&#34;&gt;&lt;/figure&gt;&#xA;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Mate features list: dof_pinky_dip through dof_thumb_ip, then Fastened mates&#34;&#xA;    width=&#34;255&#34;&#xA;    height=&#34;750&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_mate_list.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_mate_list.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_mate_list.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/current_mate_list.png&#34;&gt;&lt;/figure&gt;&#xA;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;em&gt;43 part instances, 112 mate features: the 15 &lt;code&gt;dof_*&lt;/code&gt; knuckle mates, the servo mates, and many &lt;code&gt;Fastened&lt;/code&gt; mates.&lt;/em&gt;&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/onshape-to-mujoco/cover.png" />
    </item>
    
    <item>
      <title>From finger flexion to tendon force: linear interpolation onto a MuJoCo motor</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  On the far side of the ROS 2 topic, five flexions arrive and five tendon motors wait. One line of&#xA;linear interpolation connects them — plus a name lookup that can fail silently, and a string that is&#xA;allowed to push.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The formula&#xA;    &lt;div id=&#34;the-formula&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-formula&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;$$F(t) = F_\text{open} + t\,(F_\text{closed} - F_\text{open}) = 50 + t\,(-50 - 50) = 50 - 100\,t$$&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;FORCE_OPEN&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;50.0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;FORCE_CLOSED&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;mf&#34;&gt;50.0&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;_lerp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;end_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;t&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;*&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;end_val&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;start_val&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;def&lt;/span&gt; &lt;span class=&#34;nf&#34;&gt;apply_flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;):&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;finger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexion_amount&lt;/span&gt; &lt;span class=&#34;ow&#34;&gt;in&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;():&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;n&#34;&gt;target_force&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;_lerp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;FORCE_OPEN&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;FORCE_CLOSED&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;flexion_amount&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;ctrl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;bp&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;motors&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;target_force&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-0ef11f389f0a690c0b3dc91c1b080552&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-0ef11f389f0a690c0b3dc91c1b080552&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;line&#39;,&#xA;data: {&#xA;  labels: [&#39;0.0&#39;,&#39;0.1&#39;,&#39;0.2&#39;,&#39;0.3&#39;,&#39;0.4&#39;,&#39;0.5&#39;,&#39;0.6&#39;,&#39;0.7&#39;,&#39;0.8&#39;,&#39;0.9&#39;,&#39;1.0&#39;],&#xA;  datasets: [{&#xA;    label: &#39;Tendon motor command (N)&#39;,&#xA;    data: [50, 40, 30, 20, 10, 0, -10, -20, -30, -40, -50],&#xA;    borderColor: &#39;#2a78d6&#39;, backgroundColor: &#39;#2a78d6&#39;, borderWidth: 2, pointRadius: 3, tension: 0&#xA;  }]&#xA;},&#xA;options: {&#xA;  plugins: { legend: { display: false }, title: { display: true, text: &#39;Flexion → force: +50 N open, 0 N at 0.5, −50 N closed&#39; } },&#xA;  scales: { x: { title: { display: true, text: &#39;flexion&#39; } }, y: { title: { display: true, text: &#39;force (N) — negative pulls&#39; } } }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Three parts of one line&#xA;    &lt;div id=&#34;three-parts-of-one-line&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#three-parts-of-one-line&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;With \(t = 0.75\), a finger 75% closed:&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/cover.png" />
    </item>
    
    <item>
      <title>From tutorial project to production robot: a roadmap for the tendon hand twin</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/roadmap-to-production/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/roadmap-to-production/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The system works, and it teaches well. Getting it to drive real servos safely is a sequence of&#xA;well-scoped upgrades — each one grounded in a limitation measured earlier in this series.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Where it stands&#xA;    &lt;div id=&#34;where-it-stands&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#where-it-stands&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Area&lt;/th&gt;&#xA;          &lt;th&gt;Today&lt;/th&gt;&#xA;          &lt;th&gt;Production target&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Hand tracking&lt;/td&gt;&#xA;          &lt;td&gt;image-normalized landmarks, one global calibration&lt;/td&gt;&#xA;          &lt;td&gt;metric world landmarks, per-finger calibration, a temporal filter&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Command mapping&lt;/td&gt;&#xA;          &lt;td&gt;flexion → ±50 N; the twin is a switch&lt;/td&gt;&#xA;          &lt;td&gt;flexion → tendon length; proportional curl&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Physics model&lt;/td&gt;&#xA;          &lt;td&gt;force motors, decorative horns, no self-contact&lt;/td&gt;&#xA;          &lt;td&gt;position servos on horns, tuned stiffness, contacts for grasping&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Middleware&lt;/td&gt;&#xA;          &lt;td&gt;one topic, default QoS, open on the LAN&lt;/td&gt;&#xA;          &lt;td&gt;parameters, explicit QoS, a watchdog, SROS 2&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Containers&lt;/td&gt;&#xA;          &lt;td&gt;privileged, host namespaces, root, &lt;code&gt;xhost&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;least privilege, non-root, optional headless&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Code&lt;/td&gt;&#xA;          &lt;td&gt;classes copied into three files, no tests&lt;/td&gt;&#xA;          &lt;td&gt;one shared package, tests, CI&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Hardware&lt;/td&gt;&#xA;          &lt;td&gt;simulation only&lt;/td&gt;&#xA;          &lt;td&gt;a servo driver on the same topic&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;ol class=&#34;border-s-2 border-primary-500 dark:border-primary-300 list-none&#34;&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&lt;li&gt;&#xA;  &lt;div class=&#34;flex&#34;&gt;&#xA;    &lt;div&#xA;      class=&#34;bg-primary-500 dark:bg-primary-300 text-neutral-50 dark:text-neutral-700 min-w-[30px] h-8 text-2xl flex items-center justify-center rounded-full ltr:-ml-12 rtl:-mr-[79px] mt-5&#34;&gt;&#xA;      &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 448 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M438.6 105.4C451.1 117.9 451.1 138.1 438.6 150.6L182.6 406.6C170.1 419.1 149.9 419.1 137.4 406.6L9.372 278.6C-3.124 266.1-3.124 245.9 9.372 233.4C21.87 220.9 42.13 220.9 54.63 233.4L159.1 338.7L393.4 105.4C405.9 92.88 426.1 92.88 438.6 105.4H438.6z&#34;/&gt;&lt;/svg&gt;&#xA;&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;block p-6 rounded-lg shadow-2xl flex-1 ms-6 mb-10 break-words&#34;&gt;&#xA;      &lt;div class=&#34;flex justify-between&#34;&gt;&#xA;        &#xA;          &lt;h2 class=&#34;mt-0&#34;&gt;Stage 1 · Correctness&lt;/h2&gt;&#xA;        &#xA;        &#xA;      &lt;/div&gt;&#xA;      &#xA;        &lt;h4 class=&#34;mt-0&#34;&gt;&#xA;          Measure the right thing, command the right quantity&#xA;        &lt;/h4&gt;&#xA;      &#xA;      &lt;div class=&#34;mb-6&#34;&gt;&#xA;        &lt;p&gt;&lt;strong&gt;World landmarks.&lt;/strong&gt; Image-normalized coordinates bend angles by up to 16° with hand orientation&#xA;(&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/&#34; &gt;Part 4&lt;/a&gt;).&#xA;Read &lt;code&gt;multi_hand_world_landmarks&lt;/code&gt; instead, then recalibrate.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/roadmap-to-production/cover.png" />
    </item>
    
    <item>
      <title>GUI, webcam and GPU passthrough for ROS 2 and MuJoCo containers</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-gui-camera-gpu/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-gui-camera-gpu/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Containers are headless by design. This project needs two windows, a webcam and a GPU — so the&#xA;Compose file spends most of its lines punching carefully chosen holes back through the isolation.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;low&#34;&#xA;    alt=&#34;Both GUI windows from two containers running together on one display&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/hero_teleoperation.gif&#34;&#xA;    &gt;&lt;figcaption&gt;Two containers, two windows, one X display: the Qt/OpenCV tracker (vision_tracker) and the GLFW MuJoCo viewer (mujoco_twin), recorded on Wayland through XWayland.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Windows: pass the X11 socket through&#xA;    &lt;div id=&#34;windows-pass-the-x11-socket-through&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#windows-pass-the-x11-socket-through&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-yaml&#34; data-lang=&#34;yaml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;environment&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;DISPLAY&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;l&#34;&gt;${DISPLAY:-:0}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;QT_X11_NO_MITSHM&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;m&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;volumes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;  &lt;/span&gt;- &lt;span class=&#34;l&#34;&gt;/tmp/.X11-unix:/tmp/.X11-unix:rw&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Piece&lt;/th&gt;&#xA;          &lt;th&gt;What it does&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;/tmp/.X11-unix&lt;/code&gt; mount&lt;/td&gt;&#xA;          &lt;td&gt;the X server listens on a Unix socket here (&lt;code&gt;X0&lt;/code&gt; for &lt;code&gt;:0&lt;/code&gt;); mounting it gives the container a line to it&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;DISPLAY&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;tells X clients (GLFW, Qt) which display to use; defaults to &lt;code&gt;:0&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;QT_X11_NO_MITSHM=1&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;stops Qt — OpenCV&amp;rsquo;s &lt;code&gt;imshow&lt;/code&gt; backend — from using MIT-SHM, which fails across containers and gives blank or garbled windows&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;xhost +local:root&lt;/code&gt; (in &lt;code&gt;setup_host.sh&lt;/code&gt;)&lt;/td&gt;&#xA;          &lt;td&gt;the X server refuses untrusted clients; this admits local root, which is who the containers run as&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;&lt;h3 class=&#34;relative group&#34;&gt;On Wayland&#xA;    &lt;div id=&#34;on-wayland&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#on-wayland&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h3&gt;&#xA;&lt;p&gt;Wayland sessions (KDE Plasma, GNOME) still run &lt;strong&gt;XWayland&lt;/strong&gt; on &lt;code&gt;:0&lt;/code&gt;, and both windows open through it —&#xA;the recording above was made exactly that way. Running MuJoCo &lt;em&gt;natively&lt;/em&gt; on a Wayland host, GLFW may&#xA;warn &lt;code&gt;Wayland: The platform does not provide the window position&lt;/code&gt;; it&amp;rsquo;s harmless.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-gui-camera-gpu/cover.png" />
    </item>
    
    <item>
      <title>How a webcam moves a simulated tendon-driven hand</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  One vision container turns webcam frames into five numbers. One simulation container turns those&#xA;numbers into tendon forces. Everything else in this series is detail inside one of those two boxes&#xA;— or the pipe between them.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Peace sign, live: camera with MediaPipe landmarks, the twin with ring and pinky curled, and the motor forces −21 N on ring and pinky&#34;&#xA;    width=&#34;1570&#34;&#xA;    height=&#34;480&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign_hu_10d6cdce7106932.jpg&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign_hu_10d6cdce7106932.jpg 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign_hu_c82c14c5f3244f84.jpg 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/live_peace_sign.jpg&#34;&gt;&lt;figcaption&gt;Left to right, three layers in one frame: vision (landmarks), simulation (the twin), actuation (live motor forces from MuJoCo&amp;rsquo;s Control panel).&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;End to end&#xA;    &lt;div id=&#34;end-to-end&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#end-to-end&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    subgraph VISION[&#34;🐳 vision_tracker container&#34;]&#xA;        direction TB&#xA;        A[&#34;Webcam frame&lt;br&gt;640×480 BGR&#34;] --&gt; B[&#34;MediaPipe Hands&lt;br&gt;21 landmarks&#34;]&#xA;        B --&gt; C[&#34;3 knuckle angles / finger&lt;br&gt;dot product&#34;]&#xA;        C --&gt; D[&#34;mean → 1 curl angle&lt;br&gt;(underactuation)&#34;]&#xA;        D --&gt; E[&#34;normalize + clip&lt;br&gt;flexion 0..1&#34;]&#xA;    end&#xA;    subgraph TWIN[&#34;🐳 mujoco_twin container&#34;]&#xA;        direction TB&#xA;        F[&#34;lerp&lt;br&gt;+50 N … −50 N&#34;] --&gt; G[&#34;data.ctrl on&lt;br&gt;pull_{finger} motor&#34;]&#xA;        G --&gt; H[&#34;spatial tendon&lt;br&gt;through 6 sites&#34;]&#xA;        H --&gt; I[&#34;3 passive hinge joints&lt;br&gt;curl&#34;]&#xA;        I --&gt; J[&#34;MuJoCo viewer&#34;]&#xA;    end&#xA;    E -- &#34;ROS 2 · /hand/target_flexions&lt;br&gt;sensor_msgs/JointState&#34; --&gt; F&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Stage&lt;/th&gt;&#xA;          &lt;th&gt;What comes out&lt;/th&gt;&#xA;          &lt;th&gt;Deep dive&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;MediaPipe Hands&lt;/td&gt;&#xA;          &lt;td&gt;21 (x, y, z) landmarks per frame&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/&#34; &gt;Part 4&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Triplet angles&lt;/td&gt;&#xA;          &lt;td&gt;3 interior angles per finger, in radians&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/joint-angles-dot-product/&#34; &gt;Part 5&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Averaging&lt;/td&gt;&#xA;          &lt;td&gt;1 curl angle per finger&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/&#34; &gt;Part 6&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Normalization&lt;/td&gt;&#xA;          &lt;td&gt;flexion 0..1 (the thumb has its own window)&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/&#34; &gt;Parts 7&lt;/a&gt;–&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/&#34; &gt;8&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;ROS 2 topic&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;JointState&lt;/code&gt;: names are fingers, positions are flexions&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/&#34; &gt;Part 15&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Lerp&lt;/td&gt;&#xA;          &lt;td&gt;force in newtons per tendon&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-to-force/&#34; &gt;Part 10&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Tendon physics&lt;/td&gt;&#xA;          &lt;td&gt;joint angles&lt;/td&gt;&#xA;          &lt;td&gt;&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/&#34; &gt;Part 11&lt;/a&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Why the pipe carries flexions, not angles or forces&#xA;    &lt;div id=&#34;why-the-pipe-carries-flexions-not-angles-or-forces&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#why-the-pipe-carries-flexions-not-angles-or-forces&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The contract between the containers is five unitless numbers: 0.0 is an open finger, 1.0 is a closed&#xA;one. That choice is the architecture.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/architecture/cover.png" />
    </item>
    
    <item>
      <title>Inside the MuJoCo tendon hand model: bodies, joints, sites and sign conventions</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  A fixed palm, five three-segment digits, five free-spinning servo horns, ten strings through seventy&#xA;points, five motors. Every number here was read from the compiled model with MuJoCo 3.13.0.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;  &#xA;    &#xA;      &#xA;    &#xA;  &#xA;&#xA;&#xA;&#xA;&#xA;&#xA;  &#xA;  &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;    &#xA;    &#xA;      &#xA;      &#xA;      &#xA;    &#xA;  &#xA;&#xA;&#xA;&lt;div class=&#34;width-patch&#34;&gt;&lt;/div&gt;&#xA;&lt;div&#xA;  id=&#34;carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;  class=&#34;relative&#34;&#xA;  data-twe-carousel-init&#xA;  data-twe-ride=&#34;carousel&#34;&#xA;  data-twe-interval=&#34;2500&#34;&gt;&#xA;  &lt;div&#xA;    class=&#34;absolute right-0 bottom-0 left-0 z-2 mx-[15%] mb-10 flex list-none justify-center p-0&#34;&#xA;    data-twe-carousel-indicators&gt;&#xA;    &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;0&#34;&#xA;        data-twe-carousel-active aria-current=&#34;true&#34;&#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 0&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;1&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 1&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;2&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 2&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;3&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 3&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;4&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 4&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;5&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 5&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;6&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 6&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;      &lt;button&#xA;        type=&#34;button&#34;&#xA;        data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;        data-twe-slide-to=&#34;7&#34;&#xA;        &#xA;        class=&#34;mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-neutral bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none&#34;&#xA;        aria-label=&#34;Slide 7&#34;&gt;&lt;/button&gt;&#xA;      &#xA;    &#xA;  &lt;/div&gt;&#xA;&#xA;  &lt;div&#xA;    class=&#34;relative w-full after:clear-both after:block after:content-[&#39;&#39;]&#34;&#xA;    style=&#34;overflow-x: clip; overflow-y: visible;&#34;&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%]  w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      data-twe-carousel-active&gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/pose_open_front.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 1&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Open (+50 N)&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/pose_neutral_front.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 2&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Neutral (0 N)&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/pose_fist_front.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 3&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Fist (−50 N)&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/isolate_index.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 4&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Index tendon only&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/isolate_middle.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 5&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Middle tendon only&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/isolate_ring.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 6&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Ring tendon only&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/isolate_pinky.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 7&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Pinky tendon only&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &#xA;    &lt;div&#xA;      class=&#34;relative float-left -mr-[100%] hidden w-full transition-transform ease-in-out motion-reduce:transition-none&#34;&#xA;      data-twe-carousel-item&#xA;      style=&#34;transition-duration: 2500ms;&#34;&#xA;      &gt;&#xA;        &lt;div class=&#34;single_hero_background relative overflow-hidden&#34; style=&#34;aspect-ratio: 4 / 3;&#34;&gt;&#xA;          &lt;img&#xA;            src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/isolate_thumb.png&#34;&#xA;            class=&#34;block absolute top-0 object-cover w-full h-full not-prose nozoom&#34;&#xA;            alt=&#34;carousel image 8&#34;&gt;&#xA;        &lt;/div&gt;&#xA;        &#xA;        &lt;figcaption&#xA;          class=&#34;absolute left-0 right-0&#34;&#xA;          style=&#34;top: calc(100%);&#34;&#xA;        &gt;Thumb tendon only&lt;/figcaption&gt;&#xA;        &#xA;    &lt;/div&gt;&#xA;    &#xA;  &lt;/div&gt;&#xA;&#xA;  &lt;button&#xA;    class=&#34;absolute top-0 bottom-0 left-0 z-2 flex w-[15%] items-center justify-center border-0 bg-none p-0 text-center opacity-50 transition-opacity duration-150 ease-[cubic-bezier(0.25,0.1,0.25,1.0)] hover:no-underline hover:opacity-90 hover:outline-none focus:no-underline focus:opacity-90 focus:outline-none motion-reduce:transition-none&#34;&#xA;    type=&#34;button&#34;&#xA;    data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;    data-twe-slide=&#34;prev&#34;&gt;&#xA;    &lt;span class=&#34;inline-block h-8 w-8&#34;&gt;&#xA;      &lt;svg&#xA;        xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;        fill=&#34;none&#34;&#xA;        viewBox=&#34;0 0 24 24&#34;&#xA;        stroke-width=&#34;4.5&#34;&#xA;        stroke=&#34;currentColor&#34;&#xA;        class=&#34;h-6 w-6&#34;&gt;&#xA;        &lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; d=&#34;M15.75 19.5L8.25 12l7.5-7.5&#34; /&gt;&#xA;      &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;    &lt;span&#xA;      class=&#34;!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]&#34;&#xA;      &gt;Previous&lt;/span&#xA;    &gt;&#xA;  &lt;/button&gt;&#xA;&#xA;  &lt;button&#xA;    class=&#34;absolute top-0 bottom-0 right-0 z-[1] flex w-[15%] items-center justify-center border-0 bg-none p-0 text-center opacity-50 transition-opacity duration-150 ease-[cubic-bezier(0.25,0.1,0.25,1.0)] hover:no-underline hover:opacity-90 hover:outline-none focus:no-underline focus:opacity-90 focus:outline-none motion-reduce:transition-none&#34;&#xA;    type=&#34;button&#34;&#xA;    data-twe-target=&#34;#carousel-aa5a96a9b087d346b8360ef1519723f8-1789572420471281821&#34;&#xA;    data-twe-slide=&#34;next&#34;&gt;&#xA;    &lt;span class=&#34;inline-block h-8 w-8&#34;&gt;&#xA;      &lt;svg&#xA;        xmlns=&#34;http://www.w3.org/2000/svg&#34;&#xA;        fill=&#34;none&#34;&#xA;        viewBox=&#34;0 0 24 24&#34;&#xA;        stroke-width=&#34;4.5&#34;&#xA;        stroke=&#34;currentColor&#34;&#xA;        class=&#34;h-6 w-6&#34;&gt;&#xA;        &lt;path stroke-linecap=&#34;round&#34; stroke-linejoin=&#34;round&#34; d=&#34;M8.25 4.5l7.5 7.5-7.5 7.5&#34; /&gt;&#xA;      &lt;/svg&gt;&#xA;    &lt;/span&gt;&#xA;    &lt;span&#xA;      class=&#34;!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]&#34;&#xA;      &gt;Next&lt;/span&#xA;    &gt;&#xA;  &lt;/button&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;At a glance&#xA;    &lt;div id=&#34;at-a-glance&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#at-a-glance&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Quantity&lt;/th&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;          &lt;th&gt;Notes&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Bodies&lt;/td&gt;&#xA;          &lt;td&gt;22&lt;/td&gt;&#xA;          &lt;td&gt;world, palm, 15 phalanges, 5 servo horns&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Joints / DoF&lt;/td&gt;&#xA;          &lt;td&gt;20 / 20&lt;/td&gt;&#xA;          &lt;td&gt;all hinges; &lt;strong&gt;no free joint&lt;/strong&gt; — the palm is welded to the world&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Knuckle joints&lt;/td&gt;&#xA;          &lt;td&gt;15&lt;/td&gt;&#xA;          &lt;td&gt;passive, 90° limits&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Servo horn joints&lt;/td&gt;&#xA;          &lt;td&gt;5&lt;/td&gt;&#xA;          &lt;td&gt;unlimited and unactuated&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Geoms&lt;/td&gt;&#xA;          &lt;td&gt;87&lt;/td&gt;&#xA;          &lt;td&gt;43 visual + 43 collision meshes + floor&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Meshes&lt;/td&gt;&#xA;          &lt;td&gt;14&lt;/td&gt;&#xA;          &lt;td&gt;STL&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Sites&lt;/td&gt;&#xA;          &lt;td&gt;70&lt;/td&gt;&#xA;          &lt;td&gt;tendon via-points and anchors&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Tendons&lt;/td&gt;&#xA;          &lt;td&gt;10&lt;/td&gt;&#xA;          &lt;td&gt;5 flexor (actuated) + 5 extensor (passive)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Actuators&lt;/td&gt;&#xA;          &lt;td&gt;5&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;&amp;lt;motor&amp;gt;&lt;/code&gt; on flexors, ±50 N&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Timestep / integrator&lt;/td&gt;&#xA;          &lt;td&gt;0.002 s / Euler&lt;/td&gt;&#xA;          &lt;td&gt;500 steps per simulated second&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Total mass&lt;/td&gt;&#xA;          &lt;td&gt;0.398 kg&lt;/td&gt;&#xA;          &lt;td&gt;palm block 341.8 g&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The kinematic tree&#xA;    &lt;div id=&#34;the-kinematic-tree&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-kinematic-tree&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    W[&#34;world&#34;] --&gt; P[&#34;part_1 · palm + servo block&lt;br&gt;341.8 g · 20 sites&#34;]&#xA;    P --&gt; I1[&#34;part_2_4 · index proximal&lt;br&gt;4.6 g · index_mcp&#34;] --&gt; I2[&#34;part_3_4&lt;br&gt;1.8 g · index_pip&#34;] --&gt; I3[&#34;part_4_4&lt;br&gt;4.6 g · index_dip&#34;]&#xA;    P --&gt; M1[&#34;part_2_3 · middle&lt;br&gt;middle_mcp&#34;] --&gt; M2[&#34;part_3_3&lt;br&gt;middle_pip&#34;] --&gt; M3[&#34;part_4_3&lt;br&gt;middle_dip&#34;]&#xA;    P --&gt; R1[&#34;part_2_2 · ring&lt;br&gt;ring_mcp&#34;] --&gt; R2[&#34;part_3_2&lt;br&gt;ring_pip&#34;] --&gt; R3[&#34;part_4_2&lt;br&gt;ring_dip&#34;]&#xA;    P --&gt; K1[&#34;part_2 · pinky&lt;br&gt;pinky_mcp&#34;] --&gt; K2[&#34;part_3&lt;br&gt;pinky_pip&#34;] --&gt; K3[&#34;part_4&lt;br&gt;pinky_dip&#34;]&#xA;    P --&gt; T1[&#34;part_5 · thumb&lt;br&gt;5.4 g · thumb_cmc&#34;] --&gt; T2[&#34;part_6&lt;br&gt;2.4 g · thumb_mp&#34;] --&gt; T3[&#34;part_7&lt;br&gt;2.5 g · thumb_ip&#34;]&#xA;    P --&gt; H[&#34;servo_horn … servo_horn_5&lt;br&gt;0.3 g each&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&#xA;  &#xA;  &#xA;  &#xA;  &#xA;&#xA;&#xA;&#xA;&lt;div&#xA;  &#xA;    class=&#34;flex px-4 py-3 rounded-md shadow bg-primary-100 dark:bg-primary-900&#34;&#xA;  &#xA;  &gt;&#xA;  &lt;span&#xA;    &#xA;      class=&#34;text-primary-400 pe-3 flex items-center&#34;&#xA;    &#xA;    &gt;&#xA;    &lt;span class=&#34;relative block icon&#34;&gt;&lt;svg xmlns=&#34;http://www.w3.org/2000/svg&#34; viewBox=&#34;0 0 512 512&#34;&gt;&lt;path fill=&#34;currentColor&#34; d=&#34;M506.3 417l-213.3-364c-16.33-28-57.54-28-73.98 0l-213.2 364C-10.59 444.9 9.849 480 42.74 480h426.6C502.1 480 522.6 445 506.3 417zM232 168c0-13.25 10.75-24 24-24S280 154.8 280 168v128c0 13.25-10.75 24-23.1 24S232 309.3 232 296V168zM256 416c-17.36 0-31.44-14.08-31.44-31.44c0-17.36 14.07-31.44 31.44-31.44s31.44 14.08 31.44 31.44C287.4 401.9 273.4 416 256 416z&#34;/&gt;&lt;/svg&gt;&#xA;&lt;/span&gt;&#xA;  &lt;/span&gt;&#xA;&#xA;  &lt;span&#xA;    &#xA;      class=&#34;dark:text-neutral-300&#34;&#xA;    &#xA;    &gt;&lt;strong&gt;Body names are CAD part names, not finger names.&lt;/strong&gt; Repeated instances get &lt;code&gt;_2&lt;/code&gt;, &lt;code&gt;_3&lt;/code&gt;, … appended, so&#xA;the &lt;em&gt;pinky&lt;/em&gt; chain is &lt;code&gt;part_2 → part_3 → part_4&lt;/code&gt; and the &lt;em&gt;index&lt;/em&gt; chain is &lt;code&gt;part_2_4 → part_3_4 → part_4_4&lt;/code&gt;.&#xA;Address &lt;strong&gt;joints, sites, tendons and actuators&lt;/strong&gt; in code — never bodies.&lt;/span&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Joints and their signs&#xA;    &lt;div id=&#34;joints-and-their-signs&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#joints-and-their-signs&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Joint&lt;/th&gt;&#xA;          &lt;th&gt;Range (rad)&lt;/th&gt;&#xA;          &lt;th&gt;Bend direction&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;index_mcp&lt;/code&gt;, &lt;code&gt;index_pip&lt;/code&gt;, &lt;code&gt;index_dip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[−1.571, 0]&lt;/td&gt;&#xA;          &lt;td&gt;negative&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;middle_mcp&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[−1.571, 0]&lt;/td&gt;&#xA;          &lt;td&gt;negative&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;middle_pip&lt;/code&gt;, &lt;code&gt;middle_dip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[0, +1.571]&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;positive&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ring_mcp&lt;/code&gt;, &lt;code&gt;ring_dip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[−1.571, 0]&lt;/td&gt;&#xA;          &lt;td&gt;negative&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;ring_pip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[0, +1.571]&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;positive&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pinky_mcp&lt;/code&gt;, &lt;code&gt;pinky_dip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[−1.571, 0]&lt;/td&gt;&#xA;          &lt;td&gt;negative&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;pinky_pip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[0, +1.571]&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;positive&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;thumb_cmc&lt;/code&gt;, &lt;code&gt;thumb_mp&lt;/code&gt;, &lt;code&gt;thumb_ip&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;[−1.571, 0]&lt;/td&gt;&#xA;          &lt;td&gt;negative&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;servo_*&lt;/code&gt; × 5&lt;/td&gt;&#xA;          &lt;td&gt;unlimited&lt;/td&gt;&#xA;          &lt;td&gt;—&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;strong&gt;Bend direction is inconsistent&lt;/strong&gt;, because each Onshape mate&amp;rsquo;s axis was exported as-is. Anything that&#xA;reads joint angles must normalize per joint:&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/model-anatomy/cover.png" />
    </item>
    
    <item>
      <title>MediaPipe Hands for robotics — and the 16° angle trap in normalized landmarks</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Twenty-one points per frame, on a laptop CPU, from a flat RGB image. That is what MediaPipe hands&#xA;to a robot. It is easy to treat as a black box — until you compute angles from it and discover the&#xA;box stretched your coordinate space.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Where MediaPipe sits&#xA;    &lt;div id=&#34;where-mediapipe-sits&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#where-mediapipe-sits&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    CAM[&#34;USB webcam&lt;br&gt;640×480 @ 30 fps · YUYV&#34;] --&gt;|&#34;BGR frame&#34;| RGB[&#34;cv2.cvtColor&lt;br&gt;BGR → RGB&#34;]&#xA;    RGB --&gt; MP[&#34;MediaPipe Hands&lt;br&gt;palm detector + landmark model&#34;]&#xA;    MP --&gt;|&#34;21 × (x, y, z)&#34;| ANG[&#34;Triplet angles&lt;br&gt;Part 5&#34;]&#xA;    ANG --&gt; AVG[&#34;Average per finger&lt;br&gt;Part 6&#34;]&#xA;    AVG --&gt; NORM[&#34;Flexion 0..1&lt;br&gt;Parts 7–8&#34;]&#xA;    NORM --&gt;|&#34;/hand/target_flexions&#34;| TWIN[&#34;MuJoCo twin&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;p&gt;The vision layer knows nothing about MuJoCo. Its entire output is &lt;strong&gt;five numbers between 0.0 (open)&#xA;and 1.0 (closed)&lt;/strong&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Two networks, not one&#xA;    &lt;div id=&#34;two-networks-not-one&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#two-networks-not-one&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;MediaPipe Hands is a &lt;strong&gt;cascade&lt;/strong&gt;:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;BlazePalm&lt;/strong&gt;, a single-shot detector, finds a &lt;em&gt;palm&lt;/em&gt; bounding box in the full frame. Palms, not&#xA;hands: a palm is close to a rigid square; a hand with moving fingers is not.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;A landmark model&lt;/strong&gt; crops that region and regresses 21 keypoints, a hand-presence score and&#xA;handedness.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;In video mode (&lt;code&gt;static_image_mode=False&lt;/code&gt;) the detector barely runs. Landmarks from frame &lt;em&gt;t&lt;/em&gt; define&#xA;the crop for frame &lt;em&gt;t+1&lt;/em&gt;, and the detector wakes only when tracking confidence drops. That shortcut&#xA;is why this pipeline holds 30 fps on a CPU.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mediapipe-hands/cover.png" />
    </item>
    
    <item>
      <title>Normalizing finger curl: from radians to a 0–1 flexion</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Radians belong to the camera. Newtons belong to the robot. The number that crosses between them is a&#xA;plain percentage — and producing it takes one inverted formula and one clip.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The formula&#xA;    &lt;div id=&#34;the-formula&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-formula&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-python&#34; data-lang=&#34;python&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RAW_STRAIGHT_ANGLE&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;3.10&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# ~177.6° — open finger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;RAW_CURLED_ANGLE&lt;/span&gt;   &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.60&lt;/span&gt;  &lt;span class=&#34;c1&#34;&gt;# ~91.7° — fully curled finger&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flexion&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;straight_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;avg_angle&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;/&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;straight_limit&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;-&lt;/span&gt; &lt;span class=&#34;n&#34;&gt;curled_limit&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;n&#34;&gt;flexions&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;finger_name&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;float&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;np&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;clip&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;n&#34;&gt;flexion&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;0.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mf&#34;&gt;1.0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;$$\text{flexion} = \operatorname{clip}\!\left(\frac{\theta_\text{straight} - \bar\theta}{\theta_\text{straight} - \theta_\text{curled}},\; 0,\; 1\right)$$&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-0927e0044df1c84967fec69d742e1c22&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-0927e0044df1c84967fec69d742e1c22&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;line&#39;,&#xA;data: {&#xA;  labels: [&#39;3.30&#39;,&#39;3.20&#39;,&#39;3.10&#39;,&#39;3.00&#39;,&#39;2.90&#39;,&#39;2.80&#39;,&#39;2.70&#39;,&#39;2.60&#39;,&#39;2.50&#39;,&#39;2.40&#39;,&#39;2.30&#39;,&#39;2.20&#39;,&#39;2.10&#39;,&#39;2.00&#39;,&#39;1.90&#39;,&#39;1.80&#39;,&#39;1.70&#39;,&#39;1.60&#39;,&#39;1.50&#39;,&#39;1.40&#39;,&#39;1.30&#39;],&#xA;  datasets: [&#xA;    { label: &#39;Index–pinky window (3.10 → 1.60 rad)&#39;, data: [0,0,0,0.067,0.133,0.2,0.267,0.333,0.4,0.467,0.533,0.6,0.667,0.733,0.8,0.867,0.933,1,1,1,1], borderColor: &#39;#2a78d6&#39;, backgroundColor: &#39;#2a78d6&#39;, borderWidth: 2, pointRadius: 2, tension: 0 },&#xA;    { label: &#39;Thumb window (2.90 → 2.30 rad)&#39;, data: [0,0,0,0,0,0.167,0.333,0.5,0.667,0.833,1,1,1,1,1,1,1,1,1,1,1], borderColor: &#39;#eb6834&#39;, backgroundColor: &#39;#eb6834&#39;, borderWidth: 2, pointRadius: 2, tension: 0 }&#xA;  ]&#xA;},&#xA;options: {&#xA;  plugins: { title: { display: true, text: &#39;Average knuckle angle → normalized flexion&#39; } },&#xA;  scales: {&#xA;    x: { title: { display: true, text: &#39;average knuckle angle (rad) — straight → curled&#39; } },&#xA;    y: { min: 0, max: 1.05, title: { display: true, text: &#39;flexion (0 open · 1 closed)&#39; } }&#xA;  }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The three pieces&#xA;    &lt;div id=&#34;the-three-pieces&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-three-pieces&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Denominator — the range.&lt;/strong&gt; \(3.10 - 1.60 = 1.50\) rad of travel. It sets the scale.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/flexion-normalization/cover.png" />
    </item>
    
    <item>
      <title>ROS 2 across Docker containers: shared network, discovery and Fast DDS shared memory</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-docker-network-shared-memory/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-docker-network-shared-memory/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  &amp;ldquo;The topic shows up in &lt;code&gt;ros2 topic list&lt;/code&gt;, but &lt;code&gt;echo&lt;/code&gt; prints nothing&amp;rdquo; — the classic ROS-in-Docker&#xA;symptom. Here&amp;rsquo;s why it happens, how two containers in this project share a network and a block of&#xA;memory instead, and what that costs.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;How ROS 2 nodes find each other&#xA;    &lt;div id=&#34;how-ros-2-nodes-find-each-other&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#how-ros-2-nodes-find-each-other&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;ROS 2 has no master. Nodes discover each other through &lt;strong&gt;DDS&lt;/strong&gt; — in Jazzy, &lt;strong&gt;eProsima Fast DDS&lt;/strong&gt; by&#xA;default — using the SPDP protocol:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Each participant announces itself over &lt;strong&gt;UDP multicast&lt;/strong&gt; (239.255.0.1) on ports derived from the&#xA;domain ID \(d\): discovery multicast on \(7400 + 250d\), unicast on \(7410 + 250d + 2p\) for&#xA;participant \(p\). For &lt;strong&gt;domain 42&lt;/strong&gt;: UDP &lt;strong&gt;17900&lt;/strong&gt; and &lt;strong&gt;17910+&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Peers exchange their topic endpoints; matching publishers and subscribers connect.&lt;/li&gt;&#xA;&lt;li&gt;Data flows over the best &lt;strong&gt;transport&lt;/strong&gt; both support — shared memory when they share a host and&#xA;&lt;code&gt;/dev/shm&lt;/code&gt;, UDP otherwise.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Why Docker&amp;rsquo;s default network breaks it&#xA;    &lt;div id=&#34;why-dockers-default-network-breaks-it&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#why-dockers-default-network-breaks-it&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Compose attaches services to a &lt;strong&gt;bridge network&lt;/strong&gt; — a private NATed subnet. Multicast isn&amp;rsquo;t reliably&#xA;routed across it, and machines on your LAN can&amp;rsquo;t reach container IPs at all.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-docker-network-shared-memory/cover.png" />
    </item>
    
    <item>
      <title>Run the tendon-driven hand digital twin in 10 minutes</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/quickstart/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/quickstart/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Clone, run one setup script, run one Compose command — and a webcam window and a MuJoCo viewer open&#xA;side by side, with a simulated hand that closes when you close yours. Here are four ways in,&#xA;from the full stack down to the bare model.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;low&#34;&#xA;    alt=&#34;What a working setup looks like: MediaPipe tracker window and the MuJoCo twin moving together&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/hero_teleoperation.gif&#34;&#xA;    &gt;&lt;figcaption&gt;The goal of this article: both windows open, and the twin follows your hand.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;What you need&#xA;    &lt;div id=&#34;what-you-need&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#what-you-need&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Requirement&lt;/th&gt;&#xA;          &lt;th&gt;Check with&lt;/th&gt;&#xA;          &lt;th&gt;Notes&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Linux desktop, X11 or XWayland&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;echo $DISPLAY&lt;/code&gt; → &lt;code&gt;:0&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Wayland sessions work through XWayland&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Docker Engine + Compose v2&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;docker compose version&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Built with Docker 29.8 / Compose 5.5&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;A webcam&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ls /dev/video0&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Close any other app using it&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;GPU device nodes&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ls /dev/dri&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;Intel/AMD out of the box; NVIDIA needs the container toolkit (&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-gui-camera-gpu/&#34; &gt;Part 18&lt;/a&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;~5 GB of disk&lt;/td&gt;&#xA;          &lt;td&gt;&lt;/td&gt;&#xA;          &lt;td&gt;images: vision 2.9 GB, twin 1.7 GB&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;No ROS installation is needed on the host — ROS 2 Jazzy lives inside the containers.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Four ways to run it&#xA;    &lt;div id=&#34;four-ways-to-run-it&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#four-ways-to-run-it&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div&#xA;  class=&#34;tab__container w-full&#34;&#xA;  &#xA;  &gt;&#xA;  &lt;div class=&#34;tab__nav&#34; role=&#34;tablist&#34;&gt;&#xA;    &lt;div class=&#34;flex flex-wrap gap-1&#34;&gt;&lt;button&#xA;          class=&#34;tab__button px-3 py-2 text-sm font-semibold border-b-2 border-transparent rounded-t-md hover:bg-neutral-200 dark:hover:bg-neutral-700 tab--active&#34;&#xA;          role=&#34;tab&#34;&#xA;          aria-selected=&#34;true&#34;&#xA;          data-tab-index=&#34;0&#34;&#xA;          data-tab-label=&#34;Docker Compose (full stack)&#34;&gt;&#xA;          &lt;span class=&#34;flex items-center gap-1&#34;&gt;&#xA;            &#xA;            Docker Compose (full stack)&#xA;          &lt;/span&gt;&#xA;        &lt;/button&gt;&lt;button&#xA;          class=&#34;tab__button px-3 py-2 text-sm font-semibold border-b-2 border-transparent rounded-t-md hover:bg-neutral-200 dark:hover:bg-neutral-700 &#34;&#xA;          role=&#34;tab&#34;&#xA;          aria-selected=&#34;false&#34;&#xA;          data-tab-index=&#34;1&#34;&#xA;          data-tab-label=&#34;Single Python script&#34;&gt;&#xA;          &lt;span class=&#34;flex items-center gap-1&#34;&gt;&#xA;            &#xA;            Single Python script&#xA;          &lt;/span&gt;&#xA;        &lt;/button&gt;&lt;button&#xA;          class=&#34;tab__button px-3 py-2 text-sm font-semibold border-b-2 border-transparent rounded-t-md hover:bg-neutral-200 dark:hover:bg-neutral-700 &#34;&#xA;          role=&#34;tab&#34;&#xA;          aria-selected=&#34;false&#34;&#xA;          data-tab-index=&#34;2&#34;&#xA;          data-tab-label=&#34;Twin only, no camera&#34;&gt;&#xA;          &lt;span class=&#34;flex items-center gap-1&#34;&gt;&#xA;            &#xA;            Twin only, no camera&#xA;          &lt;/span&gt;&#xA;        &lt;/button&gt;&lt;button&#xA;          class=&#34;tab__button px-3 py-2 text-sm font-semibold border-b-2 border-transparent rounded-t-md hover:bg-neutral-200 dark:hover:bg-neutral-700 &#34;&#xA;          role=&#34;tab&#34;&#xA;          aria-selected=&#34;false&#34;&#xA;          data-tab-index=&#34;3&#34;&#xA;          data-tab-label=&#34;Model viewer&#34;&gt;&#xA;          &lt;span class=&#34;flex items-center gap-1&#34;&gt;&#xA;            &#xA;            Model viewer&#xA;          &lt;/span&gt;&#xA;        &lt;/button&gt;&lt;/div&gt;&#xA;  &lt;/div&gt;&#xA;  &lt;div class=&#34;tab__content mt-4&#34;&gt;&lt;div class=&#34;tab__panel tab--active&#34; data-tab-index=&#34;0&#34;&gt;&#xA;        &lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/mulhamfetna/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation.git&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nb&#34;&gt;cd&lt;/span&gt; ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;./setup_host.sh              &lt;span class=&#34;c1&#34;&gt;# X11 access for the containers + camera/GPU checks (once per login)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose up --build    &lt;span class=&#34;c1&#34;&gt;# builds both images, starts vision_tracker and mujoco_twin&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;p&gt;Stop with &lt;code&gt;Ctrl+C&lt;/code&gt;, then &lt;code&gt;docker compose down&lt;/code&gt; to release the camera.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/quickstart/cover.png" />
    </item>
    
    <item>
      <title>The manual MJCF edits that turn a CAD export into a tendon-driven hand</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mjcf-edits/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mjcf-edits/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  A raw CAD export gives you bodies, joints, meshes and sites — and a hand that does nothing. Four&#xA;edits and one extra file turn it into a tendon-driven twin. One of those edits, it turns out, does&#xA;nothing at all — which is worth understanding too.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Which edits survive a re-export&#xA;    &lt;div id=&#34;which-edits-survive-a-re-export&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#which-edits-survive-a-re-export&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart TB&#xA;    subgraph generated[&#34;robot.xml — regenerated by onshape-to-robot&#34;]&#xA;        D[&#34;① joint defaults&lt;br&gt;manual — re-apply&#34;]&#xA;        T[&#34;② tendons + ③ contacts&lt;br&gt;auto-injected from tendons.xml&#34;]&#xA;        B[&#34;bodies · joints · sites · meshes&lt;br&gt;generated&#34;]&#xA;        A[&#34;④ actuators&lt;br&gt;manual — re-apply&#34;]&#xA;    end&#xA;    S[&#34;⑤ scene.xml&lt;br&gt;floor · lights · skybox&lt;br&gt;never regenerated&#34;] --&gt;|&#34;include robot.xml&#34;| generated&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;#&lt;/th&gt;&#xA;          &lt;th&gt;Edit&lt;/th&gt;&#xA;          &lt;th&gt;Lives in&lt;/th&gt;&#xA;          &lt;th&gt;Survives re-export?&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;①&lt;/td&gt;&#xA;          &lt;td&gt;Joint friction / armature / damping defaults&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;robot.xml&lt;/code&gt; &lt;code&gt;&amp;lt;default&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;✘ re-apply&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;②&lt;/td&gt;&#xA;          &lt;td&gt;Flexor + extensor spatial tendons&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tendons.xml&lt;/code&gt; → injected&lt;/td&gt;&#xA;          &lt;td&gt;✔&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;③&lt;/td&gt;&#xA;          &lt;td&gt;Contact exclusions&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;tendons.xml&lt;/code&gt; → injected&lt;/td&gt;&#xA;          &lt;td&gt;✔&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;④&lt;/td&gt;&#xA;          &lt;td&gt;Five tendon motors&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;robot.xml&lt;/code&gt; &lt;code&gt;&amp;lt;actuator&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;✘ re-apply&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;⑤&lt;/td&gt;&#xA;          &lt;td&gt;Environment&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;scene.xml&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;✔ separate file&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;code&gt;tendons.xml&lt;/code&gt; is kept byte-identical to the &lt;code&gt;&amp;lt;tendon&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;contact&amp;gt;&lt;/code&gt; region of &lt;code&gt;robot.xml&lt;/code&gt;. Tune a&#xA;tendon in &lt;code&gt;robot.xml&lt;/code&gt; without copying it back and the next export silently reverts it.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;① Joint defaults — stability&#xA;    &lt;div id=&#34;-joint-defaults--stability&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#-joint-defaults--stability&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-xml&#34; data-lang=&#34;xml&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nt&#34;&gt;&amp;lt;default&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;class=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ros2-tendon-driven-hand-gazebo-digital-twin&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;lt;joint&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;frictionloss=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;0.001&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;armature=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;0.0001&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;damping=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;0.01&amp;#34;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Attribute&lt;/th&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;          &lt;th&gt;Role&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;damping&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;0.01 N·m·s/rad&lt;/td&gt;&#xA;          &lt;td&gt;stops a 2 g phalanx reaching absurd speed when 50 N yanks it&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;armature&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;0.0001 kg·m²&lt;/td&gt;&#xA;          &lt;td&gt;rotor-like inertia on each joint&amp;rsquo;s diagonal — conditions the solver for very light bodies&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;frictionloss&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;0.001 N·m&lt;/td&gt;&#xA;          &lt;td&gt;a small dry-friction dead-band so joints settle instead of creeping&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Phalanges weigh &lt;strong&gt;1.8–5.4 g&lt;/strong&gt;. Without these, tiny inertias under large tendon forces blow up the&#xA;integrator — an earlier, larger revision of the model logged&#xA;&lt;code&gt;Nan, Inf or huge value in QACC at DOF 128. The simulation is unstable.&lt;/code&gt;&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/mjcf-edits/cover.png" />
    </item>
    
    <item>
      <title>The ROS 2 topic contract: one JointState topic, two nodes, and rclpy inside a render loop</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The whole ROS 2 layer is one topic carrying five numbers. The interesting decisions are what those&#xA;numbers mean, which message type carries them, and how to run a ROS subscriber when a 3D viewer owns&#xA;your main thread.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Why ROS 2 between vision and physics at all?&#xA;    &lt;div id=&#34;why-ros-2-between-vision-and-physics-at-all&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#why-ros-2-between-vision-and-physics-at-all&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The single-process script works well. Splitting it across ROS 2 buys:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Benefit&lt;/th&gt;&#xA;          &lt;th&gt;Concretely&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Process isolation&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;a MediaPipe crash doesn&amp;rsquo;t kill the simulator, and vice versa&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Independent environments&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;vision and physics get their own container, dependencies and restarts&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Swappable endpoints&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;replace the twin with a servo driver, or the tracker with a data glove&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Free observability&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;ros2 topic echo&lt;/code&gt;, &lt;code&gt;hz&lt;/code&gt;, &lt;code&gt;bag record&lt;/code&gt; on the live stream&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;strong&gt;Network transparency&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;any machine on the LAN can subscribe&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;The costs — a bigger stack, DDS configuration, one more hop — are small next to 19–85 ms of inference&#xA;(&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/&#34; &gt;Part 19&lt;/a&gt;).&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The node graph&#xA;    &lt;div id=&#34;the-node-graph&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-node-graph&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;pre class=&#34;not-prose mermaid&#34;&gt;&#xA;flowchart LR&#xA;    subgraph C1[&#34;🐳 vision_tracker&#34;]&#xA;        V[&#34;/vision_tracker_node&lt;br&gt;timer · 30 Hz&#34;]&#xA;    end&#xA;    subgraph C2[&#34;🐳 mujoco_twin&#34;]&#xA;        T[&#34;/mujoco_twin_node&lt;br&gt;spin_once in viewer loop&#34;]&#xA;    end&#xA;    V -- &#34;/hand/target_flexions&lt;br&gt;sensor_msgs/JointState · depth 10&#34; --&gt; T&#xA;    V -. &#34;any LAN subscriber&lt;br&gt;ROS_DOMAIN_ID=42&#34; .-&gt; X[&#34;ros2 topic echo · rosbag&lt;br&gt;future servo driver&#34;]&#xA;&lt;/pre&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Peace sign live: the tracked hand, the twin, and the forces produced from the topic&amp;rsquo;s flexions&#34;&#xA;    width=&#34;1570&#34;&#xA;    height=&#34;480&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign_hu_10d6cdce7106932.jpg&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign_hu_10d6cdce7106932.jpg 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign_hu_c82c14c5f3244f84.jpg 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/live_peace_sign.jpg&#34;&gt;&lt;figcaption&gt;Every frame of the twin is driven by one JointState message like the one below.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The contract&#xA;    &lt;div id=&#34;the-contract&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-contract&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Field&lt;/th&gt;&#xA;          &lt;th&gt;Value&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Topic&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;/hand/target_flexions&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Type&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;sensor_msgs/msg/JointState&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Publisher&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;vision_tracker_node&lt;/code&gt;, timer at 30 Hz — effective rate bounded by inference&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Subscriber&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;mujoco_twin_node&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;QoS&lt;/td&gt;&#xA;          &lt;td&gt;default reliable, keep-last &lt;strong&gt;10&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;header.stamp&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;publisher clock at publish time&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;&lt;code&gt;[&amp;quot;thumb&amp;quot;, &amp;quot;index&amp;quot;, &amp;quot;middle&amp;quot;, &amp;quot;ring&amp;quot;, &amp;quot;pinky&amp;quot;]&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;position&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;flexion per finger, &lt;strong&gt;0.0 open … 1.0 closed&lt;/strong&gt;, same order as &lt;code&gt;name&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;&lt;code&gt;velocity&lt;/code&gt;, &lt;code&gt;effort&lt;/code&gt;&lt;/td&gt;&#xA;          &lt;td&gt;empty&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;A real message, captured with &lt;code&gt;ros2 topic echo&lt;/code&gt; during testing (published by hand with &lt;code&gt;ros2 topic pub&lt;/code&gt;,&#xA;hence the zero stamp):&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/ros2-topic-contract/cover.png" />
    </item>
    
    <item>
      <title>Troubleshooting and FAQ: MuJoCo, MediaPipe and ROS 2 in Docker</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/troubleshooting-faq/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/troubleshooting-faq/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Every entry here was hit, or deliberately checked, while building this project. Error messages are&#xA;quoted exactly so a search for the message lands on the fix.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Camera and windows&#xA;    &lt;div id=&#34;camera-and-windows&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#camera-and-windows&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&#xA;&lt;h3 class=&#34;relative group&#34;&gt;&lt;code&gt;RuntimeError: Failed to open camera at index 0&lt;/code&gt;&#xA;    &lt;div id=&#34;runtimeerror-failed-to-open-camera-at-index-0&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#runtimeerror-failed-to-open-camera-at-index-0&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Something else holds the webcam&lt;/strong&gt; — the standalone script, a previous container, a browser tab.&#xA;Run &lt;code&gt;docker compose down&lt;/code&gt; and close video apps. A camera opens in one process at a time.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The camera is another node.&lt;/strong&gt; &lt;code&gt;ls /dev/video*&lt;/code&gt;, then map that device and set &lt;code&gt;CAMERA_INDEX&lt;/code&gt;&#xA;(many webcams expose &lt;code&gt;/dev/video0&lt;/code&gt; for frames and &lt;code&gt;/dev/video1&lt;/code&gt; for metadata — use the first).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The device isn&amp;rsquo;t mapped.&lt;/strong&gt; Check with &lt;code&gt;docker compose config | grep video&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3 class=&#34;relative group&#34;&gt;Windows don&amp;rsquo;t open — &lt;code&gt;cannot connect to X server&lt;/code&gt;, &lt;code&gt;could not connect to display&lt;/code&gt;&#xA;    &lt;div id=&#34;windows-dont-open--cannot-connect-to-x-server-could-not-connect-to-display&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#windows-dont-open--cannot-connect-to-x-server-could-not-connect-to-display&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Run &lt;code&gt;./setup_host.sh&lt;/code&gt; (it runs &lt;code&gt;xhost +local:root&lt;/code&gt;). The permission resets when you log out.&lt;/li&gt;&#xA;&lt;li&gt;Make sure &lt;code&gt;echo $DISPLAY&lt;/code&gt; on the host prints &lt;code&gt;:0&lt;/code&gt;, or export &lt;code&gt;DISPLAY&lt;/code&gt; before &lt;code&gt;docker compose up&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;On Wayland, confirm XWayland is running: &lt;code&gt;ls /tmp/.X11-unix/&lt;/code&gt; should list &lt;code&gt;X0&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h3 class=&#34;relative group&#34;&gt;Black, blank or garbled OpenCV window&#xA;    &lt;div id=&#34;black-blank-or-garbled-opencv-window&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#black-blank-or-garbled-opencv-window&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h3&gt;&#xA;&lt;p&gt;&lt;code&gt;QT_X11_NO_MITSHM=1&lt;/code&gt; must reach the container. It&amp;rsquo;s in the shared Compose environment — if a service&#xA;defines its own &lt;code&gt;environment:&lt;/code&gt;, it must merge the shared anchor with &lt;code&gt;&amp;lt;&amp;lt;: *ros-env&lt;/code&gt; rather than&#xA;replace it (&lt;a href=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/docker-compose-architecture/&#34; &gt;Part 16&lt;/a&gt;).&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/troubleshooting-faq/cover.png" />
    </item>
    
    <item>
      <title>Underactuation: why three knuckle angles become one tendon command</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  &lt;code&gt;avg_angle = np.mean(angles)&lt;/code&gt; looks like noise filtering. It isn&amp;rsquo;t. It is a mechanical decision: a&#xA;human finger has three joints you can move separately, and this robot finger has one string.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Not smoothing — compression&#xA;    &lt;div id=&#34;not-smoothing--compression&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#not-smoothing--compression&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;&amp;ldquo;Averaging&amp;rdquo; in a sensor pipeline usually means averaging over &lt;strong&gt;time&lt;/strong&gt; to reduce noise. Nothing here&#xA;keeps history between frames. The mean is taken &lt;strong&gt;across space&lt;/strong&gt; — over the three joints of one&#xA;finger in a single frame — to solve a problem called &lt;strong&gt;underactuation&lt;/strong&gt;.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The 3-to-1 problem&#xA;    &lt;div id=&#34;the-3-to-1-problem&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-3-to-1-problem&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Human finger&lt;/th&gt;&#xA;          &lt;th&gt;Robot finger&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Joints&lt;/td&gt;&#xA;          &lt;td&gt;3 (MCP, PIP, DIP)&lt;/td&gt;&#xA;          &lt;td&gt;3 hinges (&lt;code&gt;*_mcp&lt;/code&gt;, &lt;code&gt;*_pip&lt;/code&gt;, &lt;code&gt;*_dip&lt;/code&gt;)&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Independent actuators&lt;/td&gt;&#xA;          &lt;td&gt;many muscles; joints move semi-independently&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1 flexor tendon, 1 motor&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Degrees of freedom you can command&lt;/td&gt;&#xA;          &lt;td&gt;~3&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;1&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;A system with fewer actuators than joints is &lt;strong&gt;underactuated&lt;/strong&gt;. A single tendon threads all three&#xA;joints of each robot finger, so the only command is &lt;em&gt;&amp;ldquo;pull this string with force F&amp;rdquo;&lt;/em&gt; — and the&#xA;joints share that pull according to routing geometry and dynamics.&lt;/p&gt;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;The index finger&amp;rsquo;s single flexor tendon threaded through six labelled sites from palm to fingertip&#34;&#xA;    width=&#34;1280&#34;&#xA;    height=&#34;960&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index_hu_2b0b115de9facdf8.png&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index_hu_2b0b115de9facdf8.png 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index.png 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/tendon_routing_index.png&#34;&gt;&lt;figcaption&gt;One string, six via-points, three joints. Pull it and all three knuckles move together.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&lt;p&gt;So the vision layer must compress &lt;strong&gt;three human measurements into one robot command&lt;/strong&gt;.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/underactuation-averaging/cover.png" />
    </item>
    
    <item>
      <title>What limits the frame rate: MediaPipe, Docker and a MuJoCo viewer on one laptop</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The single-script version felt smoother than the containers. The obvious suspect was Docker. The&#xA;measurements say otherwise — and point at something more interesting: two programs sharing one&#xA;integrated GPU.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Setup&#xA;    &lt;div id=&#34;setup&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#setup&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Laptop with an Intel Comet Lake CPU (12 threads) and Intel UHD Graphics; Linux on Wayland with&#xA;XWayland; USB webcam at 640×480, 30 fps, YUYV. Each row: mean of 150 frames after a 10-frame warm-up,&#xA;no hand in view. Measured 2026-09-16.&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The result&#xA;    &lt;div id=&#34;the-result&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-result&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-f3b6ad3cd76a079ab08a776ccfdc0f69&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-f3b6ad3cd76a079ab08a776ccfdc0f69&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;bar&#39;,&#xA;data: {&#xA;  labels: [&#39;Vision alone (container)&#39;, &#39;Vision alone (host venv)&#39;, &#39;+ 1-core CPU burner&#39;, &#39;+ MuJoCo viewer (host, no Docker)&#39;, &#39;+ mujoco_twin (loop capped 30 Hz)&#39;, &#39;+ mujoco_twin (as shipped)&#39;],&#xA;  datasets: [{&#xA;    label: &#39;MediaPipe time per frame (ms)&#39;,&#xA;    data: [19.0, 19.2, 23.5, 54.9, 63.7, 85.6],&#xA;    backgroundColor: [&#39;rgba(42,120,214,0.75)&#39;,&#39;rgba(42,120,214,0.75)&#39;,&#39;rgba(42,120,214,0.75)&#39;,&#39;rgba(235,104,52,0.75)&#39;,&#39;rgba(235,104,52,0.75)&#39;,&#39;rgba(235,104,52,0.75)&#39;]&#xA;  }]&#xA;},&#xA;options: {&#xA;  indexAxis: &#39;y&#39;,&#xA;  plugins: { legend: { display: false }, title: { display: true, text: &#39;MediaPipe inference time by what else is running (30 fps budget = 33 ms)&#39; } },&#xA;  scales: { x: { min: 0, max: 100, title: { display: true, text: &#39;ms per frame&#39; } } }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Condition&lt;/th&gt;&#xA;          &lt;th&gt;Camera read&lt;/th&gt;&#xA;          &lt;th&gt;MediaPipe&lt;/th&gt;&#xA;          &lt;th&gt;&lt;code&gt;imshow&lt;/code&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Loop&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Vision alone, &lt;strong&gt;container&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;10.0 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;19.0 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;4.5 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;29.8 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Vision alone, &lt;strong&gt;host venv&lt;/strong&gt; (MediaPipe 0.10.11)&lt;/td&gt;&#xA;          &lt;td&gt;10.6 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;19.2 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;3.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;29.7 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Container vision + &lt;strong&gt;1-core CPU burner&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;8.2 ms&lt;/td&gt;&#xA;          &lt;td&gt;23.5 ms&lt;/td&gt;&#xA;          &lt;td&gt;3.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;28.1 Hz&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Host vision + &lt;strong&gt;plain MuJoCo viewer on host&lt;/strong&gt; (no Docker, no ROS)&lt;/td&gt;&#xA;          &lt;td&gt;1.6 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;54.9 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;5.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;16.0 Hz&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Container vision + &lt;strong&gt;&lt;code&gt;mujoco_twin&lt;/code&gt; capped at 30 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;1.7 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;63.7 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;8.1 ms&lt;/td&gt;&#xA;          &lt;td&gt;13.6 Hz&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Container vision + &lt;strong&gt;&lt;code&gt;mujoco_twin&lt;/code&gt; as shipped&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;1.9 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;85.6 ms&lt;/strong&gt;&lt;/td&gt;&#xA;          &lt;td&gt;10.2 ms&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;10.2 Hz&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;End to end, &lt;code&gt;ros2 topic hz /hand/target_flexions&lt;/code&gt; read &lt;strong&gt;30.0 Hz&lt;/strong&gt; with only &lt;code&gt;vision_tracker&lt;/code&gt; running and&#xA;&lt;strong&gt;10.7 Hz&lt;/strong&gt; once &lt;code&gt;mujoco_twin&lt;/code&gt; started.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/latency-benchmark/cover.png" />
    </item>
    
    <item>
      <title>Why the simulated tendon finger is a switch — and three fixes tested in MuJoCo</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  The vision layer sends a carefully normalized, smooth 0-to-1 signal. The simulated finger turns it&#xA;into two states: open and closed. This is the most important thing to understand about the twin —&#xA;and every number below was measured on the model in the repository.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The measurement&#xA;    &lt;div id=&#34;the-measurement&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-measurement&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;The same flexion was applied to all five motors, the model simulated for 3 s from rest, and the&#xA;steady-state joint angles recorded — 41 flexion values from 0 to 1.&lt;/p&gt;&#xA;&#xA;&lt;div class=&#34;chart&#34;&gt;&#xA;  &lt;canvas id=&#34;chart-21ff2680002d0af6f1365e2e937bcf1a&#34;&gt;&lt;/canvas&gt;&#xA;  &lt;script type=&#34;text/javascript&#34;&gt;&#xA;    window.addEventListener(&#34;DOMContentLoaded&#34;, (event) =&gt; {&#xA;      const ctx = document.getElementById(&#34;chart-21ff2680002d0af6f1365e2e937bcf1a&#34;);&#xA;      const chart = new Chart(ctx, {&#xA;        &#xA;type: &#39;line&#39;,&#xA;data: {&#xA;  labels: [&#39;0.000&#39;,&#39;0.025&#39;,&#39;0.050&#39;,&#39;0.075&#39;,&#39;0.100&#39;,&#39;0.125&#39;,&#39;0.150&#39;,&#39;0.175&#39;,&#39;0.200&#39;,&#39;0.225&#39;,&#39;0.250&#39;,&#39;0.275&#39;,&#39;0.300&#39;,&#39;0.325&#39;,&#39;0.350&#39;,&#39;0.375&#39;,&#39;0.400&#39;,&#39;0.425&#39;,&#39;0.450&#39;,&#39;0.475&#39;,&#39;0.500&#39;,&#39;0.525&#39;,&#39;0.550&#39;,&#39;0.575&#39;,&#39;0.600&#39;,&#39;0.625&#39;,&#39;0.650&#39;,&#39;0.675&#39;,&#39;0.700&#39;,&#39;0.725&#39;,&#39;0.750&#39;,&#39;0.775&#39;,&#39;0.800&#39;,&#39;0.825&#39;,&#39;0.850&#39;,&#39;0.875&#39;,&#39;0.900&#39;,&#39;0.925&#39;,&#39;0.950&#39;,&#39;0.975&#39;,&#39;1.000&#39;],&#xA;  datasets: [&#xA;    { label: &#39;Index finger (mean of MCP, PIP, DIP)&#39;, data: [-3.3,-3.1,-2.9,-2.8,-2.6,-2.5,-2.3,-2.1,-2.0,-1.8,-1.7,-1.5,-1.3,-1.2,-1.0,-0.8,-0.7,-0.5,-0.3,-0.2,0.0,90.2,90.4,90.6,90.8,91.0,91.2,91.5,91.7,91.9,92.1,92.2,92.4,92.6,92.8,93.0,93.2,93.4,93.5,93.7,93.9], borderColor: &#39;#2a78d6&#39;, backgroundColor: &#39;#2a78d6&#39;, borderWidth: 2, pointRadius: 1.5, tension: 0 },&#xA;    { label: &#39;Thumb CMC&#39;, data: [-2.9,-2.8,-2.6,-2.5,-2.4,-2.2,-2.1,-1.9,-1.8,-1.6,-1.5,-1.4,-1.2,-1.1,-0.9,-0.8,-0.6,-0.5,-0.3,-0.1,0.0,90.1,90.2,90.4,90.5,90.6,90.7,90.8,91.0,91.1,91.2,91.3,91.4,91.5,91.6,91.7,91.8,92.0,92.1,92.2,92.3], borderColor: &#39;#1baf7a&#39;, backgroundColor: &#39;#1baf7a&#39;, borderWidth: 2, pointRadius: 1.5, tension: 0 },&#xA;    { label: &#39;Thumb IP&#39;, data: [-2.7,-2.6,-2.5,-2.3,-2.2,-2.0,-1.9,-1.8,-1.6,-1.5,-1.4,-1.2,-1.1,-0.9,-0.8,-0.7,-0.5,-0.4,-0.3,-0.1,0.0,41.2,44.3,45.1,45.5,45.6,45.7,45.8,45.8,45.9,45.9,45.9,45.9,45.9,45.9,46.0,46.0,46.0,46.0,46.0,46.0], borderColor: &#39;#eb6834&#39;, backgroundColor: &#39;#eb6834&#39;, borderWidth: 2, pointRadius: 1.5, tension: 0 }&#xA;  ]&#xA;},&#xA;options: {&#xA;  plugins: { title: { display: true, text: &#39;Simulated joint bend vs flexion — flat, then a wall at 0.5&#39; } },&#xA;  scales: { x: { title: { display: true, text: &#39;flexion command (0.5 = 0 N)&#39; } }, y: { min: -10, max: 100, title: { display: true, text: &#39;joint bend (°)&#39; } } }&#xA;}&#xA;&#xA;      });&#xA;    });&#xA;  &lt;/script&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Flexion&lt;/th&gt;&#xA;          &lt;th&gt;Force&lt;/th&gt;&#xA;          &lt;th&gt;Index MCP / PIP / DIP&lt;/th&gt;&#xA;          &lt;th&gt;Thumb CMC / MP / IP&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;0.000&lt;/td&gt;&#xA;          &lt;td&gt;+50.0 N&lt;/td&gt;&#xA;          &lt;td&gt;−3.3° / −3.2° / −3.3°&lt;/td&gt;&#xA;          &lt;td&gt;−2.9° / −2.7° / −2.7°&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;0.250&lt;/td&gt;&#xA;          &lt;td&gt;+25.0 N&lt;/td&gt;&#xA;          &lt;td&gt;−1.7° / −1.6° / −1.7°&lt;/td&gt;&#xA;          &lt;td&gt;−1.5° / −1.3° / −1.4°&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;0.500&lt;/td&gt;&#xA;          &lt;td&gt;0.0 N&lt;/td&gt;&#xA;          &lt;td&gt;0.0° / 0.0° / 0.0°&lt;/td&gt;&#xA;          &lt;td&gt;0.0° / 0.0° / 0.0°&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;0.625&lt;/td&gt;&#xA;          &lt;td&gt;−12.5 N&lt;/td&gt;&#xA;          &lt;td&gt;91.1° / 91.0° / 91.0°&lt;/td&gt;&#xA;          &lt;td&gt;90.6° / 53.9° / 45.6°&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;1.000&lt;/td&gt;&#xA;          &lt;td&gt;−50.0 N&lt;/td&gt;&#xA;          &lt;td&gt;94.0° / 93.8° / 93.9°&lt;/td&gt;&#xA;          &lt;td&gt;92.3° / 54.2° / 46.0°&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;&lt;em&gt;Joint limits are 90°; values just above it are MuJoCo&amp;rsquo;s soft limit being pressed.&lt;/em&gt;&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/tendon-physics-switch/cover.png" />
    </item>
    
    <item>
      <title>Why the thumb needs its own thresholds in hand tracking</title>
      <link>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/</link>
      <pubDate>Wed, 16 Sep 2026 00:00:00 +0000</pubDate>
      <author>contact@mulhamfetna.com (Mulham Fetna)</author>
      <guid>https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Squeeze your thumb across your palm as hard as you can and, by the finger thresholds, it is only&#xA;half closed. The thumb isn&amp;rsquo;t a finger with a shorter bone — it&amp;rsquo;s a different joint.&#xA;&lt;/div&gt;&#xA;&#xA;&lt;figure&gt;&lt;img&#xA;    class=&#34;my-0 rounded-md&#34;&#xA;    loading=&#34;lazy&#34;&#xA;    decoding=&#34;async&#34;&#xA;    fetchpriority=&#34;auto&#34;&#xA;    alt=&#34;Live: thumb tucked across the palm, fingers open — only pull_thumb goes negative at −19.5 N&#34;&#xA;    width=&#34;1570&#34;&#xA;    height=&#34;480&#34;&#xA;    src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled_hu_3439f03a327be93f.jpg&#34;&#xA;    srcset=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled_hu_3439f03a327be93f.jpg 800w, https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled_hu_eed9092295f33e0d.jpg 1280w&#34;&#xA;    sizes=&#34;(min-width: 768px) 50vw, 65vw&#34;&#xA;    data-zoom-src=&#34;https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/live_thumb_curled.jpg&#34;&gt;&lt;figcaption&gt;Thumb curled, fingers open. Only &lt;code&gt;pull_thumb&lt;/code&gt; goes negative (−19.5 N, flexion 0.69); every other motor pushes open.&lt;/figcaption&gt;&lt;/figure&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Hinges versus a saddle&#xA;    &lt;div id=&#34;hinges-versus-a-saddle&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#hinges-versus-a-saddle&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Index to pinky&lt;/strong&gt; are chains of &lt;strong&gt;hinge joints&lt;/strong&gt;. A fist rolls them into a tight spiral, each&#xA;knuckle approaching 90°.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;The thumb&lt;/strong&gt; hangs from the &lt;strong&gt;carpometacarpal (CMC) saddle joint&lt;/strong&gt; at the wrist. It &lt;em&gt;sweeps&lt;/em&gt;&#xA;across the palm — opposition — instead of simply curling. Much of &amp;ldquo;closing the thumb&amp;rdquo; is rotation&#xA;of the whole thumb, not bending at its knuckles.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;What MediaPipe measures because of it&#xA;    &lt;div id=&#34;what-mediapipe-measures-because-of-it&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#what-mediapipe-measures-because-of-it&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Interior angles come from 3D landmark positions, so the sweep only partly shows up as knuckle bend:&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;&lt;/th&gt;&#xA;          &lt;th&gt;Open&lt;/th&gt;&#xA;          &lt;th&gt;Fully closed&lt;/th&gt;&#xA;          &lt;th&gt;Travel&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Finger — mean of MCP/PIP/DIP&lt;/td&gt;&#xA;          &lt;td&gt;≈ 3.10 rad&lt;/td&gt;&#xA;          &lt;td&gt;≈ 1.60 rad&lt;/td&gt;&#xA;          &lt;td&gt;1.50 rad&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Thumb — mean of CMC/MCP/IP&lt;/td&gt;&#xA;          &lt;td&gt;≈ 2.90 rad&lt;/td&gt;&#xA;          &lt;td&gt;≈ 2.30 rad&lt;/td&gt;&#xA;          &lt;td&gt;&lt;strong&gt;0.60 rad&lt;/strong&gt;&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;A fully closed thumb stops near &lt;strong&gt;2.30 rad (~132°)&lt;/strong&gt;. Even its &lt;em&gt;open&lt;/em&gt; angle sits below a finger&amp;rsquo;s —&#xA;a relaxed thumb is never in line with the wrist.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://mulhamfetna.com/projects/ros2-tendon-driven-hand-mujoco-digital-twin-vision-teleoperation/thumb-thresholds/cover.png" />
    </item>
    
  </channel>
</rss>
