mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into bugfix/OP-8088_validate-write-node-report
This commit is contained in:
commit
a5c36f3711
42 changed files with 796 additions and 201 deletions
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,9 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.18.7-nightly.2
|
||||
- 3.18.7-nightly.1
|
||||
- 3.18.6
|
||||
- 3.18.6-nightly.2
|
||||
- 3.18.6-nightly.1
|
||||
- 3.18.5
|
||||
|
|
@ -132,9 +135,6 @@ body:
|
|||
- 3.15.10-nightly.1
|
||||
- 3.15.9
|
||||
- 3.15.9-nightly.2
|
||||
- 3.15.9-nightly.1
|
||||
- 3.15.8
|
||||
- 3.15.8-nightly.3
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
126
CHANGELOG.md
126
CHANGELOG.md
|
|
@ -1,6 +1,132 @@
|
|||
# Changelog
|
||||
|
||||
|
||||
## [3.18.6](https://github.com/ynput/OpenPype/tree/3.18.6)
|
||||
|
||||
|
||||
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.18.5...3.18.6)
|
||||
|
||||
### **🚀 Enhancements**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Use `SettingsField` from ayon server <a href="https://github.com/ynput/OpenPype/pull/6173">#6173</a></summary>
|
||||
|
||||
This is preparation for new version of pydantic which will require to customize the field class for AYON purposes as raw pydantic Field could not be used.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Nuke: Expose write knobs - OP-7592 <a href="https://github.com/ynput/OpenPype/pull/6137">#6137</a></summary>
|
||||
|
||||
This PR adds `exposed_knobs` to the creator plugins settings at `ayon+settings://nuke/create/CreateWriteRender/exposed_knobs`.When exposed knobs will be linked from the write node to the outside publish group, for users to adjust.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>AYON: Remove kitsu addon <a href="https://github.com/ynput/OpenPype/pull/6172">#6172</a></summary>
|
||||
|
||||
Removed kitsu addon from server addons because already has own repository.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🐛 Bug fixes**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Fusion: provide better logging for validate saver crash due type error <a href="https://github.com/ynput/OpenPype/pull/6082">#6082</a></summary>
|
||||
|
||||
Handles reported issue for `NoneType` error thrown in conversion `int(tool["Comments"][frame])`. It is most likely happening when saver node has no input connections.There is a validator for that, but it might be not obvious, that this error is caused by missing input connections and it has been already reported by `"Validate Saver Has Input"`.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Workfile Template Builder: Use correct variable in create placeholder <a href="https://github.com/ynput/OpenPype/pull/6141">#6141</a></summary>
|
||||
|
||||
Use correct variable where failed instances are stored for validation.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>ExtractOIIOTranscode: Missing product_names to subsets conversion <a href="https://github.com/ynput/OpenPype/pull/6159">#6159</a></summary>
|
||||
|
||||
The `Product Names` filtering should be fixed with this.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Blender: Fix missing animation data when updating blend assets <a href="https://github.com/ynput/OpenPype/pull/6165">#6165</a></summary>
|
||||
|
||||
Fix missing animation data when updating blend assets.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>TrayPublisher: Pre-fill of version works in AYON <a href="https://github.com/ynput/OpenPype/pull/6180">#6180</a></summary>
|
||||
|
||||
Use `folderPath` instead of `asset` in AYON mode to calculate next available version.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **🔀 Refactored code**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Chore: remove Muster <a href="https://github.com/ynput/OpenPype/pull/6085">#6085</a></summary>
|
||||
|
||||
Muster isn't maintained for a long time and it wasn't working anyway. This is removing related code from the code base. If there is renewed interest in Muster, it needs to be re-implemented in modern AYON compatible way.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
### **Merged pull requests**
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Maya: change label in the render settings to be more readable <a href="https://github.com/ynput/OpenPype/pull/6134">#6134</a></summary>
|
||||
|
||||
AYON replacement for #5713.
|
||||
|
||||
|
||||
___
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.18.5](https://github.com/ynput/OpenPype/tree/3.18.5)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ class RenderCreator(Creator):
|
|||
|
||||
# Settings
|
||||
mark_for_review = True
|
||||
force_setting_values = True
|
||||
|
||||
def create(self, subset_name_from_ui, data, pre_create_data):
|
||||
stub = api.get_stub() # only after After Effects is up
|
||||
|
|
@ -96,7 +97,9 @@ class RenderCreator(Creator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
|
||||
stub.rename_item(comp.id, subset_name)
|
||||
set_settings(True, True, [comp.id], print_msg=False)
|
||||
|
||||
if self.force_setting_values:
|
||||
set_settings(True, True, [comp.id], print_msg=False)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
output = [
|
||||
|
|
@ -173,6 +176,7 @@ class RenderCreator(Creator):
|
|||
)
|
||||
|
||||
self.mark_for_review = plugin_settings["mark_for_review"]
|
||||
self.force_setting_values = plugin_settings["force_setting_values"]
|
||||
self.default_variants = plugin_settings.get(
|
||||
"default_variants",
|
||||
plugin_settings.get("defaults") or []
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from openpype.hosts.fusion.api.lib import (
|
|||
)
|
||||
from openpype.pipeline import get_current_asset_name
|
||||
from openpype.resources import get_openpype_icon_filepath
|
||||
from openpype.tools.utils import get_qt_app
|
||||
|
||||
from .pipeline import FusionEventHandler
|
||||
from .pulse import FusionPulse
|
||||
|
|
@ -174,7 +175,8 @@ class OpenPypeMenu(QtWidgets.QWidget):
|
|||
|
||||
|
||||
def launch_openpype_menu():
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
app = get_qt_app()
|
||||
|
||||
pype_menu = OpenPypeMenu()
|
||||
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ import sys
|
|||
import contextlib
|
||||
import traceback
|
||||
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from openpype.lib import env_value_to_bool, Logger
|
||||
from openpype.modules import ModulesManager
|
||||
from openpype.pipeline import install_host
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.tools.utils import get_openpype_qt_app
|
||||
from openpype.tests.lib import is_in_tests
|
||||
|
||||
from .launch_logic import ProcessLauncher, stub
|
||||
|
|
@ -30,7 +29,7 @@ def main(*subprocess_args):
|
|||
|
||||
# coloring in StdOutBroker
|
||||
os.environ["OPENPYPE_LOG_NO_COLORS"] = "False"
|
||||
app = QtWidgets.QApplication([])
|
||||
app = get_openpype_qt_app()
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
launcher = ProcessLauncher(subprocess_args)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import os
|
||||
|
||||
import click
|
||||
|
||||
from openpype.lib import get_openpype_execute_args
|
||||
from openpype.lib.execute import run_detached_process
|
||||
from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon
|
||||
from openpype.modules import (
|
||||
click_wrap,
|
||||
OpenPypeModule,
|
||||
ITrayAction,
|
||||
IHostAddon,
|
||||
)
|
||||
|
||||
STANDALONEPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
|
@ -37,10 +40,10 @@ class StandAlonePublishAddon(OpenPypeModule, ITrayAction, IHostAddon):
|
|||
run_detached_process(args)
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
@click.group(
|
||||
@click_wrap.group(
|
||||
StandAlonePublishAddon.name,
|
||||
help="StandalonePublisher related commands.")
|
||||
def cli_main():
|
||||
|
|
|
|||
|
|
@ -1,10 +1,13 @@
|
|||
import os
|
||||
|
||||
import click
|
||||
|
||||
from openpype.lib import get_openpype_execute_args
|
||||
from openpype.lib.execute import run_detached_process
|
||||
from openpype.modules import OpenPypeModule, ITrayAction, IHostAddon
|
||||
from openpype.modules import (
|
||||
click_wrap,
|
||||
OpenPypeModule,
|
||||
ITrayAction,
|
||||
IHostAddon,
|
||||
)
|
||||
|
||||
TRAYPUBLISH_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
|
@ -38,10 +41,12 @@ class TrayPublishAddon(OpenPypeModule, IHostAddon, ITrayAction):
|
|||
run_detached_process(args)
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
@click.group(TrayPublishAddon.name, help="TrayPublisher related commands.")
|
||||
@click_wrap.group(
|
||||
TrayPublishAddon.name,
|
||||
help="TrayPublisher related commands.")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ SHARED_DATA_KEY = "openpype.traypublisher.instances"
|
|||
|
||||
class HiddenTrayPublishCreator(HiddenCreator):
|
||||
host_name = "traypublisher"
|
||||
settings_category = "traypublisher"
|
||||
|
||||
def collect_instances(self):
|
||||
instances_by_identifier = cache_and_get_instances(
|
||||
|
|
@ -68,6 +69,7 @@ class HiddenTrayPublishCreator(HiddenCreator):
|
|||
class TrayPublishCreator(Creator):
|
||||
create_allow_context_change = True
|
||||
host_name = "traypublisher"
|
||||
settings_category = "traypublisher"
|
||||
|
||||
def collect_instances(self):
|
||||
instances_by_identifier = cache_and_get_instances(
|
||||
|
|
|
|||
|
|
@ -381,15 +381,19 @@ or updating already created. Publishing will create OTIO file.
|
|||
"""
|
||||
self.asset_name_check = []
|
||||
|
||||
tracks = otio_timeline.each_child(
|
||||
descended_from_type=otio.schema.Track
|
||||
)
|
||||
tracks = [
|
||||
track for track in otio_timeline.each_child(
|
||||
descended_from_type=otio.schema.Track)
|
||||
if track.kind == "Video"
|
||||
]
|
||||
|
||||
# media data for audio sream and reference solving
|
||||
# media data for audio stream and reference solving
|
||||
media_data = self._get_media_source_metadata(media_path)
|
||||
|
||||
for track in tracks:
|
||||
# set track name
|
||||
track.name = f"{sequence_file_name} - {otio_timeline.name}"
|
||||
|
||||
try:
|
||||
track_start_frame = (
|
||||
abs(track.source_range.start_time.value)
|
||||
|
|
@ -398,19 +402,19 @@ or updating already created. Publishing will create OTIO file.
|
|||
except AttributeError:
|
||||
track_start_frame = 0
|
||||
|
||||
|
||||
for clip in track.each_child():
|
||||
if not self._validate_clip_for_processing(clip):
|
||||
for otio_clip in track.each_child():
|
||||
if not self._validate_clip_for_processing(otio_clip):
|
||||
continue
|
||||
|
||||
|
||||
# get available frames info to clip data
|
||||
self._create_otio_reference(clip, media_path, media_data)
|
||||
self._create_otio_reference(otio_clip, media_path, media_data)
|
||||
|
||||
# convert timeline range to source range
|
||||
self._restore_otio_source_range(clip)
|
||||
self._restore_otio_source_range(otio_clip)
|
||||
|
||||
base_instance_data = self._get_base_instance_data(
|
||||
clip,
|
||||
otio_clip,
|
||||
instance_data,
|
||||
track_start_frame
|
||||
)
|
||||
|
|
@ -429,7 +433,7 @@ or updating already created. Publishing will create OTIO file.
|
|||
continue
|
||||
|
||||
instance = self._make_subset_instance(
|
||||
clip,
|
||||
otio_clip,
|
||||
_fpreset,
|
||||
deepcopy(base_instance_data),
|
||||
parenting_data
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class CollectShotInstance(pyblish.api.InstancePlugin):
|
|||
clip for clip in otio_timeline.each_child(
|
||||
descended_from_type=otio.schema.Clip)
|
||||
if clip.name == otio_clip.name
|
||||
if clip.parent().kind == "Video"
|
||||
]
|
||||
|
||||
otio_clip = clips.pop()
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 63266607ceb972a61484f046634ddfc9eb0b5757
|
||||
Subproject commit a4755d2869694fcf58c98119298cde8d204e2ce4
|
||||
|
|
@ -1,8 +1,6 @@
|
|||
import os
|
||||
|
||||
import click
|
||||
|
||||
from openpype.modules import OpenPypeModule, IHostAddon
|
||||
from openpype.modules import click_wrap, OpenPypeModule, IHostAddon
|
||||
|
||||
WEBPUBLISHER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
|
@ -38,10 +36,10 @@ class WebpublisherAddon(OpenPypeModule, IHostAddon):
|
|||
)
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
@click.group(
|
||||
@click_wrap.group(
|
||||
WebpublisherAddon.name,
|
||||
help="Webpublisher related commands.")
|
||||
def cli_main():
|
||||
|
|
@ -49,10 +47,10 @@ def cli_main():
|
|||
|
||||
|
||||
@cli_main.command()
|
||||
@click.argument("path")
|
||||
@click.option("-u", "--user", help="User email address")
|
||||
@click.option("-p", "--project", help="Project")
|
||||
@click.option("-t", "--targets", help="Targets", default=None,
|
||||
@click_wrap.argument("path")
|
||||
@click_wrap.option("-u", "--user", help="User email address")
|
||||
@click_wrap.option("-p", "--project", help="Project")
|
||||
@click_wrap.option("-t", "--targets", help="Targets", default=None,
|
||||
multiple=True)
|
||||
def publish(project, path, user=None, targets=None):
|
||||
"""Start publishing (Inner command).
|
||||
|
|
@ -67,11 +65,11 @@ def publish(project, path, user=None, targets=None):
|
|||
|
||||
|
||||
@cli_main.command()
|
||||
@click.argument("path")
|
||||
@click.option("-p", "--project", help="Project")
|
||||
@click.option("-h", "--host", help="Host")
|
||||
@click.option("-u", "--user", help="User email address")
|
||||
@click.option("-t", "--targets", help="Targets", default=None,
|
||||
@click_wrap.argument("path")
|
||||
@click_wrap.option("-p", "--project", help="Project")
|
||||
@click_wrap.option("-h", "--host", help="Host")
|
||||
@click_wrap.option("-u", "--user", help="User email address")
|
||||
@click_wrap.option("-t", "--targets", help="Targets", default=None,
|
||||
multiple=True)
|
||||
def publishfromapp(project, path, host, user=None, targets=None):
|
||||
"""Start publishing through application (Inner command).
|
||||
|
|
@ -86,10 +84,10 @@ def publishfromapp(project, path, host, user=None, targets=None):
|
|||
|
||||
|
||||
@cli_main.command()
|
||||
@click.option("-e", "--executable", help="Executable")
|
||||
@click.option("-u", "--upload_dir", help="Upload dir")
|
||||
@click.option("-h", "--host", help="Host", default=None)
|
||||
@click.option("-p", "--port", help="Port", default=None)
|
||||
@click_wrap.option("-e", "--executable", help="Executable")
|
||||
@click_wrap.option("-u", "--upload_dir", help="Upload dir")
|
||||
@click_wrap.option("-h", "--host", help="Host", default=None)
|
||||
@click_wrap.option("-p", "--port", help="Port", default=None)
|
||||
def webserver(executable, upload_dir, host=None, port=None):
|
||||
"""Start service for communication with Webpublish Front end.
|
||||
|
||||
|
|
|
|||
|
|
@ -1227,12 +1227,8 @@ def get_rescaled_command_arguments(
|
|||
target_par = target_par or 1.0
|
||||
input_par = 1.0
|
||||
|
||||
# ffmpeg command
|
||||
input_file_metadata = get_ffprobe_data(input_path, logger=log)
|
||||
stream = input_file_metadata["streams"][0]
|
||||
input_width = int(stream["width"])
|
||||
input_height = int(stream["height"])
|
||||
stream_input_par = stream.get("sample_aspect_ratio")
|
||||
input_height, input_width, stream_input_par = _get_image_dimensions(
|
||||
application, input_path, log)
|
||||
if stream_input_par:
|
||||
input_par = (
|
||||
float(stream_input_par.split(":")[0])
|
||||
|
|
@ -1345,6 +1341,48 @@ def get_rescaled_command_arguments(
|
|||
return command_args
|
||||
|
||||
|
||||
def _get_image_dimensions(application, input_path, log):
|
||||
"""Uses 'ffprobe' first and then 'oiiotool' if available to get dim.
|
||||
|
||||
Args:
|
||||
application (str): "oiiotool"|"ffmpeg"
|
||||
input_path (str): path to image file
|
||||
log (Optional[logging.Logger]): Logger used for logging.
|
||||
Returns:
|
||||
(tuple) (int, int, dict) - (height, width, sample_aspect_ratio)
|
||||
Raises:
|
||||
RuntimeError if image dimensions couldn't be parsed out.
|
||||
"""
|
||||
# ffmpeg command
|
||||
input_file_metadata = get_ffprobe_data(input_path, logger=log)
|
||||
input_width = input_height = 0
|
||||
stream = next(
|
||||
(
|
||||
s for s in input_file_metadata["streams"]
|
||||
if s.get("codec_type") == "video"
|
||||
),
|
||||
{}
|
||||
)
|
||||
if stream:
|
||||
input_width = int(stream["width"])
|
||||
input_height = int(stream["height"])
|
||||
|
||||
# fallback for weird files with width=0, height=0
|
||||
if (input_width == 0 or input_height == 0) and application == "oiiotool":
|
||||
# Load info about file from oiio tool
|
||||
input_info = get_oiio_info_for_input(input_path, logger=log)
|
||||
if input_info:
|
||||
input_width = int(input_info["width"])
|
||||
input_height = int(input_info["height"])
|
||||
|
||||
if input_width == 0 or input_height == 0:
|
||||
raise RuntimeError("Couldn't read {} either "
|
||||
"with ffprobe or oiiotool".format(input_path))
|
||||
|
||||
stream_input_par = stream.get("sample_aspect_ratio")
|
||||
return input_height, input_width, stream_input_par
|
||||
|
||||
|
||||
def convert_color_values(application, color_value):
|
||||
"""Get color mapping for ffmpeg and oiiotool.
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from . import click_wrap
|
||||
from .interfaces import (
|
||||
ILaunchHookPaths,
|
||||
IPluginPaths,
|
||||
|
|
@ -28,6 +29,8 @@ from .base import (
|
|||
|
||||
|
||||
__all__ = (
|
||||
"click_wrap",
|
||||
|
||||
"ILaunchHookPaths",
|
||||
"IPluginPaths",
|
||||
"ITrayModule",
|
||||
|
|
|
|||
365
openpype/modules/click_wrap.py
Normal file
365
openpype/modules/click_wrap.py
Normal file
|
|
@ -0,0 +1,365 @@
|
|||
"""Simplified wrapper for 'click' python module.
|
||||
|
||||
Module 'click' is used as main cli handler in AYON/OpenPype. Addons can
|
||||
register their own subcommands with options. This wrapper allows to define
|
||||
commands and options as with 'click', but without any dependency.
|
||||
|
||||
Why not to use 'click' directly? Version of 'click' used in AYON/OpenPype
|
||||
is not compatible with 'click' version used in some DCCs (e.g. Houdini 20+).
|
||||
And updating 'click' would break other DCCs.
|
||||
|
||||
How to use it? If you already have cli commands defined in addon, just replace
|
||||
'click' with 'click_wrap' and it should work and modify your addon's cli
|
||||
method to convert 'click_wrap' object to 'click' object.
|
||||
|
||||
Before
|
||||
```python
|
||||
import click
|
||||
from openpype.modules import OpenPypeModule
|
||||
|
||||
|
||||
class ExampleAddon(OpenPypeModule):
|
||||
name = "example"
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
|
||||
|
||||
@click.group(ExampleAddon.name, help="Example addon")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
||||
@cli_main.command(help="Example command")
|
||||
@click.option("--arg1", help="Example argument 1", default="default1")
|
||||
@click.option("--arg2", help="Example argument 2", is_flag=True)
|
||||
def mycommand(arg1, arg2):
|
||||
print(arg1, arg2)
|
||||
```
|
||||
|
||||
Now
|
||||
```
|
||||
from openpype import click_wrap
|
||||
from openpype.modules import OpenPypeModule
|
||||
|
||||
|
||||
class ExampleAddon(OpenPypeModule):
|
||||
name = "example"
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
@click_wrap.group(ExampleAddon.name, help="Example addon")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
||||
@cli_main.command(help="Example command")
|
||||
@click_wrap.option("--arg1", help="Example argument 1", default="default1")
|
||||
@click_wrap.option("--arg2", help="Example argument 2", is_flag=True)
|
||||
def mycommand(arg1, arg2):
|
||||
print(arg1, arg2)
|
||||
```
|
||||
|
||||
|
||||
Added small enhancements:
|
||||
- most of the methods can be used as chained calls
|
||||
- functions/methods 'command' and 'group' can be used in a way that
|
||||
first argument is callback function and the rest are arguments
|
||||
for click
|
||||
|
||||
Example:
|
||||
```python
|
||||
from openpype import click_wrap
|
||||
from openpype.modules import OpenPypeModule
|
||||
|
||||
|
||||
class ExampleAddon(OpenPypeModule):
|
||||
name = "example"
|
||||
|
||||
def cli(self, click_group):
|
||||
# Define main command (name 'example')
|
||||
main = click_wrap.group(
|
||||
self._cli_main, name=self.name, help="Example addon"
|
||||
)
|
||||
# Add subcommand (name 'mycommand')
|
||||
(
|
||||
main.command(
|
||||
self._cli_command, name="mycommand", help="Example command"
|
||||
)
|
||||
.option(
|
||||
"--arg1", help="Example argument 1", default="default1"
|
||||
)
|
||||
.option(
|
||||
"--arg2", help="Example argument 2", is_flag=True,
|
||||
)
|
||||
)
|
||||
# Convert main command to click object and add it to parent group
|
||||
click_group.add_command(main.to_click_obj())
|
||||
|
||||
def _cli_main(self):
|
||||
pass
|
||||
|
||||
def _cli_command(self, arg1, arg2):
|
||||
print(arg1, arg2)
|
||||
```
|
||||
|
||||
```shell
|
||||
openpype_console addon example mycommand --arg1 value1 --arg2
|
||||
```
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
FUNC_ATTR_NAME = "__ayon_cli_options__"
|
||||
|
||||
|
||||
class Command(object):
|
||||
def __init__(self, func, *args, **kwargs):
|
||||
# Command function
|
||||
self._func = func
|
||||
# Command definition arguments
|
||||
self._args = args
|
||||
# Command definition kwargs
|
||||
self._kwargs = kwargs
|
||||
# Both 'options' and 'arguments' are stored to the same variable
|
||||
# - keep order of options and arguments
|
||||
self._options = getattr(func, FUNC_ATTR_NAME, [])
|
||||
|
||||
def to_click_obj(self):
|
||||
"""Converts this object to click object.
|
||||
|
||||
Returns:
|
||||
click.Command: Click command object.
|
||||
"""
|
||||
return convert_to_click(self)
|
||||
|
||||
# --- Methods for 'convert_to_click' function ---
|
||||
def get_args(self):
|
||||
"""
|
||||
Returns:
|
||||
tuple: Command definition arguments.
|
||||
"""
|
||||
return self._args
|
||||
|
||||
def get_kwargs(self):
|
||||
"""
|
||||
Returns:
|
||||
dict[str, Any]: Command definition kwargs.
|
||||
"""
|
||||
return self._kwargs
|
||||
|
||||
def get_func(self):
|
||||
"""
|
||||
Returns:
|
||||
Function: Function to invoke on command trigger.
|
||||
"""
|
||||
return self._func
|
||||
|
||||
def iter_options(self):
|
||||
"""
|
||||
Yields:
|
||||
tuple[str, tuple, dict]: Option type name with args and kwargs.
|
||||
"""
|
||||
for item in self._options:
|
||||
yield item
|
||||
# -----------------------------------------------
|
||||
|
||||
def add_option(self, *args, **kwargs):
|
||||
return self.add_option_by_type("option", *args, **kwargs)
|
||||
|
||||
def add_argument(self, *args, **kwargs):
|
||||
return self.add_option_by_type("argument", *args, **kwargs)
|
||||
|
||||
option = add_option
|
||||
argument = add_argument
|
||||
|
||||
def add_option_by_type(self, option_name, *args, **kwargs):
|
||||
self._options.append((option_name, args, kwargs))
|
||||
return self
|
||||
|
||||
|
||||
class Group(Command):
|
||||
def __init__(self, func, *args, **kwargs):
|
||||
super(Group, self).__init__(func, *args, **kwargs)
|
||||
# Store sub-groupd and sub-commands to the same variable
|
||||
self._commands = []
|
||||
|
||||
# --- Methods for 'convert_to_click' function ---
|
||||
def iter_commands(self):
|
||||
for command in self._commands:
|
||||
yield command
|
||||
# -----------------------------------------------
|
||||
|
||||
def add_command(self, command):
|
||||
"""Add prepared command object as child.
|
||||
|
||||
Args:
|
||||
command (Command): Prepared command object.
|
||||
"""
|
||||
if command not in self._commands:
|
||||
self._commands.append(command)
|
||||
|
||||
def add_group(self, group):
|
||||
"""Add prepared group object as child.
|
||||
|
||||
Args:
|
||||
group (Group): Prepared group object.
|
||||
"""
|
||||
if group not in self._commands:
|
||||
self._commands.append(group)
|
||||
|
||||
def command(self, *args, **kwargs):
|
||||
"""Add child command.
|
||||
|
||||
Returns:
|
||||
Union[Command, Function]: New command object, or wrapper function.
|
||||
"""
|
||||
return self._add_new(Command, *args, **kwargs)
|
||||
|
||||
def group(self, *args, **kwargs):
|
||||
"""Add child group.
|
||||
|
||||
Returns:
|
||||
Union[Group, Function]: New group object, or wrapper function.
|
||||
"""
|
||||
return self._add_new(Group, *args, **kwargs)
|
||||
|
||||
def _add_new(self, target_cls, *args, **kwargs):
|
||||
func = None
|
||||
if args and callable(args[0]):
|
||||
args = list(args)
|
||||
func = args.pop(0)
|
||||
args = tuple(args)
|
||||
|
||||
def decorator(_func):
|
||||
out = target_cls(_func, *args, **kwargs)
|
||||
self._commands.append(out)
|
||||
return out
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
return decorator
|
||||
|
||||
|
||||
def convert_to_click(obj_to_convert):
|
||||
"""Convert wrapped object to click object.
|
||||
|
||||
Args:
|
||||
obj_to_convert (Command): Object to convert to click object.
|
||||
|
||||
Returns:
|
||||
click.Command: Click command object.
|
||||
"""
|
||||
import click
|
||||
|
||||
commands_queue = collections.deque()
|
||||
commands_queue.append((obj_to_convert, None))
|
||||
top_obj = None
|
||||
while commands_queue:
|
||||
item = commands_queue.popleft()
|
||||
command_obj, parent_obj = item
|
||||
if not isinstance(command_obj, Command):
|
||||
raise TypeError(
|
||||
"Invalid type '{}' expected 'Command'".format(
|
||||
type(command_obj)
|
||||
)
|
||||
)
|
||||
|
||||
if isinstance(command_obj, Group):
|
||||
click_obj = (
|
||||
click.group(
|
||||
*command_obj.get_args(),
|
||||
**command_obj.get_kwargs()
|
||||
)(command_obj.get_func())
|
||||
)
|
||||
|
||||
else:
|
||||
click_obj = (
|
||||
click.command(
|
||||
*command_obj.get_args(),
|
||||
**command_obj.get_kwargs()
|
||||
)(command_obj.get_func())
|
||||
)
|
||||
|
||||
for item in command_obj.iter_options():
|
||||
option_name, args, kwargs = item
|
||||
if option_name == "option":
|
||||
click.option(*args, **kwargs)(click_obj)
|
||||
elif option_name == "argument":
|
||||
click.argument(*args, **kwargs)(click_obj)
|
||||
else:
|
||||
raise ValueError(
|
||||
"Invalid option name '{}'".format(option_name)
|
||||
)
|
||||
|
||||
if top_obj is None:
|
||||
top_obj = click_obj
|
||||
|
||||
if parent_obj is not None:
|
||||
parent_obj.add_command(click_obj)
|
||||
|
||||
if isinstance(command_obj, Group):
|
||||
for command in command_obj.iter_commands():
|
||||
commands_queue.append((command, click_obj))
|
||||
|
||||
return top_obj
|
||||
|
||||
|
||||
def group(*args, **kwargs):
|
||||
func = None
|
||||
if args and callable(args[0]):
|
||||
args = list(args)
|
||||
func = args.pop(0)
|
||||
args = tuple(args)
|
||||
|
||||
def decorator(_func):
|
||||
return Group(_func, *args, **kwargs)
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
return decorator
|
||||
|
||||
|
||||
def command(*args, **kwargs):
|
||||
func = None
|
||||
if args and callable(args[0]):
|
||||
args = list(args)
|
||||
func = args.pop(0)
|
||||
args = tuple(args)
|
||||
|
||||
def decorator(_func):
|
||||
return Command(_func, *args, **kwargs)
|
||||
|
||||
if func is not None:
|
||||
return decorator(func)
|
||||
return decorator
|
||||
|
||||
|
||||
def argument(*args, **kwargs):
|
||||
def decorator(func):
|
||||
return _add_option_to_func(
|
||||
func, "argument", *args, **kwargs
|
||||
)
|
||||
return decorator
|
||||
|
||||
|
||||
def option(*args, **kwargs):
|
||||
def decorator(func):
|
||||
return _add_option_to_func(
|
||||
func, "option", *args, **kwargs
|
||||
)
|
||||
return decorator
|
||||
|
||||
|
||||
def _add_option_to_func(func, option_name, *args, **kwargs):
|
||||
if isinstance(func, Command):
|
||||
func.add_option_by_type(option_name, *args, **kwargs)
|
||||
return func
|
||||
|
||||
if not hasattr(func, FUNC_ATTR_NAME):
|
||||
setattr(func, FUNC_ATTR_NAME, [])
|
||||
cli_options = getattr(func, FUNC_ATTR_NAME)
|
||||
cli_options.append((option_name, args, kwargs))
|
||||
return func
|
||||
|
|
@ -8,9 +8,9 @@ in global space here until are required or used.
|
|||
"""
|
||||
|
||||
import os
|
||||
import click
|
||||
|
||||
from openpype.modules import (
|
||||
click_wrap,
|
||||
JsonFilesSettingsDef,
|
||||
OpenPypeAddOn,
|
||||
ModulesManager,
|
||||
|
|
@ -115,10 +115,12 @@ class ExampleAddon(OpenPypeAddOn, IPluginPaths, ITrayAction):
|
|||
}
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
@click.group(ExampleAddon.name, help="Example addon dynamic cli commands.")
|
||||
@click_wrap.group(
|
||||
ExampleAddon.name,
|
||||
help="Example addon dynamic cli commands.")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@ import json
|
|||
import collections
|
||||
import platform
|
||||
|
||||
import click
|
||||
|
||||
from openpype.modules import (
|
||||
click_wrap,
|
||||
OpenPypeModule,
|
||||
ITrayModule,
|
||||
IPluginPaths,
|
||||
|
|
@ -489,7 +488,7 @@ class FtrackModule(
|
|||
return cred.get("username"), cred.get("api_key")
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
def _check_ftrack_url(url):
|
||||
|
|
@ -540,24 +539,24 @@ def resolve_ftrack_url(url, logger=None):
|
|||
return ftrack_url
|
||||
|
||||
|
||||
@click.group(FtrackModule.name, help="Ftrack module related commands.")
|
||||
@click_wrap.group(FtrackModule.name, help="Ftrack module related commands.")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
||||
@cli_main.command()
|
||||
@click.option("-d", "--debug", is_flag=True, help="Print debug messages")
|
||||
@click.option("--ftrack-url", envvar="FTRACK_SERVER",
|
||||
@click_wrap.option("-d", "--debug", is_flag=True, help="Print debug messages")
|
||||
@click_wrap.option("--ftrack-url", envvar="FTRACK_SERVER",
|
||||
help="Ftrack server url")
|
||||
@click.option("--ftrack-user", envvar="FTRACK_API_USER",
|
||||
@click_wrap.option("--ftrack-user", envvar="FTRACK_API_USER",
|
||||
help="Ftrack api user")
|
||||
@click.option("--ftrack-api-key", envvar="FTRACK_API_KEY",
|
||||
@click_wrap.option("--ftrack-api-key", envvar="FTRACK_API_KEY",
|
||||
help="Ftrack api key")
|
||||
@click.option("--legacy", is_flag=True,
|
||||
@click_wrap.option("--legacy", is_flag=True,
|
||||
help="run event server without mongo storing")
|
||||
@click.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY",
|
||||
@click_wrap.option("--clockify-api-key", envvar="CLOCKIFY_API_KEY",
|
||||
help="Clockify API key.")
|
||||
@click.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE",
|
||||
@click_wrap.option("--clockify-workspace", envvar="CLOCKIFY_WORKSPACE",
|
||||
help="Clockify workspace")
|
||||
def eventserver(
|
||||
debug,
|
||||
|
|
|
|||
|
|
@ -131,6 +131,8 @@ class PostFtrackHook(PostLaunchHook):
|
|||
for key, value in status_mapping.items():
|
||||
if key in already_tested:
|
||||
continue
|
||||
|
||||
value = value.lower()
|
||||
if actual_status in value or "__any__" in value:
|
||||
if key != "__ignore__":
|
||||
next_status_name = key
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ import json
|
|||
import copy
|
||||
import platform
|
||||
|
||||
import click
|
||||
from openpype.modules import OpenPypeModule
|
||||
from openpype.modules import OpenPypeModule, click_wrap
|
||||
from openpype.settings import get_system_settings
|
||||
|
||||
|
||||
|
|
@ -153,7 +152,7 @@ class JobQueueModule(OpenPypeModule):
|
|||
return requests.get(api_path).json()
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
@classmethod
|
||||
def get_server_url_from_settings(cls):
|
||||
|
|
@ -213,7 +212,7 @@ class JobQueueModule(OpenPypeModule):
|
|||
return main(str(executable), server_url)
|
||||
|
||||
|
||||
@click.group(
|
||||
@click_wrap.group(
|
||||
JobQueueModule.name,
|
||||
help="Application job server. Can be used as render farm."
|
||||
)
|
||||
|
|
@ -225,8 +224,8 @@ def cli_main():
|
|||
"start_server",
|
||||
help="Start server handling workers and their jobs."
|
||||
)
|
||||
@click.option("--port", help="Server port")
|
||||
@click.option("--host", help="Server host (ip address)")
|
||||
@click_wrap.option("--port", help="Server port")
|
||||
@click_wrap.option("--host", help="Server host (ip address)")
|
||||
def cli_start_server(port, host):
|
||||
JobQueueModule.start_server(port, host)
|
||||
|
||||
|
|
@ -236,7 +235,9 @@ def cli_start_server(port, host):
|
|||
"Start a worker for a specific application. (e.g. \"tvpaint/11.5\")"
|
||||
)
|
||||
)
|
||||
@click.argument("app_name")
|
||||
@click.option("--server_url", help="Server url which handle workers and jobs.")
|
||||
@click_wrap.argument("app_name")
|
||||
@click_wrap.option(
|
||||
"--server_url",
|
||||
help="Server url which handle workers and jobs.")
|
||||
def cli_start_worker(app_name, server_url):
|
||||
JobQueueModule.start_worker(app_name, server_url)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
"""Kitsu module."""
|
||||
|
||||
import click
|
||||
import os
|
||||
|
||||
from openpype.modules import (
|
||||
click_wrap,
|
||||
OpenPypeModule,
|
||||
IPluginPaths,
|
||||
ITrayAction,
|
||||
|
|
@ -98,17 +98,17 @@ class KitsuModule(OpenPypeModule, IPluginPaths, ITrayAction):
|
|||
}
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
|
||||
@click.group(KitsuModule.name, help="Kitsu dynamic cli commands.")
|
||||
@click_wrap.group(KitsuModule.name, help="Kitsu dynamic cli commands.")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
||||
@cli_main.command()
|
||||
@click.option("--login", envvar="KITSU_LOGIN", help="Kitsu login")
|
||||
@click.option(
|
||||
@click_wrap.option("--login", envvar="KITSU_LOGIN", help="Kitsu login")
|
||||
@click_wrap.option(
|
||||
"--password", envvar="KITSU_PWD", help="Password for kitsu username"
|
||||
)
|
||||
def push_to_zou(login, password):
|
||||
|
|
@ -124,11 +124,11 @@ def push_to_zou(login, password):
|
|||
|
||||
|
||||
@cli_main.command()
|
||||
@click.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login")
|
||||
@click.option(
|
||||
@click_wrap.option("-l", "--login", envvar="KITSU_LOGIN", help="Kitsu login")
|
||||
@click_wrap.option(
|
||||
"-p", "--password", envvar="KITSU_PWD", help="Password for kitsu username"
|
||||
)
|
||||
@click.option(
|
||||
@click_wrap.option(
|
||||
"-prj",
|
||||
"--project",
|
||||
"projects",
|
||||
|
|
@ -136,7 +136,7 @@ def push_to_zou(login, password):
|
|||
default=[],
|
||||
help="Sync specific kitsu projects",
|
||||
)
|
||||
@click.option(
|
||||
@click_wrap.option(
|
||||
"-lo",
|
||||
"--listen-only",
|
||||
"listen_only",
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import copy
|
|||
import signal
|
||||
from collections import deque, defaultdict
|
||||
|
||||
import click
|
||||
from bson.objectid import ObjectId
|
||||
|
||||
from openpype.client import (
|
||||
|
|
@ -15,7 +14,12 @@ from openpype.client import (
|
|||
get_representations,
|
||||
get_representation_by_id,
|
||||
)
|
||||
from openpype.modules import OpenPypeModule, ITrayModule, IPluginPaths
|
||||
from openpype.modules import (
|
||||
OpenPypeModule,
|
||||
ITrayModule,
|
||||
IPluginPaths,
|
||||
click_wrap,
|
||||
)
|
||||
from openpype.settings import (
|
||||
get_project_settings,
|
||||
get_system_settings,
|
||||
|
|
@ -2405,7 +2409,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule, IPluginPaths):
|
|||
return presets[project_name]['sites'][site_name]['root']
|
||||
|
||||
def cli(self, click_group):
|
||||
click_group.add_command(cli_main)
|
||||
click_group.add_command(cli_main.to_click_obj())
|
||||
|
||||
# Webserver module implementation
|
||||
def webserver_initialization(self, server_manager):
|
||||
|
|
@ -2417,13 +2421,15 @@ class SyncServerModule(OpenPypeModule, ITrayModule, IPluginPaths):
|
|||
)
|
||||
|
||||
|
||||
@click.group(SyncServerModule.name, help="SyncServer module related commands.")
|
||||
@click_wrap.group(
|
||||
SyncServerModule.name,
|
||||
help="SyncServer module related commands.")
|
||||
def cli_main():
|
||||
pass
|
||||
|
||||
|
||||
@cli_main.command()
|
||||
@click.option(
|
||||
@click_wrap.option(
|
||||
"-a",
|
||||
"--active_site",
|
||||
required=True,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class WidgetUserIdle(QtWidgets.QWidget):
|
|||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowMinimizeButtonHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
|
||||
self._is_showed = False
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin):
|
|||
"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
label = "Resources"
|
||||
label = "Validate Resources"
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
|
|
|
|||
|
|
@ -1458,7 +1458,7 @@ class _AyonSettingsCache:
|
|||
|
||||
variant = "production"
|
||||
if is_dev_mode_enabled():
|
||||
variant = cls._get_dev_mode_settings_variant()
|
||||
variant = cls._get_bundle_name()
|
||||
elif is_staging_enabled():
|
||||
variant = "staging"
|
||||
|
||||
|
|
@ -1474,28 +1474,6 @@ class _AyonSettingsCache:
|
|||
def _get_bundle_name(cls):
|
||||
return os.environ["AYON_BUNDLE_NAME"]
|
||||
|
||||
@classmethod
|
||||
def _get_dev_mode_settings_variant(cls):
|
||||
"""Develop mode settings variant.
|
||||
|
||||
Returns:
|
||||
str: Name of settings variant.
|
||||
"""
|
||||
|
||||
con = get_ayon_server_api_connection()
|
||||
bundles = con.get_bundles()
|
||||
user = con.get_user()
|
||||
username = user["name"]
|
||||
for bundle in bundles["bundles"]:
|
||||
if (
|
||||
bundle.get("isDev")
|
||||
and bundle.get("activeUser") == username
|
||||
):
|
||||
return bundle["name"]
|
||||
# Return fake variant - distribution logic will tell user that he
|
||||
# does not have set any dev bundle
|
||||
return "dev"
|
||||
|
||||
@classmethod
|
||||
def get_value_by_project(cls, project_name):
|
||||
cache_item = _AyonSettingsCache.cache_by_project_name[project_name]
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"mark_for_review": true
|
||||
"mark_for_review": true,
|
||||
"force_setting_values": true
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,12 @@
|
|||
"key": "mark_for_review",
|
||||
"label": "Review",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "force_setting_values",
|
||||
"label": "Force resolution and duration values from Asset",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -531,6 +531,9 @@ class FrontendLoaderController(_BaseLoaderController):
|
|||
|
||||
Product types have defined if are checked for filtering or not.
|
||||
|
||||
Args:
|
||||
project_name (Union[str, None]): Project name.
|
||||
|
||||
Returns:
|
||||
list[ProductTypeItem]: List of product type items for a project.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -179,12 +179,15 @@ class ProductsModel:
|
|||
"""Product type items for project.
|
||||
|
||||
Args:
|
||||
project_name (str): Project name.
|
||||
project_name (Union[str, None]): Project name.
|
||||
|
||||
Returns:
|
||||
list[ProductTypeItem]: Product type items.
|
||||
"""
|
||||
|
||||
if not project_name:
|
||||
return []
|
||||
|
||||
cache = self._product_type_items_cache[project_name]
|
||||
if not cache.is_valid:
|
||||
product_types = ayon_api.get_project_product_types(project_name)
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ class LoaderWindow(QtWidgets.QWidget):
|
|||
)
|
||||
|
||||
def refresh(self):
|
||||
self._reset_on_show = False
|
||||
self._controller.reset()
|
||||
|
||||
def showEvent(self, event):
|
||||
|
|
@ -332,6 +333,13 @@ class LoaderWindow(QtWidgets.QWidget):
|
|||
|
||||
self._show_timer.start()
|
||||
|
||||
def closeEvent(self, event):
|
||||
super(LoaderWindow, self).closeEvent(event)
|
||||
# Deselect project so current context will be selected
|
||||
# on next 'showEvent'
|
||||
self._controller.set_selected_project(None)
|
||||
self._reset_on_show = True
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
modifiers = event.modifiers()
|
||||
ctrl_pressed = QtCore.Qt.ControlModifier & modifiers
|
||||
|
|
@ -378,8 +386,7 @@ class LoaderWindow(QtWidgets.QWidget):
|
|||
self._show_timer.stop()
|
||||
|
||||
if self._reset_on_show:
|
||||
self._reset_on_show = False
|
||||
self._controller.reset()
|
||||
self.refresh()
|
||||
|
||||
def _show_group_dialog(self):
|
||||
project_name = self._projects_combobox.get_selected_project_name()
|
||||
|
|
|
|||
|
|
@ -1212,12 +1212,12 @@ class SwitchAssetDialog(QtWidgets.QDialog):
|
|||
))
|
||||
|
||||
version_ids = set()
|
||||
version_docs_by_parent_id = {}
|
||||
version_docs_by_parent_id_and_name = collections.defaultdict(dict)
|
||||
for version_doc in version_docs:
|
||||
parent_id = version_doc["parent"]
|
||||
if parent_id not in version_docs_by_parent_id:
|
||||
version_ids.add(version_doc["_id"])
|
||||
version_docs_by_parent_id[parent_id] = version_doc
|
||||
version_ids.add(version_doc["_id"])
|
||||
product_id = version_doc["parent"]
|
||||
name = version_doc["name"]
|
||||
version_docs_by_parent_id_and_name[product_id][name] = version_doc
|
||||
|
||||
hero_version_docs_by_parent_id = {}
|
||||
for hero_version_doc in hero_version_docs:
|
||||
|
|
@ -1242,7 +1242,7 @@ class SwitchAssetDialog(QtWidgets.QDialog):
|
|||
selected_product_name,
|
||||
selected_representation,
|
||||
product_docs_by_parent_and_name,
|
||||
version_docs_by_parent_id,
|
||||
version_docs_by_parent_id_and_name,
|
||||
hero_version_docs_by_parent_id,
|
||||
repre_docs_by_parent_id_by_name,
|
||||
)
|
||||
|
|
@ -1256,10 +1256,10 @@ class SwitchAssetDialog(QtWidgets.QDialog):
|
|||
container,
|
||||
loader,
|
||||
selected_folder_id,
|
||||
product_name,
|
||||
selected_product_name,
|
||||
selected_representation,
|
||||
product_docs_by_parent_and_name,
|
||||
version_docs_by_parent_id,
|
||||
version_docs_by_parent_id_and_name,
|
||||
hero_version_docs_by_parent_id,
|
||||
repre_docs_by_parent_id_by_name,
|
||||
):
|
||||
|
|
@ -1272,15 +1272,18 @@ class SwitchAssetDialog(QtWidgets.QDialog):
|
|||
|
||||
container_product_id = container_version["parent"]
|
||||
container_product = self._product_docs_by_id[container_product_id]
|
||||
container_product_name = container_product["name"]
|
||||
|
||||
container_folder_id = container_product["parent"]
|
||||
|
||||
if selected_folder_id:
|
||||
folder_id = selected_folder_id
|
||||
else:
|
||||
folder_id = container_product["parent"]
|
||||
folder_id = container_folder_id
|
||||
|
||||
products_by_name = product_docs_by_parent_and_name[folder_id]
|
||||
if product_name:
|
||||
product_doc = products_by_name[product_name]
|
||||
if selected_product_name:
|
||||
product_doc = products_by_name[selected_product_name]
|
||||
else:
|
||||
product_doc = products_by_name[container_product["name"]]
|
||||
|
||||
|
|
@ -1300,7 +1303,26 @@ class SwitchAssetDialog(QtWidgets.QDialog):
|
|||
repre_doc = _repres.get(container_repre_name)
|
||||
|
||||
if not repre_doc:
|
||||
version_doc = version_docs_by_parent_id[product_id]
|
||||
version_docs_by_name = (
|
||||
version_docs_by_parent_id_and_name[product_id]
|
||||
)
|
||||
# If asset or subset are selected for switching, we use latest
|
||||
# version else we try to keep the current container version.
|
||||
version_name = None
|
||||
if (
|
||||
selected_folder_id in (None, container_folder_id)
|
||||
and selected_product_name in (None, container_product_name)
|
||||
):
|
||||
version_name = container_version.get("name")
|
||||
|
||||
version_doc = None
|
||||
if version_name is not None:
|
||||
version_doc = version_docs_by_name.get(version_name)
|
||||
|
||||
if version_doc is None:
|
||||
version_name = max(version_docs_by_name)
|
||||
version_doc = version_docs_by_name[version_name]
|
||||
|
||||
version_id = version_doc["_id"]
|
||||
repres_by_name = repre_docs_by_parent_id_by_name[version_id]
|
||||
if selected_representation:
|
||||
|
|
|
|||
|
|
@ -2329,8 +2329,25 @@ class PublisherController(BasePublisherController):
|
|||
result = pyblish.plugin.process(
|
||||
plugin, self._publish_context, None, action.id
|
||||
)
|
||||
exception = result.get("error")
|
||||
if exception:
|
||||
self._emit_event(
|
||||
"publish.action.failed",
|
||||
{
|
||||
"title": "Action failed",
|
||||
"message": "Action failed.",
|
||||
"traceback": "".join(
|
||||
traceback.format_exception(exception)
|
||||
),
|
||||
"label": action.__name__,
|
||||
"identifier": action.id
|
||||
}
|
||||
)
|
||||
|
||||
self._publish_report.add_action_result(action, result)
|
||||
|
||||
self.emit_card_message("Action finished.")
|
||||
|
||||
def _publish_next_process(self):
|
||||
# Validations of progress before using iterator
|
||||
# - same conditions may be inside iterator but they may be used
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ from .widgets import (
|
|||
)
|
||||
|
||||
|
||||
class PublisherWindow(QtWidgets.QWidget):
|
||||
class PublisherWindow(QtWidgets.QDialog):
|
||||
"""Main window of publisher."""
|
||||
default_width = 1300
|
||||
default_height = 800
|
||||
|
|
@ -50,7 +50,7 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
publish_footer_spacer = 2
|
||||
|
||||
def __init__(self, parent=None, controller=None, reset_on_show=None):
|
||||
super(PublisherWindow, self).__init__()
|
||||
super(PublisherWindow, self).__init__(parent)
|
||||
|
||||
self.setObjectName("PublishWindow")
|
||||
|
||||
|
|
@ -294,12 +294,6 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
controller.event_system.add_callback(
|
||||
"publish.process.stopped", self._on_publish_stop
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.process.instance.changed", self._on_instance_change
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.process.plugin.changed", self._on_plugin_change
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"show.card.message", self._on_overlay_message
|
||||
)
|
||||
|
|
@ -321,6 +315,9 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
controller.event_system.add_callback(
|
||||
"convertors.find.failed", self._on_convertor_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"publish.action.failed", self._on_action_error
|
||||
)
|
||||
controller.event_system.add_callback(
|
||||
"export_report.request", self._export_report
|
||||
)
|
||||
|
|
@ -328,7 +325,6 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
"copy_report.request", self._copy_report
|
||||
)
|
||||
|
||||
|
||||
# Store extra header widget for TrayPublisher
|
||||
# - can be used to add additional widgets to header between context
|
||||
# label and help button
|
||||
|
|
@ -491,8 +487,14 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
app.removeEventFilter(self)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
# Ignore escape button to close window
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
if event.key() in {
|
||||
# Ignore escape button to close window
|
||||
QtCore.Qt.Key_Escape,
|
||||
# Ignore enter keyboard event which by default triggers
|
||||
# first available button in QDialog
|
||||
QtCore.Qt.Key_Enter,
|
||||
QtCore.Qt.Key_Return,
|
||||
}:
|
||||
event.accept()
|
||||
return
|
||||
|
||||
|
|
@ -558,18 +560,6 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
self._reset_on_show = False
|
||||
self.reset()
|
||||
|
||||
def _make_sure_on_top(self):
|
||||
"""Raise window to top and activate it.
|
||||
|
||||
This may not work for some DCCs without Qt.
|
||||
"""
|
||||
|
||||
if not self._window_is_visible:
|
||||
self.show()
|
||||
|
||||
self.setWindowState(QtCore.Qt.WindowActive)
|
||||
self.raise_()
|
||||
|
||||
def _checks_before_save(self, explicit_save):
|
||||
"""Save of changes may trigger some issues.
|
||||
|
||||
|
|
@ -882,12 +872,6 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
if self._is_on_create_tab():
|
||||
self._go_to_publish_tab()
|
||||
|
||||
def _on_instance_change(self):
|
||||
self._make_sure_on_top()
|
||||
|
||||
def _on_plugin_change(self):
|
||||
self._make_sure_on_top()
|
||||
|
||||
def _on_publish_validated_change(self, event):
|
||||
if event["value"]:
|
||||
self._validate_btn.setEnabled(False)
|
||||
|
|
@ -898,7 +882,6 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
self._comment_input.setText("")
|
||||
|
||||
def _on_publish_stop(self):
|
||||
self._make_sure_on_top()
|
||||
self._set_publish_overlay_visibility(False)
|
||||
self._reset_btn.setEnabled(True)
|
||||
self._stop_btn.setEnabled(False)
|
||||
|
|
@ -1012,6 +995,18 @@ class PublisherWindow(QtWidgets.QWidget):
|
|||
event["title"], new_failed_info, "Convertor:"
|
||||
)
|
||||
|
||||
def _on_action_error(self, event):
|
||||
self.add_error_message_dialog(
|
||||
event["title"],
|
||||
[{
|
||||
"message": event["message"],
|
||||
"traceback": event["traceback"],
|
||||
"label": event["label"],
|
||||
"identifier": event["identifier"]
|
||||
}],
|
||||
"Action:"
|
||||
)
|
||||
|
||||
def _update_create_overlay_size(self):
|
||||
metrics = self._create_overlay_button.fontMetrics()
|
||||
height = int(metrics.height())
|
||||
|
|
|
|||
|
|
@ -1299,15 +1299,21 @@ class SwitchAssetDialog(QtWidgets.QDialog):
|
|||
|
||||
# If asset or subset are selected for switching, we use latest
|
||||
# version else we try to keep the current container version.
|
||||
version_name = None
|
||||
if (
|
||||
selected_asset not in (None, container_asset_name)
|
||||
or selected_subset not in (None, container_subset_name)
|
||||
selected_asset in (None, container_asset_name)
|
||||
and selected_subset in (None, container_subset_name)
|
||||
):
|
||||
version_name = max(version_docs_by_name)
|
||||
else:
|
||||
version_name = container_version["name"]
|
||||
version_name = container_version.get("name")
|
||||
|
||||
version_doc = None
|
||||
if version_name is not None:
|
||||
version_doc = version_docs_by_name.get(version_name)
|
||||
|
||||
if version_doc is None:
|
||||
version_name = max(version_docs_by_name)
|
||||
version_doc = version_docs_by_name[version_name]
|
||||
|
||||
version_doc = version_docs_by_name[version_name]
|
||||
version_id = version_doc["_id"]
|
||||
repres_docs_by_name = repre_docs_by_parent_id_by_name[
|
||||
version_id
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ from .lib import (
|
|||
set_style_property,
|
||||
DynamicQThread,
|
||||
qt_app_context,
|
||||
get_qt_app,
|
||||
get_openpype_qt_app,
|
||||
get_asset_icon,
|
||||
get_asset_icon_by_name,
|
||||
|
|
|
|||
|
|
@ -154,11 +154,15 @@ def qt_app_context():
|
|||
yield app
|
||||
|
||||
|
||||
def get_openpype_qt_app():
|
||||
"""Main Qt application initialized for OpenPype processed.
|
||||
def get_qt_app():
|
||||
"""Get Qt application.
|
||||
|
||||
This function should be used only inside OpenPype process and never inside
|
||||
other processes.
|
||||
The function initializes new Qt application if it is not already
|
||||
initialized. It also sets some attributes to the application to
|
||||
ensure that it will work properly on high DPI displays.
|
||||
|
||||
Returns:
|
||||
QtWidgets.QApplication: Current Qt application.
|
||||
"""
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
|
|
@ -184,6 +188,17 @@ def get_openpype_qt_app():
|
|||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def get_openpype_qt_app():
|
||||
"""Main Qt application initialized for OpenPype processed.
|
||||
|
||||
This function should be used only inside OpenPype process and never inside
|
||||
other processes.
|
||||
"""
|
||||
|
||||
app = get_qt_app()
|
||||
app.setWindowIcon(QtGui.QIcon(get_app_icon_path()))
|
||||
return app
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.18.6-nightly.2"
|
||||
__version__ = "3.18.7-nightly.2"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.18.5" # OpenPype
|
||||
version = "3.18.6" # OpenPype
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ class CreateRenderPlugin(BaseSettingsModel):
|
|||
default_factory=list,
|
||||
title="Default Variants"
|
||||
)
|
||||
force_setting_values: bool = SettingsField(
|
||||
True, title="Force resolution and duration values from Asset")
|
||||
|
||||
|
||||
class AfterEffectsCreatorPlugins(BaseSettingsModel):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring addon version."""
|
||||
__version__ = "0.1.2"
|
||||
__version__ = "0.1.3"
|
||||
|
|
|
|||
|
|
@ -1175,30 +1175,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"djvview": {
|
||||
"enabled": true,
|
||||
"label": "DJV View",
|
||||
"icon": "{}/app_icons/djvView.png",
|
||||
"host_name": "",
|
||||
"environment": "{}",
|
||||
"variants": [
|
||||
{
|
||||
"name": "1-1",
|
||||
"label": "1.1",
|
||||
"executables": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"arguments": {
|
||||
"windows": [],
|
||||
"darwin": [],
|
||||
"linux": []
|
||||
},
|
||||
"environment": "{}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"wrap": {
|
||||
"enabled": true,
|
||||
"label": "Wrap",
|
||||
|
|
|
|||
|
|
@ -57,9 +57,9 @@ class CreateSaverPluginModel(BaseSettingsModel):
|
|||
enum_resolver=_create_saver_instance_attributes_enum,
|
||||
title="Instance attributes"
|
||||
)
|
||||
output_formats: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Output formats"
|
||||
image_format: str = SettingsField(
|
||||
enum_resolver=_image_format_enum,
|
||||
title="Output Image Format"
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -90,6 +90,8 @@ class CreateImageSaverModel(CreateSaverPluginModel):
|
|||
0,
|
||||
title="Default rendered frame"
|
||||
)
|
||||
|
||||
|
||||
class CreatPluginsModel(BaseSettingsModel):
|
||||
CreateSaver: CreateSaverModel = SettingsField(
|
||||
default_factory=CreateSaverModel,
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.3"
|
||||
__version__ = "0.1.4"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue