94 lines
4.7 KiB
Markdown
94 lines
4.7 KiB
Markdown
|
|
# Marvelous Designer Bridge (TinqsMDBridge)
|
||
|
|
|
||
|
|
Live control of Marvelous Designer's Python scripting API from the terminal,
|
||
|
|
sibling of the iClone bridge (`docs/iclone-bridge.md`): a socket server
|
||
|
|
plug-in inside the app + a thin JSON-over-TCP client.
|
||
|
|
|
||
|
|
**Key architectural difference from the iClone bridge, found the hard way
|
||
|
|
(2026-07-30, MD 2026 Personal):** MD's embedded Python (3.11.8) does NOT
|
||
|
|
schedule background threads while MD idles, and ships no Python Qt binding —
|
||
|
|
so both the daemon-thread server and the QTimer executor patterns are dead on
|
||
|
|
arrival (a threaded server binds, the OS accepts connections, and every
|
||
|
|
request times out). Instead the bridge is a **blocking main-thread serve
|
||
|
|
loop**: clicking the plugin starts a *bridge session* during which **MD's UI
|
||
|
|
is frozen** ("Not Responding" is normal) and all api calls run on the main
|
||
|
|
thread. The session ends via `--stop` or an idle timeout (default 14400 s / 4 h —
|
||
|
|
the escape hatch, since a frozen UI can't be clicked).
|
||
|
|
|
||
|
|
| | iClone bridge | MD bridge |
|
||
|
|
|---|---|---|
|
||
|
|
| Plug-in source | `tools/iclone_bridge/TinqsBridge/main.py` | `tools/md_bridge/TinqsMDBridge.py` |
|
||
|
|
| Client | `tools/iclone_bridge.py` | `tools/md_bridge.py` |
|
||
|
|
| Port | 18800 | **18900** (`TINQS_MD_BRIDGE_PORT` overrides) |
|
||
|
|
| Install | ps1 copies to OpenPlugin, auto-loads | **manual register**, click = start session |
|
||
|
|
| Executor | QTimer on Qt main thread, app stays live | main-thread serve loop, **UI frozen during session** |
|
||
|
|
| Session end | app exit | `--stop`, or idle `TINQS_MD_BRIDGE_IDLE` (4 h default) |
|
||
|
|
| Embedded Python | 3.8 | 3.11.8 |
|
||
|
|
| Log | `%TEMP%\tinqs_iclone_bridge.log` | `%TEMP%\tinqs_md_bridge.log` |
|
||
|
|
|
||
|
|
## Install (one-time)
|
||
|
|
|
||
|
|
1. Marvelous Designer > **Plugin** tab > **Plug-in Manager** > **+ ADD**
|
||
|
|
2. Select `tools/md_bridge/TinqsMDBridge.py`, name it `TinqsMDBridge`, OK.
|
||
|
|
|
||
|
|
âš Unverified: whether the Plug-in Manager references the .py in place or
|
||
|
|
copies it. `--ping` returns a `source` field — if it isn't the repo path,
|
||
|
|
MD copied it and **every edit to the plug-in needs remove + re-ADD**. If a
|
||
|
|
click appears to run stale code (old prints, old behavior), re-register.
|
||
|
|
|
||
|
|
## Session workflow
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# 1. In MD: click Plugin > TinqsMDBridge (UI freezes -- session active)
|
||
|
|
python tools/md_bridge.py --ping # 2. verify
|
||
|
|
python tools/md_bridge.py --exec "result = dir(pattern_api)"
|
||
|
|
python tools/md_bridge.py --file draft_tshirt.py
|
||
|
|
python tools/md_bridge.py --timeout 300 --exec "utility_api.Simulate(200)"
|
||
|
|
python tools/md_bridge.py --stop # 3. unfreeze MD
|
||
|
|
```
|
||
|
|
|
||
|
|
Wire protocol and `result` convention are identical to the iClone bridge:
|
||
|
|
newline-delimited JSON, one request per connection; set `result` in submitted
|
||
|
|
code to get a JSON value back; stdout/stderr captured; exceptions return
|
||
|
|
`ok:false` + traceback without ending the session. The exec namespace
|
||
|
|
persists across requests within a session (NOT across sessions) and is
|
||
|
|
pre-seeded with:
|
||
|
|
|
||
|
|
- MD api modules that imported successfully: `import_api`, `export_api`,
|
||
|
|
`pattern_api`, `fabric_api`, `utility_api`, `ApiTypes` (all 6 confirmed
|
||
|
|
importable in MD 2026 Personal)
|
||
|
|
- `BRIDGE` — info dict: `version`, `mode` ("mainloop"), `port`,
|
||
|
|
`idle_timeout_s`, `source`, `api_modules`, `api_missing`, `python`
|
||
|
|
|
||
|
|
## Day-1 checklist (next session)
|
||
|
|
|
||
|
|
```bash
|
||
|
|
python tools/md_bridge.py --ping # mode=mainloop, source path?
|
||
|
|
python tools/md_bridge.py --exec "result = {m: [f for f in dir(globals()[m]) if not f.startswith('_')] for m in BRIDGE['api_modules']}"
|
||
|
|
# dump the REAL api surface (docs are incomplete)
|
||
|
|
python tools/md_bridge.py --file tools/md_bridge/smoke_test.py --timeout 180
|
||
|
|
# signatures + square-of-fabric + Simulate + snapshot
|
||
|
|
python tools/md_bridge.py --stop
|
||
|
|
```
|
||
|
|
|
||
|
|
Open question the smoke test answers: do viewport snapshots
|
||
|
|
(`ExportSnapshot3D`) render while the Qt event loop is blocked? If not,
|
||
|
|
plan B is pumping events from inside the loop or snapshotting after --stop.
|
||
|
|
|
||
|
|
Then the real milestones:
|
||
|
|
- import `Ariki_Female_QuatSkin` as FBX avatar (Blender-convert the GLB
|
||
|
|
first), drape a rectangle, snapshot.
|
||
|
|
- t-shirt + shorts from `tools/tailor/lena_measurements.json` drafts.
|
||
|
|
- EveryWear rig + GLB export, check bone names == Quaternius.
|
||
|
|
|
||
|
|
## Gotchas
|
||
|
|
|
||
|
|
- **A session must be started by a human click** in MD — plan work in
|
||
|
|
batches, and always `--stop` when done so Jeremy gets his UI back.
|
||
|
|
- Long `Simulate()` calls: raise client `--timeout`; the idle timer only
|
||
|
|
resets when a request *completes*, so a 4-minute drape is safe but two
|
||
|
|
6-minute gaps in a row end the session.
|
||
|
|
- Windows may dim the MD window and offer to kill it — decline; the
|
||
|
|
process is healthy.
|
||
|
|
- Port clash: 18900 chosen to coexist with iClone's 18800.
|