diff --git a/server_addon/houdini/client/ayon_houdini/__init__.py b/server_addon/houdini/client/ayon_houdini/__init__.py
deleted file mode 100644
index afb51f7315..0000000000
--- a/server_addon/houdini/client/ayon_houdini/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from .version import __version__
-from .addon import (
- HoudiniAddon,
- HOUDINI_HOST_DIR,
-)
-
-
-__all__ = (
- "__version__",
-
- "HoudiniAddon",
- "HOUDINI_HOST_DIR",
-)
diff --git a/server_addon/houdini/client/ayon_houdini/addon.py b/server_addon/houdini/client/ayon_houdini/addon.py
deleted file mode 100644
index 4c23553008..0000000000
--- a/server_addon/houdini/client/ayon_houdini/addon.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import os
-from ayon_core.addon import AYONAddon, IHostAddon
-
-from .version import __version__
-
-HOUDINI_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
-
-
-class HoudiniAddon(AYONAddon, IHostAddon):
- name = "houdini"
- version = __version__
- host_name = "houdini"
-
- def add_implementation_envs(self, env, _app):
- # Add requirements to HOUDINI_PATH and HOUDINI_MENU_PATH
- startup_path = os.path.join(HOUDINI_HOST_DIR, "startup")
- new_houdini_path = [startup_path]
- new_houdini_menu_path = [startup_path]
-
- old_houdini_path = env.get("HOUDINI_PATH") or ""
- old_houdini_menu_path = env.get("HOUDINI_MENU_PATH") or ""
-
- for path in old_houdini_path.split(os.pathsep):
- if not path:
- continue
-
- norm_path = os.path.normpath(path)
- if norm_path not in new_houdini_path:
- new_houdini_path.append(norm_path)
-
- for path in old_houdini_menu_path.split(os.pathsep):
- if not path:
- continue
-
- norm_path = os.path.normpath(path)
- if norm_path not in new_houdini_menu_path:
- new_houdini_menu_path.append(norm_path)
-
- # Add ampersand for unknown reason (Maybe is needed in Houdini?)
- new_houdini_path.append("&")
- new_houdini_menu_path.append("&")
-
- env["HOUDINI_PATH"] = os.pathsep.join(new_houdini_path)
- env["HOUDINI_MENU_PATH"] = os.pathsep.join(new_houdini_menu_path)
-
- def get_launch_hook_paths(self, app):
- if app.host_name != self.host_name:
- return []
- return [
- os.path.join(HOUDINI_HOST_DIR, "hooks")
- ]
-
- def get_workfile_extensions(self):
- return [".hip", ".hiplc", ".hipnc"]
diff --git a/server_addon/houdini/client/ayon_houdini/api/__init__.py b/server_addon/houdini/client/ayon_houdini/api/__init__.py
deleted file mode 100644
index 358113a555..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/__init__.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from .pipeline import (
- HoudiniHost,
- ls,
- containerise
-)
-
-from .lib import (
- lsattr,
- lsattrs,
- read,
-
- maintained_selection
-)
-
-
-__all__ = [
- "HoudiniHost",
-
- "ls",
- "containerise",
-
- # Utility functions
- "lsattr",
- "lsattrs",
- "read",
-
- "maintained_selection"
-]
diff --git a/server_addon/houdini/client/ayon_houdini/api/action.py b/server_addon/houdini/client/ayon_houdini/api/action.py
deleted file mode 100644
index a14296950b..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/action.py
+++ /dev/null
@@ -1,83 +0,0 @@
-import pyblish.api
-import hou
-
-from ayon_core.pipeline.publish import get_errored_instances_from_context
-
-
-class SelectInvalidAction(pyblish.api.Action):
- """Select invalid nodes in Maya when plug-in failed.
-
- To retrieve the invalid nodes this assumes a static `get_invalid()`
- method is available on the plugin.
-
- """
- label = "Select invalid"
- on = "failed" # This action is only available on a failed plug-in
- icon = "search" # Icon from Awesome Icon
-
- def process(self, context, plugin):
-
- errored_instances = get_errored_instances_from_context(context,
- plugin=plugin)
-
- # Get the invalid nodes for the plug-ins
- self.log.info("Finding invalid nodes..")
- invalid = list()
- for instance in errored_instances:
- invalid_nodes = plugin.get_invalid(instance)
- if invalid_nodes:
- if isinstance(invalid_nodes, (list, tuple)):
- invalid.extend(invalid_nodes)
- else:
- self.log.warning("Plug-in returned to be invalid, "
- "but has no selectable nodes.")
-
- hou.clearAllSelected()
- if invalid:
- self.log.info("Selecting invalid nodes: {}".format(
- ", ".join(node.path() for node in invalid)
- ))
- for node in invalid:
- node.setSelected(True)
- node.setCurrent(True)
- else:
- self.log.info("No invalid nodes found.")
-
-
-class SelectROPAction(pyblish.api.Action):
- """Select ROP.
-
- It's used to select the associated ROPs with the errored instances.
- """
-
- label = "Select ROP"
- on = "failed" # This action is only available on a failed plug-in
- icon = "mdi.cursor-default-click"
-
- def process(self, context, plugin):
- errored_instances = get_errored_instances_from_context(context, plugin)
-
- # Get the invalid nodes for the plug-ins
- self.log.info("Finding ROP nodes..")
- rop_nodes = list()
- for instance in errored_instances:
- node_path = instance.data.get("instance_node")
- if not node_path:
- continue
-
- node = hou.node(node_path)
- if not node:
- continue
-
- rop_nodes.append(node)
-
- hou.clearAllSelected()
- if rop_nodes:
- self.log.info("Selecting ROP nodes: {}".format(
- ", ".join(node.path() for node in rop_nodes)
- ))
- for node in rop_nodes:
- node.setSelected(True)
- node.setCurrent(True)
- else:
- self.log.info("No ROP nodes found.")
diff --git a/server_addon/houdini/client/ayon_houdini/api/colorspace.py b/server_addon/houdini/client/ayon_houdini/api/colorspace.py
deleted file mode 100644
index ec6e4c2091..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/colorspace.py
+++ /dev/null
@@ -1,69 +0,0 @@
-import attr
-import hou
-from ayon_houdini.api.lib import get_color_management_preferences
-from ayon_core.pipeline.colorspace import get_display_view_colorspace_name
-
-@attr.s
-class LayerMetadata(object):
- """Data class for Render Layer metadata."""
- frameStart = attr.ib()
- frameEnd = attr.ib()
-
-
-@attr.s
-class RenderProduct(object):
- """Getting Colorspace as
- Specific Render Product Parameter for submitting
- publish job.
-
- """
- colorspace = attr.ib() # colorspace
- view = attr.ib()
- productName = attr.ib(default=None)
-
-
-class ARenderProduct(object):
-
- def __init__(self):
- """Constructor."""
- # Initialize
- self.layer_data = self._get_layer_data()
- self.layer_data.products = self.get_colorspace_data()
-
- def _get_layer_data(self):
- return LayerMetadata(
- frameStart=int(hou.playbar.frameRange()[0]),
- frameEnd=int(hou.playbar.frameRange()[1]),
- )
-
- def get_colorspace_data(self):
- """To be implemented by renderer class.
-
- This should return a list of RenderProducts.
-
- Returns:
- list: List of RenderProduct
-
- """
- data = get_color_management_preferences()
- colorspace_data = [
- RenderProduct(
- colorspace=data["display"],
- view=data["view"],
- productName=""
- )
- ]
- return colorspace_data
-
-
-def get_default_display_view_colorspace():
- """Returns the colorspace attribute of the default (display, view) pair.
-
- It's used for 'ociocolorspace' parm in OpenGL Node."""
-
- prefs = get_color_management_preferences()
- return get_display_view_colorspace_name(
- config_path=prefs["config"],
- display=prefs["display"],
- view=prefs["view"]
- )
diff --git a/server_addon/houdini/client/ayon_houdini/api/creator_node_shelves.py b/server_addon/houdini/client/ayon_houdini/api/creator_node_shelves.py
deleted file mode 100644
index 4d5a706749..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/creator_node_shelves.py
+++ /dev/null
@@ -1,244 +0,0 @@
-"""Library to register OpenPype Creators for Houdini TAB node search menu.
-
-This can be used to install custom houdini tools for the TAB search
-menu which will trigger a publish instance to be created interactively.
-
-The Creators are automatically registered on launch of Houdini through the
-Houdini integration's `host.install()` method.
-
-"""
-import contextlib
-import tempfile
-import logging
-import os
-
-import ayon_api
-
-from ayon_core.pipeline import registered_host
-from ayon_core.pipeline.create import CreateContext
-from ayon_core.resources import get_ayon_icon_filepath
-
-import hou
-import stateutils
-import soptoolutils
-import loptoolutils
-import cop2toolutils
-
-
-log = logging.getLogger(__name__)
-
-CATEGORY_GENERIC_TOOL = {
- hou.sopNodeTypeCategory(): soptoolutils.genericTool,
- hou.cop2NodeTypeCategory(): cop2toolutils.genericTool,
- hou.lopNodeTypeCategory(): loptoolutils.genericTool
-}
-
-
-CREATE_SCRIPT = """
-from ayon_houdini.api.creator_node_shelves import create_interactive
-create_interactive("{identifier}", **kwargs)
-"""
-
-
-def create_interactive(creator_identifier, **kwargs):
- """Create a Creator using its identifier interactively.
-
- This is used by the generated shelf tools as callback when a user selects
- the creator from the node tab search menu.
-
- The `kwargs` should be what Houdini passes to the tool create scripts
- context. For more information see:
- https://www.sidefx.com/docs/houdini/hom/tool_script.html#arguments
-
- Args:
- creator_identifier (str): The creator identifier of the Creator plugin
- to create.
-
- Return:
- list: The created instances.
-
- """
- host = registered_host()
- context = CreateContext(host)
- creator = context.manual_creators.get(creator_identifier)
- if not creator:
- raise RuntimeError("Invalid creator identifier: {}".format(
- creator_identifier)
- )
-
- # TODO Use Qt instead
- result, variant = hou.ui.readInput(
- "Define variant name",
- buttons=("Ok", "Cancel"),
- initial_contents=creator.get_default_variant(),
- title="Define variant",
- help="Set the variant for the publish instance",
- close_choice=1
- )
-
- if result == 1:
- # User interrupted
- return
-
- variant = variant.strip()
- if not variant:
- raise RuntimeError("Empty variant value entered.")
-
- # TODO: Once more elaborate unique create behavior should exist per Creator
- # instead of per network editor area then we should move this from here
- # to a method on the Creators for which this could be the default
- # implementation.
- pane = stateutils.activePane(kwargs)
- if isinstance(pane, hou.NetworkEditor):
- pwd = pane.pwd()
- project_name = context.get_current_project_name()
- folder_path = context.get_current_folder_path()
- task_name = context.get_current_task_name()
- folder_entity = ayon_api.get_folder_by_path(
- project_name, folder_path
- )
- task_entity = ayon_api.get_task_by_name(
- project_name, folder_entity["id"], task_name
- )
- product_name = creator.get_product_name(
- project_name=context.get_current_project_name(),
- folder_entity=folder_entity,
- task_entity=task_entity,
- variant=variant,
- host_name=context.host_name,
- )
-
- tool_fn = CATEGORY_GENERIC_TOOL.get(pwd.childTypeCategory())
- if tool_fn is not None:
- out_null = tool_fn(kwargs, "null")
- out_null.setName("OUT_{}".format(product_name), unique_name=True)
-
- before = context.instances_by_id.copy()
-
- # Create the instance
- context.create(
- creator_identifier=creator_identifier,
- variant=variant,
- pre_create_data={"use_selection": True}
- )
-
- # For convenience we set the new node as current since that's much more
- # familiar to the artist when creating a node interactively
- # TODO Allow to disable auto-select in studio settings or user preferences
- after = context.instances_by_id
- new = set(after) - set(before)
- if new:
- # Select the new instance
- for instance_id in new:
- instance = after[instance_id]
- node = hou.node(instance.get("instance_node"))
- node.setCurrent(True)
-
- return list(new)
-
-
-@contextlib.contextmanager
-def shelves_change_block():
- """Write shelf changes at the end of the context."""
- hou.shelves.beginChangeBlock()
- try:
- yield
- finally:
- hou.shelves.endChangeBlock()
-
-
-def install():
- """Install the Creator plug-ins to show in Houdini's TAB node search menu.
-
- This function is re-entrant and can be called again to reinstall and
- update the node definitions. For example during development it can be
- useful to call it manually:
- >>> from ayon_houdini.api.creator_node_shelves import install
- >>> install()
-
- Returns:
- list: List of `hou.Tool` instances
-
- """
-
- host = registered_host()
-
- # Store the filepath on the host
- # TODO: Define a less hacky static shelf path for current houdini session
- filepath_attr = "_creator_node_shelf_filepath"
- filepath = getattr(host, filepath_attr, None)
- if filepath is None:
- f = tempfile.NamedTemporaryFile(prefix="houdini_creator_nodes_",
- suffix=".shelf",
- delete=False)
- f.close()
- filepath = f.name
- setattr(host, filepath_attr, filepath)
- elif os.path.exists(filepath):
- # Remove any existing shelf file so that we can completey regenerate
- # and update the tools file if creator identifiers change
- os.remove(filepath)
-
- icon = get_ayon_icon_filepath()
- tab_menu_label = os.environ.get("AYON_MENU_LABEL") or "AYON"
-
- # Create context only to get creator plugins, so we don't reset and only
- # populate what we need to retrieve the list of creator plugins
- create_context = CreateContext(host, reset=False)
- create_context.reset_current_context()
- create_context._reset_creator_plugins()
-
- log.debug("Writing OpenPype Creator nodes to shelf: {}".format(filepath))
- tools = []
-
- with shelves_change_block():
- for identifier, creator in create_context.manual_creators.items():
-
- # Allow the creator plug-in itself to override the categories
- # for where they are shown with `Creator.get_network_categories()`
- if not hasattr(creator, "get_network_categories"):
- log.debug("Creator {} has no `get_network_categories` method "
- "and will not be added to TAB search.")
- continue
-
- network_categories = creator.get_network_categories()
- if not network_categories:
- continue
-
- key = "ayon_create.{}".format(identifier)
- log.debug(f"Registering {key}")
- script = CREATE_SCRIPT.format(identifier=identifier)
- data = {
- "script": script,
- "language": hou.scriptLanguage.Python,
- "icon": icon,
- "help": "Create Ayon publish instance for {}".format(
- creator.label
- ),
- "help_url": None,
- "network_categories": network_categories,
- "viewer_categories": [],
- "cop_viewer_categories": [],
- "network_op_type": None,
- "viewer_op_type": None,
- "locations": [tab_menu_label]
- }
- label = "Create {}".format(creator.label)
- tool = hou.shelves.tool(key)
- if tool:
- tool.setData(**data)
- tool.setLabel(label)
- else:
- tool = hou.shelves.newTool(
- file_path=filepath,
- name=key,
- label=label,
- **data
- )
-
- tools.append(tool)
-
- # Ensure the shelf is reloaded
- hou.shelves.loadFile(filepath)
-
- return tools
diff --git a/server_addon/houdini/client/ayon_houdini/api/hda_utils.py b/server_addon/houdini/client/ayon_houdini/api/hda_utils.py
deleted file mode 100644
index 412364bc04..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/hda_utils.py
+++ /dev/null
@@ -1,593 +0,0 @@
-"""Helper functions for load HDA"""
-
-import os
-import contextlib
-import uuid
-from typing import List
-
-import ayon_api
-from ayon_api import (
- get_project,
- get_representation_by_id,
- get_versions,
- get_folder_by_path,
- get_product_by_name,
- get_version_by_name,
- get_representation_by_name
-)
-from ayon_core.pipeline.load import (
- get_representation_context,
- get_representation_path_from_context
-)
-from ayon_core.pipeline.context_tools import (
- get_current_project_name,
- get_current_folder_path
-)
-from ayon_core.tools.utils import SimpleFoldersWidget
-from ayon_core.style import load_stylesheet
-
-from ayon_houdini.api import lib
-
-from qtpy import QtCore, QtWidgets
-import hou
-
-
-def is_valid_uuid(value) -> bool:
- """Return whether value is a valid UUID"""
- try:
- uuid.UUID(value)
- except ValueError:
- return False
- return True
-
-
-@contextlib.contextmanager
-def _unlocked_parm(parm):
- """Unlock parm during context; will always lock after"""
- try:
- parm.lock(False)
- yield
- finally:
- parm.lock(True)
-
-
-def get_available_versions(node):
- """Return the versions list for node.
-
- The versions are sorted with the latest version first and oldest lower
- version last.
-
- Args:
- node (hou.Node): Node to query selected products' versions for.
-
- Returns:
- list[int]: Version numbers for the product
- """
-
- project_name = node.evalParm("project_name") or get_current_project_name()
- folder_path = node.evalParm("folder_path")
- product_name = node.evalParm("product_name")
-
- if not all([
- project_name, folder_path, product_name
- ]):
- return []
-
- folder_entity = get_folder_by_path(
- project_name,
- folder_path,
- fields={"id"})
- if not folder_entity:
- return []
- product_entity = get_product_by_name(
- project_name,
- product_name=product_name,
- folder_id=folder_entity["id"],
- fields={"id"})
- if not product_entity:
- return []
-
- # TODO: Support hero versions
- versions = get_versions(
- project_name,
- product_ids={product_entity["id"]},
- fields={"version"},
- hero=False)
- version_names = [version["version"] for version in versions]
- version_names.reverse()
- return version_names
-
-
-def update_info(node, context):
- """Update project, folder, product, version, representation name parms.
-
- Arguments:
- node (hou.Node): Node to update
- context (dict): Context of representation
-
- """
- # TODO: Avoid 'duplicate' taking over the expression if originally
- # it was $OS and by duplicating, e.g. the `folder` does not exist
- # anymore since it is now `hero1` instead of `hero`
- # TODO: Support hero versions
- version = str(context["version"]["version"])
-
- # We only set the values if the value does not match the currently
- # evaluated result of the other parms, so that if the project name
- # value was dynamically set by the user with an expression or alike
- # then if it still matches the value of the current representation id
- # we preserve it. In essence, only update the value if the current
- # *evaluated* value of the parm differs.
- parms = {
- "project_name": context["project"]["name"],
- "folder_path": context["folder"]["path"],
- "product_name": context["product"]["name"],
- "version": version,
- "representation_name": context["representation"]["name"],
- }
- parms = {key: value for key, value in parms.items()
- if node.evalParm(key) != value}
- parms["load_message"] = "" # clear any warnings/errors
-
- # Note that these never trigger any parm callbacks since we do not
- # trigger the `parm.pressButton` and programmatically setting values
- # in Houdini does not trigger callbacks automatically
- node.setParms(parms)
-
-
-def _get_thumbnail(project_name: str, version_id: str, thumbnail_dir: str):
- folder = hou.text.expandString(thumbnail_dir)
- path = os.path.join(folder, "{}_thumbnail.jpg".format(version_id))
- expanded_path = hou.text.expandString(path)
- if os.path.isfile(expanded_path):
- return path
-
- # Try and create a thumbnail cache file
- data = ayon_api.get_thumbnail(project_name,
- entity_type="version",
- entity_id=version_id)
- if data:
- thumbnail_dir_expanded = hou.text.expandString(thumbnail_dir)
- os.makedirs(thumbnail_dir_expanded, exist_ok=True)
- with open(expanded_path, "wb") as f:
- f.write(data.content)
- return path
-
-
-def set_representation(node, representation_id: str):
- file_parm = node.parm("file")
- if not representation_id:
- # Clear filepath and thumbnail
- with _unlocked_parm(file_parm):
- file_parm.set("")
- set_node_thumbnail(node, None)
- return
-
- project_name = (
- node.evalParm("project_name")
- or get_current_project_name()
- )
-
- # Ignore invalid representation ids silently
- # TODO remove - added for backwards compatibility with OpenPype scenes
- if not is_valid_uuid(representation_id):
- return
-
- repre_entity = get_representation_by_id(project_name, representation_id)
- if not repre_entity:
- return
-
- context = get_representation_context(project_name, repre_entity)
- update_info(node, context)
- path = get_representation_path_from_context(context)
- # Load fails on UNC paths with backslashes and also
- # fails to resolve @sourcename var with backslashed
- # paths correctly. So we force forward slashes
- path = path.replace("\\", "/")
- with _unlocked_parm(file_parm):
- file_parm.set(path)
-
- if node.evalParm("show_thumbnail"):
- # Update thumbnail
- # TODO: Cache thumbnail path as well
- version_id = repre_entity["versionId"]
- thumbnail_dir = node.evalParm("thumbnail_cache_dir")
- thumbnail_path = _get_thumbnail(
- project_name, version_id, thumbnail_dir
- )
- set_node_thumbnail(node, thumbnail_path)
-
-
-def set_node_thumbnail(node, thumbnail: str):
- """Update node thumbnail to thumbnail"""
- if thumbnail is None:
- lib.set_node_thumbnail(node, None)
-
- rect = compute_thumbnail_rect(node)
- lib.set_node_thumbnail(node, thumbnail, rect)
-
-
-def compute_thumbnail_rect(node):
- """Compute thumbnail bounding rect based on thumbnail parms"""
- offset_x = node.evalParm("thumbnail_offsetx")
- offset_y = node.evalParm("thumbnail_offsety")
- width = node.evalParm("thumbnail_size")
- # todo: compute height from aspect of actual image file.
- aspect = 0.5625 # for now assume 16:9
- height = width * aspect
-
- center = 0.5
- half_width = (width * .5)
-
- return hou.BoundingRect(
- offset_x + center - half_width,
- offset_y,
- offset_x + center + half_width,
- offset_y + height
- )
-
-
-def on_thumbnail_show_changed(node):
- """Callback on thumbnail show parm changed"""
- if node.evalParm("show_thumbnail"):
- # For now, update all
- on_representation_id_changed(node)
- else:
- lib.remove_all_thumbnails(node)
-
-
-def on_thumbnail_size_changed(node):
- """Callback on thumbnail offset or size parms changed"""
- thumbnail = lib.get_node_thumbnail(node)
- if thumbnail:
- rect = compute_thumbnail_rect(node)
- thumbnail.setRect(rect)
- lib.set_node_thumbnail(node, thumbnail)
-
-
-def on_representation_id_changed(node):
- """Callback on representation id changed
-
- Args:
- node (hou.Node): Node to update.
- """
- repre_id = node.evalParm("representation")
- set_representation(node, repre_id)
-
-
-def on_representation_parms_changed(node):
- """
- Usually used as callback to the project, folder, product, version and
- representation parms which on change - would result in a different
- representation id to be resolved.
-
- Args:
- node (hou.Node): Node to update.
- """
- project_name = node.evalParm("project_name") or get_current_project_name()
- representation_id = get_representation_id(
- project_name=project_name,
- folder_path=node.evalParm("folder_path"),
- product_name=node.evalParm("product_name"),
- version=node.evalParm("version"),
- representation_name=node.evalParm("representation_name"),
- load_message_parm=node.parm("load_message")
- )
- if representation_id is None:
- representation_id = ""
- else:
- representation_id = str(representation_id)
-
- if node.evalParm("representation") != representation_id:
- node.parm("representation").set(representation_id)
- node.parm("representation").pressButton() # trigger callback
-
-
-def get_representation_id(
- project_name,
- folder_path,
- product_name,
- version,
- representation_name,
- load_message_parm,
-):
- """Get representation id.
-
- Args:
- project_name (str): Project name
- folder_path (str): Folder name
- product_name (str): Product name
- version (str): Version name as string
- representation_name (str): Representation name
- load_message_parm (hou.Parm): A string message parm to report
- any error messages to.
-
- Returns:
- Optional[str]: Representation id or None if not found.
-
- """
-
- if not all([
- project_name, folder_path, product_name, version, representation_name
- ]):
- labels = {
- "project": project_name,
- "folder": folder_path,
- "product": product_name,
- "version": version,
- "representation": representation_name
- }
- missing = ", ".join(key for key, value in labels.items() if not value)
- load_message_parm.set(f"Load info incomplete. Found empty: {missing}")
- return
-
- try:
- version = int(version.strip())
- except ValueError:
- load_message_parm.set(f"Invalid version format: '{version}'\n"
- "Make sure to set a valid version number.")
- return
-
- folder_entity = get_folder_by_path(project_name,
- folder_path=folder_path,
- fields={"id"})
- if not folder_entity:
- # This may be due to the project not existing - so let's validate
- # that first
- if not get_project(project_name):
- load_message_parm.set(f"Project not found: '{project_name}'")
- return
- load_message_parm.set(f"Folder not found: '{folder_path}'")
- return
-
- product_entity = get_product_by_name(
- project_name,
- product_name=product_name,
- folder_id=folder_entity["id"],
- fields={"id"})
- if not product_entity:
- load_message_parm.set(f"Product not found: '{product_name}'")
- return
- version_entity = get_version_by_name(
- project_name,
- version,
- product_id=product_entity["id"],
- fields={"id"})
- if not version_entity:
- load_message_parm.set(f"Version not found: '{version}'")
- return
- representation_entity = get_representation_by_name(
- project_name,
- representation_name,
- version_id=version_entity["id"],
- fields={"id"})
- if not representation_entity:
- load_message_parm.set(
- f"Representation not found: '{representation_name}'.")
- return
- return representation_entity["id"]
-
-
-def setup_flag_changed_callback(node):
- """Register flag changed callback (for thumbnail brightness)"""
- node.addEventCallback(
- (hou.nodeEventType.FlagChanged,),
- on_flag_changed
- )
-
-
-def on_flag_changed(node, **kwargs):
- """On node flag changed callback.
-
- Updates the brightness of attached thumbnails
- """
- # Showing thumbnail is disabled so can return early since
- # there should be no thumbnail to update.
- if not node.evalParm('show_thumbnail'):
- return
-
- # Update node thumbnails brightness with the
- # bypass state of the node.
- parent = node.parent()
- images = lib.get_background_images(parent)
- if not images:
- return
-
- brightness = 0.3 if node.isBypassed() else 1.0
- has_changes = False
- node_path = node.path()
- for image in images:
- if image.relativeToPath() == node_path:
- image.setBrightness(brightness)
- has_changes = True
-
- if has_changes:
- lib.set_background_images(parent, images)
-
-
-def keep_background_images_linked(node, old_name):
- """Reconnect background images to node from old name.
-
- Used as callback on node name changes to keep thumbnails linked."""
- from ayon_houdini.api.lib import (
- get_background_images,
- set_background_images
- )
-
- parent = node.parent()
- images = get_background_images(parent)
- if not images:
- return
-
- changes = False
- old_path = f"{node.parent().path()}/{old_name}"
- for image in images:
- if image.relativeToPath() == old_path:
- image.setRelativeToPath(node.path())
- changes = True
-
- if changes:
- set_background_images(parent, images)
-
-
-class SelectFolderPathDialog(QtWidgets.QDialog):
- """Simple dialog to allow a user to select project and asset."""
-
- def __init__(self, parent=None):
- super(SelectFolderPathDialog, self).__init__(parent)
- self.setWindowTitle("Set project and folder path")
- self.setStyleSheet(load_stylesheet())
-
- project_widget = QtWidgets.QComboBox()
- project_widget.addItems(self.get_projects())
-
- filter_widget = QtWidgets.QLineEdit()
- filter_widget.setPlaceholderText("Folder name filter...")
-
- folder_widget = SimpleFoldersWidget(parent=self)
-
- accept_button = QtWidgets.QPushButton("Accept")
-
- main_layout = QtWidgets.QVBoxLayout(self)
- main_layout.addWidget(project_widget, 0)
- main_layout.addWidget(filter_widget, 0)
- main_layout.addWidget(folder_widget, 1)
- main_layout.addWidget(accept_button, 0)
-
- self.project_widget = project_widget
- self.folder_widget = folder_widget
-
- project_widget.currentTextChanged.connect(self.on_project_changed)
- filter_widget.textChanged.connect(folder_widget.set_name_filter)
- folder_widget.double_clicked.connect(self.accept)
- accept_button.clicked.connect(self.accept)
-
- def get_selected_folder_path(self) -> str:
- return self.folder_widget.get_selected_folder_path()
-
- def get_selected_project_name(self) -> str:
- return self.project_widget.currentText()
-
- def get_projects(self) -> List[str]:
- projects = ayon_api.get_projects(fields=["name"])
- return [p["name"] for p in projects]
-
- def on_project_changed(self, project_name: str):
- self.folder_widget.set_project_name(project_name)
-
- def set_project_name(self, project_name: str):
- self.project_widget.setCurrentText(project_name)
-
- if self.project_widget.currentText() != project_name:
- # Project does not exist
- return
-
- # Force the set of widget because even though a callback exist on the
- # project widget it may have been initialized to that value and hence
- # detect no change.
- self.folder_widget.set_project_name(project_name)
-
-
-def select_folder_path(node):
- """Show dialog to select folder path.
-
- When triggered it opens a dialog that shows the available
- folder paths within a given project.
-
- Note:
- This function should be refactored.
- It currently shows the available
- folder paths within the current project only.
-
- Args:
- node (hou.OpNode): The HDA node.
- """
- main_window = lib.get_main_window()
-
- project_name = node.evalParm("project_name")
- folder_path = node.evalParm("folder_path")
-
- dialog = SelectFolderPathDialog(parent=main_window)
- dialog.set_project_name(project_name)
- if folder_path:
- # We add a small delay to the setting of the selected folder
- # because the folder widget's set project logic itself also runs
- # with a bit of a delay, and unfortunately otherwise the project
- # has not been selected yet and thus selection does not work.
- def _select_folder_path():
- dialog.folder_widget.set_selected_folder_path(folder_path)
- QtCore.QTimer.singleShot(100, _select_folder_path)
-
- dialog.setStyleSheet(load_stylesheet())
-
- result = dialog.exec_()
- if result != QtWidgets.QDialog.Accepted:
- return
-
- # Set project
- selected_project_name = dialog.get_selected_project_name()
- if selected_project_name == get_current_project_name():
- selected_project_name = '$AYON_PROJECT_NAME'
-
- project_parm = node.parm("project_name")
- project_parm.set(selected_project_name)
- project_parm.pressButton() # allow any callbacks to trigger
-
- # Set folder path
- selected_folder_path = dialog.get_selected_folder_path()
- if not selected_folder_path:
- # Do nothing if user accepted with nothing selected
- return
-
- if selected_folder_path == get_current_folder_path():
- selected_folder_path = '$AYON_FOLDER_PATH'
-
- folder_parm = node.parm("folder_path")
- folder_parm.set(selected_folder_path)
- folder_parm.pressButton() # allow any callbacks to trigger
-
-
-def get_available_products(node):
- """Return products menu items
- It gets a list of available products of the specified product types
- within the specified folder path with in the specified project.
- Users can specify those in the HDA parameters.
-
- Args:
- node (hou.OpNode): The HDA node.
-
- Returns:
- list[str]: Product names for Products menu.
- """
- project_name = node.evalParm("project_name")
- folder_path = node.evalParm("folder_path")
- product_type = node.evalParm("product_type")
-
- folder_entity = ayon_api.get_folder_by_path(project_name,
- folder_path,
- fields={"id"})
- if not folder_entity:
- return []
-
- products = ayon_api.get_products(
- project_name,
- folder_ids=[folder_entity["id"]],
- product_types=[product_type]
- )
-
- return [product["name"] for product in products]
-
-
-def set_to_latest_version(node):
- """Callback on product name change
-
- Refresh version parameter value by setting its value to
- the latest version of the selected product.
-
- Args:
- node (hou.OpNode): The HDA node.
- """
-
- versions = get_available_versions(node)
- if versions:
- node.parm("version").set(str(versions[0]))
diff --git a/server_addon/houdini/client/ayon_houdini/api/lib.py b/server_addon/houdini/client/ayon_houdini/api/lib.py
deleted file mode 100644
index eec3995821..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/lib.py
+++ /dev/null
@@ -1,1365 +0,0 @@
-# -*- coding: utf-8 -*-
-import sys
-import os
-import errno
-import re
-import logging
-import json
-from contextlib import contextmanager
-
-import six
-import ayon_api
-
-from ayon_core.lib import StringTemplate
-from ayon_core.settings import get_current_project_settings
-from ayon_core.pipeline import (
- Anatomy,
- get_current_project_name,
- get_current_folder_path,
- registered_host,
- get_current_context,
- get_current_host_name,
-)
-from ayon_core.pipeline.create import CreateContext
-from ayon_core.pipeline.template_data import get_template_data
-from ayon_core.pipeline.context_tools import get_current_folder_entity
-from ayon_core.tools.utils import PopupUpdateKeys, SimplePopup
-from ayon_core.tools.utils.host_tools import get_tool_by_name
-
-import hou
-
-
-self = sys.modules[__name__]
-self._parent = None
-log = logging.getLogger(__name__)
-JSON_PREFIX = "JSON:::"
-
-
-def get_folder_fps(folder_entity=None):
- """Return current folder fps."""
-
- if folder_entity is None:
- folder_entity = get_current_folder_entity(fields=["attrib.fps"])
- return folder_entity["attrib"]["fps"]
-
-
-def get_output_parameter(node):
- """Return the render output parameter of the given node
-
- Example:
- root = hou.node("/obj")
- my_alembic_node = root.createNode("alembic")
- get_output_parameter(my_alembic_node)
- >>> "filename"
-
- Notes:
- I'm using node.type().name() to get on par with the creators,
- Because the return value of `node.type().name()` is the
- same string value used in creators
- e.g. instance_data.update({"node_type": "alembic"})
-
- Rop nodes in different network categories have
- the same output parameter.
- So, I took that into consideration as a hint for
- future development.
-
- Args:
- node(hou.Node): node instance
-
- Returns:
- hou.Parm
- """
-
- node_type = node.type().name()
-
- # Figure out which type of node is being rendered
- if node_type in {"alembic", "rop_alembic"}:
- return node.parm("filename")
- elif node_type == "arnold":
- if node_type.evalParm("ar_ass_export_enable"):
- return node.parm("ar_ass_file")
- return node.parm("ar_picture")
- elif node_type in {
- "geometry",
- "rop_geometry",
- "filmboxfbx",
- "rop_fbx"
- }:
- return node.parm("sopoutput")
- elif node_type == "comp":
- return node.parm("copoutput")
- elif node_type in {"karma", "opengl"}:
- return node.parm("picture")
- elif node_type == "ifd": # Mantra
- if node.evalParm("soho_outputmode"):
- return node.parm("soho_diskfile")
- return node.parm("vm_picture")
- elif node_type == "Redshift_Proxy_Output":
- return node.parm("RS_archive_file")
- elif node_type == "Redshift_ROP":
- return node.parm("RS_outputFileNamePrefix")
- elif node_type in {"usd", "usd_rop", "usdexport"}:
- return node.parm("lopoutput")
- elif node_type in {"usdrender", "usdrender_rop"}:
- return node.parm("outputimage")
- elif node_type == "vray_renderer":
- return node.parm("SettingsOutput_img_file_path")
-
- raise TypeError("Node type '%s' not supported" % node_type)
-
-
-def set_scene_fps(fps):
- hou.setFps(fps)
-
-
-# Valid FPS
-def validate_fps():
- """Validate current scene FPS and show pop-up when it is incorrect
-
- Returns:
- bool
-
- """
-
- fps = get_folder_fps()
- current_fps = hou.fps() # returns float
-
- if current_fps != fps:
-
- # Find main window
- parent = hou.ui.mainQtWindow()
- if parent is None:
- pass
- else:
- dialog = PopupUpdateKeys(parent=parent)
- dialog.setModal(True)
- dialog.setWindowTitle("Houdini scene does not match project FPS")
- dialog.set_message("Scene %i FPS does not match project %i FPS" %
- (current_fps, fps))
- dialog.set_button_text("Fix")
-
- # on_show is the Fix button clicked callback
- dialog.on_clicked_state.connect(lambda: set_scene_fps(fps))
-
- dialog.show()
-
- return False
-
- return True
-
-
-def render_rop(ropnode):
- """Render ROP node utility for Publishing.
-
- This renders a ROP node with the settings we want during Publishing.
- """
- # Print verbose when in batch mode without UI
- verbose = not hou.isUIAvailable()
-
- # Render
- try:
- ropnode.render(verbose=verbose,
- # Allow Deadline to capture completion percentage
- output_progress=verbose,
- # Render only this node
- # (do not render any of its dependencies)
- ignore_inputs=True)
- except hou.Error as exc:
- # The hou.Error is not inherited from a Python Exception class,
- # so we explicitly capture the houdini error, otherwise pyblish
- # will remain hanging.
- import traceback
- traceback.print_exc()
- raise RuntimeError("Render failed: {0}".format(exc))
-
-
-def imprint(node, data, update=False):
- """Store attributes with value on a node
-
- Depending on the type of attribute it creates the correct parameter
- template. Houdini uses a template per type, see the docs for more
- information.
-
- http://www.sidefx.com/docs/houdini/hom/hou/ParmTemplate.html
-
- Because of some update glitch where you cannot overwrite existing
- ParmTemplates on node using:
- `setParmTemplates()` and `parmTuplesInFolder()`
- update is done in another pass.
-
- Args:
- node(hou.Node): node object from Houdini
- data(dict): collection of attributes and their value
- update (bool, optional): flag if imprint should update
- already existing data or leave them untouched and only
- add new.
-
- Returns:
- None
-
- """
- if not data:
- return
- if not node:
- self.log.error("Node is not set, calling imprint on invalid data.")
- return
-
- current_parms = {p.name(): p for p in node.spareParms()}
- update_parm_templates = []
- new_parm_templates = []
-
- for key, value in data.items():
- if value is None:
- continue
-
- parm_template = get_template_from_value(key, value)
-
- if key in current_parms:
- if node.evalParm(key) == value:
- continue
- if not update:
- log.debug(f"{key} already exists on {node}")
- else:
- log.debug(f"replacing {key}")
- update_parm_templates.append(parm_template)
- continue
-
- new_parm_templates.append(parm_template)
-
- if not new_parm_templates and not update_parm_templates:
- return
-
- parm_group = node.parmTemplateGroup()
-
- # Add new parm templates
- if new_parm_templates:
- parm_folder = parm_group.findFolder("Extra")
-
- # if folder doesn't exist yet, create one and append to it,
- # else append to existing one
- if not parm_folder:
- parm_folder = hou.FolderParmTemplate("folder", "Extra")
- parm_folder.setParmTemplates(new_parm_templates)
- parm_group.append(parm_folder)
- else:
- # Add to parm template folder instance then replace with updated
- # one in parm template group
- for template in new_parm_templates:
- parm_folder.addParmTemplate(template)
- parm_group.replace(parm_folder.name(), parm_folder)
-
- # Update existing parm templates
- for parm_template in update_parm_templates:
- parm_group.replace(parm_template.name(), parm_template)
-
- # When replacing a parm with a parm of the same name it preserves its
- # value if before the replacement the parm was not at the default,
- # because it has a value override set. Since we're trying to update the
- # parm by using the new value as `default` we enforce the parm is at
- # default state
- node.parm(parm_template.name()).revertToDefaults()
-
- node.setParmTemplateGroup(parm_group)
-
-
-def lsattr(attr, value=None, root="/"):
- """Return nodes that have `attr`
- When `value` is not None it will only return nodes matching that value
- for the given attribute.
- Args:
- attr (str): Name of the attribute (hou.Parm)
- value (object, Optional): The value to compare the attribute too.
- When the default None is provided the value check is skipped.
- root (str): The root path in Houdini to search in.
- Returns:
- list: Matching nodes that have attribute with value.
- """
- if value is None:
- # Use allSubChildren() as allNodes() errors on nodes without
- # permission to enter without a means to continue of querying
- # the rest
- nodes = hou.node(root).allSubChildren()
- return [n for n in nodes if n.parm(attr)]
- return lsattrs({attr: value})
-
-
-def lsattrs(attrs, root="/"):
- """Return nodes matching `key` and `value`
- Arguments:
- attrs (dict): collection of attribute: value
- root (str): The root path in Houdini to search in.
- Example:
- >> lsattrs({"id": "myId"})
- ["myNode"]
- >> lsattr("id")
- ["myNode", "myOtherNode"]
- Returns:
- list: Matching nodes that have attribute with value.
- """
-
- matches = set()
- # Use allSubChildren() as allNodes() errors on nodes without
- # permission to enter without a means to continue of querying
- # the rest
- nodes = hou.node(root).allSubChildren()
- for node in nodes:
- for attr in attrs:
- if not node.parm(attr):
- continue
- elif node.evalParm(attr) != attrs[attr]:
- continue
- else:
- matches.add(node)
-
- return list(matches)
-
-
-def read(node):
- """Read the container data in to a dict
-
- Args:
- node(hou.Node): Houdini node
-
- Returns:
- dict
-
- """
- # `spareParms` returns a tuple of hou.Parm objects
- data = {}
- if not node:
- return data
- for parameter in node.spareParms():
- value = parameter.eval()
- # test if value is json encoded dict
- if isinstance(value, six.string_types) and \
- value.startswith(JSON_PREFIX):
- try:
- value = json.loads(value[len(JSON_PREFIX):])
- except json.JSONDecodeError:
- # not a json
- pass
- data[parameter.name()] = value
-
- return data
-
-
-@contextmanager
-def maintained_selection():
- """Maintain selection during context
- Example:
- >>> with maintained_selection():
- ... # Modify selection
- ... node.setSelected(on=False, clear_all_selected=True)
- >>> # Selection restored
- """
-
- previous_selection = hou.selectedNodes()
- try:
- yield
- finally:
- # Clear the selection
- # todo: does hou.clearAllSelected() do the same?
- for node in hou.selectedNodes():
- node.setSelected(on=False)
-
- if previous_selection:
- for node in previous_selection:
- node.setSelected(on=True)
-
-
-@contextmanager
-def parm_values(overrides):
- """Override Parameter values during the context.
- Arguments:
- overrides (List[Tuple[hou.Parm, Any]]): The overrides per parm
- that should be applied during 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)
-
-
-def reset_framerange(fps=True, frame_range=True):
- """Set frame range and FPS to current folder."""
-
- project_name = get_current_project_name()
- folder_path = get_current_folder_path()
-
- folder_entity = ayon_api.get_folder_by_path(project_name, folder_path)
- folder_attributes = folder_entity["attrib"]
-
- # Set FPS
- if fps:
- fps = get_folder_fps(folder_entity)
- print("Setting scene FPS to {}".format(int(fps)))
- set_scene_fps(fps)
-
- if frame_range:
-
- # Set Start and End Frames
- frame_start = folder_attributes.get("frameStart")
- frame_end = folder_attributes.get("frameEnd")
-
- if frame_start is None or frame_end is None:
- log.warning("No edit information found for '%s'", folder_path)
- return
-
- handle_start = folder_attributes.get("handleStart", 0)
- handle_end = folder_attributes.get("handleEnd", 0)
-
- frame_start -= int(handle_start)
- frame_end += int(handle_end)
-
- # Set frame range and FPS
- hou.playbar.setFrameRange(frame_start, frame_end)
- hou.playbar.setPlaybackRange(frame_start, frame_end)
- hou.setFrame(frame_start)
-
-
-def get_main_window():
- """Acquire Houdini's main window"""
- if self._parent is None:
- self._parent = hou.ui.mainQtWindow()
- return self._parent
-
-
-def get_template_from_value(key, value):
- if isinstance(value, float):
- parm = hou.FloatParmTemplate(name=key,
- label=key,
- num_components=1,
- default_value=(value,))
- elif isinstance(value, bool):
- parm = hou.ToggleParmTemplate(name=key,
- label=key,
- default_value=value)
- elif isinstance(value, int):
- parm = hou.IntParmTemplate(name=key,
- label=key,
- num_components=1,
- default_value=(value,))
- elif isinstance(value, six.string_types):
- parm = hou.StringParmTemplate(name=key,
- label=key,
- num_components=1,
- default_value=(value,))
- elif isinstance(value, (dict, list, tuple)):
- parm = hou.StringParmTemplate(name=key,
- label=key,
- num_components=1,
- default_value=(
- JSON_PREFIX + json.dumps(value),))
- else:
- raise TypeError("Unsupported type: %r" % type(value))
-
- return parm
-
-
-def get_frame_data(node, log=None):
- """Get the frame data: `frameStartHandle`, `frameEndHandle`
- and `byFrameStep`.
-
- This function uses Houdini node's `trange`, `t1, `t2` and `t3`
- parameters as the source of truth for the full inclusive frame
- range to render, as such these are considered as the frame
- range including the handles.
-
- The non-inclusive frame start and frame end without handles
- can be computed by subtracting the handles from the inclusive
- frame range.
-
- Args:
- node (hou.Node): ROP node to retrieve frame range from,
- the frame range is assumed to be the frame range
- *including* the start and end handles.
-
- Returns:
- dict: frame data for `frameStartHandle`, `frameEndHandle`
- and `byFrameStep`.
-
- """
-
- if log is None:
- log = self.log
-
- data = {}
-
- if node.parm("trange") is None:
- log.debug(
- "Node has no 'trange' parameter: {}".format(node.path())
- )
- return data
-
- if node.evalParm("trange") == 0:
- data["frameStartHandle"] = hou.intFrame()
- data["frameEndHandle"] = hou.intFrame()
- data["byFrameStep"] = 1.0
-
- log.info(
- "Node '{}' has 'Render current frame' set.\n"
- "Folder Handles are ignored.\n"
- "frameStart and frameEnd are set to the "
- "current frame.".format(node.path())
- )
- else:
- data["frameStartHandle"] = int(node.evalParm("f1"))
- data["frameEndHandle"] = int(node.evalParm("f2"))
- data["byFrameStep"] = node.evalParm("f3")
-
- return data
-
-
-def splitext(name, allowed_multidot_extensions):
- # type: (str, list) -> tuple
- """Split file name to name and extension.
-
- Args:
- name (str): File name to split.
- allowed_multidot_extensions (list of str): List of allowed multidot
- extensions.
-
- Returns:
- tuple: Name and extension.
- """
-
- for ext in allowed_multidot_extensions:
- if name.endswith(ext):
- return name[:-len(ext)], ext
-
- return os.path.splitext(name)
-
-
-def get_top_referenced_parm(parm):
-
- processed = set() # disallow infinite loop
- while True:
- if parm.path() in processed:
- raise RuntimeError("Parameter references result in cycle.")
-
- processed.add(parm.path())
-
- ref = parm.getReferencedParm()
- if ref.path() == parm.path():
- # It returns itself when it doesn't reference
- # another parameter
- return ref
- else:
- parm = ref
-
-
-def evalParmNoFrame(node, parm, pad_character="#"):
-
- parameter = node.parm(parm)
- assert parameter, "Parameter does not exist: %s.%s" % (node, parm)
-
- # If the parameter has a parameter reference, then get that
- # parameter instead as otherwise `unexpandedString()` fails.
- parameter = get_top_referenced_parm(parameter)
-
- # Substitute out the frame numbering with padded characters
- try:
- raw = parameter.unexpandedString()
- except hou.Error as exc:
- print("Failed: %s" % parameter)
- raise RuntimeError(exc)
-
- def replace(match):
- padding = 1
- n = match.group(2)
- if n and int(n):
- padding = int(n)
- return pad_character * padding
-
- expression = re.sub(r"(\$F([0-9]*))", replace, raw)
-
- with hou.ScriptEvalContext(parameter):
- return hou.expandStringAtFrame(expression, 0)
-
-
-def get_color_management_preferences():
- """Get default OCIO preferences"""
- return {
- "config": hou.Color.ocio_configPath(),
- "display": hou.Color.ocio_defaultDisplay(),
- "view": hou.Color.ocio_defaultView()
- }
-
-
-def get_obj_node_output(obj_node):
- """Find output node.
-
- If the node has any output node return the
- output node with the minimum `outputidx`.
- When no output is present return the node
- with the display flag set. If no output node is
- detected then None is returned.
-
- Arguments:
- node (hou.Node): The node to retrieve a single
- the output node for.
-
- Returns:
- Optional[hou.Node]: The child output node.
-
- """
-
- outputs = obj_node.subnetOutputs()
- if not outputs:
- return
-
- elif len(outputs) == 1:
- return outputs[0]
-
- else:
- return min(outputs,
- key=lambda node: node.evalParm('outputidx'))
-
-
-def get_output_children(output_node, include_sops=True):
- """Recursively return a list of all output nodes
- contained in this node including this node.
-
- It works in a similar manner to output_node.allNodes().
- """
- out_list = [output_node]
-
- if output_node.childTypeCategory() == hou.objNodeTypeCategory():
- for child in output_node.children():
- out_list += get_output_children(child, include_sops=include_sops)
-
- elif include_sops and \
- output_node.childTypeCategory() == hou.sopNodeTypeCategory():
- out = get_obj_node_output(output_node)
- if out:
- out_list += [out]
-
- return out_list
-
-
-def get_resolution_from_folder(folder_entity):
- """Get resolution from the given folder entity.
-
- Args:
- folder_entity (dict[str, Any]): Folder entity.
-
- Returns:
- Union[Tuple[int, int], None]: Resolution width and height.
-
- """
- if not folder_entity or "attrib" not in folder_entity:
- print("Entered folder is not valid. \"{}\"".format(
- str(folder_entity)
- ))
- return None
-
- folder_attributes = folder_entity["attrib"]
- resolution_width = folder_attributes.get("resolutionWidth")
- resolution_height = folder_attributes.get("resolutionHeight")
-
- # Make sure both width and height are set
- if resolution_width is None or resolution_height is None:
- print("No resolution information found for '{}'".format(
- folder_entity["path"]
- ))
- return None
-
- return int(resolution_width), int(resolution_height)
-
-
-def set_camera_resolution(camera, folder_entity=None):
- """Apply resolution to camera from folder entity of the publish"""
-
- if not folder_entity:
- folder_entity = get_current_folder_entity()
-
- resolution = get_resolution_from_folder(folder_entity)
-
- if resolution:
- print("Setting camera resolution: {} -> {}x{}".format(
- camera.name(), resolution[0], resolution[1]
- ))
- camera.parm("resx").set(resolution[0])
- camera.parm("resy").set(resolution[1])
-
-
-def get_camera_from_container(container):
- """Get camera from container node. """
-
- cameras = container.recursiveGlob(
- "*",
- filter=hou.nodeTypeFilter.ObjCamera,
- include_subnets=False
- )
-
- assert len(cameras) == 1, "Camera instance must have only one camera"
- return cameras[0]
-
-
-def get_current_context_template_data_with_folder_attrs():
- """
-
- Output contains 'folderAttributes' key with folder attribute values.
-
- Returns:
- dict[str, Any]: Template data to fill templates.
-
- """
- context = get_current_context()
- project_name = context["project_name"]
- folder_path = context["folder_path"]
- task_name = context["task_name"]
- host_name = get_current_host_name()
-
- project_entity = ayon_api.get_project(project_name)
- anatomy = Anatomy(project_name, project_entity=project_entity)
- folder_entity = ayon_api.get_folder_by_path(project_name, folder_path)
- task_entity = ayon_api.get_task_by_name(
- project_name, folder_entity["id"], task_name
- )
-
- # get context specific vars
- folder_attributes = folder_entity["attrib"]
-
- # compute `frameStartHandle` and `frameEndHandle`
- frame_start = folder_attributes.get("frameStart")
- frame_end = folder_attributes.get("frameEnd")
- handle_start = folder_attributes.get("handleStart")
- handle_end = folder_attributes.get("handleEnd")
- if frame_start is not None and handle_start is not None:
- folder_attributes["frameStartHandle"] = frame_start - handle_start
-
- if frame_end is not None and handle_end is not None:
- folder_attributes["frameEndHandle"] = frame_end + handle_end
-
- template_data = get_template_data(
- project_entity, folder_entity, task_entity, host_name
- )
- template_data["root"] = anatomy.roots
- template_data["folderAttributes"] = folder_attributes
-
- return template_data
-
-
-def set_review_color_space(opengl_node, review_color_space="", log=None):
- """Set ociocolorspace parameter for the given OpenGL node.
-
- Set `ociocolorspace` parameter of the given OpenGl node
- to to the given review_color_space value.
- If review_color_space is empty, a default colorspace corresponding to
- the display & view of the current Houdini session will be used.
-
- Args:
- opengl_node (hou.Node): ROP node to set its ociocolorspace parm.
- review_color_space (str): Colorspace value for ociocolorspace parm.
- log (logging.Logger): Logger to log to.
- """
-
- if log is None:
- log = self.log
-
- # Set Color Correction parameter to OpenColorIO
- colorcorrect_parm = opengl_node.parm("colorcorrect")
- if colorcorrect_parm.eval() != 2:
- colorcorrect_parm.set(2)
- log.debug(
- "'Color Correction' parm on '{}' has been set to"
- " 'OpenColorIO'".format(opengl_node.path())
- )
-
- opengl_node.setParms(
- {"ociocolorspace": review_color_space}
- )
-
- log.debug(
- "'OCIO Colorspace' parm on '{}' has been set to "
- "the view color space '{}'"
- .format(opengl_node, review_color_space)
- )
-
-
-def get_context_var_changes():
- """get context var changes."""
-
- houdini_vars_to_update = {}
-
- project_settings = get_current_project_settings()
- houdini_vars_settings = \
- project_settings["houdini"]["general"]["update_houdini_var_context"]
-
- if not houdini_vars_settings["enabled"]:
- return houdini_vars_to_update
-
- houdini_vars = houdini_vars_settings["houdini_vars"]
-
- # No vars specified - nothing to do
- if not houdini_vars:
- return houdini_vars_to_update
-
- # Get Template data
- template_data = get_current_context_template_data_with_folder_attrs()
-
- # Set Houdini Vars
- for item in houdini_vars:
- # For consistency reasons we always force all vars to be uppercase
- # Also remove any leading, and trailing whitespaces.
- var = item["var"].strip().upper()
-
- # get and resolve template in value
- item_value = StringTemplate.format_template(
- item["value"],
- template_data
- )
-
- if var == "JOB" and item_value == "":
- # sync $JOB to $HIP if $JOB is empty
- item_value = os.environ["HIP"]
-
- if item["is_directory"]:
- item_value = item_value.replace("\\", "/")
-
- current_value = hou.hscript("echo -n `${}`".format(var))[0]
-
- if current_value != item_value:
- houdini_vars_to_update[var] = (
- current_value, item_value, item["is_directory"]
- )
-
- return houdini_vars_to_update
-
-
-def update_houdini_vars_context():
- """Update folder context variables"""
-
- for var, (_old, new, is_directory) in get_context_var_changes().items():
- if is_directory:
- try:
- os.makedirs(new)
- except OSError as e:
- if e.errno != errno.EEXIST:
- print(
- "Failed to create ${} dir. Maybe due to "
- "insufficient permissions.".format(var)
- )
-
- hou.hscript("set {}={}".format(var, new))
- os.environ[var] = new
- print("Updated ${} to {}".format(var, new))
-
-
-def update_houdini_vars_context_dialog():
- """Show pop-up to update folder context variables"""
- update_vars = get_context_var_changes()
- if not update_vars:
- # Nothing to change
- print("Nothing to change, Houdini vars are already up to date.")
- return
-
- message = "\n".join(
- "${}: {} -> {}".format(var, old or "None", new or "None")
- for var, (old, new, _is_directory) in update_vars.items()
- )
-
- # TODO: Use better UI!
- parent = hou.ui.mainQtWindow()
- dialog = SimplePopup(parent=parent)
- dialog.setModal(True)
- dialog.setWindowTitle("Houdini scene has outdated folder variables")
- dialog.set_message(message)
- dialog.set_button_text("Fix")
-
- # on_show is the Fix button clicked callback
- dialog.on_clicked.connect(update_houdini_vars_context)
-
- dialog.show()
-
-
-def publisher_show_and_publish(comment=None):
- """Open publisher window and trigger publishing action.
-
- Args:
- comment (Optional[str]): Comment to set in publisher window.
- """
-
- main_window = get_main_window()
- publisher_window = get_tool_by_name(
- tool_name="publisher",
- parent=main_window,
- )
- publisher_window.show_and_publish(comment)
-
-
-def find_rop_input_dependencies(input_tuple):
- """Self publish from ROP nodes.
-
- Arguments:
- tuple (hou.RopNode.inputDependencies) which can be a nested tuples
- represents the input dependencies of the ROP node, consisting of ROPs,
- and the frames that need to be be rendered prior to rendering the ROP.
-
- Returns:
- list of the RopNode.path() that can be found inside
- the input tuple.
- """
-
- out_list = []
- if isinstance(input_tuple[0], hou.RopNode):
- return input_tuple[0].path()
-
- if isinstance(input_tuple[0], tuple):
- for item in input_tuple:
- out_list.append(find_rop_input_dependencies(item))
-
- return out_list
-
-
-def self_publish():
- """Self publish from ROP nodes.
-
- Firstly, it gets the node and its dependencies.
- Then, it deactivates all other ROPs
- And finally, it triggers the publishing action.
- """
-
- result, comment = hou.ui.readInput(
- "Add Publish Comment",
- buttons=("Publish", "Cancel"),
- title="Publish comment",
- close_choice=1
- )
-
- if result:
- return
-
- current_node = hou.node(".")
- inputs_paths = find_rop_input_dependencies(
- current_node.inputDependencies()
- )
- inputs_paths.append(current_node.path())
-
- host = registered_host()
- context = CreateContext(host, reset=True)
-
- for instance in context.instances:
- node_path = instance.data.get("instance_node")
- instance["active"] = node_path and node_path in inputs_paths
-
- context.save_changes()
-
- publisher_show_and_publish(comment)
-
-
-def add_self_publish_button(node):
- """Adds a self publish button to the rop node."""
-
- label = os.environ.get("AYON_MENU_LABEL") or "AYON"
-
- button_parm = hou.ButtonParmTemplate(
- "ayon_self_publish",
- "{} Publish".format(label),
- script_callback="from ayon_houdini.api.lib import "
- "self_publish; self_publish()",
- script_callback_language=hou.scriptLanguage.Python,
- join_with_next=True
- )
-
- template = node.parmTemplateGroup()
- template.insertBefore((0,), button_parm)
- node.setParmTemplateGroup(template)
-
-
-def get_scene_viewer(visible_only=True):
- """
- Return an instance of a visible viewport.
-
- There may be many, some could be closed, any visible are current
-
- Arguments:
- visible_only (Optional[bool]): Only return viewers that currently
- are the active tab (and hence are visible).
-
- Returns:
- Optional[hou.SceneViewer]: A scene viewer, if any.
- """
- panes = hou.ui.paneTabs()
- panes = [x for x in panes if x.type() == hou.paneTabType.SceneViewer]
-
- if visible_only:
- return next((pane for pane in panes if pane.isCurrentTab()), None)
-
- panes = sorted(panes, key=lambda x: x.isCurrentTab())
- if panes:
- return panes[-1]
-
- return None
-
-
-def sceneview_snapshot(
- sceneview,
- filepath="$HIP/thumbnails/$HIPNAME.$F4.jpg",
- frame_start=None,
- frame_end=None):
- """Take a snapshot of your scene view.
-
- It takes snapshot of your scene view for the given frame range.
- So, it's capable of generating snapshots image sequence.
- It works in different Houdini context e.g. Objects, Solaris
-
- Example::
- >>> from ayon_houdini.api import lib
- >>> sceneview = hou.ui.paneTabOfType(hou.paneTabType.SceneViewer)
- >>> lib.sceneview_snapshot(sceneview)
-
- Notes:
- .png output will render poorly, so use .jpg.
-
- How it works:
- Get the current sceneviewer (may be more than one or hidden)
- and screengrab the perspective viewport to a file in the
- publish location to be picked up with the publish.
-
- Credits:
- https://www.sidefx.com/forum/topic/42808/?page=1#post-354796
-
- Args:
- sceneview (hou.SceneViewer): The scene view pane from which you want
- to take a snapshot.
- filepath (str): thumbnail filepath. it expects `$F4` token
- when frame_end is bigger than frame_star other wise
- each frame will override its predecessor.
- frame_start (int): the frame at which snapshot starts
- frame_end (int): the frame at which snapshot ends
- """
-
- if frame_start is None:
- frame_start = hou.frame()
- if frame_end is None:
- frame_end = frame_start
-
- if not isinstance(sceneview, hou.SceneViewer):
- log.debug("Wrong Input. {} is not of type hou.SceneViewer."
- .format(sceneview))
- return
- viewport = sceneview.curViewport()
-
- flip_settings = sceneview.flipbookSettings().stash()
- flip_settings.frameRange((frame_start, frame_end))
- flip_settings.output(filepath)
- flip_settings.outputToMPlay(False)
- sceneview.flipbook(viewport, flip_settings)
- log.debug("A snapshot of sceneview has been saved to: {}".format(filepath))
-
-
-def get_background_images(node, raw=False):
- """"Return background images defined inside node.
-
- Similar to `nodegraphutils.saveBackgroundImages` but this method also
- allows to retrieve the data as JSON encodable data instead of
- `hou.NetworkImage` instances when using `raw=True`
- """
-
- def _parse(image_data):
- image = hou.NetworkImage(image_data["path"],
- hou.BoundingRect(*image_data["rect"]))
- if "relativetopath" in image_data:
- image.setRelativeToPath(image_data["relativetopath"])
- if "brightness" in image_data:
- image.setBrightness(image_data["brightness"])
- return image
-
- data = node.userData("backgroundimages")
- if not data:
- return []
-
- try:
- images = json.loads(data)
- except json.decoder.JSONDecodeError:
- images = []
-
- if not raw:
- images = [_parse(_data) for _data in images]
- return images
-
-
-def set_background_images(node, images):
- """Set hou.NetworkImage background images under given hou.Node
-
- Similar to: `nodegraphutils.loadBackgroundImages`
-
- """
-
- def _serialize(image):
- """Return hou.NetworkImage as serialized dict"""
- if isinstance(image, dict):
- # Assume already serialized, only do some minor validations
- if "path" not in image:
- raise ValueError("Missing `path` key in image dictionary.")
- if "rect" not in image:
- raise ValueError("Missing `rect` key in image dictionary.")
- if len(image["rect"]) != 4:
- raise ValueError("`rect` value must be list of four floats.")
- return image
-
- rect = image.rect()
- rect_min = rect.min()
- rect_max = rect.max()
- data = {
- "path": image.path(),
- "rect": [rect_min.x(), rect_min.y(), rect_max.x(), rect_max.y()],
- }
- if image.brightness() != 1.0:
- data["brightness"] = image.brightness()
- if image.relativeToPath():
- data["relativetopath"] = image.relativeToPath()
- return data
-
- with hou.undos.group('Edit Background Images'):
- if images:
- assert all(isinstance(image, (dict, hou.NetworkImage))
- for image in images)
- data = json.dumps([_serialize(image) for image in images])
- node.setUserData("backgroundimages", data)
- else:
- node.destroyUserData("backgroundimages", must_exist=False)
-
-
-def set_node_thumbnail(node, image_path, rect=None):
- """Set hou.NetworkImage attached to node.
-
- If an existing connected image is found it assumes that is the existing
- thumbnail and will update that particular instance instead.
-
- When `image_path` is None an existing attached `hou.NetworkImage` will be
- removed.
-
- Arguments:
- node (hou.Node): Node to set thumbnail for.
- image_path (Union[str, None]): Path to image to set.
- If None is set then the thumbnail will be removed if it exists.
- rect (hou.BoundingRect): Bounding rect for the relative placement
- to the node.
-
- Returns:
- hou.NetworkImage or None: The network image that was set or None if
- instead it not set or removed.
-
- """
-
- parent = node.parent()
- images = get_background_images(parent)
-
- node_path = node.path()
- # Find first existing image attached to node
- index, image = next(
- (
- (index, image) for index, image in enumerate(images) if
- image.relativeToPath() == node_path
- ),
- (None, None)
- )
- if image_path is None:
- # Remove image if it exists
- if image:
- images.remove(image)
- set_background_images(parent, images)
- return
-
- if rect is None:
- rect = hou.BoundingRect(-1, -1, 1, 1)
-
- if isinstance(image_path, hou.NetworkImage):
- image = image_path
- if index is not None:
- images[index] = image
- else:
- images.append(image)
- elif image is None:
- # Create the image
- image = hou.NetworkImage(image_path, rect)
- image.setRelativeToPath(node.path())
- images.append(image)
- else:
- # Update first existing image
- image.setRect(rect)
- image.setPath(image_path)
-
- set_background_images(parent, images)
-
- return image
-
-
-def remove_all_thumbnails(node):
- """Remove all node thumbnails.
-
- Removes all network background images that are linked to the given node.
- """
- parent = node.parent()
- images = get_background_images(parent)
- node_path = node.path()
- images = [
- image for image in images if image.relativeToPath() != node_path
- ]
- set_background_images(parent, images)
-
-
-def get_node_thumbnail(node, first_only=True):
- """Return node thumbnails.
-
- Return network background images that are linked to the given node.
- By default, only returns the first one found, unless `first_only` is False.
-
- Returns:
- Union[hou.NetworkImage, List[hou.NetworkImage]]:
- Connected network images
-
- """
- parent = node.parent()
- images = get_background_images(parent)
- node_path = node.path()
-
- def is_attached_to_node(image):
- return image.relativeToPath() == node_path
-
- attached_images = filter(is_attached_to_node, images)
-
- # Find first existing image attached to node
- if first_only:
- return next(attached_images, None)
- else:
- return attached_images
-
-
-def find_active_network(category, default):
- """Find the first active network editor in the UI.
-
- If no active network editor pane is found at the given category then the
- `default` path will be used as fallback.
-
- For example, to find an active LOPs network:
- >>> network = find_active_network(
- ... category=hou.lopNodeTypeCategory(),
- ... fallback="/stage"
- ... )
- hou.Node("/stage/lopnet1")
-
- Arguments:
- category (hou.NodeTypeCategory): The node network category type.
- default (str): The default path to fallback to if no active pane
- is found with the given category.
-
- Returns:
- hou.Node: The node network to return.
-
- """
- # Find network editors that are current tab of given category
- index = 0
- while True:
- pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor, index)
- if pane is None:
- break
-
- index += 1
- if not pane.isCurrentTab():
- continue
-
- pwd = pane.pwd()
- if pwd.type().category() != category:
- continue
-
- if not pwd.isEditable():
- continue
-
- return pwd
-
- # Default to the fallback if no valid candidate was found
- return hou.node(default)
-
-
-def update_content_on_context_change():
- """Update all Creator instances to current asset"""
- host = registered_host()
- context = host.get_current_context()
-
- folder_path = context["folder_path"]
- task = context["task_name"]
-
- create_context = CreateContext(host, reset=True)
-
- for instance in create_context.instances:
- instance_folder_path = instance.get("folderPath")
- if instance_folder_path and instance_folder_path != folder_path:
- instance["folderPath"] = folder_path
- instance_task = instance.get("task")
- if instance_task and instance_task != task:
- instance["task"] = task
-
- create_context.save_changes()
-
-
-def prompt_reset_context():
- """Prompt the user what context settings to reset.
- This prompt is used on saving to a different task to allow the scene to
- get matched to the new context.
- """
- # TODO: Cleanup this prototyped mess of imports and odd dialog
- from ayon_core.tools.attribute_defs.dialog import (
- AttributeDefinitionsDialog
- )
- from ayon_core.style import load_stylesheet
- from ayon_core.lib import BoolDef, UILabelDef
-
- definitions = [
- UILabelDef(
- label=(
- "You are saving your workfile into a different folder or task."
- "\n\n"
- "Would you like to update some settings to the new context?\n"
- )
- ),
- BoolDef(
- "fps",
- label="FPS",
- tooltip="Reset workfile FPS",
- default=True
- ),
- BoolDef(
- "frame_range",
- label="Frame Range",
- tooltip="Reset workfile start and end frame ranges",
- default=True
- ),
- BoolDef(
- "instances",
- label="Publish instances",
- tooltip="Update all publish instance's folder and task to match "
- "the new folder and task",
- default=True
- ),
- ]
-
- dialog = AttributeDefinitionsDialog(definitions)
- dialog.setWindowTitle("Saving to different context.")
- dialog.setStyleSheet(load_stylesheet())
- if not dialog.exec_():
- return None
-
- options = dialog.get_values()
- if options["fps"] or options["frame_range"]:
- reset_framerange(
- fps=options["fps"],
- frame_range=options["frame_range"]
- )
-
- if options["instances"]:
- update_content_on_context_change()
-
- dialog.deleteLater()
diff --git a/server_addon/houdini/client/ayon_houdini/api/pipeline.py b/server_addon/houdini/client/ayon_houdini/api/pipeline.py
deleted file mode 100644
index c6fbbd5b62..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/pipeline.py
+++ /dev/null
@@ -1,449 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Pipeline tools for OpenPype Houdini integration."""
-import os
-import json
-import logging
-
-import hou # noqa
-
-from ayon_core.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
-from ayon_core.tools.utils import host_tools
-import pyblish.api
-
-from ayon_core.pipeline import (
- register_creator_plugin_path,
- register_loader_plugin_path,
- register_inventory_action_path,
- AVALON_CONTAINER_ID,
- AYON_CONTAINER_ID,
-)
-from ayon_core.pipeline.load import any_outdated_containers
-from ayon_houdini import HOUDINI_HOST_DIR
-from ayon_houdini.api import lib, shelves, creator_node_shelves
-
-from ayon_core.lib import (
- register_event_callback,
- emit_event,
- env_value_to_bool,
-)
-
-from .lib import JSON_PREFIX
-
-
-log = logging.getLogger("ayon_houdini")
-
-AVALON_CONTAINERS = "/obj/AVALON_CONTAINERS"
-CONTEXT_CONTAINER = "/obj/OpenPypeContext"
-IS_HEADLESS = not hasattr(hou, "ui")
-
-PLUGINS_DIR = os.path.join(HOUDINI_HOST_DIR, "plugins")
-PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
-LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
-CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
-INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
-
-# Track whether the workfile tool is about to save
-_about_to_save = False
-
-
-class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
- name = "houdini"
-
- def __init__(self):
- super(HoudiniHost, self).__init__()
- self._op_events = {}
- self._has_been_setup = False
-
- def install(self):
- pyblish.api.register_host("houdini")
- pyblish.api.register_host("hython")
- pyblish.api.register_host("hpython")
-
- pyblish.api.register_plugin_path(PUBLISH_PATH)
- register_loader_plugin_path(LOAD_PATH)
- register_creator_plugin_path(CREATE_PATH)
- register_inventory_action_path(INVENTORY_PATH)
-
- log.info("Installing callbacks ... ")
- # register_event_callback("init", on_init)
- self._register_callbacks()
- register_event_callback("workfile.save.before", before_workfile_save)
- register_event_callback("before.save", before_save)
- register_event_callback("save", on_save)
- register_event_callback("open", on_open)
- register_event_callback("new", on_new)
- register_event_callback("taskChanged", on_task_changed)
-
- self._has_been_setup = True
-
- # Set folder settings for the empty scene directly after launch of
- # Houdini so it initializes into the correct scene FPS,
- # Frame Range, etc.
- # TODO: make sure this doesn't trigger when
- # opening with last workfile.
- _set_context_settings()
-
- if not IS_HEADLESS:
- import hdefereval # noqa, hdefereval is only available in ui mode
- # Defer generation of shelves due to issue on Windows where shelf
- # initialization during start up delays Houdini UI by minutes
- # making it extremely slow to launch.
- hdefereval.executeDeferred(shelves.generate_shelves)
- hdefereval.executeDeferred(creator_node_shelves.install)
- if env_value_to_bool("AYON_WORKFILE_TOOL_ON_START"):
- hdefereval.executeDeferred(lambda: host_tools.show_workfiles(parent=hou.qt.mainWindow()))
-
- def workfile_has_unsaved_changes(self):
- return hou.hipFile.hasUnsavedChanges()
-
- def get_workfile_extensions(self):
- return [".hip", ".hiplc", ".hipnc"]
-
- def save_workfile(self, dst_path=None):
- # Force forwards slashes to avoid segfault
- if dst_path:
- dst_path = dst_path.replace("\\", "/")
- hou.hipFile.save(file_name=dst_path,
- save_to_recent_files=True)
- return dst_path
-
- def open_workfile(self, filepath):
- # Force forwards slashes to avoid segfault
- filepath = filepath.replace("\\", "/")
-
- hou.hipFile.load(filepath,
- suppress_save_prompt=True,
- ignore_load_warnings=False)
-
- return filepath
-
- def get_current_workfile(self):
- current_filepath = hou.hipFile.path()
- if (os.path.basename(current_filepath) == "untitled.hip" and
- not os.path.exists(current_filepath)):
- # By default a new scene in houdini is saved in the current
- # working directory as "untitled.hip" so we need to capture
- # that and consider it 'not saved' when it's in that state.
- return None
-
- return current_filepath
-
- def get_containers(self):
- return ls()
-
- def _register_callbacks(self):
- for event in self._op_events.copy().values():
- if event is None:
- continue
-
- try:
- hou.hipFile.removeEventCallback(event)
- except RuntimeError as e:
- log.info(e)
-
- self._op_events[on_file_event_callback] = hou.hipFile.addEventCallback(
- on_file_event_callback
- )
-
- @staticmethod
- def create_context_node():
- """Helper for creating context holding node.
-
- Returns:
- hou.Node: context node
-
- """
- obj_network = hou.node("/obj")
- op_ctx = obj_network.createNode("subnet",
- node_name="OpenPypeContext",
- run_init_scripts=False,
- load_contents=False)
-
- op_ctx.moveToGoodPosition()
- op_ctx.setBuiltExplicitly(False)
- op_ctx.setCreatorState("OpenPype")
- op_ctx.setComment("OpenPype node to hold context metadata")
- op_ctx.setColor(hou.Color((0.081, 0.798, 0.810)))
- op_ctx.setDisplayFlag(False)
- op_ctx.hide(True)
- return op_ctx
-
- def update_context_data(self, data, changes):
- op_ctx = hou.node(CONTEXT_CONTAINER)
- if not op_ctx:
- op_ctx = self.create_context_node()
-
- lib.imprint(op_ctx, data, update=True)
-
- def get_context_data(self):
- op_ctx = hou.node(CONTEXT_CONTAINER)
- if not op_ctx:
- op_ctx = self.create_context_node()
- return lib.read(op_ctx)
-
- def save_file(self, dst_path=None):
- # Force forwards slashes to avoid segfault
- dst_path = dst_path.replace("\\", "/")
-
- hou.hipFile.save(file_name=dst_path,
- save_to_recent_files=True)
-
-
-def on_file_event_callback(event):
- if event == hou.hipFileEventType.AfterLoad:
- emit_event("open")
- elif event == hou.hipFileEventType.AfterSave:
- emit_event("save")
- elif event == hou.hipFileEventType.BeforeSave:
- emit_event("before.save")
- elif event == hou.hipFileEventType.AfterClear:
- emit_event("new")
-
-
-def containerise(name,
- namespace,
- nodes,
- context,
- loader=None,
- suffix=""):
- """Bundle `nodes` into a subnet and imprint it with metadata
-
- Containerisation enables a tracking of version, author and origin
- for loaded assets.
-
- Arguments:
- name (str): Name of resulting assembly
- namespace (str): Namespace under which to host container
- nodes (list): Long names of nodes to containerise
- context (dict): Asset information
- loader (str, optional): Name of loader used to produce this container.
- suffix (str, optional): Suffix of container, defaults to `_CON`.
-
- Returns:
- container (str): Name of container assembly
-
- """
-
- # Get AVALON_CONTAINERS subnet
- subnet = get_or_create_avalon_container()
-
- # Create proper container name
- container_name = "{}_{}".format(name, suffix or "CON")
- container = hou.node("/obj/{}".format(name))
- container.setName(container_name, unique_name=True)
-
- data = {
- "schema": "openpype:container-2.0",
- "id": AVALON_CONTAINER_ID,
- "name": name,
- "namespace": namespace,
- "loader": str(loader),
- "representation": context["representation"]["id"],
- }
-
- lib.imprint(container, data)
-
- # "Parent" the container under the container network
- hou.moveNodesTo([container], subnet)
-
- subnet.node(container_name).moveToGoodPosition()
-
- return container
-
-
-def parse_container(container):
- """Return the container node's full container data.
-
- Args:
- container (hou.Node): A container node name.
-
- Returns:
- dict: The container schema data for this container node.
-
- """
- # Read only relevant parms
- # TODO: Clean up this hack replacing `lib.read(container)`
-
- data = {}
- for name in ["name", "namespace", "loader", "representation", "id"]:
- parm = container.parm(name)
- if not parm:
- return {}
-
- value = parm.eval()
-
- # test if value is json encoded dict
- if isinstance(value, str) and value.startswith(JSON_PREFIX):
- try:
- value = json.loads(value[len(JSON_PREFIX):])
- except json.JSONDecodeError:
- # not a json
- pass
- data[name] = value
-
- # Backwards compatibility pre-schemas for containers
- data["schema"] = data.get("schema", "openpype:container-1.0")
-
- # Append transient data
- data["objectName"] = container.path()
- data["node"] = container
-
- return data
-
-
-def ls():
- containers = []
- for identifier in (
- AYON_CONTAINER_ID,
- AVALON_CONTAINER_ID,
- "pyblish.mindbender.container"
- ):
- containers += lib.lsattr("id", identifier)
-
- for container in sorted(containers,
- # Hou 19+ Python 3 hou.ObjNode are not
- # sortable due to not supporting greater
- # than comparisons
- key=lambda node: node.path()):
- yield parse_container(container)
-
-
-def before_workfile_save(event):
- global _about_to_save
- _about_to_save = True
-
-
-def before_save():
- return lib.validate_fps()
-
-
-def on_save():
-
- log.info("Running callback on save..")
-
- # update houdini vars
- lib.update_houdini_vars_context_dialog()
-
- # We are now starting the actual save directly
- global _about_to_save
- _about_to_save = False
-
-
-def on_task_changed():
- global _about_to_save
- if not IS_HEADLESS and _about_to_save:
- # Let's prompt the user to update the context settings or not
- lib.prompt_reset_context()
-
-
-def _show_outdated_content_popup():
- # Get main window
- parent = lib.get_main_window()
- if parent is None:
- log.info("Skipping outdated content pop-up "
- "because Houdini window can't be found.")
- return
-
- from ayon_core.tools.utils import SimplePopup
-
- # Show outdated pop-up
- def _on_show_inventory():
- from ayon_core.tools.utils import host_tools
- host_tools.show_scene_inventory(parent=parent)
-
- dialog = SimplePopup(parent=parent)
- dialog.setWindowTitle("Houdini scene has outdated content")
- dialog.set_message("There are outdated containers in "
- "your Houdini scene.")
- dialog.on_clicked.connect(_on_show_inventory)
- dialog.show()
-
-
-def on_open():
-
- if not hou.isUIAvailable():
- log.debug("Batch mode detected, ignoring `on_open` callbacks..")
- return
-
- log.info("Running callback on open..")
-
- # update houdini vars
- lib.update_houdini_vars_context_dialog()
-
- # Validate FPS after update_task_from_path to
- # ensure it is using correct FPS for the folder
- lib.validate_fps()
-
- if any_outdated_containers():
- parent = lib.get_main_window()
- if parent is None:
- # When opening Houdini with last workfile on launch the UI hasn't
- # initialized yet completely when the `on_open` callback triggers.
- # We defer the dialog popup to wait for the UI to become available.
- # We assume it will open because `hou.isUIAvailable()` returns True
- import hdefereval
- hdefereval.executeDeferred(_show_outdated_content_popup)
- else:
- _show_outdated_content_popup()
-
- log.warning("Scene has outdated content.")
-
-
-def on_new():
- """Set project resolution and fps when create a new file"""
-
- if hou.hipFile.isLoadingHipFile():
- # This event also triggers when Houdini opens a file due to the
- # new event being registered to 'afterClear'. As such we can skip
- # 'new' logic if the user is opening a file anyway
- log.debug("Skipping on new callback due to scene being opened.")
- return
-
- log.info("Running callback on new..")
- _set_context_settings()
-
- # It seems that the current frame always gets reset to frame 1 on
- # new scene. So we enforce current frame to be at the start of the playbar
- # with execute deferred
- def _enforce_start_frame():
- start = hou.playbar.playbackRange()[0]
- hou.setFrame(start)
-
- if hou.isUIAvailable():
- import hdefereval
- hdefereval.executeDeferred(_enforce_start_frame)
- else:
- # Run without execute deferred when no UI is available because
- # without UI `hdefereval` is not available to import
- _enforce_start_frame()
-
-
-def get_or_create_avalon_container() -> "hou.OpNode":
- avalon_container = hou.node(AVALON_CONTAINERS)
- if avalon_container:
- return avalon_container
-
- parent_path, name = AVALON_CONTAINERS.rsplit("/", 1)
- parent = hou.node(parent_path)
- return parent.createNode(
- "subnet", node_name=name
- )
-
-
-def _set_context_settings():
- """Apply the project settings from the project definition
-
- Settings can be overwritten by a folder if the folder.attrib contains
- any information regarding those settings.
-
- Examples of settings:
- fps
- resolution
- renderer
-
- Returns:
- None
- """
-
- lib.reset_framerange()
- lib.update_houdini_vars_context()
diff --git a/server_addon/houdini/client/ayon_houdini/api/plugin.py b/server_addon/houdini/client/ayon_houdini/api/plugin.py
deleted file mode 100644
index 8a2344febb..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/plugin.py
+++ /dev/null
@@ -1,347 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Houdini specific Avalon/Pyblish plugin definitions."""
-import sys
-from abc import (
- ABCMeta
-)
-import six
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import (
- CreatorError,
- Creator,
- CreatedInstance,
- AYON_INSTANCE_ID,
- AVALON_INSTANCE_ID,
- load,
- publish
-)
-from ayon_core.lib import BoolDef
-
-from .lib import imprint, read, lsattr, add_self_publish_button
-
-
-SETTINGS_CATEGORY = "houdini"
-
-
-class HoudiniCreatorBase(object):
- @staticmethod
- def cache_instance_data(shared_data):
- """Cache instances for Creators to shared data.
-
- Create `houdini_cached_instances` key when needed in shared data and
- fill it with all collected instances from the scene under its
- respective creator identifiers.
-
- Create `houdini_cached_legacy_instance` key for any legacy instances
- detected in the scene as instances per family.
-
- Args:
- Dict[str, Any]: Shared data.
-
- """
- if shared_data.get("houdini_cached_instances") is None:
- cache = dict()
- cache_legacy = dict()
-
- nodes = []
- for id_type in [AYON_INSTANCE_ID, AVALON_INSTANCE_ID]:
- nodes.extend(lsattr("id", id_type))
- for node in nodes:
-
- creator_identifier_parm = node.parm("creator_identifier")
- if creator_identifier_parm:
- # creator instance
- creator_id = creator_identifier_parm.eval()
- cache.setdefault(creator_id, []).append(node)
-
- else:
- # legacy instance
- family_parm = node.parm("family")
- if not family_parm:
- # must be a broken instance
- continue
-
- family = family_parm.eval()
- cache_legacy.setdefault(family, []).append(node)
-
- shared_data["houdini_cached_instances"] = cache
- shared_data["houdini_cached_legacy_instance"] = cache_legacy
-
- return shared_data
-
- @staticmethod
- def create_instance_node(
- folder_path,
- node_name,
- parent,
- node_type="geometry",
- pre_create_data=None
- ):
- """Create node representing instance.
-
- Arguments:
- folder_path (str): Folder path.
- node_name (str): Name of the new node.
- parent (str): Name of the parent node.
- node_type (str, optional): Type of the node.
- pre_create_data (Optional[Dict]): Pre create data.
-
- Returns:
- hou.Node: Newly created instance node.
-
- """
- parent_node = hou.node(parent)
- instance_node = parent_node.createNode(
- node_type, node_name=node_name)
- instance_node.moveToGoodPosition()
- return instance_node
-
-
-@six.add_metaclass(ABCMeta)
-class HoudiniCreator(Creator, HoudiniCreatorBase):
- """Base class for most of the Houdini creator plugins."""
- selected_nodes = []
- settings_name = None
- add_publish_button = False
-
- settings_category = SETTINGS_CATEGORY
-
- def create(self, product_name, instance_data, pre_create_data):
- try:
- self.selected_nodes = []
-
- if pre_create_data.get("use_selection"):
- self.selected_nodes = hou.selectedNodes()
-
- # Get the node type and remove it from the data, not needed
- node_type = instance_data.pop("node_type", None)
- if node_type is None:
- node_type = "geometry"
-
- folder_path = instance_data["folderPath"]
-
- instance_node = self.create_instance_node(
- folder_path,
- product_name,
- "/out",
- node_type,
- pre_create_data
- )
-
- self.customize_node_look(instance_node)
-
- instance_data["instance_node"] = instance_node.path()
- instance_data["instance_id"] = instance_node.path()
- instance_data["families"] = self.get_publish_families()
- instance = CreatedInstance(
- self.product_type,
- product_name,
- instance_data,
- self)
- self._add_instance_to_context(instance)
- self.imprint(instance_node, instance.data_to_store())
-
- if self.add_publish_button:
- add_self_publish_button(instance_node)
-
- return instance
-
- except hou.Error as er:
- six.reraise(
- CreatorError,
- CreatorError("Creator error: {}".format(er)),
- sys.exc_info()[2])
-
- def lock_parameters(self, node, parameters):
- """Lock list of specified parameters on the node.
-
- Args:
- node (hou.Node): Houdini node to lock parameters on.
- parameters (list of str): List of parameter names.
-
- """
- for name in parameters:
- try:
- parm = node.parm(name)
- parm.lock(True)
- except AttributeError:
- self.log.debug("missing lock pattern {}".format(name))
-
- def collect_instances(self):
- # cache instances if missing
- self.cache_instance_data(self.collection_shared_data)
- for instance in self.collection_shared_data[
- "houdini_cached_instances"].get(self.identifier, []):
-
- node_data = read(instance)
-
- # Node paths are always the full node path since that is unique
- # Because it's the node's path it's not written into attributes
- # but explicitly collected
- node_path = instance.path()
- node_data["instance_id"] = node_path
- node_data["instance_node"] = node_path
- node_data["families"] = self.get_publish_families()
- if "AYON_productName" in node_data:
- node_data["productName"] = node_data.pop("AYON_productName")
-
- created_instance = CreatedInstance.from_existing(
- node_data, self
- )
- self._add_instance_to_context(created_instance)
-
- def update_instances(self, update_list):
- for created_inst, changes in update_list:
- instance_node = hou.node(created_inst.get("instance_node"))
- new_values = {
- key: changes[key].new_value
- for key in changes.changed_keys
- }
- # Update parm templates and values
- self.imprint(
- instance_node,
- new_values,
- update=True
- )
-
- def imprint(self, node, values, update=False):
- # Never store instance node and instance id since that data comes
- # from the node's path
- if "productName" in values:
- values["AYON_productName"] = values.pop("productName")
- values.pop("instance_node", None)
- values.pop("instance_id", None)
- values.pop("families", None)
- imprint(node, values, update=update)
-
- def remove_instances(self, instances):
- """Remove specified instance from the scene.
-
- This is only removing `id` parameter so instance is no longer
- instance, because it might contain valuable data for artist.
-
- """
- for instance in instances:
- instance_node = hou.node(instance.data.get("instance_node"))
- if instance_node:
- instance_node.destroy()
-
- self._remove_instance_from_context(instance)
-
- def get_pre_create_attr_defs(self):
- return [
- BoolDef("use_selection", label="Use selection")
- ]
-
- @staticmethod
- def customize_node_look(
- node, color=None,
- shape="chevron_down"):
- """Set custom look for instance nodes.
-
- Args:
- node (hou.Node): Node to set look.
- color (hou.Color, Optional): Color of the node.
- shape (str, Optional): Shape name of the node.
-
- Returns:
- None
-
- """
- if not color:
- color = hou.Color((0.616, 0.871, 0.769))
- node.setUserData('nodeshape', shape)
- node.setColor(color)
-
- def get_publish_families(self):
- """Return families for the instances of this creator.
-
- Allow a Creator to define multiple families so that a creator can
- e.g. specify `usd` and `usdrop`.
-
- There is no need to override this method if you only have the
- primary family defined by the `product_type` property as that will
- always be set.
-
- Returns:
- List[str]: families for instances of this creator
- """
- return []
-
- def get_network_categories(self):
- """Return in which network view type this creator should show.
-
- The node type categories returned here will be used to define where
- the creator will show up in the TAB search for nodes in Houdini's
- Network View.
-
- This can be overridden in inherited classes to define where that
- particular Creator should be visible in the TAB search.
-
- Returns:
- list: List of houdini node type categories
-
- """
- return [hou.ropNodeTypeCategory()]
-
- def apply_settings(self, project_settings):
- """Method called on initialization of plugin to apply settings."""
-
- # Apply General Settings
- houdini_general_settings = project_settings["houdini"]["general"]
- self.add_publish_button = houdini_general_settings.get(
- "add_self_publish_button", False)
-
- # Apply Creator Settings
- settings_name = self.settings_name
- if settings_name is None:
- settings_name = self.__class__.__name__
-
- settings = project_settings["houdini"]["create"]
- settings = settings.get(settings_name)
- if settings is None:
- self.log.debug(
- "No settings found for {}".format(self.__class__.__name__)
- )
- return
-
- for key, value in settings.items():
- setattr(self, key, value)
-
-
-class HoudiniLoader(load.LoaderPlugin):
- """Base class for Houdini load plugins."""
-
- hosts = ["houdini"]
- settings_category = SETTINGS_CATEGORY
-
-
-class HoudiniInstancePlugin(pyblish.api.InstancePlugin):
- """Base class for Houdini instance publish plugins."""
-
- hosts = ["houdini"]
- settings_category = SETTINGS_CATEGORY
-
-
-class HoudiniContextPlugin(pyblish.api.ContextPlugin):
- """Base class for Houdini context publish plugins."""
-
- hosts = ["houdini"]
- settings_category = SETTINGS_CATEGORY
-
-
-class HoudiniExtractorPlugin(publish.Extractor):
- """Base class for Houdini extract plugins.
-
- Note:
- The `HoudiniExtractorPlugin` is a subclass of `publish.Extractor`,
- which in turn is a subclass of `pyblish.api.InstancePlugin`.
- Should there be a requirement to create an extractor that operates
- as a context plugin, it would be beneficial to incorporate
- the functionalities present in `publish.Extractor`.
- """
-
- hosts = ["houdini"]
- settings_category = SETTINGS_CATEGORY
diff --git a/server_addon/houdini/client/ayon_houdini/api/shelves.py b/server_addon/houdini/client/ayon_houdini/api/shelves.py
deleted file mode 100644
index 2987568af1..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/shelves.py
+++ /dev/null
@@ -1,215 +0,0 @@
-import os
-import re
-import logging
-import platform
-
-from ayon_core.settings import get_project_settings
-from ayon_core.pipeline import get_current_project_name
-
-from ayon_core.lib import StringTemplate
-
-import hou
-
-from .lib import get_current_context_template_data_with_folder_attrs
-
-log = logging.getLogger("ayon_houdini.shelves")
-
-
-def generate_shelves():
- """This function generates complete shelves from shelf set to tools
- in Houdini from openpype project settings houdini shelf definition.
- """
- current_os = platform.system().lower()
-
- # load configuration of houdini shelves
- project_name = get_current_project_name()
- project_settings = get_project_settings(project_name)
- shelves_configs = project_settings["houdini"]["shelves"]
-
- if not shelves_configs:
- log.debug("No custom shelves found in project settings.")
- return
-
- # Get Template data
- template_data = get_current_context_template_data_with_folder_attrs()
-
- for config in shelves_configs:
- selected_option = config["options"]
- shelf_set_config = config[selected_option]
-
- shelf_set_filepath = shelf_set_config.get('shelf_set_source_path')
- if shelf_set_filepath:
- shelf_set_os_filepath = shelf_set_filepath[current_os]
- if shelf_set_os_filepath:
- shelf_set_os_filepath = get_path_using_template_data(
- shelf_set_os_filepath, template_data
- )
- if not os.path.isfile(shelf_set_os_filepath):
- log.error("Shelf path doesn't exist - "
- "{}".format(shelf_set_os_filepath))
- continue
-
- hou.shelves.loadFile(shelf_set_os_filepath)
- continue
-
- shelf_set_name = shelf_set_config.get('shelf_set_name')
- if not shelf_set_name:
- log.warning("No name found in shelf set definition.")
- continue
-
- shelves_definition = shelf_set_config.get('shelf_definition')
- if not shelves_definition:
- log.debug(
- "No shelf definition found for shelf set named '{}'".format(
- shelf_set_name
- )
- )
- continue
-
- shelf_set = get_or_create_shelf_set(shelf_set_name)
- for shelf_definition in shelves_definition:
- shelf_name = shelf_definition.get('shelf_name')
- if not shelf_name:
- log.warning("No name found in shelf definition.")
- continue
-
- shelf = get_or_create_shelf(shelf_name)
-
- if not shelf_definition.get('tools_list'):
- log.debug(
- "No tool definition found for shelf named {}".format(
- shelf_name
- )
- )
- continue
-
- mandatory_attributes = {'label', 'script'}
- for tool_definition in shelf_definition.get('tools_list'):
- # We verify that the name and script attributes of the tool
- # are set
- if not all(
- tool_definition[key] for key in mandatory_attributes
- ):
- log.warning(
- "You need to specify at least the name and the "
- "script path of the tool.")
- continue
-
- tool = get_or_create_tool(
- tool_definition, shelf, template_data
- )
-
- if not tool:
- continue
-
- # Add the tool to the shelf if not already in it
- if tool not in shelf.tools():
- shelf.setTools(list(shelf.tools()) + [tool])
-
- # Add the shelf in the shelf set if not already in it
- if shelf not in shelf_set.shelves():
- shelf_set.setShelves(shelf_set.shelves() + (shelf,))
-
-
-def get_or_create_shelf_set(shelf_set_label):
- """This function verifies if the shelf set label exists. If not,
- creates a new shelf set.
-
- Arguments:
- shelf_set_label (str): The label of the shelf set
-
- Returns:
- hou.ShelfSet: The shelf set existing or the new one
- """
- all_shelves_sets = hou.shelves.shelfSets().values()
-
- shelf_set = next((shelf for shelf in all_shelves_sets if
- shelf.label() == shelf_set_label), None)
- if shelf_set:
- return shelf_set
-
- shelf_set_name = shelf_set_label.replace(' ', '_').lower()
- new_shelf_set = hou.shelves.newShelfSet(
- name=shelf_set_name,
- label=shelf_set_label
- )
- return new_shelf_set
-
-
-def get_or_create_shelf(shelf_label):
- """This function verifies if the shelf label exists. If not, creates
- a new shelf.
-
- Arguments:
- shelf_label (str): The label of the shelf
-
- Returns:
- hou.Shelf: The shelf existing or the new one
- """
- all_shelves = hou.shelves.shelves().values()
-
- shelf = next((s for s in all_shelves if s.label() == shelf_label), None)
- if shelf:
- return shelf
-
- shelf_name = shelf_label.replace(' ', '_').lower()
- new_shelf = hou.shelves.newShelf(
- name=shelf_name,
- label=shelf_label
- )
- return new_shelf
-
-
-def get_or_create_tool(tool_definition, shelf, template_data):
- """This function verifies if the tool exists and updates it. If not, creates
- a new one.
-
- Arguments:
- tool_definition (dict): Dict with label, script, icon and help
- shelf (hou.Shelf): The parent shelf of the tool
-
- Returns:
- hou.Tool: The tool updated or the new one
- """
-
- tool_label = tool_definition.get("label")
- if not tool_label:
- log.warning("Skipped shelf without label")
- return
-
- script_path = tool_definition["script"]
- script_path = get_path_using_template_data(script_path, template_data)
- if not script_path or not os.path.exists(script_path):
- log.warning("This path doesn't exist - {}".format(script_path))
- return
-
- icon_path = tool_definition["icon"]
- if icon_path:
- icon_path = get_path_using_template_data(icon_path, template_data)
- tool_definition["icon"] = icon_path
-
- existing_tools = shelf.tools()
- existing_tool = next(
- (tool for tool in existing_tools if tool.label() == tool_label),
- None
- )
-
- with open(script_path) as stream:
- script = stream.read()
-
- tool_definition["script"] = script
-
- if existing_tool:
- tool_definition.pop("label", None)
- existing_tool.setData(**tool_definition)
- return existing_tool
-
- tool_name = re.sub(r"[^\w\d]+", "_", tool_label).lower()
- return hou.shelves.newTool(name=tool_name, **tool_definition)
-
-
-def get_path_using_template_data(path, template_data):
- path = StringTemplate.format_template(path, template_data)
- path = path.replace("\\", "/")
-
- return path
diff --git a/server_addon/houdini/client/ayon_houdini/api/usd.py b/server_addon/houdini/client/ayon_houdini/api/usd.py
deleted file mode 100644
index a416d581c3..0000000000
--- a/server_addon/houdini/client/ayon_houdini/api/usd.py
+++ /dev/null
@@ -1,379 +0,0 @@
-"""Houdini-specific USD Library functions."""
-
-import contextlib
-import logging
-import json
-import itertools
-from typing import List
-
-import hou
-from pxr import Usd, Sdf, Tf, Vt, UsdRender
-
-log = logging.getLogger(__name__)
-
-
-def add_usd_output_processor(ropnode, processor):
- """Add USD Output Processor to USD Rop node.
-
- Args:
- ropnode (hou.RopNode): The USD Rop node.
- processor (str): The output processor name. This is the basename of
- the python file that contains the Houdini USD Output Processor.
-
- """
-
- import loputils
-
- loputils.handleOutputProcessorAdd(
- {
- "node": ropnode,
- "parm": ropnode.parm("outputprocessors"),
- "script_value": processor,
- }
- )
-
-
-def remove_usd_output_processor(ropnode, processor):
- """Removes USD Output Processor from USD Rop node.
-
- Args:
- ropnode (hou.RopNode): The USD Rop node.
- processor (str): The output processor name. This is the basename of
- the python file that contains the Houdini USD Output Processor.
-
- """
- import loputils
-
- parm = ropnode.parm(processor + "_remove")
- if not parm:
- raise RuntimeError(
- "Output Processor %s does not "
- "exist on %s" % (processor, ropnode.name())
- )
-
- loputils.handleOutputProcessorRemove({"node": ropnode, "parm": parm})
-
-
-@contextlib.contextmanager
-def outputprocessors(ropnode, processors=tuple(), disable_all_others=True):
- """Context manager to temporarily add Output Processors to USD ROP node.
-
- Args:
- ropnode (hou.RopNode): The USD Rop node.
- processors (tuple or list): The processors to add.
- disable_all_others (bool, Optional): Whether to disable all
- output processors currently on the ROP node that are not in the
- `processors` list passed to this function.
-
- """
- # TODO: Add support for forcing the correct Order of the processors
-
- original = []
- prefix = "enableoutputprocessor_"
- processor_parms = ropnode.globParms(prefix + "*")
- for parm in processor_parms:
- original.append((parm, parm.eval()))
-
- if disable_all_others:
- for parm in processor_parms:
- parm.set(False)
-
- added = []
- for processor in processors:
-
- parm = ropnode.parm(prefix + processor)
- if parm:
- # If processor already exists, just enable it
- parm.set(True)
-
- else:
- # Else add the new processor
- add_usd_output_processor(ropnode, processor)
- added.append(processor)
-
- try:
- yield
- finally:
-
- # Remove newly added processors
- for processor in added:
- remove_usd_output_processor(ropnode, processor)
-
- # Revert to original values
- for parm, value in original:
- if parm:
- parm.set(value)
-
-
-def get_usd_rop_loppath(node):
-
- # Get sop path
- node_type = node.type().name()
- if node_type == "usd":
- return node.parm("loppath").evalAsNode()
-
- elif node_type in {"usd_rop", "usdrender_rop"}:
- # Inside Solaris e.g. /stage (not in ROP context)
- # When incoming connection is present it takes it directly
- inputs = node.inputs()
- if inputs:
- return inputs[0]
- else:
- return node.parm("loppath").evalAsNode()
-
-
-def get_layer_save_path(layer, expand_string=True):
- """Get custom HoudiniLayerInfo->HoudiniSavePath from SdfLayer.
-
- Args:
- layer (pxr.Sdf.Layer): The Layer to retrieve the save pah data from.
- expand_string (bool): Whether to expand any houdini vars in the save
- path before computing the absolute path.
-
- Returns:
- str or None: Path to save to when data exists.
-
- """
- hou_layer_info = layer.rootPrims.get("HoudiniLayerInfo")
- if not hou_layer_info:
- return
-
- save_path = hou_layer_info.customData.get("HoudiniSavePath", None)
- if save_path:
- # Unfortunately this doesn't actually resolve the full absolute path
- if expand_string:
- save_path = hou.text.expandString(save_path)
- return layer.ComputeAbsolutePath(save_path)
-
-
-def get_referenced_layers(layer):
- """Return SdfLayers for all external references of the current layer
-
- Args:
- layer (pxr.Sdf.Layer): The Layer to retrieve the save pah data from.
-
- Returns:
- list: List of pxr.Sdf.Layer that are external references to this layer
-
- """
-
- layers = []
- for layer_id in layer.GetExternalReferences():
- layer = Sdf.Layer.Find(layer_id)
- if not layer:
- # A file may not be in memory and is
- # referenced from disk. As such it cannot
- # be found. We will ignore those layers.
- continue
-
- layers.append(layer)
-
- return layers
-
-
-def iter_layer_recursive(layer):
- """Recursively iterate all 'external' referenced layers"""
-
- layers = get_referenced_layers(layer)
- traversed = set(layers) # Avoid recursion to itself (if even possible)
- traverse = list(layers)
- for layer in traverse:
-
- # Include children layers (recursion)
- children_layers = get_referenced_layers(layer)
- children_layers = [x for x in children_layers if x not in traversed]
- traverse.extend(children_layers)
- traversed.update(children_layers)
-
- yield layer
-
-
-def get_configured_save_layers(usd_rop, strip_above_layer_break=True):
- """Retrieve the layer save paths from a USD ROP.
-
- Arguments:
- usdrop (hou.RopNode): USD Rop Node
- strip_above_layer_break (Optional[bool]): Whether to exclude any
- layers that are above layer breaks. This defaults to True.
-
- Returns:
- List[Sdf.Layer]: The layers with configured save paths.
-
- """
-
- lop_node = get_usd_rop_loppath(usd_rop)
- stage = lop_node.stage(apply_viewport_overrides=False)
- if not stage:
- raise RuntimeError(
- "No valid USD stage for ROP node: " "%s" % usd_rop.path()
- )
-
- root_layer = stage.GetRootLayer()
-
- if strip_above_layer_break:
- layers_above_layer_break = set(lop_node.layersAboveLayerBreak())
- else:
- layers_above_layer_break = set()
-
- save_layers = []
- for layer in iter_layer_recursive(root_layer):
- if (
- strip_above_layer_break and
- layer.identifier in layers_above_layer_break
- ):
- continue
-
- save_path = get_layer_save_path(layer)
- if save_path is not None:
- save_layers.append(layer)
-
- return save_layers
-
-
-def setup_lop_python_layer(layer, node, savepath=None,
- apply_file_format_args=True):
- """Set up Sdf.Layer with HoudiniLayerInfo prim for metadata.
-
- This is the same as `loputils.createPythonLayer` but can be run on top
- of `pxr.Sdf.Layer` instances that are already created in a Python LOP node.
- That's useful if your layer creation itself is built to be DCC agnostic,
- then we just need to run this after per layer to make it explicitly
- stored for houdini.
-
- By default, Houdini doesn't apply the FileFormatArguments supplied to
- the created layer; however it does support USD's file save suffix
- of `:SDF_FORMAT_ARGS:` to supply them. With `apply_file_format_args` any
- file format args set on the layer's creation will be added to the
- save path through that.
-
- Note: The `node.addHeldLayer` call will only work from a LOP python node
- whenever `node.editableStage()` or `node.editableLayer()` was called.
-
- Arguments:
- layer (Sdf.Layer): An existing layer (most likely just created
- in the current runtime)
- node (hou.LopNode): The Python LOP node to attach the layer to so
- it does not get garbage collected/mangled after the downstream.
- savepath (Optional[str]): When provided the HoudiniSaveControl
- will be set to Explicit with HoudiniSavePath to this path.
- apply_file_format_args (Optional[bool]): When enabled any
- FileFormatArgs defined for the layer on creation will be set
- in the HoudiniSavePath so Houdini USD ROP will use them top.
-
- Returns:
- Sdf.PrimSpec: The Created HoudiniLayerInfo prim spec.
-
- """
- # Add a Houdini Layer Info prim where we can put the save path.
- p = Sdf.CreatePrimInLayer(layer, '/HoudiniLayerInfo')
- p.specifier = Sdf.SpecifierDef
- p.typeName = 'HoudiniLayerInfo'
- if savepath:
- if apply_file_format_args:
- args = layer.GetFileFormatArguments()
- savepath = Sdf.Layer.CreateIdentifier(savepath, args)
-
- p.customData['HoudiniSavePath'] = savepath
- p.customData['HoudiniSaveControl'] = 'Explicit'
- # Let everyone know what node created this layer.
- p.customData['HoudiniCreatorNode'] = node.sessionId()
- p.customData['HoudiniEditorNodes'] = Vt.IntArray([node.sessionId()])
- node.addHeldLayer(layer.identifier)
-
- return p
-
-
-@contextlib.contextmanager
-def remap_paths(rop_node, mapping):
- """Enable the AyonRemapPaths output processor with provided `mapping`"""
- from ayon_houdini.api.lib import parm_values
-
- if not mapping:
- # Do nothing
- yield
- return
-
- # Houdini string parms need to escape backslashes due to the support
- # of expressions - as such we do so on the json data
- value = json.dumps(mapping).replace("\\", "\\\\")
- with outputprocessors(
- rop_node,
- processors=["ayon_remap_paths"],
- disable_all_others=True,
- ):
- with parm_values([
- (rop_node.parm("ayon_remap_paths_remap_json"), value)
- ]):
- yield
-
-
-def get_usd_render_rop_rendersettings(rop_node, stage=None, logger=None):
- """Return the chosen UsdRender.Settings from the stage (if any).
-
- Args:
- rop_node (hou.Node): The Houdini USD Render ROP node.
- stage (pxr.Usd.Stage): The USD stage to find the render settings
- in. This is usually the stage from the LOP path the USD Render
- ROP node refers to.
- logger (logging.Logger): Logger to log warnings to if no render
- settings were find in stage.
-
- Returns:
- Optional[UsdRender.Settings]: Render Settings.
-
- """
- if logger is None:
- logger = log
-
- if stage is None:
- lop_node = get_usd_rop_loppath(rop_node)
- stage = lop_node.stage()
-
- path = rop_node.evalParm("rendersettings")
- if not path:
- # Default behavior
- path = "/Render/rendersettings"
-
- prim = stage.GetPrimAtPath(path)
- if not prim:
- logger.warning("No render settings primitive found at: %s", path)
- return
-
- render_settings = UsdRender.Settings(prim)
- if not render_settings:
- logger.warning("Prim at %s is not a valid RenderSettings prim.", path)
- return
-
- return render_settings
-
-
-def get_schema_type_names(type_name: str) -> List[str]:
- """Return schema type name for type name and its derived types
-
- This can be useful for checking whether a `Sdf.PrimSpec`'s type name is of
- a given type or any of its derived types.
-
- Args:
- type_name (str): The type name, like e.g. 'UsdGeomMesh'
-
- Returns:
- List[str]: List of schema type names and their derived types.
-
- """
- schema_registry = Usd.SchemaRegistry
- type_ = Tf.Type.FindByName(type_name)
-
- if type_ == Tf.Type.Unknown:
- type_ = schema_registry.GetTypeFromSchemaTypeName(type_name)
- if type_ == Tf.Type.Unknown:
- # Type not found
- return []
-
- results = []
- derived = type_.GetAllDerivedTypes()
- for derived_type in itertools.chain([type_], derived):
- schema_type_name = schema_registry.GetSchemaTypeName(derived_type)
- if schema_type_name:
- results.append(schema_type_name)
-
- return results
diff --git a/server_addon/houdini/client/ayon_houdini/hooks/set_default_display_and_view.py b/server_addon/houdini/client/ayon_houdini/hooks/set_default_display_and_view.py
deleted file mode 100644
index 7d41979600..0000000000
--- a/server_addon/houdini/client/ayon_houdini/hooks/set_default_display_and_view.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from ayon_applications import PreLaunchHook, LaunchTypes
-
-
-class SetDefaultDisplayView(PreLaunchHook):
- """Set default view and default display for houdini via OpenColorIO.
-
- Houdini's defaultDisplay and defaultView are set by
- setting 'OCIO_ACTIVE_DISPLAYS' and 'OCIO_ACTIVE_VIEWS'
- environment variables respectively.
-
- More info: https://www.sidefx.com/docs/houdini/io/ocio.html#set-up
- """
-
- app_groups = {"houdini"}
- launch_types = {LaunchTypes.local}
-
- def execute(self):
-
- OCIO = self.launch_context.env.get("OCIO")
-
- # This is a cheap way to skip this hook if either global color
- # management or houdini color management was disabled because the
- # OCIO var would be set by the global OCIOEnvHook
- if not OCIO:
- return
-
- # workfile settings added in '0.2.13'
- houdini_color_settings = \
- self.data["project_settings"]["houdini"]["imageio"].get("workfile")
-
- if not houdini_color_settings:
- self.log.info("Hook 'SetDefaultDisplayView' requires Houdini "
- "addon version >= '0.2.13'")
- return
-
- if not houdini_color_settings["enabled"]:
- self.log.info(
- "Houdini workfile color management is disabled."
- )
- return
-
- # 'OCIO_ACTIVE_DISPLAYS', 'OCIO_ACTIVE_VIEWS' are checked
- # as Admins can add them in Ayon env vars or Ayon tools.
-
- default_display = houdini_color_settings["default_display"]
- if default_display:
- # get 'OCIO_ACTIVE_DISPLAYS' value if exists.
- self._set_context_env("OCIO_ACTIVE_DISPLAYS", default_display)
-
- default_view = houdini_color_settings["default_view"]
- if default_view:
- # get 'OCIO_ACTIVE_VIEWS' value if exists.
- self._set_context_env("OCIO_ACTIVE_VIEWS", default_view)
-
- def _set_context_env(self, env_var, default_value):
- env_value = self.launch_context.env.get(env_var, "")
- new_value = ":".join(
- key for key in [default_value, env_value] if key
- )
- self.log.info(
- "Setting {} environment to: {}"
- .format(env_var, new_value)
- )
- self.launch_context.env[env_var] = new_value
diff --git a/server_addon/houdini/client/ayon_houdini/hooks/set_paths.py b/server_addon/houdini/client/ayon_houdini/hooks/set_paths.py
deleted file mode 100644
index 4b89ebe944..0000000000
--- a/server_addon/houdini/client/ayon_houdini/hooks/set_paths.py
+++ /dev/null
@@ -1,18 +0,0 @@
-from ayon_applications import PreLaunchHook, LaunchTypes
-
-
-class SetPath(PreLaunchHook):
- """Set current dir to workdir.
-
- Hook `GlobalHostDataHook` must be executed before this hook.
- """
- app_groups = {"houdini"}
- launch_types = {LaunchTypes.local}
-
- def execute(self):
- workdir = self.launch_context.env.get("AYON_WORKDIR", "")
- if not workdir:
- self.log.warning("BUG: Workdir is not filled.")
- return
-
- self.launch_context.kwargs["cwd"] = workdir
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/convert_legacy.py b/server_addon/houdini/client/ayon_houdini/plugins/create/convert_legacy.py
deleted file mode 100644
index 4c8c8062ce..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/convert_legacy.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Converter for legacy Houdini products."""
-from ayon_core.pipeline.create.creator_plugins import ProductConvertorPlugin
-from ayon_houdini.api.lib import imprint
-
-
-class HoudiniLegacyConvertor(ProductConvertorPlugin):
- """Find and convert any legacy products in the scene.
-
- This Converter will find all legacy products in the scene and will
- transform them to the current system. Since the old products doesn't
- retain any information about their original creators, the only mapping
- we can do is based on their families.
-
- Its limitation is that you can have multiple creators creating product
- name of the same product type and there is no way to handle it. This code
- should nevertheless cover all creators that came with AYON.
-
- """
- identifier = "io.openpype.creators.houdini.legacy"
- product_type_to_id = {
- "camera": "io.openpype.creators.houdini.camera",
- "ass": "io.openpype.creators.houdini.ass",
- "imagesequence": "io.openpype.creators.houdini.imagesequence",
- "hda": "io.openpype.creators.houdini.hda",
- "pointcache": "io.openpype.creators.houdini.pointcache",
- "redshiftproxy": "io.openpype.creators.houdini.redshiftproxy",
- "redshift_rop": "io.openpype.creators.houdini.redshift_rop",
- "usd": "io.openpype.creators.houdini.usd",
- "usdrender": "io.openpype.creators.houdini.usdrender",
- "vdbcache": "io.openpype.creators.houdini.vdbcache"
- }
-
- def __init__(self, *args, **kwargs):
- super(HoudiniLegacyConvertor, self).__init__(*args, **kwargs)
- self.legacy_instances = {}
-
- def find_instances(self):
- """Find legacy products in the scene.
-
- Legacy products are the ones that doesn't have `creator_identifier`
- parameter on them.
-
- This is using cached entries done in
- :py:meth:`~HoudiniCreatorBase.cache_instance_data()`
-
- """
- self.legacy_instances = self.collection_shared_data.get(
- "houdini_cached_legacy_instance")
- if not self.legacy_instances:
- return
- self.add_convertor_item("Found {} incompatible product{}.".format(
- len(self.legacy_instances),
- "s" if len(self.legacy_instances) > 1 else ""
- ))
-
- def convert(self):
- """Convert all legacy products to current.
-
- It is enough to add `creator_identifier` and `instance_node`.
-
- """
- if not self.legacy_instances:
- return
-
- for product_type, legacy_instances in self.legacy_instances.items():
- if product_type in self.product_type_to_id:
- for instance in legacy_instances:
- creator_id = self.product_type_to_id[product_type]
- data = {
- "creator_identifier": creator_id,
- "instance_node": instance.path()
- }
- if product_type == "pointcache":
- data["families"] = ["abc"]
- self.log.info("Converting {} to {}".format(
- instance.path(), creator_id))
- imprint(instance, data)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_alembic_camera.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_alembic_camera.py
deleted file mode 100644
index 4a92e24671..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_alembic_camera.py
+++ /dev/null
@@ -1,57 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating alembic camera products."""
-from ayon_houdini.api import plugin
-from ayon_core.pipeline import CreatorError
-
-import hou
-
-
-class CreateAlembicCamera(plugin.HoudiniCreator):
- """Single baked camera from Alembic ROP."""
-
- identifier = "io.openpype.creators.houdini.camera"
- label = "Camera (Abc)"
- product_type = "camera"
- icon = "camera"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "alembic"})
-
- instance = super(CreateAlembicCamera, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
- parms = {
- "filename": hou.text.expandString(
- "$HIP/pyblish/{}.abc".format(product_name)),
- "use_sop_path": False,
- }
-
- if self.selected_nodes:
- if len(self.selected_nodes) > 1:
- raise CreatorError("More than one item selected.")
- path = self.selected_nodes[0].path()
- # Split the node path into the first root and the remainder
- # So we can set the root and objects parameters correctly
- _, root, remainder = path.split("/", 2)
- parms.update({"root": "/" + root, "objects": remainder})
-
- instance_node.setParms(parms)
-
- # Lock the Use Sop Path setting so the
- # user doesn't accidentally enable it.
- to_lock = ["use_sop_path"]
- self.lock_parameters(instance_node, to_lock)
-
- instance_node.parm("trange").set(1)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.objNodeTypeCategory()
- ]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_arnold_ass.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_arnold_ass.py
deleted file mode 100644
index 4f5fb5833e..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_arnold_ass.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating Arnold ASS files."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef
-
-
-class CreateArnoldAss(plugin.HoudiniCreator):
- """Arnold .ass Archive"""
-
- identifier = "io.openpype.creators.houdini.ass"
- label = "Arnold ASS"
- product_type = "ass"
- icon = "magic"
-
- # Default extension: `.ass` or `.ass.gz`
- # however calling HoudiniCreator.create()
- # will override it by the value in the project settings
- ext = ".ass"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "arnold"})
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- creator_attributes["farm"] = pre_create_data["farm"]
-
- instance = super(CreateArnoldAss, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- # Hide Properties Tab on Arnold ROP since that's used
- # for rendering instead of .ass Archive Export
- parm_template_group = instance_node.parmTemplateGroup()
- parm_template_group.hideFolder("Properties", True)
- instance_node.setParmTemplateGroup(parm_template_group)
-
- filepath = "{}{}".format(
- hou.text.expandString("$HIP/pyblish/"),
- "{}.$F4{}".format(product_name, self.ext)
- )
- parms = {
- # Render frame range
- "trange": 1,
- # Arnold ROP settings
- "ar_ass_file": filepath,
- "ar_ass_export_enable": 1
- }
-
- instance_node.setParms(parms)
-
- # Lock any parameters in this list
- to_lock = ["ar_ass_export_enable", "productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_instance_attr_defs(self):
- return [
- BoolDef("farm",
- label="Submitting to Farm",
- default=False)
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super().get_pre_create_attr_defs()
- # Use same attributes as for instance attributes
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_arnold_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_arnold_rop.py
deleted file mode 100644
index 43875ccbd6..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_arnold_rop.py
+++ /dev/null
@@ -1,110 +0,0 @@
-from ayon_houdini.api import plugin
-from ayon_core.lib import EnumDef, BoolDef
-
-
-class CreateArnoldRop(plugin.HoudiniCreator):
- """Arnold ROP"""
-
- identifier = "io.openpype.creators.houdini.arnold_rop"
- label = "Arnold ROP"
- product_type = "arnold_rop"
- icon = "magic"
-
- # Default extension
- ext = "exr"
-
- # Default render target
- render_target = "farm_split"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou
- # Transfer settings from pre create to instance
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- for key in ["render_target", "review"]:
- if key in pre_create_data:
- creator_attributes[key] = pre_create_data[key]
-
- # Remove the active, we are checking the bypass flag of the nodes
- instance_data.pop("active", None)
- instance_data.update({"node_type": "arnold"})
-
- # Add chunk size attribute
- instance_data["chunkSize"] = 1
-
- instance = super(CreateArnoldRop, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- ext = pre_create_data.get("image_format")
-
- filepath = "{renders_dir}{product_name}/{product_name}.$F4.{ext}".format(
- renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
- product_name=product_name,
- ext=ext,
- )
- parms = {
- # Render frame range
- "trange": 1,
-
- # Arnold ROP settings
- "ar_picture": filepath,
- "ar_exr_half_precision": 1 # half precision
- }
-
- if pre_create_data.get("render_target") == "farm_split":
- ass_filepath = \
- "{export_dir}{product_name}/{product_name}.$F4.ass".format(
- export_dir=hou.text.expandString("$HIP/pyblish/ass/"),
- product_name=product_name,
- )
- parms["ar_ass_export_enable"] = 1
- parms["ar_ass_file"] = ass_filepath
-
- instance_node.setParms(parms)
-
- # Lock any parameters in this list
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_instance_attr_defs(self):
- """get instance attribute definitions.
-
- Attributes defined in this method are exposed in
- publish tab in the publisher UI.
- """
-
- render_target_items = {
- "local": "Local machine rendering",
- "local_no_render": "Use existing frames (local)",
- "farm": "Farm Rendering",
- "farm_split": "Farm Rendering - Split export & render jobs",
- }
-
- return [
- BoolDef("review",
- label="Review",
- tooltip="Mark as reviewable",
- default=True),
- EnumDef("render_target",
- items=render_target_items,
- label="Render target",
- default=self.render_target),
- ]
-
- def get_pre_create_attr_defs(self):
- image_format_enum = [
- "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
- "rad", "rat", "rta", "sgi", "tga", "tif",
- ]
-
- attrs = [
- EnumDef("image_format",
- image_format_enum,
- default=self.ext,
- label="Image Format Options"),
- ]
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_bgeo.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_bgeo.py
deleted file mode 100644
index 93cf0e0998..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_bgeo.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating pointcache bgeo files."""
-from ayon_houdini.api import plugin
-from ayon_core.pipeline import CreatorError
-import hou
-from ayon_core.lib import EnumDef, BoolDef
-
-
-class CreateBGEO(plugin.HoudiniCreator):
- """BGEO pointcache creator."""
- identifier = "io.openpype.creators.houdini.bgeo"
- label = "PointCache (Bgeo)"
- product_type = "pointcache"
- icon = "gears"
-
- def create(self, product_name, instance_data, pre_create_data):
-
- instance_data.pop("active", None)
-
- instance_data.update({"node_type": "geometry"})
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- creator_attributes["farm"] = pre_create_data["farm"]
-
- instance = super(CreateBGEO, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- file_path = "{}{}".format(
- hou.text.expandString("$HIP/pyblish/"),
- "{}.$F4.{}".format(
- product_name,
- pre_create_data.get("bgeo_type") or "bgeo.sc")
- )
- parms = {
- "sopoutput": file_path
- }
-
- instance_node.parm("trange").set(1)
- if self.selected_nodes:
- # if selection is on SOP level, use it
- if isinstance(self.selected_nodes[0], hou.SopNode):
- parms["soppath"] = self.selected_nodes[0].path()
- else:
- # try to find output node with the lowest index
- outputs = [
- child for child in self.selected_nodes[0].children()
- if child.type().name() == "output"
- ]
- if not outputs:
- instance_node.setParms(parms)
- raise CreatorError((
- "Missing output node in SOP level for the selection. "
- "Please select correct SOP path in created instance."
- ))
- outputs.sort(key=lambda output: output.evalParm("outputidx"))
- parms["soppath"] = outputs[0].path()
-
- instance_node.setParms(parms)
-
- def get_instance_attr_defs(self):
- return [
- BoolDef("farm",
- label="Submitting to Farm",
- default=False)
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super().get_pre_create_attr_defs()
- bgeo_enum = [
- {
- "value": "bgeo",
- "label": "uncompressed bgeo (.bgeo)"
- },
- {
- "value": "bgeosc",
- "label": "BLOSC compressed bgeo (.bgeosc)"
- },
- {
- "value": "bgeo.sc",
- "label": "BLOSC compressed bgeo (.bgeo.sc)"
- },
- {
- "value": "bgeo.gz",
- "label": "GZ compressed bgeo (.bgeo.gz)"
- },
- {
- "value": "bgeo.lzma",
- "label": "LZMA compressed bgeo (.bgeo.lzma)"
- },
- {
- "value": "bgeo.bz2",
- "label": "BZip2 compressed bgeo (.bgeo.bz2)"
- }
- ]
-
- return attrs + [
- EnumDef("bgeo_type", bgeo_enum, label="BGEO Options"),
- ] + self.get_instance_attr_defs()
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.sopNodeTypeCategory()
- ]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_composite.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_composite.py
deleted file mode 100644
index 8c0ee8a099..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_composite.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating composite sequences."""
-from ayon_houdini.api import plugin
-from ayon_core.pipeline import CreatorError
-
-import hou
-
-
-class CreateCompositeSequence(plugin.HoudiniCreator):
- """Composite ROP to Image Sequence"""
-
- identifier = "io.openpype.creators.houdini.imagesequence"
- label = "Composite (Image Sequence)"
- product_type = "imagesequence"
- icon = "gears"
-
- ext = ".exr"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou # noqa
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "comp"})
-
- instance = super(CreateCompositeSequence, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
- filepath = "{}{}".format(
- hou.text.expandString("$HIP/pyblish/"),
- "{}.$F4{}".format(product_name, self.ext)
- )
- parms = {
- "trange": 1,
- "copoutput": filepath
- }
-
- if self.selected_nodes:
- if len(self.selected_nodes) > 1:
- raise CreatorError("More than one item selected.")
- path = self.selected_nodes[0].path()
- parms["coppath"] = path
-
- instance_node.setParms(parms)
-
- # Manually set f1 & f2 to $FSTART and $FEND respectively
- # to match other Houdini nodes default.
- instance_node.parm("f1").setExpression("$FSTART")
- instance_node.parm("f2").setExpression("$FEND")
-
- # Lock any parameters in this list
- to_lock = ["prim_to_detail_pattern"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.cop2NodeTypeCategory()
- ]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_hda.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_hda.py
deleted file mode 100644
index ed2fd980e7..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_hda.py
+++ /dev/null
@@ -1,323 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating publishable Houdini Digital Assets."""
-import hou
-
-import ayon_api
-from ayon_core.pipeline import (
- CreatorError,
- get_current_project_name
-)
-from ayon_core.lib import (
- get_ayon_username,
- BoolDef
-)
-
-from ayon_houdini.api import plugin
-
-
-# region assettools
-# logic based on Houdini 19.5.752 `assettools.py` because
-# this logic was removed in Houdini 20+
-def get_tool_submenus(hda_def):
- """Returns the tab submenu entries of this node.
-
- Note: A node could be placed in multiple entries at once.
-
- Arguments:
- hda_def: the HDA Definition by hou.node.type().definition()
-
- Returns:
- Optional[list[str]]: A list of submenus
- """
-
- import xml.etree.ElementTree as ET
- if hda_def.hasSection('Tools.shelf'):
- sections = hda_def.sections()
- ts_section = sections['Tools.shelf'].contents()
- try:
- root = ET.fromstring(ts_section)
- except ET.ParseError:
- return None
- tool = root[0]
- submenus = tool.findall('toolSubmenu')
- if submenus:
- tool_submenus = []
- for submenu in submenus:
- if submenu is not None:
- text = submenu.text
- if text:
- tool_submenus.append(submenu.text)
- if tool_submenus:
- return tool_submenus
- else:
- return None
- else:
- return None
- else:
- return None
-
-
-def set_tool_submenu(hda_def,
- new_submenu='Digital Assets'):
- """Sets the tab menu entry for a node.
-
- Arguments:
- hda_def: the HDA Definition by hou.node.type().definition()
- new_submenu (Optional[str]): This will be the new submenu, replacing
- old_submenu entry
- """
-
- context_dict = {
- 'Shop': 'SHOP',
- 'Cop2': 'COP2',
- 'Object': 'OBJ',
- 'Chop': 'CHOP',
- 'Sop': 'SOP',
- 'Vop': 'VOP',
- 'VopNet': 'VOPNET',
- 'Driver': 'ROP',
- 'TOP': 'TOP',
- 'Top': 'TOP',
- 'Lop': 'LOP',
- 'Dop': 'DOP'}
-
- utils_dict = {
- 'Shop': 'shoptoolutils',
- 'Cop2': 'cop2toolutils',
- 'Object': 'objecttoolutils',
- 'Chop': 'choptoolutils',
- 'Sop': 'soptoolutils',
- 'Vop': 'voptoolutils',
- 'VopNet': 'vopnettoolutils',
- 'Driver': 'drivertoolutils',
- 'TOP': 'toptoolutils',
- 'Top': 'toptoolutils',
- 'Lop': 'loptoolutils',
- 'Dop': 'doptoolutils'}
-
- if hda_def.hasSection('Tools.shelf'):
- old_submenu = get_tool_submenus(hda_def)[0]
- else:
- # Add default tools shelf section
- content = """
-
-
-
-
-SOP
-
-
-$HDA_TABLE_AND_NAME
-
-Digital Assets
-
-
-
- """
-
- nodetype_category_name = hda_def.nodeType().category().name()
- context = context_dict[nodetype_category_name]
- util = utils_dict[nodetype_category_name]
- content = content.replace(
- "SOP",
- f"{context}")
- content = content.replace('soptoolutils', util)
- hda_def.addSection('Tools.shelf', content)
- old_submenu = 'Digital Assets'
-
- # Replace submenu
- tools = hda_def.sections()["Tools.shelf"]
- content = tools.contents()
- content = content.replace(
- f"{old_submenu}",
- f"{new_submenu}"
- )
-
- hda_def.addSection('Tools.shelf', content)
-# endregion
-
-
-class CreateHDA(plugin.HoudiniCreator):
- """Publish Houdini Digital Asset file."""
-
- identifier = "io.openpype.creators.houdini.hda"
- label = "Houdini Digital Asset (Hda)"
- product_type = "hda"
- icon = "gears"
- maintain_selection = False
-
- def _check_existing(self, folder_path, product_name):
- # type: (str, str) -> bool
- """Check if existing product name versions already exists."""
- # Get all products of the current folder
- project_name = self.project_name
- folder_entity = ayon_api.get_folder_by_path(
- project_name, folder_path, fields={"id"}
- )
- product_entities = ayon_api.get_products(
- project_name, folder_ids={folder_entity["id"]}, fields={"name"}
- )
- existing_product_names_low = {
- product_entity["name"].lower()
- for product_entity in product_entities
- }
- return product_name.lower() in existing_product_names_low
-
- def create_instance_node(
- self,
- folder_path,
- node_name,
- parent,
- node_type="geometry",
- pre_create_data=None
- ):
- if pre_create_data is None:
- pre_create_data = {}
-
- if self.selected_nodes:
- # if we have `use selection` enabled, and we have some
- # selected nodes ...
- if self.selected_nodes[0].type().name() == "subnet":
- to_hda = self.selected_nodes[0]
- to_hda.setName("{}_subnet".format(node_name), unique_name=True)
- else:
- parent_node = self.selected_nodes[0].parent()
- subnet = parent_node.collapseIntoSubnet(
- self.selected_nodes,
- subnet_name="{}_subnet".format(node_name))
- subnet.moveToGoodPosition()
- to_hda = subnet
- else:
- # Use Obj as the default path
- parent_node = hou.node("/obj")
- # Find and return the NetworkEditor pane tab with the minimum index
- pane = hou.ui.paneTabOfType(hou.paneTabType.NetworkEditor)
- if isinstance(pane, hou.NetworkEditor):
- # Use the NetworkEditor pane path as the parent path.
- parent_node = pane.pwd()
-
- to_hda = parent_node.createNode(
- "subnet", node_name="{}_subnet".format(node_name))
- if not to_hda.type().definition():
- # if node type has not its definition, it is not user
- # created hda. We test if hda can be created from the node.
- if not to_hda.canCreateDigitalAsset():
- raise CreatorError(
- "cannot create hda from node {}".format(to_hda))
-
- # Pick a unique type name for HDA product per folder path per project.
- type_name = (
- "{project_name}{folder_path}_{node_name}".format(
- project_name=get_current_project_name(),
- folder_path=folder_path.replace("/","_"),
- node_name=node_name
- )
- )
-
- hda_node = to_hda.createDigitalAsset(
- name=type_name,
- description=node_name,
- hda_file_name="$HIP/{}.hda".format(node_name),
- ignore_external_references=True
- )
- hda_node.layoutChildren()
- elif self._check_existing(folder_path, node_name):
- raise CreatorError(
- ("product {} is already published with different HDA"
- "definition.").format(node_name))
- else:
- hda_node = to_hda
-
- # If user tries to create the same HDA instance more than
- # once, then all of them will have the same product name and
- # point to the same hda_file_name. But, their node names will
- # be incremented.
- hda_node.setName(node_name, unique_name=True)
- self.customize_node_look(hda_node)
-
- # Set Custom settings.
- hda_def = hda_node.type().definition()
-
- if pre_create_data.get("set_user"):
- hda_def.setUserInfo(get_ayon_username())
-
- if pre_create_data.get("use_project"):
- set_tool_submenu(hda_def, "AYON/{}".format(self.project_name))
-
- return hda_node
-
- def create(self, product_name, instance_data, pre_create_data):
- instance_data.pop("active", None)
-
- return super(CreateHDA, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- def get_network_categories(self):
- # Houdini allows creating sub-network nodes inside
- # these categories.
- # Therefore this plugin can work in these categories.
- return [
- hou.chopNodeTypeCategory(),
- hou.cop2NodeTypeCategory(),
- hou.dopNodeTypeCategory(),
- hou.ropNodeTypeCategory(),
- hou.lopNodeTypeCategory(),
- hou.objNodeTypeCategory(),
- hou.sopNodeTypeCategory(),
- hou.topNodeTypeCategory(),
- hou.vopNodeTypeCategory()
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super(CreateHDA, self).get_pre_create_attr_defs()
- return attrs + [
- BoolDef("set_user",
- tooltip="Set current user as the author of the HDA",
- default=False,
- label="Set Current User"),
- BoolDef("use_project",
- tooltip="Use project name as tab submenu path.\n"
- "The location in TAB Menu will be\n"
- "'AYON/project_name/your_HDA_name'",
- default=True,
- label="Use Project as menu entry"),
- ]
-
- def get_dynamic_data(
- self,
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- instance
- ):
- """
- Pass product name from product name templates as dynamic data.
- """
- dynamic_data = super(CreateHDA, self).get_dynamic_data(
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- instance
- )
-
- dynamic_data.update(
- {
- "asset": folder_entity["name"],
- "folder": {
- "label": folder_entity["label"],
- "name": folder_entity["name"]
- }
- }
- )
-
- return dynamic_data
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_karma_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_karma_rop.py
deleted file mode 100644
index 693e6295e2..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_karma_rop.py
+++ /dev/null
@@ -1,147 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin to create Karma ROP."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef, EnumDef, NumberDef
-
-
-class CreateKarmaROP(plugin.HoudiniCreator):
- """Karma ROP"""
- identifier = "io.openpype.creators.houdini.karma_rop"
- label = "Karma ROP"
- product_type = "karma_rop"
- icon = "magic"
-
- # Default render target
- render_target = "farm"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou # noqa
- # Transfer settings from pre create to instance
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
-
- for key in ["render_target", "review"]:
- if key in pre_create_data:
- creator_attributes[key] = pre_create_data[key]
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "karma"})
- # Add chunk size attribute
- instance_data["chunkSize"] = 10
-
- instance = super(CreateKarmaROP, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- ext = pre_create_data.get("image_format")
-
- filepath = "{renders_dir}{product_name}/{product_name}.$F4.{ext}".format(
- renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
- product_name=product_name,
- ext=ext,
- )
- checkpoint = "{cp_dir}{product_name}.$F4.checkpoint".format(
- cp_dir=hou.text.expandString("$HIP/pyblish/"),
- product_name=product_name
- )
-
- usd_directory = "{usd_dir}{product_name}_$RENDERID".format(
- usd_dir=hou.text.expandString("$HIP/pyblish/renders/usd_renders/"), # noqa
- product_name=product_name
- )
-
- parms = {
- # Render Frame Range
- "trange": 1,
- # Karma ROP Setting
- "picture": filepath,
- # Karma Checkpoint Setting
- "productName": checkpoint,
- # USD Output Directory
- "savetodirectory": usd_directory,
- }
-
- res_x = pre_create_data.get("res_x")
- res_y = pre_create_data.get("res_y")
-
- if self.selected_nodes:
- # If camera found in selection
- # we will use as render camera
- camera = None
- for node in self.selected_nodes:
- if node.type().name() == "cam":
- camera = node.path()
- has_camera = pre_create_data.get("cam_res")
- if has_camera:
- res_x = node.evalParm("resx")
- res_y = node.evalParm("resy")
-
- if not camera:
- self.log.warning("No render camera found in selection")
-
- parms.update({
- "camera": camera or "",
- "resolutionx": res_x,
- "resolutiony": res_y,
- })
-
- instance_node.setParms(parms)
-
- # Lock some Avalon attributes
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_instance_attr_defs(self):
- """get instance attribute definitions.
-
- Attributes defined in this method are exposed in
- publish tab in the publisher UI.
- """
-
- render_target_items = {
- "local": "Local machine rendering",
- "local_no_render": "Use existing frames (local)",
- "farm": "Farm Rendering",
- }
-
- return [
- BoolDef("review",
- label="Review",
- tooltip="Mark as reviewable",
- default=True),
- EnumDef("render_target",
- items=render_target_items,
- label="Render target",
- default=self.render_target)
- ]
-
-
- def get_pre_create_attr_defs(self):
- image_format_enum = [
- "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
- "rad", "rat", "rta", "sgi", "tga", "tif",
- ]
-
- attrs = super(CreateKarmaROP, self).get_pre_create_attr_defs()
-
- attrs += [
- EnumDef("image_format",
- image_format_enum,
- default="exr",
- label="Image Format Options"),
- NumberDef("res_x",
- label="width",
- default=1920,
- decimals=0),
- NumberDef("res_y",
- label="height",
- default=720,
- decimals=0),
- BoolDef("cam_res",
- label="Camera Resolution",
- default=False),
- ]
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_mantra_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_mantra_rop.py
deleted file mode 100644
index ce1c96f8b2..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_mantra_rop.py
+++ /dev/null
@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin to create Mantra ROP."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import EnumDef, BoolDef
-
-
-class CreateMantraROP(plugin.HoudiniCreator):
- """Mantra ROP"""
- identifier = "io.openpype.creators.houdini.mantra_rop"
- label = "Mantra ROP"
- product_type = "mantra_rop"
- icon = "magic"
-
- # Default render target
- render_target = "farm_split"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou # noqa
- # Transfer settings from pre create to instance
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- for key in ["render_target", "review"]:
- if key in pre_create_data:
- creator_attributes[key] = pre_create_data[key]
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "ifd"})
- # Add chunk size attribute
- instance_data["chunkSize"] = 10
-
- instance = super(CreateMantraROP, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- ext = pre_create_data.get("image_format")
-
- filepath = "{renders_dir}{product_name}/{product_name}.$F4.{ext}".format(
- renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
- product_name=product_name,
- ext=ext,
- )
-
- parms = {
- # Render Frame Range
- "trange": 1,
- # Mantra ROP Setting
- "vm_picture": filepath,
- }
-
- if pre_create_data.get("render_target") == "farm_split":
- ifd_filepath = \
- "{export_dir}{product_name}/{product_name}.$F4.ifd".format(
- export_dir=hou.text.expandString("$HIP/pyblish/ifd/"),
- product_name=product_name,
- )
- parms["soho_outputmode"] = 1
- parms["soho_diskfile"] = ifd_filepath
-
- if self.selected_nodes:
- # If camera found in selection
- # we will use as render camera
- camera = None
- for node in self.selected_nodes:
- if node.type().name() == "cam":
- camera = node.path()
-
- if not camera:
- self.log.warning("No render camera found in selection")
-
- parms.update({"camera": camera or ""})
-
- custom_res = pre_create_data.get("override_resolution")
- if custom_res:
- parms.update({"override_camerares": 1})
- instance_node.setParms(parms)
-
- # Lock some Avalon attributes
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_instance_attr_defs(self):
- """get instance attribute definitions.
-
- Attributes defined in this method are exposed in
- publish tab in the publisher UI.
- """
-
- render_target_items = {
- "local": "Local machine rendering",
- "local_no_render": "Use existing frames (local)",
- "farm": "Farm Rendering",
- "farm_split": "Farm Rendering - Split export & render jobs",
- }
-
- return [
- BoolDef("review",
- label="Review",
- tooltip="Mark as reviewable",
- default=True),
- EnumDef("render_target",
- items=render_target_items,
- label="Render target",
- default=self.render_target)
- ]
-
- def get_pre_create_attr_defs(self):
- image_format_enum = [
- "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
- "rad", "rat", "rta", "sgi", "tga", "tif",
- ]
-
- attrs = super(CreateMantraROP, self).get_pre_create_attr_defs()
-
- attrs += [
- EnumDef("image_format",
- image_format_enum,
- default="exr",
- label="Image Format Options"),
- BoolDef("override_resolution",
- label="Override Camera Resolution",
- tooltip="Override the current camera "
- "resolution, recommended for IPR.",
- default=False),
- ]
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_model.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_model.py
deleted file mode 100644
index ed6b2096c5..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_model.py
+++ /dev/null
@@ -1,141 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating Model product type.
-
-Note:
- Currently, This creator plugin is the same as 'create_pointcache.py'
- But renaming the product type to 'model'.
-
- It's purpose to support
- Maya (load/publish model from maya to/from houdini).
-
- It's considered to support multiple representations in the future.
-"""
-
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef
-
-import hou
-
-
-
-class CreateModel(plugin.HoudiniCreator):
- """Create Model"""
- identifier = "io.openpype.creators.houdini.model"
- label = "Model"
- product_type = "model"
- icon = "cube"
-
- def create(self, product_name, instance_data, pre_create_data):
- instance_data.pop("active", None)
- instance_data.update({"node_type": "alembic"})
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- creator_attributes["farm"] = pre_create_data["farm"]
-
- instance = super(CreateModel, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
- parms = {
- "use_sop_path": True,
- "build_from_path": True,
- "path_attrib": "path",
- "prim_to_detail_pattern": "cbId",
- "format": 2,
- "facesets": 0,
- "filename": hou.text.expandString(
- "$HIP/pyblish/{}.abc".format(product_name))
- }
-
- if self.selected_nodes:
- selected_node = self.selected_nodes[0]
-
- # Although Houdini allows ObjNode path on `sop_path` for the
- # the ROP node we prefer it set to the SopNode path explicitly
-
- # Allow sop level paths (e.g. /obj/geo1/box1)
- if isinstance(selected_node, hou.SopNode):
- parms["sop_path"] = selected_node.path()
- self.log.debug(
- "Valid SopNode selection, 'SOP Path' in ROP will be set to '%s'."
- % selected_node.path()
- )
-
- # Allow object level paths to Geometry nodes (e.g. /obj/geo1)
- # but do not allow other object level nodes types like cameras, etc.
- elif isinstance(selected_node, hou.ObjNode) and \
- selected_node.type().name() in ["geo"]:
-
- # get the output node with the minimum
- # 'outputidx' or the node with display flag
- sop_path = self.get_obj_output(selected_node)
-
- if sop_path:
- parms["sop_path"] = sop_path.path()
- self.log.debug(
- "Valid ObjNode selection, 'SOP Path' in ROP will be set to "
- "the child path '%s'."
- % sop_path.path()
- )
-
- if not parms.get("sop_path", None):
- self.log.debug(
- "Selection isn't valid. 'SOP Path' in ROP will be empty."
- )
- else:
- self.log.debug(
- "No Selection. 'SOP Path' in ROP will be empty."
- )
-
- instance_node.setParms(parms)
- instance_node.parm("trange").set(1)
-
- # Explicitly set f1 and f2 to frame start.
- # Which forces the rop node to export one frame.
- instance_node.parmTuple('f').deleteAllKeyframes()
- fstart = int(hou.hscriptExpression("$FSTART"))
- instance_node.parmTuple('f').set((fstart, fstart, 1))
-
- # Lock any parameters in this list
- to_lock = ["prim_to_detail_pattern"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.sopNodeTypeCategory()
- ]
-
- def get_obj_output(self, obj_node):
- """Find output node with the smallest 'outputidx'."""
-
- outputs = obj_node.subnetOutputs()
-
- # if obj_node is empty
- if not outputs:
- return
-
- # if obj_node has one output child whether its
- # sop output node or a node with the render flag
- elif len(outputs) == 1:
- return outputs[0]
-
- # if there are more than one, then it have multiple output nodes
- # return the one with the minimum 'outputidx'
- else:
- return min(outputs,
- key=lambda node: node.evalParm('outputidx'))
-
- def get_instance_attr_defs(self):
- return [
- BoolDef("farm",
- label="Submitting to Farm",
- default=False)
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super().get_pre_create_attr_defs()
- # Use same attributes as for instance attributes
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_pointcache.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_pointcache.py
deleted file mode 100644
index 6a63659053..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_pointcache.py
+++ /dev/null
@@ -1,124 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating pointcache alembics."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef
-
-import hou
-
-
-
-class CreatePointCache(plugin.HoudiniCreator):
- """Alembic ROP to pointcache"""
- identifier = "io.openpype.creators.houdini.pointcache"
- label = "PointCache (Abc)"
- product_type = "pointcache"
- icon = "gears"
-
- def create(self, product_name, instance_data, pre_create_data):
- instance_data.pop("active", None)
- instance_data.update({"node_type": "alembic"})
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- creator_attributes["farm"] = pre_create_data["farm"]
-
- instance = super(CreatePointCache, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
- parms = {
- "use_sop_path": True,
- "build_from_path": True,
- "path_attrib": "path",
- "prim_to_detail_pattern": "cbId",
- "format": 2,
- "facesets": 0,
- "filename": hou.text.expandString(
- "$HIP/pyblish/{}.abc".format(product_name))
- }
-
- if self.selected_nodes:
- selected_node = self.selected_nodes[0]
-
- # Although Houdini allows ObjNode path on `sop_path` for the
- # the ROP node we prefer it set to the SopNode path explicitly
-
- # Allow sop level paths (e.g. /obj/geo1/box1)
- if isinstance(selected_node, hou.SopNode):
- parms["sop_path"] = selected_node.path()
- self.log.debug(
- "Valid SopNode selection, 'SOP Path' in ROP will be set to '%s'."
- % selected_node.path()
- )
-
- # Allow object level paths to Geometry nodes (e.g. /obj/geo1)
- # but do not allow other object level nodes types like cameras, etc.
- elif isinstance(selected_node, hou.ObjNode) and \
- selected_node.type().name() in ["geo"]:
-
- # get the output node with the minimum
- # 'outputidx' or the node with display flag
- sop_path = self.get_obj_output(selected_node)
-
- if sop_path:
- parms["sop_path"] = sop_path.path()
- self.log.debug(
- "Valid ObjNode selection, 'SOP Path' in ROP will be set to "
- "the child path '%s'."
- % sop_path.path()
- )
-
- if not parms.get("sop_path", None):
- self.log.debug(
- "Selection isn't valid. 'SOP Path' in ROP will be empty."
- )
- else:
- self.log.debug(
- "No Selection. 'SOP Path' in ROP will be empty."
- )
-
- instance_node.setParms(parms)
- instance_node.parm("trange").set(1)
-
- # Lock any parameters in this list
- to_lock = ["prim_to_detail_pattern"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.sopNodeTypeCategory()
- ]
-
- def get_obj_output(self, obj_node):
- """Find output node with the smallest 'outputidx'."""
-
- outputs = obj_node.subnetOutputs()
-
- # if obj_node is empty
- if not outputs:
- return
-
- # if obj_node has one output child whether its
- # sop output node or a node with the render flag
- elif len(outputs) == 1:
- return outputs[0]
-
- # if there are more than one, then it have multiple output nodes
- # return the one with the minimum 'outputidx'
- else:
- return min(outputs,
- key=lambda node: node.evalParm('outputidx'))
-
- def get_instance_attr_defs(self):
- return [
- BoolDef("farm",
- label="Submitting to Farm",
- default=False)
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super().get_pre_create_attr_defs()
- # Use same attributes as for instance attributes
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_redshift_proxy.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_redshift_proxy.py
deleted file mode 100644
index 0e3eb03ddd..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_redshift_proxy.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating Redshift proxies."""
-from ayon_houdini.api import plugin
-import hou
-from ayon_core.lib import BoolDef
-
-
-class CreateRedshiftProxy(plugin.HoudiniCreator):
- """Redshift Proxy"""
- identifier = "io.openpype.creators.houdini.redshiftproxy"
- label = "Redshift Proxy"
- product_type = "redshiftproxy"
- icon = "magic"
-
- def create(self, product_name, instance_data, pre_create_data):
-
- # Remove the active, we are checking the bypass flag of the nodes
- instance_data.pop("active", None)
-
- # Redshift provides a `Redshift_Proxy_Output` node type which shows
- # a limited set of parameters by default and is set to extract a
- # Redshift Proxy. However when "imprinting" extra parameters needed
- # for OpenPype it starts showing all its parameters again. It's unclear
- # why this happens.
- # TODO: Somehow enforce so that it only shows the original limited
- # attributes of the Redshift_Proxy_Output node type
- instance_data.update({"node_type": "Redshift_Proxy_Output"})
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- creator_attributes["farm"] = pre_create_data["farm"]
-
- instance = super(CreateRedshiftProxy, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- parms = {
- "RS_archive_file": '$HIP/pyblish/{}.$F4.rs'.format(product_name),
- }
-
- if self.selected_nodes:
- parms["RS_archive_sopPath"] = self.selected_nodes[0].path()
-
- instance_node.setParms(parms)
-
- # Lock some Avalon attributes
- to_lock = ["productType", "id", "prim_to_detail_pattern"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.sopNodeTypeCategory()
- ]
-
- def get_instance_attr_defs(self):
- return [
- BoolDef("farm",
- label="Submitting to Farm",
- default=False)
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super().get_pre_create_attr_defs()
- # Use same attributes as for instance attributes
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_redshift_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_redshift_rop.py
deleted file mode 100644
index d63e584692..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_redshift_rop.py
+++ /dev/null
@@ -1,172 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin to create Redshift ROP."""
-import hou # noqa
-
-from ayon_core.pipeline import CreatorError
-from ayon_houdini.api import plugin
-from ayon_core.lib import EnumDef, BoolDef
-
-
-class CreateRedshiftROP(plugin.HoudiniCreator):
- """Redshift ROP"""
-
- identifier = "io.openpype.creators.houdini.redshift_rop"
- label = "Redshift ROP"
- product_type = "redshift_rop"
- icon = "magic"
- ext = "exr"
- multi_layered_mode = "No Multi-Layered EXR File"
-
- # Default render target
- render_target = "farm_split"
-
- def create(self, product_name, instance_data, pre_create_data):
- # Transfer settings from pre create to instance
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- for key in ["render_target", "review"]:
- if key in pre_create_data:
- creator_attributes[key] = pre_create_data[key]
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "Redshift_ROP"})
- # Add chunk size attribute
- instance_data["chunkSize"] = 10
-
- instance = super(CreateRedshiftROP, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- basename = instance_node.name()
-
- # Also create the linked Redshift IPR Rop
- try:
- ipr_rop = instance_node.parent().createNode(
- "Redshift_IPR", node_name=f"{basename}_IPR"
- )
- except hou.OperationFailed as e:
- raise CreatorError(
- (
- "Cannot create Redshift node. Is Redshift "
- "installed and enabled?"
- )
- ) from e
-
- # Move it to directly under the Redshift ROP
- ipr_rop.setPosition(instance_node.position() + hou.Vector2(0, -1))
-
- # Set the linked rop to the Redshift ROP
- ipr_rop.parm("linked_rop").set(instance_node.path())
- ext = pre_create_data.get("image_format")
- multi_layered_mode = pre_create_data.get("multi_layered_mode")
-
- ext_format_index = {"exr": 0, "tif": 1, "jpg": 2, "png": 3}
- multilayer_mode_index = {"No Multi-Layered EXR File": "1",
- "Full Multi-Layered EXR File": "2" }
-
- filepath = "{renders_dir}{product_name}/{product_name}.{fmt}".format(
- renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
- product_name=product_name,
- fmt="$AOV.$F4.{ext}".format(ext=ext)
- )
-
- if multilayer_mode_index[multi_layered_mode] == "1":
- multipart = False
-
- elif multilayer_mode_index[multi_layered_mode] == "2":
- multipart = True
-
- parms = {
- # Render frame range
- "trange": 1,
- # Redshift ROP settings
- "RS_outputFileNamePrefix": filepath,
- "RS_outputBeautyAOVSuffix": "beauty",
- "RS_outputFileFormat": ext_format_index[ext],
- }
- if ext == "exr":
- parms["RS_outputMultilayerMode"] = multilayer_mode_index[multi_layered_mode]
- parms["RS_aovMultipart"] = multipart
-
- if self.selected_nodes:
- # set up the render camera from the selected node
- camera = None
- for node in self.selected_nodes:
- if node.type().name() == "cam":
- camera = node.path()
- parms["RS_renderCamera"] = camera or ""
-
- export_dir = hou.text.expandString("$HIP/pyblish/rs/")
- rs_filepath = f"{export_dir}{product_name}/{product_name}.$F4.rs"
- parms["RS_archive_file"] = rs_filepath
-
- if pre_create_data.get("render_target") == "farm_split":
- parms["RS_archive_enable"] = 1
-
- instance_node.setParms(parms)
-
- # Lock some Avalon attributes
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def remove_instances(self, instances):
- for instance in instances:
- node = instance.data.get("instance_node")
-
- ipr_node = hou.node(f"{node}_IPR")
- if ipr_node:
- ipr_node.destroy()
-
- return super(CreateRedshiftROP, self).remove_instances(instances)
-
- def get_instance_attr_defs(self):
- """get instance attribute definitions.
-
- Attributes defined in this method are exposed in
- publish tab in the publisher UI.
- """
-
- render_target_items = {
- "local": "Local machine rendering",
- "local_no_render": "Use existing frames (local)",
- "farm": "Farm Rendering",
- "farm_split": "Farm Rendering - Split export & render jobs",
- }
-
- return [
- BoolDef("review",
- label="Review",
- tooltip="Mark as reviewable",
- default=True),
- EnumDef("render_target",
- items=render_target_items,
- label="Render target",
- default=self.render_target)
- ]
-
- def get_pre_create_attr_defs(self):
-
- image_format_enum = [
- "exr", "tif", "jpg", "png",
- ]
-
- multi_layered_mode = [
- "No Multi-Layered EXR File",
- "Full Multi-Layered EXR File"
- ]
-
- attrs = super(CreateRedshiftROP, self).get_pre_create_attr_defs()
- attrs += [
- EnumDef("image_format",
- image_format_enum,
- default=self.ext,
- label="Image Format Options"),
- EnumDef("multi_layered_mode",
- multi_layered_mode,
- default=self.multi_layered_mode,
- label="Multi-Layered EXR"),
- ]
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_review.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_review.py
deleted file mode 100644
index b27264f400..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_review.py
+++ /dev/null
@@ -1,153 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating openGL reviews."""
-from ayon_houdini.api import lib, plugin
-from ayon_core.lib import EnumDef, BoolDef, NumberDef
-
-import os
-import hou
-
-
-class CreateReview(plugin.HoudiniCreator):
- """Review with OpenGL ROP"""
-
- identifier = "io.openpype.creators.houdini.review"
- label = "Review"
- product_type = "review"
- icon = "video-camera"
- review_color_space = ""
-
- def apply_settings(self, project_settings):
- super(CreateReview, self).apply_settings(project_settings)
- # workfile settings added in '0.2.13'
- color_settings = project_settings["houdini"]["imageio"].get(
- "workfile", {}
- )
- if color_settings.get("enabled"):
- self.review_color_space = color_settings.get("review_color_space")
-
- def create(self, product_name, instance_data, pre_create_data):
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "opengl"})
- instance_data["imageFormat"] = pre_create_data.get("imageFormat")
- instance_data["keepImages"] = pre_create_data.get("keepImages")
-
- instance = super(CreateReview, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- frame_range = hou.playbar.frameRange()
-
- filepath = "{root}/{product_name}/{product_name}.$F4.{ext}".format(
- root=hou.text.expandString("$HIP/pyblish"),
- # keep dynamic link to product name
- product_name="`chs(\"AYON_productName\")`",
- ext=pre_create_data.get("image_format") or "png"
- )
-
- parms = {
- "picture": filepath,
-
- "trange": 1,
-
- # Unlike many other ROP nodes the opengl node does not default
- # to expression of $FSTART and $FEND so we preserve that behavior
- # but do set the range to the frame range of the playbar
- "f1": frame_range[0],
- "f2": frame_range[1],
- }
-
- override_resolution = pre_create_data.get("override_resolution")
- if override_resolution:
- parms.update({
- "tres": override_resolution,
- "res1": pre_create_data.get("resx"),
- "res2": pre_create_data.get("resy"),
- "aspect": pre_create_data.get("aspect"),
- })
-
- if self.selected_nodes:
- # The first camera found in selection we will use as camera
- # Other node types we set in force objects
- camera = None
- force_objects = []
- for node in self.selected_nodes:
- path = node.path()
- if node.type().name() == "cam":
- if camera:
- continue
- camera = path
- else:
- force_objects.append(path)
-
- if not camera:
- self.log.warning("No camera found in selection.")
-
- parms.update({
- "camera": camera or "",
- "scenepath": "/obj",
- "forceobjects": " ".join(force_objects),
- "vobjects": "" # clear candidate objects from '*' value
- })
-
- instance_node.setParms(parms)
-
- # Set OCIO Colorspace to the default colorspace
- # if there's OCIO
- if os.getenv("OCIO"):
- # Fall to the default value if cls.review_color_space is empty.
- if not self.review_color_space:
- # cls.review_color_space is an empty string
- # when the imageio/workfile setting is disabled or
- # when the Review colorspace setting is empty.
- from ayon_houdini.api.colorspace import get_default_display_view_colorspace # noqa
- self.review_color_space = get_default_display_view_colorspace()
-
- lib.set_review_color_space(instance_node,
- self.review_color_space,
- self.log)
-
- to_lock = ["id", "productType"]
-
- self.lock_parameters(instance_node, to_lock)
-
- def get_pre_create_attr_defs(self):
- attrs = super(CreateReview, self).get_pre_create_attr_defs()
-
- image_format_enum = [
- "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
- "rad", "rat", "rta", "sgi", "tga", "tif",
- ]
-
- return attrs + [
- BoolDef("keepImages",
- label="Keep Image Sequences",
- default=False),
- EnumDef("imageFormat",
- image_format_enum,
- default="png",
- label="Image Format Options"),
- BoolDef("override_resolution",
- label="Override resolution",
- tooltip="When disabled the resolution set on the camera "
- "is used instead.",
- default=True),
- NumberDef("resx",
- label="Resolution Width",
- default=1280,
- minimum=2,
- decimals=0),
- NumberDef("resy",
- label="Resolution Height",
- default=720,
- minimum=2,
- decimals=0),
- NumberDef("aspect",
- label="Aspect Ratio",
- default=1.0,
- minimum=0.0001,
- decimals=3)
- ]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_staticmesh.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_staticmesh.py
deleted file mode 100644
index 17b646040c..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_staticmesh.py
+++ /dev/null
@@ -1,155 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator for Unreal Static Meshes."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef, EnumDef
-
-import hou
-
-
-class CreateStaticMesh(plugin.HoudiniCreator):
- """Static Meshes as FBX. """
-
- identifier = "io.openpype.creators.houdini.staticmesh.fbx"
- label = "Static Mesh (FBX)"
- product_type = "staticMesh"
- icon = "fa5s.cubes"
-
- default_variants = ["Main"]
-
- def create(self, product_name, instance_data, pre_create_data):
-
- instance_data.update({"node_type": "filmboxfbx"})
-
- instance = super(CreateStaticMesh, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- # get the created rop node
- instance_node = hou.node(instance.get("instance_node"))
-
- # prepare parms
- output_path = hou.text.expandString(
- "$HIP/pyblish/{}.fbx".format(product_name)
- )
-
- parms = {
- "startnode": self.get_selection(),
- "sopoutput": output_path,
- # vertex cache format
- "vcformat": pre_create_data.get("vcformat"),
- "convertunits": pre_create_data.get("convertunits"),
- # set render range to use frame range start-end frame
- "trange": 1,
- "createsubnetroot": pre_create_data.get("createsubnetroot")
- }
-
- # set parms
- instance_node.setParms(parms)
-
- # Lock any parameters in this list
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.objNodeTypeCategory(),
- hou.sopNodeTypeCategory()
- ]
-
- def get_pre_create_attr_defs(self):
- """Add settings for users. """
-
- attrs = super(CreateStaticMesh, self).get_pre_create_attr_defs()
- createsubnetroot = BoolDef("createsubnetroot",
- tooltip="Create an extra root for the "
- "Export node when it's a "
- "subnetwork. This causes the "
- "exporting subnetwork node to be "
- "represented in the FBX file.",
- default=False,
- label="Create Root for Subnet")
- vcformat = EnumDef("vcformat",
- items={
- 0: "Maya Compatible (MC)",
- 1: "3DS MAX Compatible (PC2)"
- },
- default=0,
- label="Vertex Cache Format")
- convert_units = BoolDef("convertunits",
- tooltip="When on, the FBX is converted"
- "from the current Houdini "
- "system units to the native "
- "FBX unit of centimeters.",
- default=False,
- label="Convert Units")
-
- return attrs + [createsubnetroot, vcformat, convert_units]
-
- def get_dynamic_data(
- self,
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- instance
- ):
- """
- The default prodcut name templates for Unreal include {asset} and thus
- we should pass that along as dynamic data.
- """
- dynamic_data = super(CreateStaticMesh, self).get_dynamic_data(
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- instance
- )
- dynamic_data["asset"] = folder_entity["name"]
- return dynamic_data
-
- def get_selection(self):
- """Selection Logic.
-
- how self.selected_nodes should be processed to get
- the desirable node from selection.
-
- Returns:
- str : node path
- """
-
- selection = ""
-
- if self.selected_nodes:
- selected_node = self.selected_nodes[0]
-
- # Accept sop level nodes (e.g. /obj/geo1/box1)
- if isinstance(selected_node, hou.SopNode):
- selection = selected_node.path()
- self.log.debug(
- "Valid SopNode selection, 'Export' in filmboxfbx"
- " will be set to '%s'.", selected_node
- )
-
- # Accept object level nodes (e.g. /obj/geo1)
- elif isinstance(selected_node, hou.ObjNode):
- selection = selected_node.path()
- self.log.debug(
- "Valid ObjNode selection, 'Export' in filmboxfbx "
- "will be set to the child path '%s'.", selection
- )
-
- else:
- self.log.debug(
- "Selection isn't valid. 'Export' in "
- "filmboxfbx will be empty."
- )
- else:
- self.log.debug(
- "No Selection. 'Export' in filmboxfbx will be empty."
- )
-
- return selection
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_usd.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_usd.py
deleted file mode 100644
index b6c0aa8895..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_usd.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating USDs."""
-from ayon_houdini.api import plugin
-
-import hou
-
-
-class CreateUSD(plugin.HoudiniCreator):
- """Universal Scene Description"""
- identifier = "io.openpype.creators.houdini.usd"
- label = "USD"
- product_type = "usd"
- icon = "cubes"
- enabled = False
- description = "Create USD"
-
- def create(self, product_name, instance_data, pre_create_data):
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "usd"})
-
- instance = super(CreateUSD, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- parms = {
- "lopoutput": "$HIP/pyblish/{}.usd".format(product_name),
- "enableoutputprocessor_simplerelativepaths": False,
- }
-
- if self.selected_nodes:
- parms["loppath"] = self.selected_nodes[0].path()
-
- instance_node.setParms(parms)
-
- # Lock any parameters in this list
- to_lock = [
- "fileperframe",
- # Lock some Avalon attributes
- "productType",
- "id",
- ]
- self.lock_parameters(instance_node, to_lock)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.lopNodeTypeCategory()
- ]
-
- def get_publish_families(self):
- return ["usd", "usdrop"]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_usd_look.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_usd_look.py
deleted file mode 100644
index 58a7aa77be..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_usd_look.py
+++ /dev/null
@@ -1,73 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating USD looks with textures."""
-import inspect
-
-from ayon_houdini.api import plugin
-
-import hou
-
-
-class CreateUSDLook(plugin.HoudiniCreator):
- """Universal Scene Description Look"""
-
- identifier = "io.openpype.creators.houdini.usd.look"
- label = "Look"
- product_type = "look"
- icon = "paint-brush"
- enabled = True
- description = "Create USD Look"
-
- def create(self, product_name, instance_data, pre_create_data):
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "usd"})
-
- instance = super(CreateUSDLook, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- parms = {
- "lopoutput": "$HIP/pyblish/{}.usd".format(product_name),
- "enableoutputprocessor_simplerelativepaths": False,
-
- # Set the 'default prim' by default to the folder name being
- # published to
- "defaultprim": '/`strsplit(chs("folderPath"), "/", -1)`',
- }
-
- if self.selected_nodes:
- parms["loppath"] = self.selected_nodes[0].path()
-
- instance_node.setParms(parms)
-
- # Lock any parameters in this list
- to_lock = [
- "fileperframe",
- # Lock some Avalon attributes
- "family",
- "id",
- ]
- self.lock_parameters(instance_node, to_lock)
-
- def get_detail_description(self):
- return inspect.cleandoc("""Publish looks in USD data.
-
- From the Houdini Solaris context (LOPs) this will publish the look for
- an asset as a USD file with the used textures.
-
- Any assets used by the look will be relatively remapped to the USD
- file and integrated into the publish as `resources`.
-
- """)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.lopNodeTypeCategory()
- ]
-
- def get_publish_families(self):
- return ["usd", "look", "usdrop"]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_usdrender.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_usdrender.py
deleted file mode 100644
index 9c7bc0fd3e..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_usdrender.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating USD renders."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef, EnumDef
-
-import hou
-
-
-def get_usd_rop_renderers():
- """Return all available renderers supported by USD Render ROP.
- Note that the USD Render ROP does not include all Hydra renderers, because
- it excludes the GL ones like Houdini GL and Storm. USD Render ROP only
- lists the renderers that have `aovsupport` enabled. Also see:
- https://www.sidefx.com/docs/houdini/nodes/out/usdrender.html#list
- Returns:
- dict[str, str]: Plug-in name to display name mapping.
- """
- return {
- info["name"]: info["displayname"] for info
- in hou.lop.availableRendererInfo() if info.get('aovsupport')
- }
-
-
-class CreateUSDRender(plugin.HoudiniCreator):
- """USD Render ROP in /stage"""
- identifier = "io.openpype.creators.houdini.usdrender"
- label = "USD Render"
- product_type = "usdrender"
- icon = "magic"
- description = "Create USD Render"
-
- default_renderer = "Karma CPU"
- # Default render target
- render_target = "farm_split"
-
- def create(self, product_name, instance_data, pre_create_data):
-
- # Transfer settings from pre create to instance
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
-
- for key in ["render_target", "review"]:
- if key in pre_create_data:
- creator_attributes[key] = pre_create_data[key]
-
- # TODO: Support creation in /stage if wanted by user
- # pre_create_data["parent"] = "/stage"
-
- # Remove the active, we are checking the bypass flag of the nodes
- instance_data.pop("active", None)
- instance_data.update({"node_type": "usdrender"})
-
- # Override default value for the Export Chunk Size because if the
- # a single USD file is written as opposed to per frame we want to
- # ensure only one machine picks up that sequence
- # TODO: Probably better to change the default somehow for just this
- # Creator on the HoudiniSubmitDeadline plug-in, if possible?
- (
- instance_data
- .setdefault("publish_attributes", {})
- .setdefault("HoudiniSubmitDeadlineUsdRender", {})["export_chunk"]
- ) = 1000
-
- instance = super(CreateUSDRender, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- parms = {
- # Render frame range
- "trange": 1
- }
- if self.selected_nodes:
- parms["loppath"] = self.selected_nodes[0].path()
-
- if pre_create_data.get("render_target") == "farm_split":
- # Do not trigger the husk render, only trigger the USD export
- parms["runcommand"] = False
- # By default, the render ROP writes out the render file to a
- # temporary directory. But if we want to render the USD file on
- # the farm we instead want it in the project available
- # to all machines. So we ensure all USD files are written to a
- # folder to our choice. The
- # `__render__.usd` (default name, defined by `lopoutput` parm)
- # in that folder will then be the file to render.
- parms["savetodirectory_directory"] = "$HIP/render/usd/$HIPNAME/$OS"
- parms["lopoutput"] = "__render__.usd"
- parms["allframesatonce"] = True
-
- # By default strip any Houdini custom data from the output file
- # since the renderer doesn't care about it
- parms["clearhoudinicustomdata"] = True
-
- # Use the first selected LOP node if "Use Selection" is enabled
- # and the user had any nodes selected
- if self.selected_nodes:
- for node in self.selected_nodes:
- if node.type().category() == hou.lopNodeTypeCategory():
- parms["loppath"] = node.path()
- break
-
- # Set default renderer if defined in settings
- if pre_create_data.get("renderer"):
- parms["renderer"] = pre_create_data.get("renderer")
-
- instance_node.setParms(parms)
-
- # Lock some AYON attributes
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def get_instance_attr_defs(self):
- """get instance attribute definitions.
- Attributes defined in this method are exposed in
- publish tab in the publisher UI.
- """
-
- render_target_items = {
- "local": "Local machine rendering",
- "local_no_render": "Use existing frames (local)",
- "farm": "Farm Rendering",
- "farm_split": "Farm Rendering - Split export & render jobs",
- }
-
- return [
- BoolDef("review",
- label="Review",
- tooltip="Mark as reviewable",
- default=True),
- EnumDef("render_target",
- items=render_target_items,
- label="Render target",
- default=self.render_target)
- ]
-
- def get_pre_create_attr_defs(self):
-
- # Retrieve available renderers and convert default renderer to
- # plug-in name if settings provided the display name
- renderer_plugin_to_display_name = get_usd_rop_renderers()
- default_renderer = self.default_renderer or None
- if (
- default_renderer
- and default_renderer not in renderer_plugin_to_display_name
- ):
- # Map default renderer display name to plugin name
- for name, display_name in renderer_plugin_to_display_name.items():
- if default_renderer == display_name:
- default_renderer = name
- break
- else:
- # Default renderer not found in available renderers
- default_renderer = None
-
- attrs = super(CreateUSDRender, self).get_pre_create_attr_defs()
- attrs += [
- EnumDef("renderer",
- label="Renderer",
- default=default_renderer,
- items=renderer_plugin_to_display_name),
- ]
-
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_vbd_cache.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_vbd_cache.py
deleted file mode 100644
index e8c0920ec8..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_vbd_cache.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating VDB Caches."""
-from ayon_houdini.api import plugin
-from ayon_core.lib import BoolDef
-
-import hou
-
-
-class CreateVDBCache(plugin.HoudiniCreator):
- """OpenVDB from Geometry ROP"""
- identifier = "io.openpype.creators.houdini.vdbcache"
- name = "vbdcache"
- label = "VDB Cache"
- product_type = "vdbcache"
- icon = "cloud"
-
- def create(self, product_name, instance_data, pre_create_data):
- import hou
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "geometry"})
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- creator_attributes["farm"] = pre_create_data["farm"]
- instance = super(CreateVDBCache, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
- file_path = "{}{}".format(
- hou.text.expandString("$HIP/pyblish/"),
- "{}.$F4.vdb".format(product_name))
- parms = {
- "sopoutput": file_path,
- "initsim": True,
- "trange": 1
- }
-
- if self.selected_nodes:
- parms["soppath"] = self.get_sop_node_path(self.selected_nodes[0])
-
- instance_node.setParms(parms)
-
- def get_network_categories(self):
- return [
- hou.ropNodeTypeCategory(),
- hou.objNodeTypeCategory(),
- hou.sopNodeTypeCategory()
- ]
-
- def get_sop_node_path(self, selected_node):
- """Get Sop Path of the selected node.
-
- Although Houdini allows ObjNode path on `sop_path` for the
- the ROP node, we prefer it set to the SopNode path explicitly.
- """
-
- # Allow sop level paths (e.g. /obj/geo1/box1)
- if isinstance(selected_node, hou.SopNode):
- self.log.debug(
- "Valid SopNode selection, 'SOP Path' in ROP will"
- " be set to '%s'.", selected_node.path()
- )
- return selected_node.path()
-
- # Allow object level paths to Geometry nodes (e.g. /obj/geo1)
- # but do not allow other object level nodes types like cameras, etc.
- elif isinstance(selected_node, hou.ObjNode) and \
- selected_node.type().name() == "geo":
-
- # Try to find output node.
- sop_node = self.get_obj_output(selected_node)
- if sop_node:
- self.log.debug(
- "Valid ObjNode selection, 'SOP Path' in ROP will "
- "be set to the child path '%s'.", sop_node.path()
- )
- return sop_node.path()
-
- self.log.debug(
- "Selection isn't valid. 'SOP Path' in ROP will be empty."
- )
- return ""
-
- def get_obj_output(self, obj_node):
- """Try to find output node.
-
- If any output nodes are present, return the output node with
- the minimum 'outputidx'
- If no output nodes are present, return the node with display flag
- If no nodes are present at all, return None
- """
-
- outputs = obj_node.subnetOutputs()
-
- # if obj_node is empty
- if not outputs:
- return
-
- # if obj_node has one output child whether its
- # sop output node or a node with the render flag
- elif len(outputs) == 1:
- return outputs[0]
-
- # if there are more than one, then it has multiple output nodes
- # return the one with the minimum 'outputidx'
- else:
- return min(outputs,
- key=lambda node: node.evalParm('outputidx'))
-
- def get_instance_attr_defs(self):
- return [
- BoolDef("farm",
- label="Submitting to Farm",
- default=False)
- ]
-
- def get_pre_create_attr_defs(self):
- attrs = super().get_pre_create_attr_defs()
- # Use same attributes as for instance attributes
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_vray_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_vray_rop.py
deleted file mode 100644
index d15ee23825..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_vray_rop.py
+++ /dev/null
@@ -1,200 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin to create VRay ROP."""
-import hou
-
-from ayon_houdini.api import plugin
-from ayon_core.pipeline import CreatorError
-from ayon_core.lib import EnumDef, BoolDef
-
-
-class CreateVrayROP(plugin.HoudiniCreator):
- """VRay ROP"""
-
- identifier = "io.openpype.creators.houdini.vray_rop"
- label = "VRay ROP"
- product_type = "vray_rop"
- icon = "magic"
- ext = "exr"
-
- # Default render target
- render_target = "farm_split"
-
- def create(self, product_name, instance_data, pre_create_data):
- # Transfer settings from pre create to instance
- creator_attributes = instance_data.setdefault(
- "creator_attributes", dict())
- for key in ["render_target", "review"]:
- if key in pre_create_data:
- creator_attributes[key] = pre_create_data[key]
-
- instance_data.pop("active", None)
- instance_data.update({"node_type": "vray_renderer"})
- # Add chunk size attribute
- instance_data["chunkSize"] = 10
-
- instance = super(CreateVrayROP, self).create(
- product_name,
- instance_data,
- pre_create_data)
-
- instance_node = hou.node(instance.get("instance_node"))
-
- # Add IPR for Vray
- basename = instance_node.name()
- try:
- ipr_rop = instance_node.parent().createNode(
- "vray", node_name=basename + "_IPR"
- )
- except hou.OperationFailed:
- raise CreatorError(
- "Cannot create Vray render node. "
- "Make sure Vray installed and enabled!"
- )
-
- ipr_rop.setPosition(instance_node.position() + hou.Vector2(0, -1))
- ipr_rop.parm("rop").set(instance_node.path())
-
- parms = {
- "trange": 1,
- "SettingsEXR_bits_per_channel": "16" # half precision
- }
-
- if pre_create_data.get("render_target") == "farm_split":
- scene_filepath = \
- "{export_dir}{product_name}/{product_name}.$F4.vrscene".format(
- export_dir=hou.text.expandString("$HIP/pyblish/vrscene/"),
- product_name=product_name,
- )
- # Setting render_export_mode to "2" because that's for
- # "Export only" ("1" is for "Export & Render")
- parms["render_export_mode"] = "2"
- parms["render_export_filepath"] = scene_filepath
-
- if self.selected_nodes:
- # set up the render camera from the selected node
- camera = None
- for node in self.selected_nodes:
- if node.type().name() == "cam":
- camera = node.path()
- parms.update({
- "render_camera": camera or ""
- })
-
- # Enable render element
- ext = pre_create_data.get("image_format")
- instance_data["RenderElement"] = pre_create_data.get("render_element_enabled") # noqa
- if pre_create_data.get("render_element_enabled", True):
- # Vray has its own tag for AOV file output
- filepath = "{renders_dir}{product_name}/{product_name}.{fmt}".format(
- renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
- product_name=product_name,
- fmt="${aov}.$F4.{ext}".format(aov="AOV",
- ext=ext)
- )
- filepath = "{}{}".format(
- hou.text.expandString("$HIP/pyblish/renders/"),
- "{}/{}.${}.$F4.{}".format(product_name,
- product_name,
- "AOV",
- ext)
- )
- re_rop = instance_node.parent().createNode(
- "vray_render_channels",
- node_name=basename + "_render_element"
- )
- # move the render element node next to the vray renderer node
- re_rop.setPosition(instance_node.position() + hou.Vector2(0, 1))
- re_path = re_rop.path()
- parms.update({
- "use_render_channels": 1,
- "SettingsOutput_img_file_path": filepath,
- "render_network_render_channels": re_path
- })
-
- else:
- filepath = "{renders_dir}{product_name}/{product_name}.{fmt}".format(
- renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
- product_name=product_name,
- fmt="$F4.{ext}".format(ext=ext)
- )
- parms.update({
- "use_render_channels": 0,
- "SettingsOutput_img_file_path": filepath
- })
-
- custom_res = pre_create_data.get("override_resolution")
- if custom_res:
- parms.update({"override_camerares": 1})
-
- instance_node.setParms(parms)
-
- # lock parameters from AVALON
- to_lock = ["productType", "id"]
- self.lock_parameters(instance_node, to_lock)
-
- def remove_instances(self, instances):
- for instance in instances:
- node = instance.data.get("instance_node")
- # for the extra render node from the plugins
- # such as vray and redshift
- ipr_node = hou.node("{}{}".format(node, "_IPR"))
- if ipr_node:
- ipr_node.destroy()
- re_node = hou.node("{}{}".format(node,
- "_render_element"))
- if re_node:
- re_node.destroy()
-
- return super(CreateVrayROP, self).remove_instances(instances)
-
- def get_instance_attr_defs(self):
- """get instance attribute definitions.
-
- Attributes defined in this method are exposed in
- publish tab in the publisher UI.
- """
-
-
- render_target_items = {
- "local": "Local machine rendering",
- "local_no_render": "Use existing frames (local)",
- "farm": "Farm Rendering",
- "farm_split": "Farm Rendering - Split export & render jobs",
- }
-
- return [
- BoolDef("review",
- label="Review",
- tooltip="Mark as reviewable",
- default=True),
- EnumDef("render_target",
- items=render_target_items,
- label="Render target",
- default=self.render_target)
- ]
-
- def get_pre_create_attr_defs(self):
- image_format_enum = [
- "bmp", "cin", "exr", "jpg", "pic", "pic.gz", "png",
- "rad", "rat", "rta", "sgi", "tga", "tif",
- ]
-
- attrs = super(CreateVrayROP, self).get_pre_create_attr_defs()
-
- attrs += [
- EnumDef("image_format",
- image_format_enum,
- default=self.ext,
- label="Image Format Options"),
- BoolDef("override_resolution",
- label="Override Camera Resolution",
- tooltip="Override the current camera "
- "resolution, recommended for IPR.",
- default=False),
- BoolDef("render_element_enabled",
- label="Render Element",
- tooltip="Create Render Element Node "
- "if enabled",
- default=False)
- ]
- return attrs + self.get_instance_attr_defs()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/create/create_workfile.py b/server_addon/houdini/client/ayon_houdini/plugins/create/create_workfile.py
deleted file mode 100644
index babf602855..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/create/create_workfile.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Creator plugin for creating workfiles."""
-import ayon_api
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.lib import read, imprint
-from ayon_houdini.api.pipeline import CONTEXT_CONTAINER
-from ayon_core.pipeline import CreatedInstance, AutoCreator
-import hou
-
-
-class CreateWorkfile(plugin.HoudiniCreatorBase, AutoCreator):
- """Workfile auto-creator."""
- identifier = "io.openpype.creators.houdini.workfile"
- label = "Workfile"
- product_type = "workfile"
- icon = "fa5.file"
-
- default_variant = "Main"
-
- def create(self):
- variant = self.default_variant
- current_instance = next(
- (
- instance for instance in self.create_context.instances
- if instance.creator_identifier == self.identifier
- ), None)
-
- project_name = self.project_name
- folder_path = self.create_context.get_current_folder_path()
- task_name = self.create_context.get_current_task_name()
- host_name = self.host_name
-
- if current_instance is None:
- current_folder_path = None
- else:
- current_folder_path = current_instance["folderPath"]
-
- if current_instance is None:
- folder_entity = ayon_api.get_folder_by_path(
- project_name, folder_path
- )
- task_entity = ayon_api.get_task_by_name(
- project_name, folder_entity["id"], task_name
- )
- product_name = self.get_product_name(
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- )
- data = {
- "folderPath": folder_path,
- "task": task_name,
- "variant": variant,
- }
-
- data.update(
- self.get_dynamic_data(
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- current_instance)
- )
- self.log.info("Auto-creating workfile instance...")
- current_instance = CreatedInstance(
- self.product_type, product_name, data, self
- )
- self._add_instance_to_context(current_instance)
- elif (
- current_folder_path != folder_path
- or current_instance["task"] != task_name
- ):
- # Update instance context if is not the same
- folder_entity = ayon_api.get_folder_by_path(
- project_name, folder_path
- )
- task_entity = ayon_api.get_task_by_name(
- project_name, folder_entity["id"], task_name
- )
- product_name = self.get_product_name(
- project_name,
- folder_entity,
- task_entity,
- variant,
- host_name,
- )
- current_instance["folderPath"] = folder_path
- current_instance["task"] = task_name
- current_instance["productName"] = product_name
-
- # write workfile information to context container.
- op_ctx = hou.node(CONTEXT_CONTAINER)
- if not op_ctx:
- op_ctx = self.host.create_context_node()
-
- workfile_data = {"workfile": current_instance.data_to_store()}
- imprint(op_ctx, workfile_data)
-
- def collect_instances(self):
- op_ctx = hou.node(CONTEXT_CONTAINER)
- instance = read(op_ctx)
- if not instance:
- return
- workfile = instance.get("workfile")
- if not workfile:
- return
- created_instance = CreatedInstance.from_existing(
- workfile, self
- )
- self._add_instance_to_context(created_instance)
-
- def update_instances(self, update_list):
- op_ctx = hou.node(CONTEXT_CONTAINER)
- for created_inst, _changes in update_list:
- if created_inst["creator_identifier"] == self.identifier:
- workfile_data = {"workfile": created_inst.data_to_store()}
- imprint(op_ctx, workfile_data, update=True)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/inventory/set_camera_resolution.py b/server_addon/houdini/client/ayon_houdini/plugins/inventory/set_camera_resolution.py
deleted file mode 100644
index e2f8fcfa9b..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/inventory/set_camera_resolution.py
+++ /dev/null
@@ -1,26 +0,0 @@
-from ayon_core.pipeline import InventoryAction
-from ayon_houdini.api.lib import (
- get_camera_from_container,
- set_camera_resolution
-)
-from ayon_core.pipeline.context_tools import get_current_folder_entity
-
-
-class SetCameraResolution(InventoryAction):
-
- label = "Set Camera Resolution"
- icon = "desktop"
- color = "orange"
-
- @staticmethod
- def is_compatible(container):
- return (
- container.get("loader") == "CameraLoader"
- )
-
- def process(self, containers):
- folder_entity = get_current_folder_entity()
- for container in containers:
- node = container["node"]
- camera = get_camera_from_container(node)
- set_camera_resolution(camera, folder_entity)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/actions.py b/server_addon/houdini/client/ayon_houdini/plugins/load/actions.py
deleted file mode 100644
index 5fe545ced9..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/actions.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""A module containing generic loader actions that will display in the Loader.
-
-"""
-
-from ayon_houdini.api import plugin
-
-
-class SetFrameRangeLoader(plugin.HoudiniLoader):
- """Set frame range excluding pre- and post-handles"""
-
- product_types = {
- "animation",
- "camera",
- "pointcache",
- "vdbcache",
- "usd",
- }
- representations = {"abc", "vdb", "usd"}
-
- label = "Set frame range"
- order = 11
- icon = "clock-o"
- color = "white"
-
- def load(self, context, name, namespace, data):
-
- import hou
-
- version_attributes = context["version"]["attrib"]
-
- start = version_attributes.get("frameStart")
- end = version_attributes.get("frameEnd")
-
- if start is None or end is None:
- print(
- "Skipping setting frame range because start or "
- "end frame data is missing.."
- )
- return
-
- hou.playbar.setFrameRange(start, end)
- hou.playbar.setPlaybackRange(start, end)
-
-
-class SetFrameRangeWithHandlesLoader(plugin.HoudiniLoader):
- """Set frame range including pre- and post-handles"""
-
- product_types = {
- "animation",
- "camera",
- "pointcache",
- "vdbcache",
- "usd",
- }
- representations = {"abc", "vdb", "usd"}
-
- label = "Set frame range (with handles)"
- order = 12
- icon = "clock-o"
- color = "white"
-
- def load(self, context, name, namespace, data):
-
- import hou
-
- version_attributes = context["version"]["attrib"]
-
- start = version_attributes.get("frameStart")
- end = version_attributes.get("frameEnd")
-
- if start is None or end is None:
- print(
- "Skipping setting frame range because start or "
- "end frame data is missing.."
- )
- return
-
- # Include handles
- start -= version_attributes.get("handleStart", 0)
- end += version_attributes.get("handleEnd", 0)
-
- hou.playbar.setFrameRange(start, end)
- hou.playbar.setPlaybackRange(start, end)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_alembic.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_alembic.py
deleted file mode 100644
index 7db2fe93ed..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_alembic.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import os
-from ayon_core.pipeline import get_representation_path
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class AbcLoader(plugin.HoudiniLoader):
- """Load Alembic"""
-
- product_types = {"model", "animation", "pointcache", "gpuCache"}
- label = "Load Alembic"
- representations = {"*"}
- extensions = {"abc"}
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
- import hou
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context)
- file_path = os.path.normpath(file_path)
- file_path = file_path.replace("\\", "/")
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a new geo node
- container = obj.createNode("geo", node_name=node_name)
-
- # Remove the file node, it only loads static meshes
- # Houdini 17 has removed the file node from the geo node
- file_node = container.node("file1")
- if file_node:
- file_node.destroy()
-
- # Create an alembic node (supports animation)
- alembic = container.createNode("alembic", node_name=node_name)
- alembic.setParms({"fileName": file_path})
-
- # Position nodes nicely
- container.moveToGoodPosition()
- container.layoutChildren()
-
- nodes = [container, alembic]
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
- try:
- alembic_node = next(
- n for n in node.children() if n.type().name() == "alembic"
- )
- except StopIteration:
- self.log.error("Could not find node of type `alembic`")
- return
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
-
- alembic_node.setParms({"fileName": file_path})
-
- # Update attribute
- node.setParms({"representation": repre_entity["id"]})
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_alembic_archive.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_alembic_archive.py
deleted file mode 100644
index a34a43e48a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_alembic_archive.py
+++ /dev/null
@@ -1,81 +0,0 @@
-
-import os
-from ayon_core.pipeline import get_representation_path
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class AbcArchiveLoader(plugin.HoudiniLoader):
- """Load Alembic as full geometry network hierarchy """
-
- product_types = {"model", "animation", "pointcache", "gpuCache"}
- label = "Load Alembic as Archive"
- representations = {"*"}
- extensions = {"abc"}
- order = -5
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- import hou
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context)
- file_path = os.path.normpath(file_path)
- file_path = file_path.replace("\\", "/")
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create an Alembic archive node
- node = obj.createNode("alembicarchive", node_name=node_name)
- node.moveToGoodPosition()
-
- # TODO: add FPS of project / folder
- node.setParms({"fileName": file_path,
- "channelRef": True})
-
- # Apply some magic
- node.parm("buildHierarchy").pressButton()
- node.moveToGoodPosition()
-
- nodes = [node]
-
- self[:] = nodes
-
- return pipeline.containerise(node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="")
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
-
- # Update attributes
- node.setParms({"fileName": file_path,
- "representation": repre_entity["id"]})
-
- # Rebuild
- node.parm("buildHierarchy").pressButton()
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_ass.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_ass.py
deleted file mode 100644
index 5fd97bc2a6..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_ass.py
+++ /dev/null
@@ -1,91 +0,0 @@
-import os
-import re
-
-from ayon_core.pipeline import get_representation_path
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class AssLoader(plugin.HoudiniLoader):
- """Load .ass with Arnold Procedural"""
-
- product_types = {"ass"}
- label = "Load Arnold Procedural"
- representations = {"ass"}
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
- import hou
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a new geo node
- procedural = obj.createNode("arnold::procedural", node_name=node_name)
-
- procedural.setParms(
- {
- "ar_filename": self.format_path(context["representation"])
- })
-
- nodes = [procedural]
- self[:] = nodes
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- def update(self, container, context):
- # Update the file path
- repre_entity = context["representation"]
- procedural = container["node"]
- procedural.setParms({"ar_filename": self.format_path(repre_entity)})
-
- # Update attribute
- procedural.setParms({"representation": repre_entity["id"]})
-
- def remove(self, container):
- node = container["node"]
- node.destroy()
-
- @staticmethod
- def format_path(representation):
- """Format file path correctly for single ass.* or ass.* sequence.
-
- Args:
- representation (dict): representation to be loaded.
-
- Returns:
- str: Formatted path to be used by the input node.
-
- """
- path = get_representation_path(representation)
- if not os.path.exists(path):
- raise RuntimeError("Path does not exist: {}".format(path))
-
- is_sequence = bool(representation["context"].get("frame"))
- # The path is either a single file or sequence in a folder.
- if is_sequence:
- dir_path, file_name = os.path.split(path)
- path = os.path.join(
- dir_path,
- re.sub(r"(.*)\.(\d+)\.(ass.*)", "\\1.$F4.\\3", file_name)
- )
-
- return os.path.normpath(path).replace("\\", "/")
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_asset_lop.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_asset_lop.py
deleted file mode 100644
index d9ab438d6d..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_asset_lop.py
+++ /dev/null
@@ -1,52 +0,0 @@
-from ayon_core.pipeline import load
-from ayon_houdini.api.lib import find_active_network
-
-import hou
-
-
-class LOPLoadAssetLoader(load.LoaderPlugin):
- """Load reference/payload into Solaris using AYON `lop_import` LOP"""
-
- product_types = {"*"}
- label = "Load Asset (LOPs)"
- representations = ["usd", "abc", "usda", "usdc"]
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create node
- network = find_active_network(
- category=hou.lopNodeTypeCategory(),
- default="/stage"
- )
- node = network.createNode("ayon::lop_import", node_name=node_name)
- node.moveToGoodPosition()
-
- # Set representation id
- parm = node.parm("representation")
- parm.set(context["representation"]["id"])
- parm.pressButton() # trigger callbacks
-
- nodes = [node]
- self[:] = nodes
-
- def update(self, container, context):
- node = container["node"]
-
- # Set representation id
- parm = node.parm("representation")
- parm.set(context["representation"]["id"])
- parm.pressButton() # trigger callbacks
-
- def remove(self, container):
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_bgeo.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_bgeo.py
deleted file mode 100644
index 7119612cda..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_bgeo.py
+++ /dev/null
@@ -1,111 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import re
-
-from ayon_core.pipeline import get_representation_path
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class BgeoLoader(plugin.HoudiniLoader):
- """Load bgeo files to Houdini."""
-
- label = "Load bgeo"
- product_types = {"model", "pointcache", "bgeo"}
- representations = {
- "bgeo", "bgeosc", "bgeogz",
- "bgeo.sc", "bgeo.gz", "bgeo.lzma", "bgeo.bz2"}
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- import hou
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a new geo node
- container = obj.createNode("geo", node_name=node_name)
-
- # Remove the file node, it only loads static meshes
- # Houdini 17 has removed the file node from the geo node
- file_node = container.node("file1")
- if file_node:
- file_node.destroy()
-
- # Explicitly create a file node
- path = self.filepath_from_context(context)
- file_node = container.createNode("file", node_name=node_name)
- file_node.setParms(
- {"file": self.format_path(path, context["representation"])})
-
- # Set display on last node
- file_node.setDisplayFlag(True)
-
- nodes = [container, file_node]
- self[:] = nodes
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- @staticmethod
- def format_path(path, representation):
- """Format file path correctly for single bgeo or bgeo sequence."""
- if not os.path.exists(path):
- raise RuntimeError("Path does not exist: %s" % path)
-
- is_sequence = bool(representation["context"].get("frame"))
- # The path is either a single file or sequence in a folder.
- if not is_sequence:
- filename = path
- else:
- filename = re.sub(r"(.*)\.(\d+)\.(bgeo.*)", "\\1.$F4.\\3", path)
-
- filename = os.path.join(path, filename)
-
- filename = os.path.normpath(filename)
- filename = filename.replace("\\", "/")
-
- return filename
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
- try:
- file_node = next(
- n for n in node.children() if n.type().name() == "file"
- )
- except StopIteration:
- self.log.error("Could not find node of type `alembic`")
- return
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = self.format_path(file_path, repre_entity)
-
- file_node.setParms({"file": file_path})
-
- # Update attribute
- node.setParms({"representation": repre_entity["id"]})
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_camera.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_camera.py
deleted file mode 100644
index b597519813..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_camera.py
+++ /dev/null
@@ -1,212 +0,0 @@
-import hou
-from ayon_core.pipeline import get_representation_path
-
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-from ayon_houdini.api.lib import (
- set_camera_resolution,
- get_camera_from_container
-)
-
-
-ARCHIVE_EXPRESSION = ('__import__("_alembic_hom_extensions")'
- '.alembicGetCameraDict')
-
-
-def transfer_non_default_values(src, dest, ignore=None):
- """Copy parm from src to dest.
-
- Because the Alembic Archive rebuilds the entire node
- hierarchy on triggering "Build Hierarchy" we want to
- preserve any local tweaks made by the user on the camera
- for ease of use. That could be a background image, a
- resolution change or even Redshift camera parameters.
-
- We try to do so by finding all Parms that exist on both
- source and destination node, include only those that both
- are not at their default value, they must be visible,
- we exclude those that have the special "alembic archive"
- channel expression and ignore certain Parm types.
-
- """
-
- ignore_types = {
- hou.parmTemplateType.Toggle,
- hou.parmTemplateType.Menu,
- hou.parmTemplateType.Button,
- hou.parmTemplateType.FolderSet,
- hou.parmTemplateType.Separator,
- hou.parmTemplateType.Label,
- }
-
- src.updateParmStates()
-
- for parm in src.allParms():
-
- if ignore and parm.name() in ignore:
- continue
-
- # If destination parm does not exist, ignore..
- dest_parm = dest.parm(parm.name())
- if not dest_parm:
- continue
-
- # Ignore values that are currently at default
- if parm.isAtDefault() and dest_parm.isAtDefault():
- continue
-
- if not parm.isVisible():
- # Ignore hidden parameters, assume they
- # are implementation details
- continue
-
- expression = None
- try:
- expression = parm.expression()
- except hou.OperationFailed:
- # No expression present
- pass
-
- if expression is not None and ARCHIVE_EXPRESSION in expression:
- # Assume it's part of the automated connections that the
- # Alembic Archive makes on loading of the camera and thus we do
- # not want to transfer the expression
- continue
-
- # Ignore folders, separators, etc.
- if parm.parmTemplate().type() in ignore_types:
- continue
-
- print("Preserving attribute: %s" % parm.name())
- dest_parm.setFromParm(parm)
-
-
-class CameraLoader(plugin.HoudiniLoader):
- """Load camera from an Alembic file"""
-
- product_types = {"camera"}
- label = "Load Camera (abc)"
- representations = {"abc"}
- order = -10
-
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context).replace("\\", "/")
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a archive node
- node = self.create_and_connect(obj, "alembicarchive", node_name)
-
- # TODO: add FPS of project / folder
- node.setParms({"fileName": file_path, "channelRef": True})
-
- # Apply some magic
- node.parm("buildHierarchy").pressButton()
- node.moveToGoodPosition()
-
- # Create an alembic xform node
- nodes = [node]
-
- camera = get_camera_from_container(node)
- self._match_maya_render_mask(camera)
- set_camera_resolution(camera, folder_entity=context["folder"])
- self[:] = nodes
-
- return pipeline.containerise(node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="")
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
-
- # Update attributes
- node.setParms({"fileName": file_path,
- "representation": repre_entity["id"]})
-
- # Store the cam temporarily next to the Alembic Archive
- # so that we can preserve parm values the user set on it
- # after build hierarchy was triggered.
- old_camera = get_camera_from_container(node)
- temp_camera = old_camera.copyTo(node.parent())
-
- # Rebuild
- node.parm("buildHierarchy").pressButton()
-
- # Apply values to the new camera
- new_camera = get_camera_from_container(node)
- transfer_non_default_values(temp_camera,
- new_camera,
- # The hidden uniform scale attribute
- # gets a default connection to
- # "icon_scale" just skip that completely
- ignore={"scale"})
-
- self._match_maya_render_mask(new_camera)
- set_camera_resolution(new_camera)
-
- temp_camera.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def create_and_connect(self, node, node_type, name=None):
- """Create a node within a node which and connect it to the input
-
- Args:
- node(hou.Node): parent of the new node
- node_type(str) name of the type of node, eg: 'alembic'
- name(str, Optional): name of the node
-
- Returns:
- hou.Node
-
- """
- if name:
- new_node = node.createNode(node_type, node_name=name)
- else:
- new_node = node.createNode(node_type)
-
- new_node.moveToGoodPosition()
- return new_node
-
- def _match_maya_render_mask(self, camera):
- """Workaround to match Maya render mask in Houdini"""
-
- parm = camera.parm("aperture")
- expression = parm.expression()
- expression = expression.replace("return ", "aperture = ")
- expression += """
-# Match maya render mask (logic from Houdini's own FBX importer)
-node = hou.pwd()
-resx = node.evalParm('resx')
-resy = node.evalParm('resy')
-aspect = node.evalParm('aspect')
-aperture *= min(1, (resx / resy * aspect) / 1.5)
-return aperture
-"""
- parm.setExpression(expression, language=hou.exprLanguage.Python)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_fbx.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_fbx.py
deleted file mode 100644
index 273ca43bc4..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_fbx.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Fbx Loader for houdini. """
-from ayon_core.pipeline import get_representation_path
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class FbxLoader(plugin.HoudiniLoader):
- """Load fbx files. """
-
- label = "Load FBX"
- icon = "code-fork"
- color = "orange"
-
- order = -10
-
- product_types = {"*"}
- representations = {"*"}
- extensions = {"fbx"}
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # get file path from context
- file_path = self.filepath_from_context(context)
- file_path = file_path.replace("\\", "/")
-
- # get necessary data
- namespace, node_name = self.get_node_name(context, name, namespace)
-
- # create load tree
- nodes = self.create_load_node_tree(file_path, node_name, name)
-
- self[:] = nodes
-
- # Call containerise function which does some automations for you
- # like moving created nodes to the AVALON_CONTAINERS subnetwork
- containerised_nodes = pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- return containerised_nodes
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
- try:
- file_node = next(
- n for n in node.children() if n.type().name() == "file"
- )
- except StopIteration:
- self.log.error("Could not find node of type `file`")
- return
-
- # Update the file path from representation
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
-
- file_node.setParms({"file": file_path})
-
- # Update attribute
- node.setParms({"representation": repre_entity["id"]})
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
-
- def get_node_name(self, context, name=None, namespace=None):
- """Define node name."""
-
- if not namespace:
- namespace = context["folder"]["name"]
-
- if namespace:
- node_name = "{}_{}".format(namespace, name)
- else:
- node_name = name
-
- return namespace, node_name
-
- def create_load_node_tree(self, file_path, node_name, product_name):
- """Create Load network.
-
- you can start building your tree at any obj level.
- it'll be much easier to build it in the root obj level.
-
- Afterwards, your tree will be automatically moved to
- '/obj/AVALON_CONTAINERS' subnetwork.
- """
- import hou
-
- # Get the root obj level
- obj = hou.node("/obj")
-
- # Create a new obj geo node
- parent_node = obj.createNode("geo", node_name=node_name)
-
- # In older houdini,
- # when reating a new obj geo node, a default file node will be
- # automatically created.
- # so, we will delete it if exists.
- file_node = parent_node.node("file1")
- if file_node:
- file_node.destroy()
-
- # Create a new file node
- file_node = parent_node.createNode("file", node_name=node_name)
- file_node.setParms({"file": file_path})
-
- # Create attribute delete
- attribdelete_name = "attribdelete_{}".format(product_name)
- attribdelete = parent_node.createNode("attribdelete",
- node_name=attribdelete_name)
- attribdelete.setParms({"ptdel": "fbx_*"})
- attribdelete.setInput(0, file_node)
-
- # Create a Null node
- null_name = "OUT_{}".format(product_name)
- null = parent_node.createNode("null", node_name=null_name)
- null.setInput(0, attribdelete)
-
- # Ensure display flag is on the file_node input node and not on the OUT
- # node to optimize "debug" displaying in the viewport.
- file_node.setDisplayFlag(True)
-
- # Set new position for children nodes
- parent_node.layoutChildren()
-
- # Return all the nodes
- return [parent_node, file_node, attribdelete, null]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_filepath.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_filepath.py
deleted file mode 100644
index 2ce9bd7ffb..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_filepath.py
+++ /dev/null
@@ -1,130 +0,0 @@
-import os
-import re
-import hou
-
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class FilePathLoader(plugin.HoudiniLoader):
- """Load a managed filepath to a null node.
-
- This is useful if for a particular workflow there is no existing loader
- yet. A Houdini artists can load as the generic filepath loader and then
- reference the relevant Houdini parm to use the exact value. The benefit
- is that this filepath will be managed and can be updated as usual.
-
- """
-
- label = "Load filepath to node"
- order = 9
- icon = "link"
- color = "white"
- product_types = {"*"}
- representations = {"*"}
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a null node
- container = obj.createNode("null", node_name=node_name)
-
- # Destroy any children
- for node in container.children():
- node.destroy()
-
- # Add filepath attribute, set value as default value
- filepath = self.format_path(
- path=self.filepath_from_context(context),
- representation=context["representation"]
- )
- parm_template_group = container.parmTemplateGroup()
- attr_folder = hou.FolderParmTemplate("attributes_folder", "Attributes")
- parm = hou.StringParmTemplate(name="filepath",
- label="Filepath",
- num_components=1,
- default_value=(filepath,))
- attr_folder.addParmTemplate(parm)
- parm_template_group.append(attr_folder)
-
- # Hide some default labels
- for folder_label in ["Transform", "Render", "Misc", "Redshift OBJ"]:
- folder = parm_template_group.findFolder(folder_label)
- if not folder:
- continue
- parm_template_group.hideFolder(folder_label, True)
-
- container.setParmTemplateGroup(parm_template_group)
-
- container.setDisplayFlag(False)
- container.setSelectableInViewport(False)
- container.useXray(False)
-
- nodes = [container]
-
- self[:] = nodes
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- def update(self, container, context):
-
- # Update the file path
- representation_entity = context["representation"]
- file_path = self.format_path(
- path=self.filepath_from_context(context),
- representation=representation_entity
- )
-
- node = container["node"]
- node.setParms({
- "filepath": file_path,
- "representation": str(representation_entity["id"])
- })
-
- # Update the parameter default value (cosmetics)
- parm_template_group = node.parmTemplateGroup()
- parm = parm_template_group.find("filepath")
- parm.setDefaultValue((file_path,))
- parm_template_group.replace(parm_template_group.find("filepath"),
- parm)
- node.setParmTemplateGroup(parm_template_group)
-
- def switch(self, container, context):
- self.update(container, context)
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- @staticmethod
- def format_path(path: str, representation: dict) -> str:
- """Format file path for sequence with $F."""
- if not os.path.exists(path):
- raise RuntimeError("Path does not exist: %s" % path)
-
- # The path is either a single file or sequence in a folder.
- frame = representation["context"].get("frame")
- if frame is not None:
- # Substitute frame number in sequence with $F with padding
- ext = representation.get("ext", representation["name"])
- token = "$F{}".format(len(frame)) # e.g. $F4
- pattern = r"\.(\d+)\.{ext}$".format(ext=re.escape(ext))
- path = re.sub(pattern, ".{}.{}".format(token, ext), path)
-
- return os.path.normpath(path).replace("\\", "/")
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_hda.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_hda.py
deleted file mode 100644
index fcf0e834f8..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_hda.py
+++ /dev/null
@@ -1,121 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import hou
-from ayon_core.pipeline import (
- get_representation_path,
- AVALON_CONTAINER_ID
-)
-from ayon_core.pipeline.load import LoadError
-from ayon_houdini.api import (
- lib,
- pipeline,
- plugin
-)
-
-
-class HdaLoader(plugin.HoudiniLoader):
- """Load Houdini Digital Asset file."""
-
- product_types = {"hda"}
- label = "Load Hda"
- representations = {"hda"}
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context)
- file_path = os.path.normpath(file_path)
- file_path = file_path.replace("\\", "/")
-
- namespace = namespace or context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- hou.hda.installFile(file_path)
-
- hda_defs = hou.hda.definitionsInFile(file_path)
- if not hda_defs:
- raise LoadError(f"No HDA definitions found in file: {file_path}")
-
- parent_node = self._create_dedicated_parent_node(hda_defs[-1])
-
- # Get the type name from the HDA definition.
- type_name = hda_defs[-1].nodeTypeName()
- hda_node = parent_node.createNode(type_name, node_name)
- hda_node.moveToGoodPosition()
-
- # Imprint it manually
- data = {
- "schema": "openpype:container-2.0",
- "id": AVALON_CONTAINER_ID,
- "name": node_name,
- "namespace": namespace,
- "loader": self.__class__.__name__,
- "representation": context["representation"]["id"],
- }
-
- lib.imprint(hda_node, data)
-
- return hda_node
-
- def update(self, container, context):
-
- repre_entity = context["representation"]
- hda_node = container["node"]
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
- hou.hda.installFile(file_path)
- defs = hda_node.type().allInstalledDefinitions()
- def_paths = [d.libraryFilePath() for d in defs]
- new = def_paths.index(file_path)
- defs[new].setIsPreferred(True)
- hda_node.setParms({
- "representation": repre_entity["id"]
- })
-
- def remove(self, container):
- node = container["node"]
- parent = node.parent()
- node.destroy()
-
- if parent.path() == pipeline.AVALON_CONTAINERS:
- return
-
- # Remove parent if empty.
- if not parent.children():
- parent.destroy()
-
- def _create_dedicated_parent_node(self, hda_def):
-
- # Get the root node
- parent_node = pipeline.get_or_create_avalon_container()
- node = None
- node_type = None
- if hda_def.nodeTypeCategory() == hou.objNodeTypeCategory():
- return parent_node
- elif hda_def.nodeTypeCategory() == hou.chopNodeTypeCategory():
- node_type, node_name = "chopnet", "MOTION"
- elif hda_def.nodeTypeCategory() == hou.cop2NodeTypeCategory():
- node_type, node_name = "cop2net", "IMAGES"
- elif hda_def.nodeTypeCategory() == hou.dopNodeTypeCategory():
- node_type, node_name = "dopnet", "DOPS"
- elif hda_def.nodeTypeCategory() == hou.ropNodeTypeCategory():
- node_type, node_name = "ropnet", "ROPS"
- elif hda_def.nodeTypeCategory() == hou.lopNodeTypeCategory():
- node_type, node_name = "lopnet", "LOPS"
- elif hda_def.nodeTypeCategory() == hou.sopNodeTypeCategory():
- node_type, node_name = "geo", "SOPS"
- elif hda_def.nodeTypeCategory() == hou.topNodeTypeCategory():
- node_type, node_name = "topnet", "TOPS"
- # TODO: Create a dedicated parent node based on Vop Node vex context.
- elif hda_def.nodeTypeCategory() == hou.vopNodeTypeCategory():
- node_type, node_name = "matnet", "MATSandVOPS"
-
- node = parent_node.node(node_name)
- if not node:
- node = parent_node.createNode(node_type, node_name)
-
- node.moveToGoodPosition()
- return node
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_image.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_image.py
deleted file mode 100644
index 9d4cd2fb18..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_image.py
+++ /dev/null
@@ -1,188 +0,0 @@
-import os
-import re
-import hou
-
-from ayon_core.pipeline import (
- get_representation_path,
- AVALON_CONTAINER_ID,
-)
-from ayon_houdini.api import (
- pipeline,
- plugin,
- lib
-)
-
-
-def get_image_avalon_container():
- """The COP2 files must be in a COP2 network.
-
- So we maintain a single entry point within AVALON_CONTAINERS,
- just for ease of use.
-
- """
-
- path = pipeline.AVALON_CONTAINERS
- avalon_container = hou.node(path)
- if not avalon_container:
- # Let's create avalon container secretly
- # but make sure the pipeline still is built the
- # way we anticipate it was built, asserting it.
- assert path == "/obj/AVALON_CONTAINERS"
-
- parent = hou.node("/obj")
- avalon_container = parent.createNode(
- "subnet", node_name="AVALON_CONTAINERS"
- )
-
- image_container = hou.node(path + "/IMAGES")
- if not image_container:
- image_container = avalon_container.createNode(
- "cop2net", node_name="IMAGES"
- )
- image_container.moveToGoodPosition()
-
- return image_container
-
-
-class ImageLoader(plugin.HoudiniLoader):
- """Load images into COP2"""
-
- product_types = {
- "imagesequence",
- "review",
- "render",
- "plate",
- "image",
- "online",
- }
- label = "Load Image (COP2)"
- representations = {"*"}
- order = -10
-
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # Format file name, Houdini only wants forward slashes
- path = self.filepath_from_context(context)
- path = self.format_path(path, representation=context["representation"])
-
- # Get the root node
- parent = get_image_avalon_container()
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- node = parent.createNode("file", node_name=node_name)
- node.moveToGoodPosition()
-
- parms = {"filename1": path}
- parms.update(self.get_colorspace_parms(context["representation"]))
-
- node.setParms(parms)
-
- # Imprint it manually
- data = {
- "schema": "openpype:container-2.0",
- "id": AVALON_CONTAINER_ID,
- "name": node_name,
- "namespace": namespace,
- "loader": str(self.__class__.__name__),
- "representation": context["representation"]["id"],
- }
-
- # todo: add folder="Avalon"
- lib.imprint(node, data)
-
- return node
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = self.format_path(file_path, repre_entity)
-
- parms = {
- "filename1": file_path,
- "representation": repre_entity["id"],
- }
-
- parms.update(self.get_colorspace_parms(repre_entity))
-
- # Update attributes
- node.setParms(parms)
-
- def remove(self, container):
-
- node = container["node"]
-
- # Let's clean up the IMAGES COP2 network
- # if it ends up being empty and we deleted
- # the last file node. Store the parent
- # before we delete the node.
- parent = node.parent()
-
- node.destroy()
-
- if not parent.children():
- parent.destroy()
-
- @staticmethod
- def format_path(path, representation):
- """Format file path correctly for single image or sequence."""
- if not os.path.exists(path):
- raise RuntimeError("Path does not exist: %s" % path)
-
- ext = os.path.splitext(path)[-1]
-
- is_sequence = bool(representation["context"].get("frame"))
- # The path is either a single file or sequence in a folder.
- if not is_sequence:
- filename = path
- else:
- filename = re.sub(r"(.*)\.(\d+){}$".format(re.escape(ext)),
- "\\1.$F4{}".format(ext),
- path)
-
- filename = os.path.join(path, filename)
-
- filename = os.path.normpath(filename)
- filename = filename.replace("\\", "/")
-
- return filename
-
- def get_colorspace_parms(self, representation: dict) -> dict:
- """Return the color space parameters.
-
- Returns the values for the colorspace parameters on the node if there
- is colorspace data on the representation.
-
- Arguments:
- representation (dict): The representation entity.
-
- Returns:
- dict: Parm to value mapping if colorspace data is defined.
-
- """
- # Using OCIO colorspace on COP2 File node is only supported in Hou 20+
- major, _, _ = hou.applicationVersion()
- if major < 20:
- return {}
-
- data = representation.get("data", {}).get("colorspaceData", {})
- if not data:
- return {}
-
- colorspace = data["colorspace"]
- if colorspace:
- return {
- "colorspace": 3, # Use OpenColorIO
- "ocio_space": colorspace
- }
-
- def switch(self, container, representation):
- self.update(container, representation)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_redshift_proxy.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_redshift_proxy.py
deleted file mode 100644
index 514dbe109f..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_redshift_proxy.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import os
-import re
-import hou
-
-from ayon_core.pipeline import get_representation_path
-from ayon_core.pipeline.load import LoadError
-
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class RedshiftProxyLoader(plugin.HoudiniLoader):
- """Load Redshift Proxy"""
-
- product_types = {"redshiftproxy"}
- label = "Load Redshift Proxy"
- representations = {"rs"}
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a new geo node
- container = obj.createNode("geo", node_name=node_name)
-
- # Check whether the Redshift parameters exist - if not, then likely
- # redshift is not set up or initialized correctly
- if not container.parm("RS_objprop_proxy_enable"):
- container.destroy()
- raise LoadError("Unable to initialize geo node with Redshift "
- "attributes. Make sure you have the Redshift "
- "plug-in set up correctly for Houdini.")
-
- # Enable by default
- container.setParms({
- "RS_objprop_proxy_enable": True,
- "RS_objprop_proxy_file": self.format_path(
- self.filepath_from_context(context),
- context["representation"])
- })
-
- # Remove the file node, it only loads static meshes
- # Houdini 17 has removed the file node from the geo node
- file_node = container.node("file1")
- if file_node:
- file_node.destroy()
-
- # Add this stub node inside so it previews ok
- proxy_sop = container.createNode("redshift_proxySOP",
- node_name=node_name)
- proxy_sop.setDisplayFlag(True)
-
- nodes = [container, proxy_sop]
-
- self[:] = nodes
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- def update(self, container, context):
- repre_entity = context["representation"]
- # Update the file path
- file_path = get_representation_path(repre_entity)
-
- node = container["node"]
- node.setParms({
- "RS_objprop_proxy_file": self.format_path(
- file_path, repre_entity)
- })
-
- # Update attribute
- node.setParms({"representation": repre_entity["id"]})
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- @staticmethod
- def format_path(path, representation):
- """Format file path correctly for single redshift proxy
- or redshift proxy sequence."""
- if not os.path.exists(path):
- raise RuntimeError("Path does not exist: %s" % path)
-
- is_sequence = bool(representation["context"].get("frame"))
- # The path is either a single file or sequence in a folder.
- if is_sequence:
- filename = re.sub(r"(.*)\.(\d+)\.(rs.*)", "\\1.$F4.\\3", path)
- filename = os.path.join(path, filename)
- else:
- filename = path
-
- filename = os.path.normpath(filename)
- filename = filename.replace("\\", "/")
-
- return filename
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_layer.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_layer.py
deleted file mode 100644
index fb302fd943..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_layer.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from ayon_core.pipeline import (
- get_representation_path,
- AVALON_CONTAINER_ID,
-)
-from ayon_houdini.api import (
- plugin,
- lib
-)
-
-
-class USDSublayerLoader(plugin.HoudiniLoader):
- """Sublayer USD file in Solaris"""
-
- product_types = {
- "usd",
- "usdCamera",
- }
- label = "Sublayer USD"
- representations = {"usd", "usda", "usdlc", "usdnc", "abc"}
- order = 1
-
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- import os
- import hou
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context)
- file_path = os.path.normpath(file_path)
- file_path = file_path.replace("\\", "/")
-
- # Get the root node
- stage = hou.node("/stage")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create USD reference
- container = stage.createNode("sublayer", node_name=node_name)
- container.setParms({"filepath1": file_path})
- container.moveToGoodPosition()
-
- # Imprint it manually
- data = {
- "schema": "openpype:container-2.0",
- "id": AVALON_CONTAINER_ID,
- "name": node_name,
- "namespace": namespace,
- "loader": str(self.__class__.__name__),
- "representation": context["representation"]["id"],
- }
-
- # todo: add folder="Avalon"
- lib.imprint(container, data)
-
- return container
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
-
- # Update attributes
- node.setParms(
- {
- "filepath1": file_path,
- "representation": repre_entity["id"],
- }
- )
-
- # Reload files
- node.parm("reload").pressButton()
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_reference.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_reference.py
deleted file mode 100644
index 690f6ce187..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_reference.py
+++ /dev/null
@@ -1,87 +0,0 @@
-from ayon_core.pipeline import (
- get_representation_path,
- AVALON_CONTAINER_ID,
-)
-from ayon_houdini.api import (
- plugin,
- lib
-)
-
-
-class USDReferenceLoader(plugin.HoudiniLoader):
- """Reference USD file in Solaris"""
-
- product_types = {
- "usd",
- "usdCamera",
- }
- label = "Reference USD"
- representations = {"usd", "usda", "usdlc", "usdnc", "abc"}
- order = -8
-
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- import os
- import hou
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context)
- file_path = os.path.normpath(file_path)
- file_path = file_path.replace("\\", "/")
-
- # Get the root node
- stage = hou.node("/stage")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create USD reference
- container = stage.createNode("reference", node_name=node_name)
- container.setParms({"filepath1": file_path})
- container.moveToGoodPosition()
-
- # Imprint it manually
- data = {
- "schema": "openpype:container-2.0",
- "id": AVALON_CONTAINER_ID,
- "name": node_name,
- "namespace": namespace,
- "loader": str(self.__class__.__name__),
- "representation": context["representation"]["id"],
- }
-
- # todo: add folder="Avalon"
- lib.imprint(container, data)
-
- return container
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = file_path.replace("\\", "/")
-
- # Update attributes
- node.setParms(
- {
- "filepath1": file_path,
- "representation": repre_entity["id"],
- }
- )
-
- # Reload files
- node.parm("reload").pressButton()
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_sop.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_sop.py
deleted file mode 100644
index 347e3283de..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_usd_sop.py
+++ /dev/null
@@ -1,79 +0,0 @@
-import os
-
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class SopUsdImportLoader(plugin.HoudiniLoader):
- """Load USD to SOPs via `usdimport`"""
-
- label = "Load USD to SOPs"
- product_types = {"*"}
- representations = {"usd"}
- order = -6
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
- import hou
-
- # Format file name, Houdini only wants forward slashes
- file_path = self.filepath_from_context(context)
- file_path = os.path.normpath(file_path)
- file_path = file_path.replace("\\", "/")
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a new geo node
- container = obj.createNode("geo", node_name=node_name)
-
- # Create a usdimport node
- usdimport = container.createNode("usdimport", node_name=node_name)
- usdimport.setParms({"filepath1": file_path})
-
- # Set new position for unpack node else it gets cluttered
- nodes = [container, usdimport]
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- def update(self, container, context):
-
- node = container["node"]
- try:
- usdimport_node = next(
- n for n in node.children() if n.type().name() == "usdimport"
- )
- except StopIteration:
- self.log.error("Could not find node of type `usdimport`")
- return
-
- # Update the file path
- file_path = self.filepath_from_context(context)
- file_path = file_path.replace("\\", "/")
-
- usdimport_node.setParms({"filepath1": file_path})
-
- # Update attribute
- node.setParms({"representation": context["representation"]["id"]})
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, representation):
- self.update(container, representation)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/load_vdb.py b/server_addon/houdini/client/ayon_houdini/plugins/load/load_vdb.py
deleted file mode 100644
index 9014f4c5e2..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/load_vdb.py
+++ /dev/null
@@ -1,108 +0,0 @@
-import os
-import re
-
-from ayon_core.pipeline import get_representation_path
-from ayon_houdini.api import (
- pipeline,
- plugin
-)
-
-
-class VdbLoader(plugin.HoudiniLoader):
- """Load VDB"""
-
- product_types = {"vdbcache"}
- label = "Load VDB"
- representations = {"vdb"}
- order = -10
- icon = "code-fork"
- color = "orange"
-
- def load(self, context, name=None, namespace=None, data=None):
-
- import hou
-
- # Get the root node
- obj = hou.node("/obj")
-
- # Define node name
- namespace = namespace if namespace else context["folder"]["name"]
- node_name = "{}_{}".format(namespace, name) if namespace else name
-
- # Create a new geo node
- container = obj.createNode("geo", node_name=node_name)
-
- # Remove the file node, it only loads static meshes
- # Houdini 17 has removed the file node from the geo node
- file_node = container.node("file1")
- if file_node:
- file_node.destroy()
-
- # Explicitly create a file node
- file_node = container.createNode("file", node_name=node_name)
- path = self.filepath_from_context(context)
- file_node.setParms(
- {"file": self.format_path(path, context["representation"])})
-
- # Set display on last node
- file_node.setDisplayFlag(True)
-
- nodes = [container, file_node]
- self[:] = nodes
-
- return pipeline.containerise(
- node_name,
- namespace,
- nodes,
- context,
- self.__class__.__name__,
- suffix="",
- )
-
- @staticmethod
- def format_path(path, representation):
- """Format file path correctly for single vdb or vdb sequence."""
- if not os.path.exists(path):
- raise RuntimeError("Path does not exist: %s" % path)
-
- is_sequence = bool(representation["context"].get("frame"))
- # The path is either a single file or sequence in a folder.
- if not is_sequence:
- filename = path
- else:
- filename = re.sub(r"(.*)\.(\d+)\.vdb$", "\\1.$F4.vdb", path)
-
- filename = os.path.join(path, filename)
-
- filename = os.path.normpath(filename)
- filename = filename.replace("\\", "/")
-
- return filename
-
- def update(self, container, context):
- repre_entity = context["representation"]
- node = container["node"]
- try:
- file_node = next(
- n for n in node.children() if n.type().name() == "file"
- )
- except StopIteration:
- self.log.error("Could not find node of type `alembic`")
- return
-
- # Update the file path
- file_path = get_representation_path(repre_entity)
- file_path = self.format_path(file_path, repre_entity)
-
- file_node.setParms({"file": file_path})
-
- # Update attribute
- node.setParms({"representation": repre_entity["id"]})
-
- def remove(self, container):
-
- node = container["node"]
- node.destroy()
-
- def switch(self, container, context):
- self.update(container, context)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/load/show_usdview.py b/server_addon/houdini/client/ayon_houdini/plugins/load/show_usdview.py
deleted file mode 100644
index 4e18bc038a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/load/show_usdview.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import os
-import platform
-import subprocess
-
-from ayon_core.lib.vendor_bin_utils import find_executable
-from ayon_houdini.api import plugin
-
-
-class ShowInUsdview(plugin.HoudiniLoader):
- """Open USD file in usdview"""
-
- label = "Show in usdview"
- representations = {"*"}
- product_types = {"*"}
- extensions = {"usd", "usda", "usdlc", "usdnc", "abc"}
- order = 15
-
- icon = "code-fork"
- color = "white"
-
- def load(self, context, name=None, namespace=None, data=None):
- from pathlib import Path
-
- if platform.system() == "Windows":
- executable = "usdview.bat"
- else:
- executable = "usdview"
-
- usdview = find_executable(executable)
- if not usdview:
- raise RuntimeError("Unable to find usdview")
-
- # For some reason Windows can return the path like:
- # C:/PROGRA~1/SIDEEF~1/HOUDIN~1.435/bin/usdview
- # convert to resolved path so `subprocess` can take it
- usdview = str(Path(usdview).resolve().as_posix())
-
- filepath = self.filepath_from_context(context)
- filepath = os.path.normpath(filepath)
- filepath = filepath.replace("\\", "/")
-
- if not os.path.exists(filepath):
- self.log.error("File does not exist: %s" % filepath)
- return
-
- self.log.info("Start houdini variant of usdview...")
-
- subprocess.Popen([usdview, filepath, "--renderer", "GL"])
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_active_state.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_active_state.py
deleted file mode 100644
index e09a347e9f..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_active_state.py
+++ /dev/null
@@ -1,42 +0,0 @@
-import hou
-
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectInstanceActiveState(plugin.HoudiniInstancePlugin):
- """Collect default active state for instance from its node bypass state.
-
- This is done at the very end of the CollectorOrder so that any required
- collecting of data iterating over instances (with InstancePlugin) will
- actually collect the data for when the user enables the state in the UI.
- Otherwise potentially required data might have skipped collecting.
-
- """
-
- order = pyblish.api.CollectorOrder + 0.299
- families = ["*"]
- label = "Instance Active State"
-
- def process(self, instance):
-
- # Must have node to check for bypass state
- if len(instance) == 0:
- return
-
- # Check bypass state and reverse
- active = True
- node = hou.node(instance.data.get("instance_node"))
- if hasattr(node, "isBypassed"):
- active = not node.isBypassed()
-
- # Set instance active state
- instance.data.update(
- {
- "active": active,
- # temporarily translation of `active` to `publish` till
- # issue has been resolved:
- # https://github.com/pyblish/pyblish-base/issues/307
- "publish": active,
- }
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_arnold_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_arnold_rop.py
deleted file mode 100644
index 10c6d91d26..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_arnold_rop.py
+++ /dev/null
@@ -1,168 +0,0 @@
-import os
-import re
-
-import hou
-import pyblish.api
-
-from ayon_houdini.api import colorspace, plugin
-from ayon_houdini.api.lib import (
- get_color_management_preferences,
- evalParmNoFrame
-)
-
-
-class CollectArnoldROPRenderProducts(plugin.HoudiniInstancePlugin):
- """Collect Arnold ROP Render Products
-
- Collects the instance.data["files"] for the render products.
-
- Provides:
- instance -> files
-
- """
-
- label = "Arnold ROP Render Products"
- # This specific order value is used so that
- # this plugin runs after CollectFrames
- order = pyblish.api.CollectorOrder + 0.11
- families = ["arnold_rop"]
-
- def process(self, instance):
-
- rop = hou.node(instance.data.get("instance_node"))
-
- # Collect chunkSize
- chunk_size_parm = rop.parm("chunkSize")
- if chunk_size_parm:
- chunk_size = int(chunk_size_parm.eval())
- instance.data["chunkSize"] = chunk_size
- self.log.debug("Chunk Size: %s" % chunk_size)
-
- default_prefix = evalParmNoFrame(rop, "ar_picture")
- render_products = []
-
- export_prefix = None
- export_products = []
- if instance.data["splitRender"]:
- export_prefix = evalParmNoFrame(
- rop, "ar_ass_file", pad_character="0"
- )
- beauty_export_product = self.get_render_product_name(
- prefix=export_prefix,
- suffix=None)
- export_products.append(beauty_export_product)
- self.log.debug(
- "Found export product: {}".format(beauty_export_product)
- )
- instance.data["ifdFile"] = beauty_export_product
- instance.data["exportFiles"] = list(export_products)
-
- # Default beauty AOV
- beauty_product = self.get_render_product_name(prefix=default_prefix,
- suffix=None)
- render_products.append(beauty_product)
-
- files_by_aov = {
- "": self.generate_expected_files(instance, beauty_product)
- }
-
- # Assume it's a multipartExr Render.
- multipartExr = True
-
- num_aovs = rop.evalParm("ar_aovs")
- # TODO: Check the following logic.
- # as it always assumes that all AOV are not merged.
- for index in range(1, num_aovs + 1):
- # Skip disabled AOVs
- if not rop.evalParm("ar_enable_aov{}".format(index)):
- continue
-
- if rop.evalParm("ar_aov_exr_enable_layer_name{}".format(index)):
- label = rop.evalParm("ar_aov_exr_layer_name{}".format(index))
- else:
- label = evalParmNoFrame(rop, "ar_aov_label{}".format(index))
-
- aov_product = self.get_render_product_name(default_prefix,
- suffix=label)
- render_products.append(aov_product)
- files_by_aov[label] = self.generate_expected_files(instance,
- aov_product)
-
- # Set to False as soon as we have a separated aov.
- multipartExr = False
-
- # Review Logic expects this key to exist and be True
- # if render is a multipart Exr.
- # As long as we have one AOV then multipartExr should be True.
- instance.data["multipartExr"] = multipartExr
-
- for product in render_products:
- self.log.debug("Found render product: {}".format(product))
-
- instance.data["files"] = list(render_products)
- instance.data["renderProducts"] = colorspace.ARenderProduct()
-
- # For now by default do NOT try to publish the rendered output
- instance.data["publishJobState"] = "Suspended"
- instance.data["attachTo"] = [] # stub required data
-
- if "expectedFiles" not in instance.data:
- instance.data["expectedFiles"] = list()
- instance.data["expectedFiles"].append(files_by_aov)
-
- # update the colorspace data
- colorspace_data = get_color_management_preferences()
- instance.data["colorspaceConfig"] = colorspace_data["config"]
- instance.data["colorspaceDisplay"] = colorspace_data["display"]
- instance.data["colorspaceView"] = colorspace_data["view"]
-
- def get_render_product_name(self, prefix, suffix):
- """Return the output filename using the AOV prefix and suffix"""
-
- # When AOV is explicitly defined in prefix we just swap it out
- # directly with the AOV suffix to embed it.
- # Note: ${AOV} seems to be evaluated in the parameter as %AOV%
- if "%AOV%" in prefix:
- # It seems that when some special separator characters are present
- # before the %AOV% token that Redshift will secretly remove it if
- # there is no suffix for the current product, for example:
- # foo_%AOV% -> foo.exr
- pattern = "%AOV%" if suffix else "[._-]?%AOV%"
- product_name = re.sub(pattern,
- suffix,
- prefix,
- flags=re.IGNORECASE)
- else:
- if suffix:
- # Add ".{suffix}" before the extension
- prefix_base, ext = os.path.splitext(prefix)
- product_name = prefix_base + "." + suffix + ext
- else:
- product_name = prefix
-
- return product_name
-
- def generate_expected_files(self, instance, path):
- """Create expected files in instance data"""
-
- dir = os.path.dirname(path)
- file = os.path.basename(path)
-
- if "#" in file:
- def replace(match):
- return "%0{}d".format(len(match.group()))
-
- file = re.sub("#+", replace, file)
-
- if "%" not in file:
- return path
-
- expected_files = []
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"]
-
- for i in range(int(start), (int(end) + 1)):
- expected_files.append(
- os.path.join(dir, (file % i)).replace("\\", "/"))
-
- return expected_files
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_asset_handles.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_asset_handles.py
deleted file mode 100644
index db9bde8595..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_asset_handles.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Collector plugin for frames data on ROP instances."""
-import pyblish.api
-from ayon_core.lib import BoolDef
-from ayon_core.pipeline import AYONPyblishPluginMixin
-from ayon_houdini.api import plugin
-
-
-class CollectAssetHandles(plugin.HoudiniInstancePlugin,
- AYONPyblishPluginMixin):
- """Apply folder handles.
-
- If instance does not have:
- - frameStart
- - frameEnd
- - handleStart
- - handleEnd
- But it does have:
- - frameStartHandle
- - frameEndHandle
-
- Then we will retrieve the folder's handles to compute
- the exclusive frame range and actual handle ranges.
- """
-
- # This specific order value is used so that
- # this plugin runs after CollectAnatomyInstanceData
- order = pyblish.api.CollectorOrder + 0.499
-
- label = "Collect Folder Handles"
- use_asset_handles = True
-
- def process(self, instance):
- # Only process instances without already existing handles data
- # but that do have frameStartHandle and frameEndHandle defined
- # like the data collected from CollectRopFrameRange
- if "frameStartHandle" not in instance.data:
- return
- if "frameEndHandle" not in instance.data:
- return
-
- has_existing_data = {
- "handleStart",
- "handleEnd",
- "frameStart",
- "frameEnd"
- }.issubset(instance.data)
- if has_existing_data:
- return
-
- attr_values = self.get_attr_values_from_data(instance.data)
- if attr_values.get("use_handles", self.use_asset_handles):
- folder_attributes = instance.data["folderEntity"]["attrib"]
- handle_start = folder_attributes.get("handleStart", 0)
- handle_end = folder_attributes.get("handleEnd", 0)
- else:
- handle_start = 0
- handle_end = 0
-
- frame_start = instance.data["frameStartHandle"] + handle_start
- frame_end = instance.data["frameEndHandle"] - handle_end
-
- instance.data.update({
- "handleStart": handle_start,
- "handleEnd": handle_end,
- "frameStart": frame_start,
- "frameEnd": frame_end
- })
-
- # Log debug message about the collected frame range
- if attr_values.get("use_handles", self.use_asset_handles):
- self.log.debug(
- "Full Frame range with Handles "
- "[{frame_start_handle} - {frame_end_handle}]"
- .format(
- frame_start_handle=instance.data["frameStartHandle"],
- frame_end_handle=instance.data["frameEndHandle"]
- )
- )
- else:
- self.log.debug(
- "Use handles is deactivated for this instance, "
- "start and end handles are set to 0."
- )
-
- # Log collected frame range to the user
- message = "Frame range [{frame_start} - {frame_end}]".format(
- frame_start=frame_start,
- frame_end=frame_end
- )
- if handle_start or handle_end:
- message += " with handles [{handle_start}]-[{handle_end}]".format(
- handle_start=handle_start,
- handle_end=handle_end
- )
- self.log.info(message)
-
- if instance.data.get("byFrameStep", 1.0) != 1.0:
- self.log.info(
- "Frame steps {}".format(instance.data["byFrameStep"]))
-
- # Add frame range to label if the instance has a frame range.
- label = instance.data.get("label", instance.data["name"])
- instance.data["label"] = (
- "{label} [{frame_start_handle} - {frame_end_handle}]"
- .format(
- label=label,
- frame_start_handle=instance.data["frameStartHandle"],
- frame_end_handle=instance.data["frameEndHandle"]
- )
- )
-
- @classmethod
- def get_attribute_defs(cls):
- return [
- BoolDef("use_handles",
- tooltip="Disable this if you want the publisher to"
- " ignore start and end handles specified in the"
- " folder attributes for this publish instance",
- default=cls.use_asset_handles,
- label="Use asset handles")
- ]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_cache_farm.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_cache_farm.py
deleted file mode 100644
index b7c3b55cae..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_cache_farm.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import os
-import hou
-import pyblish.api
-from ayon_houdini.api import (
- lib,
- plugin
-)
-
-
-class CollectDataforCache(plugin.HoudiniInstancePlugin):
- """Collect data for caching to Deadline."""
-
- # Run after Collect Frames
- order = pyblish.api.CollectorOrder + 0.11
- families = ["ass", "pointcache", "redshiftproxy", "vdbcache", "model"]
- targets = ["local", "remote"]
- label = "Collect Data for Cache"
-
- def process(self, instance):
- creator_attribute = instance.data["creator_attributes"]
- farm_enabled = creator_attribute["farm"]
- instance.data["farm"] = farm_enabled
- if not farm_enabled:
- self.log.debug("Caching on farm is disabled. "
- "Skipping farm collecting.")
- return
- # Why do we need this particular collector to collect the expected
- # output files from a ROP node. Don't we have a dedicated collector
- # for that yet?
- # Answer: No, we don't have a generic expected file collector.
- # Because different product types needs different logic.
- # e.g. check CollectMantraROPRenderProducts
- # and CollectKarmaROPRenderProducts
- # Collect expected files
- ropnode = hou.node(instance.data["instance_node"])
- output_parm = lib.get_output_parameter(ropnode)
- expected_filepath = output_parm.eval()
- instance.data.setdefault("files", list())
- instance.data.setdefault("expectedFiles", list())
-
- frames = instance.data.get("frames", "")
- if isinstance(frames, str):
- # single file
- instance.data["files"].append(expected_filepath)
- else:
- # list of files
- staging_dir, _ = os.path.split(expected_filepath)
- instance.data["files"].extend(
- ["{}/{}".format(staging_dir, f) for f in frames]
- )
-
- cache_files = {"cache": instance.data["files"]}
-
- instance.data.update({
- "plugin": "Houdini",
- "publish": True
- })
- instance.data["families"].append("publish.hou")
- instance.data["expectedFiles"].append(cache_files)
-
- self.log.debug("Caching on farm expected files: {}".format(instance.data["expectedFiles"]))
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_chunk_size.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_chunk_size.py
deleted file mode 100644
index cd94827ba7..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_chunk_size.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import pyblish.api
-from ayon_core.lib import NumberDef
-from ayon_core.pipeline import AYONPyblishPluginMixin
-from ayon_houdini.api import plugin
-
-
-class CollectChunkSize(plugin.HoudiniInstancePlugin,
- AYONPyblishPluginMixin):
- """Collect chunk size for cache submission to Deadline."""
-
- order = pyblish.api.CollectorOrder + 0.05
- families = ["ass", "pointcache", "vdbcache", "redshiftproxy", "model"]
- targets = ["local", "remote"]
- label = "Collect Chunk Size"
- chunk_size = 999999
-
- def process(self, instance):
- # need to get the chunk size info from the setting
- attr_values = self.get_attr_values_from_data(instance.data)
- instance.data["chunkSize"] = attr_values.get("chunkSize")
-
- @classmethod
- def get_attribute_defs(cls):
- return [
- NumberDef("chunkSize",
- minimum=1,
- maximum=999999,
- decimals=0,
- default=cls.chunk_size,
- label="Frame Per Task")
- ]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_current_file.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_current_file.py
deleted file mode 100644
index 8e339e0e04..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_current_file.py
+++ /dev/null
@@ -1,37 +0,0 @@
-import os
-import hou
-
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectHoudiniCurrentFile(plugin.HoudiniContextPlugin):
- """Inject the current working file into context"""
-
- order = pyblish.api.CollectorOrder - 0.1
- label = "Houdini Current File"
-
- def process(self, context):
- """Inject the current working file"""
-
- current_file = hou.hipFile.path()
- if not os.path.exists(current_file):
- # By default, Houdini will even point a new scene to a path.
- # However if the file is not saved at all and does not exist,
- # we assume the user never set it.
- current_file = ""
-
- elif os.path.basename(current_file) == "untitled.hip":
- # Due to even a new file being called 'untitled.hip' we are unable
- # to confirm the current scene was ever saved because the file
- # could have existed already. We will allow it if the file exists,
- # but show a warning for this edge case to clarify the potential
- # false positive.
- self.log.warning(
- "Current file is 'untitled.hip' and we are "
- "unable to detect whether the current scene is "
- "saved correctly."
- )
-
- context.data["currentFile"] = current_file
- self.log.info('Current workfile path: {}'.format(current_file))
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_farm_instances.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_farm_instances.py
deleted file mode 100644
index f14ff65518..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_farm_instances.py
+++ /dev/null
@@ -1,36 +0,0 @@
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectFarmInstances(plugin.HoudiniInstancePlugin):
- """Collect instances for farm render."""
-
- order = pyblish.api.CollectorOrder
- families = ["mantra_rop",
- "karma_rop",
- "redshift_rop",
- "arnold_rop",
- "vray_rop",
- "usdrender"]
-
- targets = ["local", "remote"]
- label = "Collect farm instances"
-
- def process(self, instance):
-
- creator_attribute = instance.data["creator_attributes"]
-
- # Collect Render Target
- if creator_attribute.get("render_target") not in {
- "farm_split", "farm"
- }:
- instance.data["farm"] = False
- instance.data["splitRender"] = False
- self.log.debug("Render on farm is disabled. "
- "Skipping farm collecting.")
- return
-
- instance.data["farm"] = True
- instance.data["splitRender"] = (
- creator_attribute.get("render_target") == "farm_split"
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_files_for_cleaning_up.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_files_for_cleaning_up.py
deleted file mode 100644
index 3ab03babf4..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_files_for_cleaning_up.py
+++ /dev/null
@@ -1,98 +0,0 @@
-import os
-from typing import List
-
-import pyblish.api
-from ayon_core.pipeline import AYONPyblishPluginMixin
-from ayon_houdini.api import plugin
-
-
-class CollectFilesForCleaningUp(plugin.HoudiniInstancePlugin,
- AYONPyblishPluginMixin):
- """Collect Files For Cleaning Up.
-
- This collector collects output files and adds them to file remove list.
-
- CAUTION:
- This collector registers exported files and
- the parent folder for deletion in `ExplicitCleanUp` plug-in.
- please refer to `ExplicitCleanUp`'s docstring for further info.
-
- Notes:
- Artists are free to change the file path in the ROP node.
-
- Farm instances will be processed on farm by other dedicated plugins
- that live in core addon e.g. `CollectRenderedFiles` plugin.
- These dedicated plugins don't support tracking and removing
- intermediate render files.
-
- Local Render instances don't track intermediate render files,
- Therefore, this plugin doesn't support removing
- intermediate render files.
-
- HDA is not added to this plugin's options in server settings.
- Cleaning up HDA products will break the scene as Houdini will no longer
- be able to find the HDA file.
- In addition,HDA plugins always save HDAs to external files.
- Therefore, Cleaning up HDA products will break the ability to go back
- to the workfile and continue on the HDA.
- """
-
- # It should run after CollectFrames and Collect Render plugins,
- # and before CollectLocalRenderInstances.
- order = pyblish.api.CollectorOrder + 0.115
-
- hosts = ["houdini"]
- families = ["*"]
- label = "Collect Files For Cleaning Up"
-
- def process(self, instance):
-
- if instance.data.get("farm"):
- self.log.debug("Should be processed on farm, skipping.")
- return
-
- files: List[str] = []
- staging_dirs: List[str] = []
- expected_files = instance.data.get("expectedFiles", [])
-
- # Prefer 'expectedFiles' over 'frames' because it usually contains more
- # output files than just a single file or single sequence of files.
- if expected_files:
- # Products with expected files
- # This can be Render products or submitted cache to farm.
- for expected in expected_files:
- # expected.values() is a list of lists
- for output_files in expected.values():
- staging_dir, _ = os.path.split(output_files[0])
- if staging_dir not in staging_dirs:
- staging_dirs.append(staging_dir)
- files.extend(output_files)
- else:
- # Products with frames or single file.
-
- frames = instance.data.get("frames")
- if frames is None:
- self.log.warning(
- f"No frames data found on instance {instance}"
- ". Skipping collection for caching on farm..."
- )
- return
-
- staging_dir = instance.data.get("stagingDir")
- staging_dirs.append(staging_dir)
-
- if isinstance(frames, str):
- # single file.
- files.append(f"{staging_dir}/{frames}")
- else:
- # list of frame.
- files.extend(
- [f"{staging_dir}/{frame}" for frame in frames]
- )
-
- self.log.debug(
- f"Add directories to 'cleanupEmptyDir': {staging_dirs}")
- instance.context.data["cleanupEmptyDirs"].extend(staging_dirs)
-
- self.log.debug("Add files to 'cleanupFullPaths': {}".format(files))
- instance.context.data["cleanupFullPaths"].extend(files)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_frames.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_frames.py
deleted file mode 100644
index a442e74835..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_frames.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Collector plugin for frames data on ROP instances."""
-import os
-import hou # noqa
-import clique
-import pyblish.api
-from ayon_houdini.api import lib, plugin
-
-
-class CollectFrames(plugin.HoudiniInstancePlugin):
- """Collect all frames which would be saved from the ROP nodes"""
-
- # This specific order value is used so that
- # this plugin runs after CollectRopFrameRange
- order = pyblish.api.CollectorOrder + 0.1
- label = "Collect Frames"
- families = ["camera", "vdbcache", "imagesequence", "ass",
- "redshiftproxy", "review", "pointcache", "fbx",
- "model"]
-
- def process(self, instance):
-
- # CollectRopFrameRange computes `start_frame` and `end_frame`
- # depending on the trange value.
- start_frame = instance.data["frameStartHandle"]
- end_frame = instance.data["frameEndHandle"]
-
- # Evaluate the file name at the first frame.
- ropnode = hou.node(instance.data["instance_node"])
- output_parm = lib.get_output_parameter(ropnode)
- output = output_parm.evalAtFrame(start_frame)
- file_name = os.path.basename(output)
-
- # todo: `frames` currently conflicts with "explicit frames" for a
- # for a custom frame list. So this should be refactored.
-
- instance.data.update({
- "frames": file_name, # Set frames to the file name by default.
- "stagingDir": os.path.dirname(output)
- })
-
- # Skip unnecessary logic if start and end frames are equal.
- if start_frame == end_frame:
- return
-
- # Create collection using frame pattern.
- # e.g. 'pointcacheBgeoCache_AB010.1001.bgeo'
- # will be
- frame_collection, _ = clique.assemble(
- [file_name],
- patterns=[clique.PATTERNS["frames"]],
- minimum_items=1
- )
-
- # Return as no frame pattern detected.
- if not frame_collection:
- return
-
- # It's always expected to be one collection.
- frame_collection = frame_collection[0]
- frame_collection.indexes.clear()
- frame_collection.indexes.update(list(range(start_frame, (end_frame + 1))))
- instance.data["frames"] = list(frame_collection)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_inputs.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_inputs.py
deleted file mode 100644
index f2904a68f6..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_inputs.py
+++ /dev/null
@@ -1,137 +0,0 @@
-from collections import deque
-
-import pyblish.api
-from ayon_core.pipeline import registered_host
-from ayon_houdini.api import plugin
-
-
-def get_container_members(container):
- node = container["node"]
- # Usually the loaded containers don't have any complex references
- # and the contained children should be all we need. So we disregard
- # checking for .references() on the nodes.
- members = set(node.allSubChildren())
- members.add(node) # include the node itself
- return members
-
-
-def collect_input_containers(containers, nodes):
- """Collect containers that contain any of the node in `nodes`.
-
- This will return any loaded Avalon container that contains at least one of
- the nodes. As such, the Avalon container is an input for it. Or in short,
- there are member nodes of that container.
-
- Returns:
- list: Loaded containers that contain the `nodes`
-
- """
- # Assume the containers have collected their cached '_members' data
- # in the collector.
- return [container for container in containers
- if any(node in container["_members"] for node in nodes)]
-
-
-def iter_upstream(node):
- """Yields all upstream inputs for the current node.
-
- This includes all `node.inputAncestors()` but also traverses through all
- `node.references()` for the node itself and for any of the upstream nodes.
- This method has no max-depth and will collect all upstream inputs.
-
- Yields:
- hou.Node: The upstream nodes, including references.
-
- """
-
- upstream = node.inputAncestors(
- include_ref_inputs=True, follow_subnets=True
- )
-
- # Initialize process queue with the node's ancestors itself
- queue = deque(upstream)
- collected = set(upstream)
-
- # Traverse upstream references for all nodes and yield them as we
- # process the queue.
- while queue:
- upstream_node = queue.pop()
- yield upstream_node
-
- # Find its references that are not collected yet.
- references = upstream_node.references()
- references = [n for n in references if n not in collected]
-
- queue.extend(references)
- collected.update(references)
-
- # Include the references' ancestors that have not been collected yet.
- for reference in references:
- if reference in collected:
- # Might have been collected in previous iteration
- continue
-
- ancestors = reference.inputAncestors(
- include_ref_inputs=True, follow_subnets=True
- )
- ancestors = [n for n in ancestors if n not in collected]
-
- queue.extend(ancestors)
- collected.update(ancestors)
-
-
-class CollectUpstreamInputs(plugin.HoudiniInstancePlugin):
- """Collect source input containers used for this publish.
-
- This will include `inputs` data of which loaded publishes were used in the
- generation of this publish. This leaves an upstream trace to what was used
- as input.
-
- """
-
- label = "Collect Inputs"
- order = pyblish.api.CollectorOrder + 0.4
-
- def process(self, instance):
- # We can't get the "inputAncestors" directly from the ROP
- # node, so we find the related output node (set in SOP/COP path)
- # and include that together with its ancestors
- output = instance.data.get("output_node")
-
- if output is None:
- # If no valid output node is set then ignore it as validation
- # will be checking those cases.
- self.log.debug(
- "No output node found, skipping collecting of inputs.."
- )
- return
-
- # For large scenes the querying of "host.ls()" can be relatively slow
- # e.g. up to a second. Many instances calling it easily slows this
- # down. As such, we cache it so we trigger it only once.
- # todo: Instead of hidden cache make "CollectContainers" plug-in
- cache_key = "__cache_containers"
- scene_containers = instance.context.data.get(cache_key, None)
- if scene_containers is None:
- # Query the scenes' containers if there's no cache yet
- host = registered_host()
- scene_containers = list(host.ls())
- for container in scene_containers:
- # Embed the members into the container dictionary
- container_members = set(get_container_members(container))
- container["_members"] = container_members
- instance.context.data[cache_key] = scene_containers
-
- inputs = []
- if scene_containers:
- # Collect all upstream parents
- nodes = list(iter_upstream(output))
- nodes.append(output)
-
- # Collect containers for the given set of nodes
- containers = collect_input_containers(scene_containers, nodes)
-
- inputs = [c["representation"] for c in containers]
-
- instance.data["inputRepresentations"] = inputs
- self.log.debug("Collected inputs: %s" % inputs)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_instances_type.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_instances_type.py
deleted file mode 100644
index 75a394a1f9..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_instances_type.py
+++ /dev/null
@@ -1,24 +0,0 @@
-"""Collector for different types.
-
-This will add additional families to different instance based on
-the creator_identifier parameter.
-"""
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectPointcacheType(plugin.HoudiniInstancePlugin):
- """Collect data type for different instances."""
-
- order = pyblish.api.CollectorOrder
- families = ["pointcache", "model"]
- label = "Collect instances types"
-
- def process(self, instance):
- if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501
- instance.data["families"] += ["bgeo"]
- elif instance.data["creator_identifier"] in {
- "io.openpype.creators.houdini.pointcache",
- "io.openpype.creators.houdini.model"
- }:
- instance.data["families"] += ["abc"]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_karma_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_karma_rop.py
deleted file mode 100644
index 60fec9d2e0..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_karma_rop.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import re
-import os
-
-import hou
-import pyblish.api
-
-from ayon_houdini.api.lib import (
- evalParmNoFrame,
- get_color_management_preferences
-)
-from ayon_houdini.api import (
- colorspace,
- plugin
-)
-
-
-class CollectKarmaROPRenderProducts(plugin.HoudiniInstancePlugin):
- """Collect Karma Render Products
-
- Collects the instance.data["files"] for the multipart render product.
-
- Provides:
- instance -> files
-
- """
-
- label = "Karma ROP Render Products"
- # This specific order value is used so that
- # this plugin runs after CollectFrames
- order = pyblish.api.CollectorOrder + 0.11
- families = ["karma_rop"]
-
- def process(self, instance):
-
- rop = hou.node(instance.data.get("instance_node"))
-
- # Collect chunkSize
- chunk_size_parm = rop.parm("chunkSize")
- if chunk_size_parm:
- chunk_size = int(chunk_size_parm.eval())
- instance.data["chunkSize"] = chunk_size
- self.log.debug("Chunk Size: %s" % chunk_size)
-
- default_prefix = evalParmNoFrame(rop, "picture")
- render_products = []
-
- # Default beauty AOV
- beauty_product = self.get_render_product_name(
- prefix=default_prefix, suffix=None
- )
- render_products.append(beauty_product)
-
- files_by_aov = {
- "beauty": self.generate_expected_files(instance,
- beauty_product)
- }
-
- # Review Logic expects this key to exist and be True
- # if render is a multipart Exr.
- # As long as we have one AOV then multipartExr should be True.
- # By default karma render is a multipart Exr.
- instance.data["multipartExr"] = True
-
- filenames = list(render_products)
- instance.data["files"] = filenames
- instance.data["renderProducts"] = colorspace.ARenderProduct()
-
- for product in render_products:
- self.log.debug("Found render product: %s" % product)
-
- if "expectedFiles" not in instance.data:
- instance.data["expectedFiles"] = list()
- instance.data["expectedFiles"].append(files_by_aov)
-
- # update the colorspace data
- colorspace_data = get_color_management_preferences()
- instance.data["colorspaceConfig"] = colorspace_data["config"]
- instance.data["colorspaceDisplay"] = colorspace_data["display"]
- instance.data["colorspaceView"] = colorspace_data["view"]
-
- def get_render_product_name(self, prefix, suffix):
- product_name = prefix
- if suffix:
- # Add ".{suffix}" before the extension
- prefix_base, ext = os.path.splitext(prefix)
- product_name = "{}.{}{}".format(prefix_base, suffix, ext)
-
- return product_name
-
- def generate_expected_files(self, instance, path):
- """Create expected files in instance data"""
-
- dir = os.path.dirname(path)
- file = os.path.basename(path)
-
- if "#" in file:
- def replace(match):
- return "%0{}d".format(len(match.group()))
-
- file = re.sub("#+", replace, file)
-
- if "%" not in file:
- return path
-
- expected_files = []
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"]
-
- for i in range(int(start), (int(end) + 1)):
- expected_files.append(
- os.path.join(dir, (file % i)).replace("\\", "/"))
-
- return expected_files
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_local_render_instances.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_local_render_instances.py
deleted file mode 100644
index 931a79535b..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_local_render_instances.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import os
-import pyblish.api
-from ayon_core.pipeline.create import get_product_name
-from ayon_core.pipeline.farm.patterning import match_aov_pattern
-from ayon_core.pipeline.publish import (
- get_plugin_settings,
- apply_plugin_settings_automatically
-)
-from ayon_houdini.api import plugin
-
-
-class CollectLocalRenderInstances(plugin.HoudiniInstancePlugin):
- """Collect instances for local render.
-
- Agnostic Local Render Collector.
- """
-
- # this plugin runs after Collect Render Products
- order = pyblish.api.CollectorOrder + 0.12
- families = ["mantra_rop",
- "karma_rop",
- "redshift_rop",
- "arnold_rop",
- "vray_rop",
- "usdrender"]
-
- label = "Collect local render instances"
-
- use_deadline_aov_filter = False
- aov_filter = {"host_name": "houdini",
- "value": [".*([Bb]eauty).*"]}
-
- @classmethod
- def apply_settings(cls, project_settings):
- # Preserve automatic settings applying logic
- settings = get_plugin_settings(plugin=cls,
- project_settings=project_settings,
- log=cls.log,
- category="houdini")
- apply_plugin_settings_automatically(cls, settings, logger=cls.log)
-
- if not cls.use_deadline_aov_filter:
- # get aov_filter from collector settings
- # and restructure it as match_aov_pattern requires.
- cls.aov_filter = {
- cls.aov_filter["host_name"]: cls.aov_filter["value"]
- }
- else:
- # get aov_filter from deadline settings
- cls.aov_filter = project_settings["deadline"]["publish"]["ProcessSubmittedJobOnFarm"]["aov_filter"]
- cls.aov_filter = {
- item["name"]: item["value"]
- for item in cls.aov_filter
- }
-
- def process(self, instance):
-
- if instance.data["farm"]:
- self.log.debug("Render on farm is enabled. "
- "Skipping local render collecting.")
- return
-
- # Create Instance for each AOV.
- context = instance.context
- expectedFiles = next(iter(instance.data["expectedFiles"]), {})
-
- product_type = "render" # is always render
- product_group = get_product_name(
- context.data["projectName"],
- context.data["taskEntity"]["name"],
- context.data["taskEntity"]["taskType"],
- context.data["hostName"],
- product_type,
- instance.data["productName"]
- )
-
- for aov_name, aov_filepaths in expectedFiles.items():
- product_name = product_group
-
- if aov_name:
- product_name = "{}_{}".format(product_name, aov_name)
-
- # Create instance for each AOV
- aov_instance = context.create_instance(product_name)
-
- # Prepare Representation for each AOV
- aov_filenames = [os.path.basename(path) for path in aov_filepaths]
- staging_dir = os.path.dirname(aov_filepaths[0])
- ext = aov_filepaths[0].split(".")[-1]
-
- # Decide if instance is reviewable
- preview = False
- if instance.data.get("multipartExr", False):
- # Add preview tag because its multipartExr.
- preview = True
- else:
- # Add Preview tag if the AOV matches the filter.
- preview = match_aov_pattern(
- "houdini", self.aov_filter, aov_filenames[0]
- )
-
- preview = preview and instance.data.get("review", False)
-
- # Support Single frame.
- # The integrator wants single files to be a single
- # filename instead of a list.
- # More info: https://github.com/ynput/ayon-core/issues/238
- if len(aov_filenames) == 1:
- aov_filenames = aov_filenames[0]
-
- aov_instance.data.update({
- # 'label': label,
- "task": instance.data["task"],
- "folderPath": instance.data["folderPath"],
- "frameStart": instance.data["frameStartHandle"],
- "frameEnd": instance.data["frameEndHandle"],
- "productType": product_type,
- "family": product_type,
- "productName": product_name,
- "productGroup": product_group,
- "families": ["render.local.hou", "review"],
- "instance_node": instance.data["instance_node"],
- "representations": [
- {
- "stagingDir": staging_dir,
- "ext": ext,
- "name": ext,
- "tags": ["review"] if preview else [],
- "files": aov_filenames,
- "frameStart": instance.data["frameStartHandle"],
- "frameEnd": instance.data["frameEndHandle"]
- }
- ]
- })
-
- # Skip integrating original render instance.
- # We are not removing it because it's used to trigger the render.
- instance.data["integrate"] = False
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_mantra_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_mantra_rop.py
deleted file mode 100644
index f7feeee63b..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_mantra_rop.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import re
-import os
-
-import hou
-import pyblish.api
-
-from ayon_houdini.api.lib import (
- evalParmNoFrame,
- get_color_management_preferences
-)
-from ayon_houdini.api import (
- colorspace,
- plugin
-)
-
-
-class CollectMantraROPRenderProducts(plugin.HoudiniInstancePlugin):
- """Collect Mantra Render Products
-
- Collects the instance.data["files"] for the render products.
-
- Provides:
- instance -> files
-
- """
-
- label = "Mantra ROP Render Products"
- # This specific order value is used so that
- # this plugin runs after CollectFrames
- order = pyblish.api.CollectorOrder + 0.11
- families = ["mantra_rop"]
-
- def process(self, instance):
-
- rop = hou.node(instance.data.get("instance_node"))
-
- # Collect chunkSize
- chunk_size_parm = rop.parm("chunkSize")
- if chunk_size_parm:
- chunk_size = int(chunk_size_parm.eval())
- instance.data["chunkSize"] = chunk_size
- self.log.debug("Chunk Size: %s" % chunk_size)
-
- default_prefix = evalParmNoFrame(rop, "vm_picture")
- render_products = []
-
- export_prefix = None
- export_products = []
- if instance.data["splitRender"]:
- export_prefix = evalParmNoFrame(
- rop, "soho_diskfile", pad_character="0"
- )
- beauty_export_product = self.get_render_product_name(
- prefix=export_prefix,
- suffix=None)
- export_products.append(beauty_export_product)
- self.log.debug(
- "Found export product: {}".format(beauty_export_product)
- )
- instance.data["ifdFile"] = beauty_export_product
- instance.data["exportFiles"] = list(export_products)
-
- # Default beauty AOV
- beauty_product = self.get_render_product_name(
- prefix=default_prefix, suffix=None
- )
- render_products.append(beauty_product)
-
- files_by_aov = {
- "beauty": self.generate_expected_files(instance,
- beauty_product)
- }
-
- # Assume it's a multipartExr Render.
- multipartExr = True
-
- # TODO: This logic doesn't take into considerations
- # cryptomatte defined in 'Images > Cryptomatte'
- aov_numbers = rop.evalParm("vm_numaux")
- if aov_numbers > 0:
- # get the filenames of the AOVs
- for i in range(1, aov_numbers + 1):
- var = rop.evalParm("vm_variable_plane%d" % i)
- if var:
- aov_name = "vm_filename_plane%d" % i
- aov_boolean = "vm_usefile_plane%d" % i
- aov_enabled = rop.evalParm(aov_boolean)
- has_aov_path = rop.evalParm(aov_name)
- if has_aov_path and aov_enabled == 1:
- aov_prefix = evalParmNoFrame(rop, aov_name)
- aov_product = self.get_render_product_name(
- prefix=aov_prefix, suffix=None
- )
- render_products.append(aov_product)
-
- files_by_aov[var] = self.generate_expected_files(instance, aov_product) # noqa
-
- # Set to False as soon as we have a separated aov.
- multipartExr = False
-
- # Review Logic expects this key to exist and be True
- # if render is a multipart Exr.
- # As long as we have one AOV then multipartExr should be True.
- instance.data["multipartExr"] = multipartExr
-
- for product in render_products:
- self.log.debug("Found render product: %s" % product)
-
- filenames = list(render_products)
- instance.data["files"] = filenames
- instance.data["renderProducts"] = colorspace.ARenderProduct()
-
- # For now by default do NOT try to publish the rendered output
- instance.data["publishJobState"] = "Suspended"
- instance.data["attachTo"] = [] # stub required data
-
- if "expectedFiles" not in instance.data:
- instance.data["expectedFiles"] = list()
- instance.data["expectedFiles"].append(files_by_aov)
-
- # update the colorspace data
- colorspace_data = get_color_management_preferences()
- instance.data["colorspaceConfig"] = colorspace_data["config"]
- instance.data["colorspaceDisplay"] = colorspace_data["display"]
- instance.data["colorspaceView"] = colorspace_data["view"]
-
- def get_render_product_name(self, prefix, suffix):
- product_name = prefix
- if suffix:
- # Add ".{suffix}" before the extension
- prefix_base, ext = os.path.splitext(prefix)
- product_name = prefix_base + "." + suffix + ext
-
- return product_name
-
- def generate_expected_files(self, instance, path):
- """Create expected files in instance data"""
-
- dir = os.path.dirname(path)
- file = os.path.basename(path)
-
- if "#" in file:
- def replace(match):
- return "%0{}d".format(len(match.group()))
-
- file = re.sub("#+", replace, file)
-
- if "%" not in file:
- return path
-
- expected_files = []
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"]
-
- for i in range(int(start), (int(end) + 1)):
- expected_files.append(
- os.path.join(dir, (file % i)).replace("\\", "/"))
-
- return expected_files
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_output_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_output_node.py
deleted file mode 100644
index ff51669376..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_output_node.py
+++ /dev/null
@@ -1,77 +0,0 @@
-import pyblish.api
-from ayon_core.pipeline.publish import KnownPublishError
-from ayon_houdini.api import plugin
-
-
-class CollectOutputSOPPath(plugin.HoudiniInstancePlugin):
- """Collect the out node's SOP/COP Path value."""
-
- order = pyblish.api.CollectorOrder
- families = [
- "pointcache",
- "camera",
- "vdbcache",
- "imagesequence",
- "usd",
- "usdrender",
- "redshiftproxy",
- "staticMesh",
- "model"
- ]
-
- label = "Collect Output Node Path"
-
- def process(self, instance):
-
- import hou
-
- node = hou.node(instance.data["instance_node"])
-
- # Get sop path
- node_type = node.type().name()
- if node_type == "geometry":
- out_node = node.parm("soppath").evalAsNode()
-
- elif node_type == "alembic":
-
- # Alembic can switch between using SOP Path or object
- if node.parm("use_sop_path").eval():
- out_node = node.parm("sop_path").evalAsNode()
- else:
- root = node.parm("root").eval()
- objects = node.parm("objects").eval()
- path = root + "/" + objects
- out_node = hou.node(path)
-
- elif node_type == "comp":
- out_node = node.parm("coppath").evalAsNode()
-
- elif node_type == "usd" or node_type == "usdrender":
- out_node = node.parm("loppath").evalAsNode()
-
- elif node_type == "usd_rop" or node_type == "usdrender_rop":
- # Inside Solaris e.g. /stage (not in ROP context)
- # When incoming connection is present it takes it directly
- inputs = node.inputs()
- if inputs:
- out_node = inputs[0]
- else:
- out_node = node.parm("loppath").evalAsNode()
-
- elif node_type == "Redshift_Proxy_Output":
- out_node = node.parm("RS_archive_sopPath").evalAsNode()
-
- elif node_type == "filmboxfbx":
- out_node = node.parm("startnode").evalAsNode()
-
- else:
- raise KnownPublishError(
- "ROP node type '{}' is not supported.".format(node_type)
- )
-
- if not out_node:
- self.log.warning("No output node collected.")
- return
-
- self.log.debug("Output node: %s" % out_node.path())
- instance.data["output_node"] = out_node
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_redshift_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_redshift_rop.py
deleted file mode 100644
index 96cb6ebeaf..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_redshift_rop.py
+++ /dev/null
@@ -1,185 +0,0 @@
-import re
-import os
-
-import hou
-import pyblish.api
-
-from ayon_houdini.api.lib import (
- evalParmNoFrame,
- get_color_management_preferences
-)
-from ayon_houdini.api import (
- colorspace,
- plugin
-)
-
-
-class CollectRedshiftROPRenderProducts(plugin.HoudiniInstancePlugin):
- """Collect USD Render Products
-
- Collects the instance.data["files"] for the render products.
-
- Provides:
- instance -> files
-
- """
-
- label = "Redshift ROP Render Products"
- # This specific order value is used so that
- # this plugin runs after CollectFrames
- order = pyblish.api.CollectorOrder + 0.11
- families = ["redshift_rop"]
-
- def process(self, instance):
- rop = hou.node(instance.data.get("instance_node"))
-
- # Collect chunkSize
- chunk_size_parm = rop.parm("chunkSize")
- if chunk_size_parm:
- chunk_size = int(chunk_size_parm.eval())
- instance.data["chunkSize"] = chunk_size
- self.log.debug("Chunk Size: %s" % chunk_size)
-
- default_prefix = evalParmNoFrame(rop, "RS_outputFileNamePrefix")
- beauty_suffix = rop.evalParm("RS_outputBeautyAOVSuffix")
-
- export_products = []
- if instance.data["splitRender"]:
- export_prefix = evalParmNoFrame(
- rop, "RS_archive_file", pad_character="0"
- )
- beauty_export_product = self.get_render_product_name(
- prefix=export_prefix,
- suffix=None)
- export_products.append(beauty_export_product)
- self.log.debug(
- "Found export product: {}".format(beauty_export_product)
- )
- instance.data["ifdFile"] = beauty_export_product
- instance.data["exportFiles"] = list(export_products)
-
- full_exr_mode = (rop.evalParm("RS_outputMultilayerMode") == "2")
- if full_exr_mode:
- # Ignore beauty suffix if full mode is enabled
- # As this is what the rop does.
- beauty_suffix = ""
-
- # Assume it's a multipartExr Render.
- multipartExr = True
-
- # Default beauty/main layer AOV
- beauty_product = self.get_render_product_name(
- prefix=default_prefix, suffix=beauty_suffix
- )
- render_products = [beauty_product]
- files_by_aov = {
- beauty_suffix: self.generate_expected_files(instance,
- beauty_product)
- }
-
- aovs_rop = rop.parm("RS_aovGetFromNode").evalAsNode()
- if aovs_rop:
- rop = aovs_rop
-
- num_aovs = 0
- if not rop.evalParm('RS_aovAllAOVsDisabled'):
- num_aovs = rop.evalParm("RS_aov")
-
- for index in range(num_aovs):
- i = index + 1
-
- # Skip disabled AOVs
- if not rop.evalParm(f"RS_aovEnable_{i}"):
- continue
-
- aov_suffix = rop.evalParm(f"RS_aovSuffix_{i}")
- aov_prefix = evalParmNoFrame(rop, f"RS_aovCustomPrefix_{i}")
- if not aov_prefix:
- aov_prefix = default_prefix
-
- if rop.parm(f"RS_aovID_{i}").evalAsString() == "CRYPTOMATTE" or \
- not full_exr_mode:
-
- aov_product = self.get_render_product_name(aov_prefix, aov_suffix)
- render_products.append(aov_product)
-
- files_by_aov[aov_suffix] = self.generate_expected_files(instance,
- aov_product) # noqa
-
- # Set to False as soon as we have a separated aov.
- multipartExr = False
-
- # Review Logic expects this key to exist and be True
- # if render is a multipart Exr.
- # As long as we have one AOV then multipartExr should be True.
- instance.data["multipartExr"] = multipartExr
-
- for product in render_products:
- self.log.debug("Found render product: %s" % product)
-
- filenames = list(render_products)
- instance.data["files"] = filenames
- instance.data["renderProducts"] = colorspace.ARenderProduct()
-
- # For now by default do NOT try to publish the rendered output
- instance.data["publishJobState"] = "Suspended"
- instance.data["attachTo"] = [] # stub required data
-
- if "expectedFiles" not in instance.data:
- instance.data["expectedFiles"] = []
- instance.data["expectedFiles"].append(files_by_aov)
-
- # update the colorspace data
- colorspace_data = get_color_management_preferences()
- instance.data["colorspaceConfig"] = colorspace_data["config"]
- instance.data["colorspaceDisplay"] = colorspace_data["display"]
- instance.data["colorspaceView"] = colorspace_data["view"]
-
- def get_render_product_name(self, prefix, suffix):
- """Return the output filename using the AOV prefix and suffix"""
-
- # When AOV is explicitly defined in prefix we just swap it out
- # directly with the AOV suffix to embed it.
- # Note: '$AOV' seems to be evaluated in the parameter as '%AOV%'
- has_aov_in_prefix = "%AOV%" in prefix
- if has_aov_in_prefix:
- # It seems that when some special separator characters are present
- # before the %AOV% token that Redshift will secretly remove it if
- # there is no suffix for the current product, for example:
- # foo_%AOV% -> foo.exr
- pattern = "%AOV%" if suffix else "[._-]?%AOV%"
- product_name = re.sub(pattern, suffix, prefix, flags=re.IGNORECASE)
- else:
- if suffix:
- # Add ".{suffix}" before the extension
- prefix_base, ext = os.path.splitext(prefix)
- product_name = prefix_base + "." + suffix + ext
- else:
- product_name = prefix
-
- return product_name
-
- def generate_expected_files(self, instance, path):
- """Create expected files in instance data"""
-
- dir = os.path.dirname(path)
- file = os.path.basename(path)
-
- if "#" in file:
- def replace(match):
- return "%0{}d".format(len(match.group()))
-
- file = re.sub("#+", replace, file)
-
- if "%" not in file:
- return path
-
- expected_files = []
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"]
-
- for i in range(int(start), (int(end) + 1)):
- expected_files.append(
- os.path.join(dir, (file % i)).replace("\\", "/"))
-
- return expected_files
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_render_products.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_render_products.py
deleted file mode 100644
index 9dea2364f8..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_render_products.py
+++ /dev/null
@@ -1,248 +0,0 @@
-import re
-import os
-
-import hou
-import pxr.UsdRender
-
-import pyblish.api
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.usd import (
- get_usd_render_rop_rendersettings
-)
-
-
-class CollectRenderProducts(plugin.HoudiniInstancePlugin):
- """Collect USD Render Products.
-
- The render products are collected from the USD Render ROP node by detecting
- what the selected Render Settings prim path is, then finding those
- Render Settings in the USD Stage and collecting the targeted Render
- Products and their expected filenames.
-
- Note: Product refers USD Render Product, not to an AYON Product
-
- """
-
- label = "Collect Render Products"
- # This plugin should run after CollectUsdRender
- # and, before CollectLocalRenderInstances
- order = pyblish.api.CollectorOrder + 0.04
- families = ["usdrender"]
-
- def process(self, instance):
-
- rop_node = hou.node(instance.data["instance_node"])
- node = instance.data.get("output_node")
- if not node:
- rop_path = rop_node.path()
- self.log.error(
- "No output node found. Make sure to connect a valid "
- "input to the USD ROP: %s" % rop_path
- )
- return
-
- override_output_image = rop_node.evalParm("outputimage")
-
- filenames = []
- files_by_product = {}
- stage = node.stage()
- for prim_path in self.get_render_products(rop_node, stage):
- prim = stage.GetPrimAtPath(prim_path)
- if not prim or not prim.IsA(pxr.UsdRender.Product):
- self.log.warning("Found invalid render product path "
- "configured in render settings that is not a "
- "Render Product prim: %s", prim_path)
- continue
-
- render_product = pxr.UsdRender.Product(prim)
- # Get Render Product Name
- if override_output_image:
- name = override_output_image
- else:
- # We force taking it from any random time sample as opposed to
- # "default" that the USD Api falls back to since that won't
- # return time sampled values if they were set per time sample.
- name = render_product.GetProductNameAttr().Get(time=0)
-
- dirname = os.path.dirname(name)
- basename = os.path.basename(name)
-
- dollarf_regex = r"(\$F([0-9]?))"
- if re.match(dollarf_regex, basename):
- # TODO: Confirm this actually is allowed USD stages and HUSK
- # Substitute $F
- def replace(match):
- """Replace $F4 with padded #."""
- padding = int(match.group(2)) if match.group(2) else 1
- return "#" * padding
-
- filename_base = re.sub(dollarf_regex, replace, basename)
- filename = os.path.join(dirname, filename_base)
- else:
- # Last group of digits in the filename before the extension
- # The frame number must always be prefixed by underscore or dot
- # Allow product names like:
- # - filename.1001.exr
- # - filename.1001.aov.exr
- # - filename.aov.1001.exr
- # - filename_1001.exr
- frame_regex = r"(.*[._])(\d+)(?!.*\d)(.*\.[A-Za-z0-9]+$)"
-
- # It may be the case that the current USD stage has stored
- # product name samples (e.g. when loading a USD file with
- # time samples) where it does not refer to e.g. $F4. And thus
- # it refers to the actual path like /path/to/frame.1001.exr
- # TODO: It would be better to maybe sample product name
- # attribute `ValueMightBeTimeVarying` and if so get it per
- # frame using `attr.Get(time=frame)` to ensure we get the
- # actual product name set at that point in time?
- # Substitute basename.0001.ext
- def replace(match):
- head, frame, tail = match.groups()
- padding = "#" * len(frame)
- return head + padding + tail
-
- filename_base = re.sub(frame_regex, replace, basename)
- filename = os.path.join(dirname, filename_base)
- filename = filename.replace("\\", "/")
-
- assert "#" in filename, (
- "Couldn't resolve render product name "
- "with frame number: %s" % name
- )
-
- filenames.append(filename)
-
- # TODO: Improve AOV name detection logic
- aov_identifier = self.get_aov_identifier(render_product)
- if aov_identifier in files_by_product:
- self.log.error(
- "Multiple render products are identified as the same AOV "
- "which means one of the two will not be ingested during"
- "publishing. AOV: '%s'", aov_identifier
- )
- self.log.warning("Skipping Render Product: %s", render_product)
-
- files_by_product[aov_identifier] = self.generate_expected_files(
- instance,
- filename
- )
-
- aov_label = f"'{aov_identifier}' aov in " if aov_identifier else ""
- self.log.debug("Render Product %s%s", aov_label, prim_path)
- self.log.debug("Product name: %s", filename)
-
- # Filenames for Deadline
- instance.data["files"] = filenames
- instance.data.setdefault("expectedFiles", []).append(files_by_product)
-
- # Farm Publishing add review logic expects this key to exist and
- # be True if render is a multipart Exr.
- # otherwise it will most probably fail the AOV filter as multipartExr
- # files mostly don't include aov name in the file path.
- # Assume multipartExr is 'True' as long as we have one AOV.
- instance.data["multipartExr"] = len(files_by_product) <= 1
-
- def get_aov_identifier(self, render_product):
- """Return the AOV identifier for a Render Product
-
- A Render Product does not really define what 'AOV' it is, it
- defines the product name (output path) and the render vars to
- include.
-
- So we need to define what in particular of a `UsdRenderProduct`
- we use to separate the AOV (and thus apply sub-grouping with).
-
- For now we'll consider any Render Product that only refers
- to a single rendervar that the rendervars prim name is the AOV
- otherwise we'll assume renderproduct to be a combined multilayer
- 'main' layer
-
- Args:
- render_product (pxr.UsdRender.Product): The Render Product
-
- Returns:
- str: The AOV identifier
-
- """
- targets = render_product.GetOrderedVarsRel().GetTargets()
- if len(targets) > 1:
- # Cryptomattes usually are combined render vars, for example:
- # - crypto_asset, crypto_asset01, crypto_asset02, crypto_asset03
- # - crypto_object, crypto_object01, etc.
- # These still refer to the same AOV so we take the common prefix
- # e.g. `crypto_asset` or `crypto` (if multiple are combined)
- if all(target.name.startswith("crypto") for target in targets):
- start = os.path.commonpath([target.name for target in targets])
- return start.rstrip("_") # remove any trailing _
-
- # Main layer
- return ""
- elif len(targets) == 1:
- # AOV for a single var
- return targets[0].name
- else:
- self.log.warning(
- f"Render product has no rendervars set: {render_product}")
- return ""
-
- def get_render_products(self, usdrender_rop, stage):
- """"The render products in the defined render settings
-
- Args:
- usdrender_rop (hou.Node): The Houdini USD Render ROP node.
- stage (pxr.Usd.Stage): The USD stage to find the render settings
- in. This is usually the stage from the LOP path the USD Render
- ROP node refers to.
-
- Returns:
- List[Sdf.Path]: Render Product paths enabled in the render settings
-
- """
- render_settings = get_usd_render_rop_rendersettings(usdrender_rop,
- stage,
- logger=self.log)
- if not render_settings:
- return []
-
- return render_settings.GetProductsRel().GetTargets()
-
- def generate_expected_files(self, instance, path):
- """Generate full sequence of expected files from a filepath.
-
- The filepath should have '#' token as placeholder for frame numbers or
- should have %04d or %d placeholders. The `#` characters indicate frame
- number and padding, e.g. #### becomes 0001 for frame 1.
-
- Args:
- instance (pyblish.api.Instance): The publish instance.
- path (str): The filepath to generate the list of output files for.
-
- Returns:
- list: Filepath per frame.
-
- """
-
- folder = os.path.dirname(path)
- filename = os.path.basename(path)
-
- if "#" in filename:
- def replace(match):
- return "%0{}d".format(len(match.group()))
-
- filename = re.sub("#+", replace, filename)
-
- if "%" not in filename:
- # Not a sequence, single file
- return path
-
- expected_files = []
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"]
-
- for frame in range(int(start), (int(end) + 1)):
- expected_files.append(
- os.path.join(folder, (filename % frame)).replace("\\", "/"))
-
- return expected_files
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_review_data.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_review_data.py
deleted file mode 100644
index cca55463e6..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_review_data.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import hou
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectHoudiniReviewData(plugin.HoudiniInstancePlugin):
- """Collect Review Data."""
-
- label = "Collect Review Data"
- # This specific order value is used so that
- # this plugin runs after CollectRopFrameRange
- # Also after CollectLocalRenderInstances
- order = pyblish.api.CollectorOrder + 0.13
- families = ["review"]
-
- def process(self, instance):
-
- # This fixes the burnin having the incorrect start/end timestamps
- # because without this it would take it from the context instead
- # which isn't the actual frame range that this instance renders.
- instance.data["handleStart"] = 0
- instance.data["handleEnd"] = 0
- instance.data["fps"] = instance.context.data["fps"]
-
- # Enable ftrack functionality
- instance.data.setdefault("families", []).append('ftrack')
-
- # Get the camera from the rop node to collect the focal length
- ropnode_path = instance.data["instance_node"]
- ropnode = hou.node(ropnode_path)
-
- # Get camera based on the instance_node type.
- camera_path = self._get_camera_path(ropnode)
- camera_node = hou.node(camera_path)
- if not camera_node:
- self.log.warning("No valid camera node found on review node: "
- "{}".format(camera_path))
- return
-
- # Collect focal length.
- focal_length_parm = camera_node.parm("focal")
- if not focal_length_parm:
- self.log.warning("No 'focal' (focal length) parameter found on "
- "camera: {}".format(camera_path))
- return
-
- if focal_length_parm.isTimeDependent():
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"] + 1
- focal_length = [
- focal_length_parm.evalAsFloatAtFrame(t)
- for t in range(int(start), int(end))
- ]
- else:
- focal_length = focal_length_parm.evalAsFloat()
-
- # Store focal length in `burninDataMembers`
- burnin_members = instance.data.setdefault("burninDataMembers", {})
- burnin_members["focalLength"] = focal_length
-
- def _get_camera_path(self, ropnode):
- """Get the camera path associated with the given rop node.
-
- This function evaluates the camera parameter according to the
- type of the given rop node.
-
- Returns:
- Union[str, None]: Camera path or None.
-
- This function can return empty string if the camera
- path is empty i.e. no camera path.
- """
-
- if ropnode.type().name() in {
- "opengl", "karma", "ifd", "arnold"
- }:
- return ropnode.parm("camera").eval()
-
- elif ropnode.type().name() == "Redshift_ROP":
- return ropnode.parm("RS_renderCamera").eval()
-
- elif ropnode.type().name() == "vray_renderer":
- return ropnode.parm("render_camera").eval()
-
- return None
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_reviewable_instances.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_reviewable_instances.py
deleted file mode 100644
index 1bc797a1c1..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_reviewable_instances.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectReviewableInstances(plugin.HoudiniInstancePlugin):
- """Collect Reviewable Instances.
-
- Basically, all instances of the specified families
- with creator_attribure["review"]
- """
-
- order = pyblish.api.CollectorOrder
- label = "Collect Reviewable Instances"
- families = ["mantra_rop",
- "karma_rop",
- "redshift_rop",
- "arnold_rop",
- "vray_rop",
- "usdrender"]
-
- def process(self, instance):
- creator_attribute = instance.data["creator_attributes"]
-
- instance.data["review"] = creator_attribute.get("review", False)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_rop_frame_range.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_rop_frame_range.py
deleted file mode 100644
index c0f8d7aef9..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_rop_frame_range.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Collector plugin for frames data on ROP instances."""
-import hou # noqa
-import pyblish.api
-from ayon_houdini.api import lib, plugin
-
-
-class CollectRopFrameRange(plugin.HoudiniInstancePlugin):
- """Collect all frames which would be saved from the ROP nodes"""
-
- order = pyblish.api.CollectorOrder
- label = "Collect RopNode Frame Range"
-
- def process(self, instance):
-
- node_path = instance.data.get("instance_node")
- if node_path is None:
- # Instance without instance node like a workfile instance
- self.log.debug(
- "No instance node found for instance: {}".format(instance)
- )
- return
-
- ropnode = hou.node(node_path)
- frame_data = lib.get_frame_data(
- ropnode, self.log
- )
-
- if not frame_data:
- return
-
- # Log debug message about the collected frame range
- self.log.debug(
- "Collected frame_data: {}".format(frame_data)
- )
-
- instance.data.update(frame_data)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_staticmesh_type.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_staticmesh_type.py
deleted file mode 100644
index 1aab655532..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_staticmesh_type.py
+++ /dev/null
@@ -1,20 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Collector for staticMesh types. """
-
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectStaticMeshType(plugin.HoudiniInstancePlugin):
- """Collect data type for fbx instance."""
-
- families = ["staticMesh"]
- label = "Collect type of staticMesh"
-
- order = pyblish.api.CollectorOrder
-
- def process(self, instance):
-
- if instance.data["creator_identifier"] == "io.openpype.creators.houdini.staticmesh.fbx": # noqa: E501
- # Marking this instance as FBX triggers the FBX extractor.
- instance.data["families"] += ["fbx"]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_layers.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_layers.py
deleted file mode 100644
index 5fa787fb39..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_layers.py
+++ /dev/null
@@ -1,158 +0,0 @@
-import copy
-import os
-import re
-
-import pyblish.api
-
-from ayon_core.pipeline.create import get_product_name
-from ayon_houdini.api import plugin
-import ayon_houdini.api.usd as usdlib
-
-import hou
-
-
-def copy_instance_data(instance_src, instance_dest, attr):
- """Copy instance data from `src` instance to `dest` instance.
-
- Examples:
- >>> copy_instance_data(instance_src, instance_dest,
- >>> attr="publish_attributes.CollectRopFrameRange")
-
- Arguments:
- instance_src (pyblish.api.Instance): Source instance to copy from
- instance_dest (pyblish.api.Instance): Target instance to copy to
- attr (str): Attribute on the source instance to copy. This can be
- a nested key joined by `.` to only copy sub entries of dictionaries
- in the source instance's data.
-
- Raises:
- KeyError: If the key does not exist on the source instance.
- AssertionError: If a parent key already exists on the destination
- instance but is not of the correct type (= is not a dict)
-
- """
-
- src_data = instance_src.data
- dest_data = instance_dest.data
- keys = attr.split(".")
- for i, key in enumerate(keys):
- if key not in src_data:
- break
-
- src_value = src_data[key]
- if i != len(key):
- dest_data = dest_data.setdefault(key, {})
- assert isinstance(dest_data, dict), "Destination must be a dict"
- src_data = src_value
- else:
- # Last iteration - assign the value
- dest_data[key] = copy.deepcopy(src_value)
-
-
-class CollectUsdLayers(plugin.HoudiniInstancePlugin):
- """Collect the USD Layers that have configured save paths."""
-
- order = pyblish.api.CollectorOrder + 0.25
- label = "Collect USD Layers"
- families = ["usdrop"]
-
- def process(self, instance):
- # TODO: Replace this with a Hidden Creator so we collect these BEFORE
- # starting the publish so the user sees them before publishing
- # - however user should not be able to individually enable/disable
- # this from the main ROP its created from?
-
- output = instance.data.get("output_node")
- if not output:
- self.log.debug("No output node found..")
- return
-
- rop_node = hou.node(instance.data["instance_node"])
-
- save_layers = []
- for layer in usdlib.get_configured_save_layers(rop_node):
-
- info = layer.rootPrims.get("HoudiniLayerInfo")
- save_path = info.customData.get("HoudiniSavePath")
- creator = info.customData.get("HoudiniCreatorNode")
-
- self.log.debug("Found configured save path: "
- "%s -> %s", layer, save_path)
-
- # Log node that configured this save path
- creator_node = hou.nodeBySessionId(creator) if creator else None
- if creator_node:
- self.log.debug(
- "Created by: %s", creator_node.path()
- )
-
- save_layers.append((layer, save_path, creator_node))
-
- # Store on the instance
- instance.data["usdConfiguredSavePaths"] = save_layers
-
- # Create configured layer instances so User can disable updating
- # specific configured layers for publishing.
- context = instance.context
- for layer, save_path, creator_node in save_layers:
- name = os.path.basename(save_path)
- layer_inst = context.create_instance(name)
-
- # include same USD ROP
- layer_inst.append(rop_node)
-
- staging_dir, fname = os.path.split(save_path)
- fname_no_ext, ext = os.path.splitext(fname)
-
- variant = fname_no_ext
-
- # Strip off any trailing version number in the form of _v[0-9]+
- variant = re.sub("_v[0-9]+$", "", variant)
-
- layer_inst.data["usd_layer"] = layer
- layer_inst.data["usd_layer_save_path"] = save_path
-
- project_name = context.data["projectName"]
- variant_base = instance.data["variant"]
- subset = get_product_name(
- project_name=project_name,
- # TODO: This should use task from `instance`
- task_name=context.data["anatomyData"]["task"]["name"],
- task_type=context.data["anatomyData"]["task"]["type"],
- host_name=context.data["hostName"],
- product_type="usd",
- variant=variant_base + "_" + variant,
- project_settings=context.data["project_settings"]
- )
-
- label = "{0} -> {1}".format(instance.data["name"], subset)
- family = "usd"
- layer_inst.data["family"] = family
- layer_inst.data["families"] = [family]
- layer_inst.data["subset"] = subset
- layer_inst.data["label"] = label
- layer_inst.data["asset"] = instance.data["asset"]
- layer_inst.data["task"] = instance.data.get("task")
- layer_inst.data["instance_node"] = instance.data["instance_node"]
- layer_inst.data["render"] = False
- layer_inst.data["output_node"] = creator_node
-
- # Inherit "use handles" from the source instance
- # TODO: Do we want to maybe copy full `publish_attributes` instead?
- copy_instance_data(
- instance, layer_inst,
- attr="publish_attributes.CollectRopFrameRange.use_handles"
- )
-
- # Allow this subset to be grouped into a USD Layer on creation
- layer_inst.data["subsetGroup"] = "USD Layer"
-
- # For now just assume the representation will get published
- representation = {
- "name": "usd",
- "ext": ext.lstrip("."),
- "stagingDir": staging_dir,
- "files": fname
- }
- layer_inst.data.setdefault("representations", []).append(
- representation)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_look_assets.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_look_assets.py
deleted file mode 100644
index 0874cef0b6..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_look_assets.py
+++ /dev/null
@@ -1,243 +0,0 @@
-import re
-
-import os
-import glob
-from typing import List, Optional
-import dataclasses
-
-import pyblish.api
-import hou
-from pxr import Sdf
-
-from ayon_houdini.api import plugin
-
-
-# Colorspace attributes differ per renderer implementation in the USD data
-# Some have dedicated input names like Arnold and Redshift, whereas others like
-# MaterialX store `colorSpace` metadata on the asset property itself.
-# See `get_colorspace` method on the plug-in for more details
-COLORSPACE_ATTRS = [
- "inputs:color_space", # Image Vop (arnold::image)
- "inputs:tex0_colorSpace", # RS Texture Vop (redshift::TextureSampler)
- # TODO: USD UV Texture VOP doesn't seem to use colorspaces from the actual
- # OCIO configuration so we skip these for now. Especially since the
- # texture is usually used for 'preview' purposes anyway.
- # "inputs:sourceColorSpace", # USD UV Texture Vop (usduvtexture::2.0)
-]
-
-
-@dataclasses.dataclass
-class Resource:
- attribute: str # property path
- source: str # unresolved source path
- files: List[str] # resolve list of files, e.g. multiple for
- color_space: str = None # colorspace of the resource
-
-
-def get_layer_property_paths(layer: Sdf.Layer) -> List[Sdf.Path]:
- """Return all property paths from a layer"""
- paths = []
-
- def collect_paths(path):
- if not path.IsPropertyPath():
- return
- paths.append(path)
-
- layer.Traverse("/", collect_paths)
-
- return paths
-
-
-class CollectUsdLookAssets(plugin.HoudiniInstancePlugin):
- """Collect all assets introduced by the look.
-
- We are looking to collect e.g. all texture resources so we can transfer
- them with the publish and write then to the publish location.
-
- If possible, we'll also try to identify the colorspace of the asset.
-
- """
- # TODO: Implement $F frame support (per frame values)
- # TODO: If input image is already a published texture or resource than
- # preferably we'd keep the link in-tact and NOT update it. We can just
- # start ignoring AYON URIs
-
- label = "Collect USD Look Assets"
- order = pyblish.api.CollectorOrder
- hosts = ["houdini"]
- families = ["look"]
-
- exclude_suffixes = [".usd", ".usda", ".usdc", ".usdz", ".abc", ".vbd"]
-
- def process(self, instance):
-
- rop: hou.RopNode = hou.node(instance.data.get("instance_node"))
- if not rop:
- return
-
- lop_node: hou.LopNode = instance.data.get("output_node")
- if not lop_node:
- return
-
- above_break_layers = set(lop_node.layersAboveLayerBreak())
-
- stage = lop_node.stage()
- layers = [
- layer for layer
- in stage.GetLayerStack(includeSessionLayers=False)
- if layer.identifier not in above_break_layers
- ]
-
- instance_resources = self.get_layer_assets(layers)
-
- # Define a relative asset remapping for the USD Extractor so that
- # any textures are remapped to their 'relative' publish path.
- # All textures will be in a relative `./resources/` folder
- remap = {}
- for resource in instance_resources:
- source = resource.source
- name = os.path.basename(source)
- remap[os.path.normpath(source)] = f"./resources/{name}"
- instance.data["assetRemap"] = remap
-
- # Store resources on instance
- resources = instance.data.setdefault("resources", [])
- for resource in instance_resources:
- resources.append(dataclasses.asdict(resource))
-
- # Log all collected textures
- # Note: It is fine for a single texture to be included more than once
- # where even one of them does not have a color space set, but the other
- # does. For example, there may be a USD UV Texture just for a GL
- # preview material which does not specify an OCIO color
- # space.
- all_files = []
- for resource in instance_resources:
- all_files.append(f"{resource.attribute}:")
-
- for filepath in resource.files:
- if resource.color_space:
- file_label = f"- {filepath} ({resource.color_space})"
- else:
- file_label = f"- {filepath}"
- all_files.append(file_label)
-
- self.log.info(
- "Collected assets:\n{}".format(
- "\n".join(all_files)
- )
- )
-
- def get_layer_assets(self, layers: List[Sdf.Layer]) -> List[Resource]:
- # TODO: Correctly resolve paths using Asset Resolver.
- # Preferably this would use one cached
- # resolver context to optimize the path resolving.
- # TODO: Fix for timesamples - if timesamples, then `.default` might
- # not be authored on the spec
-
- resources: List[Resource] = list()
- for layer in layers:
- for path in get_layer_property_paths(layer):
-
- spec = layer.GetAttributeAtPath(path)
- if not spec:
- continue
-
- if spec.typeName != "asset":
- continue
-
- asset: Sdf.AssetPath = spec.default
- base, ext = os.path.splitext(asset.path)
- if ext in self.exclude_suffixes:
- continue
-
- filepath = asset.path.replace("\\", "/")
-
- # Expand to all files of the available files on disk
- # TODO: Add support for ``
- # TODO: Add support for ``
- if "" in filepath.upper():
- pattern = re.sub(
- r"",
- # UDIM is always four digits
- "[0-9]" * 4,
- filepath,
- flags=re.IGNORECASE
- )
- files = glob.glob(pattern)
- else:
- # Single file
- files = [filepath]
-
- # Detect the colorspace of the input asset property
- colorspace = self.get_colorspace(spec)
-
- resource = Resource(
- attribute=path.pathString,
- source=asset.path,
- files=files,
- color_space=colorspace
- )
- resources.append(resource)
-
- # Sort by filepath
- resources.sort(key=lambda r: r.source)
-
- return resources
-
- def get_colorspace(self, spec: Sdf.AttributeSpec) -> Optional[str]:
- """Return colorspace for a Asset attribute spec.
-
- There is currently no USD standard on how colorspaces should be
- represented for shaders or asset properties - each renderer's material
- implementations seem to currently use their own way of specifying the
- colorspace on the shader. As such, this comes with some guesswork.
-
- Args:
- spec (Sdf.AttributeSpec): The asset type attribute to retrieve
- the colorspace for.
-
- Returns:
- Optional[str]: The colorspace for the given attribute, if any.
-
- """
- # TODO: Support Karma, V-Ray, Renderman texture colorspaces
- # Materialx image defines colorspace as custom info on the attribute
- if spec.HasInfo("colorSpace"):
- return spec.GetInfo("colorSpace")
-
- # Arnold materials define the colorspace as a separate primvar
- # TODO: Fix for timesamples - if timesamples, then `.default` might
- # not be authored on the spec
- prim_path = spec.path.GetPrimPath()
- layer = spec.layer
- for name in COLORSPACE_ATTRS:
- colorspace_property_path = prim_path.AppendProperty(name)
- colorspace_spec = layer.GetAttributeAtPath(
- colorspace_property_path
- )
- if colorspace_spec and colorspace_spec.default:
- return colorspace_spec.default
-
-
-class CollectUsdLookResourceTransfers(plugin.HoudiniInstancePlugin):
- """Define the publish direct file transfers for any found resources.
-
- This ensures that any source texture will end up in the published look
- in the `resourcesDir`.
-
- """
- label = "Collect USD Look Transfers"
- order = pyblish.api.CollectorOrder + 0.496
- hosts = ["houdini"]
- families = ["look"]
-
- def process(self, instance):
-
- resources_dir = instance.data["resourcesDir"]
- transfers = instance.data.setdefault("transfers", [])
- for resource in instance.data.get("resources", []):
- for src in resource["files"]:
- dest = os.path.join(resources_dir, os.path.basename(src))
- transfers.append((src, dest))
- self.log.debug("Registering transfer: %s -> %s", src, dest)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_render.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_render.py
deleted file mode 100644
index a6e7572a18..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_usd_render.py
+++ /dev/null
@@ -1,86 +0,0 @@
-import os
-import re
-
-import hou
-import pyblish.api
-
-from ayon_houdini.api import (
- colorspace,
- plugin
-)
-from ayon_houdini.api.lib import (
- evalParmNoFrame,
- get_color_management_preferences
-)
-
-
-class CollectUsdRender(plugin.HoudiniInstancePlugin):
- """Collect publishing data for USD Render ROP.
-
- If `rendercommand` parm is disabled (and thus no rendering triggers by the
- usd render rop) it is assumed to be a "Split Render" job where the farm
- will get an additional render job after the USD file is extracted.
-
- Provides:
- instance -> ifdFile
- instance -> colorspaceConfig
- instance -> colorspaceDisplay
- instance -> colorspaceView
-
- """
-
- label = "Collect USD Render Rop"
- order = pyblish.api.CollectorOrder
- hosts = ["houdini"]
- families = ["usdrender"]
-
- def process(self, instance):
-
- rop = hou.node(instance.data.get("instance_node"))
-
- if instance.data["splitRender"]:
- # USD file output
- lop_output = evalParmNoFrame(
- rop, "lopoutput", pad_character="#"
- )
-
- # The file is usually relative to the Output Processor's 'Save to
- # Directory' which forces all USD files to end up in that directory
- # TODO: It is possible for a user to disable this
- # TODO: When enabled I think only the basename of the `lopoutput`
- # parm is preserved, any parent folders defined are likely ignored
- folder = evalParmNoFrame(
- rop, "savetodirectory_directory", pad_character="#"
- )
-
- export_file = os.path.join(folder, lop_output)
-
- # Substitute any # characters in the name back to their $F4
- # equivalent
- def replace_to_f(match):
- number = len(match.group(0))
- if number <= 1:
- number = "" # make it just $F not $F1 or $F0
- return "$F{}".format(number)
-
- export_file = re.sub("#+", replace_to_f, export_file)
- self.log.debug(
- "Found export file: {}".format(export_file)
- )
- instance.data["ifdFile"] = export_file
-
- # The render job is not frame dependent but fully dependent on
- # the job having been completed, since the extracted file is a
- # single file.
- if "$F" not in export_file:
- instance.data["splitRenderFrameDependent"] = False
-
- # update the colorspace data
- colorspace_data = get_color_management_preferences()
- instance.data["colorspaceConfig"] = colorspace_data["config"]
- instance.data["colorspaceDisplay"] = colorspace_data["display"]
- instance.data["colorspaceView"] = colorspace_data["view"]
-
- # stub required data for Submit Publish Job publish plug-in
- instance.data["attachTo"] = []
- instance.data["renderProducts"] = colorspace.ARenderProduct()
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_vray_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_vray_rop.py
deleted file mode 100644
index 2f9c2bb18e..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_vray_rop.py
+++ /dev/null
@@ -1,154 +0,0 @@
-import re
-import os
-
-import hou
-import pyblish.api
-
-from ayon_houdini.api.lib import (
- evalParmNoFrame,
- get_color_management_preferences
-)
-from ayon_houdini.api import (
- colorspace,
- plugin
-)
-
-
-class CollectVrayROPRenderProducts(plugin.HoudiniInstancePlugin):
- """Collect Vray Render Products
-
- Collects the instance.data["files"] for the render products.
-
- Provides:
- instance -> files
-
- """
-
- label = "VRay ROP Render Products"
- # This specific order value is used so that
- # this plugin runs after CollectFrames
- order = pyblish.api.CollectorOrder + 0.11
- families = ["vray_rop"]
-
- def process(self, instance):
-
- rop = hou.node(instance.data.get("instance_node"))
-
- # Collect chunkSize
- chunk_size_parm = rop.parm("chunkSize")
- if chunk_size_parm:
- chunk_size = int(chunk_size_parm.eval())
- instance.data["chunkSize"] = chunk_size
- self.log.debug("Chunk Size: %s" % chunk_size)
-
- default_prefix = evalParmNoFrame(rop, "SettingsOutput_img_file_path")
- render_products = []
- # TODO: add render elements if render element
-
- export_prefix = None
- export_products = []
- if instance.data["splitRender"]:
- export_prefix = evalParmNoFrame(
- rop, "render_export_filepath", pad_character="0"
- )
- beauty_export_product = self.get_render_product_name(
- prefix=export_prefix,
- suffix=None)
- export_products.append(beauty_export_product)
- self.log.debug(
- "Found export product: {}".format(beauty_export_product)
- )
- instance.data["ifdFile"] = beauty_export_product
- instance.data["exportFiles"] = list(export_products)
-
- beauty_product = self.get_render_product_name(default_prefix)
- render_products.append(beauty_product)
- files_by_aov = {
- "": self.generate_expected_files(instance,
- beauty_product)}
-
- # Assume it's a multipartExr Render.
- multipartExr = True
-
- if instance.data.get("RenderElement", True):
- render_element = self.get_render_element_name(rop, default_prefix)
- if render_element:
- for aov, renderpass in render_element.items():
- render_products.append(renderpass)
- files_by_aov[aov] = self.generate_expected_files(
- instance, renderpass)
- # Set to False as soon as we have a separated aov.
- multipartExr = False
-
- # Review Logic expects this key to exist and be True
- # if render is a multipart Exr.
- # As long as we have one AOV then multipartExr should be True.
- instance.data["multipartExr"] = multipartExr
-
- for product in render_products:
- self.log.debug("Found render product: %s" % product)
- filenames = list(render_products)
- instance.data["files"] = filenames
- instance.data["renderProducts"] = colorspace.ARenderProduct()
-
- # For now by default do NOT try to publish the rendered output
- instance.data["publishJobState"] = "Suspended"
- instance.data["attachTo"] = [] # stub required data
-
- if "expectedFiles" not in instance.data:
- instance.data["expectedFiles"] = list()
- instance.data["expectedFiles"].append(files_by_aov)
- self.log.debug("expectedFiles:{}".format(files_by_aov))
-
- # update the colorspace data
- colorspace_data = get_color_management_preferences()
- instance.data["colorspaceConfig"] = colorspace_data["config"]
- instance.data["colorspaceDisplay"] = colorspace_data["display"]
- instance.data["colorspaceView"] = colorspace_data["view"]
-
- def get_render_product_name(self, prefix, suffix=""):
- """Return the beauty output filename if render element enabled
- """
- # Remove aov suffix from the product: `prefix.aov_suffix` -> `prefix`
- aov_parm = ".{}".format(suffix)
- return prefix.replace(aov_parm, "")
-
- def get_render_element_name(self, node, prefix, suffix=""):
- """Return the output filename using the AOV prefix and suffix
- """
- render_element_dict = {}
- # need a rewrite
- re_path = node.evalParm("render_network_render_channels")
- if re_path:
- node_children = hou.node(re_path).children()
- for element in node_children:
- if element.shaderName() != "vray:SettingsRenderChannels":
- aov = str(element)
- render_product = prefix.replace(suffix, aov)
- render_element_dict[aov] = render_product
- return render_element_dict
-
- def generate_expected_files(self, instance, path):
- """Create expected files in instance data"""
-
- dir = os.path.dirname(path)
- file = os.path.basename(path)
-
- if "#" in file:
- def replace(match):
- return "%0{}d".format(len(match.group()))
-
- file = re.sub("#+", replace, file)
-
- if "%" not in file:
- return path
-
- expected_files = []
- start = instance.data["frameStartHandle"]
- end = instance.data["frameEndHandle"]
-
- for i in range(int(start), (int(end) + 1)):
- expected_files.append(
- os.path.join(dir, (file % i)).replace("\\", "/"))
-
- return expected_files
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_workfile.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_workfile.py
deleted file mode 100644
index 8d0939a803..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_workfile.py
+++ /dev/null
@@ -1,34 +0,0 @@
-import os
-
-import pyblish.api
-from ayon_houdini.api import plugin
-
-class CollectWorkfile(plugin.HoudiniInstancePlugin):
- """Inject workfile representation into instance"""
-
- order = pyblish.api.CollectorOrder - 0.01
- label = "Houdini Workfile Data"
- families = ["workfile"]
-
- def process(self, instance):
-
- current_file = instance.context.data["currentFile"]
- folder, file = os.path.split(current_file)
- filename, ext = os.path.splitext(file)
-
- instance.data.update({
- "setMembers": [current_file],
- "frameStart": instance.context.data['frameStart'],
- "frameEnd": instance.context.data['frameEnd'],
- "handleStart": instance.context.data['handleStart'],
- "handleEnd": instance.context.data['handleEnd']
- })
-
- instance.data['representations'] = [{
- 'name': ext.lstrip("."),
- 'ext': ext.lstrip("."),
- 'files': file,
- "stagingDir": folder,
- }]
-
- self.log.debug('Collected workfile instance: {}'.format(file))
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_workscene_fps.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_workscene_fps.py
deleted file mode 100644
index 0091eb0abb..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/collect_workscene_fps.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import hou
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class CollectWorksceneFPS(plugin.HoudiniContextPlugin):
- """Get the FPS of the work scene."""
-
- label = "Workscene FPS"
- order = pyblish.api.CollectorOrder
-
- def process(self, context):
- fps = hou.fps()
- self.log.info("Workscene FPS: %s" % fps)
- context.data.update({"fps": fps})
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_active_view_thumbnail.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_active_view_thumbnail.py
deleted file mode 100644
index e85df4ee81..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_active_view_thumbnail.py
+++ /dev/null
@@ -1,59 +0,0 @@
-import tempfile
-import pyblish.api
-
-from ayon_core.pipeline import OptionalPyblishPluginMixin
-from ayon_houdini.api import lib, plugin
-from ayon_houdini.api.pipeline import IS_HEADLESS
-
-
-class ExtractActiveViewThumbnail(plugin.HoudiniExtractorPlugin,
- OptionalPyblishPluginMixin):
- """Set instance thumbnail to a screengrab of current active viewport.
-
- This makes it so that if an instance does not have a thumbnail set yet that
- it will get a thumbnail of the currently active view at the time of
- publishing as a fallback.
-
- """
- order = pyblish.api.ExtractorOrder + 0.49
- label = "Extract Active View Thumbnail"
- families = ["workfile"]
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
-
- if IS_HEADLESS:
- self.log.debug(
- "Skip extraction of active view thumbnail, due to being in"
- "headless mode."
- )
- return
-
- thumbnail = instance.data.get("thumbnailPath")
- if thumbnail:
- # A thumbnail was already set for this instance
- return
-
- view_thumbnail = self.get_view_thumbnail(instance)
- if not view_thumbnail:
- return
- self.log.debug("Setting instance thumbnail path to: {}"
- .format(view_thumbnail)
- )
- instance.data["thumbnailPath"] = view_thumbnail
-
- def get_view_thumbnail(self, instance):
-
- sceneview = lib.get_scene_viewer()
- if sceneview is None:
- self.log.debug("Skipping Extract Active View Thumbnail"
- " because no scene view was detected.")
- return
-
- with tempfile.NamedTemporaryFile("w", suffix=".jpg", delete=False) as tmp:
- lib.sceneview_snapshot(sceneview, tmp.name)
- thumbnail_path = tmp.name
-
- instance.context.data["cleanupFullPaths"].append(thumbnail_path)
- return thumbnail_path
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_hda.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_hda.py
deleted file mode 100644
index e4449d11f8..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_hda.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-from pprint import pformat
-import hou
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class ExtractHDA(plugin.HoudiniExtractorPlugin):
-
- order = pyblish.api.ExtractorOrder
- label = "Extract HDA"
- families = ["hda"]
-
- def process(self, instance):
- self.log.info(pformat(instance.data))
- hda_node = hou.node(instance.data.get("instance_node"))
- hda_def = hda_node.type().definition()
- hda_options = hda_def.options()
- hda_options.setSaveInitialParmsAndContents(True)
-
- next_version = instance.data["anatomyData"]["version"]
- self.log.info("setting version: {}".format(next_version))
- hda_def.setVersion(str(next_version))
- hda_def.setOptions(hda_options)
- hda_def.save(hda_def.libraryFilePath(), hda_node, hda_options)
-
- if "representations" not in instance.data:
- instance.data["representations"] = []
-
- file = os.path.basename(hda_def.libraryFilePath())
- staging_dir = os.path.dirname(hda_def.libraryFilePath())
- self.log.info("Using HDA from {}".format(hda_def.libraryFilePath()))
-
- representation = {
- 'name': 'hda',
- 'ext': 'hda',
- 'files': file,
- "stagingDir": staging_dir,
- }
- instance.data["representations"].append(representation)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_render.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_render.py
deleted file mode 100644
index c7ec7603f4..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_render.py
+++ /dev/null
@@ -1,85 +0,0 @@
-import os
-import hou
-
-import pyblish.api
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.lib import render_rop
-
-
-class ExtractRender(plugin.HoudiniExtractorPlugin):
-
- order = pyblish.api.ExtractorOrder
- label = "Extract Render"
- families = ["mantra_rop",
- "karma_rop",
- "redshift_rop",
- "arnold_rop",
- "vray_rop",
- "usdrender"]
-
- def process(self, instance):
- creator_attribute = instance.data["creator_attributes"]
- product_type = instance.data["productType"]
- rop_node = hou.node(instance.data.get("instance_node"))
-
- # TODO: This section goes against pyblish concepts where
- # pyblish plugins should change the state of the scene.
- # However, in ayon publisher tool users can have options and
- # these options should some how synced with the houdini nodes.
- # More info: https://github.com/ynput/ayon-core/issues/417
-
- # Align split parameter value on rop node to the render target.
- if instance.data["splitRender"]:
- if product_type == "arnold_rop":
- rop_node.setParms({"ar_ass_export_enable": 1})
- elif product_type == "mantra_rop":
- rop_node.setParms({"soho_outputmode": 1})
- elif product_type == "redshift_rop":
- rop_node.setParms({"RS_archive_enable": 1})
- elif product_type == "vray_rop":
- rop_node.setParms({"render_export_mode": "2"})
- elif product_type == "usdrender":
- rop_node.setParms({"runcommand": 0})
- else:
- if product_type == "arnold_rop":
- rop_node.setParms({"ar_ass_export_enable": 0})
- elif product_type == "mantra_rop":
- rop_node.setParms({"soho_outputmode": 0})
- elif product_type == "redshift_rop":
- rop_node.setParms({"RS_archive_enable": 0})
- elif product_type == "vray_rop":
- rop_node.setParms({"render_export_mode": "1"})
- elif product_type == "usdrender":
- rop_node.setParms({"runcommand": 1})
-
- if instance.data.get("farm"):
- self.log.debug("Render should be processed on farm, skipping local render.")
- return
-
- if creator_attribute.get("render_target") == "local":
- ropnode = hou.node(instance.data.get("instance_node"))
- render_rop(ropnode)
-
- # `ExpectedFiles` is a list that includes one dict.
- expected_files = instance.data["expectedFiles"][0]
- # Each key in that dict is a list of files.
- # Combine lists of files into one big list.
- all_frames = []
- for value in expected_files.values():
- if isinstance(value, str):
- all_frames.append(value)
- elif isinstance(value, list):
- all_frames.extend(value)
- # Check missing frames.
- # Frames won't exist if user cancels the render.
- missing_frames = [
- frame
- for frame in all_frames
- if not os.path.exists(frame)
- ]
- if missing_frames:
- # TODO: Use user friendly error reporting.
- raise RuntimeError("Failed to complete render extraction. "
- "Missing output files: {}".format(
- missing_frames))
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_rop.py
deleted file mode 100644
index 62a38c0b93..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_rop.py
+++ /dev/null
@@ -1,150 +0,0 @@
-import os
-import hou
-
-import pyblish.api
-
-from ayon_core.pipeline import publish
-from ayon_houdini.api import plugin
-from ayon_houdini.api.lib import render_rop, splitext
-
-
-class ExtractROP(plugin.HoudiniExtractorPlugin):
- """Generic Extractor for any ROP node."""
- label = "Extract ROP"
- order = pyblish.api.ExtractorOrder
-
- families = ["abc", "camera", "bgeo", "pointcache", "fbx",
- "vdbcache", "ass", "redshiftproxy", "mantraifd"]
- targets = ["local", "remote"]
-
- def process(self, instance: pyblish.api.Instance):
- if instance.data.get("farm"):
- self.log.debug("Should be processed on farm, skipping.")
- return
-
- rop_node = hou.node(instance.data["instance_node"])
-
- files = instance.data["frames"]
- first_file = files[0] if isinstance(files, (list, tuple)) else files
- _, ext = splitext(
- first_file, allowed_multidot_extensions=[
- ".ass.gz", ".bgeo.sc", ".bgeo.gz",
- ".bgeo.lzma", ".bgeo.bz2"]
- )
- ext = ext.lstrip(".")
-
- self.log.debug(f"Rendering {rop_node.path()} to {first_file}..")
-
- render_rop(rop_node)
- self.validate_expected_frames(instance)
-
- # In some cases representation name is not the the extension
- # TODO: Preferably we remove this very specific naming
- product_type = instance.data["productType"]
- name = {
- "bgeo": "bgeo",
- "rs": "rs",
- "ass": "ass"
- }.get(product_type, ext)
-
- representation = {
- "name": name,
- "ext": ext,
- "files": instance.data["frames"],
- "stagingDir": instance.data["stagingDir"],
- "frameStart": instance.data["frameStartHandle"],
- "frameEnd": instance.data["frameEndHandle"],
- }
- self.update_representation_data(instance, representation)
- instance.data.setdefault("representations", []).append(representation)
-
- def validate_expected_frames(self, instance: pyblish.api.Instance):
- """
- Validate all expected files in `instance.data["frames"]` exist in
- the staging directory.
- """
- filenames = instance.data["frames"]
- staging_dir = instance.data["stagingDir"]
- if isinstance(filenames, str):
- # Single frame
- filenames = [filenames]
-
- missing_filenames = [
- filename for filename in filenames
- if not os.path.isfile(os.path.join(staging_dir, filename))
- ]
- if missing_filenames:
- raise RuntimeError(f"Missing frames: {missing_filenames}")
-
- def update_representation_data(self,
- instance: pyblish.api.Instance,
- representation: dict):
- """Allow subclass to override the representation data in-place"""
- pass
-
-
-class ExtractOpenGL(ExtractROP,
- publish.ColormanagedPyblishPluginMixin):
-
- order = pyblish.api.ExtractorOrder - 0.01
- label = "Extract OpenGL"
- families = ["review"]
-
- def process(self, instance):
- # This plugin is triggered when marking render as reviewable.
- # Therefore, this plugin will run over wrong instances.
- # TODO: Don't run this plugin on wrong instances.
- # This plugin should run only on review product type
- # with instance node of opengl type.
- instance_node = instance.data.get("instance_node")
- if not instance_node:
- self.log.debug("Skipping instance without instance node.")
- return
-
- rop_node = hou.node(instance_node)
- if rop_node.type().name() != "opengl":
- self.log.debug("Skipping OpenGl extraction. Rop node {} "
- "is not an OpenGl node.".format(rop_node.path()))
- return
-
- super(ExtractOpenGL, self).process(instance)
-
- def update_representation_data(self,
- instance: pyblish.api.Instance,
- representation: dict):
- tags = ["review"]
- if not instance.data.get("keepImages"):
- tags.append("delete")
-
- representation.update({
- # TODO: Avoid this override?
- "name": instance.data["imageFormat"],
- "ext": instance.data["imageFormat"],
-
- "tags": tags,
- "preview": True,
- "camera_name": instance.data.get("review_camera")
- })
-
-
-class ExtractComposite(ExtractROP,
- publish.ColormanagedPyblishPluginMixin):
-
- label = "Extract Composite (Image Sequence)"
- families = ["imagesequence"]
-
- def update_representation_data(self,
- instance: pyblish.api.Instance,
- representation: dict):
-
- if representation["ext"].lower() != "exr":
- return
-
- # Inject colorspace with 'scene_linear' as that's the
- # default Houdini working colorspace and all extracted
- # OpenEXR images should be in that colorspace.
- # https://www.sidefx.com/docs/houdini/render/linear.html#image-formats
- self.set_representation_colorspace(
- representation, instance.context,
- colorspace="scene_linear"
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_usd.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_usd.py
deleted file mode 100644
index e8e7d6a583..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/extract_usd.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import os
-from typing import List, AnyStr
-
-import pyblish.api
-
-from ayon_core.pipeline.publish.lib import get_instance_expected_output_path
-from ayon_houdini.api import plugin
-from ayon_houdini.api.lib import render_rop
-from ayon_houdini.api.usd import remap_paths
-
-import hou
-
-
-class ExtractUSD(plugin.HoudiniExtractorPlugin):
-
- order = pyblish.api.ExtractorOrder
- label = "Extract USD"
- families = ["usdrop"]
-
- def process(self, instance):
-
- ropnode = hou.node(instance.data.get("instance_node"))
-
- # Get the filename from the filename parameter
- output = ropnode.evalParm("lopoutput")
- staging_dir = os.path.dirname(output)
- instance.data["stagingDir"] = staging_dir
- file_name = os.path.basename(output)
-
- self.log.info("Writing USD '%s' to '%s'" % (file_name, staging_dir))
-
- mapping = self.get_source_to_publish_paths(instance.context)
-
- # Allow instance-specific path remapping overrides, e.g. changing
- # paths on used resources/textures for looks
- instance_mapping = instance.data.get("assetRemap", {})
- if instance_mapping:
- self.log.debug("Instance-specific asset path remapping:\n"
- f"{instance_mapping}")
- mapping.update(instance_mapping)
-
- with remap_paths(ropnode, mapping):
- render_rop(ropnode)
-
- assert os.path.exists(output), "Output does not exist: %s" % output
-
- if "representations" not in instance.data:
- instance.data["representations"] = []
-
- representation = {
- 'name': 'usd',
- 'ext': 'usd',
- 'files': file_name,
- "stagingDir": staging_dir,
- }
- instance.data["representations"].append(representation)
-
- def get_source_to_publish_paths(self, context):
- """Define a mapping of all current instances in context from source
- file to publish file so this can be used on the USD save to remap
- asset layer paths on publish via AyonRemapPaths output processor"""
-
- mapping = {}
- for instance in context:
- if not instance.data.get("active", True):
- continue
-
- if not instance.data.get("publish", True):
- continue
-
- for repre in instance.data.get("representations", []):
- name = repre.get("name")
- ext = repre.get("ext")
-
- # TODO: The remapping might need to get more involved if the
- # asset paths that are set use e.g. $F
- # TODO: If the representation has multiple files we might need
- # to define the path remapping per file of the sequence
- path = get_instance_expected_output_path(
- instance, representation_name=name, ext=ext
- )
- for source_path in get_source_paths(instance, repre):
- source_path = os.path.normpath(source_path)
- mapping[source_path] = path
-
- return mapping
-
-
-def get_source_paths(
- instance: pyblish.api.Instance,
- repre: dict
-) -> List[AnyStr]:
- """Return the full source filepaths for an instance's representations"""
-
- staging = repre.get("stagingDir", instance.data.get("stagingDir"))
- files = repre.get("files", [])
- if isinstance(files, list):
- return [os.path.join(staging, fname) for fname in files]
- elif isinstance(files, str):
- # Single file
- return [os.path.join(staging, files)]
-
- raise TypeError(f"Unsupported type for representation files: {files} "
- "(supports list or str)")
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/help/validate_vdb_output_node.xml b/server_addon/houdini/client/ayon_houdini/plugins/publish/help/validate_vdb_output_node.xml
deleted file mode 100644
index 8aac9a6a07..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/help/validate_vdb_output_node.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-Invalid VDB
-
-## Invalid VDB output
-
-All primitives of the output geometry must be VDBs, no other primitive
-types are allowed. That means that regardless of the amount of VDBs in the
-geometry it will have an equal amount of VDBs, points, primitives and
-vertices since each VDB primitive is one point, one vertex and one VDB.
-
-This validation only checks the geometry on the first frame of the export
-frame range.
-
-
-
-
-
-### Detailed Info
-
-ROP node `{rop_path}` is set to export SOP path `{sop_path}`.
-
-{message}
-
-
-
-
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/increment_current_file.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/increment_current_file.py
deleted file mode 100644
index 878500f605..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/increment_current_file.py
+++ /dev/null
@@ -1,54 +0,0 @@
-import pyblish.api
-
-from ayon_core.lib import version_up
-from ayon_core.pipeline import registered_host
-from ayon_core.pipeline.publish import (
- get_errored_plugins_from_context,
- KnownPublishError
-)
-
-from ayon_houdini.api import plugin
-
-
-class IncrementCurrentFile(plugin.HoudiniContextPlugin):
- """Increment the current file.
-
- Saves the current scene with an increased version number.
-
- """
-
- label = "Increment current file"
- order = pyblish.api.IntegratorOrder + 9.0
- families = ["workfile",
- "usdrender",
- "mantra_rop",
- "karma_rop",
- "redshift_rop",
- "arnold_rop",
- "vray_rop",
- "render.local.hou",
- "publish.hou"]
- optional = True
-
- def process(self, context):
-
- errored_plugins = get_errored_plugins_from_context(context)
- if any(
- plugin.__name__ == "HoudiniSubmitPublishDeadline"
- for plugin in errored_plugins
- ):
- raise KnownPublishError(
- "Skipping incrementing current file because "
- "submission to deadline failed."
- )
-
- # Filename must not have changed since collecting
- host = registered_host()
- current_file = host.current_file()
- if context.data["currentFile"] != current_file:
- raise KnownPublishError(
- "Collected filename mismatches from current scene name."
- )
-
- new_filepath = version_up(current_file)
- host.save_workfile(new_filepath)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/save_scene.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/save_scene.py
deleted file mode 100644
index e0734da5d1..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/save_scene.py
+++ /dev/null
@@ -1,27 +0,0 @@
-import pyblish.api
-
-from ayon_core.pipeline import registered_host
-
-from ayon_houdini.api import plugin
-
-
-class SaveCurrentScene(plugin.HoudiniContextPlugin):
- """Save current scene"""
-
- label = "Save current file"
- order = pyblish.api.ExtractorOrder - 0.49
-
- def process(self, context):
-
- # Filename must not have changed since collecting
- host = registered_host()
- current_file = host.get_current_workfile()
- assert context.data['currentFile'] == current_file, (
- "Collected filename from current scene name."
- )
-
- if host.workfile_has_unsaved_changes():
- self.log.info("Saving current file: {}".format(current_file))
- host.save_workfile(current_file)
- else:
- self.log.debug("No unsaved changes, skipping file save..")
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_abc_primitive_to_detail.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_abc_primitive_to_detail.py
deleted file mode 100644
index 51885a963e..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_abc_primitive_to_detail.py
+++ /dev/null
@@ -1,150 +0,0 @@
-# -*- coding: utf-8 -*-
-from collections import defaultdict
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateAbcPrimitiveToDetail(plugin.HoudiniInstancePlugin):
- """Validate Alembic ROP Primitive to Detail attribute is consistent.
-
- The Alembic ROP crashes Houdini whenever an attribute in the "Primitive to
- Detail" parameter exists on only a part of the primitives that belong to
- the same hierarchy path. Whenever it encounters inconsistent values,
- specifically where some are empty as opposed to others then Houdini
- crashes. (Tested in Houdini 17.5.229)
-
- """
-
- order = pyblish.api.ValidatorOrder + 0.1
- families = ["abc"]
- label = "Validate Primitive to Detail (Abc)"
-
- def process(self, instance):
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- ("Primitives found with inconsistent primitive "
- "to detail attributes. See log."),
- title=self.label
- )
-
- @classmethod
- def get_invalid(cls, instance):
- import hou # noqa
- output_node = instance.data.get("output_node")
- rop_node = hou.node(instance.data["instance_node"])
- if output_node is None:
- cls.log.error(
- "SOP Output node in '%s' does not exist. "
- "Ensure a valid SOP output path is set." % rop_node.path()
- )
-
- return [rop_node.path()]
-
- pattern = rop_node.parm("prim_to_detail_pattern").eval().strip()
- if not pattern:
- cls.log.debug(
- "Alembic ROP has no 'Primitive to Detail' pattern. "
- "Validation is ignored.."
- )
- return
-
- build_from_path = rop_node.parm("build_from_path").eval()
- if not build_from_path:
- cls.log.debug(
- "Alembic ROP has 'Build from Path' disabled. "
- "Validation is ignored.."
- )
- return
-
- path_attr = rop_node.parm("path_attrib").eval()
- if not path_attr:
- cls.log.error(
- "The Alembic ROP node has no Path Attribute"
- "value set, but 'Build Hierarchy from Attribute'"
- "is enabled."
- )
- return [rop_node.path()]
-
- # Let's assume each attribute is explicitly named for now and has no
- # wildcards for Primitive to Detail. This simplifies the check.
- cls.log.debug("Checking Primitive to Detail pattern: %s" % pattern)
- cls.log.debug("Checking with path attribute: %s" % path_attr)
-
- if not hasattr(output_node, "geometry"):
- # In the case someone has explicitly set an Object
- # node instead of a SOP node in Geometry context
- # then for now we ignore - this allows us to also
- # export object transforms.
- cls.log.warning("No geometry output node found, skipping check..")
- return
-
- # Check if the primitive attribute exists
- frame = instance.data.get("frameStart", 0)
- geo = output_node.geometryAtFrame(frame)
-
- # If there are no primitives on the start frame then it might be
- # something that is emitted over time. As such we can't actually
- # validate whether the attributes exist, because they won't exist
- # yet. In that case, just warn the user and allow it.
- if len(geo.iterPrims()) == 0:
- cls.log.warning(
- "No primitives found on current frame. Validation"
- " for Primitive to Detail will be skipped."
- )
- return
-
- attrib = geo.findPrimAttrib(path_attr)
- if not attrib:
- cls.log.info(
- "Geometry Primitives are missing "
- "path attribute: `%s`" % path_attr
- )
- return [output_node.path()]
-
- # Ensure at least a single string value is present
- if not attrib.strings():
- cls.log.info(
- "Primitive path attribute has no "
- "string values: %s" % path_attr
- )
- return [output_node.path()]
-
- paths = None
- for attr in pattern.split(" "):
- if not attr.strip():
- # Ignore empty values
- continue
-
- # Check if the primitive attribute exists
- attrib = geo.findPrimAttrib(attr)
- if not attrib:
- # It is allowed to not have the attribute at all
- continue
-
- # The issue can only happen if at least one string attribute is
- # present. So we ignore cases with no values whatsoever.
- if not attrib.strings():
- continue
-
- check = defaultdict(set)
- values = geo.primStringAttribValues(attr)
- if paths is None:
- paths = geo.primStringAttribValues(path_attr)
-
- for path, value in zip(paths, values):
- check[path].add(value)
-
- for path, values in check.items():
- # Whenever a single path has multiple values for the
- # Primitive to Detail attribute then we consider it
- # inconsistent and invalidate the ROP node's content.
- if len(values) > 1:
- cls.log.warning(
- "Path has multiple values: %s (path: %s)"
- % (list(values), path)
- )
- return [output_node.path()]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_alembic_face_sets.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_alembic_face_sets.py
deleted file mode 100644
index 00ce554ff1..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_alembic_face_sets.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-import pyblish.api
-from ayon_houdini.api import plugin
-
-
-class ValidateAlembicROPFaceSets(plugin.HoudiniInstancePlugin):
- """Validate Face Sets are disabled for extraction to pointcache.
-
- When groups are saved as Face Sets with the Alembic these show up
- as shadingEngine connections in Maya - however, with animated groups
- these connections in Maya won't work as expected, it won't update per
- frame. Additionally, it can break shader assignments in some cases
- where it requires to first break this connection to allow a shader to
- be assigned.
-
- It is allowed to include Face Sets, so only an issue is logged to
- identify that it could introduce issues down the pipeline.
-
- """
-
- order = pyblish.api.ValidatorOrder + 0.1
- families = ["abc"]
- label = "Validate Alembic ROP Face Sets"
-
- def process(self, instance):
-
- rop = hou.node(instance.data["instance_node"])
- facesets = rop.parm("facesets").eval()
-
- # 0 = No Face Sets
- # 1 = Save Non-Empty Groups as Face Sets
- # 2 = Save All Groups As Face Sets
- if facesets != 0:
- self.log.warning(
- "Alembic ROP saves 'Face Sets' for Geometry. "
- "Are you sure you want this?"
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_alembic_input_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_alembic_input_node.py
deleted file mode 100644
index aab3068171..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_alembic_input_node.py
+++ /dev/null
@@ -1,66 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateAlembicInputNode(plugin.HoudiniInstancePlugin):
- """Validate that the node connected to the output is correct.
-
- The connected node cannot be of the following types for Alembic:
- - VDB
- - Volume
-
- """
-
- order = pyblish.api.ValidatorOrder + 0.1
- families = ["abc"]
- label = "Validate Input Node (Abc)"
-
- def process(self, instance):
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- ("Primitive types found that are not supported "
- "for Alembic output."),
- title=self.label
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- invalid_prim_types = ["VDB", "Volume"]
- output_node = instance.data.get("output_node")
-
- if output_node is None:
- node = hou.node(instance.data["instance_node"])
- cls.log.error(
- "SOP Output node in '%s' does not exist. "
- "Ensure a valid SOP output path is set." % node.path()
- )
-
- return [node.path()]
-
- if not hasattr(output_node, "geometry"):
- # In the case someone has explicitly set an Object
- # node instead of a SOP node in Geometry context
- # then for now we ignore - this allows us to also
- # export object transforms.
- cls.log.warning("No geometry output node found, skipping check..")
- return
-
- frame = instance.data.get("frameStart", 0)
- geo = output_node.geometryAtFrame(frame)
-
- invalid = False
- for prim_type in invalid_prim_types:
- if geo.countPrimType(prim_type) > 0:
- cls.log.error(
- "Found a primitive which is of type '%s' !" % prim_type
- )
- invalid = True
-
- if invalid:
- return [instance]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_animation_settings.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_animation_settings.py
deleted file mode 100644
index 1cc9e24dc9..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_animation_settings.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import hou
-
-import pyblish.api
-from ayon_core.pipeline.publish import PublishValidationError
-
-from ayon_houdini.api import lib, plugin
-
-
-class ValidateAnimationSettings(plugin.HoudiniInstancePlugin):
- """Validate if the unexpanded string contains the frame ('$F') token
-
- This validator will only check the output parameter of the node if
- the Valid Frame Range is not set to 'Render Current Frame'
-
- Rules:
- If you render out a frame range it is mandatory to have the
- frame token - '$F4' or similar - to ensure that each frame gets
- written. If this is not the case you will override the same file
- every time a frame is written out.
-
- Examples:
- Good: 'my_vbd_cache.$F4.vdb'
- Bad: 'my_vbd_cache.vdb'
-
- """
-
- order = pyblish.api.ValidatorOrder
- label = "Validate Frame Settings"
- families = ["vdbcache"]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- "Output settings do no match for '%s'" % instance
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- node = hou.node(instance.data["instance_node"])
- # Check trange parm, 0 means Render Current Frame
- frame_range = node.evalParm("trange")
- if frame_range == 0:
- return []
-
- output_parm = lib.get_output_parameter(node)
- unexpanded_str = output_parm.unexpandedString()
-
- if "$F" not in unexpanded_str:
- cls.log.error("No frame token found in '%s'" % node.path())
- return [instance]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_bypass.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_bypass.py
deleted file mode 100644
index d984c63756..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_bypass.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateBypassed(plugin.HoudiniInstancePlugin):
- """Validate all primitives build hierarchy from attribute when enabled.
-
- The name of the attribute must exist on the prims and have the same name
- as Build Hierarchy from Attribute's `Path Attribute` value on the Alembic
- ROP node whenever Build Hierarchy from Attribute is enabled.
-
- """
-
- order = pyblish.api.ValidatorOrder - 0.1
- families = ["*"]
- label = "Validate ROP Bypass"
-
- def process(self, instance):
-
- if not instance.data.get("instance_node"):
- # Ignore instances without an instance node
- # e.g. in memory bootstrap instances
- self.log.debug(
- "Skipping instance without instance node: {}".format(instance)
- )
- return
-
- invalid = self.get_invalid(instance)
- if invalid:
- rop = invalid[0]
- raise PublishValidationError(
- ("ROP node {} is set to bypass, publishing cannot "
- "continue.".format(rop.path())),
- title=self.label
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- rop = hou.node(instance.data["instance_node"])
- if hasattr(rop, "isBypassed") and rop.isBypassed():
- return [rop]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_camera_rop.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_camera_rop.py
deleted file mode 100644
index f21addb11d..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_camera_rop.py
+++ /dev/null
@@ -1,61 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Validator plugin for Houdini Camera ROP settings."""
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateCameraROP(plugin.HoudiniInstancePlugin):
- """Validate Camera ROP settings."""
-
- order = pyblish.api.ValidatorOrder
- families = ["camera"]
- label = "Camera ROP"
-
- def process(self, instance):
-
- import hou
-
- node = hou.node(instance.data.get("instance_node"))
- if node.parm("use_sop_path").eval():
- raise PublishValidationError(
- ("Alembic ROP for Camera export should not be "
- "set to 'Use Sop Path'. Please disable."),
- title=self.label
- )
-
- # Get the root and objects parameter of the Alembic ROP node
- root = node.parm("root").eval()
- objects = node.parm("objects").eval()
- errors = []
- if not root:
- errors.append("Root parameter must be set on Alembic ROP")
- if not root.startswith("/"):
- errors.append("Root parameter must start with slash /")
- if not objects:
- errors.append("Objects parameter must be set on Alembic ROP")
- if len(objects.split(" ")) != 1:
- errors.append("Must have only a single object.")
-
- if errors:
- for error in errors:
- self.log.error(error)
- raise PublishValidationError(
- "Some checks failed, see validator log.",
- title=self.label)
-
- # Check if the object exists and is a camera
- path = root + "/" + objects
- camera = hou.node(path)
-
- if not camera:
- raise PublishValidationError(
- "Camera path does not exist: %s" % path,
- title=self.label)
-
- if camera.type().name() != "cam":
- raise PublishValidationError(
- ("Object set in Alembic ROP is not a camera: "
- "{} (type: {})").format(camera, camera.type().name()),
- title=self.label)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_cop_output_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_cop_output_node.py
deleted file mode 100644
index 1d63e15d90..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_cop_output_node.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateCopOutputNode(plugin.HoudiniInstancePlugin):
- """Validate the instance COP Output Node.
-
- This will ensure:
- - The COP Path is set.
- - The COP Path refers to an existing object.
- - The COP Path node is a COP node.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["imagesequence"]
- label = "Validate COP Output Node"
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- "Output node '{}' is incorrect. "
- "See plug-in log for details.".format(invalid),
- title=self.label,
- description=(
- "### Invalid COP output node\n\n"
- "The output node path for the instance must be set to a "
- "valid COP node path.\n\nSee the log for more details."
- )
- )
-
- @classmethod
- def get_invalid(cls, instance):
- output_node = instance.data.get("output_node")
-
- if not output_node:
- node = hou.node(instance.data.get("instance_node"))
- cls.log.error(
- "COP Output node in '%s' does not exist. "
- "Ensure a valid COP output path is set." % node.path()
- )
-
- return [node.path()]
-
- # Output node must be a Sop node.
- if not isinstance(output_node, hou.CopNode):
- cls.log.error(
- "Output node %s is not a COP node. "
- "COP Path must point to a COP node, "
- "instead found category type: %s",
- output_node.path(), output_node.type().category().name()
- )
- return [output_node.path()]
-
- # For the sake of completeness also assert the category type
- # is Cop2 to avoid potential edge case scenarios even though
- # the isinstance check above should be stricter than this category
- if output_node.type().category().name() != "Cop2":
- cls.log.error(
- "Output node %s is not of category Cop2.", output_node.path()
- )
- return [output_node.path()]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_export_is_a_single_frame.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_export_is_a_single_frame.py
deleted file mode 100644
index 62bc5e3b44..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_export_is_a_single_frame.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Validator for checking that export is a single frame."""
-from ayon_core.pipeline import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_core.pipeline.publish import ValidateContentsOrder
-from ayon_houdini.api.action import SelectInvalidAction
-from ayon_houdini.api import plugin
-
-
-class ValidateSingleFrame(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate Export is a Single Frame.
-
- It checks if rop node is exporting one frame.
- This is mainly for Model product type.
- """
-
- families = ["model"]
- label = "Validate Single Frame"
- order = ValidateContentsOrder + 0.1
- actions = [SelectInvalidAction]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- nodes = [n.path() for n in invalid]
- raise PublishValidationError(
- "See log for details. "
- "Invalid nodes: {0}".format(nodes)
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- invalid = []
-
- frame_start = instance.data.get("frameStartHandle")
- frame_end = instance.data.get("frameEndHandle")
-
- # This happens if instance node has no 'trange' parameter.
- if frame_start is None or frame_end is None:
- cls.log.debug(
- "No frame data, skipping check.."
- )
- return
-
- if frame_start != frame_end:
- invalid.append(instance.data["instance_node"])
- cls.log.error(
- "Invalid frame range on '%s'."
- "You should use the same frame number for 'f1' "
- "and 'f2' parameters.",
- instance.data["instance_node"].path()
- )
-
- return invalid
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_fbx_output_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_fbx_output_node.py
deleted file mode 100644
index 1c236bb8f7..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_fbx_output_node.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-from ayon_houdini.api.action import (
- SelectInvalidAction,
- SelectROPAction,
-)
-from ayon_houdini.api import plugin
-from ayon_houdini.api.lib import get_obj_node_output
-import hou
-
-
-class ValidateFBXOutputNode(plugin.HoudiniInstancePlugin):
- """Validate the instance Output Node.
-
- This will ensure:
- - The Output Node Path is set.
- - The Output Node Path refers to an existing object.
- - The Output Node is a Sop or Obj node.
- - The Output Node has geometry data.
- - The Output Node doesn't include invalid primitive types.
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["fbx"]
- label = "Validate FBX Output Node"
- actions = [SelectROPAction, SelectInvalidAction]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- nodes = [n.path() for n in invalid]
- raise PublishValidationError(
- "See log for details. "
- "Invalid nodes: {0}".format(nodes),
- title="Invalid output node(s)"
- )
-
- @classmethod
- def get_invalid(cls, instance):
- output_node = instance.data.get("output_node")
-
- # Check if The Output Node Path is set and
- # refers to an existing object.
- if output_node is None:
- rop_node = hou.node(instance.data["instance_node"])
- cls.log.error(
- "Output node in '%s' does not exist. "
- "Ensure a valid output path is set.", rop_node.path()
- )
-
- return [rop_node]
-
- # Check if the Output Node is a Sop or an Obj node
- # also, list all sop output nodes inside as well as
- # invalid empty nodes.
- all_out_sops = []
- invalid = []
-
- # if output_node is an ObjSubnet or an ObjNetwork
- if output_node.childTypeCategory() == hou.objNodeTypeCategory():
- for node in output_node.allSubChildren():
- if node.type().name() == "geo":
- out = get_obj_node_output(node)
- if out:
- all_out_sops.append(out)
- else:
- invalid.append(node) # empty_objs
- cls.log.error(
- "Geo Obj Node '%s' is empty!",
- node.path()
- )
- if not all_out_sops:
- invalid.append(output_node) # empty_objs
- cls.log.error(
- "Output Node '%s' is empty!",
- node.path()
- )
-
- # elif output_node is an ObjNode
- elif output_node.type().name() == "geo":
- out = get_obj_node_output(output_node)
- if out:
- all_out_sops.append(out)
- else:
- invalid.append(node) # empty_objs
- cls.log.error(
- "Output Node '%s' is empty!",
- node.path()
- )
-
- # elif output_node is a SopNode
- elif output_node.type().category().name() == "Sop":
- all_out_sops.append(output_node)
-
- # Then it's a wrong node type
- else:
- cls.log.error(
- "Output node %s is not a SOP or OBJ Geo or OBJ SubNet node. "
- "Instead found category type: %s %s",
- output_node.path(), output_node.type().category().name(),
- output_node.type().name()
- )
- return [output_node]
-
- # Check if all output sop nodes have geometry
- # and don't contain invalid prims
- invalid_prim_types = ["VDB", "Volume"]
- for sop_node in all_out_sops:
- # Empty Geometry test
- if not hasattr(sop_node, "geometry"):
- invalid.append(sop_node) # empty_geometry
- cls.log.error(
- "Sop node '%s' doesn't include any prims.",
- sop_node.path()
- )
- continue
-
- frame = instance.data.get("frameStart", 0)
- geo = sop_node.geometryAtFrame(frame)
- if len(geo.iterPrims()) == 0:
- invalid.append(sop_node) # empty_geometry
- cls.log.error(
- "Sop node '%s' doesn't include any prims.",
- sop_node.path()
- )
- continue
-
- # Invalid Prims test
- for prim_type in invalid_prim_types:
- if geo.countPrimType(prim_type) > 0:
- invalid.append(sop_node) # invalid_prims
- cls.log.error(
- "Sop node '%s' includes invalid prims of type '%s'.",
- sop_node.path(), prim_type
- )
-
- if invalid:
- return invalid
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_file_extension.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_file_extension.py
deleted file mode 100644
index 1b3a58f4b3..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_file_extension.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import lib, plugin
-
-
-class ValidateFileExtension(plugin.HoudiniInstancePlugin):
- """Validate the output file extension fits the output family.
-
- File extensions:
- - Pointcache must be .abc
- - Camera must be .abc
- - VDB must be .vdb
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["camera", "vdbcache"]
- label = "Output File Extension"
-
- family_extensions = {
- "camera": ".abc",
- "vdbcache": ".vdb",
- }
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- "ROP node has incorrect file extension: {}".format(invalid),
- title=self.label
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- # Get ROP node from instance
- node = hou.node(instance.data["instance_node"])
-
- # Create lookup for current family in instance
- families = []
- product_type = instance.data.get("productType")
- if product_type:
- families.append(product_type)
- families = set(families)
-
- # Perform extension check
- output = lib.get_output_parameter(node).eval()
- _, output_extension = os.path.splitext(output)
-
- for family in families:
- extension = cls.family_extensions.get(family, None)
- if extension is None:
- raise PublishValidationError(
- "Unsupported family: {}".format(family),
- title=cls.label)
-
- if output_extension != extension:
- return [node.path()]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_frame_range.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_frame_range.py
deleted file mode 100644
index 9435fa033a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_frame_range.py
+++ /dev/null
@@ -1,108 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-from ayon_core.pipeline.publish import RepairAction
-
-from ayon_houdini.api.action import SelectInvalidAction
-from ayon_houdini.api import plugin
-
-
-
-class DisableUseFolderHandlesAction(RepairAction):
- label = "Disable use folder handles"
- icon = "mdi.toggle-switch-off"
-
-
-class ValidateFrameRange(plugin.HoudiniInstancePlugin):
- """Validate Frame Range.
-
- Due to the usage of start and end handles,
- then Frame Range must be >= (start handle + end handle)
- which results that frameEnd be smaller than frameStart
- """
-
- order = pyblish.api.ValidatorOrder - 0.1
- label = "Validate Frame Range"
- actions = [DisableUseFolderHandlesAction, SelectInvalidAction]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- title="Invalid Frame Range",
- message=(
- "Invalid frame range because the instance "
- "start frame ({0[frameStart]}) is higher than "
- "the end frame ({0[frameEnd]})"
- .format(instance.data)
- ),
- description=(
- "## Invalid Frame Range\n"
- "The frame range for the instance is invalid because "
- "the start frame is higher than the end frame.\n\nThis "
- "is likely due to folder handles being applied to your "
- "instance or the ROP node's start frame "
- "is set higher than the end frame.\n\nIf your ROP frame "
- "range is correct and you do not want to apply folder "
- "handles make sure to disable Use folder handles on the "
- "publish instance."
- )
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- if not instance.data.get("instance_node"):
- return
-
- rop_node = hou.node(instance.data["instance_node"])
- frame_start = instance.data.get("frameStart")
- frame_end = instance.data.get("frameEnd")
-
- if frame_start is None or frame_end is None:
- cls.log.debug(
- "Skipping frame range validation for "
- "instance without frame data: {}".format(rop_node.path())
- )
- return
-
- if frame_start > frame_end:
- cls.log.info(
- "The ROP node render range is set to "
- "{0[frameStartHandle]} - {0[frameEndHandle]} "
- "The folder handles applied to the instance are start handle "
- "{0[handleStart]} and end handle {0[handleEnd]}"
- .format(instance.data)
- )
- return [rop_node]
-
- @classmethod
- def repair(cls, instance):
-
- if not cls.get_invalid(instance):
- # Already fixed
- return
-
- # Disable use folder handles
- context = instance.context
- create_context = context.data["create_context"]
- instance_id = instance.data.get("instance_id")
- if not instance_id:
- cls.log.debug("'{}' must have instance id"
- .format(instance))
- return
-
- created_instance = create_context.get_instance_by_id(instance_id)
- if not instance_id:
- cls.log.debug("Unable to find instance '{}' by id"
- .format(instance))
- return
-
- created_instance.publish_attributes["CollectAssetHandles"]["use_handles"] = False # noqa
-
- create_context.save_changes()
- cls.log.debug("use folder handles is turned off for '{}'"
- .format(instance))
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_frame_token.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_frame_token.py
deleted file mode 100644
index 46c02ba6f2..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_frame_token.py
+++ /dev/null
@@ -1,52 +0,0 @@
-import hou
-
-import pyblish.api
-
-from ayon_houdini.api import lib, plugin
-
-
-class ValidateFrameToken(plugin.HoudiniInstancePlugin):
- """Validate if the unexpanded string contains the frame ('$F') token.
-
- This validator will *only* check the output parameter of the node if
- the Valid Frame Range is not set to 'Render Current Frame'
-
- Rules:
- If you render out a frame range it is mandatory to have the
- frame token - '$F4' or similar - to ensure that each frame gets
- written. If this is not the case you will override the same file
- every time a frame is written out.
-
- Examples:
- Good: 'my_vbd_cache.$F4.vdb'
- Bad: 'my_vbd_cache.vdb'
-
- """
-
- order = pyblish.api.ValidatorOrder
- label = "Validate Frame Token"
- families = ["vdbcache"]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise RuntimeError(
- "Output settings do no match for '%s'" % instance
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- node = hou.node(instance.data["instance_node"])
- # Check trange parm, 0 means Render Current Frame
- frame_range = node.evalParm("trange")
- if frame_range == 0:
- return []
-
- output_parm = lib.get_output_parameter(node)
- unexpanded_str = output_parm.unexpandedString()
-
- if "$F" not in unexpanded_str:
- cls.log.error("No frame token found in '%s'" % node.path())
- return [instance]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_houdini_license_category.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_houdini_license_category.py
deleted file mode 100644
index 1639a28790..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_houdini_license_category.py
+++ /dev/null
@@ -1,41 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateHoudiniNotApprenticeLicense(plugin.HoudiniInstancePlugin):
- """Validate the Houdini instance runs a non Apprentice license.
-
- USD ROPs:
- When extracting USD files from an apprentice Houdini license,
- the resulting files will get "scrambled" with a license protection
- and get a special .usdnc suffix.
-
- This currently breaks the Subset/representation pipeline so we disallow
- any publish with apprentice license.
-
- Alembic ROPs:
- Houdini Apprentice does not export Alembic.
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrop", "abc", "fbx", "camera"]
- label = "Houdini Apprentice License"
-
- def process(self, instance):
-
- if hou.isApprentice():
- # Find which family was matched with the plug-in
- families = {instance.data["productType"]}
- families.update(instance.data.get("families", []))
- disallowed_families = families.intersection(self.families)
- families = " ".join(sorted(disallowed_families)).title()
-
- raise PublishValidationError(
- "{} publishing requires a non apprentice license."
- .format(families),
- title=self.label)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_instance_in_context.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_instance_in_context.py
deleted file mode 100644
index 092a1199b9..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_instance_in_context.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Validate if instance asset is the same as context asset."""
-from ayon_core.pipeline.publish import (
- RepairAction,
- ValidateContentsOrder,
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import SelectROPAction
-
-
-class ValidateInstanceInContextHoudini(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validator to check if instance asset match context asset.
-
- When working in per-shot style you always publish data in context of
- current asset (shot). This validator checks if this is so. It is optional
- so it can be disabled when needed.
- """
- # Similar to maya-equivalent `ValidateInstanceInContext`
-
- order = ValidateContentsOrder
- label = "Instance in same Context"
- optional = True
- actions = [SelectROPAction, RepairAction]
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
-
- attr_values = self.get_attr_values_from_data(instance.data)
- if not attr_values and not instance.data.get("instance_node"):
- # Skip instances that do not have the attr values because that
- # hints these are runtime-instances, like e.g. USD layer
- # contributions. We will confirm that by checking these do not
- # have an instance node. We do not need to check these because they
- # 'spawn off' from an original instance that has the check itself.
- return
-
- folder_path = instance.data.get("folderPath")
- task = instance.data.get("task")
- context = self.get_context(instance)
- if (folder_path, task) != context:
- context_label = "{} > {}".format(*context)
- instance_label = "{} > {}".format(folder_path, task)
-
- raise PublishValidationError(
- message=(
- "Instance '{}' publishes to different asset than current "
- "context: {}. Current context: {}".format(
- instance.name, instance_label, context_label
- )
- ),
- description=(
- "## Publishing to a different asset\n"
- "There are publish instances present which are publishing "
- "into a different asset than your current context.\n\n"
- "Usually this is not what you want but there can be cases "
- "where you might want to publish into another asset or "
- "shot. If that's the case you can disable the validation "
- "on the instance to ignore it."
- )
- )
-
- @classmethod
- def repair(cls, instance):
- context_folder, context_task = cls.get_context(instance)
-
- create_context = instance.context.data["create_context"]
- instance_id = instance.data["instance_id"]
- created_instance = create_context.get_instance_by_id(
- instance_id
- )
- created_instance["folderPath"] = context_folder
- created_instance["task"] = context_task
- create_context.save_changes()
-
- @staticmethod
- def get_context(instance):
- """Return folderPath, task from publishing context data"""
- context = instance.context
- return context.data["folderPath"], context.data["task"]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_mesh_is_static.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_mesh_is_static.py
deleted file mode 100644
index b6725bc36c..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_mesh_is_static.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Validator for correct naming of Static Meshes."""
-from ayon_core.pipeline import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_core.pipeline.publish import ValidateContentsOrder
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import SelectInvalidAction
-from ayon_houdini.api.lib import get_output_children
-
-
-class ValidateMeshIsStatic(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate mesh is static.
-
- It checks if output node is time dependent.
- this avoids getting different output from ROP node when extracted
- from a different frame than the first frame.
- (Might be overly restrictive though)
- """
-
- families = ["staticMesh",
- "model"]
- label = "Validate Mesh is Static"
- order = ValidateContentsOrder + 0.1
- actions = [SelectInvalidAction]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- nodes = [n.path() for n in invalid]
- raise PublishValidationError(
- "See log for details. "
- "Invalid nodes: {0}".format(nodes)
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- invalid = []
-
- output_node = instance.data.get("output_node")
- if output_node is None:
- cls.log.debug(
- "No Output Node, skipping check.."
- )
- return
-
- all_outputs = get_output_children(output_node)
-
- for output in all_outputs:
- if output.isTimeDependent():
- invalid.append(output)
- cls.log.error(
- "Output node '%s' is time dependent.",
- output.path()
- )
-
- return invalid
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_mkpaths_toggled.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_mkpaths_toggled.py
deleted file mode 100644
index 4573d4ba0b..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_mkpaths_toggled.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-import pyblish.api
-
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateIntermediateDirectoriesChecked(plugin.HoudiniInstancePlugin):
- """Validate Create Intermediate Directories is enabled on ROP node."""
-
- order = pyblish.api.ValidatorOrder
- families = ["pointcache", "camera", "vdbcache", "model"]
- label = "Create Intermediate Directories Checked"
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- ("Found ROP node with Create Intermediate "
- "Directories turned off: {}".format(invalid)),
- title=self.label)
-
- @classmethod
- def get_invalid(cls, instance):
-
- result = []
-
- for node in instance[:]:
- if node.parm("mkpath").eval() != 1:
- cls.log.error("Invalid settings found on `%s`" % node.path())
- result.append(node.path())
-
- return result
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_no_errors.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_no_errors.py
deleted file mode 100644
index 2afb6e5d78..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_no_errors.py
+++ /dev/null
@@ -1,77 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-def cook_in_range(node, start, end):
- current = hou.intFrame()
- if start >= current >= end:
- # Allow cooking current frame since we're in frame range
- node.cook(force=False)
- else:
- node.cook(force=False, frame_range=(start, start))
-
-
-def get_errors(node):
- """Get cooking errors.
-
- If node already has errors check whether it needs to recook
- If so, then recook first to see if that solves it.
-
- """
- if node.errors() and node.needsToCook():
- node.cook()
-
- return node.errors()
-
-
-class ValidateNoErrors(plugin.HoudiniInstancePlugin):
- """Validate the Instance has no current cooking errors."""
-
- order = pyblish.api.ValidatorOrder
- label = "Validate no errors"
-
- def process(self, instance):
-
- if not instance.data.get("instance_node"):
- self.log.debug(
- "Skipping 'Validate no errors' because instance "
- "has no instance node: {}".format(instance)
- )
- return
-
- validate_nodes = []
-
- if len(instance) > 0:
- validate_nodes.append(hou.node(instance.data.get("instance_node")))
- output_node = instance.data.get("output_node")
- if output_node:
- validate_nodes.append(output_node)
-
- for node in validate_nodes:
- self.log.debug("Validating for errors: %s" % node.path())
- errors = get_errors(node)
-
- if errors:
- # If there are current errors, then try an unforced cook
- # to see whether the error will disappear.
- self.log.debug(
- "Recooking to revalidate error "
- "is up to date for: %s" % node.path()
- )
- current_frame = hou.intFrame()
- start = instance.data.get("frameStart", current_frame)
- end = instance.data.get("frameEnd", current_frame)
- cook_in_range(node, start=start, end=end)
-
- # Check for errors again after the forced recook
- errors = get_errors(node)
- if errors:
- self.log.error(errors)
- raise PublishValidationError(
- "Node has errors: {}".format(node.path()),
- title=self.label)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_primitive_hierarchy_paths.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_primitive_hierarchy_paths.py
deleted file mode 100644
index 9daab2a1a3..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_primitive_hierarchy_paths.py
+++ /dev/null
@@ -1,183 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-from ayon_houdini.api import plugin
-from ayon_core.pipeline import PublishValidationError
-from ayon_core.pipeline.publish import (
- ValidateContentsOrder,
- RepairAction,
-)
-
-
-class AddDefaultPathAction(RepairAction):
- label = "Add a default path attribute"
- icon = "mdi.pencil-plus-outline"
-
-
-class ValidatePrimitiveHierarchyPaths(plugin.HoudiniInstancePlugin):
- """Validate all primitives build hierarchy from attribute when enabled.
-
- The name of the attribute must exist on the prims and have the same name
- as Build Hierarchy from Attribute's `Path Attribute` value on the Alembic
- ROP node whenever Build Hierarchy from Attribute is enabled.
-
- """
-
- order = ValidateContentsOrder + 0.1
- families = ["abc"]
- label = "Validate Prims Hierarchy Path"
- actions = [AddDefaultPathAction]
-
- def process(self, instance):
- invalid = self.get_invalid(instance)
- if invalid:
- nodes = [n.path() for n in invalid]
- raise PublishValidationError(
- "See log for details. " "Invalid nodes: {0}".format(nodes),
- title=self.label
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- output_node = instance.data.get("output_node")
- rop_node = hou.node(instance.data["instance_node"])
-
- if output_node is None:
- cls.log.error(
- "SOP Output node in '%s' does not exist. "
- "Ensure a valid SOP output path is set.", rop_node.path()
- )
-
- return [rop_node]
-
- build_from_path = rop_node.parm("build_from_path").eval()
- if not build_from_path:
- cls.log.debug(
- "Alembic ROP has 'Build from Path' disabled. "
- "Validation is ignored.."
- )
- return
-
- path_attr = rop_node.parm("path_attrib").eval()
- if not path_attr:
- cls.log.error(
- "The Alembic ROP node has no Path Attribute"
- "value set, but 'Build Hierarchy from Attribute'"
- "is enabled."
- )
- return [rop_node]
-
- cls.log.debug("Checking for attribute: %s", path_attr)
-
- if not hasattr(output_node, "geometry"):
- # In the case someone has explicitly set an Object
- # node instead of a SOP node in Geometry context
- # then for now we ignore - this allows us to also
- # export object transforms.
- cls.log.warning("No geometry output node found, skipping check..")
- return
-
- # Check if the primitive attribute exists
- frame = instance.data.get("frameStart", 0)
- geo = output_node.geometryAtFrame(frame)
-
- # If there are no primitives on the current frame then we can't
- # check whether the path names are correct. So we'll just issue a
- # warning that the check can't be done consistently and skip
- # validation.
- if len(geo.iterPrims()) == 0:
- cls.log.warning(
- "No primitives found on current frame. Validation"
- " for primitive hierarchy paths will be skipped,"
- " thus can't be validated."
- )
- return
-
- # Check if there are any values for the primitives
- attrib = geo.findPrimAttrib(path_attr)
- if not attrib:
- cls.log.info(
- "Geometry Primitives are missing "
- "path attribute: `%s`", path_attr
- )
- return [output_node]
-
- # Ensure at least a single string value is present
- if not attrib.strings():
- cls.log.info(
- "Primitive path attribute has no "
- "string values: %s", path_attr
- )
- return [output_node]
-
- paths = geo.primStringAttribValues(path_attr)
- # Ensure all primitives are set to a valid path
- # Collect all invalid primitive numbers
- invalid_prims = [i for i, path in enumerate(paths) if not path]
- if invalid_prims:
- num_prims = len(geo.iterPrims()) # faster than len(geo.prims())
- cls.log.info(
- "Prims have no value for attribute `%s` "
- "(%s of %s prims)", path_attr, len(invalid_prims), num_prims
- )
- return [output_node]
-
- @classmethod
- def repair(cls, instance):
- """Add a default path attribute Action.
-
- It is a helper action more than a repair action,
- used to add a default single value for the path.
- """
-
- rop_node = hou.node(instance.data["instance_node"])
- output_node = rop_node.parm("sop_path").evalAsNode()
-
- if not output_node:
- cls.log.debug(
- "Action isn't performed, invalid SOP Path on %s",
- rop_node
- )
- return
-
- # This check to prevent the action from running multiple times.
- # git_invalid only returns [output_node] when
- # path attribute is the problem
- if cls.get_invalid(instance) != [output_node]:
- return
-
- path_attr = rop_node.parm("path_attrib").eval()
-
- path_node = output_node.parent().createNode("name", "AUTO_PATH")
- path_node.parm("attribname").set(path_attr)
- path_node.parm("name1").set('`opname("..")`/`opname("..")`Shape')
-
- cls.log.debug(
- "'%s' was created. It adds '%s' with a default single value",
- path_node, path_attr
- )
-
- path_node.setGenericFlag(hou.nodeFlag.DisplayComment, True)
- path_node.setComment(
- 'Auto path node was created automatically by '
- '"Add a default path attribute"'
- '\nFeel free to modify or replace it.'
- )
-
- if output_node.type().name() in ["null", "output"]:
- # Connect before
- path_node.setFirstInput(output_node.input(0))
- path_node.moveToGoodPosition()
- output_node.setFirstInput(path_node)
- output_node.moveToGoodPosition()
- else:
- # Connect after
- path_node.setFirstInput(output_node)
- rop_node.parm("sop_path").set(path_node.path())
- path_node.moveToGoodPosition()
-
- cls.log.debug(
- "SOP path on '%s' updated to new output node '%s'",
- rop_node, path_node
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_render_products.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_render_products.py
deleted file mode 100644
index 774d517bfb..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_render_products.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-import hou
-import pyblish.api
-
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api import plugin
-
-
-class ValidateUsdRenderProducts(plugin.HoudiniInstancePlugin):
- """Validate at least one render product is present"""
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrender"]
- hosts = ["houdini"]
- label = "Validate Render Products"
- actions = [SelectROPAction]
-
- def get_description(self):
- return inspect.cleandoc(
- """### No Render Products
-
- The render submission specified no Render Product outputs and
- as such would not generate any rendered files.
-
- This is usually the case if no Render Settings or Render
- Products were created.
-
- Make sure to create the Render Settings
- relevant to the renderer you want to use.
-
- """
- )
-
- def process(self, instance):
-
- if not instance.data.get("output_node"):
- self.log.warning("No valid LOP node to render found.")
- return
-
- if not instance.data.get("files", []):
- node_path = instance.data["instance_node"]
- node = hou.node(node_path)
- rendersettings_path = (
- node.evalParm("rendersettings") or "/Render/rendersettings"
- )
- raise PublishValidationError(
- message=(
- "No Render Products found in Render Settings "
- "for '{}' at '{}'".format(node_path, rendersettings_path)
- ),
- description=self.get_description(),
- title=self.label
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_review_colorspace.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_review_colorspace.py
deleted file mode 100644
index e96b222446..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_review_colorspace.py
+++ /dev/null
@@ -1,140 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_core.pipeline.publish import (
- RepairAction,
- get_plugin_settings,
- apply_plugin_settings_automatically
-)
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import SelectROPAction
-
-
-class ResetViewSpaceAction(RepairAction):
- label = "Reset OCIO colorspace parm"
- icon = "mdi.monitor"
-
-
-class ValidateReviewColorspace(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate Review Colorspace parameters.
-
- It checks if 'OCIO Colorspace' parameter was set to valid value.
- """
-
- order = pyblish.api.ValidatorOrder + 0.1
- families = ["review"]
- label = "Validate Review Colorspace"
- actions = [ResetViewSpaceAction, SelectROPAction]
-
- optional = True
- review_color_space = ""
-
- @classmethod
- def apply_settings(cls, project_settings):
- # Preserve automatic settings applying logic
- settings = get_plugin_settings(plugin=cls,
- project_settings=project_settings,
- log=cls.log,
- category="houdini")
- apply_plugin_settings_automatically(cls, settings, logger=cls.log)
-
- # workfile settings added in '0.2.13'
- color_settings = project_settings["houdini"]["imageio"].get(
- "workfile", {}
- )
- # Add review color settings
- if color_settings.get("enabled"):
- cls.review_color_space = color_settings.get("review_color_space")
-
-
- def process(self, instance):
-
- rop_node = hou.node(instance.data["instance_node"])
-
- # This plugin is triggered when marking render as reviewable.
- # Therefore, this plugin will run on over wrong instances.
- # TODO: Don't run this plugin on wrong instances.
- # This plugin should run only on review product type
- # with instance node of opengl type.
- if rop_node.type().name() != "opengl":
- self.log.debug("Skipping Validation. Rop node {} "
- "is not an OpenGl node.".format(rop_node.path()))
- return
-
- if not self.is_active(instance.data):
- return
-
- if os.getenv("OCIO") is None:
- self.log.debug(
- "Using Houdini's Default Color Management, "
- " skipping check.."
- )
- return
-
- if rop_node.evalParm("colorcorrect") != 2:
- # any colorspace settings other than default requires
- # 'Color Correct' parm to be set to 'OpenColorIO'
- raise PublishValidationError(
- "'Color Correction' parm on '{}' ROP must be set to"
- " 'OpenColorIO'".format(rop_node.path())
- )
-
- current_color_space = rop_node.evalParm("ociocolorspace")
- if current_color_space not in hou.Color.ocio_spaces():
- raise PublishValidationError(
- "Invalid value: Colorspace name doesn't exist.\n"
- "Check 'OCIO Colorspace' parameter on '{}' ROP"
- .format(rop_node.path())
- )
-
- # if houdini/imageio/workfile is enabled and
- # Review colorspace setting is empty then this check should
- # actually check if the current_color_space setting equals
- # the default colorspace value.
- # However, it will make the black cmd screen show up more often
- # which is very annoying.
- if self.review_color_space and \
- self.review_color_space != current_color_space:
-
- raise PublishValidationError(
- "Invalid value: Colorspace name doesn't match"
- "the Colorspace specified in settings."
- )
-
- @classmethod
- def repair(cls, instance):
- """Reset view colorspace.
-
- It is used to set colorspace on opengl node.
-
- It uses the colorspace value specified in the Houdini addon settings.
- If the value in the Houdini addon settings is empty,
- it will fall to the default colorspace.
-
- Note:
- This repair action assumes that OCIO is enabled.
- As if OCIO is disabled the whole validation is skipped
- and this repair action won't show up.
- """
- from ayon_houdini.api.lib import set_review_color_space
-
- # Fall to the default value if cls.review_color_space is empty.
- if not cls.review_color_space:
- # cls.review_color_space is an empty string
- # when the imageio/workfile setting is disabled or
- # when the Review colorspace setting is empty.
- from ayon_houdini.api.colorspace import get_default_display_view_colorspace # noqa
- cls.review_color_space = get_default_display_view_colorspace()
-
- rop_node = hou.node(instance.data["instance_node"])
- set_review_color_space(rop_node,
- cls.review_color_space,
- cls.log)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_scene_review.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_scene_review.py
deleted file mode 100644
index f45cd1c97d..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_scene_review.py
+++ /dev/null
@@ -1,87 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateSceneReview(plugin.HoudiniInstancePlugin):
- """Validator Some Scene Settings before publishing the review
- 1. Scene Path
- 2. Resolution
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["review"]
- label = "Scene Setting for review"
-
- def process(self, instance):
-
- report = []
- instance_node = hou.node(instance.data.get("instance_node"))
-
- # This plugin is triggered when marking render as reviewable.
- # Therefore, this plugin will run on over wrong instances.
- # TODO: Don't run this plugin on wrong instances.
- # This plugin should run only on review product type
- # with instance node of opengl type.
- if instance_node.type().name() != "opengl":
- self.log.debug("Skipping Validation. Rop node {} "
- "is not an OpenGl node.".format(instance_node.path()))
- return
-
- invalid = self.get_invalid_scene_path(instance_node)
- if invalid:
- report.append(invalid)
-
- invalid = self.get_invalid_camera_path(instance_node)
- if invalid:
- report.append(invalid)
-
- invalid = self.get_invalid_resolution(instance_node)
- if invalid:
- report.extend(invalid)
-
- if report:
- raise PublishValidationError(
- "\n\n".join(report),
- title=self.label)
-
- def get_invalid_scene_path(self, rop_node):
- scene_path_parm = rop_node.parm("scenepath")
- scene_path_node = scene_path_parm.evalAsNode()
- if not scene_path_node:
- path = scene_path_parm.evalAsString()
- return "Scene path does not exist: '{}'".format(path)
-
- def get_invalid_camera_path(self, rop_node):
- camera_path_parm = rop_node.parm("camera")
- camera_node = camera_path_parm.evalAsNode()
- path = camera_path_parm.evalAsString()
- if not camera_node:
- return "Camera path does not exist: '{}'".format(path)
- type_name = camera_node.type().name()
- if type_name != "cam":
- return "Camera path is not a camera: '{}' (type: {})".format(
- path, type_name
- )
-
- def get_invalid_resolution(self, rop_node):
-
- # The resolution setting is only used when Override Camera Resolution
- # is enabled. So we skip validation if it is disabled.
- override = rop_node.parm("tres").eval()
- if not override:
- return
-
- invalid = []
- res_width = rop_node.parm("res1").eval()
- res_height = rop_node.parm("res2").eval()
- if res_width == 0:
- invalid.append("Override Resolution width is set to zero.")
- if res_height == 0:
- invalid.append("Override Resolution height is set to zero")
-
- return invalid
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_sop_output_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_sop_output_node.py
deleted file mode 100644
index 7d37927058..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_sop_output_node.py
+++ /dev/null
@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import (
- SelectInvalidAction,
- SelectROPAction,
-)
-
-
-class ValidateSopOutputNode(plugin.HoudiniInstancePlugin):
- """Validate the instance SOP Output Node.
-
- This will ensure:
- - The SOP Path is set.
- - The SOP Path refers to an existing object.
- - The SOP Path node is a SOP node.
- - The SOP Path node has at least one input connection (has an input)
- - The SOP Path has geometry data.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["pointcache", "vdbcache", "model"]
- label = "Validate Output Node (SOP)"
- actions = [SelectROPAction, SelectInvalidAction]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- raise PublishValidationError(
- "Output node(s) are incorrect",
- title="Invalid output node(s)"
- )
-
- @classmethod
- def get_invalid(cls, instance):
- output_node = instance.data.get("output_node")
-
- if output_node is None:
- node = hou.node(instance.data["instance_node"])
- cls.log.error(
- "SOP Output node in '%s' does not exist. "
- "Ensure a valid SOP output path is set." % node.path()
- )
-
- return [node]
-
- # Output node must be a Sop node.
- if not isinstance(output_node, hou.SopNode):
- cls.log.error(
- "Output node %s is not a SOP node. "
- "SOP Path must point to a SOP node, "
- "instead found category type: %s"
- % (output_node.path(), output_node.type().category().name())
- )
- return [output_node]
-
- # For the sake of completeness also assert the category type
- # is Sop to avoid potential edge case scenarios even though
- # the isinstance check above should be stricter than this category
- if output_node.type().category().name() != "Sop":
- raise PublishValidationError(
- ("Output node {} is not of category Sop. "
- "This is a bug.").format(output_node.path()),
- title=cls.label)
-
- # Ensure the node is cooked and succeeds to cook so we can correctly
- # check for its geometry data.
- if output_node.needsToCook():
- cls.log.debug("Cooking node: %s" % output_node.path())
- try:
- output_node.cook()
- except hou.Error as exc:
- cls.log.error("Cook failed: %s" % exc)
- cls.log.error(output_node.errors()[0])
- return [output_node]
-
- # Ensure the output node has at least Geometry data
- if not output_node.geometry():
- cls.log.error(
- "Output node `%s` has no geometry data." % output_node.path()
- )
- return [output_node]
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_subset_name.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_subset_name.py
deleted file mode 100644
index a63a4f16c7..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_subset_name.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Validator for correct naming of Static Meshes."""
-import hou
-
-from ayon_core.pipeline import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_core.pipeline.publish import (
- ValidateContentsOrder,
- RepairAction,
-)
-from ayon_core.pipeline.create import get_product_name
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import SelectInvalidAction
-
-
-class FixProductNameAction(RepairAction):
- label = "Fix Product Name"
-
-
-class ValidateSubsetName(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate Product name.
-
- """
-
- families = ["staticMesh", "hda"]
- label = "Validate Product Name"
- order = ValidateContentsOrder + 0.1
- actions = [FixProductNameAction, SelectInvalidAction]
-
- optional = True
-
- def process(self, instance):
-
- if not self.is_active(instance.data):
- return
-
- invalid = self.get_invalid(instance)
- if invalid:
- nodes = [n.path() for n in invalid]
- raise PublishValidationError(
- "See log for details. "
- "Invalid nodes: {0}".format(nodes)
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- invalid = []
-
- rop_node = hou.node(instance.data["instance_node"])
-
- # Check product name
- folder_entity = instance.data["folderEntity"]
- task_entity = instance.data["taskEntity"]
- task_name = task_type = None
- if task_entity:
- task_name = task_entity["name"]
- task_type = task_entity["taskType"]
- product_name = get_product_name(
- instance.context.data["projectName"],
- task_name,
- task_type,
- instance.context.data["hostName"],
- instance.data["productType"],
- variant=instance.data["variant"],
- dynamic_data={
- "asset": folder_entity["name"],
- "folder": {
- "label": folder_entity["label"],
- "name": folder_entity["name"]
- }
- }
- )
-
- if instance.data.get("productName") != product_name:
- invalid.append(rop_node)
- cls.log.error(
- "Invalid product name on rop node '%s' should be '%s'.",
- rop_node.path(), product_name
- )
-
- return invalid
-
- @classmethod
- def repair(cls, instance):
- rop_node = hou.node(instance.data["instance_node"])
-
- # Check product name
- folder_entity = instance.data["folderEntity"]
- task_entity = instance.data["taskEntity"]
- task_name = task_type = None
- if task_entity:
- task_name = task_entity["name"]
- task_type = task_entity["taskType"]
- product_name = get_product_name(
- instance.context.data["projectName"],
- task_name,
- task_type,
- instance.context.data["hostName"],
- instance.data["productType"],
- variant=instance.data["variant"],
- dynamic_data={
- "asset": folder_entity["name"],
- "folder": {
- "label": folder_entity["label"],
- "name": folder_entity["name"]
- }
- }
- )
-
- instance.data["productName"] = product_name
- rop_node.parm("AYON_productName").set(product_name)
-
- cls.log.debug(
- "Product name on rop node '%s' has been set to '%s'.",
- rop_node.path(), product_name
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_unreal_staticmesh_naming.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_unreal_staticmesh_naming.py
deleted file mode 100644
index a3d971695d..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_unreal_staticmesh_naming.py
+++ /dev/null
@@ -1,96 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Validator for correct naming of Static Meshes."""
-import hou
-
-from ayon_core.pipeline import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_core.pipeline.publish import ValidateContentsOrder
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import SelectInvalidAction
-from ayon_houdini.api.lib import get_output_children
-
-
-class ValidateUnrealStaticMeshName(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate name of Unreal Static Mesh.
-
- This validator checks if output node name has a collision prefix:
- - UBX
- - UCP
- - USP
- - UCX
-
- This validator also checks if product name is correct
- - {static mesh prefix}_{FolderName}{Variant}.
-
- """
-
- families = ["staticMesh"]
- label = "Unreal Static Mesh Name (FBX)"
- order = ValidateContentsOrder + 0.1
- actions = [SelectInvalidAction]
-
- optional = True
- collision_prefixes = []
- static_mesh_prefix = ""
-
- @classmethod
- def apply_settings(cls, project_settings):
-
- settings = (
- project_settings["houdini"]["create"]["CreateStaticMesh"]
- )
- cls.collision_prefixes = settings["collision_prefixes"]
- cls.static_mesh_prefix = settings["static_mesh_prefix"]
-
- def process(self, instance):
-
- if not self.is_active(instance.data):
- return
-
- invalid = self.get_invalid(instance)
- if invalid:
- nodes = [n.path() for n in invalid]
- raise PublishValidationError(
- "See log for details. "
- "Invalid nodes: {0}".format(nodes)
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- invalid = []
-
- rop_node = hou.node(instance.data["instance_node"])
- output_node = instance.data.get("output_node")
- if output_node is None:
- cls.log.debug(
- "No Output Node, skipping check.."
- )
- return
-
- if rop_node.evalParm("buildfrompath"):
- # This validator doesn't support naming check if
- # building hierarchy from path' is used
- cls.log.info(
- "Using 'Build Hierarchy from Path Attribute', skipping check.."
- )
- return
-
- # Check nodes names
- all_outputs = get_output_children(output_node, include_sops=False)
- for output in all_outputs:
- for prefix in cls.collision_prefixes:
- if output.name().startswith(prefix):
- invalid.append(output)
- cls.log.error(
- "Invalid node name: Node '%s' "
- "includes a collision prefix '%s'",
- output.path(), prefix
- )
- break
-
- return invalid
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_asset_contribution_default_prim.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_asset_contribution_default_prim.py
deleted file mode 100644
index 03836021dc..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_asset_contribution_default_prim.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import inspect
-
-import hou
-import pyblish.api
-
-from ayon_core.pipeline import PublishValidationError
-from ayon_core.pipeline.publish import RepairAction, OptionalPyblishPluginMixin
-
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api import plugin
-
-
-class ValidateUSDAssetContributionDefaultPrim(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate the default prim is set when USD contribution is set to asset.
-
- If the USD asset contributions is enabled and the user has it set to
- initialize asset as "asset" then most likely they are looking to publish
- into an asset structure - which should have a default prim that matches
- the folder's name. To ensure that's the case we force require the
- value to be set on the ROP node.
-
- Note that another validator "Validate USD Rop Default Prim" enforces the
- primitive actually exists (or has modifications) if the ROP specifies
- a default prim - so that does not have to be validated with this validator.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrop"]
- hosts = ["houdini"]
- label = "Validate USD Asset Contribution Default Prim"
- actions = [SelectROPAction, RepairAction]
-
- # TODO: Unfortunately currently this does not show as optional toggle
- # because the product type is `usd` and not `usdrop` - however we do
- # not want to run this for ALL `usd` product types?
- optional = True
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
-
- # Check if instance is set to be an asset contribution
- settings = self.get_attr_values_from_data_for_plugin_name(
- "CollectUSDLayerContributions", instance.data
- )
- if (
- not settings.get("contribution_enabled", False)
- or settings.get("contribution_target_product_init") != "asset"
- ):
- return
-
- rop_node = hou.node(instance.data["instance_node"])
- default_prim = rop_node.evalParm("defaultprim")
- if not default_prim:
- raise PublishValidationError(
- f"No default prim specified on ROP node: {rop_node.path()}",
- description=self.get_description()
- )
-
- folder_name = instance.data["folderPath"].rsplit("/", 1)[-1]
- if not default_prim.lstrip("/") == folder_name:
- raise PublishValidationError(
- f"Default prim specified on ROP node does not match the "
- f"asset's folder name: '{default_prim}' "
- f"(should be: '/{folder_name}')",
- description=self.get_description()
- )
-
- @classmethod
- def repair(cls, instance):
- rop_node = hou.node(instance.data["instance_node"])
- rop_node.parm("defaultprim").set(
- "/`strsplit(chs(\"folderPath\"), \"/\", -1)`"
- )
-
- @staticmethod
- def get_attr_values_from_data_for_plugin_name(
- plugin_name: str, data: dict) -> dict:
- return (
- data
- .get("publish_attributes", {})
- .get(plugin_name, {})
- )
-
- def get_description(self):
- return inspect.cleandoc(
- """### Default primitive not set to current asset
-
- The USD instance has **USD Contribution** enabled and is set to
- initialize as **asset**. The asset requires a default root
- primitive with the name of the folder it's related to.
-
- For example, you're working in `/asset/char_hero` then the
- folder's name is `char_hero`. For the asset hence all prims should
- live under `/char_hero` root primitive.
-
- This validation solely ensures the **default primitive** on the ROP
- node is set to match the folder name.
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_assignments.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_assignments.py
deleted file mode 100644
index e5037454dd..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_assignments.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-import hou
-from pxr import Usd, UsdShade, UsdGeom
-
-import pyblish.api
-
-from ayon_core.pipeline.publish import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api import plugin
-
-
-def has_material(prim: Usd.Prim,
- include_subsets: bool=True,
- purpose=UsdShade.Tokens.allPurpose) -> bool:
- """Return whether primitive has any material binding."""
- search_from = [prim]
- if include_subsets:
- subsets = UsdShade.MaterialBindingAPI(prim).GetMaterialBindSubsets()
- for subset in subsets:
- search_from.append(subset.GetPrim())
-
- bounds = UsdShade.MaterialBindingAPI.ComputeBoundMaterials(search_from,
- purpose)
- for (material, relationship) in zip(*bounds):
- material_prim = material.GetPrim()
- if material_prim.IsValid():
- # Has a material binding
- return True
-
- return False
-
-
-class ValidateUsdLookAssignments(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate all geometry prims have a material binding.
-
- Note: This does not necessarily validate the material binding is authored
- by the current layers if the input already had material bindings.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["look"]
- hosts = ["houdini"]
- label = "Validate All Geometry Has Material Assignment"
- actions = [SelectROPAction]
- optional = True
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
-
- lop_node: hou.LopNode = instance.data.get("output_node")
- if not lop_node:
- return
-
- # We iterate the composed stage for code simplicity; however this
- # means that it does not validate across e.g. multiple model variants
- # but only checks against the current composed stage. Likely this is
- # also what you actually want to validate, because your look might not
- # apply to *all* model variants.
- stage = lop_node.stage()
- invalid = []
- for prim in stage.Traverse():
- if not prim.IsA(UsdGeom.Gprim):
- continue
-
- if not has_material(prim):
- invalid.append(prim.GetPath())
-
- for path in sorted(invalid):
- self.log.warning("No material binding on: %s", path.pathString)
-
- if invalid:
- raise PublishValidationError(
- "Found geometry without material bindings.",
- title="No assigned materials",
- description=self.get_description()
- )
-
- @staticmethod
- def get_description():
- return inspect.cleandoc(
- """### Geometry has no material assignments.
-
- A look publish should usually define a material assignment for all
- geometry of a model. As such, this validates whether all geometry
- currently has at least one material binding applied.
-
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_contents.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_contents.py
deleted file mode 100644
index 43357cdb35..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_contents.py
+++ /dev/null
@@ -1,148 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-from typing import List, Union
-from functools import partial
-
-import hou
-from pxr import Sdf
-import pyblish.api
-
-from ayon_core.pipeline.publish import PublishValidationError
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api.usd import get_schema_type_names
-from ayon_houdini.api import plugin
-
-
-def get_applied_items(list_proxy) -> List[Union[Sdf.Reference, Sdf.Payload]]:
- """Backwards compatible equivalent of `GetAppliedItems()`"""
- return list_proxy.ApplyEditsToList([])
-
-
-class ValidateUsdLookContents(plugin.HoudiniInstancePlugin):
- """Validate no meshes are defined in the look.
-
- Usually, a published look should not contain generated meshes in the output
- but only the materials, material bindings and render geometry settings.
-
- To avoid accidentally including a Mesh definition we ensure none of the
- generated output layers for the instance is defining any Mesh type.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["look"]
- hosts = ["houdini"]
- label = "Validate Look No Meshes/Lights"
- actions = [SelectROPAction]
-
- disallowed_types = [
- "UsdGeomBoundable", # Meshes/Lights/Procedurals
- "UsdRenderSettingsBase", # Render Settings
- "UsdRenderVar", # Render Var
- "UsdGeomCamera" # Cameras
- ]
-
- def process(self, instance):
-
- lop_node: hou.LopNode = instance.data.get("output_node")
- if not lop_node:
- return
-
- # Get layers below layer break
- above_break_layers = set(layer for layer in lop_node.layersAboveLayerBreak())
- stage = lop_node.stage()
- layers = [
- layer for layer
- in stage.GetLayerStack(includeSessionLayers=False)
- if layer.identifier not in above_break_layers
- ]
- if not layers:
- return
-
- # The Sdf.PrimSpec type name will not have knowledge about inherited
- # types for the type, name. So we pre-collect all invalid types
- # and their child types to ensure we match inherited types as well.
- disallowed_type_names = set()
- for type_name in self.disallowed_types:
- disallowed_type_names.update(get_schema_type_names(type_name))
-
- # Find invalid prims
- invalid = []
-
- def collect_invalid(layer: Sdf.Layer, path: Sdf.Path):
- """Collect invalid paths into the `invalid` list"""
- if not path.IsPrimPath():
- return
-
- prim = layer.GetPrimAtPath(path)
- if prim.typeName in disallowed_type_names:
- self.log.warning(
- "Disallowed prim type '%s' at %s",
- prim.typeName, prim.path.pathString
- )
- invalid.append(path)
- return
-
- # TODO: We should allow referencing or payloads, but if so - we
- # should still check whether the loaded reference or payload
- # introduces any geometry. If so, disallow it because that
- # opinion would 'define' geometry in the output
- references= get_applied_items(prim.referenceList)
- if references:
- self.log.warning(
- "Disallowed references are added at %s: %s",
- prim.path.pathString,
- ", ".join(ref.assetPath for ref in references)
- )
- invalid.append(path)
-
- payloads = get_applied_items(prim.payloadList)
- if payloads:
- self.log.warning(
- "Disallowed payloads are added at %s: %s",
- prim.path.pathString,
- ", ".join(payload.assetPath for payload in payloads)
- )
- invalid.append(path)
-
- for layer in layers:
- layer.Traverse("/", partial(collect_invalid, layer))
-
- if invalid:
- raise PublishValidationError(
- "Invalid look members found.",
- title="Look Invalid Members",
- description=self.get_description()
- )
-
- @staticmethod
- def get_description():
- return inspect.cleandoc(
- """### Look contains invalid members
-
- A look publish should usually only contain materials, material
- bindings and render geometry settings.
-
- This validation invalidates any creation of:
- - Render Settings,
- - Lights,
- - Cameras,
- - Geometry (Meshes, Curves and other geometry types)
-
- To avoid writing out loaded geometry into the output make sure to
- add a Layer Break after loading all the content you do **not** want
- to save into the output file. Then your materials, material
- bindings and render geometry settings are overrides applied to the
- loaded content after the **Layer Break LOP** node.
-
- If you happen to write out additional data for the meshes via
- e.g. a SOP Modify make sure to import to LOPs only the relevant
- attributes, mark them as static attributes, static topology and
- set the Primitive Definitions to be Overlay instead of Defines.
-
- Currently, to avoid issues with referencing/payloading geometry
- from external files any references or payloads are also disallowed
- for looks.
-
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_material_defs.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_material_defs.py
deleted file mode 100644
index 273bf46b18..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_look_material_defs.py
+++ /dev/null
@@ -1,137 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-import hou
-from pxr import Sdf, UsdShade
-import pyblish.api
-
-from ayon_core.pipeline.publish import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api.usd import get_schema_type_names
-from ayon_houdini.api import plugin
-
-
-class ValidateLookShaderDefs(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate Material primitives are defined types instead of overs"""
-
- order = pyblish.api.ValidatorOrder
- families = ["look"]
- hosts = ["houdini"]
- label = "Validate Look Shaders Are Defined"
- actions = [SelectROPAction]
- optional = True
-
- # Types to validate at the low-level Sdf API
- # For Usd API we validate directly against `UsdShade.Material`
- validate_types = [
- "UsdShadeMaterial"
- ]
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
-
- lop_node: hou.LopNode = instance.data.get("output_node")
- if not lop_node:
- return
-
- # Get layers below layer break
- above_break_layers = set(
- layer for layer in lop_node.layersAboveLayerBreak())
- stage = lop_node.stage()
- layers = [
- layer for layer
- in stage.GetLayerStack(includeSessionLayers=False)
- if layer.identifier not in above_break_layers
- ]
- if not layers:
- return
-
- # The Sdf.PrimSpec type name will not have knowledge about inherited
- # types for the type, name. So we pre-collect all invalid types
- # and their child types to ensure we match inherited types as well.
- validate_type_names = set()
- for type_name in self.validate_types:
- validate_type_names.update(get_schema_type_names(type_name))
-
- invalid = []
- for layer in layers:
- def log_overs(path: Sdf.Path):
- if not path.IsPrimPath():
- return
- prim_spec = layer.GetPrimAtPath(path)
-
- if not prim_spec.typeName:
- # Typeless may mean Houdini generated the material or
- # shader as override because upstream the nodes already
- # existed. So we check the stage instead to identify
- # the composed type of the prim
- prim = stage.GetPrimAtPath(path)
- if not prim:
- return
-
- if not prim.IsA(UsdShade.Material):
- return
-
- self.log.debug("Material Prim has no type defined: %s",
- path)
-
- elif prim_spec.typeName not in validate_type_names:
- return
-
- if prim_spec.specifier != Sdf.SpecifierDef:
- specifier = {
- Sdf.SpecifierDef: "Def",
- Sdf.SpecifierOver: "Over",
- Sdf.SpecifierClass: "Class"
- }[prim_spec.specifier]
-
- self.log.warning(
- "Material is not defined but specified as "
- "'%s': %s", specifier, path
- )
- invalid.append(path)
-
- layer.Traverse("/", log_overs)
-
- if invalid:
- raise PublishValidationError(
- "Found Materials not specifying an authored definition.",
- title="Materials not defined",
- description=self.get_description()
- )
-
- @staticmethod
- def get_description():
- return inspect.cleandoc(
- """### Materials are not defined types
-
- There are materials in your current look that do not **define** the
- material primitives, but rather **override** or specify a
- **class**. This is most likely not what you want since you want
- most looks to define new materials instead of overriding existing
- materials.
-
- Usually this happens if your current scene loads an input asset
- that already has the materials you're creating in your current
- scene as well. For example, if you are loading the Asset that
- contains the previously publish of your look without muting the
- look layer. As such, Houdini sees the materials already exist and
- will not make new definitions, but only write "override changes".
- However, once your look publish would replace the previous one then
- suddenly the materials would be missing and only specified as
- overrides.
-
- So, in most cases this is solved by Layer Muting upstream the
- look layers of the loaded asset.
-
- If for a specific case the materials already existing in the input
- is correct then you can either specify new material names for what
- you're creating in the current scene or disable this validation
- if you are sure you want to write overrides in your look publish
- instead of definitions.
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_output_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_output_node.py
deleted file mode 100644
index 7ef9a80394..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_output_node.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-
-import pyblish.api
-
-from ayon_core.pipeline import PublishValidationError
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api import plugin
-
-
-class ValidateUSDOutputNode(plugin.HoudiniInstancePlugin):
- """Validate the instance USD LOPs Output Node.
-
- This will ensure:
- - The LOP Path is set.
- - The LOP Path refers to an existing object.
- - The LOP Path node is a LOP node.
-
- """
-
- # Validate early so that this error reports higher than others to the user
- # so that if another invalidation is due to the output node being invalid
- # the user will likely first focus on this first issue
- order = pyblish.api.ValidatorOrder - 0.4
- families = ["usdrop"]
- label = "Validate Output Node (USD)"
- actions = [SelectROPAction]
-
- def process(self, instance):
-
- invalid = self.get_invalid(instance)
- if invalid:
- path = invalid[0]
- raise PublishValidationError(
- "Output node '{}' has no valid LOP path set.".format(path),
- title=self.label,
- description=self.get_description()
- )
-
- @classmethod
- def get_invalid(cls, instance):
-
- import hou
-
- output_node = instance.data.get("output_node")
-
- if output_node is None:
- node = hou.node(instance.data.get("instance_node"))
- cls.log.error(
- "USD node '%s' configured LOP path does not exist. "
- "Ensure a valid LOP path is set." % node.path()
- )
-
- return [node.path()]
-
- # Output node must be a Sop node.
- if not isinstance(output_node, hou.LopNode):
- cls.log.error(
- "Output node %s is not a LOP node. "
- "LOP Path must point to a LOP node, "
- "instead found category type: %s"
- % (output_node.path(), output_node.type().category().name())
- )
- return [output_node.path()]
-
- def get_description(self):
- return inspect.cleandoc(
- """### USD ROP has invalid LOP path
-
- The USD ROP node has no or an invalid LOP path set to be exported.
- Make sure to correctly configure what you want to export for the
- publish.
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_arnold.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_arnold.py
deleted file mode 100644
index 67d1aa605a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_arnold.py
+++ /dev/null
@@ -1,311 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-import hou
-import pxr
-from pxr import UsdRender
-import pyblish.api
-
-from ayon_core.pipeline.publish import PublishValidationError, RepairAction
-
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api.usd import get_usd_render_rop_rendersettings
-from ayon_houdini.api import plugin
-
-
-class ValidateUSDRenderSingleFile(plugin.HoudiniInstancePlugin):
- """Validate the writing of a single USD Render Output file.
-
- When writing to single file with USD Render ROP make sure to write the
- output USD file from a single process to avoid overwriting it with
- different processes.
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrender"]
- hosts = ["houdini"]
- label = "Validate USD Render ROP Settings"
- actions = [SelectROPAction, RepairAction]
-
- def process(self, instance):
-
- if instance.data.get("creator_attributes",
- {}).get("render_target") != "farm_split":
- # Validation is only relevant when submitting a farm job where the
- # export and render are separate jobs.
- return
-
- # Get configured settings for this instance
- submission_data = (
- instance.data
- .get("publish_attributes", {})
- .get("HoudiniSubmitDeadlineUsdRender", {})
- )
- render_chunk_size = submission_data.get("chunk", 1)
- export_chunk_size = submission_data.get("export_chunk", 1)
- usd_file_per_frame = "$F" in instance.data["ifdFile"]
- frame_start_handle = instance.data["frameStartHandle"]
- frame_end_handle = instance.data["frameEndHandle"]
- num_frames = frame_end_handle - frame_start_handle + 1
- rop_node = hou.node(instance.data["instance_node"])
-
- # Whether ROP node is set to render all Frames within a single process
- # When this is disabled then Husk will restart completely per frame
- # no matter the chunk size.
- all_frames_at_once = rop_node.evalParm("allframesatonce")
-
- invalid = False
- if usd_file_per_frame:
- # USD file per frame
- # If rendering multiple frames per task and USD file has $F then
- # log a warning that the optimization will be less efficient
- # since husk will still restart per frame.
- if render_chunk_size > 1:
- self.log.debug(
- "Render chunk size is bigger than one but export file is "
- "a USD file per frame. Husk does not allow rendering "
- "separate USD files in one process. As such, Husk will "
- "restart per frame even within the chunk to render the "
- "correct file per frame."
- )
- else:
- # Single export USD file
- # Export chunk size must be higher than the amount of frames to
- # ensure the file is written in one go on one machine and thus
- # ends up containing all frames correctly
- if export_chunk_size < num_frames:
- self.log.error(
- "The export chunk size %s is smaller than the amount of "
- "frames %s, so multiple tasks will try to export to "
- "the same file. Make sure to increase chunk "
- "size to higher than the amount of frames to render, "
- "more than >%s",
- export_chunk_size, num_frames, num_frames
- )
- invalid = True
-
- if not all_frames_at_once:
- self.log.error(
- "Please enable 'Render All Frames With A Single Process' "
- "on the USD Render ROP node or add $F to the USD filename",
- )
- invalid = True
-
- if invalid:
- raise PublishValidationError(
- "Render USD file being overwritten during export.",
- title="Render USD file overwritten",
- description=self.get_description())
-
- @classmethod
- def repair(cls, instance):
- # Enable all frames at once and make the frames per task
- # very large
- rop_node = hou.node(instance.data["instance_node"])
- rop_node.parm("allframesatonce").set(True)
-
- # Override instance setting for export chunk size
- create_context = instance.context.data["create_context"]
- created_instance = create_context.get_instance_by_id(
- instance.data["instance_id"]
- )
- created_instance.publish_attributes["HoudiniSubmitDeadlineUsdRender"]["export_chunk"] = 1000 # noqa
- create_context.save_changes()
-
- def get_description(self):
- return inspect.cleandoc(
- """### Render USD file configured incorrectly
-
- The USD render ROP is currently configured to write a single
- USD file to render instead of a file per frame.
-
- When that is the case, a single machine must produce that file in
- one process to avoid the file being overwritten by the other
- processes.
-
- We resolve that by enabling _Render All Frames With A Single
- Process_ on the ROP node and ensure the export job task size
- is larger than the amount of frames of the sequence, so the file
- gets written in one go.
-
- Run **Repair** to resolve this for you.
-
- If instead you want to write separate render USD files, please
- include $F in the USD output filename on the `ROP node > Output >
- USD Export > Output File`
- """
- )
-
-
-class ValidateUSDRenderArnoldSettings(plugin.HoudiniInstancePlugin):
- """Validate USD Render Product names are correctly set absolute paths."""
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrender"]
- hosts = ["houdini"]
- label = "Validate USD Render Arnold Settings"
- actions = [SelectROPAction]
-
- def process(self, instance):
-
- rop_node = hou.node(instance.data["instance_node"])
- node = instance.data.get("output_node")
- if not node:
- # No valid output node was set. We ignore it since it will
- # be validated by another plug-in.
- return
-
- # Check only for Arnold renderer
- renderer = rop_node.evalParm("renderer")
- if renderer != "HdArnoldRendererPlugin":
- self.log.debug("Skipping Arnold Settings validation because "
- "renderer is set to: %s", renderer)
- return
-
- # Validate Arnold Product Type is enabled on the Arnold Render Settings
- # This is confirmed by the `includeAovs` attribute on the RenderProduct
- stage: pxr.Usd.Stage = node.stage()
- invalid = False
- for prim_path in instance.data.get("usdRenderProducts", []):
- prim = stage.GetPrimAtPath(prim_path)
- include_aovs = prim.GetAttribute("includeAovs")
- if not include_aovs.IsValid() or not include_aovs.Get(0):
- self.log.error(
- "All Render Products must be set to 'Arnold Product "
- "Type' on the Arnold Render Settings node to ensure "
- "correct output of metadata and AOVs."
- )
- invalid = True
- break
-
- # Ensure 'Delegate Products' is enabled for Husk
- if not rop_node.evalParm("husk_delegateprod"):
- invalid = True
- self.log.error("USD Render ROP has `Husk > Rendering > Delegate "
- "Products` disabled. Please enable to ensure "
- "correct output files")
-
- # TODO: Detect bug of invalid Cryptomatte state?
- # Detect if any Render Products were set that do not actually exist
- # (e.g. invalid rendervar targets for a renderproduct) because that
- # is what originated the Cryptomatte enable->disable bug.
-
- if invalid:
- raise PublishValidationError(
- "Invalid Render Settings for Arnold render."
- )
-
-
-class ValidateUSDRenderCamera(plugin.HoudiniInstancePlugin):
- """Validate USD Render Settings refer to a valid render camera.
-
- The render camera is defined in priority by this order:
- 1. ROP Node Override Camera Parm (if set)
- 2. Render Product Camera (if set - this may differ PER render product!)
- 3. Render Settings Camera (if set)
-
- If None of these are set *or* a currently set entry resolves to an invalid
- camera prim path then we'll report it as an error.
-
- """
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrender"]
- hosts = ["houdini"]
- label = "Validate USD Render Camera"
- actions = [SelectROPAction]
-
- def process(self, instance):
-
- rop_node = hou.node(instance.data["instance_node"])
- lop_node = instance.data.get("output_node")
- if not lop_node:
- # No valid output node was set. We ignore it since it will
- # be validated by another plug-in.
- return
-
- stage = lop_node.stage()
-
- render_settings = get_usd_render_rop_rendersettings(rop_node, stage,
- logger=self.log)
- if not render_settings:
- # Without render settings we basically have no defined
- self.log.error("No render settings found for %s.", rop_node.path())
- return
-
- render_settings_camera = self._get_camera(render_settings)
- rop_camera = rop_node.evalParm("override_camera")
-
- invalid = False
- camera_paths = set()
- for render_product in self.iter_render_products(render_settings,
- stage):
- render_product_camera = self._get_camera(render_product)
-
- # Get first camera path as per order in in this plug-in docstring
- camera_path = next(
- (cam_path for cam_path in [rop_camera,
- render_product_camera,
- render_settings_camera]
- if cam_path),
- None
- )
- if not camera_path:
- self.log.error(
- "No render camera defined for render product: '%s'",
- render_product.GetPath()
- )
- invalid = True
- continue
-
- camera_paths.add(camera_path)
-
- # For the camera paths used across the render products detect
- # whether the path is a valid camera in the stage
- for camera_path in sorted(camera_paths):
- camera_prim = stage.GetPrimAtPath(camera_path)
- if not camera_prim or not camera_prim.IsValid():
- self.log.error(
- "Render camera path '%s' does not exist in stage.",
- camera_path
- )
- invalid = True
- continue
-
- if not camera_prim.IsA(pxr.UsdGeom.Camera):
- self.log.error(
- "Render camera path '%s' is not a camera.",
- camera_path
- )
- invalid = True
-
- if invalid:
- raise PublishValidationError(
- f"No render camera found for {instance.name}.",
- title="Invalid Render Camera",
- description=self.get_description()
- )
-
- def iter_render_products(self, render_settings, stage):
- for product_path in render_settings.GetProductsRel().GetTargets():
- prim = stage.GetPrimAtPath(product_path)
- if prim.IsA(UsdRender.Product):
- yield UsdRender.Product(prim)
-
- def _get_camera(self, settings: UsdRender.SettingsBase):
- """Return primary camera target from RenderSettings or RenderProduct"""
- camera_targets = settings.GetCameraRel().GetForwardedTargets()
- if camera_targets:
- return camera_targets[0]
-
- def get_description(self):
- return inspect.cleandoc(
- """### Missing render camera
-
- No valid render camera was set for the USD Render Settings.
-
- The configured render camera path must be a valid camera in the
- stage. Make sure it refers to an existing path and that it is
- a camera.
-
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_product_names.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_product_names.py
deleted file mode 100644
index 2da9d009ab..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_product_names.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-
-import pyblish.api
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api import plugin
-
-
-class ValidateUSDRenderProductNames(plugin.HoudiniInstancePlugin):
- """Validate USD Render Product names are correctly set absolute paths."""
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrender"]
- label = "Validate USD Render Product Names"
- optional = True
-
- def process(self, instance):
-
- invalid = []
- for filepath in instance.data.get("files", []):
-
- if not filepath:
- invalid.append("Detected empty output filepath.")
-
- if not os.path.isabs(filepath):
- invalid.append(
- "Output file path is not absolute path: %s" % filepath
- )
-
- if invalid:
- for message in invalid:
- self.log.error(message)
- raise PublishValidationError(
- "USD Render Paths are invalid.", title=self.label)
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_product_paths.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_product_paths.py
deleted file mode 100644
index 369ec082ce..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_render_product_paths.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# -*- coding: utf-8 -*-
-import os
-import hou
-import inspect
-import pyblish.api
-
-from ayon_core.pipeline import (
- OptionalPyblishPluginMixin,
- PublishValidationError
-)
-
-from ayon_houdini.api import plugin
-
-
-class ValidateUSDRenderProductPaths(plugin.HoudiniInstancePlugin,
- OptionalPyblishPluginMixin):
- """Validate USD Render Settings refer to a valid render camera.
-
- The publishing logic uses a metadata `.json` in the render output images'
- folder to identify how the files should be published. To ensure multiple
- subsequent submitted versions of a scene do not override the same metadata
- json file we want to ensure the user has the render paths set up to
- contain the $HIPNAME in a parent folder.
-
- """
- # NOTE(colorbleed): This workflow might be relatively Colorbleed-specific
- # TODO: Preferably we find ways to make what this tries to avoid no issue
- # itself by e.g. changing how AYON deals with these metadata json files.
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrender"]
- hosts = ["houdini"]
- label = "Validate USD Render Product Paths"
- optional = True
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
-
- current_file = instance.context.data["currentFile"]
-
- # mimic `$HIPNAME:r` because `hou.text.collapseCommonVars can not
- # collapse it
- hipname_r = os.path.splitext(os.path.basename(current_file))[0]
-
- invalid = False
- for filepath in instance.data.get("files", []):
- folder = os.path.dirname(filepath)
-
- if hipname_r not in folder:
- filepath_raw = hou.text.collapseCommonVars(filepath, vars=[
- "$HIP", "$JOB", "$HIPNAME"
- ])
- filepath_raw = filepath_raw.replace(hipname_r, "$HIPNAME:r")
- self.log.error("Invalid render output path:\n%s", filepath_raw)
- invalid = True
-
- if invalid:
- raise PublishValidationError(
- "Render path is invalid. Please make sure to include a "
- "folder with '$HIPNAME:r'.",
- title=self.label,
- description=self.get_description()
- )
-
- def get_description(self):
- return inspect.cleandoc(
- """### Invalid render output path
-
- The render output path must include the current scene name in
- a parent folder to ensure uniqueness across multiple workfile
- versions. Otherwise subsequent farm publishes could fail because
- newer versions will overwrite the metadata files of older versions.
-
- The easiest way to do so is to include **`$HIPNAME:r`** somewhere
- in the render product names.
-
- A recommended output path is for example:
- ```
- $HIP/renders/$HIPNAME:r/$OS/$HIPNAME:r.$OS.$F4.exr
- ```
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_rop_default_prim.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_rop_default_prim.py
deleted file mode 100644
index ee4746f73f..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_usd_rop_default_prim.py
+++ /dev/null
@@ -1,110 +0,0 @@
-# -*- coding: utf-8 -*-
-import inspect
-import hou
-from pxr import Sdf
-import pyblish.api
-
-from ayon_core.pipeline import PublishValidationError
-
-from ayon_houdini.api.action import SelectROPAction
-from ayon_houdini.api import plugin
-
-
-class ValidateUSDRopDefaultPrim(plugin.HoudiniInstancePlugin):
- """Validate the default prim exists if default prim value is set on ROP"""
-
- order = pyblish.api.ValidatorOrder
- families = ["usdrop"]
- hosts = ["houdini"]
- label = "Validate USD ROP Default Prim"
- actions = [SelectROPAction]
-
- def process(self, instance):
-
- rop_node = hou.node(instance.data["instance_node"])
-
- default_prim = rop_node.evalParm("defaultprim")
- if not default_prim:
- self.log.debug(
- "No default prim specified on ROP node: %s", rop_node.path()
- )
- return
-
- lop_node: hou.LopNode = instance.data.get("output_node")
- if not lop_node:
- return
-
- above_break_layers = set(layer for layer in lop_node.layersAboveLayerBreak())
- stage = lop_node.stage()
- layers = [
- layer for layer
- in stage.GetLayerStack(includeSessionLayers=False)
- if layer.identifier not in above_break_layers
- ]
- if not layers:
- self.log.error("No USD layers found. This is likely a bug.")
- return
-
- # TODO: This only would detect any local opinions on that prim and thus
- # would fail to detect if a sublayer added on the stage root layer
- # being exported would actually be generating the prim path. We
- # should maybe consider that if this fails that we still check
- # whether a sublayer doesn't create the default prim path.
- for layer in layers:
- if layer.GetPrimAtPath(default_prim):
- break
- else:
- # No prim found at the given path on any of the generated layers
- raise PublishValidationError(
- "Default prim specified by USD ROP does not exist in "
- f"stage: '{default_prim}'",
- title="Default Prim",
- description=self.get_description()
- )
-
- # Warn about any paths that are authored that are not a child
- # of the default prim
- outside_paths = set()
- default_prim_path = f"/{default_prim.strip('/')}"
- for layer in layers:
-
- def collect_outside_paths(path: Sdf.Path):
- """Collect all paths that are no child of the default prim"""
-
- if not path.IsPrimPath():
- # Collect only prim paths
- return
-
- # Ignore the HoudiniLayerInfo prim
- if path.pathString == "/HoudiniLayerInfo":
- return
-
- if not path.pathString.startswith(default_prim_path):
- outside_paths.add(path)
-
- layer.Traverse("/", collect_outside_paths)
-
- if outside_paths:
- self.log.warning(
- "Found paths that are not within default primitive path '%s'. "
- "When referencing the following paths by default will not be "
- "loaded:",
- default_prim
- )
- for outside_path in sorted(outside_paths):
- self.log.warning("Outside default prim: %s", outside_path)
-
- def get_description(self):
- return inspect.cleandoc(
- """### Default Prim not found
-
- The USD render ROP is currently configured to write the output
- USD file with a default prim. However, the default prim is not
- found in the USD stage.
-
- Make sure to double check the Default Prim setting on the USD
- Render ROP for typos or make sure the hierarchy and opinions you
- are creating exist in the default prim path.
-
- """
- )
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_vdb_output_node.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_vdb_output_node.py
deleted file mode 100644
index c4ed9d2fb8..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_vdb_output_node.py
+++ /dev/null
@@ -1,177 +0,0 @@
-# -*- coding: utf-8 -*-
-import contextlib
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import PublishXmlValidationError
-
-from ayon_houdini.api import plugin
-from ayon_houdini.api.action import SelectInvalidAction
-
-
-def group_consecutive_numbers(nums):
- """
- Args:
- nums (list): List of sorted integer numbers.
-
- Yields:
- str: Group ranges as {start}-{end} if more than one number in the range
- else it yields {end}
-
- """
- start = None
- end = None
-
- def _result(a, b):
- if a == b:
- return "{}".format(a)
- else:
- return "{}-{}".format(a, b)
-
- for num in nums:
- if start is None:
- start = num
- end = num
- elif num == end + 1:
- end = num
- else:
- yield _result(start, end)
- start = num
- end = num
- if start is not None:
- yield _result(start, end)
-
-
-@contextlib.contextmanager
-def update_mode_context(mode):
- original = hou.updateModeSetting()
- try:
- hou.setUpdateMode(mode)
- yield
- finally:
- hou.setUpdateMode(original)
-
-
-def get_geometry_at_frame(sop_node, frame, force=True):
- """Return geometry at frame but force a cooked value."""
- if not hasattr(sop_node, "geometry"):
- return
- with update_mode_context(hou.updateMode.AutoUpdate):
- sop_node.cook(force=force, frame_range=(frame, frame))
- return sop_node.geometryAtFrame(frame)
-
-
-class ValidateVDBOutputNode(plugin.HoudiniInstancePlugin):
- """Validate that the node connected to the output node is of type VDB.
-
- All primitives of the output geometry must be VDBs, no other primitive
- types are allowed. That means that regardless of the amount of VDBs in the
- geometry it will have an equal amount of VDBs, points, primitives and
- vertices since each VDB primitive is one point, one vertex and one VDB.
-
- This validation only checks the geometry on the first frame of the export
- frame range for optimization purposes.
-
- A VDB is an inherited type of Prim, holds the following data:
- - Primitives: 1
- - Points: 1
- - Vertices: 1
- - VDBs: 1
-
- """
-
- order = pyblish.api.ValidatorOrder + 0.1
- families = ["vdbcache"]
- label = "Validate Output Node (VDB)"
- actions = [SelectInvalidAction]
-
- def process(self, instance):
- invalid_nodes, message = self.get_invalid_with_message(instance)
- if invalid_nodes:
-
- # instance_node is str, but output_node is hou.Node so we convert
- output = instance.data.get("output_node")
- output_path = output.path() if output else None
-
- raise PublishXmlValidationError(
- self,
- "Invalid VDB content: {}".format(message),
- formatting_data={
- "message": message,
- "rop_path": instance.data.get("instance_node"),
- "sop_path": output_path
- }
- )
-
- @classmethod
- def get_invalid_with_message(cls, instance):
-
- node = instance.data.get("output_node")
- if node is None:
- instance_node = instance.data.get("instance_node")
- error = (
- "SOP path is not correctly set on "
- "ROP node `{}`.".format(instance_node)
- )
- return [hou.node(instance_node), error]
-
- frame = instance.data.get("frameStart", 0)
- geometry = get_geometry_at_frame(node, frame)
- if geometry is None:
- # No geometry data on this node, maybe the node hasn't cooked?
- error = (
- "SOP node `{}` has no geometry data. "
- "Was it unable to cook?".format(node.path())
- )
- return [node, error]
-
- num_prims = geometry.intrinsicValue("primitivecount")
- num_points = geometry.intrinsicValue("pointcount")
- if num_prims == 0 and num_points == 0:
- # Since we are only checking the first frame it doesn't mean there
- # won't be VDB prims in a few frames. As such we'll assume for now
- # the user knows what he or she is doing
- cls.log.warning(
- "SOP node `{}` has no primitives on start frame {}. "
- "Validation is skipped and it is assumed elsewhere in the "
- "frame range VDB prims and only VDB prims will exist."
- "".format(node.path(), int(frame))
- )
- return [None, None]
-
- num_vdb_prims = geometry.countPrimType(hou.primType.VDB)
- cls.log.debug("Detected {} VDB primitives".format(num_vdb_prims))
- if num_prims != num_vdb_prims:
- # There's at least one primitive that is not a VDB.
- # Search them and report them to the artist.
- prims = geometry.prims()
- invalid_prims = [prim for prim in prims
- if not isinstance(prim, hou.VDB)]
- if invalid_prims:
- # Log prim numbers as consecutive ranges so logging isn't very
- # slow for large number of primitives
- error = (
- "Found non-VDB primitives for `{}`. "
- "Primitive indices {} are not VDB primitives.".format(
- node.path(),
- ", ".join(group_consecutive_numbers(
- prim.number() for prim in invalid_prims
- ))
- )
- )
- return [node, error]
-
- if num_points != num_vdb_prims:
- # We have points unrelated to the VDB primitives.
- error = (
- "The number of primitives and points do not match in '{}'. "
- "This likely means you have unconnected points, which we do "
- "not allow in the VDB output.".format(node.path()))
- return [node, error]
-
- return [None, None]
-
- @classmethod
- def get_invalid(cls, instance):
- nodes, _ = cls.get_invalid_with_message(instance)
- return nodes
diff --git a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_workfile_paths.py b/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_workfile_paths.py
deleted file mode 100644
index a5a742069a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/plugins/publish/validate_workfile_paths.py
+++ /dev/null
@@ -1,95 +0,0 @@
-# -*- coding: utf-8 -*-
-import hou
-
-import pyblish.api
-from ayon_core.pipeline import (
- PublishValidationError,
- OptionalPyblishPluginMixin
-)
-from ayon_core.pipeline.publish import RepairAction
-
-from ayon_houdini.api import plugin
-
-
-class ValidateWorkfilePaths(
- plugin.HoudiniInstancePlugin, OptionalPyblishPluginMixin):
- """Validate workfile paths so they are absolute."""
-
- order = pyblish.api.ValidatorOrder
- families = ["workfile"]
- label = "Validate Workfile Paths"
- actions = [RepairAction]
- optional = True
-
- node_types = ["file", "alembic"]
- prohibited_vars = ["$HIP", "$JOB"]
-
- def process(self, instance):
- if not self.is_active(instance.data):
- return
- invalid = self.get_invalid()
- self.log.debug(
- "Checking node types: {}".format(", ".join(self.node_types)))
- self.log.debug(
- "Searching prohibited vars: {}".format(
- ", ".join(self.prohibited_vars)
- )
- )
-
- if invalid:
- all_container_vars = set()
- for param in invalid:
- value = param.unexpandedString()
- contained_vars = [
- var for var in self.prohibited_vars
- if var in value
- ]
- all_container_vars.update(contained_vars)
-
- self.log.error(
- "Parm {} contains prohibited vars {}: {}".format(
- param.path(),
- ", ".join(contained_vars),
- value)
- )
-
- message = (
- "Prohibited vars {} found in parameter values".format(
- ", ".join(all_container_vars)
- )
- )
- raise PublishValidationError(message, title=self.label)
-
- @classmethod
- def get_invalid(cls):
- invalid = []
- for param, _ in hou.fileReferences():
- # it might return None for some reason
- if not param:
- continue
- # skip nodes we are not interested in
- if param.node().type().name() not in cls.node_types:
- continue
-
- if param.keyframes():
- # Calling `.unexpandedString()` below fails if param has
- # keyframes - so for now we will skip those params. These are
- # e.g. present in `filecache` nodes.
- continue
-
- if any(
- v for v in cls.prohibited_vars
- if v in param.unexpandedString()):
- invalid.append(param)
-
- return invalid
-
- @classmethod
- def repair(cls, instance):
- invalid = cls.get_invalid()
- for param in invalid:
- cls.log.info("Processing: {}".format(param.path()))
- cls.log.info("Replacing {} for {}".format(
- param.unexpandedString(),
- hou.text.expandString(param.unexpandedString())))
- param.set(hou.text.expandString(param.unexpandedString()))
diff --git a/server_addon/houdini/client/ayon_houdini/startup/MainMenuCommon.xml b/server_addon/houdini/client/ayon_houdini/startup/MainMenuCommon.xml
deleted file mode 100644
index 5b383f0085..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/MainMenuCommon.xml
+++ /dev/null
@@ -1,109 +0,0 @@
-
-
-
-
-
-
diff --git a/server_addon/houdini/client/ayon_houdini/startup/OPmenu.xml b/server_addon/houdini/client/ayon_houdini/startup/OPmenu.xml
deleted file mode 100644
index 5637d2cf6a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/OPmenu.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
diff --git a/server_addon/houdini/client/ayon_houdini/startup/husdplugins/outputprocessors/ayon_uri_processor.py b/server_addon/houdini/client/ayon_houdini/startup/husdplugins/outputprocessors/ayon_uri_processor.py
deleted file mode 100644
index 5148435ff0..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/husdplugins/outputprocessors/ayon_uri_processor.py
+++ /dev/null
@@ -1,135 +0,0 @@
-import logging
-
-from husd.outputprocessor import OutputProcessor
-
-from ayon_core.pipeline import entity_uri
-from ayon_core.pipeline.load.utils import get_representation_path_by_names
-
-
-class AYONURIOutputProcessor(OutputProcessor):
- """Process AYON Entity URIs into their full path equivalents."""
-
- def __init__(self):
- """ There is only one object of each output processor class that is
- ever created in a Houdini session. Therefore, be very careful
- about what data gets put in this object.
- """
- self._save_cache = dict()
- self._ref_cache = dict()
- self._publish_context = None
- self.log = logging.getLogger(__name__)
-
- @staticmethod
- def name():
- return "ayon_uri_processor"
-
- @staticmethod
- def displayName():
- return "AYON URI Output Processor"
-
- def processReferencePath(self,
- asset_path,
- referencing_layer_path,
- asset_is_layer):
- """
- Args:
- asset_path (str): The path to the asset, as specified in Houdini.
- If this asset is being written to disk, this will be the final
- output of the `processSavePath()` calls on all output
- processors.
- referencing_layer_path (str): The absolute file path of the file
- containing the reference to the asset. You can use this to make
- the path pointer relative.
- asset_is_layer (bool): A boolean value indicating whether this
- asset is a USD layer file. If this is `False`, the asset is
- something else (for example, a texture or volume file).
-
- Returns:
- The refactored reference path.
-
- """
-
- cache = self._ref_cache
-
- # Retrieve from cache if this query occurred before (optimization)
- if asset_path in cache:
- return cache[asset_path]
-
- uri_data = entity_uri.parse_ayon_entity_uri(asset_path)
- if not uri_data:
- cache[asset_path] = asset_path
- return asset_path
-
- # Try and find it as an existing publish
- query = {
- "project_name": uri_data["project"],
- "folder_path": uri_data["folder"],
- "product_name": uri_data["product"],
- "version_name": uri_data["version"],
- "representation_name": uri_data["representation"],
- }
- path = get_representation_path_by_names(
- **query
- )
- if path:
- self.log.debug(
- "AYON URI Resolver - ref: %s -> %s", asset_path, path
- )
- cache[asset_path] = path
- return path
-
- elif self._publish_context:
- # Query doesn't resolve to an existing version - likely
- # points to a version defined in the current publish session
- # as such we should resolve it using the current publish
- # context if that was set prior to this publish
- raise NotImplementedError("TODO")
-
- self.log.warning(f"Unable to resolve AYON URI: {asset_path}")
- cache[asset_path] = asset_path
- return asset_path
-
- def processSavePath(self,
- asset_path,
- referencing_layer_path,
- asset_is_layer):
- """
- Args:
- asset_path (str): The path to the asset, as specified in Houdini.
- If this asset is being written to disk, this will be the final
- output of the `processSavePath()` calls on all output
- processors.
- referencing_layer_path (str): The absolute file path of the file
- containing the reference to the asset. You can use this to make
- the path pointer relative.
- asset_is_layer (bool): A boolean value indicating whether this
- asset is a USD layer file. If this is `False`, the asset is
- something else (for example, a texture or volume file).
-
- Returns:
- The refactored save path.
-
- """
- cache = self._save_cache
-
- # Retrieve from cache if this query occurred before (optimization)
- if asset_path in cache:
- return cache[asset_path]
-
- uri_data = entity_uri.parse_ayon_entity_uri(asset_path)
- if not uri_data:
- cache[asset_path] = asset_path
- return asset_path
-
- relative_template = "{asset}_{product}_{version}_{representation}.usd"
- # Set save output path to a relative path so other
- # processors can potentially manage it easily?
- path = relative_template.format(**uri_data)
-
- self.log.debug("AYON URI Resolver - save: %s -> %s", asset_path, path)
- cache[asset_path] = path
- return path
-
-
-def usdOutputProcessor():
- return AYONURIOutputProcessor
diff --git a/server_addon/houdini/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py b/server_addon/houdini/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py
deleted file mode 100644
index 52e02f4160..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/husdplugins/outputprocessors/remap_to_publish.py
+++ /dev/null
@@ -1,66 +0,0 @@
-import os
-import json
-
-import hou
-from husd.outputprocessor import OutputProcessor
-
-
-class AYONRemapPaths(OutputProcessor):
- """Remap paths based on a mapping dict on rop node."""
-
- def __init__(self):
- self._mapping = dict()
-
- @staticmethod
- def name():
- return "ayon_remap_paths"
-
- @staticmethod
- def displayName():
- return "AYON Remap Paths"
-
- @staticmethod
- def hidden():
- return True
-
- @staticmethod
- def parameters():
- group = hou.ParmTemplateGroup()
-
- parm_template = hou.StringParmTemplate(
- "ayon_remap_paths_remap_json",
- "Remapping dict (json)",
- default_value="{}",
- num_components=1,
- string_type=hou.stringParmType.Regular,
- )
- group.append(parm_template)
-
- return group.asDialogScript()
-
- def beginSave(self, config_node, config_overrides, lop_node, t):
- super(AYONRemapPaths, self).beginSave(config_node,
- config_overrides,
- lop_node,
- t)
-
- value = config_node.evalParm("ayon_remap_paths_remap_json")
- mapping = json.loads(value)
- assert isinstance(self._mapping, dict)
-
- # Ensure all keys are normalized paths so the lookup can be done
- # correctly
- mapping = {
- os.path.normpath(key): value for key, value in mapping.items()
- }
- self._mapping = mapping
-
- def processReferencePath(self,
- asset_path,
- referencing_layer_path,
- asset_is_layer):
- return self._mapping.get(os.path.normpath(asset_path), asset_path)
-
-
-def usdOutputProcessor():
- return AYONRemapPaths
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/INDEX__SECTION b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/INDEX__SECTION
deleted file mode 100644
index 5b5d5a1340..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/INDEX__SECTION
+++ /dev/null
@@ -1,13 +0,0 @@
-Operator: ayon::lop_import::1.0
-Label: AYON Load Asset
-Path: oplib:/ayon::Lop/lop_import::1.0?ayon::Lop/lop_import::1.0
-Icon: opdef:/ayon::Lop/lop_import::1.0?IconImage
-Table: Lop
-License:
-Extra:
-User:
-Inputs: 0 to 1
-Subnet: true
-Python: false
-Empty: false
-Modified: Thu Jun 10 16:44:00 2024
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/Sections.list b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/Sections.list
deleted file mode 100644
index 0a1bfe4e69..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/Sections.list
+++ /dev/null
@@ -1,4 +0,0 @@
-""
-INDEX__SECTION INDEX_SECTION
-houdini.hdalibrary houdini.hdalibrary
-ayon_8_8Lop_1lop__import_8_81.0 ayon::Lop/lop_import::1.0
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/AYON__icon.png b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/AYON__icon.png
deleted file mode 100644
index ed13aeea52..0000000000
Binary files a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/AYON__icon.png and /dev/null differ
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.createtimes b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.createtimes
deleted file mode 100644
index 2d6bdf00aa..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.createtimes
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "hdaroot/warn_no_representation_set.def":1708980551,
- "hdaroot/reference.def":1698150558,
- "hdaroot/output0.def":1698215383,
- "hdaroot.def":1717451587
-}
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.houdini_versions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.houdini_versions
deleted file mode 100644
index fb7f024db3..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.houdini_versions
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "values":["20.0.703"
- ],
- "indexes":{
- "hdaroot/warn_no_representation_set.userdata":0,
- "hdaroot/reference.userdata":0,
- "hdaroot/output0.userdata":0
- }
-}
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.mime b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.mime
deleted file mode 100644
index ec12230a75..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.mime
+++ /dev/null
@@ -1,384 +0,0 @@
-MIME-Version: 1.0
-Content-Type: multipart/mixed; boundary="HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY"
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="node_type"
-Content-Type: text/plain
-
-Lop
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot.init"
-Content-Type: text/plain
-
-type = ayon::lop_import::1.0
-matchesdef = 0
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot.def"
-Content-Type: text/plain
-
-comment ""
-position -1.67827 1.26636
-connectornextid 0
-flags = lock off model off template off footprint off xray off bypass off display on render on highlight off unload off savedata off compress on colordefault on exposed on debug off
-outputsNamed3
-{
-}
-inputsNamed3
-{
-}
-inputs
-{
-}
-stat
-{
- create -1
- modify -1
- author Mustafa_Taher@Major-Kalawy
- access 0777
-}
-color UT_Color RGB 0.8 0.8 0.8
-delscript ""
-exprlanguage hscript
-end
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot.userdata"
-Content-Type: text/plain
-
-{
- "___Version___":{
- "type":"string",
- "value":""
- },
- "wirestyle":{
- "type":"string",
- "value":"rounded"
- }
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot.inp"
-Content-Type: text/plain
-
-1
-0 -1.3044269152357999 6.2635205889365251 2 0 __NO_OWNER_NETWORK_BOX__ "FROMOUTPUT"
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/output0.init"
-Content-Type: text/plain
-
-type = output
-matchesdef = 1
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/output0.def"
-Content-Type: text/plain
-
-comment ""
-position -1.13898e-08 2.03328
-connectornextid 1
-flags = lock off model off template off footprint off xray off bypass off display off render on highlight off unload off savedata off compress on colordefault on exposed on debug off
-outputsNamed3
-{
-}
-inputsNamed3
-{
-0 reference 0 1 "input1"
-}
-inputs
-{
-0 reference 0 1
-}
-stat
-{
- create -1
- modify -1
- author Maqina-05@Maqina-05
- access 0777
-}
-color UT_Color RGB 0.8 0.8 0.8
-delscript ""
-exprlanguage hscript
-end
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/output0.parm"
-Content-Type: text/plain
-
-{
-version 0.8
-outputidx [ 0 locks=0 ] ( 0 )
-modifiedprims [ 0 locks=0 ] ( "`lopinputprims(\".\", 0)`" )
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/output0.userdata"
-Content-Type: text/plain
-
-{
- "___Version___":{
- "type":"string",
- "value":"___EXTERNAL___"
- }
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/reference.init"
-Content-Type: text/plain
-
-type = reference::2.0
-matchesdef = 1
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/reference.def"
-Content-Type: text/plain
-
-comment ""
-position -0.00125004 4.19692
-connectornextid 2
-flags = lock off model off template off footprint off xray off bypass off display off render off highlight off unload off savedata off compress on colordefault on exposed on debug off
-outputsNamed3
-{
-0 "output1"
-}
-inputsNamed3
-{
-1 warn_no_representation_set 1 1 "input1"
-}
-inputs
-{
-0 warn_no_representation_set 0 1
-}
-stat
-{
- create -1
- modify -1
- author Maqina-05@Maqina-05
- access 0777
-}
-color UT_Color RGB 0.8 0.8 0.8
-delscript ""
-exprlanguage hscript
-end
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/reference.editableinputdata"
-Content-Type: text/plain
-
-[
- {
- }
-]
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/reference.chn"
-Content-Type: text/plain
-
-{
- channel primpath1 {
- lefttype = extend
- righttype = extend
- defaultString = \"/character\"
- flags = 0
- start = 40
- segment { length = 0 expr = chs(\"../primpath1\") }
- }
- channel reftype1 {
- lefttype = extend
- righttype = extend
- defaultString = \"file\"
- flags = 0
- start = 40
- segment { length = 0 expr = chs(\"../reftype1\") }
- }
- channel instanceable1 {
- lefttype = extend
- righttype = extend
- flags = 0
- start = 40
- segment { length = 0 expr = ch(\"../instanceable1\") }
- }
- channel filerefprim1 {
- lefttype = extend
- righttype = extend
- defaultString = \"automaticPrim\"
- flags = 0
- start = 40
- segment { length = 0 expr = chs(\"../filerefprim1\") }
- }
- channel filerefprimpath1 {
- lefttype = extend
- righttype = extend
- defaultString = \"\"
- flags = 0
- start = 40
- segment { length = 0 expr = chs(\"../filerefprimpath1\") }
- }
- channel timeoffset1 {
- lefttype = extend
- righttype = extend
- flags = 0
- start = 40
- segment { length = 0 expr = ch(\"../timeoffset1\") }
- }
- channel timescale1 {
- lefttype = extend
- righttype = extend
- default = 1
- flags = 0
- start = 40
- segment { length = 0 value = 1 1 expr = ch(\"../timescale1\") }
- }
- channel reload {
- lefttype = extend
- righttype = extend
- flags = 0
- start = 40
- segment { length = 0 expr = ch(\"../reload\") }
- }
- }
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/reference.parm"
-Content-Type: text/plain
-
-{
-version 0.8
-main_switcher [ 0 locks=0 ] ( 0 0 0 )
-enable [ 0 locks=0 ] ( "on" )
-input_group [ 0 locks=0 ] ( 0 )
-primpath [ 0 locks=0 ] ( /`@sourcename` )
-createprims [ 0 locks=0 ] ( "on" )
-primcount [ 0 locks=0 ] ( 1 )
-reftype [ 0 locks=0 ] ( file )
-instanceable [ 0 locks=0 ] ( "off" )
-refprim [ 0 locks=0 ] ( automaticPrim )
-refprimpath [ 0 locks=0 ] ( automaticPrim )
-files_group [ 0 locks=0 ] ( 1 )
-num_files [ 0 locks=0 ] ( 1 )
-reload [ 0 locks=0 ] ( [ reload 0 ] )
-primkind [ 0 locks=0 ] ( "" )
-parentprimtype [ 0 locks=0 ] ( UsdGeomXform )
-handlemissingfiles [ 0 locks=0 ] ( error )
-preop [ 0 locks=0 ] ( none )
-refeditop [ 0 locks=0 ] ( prependfront )
-enable1 [ 0 locks=0 ] ( "on" )
-parameterorder1 [ 0 locks=0 ] ( "filefirst" )
-createprims1 [ 0 locks=0 ] ( "on" )
-primpath1 [ 0 locks=0 ] ( [ primpath1 /`@sourcename` ] )
-primcount1 [ 0 locks=0 ] ( 1 )
-reftype1 [ 0 locks=0 ] ( [ reftype1 file ] )
-instanceable1 [ 0 locks=0 ] ( [ instanceable1 0 ] )
-filepath1 [ 0 locks=0 ] ( `chs(\"../file\")` )
-filerefprim1 [ 0 locks=0 ] ( [ filerefprim1 automaticPrim ] )
-filerefprimpath1 [ 0 locks=0 ] ( [ filerefprimpath1 "" ] )
-timeoffset1 [ 0 locks=0 ] ( [ timeoffset1 0 ] )
-timescale1 [ 0 locks=0 ] ( [ timescale1 1 ] )
-file_spacer1 [ 0 locks=0 ] ( )
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/reference.userdata"
-Content-Type: text/plain
-
-{
- "___Version___":{
- "type":"string",
- "value":"___EXTERNAL___"
- }
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.init"
-Content-Type: text/plain
-
-type = error
-matchesdef = 1
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.def"
-Content-Type: text/plain
-
-comment ""
-position -1.13898e-08 5.20999
-connectornextid 2
-flags = lock off model off template off footprint off xray off bypass off display on render off highlight off unload off savedata off compress on colordefault on exposed on debug off
-outputsNamed3
-{
-1 "output1"
-}
-inputsNamed3
-{
-0 (0) "" 1 "input1"
-}
-inputs
-{
-0 (0) 0 1
-}
-stat
-{
- create -1
- modify -1
- author User@HP-Z820-03
- access 0777
-}
-color UT_Color RGB 0.8 0.8 0.8
-delscript ""
-exprlanguage hscript
-end
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.chn"
-Content-Type: text/plain
-
-{
- channel enable1 {
- lefttype = extend
- righttype = extend
- flags = 0
- start = 39.800000000000004
- segment { length = 0 expr = "if(ch(\"../representation\"), 0, 1)" }
- }
- }
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.parm"
-Content-Type: text/plain
-
-{
-version 0.8
-numerror [ 0 locks=0 ] ( 1 )
-errormsg1 [ 0 locks=0 ] ( `chs(\"../load_message\")` )
-severity1 [ 0 locks=0 ] ( "warn" )
-enable1 [ 0 locks=0 ] ( [ enable1 0 ] )
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot/warn_no_representation_set.userdata"
-Content-Type: text/plain
-
-{
- "___Version___":{
- "type":"string",
- "value":"___EXTERNAL___"
- }
-}
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot.order"
-Content-Type: text/plain
-
-3
-output0
-reference
-warn_no_representation_set
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY
-Content-Disposition: attachment; filename="hdaroot.net"
-Content-Type: text/plain
-
-1
-
---HOUDINIMIMEBOUNDARY0xD3ADD339-0x00000F49-0x56B122C9-0x00000001HOUDINIMIMEBOUNDARY--
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.modtimes b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.modtimes
deleted file mode 100644
index dbbe22dd33..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Contents.modtimes
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "hdaroot/warn_no_representation_set.def":1711565807,
- "hdaroot/reference.def":1711565598,
- "hdaroot/output0.def":1708980807,
- "hdaroot.def":1717451686
-}
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Sections.list b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Sections.list
deleted file mode 100644
index 49f2c1522a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Contents.dir/Sections.list
+++ /dev/null
@@ -1,2 +0,0 @@
-""
-Contents.mime Contents.mime
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/CreateScript b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/CreateScript
deleted file mode 100644
index 9a59e191ee..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/CreateScript
+++ /dev/null
@@ -1,15 +0,0 @@
-# Automatically generated script
-\set noalias = 1
-#
-# Creation script for ayon::lop_import::1.0 operator
-#
-
-if ( "$arg1" == "" ) then
- echo This script is intended as a creation script
- exit
-endif
-
-# Node $arg1 (ayon::Lop/lop_import::1.0)
-opexprlanguage -s hscript $arg1
-opuserdata -n '___Version___' -v '' $arg1
-opuserdata -n 'wirestyle' -v 'rounded' $arg1
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript
deleted file mode 100644
index 3c97d10f35..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/DialogScript
+++ /dev/null
@@ -1,345 +0,0 @@
-# Dialog script for ayon::lop_import::1.0 automatically generated
-
-{
- name ayon::lop_import::1.0
- script load_asset::1.0
- label "Load Asset"
-
- help {
- ""
- }
-
- inputlabel 1 "Input Stage"
- inputlabel 2 "Input 2"
- inputlabel 3 "Input 3"
- inputlabel 4 "Input 4"
-
- groupsimple {
- name "info2"
- label "Info"
- parmtag { "script_callback" "hou.phm().refresh_available_versions(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
-
- parm {
- name "assetinfo_labelparm"
- label "Heading"
- type label
- default { "Choose Product" }
- parmtag { "sidefx::look" "heading" }
- }
- parm {
- name "project_name"
- label "Project"
- type string
- default { "$AYON_PROJECT_NAME" }
- parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_folder_path;select_folder_path(kwargs['node'])" }
- parmtag { "script_action_icon" "BUTTONS_reselect" }
- parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "folder_path"
- label "Folder Path"
- type string
- default { "$AYON_FOLDER_PATH" }
- parmtag { "script_action" "from ayon_houdini.api.hda_utils import select_folder_path;select_folder_path(kwargs['node'])" }
- parmtag { "script_action_icon" "BUTTONS_reselect" }
- parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "product_type"
- label "Product Type"
- type string
- default { "usd" }
- }
- parm {
- name "product_name"
- label "Product"
- type string
- default { "usdAsset" }
- menureplace {
- [ "products = hou.phm().get_available_products(kwargs['node'])" ]
- [ "" ]
- [ "result = []" ]
- [ "for product in products:" ]
- [ " result.append(product)" ]
- [ " result.append(product)" ]
- [ " " ]
- [ "return result" ]
- language python
- }
- parmtag { "script_callback" "hou.phm().set_to_latest_version(kwargs['node'])\nhou.phm().on_representation_parms_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "version"
- label "Version"
- type string
- default { "" }
- disablewhen "{ representation_old == \"\" }"
- menureplace {
- [ "versions = hou.phm().get_available_versions(kwargs['node'])" ]
- [ "" ]
- [ "result = []" ]
- [ "for version in versions:" ]
- [ " result.append(str(version))" ]
- [ " result.append(f\"v{version:03d}\")" ]
- [ " " ]
- [ "return result" ]
- language python
- }
- parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "representation_name"
- label "Representation"
- type string
- default { "usd" }
- parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "load_refresh"
- label "Refresh"
- type button
- joinnext
- default { "0" }
- help "Click to refresh and retry applying the product load parameters to load the correct file"
- parmtag { "button_icon" "" }
- parmtag { "script_callback" "hou.phm().on_representation_parms_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "load_message"
- label "Message"
- type label
- default { "" }
- hidewhen "{ load_message == \"\" }"
- parmtag { "sidefx::look" "block" }
- }
- parm {
- name "sepparm"
- label "Separator"
- type separator
- default { "" }
- }
- parm {
- name "reload"
- label "Reload Files"
- type button
- default { "0" }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "file"
- label "File"
- type string
- default { "" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "primpath1"
- label "Primitive Root"
- type string
- default { "`chs(\"folder_path\")`/$OS" }
- menureplace {
- [ "opmenu -l -a reference_character primpath1" ]
- }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_action" "import loputils\nloputils.selectPrimsInParm(kwargs, False)" }
- parmtag { "script_action_help" "Select a primitive in the Scene Viewer or Scene Graph Tree pane.\nCtrl-click to select using the primitive picker dialog.\nAlt-click to toggle movement of the display flag." }
- parmtag { "script_action_icon" "BUTTONS_reselect" }
- parmtag { "sidefx::usdpathtype" "prim" }
- }
- groupcollapsible {
- name "extra_options"
- label "Load Options"
-
- parm {
- name "reftype1"
- label "Reference Type"
- type string
- default { "file" }
- menu {
- "file" "Reference File"
- "payload" "Payload File"
- }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "instanceable1"
- label "Make Instanceable"
- type toggle
- default { "off" }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "filerefprim1"
- label "Reference Primitive"
- type string
- default { "automaticPrim" }
- hidewhen "{ reftype1 == prim } { reftype1 == inherit } { reftype1 == specialize }"
- menu {
- "automaticPrim" "Reference Automatically Chosen Primitive"
- "defaultPrim" "Reference Default Primitive"
- "" "Reference Specific Primitive"
- }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "filerefprimpath1"
- label "Reference Primitive Path"
- type string
- default { "" }
- disablewhen "{ filerefprim1 != \"\" reftype1 != prim reftype1 != inherit reftype1 != specialize }"
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_action" "import loputils\nnode = kwargs['node']\nparm = kwargs['parmtuple'][0]\nreftype = node.evalParm(parm.name().replace(\n 'filerefprimpath', 'reftype'))\nif reftype in ('prim', 'inherit', 'specialize'):\n prims = loputils.selectPrimsInParm(kwargs, True)\nelse:\n parm = node.parm(parm.name().replace(\n 'filerefprimpath', 'filepath'))\n prims = loputils.selectPrimsInParmFromFile(kwargs, False,\n parm.evalAsString().strip('\\'\"'))" }
- parmtag { "script_action_help" "Select a primitive from a primitive picker dialog." }
- parmtag { "script_action_icon" "BUTTONS_reselect" }
- parmtag { "sidefx::usdpathinput" "if(index(\"prim inherit specialize\", chs(\"reftype#\")) >= 0, 0, -1)" }
- parmtag { "sidefx::usdpathtype" "prim" }
- }
- parm {
- name "timeoffset1"
- label "Time Offset (in Frames)"
- type float
- default { "0" }
- range { -100 100 }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "timescale1"
- label "Time Scale"
- type float
- default { "1" }
- range { 0 5 }
- parmtag { "autoscope" "0000000000000000" }
- parmtag { "script_callback_language" "python" }
- }
- }
-
- }
-
- groupcollapsible {
- name "info_display2"
- label "Info Display"
-
- parm {
- name "show_thumbnail"
- label "Show Entity Thumbnail"
- type toggle
- joinnext
- default { "0" }
- parmtag { "script_callback" "hou.phm().on_thumbnail_show_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "thumbnail_size"
- label "Size"
- type float
- joinnext
- default { "2" }
- hidewhen "{ show_thumbnail == 0 }"
- range { 0 10 }
- parmtag { "script_callback" "hou.phm().on_thumbnail_size_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "thumbnail_cache_dir"
- label "Thumbnail Cache Dir"
- type directory
- invisible
- default { "$JOB/.houdini_loader_thumbnails" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "thumbnail_padding"
- label "Padding"
- type float
- invisible
- default { "1" }
- range { 0 10 }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "thumbnail_offset"
- label "Offset"
- type vector2
- size 2
- default { "0" "0.35" }
- hidewhen "{ show_thumbnail == 0 }"
- range { -1 1 }
- parmtag { "script_callback" "hou.phm().on_thumbnail_size_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "show_pipeline_parms"
- label "Show Pipeline Parms"
- type toggle
- default { "0" }
- parmtag { "script_callback" "hou.phm().on_thumbnail_show_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- }
-
- group {
- name "ayon_folder0"
- label "Ayon"
- hidewhen "{ show_pipeline_parms == 0 }"
-
- parm {
- name "name"
- label "Name"
- type label
- default { "$OS" }
- }
- parm {
- name "namespace"
- label "Namespace"
- type label
- default { "`opfullpath(\".\")`" }
- }
- parm {
- name "loader"
- label "Loader"
- type label
- default { "LOPLoadAssetLoader" }
- }
- parm {
- name "id"
- label "ID"
- type label
- default { "pyblish.avalon.container" }
- }
- parm {
- name "representation"
- label "Representation ID"
- type string
- default { "" }
- parmtag { "script_callback" "hou.phm().on_representation_id_changed(kwargs['node'])" }
- parmtag { "script_callback_language" "python" }
- }
- parm {
- name "version_name"
- label "Current Version Label"
- type label
- invisible
- default { "" }
- }
- parm {
- name "subset_name"
- label "Subset (backwards compatibility)"
- type label
- invisible
- default { "`chs(\"product_name\")`" }
- }
- }
-
-}
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/ExtraFileOptions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/ExtraFileOptions
deleted file mode 100644
index fb58b6889f..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/ExtraFileOptions
+++ /dev/null
@@ -1,122 +0,0 @@
-{
- "AYON_icon.png/Cursor":{
- "type":"intarray",
- "value":[0,0]
- },
- "AYON_icon.png/IsExpr":{
- "type":"bool",
- "value":false
- },
- "AYON_icon.png/IsPython":{
- "type":"bool",
- "value":false
- },
- "AYON_icon.png/IsScript":{
- "type":"bool",
- "value":false
- },
- "AYON_icon.png/Source":{
- "type":"string",
- "value":"C:/Users/Maqina-05/Desktop/AYON_icon.png"
- },
- "OnCreated/Cursor":{
- "type":"intarray",
- "value":[5,29]
- },
- "OnCreated/IsExpr":{
- "type":"bool",
- "value":false
- },
- "OnCreated/IsPython":{
- "type":"bool",
- "value":true
- },
- "OnCreated/IsScript":{
- "type":"bool",
- "value":true
- },
- "OnCreated/Source":{
- "type":"string",
- "value":""
- },
- "OnDeleted/Cursor":{
- "type":"intarray",
- "value":[1,15]
- },
- "OnDeleted/IsExpr":{
- "type":"bool",
- "value":false
- },
- "OnDeleted/IsPython":{
- "type":"bool",
- "value":true
- },
- "OnDeleted/IsScript":{
- "type":"bool",
- "value":true
- },
- "OnDeleted/Source":{
- "type":"string",
- "value":""
- },
- "OnLoaded/Cursor":{
- "type":"intarray",
- "value":[9,76]
- },
- "OnLoaded/IsExpr":{
- "type":"bool",
- "value":false
- },
- "OnLoaded/IsPython":{
- "type":"bool",
- "value":true
- },
- "OnLoaded/IsScript":{
- "type":"bool",
- "value":true
- },
- "OnLoaded/Source":{
- "type":"string",
- "value":""
- },
- "OnNameChanged/Cursor":{
- "type":"intarray",
- "value":[1,15]
- },
- "OnNameChanged/IsExpr":{
- "type":"bool",
- "value":false
- },
- "OnNameChanged/IsPython":{
- "type":"bool",
- "value":true
- },
- "OnNameChanged/IsScript":{
- "type":"bool",
- "value":true
- },
- "OnNameChanged/Source":{
- "type":"string",
- "value":""
- },
- "PythonModule/Cursor":{
- "type":"intarray",
- "value":[10,1]
- },
- "PythonModule/IsExpr":{
- "type":"bool",
- "value":false
- },
- "PythonModule/IsPython":{
- "type":"bool",
- "value":true
- },
- "PythonModule/IsScript":{
- "type":"bool",
- "value":true
- },
- "PythonModule/Source":{
- "type":"string",
- "value":""
- }
-}
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Help b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Help
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/IconImage b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/IconImage
deleted file mode 100644
index aa0d42021d..0000000000
Binary files a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/IconImage and /dev/null differ
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/InternalFileOptions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/InternalFileOptions
deleted file mode 100644
index 222988aa02..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/InternalFileOptions
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "nodeconntype":{
- "type":"bool",
- "value":false
- },
- "nodeparmtype":{
- "type":"bool",
- "value":false
- }
-}
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/MessageNodes b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/MessageNodes
deleted file mode 100644
index ff389a716b..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/MessageNodes
+++ /dev/null
@@ -1 +0,0 @@
-warn_no_representation_set reference
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated
deleted file mode 100644
index 06ccdaa653..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnCreated
+++ /dev/null
@@ -1,6 +0,0 @@
-node = kwargs["node"]
-hda_module = node.hdaModule()
-hda_module.setup_flag_changed_callback(node)
-
-node.parm("product_type").lock(True)
-node.parm("file").lock(True)
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnDeleted b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnDeleted
deleted file mode 100644
index 42e4ef2401..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnDeleted
+++ /dev/null
@@ -1,6 +0,0 @@
-from ayon_houdini.api.lib import remove_all_thumbnails
-
-
-# Clear thumbnails
-node = kwargs["node"]
-remove_all_thumbnails(node)
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnLoaded b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnLoaded
deleted file mode 100644
index 42e3c6e640..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnLoaded
+++ /dev/null
@@ -1,14 +0,0 @@
-node = kwargs["node"]
-hda_module = node.hdaModule()
-hda_module.setup_flag_changed_callback(node)
-
-
-# Duplicate callback
-def on_duplicate():
- """Duplicate thumbnail on node duplicate"""
- if node.evalParm("show_thumbnail") and node.evalParm("representation"):
- hda_module.on_representation_id_changed(node)
-
-
-if not hou.hipFile.isLoadingHipFile():
- on_duplicate()
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnNameChanged b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnNameChanged
deleted file mode 100644
index 2076f5ad9d..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/OnNameChanged
+++ /dev/null
@@ -1,8 +0,0 @@
-from ayon_houdini.api.hda_utils import (
- keep_background_images_linked
-)
-
-
-node = kwargs["node"]
-old_name = kwargs["old_name"]
-keep_background_images_linked(node, old_name)
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule
deleted file mode 100644
index 934fb2f612..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/PythonModule
+++ /dev/null
@@ -1,10 +0,0 @@
-from ayon_houdini.api.hda_utils import (
- on_thumbnail_show_changed,
- on_thumbnail_size_changed,
- on_representation_id_changed,
- on_representation_parms_changed,
- setup_flag_changed_callback,
- get_available_versions,
- get_available_products,
- set_to_latest_version
-)
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Sections.list b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Sections.list
deleted file mode 100644
index fe6cf4f0ef..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Sections.list
+++ /dev/null
@@ -1,17 +0,0 @@
-""
-DialogScript DialogScript
-CreateScript CreateScript
-InternalFileOptions InternalFileOptions
-Contents.gz Contents.gz
-TypePropertiesOptions TypePropertiesOptions
-Tools.shelf Tools.shelf
-Help Help
-IconImage IconImage
-MessageNodes MessageNodes
-PythonModule PythonModule
-OnDeleted OnDeleted
-OnNameChanged OnNameChanged
-OnLoaded OnLoaded
-OnCreated OnCreated
-ExtraFileOptions ExtraFileOptions
-AYON__icon.png AYON_icon.png
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Tools.shelf b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Tools.shelf
deleted file mode 100644
index 0c76942883..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/Tools.shelf
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
- LOP
-
-
- $HDA_TABLE_AND_NAME
-
- AYON
-
-
-
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/TypePropertiesOptions b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/TypePropertiesOptions
deleted file mode 100644
index a6d52acf2a..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/ayon_8_8Lop_1lop__import_8_81.0/TypePropertiesOptions
+++ /dev/null
@@ -1,14 +0,0 @@
-CheckExternal := 1;
-ContentsCompressionType := 1;
-ForbidOutsideParms := 1;
-GzipContents := 1;
-LockContents := 1;
-MakeDefault := 1;
-ParmsFromVfl := 0;
-PrefixDroppedParmLabel := 0;
-PrefixDroppedParmName := 0;
-SaveCachedCode := 0;
-SaveIcon := 1;
-SaveSpareParms := 0;
-UnlockOnCreate := 0;
-UseDSParms := 1;
diff --git a/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/houdini.hdalibrary b/server_addon/houdini/client/ayon_houdini/startup/otls/ayon_lop_import.hda/houdini.hdalibrary
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/server_addon/houdini/client/ayon_houdini/startup/python2.7libs/pythonrc.py b/server_addon/houdini/client/ayon_houdini/startup/python2.7libs/pythonrc.py
deleted file mode 100644
index 40ff7fb758..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/python2.7libs/pythonrc.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-"""OpenPype startup script."""
-from ayon_core.pipeline import install_host
-from ayon_houdini.api import HoudiniHost
-
-
-def main():
- print("Installing AYON ...")
- install_host(HoudiniHost())
-
-
-main()
diff --git a/server_addon/houdini/client/ayon_houdini/startup/python3.10libs/pythonrc.py b/server_addon/houdini/client/ayon_houdini/startup/python3.10libs/pythonrc.py
deleted file mode 100644
index 40ff7fb758..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/python3.10libs/pythonrc.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-"""OpenPype startup script."""
-from ayon_core.pipeline import install_host
-from ayon_houdini.api import HoudiniHost
-
-
-def main():
- print("Installing AYON ...")
- install_host(HoudiniHost())
-
-
-main()
diff --git a/server_addon/houdini/client/ayon_houdini/startup/python3.7libs/pythonrc.py b/server_addon/houdini/client/ayon_houdini/startup/python3.7libs/pythonrc.py
deleted file mode 100644
index 40ff7fb758..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/python3.7libs/pythonrc.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-"""OpenPype startup script."""
-from ayon_core.pipeline import install_host
-from ayon_houdini.api import HoudiniHost
-
-
-def main():
- print("Installing AYON ...")
- install_host(HoudiniHost())
-
-
-main()
diff --git a/server_addon/houdini/client/ayon_houdini/startup/python3.9libs/pythonrc.py b/server_addon/houdini/client/ayon_houdini/startup/python3.9libs/pythonrc.py
deleted file mode 100644
index 40ff7fb758..0000000000
--- a/server_addon/houdini/client/ayon_houdini/startup/python3.9libs/pythonrc.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# -*- coding: utf-8 -*-
-"""OpenPype startup script."""
-from ayon_core.pipeline import install_host
-from ayon_houdini.api import HoudiniHost
-
-
-def main():
- print("Installing AYON ...")
- install_host(HoudiniHost())
-
-
-main()
diff --git a/server_addon/houdini/client/ayon_houdini/version.py b/server_addon/houdini/client/ayon_houdini/version.py
deleted file mode 100644
index 9a101eeacc..0000000000
--- a/server_addon/houdini/client/ayon_houdini/version.py
+++ /dev/null
@@ -1,3 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Package declaring AYON addon 'houdini' version."""
-__version__ = "0.3.9"
diff --git a/server_addon/houdini/package.py b/server_addon/houdini/package.py
deleted file mode 100644
index ce10ad33bb..0000000000
--- a/server_addon/houdini/package.py
+++ /dev/null
@@ -1,10 +0,0 @@
-name = "houdini"
-title = "Houdini"
-version = "0.3.9"
-
-client_dir = "ayon_houdini"
-
-ayon_required_addons = {
- "core": ">0.3.2",
-}
-ayon_compatible_addons = {}
diff --git a/server_addon/houdini/server/__init__.py b/server_addon/houdini/server/__init__.py
deleted file mode 100644
index 8c1ffcb0b3..0000000000
--- a/server_addon/houdini/server/__init__.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from typing import Type
-
-from ayon_server.addons import BaseServerAddon
-
-from .settings import HoudiniSettings, DEFAULT_VALUES
-
-
-class Houdini(BaseServerAddon):
- settings_model: Type[HoudiniSettings] = HoudiniSettings
-
- async def get_default_settings(self):
- settings_model_cls = self.get_settings_model()
- return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/houdini/server/settings/__init__.py b/server_addon/houdini/server/settings/__init__.py
deleted file mode 100644
index 9fd2678925..0000000000
--- a/server_addon/houdini/server/settings/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from .main import (
- HoudiniSettings,
- DEFAULT_VALUES,
-)
-
-
-__all__ = (
- "HoudiniSettings",
- "DEFAULT_VALUES",
-)
diff --git a/server_addon/houdini/server/settings/create.py b/server_addon/houdini/server/settings/create.py
deleted file mode 100644
index ae01ab6642..0000000000
--- a/server_addon/houdini/server/settings/create.py
+++ /dev/null
@@ -1,187 +0,0 @@
-from ayon_server.settings import BaseSettingsModel, SettingsField
-
-
-# Creator Plugins
-class CreatorModel(BaseSettingsModel):
- enabled: bool = SettingsField(title="Enabled")
- default_variants: list[str] = SettingsField(
- title="Default Products",
- default_factory=list,
- )
-
-
-class CreateArnoldAssModel(BaseSettingsModel):
- enabled: bool = SettingsField(title="Enabled")
- default_variants: list[str] = SettingsField(
- title="Default Products",
- default_factory=list,
- )
- ext: str = SettingsField(Title="Extension")
-
-
-class CreateStaticMeshModel(BaseSettingsModel):
- enabled: bool = SettingsField(title="Enabled")
- default_variants: list[str] = SettingsField(
- default_factory=list,
- title="Default Products"
- )
- static_mesh_prefix: str = SettingsField("S", title="Static Mesh Prefix")
- collision_prefixes: list[str] = SettingsField(
- default_factory=list,
- title="Collision Prefixes"
- )
-
-
-class CreateUSDRenderModel(CreatorModel):
- default_renderer: str = SettingsField(
- "Karma CPU",
- title="Default Renderer",
- description=(
- "Specify either the Hydra renderer plug-in nice name, like "
- "'Karma CPU', or the plug-in name, e.g. 'BRAY_HdKarma'"
- ))
-
-
-class CreatePluginsModel(BaseSettingsModel):
- CreateAlembicCamera: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Alembic Camera")
- CreateArnoldAss: CreateArnoldAssModel = SettingsField(
- default_factory=CreateArnoldAssModel,
- title="Create Arnold Ass")
- CreateArnoldRop: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Arnold ROP")
- CreateCompositeSequence: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Composite (Image Sequence)")
- CreateHDA: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Houdini Digital Asset")
- CreateKarmaROP: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Karma ROP")
- CreateMantraROP: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Mantra ROP")
- CreateModel: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Model")
- CreatePointCache: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create PointCache (Abc)")
- CreateBGEO: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create PointCache (Bgeo)")
- CreateRedshiftProxy: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Redshift Proxy")
- CreateRedshiftROP: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Redshift ROP")
- CreateReview: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create Review")
- # "-" is not compatible in the new model
- CreateStaticMesh: CreateStaticMeshModel = SettingsField(
- default_factory=CreateStaticMeshModel,
- title="Create Static Mesh")
- CreateUSD: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create USD")
- CreateUSDRender: CreateUSDRenderModel = SettingsField(
- default_factory=CreateUSDRenderModel,
- title="Create USD render")
- CreateVDBCache: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create VDB Cache")
- CreateVrayROP: CreatorModel = SettingsField(
- default_factory=CreatorModel,
- title="Create VRay ROP")
-
-
-DEFAULT_HOUDINI_CREATE_SETTINGS = {
- "CreateAlembicCamera": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateArnoldAss": {
- "enabled": True,
- "default_variants": ["Main"],
- "ext": ".ass"
- },
- "CreateArnoldRop": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateCompositeSequence": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateHDA": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateKarmaROP": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateMantraROP": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateModel": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreatePointCache": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateBGEO": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateRedshiftProxy": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateRedshiftROP": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateReview": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateStaticMesh": {
- "enabled": True,
- "default_variants": [
- "Main"
- ],
- "static_mesh_prefix": "S",
- "collision_prefixes": [
- "UBX",
- "UCP",
- "USP",
- "UCX"
- ]
- },
- "CreateUSD": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateUSDRender": {
- "enabled": True,
- "default_variants": ["Main"],
- "default_renderer": "Karma CPU"
- },
- "CreateVDBCache": {
- "enabled": True,
- "default_variants": ["Main"]
- },
- "CreateVrayROP": {
- "enabled": True,
- "default_variants": ["Main"]
- },
-}
diff --git a/server_addon/houdini/server/settings/general.py b/server_addon/houdini/server/settings/general.py
deleted file mode 100644
index b71feae554..0000000000
--- a/server_addon/houdini/server/settings/general.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from ayon_server.settings import BaseSettingsModel, SettingsField
-
-
-class HoudiniVarModel(BaseSettingsModel):
- _layout = "expanded"
- var: str = SettingsField("", title="Var")
- value: str = SettingsField("", title="Value")
- is_directory: bool = SettingsField(False, title="Treat as directory")
-
-
-class UpdateHoudiniVarcontextModel(BaseSettingsModel):
- """Sync vars with context changes.
-
- If a value is treated as a directory on update
- it will be ensured the folder exists.
- """
-
- enabled: bool = SettingsField(title="Enabled")
- # TODO this was dynamic dictionary '{var: path}'
- houdini_vars: list[HoudiniVarModel] = SettingsField(
- default_factory=list,
- title="Houdini Vars"
- )
-
-
-class GeneralSettingsModel(BaseSettingsModel):
- add_self_publish_button: bool = SettingsField(
- False,
- title="Add Self Publish Button"
- )
- update_houdini_var_context: UpdateHoudiniVarcontextModel = SettingsField(
- default_factory=UpdateHoudiniVarcontextModel,
- title="Update Houdini Vars on context change"
- )
-
-
-DEFAULT_GENERAL_SETTINGS = {
- "add_self_publish_button": False,
- "update_houdini_var_context": {
- "enabled": True,
- "houdini_vars": [
- {
- "var": "JOB",
- "value": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task[name]}", # noqa
- "is_directory": True
- }
- ]
- }
-}
diff --git a/server_addon/houdini/server/settings/imageio.py b/server_addon/houdini/server/settings/imageio.py
deleted file mode 100644
index d77ff0751c..0000000000
--- a/server_addon/houdini/server/settings/imageio.py
+++ /dev/null
@@ -1,114 +0,0 @@
-from pydantic import validator
-from ayon_server.settings import BaseSettingsModel, SettingsField
-from ayon_server.settings.validators import ensure_unique_names
-
-
-class ImageIOConfigModel(BaseSettingsModel):
- """[DEPRECATED] Addon OCIO config settings. Please set the OCIO config
- path in the Core addon profiles here
- (ayon+settings://core/imageio/ocio_config_profiles).
- """
-
- override_global_config: bool = SettingsField(
- False,
- title="Override global OCIO config",
- description=(
- "DEPRECATED functionality. Please set the OCIO config path in the "
- "Core addon profiles here (ayon+settings://core/imageio/"
- "ocio_config_profiles)."
- ),
- )
- filepath: list[str] = SettingsField(
- default_factory=list,
- title="Config path",
- description=(
- "DEPRECATED functionality. Please set the OCIO config path in the "
- "Core addon profiles here (ayon+settings://core/imageio/"
- "ocio_config_profiles)."
- ),
- )
-
-
-class ImageIOFileRuleModel(BaseSettingsModel):
- name: str = SettingsField("", title="Rule name")
- pattern: str = SettingsField("", title="Regex pattern")
- colorspace: str = SettingsField("", title="Colorspace name")
- ext: str = SettingsField("", title="File extension")
-
-
-class ImageIOFileRulesModel(BaseSettingsModel):
- activate_host_rules: bool = SettingsField(False)
- rules: list[ImageIOFileRuleModel] = SettingsField(
- default_factory=list,
- title="Rules"
- )
-
- @validator("rules")
- def validate_unique_outputs(cls, value):
- ensure_unique_names(value)
- return value
-
-
-class WorkfileImageIOModel(BaseSettingsModel):
- """Workfile settings help.
-
- Empty values will be skipped, allowing any existing env vars to
- pass through as defined.
-
- Note: The render space in Houdini is
- always set to the 'scene_linear' role."""
-
- enabled: bool = SettingsField(False, title="Enabled")
- default_display: str = SettingsField(
- title="Default active displays",
- description="It behaves like the 'OCIO_ACTIVE_DISPLAYS' env var,"
- " Colon-separated list of displays, e.g ACES:P3"
- )
- default_view: str = SettingsField(
- title="Default active views",
- description="It behaves like the 'OCIO_ACTIVE_VIEWS' env var,"
- " Colon-separated list of views, e.g sRGB:DCDM"
- )
- review_color_space: str = SettingsField(
- title="Review colorspace",
- description="It exposes OCIO Colorspace parameter in opengl nodes."
- "if left empty, Ayon will figure out the default "
- "colorspace using your default display and default view."
- )
-
-
-class HoudiniImageIOModel(BaseSettingsModel):
- activate_host_color_management: bool = SettingsField(
- True, title="Enable Color Management"
- )
- ocio_config: ImageIOConfigModel = SettingsField(
- default_factory=ImageIOConfigModel,
- title="OCIO config"
- )
- file_rules: ImageIOFileRulesModel = SettingsField(
- default_factory=ImageIOFileRulesModel,
- title="File Rules"
- )
- workfile: WorkfileImageIOModel = SettingsField(
- default_factory=WorkfileImageIOModel,
- title="Workfile"
- )
-
-
-DEFAULT_IMAGEIO_SETTINGS = {
- "activate_host_color_management": False,
- "ocio_config": {
- "override_global_config": False,
- "filepath": []
- },
- "file_rules": {
- "activate_host_rules": False,
- "rules": []
- },
- "workfile": {
- "enabled": False,
- "default_display": "ACES",
- "default_view": "sRGB",
- "review_color_space": ""
- }
-}
diff --git a/server_addon/houdini/server/settings/main.py b/server_addon/houdini/server/settings/main.py
deleted file mode 100644
index 3acab0ce74..0000000000
--- a/server_addon/houdini/server/settings/main.py
+++ /dev/null
@@ -1,50 +0,0 @@
-from ayon_server.settings import BaseSettingsModel, SettingsField
-from .general import (
- GeneralSettingsModel,
- DEFAULT_GENERAL_SETTINGS
-)
-from .imageio import (
- HoudiniImageIOModel,
- DEFAULT_IMAGEIO_SETTINGS
-)
-from .shelves import ShelvesModel
-from .create import (
- CreatePluginsModel,
- DEFAULT_HOUDINI_CREATE_SETTINGS
-)
-from .publish import (
- PublishPluginsModel,
- DEFAULT_HOUDINI_PUBLISH_SETTINGS,
-)
-
-
-class HoudiniSettings(BaseSettingsModel):
- general: GeneralSettingsModel = SettingsField(
- default_factory=GeneralSettingsModel,
- title="General"
- )
- imageio: HoudiniImageIOModel = SettingsField(
- default_factory=HoudiniImageIOModel,
- title="Color Management (ImageIO)"
- )
- shelves: list[ShelvesModel] = SettingsField(
- default_factory=list,
- title="Shelves Manager",
- )
- create: CreatePluginsModel = SettingsField(
- default_factory=CreatePluginsModel,
- title="Creator Plugins",
- )
- publish: PublishPluginsModel = SettingsField(
- default_factory=PublishPluginsModel,
- title="Publish Plugins",
- )
-
-
-DEFAULT_VALUES = {
- "general": DEFAULT_GENERAL_SETTINGS,
- "imageio": DEFAULT_IMAGEIO_SETTINGS,
- "shelves": [],
- "create": DEFAULT_HOUDINI_CREATE_SETTINGS,
- "publish": DEFAULT_HOUDINI_PUBLISH_SETTINGS
-}
diff --git a/server_addon/houdini/server/settings/publish.py b/server_addon/houdini/server/settings/publish.py
deleted file mode 100644
index b21de39e93..0000000000
--- a/server_addon/houdini/server/settings/publish.py
+++ /dev/null
@@ -1,218 +0,0 @@
-from ayon_server.settings import (
- BaseSettingsModel,
- SettingsField
-)
-
-
-# Publish Plugins
-class CollectAssetHandlesModel(BaseSettingsModel):
- """Collect Frame Range
- Disable this if you want the publisher to
- ignore start and end handles specified in the
- asset data for publish instances
- """
- use_asset_handles: bool = SettingsField(
- title="Use asset handles")
-
-
-class CollectChunkSizeModel(BaseSettingsModel):
- """Collect Chunk Size."""
- enabled: bool = SettingsField(title="Enabled")
- optional: bool = SettingsField(title="Optional")
- chunk_size: int = SettingsField(
- title="Frames Per Task")
-
-
-class AOVFilterSubmodel(BaseSettingsModel):
- """You should use the same host name you are using for Houdini."""
- host_name: str = SettingsField("", title="Houdini Host name")
- value: list[str] = SettingsField(
- default_factory=list,
- title="AOV regex"
- )
-
-
-class CollectLocalRenderInstancesModel(BaseSettingsModel):
-
- use_deadline_aov_filter: bool = SettingsField(
- False,
- title="Use Deadline AOV Filter"
- )
-
- aov_filter: AOVFilterSubmodel = SettingsField(
- default_factory=AOVFilterSubmodel,
- title="Reviewable products filter"
- )
-
-
-def product_types_enum():
- return [
- {"value": "camera", "label": "Camera (Abc)"},
- {"value": "pointcache", "label": "PointCache (Abc)/PointCache (Bgeo)"},
- {"value": "review", "label": "Review"},
- {"value": "staticMesh", "label": "Static Mesh (FBX)"},
- {"value": "usd", "label": "USD (experimental)"},
- {"value": "vdbcache", "label": "VDB Cache"},
- {"value": "imagesequence", "label": "Composite (Image Sequence)"},
- {"value": "ass", "label": "Arnold ASS"},
- {"value": "arnold_rop", "label": "Arnold ROP"},
- {"value": "mantra_rop", "label": "Mantra ROP"},
- {"value": "redshiftproxy", "label": "Redshift Proxy"},
- {"value": "redshift_rop", "label": "Redshift ROP"},
- {"value": "karma_rop", "label": "Karma ROP"},
- {"value": "vray_rop", "label": "VRay ROP"},
- {"value": "model", "label": "Model"},
- ]
-
-
-class CollectFilesForCleaningUpModel(BaseSettingsModel):
- enabled: bool = SettingsField(title="Enabled")
- optional: bool = SettingsField(title="Optional")
- active: bool = SettingsField(title="Active")
-
- families: list[str] = SettingsField(
- default_factory=list,
- enum_resolver=product_types_enum,
- conditionalEnum=True,
- title="Product Types"
- )
-
-
-class ValidateWorkfilePathsModel(BaseSettingsModel):
- enabled: bool = SettingsField(title="Enabled")
- optional: bool = SettingsField(title="Optional")
- node_types: list[str] = SettingsField(
- default_factory=list,
- title="Node Types"
- )
- prohibited_vars: list[str] = SettingsField(
- default_factory=list,
- title="Prohibited Variables"
- )
-
-
-class BasicEnabledStatesModel(BaseSettingsModel):
- enabled: bool = SettingsField(title="Enabled")
- optional: bool = SettingsField(title="Optional")
- active: bool = SettingsField(title="Active")
-
-
-class PublishPluginsModel(BaseSettingsModel):
- CollectAssetHandles: CollectAssetHandlesModel = SettingsField(
- default_factory=CollectAssetHandlesModel,
- title="Collect Asset Handles",
- section="Collectors"
- )
- CollectChunkSize: CollectChunkSizeModel = SettingsField(
- default_factory=CollectChunkSizeModel,
- title="Collect Chunk Size"
- )
- CollectFilesForCleaningUp: CollectFilesForCleaningUpModel = SettingsField(
- default_factory=CollectFilesForCleaningUpModel,
- title="Collect Files For Cleaning Up."
- )
- CollectLocalRenderInstances: CollectLocalRenderInstancesModel = SettingsField(
- default_factory=CollectLocalRenderInstancesModel,
- title="Collect Local Render Instances"
- )
- ValidateInstanceInContextHoudini: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Validate Instance is in same Context",
- section="Validators")
- ValidateMeshIsStatic: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Validate Mesh is Static")
- ValidateReviewColorspace: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Validate Review Colorspace")
- ValidateSubsetName: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Validate Subset Name")
- ValidateUnrealStaticMeshName: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Validate Unreal Static Mesh Name")
- ValidateWorkfilePaths: ValidateWorkfilePathsModel = SettingsField(
- default_factory=ValidateWorkfilePathsModel,
- title="Validate workfile paths settings")
- ValidateUSDRenderProductPaths: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Validate USD Render Product Paths")
- ExtractActiveViewThumbnail: BasicEnabledStatesModel = SettingsField(
- default_factory=BasicEnabledStatesModel,
- title="Extract Active View Thumbnail",
- section="Extractors"
- )
-
-
-DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
- "CollectAssetHandles": {
- "use_asset_handles": True
- },
- "CollectChunkSize": {
- "enabled": True,
- "optional": True,
- "chunk_size": 999999
- },
- "CollectFilesForCleaningUp": {
- "enabled": False,
- "optional": True,
- "active": True,
- "families" : []
- },
- "CollectLocalRenderInstances": {
- "use_deadline_aov_filter": False,
- "aov_filter": {
- "host_name": "houdini",
- "value": [
- ".*([Bb]eauty).*"
- ]
- }
- },
- "ValidateInstanceInContextHoudini": {
- "enabled": True,
- "optional": True,
- "active": True
- },
- "ValidateMeshIsStatic": {
- "enabled": True,
- "optional": True,
- "active": True
- },
- "ValidateReviewColorspace": {
- "enabled": True,
- "optional": True,
- "active": True
- },
- "ValidateSubsetName": {
- "enabled": True,
- "optional": True,
- "active": True
- },
- "ValidateUnrealStaticMeshName": {
- "enabled": False,
- "optional": True,
- "active": True
- },
- "ValidateWorkfilePaths": {
- "enabled": True,
- "optional": True,
- "node_types": [
- "file",
- "alembic"
- ],
- "prohibited_vars": [
- "$HIP",
- "$JOB"
- ]
- },
- "ValidateUSDRenderProductPaths": {
- "enabled": False,
- "optional": True,
- "active": True
- },
- "ExtractActiveViewThumbnail": {
- "enabled": True,
- "optional": False,
- "active": True
- }
-}
diff --git a/server_addon/houdini/server/settings/shelves.py b/server_addon/houdini/server/settings/shelves.py
deleted file mode 100644
index f6d7f1d06c..0000000000
--- a/server_addon/houdini/server/settings/shelves.py
+++ /dev/null
@@ -1,67 +0,0 @@
-from ayon_server.settings import (
- BaseSettingsModel,
- SettingsField,
- MultiplatformPathModel
-)
-
-
-class ShelfToolsModel(BaseSettingsModel):
- """Name and Script Path are mandatory."""
- label: str = SettingsField(title="Name")
- script: str = SettingsField(title="Script Path")
- icon: str = SettingsField("", title="Icon Path")
- help: str = SettingsField("", title="Help text")
-
-
-class ShelfDefinitionModel(BaseSettingsModel):
- _layout = "expanded"
- shelf_name: str = SettingsField(title="Shelf name")
- tools_list: list[ShelfToolsModel] = SettingsField(
- default_factory=list,
- title="Shelf Tools"
- )
-
-
-class AddShelfFileModel(BaseSettingsModel):
- shelf_set_source_path: MultiplatformPathModel = SettingsField(
- default_factory=MultiplatformPathModel,
- title="Shelf Set Path"
- )
-
-
-class AddSetAndDefinitionsModel(BaseSettingsModel):
- shelf_set_name: str = SettingsField("", title="Shelf Set Name")
- shelf_definition: list[ShelfDefinitionModel] = SettingsField(
- default_factory=list,
- title="Shelves Definitions"
- )
-
-
-def shelves_enum_options():
- return [
- {
- "value": "add_shelf_file",
- "label": "Add a .shelf file"
- },
- {
- "value": "add_set_and_definitions",
- "label": "Add Shelf Set Name and Shelves Definitions"
- }
- ]
-
-
-class ShelvesModel(BaseSettingsModel):
- options: str = SettingsField(
- title="Options",
- description="Switch between shelves manager options",
- enum_resolver=shelves_enum_options,
- conditionalEnum=True
- )
- add_shelf_file: AddShelfFileModel = SettingsField(
- title="Add a .shelf file",
- default_factory=AddShelfFileModel
- )
- add_set_and_definitions: AddSetAndDefinitionsModel = SettingsField(
- title="Add Shelf Set Name and Shelves Definitions",
- default_factory=AddSetAndDefinitionsModel
- )