|
| 3D viewport | Orbit / pan / zoom (OrbitControls), presets (Front / Right / Top / Iso), one-click Fit. |
| Render modes | Visual, Collision, or Both — see Render modes → |
| Frames & inertia | Per-link TF axes (off / selected / all) and inertia ellipsoids + CoM markers. |
| Configurable up axis | +X, +Y, +Z — grid and camera adjust together. |
| Wireframe overlay | Spot cracks, inverted normals, high-poly collision meshes. |
🦾 Driving
| Joint sliders | Live sliders + numeric inputs honoring <limit> for revolute, continuous, prismatic. |
| Mimic propagation | <mimic> joints follow their master, with limit-clamp bypass so propagation isn't truncated. |
| Ignore limits | One-click bypass of every joint's <limit> for exploration. |
| Search & filter | Substring filter, only modified toggle. |
| Named states | SRDF <group_state> blocks appear in the bookmark dropdown — see Joints → |
🩺 Analysing
| Checks panel | Every parse error, missing mesh, malformed inertia, joint cycle. Catalog → |
| Link tree & inspector | Click anywhere on the robot or tree → see joints, mass, CoM, inertia tensor, mesh paths. |
| Diagnostics in VS Code | Same checks surface in the Problems panel with line numbers. |
🛠 Authoring
| Source editor (CodeMirror 6) | URDF/xacro syntax highlighting, live edit ↔ 3D preview (debounced ~160 ms), intelligent completion (link/joint/joint-type/package:// names), and inline lint markers. Toggle "Edit: on" from the Source tab. |
| Lint rule engine | 18 rules covering structural (R-001..005), physics (P-001..006), assets (A-001..003), and xacro style (S-001..005). Grouped Checks panel with health score (0-100). |
| Quick fixes | One-click fixes for missing <limit>, missing <inertial>, negative mass, zero effort/velocity, and stray limit on continuous joints. |
| Fullscreen source view | F11 (or Ctrl+Shift+F) flips the source pane to full width with the 3D viewport shrunk to a picture-in-picture corner — read tight URDF on a small screen. |
| 3D screenshot | Tools tab → Save PNG writes a transparent-background snapshot of the current viewport at 1×/2×/3×/4× scale; Copy to clipboard for instant paste into PRs/docs. |
| Reachability sampling | Monte-Carlo workspace point cloud for any tip link. |
| Never-colliding pairs | Sample for <disable_collisions> entries → write merged SRDF. |
| Pose & bookmarks | Save pose, name bookmarks, restore on next open. |
| Export | JSON pose with camera; PDF report bundling screenshot + checks + summary; BOM CSV. |
🤖 ROS / URDF / xacro
| xacro expansion | xacro:include, xacro:macro, xacro:arg, load_yaml, Python ternary / ** / slice rewrites. |
package:// URIs |
Auto-discovered from every package.xml in scope. |
| Mesh formats | STL · COLLADA · OBJ · glTF · GLB. DAE / GLTF external assets pre-resolved to blob URLs. |
| SRDF | Joint groups, named states, disable_collisions. |
Tested models
End-to-end Playwright smoke test against the upstream
franka_description
package. Reproducer:
git clone https://github.com/frankarobotics/franka_description /tmp/franka_description
npm run web:build
FRANKA_DIR=/tmp/franka_description node scripts/test-franka.mjs
| Robot | Source | Result |
|---|---|---|
Franka Research 3 (fr3) |
franka_description | ✅ 8 joints · 25 links · 0 errors · 0 warnings · ~600 ms |
Franka Research (fer) |
franka_description | ✅ 8 joints · 25 links · 0 errors · 0 warnings · ~800 ms |
Franka Production 3 (fp3) |
franka_description | ✅ 8 joints · 25 links · 0 errors · 0 warnings · ~600 ms |
The whole pipeline runs in the browser: directory pick, xacro expansion
(including load_yaml for joint limits / inertials YAMLs), package
resolution, mesh blob URL allocation, Three.js render.
Configuration
Both targets expose the same five settings.
| Setting | Default | Effect |
|---|---|---|
| Default render mode | visual |
Geometry layer on first load. |
| Up axis | +Z |
World up axis used by camera and grid. |
| Default xacro args | {} |
Args merged into every xacro file. |
| Extra package roots | [] |
Extra package.xml scan roots. |
| Semantic files | [] |
SRDF / YAML semantic files. |
Web: ⚙ button in the topbar → JSON in localStorage.
VS Code: urdfStudio.* keys in settings.json.
Details: docs/features/settings →
Architecture
A single TypeScript codebase produces both targets:
src/core/ — pure logic, no fs/path/DOM imports
▲
┌─────────────┴─────────────┐
io.node.ts ioBrowser.ts
(jsdom + node:fs) (FileSystemAccess + native DOM)
▲ ▲
src/extension.ts src/web/host.ts
▲ ▲
└─────────── postMessage ──────┘
▼
src/renderer/main.ts — Three.js + URDFLoader, identical
The core never touches Node-only modules directly; it queries a
CoreIo interface set by the host. The renderer is bundled separately
and is identical on both targets — the only thing that differs is the
postMessage source.
Deep dive: docs/architecture →
Local development
git clone https://github.com/deyuf/urdf-studio
cd urdf-studio
npm ci
Common loops:
# VS Code extension
npm run watch # incremental rebuild; press F5 in VS Code
# Web app
npm run web:dev # http://127.0.0.1:5173 with HMR
# Docs only
npm run docs:watch # rebuild dist-web/docs on every .md change
# Tests
npm run test:unit # 24 node:test cases on src/core
npx playwright test # 19 renderer + web shell specs
# Real-world smoke
FRANKA_DIR=/tmp/franka_description node scripts/test-franka.mjs
Production builds:
npm run package # VS Code extension (dist/)
npm run web:build # web app + docs (dist-web/)
npm run vsce:package # .vsix for sideload / Marketplace
# then: code --install-extension urdf-studio-*.vsix
More: docs/development/building →
Contributing & license
PRs welcome.
License: MIT.