Merge branch 'develop' into enhancement/maya_prompt_save_task_change_double

This commit is contained in:
Kayla Man 2024-04-08 16:16:25 +08:00 committed by GitHub
commit d5d274369d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 800 additions and 397 deletions

View file

@ -11,6 +11,10 @@ from .manager import ApplicationManager
class ApplicationsAddon(AYONAddon, IPluginPaths):
name = "applications"
def initialize(self, settings):
# TODO remove when addon is removed from ayon-core
self.enabled = self.name in settings
def get_app_environments_for_context(
self,
project_name,

View file

@ -4,6 +4,7 @@ import os
import sys
import code
import traceback
from pathlib import Path
import click
import acre
@ -11,7 +12,7 @@ import acre
from ayon_core import AYON_CORE_ROOT
from ayon_core.addon import AddonsManager
from ayon_core.settings import get_general_environments
from ayon_core.lib import initialize_ayon_connection
from ayon_core.lib import initialize_ayon_connection, is_running_from_build
from .cli_commands import Commands
@ -167,16 +168,27 @@ def run(script):
if not script:
print("Error: missing path to script file.")
return
# Remove first argument if it is the same as AYON executable
# - Forward compatibility with future AYON versions.
# - Current AYON launcher keeps the arguments with first argument but
# future versions might remove it.
first_arg = sys.argv[0]
if is_running_from_build():
comp_path = os.path.join(os.environ["AYON_ROOT"], "start.py")
else:
comp_path = os.getenv("AYON_EXECUTABLE")
# Compare paths and remove first argument if it is the same as AYON
if Path(first_arg).resolve() == Path(comp_path).resolve():
sys.argv.pop(0)
args = sys.argv
args.remove("run")
args.remove(script)
sys.argv = args
# Remove 'run' command from sys.argv
sys.argv.remove("run")
args_string = " ".join(args[1:])
print(f"... running: {script} {args_string}")
runpy.run_path(script, run_name="__main__", )
args_string = " ".join(sys.argv[1:])
print(f"... running: {script} {args_string}")
runpy.run_path(script, run_name="__main__")
@main_cli.command()

View file

@ -36,23 +36,23 @@ class HostDirmap(object):
host_name,
project_name,
project_settings=None,
sync_module=None
sitesync_addon=None
):
self.host_name = host_name
self.project_name = project_name
self._project_settings = project_settings
self._sync_module = sync_module
self._sitesync_addon = sitesync_addon
# to limit reinit of Modules
self._sync_module_discovered = sync_module is not None
self._sitesync_addon_discovered = sitesync_addon is not None
self._log = None
@property
def sync_module(self):
if not self._sync_module_discovered:
self._sync_module_discovered = True
def sitesync_addon(self):
if not self._sitesync_addon_discovered:
self._sitesync_addon_discovered = True
manager = AddonsManager()
self._sync_module = manager.get("sync_server")
return self._sync_module
self._sitesync_addon = manager.get("sitesync")
return self._sitesync_addon
@property
def project_settings(self):
@ -158,25 +158,25 @@ class HostDirmap(object):
"""
project_name = self.project_name
sync_module = self.sync_module
sitesync_addon = self.sitesync_addon
mapping = {}
if (
sync_module is None
or not sync_module.enabled
or project_name not in sync_module.get_enabled_projects()
sitesync_addon is None
or not sitesync_addon.enabled
or project_name not in sitesync_addon.get_enabled_projects()
):
return mapping
active_site = sync_module.get_local_normalized_site(
sync_module.get_active_site(project_name))
remote_site = sync_module.get_local_normalized_site(
sync_module.get_remote_site(project_name))
active_site = sitesync_addon.get_local_normalized_site(
sitesync_addon.get_active_site(project_name))
remote_site = sitesync_addon.get_local_normalized_site(
sitesync_addon.get_remote_site(project_name))
self.log.debug(
"active {} - remote {}".format(active_site, remote_site)
)
if active_site == "local" and active_site != remote_site:
sync_settings = sync_module.get_sync_project_setting(
sync_settings = sitesync_addon.get_sync_project_setting(
project_name,
exclude_locals=False,
cached=False)
@ -194,7 +194,7 @@ class HostDirmap(object):
self.log.debug("remote overrides {}".format(remote_overrides))
current_platform = platform.system().lower()
remote_provider = sync_module.get_provider_for_site(
remote_provider = sitesync_addon.get_provider_for_site(
project_name, remote_site
)
# dirmap has sense only with regular disk provider, in the workfile

View file

@ -16,6 +16,12 @@ from ayon_core.pipeline import (
AVALON_INSTANCE_ID,
AYON_INSTANCE_ID,
)
from ayon_core.pipeline.workfile import get_workdir
from ayon_api import (
get_project,
get_folder_by_path,
get_task_by_name
)
class GenericCreateSaver(Creator):
@ -125,6 +131,8 @@ class GenericCreateSaver(Creator):
product_name = data["productName"]
if (
original_product_name != product_name
or tool.GetData("openpype.task") != data["task"]
or tool.GetData("openpype.folderPath") != data["folderPath"]
or original_format != data["creator_attributes"]["image_format"]
):
self._configure_saver_tool(data, tool, product_name)
@ -145,7 +153,30 @@ class GenericCreateSaver(Creator):
folder_path = formatting_data["folderPath"]
folder_name = folder_path.rsplit("/", 1)[-1]
workdir = os.path.normpath(os.getenv("AYON_WORKDIR"))
# If the folder path and task do not match the current context then the
# workdir is not just the `AYON_WORKDIR`. Hence, we need to actually
# compute the resulting workdir
if (
data["folderPath"] == self.create_context.get_current_folder_path()
and data["task"] == self.create_context.get_current_task_name()
):
workdir = os.path.normpath(os.getenv("AYON_WORKDIR"))
else:
# TODO: Optimize this logic
project_name = self.create_context.get_current_project_name()
project_entity = get_project(project_name)
folder_entity = get_folder_by_path(project_name,
data["folderPath"])
task_entity = get_task_by_name(project_name,
folder_id=folder_entity["id"],
task_name=data["task"])
workdir = get_workdir(
project_entity=project_entity,
folder_entity=folder_entity,
task_entity=task_entity,
host_name=self.create_context.host_name,
)
formatting_data.update({
"workdir": workdir,
"frame": "0" * frame_padding,

View file

@ -0,0 +1,36 @@
import os
from ayon_core.lib import PreLaunchHook
from ayon_core.hosts.fusion import FUSION_HOST_DIR
class FusionLaunchMenuHook(PreLaunchHook):
"""Launch AYON menu on start of Fusion"""
app_groups = ["fusion"]
order = 9
def execute(self):
# Prelaunch hook is optional
settings = self.data["project_settings"][self.host_name]
if not settings["hooks"]["FusionLaunchMenuHook"]["enabled"]:
return
variant = self.application.name
if variant.isnumeric():
version = int(variant)
if version < 18:
print("Skipping launch of OpenPype menu on Fusion start "
"because Fusion version below 18.0 does not support "
"/execute argument on launch. "
f"Version detected: {version}")
return
else:
print(f"Application variant is not numeric: {variant}. "
"Validation for Fusion version 18+ for /execute "
"prelaunch argument skipped.")
path = os.path.join(FUSION_HOST_DIR,
"deploy",
"MenuScripts",
"launch_menu.py").replace("\\", "/")
script = f"fusion:RunScript('{path}')"
self.launch_context.launch_args.extend(["/execute", script])

View file

@ -248,8 +248,12 @@ def get_track_items(
# collect all available active sequence track items
if not return_list:
sequence = get_current_sequence(name=sequence_name)
# get all available tracks from sequence
tracks = list(sequence.audioTracks()) + list(sequence.videoTracks())
tracks = []
if sequence is not None:
# get all available tracks from sequence
tracks.extend(sequence.audioTracks())
tracks.extend(sequence.videoTracks())
# loop all tracks
for track in tracks:
if check_locked and track.isLocked():

View file

@ -137,7 +137,7 @@ class CreateShotClip(phiero.Creator):
"value": ["<track_name>", "main", "bg", "fg", "bg",
"animatic"],
"type": "QComboBox",
"label": "pRODUCT Name",
"label": "Product Name",
"target": "ui",
"toolTip": "chose product name pattern, if <track_name> is selected, name of track layer will be used", # noqa
"order": 0},
@ -159,7 +159,7 @@ class CreateShotClip(phiero.Creator):
"type": "QCheckBox",
"label": "Include audio",
"target": "tag",
"toolTip": "Process productS with corresponding audio", # noqa
"toolTip": "Process products with corresponding audio", # noqa
"order": 3},
"sourceResolution": {
"value": False,

View file

@ -37,7 +37,7 @@ from ayon_core.pipeline import (
AYON_CONTAINER_ID,
)
from ayon_core.lib import NumberDef
from ayon_core.pipeline.context_tools import get_current_folder_entity
from ayon_core.pipeline.context_tools import get_current_task_entity
from ayon_core.pipeline.create import CreateContext
from ayon_core.lib.profiles_filtering import filter_profiles
@ -2115,22 +2115,6 @@ def get_related_sets(node):
"""
# Ignore specific suffices
ignore_suffices = ["out_SET", "controls_SET", "_INST", "_CON"]
# Default nodes to ignore
defaults = {"defaultLightSet", "defaultObjectSet"}
# Ids to ignore
ignored = {
AVALON_INSTANCE_ID,
AVALON_CONTAINER_ID,
AYON_INSTANCE_ID,
AYON_CONTAINER_ID,
}
view_sets = get_isolate_view_sets()
sets = cmds.listSets(object=node, extendToShape=False)
if not sets:
return []
@ -2141,6 +2125,14 @@ def get_related_sets(node):
# returned by `cmds.listSets(allSets=True)`
sets = cmds.ls(sets)
# Ids to ignore
ignored = {
AVALON_INSTANCE_ID,
AVALON_CONTAINER_ID,
AYON_INSTANCE_ID,
AYON_CONTAINER_ID,
}
# Ignore `avalon.container`
sets = [
s for s in sets
@ -2149,21 +2141,31 @@ def get_related_sets(node):
or cmds.getAttr(f"{s}.id") not in ignored
)
]
if not sets:
return sets
# Exclude deformer sets (`type=2` for `maya.cmds.listSets`)
deformer_sets = cmds.listSets(object=node,
extendToShape=False,
type=2) or []
deformer_sets = set(deformer_sets) # optimize lookup
sets = [s for s in sets if s not in deformer_sets]
exclude_sets = cmds.listSets(object=node,
extendToShape=False,
type=2) or []
exclude_sets = set(exclude_sets) # optimize lookup
# Default nodes to ignore
exclude_sets.update({"defaultLightSet", "defaultObjectSet"})
# Filter out the sets to exclude
sets = [s for s in sets if s not in exclude_sets]
# Ignore when the set has a specific suffix
sets = [s for s in sets if not any(s.endswith(x) for x in ignore_suffices)]
ignore_suffices = ("out_SET", "controls_SET", "_INST", "_CON")
sets = [s for s in sets if not s.endswith(ignore_suffices)]
if not sets:
return sets
# Ignore viewport filter view sets (from isolate select and
# viewports)
view_sets = get_isolate_view_sets()
sets = [s for s in sets if s not in view_sets]
sets = [s for s in sets if s not in defaults]
return sets
@ -2434,12 +2436,10 @@ def set_scene_fps(fps, update=True):
cmds.currentUnit(time=unit, updateAnimation=update)
# Set time slider data back to previous state
cmds.playbackOptions(edit=True, minTime=start_frame)
cmds.playbackOptions(edit=True, maxTime=end_frame)
# Set animation data
cmds.playbackOptions(edit=True, animationStartTime=animation_start)
cmds.playbackOptions(edit=True, animationEndTime=animation_end)
cmds.playbackOptions(minTime=start_frame,
maxTime=end_frame,
animationStartTime=animation_start,
animationEndTime=animation_end)
cmds.currentTime(current_frame, edit=True, update=True)
@ -2629,21 +2629,21 @@ def reset_frame_range(playback=True, render=True, fps=True):
def reset_scene_resolution():
"""Apply the scene resolution from the project definition
scene resolution can be overwritten by an folder if the folder.attrib
contains any information regarding scene resolution .
The scene resolution will be retrieved from the current task entity's
attributes.
Returns:
None
"""
folder_attributes = get_current_folder_entity()["attrib"]
task_attributes = get_current_task_entity(fields={"attrib"})["attrib"]
# Set resolution
width = folder_attributes.get("resolutionWidth", 1920)
height = folder_attributes.get("resolutionHeight", 1080)
pixelAspect = folder_attributes.get("pixelAspect", 1)
width = task_attributes.get("resolutionWidth", 1920)
height = task_attributes.get("resolutionHeight", 1080)
pixel_aspect = task_attributes.get("pixelAspect", 1)
set_scene_resolution(width, height, pixelAspect)
set_scene_resolution(width, height, pixel_aspect)
def set_context_settings(
@ -3129,7 +3129,7 @@ def load_capture_preset(data):
return options
def get_attr_in_layer(attr, layer):
def get_attr_in_layer(attr, layer, as_string=True):
"""Return attribute value in specified renderlayer.
Same as cmds.getAttr but this gets the attribute's value in a
@ -3147,6 +3147,7 @@ def get_attr_in_layer(attr, layer):
Args:
attr (str): attribute name, ex. "node.attribute"
layer (str): layer name
as_string (bool): whether attribute should convert to a string value
Returns:
The return value from `maya.cmds.getAttr`
@ -3156,7 +3157,8 @@ def get_attr_in_layer(attr, layer):
try:
if cmds.mayaHasRenderSetup():
from . import lib_rendersetup
return lib_rendersetup.get_attr_in_layer(attr, layer)
return lib_rendersetup.get_attr_in_layer(
attr, layer, as_string=as_string)
except AttributeError:
pass
@ -3164,7 +3166,7 @@ def get_attr_in_layer(attr, layer):
current_layer = cmds.editRenderLayerGlobals(query=True,
currentRenderLayer=True)
if layer == current_layer:
return cmds.getAttr(attr)
return cmds.getAttr(attr, asString=as_string)
connections = cmds.listConnections(attr,
plugs=True,
@ -3215,7 +3217,7 @@ def get_attr_in_layer(attr, layer):
value *= conversion
return value
return cmds.getAttr(attr)
return cmds.getAttr(attr, asString=as_string)
def fix_incompatible_containers():
@ -3244,33 +3246,46 @@ def update_content_on_context_change():
"""
This will update scene content to match new folder on context change
"""
scene_sets = cmds.listSets(allSets=True)
folder_entity = get_current_folder_entity()
folder_attributes = folder_entity["attrib"]
new_folder_path = folder_entity["path"]
for s in scene_sets:
try:
if cmds.getAttr("{}.id".format(s)) in {
AYON_INSTANCE_ID, AVALON_INSTANCE_ID
}:
attr = cmds.listAttr(s)
print(s)
if "folderPath" in attr:
print(
" - setting folder to: [ {} ]".format(new_folder_path)
)
cmds.setAttr(
"{}.folderPath".format(s),
new_folder_path, type="string"
)
if "frameStart" in attr:
cmds.setAttr("{}.frameStart".format(s),
folder_attributes["frameStart"])
if "frameEnd" in attr:
cmds.setAttr("{}.frameEnd".format(s),
folder_attributes["frameEnd"],)
except ValueError:
pass
host = registered_host()
create_context = CreateContext(host)
folder_entity = get_current_task_entity(fields={"attrib"})
instance_values = {
"folderPath": create_context.get_current_folder_path(),
"task": create_context.get_current_task_name(),
}
creator_attribute_values = {
"frameStart": folder_entity["attrib"]["frameStart"],
"frameEnd": folder_entity["attrib"]["frameEnd"],
}
has_changes = False
for instance in create_context.instances:
for key, value in instance_values.items():
if key not in instance or instance[key] == value:
continue
# Update instance value
print(f"Updating {instance.product_name} {key} to: {value}")
instance[key] = value
has_changes = True
creator_attributes = instance.creator_attributes
for key, value in creator_attribute_values.items():
if (
key not in creator_attributes
or creator_attributes[key] == value
):
continue
# Update instance creator attribute value
print(f"Updating {instance.product_name} {key} to: {value}")
instance[key] = value
has_changes = True
if has_changes:
create_context.save_changes()
def show_message(title, msg):
@ -4004,17 +4019,26 @@ def len_flattened(components):
return n
def get_all_children(nodes):
def get_all_children(nodes, ignore_intermediate_objects=False):
"""Return all children of `nodes` including each instanced child.
Using maya.cmds.listRelatives(allDescendents=True) includes only the first
instance. As such, this function acts as an optimal replacement with a
focus on a fast query.
Args:
nodes (iterable): List of nodes to get children for.
ignore_intermediate_objects (bool): Ignore any children that
are intermediate objects.
Returns:
set: Children of input nodes.
"""
sel = OpenMaya.MSelectionList()
traversed = set()
iterator = OpenMaya.MItDag(OpenMaya.MItDag.kDepthFirst)
fn_dag = OpenMaya.MFnDagNode()
for node in nodes:
if node in traversed:
@ -4031,6 +4055,13 @@ def get_all_children(nodes):
iterator.next() # noqa: B305
while not iterator.isDone():
if ignore_intermediate_objects:
fn_dag.setObject(iterator.currentItem())
if fn_dag.isIntermediateObject:
iterator.prune()
iterator.next() # noqa: B305
continue
path = iterator.fullPathName()
if path in traversed:
@ -4041,7 +4072,7 @@ def get_all_children(nodes):
traversed.add(path)
iterator.next() # noqa: B305
return list(traversed)
return traversed
def get_capture_preset(

View file

@ -297,7 +297,7 @@ class ARenderProducts:
"""
return self._get_attr("defaultRenderGlobals", attribute)
def _get_attr(self, node_attr, attribute=None):
def _get_attr(self, node_attr, attribute=None, as_string=True):
"""Return the value of the attribute in the renderlayer
For readability this allows passing in the attribute in two ways.
@ -317,7 +317,7 @@ class ARenderProducts:
else:
plug = "{}.{}".format(node_attr, attribute)
return lib.get_attr_in_layer(plug, layer=self.layer)
return lib.get_attr_in_layer(plug, layer=self.layer, as_string=as_string)
@staticmethod
def extract_separator(file_prefix):
@ -1133,9 +1133,24 @@ class RenderProductsRedshift(ARenderProducts):
aovs = list(set(aovs) - set(ref_aovs))
products = []
global_aov_enabled = bool(
self._get_attr("redshiftOptions.aovGlobalEnableMode", as_string=False)
)
colorspace = lib.get_color_management_output_transform()
if not global_aov_enabled:
# only beauty output
for camera in cameras:
products.insert(0,
RenderProduct(productName="",
ext=ext,
multipart=self.multipart,
camera=camera,
colorspace=colorspace))
return products
light_groups_enabled = False
has_beauty_aov = False
colorspace = lib.get_color_management_output_transform()
for aov in aovs:
enabled = self._get_attr(aov, "enabled")
if not enabled:

View file

@ -77,7 +77,7 @@ def get_rendersetup_layer(layer):
if conn.endswith(".legacyRenderLayer")), None)
def get_attr_in_layer(node_attr, layer):
def get_attr_in_layer(node_attr, layer, as_string=True):
"""Return attribute value in Render Setup layer.
This will only work for attributes which can be
@ -124,7 +124,7 @@ def get_attr_in_layer(node_attr, layer):
node = history_overrides[-1] if history_overrides else override
node_attr_ = node + ".original"
return get_attribute(node_attr_, asString=True)
return get_attribute(node_attr_, asString=as_string)
layer = get_rendersetup_layer(layer)
rs = renderSetup.instance()
@ -144,7 +144,7 @@ def get_attr_in_layer(node_attr, layer):
# we will let it error out.
rs.switchToLayer(current_layer)
return get_attribute(node_attr, asString=True)
return get_attribute(node_attr, asString=as_string)
overrides = get_attr_overrides(node_attr, layer)
default_layer_value = get_default_layer_value(node_attr)

View file

@ -286,7 +286,7 @@ class MayaPlaceholderLoadPlugin(PlaceholderPlugin, PlaceholderLoadMixin):
if not container:
return
roots = cmds.sets(container, q=True)
roots = cmds.sets(container, q=True) or []
ref_node = None
try:
ref_node = get_reference_node(roots)

View file

@ -40,8 +40,15 @@ class CreateRenderlayer(plugin.RenderlayerCreator):
def create(self, product_name, instance_data, pre_create_data):
# Only allow a single render instance to exist
if self._get_singleton_node():
raise CreatorError("A Render instance already exists - only "
"one can be configured.")
raise CreatorError(
"A Render instance already exists - only one can be "
"configured.\n\n"
"To render multiple render layers, create extra Render Setup "
"Layers via Maya's Render Setup UI.\n"
"Then refresh the publisher to detect the new layers for "
"rendering.\n\n"
"With a render instance present all Render Setup layers in "
"your workfile are renderable instances.")
# Apply default project render settings on create
if self.render_settings.get("apply_render_settings"):

View file

@ -9,7 +9,9 @@ instance.
import json
import sys
import six
import contextlib
from ayon_core.lib import BoolDef, EnumDef
from ayon_core.pipeline import (
load,
get_representation_path
@ -21,6 +23,31 @@ from maya import cmds
import maya.app.renderSetup.model.renderSetup as renderSetup
@contextlib.contextmanager
def mark_all_imported(enabled):
"""Mark all imported nodes accepted by removing the `imported` attribute"""
if not enabled:
yield
return
node_types = cmds.pluginInfo("renderSetup", query=True, dependNode=True)
# Get node before load, then we can disable `imported`
# attribute on all new render setup layers after import
before = cmds.ls(type=node_types, long=True)
try:
yield
finally:
after = cmds.ls(type=node_types, long=True)
for node in (node for node in after if node not in before):
if cmds.attributeQuery("imported",
node=node,
exists=True):
plug = "{}.imported".format(node)
if cmds.getAttr(plug):
cmds.deleteAttr(plug)
class RenderSetupLoader(load.LoaderPlugin):
"""Load json preset for RenderSetup overwriting current one."""
@ -32,48 +59,79 @@ class RenderSetupLoader(load.LoaderPlugin):
icon = "tablet"
color = "orange"
options = [
BoolDef("accept_import",
label="Accept import on load",
tooltip=(
"By default importing or pasting Render Setup collections "
"will display them italic in the Render Setup list.\nWith "
"this enabled the load will directly mark the import "
"'accepted' and remove the italic view."
),
default=True),
BoolDef("load_managed",
label="Load Managed",
tooltip=(
"Containerize the rendersetup on load so it can be "
"'updated' later."
),
default=True),
EnumDef("import_mode",
label="Import mode",
items={
renderSetup.DECODE_AND_OVERWRITE: (
"Flush existing render setup and "
"add without any namespace"
),
renderSetup.DECODE_AND_MERGE: (
"Merge with the existing render setup objects and "
"rename the unexpected objects"
),
renderSetup.DECODE_AND_RENAME: (
"Renaming all decoded render setup objects to not "
"conflict with the existing render setup"
),
},
default=renderSetup.DECODE_AND_OVERWRITE)
]
def load(self, context, name, namespace, data):
"""Load RenderSetup settings."""
# from ayon_core.hosts.maya.api.lib import namespaced
folder_name = context["folder"]["name"]
namespace = namespace or lib.unique_namespace(
folder_name + "_",
prefix="_" if folder_name[0].isdigit() else "",
suffix="_",
)
path = self.filepath_from_context(context)
accept_import = data.get("accept_import", True)
import_mode = data.get("import_mode", renderSetup.DECODE_AND_OVERWRITE)
self.log.info(">>> loading json [ {} ]".format(path))
with open(path, "r") as file:
renderSetup.instance().decode(
json.load(file), renderSetup.DECODE_AND_OVERWRITE, None)
with mark_all_imported(accept_import):
with open(path, "r") as file:
renderSetup.instance().decode(
json.load(file), import_mode, None)
nodes = []
null = cmds.sets(name="null_SET", empty=True)
nodes.append(null)
if data.get("load_managed", True):
self.log.info(">>> containerising [ {} ]".format(name))
folder_name = context["folder"]["name"]
namespace = namespace or lib.unique_namespace(
folder_name + "_",
prefix="_" if folder_name[0].isdigit() else "",
suffix="_",
)
self[:] = nodes
if not nodes:
return
self.log.info(">>> containerising [ {} ]".format(name))
return containerise(
name=name,
namespace=namespace,
nodes=nodes,
context=context,
loader=self.__class__.__name__)
return containerise(
name=name,
namespace=namespace,
nodes=[],
context=context,
loader=self.__class__.__name__)
def remove(self, container):
"""Remove RenderSetup settings instance."""
from maya import cmds
container_name = container["objectName"]
self.log.info("Removing '%s' from Maya.." % container["name"])
container_content = cmds.sets(container_name, query=True)
container_content = cmds.sets(container_name, query=True) or []
nodes = cmds.ls(container_content, long=True)
nodes.append(container_name)

View file

@ -46,11 +46,18 @@ class CollectArnoldSceneSource(pyblish.api.InstancePlugin):
self.log.debug("data: {}".format(instance.data))
def get_hierarchy(self, nodes):
"""Return nodes with all their children"""
"""Return nodes with all their children.
Arguments:
nodes (List[str]): List of nodes to collect children hierarchy for
Returns:
list: Input nodes with their children hierarchy
"""
nodes = cmds.ls(nodes, long=True)
if not nodes:
return []
children = get_all_children(nodes)
# Make sure nodes merged with children only
# contains unique entries
return list(set(nodes + children))
children = get_all_children(nodes, ignore_intermediate_objects=True)
return list(children.union(nodes))

View file

@ -1,5 +1,3 @@
import json
from maya import cmds
import pyblish.api
@ -11,18 +9,24 @@ class CollectFileDependencies(pyblish.api.ContextPlugin):
label = "Collect File Dependencies"
order = pyblish.api.CollectorOrder - 0.49
hosts = ["maya"]
families = ["renderlayer"]
@classmethod
def apply_settings(cls, project_settings, system_settings):
# Disable plug-in if not used for deadline submission anyway
settings = project_settings["deadline"]["publish"]["MayaSubmitDeadline"] # noqa
cls.enabled = settings.get("asset_dependencies", True)
def process(self, context):
dependencies = []
dependencies = set()
for node in cmds.ls(type="file"):
path = cmds.getAttr("{}.{}".format(node, "fileTextureName"))
if path not in dependencies:
dependencies.append(path)
dependencies.add(path)
for node in cmds.ls(type="AlembicNode"):
path = cmds.getAttr("{}.{}".format(node, "abc_File"))
if path not in dependencies:
dependencies.append(path)
dependencies.add(path)
context.data["fileDependencies"] = dependencies
self.log.debug(json.dumps(dependencies, indent=4))
context.data["fileDependencies"] = list(dependencies)

View file

@ -48,15 +48,15 @@ class CollectNewInstances(pyblish.api.InstancePlugin):
# Collect members
members = cmds.ls(members, long=True) or []
# Collect full hierarchy
dag_members = cmds.ls(members, type="dagNode", long=True)
children = get_all_children(dag_members)
children = cmds.ls(children, noIntermediate=True, long=True)
parents = (
self.get_all_parents(members)
if creator_attributes.get("includeParentHierarchy", True)
else []
)
members_hierarchy = list(set(members + children + parents))
children = get_all_children(dag_members,
ignore_intermediate_objects=True)
members_hierarchy = set(members)
members_hierarchy.update(children)
if creator_attributes.get("includeParentHierarchy", True):
members_hierarchy.update(self.get_all_parents(dag_members))
instance[:] = members_hierarchy
@ -97,16 +97,16 @@ class CollectNewInstances(pyblish.api.InstancePlugin):
"""Get all parents by using string operations (optimization)
Args:
nodes (list): the nodes which are found in the objectSet
nodes (iterable): the nodes which are found in the objectSet
Returns:
list
set
"""
parents = []
parents = set()
for node in nodes:
splitted = node.split("|")
items = ["|".join(splitted[0:i]) for i in range(2, len(splitted))]
parents.extend(items)
parents.update(items)
return list(set(parents))
return parents

View file

@ -1,14 +1,18 @@
import inspect
import pyblish.api
from maya import cmds
from ayon_core.pipeline.publish import (
context_plugin_should_run,
PublishValidationError,
OptionalPyblishPluginMixin
)
class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin,
OptionalPyblishPluginMixin):
"""Validate if current render layer has a renderable camera
"""Validate if current render layer has a renderable camera.
There is a bug in Redshift which occurs when the current render layer
at file open has no renderable camera. The error raised is as follows:
@ -32,8 +36,39 @@ class ValidateCurrentRenderLayerIsRenderable(pyblish.api.ContextPlugin,
if not context_plugin_should_run(self, context):
return
layer = cmds.editRenderLayerGlobals(query=True, currentRenderLayer=True)
# This validator only makes sense when publishing renderlayer instances
# with Redshift. We skip validation if there isn't any.
if not any(self.is_active_redshift_render_instance(instance)
for instance in context):
return
cameras = cmds.ls(type="camera", long=True)
renderable = any(c for c in cameras if cmds.getAttr(c + ".renderable"))
assert renderable, ("Current render layer '%s' has no renderable "
"camera" % layer)
if not renderable:
layer = cmds.editRenderLayerGlobals(query=True,
currentRenderLayer=True)
raise PublishValidationError(
"Current render layer '{}' has no renderable camera".format(
layer
),
description=inspect.getdoc(self)
)
@staticmethod
def is_active_redshift_render_instance(instance) -> bool:
"""Return whether instance is an active renderlayer instance set to
render with Redshift renderer."""
if not instance.data.get("active", True):
return False
# Check this before families just because it's a faster check
if not instance.data.get("renderer") == "redshift":
return False
families = set()
families.add(instance.data.get("family"))
families.update(instance.data.get("families", []))
if "renderlayer" not in families:
return False
return True

View file

@ -1,3 +1,5 @@
import inspect
from maya import cmds
import pyblish.api
@ -14,8 +16,7 @@ class ValidateModelContent(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Adheres to the content of 'model' product type
- Must have one top group. (configurable)
- Must only contain: transforms, meshes and groups
See `get_description` for more details.
"""
@ -28,13 +29,16 @@ class ValidateModelContent(pyblish.api.InstancePlugin,
validate_top_group = True
optional = False
allowed = ('mesh', 'transform', 'nurbsCurve', 'nurbsSurface', 'locator')
@classmethod
def get_invalid(cls, instance):
content_instance = instance.data.get("setMembers", None)
if not content_instance:
cls.log.error("Instance has no nodes!")
return [instance.data["name"]]
cls.log.error("Model instance has no nodes. "
"It is not allowed to be empty")
return [instance.data["instance_node"]]
# All children will be included in the extracted export so we also
# validate *all* descendents of the set members and we skip any
@ -46,30 +50,42 @@ class ValidateModelContent(pyblish.api.InstancePlugin,
content_instance = list(set(content_instance + descendants))
# Ensure only valid node types
allowed = ('mesh', 'transform', 'nurbsCurve', 'nurbsSurface', 'locator')
nodes = cmds.ls(content_instance, long=True)
valid = cmds.ls(content_instance, long=True, type=allowed)
valid = cmds.ls(content_instance, long=True, type=cls.allowed)
invalid = set(nodes) - set(valid)
if invalid:
cls.log.error("These nodes are not allowed: %s" % invalid)
# List as bullet points
invalid_bullets = "\n".join(f"- {node}" for node in invalid)
cls.log.error(
"These nodes are not allowed:\n{}\n\n"
"The valid node types are: {}".format(
invalid_bullets, ", ".join(cls.allowed))
)
return list(invalid)
if not valid:
cls.log.error("No valid nodes in the instance")
return True
cls.log.error(
"No valid nodes in the model instance.\n"
"The valid node types are: {}".format(", ".join(cls.allowed))
)
return [instance.data["instance_node"]]
# Ensure it has shapes
shapes = cmds.ls(valid, long=True, shapes=True)
if not shapes:
cls.log.error("No shapes in the model instance")
return True
return [instance.data["instance_node"]]
# Top group
top_parents = set([x.split("|")[1] for x in content_instance])
# Ensure single top group
top_parents = {x.split("|", 2)[1] for x in content_instance}
if cls.validate_top_group and len(top_parents) != 1:
cls.log.error("Must have exactly one top group")
return top_parents
cls.log.error(
"A model instance must have exactly one top group. "
"Found top groups: {}".format(", ".join(top_parents))
)
return list(top_parents)
def _is_visible(node):
"""Return whether node is visible"""
@ -101,5 +117,21 @@ class ValidateModelContent(pyblish.api.InstancePlugin,
if invalid:
raise PublishValidationError(
title="Model content is invalid",
message="See log for more details"
message="Model content is invalid. See log for more details.",
description=self.get_description()
)
@classmethod
def get_description(cls):
return inspect.cleandoc(f"""
### Model content is invalid
Your model instance does not adhere to the rules of a
model product type:
- Must have at least one visible shape in it, like a mesh.
- Must have one root node. When exporting multiple meshes they
must be inside a group.
- May only contain the following node types:
{", ".join(cls.allowed)}
""")

View file

@ -37,27 +37,27 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin):
invalid = self.get_invalid(instance)
if invalid:
raise PublishValidationError(
("Found folder ids which are not related to "
"current project in instance: `{}`").format(instance.name))
"Found folder ids which are not related to "
"current project in instance: `{}`".format(instance.name))
@classmethod
def get_invalid(cls, instance):
invalid = []
nodes = instance[:]
if not nodes:
return
# Get all id required nodes
id_required_nodes = lib.get_id_required_nodes(referenced_nodes=True,
nodes=instance[:])
nodes=nodes)
if not id_required_nodes:
return []
# check ids against database ids
project_name = instance.context.data["projectName"]
folder_entities = ayon_api.get_folders(project_name, fields={"id"})
folder_ids = {
folder_entity["id"]
for folder_entity in folder_entities
}
folder_ids = cls.get_project_folder_ids(context=instance.context)
# Get all asset IDs
invalid = []
for node in id_required_nodes:
cb_id = lib.get_id(node)
@ -71,3 +71,31 @@ class ValidateNodeIdsInDatabase(pyblish.api.InstancePlugin):
invalid.append(node)
return invalid
@classmethod
def get_project_folder_ids(cls, context):
"""Return all folder ids in the current project.
Arguments:
context (pyblish.api.Context): The publish context.
Returns:
set[str]: All folder ids in the current project.
"""
# We query the database only for the first instance instead of
# per instance by storing a cache in the context
key = "__cache_project_folder_ids"
if key in context.data:
return context.data[key]
# check ids against database
project_name = context.data["projectName"]
folder_entities = ayon_api.get_folders(project_name, fields={"id"})
folder_ids = {
folder_entity["id"]
for folder_entity in folder_entities
}
context.data[key] = folder_ids
return folder_ids

View file

@ -8,6 +8,8 @@ from ayon_core.pipeline.publish import (
import ayon_core.hosts.maya.api.action
from ayon_core.hosts.maya.api import lib
from maya import cmds
class ValidateNodeIdsUnique(pyblish.api.InstancePlugin):
"""Validate the nodes in the instance have a unique Colorbleed Id
@ -41,7 +43,7 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin):
if invalid:
label = "Nodes found with non-unique folder ids"
raise PublishValidationError(
message="{}: {}".format(label, invalid),
message="{}, see log".format(label),
title="Non-unique folder ids on nodes",
description="{}\n- {}".format(label,
"\n- ".join(sorted(invalid)))
@ -54,7 +56,6 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin):
# Check only non intermediate shapes
# todo: must the instance itself ensure to have no intermediates?
# todo: how come there are intermediates?
from maya import cmds
instance_members = cmds.ls(instance, noIntermediate=True, long=True)
# Collect each id with their members
@ -67,10 +68,14 @@ class ValidateNodeIdsUnique(pyblish.api.InstancePlugin):
# Take only the ids with more than one member
invalid = list()
_iteritems = getattr(ids, "iteritems", ids.items)
for _ids, members in _iteritems():
for members in ids.values():
if len(members) > 1:
cls.log.error("ID found on multiple nodes: '%s'" % members)
members_text = "\n".join(
"- {}".format(member) for member in sorted(members)
)
cls.log.error(
"ID found on multiple nodes:\n{}".format(members_text)
)
invalid.extend(members)
return invalid

View file

@ -49,8 +49,9 @@ def get_selected_nodes():
"""Get information from current selection"""
selection = cmds.ls(selection=True, long=True)
hierarchy = lib.get_all_children(selection)
return list(set(selection + hierarchy))
hierarchy = lib.get_all_children(selection,
ignore_intermediate_objects=True)
return list(hierarchy.union(selection))
def get_all_asset_nodes():

View file

@ -2627,11 +2627,11 @@ class NukeDirmap(HostDirmap):
class DirmapCache:
"""Caching class to get settings and sync_module easily and only once."""
"""Caching class to get settings and sitesync easily and only once."""
_project_name = None
_project_settings = None
_sync_module_discovered = False
_sync_module = None
_sitesync_addon_discovered = False
_sitesync_addon = None
_mapping = None
@classmethod
@ -2647,11 +2647,11 @@ class DirmapCache:
return cls._project_settings
@classmethod
def sync_module(cls):
if not cls._sync_module_discovered:
cls._sync_module_discovered = True
cls._sync_module = AddonsManager().get("sync_server")
return cls._sync_module
def sitesync_addon(cls):
if not cls._sitesync_addon_discovered:
cls._sitesync_addon_discovered = True
cls._sitesync_addon = AddonsManager().get("sitesync")
return cls._sitesync_addon
@classmethod
def mapping(cls):
@ -2673,7 +2673,7 @@ def dirmap_file_name_filter(file_name):
"nuke",
DirmapCache.project_name(),
DirmapCache.project_settings(),
DirmapCache.sync_module(),
DirmapCache.sitesync_addon(),
)
if not DirmapCache.mapping():
DirmapCache.set_mapping(dirmap_processor.get_mappings())

View file

@ -447,7 +447,7 @@ class CacheItem:
class Anatomy(BaseAnatomy):
_sync_server_addon_cache = CacheItem()
_sitesync_addon_cache = CacheItem()
_project_cache = collections.defaultdict(CacheItem)
_default_site_id_cache = collections.defaultdict(CacheItem)
_root_overrides_cache = collections.defaultdict(
@ -482,13 +482,13 @@ class Anatomy(BaseAnatomy):
return copy.deepcopy(project_cache.data)
@classmethod
def get_sync_server_addon(cls):
if cls._sync_server_addon_cache.is_outdated:
def get_sitesync_addon(cls):
if cls._sitesync_addon_cache.is_outdated:
manager = AddonsManager()
cls._sync_server_addon_cache.update_data(
manager.get_enabled_addon("sync_server")
cls._sitesync_addon_cache.update_data(
manager.get_enabled_addon("sitesync")
)
return cls._sync_server_addon_cache.data
return cls._sitesync_addon_cache.data
@classmethod
def _get_studio_roots_overrides(cls, project_name):
@ -525,8 +525,8 @@ class Anatomy(BaseAnatomy):
"""
# First check if sync server is available and enabled
sync_server = cls.get_sync_server_addon()
if sync_server is None or not sync_server.enabled:
sitesync_addon = cls.get_sitesync_addon()
if sitesync_addon is None or not sitesync_addon.enabled:
# QUESTION is ok to force 'studio' when site sync is not enabled?
site_name = "studio"
@ -535,7 +535,7 @@ class Anatomy(BaseAnatomy):
project_cache = cls._default_site_id_cache[project_name]
if project_cache.is_outdated:
project_cache.update_data(
sync_server.get_active_site_type(project_name)
sitesync_addon.get_active_site_type(project_name)
)
site_name = project_cache.data
@ -549,7 +549,7 @@ class Anatomy(BaseAnatomy):
)
else:
# Ask sync server to get roots overrides
roots_overrides = sync_server.get_site_root_overrides(
roots_overrides = sitesync.get_site_root_overrides(
project_name, site_name
)
site_cache.update_data(roots_overrides)

View file

@ -1,5 +1,5 @@
from pyblish import api
from ayon_core.settings import get_current_project_settings
from ayon_core.settings import get_project_settings
class CollectSettings(api.ContextPlugin):
@ -9,4 +9,9 @@ class CollectSettings(api.ContextPlugin):
label = "Collect Settings"
def process(self, context):
context.data["project_settings"] = get_current_project_settings()
project_name = context.data["projectName"]
self.log.debug(
"Collecting settings for project: {}".format(project_name)
)
project_settings = get_project_settings(project_name)
context.data["project_settings"] = project_settings

View file

@ -27,7 +27,7 @@ class ExtractBurnin(publish.Extractor):
Extractor to create video with pre-defined burnins from
existing extracted video representation.
It will work only on represenations having `burnin = True` or
It will work only on representations having `burnin = True` or
`tags` including `burnin`
"""
@ -125,7 +125,7 @@ class ExtractBurnin(publish.Extractor):
burnin_defs = copy.deepcopy(src_burnin_defs)
# Filter output definition by `burnin` represetation key
# Filter output definition by `burnin` representation key
repre_linked_burnins = [
burnin_def
for burnin_def in burnin_defs
@ -378,6 +378,7 @@ class ExtractBurnin(publish.Extractor):
# Prepare subprocess arguments
args = list(executable_args)
args.append(temporary_json_filepath)
args.append("--headless")
self.log.debug("Executing: {}".format(" ".join(args)))
# Run burnin script
@ -547,7 +548,7 @@ class ExtractBurnin(publish.Extractor):
return burnin_data, temp_data
def repres_is_valid(self, repre):
"""Validation if representaion should be processed.
"""Validation if representation should be processed.
Args:
repre (dict): Representation which should be checked.
@ -579,7 +580,7 @@ class ExtractBurnin(publish.Extractor):
tags (list): Tags of processed representation.
Returns:
list: Containg all burnin definitions matching entered tags.
list: Contain all burnin definitions matching entered tags.
"""
filtered_burnins = []
@ -604,7 +605,7 @@ class ExtractBurnin(publish.Extractor):
Store data to `temp_data` for keys "full_input_path" which is full path
to source files optionally with sequence formatting,
"full_output_path" full path to otput with optionally with sequence
"full_output_path" full path to output with optionally with sequence
formatting, "full_input_paths" list of all source files which will be
deleted when burnin script ends, "repre_files" list of output
filenames.
@ -754,7 +755,7 @@ class ExtractBurnin(publish.Extractor):
profile (dict): Profile from presets matching current context.
Returns:
list: Containg all valid output definitions.
list: Contain all valid output definitions.
"""
filtered_burnin_defs = []
@ -775,7 +776,7 @@ class ExtractBurnin(publish.Extractor):
):
self.log.debug((
"Skipped burnin definition \"{}\". Family"
" fiters ({}) does not match current instance families: {}"
" filters ({}) does not match current instance families: {}"
).format(
filename_suffix, str(families_filters), str(families)
))

View file

@ -871,7 +871,7 @@ class FrontendLoaderController(_BaseLoaderController):
# Site sync functions
@abstractmethod
def is_site_sync_enabled(self, project_name=None):
def is_sitesync_enabled(self, project_name=None):
"""Is site sync enabled.
Site sync addon can be enabled but can be disabled per project.

View file

@ -113,7 +113,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
self._products_model = ProductsModel(self)
self._loader_actions_model = LoaderActionsModel(self)
self._thumbnails_model = ThumbnailsModel()
self._site_sync_model = SiteSyncModel(self)
self._sitesync_model = SiteSyncModel(self)
@property
def log(self):
@ -149,7 +149,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
self._loader_actions_model.reset()
self._projects_model.reset()
self._thumbnails_model.reset()
self._site_sync_model.reset()
self._sitesync_model.reset()
self._projects_model.refresh()
@ -240,7 +240,7 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
project_name, representation_ids)
)
action_items.extend(self._site_sync_model.get_site_sync_action_items(
action_items.extend(self._sitesync_model.get_sitesync_action_items(
project_name, representation_ids)
)
@ -254,8 +254,8 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
version_ids,
representation_ids
):
if self._site_sync_model.is_site_sync_action(identifier):
self._site_sync_model.trigger_action_item(
if self._sitesync_model.is_sitesync_action(identifier):
self._sitesync_model.trigger_action_item(
identifier,
project_name,
representation_ids
@ -368,24 +368,24 @@ class LoaderController(BackendLoaderController, FrontendLoaderController):
self._loaded_products_cache.update_data(product_ids)
return self._loaded_products_cache.get_data()
def is_site_sync_enabled(self, project_name=None):
return self._site_sync_model.is_site_sync_enabled(project_name)
def is_sitesync_enabled(self, project_name=None):
return self._sitesync_model.is_sitesync_enabled(project_name)
def get_active_site_icon_def(self, project_name):
return self._site_sync_model.get_active_site_icon_def(project_name)
return self._sitesync_model.get_active_site_icon_def(project_name)
def get_remote_site_icon_def(self, project_name):
return self._site_sync_model.get_remote_site_icon_def(project_name)
return self._sitesync_model.get_remote_site_icon_def(project_name)
def get_version_sync_availability(self, project_name, version_ids):
return self._site_sync_model.get_version_sync_availability(
return self._sitesync_model.get_version_sync_availability(
project_name, version_ids
)
def get_representations_sync_status(
self, project_name, representation_ids
):
return self._site_sync_model.get_representations_sync_status(
return self._sitesync_model.get_representations_sync_status(
project_name, representation_ids
)

View file

@ -1,7 +1,7 @@
from .selection import SelectionModel
from .products import ProductsModel
from .actions import LoaderActionsModel
from .site_sync import SiteSyncModel
from .sitesync import SiteSyncModel
__all__ = (

View file

@ -36,7 +36,7 @@ class SiteSyncModel:
self._controller = controller
self._site_icons = None
self._site_sync_enabled_cache = NestedCacheItem(
self._sitesync_enabled_cache = NestedCacheItem(
levels=1, lifetime=self.lifetime
)
self._active_site_cache = NestedCacheItem(
@ -57,17 +57,17 @@ class SiteSyncModel:
)
manager = AddonsManager()
self._site_sync_addon = manager.get("sync_server")
self._sitesync_addon = manager.get("sitesync")
def reset(self):
self._site_icons = None
self._site_sync_enabled_cache.reset()
self._sitesync_enabled_cache.reset()
self._active_site_cache.reset()
self._remote_site_cache.reset()
self._version_availability_cache.reset()
self._repre_status_cache.reset()
def is_site_sync_enabled(self, project_name=None):
def is_sitesync_enabled(self, project_name=None):
"""Site sync is enabled for a project.
Returns false if site sync addon is not available or enabled
@ -82,13 +82,13 @@ class SiteSyncModel:
bool: Site sync is enabled.
"""
if not self._is_site_sync_addon_enabled():
if not self._is_sitesync_addon_enabled():
return False
cache = self._site_sync_enabled_cache[project_name]
cache = self._sitesync_enabled_cache[project_name]
if not cache.is_valid:
enabled = True
if project_name:
enabled = self._site_sync_addon.is_project_enabled(
enabled = self._sitesync_addon.is_project_enabled(
project_name, single=True
)
cache.update_data(enabled)
@ -107,8 +107,8 @@ class SiteSyncModel:
cache = self._active_site_cache[project_name]
if not cache.is_valid:
site_name = None
if project_name and self._is_site_sync_addon_enabled():
site_name = self._site_sync_addon.get_active_site(project_name)
if project_name and self._is_sitesync_addon_enabled():
site_name = self._sitesync_addon.get_active_site(project_name)
cache.update_data(site_name)
return cache.get_data()
@ -125,8 +125,8 @@ class SiteSyncModel:
cache = self._remote_site_cache[project_name]
if not cache.is_valid:
site_name = None
if project_name and self._is_site_sync_addon_enabled():
site_name = self._site_sync_addon.get_remote_site(project_name)
if project_name and self._is_sitesync_addon_enabled():
site_name = self._sitesync_addon.get_remote_site(project_name)
cache.update_data(site_name)
return cache.get_data()
@ -140,7 +140,7 @@ class SiteSyncModel:
Union[dict[str, Any], None]: Site icon definition.
"""
if not project_name or not self.is_site_sync_enabled(project_name):
if not project_name or not self.is_sitesync_enabled(project_name):
return None
active_site = self.get_active_site(project_name)
return self._get_site_icon_def(project_name, active_site)
@ -155,14 +155,14 @@ class SiteSyncModel:
Union[dict[str, Any], None]: Site icon definition.
"""
if not project_name or not self.is_site_sync_enabled(project_name):
if not project_name or not self.is_sitesync_enabled(project_name):
return None
remote_site = self.get_remote_site(project_name)
return self._get_site_icon_def(project_name, remote_site)
def _get_site_icon_def(self, project_name, site_name):
# use different icon for studio even if provider is 'local_drive'
if site_name == self._site_sync_addon.DEFAULT_SITE:
if site_name == self._sitesync_addon.DEFAULT_SITE:
provider = "studio"
else:
provider = self._get_provider_for_site(project_name, site_name)
@ -179,7 +179,7 @@ class SiteSyncModel:
dict[str, tuple[int, int]]
"""
if not self.is_site_sync_enabled(project_name):
if not self.is_sitesync_enabled(project_name):
return {
version_id: _default_version_availability()
for version_id in version_ids
@ -217,7 +217,7 @@ class SiteSyncModel:
dict[str, tuple[float, float]]
"""
if not self.is_site_sync_enabled(project_name):
if not self.is_sitesync_enabled(project_name):
return {
repre_id: _default_repre_status()
for repre_id in representation_ids
@ -242,7 +242,7 @@ class SiteSyncModel:
output[repre_id] = repre_cache.get_data()
return output
def get_site_sync_action_items(self, project_name, representation_ids):
def get_sitesync_action_items(self, project_name, representation_ids):
"""
Args:
@ -253,7 +253,7 @@ class SiteSyncModel:
list[ActionItem]: Actions that can be shown in loader.
"""
if not self.is_site_sync_enabled(project_name):
if not self.is_sitesync_enabled(project_name):
return []
repres_status = self.get_representations_sync_status(
@ -289,7 +289,7 @@ class SiteSyncModel:
return action_items
def is_site_sync_action(self, identifier):
def is_sitesync_action(self, identifier):
"""Should be `identifier` handled by SiteSync.
Args:
@ -353,22 +353,22 @@ class SiteSyncModel:
)
elif identifier == REMOVE_IDENTIFIER:
self._site_sync_addon.remove_site(
self._sitesync_addon.remove_site(
project_name,
repre_id,
active_site,
remove_local_files=True
)
def _is_site_sync_addon_enabled(self):
def _is_sitesync_addon_enabled(self):
"""
Returns:
bool: Site sync addon is enabled.
"""
if self._site_sync_addon is None:
if self._sitesync_addon is None:
return False
return self._site_sync_addon.enabled
return self._sitesync_addon.enabled
def _get_provider_for_site(self, project_name, site_name):
"""Provider for a site.
@ -381,9 +381,9 @@ class SiteSyncModel:
Union[str, None]: Provider name.
"""
if not self._is_site_sync_addon_enabled():
if not self._is_sitesync_addon_enabled():
return None
return self._site_sync_addon.get_provider_for_site(
return self._sitesync_addon.get_provider_for_site(
project_name, site_name
)
@ -398,7 +398,7 @@ class SiteSyncModel:
return None
if self._site_icons is None:
self._site_icons = self._site_sync_addon.get_site_icons()
self._site_icons = self._sitesync_addon.get_site_icons()
return self._site_icons.get(provider)
def _refresh_version_availability(self, project_name, version_ids):
@ -406,7 +406,7 @@ class SiteSyncModel:
return
project_cache = self._version_availability_cache[project_name]
avail_by_id = self._site_sync_addon.get_version_availability(
avail_by_id = self._sitesync_addon.get_version_availability(
project_name,
version_ids,
self.get_active_site(project_name),
@ -425,7 +425,7 @@ class SiteSyncModel:
return
project_cache = self._repre_status_cache[project_name]
status_by_repre_id = (
self._site_sync_addon.get_representations_sync_state(
self._sitesync_addon.get_representations_sync_state(
project_name,
representation_ids,
self.get_active_site(project_name),
@ -496,7 +496,7 @@ class SiteSyncModel:
)
def _add_site(self, project_name, repre_entity, site_name, product_type):
self._site_sync_addon.add_site(
self._sitesync_addon.add_site(
project_name, repre_entity["id"], site_name, force=True
)
@ -513,7 +513,7 @@ class SiteSyncModel:
try:
print("Adding {} to linked representation: {}".format(
site_name, link_repre_id))
self._site_sync_addon.add_site(
self._sitesync_addon.add_site(
project_name,
link_repre_id,
site_name,

View file

@ -73,7 +73,7 @@ class ProductsModel(QtGui.QStandardItemModel):
published_time_col = column_labels.index("Time")
folders_label_col = column_labels.index("Folder")
in_scene_col = column_labels.index("In scene")
site_sync_avail_col = column_labels.index("Availability")
sitesync_avail_col = column_labels.index("Availability")
def __init__(self, controller):
super(ProductsModel, self).__init__()

View file

@ -139,9 +139,9 @@ class ProductsWidget(QtWidgets.QWidget):
products_view.setItemDelegateForColumn(
products_model.in_scene_col, in_scene_delegate)
site_sync_delegate = SiteSyncDelegate()
sitesync_delegate = SiteSyncDelegate()
products_view.setItemDelegateForColumn(
products_model.site_sync_avail_col, site_sync_delegate)
products_model.sitesync_avail_col, sitesync_delegate)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
@ -176,7 +176,7 @@ class ProductsWidget(QtWidgets.QWidget):
self._version_delegate = version_delegate
self._time_delegate = time_delegate
self._in_scene_delegate = in_scene_delegate
self._site_sync_delegate = site_sync_delegate
self._sitesync_delegate = sitesync_delegate
self._selected_project_name = None
self._selected_folder_ids = set()
@ -192,8 +192,8 @@ class ProductsWidget(QtWidgets.QWidget):
products_model.in_scene_col,
not controller.is_loaded_products_supported()
)
self._set_site_sync_visibility(
self._controller.is_site_sync_enabled()
self._set_sitesync_visibility(
self._controller.is_sitesync_enabled()
)
def set_name_filter(self, name):
@ -229,10 +229,10 @@ class ProductsWidget(QtWidgets.QWidget):
def refresh(self):
self._refresh_model()
def _set_site_sync_visibility(self, site_sync_enabled):
def _set_sitesync_visibility(self, sitesync_enabled):
self._products_view.setColumnHidden(
self._products_model.site_sync_avail_col,
not site_sync_enabled
self._products_model.sitesync_avail_col,
not sitesync_enabled
)
def _fill_version_editor(self):
@ -395,10 +395,10 @@ class ProductsWidget(QtWidgets.QWidget):
def _on_folders_selection_change(self, event):
project_name = event["project_name"]
site_sync_enabled = self._controller.is_site_sync_enabled(
sitesync_enabled = self._controller.is_sitesync_enabled(
project_name
)
self._set_site_sync_visibility(site_sync_enabled)
self._set_sitesync_visibility(sitesync_enabled)
self._selected_project_name = project_name
self._selected_folder_ids = event["folder_ids"]
self._refresh_model()

View file

@ -307,8 +307,8 @@ class RepresentationsWidget(QtWidgets.QWidget):
self._repre_model = repre_model
self._repre_proxy_model = repre_proxy_model
self._set_site_sync_visibility(
self._controller.is_site_sync_enabled()
self._set_sitesync_visibility(
self._controller.is_sitesync_enabled()
)
self._set_multiple_folders_selected(False)
@ -320,19 +320,19 @@ class RepresentationsWidget(QtWidgets.QWidget):
def _on_project_change(self, event):
self._selected_project_name = event["project_name"]
site_sync_enabled = self._controller.is_site_sync_enabled(
sitesync_enabled = self._controller.is_sitesync_enabled(
self._selected_project_name
)
self._set_site_sync_visibility(site_sync_enabled)
self._set_sitesync_visibility(sitesync_enabled)
def _set_site_sync_visibility(self, site_sync_enabled):
def _set_sitesync_visibility(self, sitesync_enabled):
self._repre_view.setColumnHidden(
self._repre_model.active_site_column,
not site_sync_enabled
not sitesync_enabled
)
self._repre_view.setColumnHidden(
self._repre_model.remote_site_column,
not site_sync_enabled
not sitesync_enabled
)
def _set_multiple_folders_selected(self, selected_multiple_folders):

View file

@ -28,7 +28,7 @@ class SceneInventoryController:
self._current_folder_id = None
self._current_folder_set = False
self._site_sync_model = SiteSyncModel(self)
self._sitesync_model = SiteSyncModel(self)
# Switch dialog requirements
self._hierarchy_model = HierarchyModel(self)
self._event_system = self._create_event_system()
@ -47,7 +47,7 @@ class SceneInventoryController:
self._current_folder_id = None
self._current_folder_set = False
self._site_sync_model.reset()
self._sitesync_model.reset()
self._hierarchy_model.reset()
def get_current_context(self):
@ -89,22 +89,22 @@ class SceneInventoryController:
return []
# Site Sync methods
def is_sync_server_enabled(self):
return self._site_sync_model.is_sync_server_enabled()
def is_sitesync_enabled(self):
return self._sitesync_model.is_sitesync_enabled()
def get_sites_information(self):
return self._site_sync_model.get_sites_information()
return self._sitesync_model.get_sites_information()
def get_site_provider_icons(self):
return self._site_sync_model.get_site_provider_icons()
return self._sitesync_model.get_site_provider_icons()
def get_representations_site_progress(self, representation_ids):
return self._site_sync_model.get_representations_site_progress(
return self._sitesync_model.get_representations_site_progress(
representation_ids
)
def resync_representations(self, representation_ids, site_type):
return self._site_sync_model.resync_representations(
return self._sitesync_model.resync_representations(
representation_ids, site_type
)

View file

@ -1,4 +1,4 @@
from .site_sync import SiteSyncModel
from .sitesync import SiteSyncModel
__all__ = (

View file

@ -9,30 +9,30 @@ class SiteSyncModel:
def __init__(self, controller):
self._controller = controller
self._sync_server_module = NOT_SET
self._sync_server_enabled = None
self._sitesync_addon = NOT_SET
self._sitesync_enabled = None
self._active_site = NOT_SET
self._remote_site = NOT_SET
self._active_site_provider = NOT_SET
self._remote_site_provider = NOT_SET
def reset(self):
self._sync_server_module = NOT_SET
self._sync_server_enabled = None
self._sitesync_addon = NOT_SET
self._sitesync_enabled = None
self._active_site = NOT_SET
self._remote_site = NOT_SET
self._active_site_provider = NOT_SET
self._remote_site_provider = NOT_SET
def is_sync_server_enabled(self):
def is_sitesync_enabled(self):
"""Site sync is enabled.
Returns:
bool: Is enabled or not.
"""
self._cache_sync_server_module()
return self._sync_server_enabled
self._cache_sitesync_addon()
return self._sitesync_enabled
def get_site_provider_icons(self):
"""Icon paths per provider.
@ -41,10 +41,10 @@ class SiteSyncModel:
dict[str, str]: Path by provider name.
"""
if not self.is_sync_server_enabled():
if not self.is_sitesync_enabled():
return {}
site_sync_addon = self._get_sync_server_module()
return site_sync_addon.get_site_icons()
sitesync_addon = self._get_sitesync_addon()
return sitesync_addon.get_site_icons()
def get_sites_information(self):
return {
@ -65,11 +65,11 @@ class SiteSyncModel:
}
for repre_id in representation_ids
}
if not self.is_sync_server_enabled():
if not self.is_sitesync_enabled():
return output
project_name = self._controller.get_current_project_name()
site_sync = self._get_sync_server_module()
sitesync_addon = self._get_sitesync_addon()
repre_entities = ayon_api.get_representations(
project_name, representation_ids
)
@ -78,7 +78,7 @@ class SiteSyncModel:
for repre_entity in repre_entities:
repre_output = output[repre_entity["id"]]
result = site_sync.get_progress_for_repre(
result = sitesync_addon.get_progress_for_repre(
repre_entity, active_site, remote_site
)
repre_output["active_site"] = result[active_site]
@ -95,7 +95,7 @@ class SiteSyncModel:
"""
project_name = self._controller.get_current_project_name()
site_sync = self._get_sync_server_module()
sitesync_addon = self._get_sitesync_addon()
active_site = self._get_active_site()
remote_site = self._get_remote_site()
progress = self.get_representations_site_progress(
@ -115,22 +115,22 @@ class SiteSyncModel:
site = remote_site
if check_progress == 1:
site_sync.add_site(
sitesync_addon.add_site(
project_name, repre_id, site, force=True
)
def _get_sync_server_module(self):
self._cache_sync_server_module()
return self._sync_server_module
def _get_sitesync_addon(self):
self._cache_sitesync_addon()
return self._sitesync_addon
def _cache_sync_server_module(self):
if self._sync_server_module is not NOT_SET:
return self._sync_server_module
def _cache_sitesync_addon(self):
if self._sitesync_addon is not NOT_SET:
return self._sitesync_addon
manager = AddonsManager()
site_sync = manager.get("sync_server")
sync_enabled = site_sync is not None and site_sync.enabled
self._sync_server_module = site_sync
self._sync_server_enabled = sync_enabled
sitesync_addon = manager.get("sitesync")
sync_enabled = sitesync_addon is not None and sitesync_addon.enabled
self._sitesync_addon = sitesync_addon
self._sitesync_enabled = sync_enabled
def _get_active_site(self):
if self._active_site is NOT_SET:
@ -157,19 +157,19 @@ class SiteSyncModel:
remote_site = None
active_site_provider = None
remote_site_provider = None
if self.is_sync_server_enabled():
site_sync = self._get_sync_server_module()
if self.is_sitesync_enabled():
sitesync_addon = self._get_sitesync_addon()
project_name = self._controller.get_current_project_name()
active_site = site_sync.get_active_site(project_name)
remote_site = site_sync.get_remote_site(project_name)
active_site = sitesync_addon.get_active_site(project_name)
remote_site = sitesync_addon.get_remote_site(project_name)
active_site_provider = "studio"
remote_site_provider = "studio"
if active_site != "studio":
active_site_provider = site_sync.get_provider_for_site(
active_site_provider = sitesync_addon.get_provider_for_site(
project_name, active_site
)
if remote_site != "studio":
remote_site_provider = site_sync.get_provider_for_site(
remote_site_provider = sitesync_addon.get_provider_for_site(
project_name, remote_site
)

View file

@ -311,9 +311,9 @@ class SceneInventoryView(QtWidgets.QTreeView):
menu.addAction(remove_action)
self._handle_sync_server(menu, repre_ids)
self._handle_sitesync(menu, repre_ids)
def _handle_sync_server(self, menu, repre_ids):
def _handle_sitesync(self, menu, repre_ids):
"""Adds actions for download/upload when SyncServer is enabled
Args:
@ -324,7 +324,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
(OptionMenu)
"""
if not self._controller.is_sync_server_enabled():
if not self._controller.is_sitesync_enabled():
return
menu.addSeparator()

View file

@ -70,7 +70,7 @@ class SceneInventoryWindow(QtWidgets.QDialog):
view = SceneInventoryView(controller, self)
view.setModel(proxy)
sync_enabled = controller.is_sync_server_enabled()
sync_enabled = controller.is_sitesync_enabled()
view.setColumnHidden(model.active_site_col, not sync_enabled)
view.setColumnHidden(model.remote_site_col, not sync_enabled)

View file

@ -659,16 +659,7 @@ class BaseWorkfileController(
folder_id != self.get_current_folder_id()
or task_name != self.get_current_task_name()
):
folder_entity = ayon_api.get_folder_by_id(
event_data["project_name"],
event_data["folder_id"],
)
task_entity = ayon_api.get_task_by_name(
event_data["project_name"],
event_data["folder_id"],
event_data["task_name"]
)
change_current_context(folder_entity, task_entity)
self._change_current_context(project_name, folder_id, task_id)
self._host_open_workfile(filepath)
@ -710,16 +701,8 @@ class BaseWorkfileController(
folder_id != self.get_current_folder_id()
or task_name != self.get_current_task_name()
):
folder_entity = ayon_api.get_folder_by_id(
project_name, folder["id"]
)
task_entity = ayon_api.get_task_by_name(
project_name, folder["id"], task_name
)
change_current_context(
folder_entity,
task_entity,
template_key=template_key
self._change_current_context(
project_name, folder_id, task_id, template_key
)
# Save workfile
@ -744,4 +727,18 @@ class BaseWorkfileController(
# Trigger after save events
emit_event("workfile.save.after", event_data, source="workfiles.tool")
self.reset()
def _change_current_context(
self, project_name, folder_id, task_id, template_key=None
):
# Change current context
folder_entity = self.get_folder_entity(project_name, folder_id)
task_entity = self.get_task_entity(project_name, task_id)
change_current_context(
folder_entity,
task_entity,
template_key=template_key
)
self._current_folder_id = folder_entity["id"]
self._current_folder_path = folder_entity["path"]
self._current_task_name = task_entity["name"]

View file

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