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>
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 "..."
--pingchecks the bridge is alive and prints the product name, iClone version, and the embedded Python version (viaRApplication+sys.version).--exec CODEruns a snippet of Python source inside iClone.--file PATHruns a.pyfile's contents inside iClone.--port(default18800) must matchTINQS_ICLONE_BRIDGE_PORTif you've overridden it for the iClone process's environment.--timeout(default30seconds) 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 aqueue.Queue. They never callRLPyor touch Qt objects. - A
QTimercreated ininitialize_plugin()(which itself runs on the main thread) polls that queue every ~50 ms and does all the actualexec()/RLPywork 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 port18800. Pass the matching--portto 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.