simready-thumbnail 2026.8.0__py3-none-any.whl

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.
@@ -0,0 +1,114 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """SimReady USD thumbnail generation.
17
+
18
+ ``_cli``, ``_ovrtx_worker``, and ``review_stage`` are deliberately not imported
19
+ here: the worker pulls in ovrtx, ovstage, numpy and Pillow, and importing this
20
+ package must stay cheap for callers that only need path resolution or backend
21
+ selection.
22
+ """
23
+
24
+ from .backends import (
25
+ CAPTURE_CMD_REJECTED,
26
+ CAPTURE_FAILED,
27
+ CAPTURE_HARNESS_MISSING,
28
+ CAPTURE_KIT_MISSING,
29
+ CAPTURE_PATH_FAILED,
30
+ CAPTURE_SKIPPED,
31
+ KitSubprocessBackend,
32
+ OvRtxSubprocessBackend,
33
+ RenderBackend,
34
+ ThumbnailBackendKind,
35
+ ThumbnailBackgroundPreset,
36
+ build_kit_capture_command,
37
+ build_ovrtx_capture_command,
38
+ create_render_backend,
39
+ default_ovrtx_harness_argv,
40
+ default_ovrtx_run_command,
41
+ resolve_kit_executable,
42
+ )
43
+ from .layout import (
44
+ DEFAULT_THUMBNAIL_FILL_PERCENT,
45
+ DEFAULT_THUMBNAIL_HEIGHT,
46
+ DEFAULT_THUMBNAIL_WIDTH,
47
+ ThumbnailLayout,
48
+ capture_output_paths,
49
+ manifest_capture_paths,
50
+ resolve_output_path,
51
+ validate_thumbnail_fill_percent,
52
+ )
53
+ from .thumbnail_gen import (
54
+ OverwritePolicy,
55
+ ThumbnailGenerator,
56
+ ThumbnailResult,
57
+ ThumbnailSpec,
58
+ ThumbnailStatus,
59
+ generate_thumbnails_for_asset_paths,
60
+ run_manifest_capture,
61
+ run_manifest_capture_all_backgrounds,
62
+ )
63
+ from .usd_setup import (
64
+ DEFAULT_THUMBNAIL_SETUP_PATH,
65
+ resolve_thumbnail_setup_path,
66
+ )
67
+
68
+ # Configure the SimReady log file (raise the "simready" logger to INFO + install
69
+ # the capture-all file sink). Best-effort so a missing/old logging package can't
70
+ # break this import.
71
+ try:
72
+ import simready.logging
73
+
74
+ simready.logging.init_log_file()
75
+ except Exception:
76
+ pass
77
+
78
+ __all__ = [
79
+ "CAPTURE_CMD_REJECTED",
80
+ "CAPTURE_FAILED",
81
+ "CAPTURE_HARNESS_MISSING",
82
+ "CAPTURE_KIT_MISSING",
83
+ "CAPTURE_PATH_FAILED",
84
+ "CAPTURE_SKIPPED",
85
+ "DEFAULT_THUMBNAIL_FILL_PERCENT",
86
+ "DEFAULT_THUMBNAIL_HEIGHT",
87
+ "DEFAULT_THUMBNAIL_SETUP_PATH",
88
+ "DEFAULT_THUMBNAIL_WIDTH",
89
+ "KitSubprocessBackend",
90
+ "OvRtxSubprocessBackend",
91
+ "OverwritePolicy",
92
+ "RenderBackend",
93
+ "ThumbnailBackendKind",
94
+ "ThumbnailBackgroundPreset",
95
+ "ThumbnailGenerator",
96
+ "ThumbnailLayout",
97
+ "ThumbnailResult",
98
+ "ThumbnailSpec",
99
+ "ThumbnailStatus",
100
+ "build_kit_capture_command",
101
+ "build_ovrtx_capture_command",
102
+ "capture_output_paths",
103
+ "create_render_backend",
104
+ "default_ovrtx_harness_argv",
105
+ "default_ovrtx_run_command",
106
+ "generate_thumbnails_for_asset_paths",
107
+ "manifest_capture_paths",
108
+ "resolve_kit_executable",
109
+ "resolve_output_path",
110
+ "resolve_thumbnail_setup_path",
111
+ "run_manifest_capture",
112
+ "run_manifest_capture_all_backgrounds",
113
+ "validate_thumbnail_fill_percent",
114
+ ]
@@ -0,0 +1,223 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """CLI entry point for simready-thumbnail."""
17
+
18
+ from __future__ import annotations
19
+
20
+ import argparse
21
+ import logging
22
+ import subprocess
23
+ import sys
24
+ from typing import List
25
+
26
+ from .backends import (
27
+ CAPTURE_SKIPPED,
28
+ ThumbnailBackendKind,
29
+ ThumbnailBackgroundPreset,
30
+ default_ovrtx_harness_argv,
31
+ default_ovrtx_run_command,
32
+ )
33
+ from .layout import (
34
+ CLI_THUMBNAIL_LAYOUTS,
35
+ DEFAULT_THUMBNAIL_FILL_PERCENT,
36
+ ThumbnailLayout,
37
+ validate_thumbnail_fill_percent,
38
+ )
39
+
40
+ _BACKGROUND_ALL = "all"
41
+ _BACKGROUND_CHOICES = [preset.value for preset in ThumbnailBackgroundPreset] + [_BACKGROUND_ALL]
42
+
43
+
44
+ def _build_parser() -> argparse.ArgumentParser:
45
+ parser = argparse.ArgumentParser(
46
+ prog="simready-thumbnail",
47
+ description="Generate SimReady USD thumbnail PNGs via a subprocess render backend.",
48
+ )
49
+ parser.add_argument(
50
+ "usd_path",
51
+ nargs="?",
52
+ help="Path to a SimReady USD asset",
53
+ )
54
+ parser.add_argument(
55
+ "--backend",
56
+ choices=[kind.value for kind in ThumbnailBackendKind],
57
+ default=ThumbnailBackendKind.OVRTX.value,
58
+ help="Subprocess render backend (default: ovrtx)",
59
+ )
60
+ parser.add_argument(
61
+ "--kit-exe",
62
+ dest="kit_exe",
63
+ required=False,
64
+ help="Path to kit.exe, or to a workspace launcher whose build tree contains it "
65
+ "(required when --backend=kit)",
66
+ )
67
+ parser.add_argument(
68
+ "--ovrtx-exe",
69
+ dest="ovrtx_exe",
70
+ required=False,
71
+ help="Path to an OVRTX capture harness executable (optional when --backend=ovrtx; "
72
+ "defaults to the in-tree python -m simready.thumbnail._ovrtx_worker worker)",
73
+ )
74
+ parser.add_argument(
75
+ "--fill",
76
+ dest="target_fill_percent",
77
+ type=float,
78
+ default=DEFAULT_THUMBNAIL_FILL_PERCENT,
79
+ metavar="PERCENT (1-90)",
80
+ help=(
81
+ "Target asset fill on the limiting frame axis, 1-90 (%%) " f"(default: {DEFAULT_THUMBNAIL_FILL_PERCENT})"
82
+ ),
83
+ )
84
+ parser.add_argument(
85
+ "--usd-setup",
86
+ dest="usd_setup",
87
+ default=None,
88
+ metavar="PATH",
89
+ help=(
90
+ "Thumbnail setup USD layer with ThumbnailLighting + optional "
91
+ "ThumbnailCamera. Must have defaultPrim ThumbnailSetup; "
92
+ "omit to use the packaged default USDA."
93
+ ),
94
+ )
95
+ parser.add_argument(
96
+ "--layout",
97
+ choices=[layout.value for layout in CLI_THUMBNAIL_LAYOUTS],
98
+ default=ThumbnailLayout.NUCLEUS.value,
99
+ help=(
100
+ "Output path convention (default: nucleus). "
101
+ "nucleus: .thumbs/256x256/{usd_filename}.png (dark) and "
102
+ "{usd_filename}_{light|transparent}.png for the other backgrounds; "
103
+ "manifest: .thumbs/{stem}_thumbnail.png (dark) and "
104
+ "{stem}_thumbnail_{light|transparent}.png for the other backgrounds"
105
+ ),
106
+ )
107
+ parser.add_argument(
108
+ "--background",
109
+ choices=_BACKGROUND_CHOICES,
110
+ default=ThumbnailBackgroundPreset.DARK.value,
111
+ help=(
112
+ "Background: dark (#1A1A1A), light (#F0F0F0), transparent RGBA, or all "
113
+ "(dark+light+transparent; default: dark if omitted)"
114
+ ),
115
+ )
116
+ parser.add_argument(
117
+ "-V",
118
+ "--show-version",
119
+ action="store_true",
120
+ help="Print version and exit",
121
+ )
122
+ return parser
123
+
124
+
125
+ def _default_run_command(cmd: List[str]) -> int:
126
+ completed = subprocess.run(cmd, check=False)
127
+ return int(completed.returncode)
128
+
129
+
130
+ def _init_console_logging() -> None:
131
+ """Echo SimReady logs to the console, as logs.py prescribes for standalone CLIs.
132
+
133
+ ``simready.logging`` is an optional dependency, so when it is missing or too
134
+ old this degrades to a stdlib stderr handler rather than going silent: the
135
+ logger is this CLI's only output channel. Console output goes to stderr so
136
+ stdout stays reserved for machine-readable output such as ``-V``.
137
+ """
138
+ try:
139
+ import simready.logging
140
+
141
+ simready.logging.init_console_streaming()
142
+ return
143
+ except Exception:
144
+ pass
145
+
146
+ logger = logging.getLogger("simready")
147
+ logger.setLevel(logging.INFO)
148
+ if not logger.handlers:
149
+ handler = logging.StreamHandler(sys.stderr)
150
+ handler.setFormatter(logging.Formatter("[%(levelname)s] %(message)s"))
151
+ logger.addHandler(handler)
152
+
153
+
154
+ def main(argv: list[str] | None = None) -> int:
155
+ _init_console_logging()
156
+ parser = _build_parser()
157
+ args = parser.parse_args(argv)
158
+
159
+ if args.show_version:
160
+ try:
161
+ from importlib.metadata import version
162
+
163
+ print(version("simready.thumbnail"))
164
+ except Exception:
165
+ print("simready.thumbnail (dev)")
166
+ return 0
167
+
168
+ if not args.usd_path:
169
+ parser.error("usd_path is required unless --show-version is passed")
170
+
171
+ try:
172
+ target_fill_percent = validate_thumbnail_fill_percent(args.target_fill_percent)
173
+ except ValueError as exc:
174
+ parser.error(str(exc))
175
+
176
+ backend = ThumbnailBackendKind(args.backend)
177
+ ovrtx_harness_argv = None
178
+ harness_exe = ""
179
+
180
+ if backend == ThumbnailBackendKind.KIT:
181
+ if not args.kit_exe:
182
+ parser.error("--kit-exe is required when --backend=kit")
183
+ harness_exe = args.kit_exe
184
+ else:
185
+ if args.ovrtx_exe:
186
+ harness_exe = args.ovrtx_exe
187
+ else:
188
+ ovrtx_harness_argv = default_ovrtx_harness_argv()
189
+ harness_exe = ovrtx_harness_argv[0]
190
+
191
+ from .thumbnail_gen import run_manifest_capture, run_manifest_capture_all_backgrounds
192
+
193
+ run_command = _default_run_command
194
+ if backend == ThumbnailBackendKind.OVRTX and ovrtx_harness_argv is not None:
195
+ run_command = default_ovrtx_run_command
196
+
197
+ common_kwargs = dict(
198
+ backend=backend,
199
+ ovrtx_harness_argv=ovrtx_harness_argv,
200
+ target_fill_percent=target_fill_percent,
201
+ usd_setup_path=args.usd_setup,
202
+ layout=ThumbnailLayout(args.layout),
203
+ run_command=run_command,
204
+ )
205
+
206
+ if args.background == _BACKGROUND_ALL:
207
+ return run_manifest_capture_all_backgrounds(
208
+ harness_exe,
209
+ args.usd_path,
210
+ **common_kwargs,
211
+ )
212
+
213
+ rc = run_manifest_capture(
214
+ harness_exe,
215
+ args.usd_path,
216
+ background_preset=ThumbnailBackgroundPreset(args.background),
217
+ **common_kwargs,
218
+ )
219
+ return 0 if rc == CAPTURE_SKIPPED else rc
220
+
221
+
222
+ if __name__ == "__main__":
223
+ raise SystemExit(main())
@@ -0,0 +1,168 @@
1
+ # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ """Direct Kit thumbnail worker injected with ``--exec``."""
17
+
18
+ from __future__ import annotations
19
+
20
+ import asyncio
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ import carb
25
+ import carb.settings
26
+ import omni.kit.app
27
+ import omni.usd
28
+
29
+ _PACKAGE_DIR = Path(__file__).resolve().parent
30
+ for _root in (_PACKAGE_DIR.parent, _PACKAGE_DIR.parent.parent):
31
+ if str(_root) not in sys.path:
32
+ sys.path.insert(0, str(_root))
33
+
34
+
35
+ _SETTLE_FRAMES = 64 # MDL warmup frames; the capture extension caps this at 64
36
+
37
+ # Kit's pathtracer renders this asset class without its normal-map relief, whatever
38
+ # the sample count, denoiser, mip and upscaler settings; RaytracedLighting keeps it,
39
+ # and is also what the OVRTX worker's renderer runs. Overridable for diagnostics.
40
+ _DEFAULT_RENDER_MODE = "realtime"
41
+
42
+
43
+ def _thumbnail_modules():
44
+ try:
45
+ from thumbnail.backends import CAPTURE_FAILED, ThumbnailBackgroundPreset, unpack_open_stage_result
46
+ from thumbnail.review_stage import _prepare_review_stage, _rgba_from_solid_plate
47
+ except ImportError:
48
+ from simready.thumbnail.backends import CAPTURE_FAILED, ThumbnailBackgroundPreset, unpack_open_stage_result
49
+ from simready.thumbnail.review_stage import _prepare_review_stage, _rgba_from_solid_plate
50
+ return (
51
+ CAPTURE_FAILED,
52
+ ThumbnailBackgroundPreset,
53
+ unpack_open_stage_result,
54
+ _prepare_review_stage,
55
+ _rgba_from_solid_plate,
56
+ )
57
+
58
+
59
+ def _apply_render_settings(settings) -> None:
60
+ """Render config the bare Kit binary does not get from an app .kit file."""
61
+ settings.set("/rtx/hydra/engine", "rtx")
62
+ settings.set_bool("/rtx/mdl/materialLoading/enabled", True)
63
+ settings.set_bool("/app/asyncRendering", False)
64
+ # A material still compiling at capture time renders as untextured plastic.
65
+ settings.set_bool("/rtx/materialDb/syncLoads", True)
66
+
67
+
68
+ def _setting(name: str, default=None):
69
+ value = carb.settings.get_settings().get(f"/simready/thumbnail/{name}")
70
+ return default if value in (None, "") else value
71
+
72
+
73
+ async def _capture() -> int:
74
+ CAPTURE_FAILED, BackgroundPreset, unpack_open_stage_result, prepare_review_stage, rgba_from_solid_plate = (
75
+ _thumbnail_modules()
76
+ )
77
+ asset_raw = str(_setting("assetPath", ""))
78
+ output_raw = str(_setting("outputPath", ""))
79
+ asset_path = Path(asset_raw)
80
+ output_path = Path(output_raw)
81
+ width = int(_setting("width", 1024))
82
+ height = int(_setting("height", 1024))
83
+ target_fill = float(_setting("targetFillPercent", 90))
84
+ background = BackgroundPreset(str(_setting("background", "dark")))
85
+ setup_raw = str(_setting("usdSetupPath", ""))
86
+ setup_path = setup_raw or None
87
+ render_mode = str(_setting("renderMode", _DEFAULT_RENDER_MODE))
88
+
89
+ if not asset_raw or not asset_path.is_file() or not output_raw:
90
+ carb.log_error(f"Invalid thumbnail paths: asset={asset_path}, output={output_path}")
91
+ return CAPTURE_FAILED
92
+
93
+ prepared = prepare_review_stage(
94
+ asset_path,
95
+ width,
96
+ height,
97
+ target_fill,
98
+ background,
99
+ usd_setup_path=setup_path,
100
+ include_asset_reference=True,
101
+ log_prefix="Kit",
102
+ )
103
+ if prepared is None:
104
+ return CAPTURE_FAILED
105
+ review_layer, camera_path, ground_free, bg_display = prepared
106
+
107
+ output_path.parent.mkdir(parents=True, exist_ok=True)
108
+ stage_path = output_path.with_suffix(".thumbnail_review.usda")
109
+ stage_path.write_text(review_layer, encoding="utf-8")
110
+
111
+ try:
112
+ context = omni.usd.get_context()
113
+ ok, open_error = unpack_open_stage_result(await context.open_stage_async(stage_path.as_posix()))
114
+ if not ok:
115
+ carb.log_error(f"Could not open thumbnail review stage: {stage_path} ({open_error})")
116
+ return CAPTURE_FAILED
117
+
118
+ import omni.simready.viewport_capture as viewport_capture
119
+
120
+ api = viewport_capture.get_viewport_capture_api()
121
+ _apply_render_settings(carb.settings.get_settings())
122
+
123
+ success = await api.capture_thumbnail(
124
+ output_path=output_path.as_posix(),
125
+ camera_path=camera_path,
126
+ render_mode=render_mode,
127
+ width=width,
128
+ height=height,
129
+ settle_frames_override=_SETTLE_FRAMES,
130
+ )
131
+ if not success:
132
+ return CAPTURE_FAILED
133
+
134
+ if ground_free:
135
+ import numpy as np
136
+ from PIL import Image
137
+
138
+ rgb = np.asarray(Image.open(output_path).convert("RGB"))
139
+ rgba = rgba_from_solid_plate(np, rgb, bg_display)
140
+ Image.fromarray(rgba, mode="RGBA").save(output_path, format="PNG")
141
+ return 0
142
+ except Exception as exc:
143
+ carb.log_error(f"Direct Kit thumbnail capture failed: {exc}")
144
+ return CAPTURE_FAILED
145
+ finally:
146
+ try:
147
+ stage_path.unlink(missing_ok=True)
148
+ except OSError:
149
+ pass
150
+
151
+
152
+ def main() -> int:
153
+ app = omni.kit.app.get_app()
154
+ task = asyncio.ensure_future(_capture())
155
+ while not task.done():
156
+ app.update()
157
+ if task.exception():
158
+ raise task.exception()
159
+ return int(task.result())
160
+
161
+
162
+ try:
163
+ _exit_code = main()
164
+ except Exception as _exc:
165
+ carb.log_error(f"Kit thumbnail worker failed: {_exc}")
166
+ _exit_code = -5
167
+
168
+ omni.kit.app.get_app().post_quit(_exit_code)