Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Nuke: Merge avalon's implementation into OpenPype #2514

Merged
merged 4 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions openpype/hosts/nuke/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ def add_implementation_envs(env, _app):
# Add requirements to NUKE_PATH
pype_root = os.environ["OPENPYPE_REPOS_ROOT"]
new_nuke_paths = [
os.path.join(pype_root, "openpype", "hosts", "nuke", "startup"),
os.path.join(
pype_root, "repos", "avalon-core", "setup", "nuke", "nuke_path"
)
os.path.join(pype_root, "openpype", "hosts", "nuke", "startup")
]
old_nuke_path = env.get("NUKE_PATH") or ""
for path in old_nuke_path.split(os.pathsep):
Expand Down
182 changes: 52 additions & 130 deletions openpype/hosts/nuke/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,130 +1,52 @@
import os
import nuke

import avalon.api
import pyblish.api
import openpype
from . import lib, menu

log = openpype.api.Logger().get_logger(__name__)

AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.nuke.__file__))
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")


# registering pyblish gui regarding settings in presets
if os.getenv("PYBLISH_GUI", None):
pyblish.api.register_gui(os.getenv("PYBLISH_GUI", None))


def reload_config():
"""Attempt to reload pipeline at run-time.

CAUTION: This is primarily for development and debugging purposes.

"""

import importlib

for module in (
"{}.api".format(AVALON_CONFIG),
"{}.hosts.nuke.api.actions".format(AVALON_CONFIG),
"{}.hosts.nuke.api.menu".format(AVALON_CONFIG),
"{}.hosts.nuke.api.plugin".format(AVALON_CONFIG),
"{}.hosts.nuke.api.lib".format(AVALON_CONFIG),
):
log.info("Reloading module: {}...".format(module))

module = importlib.import_module(module)

try:
importlib.reload(module)
except AttributeError as e:
from importlib import reload
log.warning("Cannot reload module: {}".format(e))
reload(module)


def install():
''' Installing all requarements for Nuke host
'''

# remove all registred callbacks form avalon.nuke
from avalon import pipeline
pipeline._registered_event_handlers.clear()

log.info("Registering Nuke plug-ins..")
pyblish.api.register_plugin_path(PUBLISH_PATH)
avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH)
avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH)

# Register Avalon event for workfiles loading.
avalon.api.on("workio.open_file", lib.check_inventory_versions)
avalon.api.on("taskChanged", menu.change_context_label)

pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled)
workfile_settings = lib.WorkfileSettings()
# Disable all families except for the ones we explicitly want to see
family_states = [
"write",
"review",
"nukenodes",
"model",
"gizmo"
]

avalon.api.data["familiesStateDefault"] = False
avalon.api.data["familiesStateToggled"] = family_states

# Set context settings.
nuke.addOnCreate(workfile_settings.set_context_settings, nodeClass="Root")
nuke.addOnCreate(workfile_settings.set_favorites, nodeClass="Root")
nuke.addOnCreate(lib.process_workfile_builder, nodeClass="Root")
nuke.addOnCreate(lib.launch_workfiles_app, nodeClass="Root")
menu.install()


def uninstall():
'''Uninstalling host's integration
'''
log.info("Deregistering Nuke plug-ins..")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH)
avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH)

pyblish.api.deregister_callback(
"instanceToggled", on_pyblish_instance_toggled)

reload_config()
menu.uninstall()


def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle node passthrough states on instance toggles."""

log.info("instance toggle: {}, old_value: {}, new_value:{} ".format(
instance, old_value, new_value))

from avalon.nuke import (
viewer_update_and_undo_stop,
add_publish_knob
)

# Whether instances should be passthrough based on new value

with viewer_update_and_undo_stop():
n = instance[0]
try:
n["publish"].value()
except ValueError:
n = add_publish_knob(n)
log.info(" `Publish` knob was added to write node..")

n["publish"].setValue(new_value)
from .workio import (
file_extensions,
has_unsaved_changes,
save_file,
open_file,
current_file,
work_root,
)

from .command import (
reset_frame_range,
get_handles,
reset_resolution,
viewer_update_and_undo_stop
)

from .plugin import OpenPypeCreator
from .pipeline import (
install,
uninstall,

ls,

containerise,
parse_container,
update_container,
)


__all__ = (
"file_extensions",
"has_unsaved_changes",
"save_file",
"open_file",
"current_file",
"work_root",

"reset_frame_range",
"get_handles",
"reset_resolution",
"viewer_update_and_undo_stop",

"OpenPypeCreator",
"install",
"uninstall",

"ls",

"containerise",
"parse_container",
"update_container",
)
5 changes: 2 additions & 3 deletions openpype/hosts/nuke/api/actions.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import pyblish.api

from avalon.nuke.lib import (
from openpype.api import get_errored_instances_from_context
from .lib import (
reset_selection,
select_nodes
)

from openpype.api import get_errored_instances_from_context


class SelectInvalidAction(pyblish.api.Action):
"""Select invalid nodes in Nuke when plug-in failed.
Expand Down
135 changes: 135 additions & 0 deletions openpype/hosts/nuke/api/command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import logging
import contextlib
import nuke

from avalon import api, io


log = logging.getLogger(__name__)


def reset_frame_range():
""" Set frame range to current asset
Also it will set a Viewer range with
displayed handles
"""

fps = float(api.Session.get("AVALON_FPS", 25))

nuke.root()["fps"].setValue(fps)
name = api.Session["AVALON_ASSET"]
asset = io.find_one({"name": name, "type": "asset"})
asset_data = asset["data"]

handles = get_handles(asset)

frame_start = int(asset_data.get(
"frameStart",
asset_data.get("edit_in")))

frame_end = int(asset_data.get(
"frameEnd",
asset_data.get("edit_out")))

if not all([frame_start, frame_end]):
missing = ", ".join(["frame_start", "frame_end"])
msg = "'{}' are not set for asset '{}'!".format(missing, name)
log.warning(msg)
nuke.message(msg)
return

frame_start -= handles
frame_end += handles

nuke.root()["first_frame"].setValue(frame_start)
nuke.root()["last_frame"].setValue(frame_end)

# setting active viewers
vv = nuke.activeViewer().node()
vv["frame_range_lock"].setValue(True)
vv["frame_range"].setValue("{0}-{1}".format(
int(asset_data["frameStart"]),
int(asset_data["frameEnd"]))
)


def get_handles(asset):
""" Gets handles data

Arguments:
asset (dict): avalon asset entity

Returns:
handles (int)
"""
data = asset["data"]
if "handles" in data and data["handles"] is not None:
return int(data["handles"])

parent_asset = None
if "visualParent" in data:
vp = data["visualParent"]
if vp is not None:
parent_asset = io.find_one({"_id": io.ObjectId(vp)})

if parent_asset is None:
parent_asset = io.find_one({"_id": io.ObjectId(asset["parent"])})

if parent_asset is not None:
return get_handles(parent_asset)
else:
return 0


def reset_resolution():
"""Set resolution to project resolution."""
project = io.find_one({"type": "project"})
p_data = project["data"]

width = p_data.get("resolution_width",
p_data.get("resolutionWidth"))
height = p_data.get("resolution_height",
p_data.get("resolutionHeight"))

if not all([width, height]):
missing = ", ".join(["width", "height"])
msg = "No resolution information `{0}` found for '{1}'.".format(
missing,
project["name"])
log.warning(msg)
nuke.message(msg)
return

current_width = nuke.root()["format"].value().width()
current_height = nuke.root()["format"].value().height()

if width != current_width or height != current_height:

fmt = None
for f in nuke.formats():
if f.width() == width and f.height() == height:
fmt = f.name()

if not fmt:
nuke.addFormat(
"{0} {1} {2}".format(int(width), int(height), project["name"])
)
fmt = project["name"]

nuke.root()["format"].setValue(fmt)


@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()
Loading