mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into enhancement/maya_validate_non_manifold_repair
This commit is contained in:
commit
954c129002
164 changed files with 1118 additions and 965 deletions
|
|
@ -14,3 +14,15 @@ AYON_SERVER_ENABLED = True
|
||||||
# Indicate if AYON entities should be used instead of OpenPype entities
|
# Indicate if AYON entities should be used instead of OpenPype entities
|
||||||
USE_AYON_ENTITIES = True
|
USE_AYON_ENTITIES = True
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
"__version__",
|
||||||
|
|
||||||
|
# Deprecated
|
||||||
|
"AYON_CORE_ROOT",
|
||||||
|
"PACKAGE_DIR",
|
||||||
|
"PLUGINS_DIR",
|
||||||
|
"AYON_SERVER_ENABLED",
|
||||||
|
"USE_AYON_ENTITIES",
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -1075,7 +1075,7 @@ class AddonsManager:
|
||||||
"""Print out report of time spent on addons initialization parts.
|
"""Print out report of time spent on addons initialization parts.
|
||||||
|
|
||||||
Reporting is not automated must be implemented for each initialization
|
Reporting is not automated must be implemented for each initialization
|
||||||
part separatelly. Reports must be stored to `_report` attribute.
|
part separately. Reports must be stored to `_report` attribute.
|
||||||
Print is skipped if `_report` is empty.
|
Print is skipped if `_report` is empty.
|
||||||
|
|
||||||
Attribute `_report` is dictionary where key is "label" describing
|
Attribute `_report` is dictionary where key is "label" describing
|
||||||
|
|
@ -1267,7 +1267,7 @@ class TrayAddonsManager(AddonsManager):
|
||||||
def add_doubleclick_callback(self, addon, callback):
|
def add_doubleclick_callback(self, addon, callback):
|
||||||
"""Register doubleclick callbacks on tray icon.
|
"""Register doubleclick callbacks on tray icon.
|
||||||
|
|
||||||
Currently there is no way how to determine which is launched. Name of
|
Currently, there is no way how to determine which is launched. Name of
|
||||||
callback can be defined with `doubleclick_callback` attribute.
|
callback can be defined with `doubleclick_callback` attribute.
|
||||||
|
|
||||||
Missing feature how to define default callback.
|
Missing feature how to define default callback.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import json
|
import json
|
||||||
import warnings
|
|
||||||
|
|
||||||
|
|
||||||
class Commands:
|
class Commands:
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ __all__ = [
|
||||||
"get_stub",
|
"get_stub",
|
||||||
|
|
||||||
# pipeline
|
# pipeline
|
||||||
|
"AfterEffectsHost",
|
||||||
"ls",
|
"ls",
|
||||||
"containerise",
|
"containerise",
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,11 @@
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import tempfile
|
import tempfile
|
||||||
import attr
|
|
||||||
|
|
||||||
|
import attr
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
from ayon_core.settings import get_project_settings
|
|
||||||
from ayon_core.pipeline import publish
|
from ayon_core.pipeline import publish
|
||||||
from ayon_core.pipeline.publish import RenderInstance
|
from ayon_core.pipeline.publish import RenderInstance
|
||||||
|
|
||||||
from ayon_core.hosts.aftereffects.api import get_stub
|
from ayon_core.hosts.aftereffects.api import get_stub
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,7 @@ def _process_app_events() -> Optional[float]:
|
||||||
|
|
||||||
|
|
||||||
class LaunchQtApp(bpy.types.Operator):
|
class LaunchQtApp(bpy.types.Operator):
|
||||||
"""A Base class for opertors to launch a Qt app."""
|
"""A Base class for operators to launch a Qt app."""
|
||||||
|
|
||||||
_app: QtWidgets.QApplication
|
_app: QtWidgets.QApplication
|
||||||
_window = Union[QtWidgets.QDialog, ModuleType]
|
_window = Union[QtWidgets.QDialog, ModuleType]
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ class BlendLoader(plugin.AssetLoader):
|
||||||
obj.animation_data_create()
|
obj.animation_data_create()
|
||||||
obj.animation_data.action = actions[obj.name]
|
obj.animation_data.action = actions[obj.name]
|
||||||
|
|
||||||
# Restore the old data, but reset memebers, as they don't exist anymore
|
# Restore the old data, but reset members, as they don't exist anymore
|
||||||
# This avoids a crash, because the memory addresses of those members
|
# This avoids a crash, because the memory addresses of those members
|
||||||
# are not valid anymore
|
# are not valid anymore
|
||||||
old_data["members"] = []
|
old_data["members"] = []
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import bpy
|
||||||
|
|
||||||
from ayon_core.pipeline import publish
|
from ayon_core.pipeline import publish
|
||||||
from ayon_core.hosts.blender.api import plugin
|
from ayon_core.hosts.blender.api import plugin
|
||||||
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import bpy
|
||||||
|
|
||||||
from ayon_core.pipeline import publish
|
from ayon_core.pipeline import publish
|
||||||
from ayon_core.hosts.blender.api import plugin
|
from ayon_core.hosts.blender.api import plugin
|
||||||
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ class ValidateDeadlinePublish(pyblish.api.InstancePlugin,
|
||||||
tree = bpy.context.scene.node_tree
|
tree = bpy.context.scene.node_tree
|
||||||
output_type = "CompositorNodeOutputFile"
|
output_type = "CompositorNodeOutputFile"
|
||||||
output_node = None
|
output_node = None
|
||||||
# Remove all output nodes that inlcude "AYON" in the name.
|
# Remove all output nodes that include "AYON" in the name.
|
||||||
# There should be only one.
|
# There should be only one.
|
||||||
for node in tree.nodes:
|
for node in tree.nodes:
|
||||||
if node.bl_idname == output_type and "AYON" in node.name:
|
if node.bl_idname == output_type and "AYON" in node.name:
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ class CelactionPrelaunchHook(PreLaunchHook):
|
||||||
def workfile_path(self):
|
def workfile_path(self):
|
||||||
workfile_path = self.data["last_workfile_path"]
|
workfile_path = self.data["last_workfile_path"]
|
||||||
|
|
||||||
# copy workfile from template if doesnt exist any on path
|
# copy workfile from template if doesn't exist any on path
|
||||||
if not os.path.exists(workfile_path):
|
if not os.path.exists(workfile_path):
|
||||||
# TODO add ability to set different template workfile path via
|
# TODO add ability to set different template workfile path via
|
||||||
# settings
|
# settings
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ class CollectRenderPath(pyblish.api.InstancePlugin):
|
||||||
render_path = r_template_item["path"].format_strict(anatomy_data)
|
render_path = r_template_item["path"].format_strict(anatomy_data)
|
||||||
self.log.debug("__ render_path: `{}`".format(render_path))
|
self.log.debug("__ render_path: `{}`".format(render_path))
|
||||||
|
|
||||||
# create dir if it doesnt exists
|
# create dir if it doesn't exists
|
||||||
try:
|
try:
|
||||||
if not os.path.isdir(render_dir):
|
if not os.path.isdir(render_dir):
|
||||||
os.makedirs(render_dir, exist_ok=True)
|
os.makedirs(render_dir, exist_ok=True)
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ from .lib import (
|
||||||
reset_segment_selection,
|
reset_segment_selection,
|
||||||
get_segment_attributes,
|
get_segment_attributes,
|
||||||
get_clips_in_reels,
|
get_clips_in_reels,
|
||||||
get_reformated_filename,
|
get_reformatted_filename,
|
||||||
get_frame_from_filename,
|
get_frame_from_filename,
|
||||||
get_padding_from_filename,
|
get_padding_from_filename,
|
||||||
maintained_object_duplication,
|
maintained_object_duplication,
|
||||||
|
|
@ -101,7 +101,7 @@ __all__ = [
|
||||||
"reset_segment_selection",
|
"reset_segment_selection",
|
||||||
"get_segment_attributes",
|
"get_segment_attributes",
|
||||||
"get_clips_in_reels",
|
"get_clips_in_reels",
|
||||||
"get_reformated_filename",
|
"get_reformatted_filename",
|
||||||
"get_frame_from_filename",
|
"get_frame_from_filename",
|
||||||
"get_padding_from_filename",
|
"get_padding_from_filename",
|
||||||
"maintained_object_duplication",
|
"maintained_object_duplication",
|
||||||
|
|
|
||||||
|
|
@ -607,7 +607,7 @@ def get_clips_in_reels(project):
|
||||||
return output_clips
|
return output_clips
|
||||||
|
|
||||||
|
|
||||||
def get_reformated_filename(filename, padded=True):
|
def get_reformatted_filename(filename, padded=True):
|
||||||
"""
|
"""
|
||||||
Return fixed python expression path
|
Return fixed python expression path
|
||||||
|
|
||||||
|
|
@ -615,10 +615,10 @@ def get_reformated_filename(filename, padded=True):
|
||||||
filename (str): file name
|
filename (str): file name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
type: string with reformated path
|
type: string with reformatted path
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
get_reformated_filename("plate.1001.exr") > plate.%04d.exr
|
get_reformatted_filename("plate.1001.exr") > plate.%04d.exr
|
||||||
|
|
||||||
"""
|
"""
|
||||||
found = FRAME_PATTERN.search(filename)
|
found = FRAME_PATTERN.search(filename)
|
||||||
|
|
@ -980,7 +980,7 @@ class MediaInfoFile(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def file_pattern(self):
|
def file_pattern(self):
|
||||||
"""Clips file patter
|
"""Clips file pattern.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: file pattern. ex. file.[1-2].exr
|
str: file pattern. ex. file.[1-2].exr
|
||||||
|
|
|
||||||
|
|
@ -644,13 +644,13 @@ class PublishableClip:
|
||||||
"families": [self.base_product_type, self.product_type]
|
"families": [self.base_product_type, self.product_type]
|
||||||
}
|
}
|
||||||
|
|
||||||
def _convert_to_entity(self, type, template):
|
def _convert_to_entity(self, src_type, template):
|
||||||
""" Converting input key to key with type. """
|
""" Converting input key to key with type. """
|
||||||
# convert to entity type
|
# convert to entity type
|
||||||
entity_type = self.types.get(type, None)
|
folder_type = self.types.get(src_type, None)
|
||||||
|
|
||||||
assert entity_type, "Missing entity type for `{}`".format(
|
assert folder_type, "Missing folder type for `{}`".format(
|
||||||
type
|
src_type
|
||||||
)
|
)
|
||||||
|
|
||||||
# first collect formatting data to use for formatting template
|
# first collect formatting data to use for formatting template
|
||||||
|
|
@ -661,7 +661,7 @@ class PublishableClip:
|
||||||
formatting_data[_k] = value
|
formatting_data[_k] = value
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"entity_type": entity_type,
|
"folder_type": folder_type,
|
||||||
"entity_name": template.format(
|
"entity_name": template.format(
|
||||||
**formatting_data
|
**formatting_data
|
||||||
)
|
)
|
||||||
|
|
@ -1018,7 +1018,7 @@ class OpenClipSolver(flib.MediaInfoFile):
|
||||||
self.feed_version_name))
|
self.feed_version_name))
|
||||||
else:
|
else:
|
||||||
self.log.debug("adding new track element ..")
|
self.log.debug("adding new track element ..")
|
||||||
# create new track as it doesnt exists yet
|
# create new track as it doesn't exist yet
|
||||||
# set current version to feeds on tmp
|
# set current version to feeds on tmp
|
||||||
tmp_xml_feeds = tmp_xml_track.find('feeds')
|
tmp_xml_feeds = tmp_xml_track.find('feeds')
|
||||||
tmp_xml_feeds.set('currentVersion', self.feed_version_name)
|
tmp_xml_feeds.set('currentVersion', self.feed_version_name)
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ def create_otio_reference(clip_data, fps=None):
|
||||||
|
|
||||||
if not otio_ex_ref_item:
|
if not otio_ex_ref_item:
|
||||||
dirname, file_name = os.path.split(path)
|
dirname, file_name = os.path.split(path)
|
||||||
file_name = utils.get_reformated_filename(file_name, padded=False)
|
file_name = utils.get_reformatted_filename(file_name, padded=False)
|
||||||
reformated_path = os.path.join(dirname, file_name)
|
reformated_path = os.path.join(dirname, file_name)
|
||||||
# in case old OTIO or video file create `ExternalReference`
|
# in case old OTIO or video file create `ExternalReference`
|
||||||
otio_ex_ref_item = otio.schema.ExternalReference(
|
otio_ex_ref_item = otio.schema.ExternalReference(
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ def frames_to_seconds(frames, framerate):
|
||||||
return otio.opentime.to_seconds(rt)
|
return otio.opentime.to_seconds(rt)
|
||||||
|
|
||||||
|
|
||||||
def get_reformated_filename(filename, padded=True):
|
def get_reformatted_filename(filename, padded=True):
|
||||||
"""
|
"""
|
||||||
Return fixed python expression path
|
Return fixed python expression path
|
||||||
|
|
||||||
|
|
@ -29,10 +29,10 @@ def get_reformated_filename(filename, padded=True):
|
||||||
filename (str): file name
|
filename (str): file name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
type: string with reformated path
|
type: string with reformatted path
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
get_reformated_filename("plate.1001.exr") > plate.%04d.exr
|
get_reformatted_filename("plate.1001.exr") > plate.%04d.exr
|
||||||
|
|
||||||
"""
|
"""
|
||||||
found = FRAME_PATTERN.search(filename)
|
found = FRAME_PATTERN.search(filename)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class CreateShotClip(opfapi.Creator):
|
||||||
presets = deepcopy(self.presets)
|
presets = deepcopy(self.presets)
|
||||||
gui_inputs = self.get_gui_inputs()
|
gui_inputs = self.get_gui_inputs()
|
||||||
|
|
||||||
# get key pares from presets and match it on ui inputs
|
# get key pairs from presets and match it on ui inputs
|
||||||
for k, v in gui_inputs.items():
|
for k, v in gui_inputs.items():
|
||||||
if v["type"] in ("dict", "section"):
|
if v["type"] in ("dict", "section"):
|
||||||
# nested dictionary (only one level allowed
|
# nested dictionary (only one level allowed
|
||||||
|
|
@ -236,7 +236,7 @@ class CreateShotClip(opfapi.Creator):
|
||||||
"type": "QCheckBox",
|
"type": "QCheckBox",
|
||||||
"label": "Source resolution",
|
"label": "Source resolution",
|
||||||
"target": "tag",
|
"target": "tag",
|
||||||
"toolTip": "Is resloution taken from timeline or source?", # noqa
|
"toolTip": "Is resolution taken from timeline or source?", # noqa
|
||||||
"order": 4},
|
"order": 4},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
||||||
self.otio_timeline = context.data["otioTimeline"]
|
self.otio_timeline = context.data["otioTimeline"]
|
||||||
self.fps = context.data["fps"]
|
self.fps = context.data["fps"]
|
||||||
|
|
||||||
# process all sellected
|
# process all selected
|
||||||
for segment in selected_segments:
|
for segment in selected_segments:
|
||||||
# get openpype tag data
|
# get openpype tag data
|
||||||
marker_data = opfapi.get_segment_data_marker(segment)
|
marker_data = opfapi.get_segment_data_marker(segment)
|
||||||
|
|
@ -100,6 +100,12 @@ class CollectTimelineInstances(pyblish.api.ContextPlugin):
|
||||||
marker_data["handleEnd"] = min(
|
marker_data["handleEnd"] = min(
|
||||||
marker_data["handleEnd"], tail)
|
marker_data["handleEnd"], tail)
|
||||||
|
|
||||||
|
# Backward compatibility fix of 'entity_type' > 'folder_type'
|
||||||
|
if "parents" in marker_data:
|
||||||
|
for parent in marker_data["parents"]:
|
||||||
|
if "entity_type" in parent:
|
||||||
|
parent["folder_type"] = parent.pop("entity_type")
|
||||||
|
|
||||||
workfile_start = self._set_workfile_start(marker_data)
|
workfile_start = self._set_workfile_start(marker_data)
|
||||||
|
|
||||||
with_audio = bool(marker_data.pop("audio"))
|
with_audio = bool(marker_data.pop("audio"))
|
||||||
|
|
|
||||||
|
|
@ -396,7 +396,7 @@ class FtrackEntityOperator:
|
||||||
|
|
||||||
entity = session.query(query).first()
|
entity = session.query(query).first()
|
||||||
|
|
||||||
# if entity doesnt exist then create one
|
# if entity doesn't exist then create one
|
||||||
if not entity:
|
if not entity:
|
||||||
entity = self.create_ftrack_entity(
|
entity = self.create_ftrack_entity(
|
||||||
session,
|
session,
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ from ayon_core.tools.utils import host_tools
|
||||||
|
|
||||||
from .lib import (
|
from .lib import (
|
||||||
get_current_comp,
|
get_current_comp,
|
||||||
comp_lock_and_undo_chunk,
|
|
||||||
validate_comp_prefs
|
validate_comp_prefs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
from ayon_core.lib import EnumDef
|
from ayon_core.lib import (
|
||||||
|
UILabelDef,
|
||||||
|
NumberDef,
|
||||||
|
EnumDef
|
||||||
|
)
|
||||||
|
|
||||||
from ayon_core.hosts.fusion.api.plugin import GenericCreateSaver
|
from ayon_core.hosts.fusion.api.plugin import GenericCreateSaver
|
||||||
|
from ayon_core.hosts.fusion.api.lib import get_current_comp
|
||||||
|
|
||||||
|
|
||||||
class CreateSaver(GenericCreateSaver):
|
class CreateSaver(GenericCreateSaver):
|
||||||
|
|
@ -45,6 +50,7 @@ class CreateSaver(GenericCreateSaver):
|
||||||
self._get_reviewable_bool(),
|
self._get_reviewable_bool(),
|
||||||
self._get_frame_range_enum(),
|
self._get_frame_range_enum(),
|
||||||
self._get_image_format_enum(),
|
self._get_image_format_enum(),
|
||||||
|
*self._get_custom_frame_range_attribute_defs()
|
||||||
]
|
]
|
||||||
return attr_defs
|
return attr_defs
|
||||||
|
|
||||||
|
|
@ -53,6 +59,7 @@ class CreateSaver(GenericCreateSaver):
|
||||||
"current_folder": "Current Folder context",
|
"current_folder": "Current Folder context",
|
||||||
"render_range": "From render in/out",
|
"render_range": "From render in/out",
|
||||||
"comp_range": "From composition timeline",
|
"comp_range": "From composition timeline",
|
||||||
|
"custom_range": "Custom frame range",
|
||||||
}
|
}
|
||||||
|
|
||||||
return EnumDef(
|
return EnumDef(
|
||||||
|
|
@ -61,3 +68,82 @@ class CreateSaver(GenericCreateSaver):
|
||||||
label="Frame range source",
|
label="Frame range source",
|
||||||
default=self.default_frame_range_option
|
default=self.default_frame_range_option
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _get_custom_frame_range_attribute_defs() -> list:
|
||||||
|
|
||||||
|
# Define custom frame range defaults based on current comp
|
||||||
|
# timeline settings (if a comp is currently open)
|
||||||
|
comp = get_current_comp()
|
||||||
|
if comp is not None:
|
||||||
|
attrs = comp.GetAttrs()
|
||||||
|
frame_defaults = {
|
||||||
|
"frameStart": int(attrs["COMPN_GlobalStart"]),
|
||||||
|
"frameEnd": int(attrs["COMPN_GlobalEnd"]),
|
||||||
|
"handleStart": int(
|
||||||
|
attrs["COMPN_RenderStart"] - attrs["COMPN_GlobalStart"]
|
||||||
|
),
|
||||||
|
"handleEnd": int(
|
||||||
|
attrs["COMPN_GlobalEnd"] - attrs["COMPN_RenderEnd"]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
frame_defaults = {
|
||||||
|
"frameStart": 1001,
|
||||||
|
"frameEnd": 1100,
|
||||||
|
"handleStart": 0,
|
||||||
|
"handleEnd": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
UILabelDef(
|
||||||
|
label="<br><b>Custom Frame Range</b><br>"
|
||||||
|
"<i>only used with 'Custom frame range' source</i>"
|
||||||
|
),
|
||||||
|
NumberDef(
|
||||||
|
"custom_frameStart",
|
||||||
|
label="Frame Start",
|
||||||
|
default=frame_defaults["frameStart"],
|
||||||
|
minimum=0,
|
||||||
|
decimals=0,
|
||||||
|
tooltip=(
|
||||||
|
"Set the start frame for the export.\n"
|
||||||
|
"Only used if frame range source is 'Custom frame range'."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
NumberDef(
|
||||||
|
"custom_frameEnd",
|
||||||
|
label="Frame End",
|
||||||
|
default=frame_defaults["frameEnd"],
|
||||||
|
minimum=0,
|
||||||
|
decimals=0,
|
||||||
|
tooltip=(
|
||||||
|
"Set the end frame for the export.\n"
|
||||||
|
"Only used if frame range source is 'Custom frame range'."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
NumberDef(
|
||||||
|
"custom_handleStart",
|
||||||
|
label="Handle Start",
|
||||||
|
default=frame_defaults["handleStart"],
|
||||||
|
minimum=0,
|
||||||
|
decimals=0,
|
||||||
|
tooltip=(
|
||||||
|
"Set the start handles for the export, this will be "
|
||||||
|
"added before the start frame.\n"
|
||||||
|
"Only used if frame range source is 'Custom frame range'."
|
||||||
|
)
|
||||||
|
),
|
||||||
|
NumberDef(
|
||||||
|
"custom_handleEnd",
|
||||||
|
label="Handle End",
|
||||||
|
default=frame_defaults["handleEnd"],
|
||||||
|
minimum=0,
|
||||||
|
decimals=0,
|
||||||
|
tooltip=(
|
||||||
|
"Set the end handles for the export, this will be added "
|
||||||
|
"after the end frame.\n"
|
||||||
|
"Only used if frame range source is 'Custom frame range'."
|
||||||
|
)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,14 @@ class CollectInstanceData(pyblish.api.InstancePlugin):
|
||||||
start_with_handle = comp_start
|
start_with_handle = comp_start
|
||||||
end_with_handle = comp_end
|
end_with_handle = comp_end
|
||||||
|
|
||||||
|
if frame_range_source == "custom_range":
|
||||||
|
start = int(instance.data["custom_frameStart"])
|
||||||
|
end = int(instance.data["custom_frameEnd"])
|
||||||
|
handle_start = int(instance.data["custom_handleStart"])
|
||||||
|
handle_end = int(instance.data["custom_handleEnd"])
|
||||||
|
start_with_handle = start - handle_start
|
||||||
|
end_with_handle = end + handle_end
|
||||||
|
|
||||||
frame = instance.data["creator_attributes"].get("frame")
|
frame = instance.data["creator_attributes"].get("frame")
|
||||||
# explicitly publishing only single frame
|
# explicitly publishing only single frame
|
||||||
if frame is not None:
|
if frame is not None:
|
||||||
|
|
|
||||||
|
|
@ -568,7 +568,7 @@ def save_scene():
|
||||||
"""Save the Harmony scene safely.
|
"""Save the Harmony scene safely.
|
||||||
|
|
||||||
The built-in (to Avalon) background zip and moving of the Harmony scene
|
The built-in (to Avalon) background zip and moving of the Harmony scene
|
||||||
folder, interfers with server/client communication by sending two requests
|
folder, interferes with server/client communication by sending two requests
|
||||||
at the same time. This only happens when sending "scene.saveAll()". This
|
at the same time. This only happens when sending "scene.saveAll()". This
|
||||||
method prevents this double request and safely saves the scene.
|
method prevents this double request and safely saves the scene.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,12 @@ class CreateFarmRender(plugin.Creator):
|
||||||
path = "render/{0}/{0}.".format(node.split("/")[-1])
|
path = "render/{0}/{0}.".format(node.split("/")[-1])
|
||||||
harmony.send(
|
harmony.send(
|
||||||
{
|
{
|
||||||
"function": f"PypeHarmony.Creators.CreateRender.create",
|
"function": "PypeHarmony.Creators.CreateRender.create",
|
||||||
"args": [node, path]
|
"args": [node, path]
|
||||||
})
|
})
|
||||||
harmony.send(
|
harmony.send(
|
||||||
{
|
{
|
||||||
"function": f"PypeHarmony.color",
|
"function": "PypeHarmony.color",
|
||||||
"args": [[0.9, 0.75, 0.3, 1.0]]
|
"args": [[0.9, 0.75, 0.3, 1.0]]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import os
|
import os
|
||||||
import pyblish.api
|
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
|
|
||||||
class CollectAudio(pyblish.api.InstancePlugin):
|
class CollectAudio(pyblish.api.InstancePlugin):
|
||||||
"""
|
"""
|
||||||
Collect relative path for audio file to instance.
|
Collect relative path for audio file to instance.
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ class CollectScene(pyblish.api.ContextPlugin):
|
||||||
"""Plugin entry point."""
|
"""Plugin entry point."""
|
||||||
result = harmony.send(
|
result = harmony.send(
|
||||||
{
|
{
|
||||||
f"function": "PypeHarmony.getSceneSettings",
|
"function": "PypeHarmony.getSceneSettings",
|
||||||
"args": []}
|
"args": []}
|
||||||
)["result"]
|
)["result"]
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ class CollectScene(pyblish.api.ContextPlugin):
|
||||||
|
|
||||||
result = harmony.send(
|
result = harmony.send(
|
||||||
{
|
{
|
||||||
f"function": "PypeHarmony.getVersion",
|
"function": "PypeHarmony.getVersion",
|
||||||
"args": []}
|
"args": []}
|
||||||
)["result"]
|
)["result"]
|
||||||
context.data["harmonyVersion"] = "{}.{}".format(result[0], result[1])
|
context.data["harmonyVersion"] = "{}.{}".format(result[0], result[1])
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import hiero.core.events
|
import hiero.core.events
|
||||||
|
|
||||||
from ayon_core.lib import Logger, register_event_callback
|
from ayon_core.lib import Logger, register_event_callback
|
||||||
|
|
||||||
from .lib import (
|
from .lib import (
|
||||||
sync_avalon_data_to_workfile,
|
sync_avalon_data_to_workfile,
|
||||||
launch_workfiles_app,
|
launch_workfiles_app,
|
||||||
selection_changed_timeline,
|
|
||||||
before_project_save,
|
before_project_save,
|
||||||
)
|
)
|
||||||
from .tags import add_tags_to_workfile
|
from .tags import add_tags_to_workfile
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ def get_current_track(sequence, name, audio=False):
|
||||||
Creates new if none is found.
|
Creates new if none is found.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
sequence (hiero.core.Sequence): hiero sequene object
|
sequence (hiero.core.Sequence): hiero sequence object
|
||||||
name (str): name of track we want to return
|
name (str): name of track we want to return
|
||||||
audio (bool)[optional]: switch to AudioTrack
|
audio (bool)[optional]: switch to AudioTrack
|
||||||
|
|
||||||
|
|
@ -846,8 +846,8 @@ def create_nuke_workfile_clips(nuke_workfiles, seq=None):
|
||||||
[{
|
[{
|
||||||
'path': 'P:/Jakub_testy_pipeline/test_v01.nk',
|
'path': 'P:/Jakub_testy_pipeline/test_v01.nk',
|
||||||
'name': 'test',
|
'name': 'test',
|
||||||
'handleStart': 15, # added asymetrically to handles
|
'handleStart': 15, # added asymmetrically to handles
|
||||||
'handleEnd': 10, # added asymetrically to handles
|
'handleEnd': 10, # added asymmetrically to handles
|
||||||
"clipIn": 16,
|
"clipIn": 16,
|
||||||
"frameStart": 991,
|
"frameStart": 991,
|
||||||
"frameEnd": 1023,
|
"frameEnd": 1023,
|
||||||
|
|
@ -1192,7 +1192,7 @@ def get_sequence_pattern_and_padding(file):
|
||||||
|
|
||||||
Return:
|
Return:
|
||||||
string: any matching sequence pattern
|
string: any matching sequence pattern
|
||||||
int: padding of sequnce numbering
|
int: padding of sequence numbering
|
||||||
"""
|
"""
|
||||||
foundall = re.findall(
|
foundall = re.findall(
|
||||||
r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file)
|
r"(#+)|(%\d+d)|(?<=[^a-zA-Z0-9])(\d+)(?=\.\w+$)", file)
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
if isinstance(track, hiero.core.AudioTrack):
|
if isinstance(track, hiero.core.AudioTrack):
|
||||||
kind = 'Audio'
|
kind = 'Audio'
|
||||||
|
|
||||||
# Gather TrackItems involved in trasition
|
# Gather TrackItems involved in transition
|
||||||
item_in, item_out = get_neighboring_trackitems(
|
item_in, item_out = get_neighboring_trackitems(
|
||||||
otio_item,
|
otio_item,
|
||||||
otio_track,
|
otio_track,
|
||||||
|
|
@ -101,7 +101,7 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
if transition_type == 'dissolve':
|
if transition_type == 'dissolve':
|
||||||
transition_func = getattr(
|
transition_func = getattr(
|
||||||
hiero.core.Transition,
|
hiero.core.Transition,
|
||||||
'create{kind}DissolveTransition'.format(kind=kind)
|
"create{kind}DissolveTransition".format(kind=kind)
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -109,7 +109,7 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
item_in,
|
item_in,
|
||||||
item_out,
|
item_out,
|
||||||
otio_item.in_offset.value,
|
otio_item.in_offset.value,
|
||||||
otio_item.out_offset.value
|
otio_item.out_offset.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Catch error raised if transition is bigger than TrackItem source
|
# Catch error raised if transition is bigger than TrackItem source
|
||||||
|
|
@ -134,7 +134,7 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
|
|
||||||
transition = transition_func(
|
transition = transition_func(
|
||||||
item_out,
|
item_out,
|
||||||
otio_item.out_offset.value
|
otio_item.out_offset.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif transition_type == 'fade_out':
|
elif transition_type == 'fade_out':
|
||||||
|
|
@ -183,9 +183,7 @@ def prep_url(url_in):
|
||||||
def create_offline_mediasource(otio_clip, path=None):
|
def create_offline_mediasource(otio_clip, path=None):
|
||||||
global _otio_old
|
global _otio_old
|
||||||
|
|
||||||
hiero_rate = hiero.core.TimeBase(
|
hiero_rate = hiero.core.TimeBase(otio_clip.source_range.start_time.rate)
|
||||||
otio_clip.source_range.start_time.rate
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
legal_media_refs = (
|
legal_media_refs = (
|
||||||
|
|
@ -212,7 +210,7 @@ def create_offline_mediasource(otio_clip, path=None):
|
||||||
source_range.start_time.value,
|
source_range.start_time.value,
|
||||||
source_range.duration.value,
|
source_range.duration.value,
|
||||||
hiero_rate,
|
hiero_rate,
|
||||||
source_range.start_time.value
|
source_range.start_time.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
@ -385,7 +383,8 @@ def create_trackitem(playhead, track, otio_clip, clip):
|
||||||
# Only reverse effect can be applied here
|
# Only reverse effect can be applied here
|
||||||
if abs(time_scalar) == 1.:
|
if abs(time_scalar) == 1.:
|
||||||
trackitem.setPlaybackSpeed(
|
trackitem.setPlaybackSpeed(
|
||||||
trackitem.playbackSpeed() * time_scalar)
|
trackitem.playbackSpeed() * time_scalar
|
||||||
|
)
|
||||||
|
|
||||||
elif isinstance(effect, otio.schema.FreezeFrame):
|
elif isinstance(effect, otio.schema.FreezeFrame):
|
||||||
# For freeze frame, playback speed must be set after range
|
# For freeze frame, playback speed must be set after range
|
||||||
|
|
@ -397,28 +396,21 @@ def create_trackitem(playhead, track, otio_clip, clip):
|
||||||
source_in = source_range.end_time_inclusive().value
|
source_in = source_range.end_time_inclusive().value
|
||||||
|
|
||||||
timeline_in = playhead + source_out
|
timeline_in = playhead + source_out
|
||||||
timeline_out = (
|
timeline_out = (timeline_in + source_range.duration.value) - 1
|
||||||
timeline_in +
|
|
||||||
source_range.duration.value
|
|
||||||
) - 1
|
|
||||||
else:
|
else:
|
||||||
# Normal playback speed
|
# Normal playback speed
|
||||||
source_in = source_range.start_time.value
|
source_in = source_range.start_time.value
|
||||||
source_out = source_range.end_time_inclusive().value
|
source_out = source_range.end_time_inclusive().value
|
||||||
|
|
||||||
timeline_in = playhead
|
timeline_in = playhead
|
||||||
timeline_out = (
|
timeline_out = (timeline_in + source_range.duration.value) - 1
|
||||||
timeline_in +
|
|
||||||
source_range.duration.value
|
|
||||||
) - 1
|
|
||||||
|
|
||||||
# Set source and timeline in/out points
|
# Set source and timeline in/out points
|
||||||
trackitem.setTimes(
|
trackitem.setTimes(
|
||||||
timeline_in,
|
timeline_in,
|
||||||
timeline_out,
|
timeline_out,
|
||||||
source_in,
|
source_in,
|
||||||
source_out
|
source_out,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Apply playback speed for freeze frames
|
# Apply playback speed for freeze frames
|
||||||
|
|
@ -435,7 +427,8 @@ def create_trackitem(playhead, track, otio_clip, clip):
|
||||||
|
|
||||||
|
|
||||||
def build_sequence(
|
def build_sequence(
|
||||||
otio_timeline, project=None, sequence=None, track_kind=None):
|
otio_timeline, project=None, sequence=None, track_kind=None
|
||||||
|
):
|
||||||
if project is None:
|
if project is None:
|
||||||
if sequence:
|
if sequence:
|
||||||
project = sequence.project()
|
project = sequence.project()
|
||||||
|
|
@ -509,10 +502,7 @@ def build_sequence(
|
||||||
|
|
||||||
# Create TrackItem
|
# Create TrackItem
|
||||||
trackitem = create_trackitem(
|
trackitem = create_trackitem(
|
||||||
playhead,
|
playhead, track, otio_clip, clip
|
||||||
track,
|
|
||||||
otio_clip,
|
|
||||||
clip
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add markers
|
# Add markers
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ def get_reformated_path(path, padded=True):
|
||||||
path (str): path url or simple file name
|
path (str): path url or simple file name
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
type: string with reformated path
|
type: string with reformatted path
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr
|
get_reformated_path("plate.[0001-1008].exr") > plate.%04d.exr
|
||||||
|
|
|
||||||
|
|
@ -449,7 +449,6 @@ class ClipLoader:
|
||||||
repr = self.context["representation"]
|
repr = self.context["representation"]
|
||||||
repr_cntx = repr["context"]
|
repr_cntx = repr["context"]
|
||||||
folder_path = self.context["folder"]["path"]
|
folder_path = self.context["folder"]["path"]
|
||||||
folder_name = self.context["folder"]["name"]
|
|
||||||
product_name = self.context["product"]["name"]
|
product_name = self.context["product"]["name"]
|
||||||
representation = repr["name"]
|
representation = repr["name"]
|
||||||
self.data["clip_name"] = self.clip_name_template.format(**repr_cntx)
|
self.data["clip_name"] = self.clip_name_template.format(**repr_cntx)
|
||||||
|
|
@ -906,16 +905,16 @@ class PublishClip:
|
||||||
"hierarchyData": hierarchy_formatting_data,
|
"hierarchyData": hierarchy_formatting_data,
|
||||||
"productName": self.product_name,
|
"productName": self.product_name,
|
||||||
"productType": self.product_type,
|
"productType": self.product_type,
|
||||||
"families": [self.product_type, self.data["family"]]
|
"families": [self.product_type, self.data["productType"]]
|
||||||
}
|
}
|
||||||
|
|
||||||
def _convert_to_entity(self, type, template):
|
def _convert_to_entity(self, src_type, template):
|
||||||
""" Converting input key to key with type. """
|
""" Converting input key to key with type. """
|
||||||
# convert to entity type
|
# convert to entity type
|
||||||
entity_type = self.types.get(type, None)
|
folder_type = self.types.get(src_type, None)
|
||||||
|
|
||||||
assert entity_type, "Missing entity type for `{}`".format(
|
assert folder_type, "Missing folder type for `{}`".format(
|
||||||
type
|
src_type
|
||||||
)
|
)
|
||||||
|
|
||||||
# first collect formatting data to use for formatting template
|
# first collect formatting data to use for formatting template
|
||||||
|
|
@ -926,7 +925,7 @@ class PublishClip:
|
||||||
formatting_data[_k] = value
|
formatting_data[_k] = value
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"entity_type": entity_type,
|
"folder_type": folder_type,
|
||||||
"entity_name": template.format(
|
"entity_name": template.format(
|
||||||
**formatting_data
|
**formatting_data
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@
|
||||||
# Note: This only prints the text data that is visible in the active Spreadsheet View.
|
# Note: This only prints the text data that is visible in the active Spreadsheet View.
|
||||||
# If you've filtered text, only the visible text will be printed to the CSV file
|
# If you've filtered text, only the visible text will be printed to the CSV file
|
||||||
# Usage: Copy to ~/.hiero/Python/StartupUI
|
# Usage: Copy to ~/.hiero/Python/StartupUI
|
||||||
|
import os
|
||||||
|
import csv
|
||||||
|
|
||||||
import hiero.core.events
|
import hiero.core.events
|
||||||
import hiero.ui
|
import hiero.ui
|
||||||
import os, csv
|
|
||||||
try:
|
try:
|
||||||
from PySide.QtGui import *
|
from PySide.QtGui import *
|
||||||
from PySide.QtCore import *
|
from PySide.QtCore import *
|
||||||
|
|
|
||||||
|
|
@ -641,7 +641,7 @@ def _setStatus(self, status):
|
||||||
global gStatusTags
|
global gStatusTags
|
||||||
|
|
||||||
# Get a valid Tag object from the Global list of statuses
|
# Get a valid Tag object from the Global list of statuses
|
||||||
if not status in gStatusTags.keys():
|
if status not in gStatusTags.keys():
|
||||||
print("Status requested was not a valid Status string.")
|
print("Status requested was not a valid Status string.")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
kind = "Audio"
|
kind = "Audio"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Gather TrackItems involved in trasition
|
# Gather TrackItems involved in transition
|
||||||
item_in, item_out = get_neighboring_trackitems(
|
item_in, item_out = get_neighboring_trackitems(
|
||||||
otio_item,
|
otio_item,
|
||||||
otio_track,
|
otio_track,
|
||||||
|
|
@ -101,14 +101,14 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
if transition_type == "dissolve":
|
if transition_type == "dissolve":
|
||||||
transition_func = getattr(
|
transition_func = getattr(
|
||||||
hiero.core.Transition,
|
hiero.core.Transition,
|
||||||
'create{kind}DissolveTransition'.format(kind=kind)
|
"create{kind}DissolveTransition".format(kind=kind)
|
||||||
)
|
)
|
||||||
|
|
||||||
transition = transition_func(
|
transition = transition_func(
|
||||||
item_in,
|
item_in,
|
||||||
item_out,
|
item_out,
|
||||||
otio_item.in_offset.value,
|
otio_item.in_offset.value,
|
||||||
otio_item.out_offset.value
|
otio_item.out_offset.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
elif transition_type == "fade_in":
|
elif transition_type == "fade_in":
|
||||||
|
|
@ -116,20 +116,14 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
hiero.core.Transition,
|
hiero.core.Transition,
|
||||||
'create{kind}FadeInTransition'.format(kind=kind)
|
'create{kind}FadeInTransition'.format(kind=kind)
|
||||||
)
|
)
|
||||||
transition = transition_func(
|
transition = transition_func(item_out, otio_item.out_offset.value)
|
||||||
item_out,
|
|
||||||
otio_item.out_offset.value
|
|
||||||
)
|
|
||||||
|
|
||||||
elif transition_type == "fade_out":
|
elif transition_type == "fade_out":
|
||||||
transition_func = getattr(
|
transition_func = getattr(
|
||||||
hiero.core.Transition,
|
hiero.core.Transition,
|
||||||
'create{kind}FadeOutTransition'.format(kind=kind)
|
"create{kind}FadeOutTransition".format(kind=kind)
|
||||||
)
|
|
||||||
transition = transition_func(
|
|
||||||
item_in,
|
|
||||||
otio_item.in_offset.value
|
|
||||||
)
|
)
|
||||||
|
transition = transition_func(item_in, otio_item.in_offset.value)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Unknown transition
|
# Unknown transition
|
||||||
|
|
@ -138,11 +132,10 @@ def apply_transition(otio_track, otio_item, track):
|
||||||
# Apply transition to track
|
# Apply transition to track
|
||||||
track.addTransition(transition)
|
track.addTransition(transition)
|
||||||
|
|
||||||
except Exception, e:
|
except Exception as e:
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
'Unable to apply transition "{t}": "{e}"\n'.format(
|
'Unable to apply transition "{t}": "{e}"\n'.format(
|
||||||
t=otio_item,
|
t=otio_item, e=e
|
||||||
e=e
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -153,18 +146,14 @@ def prep_url(url_in):
|
||||||
if url.startswith("file://localhost/"):
|
if url.startswith("file://localhost/"):
|
||||||
return url.replace("file://localhost/", "")
|
return url.replace("file://localhost/", "")
|
||||||
|
|
||||||
url = '{url}'.format(
|
if url.startswith(os.sep):
|
||||||
sep=url.startswith(os.sep) and "" or os.sep,
|
url = url[1:]
|
||||||
url=url.startswith(os.sep) and url[1:] or url
|
|
||||||
)
|
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
def create_offline_mediasource(otio_clip, path=None):
|
def create_offline_mediasource(otio_clip, path=None):
|
||||||
hiero_rate = hiero.core.TimeBase(
|
hiero_rate = hiero.core.TimeBase(otio_clip.source_range.start_time.rate)
|
||||||
otio_clip.source_range.start_time.rate
|
|
||||||
)
|
|
||||||
|
|
||||||
if isinstance(otio_clip.media_reference, otio.schema.ExternalReference):
|
if isinstance(otio_clip.media_reference, otio.schema.ExternalReference):
|
||||||
source_range = otio_clip.available_range()
|
source_range = otio_clip.available_range()
|
||||||
|
|
@ -180,7 +169,7 @@ def create_offline_mediasource(otio_clip, path=None):
|
||||||
source_range.start_time.value,
|
source_range.start_time.value,
|
||||||
source_range.duration.value,
|
source_range.duration.value,
|
||||||
hiero_rate,
|
hiero_rate,
|
||||||
source_range.start_time.value
|
source_range.start_time.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
return media
|
return media
|
||||||
|
|
@ -203,7 +192,7 @@ marker_color_map = {
|
||||||
"MAGENTA": "Magenta",
|
"MAGENTA": "Magenta",
|
||||||
"BLACK": "Blue",
|
"BLACK": "Blue",
|
||||||
"WHITE": "Green",
|
"WHITE": "Green",
|
||||||
"MINT": "Cyan"
|
"MINT": "Cyan",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -254,12 +243,6 @@ def add_markers(otio_item, hiero_item, tagsbin):
|
||||||
if _tag is None:
|
if _tag is None:
|
||||||
_tag = hiero.core.Tag(marker_color_map[marker.color])
|
_tag = hiero.core.Tag(marker_color_map[marker.color])
|
||||||
|
|
||||||
start = marker.marked_range.start_time.value
|
|
||||||
end = (
|
|
||||||
marker.marked_range.start_time.value +
|
|
||||||
marker.marked_range.duration.value
|
|
||||||
)
|
|
||||||
|
|
||||||
tag = hiero_item.addTag(_tag)
|
tag = hiero_item.addTag(_tag)
|
||||||
tag.setName(marker.name or marker_color_map[marker_color])
|
tag.setName(marker.name or marker_color_map[marker_color])
|
||||||
|
|
||||||
|
|
@ -275,12 +258,12 @@ def create_track(otio_track, tracknum, track_kind):
|
||||||
# Create a Track
|
# Create a Track
|
||||||
if otio_track.kind == otio.schema.TrackKind.Video:
|
if otio_track.kind == otio.schema.TrackKind.Video:
|
||||||
track = hiero.core.VideoTrack(
|
track = hiero.core.VideoTrack(
|
||||||
otio_track.name or 'Video{n}'.format(n=tracknum)
|
otio_track.name or "Video{n}".format(n=tracknum)
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
track = hiero.core.AudioTrack(
|
track = hiero.core.AudioTrack(
|
||||||
otio_track.name or 'Audio{n}'.format(n=tracknum)
|
otio_track.name or "Audio{n}".format(n=tracknum)
|
||||||
)
|
)
|
||||||
|
|
||||||
return track
|
return track
|
||||||
|
|
@ -315,34 +298,25 @@ def create_trackitem(playhead, track, otio_clip, clip, tagsbin):
|
||||||
for effect in otio_clip.effects:
|
for effect in otio_clip.effects:
|
||||||
if isinstance(effect, otio.schema.LinearTimeWarp):
|
if isinstance(effect, otio.schema.LinearTimeWarp):
|
||||||
trackitem.setPlaybackSpeed(
|
trackitem.setPlaybackSpeed(
|
||||||
trackitem.playbackSpeed() *
|
trackitem.playbackSpeed() * effect.time_scalar
|
||||||
effect.time_scalar
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# If reverse playback speed swap source in and out
|
# If reverse playback speed swap source in and out
|
||||||
if trackitem.playbackSpeed() < 0:
|
if trackitem.playbackSpeed() < 0:
|
||||||
source_out = source_range.start_time.value
|
source_out = source_range.start_time.value
|
||||||
source_in = (
|
source_in = (
|
||||||
source_range.start_time.value +
|
source_range.start_time.value + source_range.duration.value
|
||||||
source_range.duration.value
|
|
||||||
) - 1
|
) - 1
|
||||||
timeline_in = playhead + source_out
|
timeline_in = playhead + source_out
|
||||||
timeline_out = (
|
timeline_out = (timeline_in + source_range.duration.value) - 1
|
||||||
timeline_in +
|
|
||||||
source_range.duration.value
|
|
||||||
) - 1
|
|
||||||
else:
|
else:
|
||||||
# Normal playback speed
|
# Normal playback speed
|
||||||
source_in = source_range.start_time.value
|
source_in = source_range.start_time.value
|
||||||
source_out = (
|
source_out = (
|
||||||
source_range.start_time.value +
|
source_range.start_time.value + source_range.duration.value
|
||||||
source_range.duration.value
|
|
||||||
) - 1
|
) - 1
|
||||||
timeline_in = playhead
|
timeline_in = playhead
|
||||||
timeline_out = (
|
timeline_out = (timeline_in + source_range.duration.value) - 1
|
||||||
timeline_in +
|
|
||||||
source_range.duration.value
|
|
||||||
) - 1
|
|
||||||
|
|
||||||
# Set source and timeline in/out points
|
# Set source and timeline in/out points
|
||||||
trackitem.setSourceIn(source_in)
|
trackitem.setSourceIn(source_in)
|
||||||
|
|
@ -357,7 +331,8 @@ def create_trackitem(playhead, track, otio_clip, clip, tagsbin):
|
||||||
|
|
||||||
|
|
||||||
def build_sequence(
|
def build_sequence(
|
||||||
otio_timeline, project=None, sequence=None, track_kind=None):
|
otio_timeline, project=None, sequence=None, track_kind=None
|
||||||
|
):
|
||||||
|
|
||||||
if project is None:
|
if project is None:
|
||||||
if sequence:
|
if sequence:
|
||||||
|
|
@ -414,8 +389,7 @@ def build_sequence(
|
||||||
if isinstance(otio_clip, otio.schema.Stack):
|
if isinstance(otio_clip, otio.schema.Stack):
|
||||||
bar = hiero.ui.mainWindow().statusBar()
|
bar = hiero.ui.mainWindow().statusBar()
|
||||||
bar.showMessage(
|
bar.showMessage(
|
||||||
"Nested sequences are created separately.",
|
"Nested sequences are created separately.", timeout=3000
|
||||||
timeout=3000
|
|
||||||
)
|
)
|
||||||
build_sequence(otio_clip, project, otio_track.kind)
|
build_sequence(otio_clip, project, otio_track.kind)
|
||||||
|
|
||||||
|
|
@ -428,11 +402,7 @@ def build_sequence(
|
||||||
|
|
||||||
# Create TrackItem
|
# Create TrackItem
|
||||||
trackitem = create_trackitem(
|
trackitem = create_trackitem(
|
||||||
playhead,
|
playhead, track, otio_clip, clip, tagsbin
|
||||||
track,
|
|
||||||
otio_clip,
|
|
||||||
clip,
|
|
||||||
tagsbin
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Add trackitem to track
|
# Add trackitem to track
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ def update_tag(tag, data):
|
||||||
# set all data metadata to tag metadata
|
# set all data metadata to tag metadata
|
||||||
for _k, _v in data_mtd.items():
|
for _k, _v in data_mtd.items():
|
||||||
value = str(_v)
|
value = str(_v)
|
||||||
if type(_v) == dict:
|
if isinstance(_v, dict):
|
||||||
value = json.dumps(_v)
|
value = json.dumps(_v)
|
||||||
|
|
||||||
# set the value
|
# set the value
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ class CreateShotClip(phiero.Creator):
|
||||||
"type": "QCheckBox",
|
"type": "QCheckBox",
|
||||||
"label": "Source resolution",
|
"label": "Source resolution",
|
||||||
"target": "tag",
|
"target": "tag",
|
||||||
"toolTip": "Is resloution taken from timeline or source?", # noqa
|
"toolTip": "Is resolution taken from timeline or source?", # noqa
|
||||||
"order": 4},
|
"order": 4},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -211,7 +211,7 @@ class CreateShotClip(phiero.Creator):
|
||||||
presets = deepcopy(self.presets)
|
presets = deepcopy(self.presets)
|
||||||
gui_inputs = deepcopy(self.gui_inputs)
|
gui_inputs = deepcopy(self.gui_inputs)
|
||||||
|
|
||||||
# get key pares from presets and match it on ui inputs
|
# get key pairs from presets and match it on ui inputs
|
||||||
for k, v in gui_inputs.items():
|
for k, v in gui_inputs.items():
|
||||||
if v["type"] in ("dict", "section"):
|
if v["type"] in ("dict", "section"):
|
||||||
# nested dictionary (only one level allowed
|
# nested dictionary (only one level allowed
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
from itertools import product
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||||
tracks_effect_items = self.collect_sub_track_items(all_tracks)
|
tracks_effect_items = self.collect_sub_track_items(all_tracks)
|
||||||
context.data["tracksEffectItems"] = tracks_effect_items
|
context.data["tracksEffectItems"] = tracks_effect_items
|
||||||
|
|
||||||
# process all sellected timeline track items
|
# process all selected timeline track items
|
||||||
for track_item in selected_timeline_items:
|
for track_item in selected_timeline_items:
|
||||||
data = {}
|
data = {}
|
||||||
clip_name = track_item.name()
|
clip_name = track_item.name()
|
||||||
|
|
@ -62,7 +62,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||||
}:
|
}:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# get clips subtracks and anotations
|
# get clips subtracks and annotations
|
||||||
annotations = self.clip_annotations(source_clip)
|
annotations = self.clip_annotations(source_clip)
|
||||||
subtracks = self.clip_subtrack(track_item)
|
subtracks = self.clip_subtrack(track_item)
|
||||||
self.log.debug("Annotations: {}".format(annotations))
|
self.log.debug("Annotations: {}".format(annotations))
|
||||||
|
|
@ -84,6 +84,11 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||||
k: v for k, v in tag_data.items()
|
k: v for k, v in tag_data.items()
|
||||||
if k not in ("id", "applieswhole", "label")
|
if k not in ("id", "applieswhole", "label")
|
||||||
})
|
})
|
||||||
|
# Backward compatibility fix of 'entity_type' > 'folder_type'
|
||||||
|
if "parents" in data:
|
||||||
|
for parent in data["parents"]:
|
||||||
|
if "entity_type" in parent:
|
||||||
|
parent["folder_type"] = parent.pop("entity_type")
|
||||||
|
|
||||||
asset, asset_name = self._get_folder_data(tag_data)
|
asset, asset_name = self._get_folder_data(tag_data)
|
||||||
|
|
||||||
|
|
@ -378,12 +383,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||||
# collect all subtrack items
|
# collect all subtrack items
|
||||||
sub_track_items = {}
|
sub_track_items = {}
|
||||||
for track in tracks:
|
for track in tracks:
|
||||||
items = track.items()
|
effect_items = track.subTrackItems()
|
||||||
|
|
||||||
effet_items = track.subTrackItems()
|
|
||||||
|
|
||||||
# skip if no clips on track > need track with effect only
|
# skip if no clips on track > need track with effect only
|
||||||
if not effet_items:
|
if not effect_items:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# skip all disabled tracks
|
# skip all disabled tracks
|
||||||
|
|
@ -391,7 +394,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
track_index = track.trackIndex()
|
track_index = track.trackIndex()
|
||||||
_sub_track_items = phiero.flatten(effet_items)
|
_sub_track_items = phiero.flatten(effect_items)
|
||||||
|
|
||||||
_sub_track_items = list(_sub_track_items)
|
_sub_track_items = list(_sub_track_items)
|
||||||
# continue only if any subtrack items are collected
|
# continue only if any subtrack items are collected
|
||||||
|
|
@ -439,10 +442,10 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
|
||||||
for item in subTrackItems:
|
for item in subTrackItems:
|
||||||
if "TimeWarp" in item.name():
|
if "TimeWarp" in item.name():
|
||||||
continue
|
continue
|
||||||
# avoid all anotation
|
# avoid all annotation
|
||||||
if isinstance(item, hiero.core.Annotation):
|
if isinstance(item, hiero.core.Annotation):
|
||||||
continue
|
continue
|
||||||
# # avoid all not anaibled
|
# avoid all disabled
|
||||||
if not item.isEnabled():
|
if not item.isEnabled():
|
||||||
continue
|
continue
|
||||||
subtracks.append(item)
|
subtracks.append(item)
|
||||||
|
|
|
||||||
|
|
@ -35,10 +35,6 @@ class PrecollectRetime(api.InstancePlugin):
|
||||||
source_out = int(track_item.sourceOut())
|
source_out = int(track_item.sourceOut())
|
||||||
speed = track_item.playbackSpeed()
|
speed = track_item.playbackSpeed()
|
||||||
|
|
||||||
# calculate available material before retime
|
|
||||||
available_in = int(track_item.handleInLength() * speed)
|
|
||||||
available_out = int(track_item.handleOutLength() * speed)
|
|
||||||
|
|
||||||
self.log.debug((
|
self.log.debug((
|
||||||
"_BEFORE: \n timeline_in: `{0}`,\n timeline_out: `{1}`, \n "
|
"_BEFORE: \n timeline_in: `{0}`,\n timeline_out: `{1}`, \n "
|
||||||
"source_in: `{2}`,\n source_out: `{3}`,\n speed: `{4}`,\n "
|
"source_in: `{2}`,\n source_out: `{3}`,\n speed: `{4}`,\n "
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import sys
|
||||||
import os
|
import os
|
||||||
import errno
|
import errno
|
||||||
import re
|
import re
|
||||||
import uuid
|
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
@ -44,84 +43,6 @@ def get_folder_fps(folder_entity=None):
|
||||||
return folder_entity["attrib"]["fps"]
|
return folder_entity["attrib"]["fps"]
|
||||||
|
|
||||||
|
|
||||||
def set_id(node, unique_id, overwrite=False):
|
|
||||||
exists = node.parm("id")
|
|
||||||
if not exists:
|
|
||||||
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: cbId attribute of the node.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if node is not None:
|
|
||||||
return node.parm("id")
|
|
||||||
|
|
||||||
|
|
||||||
def generate_ids(nodes, folder_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.
|
|
||||||
folder_id (str): Folder id . Use current folder id if is ``None``.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
list: A list of (node, id) tuples.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
if folder_id is None:
|
|
||||||
project_name = get_current_project_name()
|
|
||||||
folder_path = get_current_folder_path()
|
|
||||||
# Get folder id of current context folder
|
|
||||||
folder_entity = ayon_api.get_folder_by_path(
|
|
||||||
project_name, folder_path, fields={"id"}
|
|
||||||
)
|
|
||||||
if not folder_entity:
|
|
||||||
raise ValueError("No current folder is set.")
|
|
||||||
|
|
||||||
folder_id = folder_entity["id"]
|
|
||||||
|
|
||||||
node_ids = []
|
|
||||||
for node in nodes:
|
|
||||||
_, uid = str(uuid.uuid4()).rsplit("-", 1)
|
|
||||||
unique_id = "{}:{}".format(folder_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_output_parameter(node):
|
def get_output_parameter(node):
|
||||||
"""Return the render output parameter of the given node
|
"""Return the render output parameter of the given node
|
||||||
|
|
||||||
|
|
@ -526,7 +447,7 @@ def maintained_selection():
|
||||||
node.setSelected(on=True)
|
node.setSelected(on=True)
|
||||||
|
|
||||||
|
|
||||||
def reset_framerange():
|
def reset_framerange(fps=True, frame_range=True):
|
||||||
"""Set frame range and FPS to current folder."""
|
"""Set frame range and FPS to current folder."""
|
||||||
|
|
||||||
project_name = get_current_project_name()
|
project_name = get_current_project_name()
|
||||||
|
|
@ -535,29 +456,32 @@ def reset_framerange():
|
||||||
folder_entity = ayon_api.get_folder_by_path(project_name, folder_path)
|
folder_entity = ayon_api.get_folder_by_path(project_name, folder_path)
|
||||||
folder_attributes = folder_entity["attrib"]
|
folder_attributes = folder_entity["attrib"]
|
||||||
|
|
||||||
# Get FPS
|
# Set FPS
|
||||||
fps = get_folder_fps(folder_entity)
|
if fps:
|
||||||
|
fps = get_folder_fps(folder_entity)
|
||||||
|
print("Setting scene FPS to {}".format(int(fps)))
|
||||||
|
set_scene_fps(fps)
|
||||||
|
|
||||||
# Get Start and End Frames
|
if frame_range:
|
||||||
frame_start = folder_attributes.get("frameStart")
|
|
||||||
frame_end = folder_attributes.get("frameEnd")
|
|
||||||
|
|
||||||
if frame_start is None or frame_end is None:
|
# Set Start and End Frames
|
||||||
log.warning("No edit information found for '{}'".format(folder_path))
|
frame_start = folder_attributes.get("frameStart")
|
||||||
return
|
frame_end = folder_attributes.get("frameEnd")
|
||||||
|
|
||||||
handle_start = folder_attributes.get("handleStart", 0)
|
if frame_start is None or frame_end is None:
|
||||||
handle_end = folder_attributes.get("handleEnd", 0)
|
log.warning("No edit information found for '%s'", folder_path)
|
||||||
|
return
|
||||||
|
|
||||||
frame_start -= int(handle_start)
|
handle_start = folder_attributes.get("handleStart", 0)
|
||||||
frame_end += int(handle_end)
|
handle_end = folder_attributes.get("handleEnd", 0)
|
||||||
|
|
||||||
# Set frame range and FPS
|
frame_start -= int(handle_start)
|
||||||
print("Setting scene FPS to {}".format(int(fps)))
|
frame_end += int(handle_end)
|
||||||
set_scene_fps(fps)
|
|
||||||
hou.playbar.setFrameRange(frame_start, frame_end)
|
# Set frame range and FPS
|
||||||
hou.playbar.setPlaybackRange(frame_start, frame_end)
|
hou.playbar.setFrameRange(frame_start, frame_end)
|
||||||
hou.setFrame(frame_start)
|
hou.playbar.setPlaybackRange(frame_start, frame_end)
|
||||||
|
hou.setFrame(frame_start)
|
||||||
|
|
||||||
|
|
||||||
def get_main_window():
|
def get_main_window():
|
||||||
|
|
@ -1072,3 +996,84 @@ def add_self_publish_button(node):
|
||||||
template = node.parmTemplateGroup()
|
template = node.parmTemplateGroup()
|
||||||
template.insertBefore((0,), button_parm)
|
template.insertBefore((0,), button_parm)
|
||||||
node.setParmTemplateGroup(template)
|
node.setParmTemplateGroup(template)
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Pipeline tools for OpenPype Houdini integration."""
|
"""Pipeline tools for OpenPype Houdini integration."""
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import hou # noqa
|
import hou # noqa
|
||||||
|
|
@ -39,6 +38,9 @@ LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
|
||||||
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")
|
||||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
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):
|
class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||||
name = "houdini"
|
name = "houdini"
|
||||||
|
|
@ -61,10 +63,12 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||||
log.info("Installing callbacks ... ")
|
log.info("Installing callbacks ... ")
|
||||||
# register_event_callback("init", on_init)
|
# register_event_callback("init", on_init)
|
||||||
self._register_callbacks()
|
self._register_callbacks()
|
||||||
|
register_event_callback("workfile.save.before", before_workfile_save)
|
||||||
register_event_callback("before.save", before_save)
|
register_event_callback("before.save", before_save)
|
||||||
register_event_callback("save", on_save)
|
register_event_callback("save", on_save)
|
||||||
register_event_callback("open", on_open)
|
register_event_callback("open", on_open)
|
||||||
register_event_callback("new", on_new)
|
register_event_callback("new", on_new)
|
||||||
|
register_event_callback("taskChanged", on_task_changed)
|
||||||
|
|
||||||
self._has_been_setup = True
|
self._has_been_setup = True
|
||||||
|
|
||||||
|
|
@ -166,7 +170,7 @@ class HoudiniHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
||||||
if not op_ctx:
|
if not op_ctx:
|
||||||
op_ctx = self.create_context_node()
|
op_ctx = self.create_context_node()
|
||||||
|
|
||||||
lib.imprint(op_ctx, data)
|
lib.imprint(op_ctx, data, update=True)
|
||||||
|
|
||||||
def get_context_data(self):
|
def get_context_data(self):
|
||||||
op_ctx = hou.node(CONTEXT_CONTAINER)
|
op_ctx = hou.node(CONTEXT_CONTAINER)
|
||||||
|
|
@ -287,6 +291,11 @@ def ls():
|
||||||
yield parse_container(container)
|
yield parse_container(container)
|
||||||
|
|
||||||
|
|
||||||
|
def before_workfile_save(event):
|
||||||
|
global ABOUT_TO_SAVE
|
||||||
|
ABOUT_TO_SAVE = True
|
||||||
|
|
||||||
|
|
||||||
def before_save():
|
def before_save():
|
||||||
return lib.validate_fps()
|
return lib.validate_fps()
|
||||||
|
|
||||||
|
|
@ -302,6 +311,17 @@ def on_save():
|
||||||
for node, new_id in lib.generate_ids(nodes):
|
for node, new_id in lib.generate_ids(nodes):
|
||||||
lib.set_id(node, new_id, overwrite=False)
|
lib.set_id(node, new_id, overwrite=False)
|
||||||
|
|
||||||
|
# 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():
|
def _show_outdated_content_popup():
|
||||||
# Get main window
|
# Get main window
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
||||||
product_type = "redshift_rop"
|
product_type = "redshift_rop"
|
||||||
icon = "magic"
|
icon = "magic"
|
||||||
ext = "exr"
|
ext = "exr"
|
||||||
|
multi_layered_mode = "No Multi-Layered EXR File"
|
||||||
|
|
||||||
# Default to split export and render jobs
|
# Default to split export and render jobs
|
||||||
split_render = True
|
split_render = True
|
||||||
|
|
@ -55,25 +56,36 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
||||||
|
|
||||||
# Set the linked rop to the Redshift ROP
|
# Set the linked rop to the Redshift ROP
|
||||||
ipr_rop.parm("linked_rop").set(instance_node.path())
|
ipr_rop.parm("linked_rop").set(instance_node.path())
|
||||||
|
|
||||||
ext = pre_create_data.get("image_format")
|
ext = pre_create_data.get("image_format")
|
||||||
filepath = "{renders_dir}{product_name}/{product_name}.{fmt}".format(
|
multi_layered_mode = pre_create_data.get("multi_layered_mode")
|
||||||
renders_dir=hou.text.expandString("$HIP/pyblish/renders/"),
|
|
||||||
product_name=product_name,
|
|
||||||
fmt="${aov}.$F4.{ext}".format(aov="AOV", ext=ext)
|
|
||||||
)
|
|
||||||
|
|
||||||
ext_format_index = {"exr": 0, "tif": 1, "jpg": 2, "png": 3}
|
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 = {
|
parms = {
|
||||||
# Render frame range
|
# Render frame range
|
||||||
"trange": 1,
|
"trange": 1,
|
||||||
# Redshift ROP settings
|
# Redshift ROP settings
|
||||||
"RS_outputFileNamePrefix": filepath,
|
"RS_outputFileNamePrefix": filepath,
|
||||||
"RS_outputMultilayerMode": "1", # no multi-layered exr
|
|
||||||
"RS_outputBeautyAOVSuffix": "beauty",
|
"RS_outputBeautyAOVSuffix": "beauty",
|
||||||
"RS_outputFileFormat": ext_format_index[ext],
|
"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:
|
if self.selected_nodes:
|
||||||
# set up the render camera from the selected node
|
# set up the render camera from the selected node
|
||||||
|
|
@ -111,6 +123,11 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
||||||
image_format_enum = [
|
image_format_enum = [
|
||||||
"exr", "tif", "jpg", "png",
|
"exr", "tif", "jpg", "png",
|
||||||
]
|
]
|
||||||
|
multi_layered_mode = [
|
||||||
|
"No Multi-Layered EXR File",
|
||||||
|
"Full Multi-Layered EXR File"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
return attrs + [
|
return attrs + [
|
||||||
BoolDef("farm",
|
BoolDef("farm",
|
||||||
|
|
@ -122,5 +139,9 @@ class CreateRedshiftROP(plugin.HoudiniCreator):
|
||||||
EnumDef("image_format",
|
EnumDef("image_format",
|
||||||
image_format_enum,
|
image_format_enum,
|
||||||
default=self.ext,
|
default=self.ext,
|
||||||
label="Image Format Options")
|
label="Image Format Options"),
|
||||||
|
EnumDef("multi_layered_mode",
|
||||||
|
multi_layered_mode,
|
||||||
|
default=self.multi_layered_mode,
|
||||||
|
label="Multi-Layered EXR")
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -76,8 +76,8 @@ class SetFrameRangeWithHandlesLoader(load.LoaderPlugin):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Include handles
|
# Include handles
|
||||||
start -= version_data.get("handleStart", 0)
|
start -= version_attributes.get("handleStart", 0)
|
||||||
end += version_data.get("handleEnd", 0)
|
end += version_attributes.get("handleEnd", 0)
|
||||||
|
|
||||||
hou.playbar.setFrameRange(start, end)
|
hou.playbar.setFrameRange(start, end)
|
||||||
hou.playbar.setPlaybackRange(start, end)
|
hou.playbar.setPlaybackRange(start, end)
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ class AbcLoader(load.LoaderPlugin):
|
||||||
|
|
||||||
normal_node.setInput(0, unpack)
|
normal_node.setInput(0, unpack)
|
||||||
|
|
||||||
null = container.createNode("null", node_name="OUT".format(name))
|
null = container.createNode("null", node_name="OUT")
|
||||||
null.setInput(0, normal_node)
|
null.setInput(0, normal_node)
|
||||||
|
|
||||||
# Ensure display flag is on the Alembic input node and not on the OUT
|
# Ensure display flag is on the Alembic input node and not on the OUT
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,9 @@ class CameraLoader(load.LoaderPlugin):
|
||||||
|
|
||||||
temp_camera.destroy()
|
temp_camera.destroy()
|
||||||
|
|
||||||
|
def switch(self, container, context):
|
||||||
|
self.update(container, context)
|
||||||
|
|
||||||
def remove(self, container):
|
def remove(self, container):
|
||||||
|
|
||||||
node = container["node"]
|
node = container["node"]
|
||||||
|
|
@ -195,7 +198,6 @@ class CameraLoader(load.LoaderPlugin):
|
||||||
def _match_maya_render_mask(self, camera):
|
def _match_maya_render_mask(self, camera):
|
||||||
"""Workaround to match Maya render mask in Houdini"""
|
"""Workaround to match Maya render mask in Houdini"""
|
||||||
|
|
||||||
# print("Setting match maya render mask ")
|
|
||||||
parm = camera.parm("aperture")
|
parm = camera.parm("aperture")
|
||||||
expression = parm.expression()
|
expression = parm.expression()
|
||||||
expression = expression.replace("return ", "aperture = ")
|
expression = expression.replace("return ", "aperture = ")
|
||||||
|
|
|
||||||
129
client/ayon_core/hosts/houdini/plugins/load/load_filepath.py
Normal file
129
client/ayon_core/hosts/houdini/plugins/load/load_filepath.py
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
|
from ayon_core.pipeline import load
|
||||||
|
from openpype.hosts.houdini.api import pipeline
|
||||||
|
|
||||||
|
import hou
|
||||||
|
|
||||||
|
|
||||||
|
class FilePathLoader(load.LoaderPlugin):
|
||||||
|
"""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, representation):
|
||||||
|
self.update(container, representation)
|
||||||
|
|
||||||
|
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("\\", "/")
|
||||||
77
client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py
Normal file
77
client/ayon_core/hosts/houdini/plugins/load/load_usd_sop.py
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ayon_core.pipeline import load
|
||||||
|
from ayon_core.hosts.houdini.api import pipeline
|
||||||
|
|
||||||
|
|
||||||
|
class SopUsdImportLoader(load.LoaderPlugin):
|
||||||
|
"""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)
|
||||||
|
|
@ -41,23 +41,23 @@ class CollectKarmaROPRenderProducts(pyblish.api.InstancePlugin):
|
||||||
instance.data["chunkSize"] = chunk_size
|
instance.data["chunkSize"] = chunk_size
|
||||||
self.log.debug("Chunk Size: %s" % chunk_size)
|
self.log.debug("Chunk Size: %s" % chunk_size)
|
||||||
|
|
||||||
default_prefix = evalParmNoFrame(rop, "picture")
|
default_prefix = evalParmNoFrame(rop, "picture")
|
||||||
render_products = []
|
render_products = []
|
||||||
|
|
||||||
# Default beauty AOV
|
# Default beauty AOV
|
||||||
beauty_product = self.get_render_product_name(
|
beauty_product = self.get_render_product_name(
|
||||||
prefix=default_prefix, suffix=None
|
prefix=default_prefix, suffix=None
|
||||||
)
|
)
|
||||||
render_products.append(beauty_product)
|
render_products.append(beauty_product)
|
||||||
|
|
||||||
files_by_aov = {
|
files_by_aov = {
|
||||||
"beauty": self.generate_expected_files(instance,
|
"beauty": self.generate_expected_files(instance,
|
||||||
beauty_product)
|
beauty_product)
|
||||||
}
|
}
|
||||||
|
|
||||||
filenames = list(render_products)
|
filenames = list(render_products)
|
||||||
instance.data["files"] = filenames
|
instance.data["files"] = filenames
|
||||||
instance.data["renderProducts"] = colorspace.ARenderProduct()
|
instance.data["renderProducts"] = colorspace.ARenderProduct()
|
||||||
|
|
||||||
for product in render_products:
|
for product in render_products:
|
||||||
self.log.debug("Found render product: %s" % product)
|
self.log.debug("Found render product: %s" % product)
|
||||||
|
|
|
||||||
|
|
@ -41,57 +41,57 @@ class CollectMantraROPRenderProducts(pyblish.api.InstancePlugin):
|
||||||
instance.data["chunkSize"] = chunk_size
|
instance.data["chunkSize"] = chunk_size
|
||||||
self.log.debug("Chunk Size: %s" % chunk_size)
|
self.log.debug("Chunk Size: %s" % chunk_size)
|
||||||
|
|
||||||
default_prefix = evalParmNoFrame(rop, "vm_picture")
|
default_prefix = evalParmNoFrame(rop, "vm_picture")
|
||||||
render_products = []
|
render_products = []
|
||||||
|
|
||||||
# Store whether we are splitting the render job (export + render)
|
# Store whether we are splitting the render job (export + render)
|
||||||
split_render = bool(rop.parm("soho_outputmode").eval())
|
split_render = bool(rop.parm("soho_outputmode").eval())
|
||||||
instance.data["splitRender"] = split_render
|
instance.data["splitRender"] = split_render
|
||||||
export_prefix = None
|
export_prefix = None
|
||||||
export_products = []
|
export_products = []
|
||||||
if split_render:
|
if split_render:
|
||||||
export_prefix = evalParmNoFrame(
|
export_prefix = evalParmNoFrame(
|
||||||
rop, "soho_diskfile", pad_character="0"
|
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)
|
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)
|
||||||
|
|
||||||
files_by_aov = {
|
# Default beauty AOV
|
||||||
"beauty": self.generate_expected_files(instance,
|
beauty_product = self.get_render_product_name(
|
||||||
beauty_product)
|
prefix=default_prefix, suffix=None
|
||||||
}
|
)
|
||||||
|
render_products.append(beauty_product)
|
||||||
|
|
||||||
aov_numbers = rop.evalParm("vm_numaux")
|
files_by_aov = {
|
||||||
if aov_numbers > 0:
|
"beauty": self.generate_expected_files(instance,
|
||||||
# get the filenames of the AOVs
|
beauty_product)
|
||||||
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
|
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
|
||||||
|
|
||||||
for product in render_products:
|
for product in render_products:
|
||||||
self.log.debug("Found render product: %s" % product)
|
self.log.debug("Found render product: %s" % product)
|
||||||
|
|
|
||||||
|
|
@ -60,15 +60,22 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
||||||
instance.data["ifdFile"] = beauty_export_product
|
instance.data["ifdFile"] = beauty_export_product
|
||||||
instance.data["exportFiles"] = list(export_products)
|
instance.data["exportFiles"] = list(export_products)
|
||||||
|
|
||||||
# Default beauty AOV
|
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 = ""
|
||||||
|
|
||||||
|
# Default beauty/main layer AOV
|
||||||
beauty_product = self.get_render_product_name(
|
beauty_product = self.get_render_product_name(
|
||||||
prefix=default_prefix, suffix=beauty_suffix
|
prefix=default_prefix, suffix=beauty_suffix
|
||||||
)
|
)
|
||||||
render_products = [beauty_product]
|
render_products = [beauty_product]
|
||||||
files_by_aov = {
|
files_by_aov = {
|
||||||
"_": self.generate_expected_files(instance,
|
beauty_suffix: self.generate_expected_files(instance,
|
||||||
beauty_product)}
|
beauty_product)
|
||||||
|
}
|
||||||
|
|
||||||
aovs_rop = rop.parm("RS_aovGetFromNode").evalAsNode()
|
aovs_rop = rop.parm("RS_aovGetFromNode").evalAsNode()
|
||||||
if aovs_rop:
|
if aovs_rop:
|
||||||
rop = aovs_rop
|
rop = aovs_rop
|
||||||
|
|
@ -89,11 +96,14 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
||||||
if not aov_prefix:
|
if not aov_prefix:
|
||||||
aov_prefix = default_prefix
|
aov_prefix = default_prefix
|
||||||
|
|
||||||
aov_product = self.get_render_product_name(aov_prefix, aov_suffix)
|
if rop.parm(f"RS_aovID_{i}").evalAsString() == "CRYPTOMATTE" or \
|
||||||
render_products.append(aov_product)
|
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,
|
files_by_aov[aov_suffix] = self.generate_expected_files(instance,
|
||||||
aov_product) # noqa
|
aov_product) # noqa
|
||||||
|
|
||||||
for product in render_products:
|
for product in render_products:
|
||||||
self.log.debug("Found render product: %s" % product)
|
self.log.debug("Found render product: %s" % product)
|
||||||
|
|
@ -121,7 +131,7 @@ class CollectRedshiftROPRenderProducts(pyblish.api.InstancePlugin):
|
||||||
|
|
||||||
# When AOV is explicitly defined in prefix we just swap it out
|
# When AOV is explicitly defined in prefix we just swap it out
|
||||||
# directly with the AOV suffix to embed it.
|
# directly with the AOV suffix to embed it.
|
||||||
# Note: ${AOV} seems to be evaluated in the parameter as %AOV%
|
# Note: '$AOV' seems to be evaluated in the parameter as '%AOV%'
|
||||||
has_aov_in_prefix = "%AOV%" in prefix
|
has_aov_in_prefix = "%AOV%" in prefix
|
||||||
if has_aov_in_prefix:
|
if has_aov_in_prefix:
|
||||||
# It seems that when some special separator characters are present
|
# It seems that when some special separator characters are present
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,8 @@ class ValidateCopOutputNode(pyblish.api.InstancePlugin):
|
||||||
# the isinstance check above should be stricter than this category
|
# the isinstance check above should be stricter than this category
|
||||||
if output_node.type().category().name() != "Cop2":
|
if output_node.type().category().name() != "Cop2":
|
||||||
raise PublishValidationError(
|
raise PublishValidationError(
|
||||||
("Output node %s is not of category Cop2. "
|
(
|
||||||
"This is a bug...").format(output_node.path()),
|
"Output node {} is not of category Cop2."
|
||||||
|
" This is a bug..."
|
||||||
|
).format(output_node.path()),
|
||||||
title=cls.label)
|
title=cls.label)
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from ayon_core.hosts.max.api.lib import (
|
||||||
maintained_selection,
|
maintained_selection,
|
||||||
object_transform_set
|
object_transform_set
|
||||||
)
|
)
|
||||||
from ayon_core.hosts.max.api.lib import maintained_selection
|
|
||||||
from ayon_core.hosts.max.api.pipeline import (
|
from ayon_core.hosts.max.api.pipeline import (
|
||||||
containerise,
|
containerise,
|
||||||
get_previous_loaded_object,
|
get_previous_loaded_object,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
from ayon_core.pipeline import PublishValidationError
|
from ayon_core.pipeline import PublishValidationError
|
||||||
from pymxs import runtime as rt
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateCameraContent(pyblish.api.InstancePlugin):
|
class ValidateCameraContent(pyblish.api.InstancePlugin):
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ class ValidateRenderPasses(OptionalPyblishPluginMixin,
|
||||||
invalid = []
|
invalid = []
|
||||||
if instance.name not in file_name:
|
if instance.name not in file_name:
|
||||||
cls.log.error("The renderpass filename should contain the instance name.")
|
cls.log.error("The renderpass filename should contain the instance name.")
|
||||||
invalid.append((f"Invalid instance name",
|
invalid.append(("Invalid instance name",
|
||||||
file_name))
|
file_name))
|
||||||
if renderpass is not None:
|
if renderpass is not None:
|
||||||
if not file_name.rstrip(".").endswith(renderpass):
|
if not file_name.rstrip(".").endswith(renderpass):
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ def get_main_window():
|
||||||
def suspended_refresh(suspend=True):
|
def suspended_refresh(suspend=True):
|
||||||
"""Suspend viewport refreshes
|
"""Suspend viewport refreshes
|
||||||
|
|
||||||
cmds.ogs(pause=True) is a toggle so we cant pass False.
|
cmds.ogs(pause=True) is a toggle so we can't pass False.
|
||||||
"""
|
"""
|
||||||
if IS_HEADLESS:
|
if IS_HEADLESS:
|
||||||
yield
|
yield
|
||||||
|
|
@ -583,7 +583,7 @@ def pairwise(iterable):
|
||||||
|
|
||||||
|
|
||||||
def collect_animation_defs(fps=False):
|
def collect_animation_defs(fps=False):
|
||||||
"""Get the basic animation attribute defintions for the publisher.
|
"""Get the basic animation attribute definitions for the publisher.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
OrderedDict
|
OrderedDict
|
||||||
|
|
@ -2152,9 +2152,13 @@ def get_related_sets(node):
|
||||||
sets = cmds.ls(sets)
|
sets = cmds.ls(sets)
|
||||||
|
|
||||||
# Ignore `avalon.container`
|
# Ignore `avalon.container`
|
||||||
sets = [s for s in sets if
|
sets = [
|
||||||
not cmds.attributeQuery("id", node=s, exists=True) or
|
s for s in sets
|
||||||
not cmds.getAttr("%s.id" % s) in ignored]
|
if (
|
||||||
|
not cmds.attributeQuery("id", node=s, exists=True)
|
||||||
|
or cmds.getAttr(f"{s}.id") not in ignored
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# Exclude deformer sets (`type=2` for `maya.cmds.listSets`)
|
# Exclude deformer sets (`type=2` for `maya.cmds.listSets`)
|
||||||
deformer_sets = cmds.listSets(object=node,
|
deformer_sets = cmds.listSets(object=node,
|
||||||
|
|
@ -3834,7 +3838,7 @@ def get_color_management_output_transform():
|
||||||
|
|
||||||
def image_info(file_path):
|
def image_info(file_path):
|
||||||
# type: (str) -> dict
|
# type: (str) -> dict
|
||||||
"""Based on tha texture path, get its bit depth and format information.
|
"""Based on the texture path, get its bit depth and format information.
|
||||||
Take reference from makeTx.py in Arnold:
|
Take reference from makeTx.py in Arnold:
|
||||||
ImageInfo(filename): Get Image Information for colorspace
|
ImageInfo(filename): Get Image Information for colorspace
|
||||||
AiTextureGetFormat(filename): Get Texture Format
|
AiTextureGetFormat(filename): Get Texture Format
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class MayaLegacyConvertor(ProductConvertorPlugin,
|
||||||
).format(product_type))
|
).format(product_type))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
creator_id = product_type_to_id[family]
|
creator_id = product_type_to_id[product_type]
|
||||||
creator = self.create_context.creators[creator_id]
|
creator = self.create_context.creators[creator_id]
|
||||||
data["creator_identifier"] = creator_id
|
data["creator_identifier"] = creator_id
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,13 +20,6 @@ class CreateUnrealSkeletalMesh(plugin.MayaCreator):
|
||||||
# Defined in settings
|
# Defined in settings
|
||||||
joint_hints = set()
|
joint_hints = set()
|
||||||
|
|
||||||
def apply_settings(self, project_settings):
|
|
||||||
"""Apply project settings to creator"""
|
|
||||||
settings = (
|
|
||||||
project_settings["maya"]["create"]["CreateUnrealSkeletalMesh"]
|
|
||||||
)
|
|
||||||
self.joint_hints = set(settings.get("joint_hints", []))
|
|
||||||
|
|
||||||
def get_dynamic_data(
|
def get_dynamic_data(
|
||||||
self,
|
self,
|
||||||
project_name,
|
project_name,
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,6 @@ class CreateUnrealStaticMesh(plugin.MayaCreator):
|
||||||
# Defined in settings
|
# Defined in settings
|
||||||
collision_prefixes = []
|
collision_prefixes = []
|
||||||
|
|
||||||
def apply_settings(self, project_settings):
|
|
||||||
"""Apply project settings to creator"""
|
|
||||||
settings = project_settings["maya"]["create"]["CreateUnrealStaticMesh"]
|
|
||||||
self.collision_prefixes = settings["collision_prefixes"]
|
|
||||||
|
|
||||||
def get_dynamic_data(
|
def get_dynamic_data(
|
||||||
self,
|
self,
|
||||||
project_name,
|
project_name,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ from ayon_core.hosts.maya.api import (
|
||||||
from ayon_core.lib import NumberDef
|
from ayon_core.lib import NumberDef
|
||||||
|
|
||||||
|
|
||||||
class CreateYetiCache(plugin.MayaCreator):
|
class CreateUnrealYetiCache(plugin.MayaCreator):
|
||||||
"""Output for procedural plugin nodes of Yeti """
|
"""Output for procedural plugin nodes of Yeti """
|
||||||
|
|
||||||
identifier = "io.openpype.creators.maya.unrealyeticache"
|
identifier = "io.openpype.creators.maya.unrealyeticache"
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ from ayon_core.hosts.maya.api.lib import (
|
||||||
unique_namespace,
|
unique_namespace,
|
||||||
get_attribute_input,
|
get_attribute_input,
|
||||||
maintained_selection,
|
maintained_selection,
|
||||||
convert_to_maya_fps
|
|
||||||
)
|
)
|
||||||
from ayon_core.hosts.maya.api.pipeline import containerise
|
from ayon_core.hosts.maya.api.pipeline import containerise
|
||||||
from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type
|
from ayon_core.hosts.maya.api.plugin import get_load_color_for_product_type
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import os
|
|
||||||
|
|
||||||
import maya.cmds as cmds
|
import maya.cmds as cmds
|
||||||
|
|
||||||
from ayon_core.hosts.maya.api.pipeline import containerise
|
from ayon_core.hosts.maya.api.pipeline import containerise
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import os
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from ayon_core.lib import EnumDef
|
from ayon_core.lib import EnumDef
|
||||||
from ayon_core.pipeline import (
|
from ayon_core.pipeline import (
|
||||||
load,
|
load,
|
||||||
get_representation_context,
|
|
||||||
get_current_host_name,
|
get_current_host_name,
|
||||||
)
|
)
|
||||||
from ayon_core.pipeline.load.utils import get_representation_path_from_context
|
from ayon_core.pipeline.load.utils import get_representation_path_from_context
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class LoadVDBtoArnold(load.LoaderPlugin):
|
||||||
path = self.filepath_from_context(context)
|
path = self.filepath_from_context(context)
|
||||||
self._set_path(grid_node,
|
self._set_path(grid_node,
|
||||||
path=path,
|
path=path,
|
||||||
representation=context["representation"])
|
repre_entity=context["representation"])
|
||||||
|
|
||||||
# Lock the shape node so the user can't delete the transform/shape
|
# Lock the shape node so the user can't delete the transform/shape
|
||||||
# as if it was referenced
|
# as if it was referenced
|
||||||
|
|
@ -91,7 +91,7 @@ class LoadVDBtoArnold(load.LoaderPlugin):
|
||||||
assert len(grid_nodes) == 1, "This is a bug"
|
assert len(grid_nodes) == 1, "This is a bug"
|
||||||
|
|
||||||
# Update the VRayVolumeGrid
|
# Update the VRayVolumeGrid
|
||||||
self._set_path(grid_nodes[0], path=path, representation=repre_entity)
|
self._set_path(grid_nodes[0], path=path, repre_entity=repre_entity)
|
||||||
|
|
||||||
# Update container representation
|
# Update container representation
|
||||||
cmds.setAttr(container["objectName"] + ".representation",
|
cmds.setAttr(container["objectName"] + ".representation",
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,6 @@ class CollectMayaRender(pyblish.api.InstancePlugin):
|
||||||
layer = instance.data["transientData"]["layer"]
|
layer = instance.data["transientData"]["layer"]
|
||||||
objset = instance.data.get("instance_node")
|
objset = instance.data.get("instance_node")
|
||||||
filepath = context.data["currentFile"].replace("\\", "/")
|
filepath = context.data["currentFile"].replace("\\", "/")
|
||||||
workspace = context.data["workspaceDir"]
|
|
||||||
|
|
||||||
# check if layer is renderable
|
# check if layer is renderable
|
||||||
if not layer.isRenderable():
|
if not layer.isRenderable():
|
||||||
|
|
@ -314,7 +313,7 @@ class CollectMayaRender(pyblish.api.InstancePlugin):
|
||||||
if not extend_frames:
|
if not extend_frames:
|
||||||
instance.data["overrideExistingFrame"] = False
|
instance.data["overrideExistingFrame"] = False
|
||||||
|
|
||||||
# Update the instace
|
# Update the instance
|
||||||
instance.data.update(data)
|
instance.data.update(data)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ class ExtractAlembic(publish.Extractor):
|
||||||
families = ["pointcache", "model", "vrayproxy.alembic"]
|
families = ["pointcache", "model", "vrayproxy.alembic"]
|
||||||
targets = ["local", "remote"]
|
targets = ["local", "remote"]
|
||||||
|
|
||||||
|
# From settings
|
||||||
|
bake_attributes = []
|
||||||
|
bake_attribute_prefixes = []
|
||||||
|
|
||||||
def process(self, instance):
|
def process(self, instance):
|
||||||
if instance.data.get("farm"):
|
if instance.data.get("farm"):
|
||||||
self.log.debug("Should be processed on farm, skipping.")
|
self.log.debug("Should be processed on farm, skipping.")
|
||||||
|
|
@ -40,10 +44,12 @@ class ExtractAlembic(publish.Extractor):
|
||||||
attrs = instance.data.get("attr", "").split(";")
|
attrs = instance.data.get("attr", "").split(";")
|
||||||
attrs = [value for value in attrs if value.strip()]
|
attrs = [value for value in attrs if value.strip()]
|
||||||
attrs += instance.data.get("userDefinedAttributes", [])
|
attrs += instance.data.get("userDefinedAttributes", [])
|
||||||
|
attrs += self.bake_attributes
|
||||||
attrs += ["cbId"]
|
attrs += ["cbId"]
|
||||||
|
|
||||||
attr_prefixes = instance.data.get("attrPrefix", "").split(";")
|
attr_prefixes = instance.data.get("attrPrefix", "").split(";")
|
||||||
attr_prefixes = [value for value in attr_prefixes if value.strip()]
|
attr_prefixes = [value for value in attr_prefixes if value.strip()]
|
||||||
|
attr_prefixes += self.bake_attribute_prefixes
|
||||||
|
|
||||||
self.log.debug("Extracting pointcache..")
|
self.log.debug("Extracting pointcache..")
|
||||||
dirname = self.staging_dir(instance)
|
dirname = self.staging_dir(instance)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""Create Unreal Skeletal Mesh data to be extracted as FBX."""
|
"""Create Unreal Skeletal Mesh data to be extracted as FBX."""
|
||||||
import os
|
import os
|
||||||
from contextlib import contextmanager
|
|
||||||
|
|
||||||
from maya import cmds # noqa
|
from maya import cmds # noqa
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ class ExtractUnrealSkeletalMeshFbx(publish.Extractor):
|
||||||
renamed_to_extract.append("|".join(node_path))
|
renamed_to_extract.append("|".join(node_path))
|
||||||
|
|
||||||
with renamed(original_parent, parent_node):
|
with renamed(original_parent, parent_node):
|
||||||
self.log.debug("Extracting: {}".format(renamed_to_extract, path))
|
self.log.debug("Extracting: {}".format(renamed_to_extract))
|
||||||
fbx_exporter.export(renamed_to_extract, path)
|
fbx_exporter.export(renamed_to_extract, path)
|
||||||
|
|
||||||
if "representations" not in instance.data:
|
if "representations" not in instance.data:
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ from maya import cmds
|
||||||
from ayon_core.pipeline import publish
|
from ayon_core.pipeline import publish
|
||||||
|
|
||||||
|
|
||||||
class ExtractYetiCache(publish.Extractor):
|
class ExtractUnrealYetiCache(publish.Extractor):
|
||||||
"""Producing Yeti cache files using scene time range.
|
"""Producing Yeti cache files using scene time range.
|
||||||
|
|
||||||
This will extract Yeti cache file sequence and fur settings.
|
This will extract Yeti cache file sequence and fur settings.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
label = "Extract Yeti Cache"
|
label = "Extract Yeti Cache (Unreal)"
|
||||||
hosts = ["maya"]
|
hosts = ["maya"]
|
||||||
families = ["yeticacheUE"]
|
families = ["yeticacheUE"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ from maya import cmds
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
from ayon_core.hosts.maya.api.lib import extract_alembic
|
from ayon_core.hosts.maya.api.lib import extract_alembic
|
||||||
from ayon_core.pipeline import publish
|
from ayon_core.pipeline import publish
|
||||||
from ayon_core.lib import StringTemplate
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractWorkfileXgen(publish.Extractor):
|
class ExtractWorkfileXgen(publish.Extractor):
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ from ayon_core.pipeline import publish
|
||||||
from ayon_core.hosts.maya.api.lib import (
|
from ayon_core.hosts.maya.api.lib import (
|
||||||
maintained_selection, attribute_values, write_xgen_file, delete_after
|
maintained_selection, attribute_values, write_xgen_file, delete_after
|
||||||
)
|
)
|
||||||
from ayon_core.lib import StringTemplate
|
|
||||||
|
|
||||||
|
|
||||||
class ExtractXgen(publish.Extractor):
|
class ExtractXgen(publish.Extractor):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<root>
|
||||||
|
<error id="main">
|
||||||
|
<title>Shape IDs mismatch original shape</title>
|
||||||
|
<description>## Shapes mismatch IDs with original shape
|
||||||
|
|
||||||
|
Meshes are detected in the **rig** where the (deformed) mesh has a different
|
||||||
|
`cbId` than the same mesh in its deformation history.
|
||||||
|
Theses should normally be the same.
|
||||||
|
|
||||||
|
### How to repair?
|
||||||
|
|
||||||
|
By using the repair action the IDs from the shape in history will be
|
||||||
|
copied to the deformed shape. For rig instances, in many cases the
|
||||||
|
correct fix is to use the repair action **unless** you explicitly tried
|
||||||
|
to update the `cbId` values on the meshes - in that case you actually want
|
||||||
|
to do to the reverse and copy the IDs from the deformed mesh to the history
|
||||||
|
mesh instead.
|
||||||
|
|
||||||
|
</description>
|
||||||
|
<detail>
|
||||||
|
### How does this happen?
|
||||||
|
|
||||||
|
When a deformer is applied in the scene on a referenced mesh that had no
|
||||||
|
deformers then Maya will create a new shape node for the mesh that
|
||||||
|
does not have the original id. Then on scene save new ids get created for the
|
||||||
|
meshes lacking a `cbId` and thus the mesh then has a different `cbId` than
|
||||||
|
the mesh in the deformation history.
|
||||||
|
|
||||||
|
</detail>
|
||||||
|
</error>
|
||||||
|
</root>
|
||||||
|
|
@ -36,18 +36,18 @@ class ValidateSubsetName(pyblish.api.InstancePlugin):
|
||||||
)
|
)
|
||||||
|
|
||||||
if not isinstance(product_name, six.string_types):
|
if not isinstance(product_name, six.string_types):
|
||||||
raise TypeError((
|
raise PublishValidationError((
|
||||||
"Instance product name must be string, got: {0} ({1})"
|
"Instance product name must be string, got: {0} ({1})"
|
||||||
).format(product_name, type(product_name)))
|
).format(product_name, type(product_name)))
|
||||||
|
|
||||||
# Ensure is not empty product
|
# Ensure is not empty product
|
||||||
if not product_name:
|
if not product_name:
|
||||||
raise ValueError(
|
raise PublishValidationError(
|
||||||
"Instance product name is empty: {0}".format(product_name)
|
"Instance product name is empty: {0}".format(product_name)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Validate product characters
|
# Validate product characters
|
||||||
if not validate_name(product_name):
|
if not validate_name(product_name):
|
||||||
raise ValueError((
|
raise PublishValidationError((
|
||||||
"Instance product name contains invalid characters: {0}"
|
"Instance product name contains invalid characters: {0}"
|
||||||
).format(product_name))
|
).format(product_name))
|
||||||
|
|
|
||||||
|
|
@ -51,5 +51,5 @@ class ValidateMeshEmpty(pyblish.api.InstancePlugin):
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise PublishValidationError(
|
raise PublishValidationError(
|
||||||
"Meshes found in instance without any vertices: %s" % invalid
|
"Meshes found without any vertices: %s" % invalid
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,11 @@ from maya import cmds
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import ayon_core.hosts.maya.api.action
|
import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.pipeline.publish import ValidateMeshOrder, OptionalPyblishPluginMixin
|
from ayon_core.pipeline.publish import (
|
||||||
|
ValidateMeshOrder,
|
||||||
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin,
|
class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin,
|
||||||
|
|
@ -20,6 +24,16 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin,
|
||||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||||
optional = True
|
optional = True
|
||||||
|
|
||||||
|
description = (
|
||||||
|
"## Meshes with Lamina Faces\n"
|
||||||
|
"Detected meshes with lamina faces. <b>Lamina faces</b> are faces "
|
||||||
|
"that share all of their edges and thus are merged together on top of "
|
||||||
|
"each other.\n\n"
|
||||||
|
"### How to repair?\n"
|
||||||
|
"You can repair them by using Maya's modeling tool `Mesh > Cleanup..` "
|
||||||
|
"and select to cleanup matching polygons for lamina faces."
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_invalid(instance):
|
def get_invalid(instance):
|
||||||
meshes = cmds.ls(instance, type='mesh', long=True)
|
meshes = cmds.ls(instance, type='mesh', long=True)
|
||||||
|
|
@ -36,5 +50,6 @@ class ValidateMeshLaminaFaces(pyblish.api.InstancePlugin,
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
|
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Meshes found with lamina faces: "
|
raise PublishValidationError(
|
||||||
"{0}".format(invalid))
|
"Meshes found with lamina faces: {0}".format(invalid),
|
||||||
|
description=self.description)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.hosts.maya.api import lib
|
from ayon_core.hosts.maya.api import lib
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -27,6 +28,15 @@ class ValidateMeshNgons(pyblish.api.Validator,
|
||||||
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
actions = [ayon_core.hosts.maya.api.action.SelectInvalidAction]
|
||||||
optional = True
|
optional = True
|
||||||
|
|
||||||
|
description = (
|
||||||
|
"## Meshes with NGONs Faces\n"
|
||||||
|
"Detected meshes with NGON faces. **NGONS** are faces that "
|
||||||
|
"with more than four sides.\n\n"
|
||||||
|
"### How to repair?\n"
|
||||||
|
"You can repair them by usings Maya's modeling tool Mesh > Cleanup.. "
|
||||||
|
"and select to cleanup matching polygons for lamina faces."
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_invalid(instance):
|
def get_invalid(instance):
|
||||||
|
|
||||||
|
|
@ -49,5 +59,6 @@ class ValidateMeshNgons(pyblish.api.Validator,
|
||||||
|
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Meshes found with n-gons"
|
raise PublishValidationError(
|
||||||
"values: {0}".format(invalid))
|
"Meshes found with n-gons: {0}".format(invalid),
|
||||||
|
description=self.description)
|
||||||
|
|
|
||||||
|
|
@ -107,8 +107,9 @@ class ValidateMeshShaderConnections(pyblish.api.InstancePlugin,
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
|
|
||||||
if invalid:
|
if invalid:
|
||||||
raise PublishValidationError("Shapes found with invalid shader "
|
raise PublishValidationError(
|
||||||
"connections: {0}".format(invalid))
|
"Shapes found with invalid shader connections: "
|
||||||
|
"{0}".format(invalid))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_invalid(instance):
|
def get_invalid(instance):
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ from ayon_core.hosts.maya.api import lib
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
RepairAction,
|
RepairAction,
|
||||||
ValidateMeshOrder,
|
ValidateMeshOrder,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -66,7 +67,7 @@ class ValidateMeshSingleUVSet(pyblish.api.InstancePlugin,
|
||||||
if allowed:
|
if allowed:
|
||||||
self.log.warning(message)
|
self.log.warning(message)
|
||||||
else:
|
else:
|
||||||
raise ValueError(message)
|
raise PublishValidationError(message)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
RepairAction,
|
RepairAction,
|
||||||
ValidateMeshOrder,
|
ValidateMeshOrder,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,8 +56,8 @@ class ValidateMeshUVSetMap1(pyblish.api.InstancePlugin,
|
||||||
|
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Meshes found without 'map1' "
|
raise PublishValidationError(
|
||||||
"UV set: {0}".format(invalid))
|
"Meshes found without 'map1' UV set: {0}".format(invalid))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -49,11 +49,17 @@ class ValidateNoNamespace(pyblish.api.InstancePlugin,
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
|
|
||||||
if invalid:
|
if invalid:
|
||||||
|
invalid_namespaces = {get_namespace(node) for node in invalid}
|
||||||
raise PublishValidationError(
|
raise PublishValidationError(
|
||||||
"Namespaces found:\n\n{0}".format(
|
message="Namespaces found:\n\n{0}".format(
|
||||||
_as_report_list(sorted(invalid))
|
_as_report_list(sorted(invalid_namespaces))
|
||||||
),
|
),
|
||||||
title="Namespaces in model"
|
title="Namespaces in model",
|
||||||
|
description=(
|
||||||
|
"## Namespaces found in model\n"
|
||||||
|
"It is not allowed to publish a model that contains "
|
||||||
|
"namespaces."
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -5,10 +5,12 @@ import pyblish.api
|
||||||
import ayon_core.hosts.maya.api.action
|
import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
class ValidateNodeNoGhosting(pyblish.api.InstancePlugin.
|
|
||||||
|
|
||||||
|
class ValidateNodeNoGhosting(pyblish.api.InstancePlugin,
|
||||||
OptionalPyblishPluginMixin):
|
OptionalPyblishPluginMixin):
|
||||||
"""Ensure nodes do not have ghosting enabled.
|
"""Ensure nodes do not have ghosting enabled.
|
||||||
|
|
||||||
|
|
@ -55,5 +57,5 @@ class ValidateNodeNoGhosting(pyblish.api.InstancePlugin.
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
|
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Nodes with ghosting enabled found: "
|
raise PublishValidationError(
|
||||||
"{0}".format(invalid))
|
"Nodes with ghosting enabled found: {0}".format(invalid))
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from ayon_core.hosts.maya.api import lib
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
RepairAction,
|
RepairAction,
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
PublishValidationError,
|
PublishXmlValidationError,
|
||||||
OptionalPyblishPluginMixin,
|
OptionalPyblishPluginMixin,
|
||||||
get_plugin_settings,
|
get_plugin_settings,
|
||||||
apply_plugin_settings_automatically
|
apply_plugin_settings_automatically
|
||||||
|
|
@ -58,8 +58,20 @@ class ValidateRigOutSetNodeIds(pyblish.api.InstancePlugin,
|
||||||
# if a deformer has been created on the shape
|
# if a deformer has been created on the shape
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise PublishValidationError(
|
|
||||||
"Nodes found with mismatching IDs: {0}".format(invalid)
|
# Use the short names
|
||||||
|
invalid = cmds.ls(invalid)
|
||||||
|
invalid.sort()
|
||||||
|
|
||||||
|
# Construct a human-readable list
|
||||||
|
invalid = "\n".join("- {}".format(node) for node in invalid)
|
||||||
|
|
||||||
|
raise PublishXmlValidationError(
|
||||||
|
plugin=ValidateRigOutSetNodeIds,
|
||||||
|
message=(
|
||||||
|
"Rig nodes have different IDs than their input "
|
||||||
|
"history: \n{0}".format(invalid)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
from ayon_core.pipeline.publish import ValidateContentsOrder
|
from ayon_core.pipeline.publish import (
|
||||||
|
ValidateContentsOrder,
|
||||||
|
PublishValidationError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateSetdressRoot(pyblish.api.InstancePlugin):
|
class ValidateSetdressRoot(pyblish.api.InstancePlugin):
|
||||||
|
|
@ -20,4 +23,6 @@ class ValidateSetdressRoot(pyblish.api.InstancePlugin):
|
||||||
root = cmds.ls(set_member, assemblies=True, long=True)
|
root = cmds.ls(set_member, assemblies=True, long=True)
|
||||||
|
|
||||||
if not root or root[0] not in set_member:
|
if not root or root[0] not in set_member:
|
||||||
raise Exception("Setdress top root node is not being published.")
|
raise PublishValidationError(
|
||||||
|
"Setdress top root node is not being published."
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,8 @@ import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
RepairAction,
|
RepairAction,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -84,8 +85,8 @@ class ValidateShapeDefaultNames(pyblish.api.InstancePlugin,
|
||||||
|
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Incorrectly named shapes "
|
raise PublishValidationError(
|
||||||
"found: {0}".format(invalid))
|
"Incorrectly named shapes found: {0}".format(invalid))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
from ayon_core.pipeline.publish import ValidateContentsOrder
|
from ayon_core.pipeline.publish import (
|
||||||
|
ValidateContentsOrder,
|
||||||
|
PublishValidationError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateSingleAssembly(pyblish.api.InstancePlugin):
|
class ValidateSingleAssembly(pyblish.api.InstancePlugin):
|
||||||
|
|
@ -30,7 +33,11 @@ class ValidateSingleAssembly(pyblish.api.InstancePlugin):
|
||||||
# ensure unique (somehow `maya.cmds.ls` doesn't manage that)
|
# ensure unique (somehow `maya.cmds.ls` doesn't manage that)
|
||||||
assemblies = set(assemblies)
|
assemblies = set(assemblies)
|
||||||
|
|
||||||
assert len(assemblies) > 0, (
|
if len(assemblies) == 0:
|
||||||
"One assembly required for: %s (currently empty?)" % instance)
|
raise PublishValidationError(
|
||||||
assert len(assemblies) < 2, (
|
"One assembly required for: %s (currently empty?)" % instance
|
||||||
'Multiple assemblies found: %s' % assemblies)
|
)
|
||||||
|
elif len(assemblies) > 1:
|
||||||
|
raise PublishValidationError(
|
||||||
|
'Multiple assemblies found: %s' % assemblies
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,11 @@ from maya import cmds
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
import ayon_core.hosts.maya.api.action
|
import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.pipeline.publish import ValidateContentsOrder,OptionalPyblishPluginMixin
|
from ayon_core.pipeline.publish import (
|
||||||
|
ValidateContentsOrder,
|
||||||
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin,
|
class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin,
|
||||||
|
|
@ -30,8 +34,10 @@ class ValidateSkinclusterDeformerSet(pyblish.api.InstancePlugin,
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
|
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Invalid skinCluster relationships "
|
raise PublishValidationError(
|
||||||
"found on meshes: {0}".format(invalid))
|
"Invalid skinCluster relationships found on meshes: {0}"
|
||||||
|
.format(invalid)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invalid(cls, instance):
|
def get_invalid(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class ValidateStepSize(pyblish.api.InstancePlugin,
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invalid(cls, instance):
|
def get_invalid(cls, instance):
|
||||||
|
|
||||||
objset = instance.data['name']
|
objset = instance.data['instance_node']
|
||||||
step = instance.data.get("step", 1.0)
|
step = instance.data.get("step", 1.0)
|
||||||
|
|
||||||
if step < cls.MIN or step > cls.MAX:
|
if step < cls.MIN or step > cls.MAX:
|
||||||
|
|
@ -47,4 +47,4 @@ class ValidateStepSize(pyblish.api.InstancePlugin,
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise PublishValidationError(
|
raise PublishValidationError(
|
||||||
"Invalid instances found: {0}".format(invalid))
|
"Instance found with invalid step size: {0}".format(invalid))
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,11 @@ import pyblish.api
|
||||||
import ayon_core.hosts.maya.api.action
|
import ayon_core.hosts.maya.api.action
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateUniqueNames(pyblish.api.Validator,
|
class ValidateUniqueNames(pyblish.api.Validator,
|
||||||
OptionalPyblishPluginMixin):
|
OptionalPyblishPluginMixin):
|
||||||
"""transform names should be unique
|
"""transform names should be unique
|
||||||
|
|
@ -40,5 +42,5 @@ class ValidateUniqueNames(pyblish.api.Validator,
|
||||||
return
|
return
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Nodes found with none unique names. "
|
raise PublishValidationError(
|
||||||
"values: {0}".format(invalid))
|
"Nodes found with non-unique names:\n{0}".format(invalid))
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ import pyblish.api
|
||||||
|
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateMeshOrder,
|
ValidateMeshOrder,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
import ayon_core.hosts.maya.api.action
|
import ayon_core.hosts.maya.api.action
|
||||||
|
|
||||||
|
|
@ -26,8 +27,8 @@ class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin,
|
||||||
invalid = []
|
invalid = []
|
||||||
meshes = cmds.ls(instance, type="mesh", long=True)
|
meshes = cmds.ls(instance, type="mesh", long=True)
|
||||||
for mesh in meshes:
|
for mesh in meshes:
|
||||||
faces = cmds.polyEvaluate(mesh, f=True)
|
faces = cmds.polyEvaluate(mesh, face=True)
|
||||||
tris = cmds.polyEvaluate(mesh, t=True)
|
tris = cmds.polyEvaluate(mesh, triangle=True)
|
||||||
if faces != tris:
|
if faces != tris:
|
||||||
invalid.append(mesh)
|
invalid.append(mesh)
|
||||||
|
|
||||||
|
|
@ -37,5 +38,5 @@ class ValidateUnrealMeshTriangulated(pyblish.api.InstancePlugin,
|
||||||
if not self.is_active(instance.data):
|
if not self.is_active(instance.data):
|
||||||
return
|
return
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
assert len(invalid) == 0, (
|
if invalid:
|
||||||
"Found meshes without triangles")
|
raise PublishValidationError("Found meshes without triangles")
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ import pyblish.api
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
RepairAction,
|
RepairAction,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,9 +27,10 @@ class ValidateUnrealUpAxis(pyblish.api.ContextPlugin,
|
||||||
if not self.is_active(context.data):
|
if not self.is_active(context.data):
|
||||||
return
|
return
|
||||||
|
|
||||||
assert cmds.upAxis(q=True, axis=True) == "z", (
|
if cmds.upAxis(q=True, axis=True) != "z":
|
||||||
"Invalid axis set as up axis"
|
raise PublishValidationError(
|
||||||
)
|
"Invalid axis set as up axis"
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,9 @@ class ValidateAlembicVisibleOnly(pyblish.api.InstancePlugin,
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
start, end = self.get_frame_range(instance)
|
start, end = self.get_frame_range(instance)
|
||||||
raise PublishValidationError("No visible nodes found in "
|
raise PublishValidationError(
|
||||||
"frame range {}-{}.".format(start, end))
|
f"No visible nodes found in frame range {start}-{end}."
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invalid(cls, instance):
|
def get_invalid(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ from maya import cmds
|
||||||
|
|
||||||
from ayon_core.hosts.maya.api import lib
|
from ayon_core.hosts.maya.api import lib
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
|
KnownPublishError,
|
||||||
PublishValidationError,
|
PublishValidationError,
|
||||||
RepairAction,
|
RepairAction,
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
|
|
@ -35,11 +36,14 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin,
|
||||||
if not self.is_active(instance.data):
|
if not self.is_active(instance.data):
|
||||||
return
|
return
|
||||||
if instance.data.get("renderer") != "vray":
|
if instance.data.get("renderer") != "vray":
|
||||||
# If not V-Ray ignore..
|
# If not V-Ray, ignore
|
||||||
return
|
return
|
||||||
|
|
||||||
vray_settings = cmds.ls("vraySettings", type="VRaySettingsNode")
|
vray_settings = cmds.ls("vraySettings", type="VRaySettingsNode")
|
||||||
assert vray_settings, "Please ensure a VRay Settings Node is present"
|
if not vray_settings:
|
||||||
|
raise KnownPublishError(
|
||||||
|
"Please ensure a VRay Settings Node is present"
|
||||||
|
)
|
||||||
|
|
||||||
renderlayer = instance.data['renderlayer']
|
renderlayer = instance.data['renderlayer']
|
||||||
|
|
||||||
|
|
@ -51,8 +55,8 @@ class ValidateVRayDistributedRendering(pyblish.api.InstancePlugin,
|
||||||
# during batch mode we invalidate the instance
|
# during batch mode we invalidate the instance
|
||||||
if not lib.get_attr_in_layer(self.ignored_attr, layer=renderlayer):
|
if not lib.get_attr_in_layer(self.ignored_attr, layer=renderlayer):
|
||||||
raise PublishValidationError(
|
raise PublishValidationError(
|
||||||
("Renderlayer has distributed rendering enabled "
|
"Renderlayer has distributed rendering enabled "
|
||||||
"but is not set to ignore in batch mode."))
|
"but is not set to ignore in batch mode.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, instance):
|
def repair(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,11 @@ from maya import cmds
|
||||||
|
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
RepairContextAction,
|
RepairContextAction,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin,
|
class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin,
|
||||||
OptionalPyblishPluginMixin):
|
OptionalPyblishPluginMixin):
|
||||||
"""Validate whether the V-Ray Render Elements (AOVs) include references.
|
"""Validate whether the V-Ray Render Elements (AOVs) include references.
|
||||||
|
|
@ -60,7 +62,7 @@ class ValidateVrayReferencedAOVs(pyblish.api.InstancePlugin,
|
||||||
self.log.error((
|
self.log.error((
|
||||||
"'Use referenced' not enabled in Vray Render Settings."
|
"'Use referenced' not enabled in Vray Render Settings."
|
||||||
))
|
))
|
||||||
raise AssertionError("Invalid render settings")
|
raise PublishValidationError("Invalid render settings")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def repair(cls, context):
|
def repair(cls, context):
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
|
|
||||||
from ayon_core.pipeline import KnownPublishError
|
from ayon_core.pipeline.publish import (
|
||||||
from ayon_core.pipeline.publish import OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin,
|
||||||
|
PublishValidationError
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateVrayProxy(pyblish.api.InstancePlugin,
|
class ValidateVrayProxy(pyblish.api.InstancePlugin,
|
||||||
OptionalPyblishPluginMixin):
|
OptionalPyblishPluginMixin):
|
||||||
|
|
@ -17,18 +20,18 @@ class ValidateVrayProxy(pyblish.api.InstancePlugin,
|
||||||
if not self.is_active(data):
|
if not self.is_active(data):
|
||||||
return
|
return
|
||||||
if not data["setMembers"]:
|
if not data["setMembers"]:
|
||||||
raise KnownPublishError(
|
raise PublishValidationError(
|
||||||
"'%s' is empty! This is a bug" % instance.name
|
f"Instance '{instance.name}' is empty."
|
||||||
)
|
)
|
||||||
|
|
||||||
if data["animation"]:
|
if data["animation"]:
|
||||||
if data["frameEnd"] < data["frameStart"]:
|
if data["frameEnd"] < data["frameStart"]:
|
||||||
raise KnownPublishError(
|
raise PublishValidationError(
|
||||||
"End frame is smaller than start frame"
|
"End frame is smaller than start frame"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not data["vrmesh"] and not data["alembic"]:
|
if not data["vrmesh"] and not data["alembic"]:
|
||||||
raise KnownPublishError(
|
raise PublishValidationError(
|
||||||
"Both vrmesh and alembic are off. Needs at least one to"
|
"Both vrmesh and alembic are off. Needs at least one to"
|
||||||
" publish."
|
" publish."
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class ValidateXgen(pyblish.api.InstancePlugin):
|
||||||
" Node type found: {}".format(node_type)
|
" Node type found: {}".format(node_type)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Cant have inactive modifiers in collection cause Xgen will try and
|
# Can't have inactive modifiers in collection cause Xgen will try and
|
||||||
# look for them when loading.
|
# look for them when loading.
|
||||||
palette = instance.data["xgmPalette"].replace("|", "")
|
palette = instance.data["xgmPalette"].replace("|", "")
|
||||||
inactive_modifiers = {}
|
inactive_modifiers = {}
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ from maya import cmds
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
from ayon_core.pipeline.publish import (
|
from ayon_core.pipeline.publish import (
|
||||||
ValidateContentsOrder,
|
ValidateContentsOrder,
|
||||||
|
PublishValidationError,
|
||||||
OptionalPyblishPluginMixin
|
OptionalPyblishPluginMixin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin,
|
class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin,
|
||||||
OptionalPyblishPluginMixin):
|
OptionalPyblishPluginMixin):
|
||||||
"""Check if the render script callbacks will be used during the rendering
|
"""Check if the render script callbacks will be used during the rendering
|
||||||
|
|
@ -45,8 +47,8 @@ class ValidateYetiRenderScriptCallbacks(pyblish.api.InstancePlugin,
|
||||||
return
|
return
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise ValueError("Invalid render callbacks found for '%s'!"
|
raise PublishValidationError(
|
||||||
% instance.name)
|
f"Invalid render callbacks found for '{instance.name}'.")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invalid(cls, instance):
|
def get_invalid(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import inspect
|
||||||
|
|
||||||
import pyblish.api
|
import pyblish.api
|
||||||
import maya.cmds as cmds
|
import maya.cmds as cmds
|
||||||
import ayon_core.hosts.maya.api.action
|
import ayon_core.hosts.maya.api.action
|
||||||
|
|
@ -8,7 +10,6 @@ from ayon_core.pipeline.publish import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ValidateYetiRigCacheState(pyblish.api.InstancePlugin,
|
class ValidateYetiRigCacheState(pyblish.api.InstancePlugin,
|
||||||
OptionalPyblishPluginMixin):
|
OptionalPyblishPluginMixin):
|
||||||
"""Validate the I/O attributes of the node
|
"""Validate the I/O attributes of the node
|
||||||
|
|
@ -32,7 +33,10 @@ class ValidateYetiRigCacheState(pyblish.api.InstancePlugin,
|
||||||
return
|
return
|
||||||
invalid = self.get_invalid(instance)
|
invalid = self.get_invalid(instance)
|
||||||
if invalid:
|
if invalid:
|
||||||
raise PublishValidationError("Nodes have incorrect I/O settings")
|
raise PublishValidationError(
|
||||||
|
"Nodes have incorrect I/O settings",
|
||||||
|
description=inspect.getdoc(self)
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invalid(cls, instance):
|
def get_invalid(cls, instance):
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,8 @@ class AssetModel(models.TreeModel):
|
||||||
self.beginResetModel()
|
self.beginResetModel()
|
||||||
|
|
||||||
# Add the items sorted by label
|
# Add the items sorted by label
|
||||||
sorter = lambda x: x["label"]
|
def sorter(x):
|
||||||
|
return x["label"]
|
||||||
|
|
||||||
for item in sorted(items, key=sorter):
|
for item in sorted(items, key=sorter):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -389,7 +389,13 @@ def imprint(node, data, tab=None):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
for knob in create_knobs(data, tab):
|
for knob in create_knobs(data, tab):
|
||||||
node.addKnob(knob)
|
# If knob name exists we set the value. Technically there could be
|
||||||
|
# multiple knobs with the same name, but the intent is not to have
|
||||||
|
# duplicated knobs so we do not account for that.
|
||||||
|
if knob.name() in node.knobs().keys():
|
||||||
|
node[knob.name()].setValue(knob.value())
|
||||||
|
else:
|
||||||
|
node.addKnob(knob)
|
||||||
|
|
||||||
|
|
||||||
@deprecated
|
@deprecated
|
||||||
|
|
@ -814,7 +820,7 @@ def on_script_load():
|
||||||
|
|
||||||
def check_inventory_versions():
|
def check_inventory_versions():
|
||||||
"""
|
"""
|
||||||
Actual version idetifier of Loaded containers
|
Actual version identifier of Loaded containers
|
||||||
|
|
||||||
Any time this function is run it will check all nodes and filter only
|
Any time this function is run it will check all nodes and filter only
|
||||||
Loader nodes for its version. It will get all versions from database
|
Loader nodes for its version. It will get all versions from database
|
||||||
|
|
@ -921,7 +927,7 @@ def writes_version_sync():
|
||||||
|
|
||||||
for each in nuke.allNodes(filter="Write"):
|
for each in nuke.allNodes(filter="Write"):
|
||||||
# check if the node is avalon tracked
|
# check if the node is avalon tracked
|
||||||
if _NODE_TAB_NAME not in each.knobs():
|
if NODE_TAB_NAME not in each.knobs():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
avalon_knob_data = read_avalon_data(each)
|
avalon_knob_data = read_avalon_data(each)
|
||||||
|
|
@ -2381,7 +2387,7 @@ def launch_workfiles_app():
|
||||||
|
|
||||||
Context.workfiles_launched = True
|
Context.workfiles_launched = True
|
||||||
|
|
||||||
# get all imortant settings
|
# get all important settings
|
||||||
open_at_start = env_value_to_bool(
|
open_at_start = env_value_to_bool(
|
||||||
env_key="AYON_WORKFILE_TOOL_ON_START",
|
env_key="AYON_WORKFILE_TOOL_ON_START",
|
||||||
default=None)
|
default=None)
|
||||||
|
|
|
||||||
|
|
@ -30,13 +30,11 @@ from ayon_core.tools.utils import host_tools
|
||||||
from ayon_core.hosts.nuke import NUKE_ROOT_DIR
|
from ayon_core.hosts.nuke import NUKE_ROOT_DIR
|
||||||
from ayon_core.tools.workfile_template_build import open_template_ui
|
from ayon_core.tools.workfile_template_build import open_template_ui
|
||||||
|
|
||||||
from .command import viewer_update_and_undo_stop
|
|
||||||
from .lib import (
|
from .lib import (
|
||||||
Context,
|
Context,
|
||||||
ROOT_DATA_KNOB,
|
ROOT_DATA_KNOB,
|
||||||
INSTANCE_DATA_KNOB,
|
INSTANCE_DATA_KNOB,
|
||||||
get_main_window,
|
get_main_window,
|
||||||
add_publish_knob,
|
|
||||||
WorkfileSettings,
|
WorkfileSettings,
|
||||||
# TODO: remove this once workfile builder will be removed
|
# TODO: remove this once workfile builder will be removed
|
||||||
process_workfile_builder,
|
process_workfile_builder,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue