diff --git a/openpype/hosts/maya/__init__.py b/openpype/hosts/maya/__init__.py index 549f100007..b7d26a7818 100644 --- a/openpype/hosts/maya/__init__.py +++ b/openpype/hosts/maya/__init__.py @@ -5,9 +5,7 @@ def add_implementation_envs(env, _app): # Add requirements to PYTHONPATH pype_root = os.environ["OPENPYPE_REPOS_ROOT"] new_python_paths = [ - os.path.join(pype_root, "openpype", "hosts", "maya", "startup"), - os.path.join(pype_root, "repos", "avalon-core", "setup", "maya"), - os.path.join(pype_root, "tools", "mayalookassigner") + os.path.join(pype_root, "openpype", "hosts", "maya", "startup") ] old_python_path = env.get("PYTHONPATH") or "" for path in old_python_path.split(os.pathsep): diff --git a/openpype/hosts/maya/api/__init__.py b/openpype/hosts/maya/api/__init__.py index 0ad1c8ba29..1b75076028 100644 --- a/openpype/hosts/maya/api/__init__.py +++ b/openpype/hosts/maya/api/__init__.py @@ -1,233 +1,90 @@ -import os -import logging -import weakref +"""Public API -from maya import utils, cmds +Anything that isn't defined here is INTERNAL and unreliable for external use. -from avalon import api as avalon -from avalon import pipeline -from avalon.maya import suspended_refresh -from avalon.maya.pipeline import IS_HEADLESS -from openpype.tools.utils import host_tools -from pyblish import api as pyblish -from openpype.lib import any_outdated -import openpype.hosts.maya -from openpype.hosts.maya.lib import copy_workspace_mel -from openpype.lib.path_tools import HostDirmap -from . import menu, lib +""" -log = logging.getLogger("openpype.hosts.maya") +from .pipeline import ( + install, + uninstall, -HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.maya.__file__)) -PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") -PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") -LOAD_PATH = os.path.join(PLUGINS_DIR, "load") -CREATE_PATH = os.path.join(PLUGINS_DIR, "create") -INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + ls, + containerise, + + lock, + unlock, + is_locked, + lock_ignored, + +) +from .plugin import ( + Creator, + Loader +) + +from .workio import ( + open_file, + save_file, + current_file, + has_unsaved_changes, + file_extensions, + work_root +) + +from .lib import ( + export_alembic, + lsattr, + lsattrs, + read, + + apply_shaders, + without_extension, + maintained_selection, + suspended_refresh, + + unique_name, + unique_namespace, +) -def install(): - from openpype.settings import get_project_settings +__all__ = [ + "install", + "uninstall", - project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) - # process path mapping - dirmap_processor = MayaDirmap("maya", project_settings) - dirmap_processor.process_dirmap() + "Creator", + "Loader", - pyblish.register_plugin_path(PUBLISH_PATH) - avalon.register_plugin_path(avalon.Loader, LOAD_PATH) - avalon.register_plugin_path(avalon.Creator, CREATE_PATH) - avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH) - log.info(PUBLISH_PATH) - menu.install() + "ls", - log.info("Installing callbacks ... ") - avalon.on("init", on_init) + "lock", + "unlock", + "is_locked", + "lock_ignored", - # Callbacks below are not required for headless mode, the `init` however - # is important to load referenced Alembics correctly at rendertime. - if IS_HEADLESS: - log.info("Running in headless mode, skipping Maya " - "save/open/new callback installation..") - return + # Workfiles API + "open_file", + "save_file", + "current_file", + "has_unsaved_changes", + "file_extensions", + "work_root", - avalon.on("save", on_save) - avalon.on("open", on_open) - avalon.on("new", on_new) - avalon.before("save", on_before_save) - avalon.on("taskChanged", on_task_changed) - avalon.on("before.workfile.save", before_workfile_save) + # Utility functions + "export_alembic", + "lsattr", + "lsattrs", + "read", - log.info("Setting default family states for loader..") - avalon.data["familiesStateToggled"] = ["imagesequence"] + "unique_name", + "unique_namespace", + "apply_shaders", + "without_extension", + "maintained_selection", + "suspended_refresh", -def uninstall(): - pyblish.deregister_plugin_path(PUBLISH_PATH) - avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH) - avalon.deregister_plugin_path(avalon.Creator, CREATE_PATH) - avalon.deregister_plugin_path(avalon.InventoryAction, INVENTORY_PATH) +] - menu.uninstall() - - -def on_init(_): - avalon.logger.info("Running callback on init..") - - def safe_deferred(fn): - """Execute deferred the function in a try-except""" - - def _fn(): - """safely call in deferred callback""" - try: - fn() - except Exception as exc: - print(exc) - - try: - utils.executeDeferred(_fn) - except Exception as exc: - print(exc) - - # Force load Alembic so referenced alembics - # work correctly on scene open - cmds.loadPlugin("AbcImport", quiet=True) - cmds.loadPlugin("AbcExport", quiet=True) - - # Force load objExport plug-in (requested by artists) - cmds.loadPlugin("objExport", quiet=True) - - from .customize import ( - override_component_mask_commands, - override_toolbox_ui - ) - safe_deferred(override_component_mask_commands) - - launch_workfiles = os.environ.get("WORKFILES_STARTUP") - - if launch_workfiles: - safe_deferred(host_tools.show_workfiles) - - if not IS_HEADLESS: - safe_deferred(override_toolbox_ui) - - -def on_before_save(return_code, _): - """Run validation for scene's FPS prior to saving""" - return lib.validate_fps() - - -def on_save(_): - """Automatically add IDs to new nodes - - Any transform of a mesh, without an existing ID, is given one - automatically on file save. - """ - - avalon.logger.info("Running callback on save..") - - # # Update current task for the current scene - # update_task_from_path(cmds.file(query=True, sceneName=True)) - - # Generate ids of the current context on nodes in the scene - nodes = lib.get_id_required_nodes(referenced_nodes=False) - for node, new_id in lib.generate_ids(nodes): - lib.set_id(node, new_id, overwrite=False) - - -def on_open(_): - """On scene open let's assume the containers have changed.""" - - from Qt import QtWidgets - from openpype.widgets import popup - - cmds.evalDeferred( - "from openpype.hosts.maya.api import lib;" - "lib.remove_render_layer_observer()") - cmds.evalDeferred( - "from openpype.hosts.maya.api import lib;" - "lib.add_render_layer_observer()") - cmds.evalDeferred( - "from openpype.hosts.maya.api import lib;" - "lib.add_render_layer_change_observer()") - # # Update current task for the current scene - # update_task_from_path(cmds.file(query=True, sceneName=True)) - - # Validate FPS after update_task_from_path to - # ensure it is using correct FPS for the asset - lib.validate_fps() - lib.fix_incompatible_containers() - - if any_outdated(): - log.warning("Scene has outdated content.") - - # Find maya main window - top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} - parent = top_level_widgets.get("MayaWindow", None) - - if parent is None: - log.info("Skipping outdated content pop-up " - "because Maya window can't be found.") - else: - - # Show outdated pop-up - def _on_show_inventory(): - host_tools.show_scene_inventory(parent=parent) - - dialog = popup.Popup(parent=parent) - dialog.setWindowTitle("Maya scene has outdated content") - dialog.setMessage("There are outdated containers in " - "your Maya scene.") - dialog.on_show.connect(_on_show_inventory) - dialog.show() - - -def on_new(_): - """Set project resolution and fps when create a new file""" - avalon.logger.info("Running callback on new..") - with suspended_refresh(): - cmds.evalDeferred( - "from openpype.hosts.maya.api import lib;" - "lib.remove_render_layer_observer()") - cmds.evalDeferred( - "from openpype.hosts.maya.api import lib;" - "lib.add_render_layer_observer()") - cmds.evalDeferred( - "from openpype.hosts.maya.api import lib;" - "lib.add_render_layer_change_observer()") - lib.set_context_settings() - - -def on_task_changed(*args): - """Wrapped function of app initialize and maya's on task changed""" - # Run - with suspended_refresh(): - lib.set_context_settings() - lib.update_content_on_context_change() - - msg = " project: {}\n asset: {}\n task:{}".format( - avalon.Session["AVALON_PROJECT"], - avalon.Session["AVALON_ASSET"], - avalon.Session["AVALON_TASK"] - ) - - lib.show_message( - "Context was changed", - ("Context was changed to:\n{}".format(msg)), - ) - - -def before_workfile_save(event): - workdir_path = event.workdir_path - if workdir_path: - copy_workspace_mel(workdir_path) - - -class MayaDirmap(HostDirmap): - def on_enable_dirmap(self): - cmds.dirmap(en=True) - - def dirmap_routine(self, source_path, destination_path): - cmds.dirmap(m=(source_path, destination_path)) - cmds.dirmap(m=(destination_path, source_path)) +# Backwards API compatibility +open = open_file +save = save_file diff --git a/openpype/hosts/maya/api/action.py b/openpype/hosts/maya/api/action.py index a98b906d8c..ab26748c8a 100644 --- a/openpype/hosts/maya/api/action.py +++ b/openpype/hosts/maya/api/action.py @@ -2,7 +2,7 @@ from __future__ import absolute_import import pyblish.api - +from avalon import io from openpype.api import get_errored_instances_from_context @@ -72,8 +72,7 @@ class GenerateUUIDsOnInvalidAction(pyblish.api.Action): nodes (list): all nodes to regenerate ids on """ - from openpype.hosts.maya.api import lib - import avalon.io as io + from . import lib asset = instance.data['asset'] asset_id = io.find_one({"name": asset, "type": "asset"}, diff --git a/openpype/hosts/maya/api/commands.py b/openpype/hosts/maya/api/commands.py index d4c2b6a225..ebae3b1399 100644 --- a/openpype/hosts/maya/api/commands.py +++ b/openpype/hosts/maya/api/commands.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- """OpenPype script commands to be used directly in Maya.""" +from maya import cmds +from avalon import api, io class ToolWindows: @@ -51,3 +53,135 @@ def edit_shader_definitions(): window = ShaderDefinitionsEditor(parent=main_window) ToolWindows.set_window("shader_definition_editor", window) window.show() + + + +def reset_frame_range(): + """Set frame range to current asset""" + # Set FPS first + fps = {15: 'game', + 24: 'film', + 25: 'pal', + 30: 'ntsc', + 48: 'show', + 50: 'palf', + 60: 'ntscf', + 23.98: '23.976fps', + 23.976: '23.976fps', + 29.97: '29.97fps', + 47.952: '47.952fps', + 47.95: '47.952fps', + 59.94: '59.94fps', + 44100: '44100fps', + 48000: '48000fps' + }.get(float(api.Session.get("AVALON_FPS", 25)), "pal") + + cmds.currentUnit(time=fps) + + # Set frame start/end + asset_name = api.Session["AVALON_ASSET"] + asset = io.find_one({"name": asset_name, "type": "asset"}) + + frame_start = asset["data"].get("frameStart") + frame_end = asset["data"].get("frameEnd") + # Backwards compatibility + if frame_start is None or frame_end is None: + frame_start = asset["data"].get("edit_in") + frame_end = asset["data"].get("edit_out") + + if frame_start is None or frame_end is None: + cmds.warning("No edit information found for %s" % asset_name) + return + + handles = asset["data"].get("handles") or 0 + handle_start = asset["data"].get("handleStart") + if handle_start is None: + handle_start = handles + + handle_end = asset["data"].get("handleEnd") + if handle_end is None: + handle_end = handles + + frame_start -= int(handle_start) + frame_end += int(handle_end) + + cmds.playbackOptions(minTime=frame_start) + cmds.playbackOptions(maxTime=frame_end) + cmds.playbackOptions(animationStartTime=frame_start) + cmds.playbackOptions(animationEndTime=frame_end) + cmds.playbackOptions(minTime=frame_start) + cmds.playbackOptions(maxTime=frame_end) + cmds.currentTime(frame_start) + + cmds.setAttr("defaultRenderGlobals.startFrame", frame_start) + cmds.setAttr("defaultRenderGlobals.endFrame", frame_end) + + +def _resolution_from_document(doc): + if not doc or "data" not in doc: + print("Entered document is not valid. \"{}\"".format(str(doc))) + return None + + resolution_width = doc["data"].get("resolutionWidth") + resolution_height = doc["data"].get("resolutionHeight") + # Backwards compatibility + if resolution_width is None or resolution_height is None: + resolution_width = doc["data"].get("resolution_width") + resolution_height = doc["data"].get("resolution_height") + + # Make sure both width and heigh are set + if resolution_width is None or resolution_height is None: + cmds.warning( + "No resolution information found for \"{}\"".format(doc["name"]) + ) + return None + + return int(resolution_width), int(resolution_height) + + +def reset_resolution(): + # Default values + resolution_width = 1920 + resolution_height = 1080 + + # Get resolution from asset + asset_name = api.Session["AVALON_ASSET"] + asset_doc = io.find_one({"name": asset_name, "type": "asset"}) + resolution = _resolution_from_document(asset_doc) + # Try get resolution from project + if resolution is None: + # TODO go through visualParents + print(( + "Asset \"{}\" does not have set resolution." + " Trying to get resolution from project" + ).format(asset_name)) + project_doc = io.find_one({"type": "project"}) + resolution = _resolution_from_document(project_doc) + + if resolution is None: + msg = "Using default resolution {}x{}" + else: + resolution_width, resolution_height = resolution + msg = "Setting resolution to {}x{}" + + print(msg.format(resolution_width, resolution_height)) + + # set for different renderers + # arnold, vray, redshift, renderman + + renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer").lower() + # handle various renderman names + if renderer.startswith("renderman"): + renderer = "renderman" + + # default attributes are usable for Arnold, Renderman and Redshift + width_attr_name = "defaultResolution.width" + height_attr_name = "defaultResolution.height" + + # Vray has its own way + if renderer == "vray": + width_attr_name = "vraySettings.width" + height_attr_name = "vraySettings.height" + + cmds.setAttr(width_attr_name, resolution_width) + cmds.setAttr(height_attr_name, resolution_height) diff --git a/openpype/hosts/maya/api/customize.py b/openpype/hosts/maya/api/customize.py index c7fb042ead..37fd543315 100644 --- a/openpype/hosts/maya/api/customize.py +++ b/openpype/hosts/maya/api/customize.py @@ -8,10 +8,9 @@ from functools import partial import maya.cmds as mc import maya.mel as mel -from avalon.maya import pipeline from openpype.api import resources from openpype.tools.utils import host_tools - +from .lib import get_main_window log = logging.getLogger(__name__) @@ -76,6 +75,7 @@ def override_component_mask_commands(): def override_toolbox_ui(): """Add custom buttons in Toolbox as replacement for Maya web help icon.""" icons = resources.get_resource("icons") + parent_widget = get_main_window() # Ensure the maya web icon on toolbox exists web_button = "ToolBox|MainToolboxLayout|mayaWebButton" @@ -115,7 +115,7 @@ def override_toolbox_ui(): label="Work Files", image=os.path.join(icons, "workfiles.png"), command=lambda: host_tools.show_workfiles( - parent=pipeline._parent + parent=parent_widget ), width=icon_size, height=icon_size, @@ -130,7 +130,7 @@ def override_toolbox_ui(): label="Loader", image=os.path.join(icons, "loader.png"), command=lambda: host_tools.show_loader( - parent=pipeline._parent, use_context=True + parent=parent_widget, use_context=True ), width=icon_size, height=icon_size, @@ -145,7 +145,7 @@ def override_toolbox_ui(): label="Inventory", image=os.path.join(icons, "inventory.png"), command=lambda: host_tools.show_scene_inventory( - parent=pipeline._parent + parent=parent_widget ), width=icon_size, height=icon_size, diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b236fa7cdb..ff19f72097 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1,7 +1,8 @@ """Standalone helper functions""" -import re import os +import sys +import re import platform import uuid import math @@ -18,16 +19,19 @@ import bson from maya import cmds, mel import maya.api.OpenMaya as om -from avalon import api, maya, io, pipeline -import avalon.maya.lib -import avalon.maya.interactive +from avalon import api, io, pipeline from openpype import lib from openpype.api import get_anatomy_settings +from .commands import reset_frame_range +self = sys.modules[__name__] +self._parent = None + log = logging.getLogger(__name__) +IS_HEADLESS = not hasattr(cmds, "about") or cmds.about(batch=True) ATTRIBUTE_DICT = {"int": {"attributeType": "long"}, "str": {"dataType": "string"}, "unicode": {"dataType": "string"}, @@ -100,6 +104,155 @@ FLOAT_FPS = {23.98, 23.976, 29.97, 47.952, 59.94} RENDERLIKE_INSTANCE_FAMILIES = ["rendering", "vrayscene"] +def get_main_window(): + """Acquire Maya's main window""" + from Qt import QtWidgets + + if self._parent is None: + self._parent = { + widget.objectName(): widget + for widget in QtWidgets.QApplication.topLevelWidgets() + }["MayaWindow"] + return self._parent + + +@contextlib.contextmanager +def suspended_refresh(): + """Suspend viewport refreshes""" + + try: + cmds.refresh(suspend=True) + yield + finally: + cmds.refresh(suspend=False) + + +@contextlib.contextmanager +def maintained_selection(): + """Maintain selection during context + + Example: + >>> scene = cmds.file(new=True, force=True) + >>> node = cmds.createNode("transform", name="Test") + >>> cmds.select("persp") + >>> with maintained_selection(): + ... cmds.select("Test", replace=True) + >>> "Test" in cmds.ls(selection=True) + False + + """ + + previous_selection = cmds.ls(selection=True) + try: + yield + finally: + if previous_selection: + cmds.select(previous_selection, + replace=True, + noExpand=True) + else: + cmds.select(clear=True) + + +def unique_name(name, format="%02d", namespace="", prefix="", suffix=""): + """Return unique `name` + + The function takes into consideration an optional `namespace` + and `suffix`. The suffix is included in evaluating whether a + name exists - such as `name` + "_GRP" - but isn't included + in the returned value. + + If a namespace is provided, only names within that namespace + are considered when evaluating whether the name is unique. + + Arguments: + format (str, optional): The `name` is given a number, this determines + how this number is formatted. Defaults to a padding of 2. + E.g. my_name01, my_name02. + namespace (str, optional): Only consider names within this namespace. + suffix (str, optional): Only consider names with this suffix. + + Example: + >>> name = cmds.createNode("transform", name="MyName") + >>> cmds.objExists(name) + True + >>> unique = unique_name(name) + >>> cmds.objExists(unique) + False + + """ + + iteration = 1 + unique = prefix + (name + format % iteration) + suffix + + while cmds.objExists(namespace + ":" + unique): + iteration += 1 + unique = prefix + (name + format % iteration) + suffix + + if suffix: + return unique[:-len(suffix)] + + return unique + + +def unique_namespace(namespace, format="%02d", prefix="", suffix=""): + """Return unique namespace + + Similar to :func:`unique_name` but evaluating namespaces + as opposed to object names. + + Arguments: + namespace (str): Name of namespace to consider + format (str, optional): Formatting of the given iteration number + suffix (str, optional): Only consider namespaces with this suffix. + + """ + + iteration = 1 + unique = prefix + (namespace + format % iteration) + suffix + + # The `existing` set does not just contain the namespaces but *all* nodes + # within "current namespace". We need all because the namespace could + # also clash with a node name. To be truly unique and valid one needs to + # check against all. + existing = set(cmds.namespaceInfo(listNamespace=True)) + while unique in existing: + iteration += 1 + unique = prefix + (namespace + format % iteration) + suffix + + return unique + + +def read(node): + """Return user-defined attributes from `node`""" + + data = dict() + + for attr in cmds.listAttr(node, userDefined=True) or list(): + try: + value = cmds.getAttr(node + "." + attr, asString=True) + + except RuntimeError: + # For Message type attribute or others that have connections, + # take source node name as value. + source = cmds.listConnections(node + "." + attr, + source=True, + destination=False) + source = cmds.ls(source, long=True) or [None] + value = source[0] + + except ValueError: + # Some attributes cannot be read directly, + # such as mesh and color attributes. These + # are considered non-essential to this + # particular publishing pipeline. + value = None + + data[attr] = value + + return data + + def _get_mel_global(name): """Return the value of a mel global variable""" return mel.eval("$%s = $%s;" % (name, name)) @@ -280,6 +433,73 @@ def shape_from_element(element): return node +def export_alembic(nodes, + file, + frame_range=None, + write_uv=True, + write_visibility=True, + attribute_prefix=None): + """Wrap native MEL command with limited set of arguments + + Arguments: + nodes (list): Long names of nodes to cache + + file (str): Absolute path to output destination + + frame_range (tuple, optional): Start- and end-frame of cache, + default to current animation range. + + write_uv (bool, optional): Whether or not to include UVs, + default to True + + write_visibility (bool, optional): Turn on to store the visibility + state of objects in the Alembic file. Otherwise, all objects are + considered visible, default to True + + attribute_prefix (str, optional): Include all user-defined + attributes with this prefix. + + """ + + if frame_range is None: + frame_range = ( + cmds.playbackOptions(query=True, ast=True), + cmds.playbackOptions(query=True, aet=True) + ) + + options = [ + ("file", file), + ("frameRange", "%s %s" % frame_range), + ] + [("root", mesh) for mesh in nodes] + + if isinstance(attribute_prefix, string_types): + # Include all attributes prefixed with "mb" + # TODO(marcus): This would be a good candidate for + # external registration, so that the developer + # doesn't have to edit this function to modify + # the behavior of Alembic export. + options.append(("attrPrefix", str(attribute_prefix))) + + if write_uv: + options.append(("uvWrite", "")) + + if write_visibility: + options.append(("writeVisibility", "")) + + # Generate MEL command + mel_args = list() + for key, value in options: + mel_args.append("-{0} {1}".format(key, value)) + + mel_args_string = " ".join(mel_args) + mel_cmd = "AbcExport -j \"{0}\"".format(mel_args_string) + + # For debuggability, put the string passed to MEL in the Script editor. + print("mel.eval('%s')" % mel_cmd) + + return mel.eval(mel_cmd) + + def collect_animation_data(fps=False): """Get the basic animation data @@ -305,6 +525,256 @@ def collect_animation_data(fps=False): return data +def imprint(node, data): + """Write `data` to `node` as userDefined attributes + + Arguments: + node (str): Long name of node + data (dict): Dictionary of key/value pairs + + Example: + >>> from maya import cmds + >>> def compute(): + ... return 6 + ... + >>> cube, generator = cmds.polyCube() + >>> imprint(cube, { + ... "regularString": "myFamily", + ... "computedValue": lambda: compute() + ... }) + ... + >>> cmds.getAttr(cube + ".computedValue") + 6 + + """ + + for key, value in data.items(): + + if callable(value): + # Support values evaluated at imprint + value = value() + + if isinstance(value, bool): + add_type = {"attributeType": "bool"} + set_type = {"keyable": False, "channelBox": True} + elif isinstance(value, string_types): + add_type = {"dataType": "string"} + set_type = {"type": "string"} + elif isinstance(value, int): + add_type = {"attributeType": "long"} + set_type = {"keyable": False, "channelBox": True} + elif isinstance(value, float): + add_type = {"attributeType": "double"} + set_type = {"keyable": False, "channelBox": True} + elif isinstance(value, (list, tuple)): + add_type = {"attributeType": "enum", "enumName": ":".join(value)} + set_type = {"keyable": False, "channelBox": True} + value = 0 # enum default + else: + raise TypeError("Unsupported type: %r" % type(value)) + + cmds.addAttr(node, longName=key, **add_type) + cmds.setAttr(node + "." + key, value, **set_type) + + +def serialise_shaders(nodes): + """Generate a shader set dictionary + + Arguments: + nodes (list): Absolute paths to nodes + + Returns: + dictionary of (shader: id) pairs + + Schema: + { + "shader1": ["id1", "id2"], + "shader2": ["id3", "id1"] + } + + Example: + { + "Bazooka_Brothers01_:blinn4SG": [ + "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4922:5001]", + "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4587:4634]", + "f9520572-ac1d-11e6-b39e-3085a99791c9.f[1120:1567]", + "f9520572-ac1d-11e6-b39e-3085a99791c9.f[4251:4362]" + ], + "lambert2SG": [ + "f9520571-ac1d-11e6-9dbb-3085a99791c9" + ] + } + + """ + + valid_nodes = cmds.ls( + nodes, + long=True, + recursive=True, + showType=True, + objectsOnly=True, + type="transform" + ) + + meshes_by_id = {} + for mesh in valid_nodes: + shapes = cmds.listRelatives(valid_nodes[0], + shapes=True, + fullPath=True) or list() + + if shapes: + shape = shapes[0] + if not cmds.nodeType(shape): + continue + + try: + id_ = cmds.getAttr(mesh + ".mbID") + + if id_ not in meshes_by_id: + meshes_by_id[id_] = list() + + meshes_by_id[id_].append(mesh) + + except ValueError: + continue + + meshes_by_shader = dict() + for id_, mesh in meshes_by_id.items(): + shape = cmds.listRelatives(mesh, + shapes=True, + fullPath=True) or list() + + for shader in cmds.listConnections(shape, + type="shadingEngine") or list(): + + # Objects in this group are those that haven't got + # any shaders. These are expected to be managed + # elsewhere, such as by the default model loader. + if shader == "initialShadingGroup": + continue + + if shader not in meshes_by_shader: + meshes_by_shader[shader] = list() + + shaded = cmds.sets(shader, query=True) or list() + meshes_by_shader[shader].extend(shaded) + + shader_by_id = {} + for shader, shaded in meshes_by_shader.items(): + + if shader not in shader_by_id: + shader_by_id[shader] = list() + + for mesh in shaded: + + # Enable shader assignment to faces. + name = mesh.split(".f[")[0] + + transform = name + if cmds.objectType(transform) == "mesh": + transform = cmds.listRelatives(name, parent=True)[0] + + try: + id_ = cmds.getAttr(transform + ".mbID") + shader_by_id[shader].append(mesh.replace(name, id_)) + except KeyError: + continue + + # Remove duplicates + shader_by_id[shader] = list(set(shader_by_id[shader])) + + return shader_by_id + + +def lsattr(attr, value=None): + """Return nodes matching `key` and `value` + + Arguments: + attr (str): Name of Maya attribute + value (object, optional): Value of attribute. If none + is provided, return all nodes with this attribute. + + Example: + >> lsattr("id", "myId") + ["myNode"] + >> lsattr("id") + ["myNode", "myOtherNode"] + + """ + + if value is None: + return cmds.ls("*.%s" % attr, + recursive=True, + objectsOnly=True, + long=True) + return lsattrs({attr: value}) + + +def lsattrs(attrs): + """Return nodes with the given attribute(s). + + Arguments: + attrs (dict): Name and value pairs of expected matches + + Example: + >> # Return nodes with an `age` of five. + >> lsattr({"age": "five"}) + >> # Return nodes with both `age` and `color` of five and blue. + >> lsattr({"age": "five", "color": "blue"}) + + Return: + list: matching nodes. + + """ + + dep_fn = om.MFnDependencyNode() + dag_fn = om.MFnDagNode() + selection_list = om.MSelectionList() + + first_attr = next(iter(attrs)) + + try: + selection_list.add("*.{0}".format(first_attr), + searchChildNamespaces=True) + except RuntimeError as exc: + if str(exc).endswith("Object does not exist"): + return [] + + matches = set() + for i in range(selection_list.length()): + node = selection_list.getDependNode(i) + if node.hasFn(om.MFn.kDagNode): + fn_node = dag_fn.setObject(node) + full_path_names = [path.fullPathName() + for path in fn_node.getAllPaths()] + else: + fn_node = dep_fn.setObject(node) + full_path_names = [fn_node.name()] + + for attr in attrs: + try: + plug = fn_node.findPlug(attr, True) + if plug.asString() != attrs[attr]: + break + except RuntimeError: + break + else: + matches.update(full_path_names) + + return list(matches) + + +@contextlib.contextmanager +def without_extension(): + """Use cmds.file with defaultExtensions=False""" + previous_setting = cmds.file(defaultExtensions=True, query=True) + try: + cmds.file(defaultExtensions=False) + yield + finally: + cmds.file(defaultExtensions=previous_setting) + + @contextlib.contextmanager def attribute_values(attr_values): """Remaps node attributes to values during context. @@ -736,7 +1206,7 @@ def namespaced(namespace, new=True): """ original = cmds.namespaceInfo(cur=True, absoluteName=True) if new: - namespace = avalon.maya.lib.unique_namespace(namespace) + namespace = unique_namespace(namespace) cmds.namespace(add=namespace) try: @@ -1408,7 +1878,7 @@ def assign_look_by_version(nodes, version_id): raise RuntimeError("Could not find LookLoader, this is a bug") # Reference the look file - with maya.maintained_selection(): + with maintained_selection(): container_node = pipeline.load(Loader, look_representation) # Get container members @@ -1947,7 +2417,7 @@ def set_context_settings(): reset_scene_resolution() # Set frame range. - avalon.maya.interactive.reset_frame_range() + reset_frame_range() # Set colorspace set_colorspace() @@ -2386,7 +2856,7 @@ def get_attr_in_layer(attr, layer): def fix_incompatible_containers(): """Return whether the current scene has any outdated content""" - host = avalon.api.registered_host() + host = api.registered_host() for container in host.ls(): loader = container['loader'] diff --git a/openpype/hosts/maya/api/menu.py b/openpype/hosts/maya/api/menu.py index df5058dfd5..5554fce583 100644 --- a/openpype/hosts/maya/api/menu.py +++ b/openpype/hosts/maya/api/menu.py @@ -1,58 +1,144 @@ -import sys import os import logging from Qt import QtWidgets, QtGui +import maya.utils import maya.cmds as cmds -from avalon.maya import pipeline +import avalon.api from openpype.api import BuildWorkfile from openpype.settings import get_project_settings from openpype.tools.utils import host_tools from openpype.hosts.maya.api import lib +from .lib import get_main_window, IS_HEADLESS +from .commands import reset_frame_range log = logging.getLogger(__name__) +MENU_NAME = "op_maya_menu" + def _get_menu(menu_name=None): """Return the menu instance if it currently exists in Maya""" if menu_name is None: - menu_name = pipeline._menu + menu_name = MENU_NAME widgets = {w.objectName(): w for w in QtWidgets.QApplication.allWidgets()} return widgets.get(menu_name) -def deferred(): - def add_build_workfiles_item(): - # Add build first workfile - cmds.menuItem(divider=True, parent=pipeline._menu) +def install(): + if cmds.about(batch=True): + log.info("Skipping openpype.menu initialization in batch mode..") + return + + def deferred(): + from avalon.tools import publish + parent_widget = get_main_window() + cmds.menu( + MENU_NAME, + label=avalon.api.Session["AVALON_LABEL"], + tearOff=True, + parent="MayaWindow" + ) + + # Create context menu + context_label = "{}, {}".format( + avalon.api.Session["AVALON_ASSET"], + avalon.api.Session["AVALON_TASK"] + ) + cmds.menuItem( + "currentContext", + label=context_label, + parent=MENU_NAME, + enable=False + ) + + cmds.setParent("..", menu=True) + + cmds.menuItem(divider=True) + + # Create default items + cmds.menuItem( + "Create...", + command=lambda *args: host_tools.show_creator(parent=parent_widget) + ) + + cmds.menuItem( + "Load...", + command=lambda *args: host_tools.show_loader( + parent=parent_widget, + use_context=True + ) + ) + + cmds.menuItem( + "Publish...", + command=lambda *args: host_tools.show_publish(parent=parent_widget), + image=publish.ICON + ) + + cmds.menuItem( + "Manage...", + command=lambda *args: host_tools.show_scene_inventory( + parent=parent_widget + ) + ) + + cmds.menuItem( + "Library...", + command=lambda *args: host_tools.show_library_loader( + parent=parent_widget + ) + ) + + cmds.menuItem(divider=True) + + cmds.menuItem( + "Work Files...", + command=lambda *args: host_tools.show_workfiles( + parent=parent_widget + ), + ) + + cmds.menuItem( + "Reset Frame Range", + command=reset_frame_range + ) + + cmds.menuItem( + "Reset Resolution", + command=lib.reset_scene_resolution + ) + + cmds.menuItem( + "Set Colorspace", + command=lib.set_colorspace, + ) + cmds.menuItem(divider=True, parent=MENU_NAME) cmds.menuItem( "Build First Workfile", - parent=pipeline._menu, + parent=MENU_NAME, command=lambda *args: BuildWorkfile().process() ) - def add_look_assigner_item(): cmds.menuItem( - "Look assigner", - parent=pipeline._menu, + "Look assigner...", command=lambda *args: host_tools.show_look_assigner( - pipeline._parent + parent_widget ) ) - def add_experimental_item(): cmds.menuItem( "Experimental tools...", - parent=pipeline._menu, command=lambda *args: host_tools.show_experimental_tools_dialog( - pipeline._parent + parent_widget ) ) + cmds.setParent(MENU_NAME, menu=True) def add_scripts_menu(): try: @@ -82,124 +168,13 @@ def deferred(): # apply configuration studio_menu.build_from_configuration(studio_menu, config) - def modify_workfiles(): - # Find the pipeline menu - top_menu = _get_menu() - - # Try to find workfile tool action in the menu - workfile_action = None - for action in top_menu.actions(): - if action.text() == "Work Files": - workfile_action = action - break - - # Add at the top of menu if "Work Files" action was not found - after_action = "" - if workfile_action: - # Use action's object name for `insertAfter` argument - after_action = workfile_action.objectName() - - # Insert action to menu - cmds.menuItem( - "Work Files", - parent=pipeline._menu, - command=lambda *args: host_tools.show_workfiles(pipeline._parent), - insertAfter=after_action - ) - - # Remove replaced action - if workfile_action: - top_menu.removeAction(workfile_action) - - def modify_resolution(): - # Find the pipeline menu - top_menu = _get_menu() - - # Try to find resolution tool action in the menu - resolution_action = None - for action in top_menu.actions(): - if action.text() == "Reset Resolution": - resolution_action = action - break - - # Add at the top of menu if "Work Files" action was not found - after_action = "" - if resolution_action: - # Use action's object name for `insertAfter` argument - after_action = resolution_action.objectName() - - # Insert action to menu - cmds.menuItem( - "Reset Resolution", - parent=pipeline._menu, - command=lambda *args: lib.reset_scene_resolution(), - insertAfter=after_action - ) - - # Remove replaced action - if resolution_action: - top_menu.removeAction(resolution_action) - - def remove_project_manager(): - top_menu = _get_menu() - - # Try to find "System" menu action in the menu - system_menu = None - for action in top_menu.actions(): - if action.text() == "System": - system_menu = action - break - - if system_menu is None: - return - - # Try to find "Project manager" action in "System" menu - project_manager_action = None - for action in system_menu.menu().children(): - if hasattr(action, "text") and action.text() == "Project Manager": - project_manager_action = action - break - - # Remove "Project manager" action if was found - if project_manager_action is not None: - system_menu.menu().removeAction(project_manager_action) - - def add_colorspace(): - # Find the pipeline menu - top_menu = _get_menu() - - # Try to find workfile tool action in the menu - workfile_action = None - for action in top_menu.actions(): - if action.text() == "Reset Resolution": - workfile_action = action - break - - # Add at the top of menu if "Work Files" action was not found - after_action = "" - if workfile_action: - # Use action's object name for `insertAfter` argument - after_action = workfile_action.objectName() - - # Insert action to menu - cmds.menuItem( - "Set Colorspace", - parent=pipeline._menu, - command=lambda *args: lib.set_colorspace(), - insertAfter=after_action - ) - - log.info("Attempting to install scripts menu ...") - - # add_scripts_menu() - add_build_workfiles_item() - add_look_assigner_item() - add_experimental_item() - modify_workfiles() - modify_resolution() - remove_project_manager() - add_colorspace() - add_scripts_menu() + # Allow time for uninstallation to finish. + # We use Maya's executeDeferred instead of QTimer.singleShot + # so that it only gets called after Maya UI has initialized too. + # This is crucial with Maya 2020+ which initializes without UI + # first as a QCoreApplication + maya.utils.executeDeferred(deferred) + cmds.evalDeferred(add_scripts_menu, lowestPriority=True) def uninstall(): @@ -214,18 +189,27 @@ def uninstall(): log.error(e) -def install(): - if cmds.about(batch=True): - log.info("Skipping openpype.menu initialization in batch mode..") - return - - # Allow time for uninstallation to finish. - cmds.evalDeferred(deferred, lowestPriority=True) - - def popup(): """Pop-up the existing menu near the mouse cursor.""" menu = _get_menu() cursor = QtGui.QCursor() point = cursor.pos() menu.exec_(point) + + +def update_menu_task_label(): + """Update the task label in Avalon menu to current session""" + + if IS_HEADLESS: + return + + object_name = "{}|currentContext".format(MENU_NAME) + if not cmds.menuItem(object_name, query=True, exists=True): + log.warning("Can't find menuItem: {}".format(object_name)) + return + + label = "{}, {}".format( + avalon.api.Session["AVALON_ASSET"], + avalon.api.Session["AVALON_TASK"] + ) + cmds.menuItem(object_name, edit=True, label=label) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py new file mode 100644 index 0000000000..a5455d0083 --- /dev/null +++ b/openpype/hosts/maya/api/pipeline.py @@ -0,0 +1,594 @@ +import os +import sys +import errno +import logging +import contextlib + +from maya import utils, cmds, OpenMaya +import maya.api.OpenMaya as om + +import pyblish.api +import avalon.api + +from avalon.lib import find_submodule +from avalon.pipeline import AVALON_CONTAINER_ID + +import openpype.hosts.maya +from openpype.tools.utils import host_tools +from openpype.lib import any_outdated +from openpype.lib.path_tools import HostDirmap +from openpype.hosts.maya.lib import copy_workspace_mel +from . import menu, lib + +log = logging.getLogger("openpype.hosts.maya") + +HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.maya.__file__)) +PLUGINS_DIR = os.path.join(HOST_DIR, "plugins") +PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish") +LOAD_PATH = os.path.join(PLUGINS_DIR, "load") +CREATE_PATH = os.path.join(PLUGINS_DIR, "create") +INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory") + +AVALON_CONTAINERS = ":AVALON_CONTAINERS" + +self = sys.modules[__name__] +self._ignore_lock = False +self._events = {} + + +def install(): + from openpype.settings import get_project_settings + + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + # process path mapping + dirmap_processor = MayaDirmap("maya", project_settings) + dirmap_processor.process_dirmap() + + pyblish.api.register_plugin_path(PUBLISH_PATH) + pyblish.api.register_host("mayabatch") + pyblish.api.register_host("mayapy") + pyblish.api.register_host("maya") + + avalon.api.register_plugin_path(avalon.api.Loader, LOAD_PATH) + avalon.api.register_plugin_path(avalon.api.Creator, CREATE_PATH) + avalon.api.register_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) + log.info(PUBLISH_PATH) + + log.info("Installing callbacks ... ") + avalon.api.on("init", on_init) + + # Callbacks below are not required for headless mode, the `init` however + # is important to load referenced Alembics correctly at rendertime. + if lib.IS_HEADLESS: + log.info(("Running in headless mode, skipping Maya " + "save/open/new callback installation..")) + return + + _set_project() + _register_callbacks() + + menu.install() + + avalon.api.on("save", on_save) + avalon.api.on("open", on_open) + avalon.api.on("new", on_new) + avalon.api.before("save", on_before_save) + avalon.api.on("taskChanged", on_task_changed) + avalon.api.on("before.workfile.save", before_workfile_save) + + log.info("Setting default family states for loader..") + avalon.api.data["familiesStateToggled"] = ["imagesequence"] + + +def _set_project(): + """Sets the maya project to the current Session's work directory. + + Returns: + None + + """ + workdir = avalon.api.Session["AVALON_WORKDIR"] + + try: + os.makedirs(workdir) + except OSError as e: + # An already existing working directory is fine. + if e.errno == errno.EEXIST: + pass + else: + raise + + cmds.workspace(workdir, openWorkspace=True) + + +def _register_callbacks(): + for handler, event in self._events.copy().items(): + if event is None: + continue + + try: + OpenMaya.MMessage.removeCallback(event) + self._events[handler] = None + except RuntimeError as e: + log.info(e) + + self._events[_on_scene_save] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save + ) + + self._events[_before_scene_save] = OpenMaya.MSceneMessage.addCheckCallback( + OpenMaya.MSceneMessage.kBeforeSaveCheck, _before_scene_save + ) + + self._events[_on_scene_new] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterNew, _on_scene_new + ) + + self._events[_on_maya_initialized] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kMayaInitialized, _on_maya_initialized + ) + + self._events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterOpen, _on_scene_open + ) + + log.info("Installed event handler _on_scene_save..") + log.info("Installed event handler _before_scene_save..") + log.info("Installed event handler _on_scene_new..") + log.info("Installed event handler _on_maya_initialized..") + log.info("Installed event handler _on_scene_open..") + + +def _on_maya_initialized(*args): + avalon.api.emit("init", args) + + if cmds.about(batch=True): + log.warning("Running batch mode ...") + return + + # Keep reference to the main Window, once a main window exists. + lib.get_main_window() + + +def _on_scene_new(*args): + avalon.api.emit("new", args) + + +def _on_scene_save(*args): + avalon.api.emit("save", args) + + +def _on_scene_open(*args): + avalon.api.emit("open", args) + + +def _before_scene_save(return_code, client_data): + + # Default to allowing the action. Registered + # callbacks can optionally set this to False + # in order to block the operation. + OpenMaya.MScriptUtil.setBool(return_code, True) + + avalon.api.emit("before_save", [return_code, client_data]) + + +def uninstall(): + pyblish.api.deregister_plugin_path(PUBLISH_PATH) + pyblish.api.deregister_host("mayabatch") + pyblish.api.deregister_host("mayapy") + pyblish.api.deregister_host("maya") + + avalon.api.deregister_plugin_path(avalon.api.Loader, LOAD_PATH) + avalon.api.deregister_plugin_path(avalon.api.Creator, CREATE_PATH) + avalon.api.deregister_plugin_path(avalon.api.InventoryAction, INVENTORY_PATH) + + menu.uninstall() + + +def lock(): + """Lock scene + + Add an invisible node to your Maya scene with the name of the + current file, indicating that this file is "locked" and cannot + be modified any further. + + """ + + if not cmds.objExists("lock"): + with lib.maintained_selection(): + cmds.createNode("objectSet", name="lock") + cmds.addAttr("lock", ln="basename", dataType="string") + + # Permanently hide from outliner + cmds.setAttr("lock.verticesOnlySet", True) + + fname = cmds.file(query=True, sceneName=True) + basename = os.path.basename(fname) + cmds.setAttr("lock.basename", basename, type="string") + + +def unlock(): + """Permanently unlock a locked scene + + Doesn't throw an error if scene is already unlocked. + + """ + + try: + cmds.delete("lock") + except ValueError: + pass + + +def is_locked(): + """Query whether current scene is locked""" + fname = cmds.file(query=True, sceneName=True) + basename = os.path.basename(fname) + + if self._ignore_lock: + return False + + try: + return cmds.getAttr("lock.basename") == basename + except ValueError: + return False + + +@contextlib.contextmanager +def lock_ignored(): + """Context manager for temporarily ignoring the lock of a scene + + The purpose of this function is to enable locking a scene and + saving it with the lock still in place. + + Example: + >>> with lock_ignored(): + ... pass # Do things without lock + + """ + + self._ignore_lock = True + + try: + yield + finally: + self._ignore_lock = False + + +def parse_container(container): + """Return the container node's full container data. + + Args: + container (str): A container node name. + + Returns: + dict: The container schema data for this container node. + + """ + data = lib.read(container) + + # Backwards compatibility pre-schemas for containers + data["schema"] = data.get("schema", "openpype:container-1.0") + + # Append transient data + data["objectName"] = container + + return data + + +def _ls(): + """Yields Avalon container node names. + + Used by `ls()` to retrieve the nodes and then query the full container's + data. + + Yields: + str: Avalon container node name (objectSet) + + """ + + def _maya_iterate(iterator): + """Helper to iterate a maya iterator""" + while not iterator.isDone(): + yield iterator.thisNode() + iterator.next() + + ids = {AVALON_CONTAINER_ID, + # Backwards compatibility + "pyblish.mindbender.container"} + + # Iterate over all 'set' nodes in the scene to detect whether + # they have the avalon container ".id" attribute. + fn_dep = om.MFnDependencyNode() + iterator = om.MItDependencyNodes(om.MFn.kSet) + for mobject in _maya_iterate(iterator): + if mobject.apiTypeStr != "kSet": + # Only match by exact type + continue + + fn_dep.setObject(mobject) + if not fn_dep.hasAttribute("id"): + continue + + plug = fn_dep.findPlug("id", True) + value = plug.asString() + if value in ids: + yield fn_dep.name() + + +def ls(): + """Yields containers from active Maya scene + + This is the host-equivalent of api.ls(), but instead of listing + assets on disk, it lists assets already loaded in Maya; once loaded + they are called 'containers' + + Yields: + dict: container + + """ + container_names = _ls() + + has_metadata_collector = False + config_host = find_submodule(avalon.api.registered_config(), "maya") + if hasattr(config_host, "collect_container_metadata"): + has_metadata_collector = True + + for container in sorted(container_names): + data = parse_container(container) + + # Collect custom data if attribute is present + if has_metadata_collector: + metadata = config_host.collect_container_metadata(container) + data.update(metadata) + + yield data + + +def containerise(name, + namespace, + nodes, + context, + loader=None, + suffix="CON"): + """Bundle `nodes` into an assembly and imprint it with metadata + + Containerisation enables a tracking of version, author and origin + for loaded assets. + + Arguments: + name (str): Name of resulting assembly + namespace (str): Namespace under which to host container + nodes (list): Long names of nodes to containerise + context (dict): Asset information + loader (str, optional): Name of loader used to produce this container. + suffix (str, optional): Suffix of container, defaults to `_CON`. + + Returns: + container (str): Name of container assembly + + """ + container = cmds.sets(nodes, name="%s_%s_%s" % (namespace, name, suffix)) + + data = [ + ("schema", "openpype:container-2.0"), + ("id", AVALON_CONTAINER_ID), + ("name", name), + ("namespace", namespace), + ("loader", str(loader)), + ("representation", context["representation"]["_id"]), + ] + + for key, value in data: + if not value: + continue + + if isinstance(value, (int, float)): + cmds.addAttr(container, longName=key, attributeType="short") + cmds.setAttr(container + "." + key, value) + + else: + cmds.addAttr(container, longName=key, dataType="string") + cmds.setAttr(container + "." + key, value, type="string") + + main_container = cmds.ls(AVALON_CONTAINERS, type="objectSet") + if not main_container: + main_container = cmds.sets(empty=True, name=AVALON_CONTAINERS) + + # Implement #399: Maya 2019+ hide AVALON_CONTAINERS on creation.. + if cmds.attributeQuery("hiddenInOutliner", + node=main_container, + exists=True): + cmds.setAttr(main_container + ".hiddenInOutliner", True) + else: + main_container = main_container[0] + + cmds.sets(container, addElement=main_container) + + # Implement #399: Maya 2019+ hide containers in outliner + if cmds.attributeQuery("hiddenInOutliner", + node=container, + exists=True): + cmds.setAttr(container + ".hiddenInOutliner", True) + + return container + + +def on_init(_): + log.info("Running callback on init..") + + def safe_deferred(fn): + """Execute deferred the function in a try-except""" + + def _fn(): + """safely call in deferred callback""" + try: + fn() + except Exception as exc: + print(exc) + + try: + utils.executeDeferred(_fn) + except Exception as exc: + print(exc) + + # Force load Alembic so referenced alembics + # work correctly on scene open + cmds.loadPlugin("AbcImport", quiet=True) + cmds.loadPlugin("AbcExport", quiet=True) + + # Force load objExport plug-in (requested by artists) + cmds.loadPlugin("objExport", quiet=True) + + from .customize import ( + override_component_mask_commands, + override_toolbox_ui + ) + safe_deferred(override_component_mask_commands) + + launch_workfiles = os.environ.get("WORKFILES_STARTUP") + + if launch_workfiles: + safe_deferred(host_tools.show_workfiles) + + if not lib.IS_HEADLESS: + safe_deferred(override_toolbox_ui) + + +def on_before_save(return_code, _): + """Run validation for scene's FPS prior to saving""" + return lib.validate_fps() + + +def on_save(_): + """Automatically add IDs to new nodes + + Any transform of a mesh, without an existing ID, is given one + automatically on file save. + """ + + log.info("Running callback on save..") + + # # Update current task for the current scene + # update_task_from_path(cmds.file(query=True, sceneName=True)) + + # Generate ids of the current context on nodes in the scene + nodes = lib.get_id_required_nodes(referenced_nodes=False) + for node, new_id in lib.generate_ids(nodes): + lib.set_id(node, new_id, overwrite=False) + + +def on_open(_): + """On scene open let's assume the containers have changed.""" + + from Qt import QtWidgets + from openpype.widgets import popup + + cmds.evalDeferred( + "from openpype.hosts.maya.api import lib;" + "lib.remove_render_layer_observer()") + cmds.evalDeferred( + "from openpype.hosts.maya.api import lib;" + "lib.add_render_layer_observer()") + cmds.evalDeferred( + "from openpype.hosts.maya.api import lib;" + "lib.add_render_layer_change_observer()") + # # Update current task for the current scene + # update_task_from_path(cmds.file(query=True, sceneName=True)) + + # Validate FPS after update_task_from_path to + # ensure it is using correct FPS for the asset + lib.validate_fps() + lib.fix_incompatible_containers() + + if any_outdated(): + log.warning("Scene has outdated content.") + + # Find maya main window + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + parent = top_level_widgets.get("MayaWindow", None) + + if parent is None: + log.info("Skipping outdated content pop-up " + "because Maya window can't be found.") + else: + + # Show outdated pop-up + def _on_show_inventory(): + host_tools.show_scene_inventory(parent=parent) + + dialog = popup.Popup(parent=parent) + dialog.setWindowTitle("Maya scene has outdated content") + dialog.setMessage("There are outdated containers in " + "your Maya scene.") + dialog.on_show.connect(_on_show_inventory) + dialog.show() + + +def on_new(_): + """Set project resolution and fps when create a new file""" + log.info("Running callback on new..") + with lib.suspended_refresh(): + cmds.evalDeferred( + "from openpype.hosts.maya.api import lib;" + "lib.remove_render_layer_observer()") + cmds.evalDeferred( + "from openpype.hosts.maya.api import lib;" + "lib.add_render_layer_observer()") + cmds.evalDeferred( + "from openpype.hosts.maya.api import lib;" + "lib.add_render_layer_change_observer()") + lib.set_context_settings() + + +def on_task_changed(*args): + """Wrapped function of app initialize and maya's on task changed""" + # Run + menu.update_menu_task_label() + + workdir = avalon.api.Session["AVALON_WORKDIR"] + if os.path.exists(workdir): + log.info("Updating Maya workspace for task change to %s", workdir) + + _set_project() + + # Set Maya fileDialog's start-dir to /scenes + frule_scene = cmds.workspace(fileRuleEntry="scene") + cmds.optionVar(stringValue=("browserLocationmayaBinaryscene", + workdir + "/" + frule_scene)) + + else: + log.warning(( + "Can't set project for new context because path does not exist: {}" + ).format(workdir)) + + with lib.suspended_refresh(): + lib.set_context_settings() + lib.update_content_on_context_change() + + msg = " project: {}\n asset: {}\n task:{}".format( + avalon.api.Session["AVALON_PROJECT"], + avalon.api.Session["AVALON_ASSET"], + avalon.api.Session["AVALON_TASK"] + ) + + lib.show_message( + "Context was changed", + ("Context was changed to:\n{}".format(msg)), + ) + + +def before_workfile_save(event): + workdir_path = event.workdir_path + if workdir_path: + copy_workspace_mel(workdir_path) + + +class MayaDirmap(HostDirmap): + def on_enable_dirmap(self): + cmds.dirmap(en=True) + + def dirmap_routine(self, source_path, destination_path): + cmds.dirmap(m=(source_path, destination_path)) + cmds.dirmap(m=(destination_path, source_path)) diff --git a/openpype/hosts/maya/api/plugin.py b/openpype/hosts/maya/api/plugin.py index a5f03cd576..64e910627d 100644 --- a/openpype/hosts/maya/api/plugin.py +++ b/openpype/hosts/maya/api/plugin.py @@ -1,8 +1,14 @@ +import os + +from maya import cmds + from avalon import api from avalon.vendor import qargparse -import avalon.maya from openpype.api import PypeCreatorMixin +from .pipeline import containerise +from . import lib + def get_reference_node(members, log=None): """Get the reference node from the container members @@ -14,8 +20,6 @@ def get_reference_node(members, log=None): """ - from maya import cmds - # Collect the references without .placeHolderList[] attributes as # unique entries (objects only) and skipping the sharedReferenceNode. references = set() @@ -61,8 +65,6 @@ def get_reference_node_parents(ref): list: The upstream parent reference nodes. """ - from maya import cmds - parent = cmds.referenceQuery(ref, referenceNode=True, parent=True) @@ -75,11 +77,25 @@ def get_reference_node_parents(ref): return parents -class Creator(PypeCreatorMixin, avalon.maya.Creator): - pass +class Creator(PypeCreatorMixin, api.Creator): + def process(self): + nodes = list() + + with lib.undo_chunk(): + if (self.options or {}).get("useSelection"): + nodes = cmds.ls(selection=True) + + instance = cmds.sets(nodes, name=self.name) + lib.imprint(instance, self.data) + + return instance -class ReferenceLoader(api.Loader): +class Loader(api.Loader): + hosts = ["maya"] + + +class ReferenceLoader(Loader): """A basic ReferenceLoader for Maya This will implement the basic behavior for a loader to inherit from that @@ -117,11 +133,6 @@ class ReferenceLoader(api.Loader): namespace=None, options=None ): - - import os - from avalon.maya import lib - from avalon.maya.pipeline import containerise - assert os.path.exists(self.fname), "%s does not exist." % self.fname asset = context['asset'] @@ -182,8 +193,6 @@ class ReferenceLoader(api.Loader): def update(self, container, representation): - - import os from maya import cmds node = container["objectName"] diff --git a/openpype/hosts/maya/api/setdress.py b/openpype/hosts/maya/api/setdress.py index 4f826b8fde..1a7c3933a1 100644 --- a/openpype/hosts/maya/api/setdress.py +++ b/openpype/hosts/maya/api/setdress.py @@ -9,8 +9,10 @@ import six from maya import cmds from avalon import api, io -from avalon.maya.lib import unique_namespace -from openpype.hosts.maya.api.lib import matrix_equals +from openpype.hosts.maya.api.lib import ( + matrix_equals, + unique_namespace +) log = logging.getLogger("PackageLoader") @@ -239,7 +241,7 @@ def get_contained_containers(container): """ import avalon.schema - from avalon.maya.pipeline import parse_container + from .pipeline import parse_container # Get avalon containers in this package setdress container containers = [] diff --git a/openpype/hosts/maya/api/workio.py b/openpype/hosts/maya/api/workio.py new file mode 100644 index 0000000000..698c48e81e --- /dev/null +++ b/openpype/hosts/maya/api/workio.py @@ -0,0 +1,67 @@ +"""Host API required Work Files tool""" +import os +from maya import cmds +from avalon import api + + +def file_extensions(): + return api.HOST_WORKFILE_EXTENSIONS["maya"] + + +def has_unsaved_changes(): + return cmds.file(query=True, modified=True) + + +def save_file(filepath): + cmds.file(rename=filepath) + ext = os.path.splitext(filepath)[1] + if ext == ".mb": + file_type = "mayaBinary" + else: + file_type = "mayaAscii" + cmds.file(save=True, type=file_type) + + +def open_file(filepath): + return cmds.file(filepath, open=True, force=True) + + +def current_file(): + + current_filepath = cmds.file(query=True, sceneName=True) + if not current_filepath: + return None + + return current_filepath + + +def work_root(session): + work_dir = session["AVALON_WORKDIR"] + scene_dir = None + + # Query scene file rule from workspace.mel if it exists in WORKDIR + # We are parsing the workspace.mel manually as opposed to temporarily + # setting the Workspace in Maya in a context manager since Maya had a + # tendency to crash on frequently changing the workspace when this + # function was called many times as one scrolled through Work Files assets. + workspace_mel = os.path.join(work_dir, "workspace.mel") + if os.path.exists(workspace_mel): + scene_rule = 'workspace -fr "scene" ' + # We need to use builtins as `open` is overridden by the workio API + open_file = __builtins__["open"] + with open_file(workspace_mel, "r") as f: + for line in f: + if line.strip().startswith(scene_rule): + # remainder == "rule"; + remainder = line[len(scene_rule):] + # scene_dir == rule + scene_dir = remainder.split('"')[1] + else: + # We can't query a workspace that does not exist + # so we return similar to what we do in other hosts. + scene_dir = session.get("AVALON_SCENEDIR") + + if scene_dir: + return os.path.join(work_dir, scene_dir) + else: + return work_dir diff --git a/openpype/hosts/maya/plugins/inventory/import_modelrender.py b/openpype/hosts/maya/plugins/inventory/import_modelrender.py index e3cad4cf2e..119edccb7a 100644 --- a/openpype/hosts/maya/plugins/inventory/import_modelrender.py +++ b/openpype/hosts/maya/plugins/inventory/import_modelrender.py @@ -1,4 +1,9 @@ -from avalon import api, io +import json +from avalon import api, io, pipeline +from openpype.hosts.maya.api.lib import ( + maintained_selection, + apply_shaders +) class ImportModelRender(api.InventoryAction): @@ -49,10 +54,8 @@ class ImportModelRender(api.InventoryAction): Returns: None """ - import json + from maya import cmds - from avalon import maya, io, pipeline - from openpype.hosts.maya.api import lib # Get representations of shader file and relationships look_repr = io.find_one({ @@ -77,7 +80,7 @@ class ImportModelRender(api.InventoryAction): json_file = pipeline.get_representation_path_from_context(context) # Import the look file - with maya.maintained_selection(): + with maintained_selection(): shader_nodes = cmds.file(maya_file, i=True, # import returnNewNodes=True) @@ -89,4 +92,4 @@ class ImportModelRender(api.InventoryAction): relationships = json.load(f) # Assign relationships - lib.apply_shaders(relationships, shader_nodes, nodes) + apply_shaders(relationships, shader_nodes, nodes) diff --git a/openpype/hosts/maya/plugins/load/_load_animation.py b/openpype/hosts/maya/plugins/load/_load_animation.py index b1784f1590..bce1f0fc67 100644 --- a/openpype/hosts/maya/plugins/load/_load_animation.py +++ b/openpype/hosts/maya/plugins/load/_load_animation.py @@ -17,7 +17,7 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def process_reference(self, context, name, namespace, data): import maya.cmds as cmds - from avalon import maya + from openpype.hosts.maya.api.lib import unique_namespace cmds.loadPlugin("AbcImport.mll", quiet=True) # Prevent identical alembic nodes from being shared @@ -27,9 +27,11 @@ class AbcLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # Assuming name is subset name from the animation, we split the number # suffix from the name to ensure the namespace is unique name = name.split("_")[0] - namespace = maya.unique_namespace("{}_".format(name), - format="%03d", - suffix="_abc") + namespace = unique_namespace( + "{}_".format(name), + format="%03d", + suffix="_abc" + ) # hero_001 (abc) # asset_counter{optional} diff --git a/openpype/hosts/maya/plugins/load/actions.py b/openpype/hosts/maya/plugins/load/actions.py index 1a9adf6142..1cc7ee0c03 100644 --- a/openpype/hosts/maya/plugins/load/actions.py +++ b/openpype/hosts/maya/plugins/load/actions.py @@ -3,6 +3,10 @@ """ from avalon import api +from openpype.hosts.maya.api.lib import ( + maintained_selection, + unique_namespace +) class SetFrameRangeLoader(api.Loader): @@ -98,22 +102,19 @@ class ImportMayaLoader(api.Loader): def load(self, context, name=None, namespace=None, data=None): import maya.cmds as cmds - from avalon import maya - from avalon.maya import lib - choice = self.display_warning() if choice is False: return asset = context['asset'] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset["name"] + "_", prefix="_" if asset["name"][0].isdigit() else "", suffix="_", ) - with maya.maintained_selection(): + with maintained_selection(): cmds.file(self.fname, i=True, preserveReferences=True, diff --git a/openpype/hosts/maya/plugins/load/load_ass.py b/openpype/hosts/maya/plugins/load/load_ass.py index 891f21916c..18b34d2233 100644 --- a/openpype/hosts/maya/plugins/load/load_ass.py +++ b/openpype/hosts/maya/plugins/load/load_ass.py @@ -1,9 +1,15 @@ +import os +import clique + from avalon import api +from openpype.api import get_project_settings import openpype.hosts.maya.api.plugin from openpype.hosts.maya.api.plugin import get_reference_node -import os -from openpype.api import get_project_settings -import clique +from openpype.hosts.maya.api.lib import ( + maintained_selection, + unique_namespace +) +from openpype.hosts.maya.api.pipeline import containerise class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -20,7 +26,6 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def process_reference(self, context, name, namespace, options): import maya.cmds as cmds - from avalon import maya import pymel.core as pm version = context['version'] @@ -35,7 +40,7 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): except ValueError: family = "ass" - with maya.maintained_selection(): + with maintained_selection(): groupName = "{}:{}".format(namespace, name) path = self.fname @@ -95,8 +100,6 @@ class AssProxyLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self.update(container, representation) def update(self, container, representation): - - import os from maya import cmds import pymel.core as pm @@ -175,8 +178,6 @@ class AssStandinLoader(api.Loader): def load(self, context, name, namespace, options): import maya.cmds as cmds - import avalon.maya.lib as lib - from avalon.maya.pipeline import containerise import mtoa.ui.arnoldmenu import pymel.core as pm @@ -188,7 +189,7 @@ class AssStandinLoader(api.Loader): frameStart = version_data.get("frameStart", None) asset = context['asset']['name'] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset + "_", prefix="_" if asset[0].isdigit() else "", suffix="_", diff --git a/openpype/hosts/maya/plugins/load/load_assembly.py b/openpype/hosts/maya/plugins/load/load_assembly.py index 2f0ca3922e..0151da7253 100644 --- a/openpype/hosts/maya/plugins/load/load_assembly.py +++ b/openpype/hosts/maya/plugins/load/load_assembly.py @@ -13,11 +13,11 @@ class AssemblyLoader(api.Loader): def load(self, context, name, namespace, data): - from avalon.maya.pipeline import containerise - from avalon.maya import lib + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace asset = context['asset']['name'] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset + "_", prefix="_" if asset[0].isdigit() else "", suffix="_", @@ -25,9 +25,11 @@ class AssemblyLoader(api.Loader): from openpype.hosts.maya.api import setdress - containers = setdress.load_package(filepath=self.fname, - name=name, - namespace=namespace) + containers = setdress.load_package( + filepath=self.fname, + name=name, + namespace=namespace + ) self[:] = containers diff --git a/openpype/hosts/maya/plugins/load/load_audio.py b/openpype/hosts/maya/plugins/load/load_audio.py index 0611dcc189..99f1f7c172 100644 --- a/openpype/hosts/maya/plugins/load/load_audio.py +++ b/openpype/hosts/maya/plugins/load/load_audio.py @@ -1,7 +1,7 @@ -from avalon import api, io -from avalon.maya.pipeline import containerise -from avalon.maya import lib from maya import cmds, mel +from avalon import api, io +from openpype.hosts.maya.api.pipeline import containerise +from openpype.hosts.maya.api.lib import unique_namespace class AudioLoader(api.Loader): @@ -27,7 +27,7 @@ class AudioLoader(api.Loader): ) asset = context["asset"]["name"] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset + "_", prefix="_" if asset[0].isdigit() else "", suffix="_", diff --git a/openpype/hosts/maya/plugins/load/load_gpucache.py b/openpype/hosts/maya/plugins/load/load_gpucache.py index 444f98f22e..2e0b7bb810 100644 --- a/openpype/hosts/maya/plugins/load/load_gpucache.py +++ b/openpype/hosts/maya/plugins/load/load_gpucache.py @@ -17,11 +17,11 @@ class GpuCacheLoader(api.Loader): def load(self, context, name, namespace, data): import maya.cmds as cmds - import avalon.maya.lib as lib - from avalon.maya.pipeline import containerise + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace asset = context['asset']['name'] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset + "_", prefix="_" if asset[0].isdigit() else "", suffix="_", diff --git a/openpype/hosts/maya/plugins/load/load_image_plane.py b/openpype/hosts/maya/plugins/load/load_image_plane.py index 0652147324..8e33f51389 100644 --- a/openpype/hosts/maya/plugins/load/load_image_plane.py +++ b/openpype/hosts/maya/plugins/load/load_image_plane.py @@ -1,8 +1,9 @@ -from avalon import api, io -from avalon.maya.pipeline import containerise -from avalon.maya import lib from Qt import QtWidgets, QtCore +from avalon import api, io +from openpype.hosts.maya.api.pipeline import containerise +from openpype.hosts.maya.api.lib import unique_namespace + from maya import cmds @@ -88,7 +89,7 @@ class ImagePlaneLoader(api.Loader): new_nodes = [] image_plane_depth = 1000 asset = context['asset']['name'] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset + "_", prefix="_" if asset[0].isdigit() else "", suffix="_", diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index 8e14778fd2..ef1076f2cb 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -1,13 +1,15 @@ # -*- coding: utf-8 -*- """Look loader.""" -import openpype.hosts.maya.api.plugin -from avalon import api, io import json -import openpype.hosts.maya.api.lib from collections import defaultdict -from openpype.widgets.message_window import ScrollMessageBox + from Qt import QtWidgets +from avalon import api, io +import openpype.hosts.maya.api.plugin +from openpype.hosts.maya.api import lib +from openpype.widgets.message_window import ScrollMessageBox + from openpype.hosts.maya.api.plugin import get_reference_node @@ -36,9 +38,8 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): """ import maya.cmds as cmds - from avalon import maya - with maya.maintained_selection(): + with lib.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, reference=True, @@ -140,9 +141,7 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): cmds.file(cr=reference_node) # cleanReference # reapply shading groups from json representation on orig nodes - openpype.hosts.maya.api.lib.apply_shaders(json_data, - shader_nodes, - orig_nodes) + lib.apply_shaders(json_data, shader_nodes, orig_nodes) msg = ["During reference update some edits failed.", "All successful edits were kept intact.\n", @@ -159,8 +158,8 @@ class LookLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # region compute lookup nodes_by_id = defaultdict(list) for n in nodes: - nodes_by_id[openpype.hosts.maya.api.lib.get_id(n)].append(n) - openpype.hosts.maya.api.lib.apply_attributes(attributes, nodes_by_id) + nodes_by_id[lib.get_id(n)].append(n) + lib.apply_attributes(attributes, nodes_by_id) # Update metadata cmds.setAttr("{}.representation".format(node), diff --git a/openpype/hosts/maya/plugins/load/load_redshift_proxy.py b/openpype/hosts/maya/plugins/load/load_redshift_proxy.py index 4c6a187bc3..fd2ae0f1d3 100644 --- a/openpype/hosts/maya/plugins/load/load_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/load/load_redshift_proxy.py @@ -1,11 +1,18 @@ # -*- coding: utf-8 -*- """Loader for Redshift proxy.""" -from avalon.maya import lib +import os +import clique + +import maya.cmds as cmds + from avalon import api from openpype.api import get_project_settings -import os -import maya.cmds as cmds -import clique +from openpype.hosts.maya.api.lib import ( + namespaced, + maintained_selection, + unique_namespace +) +from openpype.hosts.maya.api.pipeline import containerise class RedshiftProxyLoader(api.Loader): @@ -21,17 +28,13 @@ class RedshiftProxyLoader(api.Loader): def load(self, context, name=None, namespace=None, options=None): """Plugin entry point.""" - - from avalon.maya.pipeline import containerise - from openpype.hosts.maya.api.lib import namespaced - try: family = context["representation"]["context"]["family"] except ValueError: family = "redshiftproxy" asset_name = context['asset']["name"] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset_name + "_", prefix="_" if asset_name[0].isdigit() else "", suffix="_", @@ -40,7 +43,7 @@ class RedshiftProxyLoader(api.Loader): # Ensure Redshift for Maya is loaded. cmds.loadPlugin("redshift4maya", quiet=True) - with lib.maintained_selection(): + with maintained_selection(): cmds.namespace(addNamespace=namespace) with namespaced(namespace, new=False): nodes, group_node = self.create_rs_proxy( diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index 2cc24f1360..0565b0b95c 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -1,9 +1,10 @@ -import openpype.hosts.maya.api.plugin -from avalon import api, maya -from maya import cmds import os +from maya import cmds +from avalon import api from openpype.api import get_project_settings from openpype.lib import get_creator_by_name +import openpype.hosts.maya.api.plugin +from openpype.hosts.maya.api.lib import maintained_selection class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -32,7 +33,6 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def process_reference(self, context, name, namespace, options): import maya.cmds as cmds - from avalon import maya import pymel.core as pm try: @@ -44,7 +44,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # True by default to keep legacy behaviours attach_to_root = options.get("attach_to_root", True) - with maya.maintained_selection(): + with maintained_selection(): cmds.loadPlugin("AbcImport.mll", quiet=True) nodes = cmds.file(self.fname, namespace=namespace, @@ -149,7 +149,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): # Create the animation instance creator_plugin = get_creator_by_name(self.animation_creator_name) - with maya.maintained_selection(): + with maintained_selection(): cmds.select([output, controls] + roots, noExpand=True) api.create( creator_plugin, diff --git a/openpype/hosts/maya/plugins/load/load_rendersetup.py b/openpype/hosts/maya/plugins/load/load_rendersetup.py index 574ae9bd3d..efeff2f193 100644 --- a/openpype/hosts/maya/plugins/load/load_rendersetup.py +++ b/openpype/hosts/maya/plugins/load/load_rendersetup.py @@ -11,8 +11,8 @@ import six import sys from avalon import api -from avalon.maya import lib -from openpype.hosts.maya.api import lib as pypelib +from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.pipeline import containerise from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup @@ -31,7 +31,6 @@ class RenderSetupLoader(api.Loader): def load(self, context, name, namespace, data): """Load RenderSetup settings.""" - from avalon.maya.pipeline import containerise # from openpype.hosts.maya.api.lib import namespaced @@ -83,7 +82,7 @@ class RenderSetupLoader(api.Loader): def update(self, container, representation): """Update RenderSetup setting by overwriting existing settings.""" - pypelib.show_message( + lib.show_message( "Render setup update", "Render setup setting will be overwritten by new version. All " "setting specified by user not included in loaded version " diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py index f5662ba462..3e1d67ae9a 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_redshift.py @@ -1,7 +1,8 @@ -from avalon import api import os +from avalon import api from openpype.api import get_project_settings + class LoadVDBtoRedShift(api.Loader): """Load OpenVDB in a Redshift Volume Shape""" @@ -15,8 +16,8 @@ class LoadVDBtoRedShift(api.Loader): def load(self, context, name=None, namespace=None, data=None): from maya import cmds - import avalon.maya.lib as lib - from avalon.maya.pipeline import containerise + from openpype.hosts.maya.api.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace try: family = context["representation"]["context"]["family"] @@ -45,7 +46,7 @@ class LoadVDBtoRedShift(api.Loader): asset = context['asset'] asset_name = asset["name"] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset_name + "_", prefix="_" if asset_name[0].isdigit() else "", suffix="_", diff --git a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py index ed561e1131..099c020093 100644 --- a/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py +++ b/openpype/hosts/maya/plugins/load/load_vdb_to_vray.py @@ -1,6 +1,6 @@ +import os from avalon import api from openpype.api import get_project_settings -import os from maya import cmds @@ -80,8 +80,8 @@ class LoadVDBtoVRay(api.Loader): def load(self, context, name, namespace, data): - import avalon.maya.lib as lib - from avalon.maya.pipeline import containerise + from openpype.hosts.maya.api.lib import unique_namespace + from openpype.hosts.maya.api.pipeline import containerise assert os.path.exists(self.fname), ( "Path does not exist: %s" % self.fname @@ -111,7 +111,7 @@ class LoadVDBtoVRay(api.Loader): asset = context['asset'] asset_name = asset["name"] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset_name + "_", prefix="_" if asset_name[0].isdigit() else "", suffix="_", diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py index 806cf1fd18..ac2fe635b3 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -9,9 +9,14 @@ import os import maya.cmds as cmds -from avalon.maya import lib from avalon import api, io from openpype.api import get_project_settings +from openpype.hosts.maya.api.lib import ( + maintained_selection, + namespaced, + unique_namespace +) +from openpype.hosts.maya.api.pipeline import containerise class VRayProxyLoader(api.Loader): @@ -36,8 +41,6 @@ class VRayProxyLoader(api.Loader): options (dict): Optional loader options. """ - from avalon.maya.pipeline import containerise - from openpype.hosts.maya.api.lib import namespaced try: family = context["representation"]["context"]["family"] @@ -48,7 +51,7 @@ class VRayProxyLoader(api.Loader): self.fname = self._get_abc(context["version"]["_id"]) or self.fname asset_name = context['asset']["name"] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset_name + "_", prefix="_" if asset_name[0].isdigit() else "", suffix="_", @@ -57,7 +60,7 @@ class VRayProxyLoader(api.Loader): # Ensure V-Ray for Maya is loaded. cmds.loadPlugin("vrayformaya", quiet=True) - with lib.maintained_selection(): + with maintained_selection(): cmds.namespace(addNamespace=namespace) with namespaced(namespace, new=False): nodes, group_node = self.create_vray_proxy( diff --git a/openpype/hosts/maya/plugins/load/load_vrayscene.py b/openpype/hosts/maya/plugins/load/load_vrayscene.py index 465dab2a76..2e85514938 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayscene.py +++ b/openpype/hosts/maya/plugins/load/load_vrayscene.py @@ -1,8 +1,13 @@ -from avalon.maya import lib -from avalon import api -from openpype.api import config import os import maya.cmds as cmds +from avalon import api +from openpype.api import get_project_settings +from openpype.hosts.maya.api.lib import ( + maintained_selection, + namespaced, + unique_namespace +) +from openpype.hosts.maya.api.pipeline import containerise class VRaySceneLoader(api.Loader): @@ -18,8 +23,6 @@ class VRaySceneLoader(api.Loader): def load(self, context, name, namespace, data): - from avalon.maya.pipeline import containerise - from openpype.hosts.maya.lib import namespaced try: family = context["representation"]["context"]["family"] @@ -27,7 +30,7 @@ class VRaySceneLoader(api.Loader): family = "vrayscene_layer" asset_name = context['asset']["name"] - namespace = namespace or lib.unique_namespace( + namespace = namespace or unique_namespace( asset_name + "_", prefix="_" if asset_name[0].isdigit() else "", suffix="_", @@ -36,7 +39,7 @@ class VRaySceneLoader(api.Loader): # Ensure V-Ray for Maya is loaded. cmds.loadPlugin("vrayformaya", quiet=True) - with lib.maintained_selection(): + with maintained_selection(): cmds.namespace(addNamespace=namespace) with namespaced(namespace, new=False): nodes, group_node = self.create_vray_scene(name, @@ -47,8 +50,8 @@ class VRaySceneLoader(api.Loader): return # colour the group node - presets = config.get_presets(project=os.environ['AVALON_PROJECT']) - colors = presets['plugins']['maya']['load']['colors'] + presets = get_project_settings(os.environ['AVALON_PROJECT']) + colors = presets['maya']['load']['colors'] c = colors.get(family) if c is not None: cmds.setAttr("{0}.useOutlinerColor".format(group_node), 1) diff --git a/openpype/hosts/maya/plugins/load/load_yeti_cache.py b/openpype/hosts/maya/plugins/load/load_yeti_cache.py index de0ea6823c..dfe75173ac 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_cache.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_cache.py @@ -3,14 +3,14 @@ import json import re import glob from collections import defaultdict +from pprint import pprint from maya import cmds from avalon import api, io -from avalon.maya import lib as avalon_lib, pipeline -from openpype.hosts.maya.api import lib from openpype.api import get_project_settings -from pprint import pprint +from openpype.hosts.maya.api import lib +from openpype.hosts.maya.api.pipeline import containerise class YetiCacheLoader(api.Loader): @@ -75,11 +75,13 @@ class YetiCacheLoader(api.Loader): self[:] = nodes - return pipeline.containerise(name=name, - namespace=namespace, - nodes=nodes, - context=context, - loader=self.__class__.__name__) + return containerise( + name=name, + namespace=namespace, + nodes=nodes, + context=context, + loader=self.__class__.__name__ + ) def remove(self, container): @@ -239,9 +241,11 @@ class YetiCacheLoader(api.Loader): asset_name = "{}_".format(asset) prefix = "_" if asset_name[0].isdigit()else "" - namespace = avalon_lib.unique_namespace(asset_name, - prefix=prefix, - suffix="_") + namespace = lib.unique_namespace( + asset_name, + prefix=prefix, + suffix="_" + ) return namespace diff --git a/openpype/hosts/maya/plugins/load/load_yeti_rig.py b/openpype/hosts/maya/plugins/load/load_yeti_rig.py index 3f67f98f51..b4d31b473f 100644 --- a/openpype/hosts/maya/plugins/load/load_yeti_rig.py +++ b/openpype/hosts/maya/plugins/load/load_yeti_rig.py @@ -25,7 +25,6 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): self, context, name=None, namespace=None, options=None): import maya.cmds as cmds - from avalon import maya # get roots of selected hierarchies selected_roots = [] @@ -53,7 +52,7 @@ class YetiRigLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): scene_lookup[cb_id] = node # load rig - with maya.maintained_selection(): + with lib.maintained_selection(): nodes = cmds.file(self.fname, namespace=namespace, reference=True, diff --git a/openpype/hosts/maya/plugins/publish/collect_assembly.py b/openpype/hosts/maya/plugins/publish/collect_assembly.py index 313636793b..1a65bf1fde 100644 --- a/openpype/hosts/maya/plugins/publish/collect_assembly.py +++ b/openpype/hosts/maya/plugins/publish/collect_assembly.py @@ -2,7 +2,7 @@ from collections import defaultdict import pyblish.api from maya import cmds, mel -from avalon import maya as avalon +from openpype.hosts.maya import api from openpype.hosts.maya.api import lib # TODO : Publish of assembly: -unique namespace for all assets, VALIDATOR! @@ -30,7 +30,7 @@ class CollectAssembly(pyblish.api.InstancePlugin): def process(self, instance): # Find containers - containers = avalon.ls() + containers = api.ls() # Get all content from the instance instance_lookup = set(cmds.ls(instance, type="transform", long=True)) diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index cbddb86e53..13ae1924b9 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -49,7 +49,7 @@ import maya.app.renderSetup.model.renderSetup as renderSetup import pyblish.api -from avalon import maya, api +from avalon import api from openpype.hosts.maya.api.lib_renderproducts import get as get_layer_render_products # noqa: E501 from openpype.hosts.maya.api import lib @@ -409,7 +409,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): dict: only overrides with values """ - attributes = maya.read(render_globals) + attributes = lib.read(render_globals) options = {"renderGlobals": {}} options["renderGlobals"]["Priority"] = attributes["priority"] diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 7ecc40a68d..269972d996 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -2,9 +2,12 @@ import os from maya import cmds -import avalon.maya import openpype.api -from openpype.hosts.maya.api.lib import extract_alembic +from openpype.hosts.maya.api.lib import ( + extract_alembic, + suspended_refresh, + maintained_selection +) class ExtractAnimation(openpype.api.Extractor): @@ -71,8 +74,8 @@ class ExtractAnimation(openpype.api.Extractor): # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True - with avalon.maya.suspended_refresh(): - with avalon.maya.maintained_selection(): + with suspended_refresh(): + with maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=float(start), diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py index 7461ccdf78..ab149de700 100644 --- a/openpype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,9 +1,9 @@ import os -import avalon.maya import openpype.api from maya import cmds +from openpype.hosts.maya.api.lib import maintained_selection class ExtractAssStandin(openpype.api.Extractor): @@ -30,7 +30,7 @@ class ExtractAssStandin(openpype.api.Extractor): # Write out .ass file self.log.info("Writing: '%s'" % file_path) - with avalon.maya.maintained_selection(): + with maintained_selection(): self.log.info("Writing: {}".format(instance.data["setMembers"])) cmds.select(instance.data["setMembers"], noExpand=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_assproxy.py b/openpype/hosts/maya/plugins/publish/extract_assproxy.py index f53f385b8b..93720dbb82 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_assproxy.py @@ -1,10 +1,10 @@ import os - -from maya import cmds import contextlib -import avalon.maya +from maya import cmds + import openpype.api +from openpype.hosts.maya.api.lib import maintained_selection class ExtractAssProxy(openpype.api.Extractor): @@ -54,7 +54,7 @@ class ExtractAssProxy(openpype.api.Extractor): noIntermediate=True) self.log.info(members) - with avalon.maya.maintained_selection(): + with maintained_selection(): with unparent(members[0]): cmds.select(members, noExpand=True) cmds.file(path, diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index 8950ed6254..806a079940 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -2,9 +2,7 @@ import os from maya import cmds -import avalon.maya import openpype.api - from openpype.hosts.maya.api import lib @@ -54,7 +52,7 @@ class ExtractCameraAlembic(openpype.api.Extractor): path = os.path.join(dir_path, filename) # Perform alembic extraction - with avalon.maya.maintained_selection(): + with lib.maintained_selection(): cmds.select(camera, replace=True, noExpand=True) # Enforce forward slashes for AbcExport because we're @@ -86,7 +84,7 @@ class ExtractCameraAlembic(openpype.api.Extractor): job_str += " -attr {0}".format(attr) with lib.evaluation("off"): - with avalon.maya.suspended_refresh(): + with lib.suspended_refresh(): cmds.AbcExport(j=job_str, verbose=False) if "representations" not in instance.data: diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 888dc636b2..9d25b147de 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -5,7 +5,6 @@ import itertools from maya import cmds -import avalon.maya import openpype.api from openpype.hosts.maya.api import lib @@ -157,9 +156,9 @@ class ExtractCameraMayaScene(openpype.api.Extractor): path = os.path.join(dir_path, filename) # Perform extraction - with avalon.maya.maintained_selection(): + with lib.maintained_selection(): with lib.evaluation("off"): - with avalon.maya.suspended_refresh(): + with lib.suspended_refresh(): if bake_to_worldspace: self.log.info( "Performing camera bakes: {}".format(transform)) diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx.py b/openpype/hosts/maya/plugins/publish/extract_fbx.py index e4894f28cd..844084b9ab 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -3,12 +3,12 @@ import os from maya import cmds # noqa import maya.mel as mel # noqa -from openpype.hosts.maya.api.lib import root_parent - import pyblish.api -import avalon.maya - import openpype.api +from openpype.hosts.maya.api.lib import ( + root_parent, + maintained_selection +) class ExtractFBX(openpype.api.Extractor): @@ -205,13 +205,13 @@ class ExtractFBX(openpype.api.Extractor): # Export if "unrealStaticMesh" in instance.data["families"]: - with avalon.maya.maintained_selection(): + with maintained_selection(): with root_parent(members): self.log.info("Un-parenting: {}".format(members)) cmds.select(members, r=1, noExpand=True) mel.eval('FBXExport -f "{}" -s'.format(path)) else: - with avalon.maya.maintained_selection(): + with maintained_selection(): cmds.select(members, r=1, noExpand=True) mel.eval('FBXExport -f "{}" -s'.format(path)) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index bf79ddbf44..fe89038a24 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -11,8 +11,7 @@ from collections import OrderedDict from maya import cmds # noqa import pyblish.api -import avalon.maya -from avalon import io, api +from avalon import io import openpype.api from openpype.hosts.maya.api import lib @@ -239,7 +238,7 @@ class ExtractLook(openpype.api.Extractor): # getting incorrectly remapped. (LKD-17, PLN-101) with no_workspace_dir(): with lib.attribute_values(remap): - with avalon.maya.maintained_selection(): + with lib.maintained_selection(): cmds.select(sets, noExpand=True) cmds.file( maya_path, diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index e7fb5bc8cb..9c432cbc67 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -4,8 +4,8 @@ import os from maya import cmds -import avalon.maya import openpype.api +from openpype.hosts.maya.api.lib import maintained_selection class ExtractMayaSceneRaw(openpype.api.Extractor): @@ -59,7 +59,7 @@ class ExtractMayaSceneRaw(openpype.api.Extractor): # Perform extraction self.log.info("Performing extraction ...") - with avalon.maya.maintained_selection(): + with maintained_selection(): cmds.select(members, noExpand=True) cmds.file(path, force=True, diff --git a/openpype/hosts/maya/plugins/publish/extract_model.py b/openpype/hosts/maya/plugins/publish/extract_model.py index 40cc9427f3..0282d1e9c8 100644 --- a/openpype/hosts/maya/plugins/publish/extract_model.py +++ b/openpype/hosts/maya/plugins/publish/extract_model.py @@ -4,7 +4,6 @@ import os from maya import cmds -import avalon.maya import openpype.api from openpype.hosts.maya.api import lib @@ -74,7 +73,7 @@ class ExtractModel(openpype.api.Extractor): polygonObject=1): with lib.shader(members, shadingEngine="initialShadingGroup"): - with avalon.maya.maintained_selection(): + with lib.maintained_selection(): cmds.select(members, noExpand=True) cmds.file(path, force=True, diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 630cc39398..60502fdde1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -2,9 +2,12 @@ import os from maya import cmds -import avalon.maya import openpype.api -from openpype.hosts.maya.api.lib import extract_alembic +from openpype.hosts.maya.api.lib import ( + extract_alembic, + suspended_refresh, + maintained_selection +) class ExtractAlembic(openpype.api.Extractor): @@ -70,8 +73,8 @@ class ExtractAlembic(openpype.api.Extractor): # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True - with avalon.maya.suspended_refresh(): - with avalon.maya.maintained_selection(): + with suspended_refresh(): + with maintained_selection(): cmds.select(nodes, noExpand=True) extract_alembic(file=path, startFrame=start, diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 7c9e201986..23cac9190d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -2,11 +2,11 @@ """Redshift Proxy extractor.""" import os -import avalon.maya -import openpype.api - from maya import cmds +import openpype.api +from openpype.hosts.maya.api.lib import maintained_selection + class ExtractRedshiftProxy(openpype.api.Extractor): """Extract the content of the instance to a redshift proxy file.""" @@ -54,7 +54,7 @@ class ExtractRedshiftProxy(openpype.api.Extractor): # Write out rs file self.log.info("Writing: '%s'" % file_path) - with avalon.maya.maintained_selection(): + with maintained_selection(): cmds.select(instance.data["setMembers"], noExpand=True) cmds.file(file_path, pr=False, diff --git a/openpype/hosts/maya/plugins/publish/extract_rig.py b/openpype/hosts/maya/plugins/publish/extract_rig.py index b28b60114e..53c1eeb671 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig.py @@ -4,8 +4,8 @@ import os from maya import cmds -import avalon.maya import openpype.api +from openpype.hosts.maya.api.lib import maintained_selection class ExtractRig(openpype.api.Extractor): @@ -40,7 +40,7 @@ class ExtractRig(openpype.api.Extractor): # Perform extraction self.log.info("Performing extraction ...") - with avalon.maya.maintained_selection(): + with maintained_selection(): cmds.select(instance, noExpand=True) cmds.file(path, force=True, diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py index 7103601b85..615bc27878 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py @@ -1,10 +1,10 @@ import os -import avalon.maya -import openpype.api - from maya import cmds +import openpype.api +from openpype.hosts.maya.api.lib import maintained_selection + class ExtractVRayProxy(openpype.api.Extractor): """Extract the content of the instance to a vrmesh file @@ -41,7 +41,7 @@ class ExtractVRayProxy(openpype.api.Extractor): # Write out vrmesh file self.log.info("Writing: '%s'" % file_path) - with avalon.maya.maintained_selection(): + with maintained_selection(): cmds.select(instance.data["setMembers"], noExpand=True) cmds.vrayCreateProxy(exportType=1, dir=staging_dir, diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayscene.py b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py index 1d7c0fa717..5d41697e5f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayscene.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py @@ -3,9 +3,9 @@ import os import re -import avalon.maya import openpype.api from openpype.hosts.maya.api.render_setup_tools import export_in_rs_layer +from openpype.hosts.maya.api.lib import maintained_selection from maya import cmds @@ -57,7 +57,7 @@ class ExtractVrayscene(openpype.api.Extractor): # Write out vrscene file self.log.info("Writing: '%s'" % file_path) - with avalon.maya.maintained_selection(): + with maintained_selection(): if "*" not in instance.data["setMembers"]: self.log.info( "Exporting: {}".format(instance.data["setMembers"])) diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py index d69911c404..5728682abe 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py @@ -2,8 +2,11 @@ import os from maya import cmds -import avalon.maya import openpype.api +from openpype.hosts.maya.api.lib import ( + suspended_refresh, + maintained_selection +) class ExtractXgenCache(openpype.api.Extractor): @@ -32,8 +35,8 @@ class ExtractXgenCache(openpype.api.Extractor): filename = "{name}.abc".format(**instance.data) path = os.path.join(parent_dir, filename) - with avalon.maya.suspended_refresh(): - with avalon.maya.maintained_selection(): + with suspended_refresh(): + with maintained_selection(): command = ( '-file ' + path diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 56d5dfe901..d12567a55a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -7,9 +7,8 @@ import contextlib from maya import cmds -import avalon.maya.lib as lib import openpype.api -import openpype.hosts.maya.api.lib as maya +from openpype.hosts.maya.api import lib @contextlib.contextmanager diff --git a/openpype/hosts/maya/plugins/publish/validate_cycle_error.py b/openpype/hosts/maya/plugins/publish/validate_cycle_error.py index d4faf2e562..4dfe0b8add 100644 --- a/openpype/hosts/maya/plugins/publish/validate_cycle_error.py +++ b/openpype/hosts/maya/plugins/publish/validate_cycle_error.py @@ -2,10 +2,9 @@ from maya import cmds import pyblish.api -from avalon import maya - import openpype.api import openpype.hosts.maya.api.action +from openpype.hosts.maya.api.lib import maintained_selection class ValidateCycleError(pyblish.api.InstancePlugin): @@ -26,7 +25,7 @@ class ValidateCycleError(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): - with maya.maintained_selection(): + with maintained_selection(): cmds.select(instance[:], noExpand=True) plugs = cmds.cycleCheck(all=False, # check selection only list=True) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py index 6b3f508561..90eb01aa12 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_arnold_attributes.py @@ -3,7 +3,7 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from avalon import maya +from openpype.hosts.maya.api.lib import maintained_selection class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): @@ -67,7 +67,7 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin): @classmethod def repair(cls, instance): - with maya.maintained_selection(): + with maintained_selection(): with pc.UndoChunk(): temp_transform = pc.polyCube()[0] diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py b/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py index 839aab0d0b..ab0beb2a9c 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_ngons.py @@ -3,7 +3,6 @@ from maya import cmds import pyblish.api import openpype.api import openpype.hosts.maya.api.action -from avalon import maya from openpype.hosts.maya.api import lib diff --git a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py index 6b5c5d1398..343eaccb7d 100644 --- a/openpype/hosts/maya/plugins/publish/validate_shape_zero.py +++ b/openpype/hosts/maya/plugins/publish/validate_shape_zero.py @@ -5,8 +5,6 @@ import openpype.api import openpype.hosts.maya.api.action from openpype.hosts.maya.api import lib -from avalon.maya import maintained_selection - class ValidateShapeZero(pyblish.api.Validator): """Shape components may not have any "tweak" values @@ -51,7 +49,7 @@ class ValidateShapeZero(pyblish.api.Validator): if not invalid_shapes: return - with maintained_selection(): + with lib.maintained_selection(): with lib.tool("selectSuperContext"): for shape in invalid_shapes: cmds.polyCollapseTweaks(shape) diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 6d27c66882..b89244817a 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -1,8 +1,12 @@ import os +import avalon.api from openpype.api import get_project_settings +from openpype.hosts.maya import api import openpype.hosts.maya.api.lib as mlib from maya import cmds +avalon.api.install(api) + print("starting OpenPype usersetup")