diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 394f92ed42..da34896c3f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2778,9 +2778,37 @@ def bake_to_world_space(nodes, list: The newly created and baked node names. """ + @contextlib.contextmanager + def _unlock_attr(attr): + """Unlock attribute during context if it is locked""" + if not cmds.getAttr(attr, lock=True): + # If not locked, do nothing + yield + return + try: + cmds.setAttr(attr, lock=False) + yield + finally: + cmds.setAttr(attr, lock=True) def _get_attrs(node): - """Workaround for buggy shape attribute listing with listAttr""" + """Workaround for buggy shape attribute listing with listAttr + + This will only return keyable settable attributes that have an + incoming connections (those that have a reason to be baked). + + Technically this *may* fail to return attributes driven by complex + expressions for which maya makes no connections, e.g. doing actual + `setAttr` calls in expressions. + + Arguments: + node (str): The node to list attributes for. + + Returns: + list: Keyable attributes with incoming connections. + The attribute may be locked. + + """ attrs = cmds.listAttr(node, write=True, scalar=True, @@ -2805,14 +2833,14 @@ def bake_to_world_space(nodes, return valid_attrs - transform_attrs = set(["t", "r", "s", - "tx", "ty", "tz", - "rx", "ry", "rz", - "sx", "sy", "sz"]) + transform_attrs = {"t", "r", "s", + "tx", "ty", "tz", + "rx", "ry", "rz", + "sx", "sy", "sz"} world_space_nodes = [] - with delete_after() as delete_bin: - + with ExitStack() as stack: + delete_bin = stack.enter_context(delete_after()) # Create the duplicate nodes that are in world-space connected to # the originals for node in nodes: @@ -2824,23 +2852,26 @@ def bake_to_world_space(nodes, name=new_name, renameChildren=True)[0] # noqa - # Connect all attributes on the node except for transform - # attributes - attrs = _get_attrs(node) - attrs = set(attrs) - transform_attrs if attrs else [] + # Parent new node to world + if cmds.listRelatives(new_node, parent=True): + new_node = cmds.parent(new_node, world=True)[0] + # Temporarily unlock and passthrough connect all attributes + # so we can bake them over time + # Skip transform attributes because we will constrain them later + attrs = set(_get_attrs(node)) - transform_attrs for attr in attrs: - orig_node_attr = '{0}.{1}'.format(node, attr) - new_node_attr = '{0}.{1}'.format(new_node, attr) - - # unlock to avoid connection errors - cmds.setAttr(new_node_attr, lock=False) + orig_node_attr = "{}.{}".format(node, attr) + new_node_attr = "{}.{}".format(new_node, attr) + # unlock during context to avoid connection errors + stack.enter_context(_unlock_attr(new_node_attr)) cmds.connectAttr(orig_node_attr, new_node_attr, force=True) - # If shapes are also baked then connect those keyable attributes + # If shapes are also baked then also temporarily unlock and + # passthrough connect all shape attributes for baking if shape: children_shapes = cmds.listRelatives(new_node, children=True, @@ -2855,25 +2886,19 @@ def bake_to_world_space(nodes, children_shapes): attrs = _get_attrs(orig_shape) for attr in attrs: - orig_node_attr = '{0}.{1}'.format(orig_shape, attr) - new_node_attr = '{0}.{1}'.format(new_shape, attr) - - # unlock to avoid connection errors - cmds.setAttr(new_node_attr, lock=False) + orig_node_attr = "{}.{}".format(orig_shape, attr) + new_node_attr = "{}.{}".format(new_shape, attr) + # unlock during context to avoid connection errors + stack.enter_context(_unlock_attr(new_node_attr)) cmds.connectAttr(orig_node_attr, new_node_attr, force=True) - # Parent to world - if cmds.listRelatives(new_node, parent=True): - new_node = cmds.parent(new_node, world=True)[0] - - # Unlock transform attributes so constraint can be created + # Constraint transforms for attr in transform_attrs: - cmds.setAttr('{0}.{1}'.format(new_node, attr), lock=False) - - # Constraints + transform_attr = "{}.{}".format(new_node, attr) + stack.enter_context(_unlock_attr(transform_attr)) delete_bin.extend(cmds.parentConstraint(node, new_node, mo=False)) delete_bin.extend(cmds.scaleConstraint(node, new_node, mo=False)) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 03fd91bec2..c8ddbde061 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -44,17 +44,17 @@ XML_CHAR_REF_REGEX_HEX = re.compile(r"&#x?[0-9a-fA-F]+;") ARRAY_TYPE_REGEX = re.compile(r"^(int|float|string)\[\d+\]$") IMAGE_EXTENSIONS = { - ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal", - ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits", - ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer", - ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", - ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr", - ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd", - ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", - ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", ".ras", - ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep", - ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf", - ".xpm", ".xwd" + ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", + ".cal", ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", + ".fits", ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", + ".icer", ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2", + ".jng", ".jpeg", ".jpeg-ls", ".jpeg-hdr", ".2000", ".jpg", + ".kra", ".logluv", ".mng", ".miff", ".nrrd", ".ora", + ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf", + ".pictor", ".png", ".psd", ".psb", ".psp", ".qtvr", + ".ras", ".rgbe", ".sgi", ".tga", + ".tif", ".tiff", ".tiff/ep", ".tiff/it", ".ufo", ".ufp", + ".wbmp", ".webp", ".xr", ".xt", ".xbm", ".xcf", ".xpm", ".xwd" } VIDEO_EXTENSIONS = { diff --git a/openpype/modules/base.py b/openpype/modules/base.py index 1a2513b4b4..41be2998f9 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -542,7 +542,8 @@ def _load_modules(): module_dirs.insert(0, current_dir) addons_dir = os.path.join(os.path.dirname(current_dir), "addons") - module_dirs.append(addons_dir) + if os.path.exists(addons_dir): + module_dirs.append(addons_dir) ignored_host_names = set(IGNORED_HOSTS_IN_AYON) ignored_current_dir_filenames = set(IGNORED_DEFAULT_FILENAMES) diff --git a/server_addon/deadline/server/settings/publish_plugins.py b/server_addon/deadline/server/settings/publish_plugins.py index a989f3ad9d..dfb30f9b41 100644 --- a/server_addon/deadline/server/settings/publish_plugins.py +++ b/server_addon/deadline/server/settings/publish_plugins.py @@ -87,7 +87,7 @@ class MayaSubmitDeadlineModel(BaseSettingsModel): title="Disable Strict Error Check profiles" ) - @validator("limit", "scene_patches") + @validator("scene_patches") def validate_unique_names(cls, value): ensure_unique_names(value) return value diff --git a/server_addon/deadline/server/version.py b/server_addon/deadline/server/version.py index 0a8da88258..f1380eede2 100644 --- a/server_addon/deadline/server/version.py +++ b/server_addon/deadline/server/version.py @@ -1 +1 @@ -__version__ = "0.1.6" +__version__ = "0.1.7"