Control Blender 4.0+ from your FlexBar. Run Python snippets, trigger playback, scrub frames, toggle viewport modes — all from physical keys.
The bridge has two halves that talk over a local TCP socket:
FlexBar ──► FlexDesigner Plugin (Node.js) ──► Blender Addon (Python) ──► bpy
Both halves must be installed: the Blender addon (serves Blender over loopback) and the FlexDesigner plugin (drives your FlexBar keys).
| Component | Minimum version |
|---|---|
| Blender | 4.0 |
| FlexDesigner | 1.0.0 |
Building from source additionally needs Node.js 18+ and npm 8+ — see Development.
Download the two assets from the latest release:
FlexBar-Bridge.zip— the Blender addoncom.gpijat.blender.flexplugin— the FlexDesigner plugin
- Open Blender → Edit → Preferences → Add-ons
- Click Install from Disk… (top-right) and select
FlexBar-Bridge.zip - Tick the checkbox next to FlexBar Bridge to enable it
- In the 3D Viewport, press N, open the FlexBar tab, and click Start (default port 8765). Status should read Running.
The server binds only to 127.0.0.1 (loopback) — it is never reachable from the network.
Optional: in Preferences → Add-ons → FlexBar Bridge, enable Auto Start to launch the server every time Blender opens.
Install com.gpijat.blender.flexplugin in FlexDesigner — double-click it, or use FlexDesigner → Plugins → Install from file. Once published, you can also install it from FlexGate by searching for "Blender Bridge".
In FlexDesigner, open Plugin Settings → Blender Bridge:
| Setting | Default | Description |
|---|---|---|
| Blender Host | 127.0.0.1 |
Keep it loopback |
| Port | 8765 |
Must match the port in Blender's N-panel |
| Poll Interval | 500 ms |
How often polled keys (display, toggle…) refresh |
| Reconnect Delay | 1000 ms |
Delay before reconnecting after a disconnect |
| Auto-connect | ✓ | Connect to Blender when FlexDesigner starts |
| Notify on error | ✓ | Show on-device error messages by default |
Click Refresh to check the connection. A green chip means the bridge is live.
Add any of these from the FlexBar key library and configure them in FlexDesigner.
- Command — run a Python snippet when tapped.
bpy,C(bpy.context), andD(bpy.data) are pre-injected. Use the Test Command button to preview a command's return value before assigning it. - Preset — one-tap built-in actions (prev/next frame, render, frame selected…) or your own snippet.
- Toggle — flip a boolean (X-ray, overlays, snap, auto-keyframe…) with live on/off visual state.
- Cycle — step through a set of states (object mode, shading type, pivot point…).
- Display — read-only live readout (current frame, scene name, selection count…).
- Timeline — a scrubbable timeline page: tap to open, touch to scrub, Return to exit.
- Slider — drag to set a bounded value (render %, influence, camera lens…).
- Wheel — rotate to increment/decrement a value (frame, Z height…).
| Command | Effect |
|---|---|
bpy.ops.screen.animation_play() |
Toggle playback |
bpy.context.scene.frame_current = 1 |
Jump to frame 1 |
bpy.ops.object.select_all(action='DESELECT') |
Deselect all |
bpy.ops.render.render(write_still=True) |
Render still |
Multi-line snippets work too:
import bpy
for obj in bpy.context.selected_objects:
obj.location.z += 1Status shows "Error: [Errno 98] Address already in use" Another process is using port 8765. Stop it, or change the port in the N-panel and update the FlexDesigner config to match.
Status shows "Running" but FlexDesigner says "Disconnected"
- Make sure the port in FlexDesigner matches Blender's N-panel.
- Restart the addon server (Stop → Start).
- After restarting FlexDesigner, the plugin reconnects automatically within a few seconds.
"Protocol version mismatch" snackbar The plugin and addon are out of sync. Re-install both from the same release.
Commands execute but nothing happens in Blender
Some bpy.ops require a specific context (active object, edit mode, etc.). Add a context.temp_override(), or check the Blender console for the full error.
Blender freezes briefly when many keys fire at once
The addon drains up to 8 commands per 10 ms timer tick; heavy workloads queue briefly. The queue cap is 32 — excess commands return a Busy error and the plugin shows a snackbar.
Build from source and link the plugin live:
npm install
npm run build # bundle src/ → com.gpijat.blender.plugin/backend/plugin.cjs
npm run dev # link the plugin into FlexDesigner + watch/rebuildnpm run dev hot-reloads on changes to src/, manifest.json, and ui/*.vue.
Pack a distributable .flexplugin yourself with npm run plugin:pack that you can install using npm run plugin:install.
For the Blender addon, re-zip after changes:
Compress-Archive -Path blender_addon -DestinationPath FlexBar-Bridge.zip -Force # Windowszip -r FlexBar-Bridge.zip blender_addon -x "blender_addon/__pycache__/*" # macOS / LinuxFor quick iteration on pure-Python changes that don't touch bl_info or register(), use Blender's F3 → Reload Scripts instead of reinstalling.
With the addon running, this script exercises the handshake and eval path directly:
import socket, struct, json
def send(sock, obj):
p = json.dumps(obj).encode()
sock.sendall(struct.pack(">I", len(p)) + p)
def recv(sock):
n = struct.unpack(">I", sock.recv(4))[0]
return json.loads(sock.recv(n))
s = socket.create_connection(("127.0.0.1", 8765))
send(s, {"type": "hello", "id": 1, "protocol": 1})
print("handshake:", recv(s))
send(s, {"type": "eval", "id": 2, "code": "bpy.context.scene.frame_current"})
print("frame: ", recv(s))
s.close()- Transport: multiplexed length-prefixed JSON over TCP. Multiple requests can be in-flight at once; each has a unique
idmatched to its response. Seedocs/adr/0001-multiplexed-transport.md. - Addon threading: the TCP receiver runs in a background thread and appends to a bounded FIFO queue. The
bpy.app.timerscallback drains it on the main thread — the only thread that may touchbpy. - No coalescing: the queue is a plain FIFO. Flow control lives in the plugin (slider throttle, wheel delta accumulation, per-key poll in-flight guard).
- Eval trust boundary: the addon is a loopback-only arbitrary-Python eval server. Values written to properties pass through a
_vnamespace variable, never string-interpolated into code. Seedocs/adr/0002-eval-trust-boundary.md.