Merge branch 'develop' into houdini_opengl

This commit is contained in:
Roy Nieterau 2023-04-11 17:28:42 +02:00 committed by GitHub
commit c0ece932c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 275 additions and 74 deletions

View file

@ -15,7 +15,7 @@ jobs:
with:
ref: ${{ github.event.release.target_commitish }}
- name: Update version
uses: ShaMan123/gha-populate-form-version@v2.0.2
uses: ynput/gha-populate-form-version@main
with:
github_token: ${{ secrets.YNPUT_BOT_TOKEN }}
registry: github

View file

@ -1,4 +1,5 @@
import os
import re
from maya import cmds
@ -12,6 +13,7 @@ from openpype.pipeline import (
AVALON_CONTAINER_ID,
Anatomy,
)
from openpype.pipeline.load import LoadError
from openpype.settings import get_project_settings
from .pipeline import containerise
from . import lib
@ -82,6 +84,44 @@ def get_reference_node_parents(ref):
return parents
def get_custom_namespace(custom_namespace):
"""Return unique namespace.
The input namespace can contain a single group
of '#' number tokens to indicate where the namespace's
unique index should go. The amount of tokens defines
the zero padding of the number, e.g ### turns into 001.
Warning: Note that a namespace will always be
prefixed with a _ if it starts with a digit
Example:
>>> get_custom_namespace("myspace_##_")
# myspace_01_
>>> get_custom_namespace("##_myspace")
# _01_myspace
>>> get_custom_namespace("myspace##")
# myspace01
"""
split = re.split("([#]+)", custom_namespace, 1)
if len(split) == 3:
base, padding, suffix = split
padding = "%0{}d".format(len(padding))
else:
base = split[0]
padding = "%02d" # default padding
suffix = ""
return lib.unique_namespace(
base,
format=padding,
prefix="_" if not base or base[0].isdigit() else "",
suffix=suffix
)
class Creator(LegacyCreator):
defaults = ['Main']
@ -143,15 +183,46 @@ class ReferenceLoader(Loader):
assert os.path.exists(self.fname), "%s does not exist." % self.fname
asset = context['asset']
subset = context['subset']
settings = get_project_settings(context['project']['name'])
custom_naming = settings['maya']['load']['reference_loader']
loaded_containers = []
count = options.get("count") or 1
for c in range(0, count):
namespace = namespace or lib.unique_namespace(
"{}_{}_".format(asset["name"], context["subset"]["name"]),
prefix="_" if asset["name"][0].isdigit() else "",
suffix="_",
if not custom_naming['namespace']:
raise LoadError("No namespace specified in "
"Maya ReferenceLoader settings")
elif not custom_naming['group_name']:
raise LoadError("No group name specified in "
"Maya ReferenceLoader settings")
formatting_data = {
"asset_name": asset['name'],
"asset_type": asset['type'],
"subset": subset['name'],
"family": (
subset['data'].get('family') or
subset['data']['families'][0]
)
}
custom_namespace = custom_naming['namespace'].format(
**formatting_data
)
custom_group_name = custom_naming['group_name'].format(
**formatting_data
)
count = options.get("count") or 1
for c in range(0, count):
namespace = get_custom_namespace(custom_namespace)
group_name = "{}:{}".format(
namespace,
custom_group_name
)
options['group_name'] = group_name
# Offset loaded subset
if "offset" in options:
@ -187,7 +258,7 @@ class ReferenceLoader(Loader):
return loaded_containers
def process_reference(self, context, name, namespace, data):
def process_reference(self, context, name, namespace, options):
"""To be implemented by subclass"""
raise NotImplementedError("Must be implemented by subclass")

View file

@ -14,7 +14,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
icon = "code-fork"
color = "orange"
def process_reference(self, context, name, namespace, data):
def process_reference(self, context, name, namespace, options):
import maya.cmds as cmds
from openpype.hosts.maya.api.lib import unique_namespace
@ -41,7 +41,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
namespace=namespace,
sharedReferenceFile=False,
groupReference=True,
groupName="{}:{}".format(namespace, name),
groupName=options['group_name'],
reference=True,
returnNewNodes=True)

View file

@ -125,14 +125,15 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
except ValueError:
family = "model"
group_name = "{}:_GRP".format(namespace)
# True by default to keep legacy behaviours
attach_to_root = options.get("attach_to_root", True)
group_name = options["group_name"]
with maintained_selection():
cmds.loadPlugin("AbcImport.mll", quiet=True)
file_url = self.prepare_root_value(self.fname,
context["project"]["name"])
nodes = cmds.file(file_url,
namespace=namespace,
sharedReferenceFile=False,

View file

@ -19,8 +19,7 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
def process_reference(
self, context, name=None, namespace=None, options=None
):
group_name = "{}:{}".format(namespace, name)
group_name = options['group_name']
with lib.maintained_selection():
file_url = self.prepare_root_value(
self.fname, context["project"]["name"]

View file

@ -354,6 +354,61 @@ def publish_plugins_discover(paths=None):
return result
def _get_plugin_settings(host_name, project_settings, plugin, log):
"""Get plugin settings based on host name and plugin name.
Args:
host_name (str): Name of host.
project_settings (dict[str, Any]): Project settings.
plugin (pyliblish.Plugin): Plugin where settings are applied.
log (logging.Logger): Logger to log messages.
Returns:
dict[str, Any]: Plugin settings {'attribute': 'value'}.
"""
# Use project settings from host name category when available
try:
return (
project_settings
[host_name]
["publish"]
[plugin.__name__]
)
except KeyError:
pass
# Settings category determined from path
# - usually path is './<category>/plugins/publish/<plugin file>'
# - category can be host name of addon name ('maya', 'deadline', ...)
filepath = os.path.normpath(inspect.getsourcefile(plugin))
split_path = filepath.rsplit(os.path.sep, 5)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(filepath)
)
return {}
category_from_file = split_path[-4]
plugin_kind = split_path[-2]
# TODO: change after all plugins are moved one level up
if category_from_file == "openpype":
category_from_file = "global"
try:
return (
project_settings
[category_from_file]
[plugin_kind]
[plugin.__name__]
)
except KeyError:
pass
return {}
def filter_pyblish_plugins(plugins):
"""Pyblish plugin filter which applies OpenPype settings.
@ -372,21 +427,21 @@ def filter_pyblish_plugins(plugins):
# TODO: Don't use host from 'pyblish.api' but from defined host by us.
# - kept becau on farm is probably used host 'shell' which propably
# affect how settings are applied there
host = pyblish.api.current_host()
host_name = pyblish.api.current_host()
project_name = os.environ.get("AVALON_PROJECT")
project_setting = get_project_settings(project_name)
project_settings = get_project_settings(project_name)
system_settings = get_system_settings()
# iterate over plugins
for plugin in plugins[:]:
# Apply settings to plugins
if hasattr(plugin, "apply_settings"):
# Use classmethod 'apply_settings'
# - can be used to target settings from custom settings place
# - skip default behavior when successful
try:
# Use classmethod 'apply_settings'
# - can be used to target settings from custom settings place
# - skip default behavior when successful
plugin.apply_settings(project_setting, system_settings)
continue
plugin.apply_settings(project_settings, system_settings)
except Exception:
log.warning(
@ -395,53 +450,20 @@ def filter_pyblish_plugins(plugins):
).format(plugin.__name__),
exc_info=True
)
try:
config_data = (
project_setting
[host]
["publish"]
[plugin.__name__]
else:
# Automated
plugin_settins = _get_plugin_settings(
host_name, project_settings, plugin, log
)
except KeyError:
# host determined from path
file = os.path.normpath(inspect.getsourcefile(plugin))
file = os.path.normpath(file)
split_path = file.split(os.path.sep)
if len(split_path) < 4:
log.warning(
'plugin path too short to extract host {}'.format(file)
)
continue
host_from_file = split_path[-4]
plugin_kind = split_path[-2]
# TODO: change after all plugins are moved one level up
if host_from_file == "openpype":
host_from_file = "global"
try:
config_data = (
project_setting
[host_from_file]
[plugin_kind]
[plugin.__name__]
)
except KeyError:
continue
for option, value in config_data.items():
if option == "enabled" and value is False:
log.info('removing plugin {}'.format(plugin.__name__))
plugins.remove(plugin)
else:
log.info('setting {}:{} on plugin {}'.format(
for option, value in plugin_settins.items():
log.info("setting {}:{} on plugin {}".format(
option, value, plugin.__name__))
setattr(plugin, option, value)
# Remove disabled plugins
if getattr(plugin, "enabled", True) is False:
plugins.remove(plugin)
def find_close_plugin(close_plugin_name, log):
if close_plugin_name:

View file

@ -1047,6 +1047,10 @@
125,
255
]
},
"reference_loader": {
"namespace": "{asset_name}_{subset}_##",
"group_name": "_GRP"
}
},
"workfile_build": {

View file

@ -91,6 +91,28 @@
"key": "yetiRig"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "reference_loader",
"label": "Reference Loader",
"children": [
{
"type": "text",
"label": "Namespace",
"key": "namespace"
},
{
"type": "text",
"label": "Group name",
"key": "group_name"
},
{
"type": "label",
"label": "Here's a link to the doc where you can find explanations about customing the naming of referenced assets: https://openpype.io/docs/admin_hosts_maya#load-plugins"
}
]
}
]
}

View file

@ -6,6 +6,7 @@ import collections
import uuid
import tempfile
import shutil
import inspect
from abc import ABCMeta, abstractmethod
import six
@ -26,8 +27,8 @@ from openpype.pipeline import (
PublishValidationError,
KnownPublishError,
registered_host,
legacy_io,
get_process_id,
OptionalPyblishPluginMixin,
)
from openpype.pipeline.create import (
CreateContext,
@ -2307,6 +2308,37 @@ class PublisherController(BasePublisherController):
def _process_main_thread_item(self, item):
item()
def _is_publish_plugin_active(self, plugin):
"""Decide if publish plugin is active.
This is hack because 'active' is mis-used in mixin
'OptionalPyblishPluginMixin' where 'active' is used for default value
of optional plugins. Because of that is 'active' state of plugin
which inherit from 'OptionalPyblishPluginMixin' ignored. That affects
headless publishing inside host, potentially remote publishing.
We have to change that to match pyblish base, but we can do that
only when all hosts use Publisher because the change requires
change of settings schemas.
Args:
plugin (pyblish.Plugin): Plugin which should be checked if is
active.
Returns:
bool: Is plugin active.
"""
if plugin.active:
return True
if not plugin.optional:
return False
if OptionalPyblishPluginMixin in inspect.getmro(plugin):
return True
return False
def _publish_iterator(self):
"""Main logic center of publishing.
@ -2315,11 +2347,9 @@ class PublisherController(BasePublisherController):
states of currently processed publish plugin and instance. Also
change state of processed orders like validation order has passed etc.
Also stops publishing if should stop on validation.
QUESTION:
Does validate button still make sense?
Also stops publishing, if should stop on validation.
"""
for idx, plugin in enumerate(self._publish_plugins):
self._publish_progress = idx
@ -2344,6 +2374,11 @@ class PublisherController(BasePublisherController):
# Add plugin to publish report
self._publish_report.add_plugin_iter(plugin, self._publish_context)
# WARNING This is hack fix for optional plugins
if not self._is_publish_plugin_active(plugin):
self._publish_report.set_plugin_skipped()
continue
# Trigger callback that new plugin is going to be processed
plugin_label = plugin.__name__
if hasattr(plugin, "label") and plugin.label:
@ -2450,7 +2485,11 @@ def collect_families_from_instances(instances, only_active=False):
instances(list<pyblish.api.Instance>): List of publish instances from
which are families collected.
only_active(bool): Return families only for active instances.
Returns:
list[str]: Families available on instances.
"""
all_families = set()
for instance in instances:
if only_active:

View file

@ -162,7 +162,8 @@ class PluginsModel(QtGui.QStandardItemModel):
items = []
for plugin_item in plugin_items:
item = QtGui.QStandardItem(plugin_item.label)
label = plugin_item.label or plugin_item.name
item = QtGui.QStandardItem(label)
item.setData(False, ITEM_IS_GROUP_ROLE)
item.setData(plugin_item.label, ITEM_LABEL_ROLE)
item.setData(plugin_item.id, ITEM_ID_ROLE)

View file

@ -4,7 +4,6 @@ from qtpy import QtWidgets, QtCore, QtGui
import qtawesome
from openpype.client import get_projects
from openpype.pipeline import AvalonMongoDB
from openpype.style import get_objected_colors
from openpype.tools.utils.widgets import ImageButton
from openpype.tools.utils.lib import paint_image_with_color
@ -97,6 +96,7 @@ class CompleterView(QtWidgets.QListView):
# Open the widget unactivated
self.setAttribute(QtCore.Qt.WA_ShowWithoutActivating)
self.setAttribute(QtCore.Qt.WA_NoMouseReplay)
delegate = QtWidgets.QStyledItemDelegate()
self.setItemDelegate(delegate)
@ -241,6 +241,18 @@ class SettingsLineEdit(PlaceholderLineEdit):
if self._completer is not None:
self._completer.set_text_filter(text)
def _completer_should_be_visible(self):
return (
self.isVisible()
and (self.hasFocus() or self._completer.hasFocus())
)
def _show_completer(self):
if self._completer_should_be_visible():
self._focus_timer.start()
self._completer.show()
self._update_completer()
def _update_completer(self):
if self._completer is None or not self._completer.isVisible():
return
@ -249,7 +261,7 @@ class SettingsLineEdit(PlaceholderLineEdit):
self._completer.move(new_point)
def _on_focus_timer(self):
if not self.hasFocus() and not self._completer.hasFocus():
if not self._completer_should_be_visible():
self._completer.hide()
self._focus_timer.stop()
@ -258,9 +270,7 @@ class SettingsLineEdit(PlaceholderLineEdit):
self.focused_in.emit()
if self._completer is not None:
self._focus_timer.start()
self._completer.show()
self._update_completer()
self._show_completer()
def paintEvent(self, event):
super(SettingsLineEdit, self).paintEvent(event)

View file

@ -106,6 +106,37 @@ or Deadlines **Draft Tile Assembler**.
This is useful to fix some specific renderer glitches and advanced hacking of Maya Scene files. `Patch name` is label for patch for easier orientation.
`Patch regex` is regex used to find line in file, after `Patch line` string is inserted. Note that you need to add line ending.
## Load Plugins
### Reference Loader
#### Namespace and Group Name
Here you can create your own custom naming for the reference loader.
The custom naming is split into two parts: namespace and group name. If you don't set the namespace or the group name, an error will occur.
Here's the different variables you can use:
<div class="row markdown">
<div class="col col--5 markdown">
| Token | Description |
|---|---|
|`{asset_name}` | Asset name |
|`{asset_type}` | Asset type |
|`{subset}` | Subset name |
|`{family}` | Subset family |
</div>
</div>
The namespace field can contain a single group of '#' number tokens to indicate where the namespace's unique index should go. The amount of tokens defines the zero padding of the number, e.g ### turns into 001.
Warning: Note that a namespace will always be prefixed with a _ if it starts with a digit.
Example:
![Namespace and Group Name](assets/maya-admin_custom_namespace.png)
### Extract GPU Cache
![Maya GPU Cache](assets/maya-admin_gpu_cache.png)
@ -170,6 +201,7 @@ These options are set on the camera shape when publishing the review. They corre
![Extract Playblast Settings](assets/maya-admin_extract_playblast_settings_camera_options.png)
## Custom Menu
You can add your custom tools menu into Maya by extending definitions in **Maya -> Scripts Menu Definition**.
![Custom menu definition](assets/maya-admin_scriptsmenu.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB