janction-render 0.4.63__tar.gz → 0.4.64__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.63 → janction_render-0.4.64}/PKG-INFO +1 -1
  2. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/__init__.py +1 -1
  3. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/mcp_common.py +61 -0
  4. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/mcp_server.py +6 -38
  5. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render.egg-info/PKG-INFO +1 -1
  6. {janction_render-0.4.63 → janction_render-0.4.64}/pyproject.toml +1 -1
  7. {janction_render-0.4.63 → janction_render-0.4.64}/LICENSE +0 -0
  8. {janction_render-0.4.63 → janction_render-0.4.64}/README.md +0 -0
  9. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/brief.py +0 -0
  10. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/cli.py +0 -0
  11. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/client.py +0 -0
  12. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/hookup.py +0 -0
  13. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/links.py +0 -0
  14. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/mcp_app.html +0 -0
  15. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/prompts.py +0 -0
  16. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render/tooldocs.py +0 -0
  17. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render.egg-info/SOURCES.txt +0 -0
  18. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render.egg-info/dependency_links.txt +0 -0
  19. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render.egg-info/entry_points.txt +0 -0
  20. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render.egg-info/requires.txt +0 -0
  21. {janction_render-0.4.63 → janction_render-0.4.64}/janction_render.egg-info/top_level.txt +0 -0
  22. {janction_render-0.4.63 → janction_render-0.4.64}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.63
3
+ Version: 0.4.64
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
@@ -1,3 +1,3 @@
1
1
  """JANCTION Render — ターミナルの AI エージェントから GPU で 3DCG を描く。"""
2
2
 
3
- __version__ = "0.4.63"
3
+ __version__ = "0.4.64"
@@ -60,6 +60,67 @@ def job_params(environment: str, environment_strength: float, environment_visibl
60
60
  return out
61
61
 
62
62
 
63
+ # ------------------------------------------------------------------ 見積もりとシーンの読み取りの返事(両方の道具で同じ)
64
+
65
+ def estimate_body(kind: str, scene_id: str, frames: str, frame_start: int, frame_end: int, width: int, height: int, samples: int,
66
+ cameras: Any, preset: str) -> tuple[dict[str, Any], bool]:
67
+ """render_estimate の引数から受付への本文を作る。戻り値は (本文, シーンの読み取りが先に要るか)。
68
+ preset='animation' でコマの指定が無ければ、呼ぶ側が scene_info を済ませてから見積もる。"""
69
+ from .client import parse_frames
70
+ body: dict[str, Any] = {"kind": kind, "width": width, "height": height, "samples": samples}
71
+ if scene_id:
72
+ body["scene_id"] = scene_id
73
+ if kind == "preview":
74
+ body["frames"] = parse_frames(frames) if (frames or not preset) else None
75
+ else:
76
+ body.update({"frame_start": frame_start, "frame_end": frame_end})
77
+ if cameras:
78
+ body["cameras"] = list(cameras)
79
+ if kind != "preview":
80
+ body.pop("frame_end", None)
81
+ needs_info = bool(preset == "animation" and scene_id
82
+ and (body.get("frames") is None if kind == "preview" else frame_end <= frame_start))
83
+ body = {k: v for k, v in with_preset(body, preset, kind).items() if v is not None}
84
+ return body, needs_info
85
+
86
+
87
+ def estimate_next(est: dict[str, Any]) -> str:
88
+ """見積もりの返事の next(エージェントへの次の一手)。上限の言い方は docs/60 BRAND-02 に合わせる。"""
89
+ q = est.get("quota") or {}
90
+ cost = est.get("cost") or {}
91
+ 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")
94
+ if q and not q.get("fits_today", True) and q.get("mode") == "free_allowance":
95
+ # 有料モード: 無料枠を超えた分は残高から(10/8)。額と残高を言ってから
96
+ return (f"this goes past the free GPU time left: about {cost.get('estimated_yen')} JPY would be charged from credit "
97
+ f"(balance {cost.get('balance_yen')} JPY). Tell the user the time and the price and ask before "
98
+ "render_final; a smaller job (fewer frames, lower resolution or samples) may stay free")
99
+ if q and not q.get("fits_today", True):
100
+ return ("this does not fit today's remaining free GPU time; propose fewer frames, lower resolution or samples, "
101
+ "or wait until resets_at")
102
+ return "tell the user the estimate and ask before calling render_final"
103
+
104
+
105
+ def scene_info_reply(j: dict[str, Any], assets_param: str) -> dict[str, Any]:
106
+ """scene_info の返事(info に scene_id・エンジンの注意・警告・欠けたファイルの案内・次の一手)。assets_param は素材の渡し方の文
107
+ (remote: 'asset_urls'、stdio: 'assets=[...]')。"""
108
+ from .brief import scene_next, trim
109
+ info = dict(j.get("info") or {})
110
+ info["scene_id"] = j.get("scene_id")
111
+ if info.get("engine") and info["engine"] != "CYCLES":
112
+ info["engine_note"] = "JANCTION renders with CYCLES; the scene's engine setting is not used"
113
+ if j.get("warnings"):
114
+ info["blender_warnings"] = j["warnings"]
115
+ if info.get("missing_files"):
116
+ info["note"] = (f"missing_files will render pink/black; send them with {assets_param} (kept next to the scene), "
117
+ "pack them into the .blend (File > External Data > Pack) or fix the paths")
118
+ if isinstance(info.get("object_names"), list):
119
+ info["object_names"] = trim(info["object_names"], 30)
120
+ info.update(scene_next(info)) # 次の一手: シーンの縦横比に合わせた試し描き(10/9)
121
+ return info
122
+
123
+
63
124
  # ------------------------------------------------------------------ 引数の説明(道具の description の末尾に足す)
64
125
 
65
126
  _ENGINE_DOC = (" engine: '' or 'cycles' (path tracing, the default) | 'eevee' (Blender's real-time rasterizer: about half the "
@@ -29,7 +29,7 @@ except ImportError: # mcp 1.x
29
29
  from mcp.server.fastmcp import FastMCP as MCPServer, Image # type: ignore
30
30
  from mcp.types import ToolAnnotations
31
31
 
32
- from janction_render.brief import plan_fields, review_reply, scene_next, welcome_billing
32
+ from janction_render.brief import plan_fields, review_reply, welcome_billing
33
33
  from janction_render.brief import brief as _brief, estimate_brief, final_body_after_preview, followup_after_preview, \
34
34
  next_after_preview, trim, PRESENT_FIRST, preview_presentation, turntable_estimate
35
35
  from janction_render.client import Client, ClientError, parse_frames
@@ -390,18 +390,7 @@ def scene_info(scene_path: Annotated[str, D("scene_path", "")] = "",
390
390
  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") \
391
391
  if j["status"] in ("queued", "running") else "fix the scene and try again"
392
392
  return _j(b)
393
- info = dict(j.get("info") or {})
394
- info["scene_id"] = j["scene_id"]
395
- if info.get("engine") and info["engine"] != "CYCLES":
396
- info["engine_note"] = "JANCTION renders with CYCLES; the scene's engine setting is not used"
397
- if j.get("warnings"):
398
- info["blender_warnings"] = j["warnings"]
399
- if info.get("missing_files"):
400
- info["note"] = ("missing_files will render pink/black; send them with assets=[...] (kept next to the .blend), "
401
- "pack them into the .blend (File > External Data > Pack) or fix the paths")
402
- if isinstance(info.get("object_names"), list):
403
- info["object_names"] = trim(info["object_names"], 30)
404
- info.update(scene_next(info)) # 次の一手: シーンの縦横比に合わせた試し描き(10/9)
393
+ info = mcp_common.scene_info_reply(j, "assets=[...]")
405
394
  return _j(_attach_upload_notes(info))
406
395
 
407
396
 
@@ -579,37 +568,16 @@ def render_estimate(scene_id: Annotated[str, D("scene_id", "")] = "",
579
568
  c = _client()
580
569
  try:
581
570
  preset = _preset_of(preset)
582
- params: dict[str, Any] = {"kind": kind, "width": width, "height": height, "samples": samples}
583
- if kind == "preview":
584
- params["frames"] = parse_frames(frames) if (frames or not preset) else None
585
- else:
586
- params.update({"frame_start": frame_start, "frame_end": frame_end})
587
- if cameras:
588
- params["cameras"] = list(cameras)
589
- if kind != "preview":
590
- params.pop("frame_end", None)
591
- if preset == "animation" and scene_id and (params.get("frames") is None if kind == "preview" else frame_end <= frame_start):
571
+ params, needs_info = mcp_common.estimate_body(kind, scene_id, frames, frame_start, frame_end, width, height, samples, cameras, preset)
572
+ params.pop("scene_id", None)
573
+ if needs_info:
592
574
  _ensure_scene_info(c, scene_id)
593
- params = {k: v for k, v in _with_preset(params, preset, kind).items() if v is not None}
594
575
  est = c.estimate(scene_id or None, **params)
595
576
  except (ClientError, ValueError) as exc:
596
577
  return _j(_fail(exc))
597
578
  out = estimate_brief(est) or {}
598
579
  out["kind"] = kind
599
- q = est.get("quota") or {}
600
- cost = est.get("cost") or {}
601
- if q and q.get("fits_size") is False:
602
- out["next"] = f"too big for one job (max {q.get('max_frames_per_job')} frames and 1920x1080 per job); split it"
603
- elif q and not q.get("fits_today", True) and q.get("mode") == "free_allowance":
604
- # 有料モード(10/8〜): 無料枠を超えた分は残高から。額と残高を言ってから
605
- out["next"] = (f"this goes past the free GPU time left: about {cost.get('estimated_yen')} JPY would be charged from "
606
- f"credit (balance {cost.get('balance_yen')} JPY). Tell the user the time and the price and ask before "
607
- "render_final; a smaller job (fewer frames, lower resolution or samples) may stay free")
608
- elif q and not q.get("fits_today", True):
609
- out["next"] = ("this does not fit today's remaining free GPU time; propose fewer frames, lower resolution or "
610
- "samples, or wait until resets_at")
611
- else:
612
- out["next"] = "tell the user the estimate and ask before calling render_final"
580
+ out["next"] = mcp_common.estimate_next(est)
613
581
  return _j(out)
614
582
 
615
583
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: janction-render
3
- Version: 0.4.63
3
+ Version: 0.4.64
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "janction-render"
7
- version = "0.4.63"
7
+ version = "0.4.64"
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"