Skip to content

feat: Backlog/framework loader nuke #557

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: backlog/framework-loader
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
13 changes: 13 additions & 0 deletions projects/framework-nuke/extensions/nuke.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,17 @@ tools:
options:
tool_configs:
- nuke-setup-scene
- name: load
action: true
menu: false # True by default
label: "Loader"
dialog_name: framework_standard_loader_dialog
icon: open
options:
tool_configs:
- nuke-image-loader
- nuke-sequence-loader
- nuke-movie-loader
docked: false


52 changes: 52 additions & 0 deletions projects/framework-nuke/extensions/plugins/nuke_image_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# :coding: utf-8
# :copyright: Copyright (c) 2024 ftrack
import os

import nuke

from ftrack_utils.paths import check_image_sequence
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use clique instead

from ftrack_framework_core.plugin import BasePlugin
from ftrack_framework_core.exceptions.plugin import PluginExecutionError


class NukeImageLoaderPlugin(BasePlugin):
'''Load an image or sequence into Nuke'''

name = 'nuke_image_loader'

def run(self, store):
'''
Expects the image to load in the :obj:`self.options`, loads the image
'''
image_path = store.get('component_path')
if not image_path:
raise PluginExecutionError(f'No image path provided in store!')

n = nuke.nodes.Read()

sequence_metadata = None
if store.get('is_sequence'):
# Expect path to be on the form folder/plate.%d.exr [1-35], convert to Nuke loadable
# format
sequence_metadata = check_image_sequence(image_path)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use clique instead

image_path = image_path[: image_path.rfind(' ')].replace(
'%d', '%0{}d'.format(sequence_metadata['padding'])
)
else:
# Check that file exists
if not os.path.exists(image_path):
raise PluginExecutionError(
f'Image file does not exist: {image_path}'
)

n['file'].fromUserText(image_path)

self.logger.debug(f'Created image read node, reading: {image_path}')

if store.get('is_sequence'):
n['first'].setValue(sequence_metadata['start'])
n['last'].setValue(sequence_metadata['end'])
self.logger.debug(
'Image sequence frame range set: '
f'{sequence_metadata["start"]}-{sequence_metadata["end"]}'
)
33 changes: 33 additions & 0 deletions projects/framework-nuke/extensions/plugins/nuke_movie_loader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# :coding: utf-8
# :copyright: Copyright (c) 2024 ftrack
import os

import nuke

from ftrack_framework_core.plugin import BasePlugin
from ftrack_framework_core.exceptions.plugin import PluginExecutionError


class NukeMovieLoaderPlugin(BasePlugin):
'''Load a movie into Nuke'''

name = 'nuke_movie_loader'

def run(self, store):
'''
Expects the movie to load in the :obj:`self.options`, loads the movie
'''
movie_path = store.get('component_path')
if not movie_path:
raise PluginExecutionError(f'No movie path provided in store!')

# Check that file exists
if not os.path.exists(movie_path):
raise PluginExecutionError(
f'Image file does not exist: {movie_path}'
)

n = nuke.nodes.Read()
n['file'].fromUserText(movie_path)

self.logger.debug(f'Created movie read node, reading: {movie_path}')
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
type: tool_config
name: nuke-image-loader
config_type: loader
compatible:
entity_types:
- FileComponent
supported_file_extensions:
- ".png"
- ".jpg"
- ".jpeg"
- ".exr"
- ".tif"
- ".tiff"
- ".tga"
- ".bmp"
- ".hdr"
- ".dpx"
- ".cin"
- ".psd"
- ".tx"

engine:
- type: plugin
tags:
- context
plugin: resolve_entity_path
ui: show_component

- type: plugin
tags:
- loader
plugin: nuke_image_loader

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type: tool_config
name: nuke-movie-loader
config_type: loader
compatible:
entity_types:
- FileComponent
supported_file_extensions:
- ".mov"
- ".mp4"
- ".avi"
- ".mpg"
- ".mpeg"
- ".m4v"
- ".mkv"
- ".webm"
- ".wmv"
- ".flv"
- ".vob"
- ".ogv"

engine:
- type: plugin
tags:
- context
plugin: resolve_entity_path
ui: show_component

- type: plugin
tags:
- loader
plugin: nuke_movie_loader

Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
type: tool_config
name: nuke-sequence-loader
config_type: loader
compatible:
entity_types:
- SequenceComponent
supported_file_extensions:
- ".png"
- ".jpg"
- ".jpeg"
- ".exr"
- ".tif"
- ".tiff"
- ".tga"
- ".bmp"
- ".hdr"
- ".dpx"
- ".cin"
- ".psd"
- ".tx"
engine:
- type: plugin
tags:
- context
plugin: resolve_entity_path
ui: show_component

- type: plugin
tags:
- loader
plugin: nuke_image_loader

1 change: 1 addition & 0 deletions projects/framework-nuke/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

## upcoming

* [new] Studio asset load capability, covering single file images, movies and image sequences.
* [changed] Host, Client instance; Pass run_in_main_thread argument.
* [fix] Init; Fix on_run_tool_callback options argument.

Expand Down
1 change: 1 addition & 0 deletions projects/framework-nuke/resource/bootstrap/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

def deferred_execution():
ftrack_framework_nuke.execute_startup_tools()
ftrack_framework_nuke.subscribe_action_tools()


nuke.addOnCreate(deferred_execution, nodeClass='Root')
76 changes: 55 additions & 21 deletions projects/framework-nuke/source/ftrack_framework_nuke/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
from functools import partial
import platform

try:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment from Mattias:
mlagergren 2 weeks ago
Why this change?

from PySide6 import QtWidgets, QtCore
except ImportError:
from PySide2 import QtWidgets, QtCore

import nuke, nukescripts

import ftrack_api
Expand All @@ -25,6 +30,7 @@
from ftrack_utils.usage import set_usage_tracker, UsageTracker

from ftrack_framework_nuke.utils import (
get_nuke_session_identifier,
dock_nuke_right,
find_nodegraph_viewer,
run_in_main_thread,
Expand Down Expand Up @@ -70,6 +76,7 @@ def get_ftrack_menu(menu_name='ftrack', submenu_name=None):

client_instance = None
startup_tools = []
action_tools = []


@run_in_main_thread
Expand All @@ -78,13 +85,31 @@ def on_run_tool_callback(tool_name, dialog_name=None, options=None):
tool_name,
dialog_name,
options,
dock_func=partial(dock_nuke_right) if dialog_name else None,
dock_func=dock_nuke_right if dialog_name else None,
)
# Prevent bug in Nuke were curve editor is activated on docking a panel
if options.get("docked"):
find_nodegraph_viewer(activate=True)


@run_in_main_thread
def on_subscribe_action_tool_callback(
tool_name, label, dialog_name=None, options=None
):
client_instance.subscribe_action_tool(
tool_name,
label,
dialog_name,
options,
session_identifier_func=get_nuke_session_identifier,
)


def on_exit():
'''Nuke shutdown, tear down client'''
client_instance.close()


def bootstrap_integration(framework_extensions_path):
global client_instance

Expand Down Expand Up @@ -171,8 +196,10 @@ def bootstrap_integration(framework_extensions_path):

for tool in dcc_config['tools']:
run_on = tool.get("run_on")
action = tool.get("action")
on_menu = tool.get("menu", True)
name = tool['name']
label = tool.get('label') or tool.get('name')
name = tool.get('name')
dialog_name = tool.get('dialog_name')
options = tool.get('options', {})
# TODO: In the future, we should probably emit an event so plugins can
Expand All @@ -185,32 +212,39 @@ def bootstrap_integration(framework_extensions_path):
tool['label'],
f'{__name__}.onRunToolCallback("{name}","{dialog_name}", {options})',
)

if run_on:
if run_on == "startup":
# Add all tools on a global variable as they can't be executed until
# root node is created.
startup_tools.append(
[
name,
dialog_name,
options,
]
)
else:
logger.error(
f"Unsupported run_on value: {run_on} tool section of the "
f"tool {tool.get('name')} on the tool config file: "
f"{dcc_config['name']}. \n Currently supported values:"
f" [startup]"
)
if run_on == "startup":
startup_tools.append(
[
name,
dialog_name,
options,
]
)
if action:
action_tools.append(
[
name,
label,
dialog_name,
options,
]
)

# Add shutdown hook, for client to be properly closed when Nuke exists
app = QtWidgets.QApplication.instance()
app.aboutToQuit.connect(on_exit)


def execute_startup_tools():
for tool in startup_tools:
on_run_tool_callback(*tool)


def subscribe_action_tools():
for tool in action_tools:
on_subscribe_action_tool_callback(*tool)


# Find and read DCC config
try:
bootstrap_integration(get_extensions_path_from_environment())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# :copyright: Copyright (c) 2024 ftrack
from functools import wraps
import threading
import socket

try:
from PySide6 import QtWidgets
Expand All @@ -12,6 +13,14 @@
from nukescripts import panels


def get_nuke_session_identifier():
computer_name = socket.gethostname()
# Get the Maya scene name
script_name = nuke.root().name().split('/')[-1]
identifier = f"{script_name}_Nuke_{computer_name}"
return identifier


def dock_nuke_right(widget):
'''Dock *widget*, to the right of the properties panel in Nuke'''
class_name = widget.__class__.__name__
Expand Down
Loading