janction-render 0.3.4__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {janction_render-0.3.4 → janction_render-0.4.1}/PKG-INFO +24 -8
- {janction_render-0.3.4 → janction_render-0.4.1}/README.md +23 -7
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render/__init__.py +1 -1
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render/brief.py +3 -3
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render/cli.py +6 -3
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render/client.py +4 -4
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render/mcp_app.html +2 -2
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render/mcp_server.py +14 -6
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/PKG-INFO +24 -8
- {janction_render-0.3.4 → janction_render-0.4.1}/pyproject.toml +1 -1
- {janction_render-0.3.4 → janction_render-0.4.1}/LICENSE +0 -0
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/SOURCES.txt +0 -0
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/dependency_links.txt +0 -0
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/entry_points.txt +0 -0
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/requires.txt +0 -0
- {janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/top_level.txt +0 -0
- {janction_render-0.3.4 → janction_render-0.4.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: janction-render
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Cloud GPU render farm for Blender, for AI agents: MCP server (remote + stdio) and CLI. Render Blender without a GPU from Claude, ChatGPT, Claude Code, Codex, Cursor.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -31,7 +31,8 @@ without a GPU, or when rendering locally is slow.
|
|
|
31
31
|
- `orbit=True` turns any scene or imported model into a turntable: an orbit camera circles it once (`orbit_frames`, default 24;
|
|
32
32
|
a preview shows 0/90/180/270 degrees, a final render without `frame_end` gives the whole turn as an MP4). Flat floors and
|
|
33
33
|
walls are ignored when framing; `orbit_target` (object name or `x,y,z`) and `orbit_distance` (multiplier) adjust the shot.
|
|
34
|
-
- While the GPU is lent to another workload, tools answer right away with the queued job and a
|
|
34
|
+
- While the GPU is lent to another workload, tools answer right away with the queued job and a wait estimate (how long it
|
|
35
|
+
has been out, how long it usually stays out, how much longer to expect; `eta.gate`) instead of waiting.
|
|
35
36
|
- While a job renders, `render_status` counts finished frames inside the running chunk, so the ETA updates every few seconds.
|
|
36
37
|
- In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
|
|
37
38
|
as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
|
|
@@ -51,7 +52,8 @@ Free beta. Service page: `https://render.janction.jp` (`/v1/health`, `/llms.txt`
|
|
|
51
52
|
Guides: [render farm for agents](https://render.janction.jp/blender-render-farm) · [without a GPU](https://render.janction.jp/render-blender-without-gpu) ·
|
|
52
53
|
[Claude](https://render.janction.jp/claude-blender) · [ChatGPT](https://render.janction.jp/chatgpt-blender) · [Claude Code](https://render.janction.jp/claude-code-blender) ·
|
|
53
54
|
[Codex](https://render.janction.jp/codex-blender) · [bpy scripts](https://render.janction.jp/bpy-script-cloud-gpu) · [API](https://render.janction.jp/blender-render-api) ·
|
|
54
|
-
[comparison](https://render.janction.jp/blender-render-farm-api-comparison) ·
|
|
55
|
+
[comparison](https://render.janction.jp/blender-render-farm-api-comparison) · [Blender add-on](https://render.janction.jp/blender-addon) ·
|
|
56
|
+
[Maya / Houdini / Cinema 4D](https://render.janction.jp/maya-houdini-cinema4d) · 日本語は `/ja/`
|
|
55
57
|
|
|
56
58
|
## Connect (remote MCP, nothing to install)
|
|
57
59
|
|
|
@@ -114,7 +116,7 @@ Then, in Claude Code:
|
|
|
114
116
|
| `scene_info(scene_script | scene_url | scene_path | scene_id, assets?)` | cameras, frame range, fps, resolution, objects, lights, missing files. No render. |
|
|
115
117
|
| `render_preview(..., frames="1-24", environment?, blender?)` | up to 4 frames (720p budget) tiled with frame labels; returns the image inline |
|
|
116
118
|
| `render_estimate(scene_id, frame_start, frame_end, width, height, samples)` | GPU seconds, queue wait, "about N minutes", fits today's free quota? No GPU time used |
|
|
117
|
-
| `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?)` |
|
|
119
|
+
| `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?)` | frames (png / exr) or video (mp4 / webm / prores); returns job_id + estimate |
|
|
118
120
|
| `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
|
|
119
121
|
| `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
|
|
120
122
|
| `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
|
|
@@ -143,7 +145,7 @@ sun = bpy.data.objects.new("Sun", bpy.data.lights.new("Sun", "SUN"))
|
|
|
143
145
|
scene.collection.objects.link(sun)
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
The service sets engine (Cycles), resolution, samples and denoising; your script sets the scene, camera and frame range.
|
|
148
|
+
The service sets the engine (Cycles by default; `engine="eevee"` for cheaper animations: about half the per-frame cost after ~7 s of shader compilation per job), resolution, samples and denoising; your script sets the scene, camera and frame range. In Blender 5.0 materials always use nodes: set the Principled BSDF Base Color, not `diffuse_color`.
|
|
147
149
|
Scripts run in an isolated container with no network. See `samples/cube_scene.py`.
|
|
148
150
|
|
|
149
151
|
## HTTP API
|
|
@@ -153,7 +155,7 @@ POST /v1/keys -> {api_key} (header X-A
|
|
|
153
155
|
POST /v1/files multipart "file" (.blend|.py|.glb|.fbx|.usd|.obj|...) -> {scene_id}
|
|
154
156
|
POST /v1/files/{id}/assets multipart "files" textures, glTF .bin ... GET /v1/files/lookup?sha256= reuse an upload
|
|
155
157
|
POST /v1/estimate {kind, frames|frame_start/frame_end, width, height, samples, scene_id?} -> seconds, wall_seconds, human, quota
|
|
156
|
-
POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_end, width, height, samples, camera, fps, output,
|
|
158
|
+
POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_end, width, height, samples, camera, fps, output (png|exr|mp4|webm|prores), engine (cycles|eevee),
|
|
157
159
|
environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
|
|
158
160
|
GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
|
|
159
161
|
GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
|
|
@@ -166,11 +168,24 @@ GET /llms.txt /llms-full.txt /ja/llms.txt /faq /terms /privacy /legal /s
|
|
|
166
168
|
During the free beta a `429 quota_exceeded` response carries `resets_at`; a `400 beta_limit` means the job is too big
|
|
167
169
|
(split it). Once paid plans start, a `402 payment_required` response carries `checkout_url`.
|
|
168
170
|
|
|
171
|
+
## Blender add-on and DCC tools (for people, not agents)
|
|
172
|
+
|
|
173
|
+
- **Blender extension** (`addons/blender/`, Blender 4.2+): a JANCTION Render panel under Properties → Render with Preview,
|
|
174
|
+
Estimate and Final. A copy of the open file and the textures it references with relative paths are uploaded; results land
|
|
175
|
+
next to the .blend. Install from the extension repository `https://render.janction.jp/extensions/index.json`
|
|
176
|
+
(Preferences → Get Extensions → Repositories → + → Add Remote Repository), or install the zip from
|
|
177
|
+
`https://render.janction.jp/extensions/janction_render-0.1.0.zip` (`python scripts/build_blender_addon.py` builds it
|
|
178
|
+
from this repository). Guide: https://render.janction.jp/blender-addon
|
|
179
|
+
- **Maya, Houdini, Cinema 4D** (`integrations/`): export to USD / FBX / Alembic, upload with textures, render with Cycles,
|
|
180
|
+
open the result; Preview, Final (MP4) and Turntable. Standard-library Python only; `jr_submit.py` is the shared client.
|
|
181
|
+
Guide: https://render.janction.jp/maya-houdini-cinema4d
|
|
182
|
+
|
|
169
183
|
## Self-hosting
|
|
170
184
|
|
|
171
185
|
The server (FastAPI + SQLite, with the remote MCP endpoint) and the worker (Blender in disposable Docker containers,
|
|
172
186
|
`--network none --cap-drop ALL`) live in the internal repository and are not part of this package yet. This repository
|
|
173
|
-
holds the client side: stdio MCP server, CLI, HTTP client, samples
|
|
187
|
+
holds the client side: stdio MCP server, CLI, HTTP client, samples, the Claude Code plugin, the Blender add-on and the
|
|
188
|
+
DCC tools.
|
|
174
189
|
|
|
175
190
|
## MCP registry
|
|
176
191
|
|
|
@@ -180,4 +195,5 @@ mcp-name: io.github.JasmyLab-JANCTION/janction-render
|
|
|
180
195
|
|
|
181
196
|
## License
|
|
182
197
|
|
|
183
|
-
MIT (see LICENSE).
|
|
198
|
+
MIT (see LICENSE) for the package, the MCP servers, the CLI and the DCC tools. The Blender extension under
|
|
199
|
+
`addons/blender/` is GPL-3.0-or-later, as Blender requires for add-ons. Operated by JasmyLab Inc.
|
|
@@ -14,7 +14,8 @@ without a GPU, or when rendering locally is slow.
|
|
|
14
14
|
- `orbit=True` turns any scene or imported model into a turntable: an orbit camera circles it once (`orbit_frames`, default 24;
|
|
15
15
|
a preview shows 0/90/180/270 degrees, a final render without `frame_end` gives the whole turn as an MP4). Flat floors and
|
|
16
16
|
walls are ignored when framing; `orbit_target` (object name or `x,y,z`) and `orbit_distance` (multiplier) adjust the shot.
|
|
17
|
-
- While the GPU is lent to another workload, tools answer right away with the queued job and a
|
|
17
|
+
- While the GPU is lent to another workload, tools answer right away with the queued job and a wait estimate (how long it
|
|
18
|
+
has been out, how long it usually stays out, how much longer to expect; `eta.gate`) instead of waiting.
|
|
18
19
|
- While a job renders, `render_status` counts finished frames inside the running chunk, so the ETA updates every few seconds.
|
|
19
20
|
- In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
|
|
20
21
|
as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
|
|
@@ -34,7 +35,8 @@ Free beta. Service page: `https://render.janction.jp` (`/v1/health`, `/llms.txt`
|
|
|
34
35
|
Guides: [render farm for agents](https://render.janction.jp/blender-render-farm) · [without a GPU](https://render.janction.jp/render-blender-without-gpu) ·
|
|
35
36
|
[Claude](https://render.janction.jp/claude-blender) · [ChatGPT](https://render.janction.jp/chatgpt-blender) · [Claude Code](https://render.janction.jp/claude-code-blender) ·
|
|
36
37
|
[Codex](https://render.janction.jp/codex-blender) · [bpy scripts](https://render.janction.jp/bpy-script-cloud-gpu) · [API](https://render.janction.jp/blender-render-api) ·
|
|
37
|
-
[comparison](https://render.janction.jp/blender-render-farm-api-comparison) ·
|
|
38
|
+
[comparison](https://render.janction.jp/blender-render-farm-api-comparison) · [Blender add-on](https://render.janction.jp/blender-addon) ·
|
|
39
|
+
[Maya / Houdini / Cinema 4D](https://render.janction.jp/maya-houdini-cinema4d) · 日本語は `/ja/`
|
|
38
40
|
|
|
39
41
|
## Connect (remote MCP, nothing to install)
|
|
40
42
|
|
|
@@ -97,7 +99,7 @@ Then, in Claude Code:
|
|
|
97
99
|
| `scene_info(scene_script | scene_url | scene_path | scene_id, assets?)` | cameras, frame range, fps, resolution, objects, lights, missing files. No render. |
|
|
98
100
|
| `render_preview(..., frames="1-24", environment?, blender?)` | up to 4 frames (720p budget) tiled with frame labels; returns the image inline |
|
|
99
101
|
| `render_estimate(scene_id, frame_start, frame_end, width, height, samples)` | GPU seconds, queue wait, "about N minutes", fits today's free quota? No GPU time used |
|
|
100
|
-
| `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?)` |
|
|
102
|
+
| `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?)` | frames (png / exr) or video (mp4 / webm / prores); returns job_id + estimate |
|
|
101
103
|
| `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
|
|
102
104
|
| `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
|
|
103
105
|
| `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
|
|
@@ -126,7 +128,7 @@ sun = bpy.data.objects.new("Sun", bpy.data.lights.new("Sun", "SUN"))
|
|
|
126
128
|
scene.collection.objects.link(sun)
|
|
127
129
|
```
|
|
128
130
|
|
|
129
|
-
The service sets engine (Cycles), resolution, samples and denoising; your script sets the scene, camera and frame range.
|
|
131
|
+
The service sets the engine (Cycles by default; `engine="eevee"` for cheaper animations: about half the per-frame cost after ~7 s of shader compilation per job), resolution, samples and denoising; your script sets the scene, camera and frame range. In Blender 5.0 materials always use nodes: set the Principled BSDF Base Color, not `diffuse_color`.
|
|
130
132
|
Scripts run in an isolated container with no network. See `samples/cube_scene.py`.
|
|
131
133
|
|
|
132
134
|
## HTTP API
|
|
@@ -136,7 +138,7 @@ POST /v1/keys -> {api_key} (header X-A
|
|
|
136
138
|
POST /v1/files multipart "file" (.blend|.py|.glb|.fbx|.usd|.obj|...) -> {scene_id}
|
|
137
139
|
POST /v1/files/{id}/assets multipart "files" textures, glTF .bin ... GET /v1/files/lookup?sha256= reuse an upload
|
|
138
140
|
POST /v1/estimate {kind, frames|frame_start/frame_end, width, height, samples, scene_id?} -> seconds, wall_seconds, human, quota
|
|
139
|
-
POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_end, width, height, samples, camera, fps, output,
|
|
141
|
+
POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_end, width, height, samples, camera, fps, output (png|exr|mp4|webm|prores), engine (cycles|eevee),
|
|
140
142
|
environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
|
|
141
143
|
GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
|
|
142
144
|
GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
|
|
@@ -149,11 +151,24 @@ GET /llms.txt /llms-full.txt /ja/llms.txt /faq /terms /privacy /legal /s
|
|
|
149
151
|
During the free beta a `429 quota_exceeded` response carries `resets_at`; a `400 beta_limit` means the job is too big
|
|
150
152
|
(split it). Once paid plans start, a `402 payment_required` response carries `checkout_url`.
|
|
151
153
|
|
|
154
|
+
## Blender add-on and DCC tools (for people, not agents)
|
|
155
|
+
|
|
156
|
+
- **Blender extension** (`addons/blender/`, Blender 4.2+): a JANCTION Render panel under Properties → Render with Preview,
|
|
157
|
+
Estimate and Final. A copy of the open file and the textures it references with relative paths are uploaded; results land
|
|
158
|
+
next to the .blend. Install from the extension repository `https://render.janction.jp/extensions/index.json`
|
|
159
|
+
(Preferences → Get Extensions → Repositories → + → Add Remote Repository), or install the zip from
|
|
160
|
+
`https://render.janction.jp/extensions/janction_render-0.1.0.zip` (`python scripts/build_blender_addon.py` builds it
|
|
161
|
+
from this repository). Guide: https://render.janction.jp/blender-addon
|
|
162
|
+
- **Maya, Houdini, Cinema 4D** (`integrations/`): export to USD / FBX / Alembic, upload with textures, render with Cycles,
|
|
163
|
+
open the result; Preview, Final (MP4) and Turntable. Standard-library Python only; `jr_submit.py` is the shared client.
|
|
164
|
+
Guide: https://render.janction.jp/maya-houdini-cinema4d
|
|
165
|
+
|
|
152
166
|
## Self-hosting
|
|
153
167
|
|
|
154
168
|
The server (FastAPI + SQLite, with the remote MCP endpoint) and the worker (Blender in disposable Docker containers,
|
|
155
169
|
`--network none --cap-drop ALL`) live in the internal repository and are not part of this package yet. This repository
|
|
156
|
-
holds the client side: stdio MCP server, CLI, HTTP client, samples
|
|
170
|
+
holds the client side: stdio MCP server, CLI, HTTP client, samples, the Claude Code plugin, the Blender add-on and the
|
|
171
|
+
DCC tools.
|
|
157
172
|
|
|
158
173
|
## MCP registry
|
|
159
174
|
|
|
@@ -163,4 +178,5 @@ mcp-name: io.github.JasmyLab-JANCTION/janction-render
|
|
|
163
178
|
|
|
164
179
|
## License
|
|
165
180
|
|
|
166
|
-
MIT (see LICENSE).
|
|
181
|
+
MIT (see LICENSE) for the package, the MCP servers, the CLI and the DCC tools. The Blender extension under
|
|
182
|
+
`addons/blender/` is GPL-3.0-or-later, as Blender requires for add-ons. Operated by JasmyLab Inc.
|
|
@@ -8,7 +8,7 @@ JST = 9 * 3600
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def iso(ts: float | int | None) -> str | None:
|
|
11
|
-
"""epoch 秒 → "2026-10-
|
|
11
|
+
"""epoch 秒 → "2026-10-01T04:12:00Z (2026-10-01 13:12 JST)"。エージェントがそのまま伝えられる形。"""
|
|
12
12
|
if ts is None:
|
|
13
13
|
return None
|
|
14
14
|
try:
|
|
@@ -34,7 +34,7 @@ def eta_brief(e: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
|
34
34
|
if not e:
|
|
35
35
|
return None
|
|
36
36
|
keep = ("human", "remaining_seconds", "queue_wait_seconds", "remaining_gpu_seconds", "elapsed_seconds",
|
|
37
|
-
"running_for_seconds", "took_seconds", "workers_online", "basis")
|
|
37
|
+
"running_for_seconds", "took_seconds", "workers_online", "basis", "gate")
|
|
38
38
|
return {k: e[k] for k in keep if e.get(k) is not None}
|
|
39
39
|
|
|
40
40
|
|
|
@@ -42,7 +42,7 @@ def estimate_brief(e: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
|
42
42
|
if not e:
|
|
43
43
|
return None
|
|
44
44
|
keep = ("human", "seconds", "wall_seconds", "queue_wait_seconds", "per_frame_seconds", "frames", "chunks",
|
|
45
|
-
"workers_online", "basis", "basis_note", "note", "quota", "cost")
|
|
45
|
+
"workers_online", "basis", "basis_note", "note", "quota", "cost", "gate")
|
|
46
46
|
return {k: e[k] for k in keep if e.get(k) is not None}
|
|
47
47
|
|
|
48
48
|
|
|
@@ -79,7 +79,7 @@ def cmd_preview(c: Client, args: argparse.Namespace) -> int:
|
|
|
79
79
|
w, h = args.size
|
|
80
80
|
frames = parse_frames(args.frames, default=args.frame)
|
|
81
81
|
j = c.submit(sid, kind="preview", frames=frames, camera=args.camera, width=w, height=h,
|
|
82
|
-
samples=args.samples)
|
|
82
|
+
samples=args.samples, engine=args.engine)
|
|
83
83
|
print(f"job {j['job_id']} queued: frames {frames} (estimate ~{j['estimate']['seconds']}s)", file=sys.stderr)
|
|
84
84
|
j = c.wait(j["job_id"], timeout=args.timeout, on_progress=_progress)
|
|
85
85
|
if j["status"] != "done":
|
|
@@ -98,7 +98,7 @@ def cmd_render(c: Client, args: argparse.Namespace) -> int:
|
|
|
98
98
|
w, h = args.size
|
|
99
99
|
fs, fe = args.frames
|
|
100
100
|
j = c.submit(sid, kind="final", frame_start=fs, frame_end=fe, width=w, height=h,
|
|
101
|
-
samples=args.samples, fps=args.fps, output=args.output, camera=args.camera)
|
|
101
|
+
samples=args.samples, fps=args.fps, output=args.output, camera=args.camera, engine=args.engine)
|
|
102
102
|
print(f"job {j['job_id']} queued: {fe - fs + 1} frames in {j['progress']['chunks_total']} chunks, "
|
|
103
103
|
f"estimate ~{j['estimate']['seconds']}s", file=sys.stderr)
|
|
104
104
|
if not args.wait:
|
|
@@ -187,6 +187,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
187
187
|
s.add_argument("--camera", default=None)
|
|
188
188
|
s.add_argument("--size", type=_size, default=(1280, 720))
|
|
189
189
|
s.add_argument("--samples", type=int, default=16)
|
|
190
|
+
s.add_argument("--engine", choices=["cycles", "eevee"], default=None, help="cycles (default) or eevee (cheaper drafts)")
|
|
190
191
|
s.add_argument("--out", default=None)
|
|
191
192
|
s.add_argument("--timeout", type=float, default=300)
|
|
192
193
|
s.set_defaults(fn=cmd_preview)
|
|
@@ -197,8 +198,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
197
198
|
s.add_argument("--frames", type=_frames, required=True, help="e.g. 1-240 or 12")
|
|
198
199
|
s.add_argument("--size", type=_size, default=(1920, 1080))
|
|
199
200
|
s.add_argument("--samples", type=int, default=128)
|
|
201
|
+
s.add_argument("--engine", choices=["cycles", "eevee"], default=None, help="cycles (default) or eevee (cheaper drafts)")
|
|
200
202
|
s.add_argument("--fps", type=int, default=24)
|
|
201
|
-
s.add_argument("--output", choices=["auto", "png", "mp4"], default="auto"
|
|
203
|
+
s.add_argument("--output", choices=["auto", "png", "exr", "mp4", "webm", "prores"], default="auto",
|
|
204
|
+
help="png/exr = frames, mp4/webm/prores = video (auto: mp4 for a range, png for one frame)")
|
|
202
205
|
s.add_argument("--camera", default=None)
|
|
203
206
|
s.add_argument("--out", default=None)
|
|
204
207
|
s.add_argument("--wait", action="store_true", help="wait until done and download")
|
|
@@ -19,7 +19,7 @@ CACHE = Path.home() / ".janction-render.json"
|
|
|
19
19
|
|
|
20
20
|
# 送れるシーン: .blend、bpy スクリプト、こちらの Blender が読み込める 3D ファイル
|
|
21
21
|
SCENE_SUFFIXES = (".blend", ".py", ".fbx", ".glb", ".gltf", ".obj", ".stl", ".ply", ".usd", ".usda", ".usdc", ".usdz", ".abc")
|
|
22
|
-
ONLY_CHOICES = ("all", "mp4", "frames", "sheet", "output")
|
|
22
|
+
ONLY_CHOICES = ("all", "mp4", "frames", "sheet", "output", "video")
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
def select_artifacts(artifacts: list[dict[str, Any]], only: Optional[str] = None) -> list[dict[str, Any]]:
|
|
@@ -27,14 +27,14 @@ def select_artifacts(artifacts: list[dict[str, Any]], only: Optional[str] = None
|
|
|
27
27
|
o = (only or "all").strip().lower()
|
|
28
28
|
if o in ("", "all"):
|
|
29
29
|
return list(artifacts)
|
|
30
|
-
if o
|
|
31
|
-
return [a for a in artifacts if a["name"].endswith(".mp4")]
|
|
30
|
+
if o in ("mp4", "video"):
|
|
31
|
+
return [a for a in artifacts if a["name"].endswith((".mp4", ".webm", ".mov"))]
|
|
32
32
|
if o == "frames":
|
|
33
33
|
return [a for a in artifacts if a["name"].startswith("frame_")]
|
|
34
34
|
if o == "sheet":
|
|
35
35
|
return [a for a in artifacts if a["name"] == "sheet.png"]
|
|
36
36
|
if o == "output":
|
|
37
|
-
picked = [a for a in artifacts if a["name"] in ("output.mp4", "sheet.png")]
|
|
37
|
+
picked = [a for a in artifacts if a["name"] in ("output.mp4", "output.webm", "output.mov", "sheet.png")]
|
|
38
38
|
return picked or [a for a in artifacts if a["name"].startswith("frame_")][:1]
|
|
39
39
|
raise ValueError("only must be one of: " + ", ".join(ONLY_CHOICES))
|
|
40
40
|
|
|
@@ -90,7 +90,7 @@ function render() {
|
|
|
90
90
|
if (state.image) html += `<img class="sheet" src="${state.image}" alt="render preview">`;
|
|
91
91
|
const out = job.output_url;
|
|
92
92
|
const links = job.links || {};
|
|
93
|
-
if (status === "done" && out && /\.mp4(\?|$)/.test(out)) {
|
|
93
|
+
if (status === "done" && out && /\.(mp4|webm|mov)(\?|$)/.test(out)) {
|
|
94
94
|
html += `<video controls playsinline src="${esc(out)}"></video>`;
|
|
95
95
|
}
|
|
96
96
|
if (status === "done" && isCompare(job)) {
|
|
@@ -99,7 +99,7 @@ function render() {
|
|
|
99
99
|
}
|
|
100
100
|
html += `<div class="row">`;
|
|
101
101
|
if (status === "queued" || status === "running") html += `<button data-act="refresh">Refresh</button>`;
|
|
102
|
-
if (status === "done" && out) html += `<button class="primary" data-open="${esc(out)}">Open ${/\.mp4/.test(out) ? "
|
|
102
|
+
if (status === "done" && out) html += `<button class="primary" data-open="${esc(out)}">Open ${/\.(mp4|webm|mov)/.test(out) ? "video" : "image"}</button>`;
|
|
103
103
|
const frameLinks = Object.entries(links).filter(([n]) => n.startsWith("frame_"));
|
|
104
104
|
if (status === "done" && frameLinks.length) html += `<button data-open="${esc(frameLinks[0][1])}">Open first frame</button>`;
|
|
105
105
|
if (status === "done" && job.kind === "preview") html += `<button data-say="This preview looks right. Please ask me about frames, resolution and samples, then render the final.">Looks good, go final</button><button data-say="Please adjust the scene and preview again.">Needs changes</button>`;
|
|
@@ -41,7 +41,8 @@ mcp = MCPServer(
|
|
|
41
41
|
"in one image; look at it, fix the scene, repeat) -> ask the user 'is this OK?' -> render_estimate (tell the "
|
|
42
42
|
"user how long it takes and whether it fits today's free quota) -> render_final (frames or MP4) -> "
|
|
43
43
|
"render_status / render_download. Always tell the user the time estimate (estimate.human / eta.human). "
|
|
44
|
-
"Blender 5.0 with Cycles on GPU (blender='5.2' selects Blender 5.2 when the worker has it)
|
|
44
|
+
"Blender 5.0 with Cycles on GPU (blender='5.2' selects Blender 5.2 when the worker has it); engine='eevee' switches to EEVEE "
|
|
45
|
+
"(about half the per-frame cost on animations of a few dozen frames or more; each job pays ~7 s of shader compilation, so previews stay on Cycles); output='png'|'exr' returns frames, 'mp4'|'webm'|'prores' a video. Blender 5.0 API notes for scripts: materials always use nodes, so set a color on the Principled BSDF (mat.node_tree.nodes['Principled BSDF'].inputs['Base Color'].default_value = (r, g, b, 1)); mat.diffuse_color only affects the viewport. Animate with obj.keyframe_insert(); obj.animation_data.action.fcurves no longer exists (set bpy.context.preferences.edit.keyframe_new_interpolation_type='LINEAR' before inserting keyframes for constant-speed motion); do not set Material.use_nodes; leave resolution, samples and denoising to the service. Inputs: a .blend, a bpy "
|
|
45
46
|
"script, or a 3D file (FBX, glTF/GLB, USD, OBJ, STL, PLY, Alembic) that is imported into an empty scene; images "
|
|
46
47
|
"and other assets can be sent with the scene (assets=[...]). environment='studio'|'sunset'|'overcast'|'night' "
|
|
47
48
|
"lights the scene with a bundled HDRI for a good-looking first render. Inputs and results are deleted 24 hours "
|
|
@@ -109,8 +110,10 @@ def _wait_unless_gated(c: Client, job: dict[str, Any], timeout: float) -> dict[s
|
|
|
109
110
|
|
|
110
111
|
def _gated_hint(j: dict[str, Any], again: str) -> str:
|
|
111
112
|
if j.get("gated"):
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
# 受付の eta.human に「閉じてからの経過・ふだん戻るまでの時間・あと何分」が入っている
|
|
114
|
+
why = str((j.get("eta") or {}).get("human") or "the GPU is lent to another workload right now; the job stays queued and starts when the GPU returns")
|
|
115
|
+
return (f"{why}. Tell the user how long the wait is likely to be and offer to check later with {again}; "
|
|
116
|
+
"do not poll continuously")
|
|
114
117
|
return ""
|
|
115
118
|
|
|
116
119
|
|
|
@@ -172,8 +175,11 @@ def _resolve_scene(c: Client, scene_path: str, scene_id: str, scene_script: str
|
|
|
172
175
|
return up["scene_id"]
|
|
173
176
|
|
|
174
177
|
|
|
175
|
-
def _job_params(environment: str, environment_strength: float, environment_visible: bool, blender: str
|
|
178
|
+
def _job_params(environment: str, environment_strength: float, environment_visible: bool, blender: str,
|
|
179
|
+
engine: str = "") -> dict[str, Any]:
|
|
176
180
|
out: dict[str, Any] = {}
|
|
181
|
+
if engine:
|
|
182
|
+
out["engine"] = engine
|
|
177
183
|
if environment:
|
|
178
184
|
out.update({"environment": environment, "environment_strength": environment_strength,
|
|
179
185
|
"environment_visible": environment_visible})
|
|
@@ -256,6 +262,7 @@ def render_preview(
|
|
|
256
262
|
orbit_elevation: float = 18.0,
|
|
257
263
|
orbit_target: str = "",
|
|
258
264
|
orbit_distance: float = 1.0,
|
|
265
|
+
engine: str = "",
|
|
259
266
|
) -> list[Any]:
|
|
260
267
|
"""Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image.
|
|
261
268
|
|
|
@@ -276,7 +283,7 @@ def render_preview(
|
|
|
276
283
|
sid = _resolve_scene(c, scene_path, scene_id, scene_script, assets)
|
|
277
284
|
j = c.submit(sid, kind="preview", frames=frame_list, camera=camera or None,
|
|
278
285
|
width=width, height=height, samples=samples,
|
|
279
|
-
**_job_params(environment, environment_strength, environment_visible, blender),
|
|
286
|
+
**_job_params(environment, environment_strength, environment_visible, blender, engine=engine),
|
|
280
287
|
**({"orbit": True, "orbit_frames": orbit_frames, "orbit_elevation": orbit_elevation,
|
|
281
288
|
"orbit_target": orbit_target, "orbit_distance": orbit_distance} if orbit else {}))
|
|
282
289
|
_log(f"preview job {j['job_id']} submitted frames={frame_list}")
|
|
@@ -359,6 +366,7 @@ def render_final(
|
|
|
359
366
|
orbit_elevation: float = 18.0,
|
|
360
367
|
orbit_target: str = "",
|
|
361
368
|
orbit_distance: float = 1.0,
|
|
369
|
+
engine: str = "",
|
|
362
370
|
) -> str:
|
|
363
371
|
"""Render the final frames (or a video) of a Blender scene on JANCTION GPUs.
|
|
364
372
|
|
|
@@ -381,7 +389,7 @@ def render_final(
|
|
|
381
389
|
j = c.submit(sid, kind="final", frame_start=frame_start, frame_end=frame_end,
|
|
382
390
|
width=width, height=height, samples=samples, fps=fps, output=output,
|
|
383
391
|
camera=camera or None,
|
|
384
|
-
**_job_params(environment, environment_strength, environment_visible, blender), **extra)
|
|
392
|
+
**_job_params(environment, environment_strength, environment_visible, blender, engine=engine), **extra)
|
|
385
393
|
except ClientError as exc:
|
|
386
394
|
block = _payment_block(exc) or _quota_block(exc)
|
|
387
395
|
return _j(block if block else {"ok": False, "error": str(exc)})
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: janction-render
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Cloud GPU render farm for Blender, for AI agents: MCP server (remote + stdio) and CLI. Render Blender without a GPU from Claude, ChatGPT, Claude Code, Codex, Cursor.
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -31,7 +31,8 @@ without a GPU, or when rendering locally is slow.
|
|
|
31
31
|
- `orbit=True` turns any scene or imported model into a turntable: an orbit camera circles it once (`orbit_frames`, default 24;
|
|
32
32
|
a preview shows 0/90/180/270 degrees, a final render without `frame_end` gives the whole turn as an MP4). Flat floors and
|
|
33
33
|
walls are ignored when framing; `orbit_target` (object name or `x,y,z`) and `orbit_distance` (multiplier) adjust the shot.
|
|
34
|
-
- While the GPU is lent to another workload, tools answer right away with the queued job and a
|
|
34
|
+
- While the GPU is lent to another workload, tools answer right away with the queued job and a wait estimate (how long it
|
|
35
|
+
has been out, how long it usually stays out, how much longer to expect; `eta.gate`) instead of waiting.
|
|
35
36
|
- While a job renders, `render_status` counts finished frames inside the running chunk, so the ETA updates every few seconds.
|
|
36
37
|
- In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
|
|
37
38
|
as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
|
|
@@ -51,7 +52,8 @@ Free beta. Service page: `https://render.janction.jp` (`/v1/health`, `/llms.txt`
|
|
|
51
52
|
Guides: [render farm for agents](https://render.janction.jp/blender-render-farm) · [without a GPU](https://render.janction.jp/render-blender-without-gpu) ·
|
|
52
53
|
[Claude](https://render.janction.jp/claude-blender) · [ChatGPT](https://render.janction.jp/chatgpt-blender) · [Claude Code](https://render.janction.jp/claude-code-blender) ·
|
|
53
54
|
[Codex](https://render.janction.jp/codex-blender) · [bpy scripts](https://render.janction.jp/bpy-script-cloud-gpu) · [API](https://render.janction.jp/blender-render-api) ·
|
|
54
|
-
[comparison](https://render.janction.jp/blender-render-farm-api-comparison) ·
|
|
55
|
+
[comparison](https://render.janction.jp/blender-render-farm-api-comparison) · [Blender add-on](https://render.janction.jp/blender-addon) ·
|
|
56
|
+
[Maya / Houdini / Cinema 4D](https://render.janction.jp/maya-houdini-cinema4d) · 日本語は `/ja/`
|
|
55
57
|
|
|
56
58
|
## Connect (remote MCP, nothing to install)
|
|
57
59
|
|
|
@@ -114,7 +116,7 @@ Then, in Claude Code:
|
|
|
114
116
|
| `scene_info(scene_script | scene_url | scene_path | scene_id, assets?)` | cameras, frame range, fps, resolution, objects, lights, missing files. No render. |
|
|
115
117
|
| `render_preview(..., frames="1-24", environment?, blender?)` | up to 4 frames (720p budget) tiled with frame labels; returns the image inline |
|
|
116
118
|
| `render_estimate(scene_id, frame_start, frame_end, width, height, samples)` | GPU seconds, queue wait, "about N minutes", fits today's free quota? No GPU time used |
|
|
117
|
-
| `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?)` |
|
|
119
|
+
| `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?)` | frames (png / exr) or video (mp4 / webm / prores); returns job_id + estimate |
|
|
118
120
|
| `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
|
|
119
121
|
| `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
|
|
120
122
|
| `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
|
|
@@ -143,7 +145,7 @@ sun = bpy.data.objects.new("Sun", bpy.data.lights.new("Sun", "SUN"))
|
|
|
143
145
|
scene.collection.objects.link(sun)
|
|
144
146
|
```
|
|
145
147
|
|
|
146
|
-
The service sets engine (Cycles), resolution, samples and denoising; your script sets the scene, camera and frame range.
|
|
148
|
+
The service sets the engine (Cycles by default; `engine="eevee"` for cheaper animations: about half the per-frame cost after ~7 s of shader compilation per job), resolution, samples and denoising; your script sets the scene, camera and frame range. In Blender 5.0 materials always use nodes: set the Principled BSDF Base Color, not `diffuse_color`.
|
|
147
149
|
Scripts run in an isolated container with no network. See `samples/cube_scene.py`.
|
|
148
150
|
|
|
149
151
|
## HTTP API
|
|
@@ -153,7 +155,7 @@ POST /v1/keys -> {api_key} (header X-A
|
|
|
153
155
|
POST /v1/files multipart "file" (.blend|.py|.glb|.fbx|.usd|.obj|...) -> {scene_id}
|
|
154
156
|
POST /v1/files/{id}/assets multipart "files" textures, glTF .bin ... GET /v1/files/lookup?sha256= reuse an upload
|
|
155
157
|
POST /v1/estimate {kind, frames|frame_start/frame_end, width, height, samples, scene_id?} -> seconds, wall_seconds, human, quota
|
|
156
|
-
POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_end, width, height, samples, camera, fps, output,
|
|
158
|
+
POST /v1/jobs {scene_id, kind: info|preview|final, frames|frame_start/frame_end, width, height, samples, camera, fps, output (png|exr|mp4|webm|prores), engine (cycles|eevee),
|
|
157
159
|
environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
|
|
158
160
|
GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
|
|
159
161
|
GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
|
|
@@ -166,11 +168,24 @@ GET /llms.txt /llms-full.txt /ja/llms.txt /faq /terms /privacy /legal /s
|
|
|
166
168
|
During the free beta a `429 quota_exceeded` response carries `resets_at`; a `400 beta_limit` means the job is too big
|
|
167
169
|
(split it). Once paid plans start, a `402 payment_required` response carries `checkout_url`.
|
|
168
170
|
|
|
171
|
+
## Blender add-on and DCC tools (for people, not agents)
|
|
172
|
+
|
|
173
|
+
- **Blender extension** (`addons/blender/`, Blender 4.2+): a JANCTION Render panel under Properties → Render with Preview,
|
|
174
|
+
Estimate and Final. A copy of the open file and the textures it references with relative paths are uploaded; results land
|
|
175
|
+
next to the .blend. Install from the extension repository `https://render.janction.jp/extensions/index.json`
|
|
176
|
+
(Preferences → Get Extensions → Repositories → + → Add Remote Repository), or install the zip from
|
|
177
|
+
`https://render.janction.jp/extensions/janction_render-0.1.0.zip` (`python scripts/build_blender_addon.py` builds it
|
|
178
|
+
from this repository). Guide: https://render.janction.jp/blender-addon
|
|
179
|
+
- **Maya, Houdini, Cinema 4D** (`integrations/`): export to USD / FBX / Alembic, upload with textures, render with Cycles,
|
|
180
|
+
open the result; Preview, Final (MP4) and Turntable. Standard-library Python only; `jr_submit.py` is the shared client.
|
|
181
|
+
Guide: https://render.janction.jp/maya-houdini-cinema4d
|
|
182
|
+
|
|
169
183
|
## Self-hosting
|
|
170
184
|
|
|
171
185
|
The server (FastAPI + SQLite, with the remote MCP endpoint) and the worker (Blender in disposable Docker containers,
|
|
172
186
|
`--network none --cap-drop ALL`) live in the internal repository and are not part of this package yet. This repository
|
|
173
|
-
holds the client side: stdio MCP server, CLI, HTTP client, samples
|
|
187
|
+
holds the client side: stdio MCP server, CLI, HTTP client, samples, the Claude Code plugin, the Blender add-on and the
|
|
188
|
+
DCC tools.
|
|
174
189
|
|
|
175
190
|
## MCP registry
|
|
176
191
|
|
|
@@ -180,4 +195,5 @@ mcp-name: io.github.JasmyLab-JANCTION/janction-render
|
|
|
180
195
|
|
|
181
196
|
## License
|
|
182
197
|
|
|
183
|
-
MIT (see LICENSE).
|
|
198
|
+
MIT (see LICENSE) for the package, the MCP servers, the CLI and the DCC tools. The Blender extension under
|
|
199
|
+
`addons/blender/` is GPL-3.0-or-later, as Blender requires for add-ons. Operated by JasmyLab Inc.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "janction-render"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "Cloud GPU render farm for Blender, for AI agents: MCP server (remote + stdio) and CLI. Render Blender without a GPU from Claude, ChatGPT, Claude Code, Codex, Cursor."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
File without changes
|
|
File without changes
|
{janction_render-0.3.4 → janction_render-0.4.1}/janction_render.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|