Hi,
I’m currently trying out the tk-aftereffects engine and I didn’t find an official hook for the tk-multi-breakdown2 app, so I did my own. I’m sharing it here, as it might be useful for others. I have not yet tested this very extensively (I only tested on frame sequences, not on single files), use at your own risk of course
BTW, how many of you are using the After Effect with SG ?
import os
import re
from tank import Hook
import sgtk
class BreakdownSceneOperations(Hook):
"""
Breakdown operations for After Effects.
"""
ABSTRACT_FRAME_REGEX = re.compile(r".*[\._](%(\d+)d)\..*")
FRAME_REGEX = re.compile(r".+\.(\d+)\..+")
def scan_scene(self):
"""
The scan scene method is executed once at startup and its purpose is
to analyze the current scene and return a list of references that are
to be potentially operated on.
The return data structure is a list of dictionaries. Each scene reference
that is returned should be represented by a dictionary with three keys:
- "node_name": The name of the 'node' that is to be operated on. Most DCCs have
a concept of a node, path or some other way to address a particular
object in the scene.
- "node_type": The object type that this is. This is later passed to the
update method so that it knows how to handle the object.
- "path": Path on disk to the referenced object.
- "extra_data": Optional key to pass some extra data to the update method
in case we'd like to access them when updating the nodes.
Toolkit will scan the list of items, see if any of the objects matches
a published file and try to determine if there is a more recent version
available. Any such versions are then displayed in the UI as out of date.
"""
app = self.parent
engine = app.engine
nodes = []
all_ae_items = app.engine.adobe.app.project.items
footages = []
for item in engine.iter_collection(all_ae_items):
if item.typeName == "Footage":
if item.file.name != "": # this is to exclude solid layers
footages.append(item)
for footage in footages:
path = footage.file.fsName
path = sgtk.util.ShotgunPath.normalize(path)
match = re.search(self.FRAME_REGEX, path)
if match:
path = path.replace(match.group(1), "%0{}d".format(len(match.group(1))) )
extra_data = {"footage_obj": footage}
if engine.find_sequence_range(path):
first_frame, last_frame = engine.find_sequence_range(path)
extra_data["first_frame"] = first_frame
extra_data["last_frame"] = last_frame
node_type = "File sequence"
else:
node_type = "Single file"
nodes.append({"node_name": footage.name,
"node_type": node_type,
"path": path,
"extra_data": extra_data,
})
return nodes
def update(self, item):
"""
Perform replacements given a number of scene items passed from the app.
Once a selection has been performed in the main UI and the user clicks
the update button, this method is called.
:param item: Dictionary on the same form as was generated by the scan_scene hook above.
The path key now holds the path that the node should be updated *to* rather than the current path.
"""
tk = self.parent.sgtk
app = self.parent
engine = app.engine
node_name = item["node_name"]
node_type = item["node_type"]
path = item["path"]
extra_data = item["extra_data"]
footage_obj = extra_data.get("footage_obj")
if node_type == "File sequence":
new_first_frame, new_last_frame = engine.find_sequence_range(path)
match = re.search(self.ABSTRACT_FRAME_REGEX, path)
if match:
padding = int(match.group(2))
ae_path = path.replace(match.group(1), str(new_first_frame).zfill(padding))
new_name = os.path.basename(path).replace(match.group(1), "[%s-%s]" % (str(new_first_frame).zfill(padding), str(new_last_frame).zfill(padding)))
file_obj = engine.adobe.File(ae_path)
footage_obj.replaceWithSequence(file_obj, True)
footage_obj.name = new_name
elif node_type == "Single file":
new_name = os.path.basename(path)
file_obj = engine.adobe.File(path)
footage_obj.replace(file_obj)
footage_obj.name = new_name
Cheers
Donat