mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-26 22:02:15 +01:00
Merge branch 'master' of https://github.com/Colorbleed/colorbleed-config
This commit is contained in:
commit
94f6bda8ee
13 changed files with 660 additions and 9 deletions
99
colorbleed/houdini/__init__.py
Normal file
99
colorbleed/houdini/__init__.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import os
|
||||
import logging
|
||||
|
||||
import hou
|
||||
|
||||
from pyblish import api as pyblish
|
||||
|
||||
from avalon import api as avalon
|
||||
from avalon.houdini import pipeline as houdini
|
||||
|
||||
from colorbleed.houdini import lib
|
||||
|
||||
from colorbleed.lib import (
|
||||
any_outdated,
|
||||
update_task_from_path
|
||||
)
|
||||
|
||||
|
||||
PARENT_DIR = os.path.dirname(__file__)
|
||||
PACKAGE_DIR = os.path.dirname(PARENT_DIR)
|
||||
PLUGINS_DIR = os.path.join(PACKAGE_DIR, "plugins")
|
||||
|
||||
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "houdini", "publish")
|
||||
LOAD_PATH = os.path.join(PLUGINS_DIR, "houdini", "load")
|
||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "houdini", "create")
|
||||
|
||||
log = logging.getLogger("colorbleed.houdini")
|
||||
|
||||
|
||||
def install():
|
||||
|
||||
# Set
|
||||
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
|
||||
log.info("Installing callbacks ... ")
|
||||
avalon.on("init", on_init)
|
||||
avalon.on("save", on_save)
|
||||
avalon.on("open", on_open)
|
||||
|
||||
log.info("Overriding existing event 'taskChanged'")
|
||||
|
||||
log.info("Setting default family states for loader..")
|
||||
avalon.data["familiesStateToggled"] = ["colorbleed.imagesequence"]
|
||||
|
||||
|
||||
def on_init(_):
|
||||
houdini.on_houdini_initialize()
|
||||
|
||||
|
||||
def on_save(_):
|
||||
|
||||
avalon.logger.info("Running callback on save..")
|
||||
|
||||
update_task_from_path(hou.hipFile.path())
|
||||
|
||||
nodes = lib.get_id_required_nodes()
|
||||
for node, new_id in lib.generate_ids(nodes):
|
||||
lib.set_id(node, new_id, overwrite=False)
|
||||
|
||||
|
||||
def on_open():
|
||||
|
||||
update_task_from_path(hou.hipFile.path())
|
||||
|
||||
if any_outdated():
|
||||
from avalon.vendor.Qt import QtWidgets
|
||||
from ..widgets import popup
|
||||
|
||||
log.warning("Scene has outdated content.")
|
||||
|
||||
# Find maya main window
|
||||
top_level_widgets = {w.objectName(): w for w in
|
||||
QtWidgets.QApplication.topLevelWidgets()}
|
||||
parent = top_level_widgets.get("MayaWindow", None)
|
||||
|
||||
if parent is None:
|
||||
log.info("Skipping outdated content pop-up "
|
||||
"because Maya window can't be found.")
|
||||
else:
|
||||
|
||||
# Show outdated pop-up
|
||||
def _on_show_inventory():
|
||||
import avalon.tools.cbsceneinventory as tool
|
||||
tool.show(parent=parent)
|
||||
|
||||
dialog = popup.Popup(parent=parent)
|
||||
dialog.setWindowTitle("Maya scene has outdated content")
|
||||
dialog.setMessage("There are outdated containers in "
|
||||
"your Maya scene.")
|
||||
dialog.on_show.connect(_on_show_inventory)
|
||||
dialog.show()
|
||||
|
||||
|
||||
def on_task_changed(*args):
|
||||
"""Wrapped function of app initialize and maya's on task changed"""
|
||||
pass
|
||||
108
colorbleed/houdini/lib.py
Normal file
108
colorbleed/houdini/lib.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import uuid
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
import hou
|
||||
|
||||
from avalon import api, io
|
||||
from avalon.houdini import lib
|
||||
|
||||
|
||||
def set_id(node, unique_id, overwrite=False):
|
||||
|
||||
exists = node.parm("id")
|
||||
if not exists:
|
||||
lib.imprint(node, {"id": unique_id})
|
||||
|
||||
if not exists and overwrite:
|
||||
node.setParm("id", unique_id)
|
||||
|
||||
|
||||
def get_id(node):
|
||||
"""
|
||||
Get the `cbId` attribute of the given node
|
||||
Args:
|
||||
node (hou.Node): the name of the node to retrieve the attribute from
|
||||
|
||||
Returns:
|
||||
str
|
||||
|
||||
"""
|
||||
|
||||
if node is None:
|
||||
return
|
||||
|
||||
id = node.parm("id")
|
||||
if node is None:
|
||||
return
|
||||
return id
|
||||
|
||||
|
||||
def generate_ids(nodes, asset_id=None):
|
||||
"""Returns new unique ids for the given nodes.
|
||||
|
||||
Note: This does not assign the new ids, it only generates the values.
|
||||
|
||||
To assign new ids using this method:
|
||||
>>> nodes = ["a", "b", "c"]
|
||||
>>> for node, id in generate_ids(nodes):
|
||||
>>> set_id(node, id)
|
||||
|
||||
To also override any existing values (and assign regenerated ids):
|
||||
>>> nodes = ["a", "b", "c"]
|
||||
>>> for node, id in generate_ids(nodes):
|
||||
>>> set_id(node, id, overwrite=True)
|
||||
|
||||
Args:
|
||||
nodes (list): List of nodes.
|
||||
asset_id (str or bson.ObjectId): The database id for the *asset* to
|
||||
generate for. When None provided the current asset in the
|
||||
active session is used.
|
||||
|
||||
Returns:
|
||||
list: A list of (node, id) tuples.
|
||||
|
||||
"""
|
||||
|
||||
if asset_id is None:
|
||||
# Get the asset ID from the database for the asset of current context
|
||||
asset_data = io.find_one({"type": "asset",
|
||||
"name": api.Session["AVALON_ASSET"]},
|
||||
projection={"_id": True})
|
||||
assert asset_data, "No current asset found in Session"
|
||||
asset_id = asset_data['_id']
|
||||
|
||||
node_ids = []
|
||||
for node in nodes:
|
||||
_, uid = str(uuid.uuid4()).rsplit("-", 1)
|
||||
unique_id = "{}:{}".format(asset_id, uid)
|
||||
node_ids.append((node, unique_id))
|
||||
|
||||
return node_ids
|
||||
|
||||
|
||||
def get_id_required_nodes():
|
||||
|
||||
valid_types = ["geometry"]
|
||||
nodes = {n for n in hou.node("/out").children() if
|
||||
n.type().name() in valid_types}
|
||||
|
||||
return list(nodes)
|
||||
|
||||
|
||||
def get_additional_data(container):
|
||||
"""Not implemented yet!"""
|
||||
return container
|
||||
|
||||
|
||||
@contextmanager
|
||||
def attribute_values(node, data):
|
||||
|
||||
previous_attrs = {key: node.parm(key).eval() for key in data.keys()}
|
||||
try:
|
||||
node.setParms(data)
|
||||
yield
|
||||
except Exception as exc:
|
||||
pass
|
||||
finally:
|
||||
node.setParms(previous_attrs)
|
||||
|
|
@ -257,16 +257,52 @@ def get_project_fps():
|
|||
|
||||
Returns:
|
||||
int, float
|
||||
|
||||
"""
|
||||
|
||||
data = get_project_data()
|
||||
fps = data.get("fps", 25.0)
|
||||
|
||||
return fps
|
||||
|
||||
|
||||
def get_project_data():
|
||||
"""Get the data of the current project
|
||||
|
||||
The data of the project can contain things like:
|
||||
resolution
|
||||
fps
|
||||
renderer
|
||||
|
||||
Returns:
|
||||
dict:
|
||||
|
||||
"""
|
||||
|
||||
project_name = io.active_project()
|
||||
project = io.find_one({"name": project_name,
|
||||
"type": "project"},
|
||||
projection={"config": True})
|
||||
projection={"data": True})
|
||||
|
||||
config = project.get("config", None)
|
||||
assert config, "This is a bug"
|
||||
data = project.get("data", {})
|
||||
|
||||
fps = config.get("fps", 25.0)
|
||||
return data
|
||||
|
||||
return fps
|
||||
|
||||
def get_asset_data(asset=None):
|
||||
"""Get the data from the current asset
|
||||
|
||||
Args:
|
||||
asset(str, Optional): name of the asset, eg:
|
||||
|
||||
Returns:
|
||||
dict
|
||||
"""
|
||||
|
||||
asset_name = asset or avalon.api.Session["AVALON_ASSET"]
|
||||
document = io.find_one({"name": asset_name,
|
||||
"type": "asset"})
|
||||
|
||||
data = document.get("data", {})
|
||||
|
||||
return data
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ def install():
|
|||
|
||||
avalon.before("save", on_before_save)
|
||||
|
||||
avalon.on("new", on_new)
|
||||
|
||||
log.info("Overriding existing event 'taskChanged'")
|
||||
override_event("taskChanged", on_task_changed)
|
||||
|
||||
|
|
@ -158,6 +160,13 @@ def on_open(_):
|
|||
dialog.show()
|
||||
|
||||
|
||||
def on_new(_):
|
||||
"""Set project resolution and fps when create a new file"""
|
||||
avalon.logger.info("Running callback on new..")
|
||||
with maya.suspended_refresh():
|
||||
lib.set_context_settings()
|
||||
|
||||
|
||||
def on_task_changed(*args):
|
||||
"""Wrapped function of app initialize and maya's on task changed"""
|
||||
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ _alembic_options = {
|
|||
}
|
||||
|
||||
INT_FPS = {15, 24, 25, 30, 48, 50, 60, 44100, 48000}
|
||||
FLOAT_FPS = {23.976, 29.97, 29.97, 47.952, 59.94}
|
||||
FLOAT_FPS = {23.976, 29.97, 47.952, 59.94}
|
||||
|
||||
|
||||
def matrix_equals(a, b, tolerance=1e-10):
|
||||
|
|
@ -1371,6 +1371,7 @@ def get_id_from_history(node):
|
|||
return _id
|
||||
|
||||
|
||||
# Project settings
|
||||
def set_scene_fps(fps, update=True):
|
||||
"""Set FPS from project configuration
|
||||
|
||||
|
|
@ -1384,10 +1385,10 @@ def set_scene_fps(fps, update=True):
|
|||
"""
|
||||
|
||||
if fps in FLOAT_FPS:
|
||||
unit = "{:f}fps".format(fps)
|
||||
unit = "{}fps".format(fps)
|
||||
|
||||
elif fps in INT_FPS:
|
||||
unit = "{:d}fps".format(int(fps))
|
||||
unit = "{}fps".format(int(fps))
|
||||
|
||||
else:
|
||||
raise ValueError("Unsupported FPS value: `%s`" % fps)
|
||||
|
|
@ -1399,6 +1400,69 @@ def set_scene_fps(fps, update=True):
|
|||
cmds.file(modified=True)
|
||||
|
||||
|
||||
def set_scene_resolution(width, height):
|
||||
"""Set the render resolution
|
||||
|
||||
Args:
|
||||
width(int): value of the width
|
||||
height(int): value of the height
|
||||
|
||||
Returns:
|
||||
None
|
||||
|
||||
"""
|
||||
|
||||
control_node = "defaultResolution"
|
||||
current_renderer = cmds.getAttr("defaultRenderGlobals.currentRenderer")
|
||||
|
||||
# Give VRay a helping hand as it is slightly different from the rest
|
||||
if current_renderer == "vray":
|
||||
vray_node = "vraySettings"
|
||||
if cmds.objExists(vray_node):
|
||||
control_node = vray_node
|
||||
else:
|
||||
log.error("Can't set VRay resolution because there is no node "
|
||||
"named: `%s`" % vray_node)
|
||||
|
||||
log.info("Setting project resolution to: %s x %s" % (width, height))
|
||||
cmds.setAttr("%s.width" % control_node, width)
|
||||
cmds.setAttr("%s.height" % control_node, height)
|
||||
|
||||
|
||||
def set_context_settings():
|
||||
"""Apply the project settings from the project definition
|
||||
|
||||
Settings can be overwritten by an asset if the asset.data contains
|
||||
any information regarding those settings.
|
||||
|
||||
Examples of settings:
|
||||
fps
|
||||
resolution
|
||||
renderer
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# Todo (Wijnand): apply renderer and resolution of project
|
||||
|
||||
project_data = lib.get_project_data()
|
||||
asset_data = lib.get_asset_data()
|
||||
|
||||
# Set project fps
|
||||
fps = asset_data.get("fps", project_data.get("fps", 25))
|
||||
set_scene_fps(fps)
|
||||
|
||||
# Set project resolution
|
||||
width_key = "resolution_width"
|
||||
height_key = "resolution_height"
|
||||
|
||||
width = asset_data.get(width_key, project_data.get(width_key, 1920))
|
||||
height = asset_data.get(height_key, project_data.get(height_key, 1080))
|
||||
|
||||
set_scene_resolution(width, height)
|
||||
|
||||
|
||||
# Valid FPS
|
||||
def validate_fps():
|
||||
"""Validate current scene FPS and show pop-up when it is incorrect
|
||||
|
|
@ -1408,7 +1472,8 @@ def validate_fps():
|
|||
|
||||
"""
|
||||
|
||||
fps = lib.get_project_fps() # can be int or float
|
||||
asset_data = lib.get_asset_data()
|
||||
fps = asset_data.get("fps", lib.get_project_fps()) # can be int or float
|
||||
current_fps = mel.eval('currentTimeUnitToFPS()') # returns float
|
||||
|
||||
if current_fps != fps:
|
||||
|
|
|
|||
30
colorbleed/plugins/houdini/create/create_pointcache.py
Normal file
30
colorbleed/plugins/houdini/create/create_pointcache.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
from collections import OrderedDict
|
||||
|
||||
import hou
|
||||
|
||||
from avalon import houdini
|
||||
|
||||
|
||||
class CreatePointCache(houdini.Creator):
|
||||
"""Alembic pointcache for animated data"""
|
||||
|
||||
name = "pointcache"
|
||||
label = "Point Cache"
|
||||
family = "colorbleed.pointcache"
|
||||
icon = "gears"
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CreatePointCache, self).__init__(*args, **kwargs)
|
||||
|
||||
# create an ordered dict with the existing data first
|
||||
data = OrderedDict(**self.data)
|
||||
|
||||
# Set node type to create for output
|
||||
data["node_type"] = "alembic"
|
||||
|
||||
# Collect animation data for point cache exporting
|
||||
start, end = hou.playbar.timelineRange()
|
||||
data["startFrame"] = start
|
||||
data["endFrame"] = end
|
||||
|
||||
self.data = data
|
||||
98
colorbleed/plugins/houdini/load/load_alembic.py
Normal file
98
colorbleed/plugins/houdini/load/load_alembic.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
from avalon import api
|
||||
|
||||
from avalon.houdini import pipeline, lib
|
||||
|
||||
|
||||
class AbcLoader(api.Loader):
|
||||
"""Specific loader of Alembic for the avalon.animation family"""
|
||||
|
||||
families = ["colorbleed.animation", "colorbleed.pointcache"]
|
||||
label = "Load Animation"
|
||||
representations = ["abc"]
|
||||
order = -10
|
||||
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 = os.path.normpath(self.fname)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
|
||||
# Get the root node
|
||||
obj = hou.node("/obj")
|
||||
|
||||
# Create a unique name
|
||||
counter = 1
|
||||
namespace = namespace if namespace else context["asset"]["name"]
|
||||
formatted = "{}_{}".format(namespace, name) if namespace else name
|
||||
node_name = "{0}_{1:03d}".format(formatted, counter)
|
||||
|
||||
children = lib.children_as_string(hou.node("/obj"))
|
||||
while node_name in children:
|
||||
counter += 1
|
||||
node_name = "{0}_{1:03d}".format(formatted, counter)
|
||||
|
||||
# Create a new geo node
|
||||
container = obj.createNode("geo", node_name=node_name)
|
||||
|
||||
# Remove the file node, it only loads static meshes
|
||||
node_path = "/obj/{}/file1".format(node_name)
|
||||
hou.node(node_path)
|
||||
|
||||
# Create an alembic node (supports animation)
|
||||
alembic = container.createNode("alembic", node_name=node_name)
|
||||
alembic.setParms({"fileName": file_path})
|
||||
|
||||
# Add unpack node
|
||||
unpack = container.createNode("unpack")
|
||||
unpack.setInput(0, alembic)
|
||||
unpack.setParms({"transfer_attributes": "path"})
|
||||
|
||||
# Set new position for unpack node else it gets cluttered
|
||||
unpack.setPosition([0, -1])
|
||||
|
||||
# set unpack as display node
|
||||
unpack.setDisplayFlag(True)
|
||||
|
||||
null_node = container.createNode("null",
|
||||
node_name="OUT_{}".format(name))
|
||||
null_node.setPosition([0, -2])
|
||||
null_node.setInput(0, unpack)
|
||||
|
||||
nodes = [container, alembic, unpack, null_node]
|
||||
|
||||
self[:] = nodes
|
||||
|
||||
return pipeline.containerise(node_name,
|
||||
namespace,
|
||||
nodes,
|
||||
context,
|
||||
self.__class__.__name__)
|
||||
|
||||
def update(self, container, 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 = api.get_representation_path(representation)
|
||||
file_path = file_path.replace("\\", "/")
|
||||
|
||||
alembic_node.setParms({"fileName": file_path})
|
||||
|
||||
# Update attribute
|
||||
node.setParms({"representation": str(representation["_id"])})
|
||||
|
||||
def remove(self, container):
|
||||
|
||||
node = container["node"]
|
||||
node.destroy()
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import pyblish.api
|
||||
|
||||
|
||||
class CollectAlembicNodes(pyblish.api.InstancePlugin):
|
||||
|
||||
label = "Collect Alembic Nodes"
|
||||
|
||||
def process(self, instance):
|
||||
pass
|
||||
15
colorbleed/plugins/houdini/publish/collect_current_file.py
Normal file
15
colorbleed/plugins/houdini/publish/collect_current_file.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import hou
|
||||
|
||||
import pyblish.api
|
||||
|
||||
|
||||
class CollectMayaCurrentFile(pyblish.api.ContextPlugin):
|
||||
"""Inject the current working file into context"""
|
||||
|
||||
order = pyblish.api.CollectorOrder - 0.5
|
||||
label = "Houdini Current File"
|
||||
hosts = ['houdini']
|
||||
|
||||
def process(self, context):
|
||||
"""Inject the current working file"""
|
||||
context.data['currentFile'] = hou.hipFile.path()
|
||||
68
colorbleed/plugins/houdini/publish/collect_instances.py
Normal file
68
colorbleed/plugins/houdini/publish/collect_instances.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import hou
|
||||
|
||||
import pyblish.api
|
||||
|
||||
from avalon.houdini import lib
|
||||
|
||||
|
||||
class CollectInstances(pyblish.api.ContextPlugin):
|
||||
"""Gather instances by all node in out graph and pre-defined attributes
|
||||
|
||||
This collector takes into account assets that are associated with
|
||||
an specific node and marked with a unique identifier;
|
||||
|
||||
Identifier:
|
||||
id (str): "pyblish.avalon.instance
|
||||
|
||||
Specific node:
|
||||
The specific node is important because it dictates in which way the subset
|
||||
is being exported.
|
||||
|
||||
alembic: will export Alembic file which supports cascading attributes
|
||||
like 'cbId' and 'path'
|
||||
geometry: Can export a wide range of file types, default out
|
||||
|
||||
"""
|
||||
|
||||
label = "Collect Instances"
|
||||
order = pyblish.api.CollectorOrder
|
||||
hosts = ["houdini"]
|
||||
|
||||
def process(self, context):
|
||||
|
||||
instances = []
|
||||
|
||||
nodes = hou.node("/out").children()
|
||||
for node in nodes:
|
||||
|
||||
if not node.parm("id"):
|
||||
continue
|
||||
|
||||
if node.parm("id").eval() != "pyblish.avalon.instance":
|
||||
continue
|
||||
|
||||
has_family = node.parm("family").eval()
|
||||
assert has_family, "'%s' is missing 'family'" % node.name()
|
||||
|
||||
data = lib.read(node)
|
||||
|
||||
# temporarily translation of `active` to `publish` till issue has
|
||||
# been resolved, https://github.com/pyblish/pyblish-base/issues/307
|
||||
if "active" in data:
|
||||
data["publish"] = data["active"]
|
||||
|
||||
instance = context.create_instance(data.get("name", node.name()))
|
||||
|
||||
instance[:] = [node]
|
||||
instance.data.update(data)
|
||||
|
||||
instances.append(instance)
|
||||
|
||||
def sort_by_family(instance):
|
||||
"""Sort by family"""
|
||||
return instance.data.get("families", instance.data.get("family"))
|
||||
|
||||
# Sort/grouped by family (preserving local index)
|
||||
context[:] = sorted(context, key=sort_by_family)
|
||||
|
||||
return context
|
||||
35
colorbleed/plugins/houdini/publish/extract_alembic.py
Normal file
35
colorbleed/plugins/houdini/publish/extract_alembic.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import os
|
||||
|
||||
import pyblish.api
|
||||
import colorbleed.api
|
||||
from colorbleed.houdini import lib
|
||||
|
||||
|
||||
class ExtractAlembic(colorbleed.api.Extractor):
|
||||
|
||||
order = pyblish.api.ExtractorOrder
|
||||
label = "Extract Pointcache (Alembic)"
|
||||
hosts = ["houdini"]
|
||||
families = ["colorbleed.pointcache"]
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
staging_dir = self.staging_dir(instance)
|
||||
|
||||
file_name = "{}.abc".format(instance.data["subset"])
|
||||
tmp_filepath = os.path.join(staging_dir, file_name)
|
||||
|
||||
start_frame = float(instance.data["startFrame"])
|
||||
end_frame = float(instance.data["endFrame"])
|
||||
|
||||
ropnode = instance[0]
|
||||
attributes = {"filename": tmp_filepath,
|
||||
"trange": 2}
|
||||
|
||||
with lib.attribute_values(ropnode, attributes):
|
||||
ropnode.render(frame_range=(start_frame, end_frame, 1))
|
||||
|
||||
if "files" not in instance.data:
|
||||
instance.data["files"] = []
|
||||
|
||||
instance.data["files"].append(file_name)
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import pyblish.api
|
||||
import colorbleed.api
|
||||
|
||||
|
||||
class ValidatIntermediateDirectoriesChecked(pyblish.api.InstancePlugin):
|
||||
"""Validate if node attribute Create intermediate Directories is turned on
|
||||
|
||||
Rules:
|
||||
* The node must have Create intermediate Directories turned on to
|
||||
ensure the output file will be created
|
||||
|
||||
"""
|
||||
|
||||
order = colorbleed.api.ValidateContentsOrder
|
||||
families = ['colorbleed.pointcache']
|
||||
hosts = ['houdini']
|
||||
label = 'Create Intermediate Directories Checked'
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError("Found ROP nodes with Create Intermediate "
|
||||
"Directories turned off")
|
||||
|
||||
@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
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
import pyblish.api
|
||||
import colorbleed.api
|
||||
|
||||
|
||||
class ValidatOutputNodeExists(pyblish.api.InstancePlugin):
|
||||
"""Validate if node attribute Create intermediate Directories is turned on
|
||||
|
||||
Rules:
|
||||
* The node must have Create intermediate Directories turned on to
|
||||
ensure the output file will be created
|
||||
|
||||
"""
|
||||
|
||||
order = colorbleed.api.ValidateContentsOrder
|
||||
families = ["*"]
|
||||
hosts = ['houdini']
|
||||
label = "Output Node Exists"
|
||||
|
||||
def process(self, instance):
|
||||
invalid = self.get_invalid(instance)
|
||||
if invalid:
|
||||
raise RuntimeError("Could not find output node(s)!")
|
||||
|
||||
@classmethod
|
||||
def get_invalid(cls, instance):
|
||||
|
||||
import hou
|
||||
|
||||
result = set()
|
||||
|
||||
node = instance[0]
|
||||
sop_path = node.parm("sop_path").eval()
|
||||
if not sop_path.endswith("OUT"):
|
||||
cls.log.error("SOP Path does not end path at output node")
|
||||
result.add(node.path())
|
||||
|
||||
if hou.node(sop_path) is None:
|
||||
cls.log.error("Node at '%s' does not exist" % sop_path)
|
||||
result.add(node.path())
|
||||
|
||||
return result
|
||||
Loading…
Add table
Add a link
Reference in a new issue