mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
Maya: Remove pymel dependency (#4724)
* Remove pymel from `validate_no_namespace` * Refactor `ValidateRigOutputIds` to remove pymel dependency * Remove pymel dependency * Fix logic * Remove pymel dependency * Remove py * Remove pymel dependency * Remove pymel dependency * Remove pymel dependency * Opt-out early if possible * Remove pymel dependency * Remove pymel dependency * Remove pymel dependency * Remove pymel dependency * Remove pymel dependency * Remove code that should've been removed * Cosmetics * Shush hound * Refactor `get_parent` -> `get_node_parent` * Fix argument * Refactor logic for Validate Mesh Arnold Attributes * Fix load image plane camera transform name * Merge remote-tracking branch 'upstream/develop' into maya_remove_pymel # Conflicts: # openpype/hosts/maya/plugins/load/load_reference.py # openpype/hosts/maya/plugins/publish/collect_review.py * Fix bug * Improve labels on messagebox buttons * Fix repair * Create empty mesh instead of cube * Tweak code style based on comments by @fabiaserra --------- Co-authored-by: Jakub Ježek <jakubjezek001@gmail.com>
This commit is contained in:
parent
0c66e3c566
commit
a93b635618
15 changed files with 474 additions and 334 deletions
2
.github/pr-branch-labeler.yml
vendored
2
.github/pr-branch-labeler.yml
vendored
|
|
@ -12,4 +12,4 @@
|
|||
|
||||
# Apply label "release" if base matches "release/*"
|
||||
'Bump Minor':
|
||||
base: "release/next-minor"
|
||||
base: "release/next-minor"
|
||||
|
|
|
|||
|
|
@ -1367,6 +1367,71 @@ def set_id(node, unique_id, overwrite=False):
|
|||
cmds.setAttr(attr, unique_id, type="string")
|
||||
|
||||
|
||||
def get_attribute(plug,
|
||||
asString=False,
|
||||
expandEnvironmentVariables=False,
|
||||
**kwargs):
|
||||
"""Maya getAttr with some fixes based on `pymel.core.general.getAttr()`.
|
||||
|
||||
Like Pymel getAttr this applies some changes to `maya.cmds.getAttr`
|
||||
- maya pointlessly returned vector results as a tuple wrapped in a list
|
||||
(ex. '[(1,2,3)]'). This command unpacks the vector for you.
|
||||
- when getting a multi-attr, maya would raise an error, but this will
|
||||
return a list of values for the multi-attr
|
||||
- added support for getting message attributes by returning the
|
||||
connections instead
|
||||
|
||||
Note that the asString + expandEnvironmentVariables argument naming
|
||||
convention matches the `maya.cmds.getAttr` arguments so that it can
|
||||
act as a direct replacement for it.
|
||||
|
||||
Args:
|
||||
plug (str): Node's attribute plug as `node.attribute`
|
||||
asString (bool): Return string value for enum attributes instead
|
||||
of the index. Note that the return value can be dependent on the
|
||||
UI language Maya is running in.
|
||||
expandEnvironmentVariables (bool): Expand any environment variable and
|
||||
(tilde characters on UNIX) found in string attributes which are
|
||||
returned.
|
||||
|
||||
Kwargs:
|
||||
Supports the keyword arguments of `maya.cmds.getAttr`
|
||||
|
||||
Returns:
|
||||
object: The value of the maya attribute.
|
||||
|
||||
"""
|
||||
attr_type = cmds.getAttr(plug, type=True)
|
||||
if asString:
|
||||
kwargs["asString"] = True
|
||||
if expandEnvironmentVariables:
|
||||
kwargs["expandEnvironmentVariables"] = True
|
||||
try:
|
||||
res = cmds.getAttr(plug, **kwargs)
|
||||
except RuntimeError:
|
||||
if attr_type == "message":
|
||||
return cmds.listConnections(plug)
|
||||
|
||||
node, attr = plug.split(".", 1)
|
||||
children = cmds.attributeQuery(attr, node=node, listChildren=True)
|
||||
if children:
|
||||
return [
|
||||
get_attribute("{}.{}".format(node, child))
|
||||
for child in children
|
||||
]
|
||||
|
||||
raise
|
||||
|
||||
# Convert vector result wrapped in tuple
|
||||
if isinstance(res, list) and len(res):
|
||||
if isinstance(res[0], tuple) and len(res):
|
||||
if attr_type in {'pointArray', 'vectorArray'}:
|
||||
return res
|
||||
return res[0]
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def set_attribute(attribute, value, node):
|
||||
"""Adjust attributes based on the value from the attribute data
|
||||
|
||||
|
|
@ -1881,6 +1946,12 @@ def remove_other_uv_sets(mesh):
|
|||
cmds.removeMultiInstance(attr, b=True)
|
||||
|
||||
|
||||
def get_node_parent(node):
|
||||
"""Return full path name for parent of node"""
|
||||
parents = cmds.listRelatives(node, parent=True, fullPath=True)
|
||||
return parents[0] if parents else None
|
||||
|
||||
|
||||
def get_id_from_sibling(node, history_only=True):
|
||||
"""Return first node id in the history chain that matches this node.
|
||||
|
||||
|
|
@ -1904,10 +1975,6 @@ def get_id_from_sibling(node, history_only=True):
|
|||
|
||||
"""
|
||||
|
||||
def _get_parent(node):
|
||||
"""Return full path name for parent of node"""
|
||||
return cmds.listRelatives(node, parent=True, fullPath=True)
|
||||
|
||||
node = cmds.ls(node, long=True)[0]
|
||||
|
||||
# Find all similar nodes in history
|
||||
|
|
@ -1919,8 +1986,8 @@ def get_id_from_sibling(node, history_only=True):
|
|||
similar_nodes = [x for x in similar_nodes if x != node]
|
||||
|
||||
# The node *must be* under the same parent
|
||||
parent = _get_parent(node)
|
||||
similar_nodes = [i for i in similar_nodes if _get_parent(i) == parent]
|
||||
parent = get_node_parent(node)
|
||||
similar_nodes = [i for i in similar_nodes if get_node_parent(i) == parent]
|
||||
|
||||
# Check all of the remaining similar nodes and take the first one
|
||||
# with an id and assume it's the original.
|
||||
|
|
@ -3166,38 +3233,78 @@ def set_colorspace():
|
|||
def parent_nodes(nodes, parent=None):
|
||||
# type: (list, str) -> list
|
||||
"""Context manager to un-parent provided nodes and return them back."""
|
||||
import pymel.core as pm # noqa
|
||||
|
||||
parent_node = None
|
||||
def _as_mdagpath(node):
|
||||
"""Return MDagPath for node path."""
|
||||
if not node:
|
||||
return
|
||||
sel = OpenMaya.MSelectionList()
|
||||
sel.add(node)
|
||||
return sel.getDagPath(0)
|
||||
|
||||
# We can only parent dag nodes so we ensure input contains only dag nodes
|
||||
nodes = cmds.ls(nodes, type="dagNode", long=True)
|
||||
if not nodes:
|
||||
# opt-out early
|
||||
yield
|
||||
return
|
||||
|
||||
parent_node_path = None
|
||||
delete_parent = False
|
||||
|
||||
if parent:
|
||||
if not cmds.objExists(parent):
|
||||
parent_node = pm.createNode("transform", n=parent, ss=False)
|
||||
parent_node = cmds.createNode("transform",
|
||||
name=parent,
|
||||
skipSelect=False)
|
||||
delete_parent = True
|
||||
else:
|
||||
parent_node = pm.PyNode(parent)
|
||||
parent_node = parent
|
||||
parent_node_path = cmds.ls(parent_node, long=True)[0]
|
||||
|
||||
# Store original parents
|
||||
node_parents = []
|
||||
for node in nodes:
|
||||
n = pm.PyNode(node)
|
||||
try:
|
||||
root = pm.listRelatives(n, parent=1)[0]
|
||||
except IndexError:
|
||||
root = None
|
||||
node_parents.append((n, root))
|
||||
node_parent = get_node_parent(node)
|
||||
node_parents.append((_as_mdagpath(node), _as_mdagpath(node_parent)))
|
||||
|
||||
try:
|
||||
for node in node_parents:
|
||||
if not parent:
|
||||
node[0].setParent(world=True)
|
||||
for node, node_parent in node_parents:
|
||||
node_parent_path = node_parent.fullPathName() if node_parent else None # noqa
|
||||
if node_parent_path == parent_node_path:
|
||||
# Already a child
|
||||
continue
|
||||
|
||||
if parent_node_path:
|
||||
cmds.parent(node.fullPathName(), parent_node_path)
|
||||
else:
|
||||
node[0].setParent(parent_node)
|
||||
cmds.parent(node.fullPathName(), world=True)
|
||||
|
||||
yield
|
||||
finally:
|
||||
for node in node_parents:
|
||||
if node[1]:
|
||||
node[0].setParent(node[1])
|
||||
# Reparent to original parents
|
||||
for node, original_parent in node_parents:
|
||||
node_path = node.fullPathName()
|
||||
if not node_path:
|
||||
# Node must have been deleted
|
||||
continue
|
||||
|
||||
node_parent_path = get_node_parent(node_path)
|
||||
|
||||
original_parent_path = None
|
||||
if original_parent:
|
||||
original_parent_path = original_parent.fullPathName()
|
||||
if not original_parent_path:
|
||||
# Original parent node must have been deleted
|
||||
continue
|
||||
|
||||
if node_parent_path != original_parent_path:
|
||||
if not original_parent_path:
|
||||
cmds.parent(node_path, world=True)
|
||||
else:
|
||||
cmds.parent(node_path, original_parent_path)
|
||||
|
||||
if delete_parent:
|
||||
pm.delete(parent_node)
|
||||
cmds.delete(parent_node_path)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ from maya.app.renderSetup.model.override import (
|
|||
UniqueOverride
|
||||
)
|
||||
|
||||
from openpype.hosts.maya.api.lib import get_attribute
|
||||
|
||||
EXACT_MATCH = 0
|
||||
PARENT_MATCH = 1
|
||||
CLIENT_MATCH = 2
|
||||
|
|
@ -96,9 +98,6 @@ def get_attr_in_layer(node_attr, layer):
|
|||
|
||||
"""
|
||||
|
||||
# Delay pymel import to here because it's slow to load
|
||||
import pymel.core as pm
|
||||
|
||||
def _layer_needs_update(layer):
|
||||
"""Return whether layer needs updating."""
|
||||
# Use `getattr` as e.g. DEFAULT_RENDER_LAYER does not have
|
||||
|
|
@ -125,7 +124,7 @@ def get_attr_in_layer(node_attr, layer):
|
|||
node = history_overrides[-1] if history_overrides else override
|
||||
node_attr_ = node + ".original"
|
||||
|
||||
return pm.getAttr(node_attr_, asString=True)
|
||||
return get_attribute(node_attr_, asString=True)
|
||||
|
||||
layer = get_rendersetup_layer(layer)
|
||||
rs = renderSetup.instance()
|
||||
|
|
@ -145,7 +144,7 @@ def get_attr_in_layer(node_attr, layer):
|
|||
# we will let it error out.
|
||||
rs.switchToLayer(current_layer)
|
||||
|
||||
return pm.getAttr(node_attr, asString=True)
|
||||
return get_attribute(node_attr, asString=True)
|
||||
|
||||
overrides = get_attr_overrides(node_attr, layer)
|
||||
default_layer_value = get_default_layer_value(node_attr)
|
||||
|
|
@ -156,7 +155,7 @@ def get_attr_in_layer(node_attr, layer):
|
|||
for match, layer_override, index in overrides:
|
||||
if isinstance(layer_override, AbsOverride):
|
||||
# Absolute override
|
||||
value = pm.getAttr(layer_override.name() + ".attrValue")
|
||||
value = get_attribute(layer_override.name() + ".attrValue")
|
||||
if match == EXACT_MATCH:
|
||||
# value = value
|
||||
pass
|
||||
|
|
@ -168,8 +167,8 @@ def get_attr_in_layer(node_attr, layer):
|
|||
elif isinstance(layer_override, RelOverride):
|
||||
# Relative override
|
||||
# Value = Original * Multiply + Offset
|
||||
multiply = pm.getAttr(layer_override.name() + ".multiply")
|
||||
offset = pm.getAttr(layer_override.name() + ".offset")
|
||||
multiply = get_attribute(layer_override.name() + ".multiply")
|
||||
offset = get_attribute(layer_override.name() + ".offset")
|
||||
|
||||
if match == EXACT_MATCH:
|
||||
value = value * multiply + offset
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from openpype.pipeline import (
|
|||
get_representation_path,
|
||||
)
|
||||
from openpype.hosts.maya.api.pipeline import containerise
|
||||
from openpype.hosts.maya.api.lib import unique_namespace
|
||||
from openpype.hosts.maya.api.lib import unique_namespace, get_container_members
|
||||
|
||||
|
||||
class AudioLoader(load.LoaderPlugin):
|
||||
|
|
@ -52,17 +52,15 @@ class AudioLoader(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
import pymel.core as pm
|
||||
|
||||
audio_node = None
|
||||
for node in pm.PyNode(container["objectName"]).members():
|
||||
if node.nodeType() == "audio":
|
||||
audio_node = node
|
||||
members = get_container_members(container)
|
||||
audio_nodes = cmds.ls(members, type="audio")
|
||||
|
||||
assert audio_node is not None, "Audio node not found."
|
||||
assert audio_nodes is not None, "Audio node not found."
|
||||
audio_node = audio_nodes[0]
|
||||
|
||||
path = get_representation_path(representation)
|
||||
audio_node.filename.set(path)
|
||||
cmds.setAttr("{}.filename".format(audio_node), path, type="string")
|
||||
cmds.setAttr(
|
||||
container["objectName"] + ".representation",
|
||||
str(representation["_id"]),
|
||||
|
|
@ -80,8 +78,12 @@ class AudioLoader(load.LoaderPlugin):
|
|||
asset = get_asset_by_id(
|
||||
project_name, subset["parent"], fields=["parent"]
|
||||
)
|
||||
audio_node.sourceStart.set(1 - asset["data"]["frameStart"])
|
||||
audio_node.sourceEnd.set(asset["data"]["frameEnd"])
|
||||
|
||||
source_start = 1 - asset["data"]["frameStart"]
|
||||
source_end = asset["data"]["frameEnd"]
|
||||
|
||||
cmds.setAttr("{}.sourceStart".format(audio_node), source_start)
|
||||
cmds.setAttr("{}.sourceEnd".format(audio_node), source_end)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,26 @@ from openpype.pipeline import (
|
|||
get_representation_path
|
||||
)
|
||||
from openpype.hosts.maya.api.pipeline import containerise
|
||||
from openpype.hosts.maya.api.lib import unique_namespace
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
unique_namespace,
|
||||
namespaced,
|
||||
pairwise,
|
||||
get_container_members
|
||||
)
|
||||
|
||||
from maya import cmds
|
||||
|
||||
|
||||
def disconnect_inputs(plug):
|
||||
overrides = cmds.listConnections(plug,
|
||||
source=True,
|
||||
destination=False,
|
||||
plugs=True,
|
||||
connections=True) or []
|
||||
for dest, src in pairwise(overrides):
|
||||
cmds.disconnectAttr(src, dest)
|
||||
|
||||
|
||||
class CameraWindow(QtWidgets.QDialog):
|
||||
|
||||
def __init__(self, cameras):
|
||||
|
|
@ -74,6 +89,7 @@ class CameraWindow(QtWidgets.QDialog):
|
|||
self.camera = None
|
||||
self.close()
|
||||
|
||||
|
||||
class ImagePlaneLoader(load.LoaderPlugin):
|
||||
"""Specific loader of plate for image planes on selected camera."""
|
||||
|
||||
|
|
@ -84,9 +100,7 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
color = "orange"
|
||||
|
||||
def load(self, context, name, namespace, data, options=None):
|
||||
import pymel.core as pm
|
||||
|
||||
new_nodes = []
|
||||
image_plane_depth = 1000
|
||||
asset = context['asset']['name']
|
||||
namespace = namespace or unique_namespace(
|
||||
|
|
@ -96,16 +110,20 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
# Get camera from user selection.
|
||||
camera = None
|
||||
# is_static_image_plane = None
|
||||
# is_in_all_views = None
|
||||
if data:
|
||||
camera = pm.PyNode(data.get("camera"))
|
||||
camera = data.get("camera") if data else None
|
||||
|
||||
if not camera:
|
||||
cameras = pm.ls(type="camera")
|
||||
camera_names = {x.getParent().name(): x for x in cameras}
|
||||
camera_names["Create new camera."] = "create_camera"
|
||||
cameras = cmds.ls(type="camera")
|
||||
|
||||
# Cameras by names
|
||||
camera_names = {}
|
||||
for camera in cameras:
|
||||
parent = cmds.listRelatives(camera, parent=True, path=True)[0]
|
||||
camera_names[parent] = camera
|
||||
|
||||
camera_names["Create new camera."] = "create-camera"
|
||||
window = CameraWindow(camera_names.keys())
|
||||
window.exec_()
|
||||
# Skip if no camera was selected (Dialog was closed)
|
||||
|
|
@ -113,43 +131,48 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
return
|
||||
camera = camera_names[window.camera]
|
||||
|
||||
if camera == "create_camera":
|
||||
camera = pm.createNode("camera")
|
||||
if camera == "create-camera":
|
||||
camera = cmds.createNode("camera")
|
||||
|
||||
if camera is None:
|
||||
return
|
||||
|
||||
try:
|
||||
camera.displayResolution.set(1)
|
||||
camera.farClipPlane.set(image_plane_depth * 10)
|
||||
cmds.setAttr("{}.displayResolution".format(camera), True)
|
||||
cmds.setAttr("{}.farClipPlane".format(camera),
|
||||
image_plane_depth * 10)
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# Create image plane
|
||||
image_plane_transform, image_plane_shape = pm.imagePlane(
|
||||
fileName=context["representation"]["data"]["path"],
|
||||
camera=camera)
|
||||
image_plane_shape.depth.set(image_plane_depth)
|
||||
with namespaced(namespace):
|
||||
# Create inside the namespace
|
||||
image_plane_transform, image_plane_shape = cmds.imagePlane(
|
||||
fileName=context["representation"]["data"]["path"],
|
||||
camera=camera
|
||||
)
|
||||
start_frame = cmds.playbackOptions(query=True, min=True)
|
||||
end_frame = cmds.playbackOptions(query=True, max=True)
|
||||
|
||||
|
||||
start_frame = pm.playbackOptions(q=True, min=True)
|
||||
end_frame = pm.playbackOptions(q=True, max=True)
|
||||
|
||||
image_plane_shape.frameOffset.set(0)
|
||||
image_plane_shape.frameIn.set(start_frame)
|
||||
image_plane_shape.frameOut.set(end_frame)
|
||||
image_plane_shape.frameCache.set(end_frame)
|
||||
image_plane_shape.useFrameExtension.set(1)
|
||||
for attr, value in {
|
||||
"depth": image_plane_depth,
|
||||
"frameOffset": 0,
|
||||
"frameIn": start_frame,
|
||||
"frameOut": end_frame,
|
||||
"frameCache": end_frame,
|
||||
"useFrameExtension": True
|
||||
}.items():
|
||||
plug = "{}.{}".format(image_plane_shape, attr)
|
||||
cmds.setAttr(plug, value)
|
||||
|
||||
movie_representations = ["mov", "preview"]
|
||||
if context["representation"]["name"] in movie_representations:
|
||||
# Need to get "type" by string, because its a method as well.
|
||||
pm.Attribute(image_plane_shape + ".type").set(2)
|
||||
cmds.setAttr(image_plane_shape + ".type", 2)
|
||||
|
||||
# Ask user whether to use sequence or still image.
|
||||
if context["representation"]["name"] == "exr":
|
||||
# Ensure OpenEXRLoader plugin is loaded.
|
||||
pm.loadPlugin("OpenEXRLoader.mll", quiet=True)
|
||||
cmds.loadPlugin("OpenEXRLoader", quiet=True)
|
||||
|
||||
message = (
|
||||
"Hold image sequence on first frame?"
|
||||
|
|
@ -161,32 +184,18 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
None,
|
||||
"Frame Hold.",
|
||||
message,
|
||||
QtWidgets.QMessageBox.Ok,
|
||||
QtWidgets.QMessageBox.Cancel
|
||||
QtWidgets.QMessageBox.Yes,
|
||||
QtWidgets.QMessageBox.No
|
||||
)
|
||||
if reply == QtWidgets.QMessageBox.Ok:
|
||||
# find the input and output of frame extension
|
||||
expressions = image_plane_shape.frameExtension.inputs()
|
||||
frame_ext_output = image_plane_shape.frameExtension.outputs()
|
||||
if expressions:
|
||||
# the "time1" node is non-deletable attr
|
||||
# in Maya, use disconnectAttr instead
|
||||
pm.disconnectAttr(expressions, frame_ext_output)
|
||||
if reply == QtWidgets.QMessageBox.Yes:
|
||||
frame_extension_plug = "{}.frameExtension".format(image_plane_shape) # noqa
|
||||
|
||||
if not image_plane_shape.frameExtension.isFreeToChange():
|
||||
raise RuntimeError("Can't set frame extension for {}".format(image_plane_shape)) # noqa
|
||||
# get the node of time instead and set the time for it.
|
||||
image_plane_shape.frameExtension.set(start_frame)
|
||||
# Remove current frame expression
|
||||
disconnect_inputs(frame_extension_plug)
|
||||
|
||||
new_nodes.extend(
|
||||
[
|
||||
image_plane_transform.longName().split("|")[-1],
|
||||
image_plane_shape.longName().split("|")[-1]
|
||||
]
|
||||
)
|
||||
cmds.setAttr(frame_extension_plug, start_frame)
|
||||
|
||||
for node in new_nodes:
|
||||
pm.rename(node, "{}:{}".format(namespace, node))
|
||||
new_nodes = [image_plane_transform, image_plane_shape]
|
||||
|
||||
return containerise(
|
||||
name=name,
|
||||
|
|
@ -197,21 +206,19 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
)
|
||||
|
||||
def update(self, container, representation):
|
||||
import pymel.core as pm
|
||||
image_plane_shape = None
|
||||
for node in pm.PyNode(container["objectName"]).members():
|
||||
if node.nodeType() == "imagePlane":
|
||||
image_plane_shape = node
|
||||
|
||||
assert image_plane_shape is not None, "Image plane not found."
|
||||
members = get_container_members(container)
|
||||
image_planes = cmds.ls(members, type="imagePlane")
|
||||
assert image_planes, "Image plane not found."
|
||||
image_plane_shape = image_planes[0]
|
||||
|
||||
path = get_representation_path(representation)
|
||||
image_plane_shape.imageName.set(path)
|
||||
cmds.setAttr(
|
||||
container["objectName"] + ".representation",
|
||||
str(representation["_id"]),
|
||||
type="string"
|
||||
)
|
||||
cmds.setAttr("{}.imageName".format(image_plane_shape),
|
||||
path,
|
||||
type="string")
|
||||
cmds.setAttr("{}.representation".format(container["objectName"]),
|
||||
str(representation["_id"]),
|
||||
type="string")
|
||||
|
||||
# Set frame range.
|
||||
project_name = legacy_io.active_project()
|
||||
|
|
@ -227,10 +234,14 @@ class ImagePlaneLoader(load.LoaderPlugin):
|
|||
start_frame = asset["data"]["frameStart"]
|
||||
end_frame = asset["data"]["frameEnd"]
|
||||
|
||||
image_plane_shape.frameOffset.set(0)
|
||||
image_plane_shape.frameIn.set(start_frame)
|
||||
image_plane_shape.frameOut.set(end_frame)
|
||||
image_plane_shape.frameCache.set(end_frame)
|
||||
for attr, value in {
|
||||
"frameOffset": 0,
|
||||
"frameIn": start_frame,
|
||||
"frameOut": end_frame,
|
||||
"frameCache": end_frame
|
||||
}:
|
||||
plug = "{}.{}".format(image_plane_shape, attr)
|
||||
cmds.setAttr(plug, value)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ from openpype.pipeline.create import (
|
|||
import openpype.hosts.maya.api.plugin
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
maintained_selection,
|
||||
get_container_members
|
||||
get_container_members,
|
||||
parent_nodes
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -118,7 +119,6 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
|
||||
def process_reference(self, context, name, namespace, options):
|
||||
import maya.cmds as cmds
|
||||
import pymel.core as pm
|
||||
|
||||
try:
|
||||
family = context["representation"]["context"]["family"]
|
||||
|
|
@ -148,7 +148,7 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
# if there are cameras, try to lock their transforms
|
||||
self._lock_camera_transforms(new_nodes)
|
||||
|
||||
current_namespace = pm.namespaceInfo(currentNamespace=True)
|
||||
current_namespace = cmds.namespaceInfo(currentNamespace=True)
|
||||
|
||||
if current_namespace != ":":
|
||||
group_name = current_namespace + ":" + group_name
|
||||
|
|
@ -158,37 +158,29 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
self[:] = new_nodes
|
||||
|
||||
if attach_to_root:
|
||||
group_node = pm.PyNode(group_name)
|
||||
roots = set()
|
||||
roots = cmds.listRelatives(group_name,
|
||||
children=True,
|
||||
fullPath=True) or []
|
||||
|
||||
for node in new_nodes:
|
||||
try:
|
||||
roots.add(pm.PyNode(node).getAllParents()[-2])
|
||||
except: # noqa: E722
|
||||
pass
|
||||
if family not in {"layout", "setdress",
|
||||
"mayaAscii", "mayaScene"}:
|
||||
# QUESTION Why do we need to exclude these families?
|
||||
with parent_nodes(roots, parent=None):
|
||||
cmds.xform(group_name, zeroTransformPivots=True)
|
||||
|
||||
if family not in ["layout", "setdress",
|
||||
"mayaAscii", "mayaScene"]:
|
||||
for root in roots:
|
||||
root.setParent(world=True)
|
||||
|
||||
group_node.zeroTransformPivots()
|
||||
for root in roots:
|
||||
root.setParent(group_node)
|
||||
|
||||
cmds.setAttr(group_name + ".displayHandle", 1)
|
||||
cmds.setAttr("{}.displayHandle".format(group_name), 1)
|
||||
|
||||
settings = get_project_settings(os.environ['AVALON_PROJECT'])
|
||||
colors = settings['maya']['load']['colors']
|
||||
c = colors.get(family)
|
||||
if c is not None:
|
||||
group_node.useOutlinerColor.set(1)
|
||||
group_node.outlinerColor.set(
|
||||
(float(c[0]) / 255),
|
||||
(float(c[1]) / 255),
|
||||
(float(c[2]) / 255))
|
||||
cmds.setAttr("{}.useOutlinerColor".format(group_name), 1)
|
||||
cmds.setAttr("{}.outlinerColor".format(group_name),
|
||||
(float(c[0]) / 255),
|
||||
(float(c[1]) / 255),
|
||||
(float(c[2]) / 255))
|
||||
|
||||
cmds.setAttr(group_name + ".displayHandle", 1)
|
||||
cmds.setAttr("{}.displayHandle".format(group_name), 1)
|
||||
# get bounding box
|
||||
bbox = cmds.exactWorldBoundingBox(group_name)
|
||||
# get pivot position on world space
|
||||
|
|
@ -202,15 +194,16 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader):
|
|||
cy = cy + pivot[1]
|
||||
cz = cz + pivot[2]
|
||||
# set selection handle offset to center of bounding box
|
||||
cmds.setAttr(group_name + ".selectHandleX", cx)
|
||||
cmds.setAttr(group_name + ".selectHandleY", cy)
|
||||
cmds.setAttr(group_name + ".selectHandleZ", cz)
|
||||
cmds.setAttr("{}.selectHandleX".format(group_name), cx)
|
||||
cmds.setAttr("{}.selectHandleY".format(group_name), cy)
|
||||
cmds.setAttr("{}.selectHandleZ".format(group_name), cz)
|
||||
|
||||
if family == "rig":
|
||||
self._post_process_rig(name, namespace, context, options)
|
||||
else:
|
||||
if "translate" in options:
|
||||
cmds.setAttr(group_name + ".t", *options["translate"])
|
||||
cmds.setAttr("{}.translate".format(group_name),
|
||||
*options["translate"])
|
||||
return new_nodes
|
||||
|
||||
def switch(self, container, representation):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
from maya import cmds, mel
|
||||
import pymel.core as pm
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
|
@ -123,43 +122,42 @@ class CollectReview(pyblish.api.InstancePlugin):
|
|||
|
||||
# Collect audio
|
||||
playback_slider = mel.eval('$tmpVar=$gPlayBackSlider')
|
||||
audio_name = cmds.timeControl(playback_slider, q=True, s=True)
|
||||
audio_name = cmds.timeControl(playback_slider,
|
||||
query=True,
|
||||
sound=True)
|
||||
display_sounds = cmds.timeControl(
|
||||
playback_slider, q=True, displaySound=True
|
||||
playback_slider, query=True, displaySound=True
|
||||
)
|
||||
|
||||
audio_nodes = []
|
||||
def get_audio_node_data(node):
|
||||
return {
|
||||
"offset": cmds.getAttr("{}.offset".format(node)),
|
||||
"filename": cmds.getAttr("{}.filename".format(node))
|
||||
}
|
||||
|
||||
audio_data = []
|
||||
|
||||
if audio_name:
|
||||
audio_nodes.append(pm.PyNode(audio_name))
|
||||
audio_data.append(get_audio_node_data(audio_name))
|
||||
|
||||
if not audio_name and display_sounds:
|
||||
start_frame = int(pm.playbackOptions(q=True, min=True))
|
||||
end_frame = float(pm.playbackOptions(q=True, max=True))
|
||||
frame_range = range(int(start_frame), int(end_frame))
|
||||
elif display_sounds:
|
||||
start_frame = int(cmds.playbackOptions(query=True, min=True))
|
||||
end_frame = int(cmds.playbackOptions(query=True, max=True))
|
||||
|
||||
for node in pm.ls(type="audio"):
|
||||
for node in cmds.ls(type="audio"):
|
||||
# Check if frame range and audio range intersections,
|
||||
# for whether to include this audio node or not.
|
||||
start_audio = node.offset.get()
|
||||
end_audio = node.offset.get() + node.duration.get()
|
||||
audio_range = range(int(start_audio), int(end_audio))
|
||||
duration = cmds.getAttr("{}.duration".format(node))
|
||||
start_audio = cmds.getAttr("{}.offset".format(node))
|
||||
end_audio = start_audio + duration
|
||||
|
||||
if bool(set(frame_range).intersection(audio_range)):
|
||||
audio_nodes.append(node)
|
||||
if start_audio <= end_frame and end_audio > start_frame:
|
||||
audio_data.append(get_audio_node_data(node))
|
||||
|
||||
instance.data["audio"] = []
|
||||
for node in audio_nodes:
|
||||
instance.data["audio"].append(
|
||||
{
|
||||
"offset": node.offset.get(),
|
||||
"filename": node.filename.get()
|
||||
}
|
||||
)
|
||||
instance.data["audio"] = audio_data
|
||||
|
||||
# Collect focal length.
|
||||
attr = camera + ".focalLength"
|
||||
focal_length = None
|
||||
if get_attribute_input(attr):
|
||||
start = instance.data["frameStart"]
|
||||
end = instance.data["frameEnd"] + 1
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from openpype.pipeline import publish
|
|||
from openpype.hosts.maya.api import lib
|
||||
|
||||
from maya import cmds
|
||||
import pymel.core as pm
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
@ -110,11 +109,11 @@ class ExtractPlayblast(publish.Extractor):
|
|||
preset["filename"] = path
|
||||
preset["overwrite"] = True
|
||||
|
||||
pm.refresh(f=True)
|
||||
cmds.refresh(force=True)
|
||||
|
||||
refreshFrameInt = int(pm.playbackOptions(q=True, minTime=True))
|
||||
pm.currentTime(refreshFrameInt - 1, edit=True)
|
||||
pm.currentTime(refreshFrameInt, edit=True)
|
||||
refreshFrameInt = int(cmds.playbackOptions(q=True, minTime=True))
|
||||
cmds.currentTime(refreshFrameInt - 1, edit=True)
|
||||
cmds.currentTime(refreshFrameInt, edit=True)
|
||||
|
||||
# Override transparency if requested.
|
||||
transparency = instance.data.get("transparency", 0)
|
||||
|
|
@ -226,7 +225,7 @@ class ExtractPlayblast(publish.Extractor):
|
|||
tags.append("delete")
|
||||
|
||||
# Add camera node name to representation data
|
||||
camera_node_name = pm.ls(camera)[0].getTransform().name()
|
||||
camera_node_name = cmds.listRelatives(camera, parent=True)[0]
|
||||
|
||||
collected_files = list(frame_collection)
|
||||
# single frame file shouldn't be in list, only as a string
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ from openpype.pipeline import publish
|
|||
from openpype.hosts.maya.api import lib
|
||||
|
||||
from maya import cmds
|
||||
import pymel.core as pm
|
||||
|
||||
|
||||
class ExtractThumbnail(publish.Extractor):
|
||||
|
|
@ -99,11 +98,11 @@ class ExtractThumbnail(publish.Extractor):
|
|||
preset["filename"] = path
|
||||
preset["overwrite"] = True
|
||||
|
||||
pm.refresh(f=True)
|
||||
cmds.refresh(force=True)
|
||||
|
||||
refreshFrameInt = int(pm.playbackOptions(q=True, minTime=True))
|
||||
pm.currentTime(refreshFrameInt - 1, edit=True)
|
||||
pm.currentTime(refreshFrameInt, edit=True)
|
||||
refreshFrameInt = int(cmds.playbackOptions(q=True, minTime=True))
|
||||
cmds.currentTime(refreshFrameInt - 1, edit=True)
|
||||
cmds.currentTime(refreshFrameInt, edit=True)
|
||||
|
||||
# Override transparency if requested.
|
||||
transparency = instance.data.get("transparency", 0)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
import pymel.core as pm
|
||||
from collections import defaultdict
|
||||
|
||||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from openpype.hosts.maya.api.lib import set_attribute
|
||||
from openpype.pipeline.publish import (
|
||||
RepairContextAction,
|
||||
ValidateContentsOrder,
|
||||
)
|
||||
|
||||
|
||||
class ValidateAttributes(pyblish.api.ContextPlugin):
|
||||
class ValidateAttributes(pyblish.api.InstancePlugin):
|
||||
"""Ensure attributes are consistent.
|
||||
|
||||
Attributes to validate and their values comes from the
|
||||
|
|
@ -27,86 +31,80 @@ class ValidateAttributes(pyblish.api.ContextPlugin):
|
|||
|
||||
attributes = None
|
||||
|
||||
def process(self, context):
|
||||
def process(self, instance):
|
||||
# Check for preset existence.
|
||||
|
||||
if not self.attributes:
|
||||
return
|
||||
|
||||
invalid = self.get_invalid(context, compute=True)
|
||||
invalid = self.get_invalid(instance, compute=True)
|
||||
if invalid:
|
||||
raise RuntimeError(
|
||||
"Found attributes with invalid values: {}".format(invalid)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, context, compute=False):
|
||||
invalid = context.data.get("invalid_attributes", [])
|
||||
def get_invalid(cls, instance, compute=False):
|
||||
if compute:
|
||||
invalid = cls.get_invalid_attributes(context)
|
||||
|
||||
return invalid
|
||||
return cls.get_invalid_attributes(instance)
|
||||
else:
|
||||
return instance.data.get("invalid_attributes", [])
|
||||
|
||||
@classmethod
|
||||
def get_invalid_attributes(cls, context):
|
||||
def get_invalid_attributes(cls, instance):
|
||||
invalid_attributes = []
|
||||
for instance in context:
|
||||
# Filter publisable instances.
|
||||
if not instance.data["publish"]:
|
||||
|
||||
# Filter families.
|
||||
families = [instance.data["family"]]
|
||||
families += instance.data.get("families", [])
|
||||
families = set(families) & set(cls.attributes.keys())
|
||||
if not families:
|
||||
return []
|
||||
|
||||
# Get all attributes to validate.
|
||||
attributes = defaultdict(dict)
|
||||
for family in families:
|
||||
if family not in cls.attributes:
|
||||
# No attributes to validate for family
|
||||
continue
|
||||
|
||||
# Filter families.
|
||||
families = [instance.data["family"]]
|
||||
families += instance.data.get("families", [])
|
||||
families = list(set(families) & set(cls.attributes.keys()))
|
||||
if not families:
|
||||
for preset_attr, preset_value in cls.attributes[family].items():
|
||||
node_name, attribute_name = preset_attr.split(".", 1)
|
||||
attributes[node_name][attribute_name] = preset_value
|
||||
|
||||
if not attributes:
|
||||
return []
|
||||
|
||||
# Get invalid attributes.
|
||||
nodes = cmds.ls(long=True)
|
||||
for node in nodes:
|
||||
node_name = node.rsplit("|", 1)[-1].rsplit(":", 1)[-1]
|
||||
if node_name not in attributes:
|
||||
continue
|
||||
|
||||
# Get all attributes to validate.
|
||||
attributes = {}
|
||||
for family in families:
|
||||
for preset in cls.attributes[family]:
|
||||
[node_name, attribute_name] = preset.split(".")
|
||||
try:
|
||||
attributes[node_name].update(
|
||||
{attribute_name: cls.attributes[family][preset]}
|
||||
)
|
||||
except KeyError:
|
||||
attributes.update({
|
||||
node_name: {
|
||||
attribute_name: cls.attributes[family][preset]
|
||||
}
|
||||
})
|
||||
for attr_name, expected in attributes.items():
|
||||
|
||||
# Get invalid attributes.
|
||||
nodes = pm.ls()
|
||||
for node in nodes:
|
||||
name = node.name(stripNamespace=True)
|
||||
if name not in attributes.keys():
|
||||
# Skip if attribute does not exist
|
||||
if not cmds.attributeQuery(attr_name, node=node, exists=True):
|
||||
continue
|
||||
|
||||
presets_to_validate = attributes[name]
|
||||
for attribute in node.listAttr():
|
||||
names = [attribute.shortName(), attribute.longName()]
|
||||
attribute_name = list(
|
||||
set(names) & set(presets_to_validate.keys())
|
||||
plug = "{}.{}".format(node, attr_name)
|
||||
value = cmds.getAttr(plug)
|
||||
if value != expected:
|
||||
invalid_attributes.append(
|
||||
{
|
||||
"attribute": plug,
|
||||
"expected": expected,
|
||||
"current": value
|
||||
}
|
||||
)
|
||||
if attribute_name:
|
||||
expected = presets_to_validate[attribute_name[0]]
|
||||
if attribute.get() != expected:
|
||||
invalid_attributes.append(
|
||||
{
|
||||
"attribute": attribute,
|
||||
"expected": expected,
|
||||
"current": attribute.get()
|
||||
}
|
||||
)
|
||||
|
||||
context.data["invalid_attributes"] = invalid_attributes
|
||||
instance.data["invalid_attributes"] = invalid_attributes
|
||||
return invalid_attributes
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
invalid = cls.get_invalid(instance)
|
||||
for data in invalid:
|
||||
data["attribute"].set(data["expected"])
|
||||
node, attr = data["attribute"].split(".", 1)
|
||||
value = data["expected"]
|
||||
set_attribute(node=node, attribute=attr, value=value)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
import pymel.core as pc
|
||||
from maya import cmds
|
||||
import pyblish.api
|
||||
|
||||
import openpype.hosts.maya.api.action
|
||||
from openpype.hosts.maya.api.lib import maintained_selection
|
||||
from openpype.hosts.maya.api.lib import (
|
||||
maintained_selection,
|
||||
delete_after,
|
||||
undo_chunk,
|
||||
get_attribute,
|
||||
set_attribute
|
||||
)
|
||||
from openpype.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateMeshOrder,
|
||||
|
|
@ -31,60 +37,68 @@ class ValidateMeshArnoldAttributes(pyblish.api.InstancePlugin):
|
|||
else:
|
||||
active = False
|
||||
|
||||
@classmethod
|
||||
def get_default_attributes(cls):
|
||||
# Get default arnold attribute values for mesh type.
|
||||
defaults = {}
|
||||
with delete_after() as tmp:
|
||||
transform = cmds.createNode("transform")
|
||||
tmp.append(transform)
|
||||
|
||||
mesh = cmds.createNode("mesh", parent=transform)
|
||||
for attr in cmds.listAttr(mesh, string="ai*"):
|
||||
plug = "{}.{}".format(mesh, attr)
|
||||
try:
|
||||
defaults[attr] = get_attribute(plug)
|
||||
except RuntimeError:
|
||||
cls.log.debug("Ignoring arnold attribute: {}".format(attr))
|
||||
|
||||
return defaults
|
||||
|
||||
@classmethod
|
||||
def get_invalid_attributes(cls, instance, compute=False):
|
||||
invalid = []
|
||||
|
||||
if compute:
|
||||
# Get default arnold attributes.
|
||||
temp_transform = pc.polyCube()[0]
|
||||
|
||||
for shape in pc.ls(instance, type="mesh"):
|
||||
for attr in temp_transform.getShape().listAttr():
|
||||
if not attr.attrName().startswith("ai"):
|
||||
continue
|
||||
meshes = cmds.ls(instance, type="mesh", long=True)
|
||||
if not meshes:
|
||||
return []
|
||||
|
||||
target_attr = pc.PyNode(
|
||||
"{}.{}".format(shape.name(), attr.attrName())
|
||||
)
|
||||
if attr.get() != target_attr.get():
|
||||
invalid.append(target_attr)
|
||||
|
||||
pc.delete(temp_transform)
|
||||
# Compare the values against the defaults
|
||||
defaults = cls.get_default_attributes()
|
||||
for mesh in meshes:
|
||||
for attr_name, default_value in defaults.items():
|
||||
plug = "{}.{}".format(mesh, attr_name)
|
||||
if get_attribute(plug) != default_value:
|
||||
invalid.append(plug)
|
||||
|
||||
instance.data["nondefault_arnold_attributes"] = invalid
|
||||
else:
|
||||
invalid.extend(instance.data["nondefault_arnold_attributes"])
|
||||
|
||||
return invalid
|
||||
return instance.data.get("nondefault_arnold_attributes", [])
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
invalid = []
|
||||
|
||||
for attr in cls.get_invalid_attributes(instance, compute=False):
|
||||
invalid.append(attr.node().name())
|
||||
|
||||
return invalid
|
||||
invalid_attrs = cls.get_invalid_attributes(instance, compute=False)
|
||||
invalid_nodes = set(attr.split(".", 1)[0] for attr in invalid_attrs)
|
||||
return sorted(invalid_nodes)
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
with maintained_selection():
|
||||
with pc.UndoChunk():
|
||||
temp_transform = pc.polyCube()[0]
|
||||
|
||||
with undo_chunk():
|
||||
defaults = cls.get_default_attributes()
|
||||
attributes = cls.get_invalid_attributes(
|
||||
instance, compute=False
|
||||
)
|
||||
for attr in attributes:
|
||||
source = pc.PyNode(
|
||||
"{}.{}".format(
|
||||
temp_transform.getShape(), attr.attrName()
|
||||
)
|
||||
node, attr_name = attr.split(".", 1)
|
||||
value = defaults[attr_name]
|
||||
set_attribute(
|
||||
node=node,
|
||||
attribute=attr_name,
|
||||
value=value
|
||||
)
|
||||
attr.set(source.get())
|
||||
|
||||
pc.delete(temp_transform)
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import pyblish.api
|
||||
import openpype.hosts.maya.api.action
|
||||
import math
|
||||
import maya.api.OpenMaya as om
|
||||
import pymel.core as pm
|
||||
|
||||
from six.moves import xrange
|
||||
|
||||
from maya import cmds
|
||||
import maya.api.OpenMaya as om
|
||||
import pyblish.api
|
||||
|
||||
import openpype.hosts.maya.api.action
|
||||
from openpype.pipeline.publish import ValidateMeshOrder
|
||||
|
||||
|
||||
|
|
@ -185,8 +186,7 @@ class GetOverlappingUVs(object):
|
|||
|
||||
center, radius = self._createBoundingCircle(meshfn)
|
||||
for i in xrange(meshfn.numPolygons): # noqa: F821
|
||||
rayb1, face1Orig, face1Vec = self._createRayGivenFace(
|
||||
meshfn, i)
|
||||
rayb1, face1Orig, face1Vec = self._createRayGivenFace(meshfn, i)
|
||||
if not rayb1:
|
||||
continue
|
||||
cui = center[2*i]
|
||||
|
|
@ -206,8 +206,8 @@ class GetOverlappingUVs(object):
|
|||
if (dsqr >= (ri + rj) * (ri + rj)):
|
||||
continue
|
||||
|
||||
rayb2, face2Orig, face2Vec = self._createRayGivenFace(
|
||||
meshfn, j)
|
||||
rayb2, face2Orig, face2Vec = self._createRayGivenFace(meshfn,
|
||||
j)
|
||||
if not rayb2:
|
||||
continue
|
||||
# Exclude the degenerate face
|
||||
|
|
@ -240,37 +240,45 @@ class ValidateMeshHasOverlappingUVs(pyblish.api.InstancePlugin):
|
|||
optional = True
|
||||
|
||||
@classmethod
|
||||
def _get_overlapping_uvs(cls, node):
|
||||
""" Check if mesh has overlapping UVs.
|
||||
def _get_overlapping_uvs(cls, mesh):
|
||||
"""Return overlapping UVs of mesh.
|
||||
|
||||
Args:
|
||||
mesh (str): Mesh node name
|
||||
|
||||
Returns:
|
||||
list: Overlapping uvs for the input mesh in all uv sets.
|
||||
|
||||
:param node: node to check
|
||||
:type node: str
|
||||
:returns: True is has overlapping UVs, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
ovl = GetOverlappingUVs()
|
||||
|
||||
# Store original uv set
|
||||
original_current_uv_set = cmds.polyUVSet(mesh,
|
||||
query=True,
|
||||
currentUVSet=True)
|
||||
|
||||
overlapping_faces = []
|
||||
for i, uv in enumerate(pm.polyUVSet(node, q=1, auv=1)):
|
||||
pm.polyUVSet(node, cuv=1, uvSet=uv)
|
||||
overlapping_faces.extend(ovl._getOverlapUVFaces(str(node)))
|
||||
for uv_set in cmds.polyUVSet(mesh, query=True, allUVSets=True):
|
||||
cmds.polyUVSet(mesh, currentUVSet=True, uvSet=uv_set)
|
||||
overlapping_faces.extend(ovl._getOverlapUVFaces(mesh))
|
||||
|
||||
# Restore original uv set
|
||||
cmds.polyUVSet(mesh, currentUVSet=True, uvSet=original_current_uv_set)
|
||||
|
||||
return overlapping_faces
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance, compute=False):
|
||||
invalid = []
|
||||
|
||||
if compute:
|
||||
instance.data["overlapping_faces"] = []
|
||||
for node in pm.ls(instance, type="mesh"):
|
||||
invalid = []
|
||||
for node in cmds.ls(instance, type="mesh"):
|
||||
faces = cls._get_overlapping_uvs(node)
|
||||
invalid.extend(faces)
|
||||
# Store values for later.
|
||||
instance.data["overlapping_faces"].extend(faces)
|
||||
else:
|
||||
invalid.extend(instance.data["overlapping_faces"])
|
||||
|
||||
return invalid
|
||||
instance.data["overlapping_faces"] = invalid
|
||||
|
||||
return instance.data.get("overlapping_faces", [])
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import pymel.core as pm
|
||||
import maya.cmds as cmds
|
||||
|
||||
import pyblish.api
|
||||
|
|
@ -12,7 +11,7 @@ import openpype.hosts.maya.api.action
|
|||
|
||||
def get_namespace(node_name):
|
||||
# ensure only node's name (not parent path)
|
||||
node_name = node_name.rsplit("|")[-1]
|
||||
node_name = node_name.rsplit("|", 1)[-1]
|
||||
# ensure only namespace
|
||||
return node_name.rpartition(":")[0]
|
||||
|
||||
|
|
@ -45,13 +44,11 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin):
|
|||
|
||||
invalid = cls.get_invalid(instance)
|
||||
|
||||
# Get nodes with pymel since we'll be renaming them
|
||||
# Since we don't want to keep checking the hierarchy
|
||||
# or full paths
|
||||
nodes = pm.ls(invalid)
|
||||
# Iterate over the nodes by long to short names to iterate the lowest
|
||||
# in hierarchy nodes first. This way we avoid having renamed parents
|
||||
# before renaming children nodes
|
||||
for node in sorted(invalid, key=len, reverse=True):
|
||||
|
||||
for node in nodes:
|
||||
namespace = node.namespace()
|
||||
if namespace:
|
||||
name = node.nodeName()
|
||||
node.rename(name[len(namespace):])
|
||||
node_name = node.rsplit("|", 1)[-1]
|
||||
node_name_without_namespace = node_name.rsplit(":")[-1]
|
||||
cmds.rename(node, node_name_without_namespace)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
import pymel.core as pc
|
||||
from collections import defaultdict
|
||||
|
||||
from maya import cmds
|
||||
|
||||
import pyblish.api
|
||||
|
||||
import openpype.hosts.maya.api.action
|
||||
from openpype.hosts.maya.api.lib import get_id, set_id
|
||||
from openpype.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
)
|
||||
|
||||
|
||||
def get_basename(node):
|
||||
"""Return node short name without namespace"""
|
||||
return node.rsplit("|", 1)[-1].rsplit(":", 1)[-1]
|
||||
|
||||
|
||||
class ValidateRigOutputIds(pyblish.api.InstancePlugin):
|
||||
"""Validate rig output ids.
|
||||
|
||||
|
|
@ -30,43 +38,48 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin):
|
|||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance, compute=False):
|
||||
invalid = cls.get_invalid_matches(instance, compute=compute)
|
||||
return [x["node"].longName() for x in invalid]
|
||||
invalid_matches = cls.get_invalid_matches(instance, compute=compute)
|
||||
return list(invalid_matches.keys())
|
||||
|
||||
@classmethod
|
||||
def get_invalid_matches(cls, instance, compute=False):
|
||||
invalid = []
|
||||
invalid = {}
|
||||
|
||||
if compute:
|
||||
out_set = next(x for x in instance if x.endswith("out_SET"))
|
||||
instance_nodes = pc.sets(out_set, query=True)
|
||||
instance_nodes.extend(
|
||||
[x.getShape() for x in instance_nodes if x.getShape()])
|
||||
|
||||
scene_nodes = pc.ls(type="transform") + pc.ls(type="mesh")
|
||||
instance_nodes = cmds.sets(out_set, query=True, nodesOnly=True)
|
||||
instance_nodes = cmds.ls(instance_nodes, long=True)
|
||||
for node in instance_nodes:
|
||||
shapes = cmds.listRelatives(node, shapes=True, fullPath=True)
|
||||
if shapes:
|
||||
instance_nodes.extend(shapes)
|
||||
|
||||
scene_nodes = cmds.ls(type="transform") + cmds.ls(type="mesh")
|
||||
scene_nodes = set(scene_nodes) - set(instance_nodes)
|
||||
|
||||
scene_nodes_by_basename = defaultdict(list)
|
||||
for node in scene_nodes:
|
||||
basename = get_basename(node)
|
||||
scene_nodes_by_basename[basename].append(node)
|
||||
|
||||
for instance_node in instance_nodes:
|
||||
matches = []
|
||||
basename = instance_node.name(stripNamespace=True)
|
||||
for scene_node in scene_nodes:
|
||||
if scene_node.name(stripNamespace=True) == basename:
|
||||
matches.append(scene_node)
|
||||
basename = get_basename(instance_node)
|
||||
if basename not in scene_nodes_by_basename:
|
||||
continue
|
||||
|
||||
if matches:
|
||||
ids = [instance_node.cbId.get()]
|
||||
ids.extend([x.cbId.get() for x in matches])
|
||||
ids = set(ids)
|
||||
matches = scene_nodes_by_basename[basename]
|
||||
|
||||
if len(ids) > 1:
|
||||
cls.log.error(
|
||||
"\"{}\" id mismatch to: {}".format(
|
||||
instance_node.longName(), matches
|
||||
)
|
||||
)
|
||||
invalid.append(
|
||||
{"node": instance_node, "matches": matches}
|
||||
ids = set(get_id(node) for node in matches)
|
||||
ids.add(get_id(instance_node))
|
||||
|
||||
if len(ids) > 1:
|
||||
cls.log.error(
|
||||
"\"{}\" id mismatch to: {}".format(
|
||||
instance_node.longName(), matches
|
||||
)
|
||||
)
|
||||
invalid[instance_node] = matches
|
||||
|
||||
instance.data["mismatched_output_ids"] = invalid
|
||||
else:
|
||||
|
|
@ -76,19 +89,21 @@ class ValidateRigOutputIds(pyblish.api.InstancePlugin):
|
|||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
invalid = cls.get_invalid_matches(instance)
|
||||
invalid_matches = cls.get_invalid_matches(instance)
|
||||
|
||||
multiple_ids_match = []
|
||||
for data in invalid:
|
||||
ids = [x.cbId.get() for x in data["matches"]]
|
||||
for instance_node, matches in invalid_matches.items():
|
||||
ids = set(get_id(node) for node in matches)
|
||||
|
||||
# If there are multiple scene ids matched, and error needs to be
|
||||
# raised for manual correction.
|
||||
if len(ids) > 1:
|
||||
multiple_ids_match.append(data)
|
||||
multiple_ids_match.append({"node": instance_node,
|
||||
"matches": matches})
|
||||
continue
|
||||
|
||||
data["node"].cbId.set(ids[0])
|
||||
id_to_set = next(iter(ids))
|
||||
set_id(instance_node, id_to_set, overwrite=True)
|
||||
|
||||
if multiple_ids_match:
|
||||
raise RuntimeError(
|
||||
|
|
|
|||
|
|
@ -53,5 +53,5 @@ There are four settings available:
|
|||
|
||||
## Q&A
|
||||
### Is it safe to rename an entity from Kitsu?
|
||||
Absolutely! Entities are linked by their unique IDs between the two databases.
|
||||
Absolutely! Entities are linked by their unique IDs between the two databases.
|
||||
But renaming from the OP's Project Manager won't apply the change to Kitsu, it'll be overridden during the next synchronization.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue