mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
320 lines
9.8 KiB
Python
320 lines
9.8 KiB
Python
import os
|
|
import sys
|
|
import re
|
|
import contextlib
|
|
|
|
from openpype.lib import Logger
|
|
from openpype.client import (
|
|
get_asset_by_name,
|
|
get_subset_by_name,
|
|
get_last_version_by_subset_id,
|
|
get_representation_by_id,
|
|
get_representation_by_name,
|
|
get_representation_parents,
|
|
)
|
|
from openpype.pipeline import (
|
|
switch_container,
|
|
legacy_io,
|
|
)
|
|
from openpype.pipeline.context_tools import get_current_project_asset
|
|
|
|
self = sys.modules[__name__]
|
|
self._project = None
|
|
|
|
|
|
def update_frame_range(start, end, comp=None, set_render_range=True,
|
|
handle_start=0, handle_end=0):
|
|
"""Set Fusion comp's start and end frame range
|
|
|
|
Args:
|
|
start (float, int): start frame
|
|
end (float, int): end frame
|
|
comp (object, Optional): comp object from fusion
|
|
set_render_range (bool, Optional): When True this will also set the
|
|
composition's render start and end frame.
|
|
handle_start (float, int, Optional): frame handles before start frame
|
|
handle_end (float, int, Optional): frame handles after end frame
|
|
|
|
Returns:
|
|
None
|
|
|
|
"""
|
|
|
|
if not comp:
|
|
comp = get_current_comp()
|
|
|
|
# Convert any potential none type to zero
|
|
handle_start = handle_start or 0
|
|
handle_end = handle_end or 0
|
|
|
|
attrs = {
|
|
"COMPN_GlobalStart": start - handle_start,
|
|
"COMPN_GlobalEnd": end + handle_end
|
|
}
|
|
|
|
# set frame range
|
|
if set_render_range:
|
|
attrs.update({
|
|
"COMPN_RenderStart": start,
|
|
"COMPN_RenderEnd": end
|
|
})
|
|
|
|
with comp_lock_and_undo_chunk(comp):
|
|
comp.SetAttrs(attrs)
|
|
|
|
|
|
def set_asset_framerange():
|
|
"""Set Comp's frame range based on current asset"""
|
|
asset_doc = get_current_project_asset()
|
|
start = asset_doc["data"]["frameStart"]
|
|
end = asset_doc["data"]["frameEnd"]
|
|
handle_start = asset_doc["data"]["handleStart"]
|
|
handle_end = asset_doc["data"]["handleEnd"]
|
|
update_frame_range(start, end, set_render_range=True,
|
|
handle_start=handle_start,
|
|
handle_end=handle_end)
|
|
|
|
|
|
def set_asset_resolution():
|
|
"""Set Comp's resolution width x height default based on current asset"""
|
|
asset_doc = get_current_project_asset()
|
|
width = asset_doc["data"]["resolutionWidth"]
|
|
height = asset_doc["data"]["resolutionHeight"]
|
|
comp = get_current_comp()
|
|
|
|
print("Setting comp frame format resolution to {}x{}".format(width,
|
|
height))
|
|
comp.SetPrefs({
|
|
"Comp.FrameFormat.Width": width,
|
|
"Comp.FrameFormat.Height": height,
|
|
})
|
|
|
|
|
|
def validate_comp_prefs(comp=None, force_repair=False):
|
|
"""Validate current comp defaults with asset settings.
|
|
|
|
Validates fps, resolutionWidth, resolutionHeight, aspectRatio.
|
|
|
|
This does *not* validate frameStart, frameEnd, handleStart and handleEnd.
|
|
"""
|
|
|
|
if comp is None:
|
|
comp = get_current_comp()
|
|
|
|
log = Logger.get_logger("validate_comp_prefs")
|
|
|
|
fields = [
|
|
"name",
|
|
"data.fps",
|
|
"data.resolutionWidth",
|
|
"data.resolutionHeight",
|
|
"data.pixelAspect"
|
|
]
|
|
asset_doc = get_current_project_asset(fields=fields)
|
|
asset_data = asset_doc["data"]
|
|
|
|
comp_frame_format_prefs = comp.GetPrefs("Comp.FrameFormat")
|
|
|
|
# Pixel aspect ratio in Fusion is set as AspectX and AspectY so we convert
|
|
# the data to something that is more sensible to Fusion
|
|
asset_data["pixelAspectX"] = asset_data.pop("pixelAspect")
|
|
asset_data["pixelAspectY"] = 1.0
|
|
|
|
validations = [
|
|
("fps", "Rate", "FPS"),
|
|
("resolutionWidth", "Width", "Resolution Width"),
|
|
("resolutionHeight", "Height", "Resolution Height"),
|
|
("pixelAspectX", "AspectX", "Pixel Aspect Ratio X"),
|
|
("pixelAspectY", "AspectY", "Pixel Aspect Ratio Y")
|
|
]
|
|
|
|
invalid = []
|
|
for key, comp_key, label in validations:
|
|
asset_value = asset_data[key]
|
|
comp_value = comp_frame_format_prefs.get(comp_key)
|
|
if asset_value != comp_value:
|
|
invalid_msg = "{} {} should be {}".format(label,
|
|
comp_value,
|
|
asset_value)
|
|
invalid.append(invalid_msg)
|
|
|
|
if not force_repair:
|
|
# Do not log warning if we force repair anyway
|
|
log.warning(
|
|
"Comp {pref} {value} does not match asset "
|
|
"'{asset_name}' {pref} {asset_value}".format(
|
|
pref=label,
|
|
value=comp_value,
|
|
asset_name=asset_doc["name"],
|
|
asset_value=asset_value)
|
|
)
|
|
|
|
if invalid:
|
|
|
|
def _on_repair():
|
|
attributes = dict()
|
|
for key, comp_key, _label in validations:
|
|
value = asset_data[key]
|
|
comp_key_full = "Comp.FrameFormat.{}".format(comp_key)
|
|
attributes[comp_key_full] = value
|
|
comp.SetPrefs(attributes)
|
|
|
|
if force_repair:
|
|
log.info("Applying default Comp preferences..")
|
|
_on_repair()
|
|
return
|
|
|
|
from . import menu
|
|
from openpype.widgets import popup
|
|
from openpype.style import load_stylesheet
|
|
dialog = popup.Popup(parent=menu.menu)
|
|
dialog.setWindowTitle("Fusion comp has invalid configuration")
|
|
|
|
msg = "Comp preferences mismatches '{}'".format(asset_doc["name"])
|
|
msg += "\n" + "\n".join(invalid)
|
|
dialog.setMessage(msg)
|
|
dialog.setButtonText("Repair")
|
|
dialog.on_clicked.connect(_on_repair)
|
|
dialog.show()
|
|
dialog.raise_()
|
|
dialog.activateWindow()
|
|
dialog.setStyleSheet(load_stylesheet())
|
|
|
|
|
|
def switch_item(container,
|
|
asset_name=None,
|
|
subset_name=None,
|
|
representation_name=None):
|
|
"""Switch container asset, subset or representation of a container by name.
|
|
|
|
It'll always switch to the latest version - of course a different
|
|
approach could be implemented.
|
|
|
|
Args:
|
|
container (dict): data of the item to switch with
|
|
asset_name (str): name of the asset
|
|
subset_name (str): name of the subset
|
|
representation_name (str): name of the representation
|
|
|
|
Returns:
|
|
dict
|
|
|
|
"""
|
|
|
|
if all(not x for x in [asset_name, subset_name, representation_name]):
|
|
raise ValueError("Must have at least one change provided to switch.")
|
|
|
|
# Collect any of current asset, subset and representation if not provided
|
|
# so we can use the original name from those.
|
|
project_name = legacy_io.active_project()
|
|
if any(not x for x in [asset_name, subset_name, representation_name]):
|
|
repre_id = container["representation"]
|
|
representation = get_representation_by_id(project_name, repre_id)
|
|
repre_parent_docs = get_representation_parents(representation)
|
|
if repre_parent_docs:
|
|
version, subset, asset, _ = repre_parent_docs
|
|
else:
|
|
version = subset = asset = None
|
|
|
|
if asset_name is None:
|
|
asset_name = asset["name"]
|
|
|
|
if subset_name is None:
|
|
subset_name = subset["name"]
|
|
|
|
if representation_name is None:
|
|
representation_name = representation["name"]
|
|
|
|
# Find the new one
|
|
asset = get_asset_by_name(project_name, asset_name, fields=["_id"])
|
|
assert asset, ("Could not find asset in the database with the name "
|
|
"'%s'" % asset_name)
|
|
|
|
subset = get_subset_by_name(
|
|
project_name, subset_name, asset["_id"], fields=["_id"]
|
|
)
|
|
assert subset, ("Could not find subset in the database with the name "
|
|
"'%s'" % subset_name)
|
|
|
|
version = get_last_version_by_subset_id(
|
|
project_name, subset["_id"], fields=["_id"]
|
|
)
|
|
assert version, "Could not find a version for {}.{}".format(
|
|
asset_name, subset_name
|
|
)
|
|
|
|
representation = get_representation_by_name(
|
|
project_name, representation_name, version["_id"]
|
|
)
|
|
assert representation, ("Could not find representation in the database "
|
|
"with the name '%s'" % representation_name)
|
|
|
|
switch_container(container, representation)
|
|
|
|
return representation
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def maintained_selection():
|
|
comp = get_current_comp()
|
|
previous_selection = comp.GetToolList(True).values()
|
|
try:
|
|
yield
|
|
finally:
|
|
flow = comp.CurrentFrame.FlowView
|
|
flow.Select() # No args equals clearing selection
|
|
if previous_selection:
|
|
for tool in previous_selection:
|
|
flow.Select(tool, True)
|
|
|
|
|
|
def get_frame_path(path):
|
|
"""Get filename for the Fusion Saver with padded number as '#'
|
|
|
|
>>> get_frame_path("C:/test.exr")
|
|
('C:/test', 4, '.exr')
|
|
|
|
>>> get_frame_path("filename.00.tif")
|
|
('filename.', 2, '.tif')
|
|
|
|
>>> get_frame_path("foobar35.tif")
|
|
('foobar', 2, '.tif')
|
|
|
|
Args:
|
|
path (str): The path to render to.
|
|
|
|
Returns:
|
|
tuple: head, padding, tail (extension)
|
|
|
|
"""
|
|
filename, ext = os.path.splitext(path)
|
|
|
|
# Find a final number group
|
|
match = re.match('.*?([0-9]+)$', filename)
|
|
if match:
|
|
padding = len(match.group(1))
|
|
# remove number from end since fusion
|
|
# will swap it with the frame number
|
|
filename = filename[:-padding]
|
|
else:
|
|
padding = 4 # default Fusion padding
|
|
|
|
return filename, padding, ext
|
|
|
|
|
|
def get_current_comp():
|
|
"""Hack to get current comp in this session"""
|
|
fusion = getattr(sys.modules["__main__"], "fusion", None)
|
|
return fusion.CurrentComp if fusion else None
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def comp_lock_and_undo_chunk(comp, undo_queue_name="Script CMD"):
|
|
"""Lock comp and open an undo chunk during the context"""
|
|
try:
|
|
comp.Lock()
|
|
comp.StartUndo(undo_queue_name)
|
|
yield
|
|
finally:
|
|
comp.Unlock()
|
|
comp.EndUndo()
|