From b74774d02fc4cf2efc8fc00c482845ab1b2dbdaa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 25 Apr 2022 18:09:35 +0200 Subject: [PATCH 01/11] Implement `get_visible_in_frame_range` for extracting Alembics --- openpype/hosts/maya/api/lib.py | 204 ++++++++++++++++++ .../maya/plugins/publish/extract_animation.py | 13 +- .../plugins/publish/extract_pointcache.py | 13 +- 3 files changed, 228 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 801cdb16f4..5304112dcf 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3176,3 +3176,207 @@ def parent_nodes(nodes, parent=None): node[0].setParent(node[1]) if delete_parent: pm.delete(parent_node) + + +@contextlib.contextmanager +def maintained_time(): + ct = cmds.currentTime(query=True) + try: + yield + finally: + cmds.currentTime(ct, edit=True) + + +def get_visible_in_frame_range(nodes, start, end): + """Return nodes that are visible in start-end frame range. + + - Ignores intermediateObjects completely. + - Considers animated visibility attributes + upstream visibilities. + + This is optimized for large scenes where some nodes in the parent + hierarchy might have some input connections to the visibilities, + e.g. key, driven keys, connections to other attributes, etc. + + This only does a single time step to `start` if current frame is + not inside frame range since the assumption is made that changing + a frame isn't so slow that it beats querying all visibility + plugs through MDGContext on another frame. + + Args: + nodes (list): List of node names to consider. + start (int): Start frame. + end (int): End frame. + + Returns: + list: List of node names. These will be long full path names so + might have a longer name than the input nodes. + + """ + # States we consider per node + VISIBLE = 1 # always visible + INVISIBLE = 0 # always invisible + ANIMATED = -1 # animated visibility + + # Ensure integers + start = int(start) + end = int(end) + + # Consider only non-intermediate dag nodes and use the "long" names. + nodes = cmds.ls(nodes, long=True, noIntermediate=True, type="dagNode") + if not nodes: + return [] + + with maintained_time(): + # Go to first frame of the range if we current time is outside of + # the queried range. This is to do a single query on which are at + # least visible at a time inside the range, (e.g those that are + # always visible) + current_time = cmds.currentTime(query=True) + if not (start <= current_time <= end): + cmds.currentTime(start) + + visible = cmds.ls(nodes, long=True, visible=True) + if len(visible) == len(nodes) or start == end: + # All are visible on frame one, so they are at least visible once + # inside the frame range. + return visible + + # For the invisible ones check whether its visibility and/or + # any of its parents visibility attributes are animated. If so, it might + # get visible on other frames in the range. + def memodict(f): + """Memoization decorator for a function taking a single argument. + + See: http://code.activestate.com/recipes/ + 578231-probably-the-fastest-memoization-decorator-in-the-/ + """ + + class memodict(dict): + def __missing__(self, key): + ret = self[key] = f(key) + return ret + + return memodict().__getitem__ + + @memodict + def get_state(node): + plug = node + ".visibility" + connections = cmds.listConnections(plug, + source=True, + destination=False) + if connections: + return ANIMATED + else: + return VISIBLE if cmds.getAttr(plug) else INVISIBLE + + visible = set(visible) + invisible = [node for node in nodes if node not in visible] + always_invisible = set() + # Iterate over the nodes by short to long names, so we iterate the highest + # in hierarcy nodes first. So the collected data can be used from the + # cache for parent queries in next iterations. + node_dependencies = dict() + for node in sorted(invisible, key=len): + + state = get_state(node) + if state == INVISIBLE: + always_invisible.add(node) + continue + + # If not always invisible by itself we should go through and check + # the parents to see if any of them are always invisible. For those + # that are "ANIMATED" we consider that this node is dependent on + # that attribute, we store them as dependency. + dependencies = set() + if state == ANIMATED: + dependencies.add(node) + + traversed_parents = list() + for parent in iter_parents(node): + + if not parent: + # Workaround bug in iter_parents + continue + + if parent in always_invisible or get_state(parent) == INVISIBLE: + # When parent is always invisible then consider this parent, + # this node we started from and any of the parents we + # have traversed in-between to be *always invisible* + always_invisible.add(parent) + always_invisible.add(node) + always_invisible.update(traversed_parents) + break + + # If we have traversed the parent before and its visibility + # was dependent on animated visibilities then we can just extend + # its dependencies for to those for this node and break further + # iteration upwards. + parent_dependencies = node_dependencies.get(parent, None) + if parent_dependencies is not None: + dependencies.update(parent_dependencies) + break + + state = get_state(parent) + if state == ANIMATED: + dependencies.add(parent) + + traversed_parents.append(parent) + + if node not in always_invisible and dependencies: + node_dependencies[node] = dependencies + + if not node_dependencies: + return list(visible) + + # Now we only have to check the visibilities for nodes that have animated + # visibility dependencies upstream. The fastest way to check these + # visibility attributes across different frames is with Python api 2.0 + # so we do that. + @memodict + def get_visibility_mplug(node): + """Return api 2.0 MPlug with cached memoize decorator""" + sel = om.MSelectionList() + sel.add(node) + dag = sel.getDagPath(0) + return om.MFnDagNode(dag).findPlug("visibility", True) + + # We skip the first frame as we already used that frame to check for + # overall visibilities. And end+1 to include the end frame. + scene_units = om.MTime.uiUnit() + for frame in range(start + 1, end + 1): + + mtime = om.MTime(frame, unit=scene_units) + context = om.MDGContext(mtime) + + # Build little cache so we don't query the same MPlug's value + # again if it was checked on this frame and also is a dependency + # for another node + frame_visibilities = {} + + for node, dependencies in list(node_dependencies.items()): + + for dependency in dependencies: + + dependency_visible = frame_visibilities.get(dependency, None) + if dependency_visible is None: + mplug = get_visibility_mplug(dependency) + dependency_visible = mplug.asBool(context) + frame_visibilities[dependency] = dependency_visible + + if not dependency_visible: + # One dependency is not visible, thus the + # node is not visible. + break + + else: + # All dependencies are visible. + visible.add(node) + # Remove node with dependencies for next iterations + # because it was visible at least once. + node_dependencies.pop(node) + + # If no more nodes to process break the frame iterations.. + if not node_dependencies: + break + + return list(visible) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 8a8bd67cd8..b63cebde04 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -6,7 +6,8 @@ import openpype.api from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, - maintained_selection + maintained_selection, + get_visible_in_frame_range ) @@ -70,6 +71,16 @@ class ExtractAnimation(openpype.api.Extractor): # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True + if instance.data.get("visibleOnly", False): + # If we only want to include nodes that are visible in the frame + # range then we need to do our own check. Alembic's `visibleOnly` + # flag does not filter out those that are only hidden on some + # frames as it counts "animated" or "connected" visibilities as + # if it's always visible. + nodes = get_visible_in_frame_range(nodes, + start=start, + end=end) + with suspended_refresh(): with maintained_selection(): cmds.select(nodes, noExpand=True) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 60502fdde1..4510943a48 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -6,7 +6,8 @@ import openpype.api from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, - maintained_selection + maintained_selection, + get_visible_in_frame_range ) @@ -73,6 +74,16 @@ class ExtractAlembic(openpype.api.Extractor): # Since Maya 2017 alembic supports multiple uv sets - write them. options["writeUVSets"] = True + if instance.data.get("visibleOnly", False): + # If we only want to include nodes that are visible in the frame + # range then we need to do our own check. Alembic's `visibleOnly` + # flag does not filter out those that are only hidden on some + # frames as it counts "animated" or "connected" visibilities as + # if it's always visible. + nodes = get_visible_in_frame_range(nodes, + start=start, + end=end) + with suspended_refresh(): with maintained_selection(): cmds.select(nodes, noExpand=True) From 66f8ebbd0f175e275bae7ae4b555f55b7fe12163 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Apr 2022 09:10:03 +0200 Subject: [PATCH 02/11] Use MDGContext as context manager - Functionality added in Maya 2018 - Usage without `MDGContext.makeCurrent()` is deprecated since Maya 2022 --- openpype/hosts/maya/api/lib.py | 51 +++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 5304112dcf..82de105d16 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3340,40 +3340,47 @@ def get_visible_in_frame_range(nodes, start, end): dag = sel.getDagPath(0) return om.MFnDagNode(dag).findPlug("visibility", True) + @contextlib.contextmanager + def dgcontext(mtime): + """MDGContext context manager""" + context = om.MDGContext(mtime) + try: + previous = context.makeCurrent() + yield context + finally: + previous.makeCurrent() + # We skip the first frame as we already used that frame to check for # overall visibilities. And end+1 to include the end frame. scene_units = om.MTime.uiUnit() for frame in range(start + 1, end + 1): - mtime = om.MTime(frame, unit=scene_units) - context = om.MDGContext(mtime) # Build little cache so we don't query the same MPlug's value # again if it was checked on this frame and also is a dependency # for another node frame_visibilities = {} + with dgcontext(mtime) as context: + for node, dependencies in list(node_dependencies.items()): + for dependency in dependencies: + dependency_visible = frame_visibilities.get(dependency, + None) + if dependency_visible is None: + mplug = get_visibility_mplug(dependency) + dependency_visible = mplug.asBool(context) + frame_visibilities[dependency] = dependency_visible - for node, dependencies in list(node_dependencies.items()): + if not dependency_visible: + # One dependency is not visible, thus the + # node is not visible. + break - for dependency in dependencies: - - dependency_visible = frame_visibilities.get(dependency, None) - if dependency_visible is None: - mplug = get_visibility_mplug(dependency) - dependency_visible = mplug.asBool(context) - frame_visibilities[dependency] = dependency_visible - - if not dependency_visible: - # One dependency is not visible, thus the - # node is not visible. - break - - else: - # All dependencies are visible. - visible.add(node) - # Remove node with dependencies for next iterations - # because it was visible at least once. - node_dependencies.pop(node) + else: + # All dependencies are visible. + visible.add(node) + # Remove node with dependencies for next frame iterations + # because it was visible at least once. + node_dependencies.pop(node) # If no more nodes to process break the frame iterations.. if not node_dependencies: From 338bb1560a33582364f3a45724cf2ff19f89a2da Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Apr 2022 09:43:16 +0200 Subject: [PATCH 03/11] Fix bug in `iter_parents` Previously an empty string could be yielded for e.g. `"|cube"` splitting to `["", "cube"]` --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 82de105d16..901b8c4a4c 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1876,7 +1876,7 @@ def iter_parents(node): """ while True: split = node.rsplit("|", 1) - if len(split) == 1: + if len(split) == 1 or not split[0]: return node = split[0] From b7d52214101960e8e821efdca417306ab99957e8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 26 Apr 2022 09:43:56 +0200 Subject: [PATCH 04/11] Remove redundant workaround --- openpype/hosts/maya/api/lib.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 901b8c4a4c..52e84c00ab 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3294,10 +3294,6 @@ def get_visible_in_frame_range(nodes, start, end): traversed_parents = list() for parent in iter_parents(node): - if not parent: - # Workaround bug in iter_parents - continue - if parent in always_invisible or get_state(parent) == INVISIBLE: # When parent is always invisible then consider this parent, # this node we started from and any of the parents we From 4c259c443e1c9a96c92bf48113eda9d31bff309b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 28 Apr 2022 17:30:21 +0200 Subject: [PATCH 05/11] Improve comments --- openpype/hosts/maya/api/lib.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 52e84c00ab..79f2442d16 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3227,10 +3227,9 @@ def get_visible_in_frame_range(nodes, start, end): return [] with maintained_time(): - # Go to first frame of the range if we current time is outside of - # the queried range. This is to do a single query on which are at - # least visible at a time inside the range, (e.g those that are - # always visible) + # Go to first frame of the range if the current time is outside + # the queried range so can directly query all visible nodes on + # that frame. current_time = cmds.currentTime(query=True) if not (start <= current_time <= end): cmds.currentTime(start) @@ -3272,8 +3271,8 @@ def get_visible_in_frame_range(nodes, start, end): visible = set(visible) invisible = [node for node in nodes if node not in visible] always_invisible = set() - # Iterate over the nodes by short to long names, so we iterate the highest - # in hierarcy nodes first. So the collected data can be used from the + # Iterate over the nodes by short to long names to iterate the highest + # in hierarchy nodes first. So the collected data can be used from the # cache for parent queries in next iterations. node_dependencies = dict() for node in sorted(invisible, key=len): From 3b8b479712af6e95545539921f0439de547c460b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 09:07:28 +0200 Subject: [PATCH 06/11] Skip extraction when no visible nodes found in frame range --- openpype/hosts/maya/plugins/publish/extract_animation.py | 7 +++++++ openpype/hosts/maya/plugins/publish/extract_pointcache.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index b04e2bf0c4..4b650b4b26 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -86,6 +86,13 @@ class ExtractAnimation(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) + if not nodes: + self.log.warning( + "No visible nodes found in frame range {}-{}. " + "Skipping extraction because `visibleOnly` is enabled on " + "the instance.".format(start, end) + ) + return with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index f582a106d9..768ee6da3d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -89,6 +89,13 @@ class ExtractAlembic(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) + if not nodes: + self.log.warning( + "No visible nodes found in frame range {}-{}. " + "Skipping extraction because `visibleOnly` is enabled on " + "the instance.".format(start, end) + ) + return with suspended_refresh(): with maintained_selection(): From ee273892e871b57607a8f8ad1792d3c5ef5ad95b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:10:11 +0200 Subject: [PATCH 07/11] Add alembic visible only validator --- openpype/hosts/maya/api/lib.py | 4 +- .../plugins/publish/validate_visible_only.py | 53 +++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/maya/plugins/publish/validate_visible_only.py diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b5bdca456e..ab9c06280f 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3241,8 +3241,8 @@ def get_visible_in_frame_range(nodes, start, end): Args: nodes (list): List of node names to consider. - start (int): Start frame. - end (int): End frame. + start (int, float): Start frame. + end (int, float): End frame. Returns: list: List of node names. These will be long full path names so diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py new file mode 100644 index 0000000000..9094a421c6 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -0,0 +1,53 @@ +import pyblish.api + +import openpype.api +from openpype.hosts.maya.api.lib import get_visible_in_frame_range +import openpype.hosts.maya.api.action + + +class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): + """Validates at least a single node is visible in frame range. + + This validation only validates if the `visibleOnly` flag is enabled + on the instance - otherwise the validation is skipped. + + """ + order = openpype.api.ValidateContentsOrder + 0.05 + label = "Alembic Visible Only" + hosts = ["maya"] + families = ["pointcache", "animation"] + actions = [openpype.hosts.maya.api.action.SelectInvalidAction] + + def process(self, instance): + + if not instance.data.get("visibleOnly", False): + self.log.debug("Visible only is disabled. Validation skipped..") + return + + invalid = self.get_invalid(instance) + if invalid: + start, end = self.get_frame_range(instance) + raise RuntimeError("No visible nodes found in " + "frame range {}-{}.".format(start, end)) + + @classmethod + def get_invalid(cls, instance): + + if instance.data["family"] == "animation": + # Special behavior to use the nodes in out_SET + nodes = instance.data["out_hierarchy"] + else: + nodes = instance[:] + + start, end = cls.get_frame_range(instance) + visible = get_visible_in_frame_range(nodes, start, end) + + if not visible: + # Return the nodes we have considered so the user can identify + # them with the select invalid action + return nodes + + @staticmethod + def get_frame_range(instance): + data = instance.data + return data["frameStartHandle"], data["frameEndHandle"] From 96150eba27fc64e519b7c1756e6b0f36f886746a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:10:45 +0200 Subject: [PATCH 08/11] Remove no visible nodes warning in extractors since validator catches those cases --- openpype/hosts/maya/plugins/publish/extract_animation.py | 7 ------- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 7 ------- 2 files changed, 14 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index 4b650b4b26..b04e2bf0c4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -86,13 +86,6 @@ class ExtractAnimation(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) - if not nodes: - self.log.warning( - "No visible nodes found in frame range {}-{}. " - "Skipping extraction because `visibleOnly` is enabled on " - "the instance.".format(start, end) - ) - return with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 768ee6da3d..f582a106d9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -89,13 +89,6 @@ class ExtractAlembic(openpype.api.Extractor): nodes = get_visible_in_frame_range(nodes, start=start, end=end) - if not nodes: - self.log.warning( - "No visible nodes found in frame range {}-{}. " - "Skipping extraction because `visibleOnly` is enabled on " - "the instance.".format(start, end) - ) - return with suspended_refresh(): with maintained_selection(): From a29d1d493243915db96d6d09a40471c3177b9558 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:25:17 +0200 Subject: [PATCH 09/11] Use iterator to speed up visible only validation for most cases --- openpype/hosts/maya/api/lib.py | 15 +++++++-------- .../maya/plugins/publish/extract_animation.py | 8 ++++---- .../maya/plugins/publish/extract_pointcache.py | 8 ++++---- .../maya/plugins/publish/validate_visible_only.py | 6 ++---- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index ab9c06280f..637f9e951b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3224,8 +3224,8 @@ def maintained_time(): cmds.currentTime(ct, edit=True) -def get_visible_in_frame_range(nodes, start, end): - """Return nodes that are visible in start-end frame range. +def iter_visible_in_frame_range(nodes, start, end): + """Yield nodes that are visible in start-end frame range. - Ignores intermediateObjects completely. - Considers animated visibility attributes + upstream visibilities. @@ -3261,7 +3261,7 @@ def get_visible_in_frame_range(nodes, start, end): # Consider only non-intermediate dag nodes and use the "long" names. nodes = cmds.ls(nodes, long=True, noIntermediate=True, type="dagNode") if not nodes: - return [] + return with maintained_time(): # Go to first frame of the range if the current time is outside @@ -3275,7 +3275,8 @@ def get_visible_in_frame_range(nodes, start, end): if len(visible) == len(nodes) or start == end: # All are visible on frame one, so they are at least visible once # inside the frame range. - return visible + for node in visible: + yield node # For the invisible ones check whether its visibility and/or # any of its parents visibility attributes are animated. If so, it might @@ -3358,7 +3359,7 @@ def get_visible_in_frame_range(nodes, start, end): node_dependencies[node] = dependencies if not node_dependencies: - return list(visible) + return # Now we only have to check the visibilities for nodes that have animated # visibility dependencies upstream. The fastest way to check these @@ -3409,7 +3410,7 @@ def get_visible_in_frame_range(nodes, start, end): else: # All dependencies are visible. - visible.add(node) + yield node # Remove node with dependencies for next frame iterations # because it was visible at least once. node_dependencies.pop(node) @@ -3417,5 +3418,3 @@ def get_visible_in_frame_range(nodes, start, end): # If no more nodes to process break the frame iterations.. if not node_dependencies: break - - return list(visible) diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index b04e2bf0c4..b0beb5968e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - get_visible_in_frame_range + iter_visible_in_frame_range ) @@ -83,9 +83,9 @@ class ExtractAnimation(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = get_visible_in_frame_range(nodes, - start=start, - end=end) + nodes = list(iter_visible_in_frame_range(nodes, + start=start, + end=end)) with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index f582a106d9..7aa3aaee2a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - get_visible_in_frame_range + iter_visible_in_frame_range ) @@ -86,9 +86,9 @@ class ExtractAlembic(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = get_visible_in_frame_range(nodes, - start=start, - end=end) + nodes = list(iter_visible_in_frame_range(nodes, + start=start, + end=end)) with suspended_refresh(): with maintained_selection(): diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py index 9094a421c6..2a10171113 100644 --- a/openpype/hosts/maya/plugins/publish/validate_visible_only.py +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -1,7 +1,7 @@ import pyblish.api import openpype.api -from openpype.hosts.maya.api.lib import get_visible_in_frame_range +from openpype.hosts.maya.api.lib import iter_visible_in_frame_range import openpype.hosts.maya.api.action @@ -40,9 +40,7 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): nodes = instance[:] start, end = cls.get_frame_range(instance) - visible = get_visible_in_frame_range(nodes, start, end) - - if not visible: + if not any(iter_visible_in_frame_range(nodes, start, end)): # Return the nodes we have considered so the user can identify # them with the select invalid action return nodes From a99cc8f4971c52b55104eade2cac6c679db76d0f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 28 Jun 2022 21:30:22 +0200 Subject: [PATCH 10/11] Fix iterator --- openpype/hosts/maya/api/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 637f9e951b..43f738bfc7 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3272,11 +3272,12 @@ def iter_visible_in_frame_range(nodes, start, end): cmds.currentTime(start) visible = cmds.ls(nodes, long=True, visible=True) + for node in visible: + yield node if len(visible) == len(nodes) or start == end: # All are visible on frame one, so they are at least visible once # inside the frame range. - for node in visible: - yield node + return # For the invisible ones check whether its visibility and/or # any of its parents visibility attributes are animated. If so, it might From 19a5ed7be3967bd3ea25167c11b802275e20ac5b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 29 Jun 2022 17:22:21 +0200 Subject: [PATCH 11/11] Refactor `iter_visible_in_frame_range` -> `iter_visible_nodes_in_range` --- openpype/hosts/maya/api/lib.py | 2 +- openpype/hosts/maya/plugins/publish/extract_animation.py | 4 ++-- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 4 ++-- openpype/hosts/maya/plugins/publish/validate_visible_only.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 43f738bfc7..34340a13a5 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3224,7 +3224,7 @@ def maintained_time(): cmds.currentTime(ct, edit=True) -def iter_visible_in_frame_range(nodes, start, end): +def iter_visible_nodes_in_range(nodes, start, end): """Yield nodes that are visible in start-end frame range. - Ignores intermediateObjects completely. diff --git a/openpype/hosts/maya/plugins/publish/extract_animation.py b/openpype/hosts/maya/plugins/publish/extract_animation.py index b0beb5968e..8ed2d8d7a3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_animation.py +++ b/openpype/hosts/maya/plugins/publish/extract_animation.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - iter_visible_in_frame_range + iter_visible_nodes_in_range ) @@ -83,7 +83,7 @@ class ExtractAnimation(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_in_frame_range(nodes, + nodes = list(iter_visible_nodes_in_range(nodes, start=start, end=end)) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 7aa3aaee2a..775b5e9939 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -7,7 +7,7 @@ from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, maintained_selection, - iter_visible_in_frame_range + iter_visible_nodes_in_range ) @@ -86,7 +86,7 @@ class ExtractAlembic(openpype.api.Extractor): # flag does not filter out those that are only hidden on some # frames as it counts "animated" or "connected" visibilities as # if it's always visible. - nodes = list(iter_visible_in_frame_range(nodes, + nodes = list(iter_visible_nodes_in_range(nodes, start=start, end=end)) diff --git a/openpype/hosts/maya/plugins/publish/validate_visible_only.py b/openpype/hosts/maya/plugins/publish/validate_visible_only.py index 2a10171113..59a7f976ab 100644 --- a/openpype/hosts/maya/plugins/publish/validate_visible_only.py +++ b/openpype/hosts/maya/plugins/publish/validate_visible_only.py @@ -1,7 +1,7 @@ import pyblish.api import openpype.api -from openpype.hosts.maya.api.lib import iter_visible_in_frame_range +from openpype.hosts.maya.api.lib import iter_visible_nodes_in_range import openpype.hosts.maya.api.action @@ -40,7 +40,7 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin): nodes = instance[:] start, end = cls.get_frame_range(instance) - if not any(iter_visible_in_frame_range(nodes, start, end)): + if not any(iter_visible_nodes_in_range(nodes, start, end)): # Return the nodes we have considered so the user can identify # them with the select invalid action return nodes