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.
- simready/thumbnail/__init__.py +114 -0
- simready/thumbnail/_cli.py +223 -0
- simready/thumbnail/_kit_worker.py +168 -0
- simready/thumbnail/_ovrtx_worker.py +393 -0
- simready/thumbnail/backends.py +550 -0
- simready/thumbnail/data/default_thumbnail_setup.usda +269 -0
- simready/thumbnail/data/thumbnail_env_gradient.png +0 -0
- simready/thumbnail/layout.py +116 -0
- simready/thumbnail/review_stage.py +1063 -0
- simready/thumbnail/thumbnail_gen.py +327 -0
- simready/thumbnail/usd_setup.py +98 -0
- simready_thumbnail-2026.8.0.dist-info/METADATA +122 -0
- simready_thumbnail-2026.8.0.dist-info/RECORD +17 -0
- simready_thumbnail-2026.8.0.dist-info/WHEEL +5 -0
- simready_thumbnail-2026.8.0.dist-info/entry_points.txt +2 -0
- simready_thumbnail-2026.8.0.dist-info/licenses/LICENSE.txt +202 -0
- simready_thumbnail-2026.8.0.dist-info/top_level.txt +1 -0
|
@@ -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)
|