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>
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
# 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
|
||||
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.
|
||||
Reference in New Issue
Block a user