Merge branch 'develop' into bugfix/nuke-intermediate-steams-bugs

This commit is contained in:
Jakub Jezek 2024-05-28 12:40:30 +02:00
commit 633aa0ca7d
No known key found for this signature in database
GPG key ID: 06DBD609ADF27FD9
302 changed files with 1476 additions and 336 deletions

View file

@ -51,8 +51,13 @@ IGNORED_MODULES_IN_AYON = set()
# - this is used to log the missing addon
MOVED_ADDON_MILESTONE_VERSIONS = {
"applications": VersionInfo(0, 2, 0),
"clockify": VersionInfo(0, 2, 0),
"traypublisher": VersionInfo(0, 2, 0),
"tvpaint": VersionInfo(0, 2, 0),
"nuke": VersionInfo(0, 2, 0),
}
# Inherit from `object` for Python 2 hosts
class _ModuleClass(object):
"""Fake module class for storing AYON addons.

View file

@ -365,3 +365,62 @@ def maintained_time():
yield
finally:
bpy.context.scene.frame_current = current_time
def get_all_parents(obj):
"""Get all recursive parents of object.
Arguments:
obj (bpy.types.Object): Object to get all parents for.
Returns:
List[bpy.types.Object]: All parents of object
"""
result = []
while True:
obj = obj.parent
if not obj:
break
result.append(obj)
return result
def get_highest_root(objects):
"""Get the highest object (the least parents) among the objects.
If multiple objects have the same amount of parents (or no parents) the
first object found in the input iterable will be returned.
Note that this will *not* return objects outside of the input list, as
such it will not return the root of node from a child node. It is purely
intended to find the highest object among a list of objects. To instead
get the root from one object use, e.g. `get_all_parents(obj)[-1]`
Arguments:
objects (List[bpy.types.Object]): Objects to find the highest root in.
Returns:
Optional[bpy.types.Object]: First highest root found or None if no
`bpy.types.Object` found in input list.
"""
included_objects = {obj.name_full for obj in objects}
num_parents_to_obj = {}
for obj in objects:
if isinstance(obj, bpy.types.Object):
parents = get_all_parents(obj)
# included parents
parents = [parent for parent in parents if
parent.name_full in included_objects]
if not parents:
# A node without parents must be a highest root
return obj
num_parents_to_obj.setdefault(len(parents), obj)
if not num_parents_to_obj:
return
minimum_parent = min(num_parents_to_obj)
return num_parents_to_obj[minimum_parent]

View file

@ -26,7 +26,8 @@ from .ops import (
)
from .lib import imprint
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx",
".usd", ".usdc", ".usda"]
def prepare_scene_name(

View file

@ -0,0 +1,30 @@
"""Create a USD Export."""
from ayon_core.hosts.blender.api import plugin, lib
class CreateUSD(plugin.BaseCreator):
"""Create USD Export"""
identifier = "io.openpype.creators.blender.usd"
name = "usdMain"
label = "USD"
product_type = "usd"
icon = "gears"
def create(
self, product_name: str, instance_data: dict, pre_create_data: dict
):
# Run parent create method
collection = super().create(
product_name, instance_data, pre_create_data
)
if pre_create_data.get("use_selection"):
objects = lib.get_selection()
for obj in objects:
collection.objects.link(obj)
if obj.type == 'EMPTY':
objects.extend(obj.children)
return collection

View file

@ -26,10 +26,10 @@ class CacheModelLoader(plugin.AssetLoader):
Note:
At least for now it only supports Alembic files.
"""
product_types = {"model", "pointcache", "animation"}
representations = {"abc"}
product_types = {"model", "pointcache", "animation", "usd"}
representations = {"abc", "usd"}
label = "Load Alembic"
label = "Load Cache"
icon = "code-fork"
color = "orange"
@ -53,10 +53,21 @@ class CacheModelLoader(plugin.AssetLoader):
plugin.deselect_all()
relative = bpy.context.preferences.filepaths.use_relative_paths
bpy.ops.wm.alembic_import(
filepath=libpath,
relative_path=relative
)
if any(libpath.lower().endswith(ext)
for ext in [".usd", ".usda", ".usdc"]):
# USD
bpy.ops.wm.usd_import(
filepath=libpath,
relative_path=relative
)
else:
# Alembic
bpy.ops.wm.alembic_import(
filepath=libpath,
relative_path=relative
)
imported = lib.get_selection()

View file

@ -12,7 +12,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder
hosts = ["blender"]
families = ["model", "pointcache", "animation", "rig", "camera", "layout",
"blendScene"]
"blendScene", "usd"]
label = "Collect Instance"
def process(self, instance):

View file

@ -0,0 +1,90 @@
import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin, lib
class ExtractUSD(publish.Extractor):
"""Extract as USD."""
label = "Extract USD"
hosts = ["blender"]
families = ["usd"]
def process(self, instance):
# Ignore runtime instances (e.g. USD layers)
# TODO: This is better done via more specific `families`
if not instance.data.get("transientData", {}).get("instance_node"):
return
# Define extract output file path
stagingdir = self.staging_dir(instance)
filename = f"{instance.name}.usd"
filepath = os.path.join(stagingdir, filename)
# Perform extraction
self.log.debug("Performing extraction..")
# Select all members to "export selected"
plugin.deselect_all()
selected = []
for obj in instance:
if isinstance(obj, bpy.types.Object):
obj.select_set(True)
selected.append(obj)
root = lib.get_highest_root(objects=instance[:])
if not root:
instance_node = instance.data["transientData"]["instance_node"]
raise publish.KnownPublishError(
f"No root object found in instance: {instance_node.name}"
)
self.log.debug(f"Exporting using active root: {root.name}")
context = plugin.create_blender_context(
active=root, selected=selected)
# Export USD
with bpy.context.temp_override(**context):
bpy.ops.wm.usd_export(
filepath=filepath,
selected_objects_only=True,
export_textures=False,
relative_paths=False,
export_animation=False,
export_hair=False,
export_uvmaps=True,
# TODO: add for new version of Blender (4+?)
# export_mesh_colors=True,
export_normals=True,
export_materials=True,
use_instancing=True
)
plugin.deselect_all()
# Add representation
representation = {
'name': 'usd',
'ext': 'usd',
'files': filename,
"stagingDir": stagingdir,
}
instance.data.setdefault("representations", []).append(representation)
self.log.debug("Extracted instance '%s' to: %s",
instance.name, representation)
class ExtractModelUSD(ExtractUSD):
"""Extract model as USD."""
label = "Extract USD (Model)"
hosts = ["blender"]
families = ["model"]
# Driven by settings
optional = True

View file

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating Model product type.
Note:
Currently, This creator plugin is the same as 'create_pointcache.py'
But renaming the product type to 'model'.
It's purpose to support
Maya (load/publish model from maya to/from houdini).
It's considered to support multiple representations in the future.
"""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.lib import BoolDef
import hou
class CreateModel(plugin.HoudiniCreator):
"""Create Model"""
identifier = "io.openpype.creators.houdini.model"
label = "Model"
product_type = "model"
icon = "cube"
def create(self, product_name, instance_data, pre_create_data):
instance_data.pop("active", None)
instance_data.update({"node_type": "alembic"})
creator_attributes = instance_data.setdefault(
"creator_attributes", dict())
creator_attributes["farm"] = pre_create_data["farm"]
instance = super(CreateModel, self).create(
product_name,
instance_data,
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
parms = {
"use_sop_path": True,
"build_from_path": True,
"path_attrib": "path",
"prim_to_detail_pattern": "cbId",
"format": 2,
"facesets": 0,
"filename": hou.text.expandString(
"$HIP/pyblish/{}.abc".format(product_name))
}
if self.selected_nodes:
selected_node = self.selected_nodes[0]
# Although Houdini allows ObjNode path on `sop_path` for the
# the ROP node we prefer it set to the SopNode path explicitly
# Allow sop level paths (e.g. /obj/geo1/box1)
if isinstance(selected_node, hou.SopNode):
parms["sop_path"] = selected_node.path()
self.log.debug(
"Valid SopNode selection, 'SOP Path' in ROP will be set to '%s'."
% selected_node.path()
)
# Allow object level paths to Geometry nodes (e.g. /obj/geo1)
# but do not allow other object level nodes types like cameras, etc.
elif isinstance(selected_node, hou.ObjNode) and \
selected_node.type().name() in ["geo"]:
# get the output node with the minimum
# 'outputidx' or the node with display flag
sop_path = self.get_obj_output(selected_node)
if sop_path:
parms["sop_path"] = sop_path.path()
self.log.debug(
"Valid ObjNode selection, 'SOP Path' in ROP will be set to "
"the child path '%s'."
% sop_path.path()
)
if not parms.get("sop_path", None):
self.log.debug(
"Selection isn't valid. 'SOP Path' in ROP will be empty."
)
else:
self.log.debug(
"No Selection. 'SOP Path' in ROP will be empty."
)
instance_node.setParms(parms)
instance_node.parm("trange").set(1)
# Explicitly set f1 and f2 to frame start.
# Which forces the rop node to export one frame.
instance_node.parmTuple('f').deleteAllKeyframes()
fstart = int(hou.hscriptExpression("$FSTART"))
instance_node.parmTuple('f').set((fstart, fstart, 1))
# Lock any parameters in this list
to_lock = ["prim_to_detail_pattern"]
self.lock_parameters(instance_node, to_lock)
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
]
def get_obj_output(self, obj_node):
"""Find output node with the smallest 'outputidx'."""
outputs = obj_node.subnetOutputs()
# if obj_node is empty
if not outputs:
return
# if obj_node has one output child whether its
# sop output node or a node with the render flag
elif len(outputs) == 1:
return outputs[0]
# if there are more than one, then it have multiple output nodes
# return the one with the minimum 'outputidx'
else:
return min(outputs,
key=lambda node: node.evalParm('outputidx'))
def get_instance_attr_defs(self):
return [
BoolDef("farm",
label="Submitting to Farm",
default=False)
]
def get_pre_create_attr_defs(self):
attrs = super().get_pre_create_attr_defs()
# Use same attributes as for instance attributes
return attrs + self.get_instance_attr_defs()

View file

@ -11,7 +11,7 @@ class CollectDataforCache(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder + 0.11
families = ["ass", "pointcache",
"mantraifd", "redshiftproxy",
"vdbcache"]
"vdbcache", "model"]
hosts = ["houdini"]
targets = ["local", "remote"]
label = "Collect Data for Cache"
@ -43,10 +43,7 @@ class CollectDataforCache(pyblish.api.InstancePlugin):
cache_files = {"_": instance.data["files"]}
# Convert instance family to pointcache if it is bgeo or abc
# because ???
for family in instance.data["families"]:
if family == "bgeo" or "abc":
instance.data["productType"] = "pointcache"
break
self.log.debug(instance.data["families"])
instance.data.update({
"plugin": "Houdini",
"publish": True

View file

@ -10,7 +10,7 @@ class CollectChunkSize(pyblish.api.InstancePlugin,
order = pyblish.api.CollectorOrder + 0.05
families = ["ass", "pointcache",
"vdbcache", "mantraifd",
"redshiftproxy"]
"redshiftproxy", "model"]
hosts = ["houdini"]
targets = ["local", "remote"]
label = "Collect Chunk Size"

View file

@ -1,21 +1,24 @@
"""Collector for pointcache types.
"""Collector for different types.
This will add additional family to pointcache instance based on
This will add additional families to different instance based on
the creator_identifier parameter.
"""
import pyblish.api
class CollectPointcacheType(pyblish.api.InstancePlugin):
"""Collect data type for pointcache instance."""
"""Collect data type for different instances."""
order = pyblish.api.CollectorOrder
hosts = ["houdini"]
families = ["pointcache"]
label = "Collect type of pointcache"
families = ["pointcache", "model"]
label = "Collect instances types"
def process(self, instance):
if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501
instance.data["families"] += ["bgeo"]
elif instance.data["creator_identifier"] == "io.openpype.creators.houdini.pointcache": # noqa: E501
elif instance.data["creator_identifier"] in {
"io.openpype.creators.houdini.pointcache",
"io.openpype.creators.houdini.model"
}:
instance.data["families"] += ["abc"]

View file

@ -132,6 +132,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin):
]
})
# Remove original render instance
# I can't remove it here as I still need it to trigger the render.
# context.remove(instance)
# Skip integrating original render instance.
# We are not removing it because it's used to trigger the render.
instance.data["integrate"] = False

View file

@ -15,7 +15,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"usd",
"usdrender",
"redshiftproxy",
"staticMesh"
"staticMesh",
"model"
]
hosts = ["houdini"]

View file

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""Validator for checking that export is a single frame."""
import pyblish.api
from ayon_core.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from ayon_core.pipeline.publish import ValidateContentsOrder
from ayon_core.hosts.houdini.api.action import SelectInvalidAction
class ValidateSingleFrame(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validate Export is a Single Frame.
It checks if rop node is exporting one frame.
This is mainly for Model product type.
"""
families = ["model"]
hosts = ["houdini"]
label = "Validate Single Frame"
order = ValidateContentsOrder + 0.1
actions = [SelectInvalidAction]
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
nodes = [n.path() for n in invalid]
raise PublishValidationError(
"See log for details. "
"Invalid nodes: {0}".format(nodes)
)
@classmethod
def get_invalid(cls, instance):
invalid = []
frame_start = instance.data.get("frameStartHandle")
frame_end = instance.data.get("frameEndHandle")
# This happens if instance node has no 'trange' parameter.
if frame_start is None or frame_end is None:
cls.log.debug(
"No frame data, skipping check.."
)
return
if frame_start != frame_end:
invalid.append(instance.data["instance_node"])
cls.log.error(
"Invalid frame range on '%s'."
"You should use the same frame number for 'f1' "
"and 'f2' parameters.",
instance.data["instance_node"].path()
)
return invalid

View file

@ -16,9 +16,13 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin,
"""Validate mesh is static.
It checks if output node is time dependent.
this avoids getting different output from ROP node when extracted
from a different frame than the first frame.
(Might be overly restrictive though)
"""
families = ["staticMesh"]
families = ["staticMesh",
"model"]
hosts = ["houdini"]
label = "Validate Mesh is Static"
order = ValidateContentsOrder + 0.1

View file

@ -7,7 +7,7 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin):
"""Validate Create Intermediate Directories is enabled on ROP node."""
order = pyblish.api.ValidatorOrder
families = ["pointcache", "camera", "vdbcache"]
families = ["pointcache", "camera", "vdbcache", "model"]
hosts = ["houdini"]
label = "Create Intermediate Directories Checked"

View file

@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["pointcache", "vdbcache"]
families = ["pointcache", "vdbcache", "model"]
hosts = ["houdini"]
label = "Validate Output Node (SOP)"
actions = [SelectROPAction, SelectInvalidAction]

View file

@ -1,5 +0,0 @@
from .clockify_module import ClockifyModule
__all__ = (
"ClockifyModule",
)

View file

@ -26,27 +26,32 @@ class CollectDeadlinePools(pyblish.api.InstancePlugin,
order = pyblish.api.CollectorOrder + 0.420
label = "Collect Deadline Pools"
hosts = ["aftereffects",
"fusion",
"harmony"
"nuke",
"maya",
"max",
"houdini"]
hosts = [
"aftereffects",
"fusion",
"harmony",
"maya",
"max",
"houdini",
"nuke",
]
families = ["render",
"rendering",
"render.farm",
"renderFarm",
"renderlayer",
"maxrender",
"usdrender",
"redshift_rop",
"arnold_rop",
"mantra_rop",
"karma_rop",
"vray_rop",
"publish.hou"]
families = [
"render",
"prerender",
"rendering",
"render.farm",
"renderFarm",
"renderlayer",
"maxrender",
"usdrender",
"redshift_rop",
"arnold_rop",
"mantra_rop",
"karma_rop",
"vray_rop",
"publish.hou",
]
primary_pool = None
secondary_pool = None

View file

@ -1,8 +1,10 @@
import pyblish.api
from ayon_core.pipeline.publish import PublishValidationError
from ayon_core.pipeline.publish import (
PublishValidationError, OptionalPyblishPluginMixin
)
class ValidateVersion(pyblish.api.InstancePlugin):
class ValidateVersion(pyblish.api.InstancePlugin, OptionalPyblishPluginMixin):
"""Validate instance version.
AYON does not allow overwriting previously published versions.
@ -18,6 +20,9 @@ class ValidateVersion(pyblish.api.InstancePlugin):
active = True
def process(self, instance):
if not self.is_active(instance.data):
return
version = instance.data.get("version")
latest_version = instance.data.get("latestVersion")

View file

@ -5,7 +5,7 @@ import ayon_api
import six
from ayon_core.style import get_default_entity_icon_color
from ayon_core.lib import CacheItem
from ayon_core.lib import CacheItem, NestedCacheItem
PROJECTS_MODEL_SENDER = "projects.model"
@ -17,6 +17,49 @@ class AbstractHierarchyController:
pass
class StatusItem:
"""Item representing status of project.
Args:
name (str): Status name ("Not ready").
color (str): Status color in hex ("#434a56").
short (str): Short status name ("NRD").
icon (str): Icon name in MaterialIcons ("fiber_new").
state (Literal["not_started", "in_progress", "done", "blocked"]):
Status state.
"""
def __init__(self, name, color, short, icon, state):
self.name = name
self.color = color
self.short = short
self.icon = icon
self.state = state
def to_data(self):
return {
"name": self.name,
"color": self.color,
"short": self.short,
"icon": self.icon,
"state": self.state,
}
@classmethod
def from_data(cls, data):
return cls(**data)
@classmethod
def from_project_item(cls, status_data):
return cls(
name=status_data["name"],
color=status_data["color"],
short=status_data["shortName"],
icon=status_data["icon"],
state=status_data["state"],
)
class ProjectItem:
"""Item representing folder entity on a server.
@ -40,6 +83,23 @@ class ProjectItem:
}
self.icon = icon
@classmethod
def from_entity(cls, project_entity):
"""Creates folder item from entity.
Args:
project_entity (dict[str, Any]): Project entity.
Returns:
ProjectItem: Project item.
"""
return cls(
project_entity["name"],
project_entity["active"],
project_entity["library"],
)
def to_data(self):
"""Converts folder item to data.
@ -79,7 +139,7 @@ def _get_project_items_from_entitiy(projects):
"""
return [
ProjectItem(project["name"], project["active"], project["library"])
ProjectItem.from_entity(project)
for project in projects
]
@ -87,18 +147,29 @@ def _get_project_items_from_entitiy(projects):
class ProjectsModel(object):
def __init__(self, controller):
self._projects_cache = CacheItem(default_factory=list)
self._project_items_by_name = {}
self._projects_by_name = {}
self._project_statuses_cache = NestedCacheItem(
levels=1, default_factory=list
)
self._projects_by_name = NestedCacheItem(
levels=1, default_factory=list
)
self._is_refreshing = False
self._controller = controller
def reset(self):
self._projects_cache.reset()
self._project_items_by_name = {}
self._projects_by_name = {}
self._project_statuses_cache.reset()
self._projects_by_name.reset()
def refresh(self):
"""Refresh project items.
This method will requery list of ProjectItem returned by
'get_project_items'.
To reset all cached items use 'reset' method.
"""
self._refresh_projects_cache()
def get_project_items(self, sender):
@ -117,12 +188,51 @@ class ProjectsModel(object):
return self._projects_cache.get_data()
def get_project_entity(self, project_name):
if project_name not in self._projects_by_name:
"""Get project entity.
Args:
project_name (str): Project name.
Returns:
Union[dict[str, Any], None]: Project entity or None if project
was not found by name.
"""
project_cache = self._projects_by_name[project_name]
if not project_cache.is_valid:
entity = None
if project_name:
entity = ayon_api.get_project(project_name)
self._projects_by_name[project_name] = entity
return self._projects_by_name[project_name]
project_cache.update_data(entity)
return project_cache.get_data()
def get_project_status_items(self, project_name, sender):
"""Get project status items.
Args:
project_name (str): Project name.
sender (Union[str, None]): Name of sender who asked for items.
Returns:
list[StatusItem]: Status items for project.
"""
statuses_cache = self._project_statuses_cache[project_name]
if not statuses_cache.is_valid:
with self._project_statuses_refresh_event_manager(
sender, project_name
):
project_entity = None
if project_name:
project_entity = self.get_project_entity(project_name)
statuses = []
if project_entity:
statuses = [
StatusItem.from_project_item(status)
for status in project_entity["statuses"]
]
statuses_cache.update_data(statuses)
return statuses_cache.get_data()
@contextlib.contextmanager
def _project_refresh_event_manager(self, sender):
@ -143,6 +253,23 @@ class ProjectsModel(object):
)
self._is_refreshing = False
@contextlib.contextmanager
def _project_statuses_refresh_event_manager(self, sender, project_name):
self._controller.emit_event(
"projects.statuses.refresh.started",
{"sender": sender, "project_name": project_name},
PROJECTS_MODEL_SENDER
)
try:
yield
finally:
self._controller.emit_event(
"projects.statuses.refresh.finished",
{"sender": sender, "project_name": project_name},
PROJECTS_MODEL_SENDER
)
def _refresh_projects_cache(self, sender=None):
if self._is_refreshing:
return None

View file

@ -290,6 +290,34 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate):
painter.drawPixmap(extender_x, extender_y, pix)
class ActionsProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
def lessThan(self, left, right):
# Sort by action order and then by label
left_value = left.data(ACTION_SORT_ROLE)
right_value = right.data(ACTION_SORT_ROLE)
# Values are same -> use super sorting
if left_value == right_value:
# Default behavior is using DisplayRole
return super().lessThan(left, right)
# Validate 'None' values
if right_value is None:
return True
if left_value is None:
return False
# Sort values and handle incompatible types
try:
return left_value < right_value
except TypeError:
return True
class ActionsWidget(QtWidgets.QWidget):
def __init__(self, controller, parent):
super(ActionsWidget, self).__init__(parent)
@ -316,10 +344,7 @@ class ActionsWidget(QtWidgets.QWidget):
model = ActionsQtModel(controller)
proxy_model = QtCore.QSortFilterProxyModel()
proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxy_model.setSortRole(ACTION_SORT_ROLE)
proxy_model = ActionsProxyModel()
proxy_model.setSourceModel(model)
view.setModel(proxy_model)
@ -359,7 +384,8 @@ class ActionsWidget(QtWidgets.QWidget):
def _on_model_refresh(self):
self._proxy_model.sort(0)
# Force repaint all items
self._view.update()
viewport = self._view.viewport()
viewport.update()
def _on_animation(self):
time_now = time.time()

View file

@ -114,6 +114,7 @@ class VersionItem:
thumbnail_id (Union[str, None]): Thumbnail id.
published_time (Union[str, None]): Published time in format
'%Y%m%dT%H%M%SZ'.
status (Union[str, None]): Status name.
author (Union[str, None]): Author.
frame_range (Union[str, None]): Frame range.
duration (Union[int, None]): Duration.
@ -132,6 +133,7 @@ class VersionItem:
thumbnail_id,
published_time,
author,
status,
frame_range,
duration,
handles,
@ -146,6 +148,7 @@ class VersionItem:
self.is_hero = is_hero
self.published_time = published_time
self.author = author
self.status = status
self.frame_range = frame_range
self.duration = duration
self.handles = handles
@ -185,6 +188,7 @@ class VersionItem:
"is_hero": self.is_hero,
"published_time": self.published_time,
"author": self.author,
"status": self.status,
"frame_range": self.frame_range,
"duration": self.duration,
"handles": self.handles,
@ -488,6 +492,27 @@ class FrontendLoaderController(_BaseLoaderController):
pass
@abstractmethod
def get_project_status_items(self, project_name, sender=None):
"""Items for all projects available on server.
Triggers event topics "projects.statuses.refresh.started" and
"projects.statuses.refresh.finished" with data:
{
"sender": sender,
"project_name": project_name
}
Args:
project_name (Union[str, None]): Project name.
sender (Optional[str]): Sender who requested the items.
Returns:
list[StatusItem]: List of status items.
"""
pass
@abstractmethod
def get_product_items(self, project_name, folder_ids, sender=None):
"""Product items for folder ids.

View file

@ -180,6 +180,11 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
def get_project_items(self, sender=None):
return self._projects_model.get_project_items(sender)
def get_project_status_items(self, project_name, sender=None):
return self._projects_model.get_project_status_items(
project_name, sender
)
def get_folder_items(self, project_name, sender=None):
return self._hierarchy_model.get_folder_items(project_name, sender)

View file

@ -58,6 +58,7 @@ def version_item_from_entity(version):
thumbnail_id=version["thumbnailId"],
published_time=published_time,
author=author,
status=version["status"],
frame_range=frame_range,
duration=duration,
handles=handles,
@ -526,8 +527,11 @@ class ProductsModel:
products = list(ayon_api.get_products(project_name, **kwargs))
product_ids = {product["id"] for product in products}
# Add 'status' to fields -> fixed in ayon-python-api 1.0.4
fields = ayon_api.get_default_fields_for_type("version")
fields.add("status")
versions = ayon_api.get_versions(
project_name, product_ids=product_ids
project_name, product_ids=product_ids, fields=fields
)
return self._create_product_items(

View file

@ -6,6 +6,9 @@ from ayon_core.tools.utils.lib import format_version
from .products_model import (
PRODUCT_ID_ROLE,
VERSION_NAME_EDIT_ROLE,
VERSION_STATUS_NAME_ROLE,
VERSION_STATUS_SHORT_ROLE,
VERSION_STATUS_COLOR_ROLE,
VERSION_ID_ROLE,
PRODUCT_IN_SCENE_ROLE,
ACTIVE_SITE_ICON_ROLE,
@ -104,7 +107,10 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
style = QtWidgets.QApplication.style()
style.drawControl(
style.CE_ItemViewItem, option, painter, option.widget
QtWidgets.QCommonStyle.CE_ItemViewItem,
option,
painter,
option.widget
)
painter.save()
@ -116,9 +122,14 @@ class VersionDelegate(QtWidgets.QStyledItemDelegate):
pen.setColor(fg_color)
painter.setPen(pen)
text_rect = style.subElementRect(style.SE_ItemViewItemText, option)
text_rect = style.subElementRect(
QtWidgets.QCommonStyle.SE_ItemViewItemText,
option
)
text_margin = style.proxy().pixelMetric(
style.PM_FocusFrameHMargin, option, option.widget
QtWidgets.QCommonStyle.PM_FocusFrameHMargin,
option,
option.widget
) + 1
painter.drawText(
@ -194,6 +205,57 @@ class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate):
option.palette.setBrush(QtGui.QPalette.Text, color)
class StatusDelegate(QtWidgets.QStyledItemDelegate):
"""Delegate showing status name and short name."""
def paint(self, painter, option, index):
if option.widget:
style = option.widget.style()
else:
style = QtWidgets.QApplication.style()
style.drawControl(
QtWidgets.QCommonStyle.CE_ItemViewItem,
option,
painter,
option.widget
)
painter.save()
text_rect = style.subElementRect(
QtWidgets.QCommonStyle.SE_ItemViewItemText,
option
)
text_margin = style.proxy().pixelMetric(
QtWidgets.QCommonStyle.PM_FocusFrameHMargin,
option,
option.widget
) + 1
padded_text_rect = text_rect.adjusted(
text_margin, 0, - text_margin, 0
)
fm = QtGui.QFontMetrics(option.font)
text = index.data(VERSION_STATUS_NAME_ROLE)
if padded_text_rect.width() < fm.width(text):
text = index.data(VERSION_STATUS_SHORT_ROLE)
status_color = index.data(VERSION_STATUS_COLOR_ROLE)
fg_color = QtGui.QColor(status_color)
pen = painter.pen()
pen.setColor(fg_color)
painter.setPen(pen)
painter.drawText(
padded_text_rect,
option.displayAlignment,
text
)
painter.restore()
class SiteSyncDelegate(QtWidgets.QStyledItemDelegate):
"""Paints icons and downloaded representation ration for both sites."""

View file

@ -22,18 +22,21 @@ VERSION_HERO_ROLE = QtCore.Qt.UserRole + 11
VERSION_NAME_ROLE = QtCore.Qt.UserRole + 12
VERSION_NAME_EDIT_ROLE = QtCore.Qt.UserRole + 13
VERSION_PUBLISH_TIME_ROLE = QtCore.Qt.UserRole + 14
VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 15
VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 16
VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 17
VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 18
VERSION_STEP_ROLE = QtCore.Qt.UserRole + 19
VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 20
VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 21
ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 22
REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 23
REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 24
SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 25
SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 26
VERSION_STATUS_NAME_ROLE = QtCore.Qt.UserRole + 15
VERSION_STATUS_SHORT_ROLE = QtCore.Qt.UserRole + 16
VERSION_STATUS_COLOR_ROLE = QtCore.Qt.UserRole + 17
VERSION_AUTHOR_ROLE = QtCore.Qt.UserRole + 18
VERSION_FRAME_RANGE_ROLE = QtCore.Qt.UserRole + 19
VERSION_DURATION_ROLE = QtCore.Qt.UserRole + 20
VERSION_HANDLES_ROLE = QtCore.Qt.UserRole + 21
VERSION_STEP_ROLE = QtCore.Qt.UserRole + 22
VERSION_AVAILABLE_ROLE = QtCore.Qt.UserRole + 23
VERSION_THUMBNAIL_ID_ROLE = QtCore.Qt.UserRole + 24
ACTIVE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 25
REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 26
REPRESENTATIONS_COUNT_ROLE = QtCore.Qt.UserRole + 27
SYNC_ACTIVE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 28
SYNC_REMOTE_SITE_AVAILABILITY = QtCore.Qt.UserRole + 29
class ProductsModel(QtGui.QStandardItemModel):
@ -44,6 +47,7 @@ class ProductsModel(QtGui.QStandardItemModel):
"Product type",
"Folder",
"Version",
"Status",
"Time",
"Author",
"Frames",
@ -69,11 +73,35 @@ class ProductsModel(QtGui.QStandardItemModel):
]
]
version_col = column_labels.index("Version")
published_time_col = column_labels.index("Time")
product_name_col = column_labels.index("Product name")
product_type_col = column_labels.index("Product type")
folders_label_col = column_labels.index("Folder")
version_col = column_labels.index("Version")
status_col = column_labels.index("Status")
published_time_col = column_labels.index("Time")
author_col = column_labels.index("Author")
frame_range_col = column_labels.index("Frames")
duration_col = column_labels.index("Duration")
handles_col = column_labels.index("Handles")
step_col = column_labels.index("Step")
in_scene_col = column_labels.index("In scene")
sitesync_avail_col = column_labels.index("Availability")
_display_role_mapping = {
product_name_col: QtCore.Qt.DisplayRole,
product_type_col: PRODUCT_TYPE_ROLE,
folders_label_col: FOLDER_LABEL_ROLE,
version_col: VERSION_NAME_ROLE,
status_col: VERSION_STATUS_NAME_ROLE,
published_time_col: VERSION_PUBLISH_TIME_ROLE,
author_col: VERSION_AUTHOR_ROLE,
frame_range_col: VERSION_FRAME_RANGE_ROLE,
duration_col: VERSION_DURATION_ROLE,
handles_col: VERSION_HANDLES_ROLE,
step_col: VERSION_STEP_ROLE,
in_scene_col: PRODUCT_IN_SCENE_ROLE,
sitesync_avail_col: VERSION_AVAILABLE_ROLE,
}
def __init__(self, controller):
super(ProductsModel, self).__init__()
@ -96,6 +124,7 @@ class ProductsModel(QtGui.QStandardItemModel):
self._last_project_name = None
self._last_folder_ids = []
self._last_project_statuses = {}
def get_product_item_indexes(self):
return [
@ -141,6 +170,15 @@ class ProductsModel(QtGui.QStandardItemModel):
if not index.isValid():
return None
if role in (VERSION_STATUS_SHORT_ROLE, VERSION_STATUS_COLOR_ROLE):
status_name = self.data(index, VERSION_STATUS_NAME_ROLE)
status_item = self._last_project_statuses.get(status_name)
if status_item is None:
return ""
if role == VERSION_STATUS_SHORT_ROLE:
return status_item.short
return status_item.color
col = index.column()
if col == 0:
return super(ProductsModel, self).data(index, role)
@ -168,29 +206,8 @@ class ProductsModel(QtGui.QStandardItemModel):
if role == QtCore.Qt.DisplayRole:
if not index.data(PRODUCT_ID_ROLE):
return None
if col == self.version_col:
role = VERSION_NAME_ROLE
elif col == 1:
role = PRODUCT_TYPE_ROLE
elif col == 2:
role = FOLDER_LABEL_ROLE
elif col == 4:
role = VERSION_PUBLISH_TIME_ROLE
elif col == 5:
role = VERSION_AUTHOR_ROLE
elif col == 6:
role = VERSION_FRAME_RANGE_ROLE
elif col == 7:
role = VERSION_DURATION_ROLE
elif col == 8:
role = VERSION_HANDLES_ROLE
elif col == 9:
role = VERSION_STEP_ROLE
elif col == 10:
role = PRODUCT_IN_SCENE_ROLE
elif col == 11:
role = VERSION_AVAILABLE_ROLE
else:
role = self._display_role_mapping.get(col)
if role is None:
return None
index = self.index(index.row(), 0, index.parent())
@ -312,6 +329,7 @@ class ProductsModel(QtGui.QStandardItemModel):
version_item.published_time, VERSION_PUBLISH_TIME_ROLE
)
model_item.setData(version_item.author, VERSION_AUTHOR_ROLE)
model_item.setData(version_item.status, VERSION_STATUS_NAME_ROLE)
model_item.setData(version_item.frame_range, VERSION_FRAME_RANGE_ROLE)
model_item.setData(version_item.duration, VERSION_DURATION_ROLE)
model_item.setData(version_item.handles, VERSION_HANDLES_ROLE)
@ -393,6 +411,11 @@ class ProductsModel(QtGui.QStandardItemModel):
self._last_project_name = project_name
self._last_folder_ids = folder_ids
status_items = self._controller.get_project_status_items(project_name)
self._last_project_statuses = {
status_item.name: status_item
for status_item in status_items
}
active_site_icon_def = self._controller.get_active_site_icon_def(
project_name

View file

@ -22,7 +22,8 @@ from .products_model import (
from .products_delegates import (
VersionDelegate,
LoadedInSceneDelegate,
SiteSyncDelegate
StatusDelegate,
SiteSyncDelegate,
)
from .actions_utils import show_actions_menu
@ -89,6 +90,7 @@ class ProductsWidget(QtWidgets.QWidget):
90, # Product type
130, # Folder label
60, # Version
100, # Status
125, # Time
75, # Author
75, # Frames
@ -128,20 +130,19 @@ class ProductsWidget(QtWidgets.QWidget):
products_view.setColumnWidth(idx, width)
version_delegate = VersionDelegate()
products_view.setItemDelegateForColumn(
products_model.version_col, version_delegate)
time_delegate = PrettyTimeDelegate()
products_view.setItemDelegateForColumn(
products_model.published_time_col, time_delegate)
status_delegate = StatusDelegate()
in_scene_delegate = LoadedInSceneDelegate()
products_view.setItemDelegateForColumn(
products_model.in_scene_col, in_scene_delegate)
sitesync_delegate = SiteSyncDelegate()
products_view.setItemDelegateForColumn(
products_model.sitesync_avail_col, sitesync_delegate)
for col, delegate in (
(products_model.version_col, version_delegate),
(products_model.published_time_col, time_delegate),
(products_model.status_col, status_delegate),
(products_model.in_scene_col, in_scene_delegate),
(products_model.sitesync_avail_col, sitesync_delegate),
):
products_view.setItemDelegateForColumn(col, delegate)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
@ -175,6 +176,7 @@ class ProductsWidget(QtWidgets.QWidget):
self._version_delegate = version_delegate
self._time_delegate = time_delegate
self._status_delegate = status_delegate
self._in_scene_delegate = in_scene_delegate
self._sitesync_delegate = sitesync_delegate

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON core addon version."""
__version__ = "0.3.2-dev.1"
__version__ = "0.3.3-dev.1"

View file

@ -1,6 +1,6 @@
name = "core"
title = "Core"
version = "0.3.2-dev.1"
version = "0.3.3-dev.1"
client_dir = "ayon_core"

View file

@ -1,3 +1,3 @@
name = "blender"
title = "Blender"
version = "0.1.8"
version = "0.1.9"

View file

@ -151,6 +151,10 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=ExtractPlayblastModel,
title="Extract Playblast"
)
ExtractModelUSD: ValidatePluginModel = SettingsField(
default_factory=ValidatePluginModel,
title="Extract Model USD"
)
DEFAULT_BLENDER_PUBLISH_SETTINGS = {
@ -348,5 +352,10 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = {
},
indent=4
)
},
"ExtractModelUSD": {
"enabled": True,
"optional": True,
"active": True
}
}

View file

@ -0,0 +1,5 @@
from .addon import ClockifyAddon
__all__ = (
"ClockifyAddon",
)

View file

@ -2,12 +2,12 @@ import os
import threading
import time
from ayon_core.modules import AYONAddon, ITrayModule, IPluginPaths
from ayon_core.addon import AYONAddon, ITrayAddon, IPluginPaths
from .constants import CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH
class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
class ClockifyAddon(AYONAddon, ITrayAddon, IPluginPaths):
name = "clockify"
def initialize(self, studio_settings):
@ -31,7 +31,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
# TimersManager attributes
# - set `timers_manager_connector` only in `tray_init`
self.timers_manager_connector = None
self._timers_manager_module = None
self._timer_manager_addon = None
@property
def clockify_api(self):
@ -87,7 +87,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
return {"actions": [actions_path]}
def get_ftrack_event_handler_paths(self):
"""Function for Ftrack module to add ftrack event handler paths."""
"""Function for ftrack addon to add ftrack event handler paths."""
return {
"user": [CLOCKIFY_FTRACK_USER_PATH],
"server": [CLOCKIFY_FTRACK_SERVER_PATH],
@ -206,19 +206,19 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
self.action_stop_timer.setVisible(self.bool_timer_run)
# --- TimersManager connection methods ---
def register_timers_manager(self, timer_manager_module):
def register_timers_manager(self, timer_manager_addon):
"""Store TimersManager for future use."""
self._timers_manager_module = timer_manager_module
self._timer_manager_addon = timer_manager_addon
def timer_started(self, data):
"""Tell TimersManager that timer started."""
if self._timers_manager_module is not None:
self._timers_manager_module.timer_started(self.id, data)
if self._timer_manager_addon is not None:
self._timer_manager_addon.timer_started(self.id, data)
def timer_stopped(self):
"""Tell TimersManager that timer stopped."""
if self._timers_manager_module is not None:
self._timers_manager_module.timer_stopped(self.id)
if self._timer_manager_addon is not None:
self._timer_manager_addon.timer_stopped(self.id)
def stop_timer(self):
"""Called from TimersManager to stop timer."""

View file

@ -1,15 +1,17 @@
import os
import json
import datetime
import requests
from ayon_core.lib.local_settings import AYONSecureRegistry
from ayon_core.lib import Logger
from .constants import (
CLOCKIFY_ENDPOINT,
ADMIN_PERMISSION_NAMES,
)
from ayon_core.lib.local_settings import AYONSecureRegistry
from ayon_core.lib import Logger
class ClockifyAPI:
log = Logger.get_logger(__name__)

View file

@ -1,7 +1,9 @@
import os
import json
from openpype_modules.ftrack.lib import ServerAction
from openpype_modules.clockify.clockify_api import ClockifyAPI
from ayon_clockify.clockify_api import ClockifyAPI
from ayon_ftrack.lib import ServerAction
class SyncClockifyServer(ServerAction):

View file

@ -1,25 +1,20 @@
import json
from openpype_modules.ftrack.lib import BaseAction, statics_icon
from openpype_modules.clockify.clockify_api import ClockifyAPI
from ayon_clockify.clockify_api import ClockifyAPI
from ayon_ftrack.lib import BaseAction, statics_icon
class SyncClockifyLocal(BaseAction):
'''Synchronise project names and task types.'''
"""Synchronise project names and task types."""
#: Action identifier.
identifier = 'clockify.sync.local'
#: Action label.
label = 'Sync To Clockify (local)'
#: Action description.
description = 'Synchronise data to Clockify workspace'
#: roles that are allowed to register this action
identifier = "clockify.sync.local"
label = "Sync To Clockify"
description = "Synchronise data to Clockify workspace"
role_list = ["Administrator", "project Manager"]
#: icon
icon = statics_icon("app_icons", "clockify-white.png")
def __init__(self, *args, **kwargs):
super(SyncClockifyLocal, self).__init__(*args, **kwargs)
#: CLockifyApi
self.clockify_api = ClockifyAPI()
def discover(self, session, entities, event):
@ -56,7 +51,7 @@ class SyncClockifyLocal(BaseAction):
'user': user,
'status': 'running',
'data': json.dumps({
'description': 'Sync Ftrack to Clockify'
'description': 'Sync ftrack to Clockify'
})
})
session.commit()

View file

@ -1,7 +1,8 @@
import ayon_api
from ayon_clockify.clockify_api import ClockifyAPI
from ayon_core.pipeline import LauncherAction
from openpype_modules.clockify.clockify_api import ClockifyAPI
class ClockifyStart(LauncherAction):

View file

@ -1,6 +1,6 @@
import ayon_api
from openpype_modules.clockify.clockify_api import ClockifyAPI
from ayon_clockify.clockify_api import ClockifyAPI
from ayon_core.pipeline import LauncherAction

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'clockify' version."""
__version__ = "0.2.0"

View file

@ -1,3 +1,9 @@
name = "clockify"
title = "Clockify"
version = "0.1.1"
version = "0.2.0"
client_dir = "ayon_clockify"
ayon_required_addons = {
"core": ">0.3.2",
}
ayon_compatible_addons = {}

View file

@ -47,7 +47,7 @@ plugin_for = ["ayon_server"]
"""
CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*-
"""Package declaring AYON core addon version."""
"""Package declaring AYON addon '{}' version."""
__version__ = "{}"
'''
@ -183,6 +183,7 @@ def create_addon_zip(
def prepare_client_code(
addon_name: str,
addon_dir: Path,
addon_output_dir: Path,
addon_version: str
@ -211,7 +212,9 @@ def prepare_client_code(
version_path = subpath / "version.py"
if version_path.exists():
with open(version_path, "w") as stream:
stream.write(CLIENT_VERSION_CONTENT.format(addon_version))
stream.write(
CLIENT_VERSION_CONTENT.format(addon_name, addon_version)
)
zip_filepath = private_dir / "client.zip"
with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf:
@ -262,7 +265,9 @@ def create_addon_package(
server_dir, addon_output_dir / "server", dirs_exist_ok=True
)
prepare_client_code(addon_dir, addon_output_dir, addon_version)
prepare_client_code(
package.name, addon_dir, addon_output_dir, addon_version
)
if create_zip:
create_addon_zip(

View file

@ -57,6 +57,9 @@ class CreatePluginsModel(BaseSettingsModel):
CreateMantraROP: CreatorModel = SettingsField(
default_factory=CreatorModel,
title="Create Mantra ROP")
CreateModel: CreatorModel = SettingsField(
default_factory=CreatorModel,
title="Create Model")
CreatePointCache: CreatorModel = SettingsField(
default_factory=CreatorModel,
title="Create PointCache (Abc)")
@ -124,6 +127,10 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = {
"enabled": True,
"default_variants": ["Main"]
},
"CreateModel": {
"enabled": True,
"default_variants": ["Main"]
},
"CreatePointCache": {
"enabled": True,
"default_variants": ["Main"]

View file

@ -354,7 +354,7 @@ def imprint(node, data, tab=None):
Examples:
```
import nuke
from ayon_core.hosts.nuke.api import lib
from ayon_nuke.api import lib
node = nuke.createNode("NoOp")
data = {
@ -419,7 +419,7 @@ def add_publish_knob(node):
return node
@deprecated("ayon_core.hosts.nuke.api.lib.set_node_data")
@deprecated("ayon_nuke.api.lib.set_node_data")
def set_avalon_knob_data(node, data=None, prefix="avalon:"):
"""[DEPRECATED] Sets data into nodes's avalon knob
@ -485,7 +485,7 @@ def set_avalon_knob_data(node, data=None, prefix="avalon:"):
return node
@deprecated("ayon_core.hosts.nuke.api.lib.get_node_data")
@deprecated("ayon_nuke.api.lib.get_node_data")
def get_avalon_knob_data(node, prefix="avalon:", create=True):
"""[DEPRECATED] Gets a data from nodes's avalon knob
@ -1024,6 +1024,18 @@ def script_name():
return nuke.root().knob("name").value()
def add_button_render_on_farm(node):
name = "renderOnFarm"
label = "Render On Farm"
value = (
"from ayon_nuke.api.utils import submit_render_on_farm;"
"submit_render_on_farm(nuke.thisNode())"
)
knob = nuke.PyScript_Knob(name, label, value)
knob.clearFlag(nuke.STARTLINE)
node.addKnob(knob)
def add_button_write_to_read(node):
name = "createReadNode"
label = "Read From Rendered"
@ -1146,6 +1158,17 @@ def create_write_node(
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
@ -1270,6 +1293,10 @@ def create_write_node(
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)
@ -2442,7 +2469,7 @@ def _launch_workfile_app():
host_tools.show_workfiles(parent=None, on_top=True)
@deprecated("ayon_core.hosts.nuke.api.lib.start_workfile_template_builder")
@deprecated("ayon_nuke.api.lib.start_workfile_template_builder")
def process_workfile_builder():
""" [DEPRECATED] Process workfile builder on nuke start

View file

@ -28,7 +28,7 @@ from ayon_core.pipeline import (
)
from ayon_core.pipeline.workfile import BuildWorkfile
from ayon_core.tools.utils import host_tools
from ayon_core.hosts.nuke import NUKE_ROOT_DIR
from ayon_nuke import NUKE_ROOT_DIR
from ayon_core.tools.workfile_template_build import open_template_ui
from .lib import (
@ -188,10 +188,10 @@ def reload_config():
"""
for module in (
"ayon_core.hosts.nuke.api.actions",
"ayon_core.hosts.nuke.api.menu",
"ayon_core.hosts.nuke.api.plugin",
"ayon_core.hosts.nuke.api.lib",
"ayon_nuke.api.actions",
"ayon_nuke.api.menu",
"ayon_nuke.api.plugin",
"ayon_nuke.api.lib",
):
log.info("Reloading module: {}...".format(module))

View file

@ -572,8 +572,11 @@ class ExporterReview(object):
self.fhead = self.fhead.replace("#", "")[:-1]
def get_representation_data(
self, tags=None, range=False,
custom_tags=None, colorspace=None
self,
tags=None,
range=False,
custom_tags=None,
colorspace=None,
):
""" Add representation data to self.data
@ -584,6 +587,8 @@ class ExporterReview(object):
Defaults to False.
custom_tags (list[str], optional): user inputted custom tags.
Defaults to None.
colorspace (str, optional): colorspace name.
Defaults to None.
"""
add_tags = tags or []
repre = {
@ -591,7 +596,13 @@ class ExporterReview(object):
"ext": self.ext,
"files": self.file,
"stagingDir": self.staging_dir,
"tags": [self.name.replace("_", "-")] + add_tags
"tags": [self.name.replace("_", "-")] + add_tags,
"data": {
# making sure that once intermediate file is published
# as representation, we will be able to then identify it
# from representation.data.isIntermediate
"isIntermediate": True
},
}
if custom_tags:
@ -1000,7 +1011,7 @@ class ExporterReviewMov(ExporterReview):
tags=tags + add_tags,
custom_tags=add_custom_tags,
range=True,
colorspace=colorspace
colorspace=colorspace,
)
self.log.debug("Representation... `{}`".format(self.data))
@ -1039,7 +1050,7 @@ def convert_to_valid_instaces():
}
return mapping[product_type]
from ayon_core.hosts.nuke.api import workio
from ayon_nuke.api import workio
task_name = get_current_task_name()

View file

@ -3,9 +3,15 @@ import re
import nuke
from ayon_core import resources
import pyblish.util
import pyblish.api
from qtpy import QtWidgets
from ayon_core import resources
from ayon_core.pipeline import registered_host
from ayon_core.tools.utils import show_message_dialog
from ayon_core.pipeline.create import CreateContext
def set_context_favorites(favorites=None):
""" Adding favorite folders to nuke's browser
@ -142,3 +148,77 @@ def is_headless():
bool: headless
"""
return QtWidgets.QApplication.instance() is None
def submit_render_on_farm(node):
# Ensure code is executed in root context.
if nuke.root() == nuke.thisNode():
_submit_render_on_farm(node)
else:
# If not in root context, move to the root context and then execute the
# code.
with nuke.root():
_submit_render_on_farm(node)
def _submit_render_on_farm(node):
"""Render on farm submission
This function prepares the context for farm submission, validates it,
extracts relevant data, copies the current workfile to a timestamped copy,
and submits the job to the farm.
Args:
node (Node): The node for which the farm submission is being made.
"""
host = registered_host()
create_context = CreateContext(host)
# Ensure CreateInstance is enabled.
for instance in create_context.instances:
if node.name() != instance.transient_data["node"].name():
continue
instance.data["active"] = True
context = pyblish.api.Context()
context.data["create_context"] = create_context
# Used in pyblish plugin to determine which instance to publish.
context.data["node_name"] = node.name()
# Used in pyblish plugins to determine whether to run or not.
context.data["render_on_farm"] = True
# Since we need to bypass version validation and incrementing, we need to
# remove the plugins from the list that are responsible for these tasks.
plugins = pyblish.api.discover()
blacklist = ["IncrementScriptVersion", "ValidateVersion"]
plugins = [
plugin
for plugin in plugins
if plugin.__name__ not in blacklist
]
context = pyblish.util.publish(context, plugins=plugins)
error_message = ""
success = True
for result in context.data["results"]:
if result["success"]:
continue
success = False
err = result["error"]
error_message += "\n"
error_message += err.formatted_traceback
if not success:
show_message_dialog(
"Publish Errors", error_message, level="critical"
)
return
show_message_dialog(
"Submission Successful", "Submission to the farm was successful."
)

View file

@ -1,12 +1,12 @@
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
from ayon_core.pipeline.create.creator_plugins import ProductConvertorPlugin
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
INSTANCE_DATA_KNOB,
get_node_data,
get_avalon_knob_data,
NODE_TAB_NAME,
)
from ayon_core.hosts.nuke.api.plugin import convert_to_valid_instaces
from ayon_nuke.api.plugin import convert_to_valid_instaces
import nuke

View file

@ -1,6 +1,6 @@
from nukescripts import autoBackdrop
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
NukeCreator,
maintained_selection,
select_nodes
@ -10,6 +10,8 @@ from ayon_core.hosts.nuke.api import (
class CreateBackdrop(NukeCreator):
"""Add Publishable Backdrop"""
settings_category = "nuke"
identifier = "create_backdrop"
label = "Nukenodes (backdrop)"
product_type = "nukenodes"

View file

@ -1,10 +1,10 @@
import nuke
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
NukeCreator,
NukeCreatorError,
maintained_selection
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
create_camera_node_by_version
)
@ -12,6 +12,8 @@ from ayon_core.hosts.nuke.api.lib import (
class CreateCamera(NukeCreator):
"""Add Publishable Camera"""
settings_category = "nuke"
identifier = "create_camera"
label = "Camera (3d)"
product_type = "camera"

View file

@ -1,5 +1,5 @@
import nuke
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
NukeCreator,
NukeCreatorError,
maintained_selection
@ -9,6 +9,8 @@ from ayon_core.hosts.nuke.api import (
class CreateGizmo(NukeCreator):
"""Add Publishable Group as gizmo"""
settings_category = "nuke"
identifier = "create_gizmo"
label = "Gizmo (group)"
product_type = "gizmo"

View file

@ -1,5 +1,5 @@
import nuke
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
NukeCreator,
NukeCreatorError,
maintained_selection
@ -9,6 +9,8 @@ from ayon_core.hosts.nuke.api import (
class CreateModel(NukeCreator):
"""Add Publishable Camera"""
settings_category = "nuke"
identifier = "create_model"
label = "Model (3d)"
product_type = "model"

View file

@ -1,7 +1,7 @@
import nuke
import six
import sys
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
INSTANCE_DATA_KNOB,
NukeCreator,
NukeCreatorError,
@ -15,6 +15,8 @@ from ayon_core.pipeline import (
class CreateSource(NukeCreator):
"""Add Publishable Read with source"""
settings_category = "nuke"
identifier = "create_source"
label = "Source (read)"
product_type = "source"

View file

@ -11,11 +11,14 @@ from ayon_core.lib import (
UISeparatorDef,
EnumDef
)
from ayon_core.hosts.nuke import api as napi
from ayon_core.hosts.nuke.api.plugin import exposed_write_knobs
from ayon_nuke import api as napi
from ayon_nuke.api.plugin import exposed_write_knobs
class CreateWriteImage(napi.NukeWriteCreator):
settings_category = "nuke"
identifier = "create_write_image"
label = "Image (write)"
product_type = "image"
@ -65,12 +68,16 @@ class CreateWriteImage(napi.NukeWriteCreator):
)
def create_instance_node(self, product_name, instance_data):
settings = self.project_settings["nuke"]["create"]["CreateWriteImage"]
# add fpath_template
write_data = {
"creator": self.__class__.__name__,
"productName": product_name,
"fpath_template": self.temp_rendering_path_template
"fpath_template": self.temp_rendering_path_template,
"render_on_farm": (
"render_on_farm" in settings["instance_attributes"]
)
}
write_data.update(instance_data)

View file

@ -8,11 +8,14 @@ from ayon_core.pipeline import (
from ayon_core.lib import (
BoolDef
)
from ayon_core.hosts.nuke import api as napi
from ayon_core.hosts.nuke.api.plugin import exposed_write_knobs
from ayon_nuke import api as napi
from ayon_nuke.api.plugin import exposed_write_knobs
class CreateWritePrerender(napi.NukeWriteCreator):
settings_category = "nuke"
identifier = "create_write_prerender"
label = "Prerender (write)"
product_type = "prerender"
@ -46,11 +49,17 @@ class CreateWritePrerender(napi.NukeWriteCreator):
return attr_defs
def create_instance_node(self, product_name, instance_data):
settings = self.project_settings["nuke"]["create"]
settings = settings["CreateWritePrerender"]
# add fpath_template
write_data = {
"creator": self.__class__.__name__,
"productName": product_name,
"fpath_template": self.temp_rendering_path_template
"fpath_template": self.temp_rendering_path_template,
"render_on_farm": (
"render_on_farm" in settings["instance_attributes"]
)
}
write_data.update(instance_data)

View file

@ -8,11 +8,14 @@ from ayon_core.pipeline import (
from ayon_core.lib import (
BoolDef
)
from ayon_core.hosts.nuke import api as napi
from ayon_core.hosts.nuke.api.plugin import exposed_write_knobs
from ayon_nuke import api as napi
from ayon_nuke.api.plugin import exposed_write_knobs
class CreateWriteRender(napi.NukeWriteCreator):
settings_category = "nuke"
identifier = "create_write_render"
label = "Render (write)"
product_type = "render"
@ -40,11 +43,16 @@ class CreateWriteRender(napi.NukeWriteCreator):
return attr_defs
def create_instance_node(self, product_name, instance_data):
settings = self.project_settings["nuke"]["create"]["CreateWriteRender"]
# add fpath_template
write_data = {
"creator": self.__class__.__name__,
"productName": product_name,
"fpath_template": self.temp_rendering_path_template
"fpath_template": self.temp_rendering_path_template,
"render_on_farm": (
"render_on_farm" in settings["instance_attributes"]
)
}
write_data.update(instance_data)

View file

@ -1,11 +1,11 @@
import ayon_api
import ayon_core.hosts.nuke.api as api
import ayon_nuke.api as api
from ayon_core.pipeline import (
AutoCreator,
CreatedInstance,
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
INSTANCE_DATA_KNOB,
set_node_data
)
@ -13,6 +13,9 @@ import nuke
class WorkfileCreator(AutoCreator):
settings_category = "nuke"
identifier = "workfile"
product_type = "workfile"

View file

@ -1,6 +1,6 @@
from ayon_core.lib import Logger
from ayon_core.pipeline import InventoryAction
from ayon_core.hosts.nuke.api.lib import set_avalon_knob_data
from ayon_nuke.api.lib import set_avalon_knob_data
class RepairOldLoaders(InventoryAction):

View file

@ -1,5 +1,5 @@
from ayon_core.pipeline import InventoryAction
from ayon_core.hosts.nuke.api.command import viewer_update_and_undo_stop
from ayon_nuke.api.command import viewer_update_and_undo_stop
class SelectContainers(InventoryAction):

View file

@ -4,7 +4,7 @@
from ayon_core.lib import Logger
from ayon_core.pipeline import load
from ayon_core.hosts.nuke.api import lib
from ayon_nuke.api import lib
log = Logger.get_logger(__name__)

View file

@ -6,7 +6,7 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
find_free_space_to_paste_nodes,
maintained_selection,
reset_selection,
@ -14,8 +14,8 @@ from ayon_core.hosts.nuke.api.lib import (
get_avalon_knob_data,
set_avalon_knob_data
)
from ayon_core.hosts.nuke.api.command import viewer_update_and_undo_stop
from ayon_core.hosts.nuke.api import containerise, update_container
from ayon_nuke.api.command import viewer_update_and_undo_stop
from ayon_nuke.api import containerise, update_container
class LoadBackdropNodes(load.LoaderPlugin):
@ -25,6 +25,8 @@ class LoadBackdropNodes(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
settings_category = "nuke"
label = "Import Nuke Nodes"
order = 0
icon = "eye"

View file

@ -5,12 +5,12 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
maintained_selection
)
@ -24,6 +24,8 @@ class AlembicCameraLoader(load.LoaderPlugin):
representations = {"*"}
extensions = {"abc"}
settings_category = "nuke"
label = "Load Alembic Camera"
icon = "camera"
color = "orange"

View file

@ -12,11 +12,11 @@ from ayon_core.pipeline.colorspace import (
get_imageio_file_rules_colorspace_from_filepath,
get_current_context_imageio_config_preset,
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
get_imageio_input_colorspace,
maintained_selection
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop,
@ -26,7 +26,7 @@ from ayon_core.lib.transcoding import (
VIDEO_EXTENSIONS,
IMAGE_EXTENSIONS
)
from ayon_core.hosts.nuke.api import plugin
from ayon_nuke.api import plugin
class LoadClip(plugin.NukeLoader):
@ -48,6 +48,8 @@ class LoadClip(plugin.NukeLoader):
ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS)
)
settings_category = "nuke"
label = "Load Clip"
order = -20
icon = "file-video-o"
@ -61,7 +63,8 @@ class LoadClip(plugin.NukeLoader):
# option gui
options_defaults = {
"start_at_workfile": True,
"add_retime": True
"add_retime": True,
"deep_exr": False
}
node_name_template = "{class_name}_{ext}"
@ -78,6 +81,11 @@ class LoadClip(plugin.NukeLoader):
"add_retime",
help="Load with retime",
default=cls.options_defaults["add_retime"]
),
qargparse.Boolean(
"deep_exr",
help="Read with deep exr",
default=cls.options_defaults["deep_exr"]
)
]
@ -113,6 +121,9 @@ class LoadClip(plugin.NukeLoader):
add_retime = options.get(
"add_retime", self.options_defaults["add_retime"])
deep_exr = options.get(
"deep_exr", self.options_defaults["deep_exr"])
repre_id = repre_entity["id"]
self.log.debug(
@ -153,13 +164,21 @@ class LoadClip(plugin.NukeLoader):
return
read_name = self._get_node_name(context)
# Create the Loader with the filename path set
read_node = nuke.createNode(
"Read",
"name {}".format(read_name),
inpanel=False
)
read_node = None
if deep_exr:
# Create the Loader with the filename path set
read_node = nuke.createNode(
"DeepRead",
"name {}".format(read_name),
inpanel=False
)
else:
# Create the Loader with the filename path set
read_node = nuke.createNode(
"Read",
"name {}".format(read_name),
inpanel=False
)
# get colorspace
colorspace = (
@ -171,14 +190,14 @@ class LoadClip(plugin.NukeLoader):
# we will switch off undo-ing
with viewer_update_and_undo_stop():
read_node["file"].setValue(filepath)
self.set_colorspace_to_node(
read_node,
filepath,
project_name,
version_entity,
repre_entity
)
if read_node.Class() == "Read":
self.set_colorspace_to_node(
read_node,
filepath,
project_name,
version_entity,
repre_entity
)
self._set_range_to_node(
read_node, first, last, start_at_workfile, slate_frames
@ -328,13 +347,14 @@ class LoadClip(plugin.NukeLoader):
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
self.set_colorspace_to_node(
read_node,
filepath,
project_name,
version_entity,
repre_entity
)
if read_node.Class() == "Read":
self.set_colorspace_to_node(
read_node,
filepath,
project_name,
version_entity,
repre_entity
)
self._set_range_to_node(read_node, first, last, start_at_workfile)

View file

@ -8,7 +8,7 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -22,13 +22,14 @@ class LoadEffects(load.LoaderPlugin):
representations = {"*"}
extensions = {"json"}
settings_category = "nuke"
label = "Load Effects - nodes"
order = 0
icon = "cc"
color = "white"
ignore_attr = ["useLifetime"]
def load(self, context, name, namespace, data):
"""
Loading function to get the soft effects to particular read node

View file

@ -8,8 +8,8 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api import lib
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import lib
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -23,6 +23,8 @@ class LoadEffectsInputProcess(load.LoaderPlugin):
representations = {"*"}
extensions = {"json"}
settings_category = "nuke"
label = "Load Effects - Input Process"
order = 0
icon = "eye"

View file

@ -5,13 +5,13 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
maintained_selection,
get_avalon_knob_data,
set_avalon_knob_data,
swap_node_with_dependency,
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -25,6 +25,8 @@ class LoadGizmo(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
settings_category = "nuke"
label = "Load Gizmo"
order = 0
icon = "dropbox"

View file

@ -6,14 +6,14 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
maintained_selection,
create_backdrop,
get_avalon_knob_data,
set_avalon_knob_data,
swap_node_with_dependency,
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -27,6 +27,8 @@ class LoadGizmoInputProcess(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
settings_category = "nuke"
label = "Load Gizmo - Input Process"
order = 0
icon = "eye"

View file

@ -7,10 +7,10 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
get_imageio_input_colorspace
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -33,9 +33,9 @@ class LoadImage(load.LoaderPlugin):
"image",
}
representations = {"*"}
extensions = set(
ext.lstrip(".") for ext in IMAGE_EXTENSIONS
)
extensions = set(ext.lstrip(".") for ext in IMAGE_EXTENSIONS)
settings_category = "nuke"
label = "Load Image"
order = -10

View file

@ -11,6 +11,8 @@ class MatchmoveLoader(load.LoaderPlugin):
representations = {"*"}
extensions = {"py"}
settings_category = "nuke"
defaults = ["Camera", "Object"]
label = "Run matchmove script"

View file

@ -5,8 +5,8 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api.lib import maintained_selection
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api.lib import maintained_selection
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -22,6 +22,8 @@ class AlembicModelLoader(load.LoaderPlugin):
representations = {"*"}
extensions = {"abc"}
settings_category = "nuke"
label = "Load Alembic"
icon = "cube"
color = "orange"

View file

@ -10,7 +10,7 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api import (
containerise,
viewer_update_and_undo_stop,
update_container,
@ -24,6 +24,8 @@ class LoadOcioLookNodes(load.LoaderPlugin):
representations = {"*"}
extensions = {"json"}
settings_category = "nuke"
label = "Load OcioLook [nodes]"
order = 0
icon = "cc"

View file

@ -5,8 +5,8 @@ from ayon_core.pipeline import (
load,
get_representation_path,
)
from ayon_core.hosts.nuke.api.lib import get_avalon_knob_data
from ayon_core.hosts.nuke.api import (
from ayon_nuke.api.lib import get_avalon_knob_data
from ayon_nuke.api import (
containerise,
update_container,
viewer_update_and_undo_stop
@ -20,6 +20,8 @@ class LinkAsGroup(load.LoaderPlugin):
representations = {"*"}
extensions = {"nk"}
settings_category = "nuke"
label = "Load Precomp"
order = 0
icon = "file"

View file

@ -1,6 +1,6 @@
from pprint import pformat
import pyblish.api
from ayon_core.hosts.nuke.api import lib as pnlib
from ayon_nuke.api import lib as pnlib
import nuke
@ -13,6 +13,8 @@ class CollectBackdrops(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["nukenodes"]
settings_category = "nuke"
def process(self, instance):
self.log.debug(pformat(instance.data))

View file

@ -2,7 +2,7 @@ import os
import nuke
import pyblish.api
from ayon_core.lib import get_version_from_path
import ayon_core.hosts.nuke.api as napi
import ayon_nuke.api as napi
from ayon_core.pipeline import KnownPublishError
@ -13,6 +13,8 @@ class CollectContextData(pyblish.api.ContextPlugin):
label = "Collect context data"
hosts = ['nuke']
settings_category = "nuke"
def process(self, context): # sourcery skip: avoid-builtin-shadow
root_node = nuke.root()

View file

@ -13,5 +13,7 @@ class CollectFramerate(pyblish.api.ContextPlugin):
"nukeassist"
]
settings_category = "nuke"
def process(self, context):
context.data["fps"] = nuke.root()["fps"].getValue()

View file

@ -11,6 +11,8 @@ class CollectGizmo(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["gizmo"]
settings_category = "nuke"
def process(self, instance):
gizmo_node = instance.data["transientData"]["node"]

View file

@ -0,0 +1,58 @@
import pyblish.api
from ayon_core.pipeline.publish import (
AYONPyblishPluginMixin
)
class CollectRenderOnFarm(pyblish.api.ContextPlugin):
"""Setup instances for render on farm submission."""
# Needs to be after CollectFromCreateContext
order = pyblish.api.CollectorOrder - 0.49
label = "Collect Render On Farm"
hosts = ["nuke"]
settings_category = "nuke"
def process(self, context):
if not context.data.get("render_on_farm", False):
return
for instance in context:
if instance.data["family"] == "workfile":
instance.data["active"] = False
continue
# Filter out all other instances.
node = instance.data["transientData"]["node"]
if node.name() != instance.context.data["node_name"]:
instance.data["active"] = False
continue
instance.data["families"].append("render_on_farm")
# Enable for farm publishing.
instance.data["farm"] = True
# Skip workfile version incremental save.
instance.context.data["increment_script_version"] = False
class SetupRenderOnFarm(pyblish.api.InstancePlugin, AYONPyblishPluginMixin):
"""Setup instance for render on farm submission."""
order = pyblish.api.CollectorOrder + 0.4999
label = "Setup Render On Farm"
hosts = ["nuke"]
families = ["render_on_farm"]
def process(self, instance):
# Clear the families as we only want the main family, ei. no review
# etc.
instance.data["families"] = ["render_on_farm"]
# Use the workfile instead of published.
publish_attributes = instance.data["publish_attributes"]
plugin_attributes = publish_attributes["NukeSubmitDeadline"]
plugin_attributes["use_published_workfile"] = False

View file

@ -11,6 +11,8 @@ class CollectModel(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["model"]
settings_category = "nuke"
def process(self, instance):
geo_node = instance.data["transientData"]["node"]

View file

@ -11,6 +11,8 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
label = "Collect Nuke Instance Data"
hosts = ["nuke", "nukeassist"]
settings_category = "nuke"
# presets
sync_workfile_version_on_families = []

View file

@ -12,6 +12,8 @@ class CollectNukeReads(pyblish.api.InstancePlugin):
hosts = ["nuke", "nukeassist"]
families = ["source"]
settings_category = "nuke"
def process(self, instance):
self.log.debug("checking instance: {}".format(instance))

View file

@ -10,6 +10,8 @@ class CollectSlate(pyblish.api.InstancePlugin):
hosts = ["nuke"]
families = ["render"]
settings_category = "nuke"
def process(self, instance):
node = instance.data["transientData"]["node"]

View file

@ -11,6 +11,8 @@ class CollectWorkfile(pyblish.api.InstancePlugin):
hosts = ['nuke']
families = ["workfile"]
settings_category = "nuke"
def process(self, instance): # sourcery skip: avoid-builtin-shadow
script_data = instance.context.data["scriptData"]

View file

@ -1,7 +1,7 @@
import os
import nuke
import pyblish.api
from ayon_core.hosts.nuke import api as napi
from ayon_nuke import api as napi
from ayon_core.pipeline import publish
@ -14,6 +14,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
hosts = ["nuke", "nukeassist"]
families = ["render", "prerender", "image"]
settings_category = "nuke"
# cache
_write_nodes = {}
_frame_ranges = {}

View file

@ -5,7 +5,7 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api.lib import (
maintained_selection,
reset_selection,
select_nodes
@ -25,6 +25,8 @@ class ExtractBackdropNode(publish.Extractor):
hosts = ["nuke"]
families = ["nukenodes"]
settings_category = "nuke"
def process(self, instance):
tmp_nodes = []
child_nodes = instance.data["transientData"]["childNodes"]

View file

@ -6,7 +6,7 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
from ayon_core.hosts.nuke.api.lib import maintained_selection
from ayon_nuke.api.lib import maintained_selection
class ExtractCamera(publish.Extractor):
@ -17,6 +17,8 @@ class ExtractCamera(publish.Extractor):
families = ["camera"]
hosts = ["nuke"]
settings_category = "nuke"
# presets
write_geo_knobs = [
("file_type", "abc"),

View file

@ -4,8 +4,8 @@ import nuke
import pyblish.api
from ayon_core.pipeline import publish
from ayon_core.hosts.nuke.api import utils as pnutils
from ayon_core.hosts.nuke.api.lib import (
from ayon_nuke.api import utils as pnutils
from ayon_nuke.api.lib import (
maintained_selection,
reset_selection,
select_nodes
@ -23,6 +23,8 @@ class ExtractGizmo(publish.Extractor):
hosts = ["nuke"]
families = ["gizmo"]
settings_category = "nuke"
def process(self, instance):
tmp_nodes = []
orig_grpn = instance.data["transientData"]["node"]

Some files were not shown because too many files have changed in this diff Show more