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,108 @@
|
||||
# Blender Bridge (TinqsBlenderBridge)
|
||||
|
||||
Live control of a **running, interactive** Blender session from the terminal —
|
||||
third sibling of the iClone (`docs/iclone-bridge.md`) and MD
|
||||
(`docs/md-bridge.md`) bridges: a socket server inside the app + a thin
|
||||
JSON-over-TCP client, same wire protocol and `result` convention.
|
||||
|
||||
Distinct from the headless pipeline scripts (`tools/cc_retarget.py`,
|
||||
`tools/tailor/*`), which spawn `blender --background` and exit. Those never
|
||||
touch the open GUI. This bridge is for driving the window Jeremy is
|
||||
*looking at* — inspecting a selection, nudging a mesh, checking a drape.
|
||||
|
||||
**The good news vs. MD:** Blender has `bpy.app.timers`, so the server polls a
|
||||
non-blocking socket from a main-thread timer. **The UI stays fully
|
||||
interactive** — no frozen-window session like MD. The UI only stalls for the
|
||||
duration of a single `exec`.
|
||||
|
||||
| | iClone bridge | MD bridge | Blender bridge |
|
||||
|---|---|---|---|
|
||||
| Server source | `tools/iclone_bridge/TinqsBridge/main.py` | `tools/md_bridge/TinqsMDBridge.py` | `tools/blender_bridge/tinqs_blender_bridge.py` |
|
||||
| Client | `tools/iclone_bridge.py` | `tools/md_bridge.py` | `tools/blender_bridge.py` |
|
||||
| Port | 18800 | 18900 | **19000** (`TINQS_BLENDER_BRIDGE_PORT` overrides) |
|
||||
| Install | ps1 → OpenPlugin, auto-loads | manual register, click = session | ps1 → `scripts/startup/`, auto-starts |
|
||||
| Executor | QTimer on Qt main thread | main-thread loop, **UI frozen** | `bpy.app.timers`, **UI stays live** |
|
||||
| Session end | app exit | `--stop` / 4 h idle | `--stop`, or app exit (no idle timeout) |
|
||||
| Embedded Python | 3.8 | 3.11.8 | 3.13.9 (Blender 5.1.2) |
|
||||
| Log | `%TEMP%\tinqs_iclone_bridge.log` | `%TEMP%\tinqs_md_bridge.log` | `%TEMP%\tinqs_blender_bridge.log` |
|
||||
|
||||
## Install (one-time)
|
||||
|
||||
```powershell
|
||||
powershell -File tools\install_blender_bridge.ps1
|
||||
```
|
||||
|
||||
Copies the server into every `%APPDATA%\Blender Foundation\Blender\<ver>\scripts\startup\`.
|
||||
Blender imports startup modules at launch and calls their `register()`, so
|
||||
**the bridge auto-starts on port 19000 from the next launch onward** — zero
|
||||
clicks, forever.
|
||||
|
||||
**To start it in an ALREADY-OPEN Blender** (a session that predates the
|
||||
install — the common case, and one that matters when the window has unsaved
|
||||
work you don't want to lose to a restart):
|
||||
|
||||
> Scripting workspace → **Open** → `tools\blender_bridge\tinqs_blender_bridge.py` → **Run Script** (▶)
|
||||
|
||||
Re-running is safe: a singleton on `builtins` closes the previous socket and
|
||||
retires its timer before rebinding, so no leak and no "port busy" on reload.
|
||||
|
||||
## Workflow
|
||||
|
||||
```bash
|
||||
python tools/blender_bridge.py --ping # blender ver, open .blend
|
||||
python tools/blender_bridge.py --exec "result = [o.name for o in bpy.data.objects]"
|
||||
python tools/blender_bridge.py --file edit_mesh.py
|
||||
python tools/blender_bridge.py --timeout 300 --file long_bake.py
|
||||
python tools/blender_bridge.py --stop # server off, Blender lives on
|
||||
```
|
||||
|
||||
⚠ **Use `--file`, not `--exec`, for anything with quotes.** PowerShell strips
|
||||
inner double quotes from native-command args, so
|
||||
`--exec "bpy.data.objects[\"Cube\"]"` arrives as `bpy.data.objects[Cube]` →
|
||||
`NameError: name 'Cube' is not defined`. Write a scratchpad .py and use
|
||||
`--file`.
|
||||
|
||||
The exec namespace persists across requests within a Blender session and is
|
||||
pre-seeded with `bpy`, `view3d_override()`, and a `BRIDGE` info dict.
|
||||
|
||||
## The context trap (read this before using bpy.ops)
|
||||
|
||||
Timer callbacks run with **no window/area in `bpy.context`**, so operators
|
||||
that need one fail with *"context is incorrect"*. Two ways out:
|
||||
|
||||
1. **Prefer the data API** — `bpy.data`, `obj.location`, `mesh.vertices[i].co`,
|
||||
or `bmesh` for topology. Context-free, and the right tool anyway.
|
||||
2. **Override when an operator truly needs a viewport** — the seeded helper
|
||||
returns the kwargs:
|
||||
|
||||
```python
|
||||
with bpy.context.temp_override(**view3d_override()):
|
||||
bpy.ops.object.shade_smooth()
|
||||
```
|
||||
|
||||
## Verified (2026-08-04, Blender 5.1.2, Python 3.13.9)
|
||||
|
||||
Smoke-tested end to end in a throwaway GUI instance: socket up ~3 s after
|
||||
launch; `--ping`; direct vertex edit; `bmesh` subdivide (8 → 26 verts);
|
||||
`shade_smooth` via `temp_override`; `--stop` closed the port cleanly and
|
||||
**Blender stayed alive and responsive**.
|
||||
|
||||
## Two bugs found and fixed during that verification
|
||||
|
||||
Both are the kind that silently produce "connection refused", so they're
|
||||
worth remembering if the bridge is ever ported or rewritten:
|
||||
|
||||
1. **`bpy.data` is restricted during startup registration.** Modules in
|
||||
`scripts/startup/` register while `bpy.data` is still a `_RestrictData`
|
||||
stub — reading `bpy.data.filepath` there raises
|
||||
`AttributeError: '_RestrictData' object has no attribute 'filepath'`,
|
||||
which aborts `register()` and the socket never binds, leaving *no log
|
||||
file at all*. Fix: `register()` only schedules a one-shot
|
||||
`bpy.app.timers` callback; the real start runs after boot, when the full
|
||||
API is live. Diagnose this class of failure with
|
||||
`blender --background --python-expr "print('x')"` — startup tracebacks
|
||||
print to stdout. (Do **not** pass `--factory-startup`; it disables user
|
||||
scripts and hides the very thing you're testing.)
|
||||
2. **Unregistering a timer from inside its own callback is an error.** The
|
||||
`__STOP__` path called `stop()` from within `_poll`. Fix: `stop(in_timer=True)`
|
||||
just closes the socket and lets the callback's `return None` retire the timer.
|
||||
Reference in New Issue
Block a user