From 3e05270d54f22f3c3c94dafa0d1906aa77d95bb6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 2 Jul 2024 20:20:13 +0200 Subject: [PATCH] Fix removal of files that should've occurred via https://github.com/ynput/ayon-core/pull/295 --- .../publish/collect_instances_usd_layered.py | 152 --------- .../plugins/publish/collect_usd_bootstrap.py | 122 ------- .../plugins/publish/extract_usd_layered.py | 322 ------------------ .../validate_usd_layer_path_backslashes.py | 54 --- .../publish/validate_usd_model_and_shade.py | 79 ----- .../plugins/publish/validate_usd_setdress.py | 58 ---- .../validate_usd_shade_model_exists.py | 49 --- .../publish/validate_usd_shade_workspace.py | 67 ---- 8 files changed, 903 deletions(-) delete mode 100644 client/ayon_houdini/plugins/publish/collect_instances_usd_layered.py delete mode 100644 client/ayon_houdini/plugins/publish/collect_usd_bootstrap.py delete mode 100644 client/ayon_houdini/plugins/publish/extract_usd_layered.py delete mode 100644 client/ayon_houdini/plugins/publish/validate_usd_layer_path_backslashes.py delete mode 100644 client/ayon_houdini/plugins/publish/validate_usd_model_and_shade.py delete mode 100644 client/ayon_houdini/plugins/publish/validate_usd_setdress.py delete mode 100644 client/ayon_houdini/plugins/publish/validate_usd_shade_model_exists.py delete mode 100644 client/ayon_houdini/plugins/publish/validate_usd_shade_workspace.py diff --git a/client/ayon_houdini/plugins/publish/collect_instances_usd_layered.py b/client/ayon_houdini/plugins/publish/collect_instances_usd_layered.py deleted file mode 100644 index 4f85a629fb..0000000000 --- a/client/ayon_houdini/plugins/publish/collect_instances_usd_layered.py +++ /dev/null @@ -1,152 +0,0 @@ -import hou -import pyblish.api -from ayon_core.pipeline import usdlib -from ayon_houdini.api import lib, plugin -import ayon_houdini.api.usd as hou_usdlib - - -class CollectInstancesUsdLayered(plugin.HoudiniContextPlugin): - """Collect Instances from a ROP Network and its configured layer paths. - - The output nodes of the ROP node will only be published when *any* of the - layers remain set to 'publish' by the user. - - This works differently from most of our Avalon instances in the pipeline. - As opposed to storing `ayon.create.instance` as id on the node we store - `pyblish.avalon.usdlayered`. - - Additionally this instance has no need for storing folder, product type, - product name or name on the nodes. Instead all information is retrieved - solely from the output filepath, which is an Avalon URI: - avalon://{folder}/{product}.{representation} - - Each final ROP node is considered a dependency for any of the Configured - Save Path layers it sets along the way. As such, the instances shown in - the Pyblish UI are solely the configured layers. The encapsulating usd - files are generated whenever *any* of the dependencies is published. - - These dependency instances are stored in: - instance.data["publishDependencies"] - - """ - - order = pyblish.api.CollectorOrder - 0.01 - label = "Collect Instances (USD Configured Layers)" - - def process(self, context): - - stage = hou.node("/stage") - if not stage: - # Likely Houdini version <18 - return - - nodes = stage.recursiveGlob("*", filter=hou.nodeTypeFilter.Rop) - for node in nodes: - - if not node.parm("id"): - continue - - if node.evalParm("id") != "pyblish.avalon.usdlayered": - continue - - has_product_type = node.evalParm("productType") - assert has_product_type, ( - "'%s' is missing 'productType'" % node.name() - ) - - self.process_node(node, context) - - def sort_by_family(instance): - """Sort by family""" - return instance.data.get( - "families", - instance.data.get("productType") - ) - - # Sort/grouped by family (preserving local index) - context[:] = sorted(context, key=sort_by_family) - - return context - - def process_node(self, node, context): - - # Allow a single ROP node or a full ROP network of USD ROP nodes - # to be processed as a single entry that should "live together" on - # a publish. - if node.type().name() == "ropnet": - # All rop nodes inside ROP Network - ropnodes = node.recursiveGlob("*", filter=hou.nodeTypeFilter.Rop) - else: - # A single node - ropnodes = [node] - - data = lib.read(node) - - # Don't use the explicit "colorbleed.usd.layered" family for publishing - # instead use the "colorbleed.usd" family to integrate. - data["publishFamilies"] = ["colorbleed.usd"] - - # For now group ALL of them into USD Layer product group - # Allow this product to be grouped into a USD Layer on creation - data["productGroup"] = "USD Layer" - - instances = list() - dependencies = [] - for ropnode in ropnodes: - - # Create a dependency instance per ROP Node. - lopoutput = ropnode.evalParm("lopoutput") - dependency_save_data = self.get_save_data(lopoutput) - dependency = context.create_instance(dependency_save_data["name"]) - dependency.append(ropnode) - dependency.data.update(data) - dependency.data.update(dependency_save_data) - dependency.data["productType"] = "colorbleed.usd.dependency" - dependency.data["optional"] = False - dependencies.append(dependency) - - # Hide the dependency instance from the context - context.pop() - - # Get all configured layers for this USD ROP node - # and create a Pyblish instance for each one - layers = hou_usdlib.get_configured_save_layers(ropnode) - for layer in layers: - save_path = hou_usdlib.get_layer_save_path(layer) - save_data = self.get_save_data(save_path) - if not save_data: - continue - self.log.info(save_path) - - instance = context.create_instance(save_data["name"]) - instance[:] = [node] - - # Set the instance data - instance.data.update(data) - instance.data.update(save_data) - instance.data["usdLayer"] = layer - - instances.append(instance) - - # Store the collected ROP node dependencies - self.log.debug("Collected dependencies: %s" % (dependencies,)) - for instance in instances: - instance.data["publishDependencies"] = dependencies - - def get_save_data(self, save_path): - - # Resolve Avalon URI - uri_data = usdlib.parse_avalon_uri(save_path) - if not uri_data: - self.log.warning("Non Avalon URI Layer Path: %s" % save_path) - return {} - - # Collect folder + product from URI - name = "{product[name]} ({folder[path]})".format(**uri_data) - fname = "{folder[path]}_{product[name]}.{ext}".format(**uri_data) - - data = dict(uri_data) - data["usdSavePath"] = save_path - data["usdFilename"] = fname - data["name"] = name - return data diff --git a/client/ayon_houdini/plugins/publish/collect_usd_bootstrap.py b/client/ayon_houdini/plugins/publish/collect_usd_bootstrap.py deleted file mode 100644 index 5067b9aab2..0000000000 --- a/client/ayon_houdini/plugins/publish/collect_usd_bootstrap.py +++ /dev/null @@ -1,122 +0,0 @@ -import pyblish.api -import ayon_api -from ayon_core.pipeline import usdlib, KnownPublishError - -from ayon_houdini.api import plugin - -class CollectUsdBootstrap(plugin.HoudiniInstancePlugin): - """Collect special Asset/Shot bootstrap instances if those are needed. - - Some specific products are intended to be part of the default structure - of an "Asset" or "Shot" in our USD pipeline. For example, for an Asset - we layer a Model and Shade USD file over each other and expose that in - a Asset USD file, ready to use. - - On the first publish of any of the components of a Asset or Shot the - missing pieces are bootstrapped and generated in the pipeline too. This - means that on the very first publish of your model the Asset USD file - will exist too. - - """ - - order = pyblish.api.CollectorOrder + 0.35 - label = "Collect USD Bootstrap" - families = ["usd", "usd.layered"] - - def process(self, instance): - - # Detect whether the current product is a product in a pipeline - def get_bootstrap(instance): - instance_product_name = instance.data["productName"] - for name, layers in usdlib.PIPELINE.items(): - if instance_product_name in set(layers): - return name # e.g. "asset" - else: - return - - bootstrap = get_bootstrap(instance) - if bootstrap: - self.add_bootstrap(instance, bootstrap) - - # Check if any of the dependencies requires a bootstrap - for dependency in instance.data.get("publishDependencies", list()): - bootstrap = get_bootstrap(dependency) - if bootstrap: - self.add_bootstrap(dependency, bootstrap) - - def add_bootstrap(self, instance, bootstrap): - - self.log.debug("Add bootstrap for: %s" % bootstrap) - - project_name = instance.context.data["projectName"] - folder_path = instance.data["folderPath"] - folder_name = folder_path.rsplit("/", 1)[-1] - folder_entity = ayon_api.get_folder_by_path(project_name, folder_path) - if not folder_entity: - raise KnownPublishError( - "Folder '{}' does not exist".format(folder_path) - ) - - # Check which are not about to be created and don't exist yet - required = {"shot": ["usdShot"], "asset": ["usdAsset"]}.get(bootstrap) - - require_all_layers = instance.data.get("requireAllLayers", False) - if require_all_layers: - # USD files load fine in usdview and Houdini even when layered or - # referenced files do not exist. So by default we don't require - # the layers to exist. - layers = usdlib.PIPELINE.get(bootstrap) - if layers: - required += list(layers) - - self.log.debug("Checking required bootstrap: %s" % required) - for product_name in required: - if self._product_exists( - project_name, instance, product_name, folder_entity - ): - continue - - self.log.debug( - "Creating {0} USD bootstrap: {1} {2}".format( - bootstrap, folder_path, product_name - ) - ) - - product_type = "usd.bootstrap" - new = instance.context.create_instance(product_name) - new.data["productName"] = product_name - new.data["label"] = "{0} ({1})".format(product_name, folder_name) - new.data["productType"] = product_type - new.data["family"] = product_type - new.data["comment"] = "Automated bootstrap USD file." - new.data["publishFamilies"] = ["usd"] - - # Do not allow the user to toggle this instance - new.data["optional"] = False - - # Copy some data from the instance for which we bootstrap - for key in ["folderPath"]: - new.data[key] = instance.data[key] - - def _product_exists( - self, project_name, instance, product_name, folder_entity - ): - """Return whether product exists in current context or in database.""" - # Allow it to be created during this publish session - context = instance.context - - folder_path = folder_entity["path"] - for inst in context: - if ( - inst.data["productName"] == product_name - and inst.data["folderPath"] == folder_path - ): - return True - - # Or, if they already exist in the database we can - # skip them too. - if ayon_api.get_product_by_name( - project_name, product_name, folder_entity["id"], fields={"id"} - ): - return True - return False diff --git a/client/ayon_houdini/plugins/publish/extract_usd_layered.py b/client/ayon_houdini/plugins/publish/extract_usd_layered.py deleted file mode 100644 index 6a377c57cf..0000000000 --- a/client/ayon_houdini/plugins/publish/extract_usd_layered.py +++ /dev/null @@ -1,322 +0,0 @@ -import os -import contextlib -import sys -from collections import deque -import hou - -import ayon_api -import pyblish.api - -from ayon_core.pipeline import get_representation_path -from ayon_houdini.api import plugin -import ayon_houdini.api.usd as hou_usdlib -from ayon_houdini.api.lib import render_rop - - -class ExitStack(object): - """Context manager for dynamic management of a stack of exit callbacks. - - For example: - - with ExitStack() as stack: - files = [stack.enter_context(open(fname)) for fname in filenames] - # All opened files will automatically be closed at the end of - # the with statement, even if attempts to open files later - # in the list raise an exception - - """ - - def __init__(self): - self._exit_callbacks = deque() - - def pop_all(self): - """Preserve the context stack by transferring it to a new instance""" - new_stack = type(self)() - new_stack._exit_callbacks = self._exit_callbacks - self._exit_callbacks = deque() - return new_stack - - def _push_cm_exit(self, cm, cm_exit): - """Helper to correctly register callbacks to __exit__ methods""" - - def _exit_wrapper(*exc_details): - return cm_exit(cm, *exc_details) - - _exit_wrapper.__self__ = cm - self.push(_exit_wrapper) - - def push(self, exit): - """Registers a callback with the standard __exit__ method signature. - - Can suppress exceptions the same way __exit__ methods can. - - Also accepts any object with an __exit__ method (registering a call - to the method instead of the object itself) - - """ - # We use an unbound method rather than a bound method to follow - # the standard lookup behaviour for special methods - _cb_type = type(exit) - try: - exit_method = _cb_type.__exit__ - except AttributeError: - # Not a context manager, so assume its a callable - self._exit_callbacks.append(exit) - else: - self._push_cm_exit(exit, exit_method) - return exit # Allow use as a decorator - - def callback(self, callback, *args, **kwds): - """Registers an arbitrary callback and arguments. - - Cannot suppress exceptions. - """ - - def _exit_wrapper(exc_type, exc, tb): - callback(*args, **kwds) - - # We changed the signature, so using @wraps is not appropriate, but - # setting __wrapped__ may still help with introspection - _exit_wrapper.__wrapped__ = callback - self.push(_exit_wrapper) - return callback # Allow use as a decorator - - def enter_context(self, cm): - """Enters the supplied context manager - - If successful, also pushes its __exit__ method as a callback and - returns the result of the __enter__ method. - """ - # We look up the special methods on the type to match the with - # statement - _cm_type = type(cm) - _exit = _cm_type.__exit__ - result = _cm_type.__enter__(cm) - self._push_cm_exit(cm, _exit) - return result - - def close(self): - """Immediately unwind the context stack""" - self.__exit__(None, None, None) - - def __enter__(self): - return self - - def __exit__(self, *exc_details): - # We manipulate the exception state so it behaves as though - # we were actually nesting multiple with statements - frame_exc = sys.exc_info()[1] - - def _fix_exception_context(new_exc, old_exc): - while 1: - exc_context = new_exc.__context__ - if exc_context in (None, frame_exc): - break - new_exc = exc_context - new_exc.__context__ = old_exc - - # Callbacks are invoked in LIFO order to match the behaviour of - # nested context managers - suppressed_exc = False - while self._exit_callbacks: - cb = self._exit_callbacks.pop() - try: - if cb(*exc_details): - suppressed_exc = True - exc_details = (None, None, None) - except Exception: - new_exc_details = sys.exc_info() - # simulate the stack of exceptions by setting the context - _fix_exception_context(new_exc_details[1], exc_details[1]) - if not self._exit_callbacks: - raise - exc_details = new_exc_details - return suppressed_exc - - -@contextlib.contextmanager -def parm_values(overrides): - """Override Parameter values during the context.""" - - originals = [] - try: - for parm, value in overrides: - originals.append((parm, parm.eval())) - parm.set(value) - yield - finally: - for parm, value in originals: - # Parameter might not exist anymore so first - # check whether it's still valid - if hou.parm(parm.path()): - parm.set(value) - - -class ExtractUSDLayered(plugin.HoudiniExtractorPlugin): - - order = pyblish.api.ExtractorOrder - label = "Extract Layered USD" - families = ["usdLayered", "usdShade"] - - # Force Output Processors so it will always save any file - # into our unique staging directory with processed Avalon paths - output_processors = ["avalon_uri_processor", "stagingdir_processor"] - - def process(self, instance): - - self.log.info("Extracting: %s" % instance) - - staging_dir = self.staging_dir(instance) - fname = instance.data.get("usdFilename") - - # The individual rop nodes are collected as "publishDependencies" - dependencies = instance.data["publishDependencies"] - ropnodes = [dependency[0] for dependency in dependencies] - assert all( - node.type().name() in {"usd", "usd_rop"} for node in ropnodes - ) - - # Main ROP node, either a USD Rop or ROP network with - # multiple USD ROPs - node = hou.node(instance.data["instance_node"]) - - # Collect any output dependencies that have not been processed yet - # during extraction of other instances - outputs = [fname] - active_dependencies = [ - dep - for dep in dependencies - if dep.data.get("publish", True) - and not dep.data.get("_isExtracted", False) - ] - for dependency in active_dependencies: - outputs.append(dependency.data["usdFilename"]) - - pattern = r"*[/\]{0} {0}" - save_pattern = " ".join(pattern.format(fname) for fname in outputs) - - # Run a stack of context managers before we start the render to - # temporarily adjust USD ROP settings for our publish output. - rop_overrides = { - # This sets staging directory on the processor to force our - # output files to end up in the Staging Directory. - "stagingdiroutputprocessor_stagingDir": staging_dir, - # Force the Avalon URI Output Processor to refactor paths for - # references, payloads and layers to published paths. - "avalonurioutputprocessor_use_publish_paths": True, - # Only write out specific USD files based on our outputs - "savepattern": save_pattern, - } - overrides = list() - with ExitStack() as stack: - - for ropnode in ropnodes: - manager = hou_usdlib.outputprocessors( - ropnode, - processors=self.output_processors, - disable_all_others=True, - ) - stack.enter_context(manager) - - # Some of these must be added after we enter the output - # processor context manager because those parameters only - # exist when the Output Processor is added to the ROP node. - for name, value in rop_overrides.items(): - parm = ropnode.parm(name) - assert parm, "Parm not found: %s.%s" % ( - ropnode.path(), - name, - ) - overrides.append((parm, value)) - - stack.enter_context(parm_values(overrides)) - - # Render the single ROP node or the full ROP network - render_rop(node) - - # Assert all output files in the Staging Directory - for output_fname in outputs: - path = os.path.join(staging_dir, output_fname) - assert os.path.exists(path), "Output file must exist: %s" % path - - # Set up the dependency for publish if they have new content - # compared to previous publishes - project_name = instance.context.data["projectName"] - for dependency in active_dependencies: - dependency_fname = dependency.data["usdFilename"] - - filepath = os.path.join(staging_dir, dependency_fname) - similar = self._compare_with_latest_publish( - project_name, dependency, filepath - ) - if similar: - # Deactivate this dependency - self.log.debug( - "Dependency matches previous publish version," - " deactivating %s for publish" % dependency - ) - dependency.data["publish"] = False - else: - self.log.debug("Extracted dependency: %s" % dependency) - # This dependency should be published - dependency.data["files"] = [dependency_fname] - dependency.data["stagingDir"] = staging_dir - dependency.data["_isExtracted"] = True - - # Store the created files on the instance - if "files" not in instance.data: - instance.data["files"] = [] - instance.data["files"].append(fname) - - def _compare_with_latest_publish(self, project_name, dependency, new_file): - import filecmp - - _, ext = os.path.splitext(new_file) - - # Compare this dependency with the latest published version - # to detect whether we should make this into a new publish - # version. If not, skip it. - folder_entity = ayon_api.get_folder_by_path( - project_name, dependency.data["folderPath"], fields={"id"} - ) - product_entity = ayon_api.get_product_by_name( - project_name, - dependency.data["productName"], - folder_entity["id"], - fields={"id"} - ) - if not product_entity: - # Subset doesn't exist yet. Definitely new file - self.log.debug("No existing product..") - return False - - version_entity = ayon_api.get_last_version_by_product_id( - project_name, product_entity["id"], fields={"id"} - ) - if not version_entity: - self.log.debug("No existing version..") - return False - - representation = ayon_api.get_representation_by_name( - project_name, ext.lstrip("."), version_entity["id"] - ) - if not representation: - self.log.debug("No existing representation..") - return False - - old_file = get_representation_path(representation) - if not os.path.exists(old_file): - return False - - return filecmp.cmp(old_file, new_file) - - def staging_dir(self, instance): - """Provide a temporary directory in which to store extracted files - - Upon calling this method the staging directory is stored inside - the instance.data['stagingDir'] - """ - - from ayon_core.pipeline.publish import get_instance_staging_dir - - return get_instance_staging_dir(instance) diff --git a/client/ayon_houdini/plugins/publish/validate_usd_layer_path_backslashes.py b/client/ayon_houdini/plugins/publish/validate_usd_layer_path_backslashes.py deleted file mode 100644 index 4da67ff199..0000000000 --- a/client/ayon_houdini/plugins/publish/validate_usd_layer_path_backslashes.py +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 -*- -import hou - -import pyblish.api -from ayon_core.pipeline import PublishValidationError - -from ayon_houdini.api import plugin -import ayon_houdini.api.usd as hou_usdlib - - -class ValidateUSDLayerPathBackslashes(plugin.HoudiniInstancePlugin): - """Validate USD loaded paths have no backslashes. - - This is a crucial validation for HUSK USD rendering as Houdini's - USD Render ROP will fail to write out a .usd file for rendering that - correctly preserves the backslashes, e.g. it will incorrectly convert a - '\t' to a TAB character disallowing HUSK to find those specific files. - - This validation is redundant for usdModel since that flattens the model - before write. As such it will never have any used layers with a path. - - """ - - order = pyblish.api.ValidatorOrder - families = ["usdSetDress", "usdShade", "usd", "usdrender"] - label = "USD Layer path backslashes" - optional = True - - def process(self, instance): - - rop = hou.node(instance.data.get("instance_node")) - lop_path = hou_usdlib.get_usd_rop_loppath(rop) - stage = lop_path.stage(apply_viewport_overrides=False) - - invalid = [] - for layer in stage.GetUsedLayers(): - references = layer.externalReferences - - for ref in references: - - # Ignore anonymous layers - if ref.startswith("anon:"): - continue - - # If any backslashes in the path consider it invalid - if "\\" in ref: - self.log.error("Found invalid path: %s" % ref) - invalid.append(layer) - - if invalid: - raise PublishValidationError(( - "Loaded layers have backslashes. " - "This is invalid for HUSK USD rendering."), - title=self.label) diff --git a/client/ayon_houdini/plugins/publish/validate_usd_model_and_shade.py b/client/ayon_houdini/plugins/publish/validate_usd_model_and_shade.py deleted file mode 100644 index 935bd39e23..0000000000 --- a/client/ayon_houdini/plugins/publish/validate_usd_model_and_shade.py +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 -*- -import hou -from pxr import UsdShade, UsdRender, UsdLux - -import pyblish.api -from ayon_core.pipeline import PublishValidationError - -from ayon_houdini.api import plugin -import ayon_houdini.api.usd as hou_usdlib - - -def fullname(o): - """Get fully qualified class name""" - module = o.__module__ - if module is None or module == str.__module__: - return o.__name__ - return module + "." + o.__name__ - - -class ValidateUsdModel(plugin.HoudiniInstancePlugin): - """Validate USD Model. - - Disallow Shaders, Render settings, products and vars and Lux lights. - - """ - - order = pyblish.api.ValidatorOrder - families = ["usdModel"] - label = "Validate USD Model" - optional = True - - disallowed = [ - UsdShade.Shader, - UsdRender.Settings, - UsdRender.Product, - UsdRender.Var, - UsdLux.Light, - ] - - def process(self, instance): - - rop = hou.node(instance.data.get("instance_node")) - lop_path = hou_usdlib.get_usd_rop_loppath(rop) - stage = lop_path.stage(apply_viewport_overrides=False) - - invalid = [] - for prim in stage.Traverse(): - - for klass in self.disallowed: - if klass(prim): - # Get full class name without pxr. prefix - name = fullname(klass).split("pxr.", 1)[-1] - path = str(prim.GetPath()) - self.log.warning("Disallowed %s: %s" % (name, path)) - - invalid.append(prim) - - if invalid: - prim_paths = sorted([str(prim.GetPath()) for prim in invalid]) - raise PublishValidationError( - "Found invalid primitives: {}".format(prim_paths)) - - -class ValidateUsdShade(ValidateUsdModel): - """Validate usdShade. - - Disallow Render settings, products, vars and Lux lights. - - """ - - families = ["usdShade"] - label = "Validate USD Shade" - - disallowed = [ - UsdRender.Settings, - UsdRender.Product, - UsdRender.Var, - UsdLux.Light, - ] diff --git a/client/ayon_houdini/plugins/publish/validate_usd_setdress.py b/client/ayon_houdini/plugins/publish/validate_usd_setdress.py deleted file mode 100644 index 3e91f0418f..0000000000 --- a/client/ayon_houdini/plugins/publish/validate_usd_setdress.py +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 -*- -import pyblish.api -from ayon_core.pipeline import PublishValidationError - -from ayon_houdini.api import plugin -import ayon_houdini.api.usd as hou_usdlib - - -class ValidateUsdSetDress(plugin.HoudiniInstancePlugin): - """Validate USD Set Dress. - - Must only have references or payloads. May not generate new mesh or - flattened meshes. - - """ - - order = pyblish.api.ValidatorOrder - families = ["usdSetDress"] - label = "Validate USD Set Dress" - optional = True - - def process(self, instance): - - import hou - from pxr import UsdGeom - - rop = hou.node(instance.data.get("instance_node")) - lop_path = hou_usdlib.get_usd_rop_loppath(rop) - stage = lop_path.stage(apply_viewport_overrides=False) - - invalid = [] - for node in stage.Traverse(): - - if UsdGeom.Mesh(node): - # This solely checks whether there is any USD involved - # in this Prim's Stack and doesn't accurately tell us - # whether it was generated locally or not. - # TODO: More accurately track whether the Prim was created - # in the local scene - stack = node.GetPrimStack() - for sdf in stack: - path = sdf.layer.realPath - if path: - break - else: - prim_path = node.GetPath() - self.log.error( - "%s is not referenced geometry." % prim_path - ) - invalid.append(node) - - if invalid: - raise PublishValidationError(( - "SetDress contains local geometry. " - "This is not allowed, it must be an assembly " - "of referenced assets."), - title=self.label - ) diff --git a/client/ayon_houdini/plugins/publish/validate_usd_shade_model_exists.py b/client/ayon_houdini/plugins/publish/validate_usd_shade_model_exists.py deleted file mode 100644 index 8a93d3b4a1..0000000000 --- a/client/ayon_houdini/plugins/publish/validate_usd_shade_model_exists.py +++ /dev/null @@ -1,49 +0,0 @@ -# -*- coding: utf-8 -*- -import re - -import ayon_api -from ayon_core.pipeline.publish import ( - ValidateContentsOrder, - KnownPublishError, - PublishValidationError, -) - -from ayon_houdini.api import plugin - - -class ValidateUSDShadeModelExists(plugin.HoudiniInstancePlugin): - """Validate the Instance has no current cooking errors.""" - - order = ValidateContentsOrder - families = ["usdShade"] - label = "USD Shade model exists" - - def process(self, instance): - project_name = instance.context.data["projectName"] - folder_path = instance.data["folderPath"] - product_name = instance.data["productName"] - - # Assume shading variation starts after a dot separator - shade_product_name = product_name.split(".", 1)[0] - model_product_name = re.sub( - "^usdShade", "usdModel", shade_product_name - ) - - folder_entity = instance.data.get("folderEntity") - if not folder_entity: - raise KnownPublishError( - "Folder entity is not filled on instance." - ) - - product_entity = ayon_api.get_product_by_name( - project_name, - model_product_name, - folder_entity["id"], - fields={"id"} - ) - if not product_entity: - raise PublishValidationError( - ("USD Model product not found: " - "{} ({})").format(model_product_name, folder_path), - title=self.label - ) diff --git a/client/ayon_houdini/plugins/publish/validate_usd_shade_workspace.py b/client/ayon_houdini/plugins/publish/validate_usd_shade_workspace.py deleted file mode 100644 index 8972941253..0000000000 --- a/client/ayon_houdini/plugins/publish/validate_usd_shade_workspace.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -import hou - -import pyblish.api -from ayon_core.pipeline import PublishValidationError - -from ayon_houdini.api import plugin - - -class ValidateUsdShadeWorkspace(plugin.HoudiniInstancePlugin): - """Validate USD Shading Workspace is correct version. - - There have been some issues with outdated/erroneous Shading Workspaces - so this is to confirm everything is set as it should. - - """ - - order = pyblish.api.ValidatorOrder - families = ["usdShade"] - label = "USD Shade Workspace" - - def process(self, instance): - - rop = hou.node(instance.data.get("instance_node")) - workspace = rop.parent() - - definition = workspace.type().definition() - name = definition.nodeType().name() - library = definition.libraryFilePath() - - all_definitions = hou.hda.definitionsInFile(library) - node_type, version = name.rsplit(":", 1) - version = float(version) - - highest = version - for other_definition in all_definitions: - other_name = other_definition.nodeType().name() - other_node_type, other_version = other_name.rsplit(":", 1) - other_version = float(other_version) - - if node_type != other_node_type: - continue - - # Get the highest version - highest = max(highest, other_version) - - if version != highest: - raise PublishValidationError( - ("Shading Workspace is not the latest version." - " Found {}. Latest is {}.").format(version, highest), - title=self.label - ) - - # There were some issues with the editable node not having the right - # configured path. So for now let's assure that is correct to.from - value = ( - 'avalon://`chs("../folder_path")`/' - 'usdShade`chs("../model_variantname1")`.usd' - ) - rop_value = rop.parm("lopoutput").rawValue() - if rop_value != value: - raise PublishValidationError( - ("Shading Workspace has invalid 'lopoutput'" - " parameter value. The Shading Workspace" - " needs to be reset to its default values."), - title=self.label - )