Skip to content

api

NukeCreator

Bases: Creator

Source code in client/ayon_nuke/api/plugin.py
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
class NukeCreator(NewCreator):
    node_class_name = None

    def _pass_pre_attributes_to_instance(
        self,
        instance_data,
        pre_create_data,
        keys=None
    ):
        if keys is None:
            keys = pre_create_data.keys()
        creator_attrs = instance_data["creator_attributes"] = {}

        creator_attrs.update({
            key: value
            for key, value in pre_create_data.items()
            if key in keys
        })

    def check_existing_product(self, product_name):
        """Make sure product name is unique.

        It search within all nodes recursively
        and checks if product name is found in
        any node having instance data knob.

        Arguments:
            product_name (str): Product name
        """

        for node in nuke.allNodes(recurseGroups=True):
            # make sure testing node is having instance knob
            if INSTANCE_DATA_KNOB not in node.knobs().keys():
                continue
            node_data = get_node_data(node, INSTANCE_DATA_KNOB)

            if not node_data:
                # a node has no instance data
                continue

            # test if product name is matching
            if node_data.get("productType") == product_name:
                raise NukeCreatorError(
                    (
                        "A publish instance for '{}' already exists "
                        "in nodes! Please change the variant "
                        "name to ensure unique output."
                    ).format(product_name)
                )

    def create_instance_node(
        self,
        node_name,
        knobs=None,
        parent=None,
        node_type=None,
        node_selection=None,
    ):
        """Create node representing instance.

        Arguments:
            node_name (str): Name of the new node.
            knobs (OrderedDict): node knobs name and values
            parent (str): Name of the parent node.
            node_type (str, optional): Nuke node Class.
            node_selection (Optional[list[nuke.Node]]): The node selection.

        Returns:
            nuke.Node: Newly created instance node.

        """
        node_type = node_type or "NoOp"

        node_knobs = knobs or {}

        # set parent node
        parent_node = nuke.root()
        if parent:
            parent_node = nuke.toNode(parent)

        try:
            with parent_node:
                created_node = nuke.createNode(node_type)
                created_node["name"].setValue(node_name)

                for key, values in node_knobs.items():
                    if key in created_node.knobs():
                        created_node["key"].setValue(values)
        except Exception as _err:
            raise NukeCreatorError("Creating have failed: {}".format(_err))

        return created_node

    def _get_current_selected_nodes(
        self,
        pre_create_data,
        class_name: str = None,
    ):
        """ Get current node selection.

        Arguments:
            pre_create_data (dict): The creator initial data.
            class_name (Optional[str]): Filter on a class name.

        Returns:
            list[nuke.Node]: node selection.
        """
        class_name = class_name or self.node_class_name
        use_selection = pre_create_data.get("use_selection")

        if use_selection:
            selected_nodes = nuke.selectedNodes()
        else:
            selected_nodes = nuke.allNodes()

        if class_name:
            # Allow class name implicit last versions of class names like
            # `Camera` to match any of its explicit versions, e.g.
            # `Camera3` or `Camera4`.
            if not class_name[-1].isdigit():
                # Match name with any digit
                pattern = rf"^{class_name}\d*$"
            else:
                pattern = class_name
            regex = re.compile(pattern)
            selected_nodes = [
                node
                for node in selected_nodes
                if regex.match(node.Class())
            ]

        if class_name and use_selection and not selected_nodes:
            raise NukeCreatorError(f"Select a {class_name} node.")

        return selected_nodes

    def create(self, product_name, instance_data, pre_create_data):

        # make sure selected nodes are detected early on.
        # we do not want any further Nuke operation to change the selection.
        node_selection = self._get_current_selected_nodes(pre_create_data)

        # make sure product name is unique
        self.check_existing_product(product_name)

        try:
            instance_node = self.create_instance_node(
                product_name,
                node_type=instance_data.pop("node_type", None),
                node_selection=node_selection,
            )
            instance = CreatedInstance(
                self.product_type,
                product_name,
                instance_data,
                self
            )

            self.apply_staging_dir(instance)
            instance.transient_data["node"] = instance_node

            self._add_instance_to_context(instance)

            set_node_data(
                instance_node, INSTANCE_DATA_KNOB, instance.data_to_store())

            return instance

        except Exception as exc:
            raise NukeCreatorError(f"Creator error: {exc}") from exc

    def collect_instances(self):
        cached_instances = _collect_and_cache_nodes(self)
        attr_def_keys = {
            attr_def.key
            for attr_def in self.get_instance_attr_defs()
        }
        attr_def_keys.discard(None)

        for (node, data) in cached_instances[self.identifier]:
            created_instance = CreatedInstance.from_existing(
                data, self
            )

            self.apply_staging_dir(created_instance)
            created_instance.transient_data["node"] = node
            self._add_instance_to_context(created_instance)

            for key in (
                set(created_instance["creator_attributes"].keys())
                - attr_def_keys
            ):
                created_instance["creator_attributes"].pop(key)

    def update_instances(self, update_list):
        for created_inst, changes in update_list:
            instance_node = created_inst.transient_data["node"]

            # in case node is not existing anymore (user erased it manually)
            try:
                instance_node.fullName()
            except ValueError:
                self._remove_instance_from_context(created_inst)
                continue

            # update instance node name if product name changed
            if "productName" in changes.changed_keys:
                instance_node["name"].setValue(
                    changes["productName"].new_value
                )

            set_node_data(
                instance_node,
                INSTANCE_DATA_KNOB,
                created_inst.data_to_store()
            )

    def remove_instances(self, instances):
        for instance in instances:
            remove_instance(instance)
            self._remove_instance_from_context(instance)

    def get_pre_create_attr_defs(self):
        return [
            BoolDef(
                "use_selection",
                default=not self.create_context.headless,
                label="Use selection"
            )
        ]

    def get_creator_settings(self, project_settings, settings_key=None):
        if not settings_key:
            settings_key = self.__class__.__name__
        return project_settings["nuke"]["create"][settings_key]

check_existing_product(product_name)

Make sure product name is unique.

It search within all nodes recursively and checks if product name is found in any node having instance data knob.

Parameters:

Name Type Description Default
product_name str

Product name

required
Source code in client/ayon_nuke/api/plugin.py
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def check_existing_product(self, product_name):
    """Make sure product name is unique.

    It search within all nodes recursively
    and checks if product name is found in
    any node having instance data knob.

    Arguments:
        product_name (str): Product name
    """

    for node in nuke.allNodes(recurseGroups=True):
        # make sure testing node is having instance knob
        if INSTANCE_DATA_KNOB not in node.knobs().keys():
            continue
        node_data = get_node_data(node, INSTANCE_DATA_KNOB)

        if not node_data:
            # a node has no instance data
            continue

        # test if product name is matching
        if node_data.get("productType") == product_name:
            raise NukeCreatorError(
                (
                    "A publish instance for '{}' already exists "
                    "in nodes! Please change the variant "
                    "name to ensure unique output."
                ).format(product_name)
            )

create_instance_node(node_name, knobs=None, parent=None, node_type=None, node_selection=None)

Create node representing instance.

Parameters:

Name Type Description Default
node_name str

Name of the new node.

required
knobs OrderedDict

node knobs name and values

None
parent str

Name of the parent node.

None
node_type str

Nuke node Class.

None
node_selection Optional[list[Node]]

The node selection.

None

Returns:

Type Description

nuke.Node: Newly created instance node.

Source code in client/ayon_nuke/api/plugin.py
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
def create_instance_node(
    self,
    node_name,
    knobs=None,
    parent=None,
    node_type=None,
    node_selection=None,
):
    """Create node representing instance.

    Arguments:
        node_name (str): Name of the new node.
        knobs (OrderedDict): node knobs name and values
        parent (str): Name of the parent node.
        node_type (str, optional): Nuke node Class.
        node_selection (Optional[list[nuke.Node]]): The node selection.

    Returns:
        nuke.Node: Newly created instance node.

    """
    node_type = node_type or "NoOp"

    node_knobs = knobs or {}

    # set parent node
    parent_node = nuke.root()
    if parent:
        parent_node = nuke.toNode(parent)

    try:
        with parent_node:
            created_node = nuke.createNode(node_type)
            created_node["name"].setValue(node_name)

            for key, values in node_knobs.items():
                if key in created_node.knobs():
                    created_node["key"].setValue(values)
    except Exception as _err:
        raise NukeCreatorError("Creating have failed: {}".format(_err))

    return created_node

NukeHost

Bases: HostBase, IWorkfileHost, ILoadHost, IPublishHost

Source code in client/ayon_nuke/api/pipeline.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class NukeHost(
    HostBase, IWorkfileHost, ILoadHost, IPublishHost
):
    name = "nuke"

    def open_workfile(self, filepath):
        return open_file(filepath)

    def save_workfile(self, filepath=None):
        return save_file(filepath)

    def work_root(self, session):
        return work_root(session)

    def get_current_workfile(self):
        return current_file()

    def workfile_has_unsaved_changes(self):
        return has_unsaved_changes()

    def get_workfile_extensions(self):
        return file_extensions()

    def get_containers(self):
        return ls()

    def install(self):
        """Installing all requirements for Nuke host"""

        pyblish.api.register_host("nuke")

        self.log.info("Registering Nuke plug-ins..")
        pyblish.api.register_plugin_path(PUBLISH_PATH)
        register_loader_plugin_path(LOAD_PATH)
        register_creator_plugin_path(CREATE_PATH)
        register_inventory_action_path(INVENTORY_PATH)
        register_workfile_build_plugin_path(WORKFILE_BUILD_PATH)

        # Register AYON event for workfiles loading.
        register_event_callback("workio.open_file", check_inventory_versions)
        register_event_callback("taskChanged", change_context_label)

        _install_menu()

        # add script menu
        add_scripts_menu()
        add_scripts_gizmo()

        add_nuke_callbacks()

        launch_workfiles_app()

    def get_context_data(self):
        root_node = nuke.root()
        return get_node_data(root_node, ROOT_DATA_KNOB)

    def update_context_data(self, data, changes):
        root_node = nuke.root()
        set_node_data(root_node, ROOT_DATA_KNOB, data)

install()

Installing all requirements for Nuke host

Source code in client/ayon_nuke/api/pipeline.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
def install(self):
    """Installing all requirements for Nuke host"""

    pyblish.api.register_host("nuke")

    self.log.info("Registering Nuke plug-ins..")
    pyblish.api.register_plugin_path(PUBLISH_PATH)
    register_loader_plugin_path(LOAD_PATH)
    register_creator_plugin_path(CREATE_PATH)
    register_inventory_action_path(INVENTORY_PATH)
    register_workfile_build_plugin_path(WORKFILE_BUILD_PATH)

    # Register AYON event for workfiles loading.
    register_event_callback("workio.open_file", check_inventory_versions)
    register_event_callback("taskChanged", change_context_label)

    _install_menu()

    # add script menu
    add_scripts_menu()
    add_scripts_gizmo()

    add_nuke_callbacks()

    launch_workfiles_app()

NukeWriteCreator

Bases: NukeCreator

Add Publishable Write node

Source code in client/ayon_nuke/api/plugin.py
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
class NukeWriteCreator(NukeCreator):
    """Add Publishable Write node"""

    identifier = "create_write"
    label = "Create Write"
    product_type = "write"
    icon = "sign-out"

    temp_rendering_path_template = (  # default to be applied if settings is missing
        "{work}/renders/nuke/{product[name]}/{product[name]}.{frame}.{ext}")

    render_target = "local"  # default to be applied if settings is missing

    def get_linked_knobs(self):
        linked_knobs = []
        if "channels" in self.instance_attributes:
            linked_knobs.append("channels")
        if "ordered" in self.instance_attributes:
            linked_knobs.append("render_order")
        if "use_range_limit" in self.instance_attributes:
            linked_knobs.extend(["___", "first", "last", "use_limit"])

        return linked_knobs

    def integrate_links(self, node_selection, node, outputs=True):
        # skip if no selection
        if not node_selection:  # selection should contain either 1 or no node.
            return

        # collect dependencies
        input_nodes = node_selection
        dependent_nodes = node_selection[0].dependent() if outputs else []

        # relinking to collected connections
        for i, input in enumerate(input_nodes):
            node.setInput(i, input)

        # make it nicer in graph
        node.autoplace()

        # relink also dependent nodes
        for dep_nodes in dependent_nodes:
            dep_nodes.setInput(0, node)

    def _get_current_selected_nodes(
        self,
        pre_create_data,
    ):
        """ Get current node selection.

        Arguments:
            pre_create_data (dict): The creator initial data.
            class_name (Optional[str]): Filter on a class name.

        Returns:
            list[nuke.Node]: node selection.

        Raises:
            NukeCreatorError. When the selection contains more than 1 Write node.
        """
        if not pre_create_data.get("use_selection"):
            return []

        selected_nodes = super()._get_current_selected_nodes(
            pre_create_data,
            class_name=None,
        )

        if not selected_nodes:
            raise NukeCreatorError("No active selection")

        elif len(selected_nodes) > 1:
            raise NukeCreatorError("Select only one node")

        return selected_nodes

    def update_instances(self, update_list):
        super().update_instances(update_list)
        for created_inst, changes in update_list:
            # ensure was not deleted by super()
            if self.create_context.get_instance_by_id(created_inst.id):
                self._update_write_node_filepath(created_inst, changes)

    def _update_write_node_filepath(self, created_inst, changes):
        """Update instance node on context changes.

        Whenever any of productName, folderPath, task or productType
        changes then update:
        - output filepath of the write node
        - instance node's name to the product name
        """
        keys = ("productName", "folderPath", "task", "productType")
        if not any(key in changes.changed_keys for key in keys):
            # No relevant changes, no need to update
            return

        data = created_inst.data_to_store()
        # Update values with new formatted path
        instance_node = created_inst.transient_data["node"]
        formatting_data = copy.deepcopy(data)
        write_node = nuke.allNodes(group=instance_node, filter="Write")[0]
        _, ext = os.path.splitext(write_node["file"].value())
        formatting_data.update({"ext": ext[1:]})

        # Retieve render template and staging directory.
        fpath_template = self.temp_rendering_path_template
        formatting_data["work"] = get_work_default_directory(formatting_data)
        fpath = StringTemplate(fpath_template).format_strict(formatting_data)

        staging_dir = self.apply_staging_dir(created_inst)
        if staging_dir:
            basename = os.path.basename(fpath)
            staging_path = pathlib.Path(staging_dir)/ basename
            fpath = staging_path.as_posix()

        write_node["file"].setValue(fpath)

    def get_pre_create_attr_defs(self):
        attrs_defs = super().get_pre_create_attr_defs()
        attrs_defs.append(self._get_render_target_enum())

        return attrs_defs

    def get_instance_attr_defs(self):
        attr_defs = [self._get_render_target_enum()]

        # add reviewable attribute
        if "reviewable" in self.instance_attributes:
            attr_defs.append(
                BoolDef(
                    "review",
                    default=True,
                    label="Review"
                )
            )

        return attr_defs

    def _get_render_target_enum(self):
        rendering_targets = {
            "local": "Local machine rendering",
            "frames": "Use existing frames"
        }

        if "farm_rendering" in self.instance_attributes:
            rendering_targets.update({
                "frames_farm": "Use existing frames - farm",
                "farm": "Farm rendering",
            })

        return EnumDef(
            "render_target",
            items=rendering_targets,
            default=self.render_target,
            label="Render target",
            tooltip="Define the render target."
        )

    def create(self, product_name, instance_data, pre_create_data):
        if not pre_create_data:
            # add no selection for headless
            pre_create_data = {
                "use_selection": False
            }

        # pass values from precreate to instance
        self._pass_pre_attributes_to_instance(
            instance_data,
            pre_create_data,
            [
                "active_frame",
                "render_target"
            ]
        )
        # make sure selected nodes are added
        node_selection = self._get_current_selected_nodes(pre_create_data)

        # make sure product name is unique
        self.check_existing_product(product_name)

        try:
            instance = CreatedInstance(
                self.product_type,
                product_name,
                instance_data,
                self
            )

            staging_dir = self.apply_staging_dir(instance)
            instance_node = self.create_instance_node(
                product_name,
                instance_data,
                staging_dir=staging_dir,
                node_selection=node_selection,
            )

            instance.transient_data["node"] = instance_node

            self._add_instance_to_context(instance)

            set_node_data(
                instance_node,
                INSTANCE_DATA_KNOB,
                instance.data_to_store()
            )

            exposed_write_knobs(
                self.project_settings, self.__class__.__name__, instance_node
            )

            return instance

        except Exception as exc:
            raise NukeCreatorError(f"Creator error: {exc}") from exc

    def apply_settings(self, project_settings):
        """Method called on initialization of plugin to apply settings."""

        # plugin settings
        plugin_settings = self.get_creator_settings(project_settings)
        temp_rendering_path_template = (
            plugin_settings.get("temp_rendering_path_template")
            or self.temp_rendering_path_template
        )
        # TODO remove template key replacements
        temp_rendering_path_template = (
            temp_rendering_path_template
            .replace("{product[name]}", "{subset}")
            .replace("{product[type]}", "{family}")
            .replace("{task[name]}", "{task}")
            .replace("{folder[name]}", "{asset}")
        )
        # individual attributes
        self.instance_attributes = plugin_settings.get(
            "instance_attributes") or self.instance_attributes
        self.prenodes = plugin_settings["prenodes"]
        self.default_variants = plugin_settings.get(
            "default_variants") or self.default_variants
        self.render_target = plugin_settings.get(
            "render_target") or self.render_target
        self.temp_rendering_path_template = temp_rendering_path_template

apply_settings(project_settings)

Method called on initialization of plugin to apply settings.

Source code in client/ayon_nuke/api/plugin.py
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
def apply_settings(self, project_settings):
    """Method called on initialization of plugin to apply settings."""

    # plugin settings
    plugin_settings = self.get_creator_settings(project_settings)
    temp_rendering_path_template = (
        plugin_settings.get("temp_rendering_path_template")
        or self.temp_rendering_path_template
    )
    # TODO remove template key replacements
    temp_rendering_path_template = (
        temp_rendering_path_template
        .replace("{product[name]}", "{subset}")
        .replace("{product[type]}", "{family}")
        .replace("{task[name]}", "{task}")
        .replace("{folder[name]}", "{asset}")
    )
    # individual attributes
    self.instance_attributes = plugin_settings.get(
        "instance_attributes") or self.instance_attributes
    self.prenodes = plugin_settings["prenodes"]
    self.default_variants = plugin_settings.get(
        "default_variants") or self.default_variants
    self.render_target = plugin_settings.get(
        "render_target") or self.render_target
    self.temp_rendering_path_template = temp_rendering_path_template

SelectInstanceNodeAction

Bases: Action

Select instance node for failed plugin.

Source code in client/ayon_nuke/api/actions.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class SelectInstanceNodeAction(pyblish.api.Action):
    """Select instance node for failed plugin."""
    label = "Select instance node"
    on = "failed"  # This action is only available on a failed plug-in
    icon = "mdi.cursor-default-click"

    def process(self, context, plugin):

        # Get the errored instances for the plug-in
        errored_instances = get_errored_instances_from_context(
            context, plugin)

        # Get the invalid nodes for the plug-ins
        self.log.info("Finding instance nodes..")
        nodes = set()
        for instance in errored_instances:
            instance_node = instance.data.get("transientData", {}).get("node")
            if not instance_node:
                raise RuntimeError(
                    "No transientData['node'] found on instance: {}".format(
                        instance
                    )
                )
            nodes.add(instance_node)

        if nodes:
            self.log.info("Selecting instance nodes: {}".format(nodes))
            reset_selection()
            select_nodes(nodes)
        else:
            self.log.info("No instance nodes found.")

SelectInvalidAction

Bases: Action

Select invalid nodes in Nuke when plug-in failed.

To retrieve the invalid nodes this assumes a static get_invalid() method is available on the plugin.

Source code in client/ayon_nuke/api/actions.py
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class SelectInvalidAction(pyblish.api.Action):
    """Select invalid nodes in Nuke when plug-in failed.

    To retrieve the invalid nodes this assumes a static `get_invalid()`
    method is available on the plugin.

    """
    label = "Select invalid nodes"
    on = "failed"  # This action is only available on a failed plug-in
    icon = "search"  # Icon from Awesome Icon

    def process(self, context, plugin):

        errored_instances = get_errored_instances_from_context(context,
                                                               plugin=plugin)

        # Get the invalid nodes for the plug-ins
        self.log.info("Finding invalid nodes..")
        invalid = set()
        for instance in errored_instances:
            invalid_nodes = plugin.get_invalid(instance)

            if invalid_nodes:
                if isinstance(invalid_nodes, (list, tuple)):
                    invalid.update(invalid_nodes)
                else:
                    self.log.warning("Plug-in returned to be invalid, "
                                     "but has no selectable nodes.")

        if invalid:
            self.log.info("Selecting invalid nodes: {}".format(invalid))
            reset_selection()
            select_nodes(invalid)
        else:
            self.log.info("No invalid nodes found.")

colorspace_exists_on_node(node, colorspace_name)

Check if colorspace exists on node

Look through all options in the colorspace knob, and see if we have an exact match to one of the items.

Parameters:

Name Type Description Default
node Node

nuke node object

required
colorspace_name str

color profile name

required

Returns:

Name Type Description
bool

True if exists

Source code in client/ayon_nuke/api/colorspace.py
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
def colorspace_exists_on_node(node, colorspace_name):
    """ Check if colorspace exists on node

    Look through all options in the colorspace knob, and see if we have an
    exact match to one of the items.

    Args:
        node (nuke.Node): nuke node object
        colorspace_name (str): color profile name

    Returns:
        bool: True if exists
    """
    node_knob_keys = node.knobs().keys()

    if "colorspace" in node_knob_keys:
        colorspace_knob = node["colorspace"]
    elif "floatLut" in node_knob_keys:
        colorspace_knob = node["floatLut"]
    else:
        log.warning(f"Node '{node.name()}' does not have colorspace knob")
        return False

    return colorspace_name in get_colorspace_list(colorspace_knob, node)

containerise(node, name, namespace, context, loader=None, data=None)

Bundle node into an assembly and imprint it with metadata

Containerisation enables a tracking of version, author and origin for loaded assets.

Parameters:

Name Type Description Default
node Node

Nuke's node object to imprint as container

required
name str

Name of resulting assembly

required
namespace str

Namespace under which to host container

required
context dict

Asset information

required
loader str

Name of node used to produce this container.

None

Returns:

Name Type Description
node Node

containerised nuke's node object

Source code in client/ayon_nuke/api/pipeline.py
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
def containerise(node,
                 name,
                 namespace,
                 context,
                 loader=None,
                 data=None):
    """Bundle `node` into an assembly and imprint it with metadata

    Containerisation enables a tracking of version, author and origin
    for loaded assets.

    Arguments:
        node (nuke.Node): Nuke's node object to imprint as container
        name (str): Name of resulting assembly
        namespace (str): Namespace under which to host container
        context (dict): Asset information
        loader (str, optional): Name of node used to produce this container.

    Returns:
        node (nuke.Node): containerised nuke's node object

    """
    data = OrderedDict(
        [
            ("schema", "openpype:container-2.0"),
            ("id", AVALON_CONTAINER_ID),
            ("name", name),
            ("namespace", namespace),
            ("loader", str(loader)),
            ("representation", context["representation"]["id"]),
        ],

        **data or dict()
    )

    set_avalon_knob_data(node, data)

    # set tab to first native
    node.setTab(0)

    return node

create_write_node(name, data, input=None, prenodes=None, linked_knobs=None, **kwargs)

Creating write node which is group node

Parameters:

Name Type Description Default
name str

name of node

required
data dict

creator write instance data

required
input node)[optional]

selected node to connect to

None
prenodes Optional[list[dict]]

nodes to be created before write with dependency

None
review bool)[optional]

adding review knob

required
farm bool)[optional]

rendering workflow target

required
kwargs dict)[optional]

additional key arguments for formatting

{}
Example

prenodes = { "nodeName": { "nodeclass": "Reformat", "dependent": [ following_node_01, ... ], "knobs": [ { "type": "text", "name": "knobname", "value": "knob value" }, ... ] }, ... }

Return

node (obj): group node with avalon data as Knobs

Source code in client/ayon_nuke/api/lib.py
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
def create_write_node(
    name,
    data,
    input=None,
    prenodes=None,
    linked_knobs=None,
    **kwargs
):
    ''' Creating write node which is group node

    Arguments:
        name (str): name of node
        data (dict): creator write instance data
        input (node)[optional]: selected node to connect to
        prenodes (Optional[list[dict]]): nodes to be created before write
            with dependency
        review (bool)[optional]: adding review knob
        farm (bool)[optional]: rendering workflow target
        kwargs (dict)[optional]: additional key arguments for formatting

    Example:
        prenodes = {
            "nodeName": {
                "nodeclass": "Reformat",
                "dependent": [
                    following_node_01,
                    ...
                ],
                "knobs": [
                    {
                        "type": "text",
                        "name": "knobname",
                        "value": "knob value"
                    },
                    ...
                ]
            },
            ...
        }


    Return:
        node (obj): group node with avalon data as Knobs
    '''
    # Ensure name does not contain any invalid characters.
    special_chars = re.escape("!@#$%^&*()=[]{}|\\;',.<>/?~+-")
    special_chars_regex = re.compile(f"[{special_chars}]")
    found_special_characters = list(special_chars_regex.findall(name))

    msg = (
        f"Special characters found in name \"{name}\": "
        f"{' '.join(found_special_characters)}"
    )
    assert not found_special_characters, msg

    prenodes = prenodes or []

    # filtering variables
    plugin_name = data["creator"]
    product_name = data["productName"]

    # get knob settings for write node
    imageio_writes = get_imageio_node_setting(
        node_class="Write",
        plugin_name=plugin_name,
        product_name=product_name
    )

    ext = None
    knobs = imageio_writes["knobs"]
    knob_names = {knob["name"]: knob for knob in knobs}

    if "ext" in knob_names:
        knob_type = knob_names["ext"]["type"]
        ext = knob_names["ext"][knob_type]

    # For most extensions, setting the "file_type"
    # is enough, however sometimes they differ, e.g.:
    # ext = sxr / file_type = exr
    # ext = jpg / file_type = jpeg
    elif "file_type" in knob_names:
        knob_type = knob_names["file_type"]["type"]
        ext = knob_names["file_type"][knob_type]

    if not ext:
        raise RuntimeError(
            "Could not determine extension from settings for "
            f"plugin_name={plugin_name} product_name={product_name}"
        )

    data.update({
        "imageio_writes": imageio_writes,
        "ext": ext
    })

    # build file path to workfiles
    data["work"] = get_work_default_directory(data)
    fpath = StringTemplate(data["fpath_template"]).format_strict(data)

    # Override output directory is provided staging directory.
    if data.get("staging_dir"):
        basename = os.path.basename(fpath)
        staging_path = pathlib.Path(data["staging_dir"]) / basename
        fpath = staging_path.as_posix()

    # create directory
    if not os.path.isdir(os.path.dirname(fpath)):
        log.warning("Path does not exist! I am creating it.")
        os.makedirs(os.path.dirname(fpath))

    GN = nuke.createNode("Group", "name {}".format(name))

    prev_node = None
    with GN:
        if input:
            input_name = str(input.name()).replace(" ", "")
            # if connected input node was defined
            prev_node = nuke.createNode(
                "Input",
                "name {}".format(input_name),
                inpanel=False
            )
        else:
            # generic input node connected to nothing
            prev_node = nuke.createNode(
                "Input",
                "name {}".format("rgba"),
                inpanel=False
            )

        # creating pre-write nodes `prenodes`
        last_prenode = create_prenodes(
            prev_node,
            prenodes,
            plugin_name,
            product_name,
            **kwargs
        )
        if last_prenode:
            prev_node = last_prenode

        # creating write node
        write_node = now_node = add_write_node(
            "inside_{}".format(name),
            fpath,
            imageio_writes["knobs"],
            **data
        )
        # connect to previous node
        now_node.setInput(0, prev_node)

        # switch actual node to previous
        prev_node = now_node

        now_node = nuke.createNode("Output", "name Output1", inpanel=False)

        # connect to previous node
        now_node.setInput(0, prev_node)

    # add divider
    GN.addKnob(nuke.Text_Knob('', 'Rendering'))

    # Add linked knobs.
    linked_knob_names = []

    # add input linked knobs and create group only if any input
    if linked_knobs:
        linked_knob_names.append("_grp-start_")
        linked_knob_names.extend(linked_knobs)
        linked_knob_names.append("_grp-end_")

    linked_knob_names.append("Render")

    for _k_name in linked_knob_names:
        if "_grp-start_" in _k_name:
            knob = nuke.Tab_Knob(
                "rnd_attr", "Rendering attributes", nuke.TABBEGINCLOSEDGROUP)
            GN.addKnob(knob)
        elif "_grp-end_" in _k_name:
            knob = nuke.Tab_Knob(
                "rnd_attr_end", "Rendering attributes", nuke.TABENDGROUP)
            GN.addKnob(knob)
        else:
            if "___" in _k_name:
                # add divider
                GN.addKnob(nuke.Text_Knob(""))
            else:
                # add linked knob by _k_name
                link = nuke.Link_Knob("")
                link.makeLink(write_node.name(), _k_name)
                link.setName(_k_name)

                # make render
                if "Render" in _k_name:
                    link.setLabel("Render Local")
                link.setFlag(0x1000)
                GN.addKnob(link)

    # Adding render farm submission button.
    if data.get("render_on_farm", False):
        add_button_render_on_farm(GN)

    # adding write to read button
    add_button_write_to_read(GN)

    # adding write to read button
    add_button_clear_rendered(GN, os.path.dirname(fpath))

    # set tile color
    tile_color = next(
        iter(
            k[k["type"]] for k in imageio_writes["knobs"]
            if "tile_color" in k["name"]
        ), [255, 0, 0, 255]
    )
    new_tile_color = []
    for c in tile_color:
        if isinstance(c, float):
            c = int(c * 255)
        new_tile_color.append(c)
    GN["tile_color"].setValue(
        color_gui_to_int(new_tile_color))

    return GN

get_colorspace_list(colorspace_knob, node=None, consider_aliases=True)

Get available colorspace profile names

Parameters:

Name Type Description Default
colorspace_knob Knob

nuke knob object

required
node Optional[Node]

nuke node for caching differentiation

None
consider_aliases Optional[bool]

optional flag to indicate if

True

Returns:

Name Type Description
list

list of strings names of profiles

Source code in client/ayon_nuke/api/colorspace.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
def get_colorspace_list(colorspace_knob, node=None, consider_aliases=True):
    """Get available colorspace profile names

    Args:
        colorspace_knob (nuke.Knob): nuke knob object
        node (Optional[nuke.Node]): nuke node for caching differentiation
        consider_aliases (Optional[bool]): optional flag to indicate if
        colorspace aliases should be added to results list

    Returns:
        list: list of strings names of profiles
    """
    results = []

    # making sure any node is provided
    node = node or nuke.root()
    # unique script based identifier
    script_name = nuke.root().name()
    node_name = node.fullName()
    identifier_key = f"{script_name}_{node_name}"

    if _COLORSPACES_CACHE.get(identifier_key) is None:
        # This pattern is to match with roles which uses an indentation and
        # parentheses with original colorspace. The value returned from the
        # colorspace is the string before the indentation, so we'll need to
        # convert the values to match with value returned from the knob,
        # ei. knob.value().
        pattern = r"[\t,]+"
        for colorspace in nuke.getColorspaceList(colorspace_knob):
            colorspace_and_aliases = re.split(pattern, colorspace)
            if consider_aliases:
                results.extend(colorspace_and_aliases)
            else:
                results.append(colorspace_and_aliases[0])

        _COLORSPACES_CACHE[identifier_key] = results

    return _COLORSPACES_CACHE[identifier_key]

get_instance_group_node_childs(instance)

Return list of instance group node children

Parameters:

Name Type Description Default
instance Instance

pyblish instance

required

Returns:

Name Type Description
list

[nuke.Node]

Source code in client/ayon_nuke/api/plugin.py
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
def get_instance_group_node_childs(instance):
    """Return list of instance group node children

    Args:
        instance (pyblish.Instance): pyblish instance

    Returns:
        list: [nuke.Node]
    """
    node = instance.data["transientData"]["node"]

    if node.Class() != "Group":
        return

    # collect child nodes
    child_nodes = []
    # iterate all nodes
    for node in nuke.allNodes(group=node):
        # add contained nodes to instance's node list
        child_nodes.append(node)

    return child_nodes

get_node_data(node, knobname)

Read data from node.

Parameters:

Name Type Description Default
node Node

node object

required
knobname str

knob name

required

Returns:

Name Type Description
dict

data stored in knob

Source code in client/ayon_nuke/api/lib.py
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def get_node_data(node, knobname):
    """Read data from node.

    Args:
        node (nuke.Node): node object
        knobname (str): knob name

    Returns:
        dict: data stored in knob
    """
    if knobname not in node.knobs():
        return

    rawdata = node[knobname].getValue()
    if (
        isinstance(rawdata, str)
        and rawdata.startswith(JSON_PREFIX)
    ):
        try:
            return json.loads(rawdata[len(JSON_PREFIX):])
        except json.JSONDecodeError:
            return

Link knobs from inside group_node

Source code in client/ayon_nuke/api/lib.py
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
def link_knobs(knobs, node, group_node):
    """Link knobs from inside `group_node`"""

    missing_knobs = []
    for knob in knobs:
        if knob in group_node.knobs():
            continue

        if knob not in node.knobs().keys():
            missing_knobs.append(knob)

        link = nuke.Link_Knob("")
        link.makeLink(node.name(), knob)
        link.setName(knob)
        link.setFlag(0x1000)
        group_node.addKnob(link)

    if missing_knobs:
        raise ValueError(
            "Write node exposed knobs missing:\n\n{}\n\nPlease review"
            " project settings.".format("\n".join(missing_knobs))
        )

list_instances(creator_id=None)

List all created instances to publish from current workfile.

For SubsetManager

Parameters:

Name Type Description Default
creator_id Optional[str]

creator identifier

None

Returns:

Type Description

(list) of dictionaries matching instances format

Source code in client/ayon_nuke/api/pipeline.py
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
def list_instances(creator_id=None):
    """List all created instances to publish from current workfile.

    For SubsetManager

    Args:
        creator_id (Optional[str]): creator identifier

    Returns:
        (list) of dictionaries matching instances format
    """
    instances_by_order = defaultdict(list)
    product_instances = []
    instance_ids = set()

    for node in nuke.allNodes(recurseGroups=True):

        if node.Class() in ["Viewer", "Dot"]:
            continue

        try:
            if node["disable"].value():
                continue
        except NameError:
            # pass if disable knob doesn't exist
            pass

        # get data from avalon knob
        instance_data = get_node_data(
            node, INSTANCE_DATA_KNOB)

        if not instance_data:
            continue

        if instance_data["id"] not in {
            AYON_INSTANCE_ID, AVALON_INSTANCE_ID
        }:
            continue

        if creator_id and instance_data["creator_identifier"] != creator_id:
            continue

        instance_id = instance_data.get("instance_id")
        if not instance_id:
            pass
        elif instance_id in instance_ids:
            instance_data.pop("instance_id")
        else:
            instance_ids.add(instance_id)

        # node name could change, so update product name data
        _update_product_name_data(instance_data, node)

        if "render_order" not in node.knobs():
            product_instances.append((node, instance_data))
            continue

        order = int(node["render_order"].value())
        instances_by_order[order].append((node, instance_data))

    # Sort instances based on order attribute or product name.
    # TODO: remove in future Publisher enhanced with sorting
    ordered_instances = []
    for key in sorted(instances_by_order.keys()):
        instances_by_product = defaultdict(list)
        for node, data_ in instances_by_order[key]:
            product_name = data_.get("productName")
            if product_name is None:
                product_name = data_.get("subset")
            instances_by_product[product_name].append((node, data_))
        for subkey in sorted(instances_by_product.keys()):
            ordered_instances.extend(instances_by_product[subkey])

    instances_by_product = defaultdict(list)
    for node, data_ in product_instances:
        product_name = data_.get("productName")
        if product_name is None:
            product_name = data_.get("subset")
        instances_by_product[product_name].append((node, data_))
    for key in sorted(instances_by_product.keys()):
        ordered_instances.extend(instances_by_product[key])

    return ordered_instances

ls()

List available containers.

This function is used by the Container Manager in Nuke. You'll need to implement a for-loop that then yields one Container at a time.

Source code in client/ayon_nuke/api/pipeline.py
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
def ls():
    """List available containers.

    This function is used by the Container Manager in Nuke. You'll
    need to implement a for-loop that then *yields* one Container at
    a time.
    """
    all_nodes = nuke.allNodes(recurseGroups=False)

    nodes = [n for n in all_nodes]

    for n in nodes:
        container = parse_container(n)
        if container:
            yield container

maintained_selection(exclude_nodes=None)

Maintain selection during context

Maintain selection during context and unselect all nodes after context is done.

Parameters:

Name Type Description Default
exclude_nodes list[Node]

list of nodes to be unselected before context is done

None
Example

with maintained_selection(): ... node["selected"].setValue(True) print(node["selected"].value()) False

Source code in client/ayon_nuke/api/lib.py
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
@contextlib.contextmanager
def maintained_selection(exclude_nodes=None):
    """Maintain selection during context

    Maintain selection during context and unselect
    all nodes after context is done.

    Arguments:
        exclude_nodes (list[nuke.Node]): list of nodes to be unselected
                                         before context is done

    Example:
        >>> with maintained_selection():
        ...     node["selected"].setValue(True)
        >>> print(node["selected"].value())
        False
    """
    if exclude_nodes:
        for node in exclude_nodes:
            node["selected"].setValue(False)

    previous_selection = nuke.selectedNodes()

    try:
        yield
    finally:
        # unselect all selection in case there is some
        reset_selection()

        # and select all previously selected nodes
        if previous_selection:
            select_nodes(previous_selection)

parse_container(node)

Returns containerised data of a node

Reads the imprinted data from containerise.

Parameters:

Name Type Description Default
node Node

Nuke's node object to read imprinted data

required

Returns:

Name Type Description
dict

The container schema data for this container node.

Source code in client/ayon_nuke/api/pipeline.py
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
def parse_container(node):
    """Returns containerised data of a node

    Reads the imprinted data from `containerise`.

    Arguments:
        node (nuke.Node): Nuke's node object to read imprinted data

    Returns:
        dict: The container schema data for this container node.

    """
    data = read_avalon_data(node)

    # If not all required data return the empty container
    required = ["schema", "id", "name",
                "namespace", "loader", "representation"]
    if not all(key in data for key in required):
        return

    # Store the node's name
    data.update({
        "objectName": node.fullName(),
        "node": node,
    })

    return data

remove_instance(instance)

Remove instance from current workfile metadata.

For SubsetManager

Parameters:

Name Type Description Default
instance dict

instance representation from subsetmanager model

required
Source code in client/ayon_nuke/api/pipeline.py
619
620
621
622
623
624
625
626
627
628
629
630
def remove_instance(instance):
    """Remove instance from current workfile metadata.

    For SubsetManager

    Args:
        instance (dict): instance representation from subsetmanager model
    """
    instance_node = instance.transient_data["node"]
    instance_knob = instance_node.knobs()[INSTANCE_DATA_KNOB]
    instance_node.removeKnob(instance_knob)
    nuke.delete(instance_node)

reset_selection()

Deselect all selected nodes

Source code in client/ayon_nuke/api/lib.py
2390
2391
2392
2393
def reset_selection():
    """Deselect all selected nodes"""
    for node in nuke.selectedNodes():
        node["selected"].setValue(False)

select_instance(instance)

Select instance in Node View

Parameters:

Name Type Description Default
instance dict

instance representation from subsetmanager model

required
Source code in client/ayon_nuke/api/pipeline.py
633
634
635
636
637
638
639
640
641
def select_instance(instance):
    """
        Select instance in Node View

        Args:
            instance (dict): instance representation from subsetmanager model
    """
    instance_node = instance.transient_data["node"]
    instance_node["selected"].setValue(True)

select_nodes(nodes)

Selects all inputted nodes

Parameters:

Name Type Description Default
nodes Union[list, tuple, set]

nuke nodes to be selected

required
Source code in client/ayon_nuke/api/lib.py
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
def select_nodes(nodes):
    """Selects all inputted nodes

    Arguments:
        nodes (Union[list, tuple, set]): nuke nodes to be selected
    """
    assert isinstance(nodes, (list, tuple, set)), \
        "nodes has to be list, tuple or set"

    for node in nodes:
        node["selected"].setValue(True)

set_node_data(node, knobname, data)

Write data to node invisible knob

Will create new in case it doesn't exists or update the one already created.

Parameters:

Name Type Description Default
node Node

node object

required
knobname str

knob name

required
data dict

data to be stored in knob

required
Source code in client/ayon_nuke/api/lib.py
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
def set_node_data(node, knobname, data):
    """Write data to node invisible knob

    Will create new in case it doesn't exists
    or update the one already created.

    Args:
        node (nuke.Node): node object
        knobname (str): knob name
        data (dict): data to be stored in knob
    """
    # if exists then update data
    if knobname in node.knobs():
        update_node_data(node, knobname, data)
        return

    # else create new
    knob_value = JSON_PREFIX + json.dumps(data)
    knob = nuke.String_Knob(knobname)
    knob.setValue(knob_value)
    knob.setFlag(nuke.INVISIBLE)
    node.addKnob(knob)

update_container(node, keys=None)

Returns node with updateted containder data

Parameters:

Name Type Description Default
node Node

The node in Nuke to imprint as container,

required
keys dict

data which should be updated

None

Returns:

Name Type Description
node Node

nuke node with updated container data

Source code in client/ayon_nuke/api/pipeline.py
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
def update_container(node, keys=None):
    """Returns node with updateted containder data

    Arguments:
        node (nuke.Node): The node in Nuke to imprint as container,
        keys (dict, optional): data which should be updated

    Returns:
        node (nuke.Node): nuke node with updated container data

    Raises:
        TypeError on given an invalid container node

    """
    keys = keys or dict()

    container = parse_container(node)
    if not container:
        raise TypeError("Not a valid container node.")

    container.update(keys)
    node = set_avalon_knob_data(node, container)

    return node

update_node_data(node, knobname, data)

Update already present data.

Parameters:

Name Type Description Default
node Node

node object

required
knobname str

knob name

required
data dict

data to update knob value

required
Source code in client/ayon_nuke/api/lib.py
204
205
206
207
208
209
210
211
212
213
214
215
216
def update_node_data(node, knobname, data):
    """Update already present data.

    Args:
        node (nuke.Node): node object
        knobname (str): knob name
        data (dict): data to update knob value
    """
    knob = node[knobname]
    node_data = get_node_data(node, knobname) or {}
    node_data.update(data)
    knob_value = JSON_PREFIX + json.dumps(node_data)
    knob.setValue(knob_value)

viewer_update_and_undo_stop()

Lock viewer from updating and stop recording undo steps

Source code in client/ayon_nuke/api/command.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@contextlib.contextmanager
def viewer_update_and_undo_stop():
    """Lock viewer from updating and stop recording undo steps"""
    try:
        # stop active viewer to update any change
        viewer = nuke.activeViewer()
        if viewer:
            viewer.stop()
        else:
            log.warning("No available active Viewer")
        nuke.Undo.disable()
        yield
    finally:
        nuke.Undo.enable()