janction-render 0.4.83__tar.gz → 0.4.87__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.
Files changed (22) hide show
  1. {janction_render-0.4.83 → janction_render-0.4.87}/PKG-INFO +4 -4
  2. {janction_render-0.4.83 → janction_render-0.4.87}/README.md +3 -3
  3. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/__init__.py +1 -1
  4. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/client.py +121 -7
  5. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/mcp_common.py +36 -2
  6. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/mcp_server.py +7 -1
  7. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/prompts.py +2 -2
  8. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/tooldocs.py +13 -11
  9. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/PKG-INFO +4 -4
  10. {janction_render-0.4.83 → janction_render-0.4.87}/pyproject.toml +1 -1
  11. {janction_render-0.4.83 → janction_render-0.4.87}/LICENSE +0 -0
  12. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/brief.py +0 -0
  13. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/cli.py +0 -0
  14. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/hookup.py +0 -0
  15. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/links.py +0 -0
  16. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/mcp_app.html +0 -0
  17. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/SOURCES.txt +0 -0
  18. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/dependency_links.txt +0 -0
  19. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/entry_points.txt +0 -0
  20. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/requires.txt +0 -0
  21. {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/top_level.txt +0 -0
  22. {janction_render-0.4.83 → janction_render-0.4.87}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.83
3
+ Version: 0.4.87
4
4
  Summary: Cloud GPU rendering for Blender scenes and 3D models (GLB, FBX, USD, OBJ, STL), built for AI agents: MCP server (remote + stdio), HTTP API and CLI. Product shots, 360-degree turntables, animations and stills; preview in seconds with a critic; no local Blender or GPU. A 100 JPY welcome credit for a new key (one per network every 30 days) and 400 JPY more for saving a card that is not charged, then 0.1 JPY per GPU-second.
5
5
  Author-email: "JasmyLab Inc." <contact@jasmylab.com>
6
6
  License-Expression: MIT
@@ -57,7 +57,7 @@ Claude Code, Codex, Cursor or any MCP client**: an MCP server (remote and stdio)
57
57
  JANCTION Render is a separate product from SmartRender (JasmyLab's distributed rendering for people at a desktop) and is
58
58
  not affiliated with Render.com or the Render Network. The numbers, as of 2026-10-08 (the primary source is
59
59
  https://render.janction.jp/facts.json): a 100 JPY welcome credit (1,000 GPU-seconds, 14 days, no card) for a new key, one per network every 30 days (further keys on the same network share what is left), plus 400 JPY more once a card is saved on a page that charges nothing (once per card); after that, previews
60
- are free up to 2 GPU-minutes a day and finals cost 0.1 JPY per GPU-second from prepaid credit (paid use started on 2026-10-08); finals up to 1,000 frames per instruction (run as parts of 240 under one job id); 1080p on free time, up to 4K on prepaid credit; inputs and results deleted
60
+ are free up to 2 GPU-minutes a day and finals cost 0.1 JPY per GPU-second from prepaid credit (paid use started on 2026-10-08); finals up to 3,000 frames per instruction (run as parts of 240 under one job id); 1080p on free time, up to 4K on prepaid credit; inputs and results deleted
61
61
  24 hours after last use (7 days for keys that have topped up).
62
62
 
63
63
  Official site: https://render.janction.jp · MCP endpoint: `https://render.janction.jp/mcp` · Fact sheet: https://render.janction.jp/facts
@@ -116,7 +116,7 @@ Keep 3D files in a repository? Add a button that lets anyone render them from th
116
116
  errors with the fix, spending caps per key, safe retries with `Idempotency-Key`.
117
117
  - **3D files and turntables.** glTF / GLB, FBX, USD, OBJ, STL, PLY, Alembic are imported into an empty scene with a camera and
118
118
  HDRI lighting; `orbit=True` makes a turntable. Fixed-price outcomes: `POST /v1/outcomes/turntable` and `/product-shot`.
119
- - **Honest limits.** a 100 JPY welcome credit for a new key with no card (14 days; one per network every 30 days) and 400 JPY more after saving a card that is not charged, then 0.1 JPY per GPU-second (previews free up to 2 GPU-minutes a day), finals up to 1,000 frames per instruction as parts of 240 (1080p on free time, up to 4K on prepaid credit), one GPU shared with another
119
+ - **Honest limits.** a 100 JPY welcome credit for a new key with no card (14 days; one per network every 30 days) and 400 JPY more after saving a card that is not charged, then 0.1 JPY per GPU-second (previews free up to 2 GPU-minutes a day), finals up to 3,000 frames per instruction as parts of 240 (1080p on free time, up to 4K on prepaid credit), one GPU shared with another
120
120
  workload (jobs can wait; the ETA says so). Inputs and results are deleted 24 hours after last use (7 days for keys that have topped up) and never used for training.
121
121
  - **Not Render.com.** Same word, different product.
122
122
 
@@ -421,7 +421,7 @@ nothing adds 400 JPY more, once per card (up to 500 JPY in all). After it is use
421
421
  free up to 2 GPU-minutes a day and finals cost 0.1 JPY per
422
422
  GPU-second from prepaid credit, bought by card at a Stripe Checkout page (from 500 JPY; 2,000 JPY gives 2,200 JPY of credit,
423
423
  5,000 gives 5,750, 10,000 gives 12,000; the first top-up in the welcome period counts 1.5x, bonus up to 500 JPY). Only the part
424
- the free time does not cover is charged. Finals go up to 1,000 frames per instruction (run as parts of 240 under one job id): 1080p on free time, up to 4K (3840x2160) on prepaid credit. `POST /v1/estimate` quotes what will be charged
424
+ the free time does not cover is charged. Finals go up to 3,000 frames per instruction (run as parts of 240 under one job id): 1080p on free time, up to 4K (3840x2160) on prepaid credit. `POST /v1/estimate` quotes what will be charged
425
425
  (`cost.estimated_yen`), the currency and an expiry before any GPU time is used: https://render.janction.jp/pricing
426
426
 
427
427
  ## Troubleshooting
@@ -17,7 +17,7 @@ Claude Code, Codex, Cursor or any MCP client**: an MCP server (remote and stdio)
17
17
  JANCTION Render is a separate product from SmartRender (JasmyLab's distributed rendering for people at a desktop) and is
18
18
  not affiliated with Render.com or the Render Network. The numbers, as of 2026-10-08 (the primary source is
19
19
  https://render.janction.jp/facts.json): a 100 JPY welcome credit (1,000 GPU-seconds, 14 days, no card) for a new key, one per network every 30 days (further keys on the same network share what is left), plus 400 JPY more once a card is saved on a page that charges nothing (once per card); after that, previews
20
- are free up to 2 GPU-minutes a day and finals cost 0.1 JPY per GPU-second from prepaid credit (paid use started on 2026-10-08); finals up to 1,000 frames per instruction (run as parts of 240 under one job id); 1080p on free time, up to 4K on prepaid credit; inputs and results deleted
20
+ are free up to 2 GPU-minutes a day and finals cost 0.1 JPY per GPU-second from prepaid credit (paid use started on 2026-10-08); finals up to 3,000 frames per instruction (run as parts of 240 under one job id); 1080p on free time, up to 4K on prepaid credit; inputs and results deleted
21
21
  24 hours after last use (7 days for keys that have topped up).
22
22
 
23
23
  Official site: https://render.janction.jp · MCP endpoint: `https://render.janction.jp/mcp` · Fact sheet: https://render.janction.jp/facts
@@ -76,7 +76,7 @@ Keep 3D files in a repository? Add a button that lets anyone render them from th
76
76
  errors with the fix, spending caps per key, safe retries with `Idempotency-Key`.
77
77
  - **3D files and turntables.** glTF / GLB, FBX, USD, OBJ, STL, PLY, Alembic are imported into an empty scene with a camera and
78
78
  HDRI lighting; `orbit=True` makes a turntable. Fixed-price outcomes: `POST /v1/outcomes/turntable` and `/product-shot`.
79
- - **Honest limits.** a 100 JPY welcome credit for a new key with no card (14 days; one per network every 30 days) and 400 JPY more after saving a card that is not charged, then 0.1 JPY per GPU-second (previews free up to 2 GPU-minutes a day), finals up to 1,000 frames per instruction as parts of 240 (1080p on free time, up to 4K on prepaid credit), one GPU shared with another
79
+ - **Honest limits.** a 100 JPY welcome credit for a new key with no card (14 days; one per network every 30 days) and 400 JPY more after saving a card that is not charged, then 0.1 JPY per GPU-second (previews free up to 2 GPU-minutes a day), finals up to 3,000 frames per instruction as parts of 240 (1080p on free time, up to 4K on prepaid credit), one GPU shared with another
80
80
  workload (jobs can wait; the ETA says so). Inputs and results are deleted 24 hours after last use (7 days for keys that have topped up) and never used for training.
81
81
  - **Not Render.com.** Same word, different product.
82
82
 
@@ -381,7 +381,7 @@ nothing adds 400 JPY more, once per card (up to 500 JPY in all). After it is use
381
381
  free up to 2 GPU-minutes a day and finals cost 0.1 JPY per
382
382
  GPU-second from prepaid credit, bought by card at a Stripe Checkout page (from 500 JPY; 2,000 JPY gives 2,200 JPY of credit,
383
383
  5,000 gives 5,750, 10,000 gives 12,000; the first top-up in the welcome period counts 1.5x, bonus up to 500 JPY). Only the part
384
- the free time does not cover is charged. Finals go up to 1,000 frames per instruction (run as parts of 240 under one job id): 1080p on free time, up to 4K (3840x2160) on prepaid credit. `POST /v1/estimate` quotes what will be charged
384
+ the free time does not cover is charged. Finals go up to 3,000 frames per instruction (run as parts of 240 under one job id): 1080p on free time, up to 4K (3840x2160) on prepaid credit. `POST /v1/estimate` quotes what will be charged
385
385
  (`cost.estimated_yen`), the currency and an expiry before any GPU time is used: https://render.janction.jp/pricing
386
386
 
387
387
  ## Troubleshooting
@@ -1,3 +1,3 @@
1
1
  """JANCTION Render — ターミナルの AI エージェントから GPU で 3DCG を描く。"""
2
2
 
3
- __version__ = "0.4.83"
3
+ __version__ = "0.4.87"
@@ -28,6 +28,8 @@ ARCHIVE_MIN_FILES = 8 # これ以上の数のファイルは zip で 1 回で
28
28
  # 届かず HTML の 413 になるので、送る前に断ってリンク(scene_url / asset_urls)で渡すよう言う。素材は 100 MB ずつに分けて送る
29
29
  # (10/12)。手元や自前の受付には掛けない。JANCTION_RENDER_MAX_UPLOAD_MB で変えられる(0 で見ない)
30
30
  EDGE_UPLOAD_MB = 100
31
+ # この大きさを超えるシーンは分けて送る(10/13)。Cloudflare の 100 MB が 10^8 バイトなら 95.4 MiB から先は 1 回では通らない
32
+ RESUMABLE_ABOVE = 64 * 1024 * 1024
31
33
 
32
34
 
33
35
  def select_artifacts(artifacts: list[dict[str, Any]], only: Optional[str] = None) -> list[dict[str, Any]]:
@@ -428,13 +430,18 @@ class Client:
428
430
  out = None
429
431
  if out is None:
430
432
  cap = self.upload_cap_bytes()
431
- if cap and p.stat().st_size > cap:
432
- raise self._too_large(f"the scene file {p.name}", p.stat().st_size, cap, "scene_url")
433
- with p.open("rb") as fh:
434
- r = self.s.post(f"{self.server}/v1/files", headers=self._headers(),
435
- files={"file": (p.name, fh, "application/octet-stream")}, timeout=1800)
436
- self._raise(r)
437
- out = r.json()
433
+ if cap and p.stat().st_size > min(cap, RESUMABLE_ABOVE):
434
+ # 1 回で送れる大きさ(Cloudflare の 100 MB)に近い・超える: 分けて送る(/v1/uploads、10/13)。受付が古ければ前のとおり断る
435
+ done = self.upload_resumable(p, digest)
436
+ if done is None:
437
+ raise self._too_large(f"the scene file {p.name}", p.stat().st_size, cap, "scene_url")
438
+ out = done.get("scene") or done
439
+ else:
440
+ with p.open("rb") as fh:
441
+ r = self.s.post(f"{self.server}/v1/files", headers=self._headers(),
442
+ files={"file": (p.name, fh, "application/octet-stream")}, timeout=1800)
443
+ self._raise(r)
444
+ out = r.json()
438
445
  items: list[tuple[str, Path]] = []
439
446
  missing: list[str] = []
440
447
  if p.suffix.lower() == ".blend" and trace_assets:
@@ -485,6 +492,113 @@ class Client:
485
492
  out["missing_assets"] = missing
486
493
  return out
487
494
 
495
+ # ---- 分けて送る(10/13、docs/67。最大 5 GiB の .blend)----------------------------------------
496
+
497
+ _RESUME_FILE = Path.home() / ".janction-render-uploads.json"
498
+
499
+ def _resume_get(self, ident: str) -> Optional[str]:
500
+ try:
501
+ return json.loads(self._RESUME_FILE.read_text(encoding="utf-8")).get(ident)
502
+ except (OSError, ValueError, AttributeError):
503
+ return None
504
+
505
+ def _resume_set(self, ident: str, url: Optional[str]) -> None:
506
+ try:
507
+ data = json.loads(self._RESUME_FILE.read_text(encoding="utf-8")) if self._RESUME_FILE.exists() else {}
508
+ if not isinstance(data, dict):
509
+ data = {}
510
+ except (OSError, ValueError):
511
+ data = {}
512
+ if url:
513
+ data[ident] = url
514
+ else:
515
+ data.pop(ident, None)
516
+ try:
517
+ self._RESUME_FILE.write_text(json.dumps(data), encoding="utf-8")
518
+ except OSError:
519
+ pass
520
+
521
+ def upload_resumable(self, path: str | Path, digest: str = "",
522
+ progress: Optional[Callable[[int, int], None]] = None, part_bytes: int = 0) -> Optional[dict[str, Any]]:
523
+ """100 MB を超えるファイルを部分に分けて送る(POST /v1/uploads → PATCH、tus 1.0)。返り値は受付の状態(scene_id・scene)。
524
+ 通信が切れたら位置を聞き直して続きから送る。途中で止めても、同じコマンドをもう一度打てば続きから(~/.janction-render-uploads.json)。
525
+ 受付が分けて送る道を持っていなければ None。"""
526
+ import base64
527
+
528
+ p = Path(path)
529
+ size = p.stat().st_size
530
+ digest = digest or sha256_of(p)
531
+ ident = f"{self.server}|{digest}|{size}"
532
+ tus = {"Tus-Resumable": "1.0.0"}
533
+ url, off = self._resume_get(ident), 0
534
+ if url:
535
+ try:
536
+ h = self.s.head(url, headers={**self._headers(), **tus}, timeout=30)
537
+ if h.status_code == 200 and int(h.headers.get("Upload-Length") or -1) == size:
538
+ off = int(h.headers.get("Upload-Offset") or 0)
539
+ else:
540
+ url = None
541
+ except (ClientError, ValueError):
542
+ url = None
543
+ part = part_bytes
544
+ if not url:
545
+ meta = (f"filename {base64.b64encode(p.name.encode('utf-8')).decode()},"
546
+ f"sha256 {base64.b64encode(digest.encode()).decode()}")
547
+ r = self.s.post(f"{self.server}/v1/uploads", timeout=60,
548
+ headers={**self._headers(), **tus, "Upload-Length": str(size), "Upload-Metadata": meta})
549
+ if r.status_code in (404, 405):
550
+ return None
551
+ self._raise(r)
552
+ url = f"{self.server}{r.headers['Location']}"
553
+ part = part or int(r.json().get("part_bytes") or 0)
554
+ self._resume_set(ident, url)
555
+ part = part or 32 * 1024 * 1024
556
+ tries = 0
557
+ if progress:
558
+ progress(off, size)
559
+ with p.open("rb") as fh:
560
+ while True:
561
+ fh.seek(off)
562
+ data = fh.read(part)
563
+ try:
564
+ r = self.s.patch(url, data=data, timeout=600, headers={
565
+ **self._headers(), **tus, "Upload-Offset": str(off), "Content-Type": "application/offset+octet-stream"})
566
+ except ClientError:
567
+ r = None # つながらない・切れた: 待って位置を聞き直す
568
+ if r is not None and r.status_code == 200:
569
+ self._resume_set(ident, None)
570
+ if progress:
571
+ progress(size, size)
572
+ return r.json()
573
+ if r is not None and r.status_code == 204:
574
+ off, tries = int(r.headers.get("Upload-Offset") or off), 0
575
+ if progress:
576
+ progress(off, size)
577
+ continue
578
+ if r is not None and r.status_code == 409 and r.headers.get("Upload-Offset") is not None:
579
+ off = int(r.headers["Upload-Offset"])
580
+ continue
581
+ if r is not None and r.status_code < 500 and r.status_code not in (408, 409, 429) or r is not None and r.status_code == 507:
582
+ if r.status_code == 404:
583
+ self._resume_set(ident, None)
584
+ self._raise(r)
585
+ tries += 1
586
+ if tries > 8:
587
+ raise ClientError(0, "upload_interrupted",
588
+ f"the upload of {p.name} kept failing at {off} of {size} bytes; run the same command again "
589
+ "to continue from there", extra={"retryable": True, "offset": off})
590
+ time.sleep(min(30.0, 2.0 ** tries))
591
+ try:
592
+ h = self.s.head(url, headers={**self._headers(), **tus}, timeout=30)
593
+ if h.status_code == 200:
594
+ off = int(h.headers.get("Upload-Offset") or off)
595
+ elif h.status_code == 404:
596
+ self._resume_set(ident, None)
597
+ self._raise(h)
598
+ except ClientError as exc:
599
+ if exc.status:
600
+ raise
601
+
488
602
  def upload_cap_bytes(self) -> int:
489
603
  """1 回の送信の上限(バイト)。0 は見ない(手元・自前の受付)。"""
490
604
  raw = (os.environ.get("JANCTION_RENDER_MAX_UPLOAD_MB") or "").strip()
@@ -8,6 +8,7 @@
8
8
  受付の server/jobs.py もプリセットの名前はここから取る。"""
9
9
  from __future__ import annotations
10
10
 
11
+ import os
11
12
  from typing import Any, Optional
12
13
 
13
14
  PURPOSE_PRESETS = ("product", "turntable", "animation")
@@ -89,8 +90,10 @@ def estimate_next(est: dict[str, Any]) -> str:
89
90
  q = est.get("quota") or {}
90
91
  cost = est.get("cost") or {}
91
92
  if q and q.get("fits_size") is False:
92
- return (f"too big for one job: at most {q.get('max_frames_per_job')} frames per job part (1920x1080 on free time, 3840x2160 "
93
- "on prepaid credit); the service splits longer instructions into parts under one job id, or lower the size")
93
+ most = q.get("max_frames_per_instruction") or q.get("max_frames_per_job")
94
+ return (f"too big: at most {most} frames per render_final call (longer than {q.get('max_frames_per_job')} frames runs as "
95
+ "parts under one job id) and 1920x1080 on free time, 3840x2160 for keys with prepaid credit; split the frame range "
96
+ "into several calls or lower the size")
94
97
  if q and not q.get("fits_today", True) and q.get("mode") == "free_allowance":
95
98
  # 有料モード: 無料枠を超えた分は残高から(10/8)。額と残高を言ってから
96
99
  return (f"this goes past the free GPU time left: about {cost.get('estimated_yen')} JPY would be charged from credit "
@@ -223,3 +226,34 @@ def docs(flavor: str) -> dict[str, str]:
223
226
  f = "remote" if flavor == "remote" else "stdio"
224
227
  return {"ENV_DOC": _ENV_DOC[f] + _POLYHAVEN_DOC[f], "POLYHAVEN_DOC": _POLYHAVEN_DOC[f], "ORBIT_DOC": _ORBIT_DOC,
225
228
  "CAMERAS_DOC": _CAMERAS_DOC, "OUTPUT_DOC": _OUTPUT_DOC[f], "TRANSPARENT_DOC": _TRANSPARENT_DOC, "ENGINE_DOC": _ENGINE_DOC}
229
+
230
+
231
+ def auto_idempotency_key(tool: str, body: dict[str, Any], now: Optional[float] = None, window_s: int = 120) -> str:
232
+ """render_final にキーが無いときの自動の Idempotency-Key(10/13)。MCP の呼び出しが時間切れになって AI が同じ
233
+ render_final を呼び直すと、同じ仕事が 2 本描かれて 2 回引かれていた。同じ中身・同じ 2 分の枠なら同じキーになり、
234
+ 受付は最初の仕事を返す。中身が 1 つでも違えば別のキー(=別の仕事)。"""
235
+ import hashlib
236
+ import json as _json
237
+ import time as _time
238
+
239
+ t = _time.time() if now is None else now
240
+ blob = _json.dumps({"tool": tool, "body": body}, sort_keys=True, separators=(",", ":"), default=str)
241
+ return f"auto:{int(t // window_s)}:" + hashlib.sha256(blob.encode()).hexdigest()[:32]
242
+
243
+
244
+ def digest(text: str) -> str:
245
+ """長い引数(scene_script)をキーに入れるための短い要約。"""
246
+ import hashlib
247
+
248
+ return hashlib.sha256((text or "").encode("utf-8", "replace")).hexdigest()[:24] if text else ""
249
+
250
+
251
+ def file_stamp(path: str) -> str:
252
+ """手元のファイルの「同じもの」の目印(パス・大きさ・更新時刻)。中身を読まずに済ませる。"""
253
+ if not path:
254
+ return ""
255
+ try:
256
+ st = os.stat(os.path.expanduser(path))
257
+ return f"{os.path.abspath(os.path.expanduser(path))}:{st.st_size}:{int(st.st_mtime)}"
258
+ except OSError:
259
+ return path
@@ -658,7 +658,13 @@ def render_final(
658
658
  camera=camera or None, transparent=(True if transparent else None), notify_url=notify_url or None,
659
659
  max_yen=(max_yen if max_yen >= 0 else None), idempotency_key=idempotency_key or None,
660
660
  **_job_params(environment, environment_strength, environment_visible, blender, engine=engine), **extra)
661
- j = c.submit(sid, kind="final", **_with_preset(params, preset, "final"))
661
+ params = _with_preset(params, preset, "final")
662
+ if not params.get("idempotency_key"):
663
+ # 時間切れのあとの呼び直しで 2 本描かない(10/13、mcp_common.auto_idempotency_key)
664
+ params["idempotency_key"] = mcp_common.auto_idempotency_key(
665
+ "render_final", {"scene_id": scene_id, "scene_path": mcp_common.file_stamp(scene_path), "scene_url": scene_url,
666
+ "scene_script": mcp_common.digest(scene_script), "assets": assets, **params})
667
+ j = c.submit(sid, kind="final", **params)
662
668
  except ClientError as exc:
663
669
  block = _payment_block(exc) or _quota_block(exc)
664
670
  return _j(block if block else _fail(exc))
@@ -96,7 +96,7 @@ def blend_to_mp4_text(scene: str, frames: str, resolution: str, remote: bool) ->
96
96
  span = f"frames {fr}" if fr else "the scene's own frame range"
97
97
  return (f"Render this Blender animation to an MP4 with JANCTION Render: {scene.strip()}. " + _scene_hint(remote)
98
98
  + f"Use {span} at {res}. Start with scene_info (frame range, cameras, missing files), then render_preview with "
99
- "four frames spread over the range (frames='1-N' tiles them in one image). One instruction takes up to 1,000 frames "
99
+ "four frames spread over the range (frames='1-N' tiles them in one image). One instruction takes up to 3,000 frames "
100
100
  "(the service runs them as parts of 240 under one job id; 1080p on free time, 4K on prepaid credit): tell me the plan. " + _CONFIRM)
101
101
 
102
102
 
@@ -151,7 +151,7 @@ def register(mcp: Any, remote: bool = False) -> list[str]:
151
151
 
152
152
  @mcp.prompt(name="blend_to_mp4", title="Render a Blender animation to MP4",
153
153
  description="An animated .blend to MP4 on cloud GPUs: reads the scene, previews four frames, plans the render "
154
- "(up to 1,000 frames per instruction, run as parts of 240), then renders after you confirm.")
154
+ "(up to 3,000 frames per instruction, run as parts of 240), then renders after you confirm.")
155
155
  def blend_to_mp4(scene: Annotated[str, Field(description=_SCENE)],
156
156
  frames: Annotated[str, Field(description="Frame range such as 1-120 (empty: the scene's own range)")] = "",
157
157
  resolution: Annotated[str, Field(description="WIDTHxHEIGHT, up to 1920x1080 (default 1920x1080)")] = "1920x1080") -> str:
@@ -30,6 +30,7 @@ FREE_GPU_MINUTES_PER_KEY_PER_DAY = 10
30
30
  FREE_MAX_FRAMES = 240
31
31
  FREE_MAX_SIZE = "1920x1080"
32
32
  RETENTION_HOURS = 24
33
+ PAID_RETENTION_DAYS = 7 # 一度でもチャージした鍵(core.paid_ttl_seconds の既定 168 時間)
33
34
  YEN_PER_GPU_SECOND = 0.1 # 無料枠を超えた分の単価(10/8 から有料。billing.yen_per_gpu_second の既定値)
34
35
  # ようこそクレジット(docs/46、JR_FREE_MODEL=welcome)。billing の既定値と同じ(tests/test_tooldocs.py が突き合わせる)
35
36
  WELCOME_YEN = 500 # network の形: 新しい鍵に 500 円分
@@ -98,13 +99,13 @@ def _free_offer(short: bool = False) -> str:
98
99
  "GPU-second (prepaid)")
99
100
 
100
101
 
101
- MAX_FRAMES_PER_INSTRUCTION = 1000 # billing.max_frames_per_instruction の既定値(240 ずつの部分に分けて 1 つの仕事 ID で)
102
+ MAX_FRAMES_PER_INSTRUCTION = 3000 # billing.max_frames_per_instruction の既定値(240 ずつの部分に分けて 1 つの仕事 ID で)
102
103
  PAID_MAX_SIZE = "3840x2160" # 先払いのクレジットでの上限(無料の時間は 1080p まで)
103
104
 
104
105
 
105
106
  def _limits() -> str:
106
- return (f"LIMITS: {_free_offer(short=True)}; up to {MAX_FRAMES_PER_INSTRUCTION:,} frames per instruction (parts of {FREE_MAX_FRAMES}), "
107
- f"{FREE_MAX_SIZE} free / {PAID_MAX_SIZE} prepaid; kept {RETENTION_HOURS} h.")
107
+ return (f"LIMITS: {_free_offer(short=True)}; up to {MAX_FRAMES_PER_INSTRUCTION:,} frames per call (parts of {FREE_MAX_FRAMES}), "
108
+ f"{FREE_MAX_SIZE} free / {PAID_MAX_SIZE} prepaid; kept {RETENTION_HOURS} h ({PAID_RETENTION_DAYS} d if topped up).")
108
109
 
109
110
 
110
111
  def _presets(flavor: str) -> str:
@@ -129,7 +130,7 @@ def _free_left() -> str:
129
130
  "after it)")
130
131
  return "today's free quota"
131
132
  _SCRIPTS = ("SCRIPTS: Blender 5.0 bpy; build only the scene, camera and frame range (the service sets resolution and samples); "
132
- "`import jr_assets; jr_assets.frame_camera()` fits every object; a script that raises fails the job (nothing rendered).")
133
+ "`import jr_assets; jr_assets.frame_camera()` fits every object; a script that raises renders nothing.")
133
134
  _MORE = f"Examples and docs: {SITE}/examples, {SITE}/llms.txt"
134
135
 
135
136
 
@@ -185,12 +186,12 @@ def descriptions(flavor: str, wait_cap_s: int = 50) -> dict[str, str]:
185
186
  "Returns job_id and estimate.human at once; then render_status until done, and render_download(job_id, only='mp4') "
186
187
  "for the video. ")
187
188
  + _NOT_HERE + " Use the same environment and blender as the approved preview. "
188
- f"Limits: up to {FREE_MAX_FRAMES} frames and {FREE_MAX_SIZE} per job; {_free_offer()}; ask the user before a render "
189
- "that the free time does not cover; files are "
190
- f"deleted {RETENTION_HOURS} hours after last use. preset='product' | 'turntable' | 'animation' fills the settings for "
191
- "that purpose. Options on each parameter: output (png, exr, mp4, webm, prores, gif, webp), orbit=True (turntable MP4), "
192
- f"cameras (one image per named camera), transparent, engine='eevee', notify_url (one POST when done), {a} with "
193
- "'polyhaven:<id>'. "
189
+ # 10/13: ここが「240 コマ・1080p まで」のままで、AI が長いアニメや 4K を断っていた(受付は 3,000 コマ・先払いは 4K)
190
+ f"Limits: up to {MAX_FRAMES_PER_INSTRUCTION:,} frames per call (over {FREE_MAX_FRAMES} runs as parts under one job id); "
191
+ f"{FREE_MAX_SIZE} on free time, {PAID_MAX_SIZE} with prepaid credit; {_free_offer()}; ask before a render the "
192
+ f"free time does not cover; files are deleted {RETENTION_HOURS} h after last use ({PAID_RETENTION_DAYS} days once topped "
193
+ "up). preset='product' | 'turntable' | 'animation' fills the settings for that purpose. Options per parameter: "
194
+ f"output (png, exr, mp4, webm, prores, gif, webp), orbit, cameras, transparent, engine, notify_url, {a} ('polyhaven:<id>'). "
194
195
  + ("Pass scene_id from render_preview, or scene_script / scene_url for a new scene." if remote else
195
196
  "Pass scene_id from render_preview, or scene_path / scene_script / scene_url; files are saved locally."))
196
197
  scene_info = (
@@ -212,7 +213,8 @@ def descriptions(flavor: str, wait_cap_s: int = 50) -> dict[str, str]:
212
213
  "call render_download once status is done."))
213
214
  if remote:
214
215
  download = (
215
- "Get the download links of a job (no key needed, valid about 24 hours) and show them to the user; in a terminal, "
216
+ "Get the download links of a job (no key needed, valid about 24 hours; call again for fresh links while the files are "
217
+ f"kept, {RETENTION_HOURS} h after last use or {PAID_RETENTION_DAYS} days for keys that have topped up) and show them to the user; in a terminal, "
216
218
  "fetch them with curl -o. A finished preview also comes back as an inline image. wait_seconds > 0 first waits for "
217
219
  f"the job (the service waits at most {wait_cap_s} s per call; call again while it is still running). only picks "
218
220
  "the files: 'output' (default: the MP4 or the preview sheet, plus the first frames), 'mp4', 'sheet', 'frames' or "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.83
3
+ Version: 0.4.87
4
4
  Summary: Cloud GPU rendering for Blender scenes and 3D models (GLB, FBX, USD, OBJ, STL), built for AI agents: MCP server (remote + stdio), HTTP API and CLI. Product shots, 360-degree turntables, animations and stills; preview in seconds with a critic; no local Blender or GPU. A 100 JPY welcome credit for a new key (one per network every 30 days) and 400 JPY more for saving a card that is not charged, then 0.1 JPY per GPU-second.
5
5
  Author-email: "JasmyLab Inc." <contact@jasmylab.com>
6
6
  License-Expression: MIT
@@ -57,7 +57,7 @@ Claude Code, Codex, Cursor or any MCP client**: an MCP server (remote and stdio)
57
57
  JANCTION Render is a separate product from SmartRender (JasmyLab's distributed rendering for people at a desktop) and is
58
58
  not affiliated with Render.com or the Render Network. The numbers, as of 2026-10-08 (the primary source is
59
59
  https://render.janction.jp/facts.json): a 100 JPY welcome credit (1,000 GPU-seconds, 14 days, no card) for a new key, one per network every 30 days (further keys on the same network share what is left), plus 400 JPY more once a card is saved on a page that charges nothing (once per card); after that, previews
60
- are free up to 2 GPU-minutes a day and finals cost 0.1 JPY per GPU-second from prepaid credit (paid use started on 2026-10-08); finals up to 1,000 frames per instruction (run as parts of 240 under one job id); 1080p on free time, up to 4K on prepaid credit; inputs and results deleted
60
+ are free up to 2 GPU-minutes a day and finals cost 0.1 JPY per GPU-second from prepaid credit (paid use started on 2026-10-08); finals up to 3,000 frames per instruction (run as parts of 240 under one job id); 1080p on free time, up to 4K on prepaid credit; inputs and results deleted
61
61
  24 hours after last use (7 days for keys that have topped up).
62
62
 
63
63
  Official site: https://render.janction.jp · MCP endpoint: `https://render.janction.jp/mcp` · Fact sheet: https://render.janction.jp/facts
@@ -116,7 +116,7 @@ Keep 3D files in a repository? Add a button that lets anyone render them from th
116
116
  errors with the fix, spending caps per key, safe retries with `Idempotency-Key`.
117
117
  - **3D files and turntables.** glTF / GLB, FBX, USD, OBJ, STL, PLY, Alembic are imported into an empty scene with a camera and
118
118
  HDRI lighting; `orbit=True` makes a turntable. Fixed-price outcomes: `POST /v1/outcomes/turntable` and `/product-shot`.
119
- - **Honest limits.** a 100 JPY welcome credit for a new key with no card (14 days; one per network every 30 days) and 400 JPY more after saving a card that is not charged, then 0.1 JPY per GPU-second (previews free up to 2 GPU-minutes a day), finals up to 1,000 frames per instruction as parts of 240 (1080p on free time, up to 4K on prepaid credit), one GPU shared with another
119
+ - **Honest limits.** a 100 JPY welcome credit for a new key with no card (14 days; one per network every 30 days) and 400 JPY more after saving a card that is not charged, then 0.1 JPY per GPU-second (previews free up to 2 GPU-minutes a day), finals up to 3,000 frames per instruction as parts of 240 (1080p on free time, up to 4K on prepaid credit), one GPU shared with another
120
120
  workload (jobs can wait; the ETA says so). Inputs and results are deleted 24 hours after last use (7 days for keys that have topped up) and never used for training.
121
121
  - **Not Render.com.** Same word, different product.
122
122
 
@@ -421,7 +421,7 @@ nothing adds 400 JPY more, once per card (up to 500 JPY in all). After it is use
421
421
  free up to 2 GPU-minutes a day and finals cost 0.1 JPY per
422
422
  GPU-second from prepaid credit, bought by card at a Stripe Checkout page (from 500 JPY; 2,000 JPY gives 2,200 JPY of credit,
423
423
  5,000 gives 5,750, 10,000 gives 12,000; the first top-up in the welcome period counts 1.5x, bonus up to 500 JPY). Only the part
424
- the free time does not cover is charged. Finals go up to 1,000 frames per instruction (run as parts of 240 under one job id): 1080p on free time, up to 4K (3840x2160) on prepaid credit. `POST /v1/estimate` quotes what will be charged
424
+ the free time does not cover is charged. Finals go up to 3,000 frames per instruction (run as parts of 240 under one job id): 1080p on free time, up to 4K (3840x2160) on prepaid credit. `POST /v1/estimate` quotes what will be charged
425
425
  (`cost.estimated_yen`), the currency and an expiry before any GPU time is used: https://render.janction.jp/pricing
426
426
 
427
427
  ## Troubleshooting
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "janction-render"
7
- version = "0.4.83"
7
+ version = "0.4.87"
8
8
  description = "Cloud GPU rendering for Blender scenes and 3D models (GLB, FBX, USD, OBJ, STL), built for AI agents: MCP server (remote + stdio), HTTP API and CLI. Product shots, 360-degree turntables, animations and stills; preview in seconds with a critic; no local Blender or GPU. A 100 JPY welcome credit for a new key (one per network every 30 days) and 400 JPY more for saving a card that is not charged, then 0.1 JPY per GPU-second."
9
9
  readme = "README.md"
10
10
  license = "MIT"