mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-02 00:44:52 +01:00
Merge branch 'develop' into enhancement/OP-7120-blender_output-node-exr
This commit is contained in:
commit
1b5d52b168
38 changed files with 205 additions and 269 deletions
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
|
@ -35,6 +35,10 @@ body:
|
|||
label: Version
|
||||
description: What version are you running? Look to OpenPype Tray
|
||||
options:
|
||||
- 3.18.7-nightly.5
|
||||
- 3.18.7-nightly.4
|
||||
- 3.18.7-nightly.3
|
||||
- 3.18.7-nightly.2
|
||||
- 3.18.7-nightly.1
|
||||
- 3.18.6
|
||||
- 3.18.6-nightly.2
|
||||
|
|
@ -131,10 +135,6 @@ body:
|
|||
- 3.15.11-nightly.1
|
||||
- 3.15.10
|
||||
- 3.15.10-nightly.2
|
||||
- 3.15.10-nightly.1
|
||||
- 3.15.9
|
||||
- 3.15.9-nightly.2
|
||||
- 3.15.9-nightly.1
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
|
|||
|
|
@ -1,53 +1,12 @@
|
|||
## How to contribute to Pype
|
||||
## How to contribute to OpenPype
|
||||
|
||||
We are always happy for any contributions for OpenPype improvements. Before making a PR and starting working on an issue, please read these simple guidelines.
|
||||
OpenPype has reached the end of its life and is now in a limited maintenance mode (read more at https://community.ynput.io/t/openpype-end-of-life-timeline/877). As such we're no longer accepting contributions unless they are also ported to AYON at the same time.
|
||||
|
||||
#### **Did you find a bug?**
|
||||
## Getting my PR merged during this period
|
||||
|
||||
1. Check in the issues and our [bug triage[(https://github.com/pypeclub/pype/projects/2) to make sure it wasn't reported already.
|
||||
2. Ask on our [discord](http://pype.community/chat) Often, what appears as a bug, might be the intended behaviour for someone else.
|
||||
3. Create a new issue.
|
||||
4. Use the issue template for you PR please.
|
||||
- Each OpenPype PR MUST have a corresponding AYON PR in github. Without AYON compatibility features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from OpenPype to AYON should be really easy.
|
||||
- Please keep the corresponding OpenPype and AYON PR names the same so they can be easily identified.
|
||||
|
||||
Inside each PR, put a link to the corresponding PR from the other product. OpenPype PRs should point to AYON PR and vice versa.
|
||||
|
||||
#### **Did you write a patch that fixes a bug?**
|
||||
|
||||
- Open a new GitHub pull request with the patch.
|
||||
- Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
||||
|
||||
#### **Do you intend to add a new feature or change an existing one?**
|
||||
|
||||
- Open a new thread in the [github discussions](https://github.com/pypeclub/pype/discussions/new)
|
||||
- Do not open issue until the suggestion is discussed. We will convert accepted suggestions into backlog and point them to the relevant discussion thread to keep the context.
|
||||
- If you are already working on a new feature and you'd like it eventually merged to the main codebase, please consider making a DRAFT PR as soon as possible. This makes it a lot easier to give feedback, discuss the code and functionalit, plus it prevents multiple people tackling the same problem independently.
|
||||
|
||||
#### **Do you have questions about the source code?**
|
||||
|
||||
Open a new question on [github discussions](https://github.com/pypeclub/pype/discussions/new)
|
||||
|
||||
## Branching Strategy
|
||||
|
||||
As we move to 3.x as the primary supported version of pype and only keep 2.15 on bug bugfixes and client sponsored feature requests, we need to be very careful with merging strategy.
|
||||
|
||||
We also use this opportunity to switch the branch naming. 3.0 production branch will no longer be called MASTER, but will be renamed to MAIN. Develop will stay as it is.
|
||||
|
||||
A few important notes about 2.x and 3.x development:
|
||||
|
||||
- 3.x features are not backported to 2.x unless specifically requested
|
||||
- 3.x bugs and hotfixes can be ported to 2.x if they are relevant or severe
|
||||
- 2.x features and bugs MUST be ported to 3.x at the same time
|
||||
|
||||
## Pull Requests
|
||||
|
||||
- Each 2.x PR MUST have a corresponding 3.x PR in github. Without 3.x PR, 2.x features will not be merged! Luckily most of the code is compatible, albeit sometimes in a different place after refactor. Porting from 2.x to 3.x should be really easy.
|
||||
- Please keep the corresponding 2 and 3 PR names the same so they can be easily identified from the PR list page.
|
||||
- Each 2.x PR should be labeled with `2.x-dev` label.
|
||||
|
||||
Inside each PR, put a link to the corresponding PR for the other version
|
||||
|
||||
Of course if you want to contribute, feel free to make a PR to only 2.x/develop or develop, based on what you are using. While reviewing the PRs, we might convert the code to corresponding PR for the other release ourselves.
|
||||
|
||||
We might also change the target of you PR to and intermediate branch, rather than `develop` if we feel it requires some extra work on our end. That way, we preserve all your commits so you don't loose out on the contribution credits.
|
||||
|
||||
|
||||
If a PR is targeted at 2.x release it must be labelled with 2x-dev label in Github.
|
||||
AYON repository structure is a lot more granular compared to OpenPype. If you're unsure what repository your AYON equivalent PR should target, feel free to make OpenPype PR first and ask.
|
||||
|
|
|
|||
|
|
@ -9,8 +9,13 @@ OpenPype
|
|||
|
||||
## Important Notice!
|
||||
|
||||
OpenPype as a standalone product has reach end of it's life and this repository is now used as a pipeline core code for [AYON](https://ynput.io/ayon/). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877
|
||||
OpenPype as a standalone product has reach end of it's life and this repository is now being phased out in favour of [ayon-core](https://github.com/ynput/ayon-core). You can read more details about the end of life process here https://community.ynput.io/t/openpype-end-of-life-timeline/877
|
||||
|
||||
As such, we no longer accept Pull Requests that are not ported to AYON at the same time!
|
||||
|
||||
```
|
||||
Please refer to https://github.com/ynput/OpenPype/blob/develop/CONTRIBUTING.md for more information about the current PR process.
|
||||
```
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -59,10 +59,11 @@ class MaxHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
|
|||
|
||||
rt.callbacks.addScript(rt.Name('filePostOpen'),
|
||||
lib.check_colorspace)
|
||||
rt.callbacks.addScript(rt.Name('postWorkspaceChange'),
|
||||
self._deferred_menu_creation)
|
||||
|
||||
def has_unsaved_changes(self):
|
||||
# TODO: how to get it from 3dsmax?
|
||||
return True
|
||||
def workfile_has_unsaved_changes(self):
|
||||
return rt.getSaveRequired()
|
||||
|
||||
def get_workfile_extensions(self):
|
||||
return [".max"]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import pyblish.api
|
||||
import os
|
||||
from openpype.pipeline import registered_host
|
||||
|
||||
|
||||
class SaveCurrentScene(pyblish.api.ContextPlugin):
|
||||
"""Save current scene
|
||||
|
||||
"""
|
||||
"""Save current scene"""
|
||||
|
||||
label = "Save current file"
|
||||
order = pyblish.api.ExtractorOrder - 0.49
|
||||
|
|
@ -13,9 +11,13 @@ class SaveCurrentScene(pyblish.api.ContextPlugin):
|
|||
families = ["maxrender", "workfile"]
|
||||
|
||||
def process(self, context):
|
||||
from pymxs import runtime as rt
|
||||
folder = rt.maxFilePath
|
||||
file = rt.maxFileName
|
||||
current = os.path.join(folder, file)
|
||||
assert context.data["currentFile"] == current
|
||||
rt.saveMaxFile(current)
|
||||
host = registered_host()
|
||||
current_file = host.get_current_workfile()
|
||||
|
||||
assert context.data["currentFile"] == current_file
|
||||
|
||||
if host.workfile_has_unsaved_changes():
|
||||
self.log.info(f"Saving current file: {current_file}")
|
||||
host.save_workfile(current_file)
|
||||
else:
|
||||
self.log.debug("No unsaved changes, skipping file save..")
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
|
||||
local pythonpath = systemTools.getEnvVariable "MAX_PYTHONPATH"
|
||||
systemTools.setEnvVariable "PYTHONPATH" pythonpath
|
||||
|
||||
/*opens the create menu on startup to ensure users are presented with a useful default view.*/
|
||||
max create mode
|
||||
|
||||
python.ExecuteFile startup
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -3142,119 +3142,6 @@ def fix_incompatible_containers():
|
|||
"ReferenceLoader", type="string")
|
||||
|
||||
|
||||
def _null(*args):
|
||||
pass
|
||||
|
||||
|
||||
class shelf():
|
||||
'''A simple class to build shelves in maya. Since the build method is empty,
|
||||
it should be extended by the derived class to build the necessary shelf
|
||||
elements. By default it creates an empty shelf called "customShelf".'''
|
||||
|
||||
###########################################################################
|
||||
'''This is an example shelf.'''
|
||||
# class customShelf(_shelf):
|
||||
# def build(self):
|
||||
# self.addButon(label="button1")
|
||||
# self.addButon("button2")
|
||||
# self.addButon("popup")
|
||||
# p = cmds.popupMenu(b=1)
|
||||
# self.addMenuItem(p, "popupMenuItem1")
|
||||
# self.addMenuItem(p, "popupMenuItem2")
|
||||
# sub = self.addSubMenu(p, "subMenuLevel1")
|
||||
# self.addMenuItem(sub, "subMenuLevel1Item1")
|
||||
# sub2 = self.addSubMenu(sub, "subMenuLevel2")
|
||||
# self.addMenuItem(sub2, "subMenuLevel2Item1")
|
||||
# self.addMenuItem(sub2, "subMenuLevel2Item2")
|
||||
# self.addMenuItem(sub, "subMenuLevel1Item2")
|
||||
# self.addMenuItem(p, "popupMenuItem3")
|
||||
# self.addButon("button3")
|
||||
# customShelf()
|
||||
###########################################################################
|
||||
|
||||
def __init__(self, name="customShelf", iconPath="", preset={}):
|
||||
self.name = name
|
||||
|
||||
self.iconPath = iconPath
|
||||
|
||||
self.labelBackground = (0, 0, 0, 0)
|
||||
self.labelColour = (.9, .9, .9)
|
||||
|
||||
self.preset = preset
|
||||
|
||||
self._cleanOldShelf()
|
||||
cmds.setParent(self.name)
|
||||
self.build()
|
||||
|
||||
def build(self):
|
||||
'''This method should be overwritten in derived classes to actually
|
||||
build the shelf elements. Otherwise, nothing is added to the shelf.'''
|
||||
for item in self.preset['items']:
|
||||
if not item.get('command'):
|
||||
item['command'] = self._null
|
||||
if item['type'] == 'button':
|
||||
self.addButon(item['name'],
|
||||
command=item['command'],
|
||||
icon=item['icon'])
|
||||
if item['type'] == 'menuItem':
|
||||
self.addMenuItem(item['parent'],
|
||||
item['name'],
|
||||
command=item['command'],
|
||||
icon=item['icon'])
|
||||
if item['type'] == 'subMenu':
|
||||
self.addMenuItem(item['parent'],
|
||||
item['name'],
|
||||
command=item['command'],
|
||||
icon=item['icon'])
|
||||
|
||||
def addButon(self, label, icon="commandButton.png",
|
||||
command=_null, doubleCommand=_null):
|
||||
'''
|
||||
Adds a shelf button with the specified label, command,
|
||||
double click command and image.
|
||||
'''
|
||||
cmds.setParent(self.name)
|
||||
if icon:
|
||||
icon = os.path.join(self.iconPath, icon)
|
||||
print(icon)
|
||||
cmds.shelfButton(width=37, height=37, image=icon, label=label,
|
||||
command=command, dcc=doubleCommand,
|
||||
imageOverlayLabel=label, olb=self.labelBackground,
|
||||
olc=self.labelColour)
|
||||
|
||||
def addMenuItem(self, parent, label, command=_null, icon=""):
|
||||
'''
|
||||
Adds a shelf button with the specified label, command,
|
||||
double click command and image.
|
||||
'''
|
||||
if icon:
|
||||
icon = os.path.join(self.iconPath, icon)
|
||||
print(icon)
|
||||
return cmds.menuItem(p=parent, label=label, c=command, i="")
|
||||
|
||||
def addSubMenu(self, parent, label, icon=None):
|
||||
'''
|
||||
Adds a sub menu item with the specified label and icon to
|
||||
the specified parent popup menu.
|
||||
'''
|
||||
if icon:
|
||||
icon = os.path.join(self.iconPath, icon)
|
||||
print(icon)
|
||||
return cmds.menuItem(p=parent, label=label, i=icon, subMenu=1)
|
||||
|
||||
def _cleanOldShelf(self):
|
||||
'''
|
||||
Checks if the shelf exists and empties it if it does
|
||||
or creates it if it does not.
|
||||
'''
|
||||
if cmds.shelfLayout(self.name, ex=1):
|
||||
if cmds.shelfLayout(self.name, q=1, ca=1):
|
||||
for each in cmds.shelfLayout(self.name, q=1, ca=1):
|
||||
cmds.deleteUI(each)
|
||||
else:
|
||||
cmds.shelfLayout(self.name, p="ShelfLayout")
|
||||
|
||||
|
||||
def update_content_on_context_change():
|
||||
"""
|
||||
This will update scene content to match new asset on context change
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from openpype.hosts.maya.api import lib
|
|||
from openpype.pipeline.publish import (
|
||||
RepairAction,
|
||||
ValidateContentsOrder,
|
||||
PublishValidationError
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -38,7 +39,8 @@ class ValidateRigJointsHidden(pyblish.api.InstancePlugin):
|
|||
invalid = self.get_invalid(instance)
|
||||
|
||||
if invalid:
|
||||
raise ValueError("Visible joints found: {0}".format(invalid))
|
||||
raise PublishValidationError(
|
||||
"Visible joints found: {0}".format(invalid))
|
||||
|
||||
@classmethod
|
||||
def repair(cls, instance):
|
||||
|
|
|
|||
|
|
@ -46,24 +46,5 @@ if bool(int(os.environ.get(key, "0"))):
|
|||
lowestPriority=True
|
||||
)
|
||||
|
||||
# Build a shelf.
|
||||
shelf_preset = settings['maya'].get('project_shelf')
|
||||
if shelf_preset:
|
||||
icon_path = os.path.join(
|
||||
os.environ['OPENPYPE_PROJECT_SCRIPTS'],
|
||||
project_name,
|
||||
"icons")
|
||||
icon_path = os.path.abspath(icon_path)
|
||||
|
||||
for i in shelf_preset['imports']:
|
||||
import_string = "from {} import {}".format(project_name, i)
|
||||
print(import_string)
|
||||
exec(import_string)
|
||||
|
||||
cmds.evalDeferred(
|
||||
"mlib.shelf(name=shelf_preset['name'], iconPath=icon_path,"
|
||||
" preset=shelf_preset)"
|
||||
)
|
||||
|
||||
|
||||
print("Finished OpenPype usersetup.")
|
||||
|
|
|
|||
|
|
@ -1348,7 +1348,9 @@ def _remove_old_knobs(node):
|
|||
|
||||
|
||||
def exposed_write_knobs(settings, plugin_name, instance_node):
|
||||
exposed_knobs = settings["nuke"]["create"][plugin_name]["exposed_knobs"]
|
||||
exposed_knobs = settings["nuke"]["create"][plugin_name].get(
|
||||
"exposed_knobs", []
|
||||
)
|
||||
if exposed_knobs:
|
||||
instance_node.addKnob(nuke.Text_Knob('', 'Write Knobs'))
|
||||
write_node = nuke.allNodes(group=instance_node, filter="Write")[0]
|
||||
|
|
|
|||
|
|
@ -112,8 +112,6 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
project_name = get_current_project_name()
|
||||
version_doc = get_version_by_id(project_name, representation["parent"])
|
||||
|
||||
object_name = container["node"]
|
||||
|
||||
# get main variables
|
||||
version_data = version_doc.get("data", {})
|
||||
vname = version_doc.get("name", None)
|
||||
|
|
@ -139,7 +137,7 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
file = get_representation_path(representation).replace("\\", "/")
|
||||
|
||||
with maintained_selection():
|
||||
camera_node = nuke.toNode(object_name)
|
||||
camera_node = container["node"]
|
||||
camera_node['selected'].setValue(True)
|
||||
|
||||
# collect input output dependencies
|
||||
|
|
@ -154,9 +152,10 @@ class AlembicCameraLoader(load.LoaderPlugin):
|
|||
xpos = camera_node.xpos()
|
||||
ypos = camera_node.ypos()
|
||||
nuke.nodeCopy("%clipboard%")
|
||||
camera_name = camera_node.name()
|
||||
nuke.delete(camera_node)
|
||||
nuke.nodePaste("%clipboard%")
|
||||
camera_node = nuke.toNode(object_name)
|
||||
camera_node = nuke.toNode(camera_name)
|
||||
camera_node.setXYpos(xpos, ypos)
|
||||
|
||||
# link to original input nodes
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class ValidateExposedKnobs(
|
|||
group_node = instance.data["transientData"]["node"]
|
||||
nuke_settings = instance.context.data["project_settings"]["nuke"]
|
||||
create_settings = nuke_settings["create"][plugin]
|
||||
exposed_knobs = create_settings["exposed_knobs"]
|
||||
exposed_knobs = create_settings.get("exposed_knobs", [])
|
||||
unexposed_knobs = []
|
||||
for knob in exposed_knobs:
|
||||
if knob not in group_node.knobs():
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ class ValidateNukeWriteNode(
|
|||
and key != "file"
|
||||
and key != "tile_color"
|
||||
):
|
||||
check.append([key, node_value, write_node[key].value()])
|
||||
check.append([key, fixed_values, write_node[key].value()])
|
||||
|
||||
if check:
|
||||
self._make_error(check)
|
||||
|
|
@ -137,7 +137,7 @@ class ValidateNukeWriteNode(
|
|||
def _make_error(self, check):
|
||||
# sourcery skip: merge-assign-and-aug-assign, move-assign-in-block
|
||||
dbg_msg = "Write node's knobs values are not correct!\n"
|
||||
msg_add = "Knob '{0}' > Correct: `{1}` > Wrong: `{2}`"
|
||||
msg_add = "Knob '{0}' > Expected: `{1}` > Current: `{2}`"
|
||||
|
||||
details = [
|
||||
msg_add.format(item[0], item[1], item[2])
|
||||
|
|
|
|||
|
|
@ -713,6 +713,11 @@ def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame):
|
|||
bool: True if successfully replaced
|
||||
|
||||
"""
|
||||
# copy ACES input transform from timeline clip to new media item
|
||||
mediapool_item_from_timeline = from_clip.GetMediaPoolItem()
|
||||
_idt = mediapool_item_from_timeline.GetClipProperty('IDT')
|
||||
to_clip.SetClipProperty('IDT', _idt)
|
||||
|
||||
_clip_prop = to_clip.GetClipProperty
|
||||
to_clip_name = _clip_prop("File Name")
|
||||
# add clip item as take to timeline
|
||||
|
|
|
|||
|
|
@ -477,14 +477,16 @@ class ClipLoader:
|
|||
)
|
||||
_clip_property = media_pool_item.GetClipProperty
|
||||
|
||||
source_in = int(_clip_property("Start"))
|
||||
source_out = int(_clip_property("End"))
|
||||
# Read trimming from timeline item
|
||||
timeline_item_in = timeline_item.GetLeftOffset()
|
||||
timeline_item_len = timeline_item.GetDuration()
|
||||
timeline_item_out = timeline_item_in + timeline_item_len
|
||||
|
||||
lib.swap_clips(
|
||||
timeline_item,
|
||||
media_pool_item,
|
||||
source_in,
|
||||
source_out
|
||||
timeline_item_in,
|
||||
timeline_item_out
|
||||
)
|
||||
|
||||
print("Loading clips: `{}`".format(self.data["clip_name"]))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -132,7 +132,7 @@ class PostFtrackHook(PostLaunchHook):
|
|||
if key in already_tested:
|
||||
continue
|
||||
|
||||
value = value.lower()
|
||||
value = [i.lower() for i in value]
|
||||
if actual_status in value or "__any__" in value:
|
||||
if key != "__ignore__":
|
||||
next_status_name = key
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class WidgetUserIdle(QtWidgets.QWidget):
|
|||
self.setWindowFlags(
|
||||
QtCore.Qt.WindowCloseButtonHint
|
||||
| QtCore.Qt.WindowMinimizeButtonHint
|
||||
| QtCore.Qt.WindowStaysOnTopHint
|
||||
)
|
||||
|
||||
self._is_showed = False
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import copy
|
|||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
import pyblish.api
|
||||
from openpype.lib import (
|
||||
|
|
@ -14,9 +15,10 @@ from openpype.lib import (
|
|||
path_to_subprocess_arg,
|
||||
run_subprocess,
|
||||
)
|
||||
from openpype.lib.transcoding import convert_colorspace
|
||||
|
||||
from openpype.lib.transcoding import VIDEO_EXTENSIONS
|
||||
from openpype.lib.transcoding import (
|
||||
convert_colorspace,
|
||||
VIDEO_EXTENSIONS,
|
||||
)
|
||||
|
||||
|
||||
class ExtractThumbnail(pyblish.api.InstancePlugin):
|
||||
|
|
@ -35,6 +37,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
"traypublisher",
|
||||
"substancepainter",
|
||||
"nuke",
|
||||
"aftereffects"
|
||||
]
|
||||
enabled = False
|
||||
|
||||
|
|
@ -49,6 +52,8 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
# attribute presets from settings
|
||||
oiiotool_defaults = None
|
||||
ffmpeg_args = None
|
||||
subsets = []
|
||||
product_names = []
|
||||
|
||||
def process(self, instance):
|
||||
# run main process
|
||||
|
|
@ -103,6 +108,26 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
|
|||
self.log.debug("Skipping crypto passes.")
|
||||
return
|
||||
|
||||
# We only want to process the subsets needed from settings.
|
||||
def validate_string_against_patterns(input_str, patterns):
|
||||
for pattern in patterns:
|
||||
if re.match(pattern, input_str):
|
||||
return True
|
||||
return False
|
||||
|
||||
product_names = self.subsets + self.product_names
|
||||
if product_names:
|
||||
result = validate_string_against_patterns(
|
||||
instance.data["subset"], product_names
|
||||
)
|
||||
if not result:
|
||||
self.log.debug(
|
||||
"Subset \"{}\" did not match any valid subsets: {}".format(
|
||||
instance.data["subset"], product_names
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# first check for any explicitly marked representations for thumbnail
|
||||
explicit_repres = self._get_explicit_repres_for_thumbnail(instance)
|
||||
if explicit_repres:
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class ValidateResources(pyblish.api.InstancePlugin):
|
|||
"""
|
||||
|
||||
order = ValidateContentsOrder
|
||||
label = "Resources"
|
||||
label = "Validate Resources"
|
||||
|
||||
def process(self, instance):
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,8 @@
|
|||
"default_variants": [
|
||||
"Main"
|
||||
],
|
||||
"mark_for_review": true
|
||||
"mark_for_review": true,
|
||||
"force_setting_values": true
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@
|
|||
},
|
||||
"ExtractThumbnail": {
|
||||
"enabled": true,
|
||||
"subsets": [],
|
||||
"integrate_thumbnail": false,
|
||||
"background_color": [
|
||||
0,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,6 +202,12 @@
|
|||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"object_type": "text",
|
||||
"key": "subsets",
|
||||
"label": "Subsets"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "integrate_thumbnail",
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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.7-nightly.1"
|
||||
__version__ = "3.18.7-nightly.5"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -176,6 +176,10 @@ class ExtractThumbnailOIIODefaultsModel(BaseSettingsModel):
|
|||
class ExtractThumbnailModel(BaseSettingsModel):
|
||||
_isGroup = True
|
||||
enabled: bool = SettingsField(True)
|
||||
product_names: list[str] = SettingsField(
|
||||
default_factory=list,
|
||||
title="Product names"
|
||||
)
|
||||
integrate_thumbnail: bool = SettingsField(
|
||||
True,
|
||||
title="Integrate Thumbnail Representation"
|
||||
|
|
@ -844,6 +848,7 @@ DEFAULT_PUBLISH_VALUES = {
|
|||
},
|
||||
"ExtractThumbnail": {
|
||||
"enabled": True,
|
||||
"product_names": [],
|
||||
"integrate_thumbnail": True,
|
||||
"target_size": {
|
||||
"type": "source"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.4"
|
||||
__version__ = "0.1.5"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue