deadline-cloud-for-nuke 0.18.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.
- deadline/nuke_adaptor/NukeAdaptor/NukeAdaptor.json +3 -0
- deadline/nuke_adaptor/NukeAdaptor/__init__.py +9 -0
- deadline/nuke_adaptor/NukeAdaptor/__main__.py +37 -0
- deadline/nuke_adaptor/NukeAdaptor/adaptor.py +491 -0
- deadline/nuke_adaptor/NukeAdaptor/py.typed +1 -0
- deadline/nuke_adaptor/NukeAdaptor/schemas/init_data.schema.json +25 -0
- deadline/nuke_adaptor/NukeAdaptor/schemas/run_data.schema.json +9 -0
- deadline/nuke_adaptor/NukeClient/__init__.py +6 -0
- deadline/nuke_adaptor/NukeClient/nuke_client.py +166 -0
- deadline/nuke_adaptor/NukeClient/nuke_handler.py +263 -0
- deadline/nuke_adaptor/NukeClient/py.typed +1 -0
- deadline/nuke_adaptor/__init__.py +1 -0
- deadline/nuke_adaptor/_version.py +16 -0
- deadline/nuke_submitter/__init__.py +15 -0
- deadline/nuke_submitter/_logging.py +55 -0
- deadline/nuke_submitter/_version.py +16 -0
- deadline/nuke_submitter/adaptor_override_environment.yaml +84 -0
- deadline/nuke_submitter/assets.py +154 -0
- deadline/nuke_submitter/data_classes.py +77 -0
- deadline/nuke_submitter/deadline_submitter_for_nuke.py +356 -0
- deadline/nuke_submitter/default_nuke_job_template.yaml +123 -0
- deadline/nuke_submitter/job_bundle_output_test_runner.py +327 -0
- deadline/nuke_submitter/py.typed +1 -0
- deadline/nuke_submitter/ui/__init__.py +1 -0
- deadline/nuke_submitter/ui/components/__init__.py +1 -0
- deadline/nuke_submitter/ui/components/scene_settings_tab.py +115 -0
- deadline/nuke_util/__init__.py +1 -0
- deadline/nuke_util/_version.py +16 -0
- deadline/nuke_util/ocio.py +57 -0
- deadline_cloud_for_nuke-0.18.0.dist-info/METADATA +98 -0
- deadline_cloud_for_nuke-0.18.0.dist-info/RECORD +35 -0
- deadline_cloud_for_nuke-0.18.0.dist-info/WHEEL +4 -0
- deadline_cloud_for_nuke-0.18.0.dist-info/entry_points.txt +3 -0
- deadline_cloud_for_nuke-0.18.0.dist-info/licenses/LICENSE +175 -0
- deadline_cloud_for_nuke-0.18.0.dist-info/licenses/NOTICE +1 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import sys
|
|
5
|
+
import typing
|
|
6
|
+
import pathlib
|
|
7
|
+
|
|
8
|
+
from openjd.adaptor_runtime import EntryPoint
|
|
9
|
+
|
|
10
|
+
from .adaptor import NukeAdaptor
|
|
11
|
+
|
|
12
|
+
__all__ = ["main"]
|
|
13
|
+
_logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main(reentry_exe: typing.Optional[pathlib.Path] = None) -> int:
|
|
17
|
+
"""
|
|
18
|
+
Entry point for the Nuke Adaptor
|
|
19
|
+
"""
|
|
20
|
+
_logger.info("About to start the NukeAdaptor")
|
|
21
|
+
|
|
22
|
+
package_name = vars(sys.modules[__name__])["__package__"]
|
|
23
|
+
if not package_name:
|
|
24
|
+
raise RuntimeError(f"Must be run as a module. Do not run {__file__} directly")
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
EntryPoint(NukeAdaptor).start(reentry_exe=reentry_exe)
|
|
28
|
+
except Exception as e:
|
|
29
|
+
_logger.error(f"Entrypoint failed: {e}")
|
|
30
|
+
return 1
|
|
31
|
+
|
|
32
|
+
_logger.info("Done NukeAdaptor main")
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
sys.exit(main())
|
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import sys
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
import jsonschema # type: ignore
|
|
12
|
+
from typing import Callable, cast
|
|
13
|
+
|
|
14
|
+
from deadline.client.api import get_deadline_cloud_library_telemetry_client, TelemetryClient
|
|
15
|
+
from openjd.adaptor_runtime._version import version as openjd_adaptor_version
|
|
16
|
+
from openjd.adaptor_runtime.adaptors import Adaptor, AdaptorDataValidators, SemanticVersion
|
|
17
|
+
from openjd.adaptor_runtime_client import Action
|
|
18
|
+
from openjd.adaptor_runtime.process import LoggingSubprocess
|
|
19
|
+
from openjd.adaptor_runtime.app_handlers import RegexCallback, RegexHandler
|
|
20
|
+
from openjd.adaptor_runtime.application_ipc import ActionsQueue, AdaptorServer
|
|
21
|
+
|
|
22
|
+
from .._version import version as adaptor_version
|
|
23
|
+
|
|
24
|
+
_logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NukeNotRunningError(Exception):
|
|
28
|
+
"""Error that is raised when attempting to use Nuke while it is not running"""
|
|
29
|
+
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Actions which must be queued before any others
|
|
34
|
+
_FIRST_NUKE_ACTIONS = ["script_file"]
|
|
35
|
+
_NUKE_INIT_KEYS = {
|
|
36
|
+
"continue_on_error",
|
|
37
|
+
"proxy",
|
|
38
|
+
"write_nodes",
|
|
39
|
+
"views",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _check_for_exception(func: Callable) -> Callable:
|
|
44
|
+
"""
|
|
45
|
+
Decorator that checks if an exception has been caught before calling the
|
|
46
|
+
decorated function
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def wrapped_func(self, *args, **kwargs):
|
|
50
|
+
if not self._has_exception: # Raises if there is an exception # pragma: no branch
|
|
51
|
+
return func(self, *args, **kwargs)
|
|
52
|
+
|
|
53
|
+
return wrapped_func
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class NukeAdaptor(Adaptor):
|
|
57
|
+
"""
|
|
58
|
+
Adaptor that creates a session in Nuke to Render interactively.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
_SERVER_START_TIMEOUT_SECONDS = 30
|
|
62
|
+
_SERVER_END_TIMEOUT_SECONDS = 30
|
|
63
|
+
_NUKE_START_TIMEOUT_SECONDS = 300
|
|
64
|
+
_NUKE_END_TIMEOUT_SECONDS = 30
|
|
65
|
+
|
|
66
|
+
_server: AdaptorServer | None = None
|
|
67
|
+
_server_thread: threading.Thread | None = None
|
|
68
|
+
_nuke_client: LoggingSubprocess | None = None
|
|
69
|
+
_action_queue = ActionsQueue()
|
|
70
|
+
_is_rendering: bool = False
|
|
71
|
+
# If a thread raises an exception we will update this to raise in the main thread
|
|
72
|
+
_exc_info: Exception | None = None
|
|
73
|
+
_performing_cleanup = False
|
|
74
|
+
_regex_callbacks: list | None = None
|
|
75
|
+
_validators: AdaptorDataValidators | None = None
|
|
76
|
+
_telemetry_client: TelemetryClient | None = None
|
|
77
|
+
|
|
78
|
+
# Output tracking for progress handling
|
|
79
|
+
_curr_output: int = 1
|
|
80
|
+
_total_outputs: int = 1
|
|
81
|
+
_nuke_version: str = ""
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def integration_data_interface_version(self) -> SemanticVersion:
|
|
85
|
+
return SemanticVersion(major=0, minor=1)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _get_timer(timeout: int | float) -> Callable[[], bool]:
|
|
89
|
+
"""Given a timeout length, returns a lambda which returns False until the timeout occurs"""
|
|
90
|
+
timeout_time = time.time() + timeout
|
|
91
|
+
return lambda: time.time() >= timeout_time
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def _has_exception(self) -> bool:
|
|
95
|
+
"""Property which checks the private _exc_info property for an exception
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
self._exc_info: An exception if there is one
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
bool: False there is no exception waiting to be raised
|
|
102
|
+
"""
|
|
103
|
+
if self._exc_info and not self._performing_cleanup:
|
|
104
|
+
raise self._exc_info
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def continue_on_error(self) -> bool:
|
|
109
|
+
"""Property which returns whether to continue on errors or not
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
bool: True if the behavior is to continue on errors. False otherwise.
|
|
113
|
+
"""
|
|
114
|
+
return cast(bool, self.init_data.get("continue_on_error", True))
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def _nuke_is_running(self) -> bool:
|
|
118
|
+
"""Property which indicates that the nuke client is running
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
bool: True if the nuke client is running, false otherwise
|
|
122
|
+
"""
|
|
123
|
+
return self._nuke_client is not None and self._nuke_client.is_running
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def progress(self) -> float:
|
|
127
|
+
"""
|
|
128
|
+
Property which calculates progress based on the number of outputs
|
|
129
|
+
reported and the total outputs expected
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
float: The calculated progress
|
|
133
|
+
"""
|
|
134
|
+
return max(min(round(100.0 * self._curr_output / self._total_outputs, 2), 100), 0)
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def validators(self) -> AdaptorDataValidators:
|
|
138
|
+
if not self._validators:
|
|
139
|
+
cur_dir = os.path.dirname(__file__)
|
|
140
|
+
schema_dir = os.path.join(cur_dir, "schemas")
|
|
141
|
+
self._validators = AdaptorDataValidators.for_adaptor(schema_dir)
|
|
142
|
+
return self._validators
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
def regex_callbacks(self) -> list[RegexCallback]:
|
|
146
|
+
"""
|
|
147
|
+
Returns a list of RegexCallbacks used by the Nuke Adaptor
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
list[RegexCallback]: List of Regex Callbacks to add
|
|
151
|
+
"""
|
|
152
|
+
if not self._regex_callbacks:
|
|
153
|
+
callback_list = []
|
|
154
|
+
completed_regexes = [
|
|
155
|
+
re.compile("NukeClient: Finished Rendering Frame [0-9]+"),
|
|
156
|
+
re.compile("NukeClient: Finished Rendering Frames [0-9]+-[0-9]+"),
|
|
157
|
+
]
|
|
158
|
+
progress_regexes = [
|
|
159
|
+
re.compile(
|
|
160
|
+
"NukeClient: Creating outputs ([0-9]+)-([0-9]+) of ([0-9]+) total outputs."
|
|
161
|
+
),
|
|
162
|
+
]
|
|
163
|
+
error_regexes = [
|
|
164
|
+
re.compile(".*ERROR:.*"),
|
|
165
|
+
re.compile(".*Error:.*"),
|
|
166
|
+
re.compile(".*Error :.*"),
|
|
167
|
+
re.compile(".*Eddy\\[ERROR\\].*"),
|
|
168
|
+
]
|
|
169
|
+
# Capture the major minor group (ie. 15.0), patch version (ie. v1) is an optional subgroup.
|
|
170
|
+
version_regexes = [re.compile("NukeClient: Nuke Version ([0-9]+.[0-9]+)(v[0-9]+)?")]
|
|
171
|
+
output_complete_regexes = [re.compile(r"Writing .+ took [0-9\.]+ seconds")]
|
|
172
|
+
callback_list.append(RegexCallback(completed_regexes, self._handle_complete))
|
|
173
|
+
callback_list.append(RegexCallback(progress_regexes, self._handle_progress))
|
|
174
|
+
callback_list.append(
|
|
175
|
+
RegexCallback(output_complete_regexes, self._handle_output_complete)
|
|
176
|
+
)
|
|
177
|
+
callback_list.append(RegexCallback(error_regexes, self._handle_error))
|
|
178
|
+
callback_list.append(RegexCallback(version_regexes, self._handle_version))
|
|
179
|
+
self._regex_callbacks = callback_list
|
|
180
|
+
return self._regex_callbacks
|
|
181
|
+
|
|
182
|
+
@_check_for_exception
|
|
183
|
+
def _handle_complete(self, match: re.Match) -> None:
|
|
184
|
+
"""
|
|
185
|
+
Callback for stdout that indicates completeness of a render. Updates progress to 100
|
|
186
|
+
Args:
|
|
187
|
+
match (re.Match): The match object from the regex pattern that was matched the message
|
|
188
|
+
"""
|
|
189
|
+
self._is_rendering = False
|
|
190
|
+
self.update_status(progress=100, status_message="RENDER COMPLETE")
|
|
191
|
+
|
|
192
|
+
@_check_for_exception
|
|
193
|
+
def _handle_progress(self, match: re.Match) -> None:
|
|
194
|
+
"""
|
|
195
|
+
Callback for stdout that indicates progress of a render.
|
|
196
|
+
Args:
|
|
197
|
+
match (re.Match): The match object from the regex pattern that was matched the message
|
|
198
|
+
"""
|
|
199
|
+
self._curr_output = int(match.groups()[0])
|
|
200
|
+
self._total_outputs = int(match.groups()[2])
|
|
201
|
+
self.update_status(progress=self.progress)
|
|
202
|
+
|
|
203
|
+
@_check_for_exception
|
|
204
|
+
def _handle_output_complete(self, match: re.Match) -> None:
|
|
205
|
+
"""
|
|
206
|
+
Callback for stdout that indicates an outpout has been created.
|
|
207
|
+
Args:
|
|
208
|
+
match (re.Match): The match object from the regex pattern that was matched the message
|
|
209
|
+
"""
|
|
210
|
+
self._curr_output += 1
|
|
211
|
+
if self._is_rendering:
|
|
212
|
+
self.update_status(progress=self.progress)
|
|
213
|
+
|
|
214
|
+
def _handle_error(self, match: re.Match) -> None:
|
|
215
|
+
"""
|
|
216
|
+
Callback for stdout that indicates an error or warning. Sets the private _exc_info variable
|
|
217
|
+
which will be raised in the main thread.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
match (re.Match): The match object from the regex pattern that was matched the message
|
|
221
|
+
"""
|
|
222
|
+
if not self.continue_on_error:
|
|
223
|
+
self._exc_info = RuntimeError(f"Nuke Encountered an Error: {match.group(0)}")
|
|
224
|
+
|
|
225
|
+
def _handle_version(self, match: re.Match) -> None:
|
|
226
|
+
"""
|
|
227
|
+
Callback for stdout that records the Nuke version.
|
|
228
|
+
Args:
|
|
229
|
+
match (re.Match): The match object from the regex pattern that was matched the message
|
|
230
|
+
"""
|
|
231
|
+
self._nuke_version = match.groups()[0]
|
|
232
|
+
if len(match.groups()) > 1 and match.groups()[1]:
|
|
233
|
+
self._nuke_version += match.groups()[1]
|
|
234
|
+
|
|
235
|
+
@property
|
|
236
|
+
def server_server_path(self) -> str:
|
|
237
|
+
"""
|
|
238
|
+
Performs a busy wait for the socket path that the adaptor server is running on, then returns
|
|
239
|
+
it.
|
|
240
|
+
|
|
241
|
+
Raises:
|
|
242
|
+
RuntimeError: If the server does not finish initializing
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
str: The socket path the adaptor server is running on.
|
|
246
|
+
"""
|
|
247
|
+
is_timed_out = self._get_timer(self._SERVER_START_TIMEOUT_SECONDS)
|
|
248
|
+
while (self._server is None or self._server.server_path is None) and not is_timed_out():
|
|
249
|
+
time.sleep(0.01)
|
|
250
|
+
|
|
251
|
+
if self._server is not None and self._server.server_path is not None:
|
|
252
|
+
return self._server.server_path
|
|
253
|
+
|
|
254
|
+
raise RuntimeError("Could not find a socket because the server did not finish initializing")
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def nuke_client_path(self) -> str:
|
|
258
|
+
"""
|
|
259
|
+
Obtains the nuke_client.py path by searching directories in sys.path
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
FileNotFoundError: If the nuke_client.py file could not be found.
|
|
263
|
+
|
|
264
|
+
Returns:
|
|
265
|
+
str: The path to the nuke_client.py file.
|
|
266
|
+
"""
|
|
267
|
+
for dir_ in sys.path:
|
|
268
|
+
path = os.path.join(dir_, "deadline", "nuke_adaptor", "NukeClient", "nuke_client.py")
|
|
269
|
+
if os.path.isfile(path):
|
|
270
|
+
return path
|
|
271
|
+
raise FileNotFoundError(
|
|
272
|
+
"Could not find nuke_client.py. Check that the NukeClient package is in one of the "
|
|
273
|
+
f"following directories: {sys.path[1:]}"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def on_start(self) -> None:
|
|
277
|
+
"""
|
|
278
|
+
Initializes Nuke for job stickiness by starting Nuke, establishing IPC, and initializing the
|
|
279
|
+
Nuke environment with the given Nuke script and any other init data.
|
|
280
|
+
|
|
281
|
+
Raises:
|
|
282
|
+
jsonschema.ValidationError: When init_data fails validation against the adaptor schema.
|
|
283
|
+
jsonschema.SchemaError: When the adaptor schema itself is nonvalid.
|
|
284
|
+
RuntimeError: If Nuke did not complete initialization actions due to an exception
|
|
285
|
+
TimeoutError: If Nuke did not complete initialization actions due to timing out.
|
|
286
|
+
FileNotFoundError: If the nuke_client.py file could not be found.
|
|
287
|
+
KeyError: If a configuration for the given platform and version does not exist.
|
|
288
|
+
"""
|
|
289
|
+
self.validators.init_data.validate(self.init_data)
|
|
290
|
+
|
|
291
|
+
self.update_status(progress=0, status_message="Initializing Nuke")
|
|
292
|
+
self._server_thread = self._start_nuke_server_thread()
|
|
293
|
+
self._populate_action_queue()
|
|
294
|
+
|
|
295
|
+
self._start_nuke_client()
|
|
296
|
+
|
|
297
|
+
is_timed_out = self._get_timer(self._NUKE_START_TIMEOUT_SECONDS)
|
|
298
|
+
while self._nuke_is_running and not self._has_exception and len(self._action_queue) > 0:
|
|
299
|
+
if is_timed_out():
|
|
300
|
+
raise TimeoutError(
|
|
301
|
+
"Nuke did not complete initialization actions in "
|
|
302
|
+
f"{self._NUKE_START_TIMEOUT_SECONDS} seconds and failed to start."
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
time.sleep(0.1) # busy wait for nuke to finish initialization
|
|
306
|
+
|
|
307
|
+
self._get_deadline_telemetry_client().record_event(
|
|
308
|
+
event_type="com.amazon.rum.deadline.adaptor.runtime.start", event_details={}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
if len(self._action_queue) > 0:
|
|
312
|
+
raise RuntimeError(
|
|
313
|
+
"Nuke encountered an error and was not able to complete initialization actions."
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
def on_run(self, run_data: dict) -> None:
|
|
317
|
+
"""
|
|
318
|
+
This starts a render in Nuke for the given frame and performs a busy wait until the render
|
|
319
|
+
completes.
|
|
320
|
+
"""
|
|
321
|
+
if not self._nuke_is_running:
|
|
322
|
+
raise NukeNotRunningError("Cannot render because Nuke is not running.")
|
|
323
|
+
|
|
324
|
+
if "frameRange" not in run_data and "frame" not in run_data:
|
|
325
|
+
raise jsonschema.exceptions.ValidationError(
|
|
326
|
+
"Cannot run Nuke adaptor. 'frame' or 'frameRange' is a required property."
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
self.validators.run_data.validate(run_data)
|
|
330
|
+
self._is_rendering = True
|
|
331
|
+
|
|
332
|
+
self._action_queue.enqueue_action(
|
|
333
|
+
Action(
|
|
334
|
+
"start_render",
|
|
335
|
+
{"frameRange": run_data.get("frameRange", str(run_data.get("frame", "")))},
|
|
336
|
+
)
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
while self._nuke_is_running and self._is_rendering and not self._has_exception:
|
|
340
|
+
time.sleep(0.1) # busy wait so that on_cleanup is not called
|
|
341
|
+
|
|
342
|
+
if not self._nuke_is_running and self._nuke_client: # Nuke Client will always exist here.
|
|
343
|
+
# This is always an error case because the Nuke Client should still be running and
|
|
344
|
+
# waiting for the next command. If the thread finished, then we cannot continue
|
|
345
|
+
exit_code = self._nuke_client.returncode
|
|
346
|
+
|
|
347
|
+
self._get_deadline_telemetry_client().record_error(
|
|
348
|
+
{"exit_code": exit_code, "exception_scope": "on_run"}, str(RuntimeError)
|
|
349
|
+
)
|
|
350
|
+
raise RuntimeError(
|
|
351
|
+
"Nuke exited early and did not render successfully, please check render logs. "
|
|
352
|
+
f"Exit code {exit_code}"
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def on_stop(self) -> None:
|
|
356
|
+
"""
|
|
357
|
+
No action needed but this function must be implemented
|
|
358
|
+
"""
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
def on_cleanup(self):
|
|
362
|
+
"""
|
|
363
|
+
Cleans up the adaptor by closing the nuke client and adaptor server.
|
|
364
|
+
"""
|
|
365
|
+
self._performing_cleanup = True
|
|
366
|
+
|
|
367
|
+
self._action_queue.enqueue_action(Action("close"), front=True)
|
|
368
|
+
is_timed_out = self._get_timer(self._NUKE_END_TIMEOUT_SECONDS)
|
|
369
|
+
while self._nuke_is_running and not is_timed_out():
|
|
370
|
+
time.sleep(0.1)
|
|
371
|
+
if self._nuke_is_running and self._nuke_client:
|
|
372
|
+
_logger.error(
|
|
373
|
+
"Nuke did not complete cleanup actions and failed to gracefully shutdown. "
|
|
374
|
+
"Terminating."
|
|
375
|
+
)
|
|
376
|
+
self._nuke_client.terminate()
|
|
377
|
+
|
|
378
|
+
if self._server:
|
|
379
|
+
self._server.shutdown()
|
|
380
|
+
|
|
381
|
+
if self._server_thread and self._server_thread.is_alive():
|
|
382
|
+
self._server_thread.join(timeout=self._SERVER_END_TIMEOUT_SECONDS)
|
|
383
|
+
if self._server_thread.is_alive():
|
|
384
|
+
_logger.error("Failed to shutdown the Nuke Adaptor server.")
|
|
385
|
+
|
|
386
|
+
self._performing_cleanup = False
|
|
387
|
+
|
|
388
|
+
def on_cancel(self):
|
|
389
|
+
"""
|
|
390
|
+
Cancels the current render if Nuke is rendering.
|
|
391
|
+
"""
|
|
392
|
+
_logger.info("CANCEL REQUESTED")
|
|
393
|
+
if not self._nuke_client or not self._nuke_is_running:
|
|
394
|
+
_logger.info("Nothing to cancel because Nuke is not running")
|
|
395
|
+
return
|
|
396
|
+
|
|
397
|
+
# Terminate immediately since the Nuke client does not have a graceful shutdown
|
|
398
|
+
self._nuke_client.terminate(grace_time_s=0)
|
|
399
|
+
|
|
400
|
+
def _start_nuke_server_thread(self) -> threading.Thread:
|
|
401
|
+
"""
|
|
402
|
+
Starts the nuke adaptor server in a thread.
|
|
403
|
+
Sets the environment variable "NUKE_ADAPTOR_SERVER_PATH" to the socket the server is running
|
|
404
|
+
on after the server has finished starting.
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
threading.Thread: The thread in which the server is running
|
|
408
|
+
"""
|
|
409
|
+
|
|
410
|
+
def start_nuke_server() -> None:
|
|
411
|
+
"""
|
|
412
|
+
Starts a server with the given ActionsQueue, attaches the server to the adaptor and
|
|
413
|
+
serves forever in a blocking call.
|
|
414
|
+
"""
|
|
415
|
+
self._server = AdaptorServer(self._action_queue, self)
|
|
416
|
+
self._server.serve_forever()
|
|
417
|
+
|
|
418
|
+
server_thread = threading.Thread(target=start_nuke_server)
|
|
419
|
+
server_thread.start()
|
|
420
|
+
os.environ["NUKE_ADAPTOR_SERVER_PATH"] = self.server_server_path
|
|
421
|
+
|
|
422
|
+
return server_thread
|
|
423
|
+
|
|
424
|
+
def _start_nuke_client(self) -> None:
|
|
425
|
+
"""
|
|
426
|
+
Starts the nuke client by launching Nuke with the nuke_client.py file and the generated
|
|
427
|
+
arguments.
|
|
428
|
+
|
|
429
|
+
Nuke must be on the system PATH, for example due to a Rez environment being active.
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
FileNotFoundError: If the nuke_client.py file could not be found.
|
|
433
|
+
KeyError: If a configuration for the given platform and version does not exist.
|
|
434
|
+
"""
|
|
435
|
+
nuke_exe = os.environ.get("NUKE_ADAPTOR_NUKE_EXECUTABLE", "nuke")
|
|
436
|
+
regexhandler = RegexHandler(self.regex_callbacks)
|
|
437
|
+
|
|
438
|
+
# Add the Open Job Description namespace directory to PYTHONPATH, so that adaptor_runtime_client
|
|
439
|
+
# will be available directly to the nuke client.
|
|
440
|
+
import openjd.adaptor_runtime_client
|
|
441
|
+
import deadline.nuke_adaptor
|
|
442
|
+
import deadline.nuke_util
|
|
443
|
+
|
|
444
|
+
openjd_namespace_dir = os.path.dirname(
|
|
445
|
+
os.path.dirname(openjd.adaptor_runtime_client.__file__)
|
|
446
|
+
)
|
|
447
|
+
deadline_adaptor_namespace_dir = os.path.dirname(
|
|
448
|
+
os.path.dirname(deadline.nuke_adaptor.__file__)
|
|
449
|
+
)
|
|
450
|
+
deadline_util_namespace_dir = os.path.dirname(os.path.dirname(deadline.nuke_util.__file__))
|
|
451
|
+
python_path_addition = f"{openjd_namespace_dir}{os.pathsep}{deadline_adaptor_namespace_dir}{os.pathsep}{deadline_util_namespace_dir}"
|
|
452
|
+
if "PYTHONPATH" in os.environ:
|
|
453
|
+
os.environ["PYTHONPATH"] = (
|
|
454
|
+
f"{os.environ['PYTHONPATH']}{os.pathsep}{python_path_addition}"
|
|
455
|
+
)
|
|
456
|
+
else:
|
|
457
|
+
os.environ["PYTHONPATH"] = python_path_addition
|
|
458
|
+
|
|
459
|
+
self._nuke_client = LoggingSubprocess(
|
|
460
|
+
args=[nuke_exe, "-V", "2", "-t", self.nuke_client_path],
|
|
461
|
+
stdout_handler=regexhandler,
|
|
462
|
+
stderr_handler=regexhandler,
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
def _populate_action_queue(self) -> None:
|
|
466
|
+
"""
|
|
467
|
+
Populates the adaptor server's action queue with actions from the init_data that the Nuke
|
|
468
|
+
Client will request and perform. The action must be present in the _FIRST_NUKE_ACTIONS or
|
|
469
|
+
_NUKE_INIT_KEYS set to be added to the action queue.
|
|
470
|
+
"""
|
|
471
|
+
for name in _FIRST_NUKE_ACTIONS:
|
|
472
|
+
self._action_queue.enqueue_action(Action(name, {name: self.init_data[name]}))
|
|
473
|
+
|
|
474
|
+
for name in _NUKE_INIT_KEYS:
|
|
475
|
+
if name in self.init_data:
|
|
476
|
+
self._action_queue.enqueue_action(Action(name, {name: self.init_data[name]}))
|
|
477
|
+
|
|
478
|
+
def _get_deadline_telemetry_client(self):
|
|
479
|
+
"""
|
|
480
|
+
Wrapper around the Deadline Client Library telemetry client, in order to set package-specific information
|
|
481
|
+
"""
|
|
482
|
+
if not self._telemetry_client:
|
|
483
|
+
self._telemetry_client = get_deadline_cloud_library_telemetry_client()
|
|
484
|
+
self._telemetry_client.update_common_details(
|
|
485
|
+
{
|
|
486
|
+
"deadline-cloud-for-nuke-adaptor-version": adaptor_version,
|
|
487
|
+
"nuke-version": self._nuke_version,
|
|
488
|
+
"open-jd-adaptor-runtime-version": openjd_adaptor_version,
|
|
489
|
+
}
|
|
490
|
+
)
|
|
491
|
+
return self._telemetry_client
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Marker file that indicates this package supports typing
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"type": "object",
|
|
4
|
+
"properties": {
|
|
5
|
+
"continue_on_error": {"type": "boolean"},
|
|
6
|
+
"proxy": {"type": "boolean"},
|
|
7
|
+
"write_nodes": {
|
|
8
|
+
"type": "array",
|
|
9
|
+
"items": {
|
|
10
|
+
"type": "string"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"views": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"items": {
|
|
16
|
+
"type": "string"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"telemetry_opt_out": { "type" : "boolean" },
|
|
20
|
+
"script_file": { "type": "string" }
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"script_file"
|
|
24
|
+
]
|
|
25
|
+
}
|