pulze-scenemanager 3.0.0__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.
- pulze_scenemanager-3.0.0/.gitignore +3 -0
- pulze_scenemanager-3.0.0/LICENSE +21 -0
- pulze_scenemanager-3.0.0/PKG-INFO +51 -0
- pulze_scenemanager-3.0.0/README.md +31 -0
- pulze_scenemanager-3.0.0/pyproject.toml +37 -0
- pulze_scenemanager-3.0.0/scenemanager/__init__.py +23 -0
- pulze_scenemanager-3.0.0/scenemanager/client.py +138 -0
- pulze_scenemanager-3.0.0/scenemanager/models.pyi +331 -0
- pulze_scenemanager-3.0.0/scenemanager/openapi.json +9784 -0
- pulze_scenemanager-3.0.0/scenemanager/py.typed +0 -0
- pulze_scenemanager-3.0.0/scenemanager/wire.py +238 -0
- pulze_scenemanager-3.0.0/scripts/build.py +60 -0
- pulze_scenemanager-3.0.0/scripts/generate.py +68 -0
- pulze_scenemanager-3.0.0/tests/test_client.py +106 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sourceline / Pulze
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pulze-scenemanager
|
|
3
|
+
Version: 3.0.0
|
|
4
|
+
Summary: Official Scene Manager API client for Python
|
|
5
|
+
Project-URL: Homepage, https://pulze.io/products/scene-manager
|
|
6
|
+
Author-email: Pulze <support@pulze.io>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Keywords: 3dsmax,corona,pulze,render-setups,scene-manager,vray
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# pulze-scenemanager
|
|
22
|
+
|
|
23
|
+
The official Python client for [Scene Manager](https://pulze.io/products/scene-manager)'s local API: setups, folders, modules, batch renders and scene comments in 3ds Max.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install pulze-scenemanager
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
import scenemanager as sm
|
|
31
|
+
|
|
32
|
+
scene = sm.connect() # Scene Manager on this computer
|
|
33
|
+
|
|
34
|
+
for setup in scene.setups.list_setups().setups:
|
|
35
|
+
print(setup.name)
|
|
36
|
+
|
|
37
|
+
first = scene.setups.list_setups().setups[0]
|
|
38
|
+
scene.setups.activate_setup(first.id)
|
|
39
|
+
scene.render.start_render(body={"setups": [first.id]})
|
|
40
|
+
batch = scene.render.wait() # until the batch render is over
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Standard library only.** No dependencies, so it also imports inside 3ds Max's own Python.
|
|
44
|
+
- **Scene Manager must be running** on this computer (open it from 3ds Max). It answers on `http://127.0.0.1:45710`; set `SCENEMANAGER_URL` or `SCENEMANAGER_PORT`, or pass a URL to `connect()`, to change that.
|
|
45
|
+
- **A Scene Manager licence** on this computer is needed for every call but `scene.health.get_health()`, which says whether there is one. Without it, a call raises `ApiError` with status `402`.
|
|
46
|
+
- **Methods are the API's operations**, grouped by the route's first segment (`scene.setups`, `scene.scene`, `scene.comments`, `scene.render`, ...) and named as in the API reference, the TypeScript client and `smcli`, in snake_case. Path parameters are positional, query parameters are named, and a request body is `body=`.
|
|
47
|
+
- **Answers read with dots** (`setup.name`); `to_dict()` gives the raw payload. Pictures (thumbnails, shots) are `bytes`. A refused call raises `ApiError` with `status` and Scene Manager's reason.
|
|
48
|
+
|
|
49
|
+
`models.pyi` types every payload for editors and type checkers.
|
|
50
|
+
|
|
51
|
+
MIT licensed. The Scene Manager application itself is licensed separately by Pulze.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# pulze-scenemanager
|
|
2
|
+
|
|
3
|
+
The official Python client for [Scene Manager](https://pulze.io/products/scene-manager)'s local API: setups, folders, modules, batch renders and scene comments in 3ds Max.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install pulze-scenemanager
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
import scenemanager as sm
|
|
11
|
+
|
|
12
|
+
scene = sm.connect() # Scene Manager on this computer
|
|
13
|
+
|
|
14
|
+
for setup in scene.setups.list_setups().setups:
|
|
15
|
+
print(setup.name)
|
|
16
|
+
|
|
17
|
+
first = scene.setups.list_setups().setups[0]
|
|
18
|
+
scene.setups.activate_setup(first.id)
|
|
19
|
+
scene.render.start_render(body={"setups": [first.id]})
|
|
20
|
+
batch = scene.render.wait() # until the batch render is over
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **Standard library only.** No dependencies, so it also imports inside 3ds Max's own Python.
|
|
24
|
+
- **Scene Manager must be running** on this computer (open it from 3ds Max). It answers on `http://127.0.0.1:45710`; set `SCENEMANAGER_URL` or `SCENEMANAGER_PORT`, or pass a URL to `connect()`, to change that.
|
|
25
|
+
- **A Scene Manager licence** on this computer is needed for every call but `scene.health.get_health()`, which says whether there is one. Without it, a call raises `ApiError` with status `402`.
|
|
26
|
+
- **Methods are the API's operations**, grouped by the route's first segment (`scene.setups`, `scene.scene`, `scene.comments`, `scene.render`, ...) and named as in the API reference, the TypeScript client and `smcli`, in snake_case. Path parameters are positional, query parameters are named, and a request body is `body=`.
|
|
27
|
+
- **Answers read with dots** (`setup.name`); `to_dict()` gives the raw payload. Pictures (thumbnails, shots) are `bytes`. A refused call raises `ApiError` with `status` and Scene Manager's reason.
|
|
28
|
+
|
|
29
|
+
`models.pyi` types every payload for editors and type checkers.
|
|
30
|
+
|
|
31
|
+
MIT licensed. The Scene Manager application itself is licensed separately by Pulze.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pulze-scenemanager"
|
|
7
|
+
version = "3.0.0"
|
|
8
|
+
description = "Official Scene Manager API client for Python"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Pulze", email = "support@pulze.io" }
|
|
14
|
+
]
|
|
15
|
+
keywords = ["3dsmax", "corona", "vray", "scene-manager", "render-setups", "pulze"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
]
|
|
26
|
+
# None, deliberately (as RenderFlow's SDK): the client binds to the OpenAPI document shipped inside
|
|
27
|
+
# the package (`scenemanager/wire.py` + `scenemanager/openapi.json`), so the whole SDK is standard
|
|
28
|
+
# library and imports inside 3ds Max's Python with nothing installed. The typed models are
|
|
29
|
+
# `models.pyi`, which only editors read; datamodel-code-generator is a dev-only tool of
|
|
30
|
+
# `scripts/generate.py`, never a requirement of the package.
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://pulze.io/products/scene-manager"
|
|
35
|
+
|
|
36
|
+
[tool.hatch.build.targets.wheel]
|
|
37
|
+
packages = ["scenemanager"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Scene Manager's Python client: setups, modules, renders and comments over its local REST API.
|
|
2
|
+
|
|
3
|
+
import scenemanager as sm
|
|
4
|
+
|
|
5
|
+
scene = sm.connect()
|
|
6
|
+
for setup in scene.setups.list_setups().setups:
|
|
7
|
+
print(setup.name)
|
|
8
|
+
|
|
9
|
+
Standard library only, so it imports inside 3ds Max's own Python with nothing installed. Scene
|
|
10
|
+
Manager must be running on this computer, with a licence.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from importlib.metadata import PackageNotFoundError, version as _version
|
|
14
|
+
|
|
15
|
+
from .client import Group, RenderGroup, SceneManager, connect, default_url
|
|
16
|
+
from .wire import ApiError, Data
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
__version__ = _version("pulze-scenemanager")
|
|
20
|
+
except PackageNotFoundError: # from a checkout
|
|
21
|
+
__version__ = "0.0.0"
|
|
22
|
+
|
|
23
|
+
__all__ = ["ApiError", "Data", "Group", "RenderGroup", "SceneManager", "connect", "default_url", "__version__"]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""The Scene Manager API client.
|
|
2
|
+
|
|
3
|
+
import scenemanager as sm
|
|
4
|
+
|
|
5
|
+
scene = sm.connect() # Scene Manager on this computer
|
|
6
|
+
for setup in scene.setups.list_setups().setups:
|
|
7
|
+
print(setup.name)
|
|
8
|
+
scene.setups.activate_setup(setup.id)
|
|
9
|
+
scene.render.start_render(body={"setups": [setup.id]})
|
|
10
|
+
scene.render.wait() # until the batch render is over
|
|
11
|
+
|
|
12
|
+
Operations are grouped by the route's first segment and named after the document's
|
|
13
|
+
``operationId``: the same names the REST route, the TypeScript SDK and ``smcli`` use, so one
|
|
14
|
+
operation reads the same everywhere. ``scene.setups.list_setups`` is ``GET /setups``;
|
|
15
|
+
``scene.setups.activate_setup(id)`` is ``POST /setups/{id}/activate``.
|
|
16
|
+
|
|
17
|
+
Nothing here is written per operation, and nothing is generated: the methods are the operations of
|
|
18
|
+
the OpenAPI document shipped inside this package (``scenemanager/openapi.json``), bound on first
|
|
19
|
+
use. Bodies are plain dicts; answers read with dot access (``setup.name``) and hand back the raw
|
|
20
|
+
payload via ``to_dict()``. The typed view of it all is ``models.pyi``, which the editor reads and
|
|
21
|
+
the interpreter never pays for: the whole client is standard library, so it runs inside 3ds Max's
|
|
22
|
+
own Python.
|
|
23
|
+
|
|
24
|
+
Scene Manager needs a licence on this computer: without one every call but
|
|
25
|
+
``scene.health.get_health()`` raises ``ApiError`` with status 402.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import os
|
|
31
|
+
import time
|
|
32
|
+
from typing import Any, Callable
|
|
33
|
+
|
|
34
|
+
from . import wire
|
|
35
|
+
from .wire import ApiError, Client
|
|
36
|
+
|
|
37
|
+
DEFAULT_PORT = 45710
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def default_url() -> str:
|
|
41
|
+
"""Where Scene Manager answers on this computer: ``SCENEMANAGER_URL``, else ``SCENEMANAGER_PORT`` (45710)."""
|
|
42
|
+
url = os.environ.get("SCENEMANAGER_URL", "").strip()
|
|
43
|
+
if url:
|
|
44
|
+
return url
|
|
45
|
+
return "http://127.0.0.1:{0}".format(os.environ.get("SCENEMANAGER_PORT", "").strip() or DEFAULT_PORT)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Group:
|
|
49
|
+
"""One group's operations, bound to Scene Manager: ``scene.setups``, ``scene.comments``."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, owner: "SceneManager", name: str) -> None:
|
|
52
|
+
self._owner = owner
|
|
53
|
+
self._name = name
|
|
54
|
+
self._operations = wire.operations()[name]
|
|
55
|
+
|
|
56
|
+
def __dir__(self) -> list[str]:
|
|
57
|
+
return sorted(self._operations)
|
|
58
|
+
|
|
59
|
+
def __getattr__(self, name: str) -> Callable[..., Any]:
|
|
60
|
+
try:
|
|
61
|
+
operation = self._operations[name]
|
|
62
|
+
except KeyError:
|
|
63
|
+
raise AttributeError(f"`{self._name}` has no operation `{name}`. Available: {', '.join(self.__dir__())}") from None
|
|
64
|
+
client = self._owner._client
|
|
65
|
+
|
|
66
|
+
def call(*args: Any, **kwargs: Any) -> Any:
|
|
67
|
+
return client.call(operation, args, kwargs)
|
|
68
|
+
|
|
69
|
+
call.__name__ = name
|
|
70
|
+
call.__doc__ = operation.doc
|
|
71
|
+
setattr(self, name, call) # resolved once; afterwards a plain attribute
|
|
72
|
+
return call
|
|
73
|
+
|
|
74
|
+
def __repr__(self) -> str:
|
|
75
|
+
return f"<scenemanager group {self._name}: {len(self._operations)} operations>"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
#: How a batch render ends.
|
|
79
|
+
FINISHED = ("done", "stopped")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class RenderGroup(Group):
|
|
83
|
+
"""``render``, plus the one wait that is not an operation of the document."""
|
|
84
|
+
|
|
85
|
+
def __dir__(self) -> list[str]:
|
|
86
|
+
return sorted({*super().__dir__(), "wait"})
|
|
87
|
+
|
|
88
|
+
def wait(self, *, timeout: float | None = None, interval: float = 2.0) -> Any:
|
|
89
|
+
"""Waits for the batch render to end (done or stopped). Answers the batch, or ``None`` when
|
|
90
|
+
there is none. Polls ``get_render``; ``timeout`` is seconds (none by default: a batch can
|
|
91
|
+
render for hours), and running out raises ``TimeoutError``."""
|
|
92
|
+
deadline = None if timeout is None else time.monotonic() + timeout
|
|
93
|
+
while True:
|
|
94
|
+
batch = self.get_render().batch
|
|
95
|
+
if batch is None or batch.state in FINISHED:
|
|
96
|
+
return batch
|
|
97
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
98
|
+
raise TimeoutError(f"the batch render had not ended after {timeout}s")
|
|
99
|
+
time.sleep(interval if deadline is None else min(interval, max(0.0, deadline - time.monotonic())))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
GROUPS = {"render": RenderGroup}
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class SceneManager:
|
|
106
|
+
"""A connection to Scene Manager on this computer. Prefer ``scenemanager.connect()``."""
|
|
107
|
+
|
|
108
|
+
def __init__(self, url: str | None = None, *, timeout: float = 120.0) -> None:
|
|
109
|
+
self.url = (url or default_url()).rstrip("/")
|
|
110
|
+
base_url = self.url if self.url.endswith("/api/v1") else f"{self.url}/api/v1"
|
|
111
|
+
self._client = Client(base_url=base_url, timeout=timeout)
|
|
112
|
+
self._groups: dict[str, Group] = {}
|
|
113
|
+
|
|
114
|
+
def __dir__(self) -> list[str]:
|
|
115
|
+
return sorted({*super().__dir__(), *self.groups()})
|
|
116
|
+
|
|
117
|
+
def groups(self) -> list[str]:
|
|
118
|
+
return sorted(wire.operations())
|
|
119
|
+
|
|
120
|
+
def __getattr__(self, name: str) -> Group:
|
|
121
|
+
if name.startswith("_"):
|
|
122
|
+
raise AttributeError(name)
|
|
123
|
+
if name not in self._groups:
|
|
124
|
+
if name not in self.groups():
|
|
125
|
+
raise AttributeError(f"Scene Manager has no `{name}`. Available: {', '.join(self.groups())}")
|
|
126
|
+
self._groups[name] = GROUPS.get(name, Group)(self, name)
|
|
127
|
+
return self._groups[name]
|
|
128
|
+
|
|
129
|
+
def __repr__(self) -> str:
|
|
130
|
+
return f"<SceneManager {self.url}>"
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def connect(url: str | None = None, *, timeout: float = 120.0) -> SceneManager:
|
|
134
|
+
"""Scene Manager on this computer (``SCENEMANAGER_URL`` or ``SCENEMANAGER_PORT`` to change)."""
|
|
135
|
+
return SceneManager(url, timeout=timeout)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
__all__ = ["ApiError", "Group", "RenderGroup", "SceneManager", "connect", "default_url"]
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
# generated by datamodel-codegen:
|
|
2
|
+
# filename: openapi.json
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Literal, NotRequired, TypeAlias, TypedDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Busy(TypedDict):
|
|
10
|
+
what: str
|
|
11
|
+
label: str
|
|
12
|
+
since: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Health(TypedDict):
|
|
16
|
+
ok: bool
|
|
17
|
+
version: str
|
|
18
|
+
build: NotRequired[str]
|
|
19
|
+
connected: bool
|
|
20
|
+
licensed: bool
|
|
21
|
+
pid: NotRequired[float]
|
|
22
|
+
person: str
|
|
23
|
+
names: NotRequired[float]
|
|
24
|
+
busy: NotRequired[Busy]
|
|
25
|
+
generation: NotRequired[float]
|
|
26
|
+
helper: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Error(TypedDict):
|
|
30
|
+
error: str
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
SceneDocument: TypeAlias = dict[str, Any]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SkippedItem(TypedDict):
|
|
37
|
+
what: str
|
|
38
|
+
reason: str
|
|
39
|
+
setups: list[str]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ImportReport(TypedDict):
|
|
43
|
+
setups: float
|
|
44
|
+
modules: list[str]
|
|
45
|
+
skipped: list[SkippedItem]
|
|
46
|
+
at: NotRequired[str]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class RefusedItem(TypedDict):
|
|
50
|
+
key: str
|
|
51
|
+
reason: str
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class Source(TypedDict):
|
|
55
|
+
kind: Literal["setup", "folder", "quality"]
|
|
56
|
+
id: str
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class Change(TypedDict):
|
|
60
|
+
target: str
|
|
61
|
+
param: str
|
|
62
|
+
value: NotRequired[Any]
|
|
63
|
+
module: str
|
|
64
|
+
source: Source
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SkippedItem1(TypedDict):
|
|
68
|
+
name: str
|
|
69
|
+
reason: str
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class Result(TypedDict):
|
|
73
|
+
target: str
|
|
74
|
+
applied: list[str]
|
|
75
|
+
skipped: list[SkippedItem1]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class ActivationStep(TypedDict):
|
|
79
|
+
id: str
|
|
80
|
+
label: str
|
|
81
|
+
state: Literal["queued", "running", "done", "skipped", "failed"]
|
|
82
|
+
ms: NotRequired[float]
|
|
83
|
+
error: NotRequired[str]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ActivationProgress(TypedDict):
|
|
87
|
+
setup: str
|
|
88
|
+
name: str
|
|
89
|
+
started: str
|
|
90
|
+
steps: list[ActivationStep]
|
|
91
|
+
finished: NotRequired[str]
|
|
92
|
+
ms: NotRequired[float]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class SetupSummary(TypedDict):
|
|
96
|
+
id: str
|
|
97
|
+
name: str
|
|
98
|
+
folder: NotRequired[str]
|
|
99
|
+
description: NotRequired[str]
|
|
100
|
+
tags: NotRequired[list[str]]
|
|
101
|
+
color: NotRequired[str]
|
|
102
|
+
modulesOff: list[str]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class FolderSummary(TypedDict):
|
|
106
|
+
id: str
|
|
107
|
+
name: str
|
|
108
|
+
parent: NotRequired[str]
|
|
109
|
+
color: NotRequired[str]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class Item(TypedDict):
|
|
113
|
+
setup: str
|
|
114
|
+
set: NotRequired[str]
|
|
115
|
+
row: NotRequired[list[float]]
|
|
116
|
+
name: str
|
|
117
|
+
status: Literal["queued", "applying", "rendering", "done", "failed", "stopped"]
|
|
118
|
+
error: NotRequired[str]
|
|
119
|
+
skipped: NotRequired[float]
|
|
120
|
+
output: NotRequired[str]
|
|
121
|
+
capped: NotRequired[str]
|
|
122
|
+
started: NotRequired[str]
|
|
123
|
+
finished: NotRequired[str]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BatchRender(TypedDict):
|
|
127
|
+
id: str
|
|
128
|
+
state: Literal["running", "done", "stopped"]
|
|
129
|
+
agent: NotRequired[bool]
|
|
130
|
+
started: str
|
|
131
|
+
finished: NotRequired[str]
|
|
132
|
+
items: list[Item]
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Link(TypedDict):
|
|
136
|
+
target: str
|
|
137
|
+
name: NotRequired[str]
|
|
138
|
+
detail: NotRequired[str]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Author(TypedDict):
|
|
142
|
+
name: str
|
|
143
|
+
kind: Literal["person", "agent"]
|
|
144
|
+
via: NotRequired[str]
|
|
145
|
+
picture: NotRequired[str]
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
ShotPoint: TypeAlias = list[Any]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
ShotArea: TypeAlias = list[Any]
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Mark1(TypedDict):
|
|
155
|
+
kind: Literal["pen"]
|
|
156
|
+
color: str
|
|
157
|
+
points: list[float]
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
Mark2 = TypedDict(
|
|
161
|
+
"Mark2",
|
|
162
|
+
{
|
|
163
|
+
"kind": Literal["line", "arrow", "rect", "ellipse"],
|
|
164
|
+
"color": str,
|
|
165
|
+
"from": ShotPoint,
|
|
166
|
+
"to": ShotPoint,
|
|
167
|
+
},
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class Mark3(TypedDict):
|
|
172
|
+
kind: Literal["text"]
|
|
173
|
+
color: str
|
|
174
|
+
at: ShotPoint
|
|
175
|
+
text: str
|
|
176
|
+
size: NotRequired[Literal["small", "normal", "medium", "large"]]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
Mark: TypeAlias = Mark1 | Mark2 | Mark3
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class MadeChange(TypedDict):
|
|
183
|
+
target: str
|
|
184
|
+
name: NotRequired[str]
|
|
185
|
+
param: str
|
|
186
|
+
before: NotRequired[Any]
|
|
187
|
+
after: NotRequired[Any]
|
|
188
|
+
setup: NotRequired[str]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class SavedView(TypedDict):
|
|
192
|
+
type: str
|
|
193
|
+
camera: NotRequired[str]
|
|
194
|
+
cameraName: NotRequired[str]
|
|
195
|
+
matrix: NotRequired[list[float]]
|
|
196
|
+
fov: NotRequired[float]
|
|
197
|
+
focus: NotRequired[float]
|
|
198
|
+
target: NotRequired[list[Any]]
|
|
199
|
+
size: NotRequired[list[Any]]
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class SetupThumbnail(TypedDict):
|
|
203
|
+
source: Literal["viewport", "framebuffer", "file"]
|
|
204
|
+
width: float
|
|
205
|
+
height: float
|
|
206
|
+
mime: str
|
|
207
|
+
at: str
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class PreviewJob(TypedDict):
|
|
211
|
+
set: str
|
|
212
|
+
state: Literal["running", "done", "stopped", "failed"]
|
|
213
|
+
folder: str
|
|
214
|
+
done: float
|
|
215
|
+
total: float
|
|
216
|
+
error: NotRequired[str]
|
|
217
|
+
started: str
|
|
218
|
+
finished: NotRequired[str]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ResolutionPreset(TypedDict):
|
|
222
|
+
name: str
|
|
223
|
+
width: float
|
|
224
|
+
height: float
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class Switching(TypedDict):
|
|
228
|
+
viewport: Literal["active", "views", "render"]
|
|
229
|
+
redraw: bool
|
|
230
|
+
coronaInteractive: bool
|
|
231
|
+
vrayIpr: bool
|
|
232
|
+
notify: bool
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class Settings(TypedDict):
|
|
236
|
+
hdriFolders: list[str]
|
|
237
|
+
resolutionPresets: list[ResolutionPreset]
|
|
238
|
+
team: str | None
|
|
239
|
+
switching: Switching
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class HdriMap(TypedDict):
|
|
243
|
+
path: str
|
|
244
|
+
name: str
|
|
245
|
+
folder: str
|
|
246
|
+
size: float
|
|
247
|
+
modified: float
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
class DocumentState(TypedDict):
|
|
251
|
+
readOnly: bool
|
|
252
|
+
document: SceneDocument
|
|
253
|
+
imported: NotRequired[ImportReport]
|
|
254
|
+
generation: NotRequired[float]
|
|
255
|
+
revision: NotRequired[float]
|
|
256
|
+
unreadable: NotRequired[str]
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class ChangeList(TypedDict):
|
|
260
|
+
setup: str
|
|
261
|
+
changes: list[Change]
|
|
262
|
+
unset: list[str]
|
|
263
|
+
unknownTokens: list[str]
|
|
264
|
+
refused: list[RefusedItem]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class Activation(TypedDict):
|
|
268
|
+
setup: str
|
|
269
|
+
results: list[Result]
|
|
270
|
+
unset: list[str]
|
|
271
|
+
unknownTokens: list[str]
|
|
272
|
+
refused: list[RefusedItem]
|
|
273
|
+
steps: NotRequired[list[ActivationStep]]
|
|
274
|
+
ms: NotRequired[float]
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
class Resolved(TypedDict):
|
|
278
|
+
by: Author
|
|
279
|
+
at: str
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Taken(TypedDict):
|
|
283
|
+
by: Author
|
|
284
|
+
at: str
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class Done(TypedDict):
|
|
288
|
+
by: Author
|
|
289
|
+
at: str
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class Reply(TypedDict):
|
|
293
|
+
id: str
|
|
294
|
+
author: Author
|
|
295
|
+
text: str
|
|
296
|
+
at: str
|
|
297
|
+
changes: NotRequired[list[MadeChange]]
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
class Shot(TypedDict):
|
|
301
|
+
id: str
|
|
302
|
+
setup: NotRequired[str]
|
|
303
|
+
source: Literal["viewport", "framebuffer", "file"]
|
|
304
|
+
name: str
|
|
305
|
+
width: float
|
|
306
|
+
height: float
|
|
307
|
+
mime: str
|
|
308
|
+
view: NotRequired[SavedView]
|
|
309
|
+
marks: NotRequired[list[Mark]]
|
|
310
|
+
author: Author
|
|
311
|
+
at: str
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class Comment(TypedDict):
|
|
315
|
+
id: str
|
|
316
|
+
n: float
|
|
317
|
+
author: Author
|
|
318
|
+
text: str
|
|
319
|
+
at: str
|
|
320
|
+
status: Literal["open", "resolved"]
|
|
321
|
+
resolved: NotRequired[Resolved]
|
|
322
|
+
shot: NotRequired[str]
|
|
323
|
+
setup: NotRequired[str]
|
|
324
|
+
pin: NotRequired[ShotPoint]
|
|
325
|
+
area: NotRequired[ShotArea]
|
|
326
|
+
marks: list[Mark]
|
|
327
|
+
links: list[Link]
|
|
328
|
+
assignee: NotRequired[str]
|
|
329
|
+
taken: NotRequired[Taken]
|
|
330
|
+
done: NotRequired[Done]
|
|
331
|
+
replies: list[Reply]
|