Merge branch 'develop' into enhancement/OP-7074_Validate-Render-Passes

This commit is contained in:
moonyuet 2024-03-18 10:28:29 +00:00
commit 5c8a39e266
27 changed files with 392 additions and 134 deletions

View file

@ -13,6 +13,9 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
families = ["model", "camera", "rig", "action", "layout", "blendScene"]
optional = True
# From settings
compress = False
def process(self, instance):
if not self.is_active(instance.data):
return
@ -53,7 +56,7 @@ class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
if node.image and node.image.packed_file is None:
node.image.pack()
bpy.data.libraries.write(filepath, data_blocks)
bpy.data.libraries.write(filepath, data_blocks, compress=self.compress)
if "representations" not in instance.data:
instance.data["representations"] = []

View file

@ -16,6 +16,9 @@ class ExtractBlendAnimation(
families = ["animation"]
optional = True
# From settings
compress = False
def process(self, instance):
if not self.is_active(instance.data):
return
@ -46,7 +49,7 @@ class ExtractBlendAnimation(
data_blocks.add(child.animation_data.action)
data_blocks.add(obj)
bpy.data.libraries.write(filepath, data_blocks)
bpy.data.libraries.write(filepath, data_blocks, compress=self.compress)
if "representations" not in instance.data:
instance.data["representations"] = []

View file

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
"""Validate if instance asset is the same as context asset."""
import pyblish.api
from ayon_core.hosts.houdini.api.action import SelectROPAction
from ayon_core.pipeline.publish import (
RepairAction,
ValidateContentsOrder,
PublishValidationError,
OptionalPyblishPluginMixin
)
class ValidateInstanceInContextHoudini(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validator to check if instance asset match context asset.
When working in per-shot style you always publish data in context of
current asset (shot). This validator checks if this is so. It is optional
so it can be disabled when needed.
"""
# Similar to maya-equivalent `ValidateInstanceInContext`
order = ValidateContentsOrder
label = "Instance in same Context"
optional = True
hosts = ["houdini"]
actions = [SelectROPAction, RepairAction]
def process(self, instance):
if not self.is_active(instance.data):
return
folder_path = instance.data.get("folderPath")
task = instance.data.get("task")
context = self.get_context(instance)
if (folder_path, task) != context:
context_label = "{} > {}".format(*context)
instance_label = "{} > {}".format(folder_path, task)
raise PublishValidationError(
message=(
"Instance '{}' publishes to different asset than current "
"context: {}. Current context: {}".format(
instance.name, instance_label, context_label
)
),
description=(
"## Publishing to a different asset\n"
"There are publish instances present which are publishing "
"into a different asset than your current context.\n\n"
"Usually this is not what you want but there can be cases "
"where you might want to publish into another asset or "
"shot. If that's the case you can disable the validation "
"on the instance to ignore it."
)
)
@classmethod
def repair(cls, instance):
context_folder, context_task = cls.get_context(instance)
create_context = instance.context.data["create_context"]
instance_id = instance.data["instance_id"]
created_instance = create_context.get_instance_by_id(
instance_id
)
created_instance["folderPath"] = context_folder
created_instance["task"] = context_task
create_context.save_changes()
@staticmethod
def get_context(instance):
"""Return folderPath, task from publishing context data"""
context = instance.context
return context.data["folderPath"], context.data["task"]

View file

@ -31,23 +31,26 @@ def viewport_layout_and_camera(camera, layout="layout_1"):
layout (str): layout to use in viewport, defaults to `layout_1`
Use None to not change viewport layout during context.
"""
needs_maximise = 0
# Set to first active non extended viewport
rt.viewport.activeViewportEx(1)
original_camera = rt.viewport.getCamera()
original_layout = rt.viewport.getLayout()
if not original_camera:
# if there is no original camera
# use the current camera as original
original_camera = rt.getNodeByName(camera)
original_type = rt.viewport.getType()
review_camera = rt.getNodeByName(camera)
try:
if layout is not None:
layout = rt.Name(layout)
if rt.viewport.getLayout() != layout:
rt.viewport.setLayout(layout)
if rt.viewport.getLayout() != rt.name(layout):
rt.execute("max tool maximize")
needs_maximise = 1
rt.viewport.setCamera(review_camera)
yield
finally:
rt.viewport.setLayout(original_layout)
rt.viewport.setCamera(original_camera)
if needs_maximise == 1:
rt.execute("max tool maximize")
if original_type == rt.Name("view_camera"):
rt.viewport.setCamera(original_camera)
else:
rt.viewport.setType(original_type)
@contextlib.contextmanager

View file

@ -1,10 +1,12 @@
import os
from qtpy import QtWidgets, QtCore
from ayon_core.lib.attribute_definitions import EnumDef
from ayon_core.hosts.max.api import lib
from ayon_core.hosts.max.api.lib import (
unique_namespace,
get_namespace,
object_transform_set
object_transform_set,
is_headless
)
from ayon_core.hosts.max.api.pipeline import (
containerise, get_previous_loaded_object,
@ -14,6 +16,59 @@ from ayon_core.hosts.max.api.pipeline import (
from ayon_core.pipeline import get_representation_path, load
class MaterialDupOptionsWindow(QtWidgets.QDialog):
"""The pop-up dialog allows users to choose material
duplicate options for importing Max objects when updating
or switching assets.
"""
def __init__(self, material_options):
super(MaterialDupOptionsWindow, self).__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint)
self.material_option = None
self.material_options = material_options
self.widgets = {
"label": QtWidgets.QLabel(
"Select material duplicate options before loading the max scene."),
"material_options_list": QtWidgets.QListWidget(),
"warning": QtWidgets.QLabel("No material options selected!"),
"buttons": QtWidgets.QWidget(),
"okButton": QtWidgets.QPushButton("Ok"),
"cancelButton": QtWidgets.QPushButton("Cancel")
}
for key, value in material_options.items():
item = QtWidgets.QListWidgetItem(value)
self.widgets["material_options_list"].addItem(item)
item.setData(QtCore.Qt.UserRole, key)
# Build buttons.
layout = QtWidgets.QHBoxLayout(self.widgets["buttons"])
layout.addWidget(self.widgets["okButton"])
layout.addWidget(self.widgets["cancelButton"])
# Build layout.
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.widgets["label"])
layout.addWidget(self.widgets["material_options_list"])
layout.addWidget(self.widgets["buttons"])
self.widgets["okButton"].pressed.connect(self.on_ok_pressed)
self.widgets["cancelButton"].pressed.connect(self.on_cancel_pressed)
self.widgets["material_options_list"].itemPressed.connect(
self.on_material_options_pressed)
def on_material_options_pressed(self, item):
self.material_option = item.data(QtCore.Qt.UserRole)
def on_ok_pressed(self):
if self.material_option is None:
self.widgets["warning"].setVisible(True)
return
self.close()
def on_cancel_pressed(self):
self.material_option = "promptMtlDups"
self.close()
class MaxSceneLoader(load.LoaderPlugin):
"""Max Scene Loader."""
@ -25,14 +80,31 @@ class MaxSceneLoader(load.LoaderPlugin):
order = -8
icon = "code-fork"
color = "green"
mtl_dup_default = "promptMtlDups"
mtl_dup_enum_dict = {
"promptMtlDups": "Prompt on Duplicate Materials",
"useMergedMtlDups": "Use Incoming Material",
"useSceneMtlDups": "Use Scene Material",
"renameMtlDups": "Merge and Rename Incoming Material"
}
@classmethod
def get_options(cls, contexts):
return [
EnumDef("mtldup",
items=cls.mtl_dup_enum_dict,
default=cls.mtl_dup_default,
label="Material Duplicate Options")
]
def load(self, context, name=None, namespace=None, data=None):
def load(self, context, name=None, namespace=None, options=None):
from pymxs import runtime as rt
mat_dup_options = options.get("mtldup", self.mtl_dup_default)
path = self.filepath_from_context(context)
path = os.path.normpath(path)
# import the max scene by using "merge file"
path = path.replace('\\', '/')
rt.MergeMaxFile(path, quiet=True, includeFullGroup=True)
rt.MergeMaxFile(path, rt.Name(mat_dup_options),
quiet=True, includeFullGroup=True)
max_objects = rt.getLastMergedNodes()
max_object_names = [obj.name for obj in max_objects]
# implement the OP/AYON custom attributes before load
@ -67,7 +139,12 @@ class MaxSceneLoader(load.LoaderPlugin):
for prev_max_obj in prev_max_objects:
if rt.isValidNode(prev_max_obj): # noqa
rt.Delete(prev_max_obj)
rt.MergeMaxFile(path, quiet=True)
material_option = self.mtl_dup_default
if not is_headless():
window = MaterialDupOptionsWindow(self.mtl_dup_enum_dict)
window.exec_()
material_option = window.material_option
rt.MergeMaxFile(path, rt.Name(material_option), quiet=True)
current_max_objects = rt.getLastMergedNodes()

View file

@ -9,7 +9,7 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin):
order = pyblish.api.IntegratorOrder + 0.9
label = "Increment Workfile Version"
hosts = ["max"]
families = ["workfile"]
families = ["maxrender", "workfile"]
def process(self, context):
path = context.data["currentFile"]

View file

@ -2,7 +2,7 @@ import pyblish.api
from ayon_core.pipeline import registered_host
class SaveCurrentScene(pyblish.api.ContextPlugin):
class SaveCurrentScene(pyblish.api.InstancePlugin):
"""Save current scene"""
label = "Save current file"
@ -10,13 +10,15 @@ class SaveCurrentScene(pyblish.api.ContextPlugin):
hosts = ["max"]
families = ["maxrender", "workfile"]
def process(self, context):
def process(self, instance):
host = registered_host()
current_file = host.get_current_workfile()
assert context.data["currentFile"] == current_file
assert instance.context.data["currentFile"] == current_file
if instance.data["productType"] == "maxrender":
host.save_workfile(current_file)
if host.workfile_has_unsaved_changes():
elif host.workfile_has_unsaved_changes():
self.log.info(f"Saving current file: {current_file}")
host.save_workfile(current_file)
else:

View file

@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
import pyblish.api
from ayon_core.pipeline import PublishValidationError
from pymxs import runtime as rt
class ValidateExtendedViewport(pyblish.api.ContextPlugin):
"""Validate if the first viewport is an extended viewport."""
order = pyblish.api.ValidatorOrder
families = ["review"]
hosts = ["max"]
label = "Validate Extended Viewport"
def process(self, context):
try:
rt.viewport.activeViewportEx(1)
except RuntimeError:
raise PublishValidationError(
"Please make sure one viewport is not an extended viewport",
description = (
"Please make sure at least one viewport is not an "
"extended viewport but a 3dsmax supported viewport "
"i.e camera/persp/orthographic view.\n\n"
"To rectify it, please go to view in the top menubar, "
"go to Views -> Viewports Configuration -> Layout and "
"right click on one of the panels to change it."
))

View file

@ -103,8 +103,8 @@ class ValidateModelName(pyblish.api.InstancePlugin,
invalid = False
compare = {
"project": instance.context.data["projectName"],
"asset": instance.context.data["folderPath"],
"subset": instance.context.data["subset"],
"asset": instance.data["folderPath"],
"subset": instance.data["productName"]
}
for key, required_value in compare.items():
if key in regex.groupindex:

View file

@ -172,7 +172,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin):
"""Collects inputs from nodes in renderlayer, incl. shaders + camera"""
# Get the renderlayer
renderlayer = instance.data.get("renderlayer")
renderlayer = instance.data.get("setMembers")
if renderlayer == "defaultRenderLayer":
# Assume all loaded containers in the scene are inputs

View file

@ -1,4 +1,6 @@
import os
import re
import nuke
from ayon_core import resources
@ -103,9 +105,8 @@ def colorspace_exists_on_node(node, colorspace_name):
except ValueError:
# knob is not available on input node
return False
all_clrs = get_colorspace_list(colorspace_knob)
return colorspace_name in all_clrs
return colorspace_name in get_colorspace_list(colorspace_knob)
def get_colorspace_list(colorspace_knob):
@ -117,19 +118,22 @@ def get_colorspace_list(colorspace_knob):
Returns:
list: list of strings names of profiles
"""
results = []
all_clrs = list(colorspace_knob.values())
reduced_clrs = []
# This pattern is to match with roles which uses an indentation and
# parentheses with original colorspace. The value returned from the
# colorspace is the string before the indentation, so we'll need to
# convert the values to match with value returned from the knob,
# ei. knob.value().
pattern = r".*\t.* \(.*\)"
for colorspace in nuke.getColorspaceList(colorspace_knob):
match = re.search(pattern, colorspace)
if match:
results.append(colorspace.split("\t", 1)[0])
else:
results.append(colorspace)
if not colorspace_knob.getFlag(nuke.STRIP_CASCADE_PREFIX):
return all_clrs
# strip colorspace with nested path
for clrs in all_clrs:
clrs = clrs.split('/')[-1]
reduced_clrs.append(clrs)
return reduced_clrs
return results
def is_headless():

View file

@ -11,6 +11,9 @@ from ayon_core.pipeline import (
get_current_project_name,
get_representation_path,
)
from ayon_core.pipeline.colorspace import (
get_imageio_file_rules_colorspace_from_filepath
)
from ayon_core.hosts.nuke.api.lib import (
get_imageio_input_colorspace,
maintained_selection
@ -101,7 +104,6 @@ class LoadClip(plugin.NukeLoader):
filepath = self.filepath_from_context(context)
filepath = filepath.replace("\\", "/")
self.log.debug("_ filepath: {}".format(filepath))
start_at_workfile = options.get(
"start_at_workfile", self.options_defaults["start_at_workfile"])
@ -154,8 +156,8 @@ class LoadClip(plugin.NukeLoader):
with viewer_update_and_undo_stop():
read_node["file"].setValue(filepath)
used_colorspace = self._set_colorspace(
read_node, version_data, representation["data"], filepath)
self.set_colorspace_to_node(
read_node, filepath, version, representation)
self._set_range_to_node(read_node, first, last, start_at_workfile)
@ -180,8 +182,6 @@ class LoadClip(plugin.NukeLoader):
colorspace = representation["data"].get(key)
colorspace = colorspace or version_data.get(key)
data_imprint["db_colorspace"] = colorspace
if used_colorspace:
data_imprint["used_colorspace"] = used_colorspace
else:
value_ = context["version"]['data'].get(
key, str(None))
@ -304,8 +304,9 @@ class LoadClip(plugin.NukeLoader):
# to avoid multiple undo steps for rest of process
# we will switch off undo-ing
with viewer_update_and_undo_stop():
used_colorspace = self._set_colorspace(
read_node, version_data, repre_doc["data"], filepath)
self.set_colorspace_to_node(
read_node, filepath, version_doc, repre_doc
)
self._set_range_to_node(read_node, first, last, start_at_workfile)
@ -322,10 +323,6 @@ class LoadClip(plugin.NukeLoader):
"author": version_data.get("author")
}
# add used colorspace if found any
if used_colorspace:
updated_dict["used_colorspace"] = used_colorspace
last_version_doc = get_last_version_by_subset_id(
project_name, version_doc["parent"], fields=["_id"]
)
@ -352,6 +349,36 @@ class LoadClip(plugin.NukeLoader):
self.set_as_member(read_node)
def set_colorspace_to_node(
self,
read_node,
filepath,
version_doc,
representation_doc,
):
"""Set colorspace to read node.
Sets colorspace with available names validation.
Args:
read_node (nuke.Node): The nuke's read node
filepath (str): file path
version_doc (dict): version document
representation_doc (dict): representation document
"""
used_colorspace = self._get_colorspace_data(
version_doc, representation_doc, filepath)
if (
used_colorspace
and colorspace_exists_on_node(read_node, used_colorspace)
):
self.log.info(f"Used colorspace: {used_colorspace}")
read_node["colorspace"].setValue(used_colorspace)
else:
self.log.info("Colorspace not set...")
def remove(self, container):
read_node = container["node"]
assert read_node.Class() == "Read", "Must be Read"
@ -452,25 +479,47 @@ class LoadClip(plugin.NukeLoader):
return self.node_name_template.format(**name_data)
def _set_colorspace(self, node, version_data, repre_data, path):
output_color = None
path = path.replace("\\", "/")
# get colorspace
colorspace = repre_data.get("colorspace")
colorspace = colorspace or version_data.get("colorspace")
def _get_colorspace_data(self, version_doc, representation_doc, filepath):
"""Get colorspace data from version and representation documents
# colorspace from `project_settings/nuke/imageio/regex_inputs`
iio_colorspace = get_imageio_input_colorspace(path)
Args:
version_doc (dict): version document
representation_doc (dict): representation document
filepath (str): file path
# Set colorspace defined in version data
if (
colorspace is not None
and colorspace_exists_on_node(node, str(colorspace))
):
node["colorspace"].setValue(str(colorspace))
output_color = str(colorspace)
elif iio_colorspace is not None:
node["colorspace"].setValue(iio_colorspace)
output_color = iio_colorspace
Returns:
Any[str,None]: colorspace name or None
"""
# Get backward compatible colorspace key.
colorspace = representation_doc["data"].get("colorspace")
self.log.debug(
f"Colorspace from representation colorspace: {colorspace}"
)
return output_color
# Get backward compatible version data key if colorspace is not found.
colorspace = colorspace or version_doc["data"].get("colorspace")
self.log.debug(f"Colorspace from version colorspace: {colorspace}")
# Get colorspace from representation colorspaceData if colorspace is
# not found.
colorspace_data = representation_doc["data"].get("colorspaceData", {})
colorspace = colorspace or colorspace_data.get("colorspace")
self.log.debug(
f"Colorspace from representation colorspaceData: {colorspace}"
)
# check if any filerules are not applicable
new_parsed_colorspace = get_imageio_file_rules_colorspace_from_filepath( # noqa
filepath, "nuke", get_current_project_name()
)
self.log.debug(f"Colorspace new filerules: {new_parsed_colorspace}")
# colorspace from `project_settings/nuke/imageio/regexInputs`
old_parsed_colorspace = get_imageio_input_colorspace(filepath)
self.log.debug(f"Colorspace old filerules: {old_parsed_colorspace}")
return (
new_parsed_colorspace
or old_parsed_colorspace
or colorspace
)

View file

@ -17,7 +17,8 @@ class CollectSlate(pyblish.api.InstancePlugin):
(
n_ for n_ in nuke.allNodes()
if "slate" in n_.name().lower()
if not n_["disable"].getValue()
if not n_["disable"].getValue() and
"publish_instance" not in n_.knobs() # Exclude instance nodes.
),
None
)

View file

@ -194,7 +194,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
"frameEndHandle": last_frame,
})
# TODO temporarily set stagingDir as persistent for backward
# compatibility. This is mainly focused on `renders`folders which
# were previously not cleaned up (and could be used in read notes)
@ -269,10 +268,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin,
"tags": []
}
frame_start_str = self._get_frame_start_str(first_frame, last_frame)
representation['frameStart'] = frame_start_str
# set slate frame
collected_frames = self._add_slate_frame_to_collected_frames(
instance,

@ -1 +1 @@
Subproject commit 6d2793170ed57187842f683a943593973abcc337
Subproject commit 04b35dbf5fc42d905281fc30d3a22b139c1855e5

View file

@ -161,12 +161,6 @@ from .ayon_info import (
is_in_tests,
)
from .connections import (
requests_get,
requests_post
)
terminal = Terminal
__all__ = [
@ -283,7 +277,4 @@ __all__ = [
"is_staging_enabled",
"is_dev_mode_enabled",
"is_in_tests",
"requests_get",
"requests_post"
]

View file

@ -1,38 +0,0 @@
import requests
import os
def requests_post(*args, **kwargs):
"""Wrap request post method.
Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment
variable is found. This is useful when Deadline server is
running with self-signed certificates and its certificate is not
added to trusted certificates on client machines.
Warning:
Disabling SSL certificate validation is defeating one line
of defense SSL is providing, and it is not recommended.
"""
if "verify" not in kwargs:
kwargs["verify"] = not os.getenv("OPENPYPE_DONT_VERIFY_SSL", True)
return requests.post(*args, **kwargs)
def requests_get(*args, **kwargs):
"""Wrap request get method.
Disabling SSL certificate validation if ``DONT_VERIFY_SSL`` environment
variable is found. This is useful when Deadline server is
running with self-signed certificates and its certificate is not
added to trusted certificates on client machines.
Warning:
Disabling SSL certificate validation is defeating one line
of defense SSL is providing, and it is not recommended.
"""
if "verify" not in kwargs:
kwargs["verify"] = not os.getenv("OPENPYPE_DONT_VERIFY_SSL", True)
return requests.get(*args, **kwargs)

View file

@ -29,6 +29,10 @@ from ayon_core.pipeline.publish.lib import (
JSONDecodeError = getattr(json.decoder, "JSONDecodeError", ValueError)
# TODO both 'requests_post' and 'requests_get' should not set 'verify' based
# on environment variable. This should be done in a more controlled way,
# e.g. each deadline url could have checkbox to enabled/disable
# ssl verification.
def requests_post(*args, **kwargs):
"""Wrap request post method.

View file

@ -1,9 +1,10 @@
import os
import requests
import six
import sys
from ayon_core.lib import requests_get, Logger
import requests
import six
from ayon_core.lib import Logger
from ayon_core.modules import AYONAddon, IPluginPaths
@ -56,6 +57,8 @@ class DeadlineModule(AYONAddon, IPluginPaths):
RuntimeError: If deadline webservice is unreachable.
"""
from .abstract_submit_deadline import requests_get
if not log:
log = Logger.get_logger(__name__)

View file

@ -1067,6 +1067,38 @@ PixmapButton:disabled {
font-size: 13pt;
}
#PublisherVerticalScrollArea QScrollBar {
background: transparent;
margin: 0;
border: none;
}
#PublisherVerticalScrollArea QScrollBar:horizontal {
height: 10px;
margin: 0;
}
#PublisherVerticalScrollArea QScrollBar:vertical {
width: 10px;
margin: 0;
}
#PublisherVerticalScrollArea QScrollBar::handle {
background: {color:bg-scroll-handle};
border-radius: 4px;
margin: 1px;
}
#PublisherVerticalScrollArea QScrollBar::handle:horizontal {
min-width: 20px;
min-height: 8px;
}
#PublisherVerticalScrollArea QScrollBar::handle:vertical {
min-height: 20px;
min-width: 8px;
}
ValidationArtistMessage QLabel {
font-size: 20pt;
font-weight: bold;

View file

@ -56,6 +56,8 @@ class VerticalScrollArea(QtWidgets.QScrollArea):
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
self.setLayoutDirection(QtCore.Qt.RightToLeft)
self.setObjectName("PublisherVerticalScrollArea")
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
# Background of scrollbar will be transparent
scrollbar_bg = self.verticalScrollBar().parent()
@ -500,7 +502,9 @@ class ValidationErrorsView(QtWidgets.QWidget):
errors_scroll.setWidget(errors_widget)
errors_layout = QtWidgets.QVBoxLayout(errors_widget)
errors_layout.setContentsMargins(0, 0, 0, 0)
# Add 5 margin to left so the is not directly on the edge of the
# scroll widget
errors_layout.setContentsMargins(5, 0, 0, 0)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(errors_scroll, 1)

View file

@ -573,6 +573,7 @@ class BaseWorkfileController(
workdir,
filename,
template_key,
src_filepath=representation_filepath
)
except Exception:
failed = True

View file

@ -44,6 +44,14 @@ class ExtractBlendModel(BaseSettingsModel):
default_factory=list,
title="Families"
)
compress: bool = SettingsField(True, title="Compress")
class ExtractBlendAnimationModel(BaseSettingsModel):
enabled: bool = SettingsField(True)
optional: bool = SettingsField(title="Optional")
active: bool = SettingsField(title="Active")
compress: bool = SettingsField(False, title="Compress")
class ExtractPlayblastModel(BaseSettingsModel):
@ -51,6 +59,7 @@ class ExtractPlayblastModel(BaseSettingsModel):
optional: bool = SettingsField(title="Optional")
active: bool = SettingsField(title="Active")
presets: str = SettingsField("", title="Presets", widget="textarea")
compress: bool = SettingsField(False, title="Compress")
@validator("presets")
def validate_json(cls, value):
@ -110,8 +119,8 @@ class PublishPuginsModel(BaseSettingsModel):
default_factory=ValidatePluginModel,
title="Extract ABC"
)
ExtractBlendAnimation: ValidatePluginModel = SettingsField(
default_factory=ValidatePluginModel,
ExtractBlendAnimation: ExtractBlendAnimationModel = SettingsField(
default_factory=ExtractBlendAnimationModel,
title="Extract Blend Animation"
)
ExtractAnimationFBX: ValidatePluginModel = SettingsField(
@ -198,7 +207,8 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = {
"action",
"layout",
"blendScene"
]
],
"compress": False
},
"ExtractFBX": {
"enabled": False,
@ -213,7 +223,8 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = {
"ExtractBlendAnimation": {
"enabled": True,
"optional": True,
"active": True
"active": True,
"compress": False
},
"ExtractAnimationFBX": {
"enabled": False,

View file

@ -1 +1 @@
__version__ = "0.1.6"
__version__ = "0.1.7"

View file

@ -53,6 +53,9 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=BasicValidateModel,
title="Validate Latest Containers.",
section="Validators")
ValidateInstanceInContextHoudini: BasicValidateModel = SettingsField(
default_factory=BasicValidateModel,
title="Validate Instance is in same Context.")
ValidateMeshIsStatic: BasicValidateModel = SettingsField(
default_factory=BasicValidateModel,
title="Validate Mesh is Static.")
@ -84,6 +87,11 @@ DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
"optional": True,
"active": True
},
"ValidateInstanceInContextHoudini": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateMeshIsStatic": {
"enabled": True,
"optional": True,

View file

@ -1 +1 @@
__version__ = "0.2.11"
__version__ = "0.2.12"

View file

@ -355,7 +355,7 @@ class RedshiftSettingsModel(BaseSettingsModel):
)
additional_options: list[AdditionalOptionsModel] = SettingsField(
default_factory=list,
title="Additional Vray Options",
title="Additional Redshift Options",
description=(
"Add additional options - put attribute and value, like "
"reflectionMaxTraceDepth and 3"