Files
animation/docs/iclone-bridge.md
T
jeremy 3ba86b2ea8 feat: clothing lane, character sources, and DCC bridges
Bulk import of the working lanes that were living untracked on the PC.

Content:
- characters/  Lena/male body lanes, bakes, texture work, run logs
- clothing/    garment pipeline, configs, gates, contract docs
- garments/    MD-authored garment sources (.zprj/.zpac)
- UAL-Lib/     Universal Animation Library 2 source (.blend/.fbx/.glb)
- tools/       blender_bridge, iclone_bridge, md_bridge, tailor, glm_agent
- docs/, plans/, dev/, .agents/plans/

Repo hygiene:
- .gitattributes: LFS now covers .blend, .zprj, .zpac, .obj, .npy and the
  Reallusion .iAvatar/.ccAvatar/.ccRestore containers. Without this the
  ~3.8 GB in this commit would land as raw blobs. .png/.jpg are left out
  on purpose — ~250 are already tracked raw and converting them would
  rewrite every one without shrinking history.
- .gitignore: exclude /accurig/ (~1 GB AccuRig program files, redistributable
  from Reallusion, nothing authored here) and /dev/null/ (git-lfs hook copies
  dropped by a `>/dev/null` redirect on Windows).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 15:55:43 -07:00

5.8 KiB

iClone bridge

A terminal-to-iClone bridge: drive iClone 8 from the shell (query the scene, run RLPy code, trigger exports) without clicking through the UI.

iClone 8's Python API (RLPy) only runs inside iClone -- there is no external/network API. TinqsBridge is a small auto-load iClone plugin that opens a localhost TCP socket server inside iClone and executes whatever Python source is sent to it, on iClone's own Qt main thread.

Pieces

  • tools/iclone_bridge/TinqsBridge/main.py -- the plugin source of truth (lives in this repo, gets copied into iClone).
  • tools/install_iclone_bridge.ps1 -- copies the plugin into iClone's auto-load folder.
  • tools/iclone_bridge.py -- the terminal client (Python 3.12, stdlib only).

Install / update

powershell -File tools\install_iclone_bridge.ps1

This copies tools/iclone_bridge/TinqsBridge/ to A:\Program Files (x86)\iClone 8\Bin64\OpenPlugin\TinqsBridge\, overwriting any existing copy. It is idempotent and only ever touches the TinqsBridge subfolder -- other plugins under OpenPlugin\ (AIStudio, MotionLIVE, VideoMocap) are never touched.

iClone must be restarted after every install/update to load the new plugin code -- there is no live-reload of main.py on disk. (You can hot-swap runtime behavior once the bridge is up, by --exec-ing new code through it -- see "Persistent namespace" below -- but that only affects the running session, not what gets loaded on the next restart.)

If iClone prompts with a Windows Firewall dialog for a 127.0.0.1 bind, allow it -- the bind is localhost-only either way.

CLI usage

python tools/iclone_bridge.py --ping
python tools/iclone_bridge.py --exec "result = 1 + 1"
python tools/iclone_bridge.py --file some_script.py
python tools/iclone_bridge.py --port 18801 --timeout 120 --exec "..."
  • --ping checks the bridge is alive and prints the product name, iClone version, and the embedded Python version (via RApplication + sys.version).
  • --exec CODE runs a snippet of Python source inside iClone.
  • --file PATH runs a .py file's contents inside iClone.
  • --port (default 18800) must match TINQS_ICLONE_BRIDGE_PORT if you've overridden it for the iClone process's environment.
  • --timeout (default 30 seconds) is the client socket timeout.

On a connection failure the client exits 1 with a hint to check that iClone is running and was restarted after install. On an exec-level error (an exception raised by your code inside iClone) it also exits 1, printing the traceback captured from iClone.

Example: list scene objects

python tools/iclone_bridge.py --exec "result = [o.GetName() for o in RLPy.RScene.GetAvatars()] + [o.GetName() for o in RLPy.RScene.GetProps()]"

The result convention

Whatever code you send is exec()'d inside iClone. If it sets a variable named result, that becomes the JSON result field of the response (falling back to repr() if it isn't JSON-serializable -- RLPy objects generally aren't). If your code doesn't set result, the response's result is null. stdout/stderr printed during execution are captured and returned as the response's stdout field.

Persistent namespace

All code you send runs against one shared namespace dict that persists for the life of the iClone session (until iClone is restarted or the plugin reloaded). This means:

python tools/iclone_bridge.py --exec "x = 41"
python tools/iclone_bridge.py --exec "result = x + 1"   # -> 42

variables and imports from one --exec/--file call are visible to the next one. RLPy is pre-imported into the namespace. Each new client connection is a fresh TCP connection, but the iClone-side namespace is not reset per-connection.

Error resilience

An exception in your code (e.g. 1/0) produces ok: false with the full traceback in error -- the bridge itself keeps running and will serve the next request normally; you don't need to restart iClone after a bad --exec.

Log file

%TEMP%\tinqs_iclone_bridge.log -- append-only, timestamped lines for: plugin startup, the bind result (and port), each request's id + ok/error status, and shutdown. If --ping can't connect, or the plugin doesn't seem to be running after a restart, check this file first. If it's not being created at all, the plugin likely failed to load -- check iClone's Script > Console Log window too.

Threading rule (read this before extending the plugin)

RLPy is not thread-safe and must only be touched on iClone's Qt main thread. In main.py:

  • The socket-accept thread and per-connection handler threads (_accept_loop, _handle_conn) only move bytes and put/get on a queue.Queue. They never call RLPy or touch Qt objects.
  • A QTimer created in initialize_plugin() (which itself runs on the main thread) polls that queue every ~50 ms and does all the actual exec() / RLPy work in _drain_queue() / _execute().

If you add new functionality to the plugin, keep any RLPy-touching code inside that QTimer callback path. Calling RLPy from a socket thread can crash or corrupt iClone.

Long-running code freezes iClone's UI

Submitted code runs synchronously on the Qt main thread. iClone's UI (and the bridge itself, since the same timer that answers other requests is blocked) will freeze for the duration of your --exec/--file call. This is fine for anything that takes a few seconds (an FBX export blocks the UI anyway) but avoid submitting code with long sleeps or unbounded loops.

Environment variable

  • TINQS_ICLONE_BRIDGE_PORT -- set in iClone's process environment before launch to override the default port 18800. Pass the matching --port to the client.

Security note

The bridge executes arbitrary Python with full RLPy access. It only binds to 127.0.0.1 -- there is no authentication beyond "only processes on this machine can connect." Do not change the bind address.