Merge branch 'develop' into bugfix/updating_attribute_error_out_in_all_loaders_max

This commit is contained in:
Kayla Man 2023-09-05 21:01:27 +08:00 committed by GitHub
commit 8eafc292e2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 210 additions and 881 deletions

View file

@ -164,7 +164,7 @@ class RenderCreator(Creator):
api.get_stub().rename_item(comp_id,
new_comp_name)
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["aftereffects"]["create"]["RenderCreator"]
)

View file

@ -1,16 +0,0 @@
from openpype.hosts.fusion.api import (
comp_lock_and_undo_chunk,
get_current_comp
)
def main():
comp = get_current_comp()
"""Set all selected backgrounds to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Selected Backgrounds to 32bit'):
tools = comp.GetToolList(True, "Background").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -1,16 +0,0 @@
from openpype.hosts.fusion.api import (
comp_lock_and_undo_chunk,
get_current_comp
)
def main():
comp = get_current_comp()
"""Set all backgrounds to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Backgrounds to 32bit'):
tools = comp.GetToolList(False, "Background").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -1,16 +0,0 @@
from openpype.hosts.fusion.api import (
comp_lock_and_undo_chunk,
get_current_comp
)
def main():
comp = get_current_comp()
"""Set all selected loaders to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Selected Loaders to 32bit'):
tools = comp.GetToolList(True, "Loader").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -1,16 +0,0 @@
from openpype.hosts.fusion.api import (
comp_lock_and_undo_chunk,
get_current_comp
)
def main():
comp = get_current_comp()
"""Set all loaders to 32 bit"""
with comp_lock_and_undo_chunk(comp, 'Loaders to 32bit'):
tools = comp.GetToolList(False, "Loader").values()
for tool in tools:
tool.Depth = 5
main()

View file

@ -1,200 +0,0 @@
import os
import sys
import glob
import logging
from qtpy import QtWidgets, QtCore
import qtawesome as qta
from openpype.client import get_assets
from openpype import style
from openpype.pipeline import (
install_host,
get_current_project_name,
)
from openpype.hosts.fusion import api
from openpype.pipeline.context_tools import get_workdir_from_session
log = logging.getLogger("Fusion Switch Shot")
class App(QtWidgets.QWidget):
def __init__(self, parent=None):
################################################
# |---------------------| |------------------| #
# |Comp | |Asset | #
# |[..][ v]| |[ v]| #
# |---------------------| |------------------| #
# | Update existing comp [ ] | #
# |------------------------------------------| #
# | Switch | #
# |------------------------------------------| #
################################################
QtWidgets.QWidget.__init__(self, parent)
layout = QtWidgets.QVBoxLayout()
# Comp related input
comp_hlayout = QtWidgets.QHBoxLayout()
comp_label = QtWidgets.QLabel("Comp file")
comp_label.setFixedWidth(50)
comp_box = QtWidgets.QComboBox()
button_icon = qta.icon("fa.folder", color="white")
open_from_dir = QtWidgets.QPushButton()
open_from_dir.setIcon(button_icon)
comp_box.setFixedHeight(25)
open_from_dir.setFixedWidth(25)
open_from_dir.setFixedHeight(25)
comp_hlayout.addWidget(comp_label)
comp_hlayout.addWidget(comp_box)
comp_hlayout.addWidget(open_from_dir)
# Asset related input
asset_hlayout = QtWidgets.QHBoxLayout()
asset_label = QtWidgets.QLabel("Shot")
asset_label.setFixedWidth(50)
asset_box = QtWidgets.QComboBox()
asset_box.setLineEdit(QtWidgets.QLineEdit())
asset_box.setFixedHeight(25)
refresh_icon = qta.icon("fa.refresh", color="white")
refresh_btn = QtWidgets.QPushButton()
refresh_btn.setIcon(refresh_icon)
asset_box.setFixedHeight(25)
refresh_btn.setFixedWidth(25)
refresh_btn.setFixedHeight(25)
asset_hlayout.addWidget(asset_label)
asset_hlayout.addWidget(asset_box)
asset_hlayout.addWidget(refresh_btn)
# Options
options = QtWidgets.QHBoxLayout()
options.setAlignment(QtCore.Qt.AlignLeft)
current_comp_check = QtWidgets.QCheckBox()
current_comp_check.setChecked(True)
current_comp_label = QtWidgets.QLabel("Use current comp")
options.addWidget(current_comp_label)
options.addWidget(current_comp_check)
accept_btn = QtWidgets.QPushButton("Switch")
layout.addLayout(options)
layout.addLayout(comp_hlayout)
layout.addLayout(asset_hlayout)
layout.addWidget(accept_btn)
self._open_from_dir = open_from_dir
self._comps = comp_box
self._assets = asset_box
self._use_current = current_comp_check
self._accept_btn = accept_btn
self._refresh_btn = refresh_btn
self.setWindowTitle("Fusion Switch Shot")
self.setLayout(layout)
self.resize(260, 140)
self.setMinimumWidth(260)
self.setFixedHeight(140)
self.connections()
# Update ui to correct state
self._on_use_current_comp()
self._refresh()
def connections(self):
self._use_current.clicked.connect(self._on_use_current_comp)
self._open_from_dir.clicked.connect(self._on_open_from_dir)
self._refresh_btn.clicked.connect(self._refresh)
self._accept_btn.clicked.connect(self._on_switch)
def _on_use_current_comp(self):
state = self._use_current.isChecked()
self._open_from_dir.setEnabled(not state)
self._comps.setEnabled(not state)
def _on_open_from_dir(self):
start_dir = get_workdir_from_session()
comp_file, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "Choose comp", start_dir)
if not comp_file:
return
# Create completer
self.populate_comp_box([comp_file])
self._refresh()
def _refresh(self):
# Clear any existing items
self._assets.clear()
asset_names = self.collect_asset_names()
completer = QtWidgets.QCompleter(asset_names)
self._assets.setCompleter(completer)
self._assets.addItems(asset_names)
def _on_switch(self):
if not self._use_current.isChecked():
file_name = self._comps.itemData(self._comps.currentIndex())
else:
comp = api.get_current_comp()
file_name = comp.GetAttrs("COMPS_FileName")
asset = self._assets.currentText()
import colorbleed.scripts.fusion_switch_shot as switch_shot
switch_shot.switch(asset_name=asset, filepath=file_name, new=True)
def collect_slap_comps(self, directory):
items = glob.glob("{}/*.comp".format(directory))
return items
def collect_asset_names(self):
project_name = get_current_project_name()
asset_docs = get_assets(project_name, fields=["name"])
asset_names = {
asset_doc["name"]
for asset_doc in asset_docs
}
return list(asset_names)
def populate_comp_box(self, files):
"""Ensure we display the filename only but the path is stored as well
Args:
files (list): list of full file path [path/to/item/item.ext,]
Returns:
None
"""
for f in files:
filename = os.path.basename(f)
self._comps.addItem(filename, userData=f)
if __name__ == '__main__':
install_host(api)
app = QtWidgets.QApplication(sys.argv)
window = App()
window.setStyleSheet(style.load_stylesheet())
window.show()
sys.exit(app.exec_())

View file

@ -1,40 +0,0 @@
"""Forces Fusion to 'retrigger' the Loader to update.
Warning:
This might change settings like 'Reverse', 'Loop', trims and other
settings of the Loader. So use this at your own risk.
"""
from openpype.hosts.fusion.api.pipeline import (
get_current_comp,
comp_lock_and_undo_chunk
)
def update_loader_ranges():
comp = get_current_comp()
with comp_lock_and_undo_chunk(comp, "Reload clip time ranges"):
tools = comp.GetToolList(True, "Loader").values()
for tool in tools:
# Get tool attributes
tool_a = tool.GetAttrs()
clipTable = tool_a['TOOLST_Clip_Name']
altclipTable = tool_a['TOOLST_AltClip_Name']
startTime = tool_a['TOOLNT_Clip_Start']
old_global_in = tool.GlobalIn[comp.CurrentTime]
# Reapply
for index, _ in clipTable.items():
time = startTime[index]
tool.Clip[time] = tool.Clip[time]
for index, _ in altclipTable.items():
time = startTime[index]
tool.ProxyFilename[time] = tool.ProxyFilename[time]
tool.GlobalIn[comp.CurrentTime] = old_global_in
if __name__ == '__main__':
update_loader_ranges()

View file

@ -5,7 +5,7 @@ Global = {
Map = {
["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy",
["Config:"] = "UserPaths:Config;OpenPype:Config",
["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts",
["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts",
},
},
Script = {

View file

@ -30,10 +30,6 @@ class CreateSaver(NewCreator):
instance_attributes = [
"reviewable"
]
default_variants = [
"Main",
"Mask"
]
# TODO: This should be renamed together with Nuke so it is aligned
temp_rendering_path_template = (
@ -250,11 +246,7 @@ class CreateSaver(NewCreator):
label="Review",
)
def apply_settings(
self,
project_settings,
system_settings
):
def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
# plugin settings

View file

@ -296,7 +296,7 @@ class HoudiniCreator(NewCreator, HoudiniCreatorBase):
"""
return [hou.ropNodeTypeCategory()]
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
settings_name = self.settings_name

View file

@ -260,7 +260,7 @@ class MayaCreator(NewCreator, MayaCreatorBase):
default=True)
]
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
settings_name = self.settings_name

View file

@ -81,10 +81,8 @@ class CreateAnimation(plugin.MayaHiddenCreator):
return defs
def apply_settings(self, project_settings, system_settings):
super(CreateAnimation, self).apply_settings(
project_settings, system_settings
)
def apply_settings(self, project_settings):
super(CreateAnimation, self).apply_settings(project_settings)
# Hardcoding creator to be enabled due to existing settings would
# disable the creator causing the creator plugin to not be
# discoverable.

View file

@ -34,7 +34,7 @@ class CreateRenderlayer(plugin.RenderlayerCreator):
render_settings = {}
@classmethod
def apply_settings(cls, project_settings, system_settings):
def apply_settings(cls, project_settings):
cls.render_settings = project_settings["maya"]["RenderSettings"]
def create(self, subset_name, instance_data, pre_create_data):

View file

@ -21,7 +21,7 @@ class CreateUnrealSkeletalMesh(plugin.MayaCreator):
# Defined in settings
joint_hints = set()
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
"""Apply project settings to creator"""
settings = (
project_settings["maya"]["create"]["CreateUnrealSkeletalMesh"]

View file

@ -16,7 +16,7 @@ class CreateUnrealStaticMesh(plugin.MayaCreator):
# Defined in settings
collision_prefixes = []
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
"""Apply project settings to creator"""
settings = project_settings["maya"]["create"]["CreateUnrealStaticMesh"]
self.collision_prefixes = settings["collision_prefixes"]

View file

@ -22,7 +22,7 @@ class CreateVRayScene(plugin.RenderlayerCreator):
singleton_node_name = "vraysceneMain"
@classmethod
def apply_settings(cls, project_settings, system_settings):
def apply_settings(cls, project_settings):
cls.render_settings = project_settings["maya"]["RenderSettings"]
def create(self, subset_name, instance_data, pre_create_data):

View file

@ -379,11 +379,7 @@ class NukeWriteCreator(NukeCreator):
sys.exc_info()[2]
)
def apply_settings(
self,
project_settings,
system_settings
):
def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings."""
# plugin settings

View file

@ -98,7 +98,7 @@ class AutoImageCreator(PSAutoCreator):
)
]
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["photoshop"]["create"]["AutoImageCreator"]
)

View file

@ -171,7 +171,7 @@ class ImageCreator(Creator):
)
]
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["photoshop"]["create"]["ImageCreator"]
)

View file

@ -18,7 +18,7 @@ class ReviewCreator(PSAutoCreator):
it will get recreated in next publish either way).
"""
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["photoshop"]["create"]["ReviewCreator"]
)

View file

@ -19,7 +19,7 @@ class WorkfileCreator(PSAutoCreator):
in next publish automatically).
"""
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["photoshop"]["create"]["WorkfileCreator"]
)

View file

@ -36,7 +36,7 @@ class BatchMovieCreator(TrayPublishCreator):
# Position batch creator after simple creators
order = 110
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
creator_settings = (
project_settings["traypublisher"]["create"]["BatchMovieCreator"]
)

View file

@ -139,7 +139,7 @@ class CreateRenderlayer(TVPaintCreator):
# - Mark by default instance for review
mark_for_review = True
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["tvpaint"]["create"]["create_render_layer"]
)
@ -387,7 +387,7 @@ class CreateRenderPass(TVPaintCreator):
# Settings
mark_for_review = True
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["tvpaint"]["create"]["create_render_pass"]
)
@ -690,7 +690,7 @@ class TVPaintAutoDetectRenderCreator(TVPaintCreator):
group_idx_offset = 10
group_idx_padding = 3
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings
["tvpaint"]
@ -1029,7 +1029,7 @@ class TVPaintSceneRenderCreator(TVPaintAutoCreator):
mark_for_review = True
active_on_create = False
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["tvpaint"]["create"]["create_render_scene"]
)

View file

@ -12,7 +12,7 @@ class TVPaintReviewCreator(TVPaintAutoCreator):
# Settings
active_on_create = True
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["tvpaint"]["create"]["create_review"]
)

View file

@ -9,7 +9,7 @@ class TVPaintWorkfileCreator(TVPaintAutoCreator):
label = "Workfile"
icon = "fa.file-o"
def apply_settings(self, project_settings, system_settings):
def apply_settings(self, project_settings):
plugin_settings = (
project_settings["tvpaint"]["create"]["create_workfile"]
)

View file

@ -424,17 +424,25 @@ class TextDef(AbstractAttrDef):
class EnumDef(AbstractAttrDef):
"""Enumeration of single item from items.
"""Enumeration of items.
Enumeration of single item from items. Or list of items if multiselection
is enabled.
Args:
items: Items definition that can be converted using
'prepare_enum_items'.
default: Default value. Must be one key(value) from passed items.
items (Union[list[str], list[dict[str, Any]]): Items definition that
can be converted using 'prepare_enum_items'.
default (Optional[Any]): Default value. Must be one key(value) from
passed items or list of values for multiselection.
multiselection (Optional[bool]): If True, multiselection is allowed.
Output is list of selected items.
"""
type = "enum"
def __init__(self, key, items, default=None, **kwargs):
def __init__(
self, key, items, default=None, multiselection=False, **kwargs
):
if not items:
raise ValueError((
"Empty 'items' value. {} must have"
@ -443,30 +451,44 @@ class EnumDef(AbstractAttrDef):
items = self.prepare_enum_items(items)
item_values = [item["value"] for item in items]
if default not in item_values:
for value in item_values:
default = value
break
item_values_set = set(item_values)
if multiselection:
if default is None:
default = []
default = list(item_values_set.intersection(default))
elif default not in item_values:
default = next(iter(item_values), None)
super(EnumDef, self).__init__(key, default=default, **kwargs)
self.items = items
self._item_values = set(item_values)
self._item_values = item_values_set
self.multiselection = multiselection
def __eq__(self, other):
if not super(EnumDef, self).__eq__(other):
return False
return self.items == other.items
return (
self.items == other.items
and self.multiselection == other.multiselection
)
def convert_value(self, value):
if value in self._item_values:
return value
return self.default
if not self.multiselection:
if value in self._item_values:
return value
return self.default
if value is None:
return copy.deepcopy(self.default)
return list(self._item_values.intersection(value))
def serialize(self):
data = super(EnumDef, self).serialize()
data["items"] = copy.deepcopy(self.items)
data["multiselection"] = self.multiselection
return data
@staticmethod

View file

@ -270,8 +270,8 @@ def is_func_signature_supported(func, *args, **kwargs):
Args:
func (function): A function where the signature should be tested.
*args (tuple[Any]): Positional arguments for function signature.
**kwargs (dict[str, Any]): Keyword arguments for function signature.
*args (Any): Positional arguments for function signature.
**kwargs (Any): Keyword arguments for function signature.
Returns:
bool: Function can pass in arguments.

View file

@ -334,12 +334,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
payload = self._get_vray_render_payload(payload_data)
elif "assscene" in instance.data["families"]:
self.log.debug("Submitting Arnold .ass standalone render..")
ass_export_payload = self._get_arnold_export_payload(payload_data)
export_job = self.submit(ass_export_payload)
payload = self._get_arnold_render_payload(payload_data)
else:
self.log.debug("Submitting MayaBatch render..")
payload = self._get_maya_payload(payload_data)
@ -635,53 +629,6 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline,
return job_info, attr.asdict(plugin_info)
def _get_arnold_export_payload(self, data):
try:
from openpype.scripts import export_maya_ass_job
except Exception:
raise AssertionError(
"Expected module 'export_maya_ass_job' to be available")
module_path = export_maya_ass_job.__file__
if module_path.endswith(".pyc"):
module_path = module_path[: -len(".pyc")] + ".py"
script = os.path.normpath(module_path)
job_info = copy.deepcopy(self.job_info)
job_info.Name = self._job_info_label("Export")
# Force a single frame Python job
job_info.Plugin = "Python"
job_info.Frames = 1
renderlayer = self._instance.data["setMembers"]
# add required env vars for the export script
envs = {
"AVALON_APP_NAME": os.environ.get("AVALON_APP_NAME"),
"OPENPYPE_ASS_EXPORT_RENDER_LAYER": renderlayer,
"OPENPYPE_ASS_EXPORT_SCENE_FILE": self.scene_path,
"OPENPYPE_ASS_EXPORT_OUTPUT": job_info.OutputFilename[0],
"OPENPYPE_ASS_EXPORT_START": int(self._instance.data["frameStartHandle"]), # noqa
"OPENPYPE_ASS_EXPORT_END": int(self._instance.data["frameEndHandle"]), # noqa
"OPENPYPE_ASS_EXPORT_STEP": 1
}
for key, value in envs.items():
if not value:
continue
job_info.EnvironmentKeyValue[key] = value
plugin_info = PythonPluginInfo(
ScriptFile=script,
Version="3.6",
Arguments="",
SingleFrameOnly="True"
)
return job_info, attr.asdict(plugin_info)
def _get_vray_render_payload(self, data):
# Job Info

View file

@ -1774,7 +1774,7 @@ class CreateContext:
self.creator_discover_result = report
for creator_class in report.plugins:
if inspect.isabstract(creator_class):
self.log.info(
self.log.debug(
"Skipping abstract Creator {}".format(str(creator_class))
)
continue
@ -1804,6 +1804,7 @@ class CreateContext:
self,
self.headless
)
if not creator.enabled:
disabled_creators[creator_identifier] = creator
continue

View file

@ -1,16 +1,12 @@
import copy
import collections
from abc import (
ABCMeta,
abstractmethod,
abstractproperty
)
from abc import ABCMeta, abstractmethod
import six
from openpype.settings import get_system_settings, get_project_settings
from openpype.lib import Logger
from openpype.lib import Logger, is_func_signature_supported
from openpype.pipeline.plugin_discover import (
discover,
register_plugin,
@ -84,7 +80,8 @@ class SubsetConvertorPlugin(object):
def host(self):
return self._create_context.host
@abstractproperty
@property
@abstractmethod
def identifier(self):
"""Converted identifier.
@ -161,7 +158,6 @@ class BaseCreator:
Args:
project_settings (Dict[str, Any]): Project settings.
system_settings (Dict[str, Any]): System settings.
create_context (CreateContext): Context which initialized creator.
headless (bool): Running in headless mode.
"""
@ -208,10 +204,33 @@ class BaseCreator:
# - we may use UI inside processing this attribute should be checked
self.headless = headless
self.apply_settings(project_settings, system_settings)
expect_system_settings = False
if is_func_signature_supported(
self.apply_settings, project_settings
):
self.apply_settings(project_settings)
else:
expect_system_settings = True
# Backwards compatibility for system settings
self.apply_settings(project_settings, system_settings)
def apply_settings(self, project_settings, system_settings):
"""Method called on initialization of plugin to apply settings."""
init_overriden = self.__class__.__init__ is not BaseCreator.__init__
if init_overriden or expect_system_settings:
self.log.warning((
"WARNING: Source - Create plugin {}."
" System settings argument will not be passed to"
" '__init__' and 'apply_settings' methods in future versions"
" of OpenPype. Planned version to drop the support"
" is 3.16.6 or 3.17.0. Please contact Ynput core team if you"
" need to keep system settings."
).format(self.__class__.__name__))
def apply_settings(self, project_settings):
"""Method called on initialization of plugin to apply settings.
Args:
project_settings (dict[str, Any]): Project settings.
"""
pass
@ -224,7 +243,8 @@ class BaseCreator:
return self.family
@abstractproperty
@property
@abstractmethod
def family(self):
"""Family that plugin represents."""

View file

@ -1,105 +0,0 @@
"""This module is used for command line exporting of ASS files.
WARNING:
This need to be rewriten to be able use it in Pype 3!
"""
import os
import argparse
import logging
import subprocess
import platform
try:
from shutil import which
except ImportError:
# we are in python < 3.3
def which(command):
path = os.getenv('PATH')
for p in path.split(os.path.pathsep):
p = os.path.join(p, command)
if os.path.exists(p) and os.access(p, os.X_OK):
return p
handler = logging.basicConfig()
log = logging.getLogger("Publish Image Sequences")
log.setLevel(logging.DEBUG)
error_format = "Failed {plugin.__name__}: {error} -- {error.traceback}"
def __main__():
parser = argparse.ArgumentParser()
parser.add_argument("--paths",
nargs="*",
default=[],
help="The filepaths to publish. This can be a "
"directory or a path to a .json publish "
"configuration.")
parser.add_argument("--gui",
default=False,
action="store_true",
help="Whether to run Pyblish in GUI mode.")
parser.add_argument("--pype", help="Pype root")
kwargs, args = parser.parse_known_args()
print("Running pype ...")
auto_pype_root = os.path.dirname(os.path.abspath(__file__))
auto_pype_root = os.path.abspath(auto_pype_root + "../../../../..")
auto_pype_root = os.environ.get('OPENPYPE_SETUP_PATH') or auto_pype_root
if os.environ.get('OPENPYPE_SETUP_PATH'):
print("Got Pype location from environment: {}".format(
os.environ.get('OPENPYPE_SETUP_PATH')))
pype_command = "openpype.ps1"
if platform.system().lower() == "linux":
pype_command = "pype"
elif platform.system().lower() == "windows":
pype_command = "openpype.bat"
if kwargs.pype:
pype_root = kwargs.pype
else:
# test if pype.bat / pype is in the PATH
# if it is, which() will return its path and we use that.
# if not, we use auto_pype_root path. Caveat of that one is
# that it can be UNC path and that will not work on windows.
pype_path = which(pype_command)
if pype_path:
pype_root = os.path.dirname(pype_path)
else:
pype_root = auto_pype_root
print("Set pype root to: {}".format(pype_root))
print("Paths: {}".format(kwargs.paths or [os.getcwd()]))
# paths = kwargs.paths or [os.environ.get("OPENPYPE_METADATA_FILE")] or [os.getcwd()] # noqa
mayabatch = os.environ.get("AVALON_APP_NAME").replace("maya", "mayabatch")
args = [
os.path.join(pype_root, pype_command),
"launch",
"--app",
mayabatch,
"-script",
os.path.join(pype_root, "repos", "pype",
"pype", "scripts", "export_maya_ass_sequence.mel")
]
print("Pype command: {}".format(" ".join(args)))
# Forcing forwaring the environment because environment inheritance does
# not always work.
# Cast all values in environment to str to be safe
env = {k: str(v) for k, v in os.environ.items()}
exit_code = subprocess.call(args, env=env)
if exit_code != 0:
raise RuntimeError("Publishing failed.")
if __name__ == '__main__':
__main__()

View file

@ -1,67 +0,0 @@
/*
Script to export specified layer as ass files.
Attributes:
scene_file (str): Name of the scene to load.
start (int): Start frame.
end (int): End frame.
step (int): Step size.
output_path (str): File output path.
render_layer (str): Name of render layer.
*/
$scene_file=`getenv "OPENPYPE_ASS_EXPORT_SCENE_FILE"`;
$step=`getenv "OPENPYPE_ASS_EXPORT_STEP"`;
$start=`getenv "OPENPYPE_ASS_EXPORT_START"`;
$end=`getenv "OPENPYPE_ASS_EXPORT_END"`;
$file_path=`getenv "OPENPYPE_ASS_EXPORT_OUTPUT"`;
$render_layer = `getenv "OPENPYPE_ASS_EXPORT_RENDER_LAYER"`;
print("*** ASS Export Plugin\n");
if ($scene_file == "") {
print("!!! cannot determine scene file\n");
quit -a -ex -1;
}
if ($step == "") {
print("!!! cannot determine step size\n");
quit -a -ex -1;
}
if ($start == "") {
print("!!! cannot determine start frame\n");
quit -a -ex -1;
}
if ($end == "") {
print("!!! cannot determine end frame\n");
quit -a -ex -1;
}
if ($file_path == "") {
print("!!! cannot determine output file\n");
quit -a -ex -1;
}
if ($render_layer == "") {
print("!!! cannot determine render layer\n");
quit -a -ex -1;
}
print(">>> Opening Scene [ " + $scene_file + " ]\n");
// open scene
file -o -f $scene_file;
// switch to render layer
print(">>> Switching layer [ "+ $render_layer + " ]\n");
editRenderLayerGlobals -currentRenderLayer $render_layer;
// export
print(">>> Exporting to [ " + $file_path + " ]\n");
arnoldExportAss -mask 255 -sl 1 -ll 1 -bb 1 -sf $start -se $end -b -fs $step;
print("--- Done\n");

View file

@ -1,241 +0,0 @@
import os
import re
import sys
import logging
from openpype.client import get_asset_by_name, get_versions
# Pipeline imports
from openpype.hosts.fusion import api
import openpype.hosts.fusion.api.lib as fusion_lib
# Config imports
from openpype.lib import version_up
from openpype.pipeline import (
install_host,
registered_host,
legacy_io,
get_current_project_name,
)
from openpype.pipeline.context_tools import get_workdir_from_session
from openpype.pipeline.version_start import get_versioning_start
log = logging.getLogger("Update Slap Comp")
def _format_version_folder(folder):
"""Format a version folder based on the filepath
Args:
folder: file path to a folder
Returns:
str: new version folder name
"""
new_version = get_versioning_start(
get_current_project_name(),
"fusion",
family="workfile"
)
if os.path.isdir(folder):
re_version = re.compile(r"v\d+$")
versions = [i for i in os.listdir(folder) if os.path.isdir(i)
and re_version.match(i)]
if versions:
# ensure the "v" is not included
new_version = int(max(versions)[1:]) + 1
version_folder = "v{:03d}".format(new_version)
return version_folder
def _get_fusion_instance():
fusion = getattr(sys.modules["__main__"], "fusion", None)
if fusion is None:
try:
# Support for FuScript.exe, BlackmagicFusion module for py2 only
import BlackmagicFusion as bmf
fusion = bmf.scriptapp("Fusion")
except ImportError:
raise RuntimeError("Could not find a Fusion instance")
return fusion
def _format_filepath(session):
project = session["AVALON_PROJECT"]
asset = session["AVALON_ASSET"]
# Save updated slap comp
work_path = get_workdir_from_session(session)
walk_to_dir = os.path.join(work_path, "scenes", "slapcomp")
slapcomp_dir = os.path.abspath(walk_to_dir)
# Ensure destination exists
if not os.path.isdir(slapcomp_dir):
log.warning("Folder did not exist, creating folder structure")
os.makedirs(slapcomp_dir)
# Compute output path
new_filename = "{}_{}_slapcomp_v001.comp".format(project, asset)
new_filepath = os.path.join(slapcomp_dir, new_filename)
# Create new unqiue filepath
if os.path.exists(new_filepath):
new_filepath = version_up(new_filepath)
return new_filepath
def _update_savers(comp, session):
"""Update all savers of the current comp to ensure the output is correct
Args:
comp (object): current comp instance
session (dict): the current Avalon session
Returns:
None
"""
new_work = get_workdir_from_session(session)
renders = os.path.join(new_work, "renders")
version_folder = _format_version_folder(renders)
renders_version = os.path.join(renders, version_folder)
comp.Print("New renders to: %s\n" % renders)
with api.comp_lock_and_undo_chunk(comp):
savers = comp.GetToolList(False, "Saver").values()
for saver in savers:
filepath = saver.GetAttrs("TOOLST_Clip_Name")[1.0]
filename = os.path.basename(filepath)
new_path = os.path.join(renders_version, filename)
saver["Clip"] = new_path
def update_frame_range(comp, representations):
"""Update the frame range of the comp and render length
The start and end frame are based on the lowest start frame and the highest
end frame
Args:
comp (object): current focused comp
representations (list) collection of dicts
Returns:
None
"""
version_ids = [r["parent"] for r in representations]
project_name = get_current_project_name()
versions = list(get_versions(project_name, version_ids=version_ids))
start = min(v["data"]["frameStart"] for v in versions)
end = max(v["data"]["frameEnd"] for v in versions)
fusion_lib.update_frame_range(start, end, comp=comp)
def switch(asset_name, filepath=None, new=True):
"""Switch the current containers of the file to the other asset (shot)
Args:
filepath (str): file path of the comp file
asset_name (str): name of the asset (shot)
new (bool): Save updated comp under a different name
Returns:
comp path (str): new filepath of the updated comp
"""
# If filepath provided, ensure it is valid absolute path
if filepath is not None:
if not os.path.isabs(filepath):
filepath = os.path.abspath(filepath)
assert os.path.exists(filepath), "%s must exist " % filepath
# Assert asset name exists
# It is better to do this here then to wait till switch_shot does it
project_name = get_current_project_name()
asset = get_asset_by_name(project_name, asset_name)
assert asset, "Could not find '%s' in the database" % asset_name
# Go to comp
if not filepath:
current_comp = api.get_current_comp()
assert current_comp is not None, "Could not find current comp"
else:
fusion = _get_fusion_instance()
current_comp = fusion.LoadComp(filepath, quiet=True)
assert current_comp is not None, "Fusion could not load '%s'" % filepath
host = registered_host()
containers = list(host.ls())
assert containers, "Nothing to update"
representations = []
for container in containers:
try:
representation = fusion_lib.switch_item(container,
asset_name=asset_name)
representations.append(representation)
except Exception as e:
current_comp.Print("Error in switching! %s\n" % e.message)
message = "Switched %i Loaders of the %i\n" % (len(representations),
len(containers))
current_comp.Print(message)
# Build the session to switch to
switch_to_session = legacy_io.Session.copy()
switch_to_session["AVALON_ASSET"] = asset['name']
if new:
comp_path = _format_filepath(switch_to_session)
# Update savers output based on new session
_update_savers(current_comp, switch_to_session)
else:
comp_path = version_up(filepath)
current_comp.Print(comp_path)
current_comp.Print("\nUpdating frame range")
update_frame_range(current_comp, representations)
current_comp.Save(comp_path)
return comp_path
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description="Switch to a shot within an"
"existing comp file")
parser.add_argument("--file_path",
type=str,
default=True,
help="File path of the comp to use")
parser.add_argument("--asset_name",
type=str,
default=True,
help="Name of the asset (shot) to switch")
args, unknown = parser.parse_args()
install_host(api)
switch(args.asset_name, args.file_path)
sys.exit(0)

View file

@ -19,6 +19,7 @@ from openpype.tools.utils import (
CustomTextComboBox,
FocusSpinBox,
FocusDoubleSpinBox,
MultiSelectionComboBox,
)
from openpype.widgets.nice_checkbox import NiceCheckbox
@ -412,10 +413,19 @@ class EnumAttrWidget(_BaseAttrDefWidget):
self._multivalue = False
super(EnumAttrWidget, self).__init__(*args, **kwargs)
@property
def multiselection(self):
return self.attr_def.multiselection
def _ui_init(self):
input_widget = CustomTextComboBox(self)
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
input_widget.setItemDelegate(combo_delegate)
if self.multiselection:
input_widget = MultiSelectionComboBox(self)
else:
input_widget = CustomTextComboBox(self)
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
input_widget.setItemDelegate(combo_delegate)
self._combo_delegate = combo_delegate
if self.attr_def.tooltip:
input_widget.setToolTip(self.attr_def.tooltip)
@ -427,9 +437,11 @@ class EnumAttrWidget(_BaseAttrDefWidget):
if idx >= 0:
input_widget.setCurrentIndex(idx)
input_widget.currentIndexChanged.connect(self._on_value_change)
if self.multiselection:
input_widget.value_changed.connect(self._on_value_change)
else:
input_widget.currentIndexChanged.connect(self._on_value_change)
self._combo_delegate = combo_delegate
self._input_widget = input_widget
self.main_layout.addWidget(input_widget, 0)
@ -442,17 +454,40 @@ class EnumAttrWidget(_BaseAttrDefWidget):
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
if self.multiselection:
return self._input_widget.value()
idx = self._input_widget.currentIndex()
return self._input_widget.itemData(idx)
def _multiselection_multivalue_prep(self, values):
final = None
multivalue = False
for value in values:
value = set(value)
if final is None:
final = value
elif multivalue or final != value:
final |= value
multivalue = True
return list(final), multivalue
def set_value(self, value, multivalue=False):
if multivalue:
set_value = set(value)
if len(set_value) == 1:
multivalue = False
value = tuple(set_value)[0]
if self.multiselection:
value, multivalue = self._multiselection_multivalue_prep(
value)
else:
set_value = set(value)
if len(set_value) == 1:
multivalue = False
value = tuple(set_value)[0]
if not multivalue:
if self.multiselection:
self._input_widget.blockSignals(True)
self._input_widget.set_value(value)
self._input_widget.blockSignals(False)
elif not multivalue:
idx = self._input_widget.findData(value)
cur_idx = self._input_widget.currentIndex()
if idx != cur_idx and idx >= 0:

View file

@ -4,6 +4,7 @@ from qtpy import QtWidgets, QtCore, QtGui
from openpype.widgets.sliders import NiceSlider
from openpype.tools.settings import CHILD_OFFSET
from openpype.tools.utils import MultiSelectionComboBox
from openpype.settings.entities.exceptions import BaseInvalidValue
from .widgets import (
@ -15,7 +16,6 @@ from .widgets import (
SettingsNiceCheckbox,
SettingsLineEdit
)
from .multiselection_combobox import MultiSelectionComboBox
from .wrapper_widgets import (
WrapperWidget,
CollapsibleWrapper,

View file

@ -38,6 +38,7 @@ from .models import (
from .overlay_messages import (
MessageOverlayObject,
)
from .multiselection_combobox import MultiSelectionComboBox
__all__ = (
@ -78,4 +79,6 @@ __all__ = (
"RecursiveSortFilterProxyModel",
"MessageOverlayObject",
"MultiSelectionComboBox",
)

View file

@ -170,8 +170,12 @@ def get_openpype_qt_app():
if attr is not None:
QtWidgets.QApplication.setAttribute(attr)
if hasattr(
QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy"
policy = os.getenv("QT_SCALE_FACTOR_ROUNDING_POLICY")
if (
hasattr(
QtWidgets.QApplication, "setHighDpiScaleFactorRoundingPolicy"
)
and not policy
):
QtWidgets.QApplication.setHighDpiScaleFactorRoundingPolicy(
QtCore.Qt.HighDpiScaleFactorRoundingPolicy.PassThrough

View file

@ -1,9 +1,10 @@
from qtpy import QtCore, QtGui, QtWidgets
from openpype.tools.utils.lib import (
from .lib import (
checkstate_int_to_enum,
checkstate_enum_to_int,
)
from openpype.tools.utils.constants import (
from .constants import (
CHECKED_INT,
UNCHECKED_INT,
ITEM_IS_USER_TRISTATE,
@ -60,12 +61,25 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
self._block_mouse_release_timer = QtCore.QTimer(self, singleShot=True)
self._initial_mouse_pos = None
self._separator = separator
self.placeholder_text = placeholder
self.delegate = ComboItemDelegate(self)
self.setItemDelegate(self.delegate)
self._placeholder_text = placeholder
delegate = ComboItemDelegate(self)
self.setItemDelegate(delegate)
self.lines = {}
self.item_height = None
self._lines = {}
self._item_height = None
self._custom_text = None
self._delegate = delegate
def get_placeholder_text(self):
return self._placeholder_text
def set_placeholder_text(self, text):
self._placeholder_text = text
self._update_size_hint()
def set_custom_text(self, text):
self._custom_text = text
self._update_size_hint()
def focusInEvent(self, event):
self.focused_in.emit()
@ -158,7 +172,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
if new_state is not None:
model.setData(current_index, new_state, QtCore.Qt.CheckStateRole)
self.view().update(current_index)
self.update_size_hint()
self._update_size_hint()
self.value_changed.emit()
return True
@ -182,25 +196,33 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
self.initStyleOption(option)
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option)
# draw the icon and text
items = self.checked_items_text()
if not items:
option.currentText = self.placeholder_text
# draw the icon and text
draw_text = True
combotext = None
if self._custom_text is not None:
combotext = self._custom_text
elif not items:
combotext = self._placeholder_text
else:
draw_text = False
if draw_text:
option.currentText = combotext
option.palette.setCurrentColorGroup(QtGui.QPalette.Disabled)
painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option)
return
font_metricts = self.fontMetrics()
if self.item_height is None:
if self._item_height is None:
self.updateGeometry()
self.update()
return
for line, items in self.lines.items():
for line, items in self._lines.items():
top_y = (
option.rect.top()
+ (line * self.item_height)
+ (line * self._item_height)
+ self.top_bottom_margins
)
left_x = option.rect.left() + self.left_offset
@ -210,7 +232,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
label_rect.moveTop(top_y)
label_rect.moveLeft(left_x)
label_rect.setHeight(self.item_height)
label_rect.setHeight(self._item_height)
label_rect.setWidth(
label_rect.width() + self.left_right_padding
)
@ -239,14 +261,18 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
def resizeEvent(self, *args, **kwargs):
super(MultiSelectionComboBox, self).resizeEvent(*args, **kwargs)
self.update_size_hint()
self._update_size_hint()
def update_size_hint(self):
self.lines = {}
def _update_size_hint(self):
if self._custom_text is not None:
self.update()
return
self._lines = {}
items = self.checked_items_text()
if not items:
self.update()
self.repaint()
return
option = QtWidgets.QStyleOptionComboBox()
@ -259,7 +285,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
total_width = option.rect.width() - btn_rect.width()
line = 0
self.lines = {line: []}
self._lines = {line: []}
font_metricts = self.fontMetrics()
default_left_x = 0 + self.left_offset
@ -270,18 +296,18 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
right_x = left_x + width
if right_x > total_width:
left_x = int(default_left_x)
if self.lines.get(line):
if self._lines.get(line):
line += 1
self.lines[line] = [item]
self._lines[line] = [item]
left_x += width
else:
self.lines[line] = [item]
self._lines[line] = [item]
line += 1
else:
if line in self.lines:
self.lines[line].append(item)
if line in self._lines:
self._lines[line].append(item)
else:
self.lines[line] = [item]
self._lines[line] = [item]
left_x = left_x + width + self.item_spacing
self.update()
@ -289,18 +315,20 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
def sizeHint(self):
value = super(MultiSelectionComboBox, self).sizeHint()
lines = len(self.lines)
if lines == 0:
lines = 1
lines = 1
if self._custom_text is None:
lines = len(self._lines)
if lines == 0:
lines = 1
if self.item_height is None:
self.item_height = (
if self._item_height is None:
self._item_height = (
self.fontMetrics().height()
+ (2 * self.top_bottom_padding)
+ (2 * self.top_bottom_margins)
)
value.setHeight(
(lines * self.item_height)
(lines * self._item_height)
+ (2 * self.top_bottom_margins)
)
return value
@ -316,7 +344,7 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
else:
check_state = UNCHECKED_INT
self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole)
self.update_size_hint()
self._update_size_hint()
def value(self):
items = list()