mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 08:24:53 +01:00
Implement FUS-20: Update Fusion sequence loader consistently
This commit is contained in:
parent
354f5a0f4c
commit
a458785019
1 changed files with 168 additions and 19 deletions
|
|
@ -1,5 +1,101 @@
|
||||||
from avalon import api
|
|
||||||
import os
|
import os
|
||||||
|
import contextlib
|
||||||
|
|
||||||
|
from avalon import api
|
||||||
|
import avalon.io as io
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def preserve_inputs(tool, inputs):
|
||||||
|
"""Preserve the tool's inputs after context"""
|
||||||
|
|
||||||
|
comp = tool.Comp()
|
||||||
|
|
||||||
|
values = {}
|
||||||
|
for name in inputs:
|
||||||
|
tool_input = getattr(tool, name)
|
||||||
|
value = tool_input[comp.TIME_UNDEFINED]
|
||||||
|
values[name] = value
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
for name, value in values.items():
|
||||||
|
tool_input = getattr(tool, name)
|
||||||
|
tool_input[comp.TIME_UNDEFINED] = value
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def preserve_trim(loader, log=None):
|
||||||
|
"""Preserve the relative trim of the Loader tool.
|
||||||
|
|
||||||
|
This tries to preserve the loader's trim (trim in and trim out) after
|
||||||
|
the context by reapplying the "amount" it trims on the clip's length at
|
||||||
|
start and end.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Get original trim as amount of "trimming" from length
|
||||||
|
time = loader.Comp().TIME_UNDEFINED
|
||||||
|
length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1
|
||||||
|
trim_from_start = loader["ClipTimeStart"][time]
|
||||||
|
trim_from_end = length - loader["ClipTimeEnd"][time]
|
||||||
|
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
|
||||||
|
length = loader.GetAttrs()["TOOLIT_Clip_Length"][1] - 1
|
||||||
|
if trim_from_start > length:
|
||||||
|
trim_from_start = length
|
||||||
|
if log:
|
||||||
|
log.warning("Reducing trim in to %d "
|
||||||
|
"(because of less frames)" % trim_from_start)
|
||||||
|
|
||||||
|
remainder = length - trim_from_start
|
||||||
|
if trim_from_end > remainder:
|
||||||
|
trim_from_end = remainder
|
||||||
|
if log:
|
||||||
|
log.warning("Reducing trim in to %d "
|
||||||
|
"(because of less frames)" % trim_from_end)
|
||||||
|
|
||||||
|
loader["ClipTimeStart"][time] = trim_from_start
|
||||||
|
loader["ClipTimeEnd"][time] = length - trim_from_end
|
||||||
|
|
||||||
|
|
||||||
|
def loader_shift(loader, frame, relative=True):
|
||||||
|
"""Shift global in time by i preserving duration
|
||||||
|
|
||||||
|
This moves the loader by i frames preserving global duration. When relative
|
||||||
|
is False it will shift the global in to the start frame.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
loader (tool): The fusion loader tool.
|
||||||
|
frame (int): The amount of frames to move.
|
||||||
|
relative (bool): When True the shift is relative, else the shift will
|
||||||
|
change the global in to frame.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
int: The resulting relative frame change (how much it moved)
|
||||||
|
|
||||||
|
"""
|
||||||
|
comp = loader.Comp()
|
||||||
|
time = comp.TIME_UNDEFINED
|
||||||
|
|
||||||
|
if not relative:
|
||||||
|
start = loader["GlobalIn"][time]
|
||||||
|
frame -= start
|
||||||
|
|
||||||
|
# Shifting global in will try to automatically compensate for the change
|
||||||
|
# in the "ClipTimeStart" and "HoldFirstFrame" inputs, so we preserve those
|
||||||
|
# input values to "just shift" the clip
|
||||||
|
with preserve_inputs(loader, inputs=["ClipTimeStart",
|
||||||
|
"ClipTimeEnd",
|
||||||
|
"HoldFirstFrame",
|
||||||
|
"HoldLastFrame"]):
|
||||||
|
loader["GlobalIn"][time] = loader["GlobalIn"][time] + frame
|
||||||
|
|
||||||
|
return int(frame)
|
||||||
|
|
||||||
|
|
||||||
class FusionLoadSequence(api.Loader):
|
class FusionLoadSequence(api.Loader):
|
||||||
|
|
@ -36,46 +132,93 @@ class FusionLoadSequence(api.Loader):
|
||||||
tool = comp.AddTool("Loader", *args)
|
tool = comp.AddTool("Loader", *args)
|
||||||
tool["Clip"] = path
|
tool["Clip"] = path
|
||||||
|
|
||||||
|
# Set global in point to start frame (if in version.data)
|
||||||
|
start = context["version"]["data"].get("startFrame", None)
|
||||||
|
if start is not None:
|
||||||
|
loader_shift(tool, start, relative=False)
|
||||||
|
|
||||||
imprint_container(tool,
|
imprint_container(tool,
|
||||||
name=name,
|
name=name,
|
||||||
namespace=namespace,
|
namespace=namespace,
|
||||||
context=context,
|
context=context,
|
||||||
loader=self.__class__.__name__)
|
loader=self.__class__.__name__)
|
||||||
|
|
||||||
def _get_first_image(self, root):
|
|
||||||
"""Get first file in representation root"""
|
|
||||||
files = sorted(os.listdir(root))
|
|
||||||
return os.path.join(root, files[0])
|
|
||||||
|
|
||||||
def update(self, container, representation):
|
def update(self, container, representation):
|
||||||
"""Update the Loader's path
|
"""Update the Loader's path
|
||||||
|
|
||||||
Fusion automatically tries to reset some variables when changing
|
Fusion automatically tries to reset some variables when changing
|
||||||
the loader's path to a new file. These automatic changes are to its
|
the loader's path to a new file. These automatic changes are to its
|
||||||
inputs:
|
inputs:
|
||||||
- ClipTimeStart (if duration changes)
|
- ClipTimeStart: Fusion reset to 0 if duration changes
|
||||||
- ClipTimeEnd (if duration changes)
|
- We keep the trim in as close as possible to the previous value.
|
||||||
- GlobalIn (if duration changes)
|
When there are less frames then the amount of trim we reduce
|
||||||
- GlobalEnd (if duration changes)
|
it accordingly.
|
||||||
- Reverse (sometimes?)
|
|
||||||
- Loop (sometimes?)
|
- ClipTimeEnd: Fusion reset to 0 if duration changes
|
||||||
- Depth (always resets to "Format")
|
- We keep the trim out as close as possible to the previous value
|
||||||
- KeyCode (always resets to "")
|
within new amount of frames after trim in (ClipTimeStart) has
|
||||||
- TimeCodeOffset (always resets to 0)
|
been set.
|
||||||
|
|
||||||
|
- GlobalIn: Fusion reset to comp's global in if duration changes
|
||||||
|
- We change it to the "startFrame"
|
||||||
|
|
||||||
|
- GlobalEnd: Fusion resets to globalIn + length if duration changes
|
||||||
|
- We do the same like Fusion - allow fusion to take control.
|
||||||
|
|
||||||
|
- HoldFirstFrame: Fusion resets this to 0
|
||||||
|
- We preverse the value.
|
||||||
|
|
||||||
|
- HoldLastFrame: Fusion resets this to 0
|
||||||
|
- We preverse the value.
|
||||||
|
|
||||||
|
- Depth: Fusion resets to "Format"
|
||||||
|
- We preverse the value.
|
||||||
|
|
||||||
|
- KeyCode: Fusion resets to ""
|
||||||
|
- We preverse the value.
|
||||||
|
|
||||||
|
- TimeCodeOffset: Fusion resets to 0
|
||||||
|
- We preverse the value.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from avalon.fusion import comp_lock_and_undo_chunk
|
from avalon.fusion import comp_lock_and_undo_chunk
|
||||||
|
|
||||||
root = api.get_representation_path(representation)
|
|
||||||
path = self._get_first_image(root)
|
|
||||||
|
|
||||||
tool = container["_tool"]
|
tool = container["_tool"]
|
||||||
assert tool.ID == "Loader", "Must be Loader"
|
assert tool.ID == "Loader", "Must be Loader"
|
||||||
comp = tool.Comp()
|
comp = tool.Comp()
|
||||||
|
|
||||||
|
root = api.get_representation_path(representation)
|
||||||
|
path = self._get_first_image(root)
|
||||||
|
|
||||||
|
# Get start frame from version data
|
||||||
|
version = io.find_one({"type": "version",
|
||||||
|
"_id": representation["parent"]})
|
||||||
|
start = version["data"].get("startFrame")
|
||||||
|
if start is None:
|
||||||
|
self.log.warning("Missing start frame for updated version"
|
||||||
|
"assuming starts at frame 0 for: "
|
||||||
|
"{} ({})".format(tool.Name, representation))
|
||||||
|
start = 0
|
||||||
|
|
||||||
with comp_lock_and_undo_chunk(comp, "Update Loader"):
|
with comp_lock_and_undo_chunk(comp, "Update Loader"):
|
||||||
tool["Clip"] = path
|
|
||||||
|
# Update the loader's path whilst preserving some values
|
||||||
|
with preserve_trim(tool, log=self.log):
|
||||||
|
with preserve_inputs(tool,
|
||||||
|
inputs=("HoldFirstFrame",
|
||||||
|
"HoldLastFrame",
|
||||||
|
"Depth",
|
||||||
|
"KeyCode",
|
||||||
|
"TimeCodeOffset")):
|
||||||
|
tool["Clip"] = path
|
||||||
|
|
||||||
|
# Set the global in to the start frame of the sequence
|
||||||
|
global_in_changed = loader_shift(tool, start, relative=False)
|
||||||
|
if global_in_changed:
|
||||||
|
# Log this change to the user
|
||||||
|
self.log.debug("Changed '%s' global in: %d" % (tool.Name,
|
||||||
|
start))
|
||||||
|
|
||||||
# Update the imprinted representation
|
# Update the imprinted representation
|
||||||
tool.SetData("avalon.representation", str(representation["_id"]))
|
tool.SetData("avalon.representation", str(representation["_id"]))
|
||||||
|
|
@ -87,5 +230,11 @@ class FusionLoadSequence(api.Loader):
|
||||||
tool = container["_tool"]
|
tool = container["_tool"]
|
||||||
assert tool.ID == "Loader", "Must be Loader"
|
assert tool.ID == "Loader", "Must be Loader"
|
||||||
comp = tool.Comp()
|
comp = tool.Comp()
|
||||||
|
|
||||||
with comp_lock_and_undo_chunk(comp, "Remove Loader"):
|
with comp_lock_and_undo_chunk(comp, "Remove Loader"):
|
||||||
tool.Delete()
|
tool.Delete()
|
||||||
|
|
||||||
|
def _get_first_image(self, root):
|
||||||
|
"""Get first file in representation root"""
|
||||||
|
files = sorted(os.listdir(root))
|
||||||
|
return os.path.join(root, files[0])
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue