[Automated] Merged develop into main

This commit is contained in:
ynbot 2023-03-13 15:30:39 +01:00 committed by GitHub
commit 1ca4ca8520
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 266 additions and 90 deletions

View file

@ -39,7 +39,6 @@ class HostDirmap(object):
self._project_settings = project_settings
self._sync_module = sync_module # to limit reinit of Modules
self._log = None
self._mapping = None # cache mapping
@property
def sync_module(self):
@ -70,29 +69,28 @@ class HostDirmap(object):
"""Run host dependent remapping from source_path to destination_path"""
pass
def process_dirmap(self):
def process_dirmap(self, mapping=None):
# type: (dict) -> None
"""Go through all paths in Settings and set them using `dirmap`.
If artists has Site Sync enabled, take dirmap mapping directly from
Local Settings when artist is syncing workfile locally.
Args:
project_settings (dict): Settings for current project.
"""
if not self._mapping:
self._mapping = self.get_mappings(self.project_settings)
if not self._mapping:
if not mapping:
mapping = self.get_mappings()
if not mapping:
return
self.log.info("Processing directory mapping ...")
self.on_enable_dirmap()
self.log.info("mapping:: {}".format(self._mapping))
for k, sp in enumerate(self._mapping["source-path"]):
dst = self._mapping["destination-path"][k]
for k, sp in enumerate(mapping["source-path"]):
dst = mapping["destination-path"][k]
try:
# add trailing slash if missing
sp = os.path.join(sp, '')
dst = os.path.join(dst, '')
print("{} -> {}".format(sp, dst))
self.dirmap_routine(sp, dst)
except IndexError:
@ -110,28 +108,24 @@ class HostDirmap(object):
)
continue
def get_mappings(self, project_settings):
def get_mappings(self):
"""Get translation from source-path to destination-path.
It checks if Site Sync is enabled and user chose to use local
site, in that case configuration in Local Settings takes precedence
"""
local_mapping = self._get_local_sync_dirmap(project_settings)
dirmap_label = "{}-dirmap".format(self.host_name)
if (
not self.project_settings[self.host_name].get(dirmap_label)
and not local_mapping
):
return {}
mapping_settings = self.project_settings[self.host_name][dirmap_label]
mapping_enabled = mapping_settings["enabled"] or bool(local_mapping)
mapping_sett = self.project_settings[self.host_name].get(dirmap_label,
{})
local_mapping = self._get_local_sync_dirmap()
mapping_enabled = mapping_sett.get("enabled") or bool(local_mapping)
if not mapping_enabled:
return {}
mapping = (
local_mapping
or mapping_settings["paths"]
or mapping_sett["paths"]
or {}
)
@ -141,28 +135,27 @@ class HostDirmap(object):
or not mapping.get("source-path")
):
return {}
self.log.info("Processing directory mapping ...")
self.log.info("mapping:: {}".format(mapping))
return mapping
def _get_local_sync_dirmap(self, project_settings):
def _get_local_sync_dirmap(self):
"""
Returns dirmap if synch to local project is enabled.
Only valid mapping is from roots of remote site to local site set
in Local Settings.
Args:
project_settings (dict)
Returns:
dict : { "source-path": [XXX], "destination-path": [YYYY]}
"""
project_name = os.getenv("AVALON_PROJECT")
mapping = {}
if not project_settings["global"]["sync_server"]["enabled"]:
if (not self.sync_module.enabled or
project_name not in self.sync_module.get_enabled_projects()):
return mapping
project_name = os.getenv("AVALON_PROJECT")
active_site = self.sync_module.get_local_normalized_site(
self.sync_module.get_active_site(project_name))
remote_site = self.sync_module.get_local_normalized_site(
@ -171,11 +164,7 @@ class HostDirmap(object):
"active {} - remote {}".format(active_site, remote_site)
)
if (
active_site == "local"
and project_name in self.sync_module.get_enabled_projects()
and active_site != remote_site
):
if active_site == "local" and active_site != remote_site:
sync_settings = self.sync_module.get_sync_project_setting(
project_name,
exclude_locals=False,
@ -188,7 +177,15 @@ class HostDirmap(object):
self.log.debug("local overrides {}".format(active_overrides))
self.log.debug("remote overrides {}".format(remote_overrides))
current_platform = platform.system().lower()
remote_provider = self.sync_module.get_provider_for_site(
project_name, remote_site
)
# dirmap has sense only with regular disk provider, in the workfile
# wont be root on cloud or sftp provider
if remote_provider != "local_drive":
remote_site = "studio"
for root_name, active_site_dir in active_overrides.items():
remote_site_dir = (
remote_overrides.get(root_name)

View file

@ -3576,6 +3576,65 @@ def get_color_management_output_transform():
return colorspace
def image_info(file_path):
# type: (str) -> dict
"""Based on tha texture path, get its bit depth and format information.
Take reference from makeTx.py in Arnold:
ImageInfo(filename): Get Image Information for colorspace
AiTextureGetFormat(filename): Get Texture Format
AiTextureGetBitDepth(filename): Get Texture bit depth
Args:
file_path (str): Path to the texture file.
Returns:
dict: Dictionary with the information about the texture file.
"""
from arnold import (
AiTextureGetBitDepth,
AiTextureGetFormat
)
# Get Texture Information
img_info = {'filename': file_path}
if os.path.isfile(file_path):
img_info['bit_depth'] = AiTextureGetBitDepth(file_path) # noqa
img_info['format'] = AiTextureGetFormat(file_path) # noqa
else:
img_info['bit_depth'] = 8
img_info['format'] = "unknown"
return img_info
def guess_colorspace(img_info):
# type: (dict) -> str
"""Guess the colorspace of the input image filename.
Note:
Reference from makeTx.py
Args:
img_info (dict): Image info generated by :func:`image_info`
Returns:
str: color space name use in the `--colorconvert`
option of maketx.
"""
from arnold import (
AiTextureInvalidate,
# types
AI_TYPE_BYTE,
AI_TYPE_INT,
AI_TYPE_UINT
)
try:
if img_info['bit_depth'] <= 16:
if img_info['format'] in (AI_TYPE_BYTE, AI_TYPE_INT, AI_TYPE_UINT): # noqa
return 'sRGB'
else:
return 'linear'
# now discard the image file as AiTextureGetFormat has loaded it
AiTextureInvalidate(img_info['filename']) # noqa
except ValueError:
print(("[maketx] Error: Could not guess"
"colorspace for {}").format(img_info["filename"]))
return "linear"
def len_flattened(components):
"""Return the length of the list as if it was flattened.

View file

@ -336,7 +336,8 @@ class RenderSettings(object):
)
# Set render file format to exr
cmds.setAttr("{}.imageFormatStr".format(node), "exr", type="string")
ext = vray_render_presets["image_format"]
cmds.setAttr("{}.imageFormatStr".format(node), ext, type="string")
# animType
cmds.setAttr("{}.animType".format(node), 1)

View file

@ -16,6 +16,7 @@ import pyblish.api
from openpype.lib import source_hash, run_subprocess
from openpype.pipeline import legacy_io, publish
from openpype.hosts.maya.api import lib
from openpype.hosts.maya.api.lib import image_info, guess_colorspace
# Modes for transfer
COPY = 1
@ -367,16 +368,25 @@ class ExtractLook(publish.Extractor):
for filepath in files_metadata:
linearize = False
if do_maketx and files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501
linearize = True
# set its file node to 'raw' as tx will be linearized
files_metadata[filepath]["color_space"] = "Raw"
# if OCIO color management enabled
# it won't take the condition of the files_metadata
ocio_maya = cmds.colorManagementPrefs(q=True,
cmConfigFileEnabled=True,
cmEnabled=True)
if do_maketx and not ocio_maya:
if files_metadata[filepath]["color_space"].lower() == "srgb": # noqa: E501
linearize = True
# set its file node to 'raw' as tx will be linearized
files_metadata[filepath]["color_space"] = "Raw"
# if do_maketx:
# color_space = "Raw"
source, mode, texture_hash = self._process_texture(
filepath,
resource,
do_maketx,
staging=staging_dir,
linearize=linearize,
@ -482,7 +492,8 @@ class ExtractLook(publish.Extractor):
resources_dir, basename + ext
)
def _process_texture(self, filepath, do_maketx, staging, linearize, force):
def _process_texture(self, filepath, resource,
do_maketx, staging, linearize, force):
"""Process a single texture file on disk for publishing.
This will:
1. Check whether it's already published, if so it will do hardlink
@ -524,10 +535,47 @@ class ExtractLook(publish.Extractor):
texture_hash
]
if linearize:
self.log.info("tx: converting sRGB -> linear")
additional_args.extend(["--colorconvert", "sRGB", "linear"])
if cmds.colorManagementPrefs(query=True, cmEnabled=True):
render_colorspace = cmds.colorManagementPrefs(query=True,
renderingSpaceName=True) # noqa
config_path = cmds.colorManagementPrefs(query=True,
configFilePath=True) # noqa
if not os.path.exists(config_path):
raise RuntimeError("No OCIO config path found!")
color_space_attr = resource["node"] + ".colorSpace"
try:
color_space = cmds.getAttr(color_space_attr)
except ValueError:
# node doesn't have color space attribute
if cmds.loadPlugin("mtoa", quiet=True):
img_info = image_info(filepath)
color_space = guess_colorspace(img_info)
else:
color_space = "Raw"
self.log.info("tx: converting {0} -> {1}".format(color_space, render_colorspace)) # noqa
additional_args.extend(["--colorconvert",
color_space,
render_colorspace])
else:
if cmds.loadPlugin("mtoa", quiet=True):
img_info = image_info(filepath)
color_space = guess_colorspace(img_info)
if color_space == "sRGB":
self.log.info("tx: converting sRGB -> linear")
additional_args.extend(["--colorconvert",
"sRGB",
"Raw"])
else:
self.log.info("tx: texture's colorspace "
"is already linear")
else:
self.log.warning("cannot guess the colorspace"
"color conversion won't be available!") # noqa
config_path = get_ocio_config_path("nuke-default")
additional_args.extend(["--colorconfig", config_path])
# Ensure folder exists
if not os.path.exists(os.path.dirname(converted)):

View file

@ -0,0 +1,26 @@
from maya import cmds
import pyblish.api
from openpype.pipeline.publish import ValidateContentsOrder
from openpype.pipeline import PublishValidationError
class ValidateMayaColorSpace(pyblish.api.InstancePlugin):
"""
Check if the OCIO Color Management and maketx options
enabled at the same time
"""
order = ValidateContentsOrder
families = ['look']
hosts = ['maya']
label = 'Color Management with maketx'
def process(self, instance):
ocio_maya = cmds.colorManagementPrefs(q=True,
cmConfigFileEnabled=True,
cmEnabled=True)
maketx = instance.data["maketx"]
if ocio_maya and maketx:
raise PublishValidationError("Maya is color managed and maketx option is on. OpenPype doesn't support this combination yet.") # noqa

View file

@ -2861,10 +2861,10 @@ class NukeDirmap(HostDirmap):
pass
def dirmap_routine(self, source_path, destination_path):
log.debug("{}: {}->{}".format(self.file_name,
source_path, destination_path))
source_path = source_path.lower().replace(os.sep, '/')
destination_path = destination_path.lower().replace(os.sep, '/')
log.debug("Map: {} with: {}->{}".format(self.file_name,
source_path, destination_path))
if platform.system().lower() == "windows":
self.file_name = self.file_name.lower().replace(
source_path, destination_path)
@ -2878,6 +2878,7 @@ class DirmapCache:
_project_name = None
_project_settings = None
_sync_module = None
_mapping = None
@classmethod
def project_name(cls):
@ -2897,6 +2898,36 @@ class DirmapCache:
cls._sync_module = ModulesManager().modules_by_name["sync_server"]
return cls._sync_module
@classmethod
def mapping(cls):
return cls._mapping
@classmethod
def set_mapping(cls, mapping):
cls._mapping = mapping
def dirmap_file_name_filter(file_name):
"""Nuke callback function with single full path argument.
Checks project settings for potential mapping from source to dest.
"""
dirmap_processor = NukeDirmap(
file_name,
"nuke",
DirmapCache.project_name(),
DirmapCache.project_settings(),
DirmapCache.sync_module(),
)
if not DirmapCache.mapping():
DirmapCache.set_mapping(dirmap_processor.get_mappings())
dirmap_processor.process_dirmap(DirmapCache.mapping())
if os.path.exists(dirmap_processor.file_name):
return dirmap_processor.file_name
return file_name
@contextlib.contextmanager
def node_tempfile():
@ -2942,25 +2973,6 @@ def duplicate_node(node):
return dupli_node
def dirmap_file_name_filter(file_name):
"""Nuke callback function with single full path argument.
Checks project settings for potential mapping from source to dest.
"""
dirmap_processor = NukeDirmap(
file_name,
"nuke",
DirmapCache.project_name(),
DirmapCache.project_settings(),
DirmapCache.sync_module(),
)
dirmap_processor.process_dirmap()
if os.path.exists(dirmap_processor.file_name):
return dirmap_processor.file_name
return file_name
def get_group_io_nodes(nodes):
"""Get the input and the output of a group of nodes."""

View file

@ -1472,13 +1472,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
return sync_settings
def get_all_site_configs(self, project_name=None):
def get_all_site_configs(self, project_name=None,
local_editable_only=False):
"""
Returns (dict) with all sites configured system wide.
Args:
project_name (str)(optional): if present, check if not disabled
local_editable_only (bool)(opt): if True return only Local
Setting configurable (for LS UI)
Returns:
(dict): {'studio': {'provider':'local_drive'...},
'MY_LOCAL': {'provider':....}}
@ -1499,9 +1501,21 @@ class SyncServerModule(OpenPypeModule, ITrayModule):
if site_settings:
detail.update(site_settings)
system_sites[site] = detail
system_sites.update(self._get_default_site_configs(sync_enabled,
project_name))
if local_editable_only:
local_schema = SyncServerModule.get_local_settings_schema()
editable_keys = {}
for provider_code, editables in local_schema.items():
editable_keys[provider_code] = ["enabled", "provider"]
for editable_item in editables:
editable_keys[provider_code].append(editable_item["key"])
for _, site in system_sites.items():
provider = site["provider"]
for site_config_key in list(site.keys()):
if site_config_key not in editable_keys[provider]:
site.pop(site_config_key, None)
return system_sites

View file

@ -330,6 +330,11 @@
"optional": true,
"active": true
},
"ValidateMayaColorSpace": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateAttributes": {
"enabled": false,
"attributes": {}

View file

@ -144,6 +144,10 @@
{
"key": "ValidateShadingEngine",
"label": "Validate Look Shading Engine Naming"
},
{
"key": "ValidateMayaColorSpace",
"label": "ValidateMayaColorSpace"
}
]
},

View file

@ -295,10 +295,10 @@ class SubsetWidget(QtWidgets.QWidget):
self.model.set_grouping(state)
def _subset_changed(self, text):
if hasattr(self.proxy, "setFilterRegularExpression"):
self.proxy.setFilterRegularExpression(text)
else:
if hasattr(self.proxy, "setFilterRegExp"):
self.proxy.setFilterRegExp(text)
else:
self.proxy.setFilterRegularExpression(text)
self.view.expandAll()
def set_loading_state(self, loading, empty):

View file

@ -482,10 +482,10 @@ class FilterProxyModel(QtCore.QSortFilterProxyModel):
return True
# Filter by regex
if hasattr(self, "filterRegularExpression"):
regex = self.filterRegularExpression()
else:
if hasattr(self, "filterRegExp"):
regex = self.filterRegExp()
else:
regex = self.filterRegularExpression()
pattern = regex.pattern()
if pattern:
pattern = re.escape(pattern)

View file

@ -160,10 +160,10 @@ class SceneInventoryWindow(QtWidgets.QDialog):
self._model.set_hierarchy_view(enabled)
def _on_text_filter_change(self, text_filter):
if hasattr(self._proxy, "setFilterRegularExpression"):
self._proxy.setFilterRegularExpression(text_filter)
else:
if hasattr(self._proxy, "setFilterRegExp"):
self._proxy.setFilterRegExp(text_filter)
else:
self._proxy.setFilterRegularExpression(text_filter)
def _on_outdated_state_change(self):
self._proxy.set_filter_outdated(

View file

@ -272,7 +272,7 @@ class SitesWidget(QtWidgets.QWidget):
)
site_configs = sync_server_module.get_all_site_configs(
self._project_name)
self._project_name, local_editable_only=True)
roots_entity = (
self.project_settings[PROJECT_ANATOMY_KEY][LOCAL_ROOTS_KEY]

View file

@ -27,10 +27,10 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
if not parent.isValid():
return False
if hasattr(self, "filterRegularExpression"):
regex = self.filterRegularExpression()
else:
if hasattr(self, "filterRegExp"):
regex = self.filterRegExp()
else:
regex = self.filterRegularExpression()
pattern = regex.pattern()
if pattern and regex.isValid():
@ -111,10 +111,10 @@ class SearchEntitiesDialog(QtWidgets.QDialog):
def _on_filter_timer(self):
text = self._filter_edit.text()
if hasattr(self._proxy, "setFilterRegularExpression"):
self._proxy.setFilterRegularExpression(text)
else:
if hasattr(self._proxy, "setFilterRegExp"):
self._proxy.setFilterRegExp(text)
else:
self._proxy.setFilterRegularExpression(text)
# WARNING This expanding and resizing is relatively slow.
self._view.expandAll()

View file

@ -5,10 +5,10 @@ from qtpy import QtCore
class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
"""Filters to the regex if any of the children matches allow parent"""
def filterAcceptsRow(self, row, parent):
if hasattr(self, "filterRegularExpression"):
regex = self.filterRegularExpression()
else:
if hasattr(self, "filterRegExp"):
regex = self.filterRegExp()
else:
regex = self.filterRegularExpression()
pattern = regex.pattern()
if pattern:
model = self.sourceModel()

View file

@ -202,11 +202,20 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
Use case: Filtering by string - parent won't be filtered if does not match
the filter string but first checks if any children does.
"""
def __init__(self, *args, **kwargs):
super(RecursiveSortFilterProxyModel, self).__init__(*args, **kwargs)
recursive_enabled = False
if hasattr(self, "setRecursiveFilteringEnabled"):
self.setRecursiveFilteringEnabled(True)
recursive_enabled = True
self._recursive_enabled = recursive_enabled
def filterAcceptsRow(self, row, parent_index):
if hasattr(self, "filterRegularExpression"):
regex = self.filterRegularExpression()
else:
if hasattr(self, "filterRegExp"):
regex = self.filterRegExp()
else:
regex = self.filterRegularExpression()
pattern = regex.pattern()
if pattern:
@ -219,8 +228,9 @@ class RecursiveSortFilterProxyModel(QtCore.QSortFilterProxyModel):
# Check current index itself
value = model.data(source_index, self.filterRole())
if re.search(pattern, value, re.IGNORECASE):
return True
matched = bool(re.search(pattern, value, re.IGNORECASE))
if matched or self._recursive_enabled:
return matched
rows = model.rowCount(source_index)
for idx in range(rows):