janction-render 0.4.1__tar.gz → 0.4.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.1
3
+ Version: 0.4.3
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
@@ -37,6 +37,9 @@ without a GPU, or when rendering locally is slow.
37
37
  - In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
38
38
  as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
39
39
  Both the remote connector and the stdio package ship the panel (`janction_render/mcp_app.html`); set `JR_MCP_APPS=0` to turn it off.
40
+ - CC0 assets by name: `asset_search("wooden table")` finds Poly Haven models, textures and HDRIs; pass `polyhaven:<id>` in
41
+ `asset_urls` (remote) or `assets` (stdio) and the files land in `JR_ASSETS_DIR/<id>/` on the GPU, ready to import
42
+ (`bpy.ops.import_scene.gltf(filepath=os.path.join(os.environ['JR_ASSETS_DIR'], 'wooden_table_02', 'wooden_table_02_1k.gltf'))`).
40
43
  - `scene_info` reads the scene without rendering (cameras, frame range, missing files).
41
44
  - `render_preview` returns 1-4 low-cost frames tiled in one image within seconds, so the agent can look, fix, and repeat.
42
45
  - `render_estimate` says how long a render will take ("about 3 minutes") and whether it fits today's free quota.
@@ -116,9 +119,10 @@ Then, in Claude Code:
116
119
  | `scene_info(scene_script | scene_url | scene_path | scene_id, assets?)` | cameras, frame range, fps, resolution, objects, lights, missing files. No render. |
117
120
  | `render_preview(..., frames="1-24", environment?, blender?)` | up to 4 frames (720p budget) tiled with frame labels; returns the image inline |
118
121
  | `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 |
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 |
122
+ | `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?, transparent?, notify_url?)` | frames (png / exr) or video (mp4 / webm / prores / gif / webp); `transparent=True` keeps an alpha background (png / exr / webm / gif / webp); `notify_url` gets one JSON POST when the job finishes; returns job_id + estimate |
120
123
  | `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
121
124
  | `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
125
+ | `asset_search(query, kind)` | CC0 models / textures / HDRIs on Poly Haven by words; results carry `polyhaven:<id>` and the entry file |
122
126
  | `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
123
127
 
124
128
  Options on `render_preview` / `render_final`: `environment` (`studio`, `sunset`, `overcast`, `night`, `compare` on previews; `environment_strength`,
@@ -155,10 +159,12 @@ POST /v1/keys -> {api_key} (header X-A
155
159
  POST /v1/files multipart "file" (.blend|.py|.glb|.fbx|.usd|.obj|...) -> {scene_id}
156
160
  POST /v1/files/{id}/assets multipart "files" textures, glTF .bin ... GET /v1/files/lookup?sha256= reuse an upload
157
161
  POST /v1/estimate {kind, frames|frame_start/frame_end, width, height, samples, scene_id?} -> seconds, wall_seconds, human, quota
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),
162
+ 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|gif|webp), transparent, notify_url (https), engine (cycles|eevee),
159
163
  environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
160
164
  GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
161
165
  GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
166
+ GET /v1/assets/search?q=&kind=models|textures|hdris CC0 assets (Poly Haven) -> spec polyhaven:<id>
167
+ POST /v1/files/{id}/assets/urls {urls: ["https://...", "polyhaven:<id>"]} fetch assets server-side
162
168
  POST /mcp remote MCP (Streamable HTTP; Bearer api key or OAuth)
163
169
  GET /.well-known/oauth-protected-resource/mcp OAuth discovery
164
170
  POST /v1/billing/checkout {amount_yen} -> {checkout_url} POST /v1/billing/sync GET /v1/ledger GET /v1/me
@@ -20,6 +20,9 @@ without a GPU, or when rendering locally is slow.
20
20
  - In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
21
21
  as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
22
22
  Both the remote connector and the stdio package ship the panel (`janction_render/mcp_app.html`); set `JR_MCP_APPS=0` to turn it off.
23
+ - CC0 assets by name: `asset_search("wooden table")` finds Poly Haven models, textures and HDRIs; pass `polyhaven:<id>` in
24
+ `asset_urls` (remote) or `assets` (stdio) and the files land in `JR_ASSETS_DIR/<id>/` on the GPU, ready to import
25
+ (`bpy.ops.import_scene.gltf(filepath=os.path.join(os.environ['JR_ASSETS_DIR'], 'wooden_table_02', 'wooden_table_02_1k.gltf'))`).
23
26
  - `scene_info` reads the scene without rendering (cameras, frame range, missing files).
24
27
  - `render_preview` returns 1-4 low-cost frames tiled in one image within seconds, so the agent can look, fix, and repeat.
25
28
  - `render_estimate` says how long a render will take ("about 3 minutes") and whether it fits today's free quota.
@@ -99,9 +102,10 @@ Then, in Claude Code:
99
102
  | `scene_info(scene_script | scene_url | scene_path | scene_id, assets?)` | cameras, frame range, fps, resolution, objects, lights, missing files. No render. |
100
103
  | `render_preview(..., frames="1-24", environment?, blender?)` | up to 4 frames (720p budget) tiled with frame labels; returns the image inline |
101
104
  | `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 |
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 |
105
+ | `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?, transparent?, notify_url?)` | frames (png / exr) or video (mp4 / webm / prores / gif / webp); `transparent=True` keeps an alpha background (png / exr / webm / gif / webp); `notify_url` gets one JSON POST when the job finishes; returns job_id + estimate |
103
106
  | `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
104
107
  | `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
108
+ | `asset_search(query, kind)` | CC0 models / textures / HDRIs on Poly Haven by words; results carry `polyhaven:<id>` and the entry file |
105
109
  | `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
106
110
 
107
111
  Options on `render_preview` / `render_final`: `environment` (`studio`, `sunset`, `overcast`, `night`, `compare` on previews; `environment_strength`,
@@ -138,10 +142,12 @@ POST /v1/keys -> {api_key} (header X-A
138
142
  POST /v1/files multipart "file" (.blend|.py|.glb|.fbx|.usd|.obj|...) -> {scene_id}
139
143
  POST /v1/files/{id}/assets multipart "files" textures, glTF .bin ... GET /v1/files/lookup?sha256= reuse an upload
140
144
  POST /v1/estimate {kind, frames|frame_start/frame_end, width, height, samples, scene_id?} -> seconds, wall_seconds, human, quota
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),
145
+ 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|gif|webp), transparent, notify_url (https), engine (cycles|eevee),
142
146
  environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
143
147
  GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
144
148
  GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
149
+ GET /v1/assets/search?q=&kind=models|textures|hdris CC0 assets (Poly Haven) -> spec polyhaven:<id>
150
+ POST /v1/files/{id}/assets/urls {urls: ["https://...", "polyhaven:<id>"]} fetch assets server-side
145
151
  POST /mcp remote MCP (Streamable HTTP; Bearer api key or OAuth)
146
152
  GET /.well-known/oauth-protected-resource/mcp OAuth discovery
147
153
  POST /v1/billing/checkout {amount_yen} -> {checkout_url} POST /v1/billing/sync GET /v1/ledger GET /v1/me
@@ -1,3 +1,3 @@
1
1
  """JANCTION Render — ターミナルの AI エージェントから GPU で 3DCG を描く。"""
2
2
 
3
- __version__ = "0.4.1"
3
+ __version__ = "0.4.3"
@@ -98,7 +98,8 @@ 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, engine=args.engine)
101
+ samples=args.samples, fps=args.fps, output=args.output, camera=args.camera, engine=args.engine,
102
+ transparent=args.transparent or None, notify_url=args.notify_url or None)
102
103
  print(f"job {j['job_id']} queued: {fe - fs + 1} frames in {j['progress']['chunks_total']} chunks, "
103
104
  f"estimate ~{j['estimate']['seconds']}s", file=sys.stderr)
104
105
  if not args.wait:
@@ -200,8 +201,10 @@ def build_parser() -> argparse.ArgumentParser:
200
201
  s.add_argument("--samples", type=int, default=128)
201
202
  s.add_argument("--engine", choices=["cycles", "eevee"], default=None, help="cycles (default) or eevee (cheaper drafts)")
202
203
  s.add_argument("--fps", type=int, default=24)
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)")
204
+ s.add_argument("--output", choices=["auto", "png", "exr", "mp4", "webm", "prores", "gif", "webp"], default="auto",
205
+ help="png/exr = frames, mp4/webm/prores/gif/webp = video (auto: mp4 for a range, png for one frame)")
206
+ s.add_argument("--transparent", action="store_true", help="transparent background (png/exr/webm/gif/webp)")
207
+ s.add_argument("--notify-url", default=None, help="https URL that receives one JSON POST when the job finishes")
205
208
  s.add_argument("--camera", default=None)
206
209
  s.add_argument("--out", default=None)
207
210
  s.add_argument("--wait", action="store_true", help="wait until done and download")
@@ -361,6 +361,20 @@ class Client:
361
361
  self._raise(r)
362
362
  return r.json()
363
363
 
364
+ def fetch_asset_urls(self, scene_id: str, urls: list[str]) -> dict[str, Any]:
365
+ """URL か polyhaven:<id> の素材を受付に取らせてシーンに足す(受付が取るので手元には要らない)。"""
366
+ r = self.s.post(f"{self.server}/v1/files/{scene_id}/assets/urls", headers=self._headers(),
367
+ json={"urls": [str(u) for u in urls]}, timeout=900)
368
+ self._raise(r)
369
+ return r.json()
370
+
371
+ def asset_search(self, query: str, kind: str = "models") -> dict[str, Any]:
372
+ """Poly Haven(CC0)の素材を名前・タグで引く。"""
373
+ r = self.s.get(f"{self.server}/v1/assets/search", headers=self._headers(),
374
+ params={"q": query, "kind": kind}, timeout=30)
375
+ self._raise(r)
376
+ return r.json()
377
+
364
378
  def upload_text(self, name: str, text: str) -> dict[str, Any]:
365
379
  """文字列で受け取った bpy スクリプトを、そのままシーンとして送る(ファイルを作らなくてよい)。"""
366
380
  import tempfile
@@ -92,6 +92,8 @@ function render() {
92
92
  const links = job.links || {};
93
93
  if (status === "done" && out && /\.(mp4|webm|mov)(\?|$)/.test(out)) {
94
94
  html += `<video controls playsinline src="${esc(out)}"></video>`;
95
+ } else if (status === "done" && out && /\.(gif|webp)(\?|$)/.test(out)) {
96
+ html += `<img class="sheet" src="${esc(out)}" alt="animation">`;
95
97
  }
96
98
  if (status === "done" && isCompare(job)) {
97
99
  html += `<div class="muted">Pick the lighting for the final render:</div><div class="grid">` +
@@ -52,6 +52,13 @@ mcp = MCPServer(
52
52
  ),
53
53
  )
54
54
 
55
+ OUTPUT_DOC = (" output: 'auto' (mp4 for a frame range, png for one frame) | 'png' | 'exr' (16-bit linear OpenEXR frames) | "
56
+ "'mp4' (H.264) | 'webm' (VP9) | 'prores' (ProRes 422 HQ .mov) | 'gif' | 'webp' (animated, at most 960 px wide; "
57
+ "good for chat and web). transparent=True renders on a transparent background (alpha): png / exr frames, webm, gif "
58
+ "and webp keep it, mp4 and prores cannot. notify_url: an https URL that receives one JSON POST when the job "
59
+ "finishes (event job.done / job.failed, artifacts with download links).")
60
+ TRANSPARENT_DOC = (" transparent=True: transparent background (alpha) instead of the world/HDRI backdrop; the preview sheet "
61
+ "shows it over dark grey.")
55
62
  ORBIT_DOC = (" orbit=True: turntable; an orbit camera circles the scene once over orbit_frames frames (default 24, "
56
63
  "elevation orbit_elevation deg, default 18). Flat floors/walls are ignored when framing; orbit_target = an "
57
64
  "object name or 'x,y,z' centres on that, orbit_distance scales the distance (1.0). A preview without frames "
@@ -80,6 +87,10 @@ ENV_DOC = ("environment: '' keeps the scene's own world; 'studio' | 'sunset' | '
80
87
  "blender: '' (5.0) or '5.2' to render with Blender 5.2 when available. assets: local files (images, glTF "
81
88
  ".bin, ...) sent along with the scene; a .blend finds them through relative paths, a script through "
82
89
  "os.environ['JR_ASSETS_DIR'].")
90
+ POLYHAVEN_DOC = (" assets may also hold 'polyhaven:<id>' (a CC0 model, texture or HDRI from polyhaven.com, fetched by the "
91
+ "service into JR_ASSETS_DIR/<id>/; find ids with asset_search) or an https URL. Import a model with "
92
+ "bpy.ops.import_scene.gltf(filepath=os.path.join(os.environ['JR_ASSETS_DIR'], '<id>', '<id>_1k.gltf')).")
93
+ ENV_DOC = ENV_DOC + POLYHAVEN_DOC
83
94
 
84
95
 
85
96
  def _log(msg: str) -> None:
@@ -146,8 +157,28 @@ def _payment_block(exc: ClientError) -> Optional[dict[str, Any]]:
146
157
  _LAST_UPLOAD: dict[str, Any] = {}
147
158
 
148
159
 
160
+ def _is_remote_asset(a: str) -> bool:
161
+ s = str(a).strip().lower()
162
+ return s.startswith("polyhaven:") or s.startswith("https://")
163
+
164
+
149
165
  def _resolve_scene(c: Client, scene_path: str, scene_id: str, scene_script: str = "",
150
166
  assets: Optional[list[str]] = None) -> str:
167
+ """手元のファイルは upload、polyhaven:<id> と https の素材は受付に取らせる。"""
168
+ remote = [str(a) for a in (assets or []) if _is_remote_asset(a)]
169
+ local = [a for a in (assets or []) if not _is_remote_asset(a)]
170
+ sid = _resolve_scene_local(c, scene_path, scene_id, scene_script, local or None)
171
+ if remote:
172
+ up = c.fetch_asset_urls(sid, remote)
173
+ got = list(_LAST_UPLOAD.get("assets") or [])
174
+ _LAST_UPLOAD["assets"] = got + [x for x in (up.get("added") or []) if x not in got]
175
+ if up.get("polyhaven"):
176
+ _LAST_UPLOAD["polyhaven"] = up["polyhaven"]
177
+ return sid
178
+
179
+
180
+ def _resolve_scene_local(c: Client, scene_path: str, scene_id: str, scene_script: str = "",
181
+ assets: Optional[list[str]] = None) -> str:
151
182
  _LAST_UPLOAD.clear()
152
183
  if scene_id:
153
184
  if assets:
@@ -241,7 +272,38 @@ def scene_info(scene_path: str = "", scene_id: str = "", scene_script: str = "",
241
272
  return _j(_attach_upload_notes(info))
242
273
 
243
274
 
244
- @mcp.tool(structured_output=False, meta=_ui_meta())
275
+ RENDER_PREVIEW_DESCRIPTION = (
276
+ """Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image.
277
+
278
+ Use this when the user is making 3DCG with Blender and wants to see how it looks, but has no
279
+ GPU or local rendering is slow. Give the scene as scene_path (a .blend file OR a bpy Python
280
+ script), scene_script (bpy code as text; no file and no local Blender needed), or scene_id
281
+ from a previous call. frames: '' = frame 1;
282
+ '12' = one frame; '1,8,16,24' or '1-24' = up to 4 frames tiled in ONE image (2x2, each tile
283
+ labeled with its frame number) so you can judge camera motion and animation. Same GPU cost as
284
+ one 720p frame. Quality is deliberately low (up to 1280x720, few samples, denoised).
285
+ Look at the returned image, fix the scene, preview again; when it looks right, ask the user and
286
+ call render_final. Returns scene_id (reuse it without re-uploading), job_id, saved PNG paths,
287
+ Blender warnings (e.g. missing textures), GPU seconds, and the expiry time (24h after last use).
288
+ """ + ENV_DOC + ORBIT_DOC + TRANSPARENT_DOC
289
+ )
290
+
291
+
292
+ @mcp.tool()
293
+ def asset_search(query: str, kind: str = "models") -> str:
294
+ """Find CC0 3D models, PBR textures or HDRIs on Poly Haven by words (name, tags, category), to furnish a scene
295
+ without local files. kind: 'models' (default) | 'textures' | 'hdris'. Each result has spec 'polyhaven:<id>', its
296
+ size in metres (models) and the entry file. Then pass the spec in assets of scene_info / render_preview /
297
+ render_final; the service fetches the files into JR_ASSETS_DIR/<id>/ and the scene script imports the entry with
298
+ bpy.ops.import_scene.gltf(filepath=os.path.join(os.environ['JR_ASSETS_DIR'], '<entry>')). No GPU time is used."""
299
+ c = _client()
300
+ try:
301
+ return _j(c.asset_search(query, kind))
302
+ except ClientError as exc:
303
+ return _j({"ok": False, "error": str(exc)})
304
+
305
+
306
+ @mcp.tool(structured_output=False, meta=_ui_meta(), description=RENDER_PREVIEW_DESCRIPTION)
245
307
  def render_preview(
246
308
  scene_path: str = "",
247
309
  scene_id: str = "",
@@ -263,26 +325,15 @@ def render_preview(
263
325
  orbit_target: str = "",
264
326
  orbit_distance: float = 1.0,
265
327
  engine: str = "",
328
+ transparent: bool = False,
266
329
  ) -> list[Any]:
267
- """Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image.
268
-
269
- Use this when the user is making 3DCG with Blender and wants to see how it looks, but has no
270
- GPU or local rendering is slow. Give the scene as scene_path (a .blend file OR a bpy Python
271
- script), scene_script (bpy code as text; no file and no local Blender needed), or scene_id
272
- from a previous call. frames: '' = frame 1;
273
- '12' = one frame; '1,8,16,24' or '1-24' = up to 4 frames tiled in ONE image (2x2, each tile
274
- labeled with its frame number) so you can judge camera motion and animation. Same GPU cost as
275
- one 720p frame. Quality is deliberately low (up to 1280x720, few samples, denoised).
276
- Look at the returned image, fix the scene, preview again; when it looks right, ask the user and
277
- call render_final. Returns scene_id (reuse it without re-uploading), job_id, saved PNG paths,
278
- Blender warnings (e.g. missing textures), GPU seconds, and the expiry time (24h after last use).
279
- """ + ENV_DOC + ORBIT_DOC
330
+ """Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image. (full description on the tool)."""
280
331
  c = _client()
281
332
  try:
282
333
  frame_list = parse_frames(frames) if (frames or not orbit) else None
283
334
  sid = _resolve_scene(c, scene_path, scene_id, scene_script, assets)
284
335
  j = c.submit(sid, kind="preview", frames=frame_list, camera=camera or None,
285
- width=width, height=height, samples=samples,
336
+ width=width, height=height, samples=samples, transparent=(True if transparent else None),
286
337
  **_job_params(environment, environment_strength, environment_visible, blender, engine=engine),
287
338
  **({"orbit": True, "orbit_frames": orbit_frames, "orbit_elevation": orbit_elevation,
288
339
  "orbit_target": orbit_target, "orbit_distance": orbit_distance} if orbit else {}))
@@ -343,7 +394,20 @@ def render_estimate(scene_id: str = "", kind: str = "final", frame_start: int =
343
394
  return _j(out)
344
395
 
345
396
 
346
- @mcp.tool(meta=_ui_meta())
397
+ RENDER_FINAL_DESCRIPTION = (
398
+ """Render the final frames (or a video) of a Blender scene on JANCTION GPUs.
399
+
400
+ Call this after the user approved a preview. Pass scene_id from render_preview (or scene_path /
401
+ scene_script to upload a new .blend / bpy script / 3D file). frame_start..frame_end are inclusive; several frames are
402
+ split across GPUs and joined into output.mp4 (output='mp4', fps), a single frame gives a PNG
403
+ (output='png'; 'auto' picks). Use the same environment / blender as the approved preview. Returns job_id and a
404
+ time estimate right away; the render runs in the background: poll with render_status, then fetch with
405
+ render_download. Inputs and results are deleted 24 hours after last use, so download them.
406
+ """ + ENV_DOC + ORBIT_DOC + OUTPUT_DOC
407
+ )
408
+
409
+
410
+ @mcp.tool(meta=_ui_meta(), description=RENDER_FINAL_DESCRIPTION)
347
411
  def render_final(
348
412
  scene_path: str = "",
349
413
  scene_id: str = "",
@@ -367,16 +431,10 @@ def render_final(
367
431
  orbit_target: str = "",
368
432
  orbit_distance: float = 1.0,
369
433
  engine: str = "",
434
+ transparent: bool = False,
435
+ notify_url: str = "",
370
436
  ) -> str:
371
- """Render the final frames (or a video) of a Blender scene on JANCTION GPUs.
372
-
373
- Call this after the user approved a preview. Pass scene_id from render_preview (or scene_path /
374
- scene_script to upload a new .blend / bpy script / 3D file). frame_start..frame_end are inclusive; several frames are
375
- split across GPUs and joined into output.mp4 (output='mp4', fps), a single frame gives a PNG
376
- (output='png'; 'auto' picks). Use the same environment / blender as the approved preview. Returns job_id and a
377
- time estimate right away; the render runs in the background: poll with render_status, then fetch with
378
- render_download. Inputs and results are deleted 24 hours after last use, so download them.
379
- """ + ENV_DOC + ORBIT_DOC
437
+ """Render the final frames (or a video) of a Blender scene on JANCTION GPUs. (full description on the tool)."""
380
438
  c = _client()
381
439
  try:
382
440
  sid = _resolve_scene(c, scene_path, scene_id, scene_script, assets)
@@ -388,7 +446,7 @@ def render_final(
388
446
  frame_end = None # type: ignore[assignment] # 1 周ぶんは受付が決める
389
447
  j = c.submit(sid, kind="final", frame_start=frame_start, frame_end=frame_end,
390
448
  width=width, height=height, samples=samples, fps=fps, output=output,
391
- camera=camera or None,
449
+ camera=camera or None, transparent=(True if transparent else None), notify_url=notify_url or None,
392
450
  **_job_params(environment, environment_strength, environment_visible, blender, engine=engine), **extra)
393
451
  except ClientError as exc:
394
452
  block = _payment_block(exc) or _quota_block(exc)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.1
3
+ Version: 0.4.3
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
@@ -37,6 +37,9 @@ without a GPU, or when rendering locally is slow.
37
37
  - In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
38
38
  as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
39
39
  Both the remote connector and the stdio package ship the panel (`janction_render/mcp_app.html`); set `JR_MCP_APPS=0` to turn it off.
40
+ - CC0 assets by name: `asset_search("wooden table")` finds Poly Haven models, textures and HDRIs; pass `polyhaven:<id>` in
41
+ `asset_urls` (remote) or `assets` (stdio) and the files land in `JR_ASSETS_DIR/<id>/` on the GPU, ready to import
42
+ (`bpy.ops.import_scene.gltf(filepath=os.path.join(os.environ['JR_ASSETS_DIR'], 'wooden_table_02', 'wooden_table_02_1k.gltf'))`).
40
43
  - `scene_info` reads the scene without rendering (cameras, frame range, missing files).
41
44
  - `render_preview` returns 1-4 low-cost frames tiled in one image within seconds, so the agent can look, fix, and repeat.
42
45
  - `render_estimate` says how long a render will take ("about 3 minutes") and whether it fits today's free quota.
@@ -116,9 +119,10 @@ Then, in Claude Code:
116
119
  | `scene_info(scene_script | scene_url | scene_path | scene_id, assets?)` | cameras, frame range, fps, resolution, objects, lights, missing files. No render. |
117
120
  | `render_preview(..., frames="1-24", environment?, blender?)` | up to 4 frames (720p budget) tiled with frame labels; returns the image inline |
118
121
  | `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 |
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 |
122
+ | `render_final(scene_id, frame_start, frame_end, width, height, samples, fps, output, environment?, blender?, engine?, transparent?, notify_url?)` | frames (png / exr) or video (mp4 / webm / prores / gif / webp); `transparent=True` keeps an alpha background (png / exr / webm / gif / webp); `notify_url` gets one JSON POST when the job finishes; returns job_id + estimate |
120
123
  | `render_status(job_id)` | progress and ETA (`eta.human`); `render_download(job_id, only="mp4" / "frames" / "all")` files or links; `render_cancel(job_id)` |
121
124
  | `billing()` | free-beta quota (used today, daily limit, reset time); later balance and top-up link |
125
+ | `asset_search(query, kind)` | CC0 models / textures / HDRIs on Poly Haven by words; results carry `polyhaven:<id>` and the entry file |
122
126
  | `render_info()` | workers online or gated (GPU lent to another workload), queue, expected wait, supported inputs, environment presets, Blender versions |
123
127
 
124
128
  Options on `render_preview` / `render_final`: `environment` (`studio`, `sunset`, `overcast`, `night`, `compare` on previews; `environment_strength`,
@@ -155,10 +159,12 @@ POST /v1/keys -> {api_key} (header X-A
155
159
  POST /v1/files multipart "file" (.blend|.py|.glb|.fbx|.usd|.obj|...) -> {scene_id}
156
160
  POST /v1/files/{id}/assets multipart "files" textures, glTF .bin ... GET /v1/files/lookup?sha256= reuse an upload
157
161
  POST /v1/estimate {kind, frames|frame_start/frame_end, width, height, samples, scene_id?} -> seconds, wall_seconds, human, quota
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),
162
+ 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|gif|webp), transparent, notify_url (https), engine (cycles|eevee),
159
163
  environment, environment_strength, environment_visible, blender, orbit, orbit_frames, orbit_elevation}
160
164
  GET /v1/jobs/{id} status, progress, eta, artifacts[], cost, warnings, info DELETE /v1/jobs/{id} cancel
161
165
  GET /v1/jobs/{id}/artifacts/{name} PNG / MP4
166
+ GET /v1/assets/search?q=&kind=models|textures|hdris CC0 assets (Poly Haven) -> spec polyhaven:<id>
167
+ POST /v1/files/{id}/assets/urls {urls: ["https://...", "polyhaven:<id>"]} fetch assets server-side
162
168
  POST /mcp remote MCP (Streamable HTTP; Bearer api key or OAuth)
163
169
  GET /.well-known/oauth-protected-resource/mcp OAuth discovery
164
170
  POST /v1/billing/checkout {amount_yen} -> {checkout_url} POST /v1/billing/sync GET /v1/ledger GET /v1/me
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "janction-render"
7
- version = "0.4.1"
7
+ version = "0.4.3"
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