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.
- {janction_render-0.4.83 → janction_render-0.4.87}/PKG-INFO +4 -4
- {janction_render-0.4.83 → janction_render-0.4.87}/README.md +3 -3
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/__init__.py +1 -1
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/client.py +121 -7
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/mcp_common.py +36 -2
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/mcp_server.py +7 -1
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/prompts.py +2 -2
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/tooldocs.py +13 -11
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/PKG-INFO +4 -4
- {janction_render-0.4.83 → janction_render-0.4.87}/pyproject.toml +1 -1
- {janction_render-0.4.83 → janction_render-0.4.87}/LICENSE +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/brief.py +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/cli.py +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/hookup.py +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/links.py +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render/mcp_app.html +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/SOURCES.txt +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/dependency_links.txt +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/entry_points.txt +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/requires.txt +0 -0
- {janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/top_level.txt +0 -0
- {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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
@@ -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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
189
|
-
"
|
|
190
|
-
f"
|
|
191
|
-
"
|
|
192
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{janction_render-0.4.83 → janction_render-0.4.87}/janction_render.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|