janction-render 0.3.2__tar.gz → 0.3.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.3.2
3
+ Version: 0.3.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
@@ -29,8 +29,12 @@ without a GPU, or when rendering locally is slow.
29
29
  `environment="compare"` previews the same frame under all four presets in one labelled image, so the agent can pick one.
30
30
  - A `.gltf` or `.obj` brings its `.bin` / `.mtl` / textures along automatically (from the same folder or the same URL folder).
31
31
  - `orbit=True` turns any scene or imported model into a turntable: an orbit camera circles it once (`orbit_frames`, default 24;
32
- a preview shows 0/90/180/270 degrees, a final render without `frame_end` gives the whole turn as an MP4).
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
+ 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 note instead of waiting.
33
35
  - While a job renders, `render_status` counts finished frames inside the running chunk, so the ETA updates every few seconds.
36
+ - In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
37
+ as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
34
38
  - `scene_info` reads the scene without rendering (cameras, frame range, missing files).
35
39
  - `render_preview` returns 1-4 low-cost frames tiled in one image within seconds, so the agent can look, fix, and repeat.
36
40
  - `render_estimate` says how long a render will take ("about 3 minutes") and whether it fits today's free quota.
@@ -12,8 +12,12 @@ without a GPU, or when rendering locally is slow.
12
12
  `environment="compare"` previews the same frame under all four presets in one labelled image, so the agent can pick one.
13
13
  - A `.gltf` or `.obj` brings its `.bin` / `.mtl` / textures along automatically (from the same folder or the same URL folder).
14
14
  - `orbit=True` turns any scene or imported model into a turntable: an orbit camera circles it once (`orbit_frames`, default 24;
15
- a preview shows 0/90/180/270 degrees, a final render without `frame_end` gives the whole turn as an MP4).
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
+ 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 note instead of waiting.
16
18
  - While a job renders, `render_status` counts finished frames inside the running chunk, so the ETA updates every few seconds.
19
+ - In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
20
+ as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
17
21
  - `scene_info` reads the scene without rendering (cameras, frame range, missing files).
18
22
  - `render_preview` returns 1-4 low-cost frames tiled in one image within seconds, so the agent can look, fix, and repeat.
19
23
  - `render_estimate` says how long a render will take ("about 3 minutes") and whether it fits today's free quota.
@@ -1,3 +1,3 @@
1
1
  """JANCTION Render — ターミナルの AI エージェントから GPU で 3DCG を描く。"""
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.3"
@@ -52,8 +52,9 @@ mcp = MCPServer(
52
52
  )
53
53
 
54
54
  ORBIT_DOC = (" orbit=True: turntable; an orbit camera circles the scene once over orbit_frames frames (default 24, "
55
- "elevation orbit_elevation deg, default 18). A preview without frames shows 0/90/180/270 deg; a final without "
56
- "frame_end renders the whole turn as an MP4. Best for imported models (.glb/.fbx/.obj).")
55
+ "elevation orbit_elevation deg, default 18). Flat floors/walls are ignored when framing; orbit_target = an "
56
+ "object name or 'x,y,z' centres on that, orbit_distance scales the distance (1.0). A preview without frames "
57
+ "shows 0/90/180/270 deg; a final without frame_end renders the whole turn as an MP4. Best for imported models.")
57
58
  ENV_DOC = ("environment: '' keeps the scene's own world; 'studio' | 'sunset' | 'overcast' | 'night' replaces it with a "
58
59
  "bundled HDRI (good first render for scenes without lighting work); 'compare' (render_preview only) renders the "
59
60
  "first frame under all four presets in one 2x2 image with labels, so you can pick one; environment_strength scales it (1.0); "
@@ -75,6 +76,27 @@ def _out_root() -> Path:
75
76
  return Path(os.environ.get("JANCTION_RENDER_OUT") or (Path.home() / "janction-render"))
76
77
 
77
78
 
79
+ def _wait_unless_gated(c: Client, job: dict[str, Any], timeout: float) -> dict[str, Any]:
80
+ """仕事を待つ。ただし仕事を取れるワーカーが無い(GPU が貸し出し中)なら待たずに返し、hint で伝える。"""
81
+ if job["status"] in ("done", "failed", "canceled"):
82
+ return job
83
+ try:
84
+ q = c.health().get("queue") or {}
85
+ if int(q.get("workers_available") or 0) == 0 and job["status"] == "queued":
86
+ job["gated"] = True
87
+ return job
88
+ except Exception: # noqa: BLE001
89
+ pass
90
+ return c.wait(job["job_id"], timeout=timeout)
91
+
92
+
93
+ def _gated_hint(j: dict[str, Any], again: str) -> str:
94
+ if j.get("gated"):
95
+ return (f"the GPU is lent to another workload right now; the job stays queued and starts when the GPU returns. "
96
+ f"Tell the user and offer to check later with {again}; do not poll continuously")
97
+ return ""
98
+
99
+
78
100
  def _j(obj: Any) -> str:
79
101
  return json.dumps(obj, ensure_ascii=False, indent=2)
80
102
 
@@ -174,12 +196,12 @@ def scene_info(scene_path: str = "", scene_id: str = "", scene_script: str = "",
174
196
  sid = _resolve_scene(c, scene_path, scene_id, scene_script, assets)
175
197
  j = c.submit(sid, kind="info", **_job_params("", 1.0, True, blender))
176
198
  if j["status"] != "done":
177
- j = c.wait(j["job_id"], timeout=INFO_WAIT_S)
199
+ j = _wait_unless_gated(c, j, INFO_WAIT_S)
178
200
  except (ClientError, ValueError, FileNotFoundError) as exc:
179
201
  return _j({"ok": False, "error": str(exc)})
180
202
  if j["status"] != "done":
181
203
  b = _brief(j)
182
- b["hint"] = "still queued (a GPU worker may be busy); call scene_info again in a moment" \
204
+ b["hint"] = (_gated_hint(j, "scene_info(scene_id)") or "still queued (a GPU worker may be busy); call scene_info again in a moment") \
183
205
  if j["status"] in ("queued", "running") else "fix the scene and try again"
184
206
  return _j(b)
185
207
  info = dict(j.get("info") or {})
@@ -215,6 +237,8 @@ def render_preview(
215
237
  orbit: bool = False,
216
238
  orbit_frames: int = 24,
217
239
  orbit_elevation: float = 18.0,
240
+ orbit_target: str = "",
241
+ orbit_distance: float = 1.0,
218
242
  ) -> list[Any]:
219
243
  """Render a fast, cheap preview of a Blender scene on a JANCTION GPU and show the image.
220
244
 
@@ -236,9 +260,10 @@ def render_preview(
236
260
  j = c.submit(sid, kind="preview", frames=frame_list, camera=camera or None,
237
261
  width=width, height=height, samples=samples,
238
262
  **_job_params(environment, environment_strength, environment_visible, blender),
239
- **({"orbit": True, "orbit_frames": orbit_frames, "orbit_elevation": orbit_elevation} if orbit else {}))
263
+ **({"orbit": True, "orbit_frames": orbit_frames, "orbit_elevation": orbit_elevation,
264
+ "orbit_target": orbit_target, "orbit_distance": orbit_distance} if orbit else {}))
240
265
  _log(f"preview job {j['job_id']} submitted frames={frame_list}")
241
- j = c.wait(j["job_id"], timeout=PREVIEW_WAIT_S)
266
+ j = _wait_unless_gated(c, j, PREVIEW_WAIT_S)
242
267
  except ClientError as exc:
243
268
  block = _payment_block(exc) or _quota_block(exc)
244
269
  return [_j(block if block else {"ok": False, "error": str(exc)})]
@@ -246,7 +271,7 @@ def render_preview(
246
271
  return [_j({"ok": False, "error": str(exc)})]
247
272
  brief = _brief(j)
248
273
  if j["status"] != "done":
249
- brief["hint"] = ("still running: call render_status(job_id) and then render_download"
274
+ brief["hint"] = ((_gated_hint(j, "render_status(job_id)") or "still running: call render_status(job_id) and then render_download")
250
275
  if j["status"] in ("queued", "running")
251
276
  else "fix the scene and try again")
252
277
  return [_j(brief)]
@@ -315,6 +340,8 @@ def render_final(
315
340
  orbit: bool = False,
316
341
  orbit_frames: int = 24,
317
342
  orbit_elevation: float = 18.0,
343
+ orbit_target: str = "",
344
+ orbit_distance: float = 1.0,
318
345
  ) -> str:
319
346
  """Render the final frames (or a video) of a Blender scene on JANCTION GPUs.
320
347
 
@@ -330,7 +357,8 @@ def render_final(
330
357
  sid = _resolve_scene(c, scene_path, scene_id, scene_script, assets)
331
358
  extra: dict[str, Any] = {}
332
359
  if orbit:
333
- extra.update({"orbit": True, "orbit_frames": orbit_frames, "orbit_elevation": orbit_elevation})
360
+ extra.update({"orbit": True, "orbit_frames": orbit_frames, "orbit_elevation": orbit_elevation,
361
+ "orbit_target": orbit_target, "orbit_distance": orbit_distance})
334
362
  if frame_end <= frame_start:
335
363
  frame_end = None # type: ignore[assignment] # 1 周ぶんは受付が決める
336
364
  j = c.submit(sid, kind="final", frame_start=frame_start, frame_end=frame_end,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.3.2
3
+ Version: 0.3.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
@@ -29,8 +29,12 @@ without a GPU, or when rendering locally is slow.
29
29
  `environment="compare"` previews the same frame under all four presets in one labelled image, so the agent can pick one.
30
30
  - A `.gltf` or `.obj` brings its `.bin` / `.mtl` / textures along automatically (from the same folder or the same URL folder).
31
31
  - `orbit=True` turns any scene or imported model into a turntable: an orbit camera circles it once (`orbit_frames`, default 24;
32
- a preview shows 0/90/180/270 degrees, a final render without `frame_end` gives the whole turn as an MP4).
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
+ 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 note instead of waiting.
33
35
  - While a job renders, `render_status` counts finished frames inside the running chunk, so the ETA updates every few seconds.
36
+ - In hosts that support MCP Apps (Claude web and desktop, among others), previews, progress and download links also appear
37
+ as an interactive panel in the chat: the image, a progress bar, "Open MP4", and preset buttons after an `environment="compare"` preview.
34
38
  - `scene_info` reads the scene without rendering (cameras, frame range, missing files).
35
39
  - `render_preview` returns 1-4 low-cost frames tiled in one image within seconds, so the agent can look, fix, and repeat.
36
40
  - `render_estimate` says how long a render will take ("about 3 minutes") and whether it fits today's free quota.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "janction-render"
7
- version = "0.3.2"
7
+ version = "0.3.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