mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 12:54:40 +01:00
parent
c3018baa07
commit
180a45ac2e
4 changed files with 188 additions and 23 deletions
|
|
@ -10,6 +10,7 @@ from collections import namedtuple
|
|||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AfterEffectsServerStub():
|
||||
"""
|
||||
Stub for calling function on client (Photoshop js) side.
|
||||
|
|
@ -47,7 +48,11 @@ class AfterEffectsServerStub():
|
|||
return layers_meta.get(str(layer.id))
|
||||
|
||||
def get_metadata(self):
|
||||
layers_data = {}
|
||||
"""
|
||||
Get stored JSON with metadata from AE.Metadata.Label field
|
||||
Returns:
|
||||
(dict)
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_metadata')
|
||||
)
|
||||
|
|
@ -85,7 +90,10 @@ class AfterEffectsServerStub():
|
|||
layers_meta[str(layer.id)] = data
|
||||
# Ensure only valid ids are stored.
|
||||
if not all_layers:
|
||||
all_layers = self.get_items(False)
|
||||
# loaders create FootagetItem now
|
||||
all_layers = self.get_items(comps=True,
|
||||
folders=False,
|
||||
footages=True)
|
||||
item_ids = [int(item.id) for item in all_layers]
|
||||
cleaned_data = {}
|
||||
for id in layers_meta:
|
||||
|
|
@ -103,8 +111,8 @@ class AfterEffectsServerStub():
|
|||
Returns just a name of active document via ws call
|
||||
Returns(string): file name
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_active_document_full_name'))
|
||||
res = self.websocketserver.call(self.client.call(
|
||||
'AfterEffects.get_active_document_full_name'))
|
||||
|
||||
return res
|
||||
|
||||
|
|
@ -113,35 +121,96 @@ class AfterEffectsServerStub():
|
|||
Returns just a name of active document via ws call
|
||||
Returns(string): file name
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_active_document_name'))
|
||||
res = self.websocketserver.call(self.client.call(
|
||||
'AfterEffects.get_active_document_name'))
|
||||
|
||||
return res
|
||||
|
||||
def get_items(self, layers=True):
|
||||
def get_items(self, comps, folders=False, footages=False):
|
||||
"""
|
||||
Get all items from Project panel according to arguments.
|
||||
There are mutliple different types:
|
||||
CompItem (could have multiple layers - source for Creator)
|
||||
FolderItem (collection type, currently not used
|
||||
FootageItem (imported file - created by Loader)
|
||||
Args:
|
||||
comps (bool): return CompItems
|
||||
folders (bool): return FolderItem
|
||||
footages (bool: return FootageItem
|
||||
|
||||
Returns:
|
||||
(list) of namedtuples
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_items',
|
||||
layers=layers)
|
||||
comps=comps,
|
||||
folders=folders,
|
||||
footages=footages)
|
||||
)
|
||||
return self._to_records(res)
|
||||
|
||||
def import_file(self, path, item_name):
|
||||
def get_selected_items(self, comps, folders=False, footages=False):
|
||||
"""
|
||||
Same as get_items but using selected items only
|
||||
Args:
|
||||
comps (bool): return CompItems
|
||||
folders (bool): return FolderItem
|
||||
footages (bool: return FootageItem
|
||||
|
||||
Returns:
|
||||
(list) of namedtuples
|
||||
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_selected_items',
|
||||
comps=comps,
|
||||
folders=folders,
|
||||
footages=footages)
|
||||
)
|
||||
return self._to_records(res)
|
||||
|
||||
def import_file(self, path, item_name, import_options=None):
|
||||
"""
|
||||
Imports file as a FootageItem. Used in Loader
|
||||
Args:
|
||||
path (string): absolute path for asset file
|
||||
item_name (string): label for created FootageItem
|
||||
import_options (dict): different files (img vs psd) need different
|
||||
config
|
||||
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call(
|
||||
'AfterEffects.import_file',
|
||||
path=path,
|
||||
item_name=item_name)
|
||||
item_name=item_name,
|
||||
import_options=import_options)
|
||||
)
|
||||
return self._to_records(res).pop()
|
||||
records = self._to_records(res)
|
||||
if records:
|
||||
return records.pop()
|
||||
|
||||
log.debug("Couldn't import {} file".format(path))
|
||||
|
||||
def replace_item(self, item, path, item_name):
|
||||
""" item is currently comp, might be layer, investigate TODO """
|
||||
""" Replace FootageItem with new file
|
||||
|
||||
Args:
|
||||
item (dict):
|
||||
path (string):absolute path
|
||||
item_name (string): label on item in Project list
|
||||
|
||||
"""
|
||||
self.websocketserver.call(self.client.call
|
||||
('AfterEffects.replace_item',
|
||||
item_id=item.id,
|
||||
path=path, item_name=item_name))
|
||||
|
||||
def delete_item(self, item):
|
||||
""" item is currently comp, might be layer, investigate TODO """
|
||||
""" Deletes FootageItem with new file
|
||||
Args:
|
||||
item (dict):
|
||||
|
||||
"""
|
||||
self.websocketserver.call(self.client.call
|
||||
('AfterEffects.delete_item',
|
||||
item_id=item.id
|
||||
|
|
@ -151,6 +220,20 @@ class AfterEffectsServerStub():
|
|||
# TODO
|
||||
return True
|
||||
|
||||
def set_label_color(self, item_id, color_idx):
|
||||
"""
|
||||
Used for highlight additional information in Project panel.
|
||||
Green color is loaded asset, blue is created asset
|
||||
Args:
|
||||
item_id (int):
|
||||
color_idx (int): 0-16 Label colors from AE Project view
|
||||
"""
|
||||
self.websocketserver.call(self.client.call
|
||||
('AfterEffects.set_label_color',
|
||||
item_id=item_id,
|
||||
color_idx=color_idx
|
||||
))
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Saves active document
|
||||
|
|
|
|||
52
pype/plugins/aftereffects/create/create_render.py
Normal file
52
pype/plugins/aftereffects/create/create_render.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
from avalon import api
|
||||
from avalon.vendor import Qt
|
||||
from avalon import aftereffects
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateRender(api.Creator):
|
||||
"""Render folder for publish."""
|
||||
|
||||
name = "renderDefault"
|
||||
label = "Render"
|
||||
family = "render"
|
||||
|
||||
def process(self):
|
||||
# Photoshop can have multiple LayerSets with the same name, which does
|
||||
# not work with Avalon.
|
||||
txt = "Instance with name \"{}\" already exists.".format(self.name)
|
||||
stub = aftereffects.stub() # only after After Effects is up
|
||||
for layer in stub.get_items(comps=True,
|
||||
folders=False,
|
||||
footages=False):
|
||||
if self.name.lower() == layer.name.lower():
|
||||
msg = Qt.QtWidgets.QMessageBox()
|
||||
msg.setIcon(Qt.QtWidgets.QMessageBox.Warning)
|
||||
msg.setText(txt)
|
||||
msg.exec_()
|
||||
return False
|
||||
log.debug("options:: {}".format(self.options))
|
||||
print("options:: {}".format(self.options))
|
||||
if (self.options or {}).get("useSelection"):
|
||||
log.debug("useSelection")
|
||||
print("useSelection")
|
||||
items = stub.get_selected_items(comps=True,
|
||||
folders=False,
|
||||
footages=False)
|
||||
else:
|
||||
items = stub.get_items(comps=True,
|
||||
folders=False,
|
||||
footages=False)
|
||||
log.debug("items:: {}".format(items))
|
||||
print("items:: {}".format(items))
|
||||
if not items:
|
||||
raise ValueError("Nothing to create. Select composition " +
|
||||
"if 'useSelection' or create at least " +
|
||||
"one composition.")
|
||||
|
||||
for item in items:
|
||||
stub.imprint(item, self.data)
|
||||
stub.set_label_color(item.id, 14) # Cyan options 0 - 16
|
||||
|
|
@ -5,25 +5,55 @@ import re
|
|||
stub = aftereffects.stub()
|
||||
|
||||
|
||||
class ImageLoader(api.Loader):
|
||||
class ResourceLoader(api.Loader):
|
||||
"""Load images
|
||||
|
||||
Stores the imported asset in a container named after the asset.
|
||||
"""
|
||||
|
||||
families = ["image"]
|
||||
families = ["image",
|
||||
"render2d",
|
||||
"source",
|
||||
"plate",
|
||||
"render",
|
||||
"prerender",
|
||||
"review",
|
||||
"preview",
|
||||
"workfile"]
|
||||
representations = ["*"]
|
||||
|
||||
def load(self, context, name=None, namespace=None, data=None):
|
||||
print("Load:::")
|
||||
layer_name = lib.get_unique_layer_name(stub.get_items(False),
|
||||
context["asset"]["name"],
|
||||
name)
|
||||
comp_name = lib.get_unique_layer_name(stub.get_items(comps=True),
|
||||
context["asset"]["name"],
|
||||
name)
|
||||
|
||||
import_options = {}
|
||||
|
||||
file = self.fname
|
||||
|
||||
repr_cont = context["representation"]["context"]
|
||||
if "#" not in file:
|
||||
frame = repr_cont.get("frame")
|
||||
if frame:
|
||||
padding = len(frame)
|
||||
file = file.replace(frame, "#" * padding)
|
||||
import_options['sequence'] = True
|
||||
|
||||
if not file:
|
||||
repr_id = context["representation"]["_id"]
|
||||
self.log.warning(
|
||||
"Representation id `{}` is failing to load".format(repr_id))
|
||||
return
|
||||
|
||||
file = file.replace("\\", "/")
|
||||
if '.psd' in file:
|
||||
import_options['ImportAsType'] = 'ImportAsType.COMP'
|
||||
|
||||
#with photoshop.maintained_selection():
|
||||
comp = stub.import_file(self.fname, layer_name)
|
||||
comp = stub.import_file(self.fname, comp_name, import_options)
|
||||
|
||||
self[:] = [comp]
|
||||
namespace = namespace or layer_name
|
||||
namespace = namespace or comp_name
|
||||
|
||||
return aftereffects.containerise(
|
||||
name,
|
||||
|
|
@ -44,7 +74,7 @@ class ImageLoader(api.Loader):
|
|||
layer_name = "{}_{}".format(context["asset"], context["subset"])
|
||||
# switching assets
|
||||
if namespace_from_container != layer_name:
|
||||
layer_name = lib.get_unique_layer_name(stub.get_items(False),
|
||||
layer_name = lib.get_unique_layer_name(stub.get_items(comps=True),
|
||||
context["asset"],
|
||||
context["subset"])
|
||||
else: # switching version - keep same name
|
||||
|
|
@ -64,7 +94,7 @@ class ImageLoader(api.Loader):
|
|||
"""
|
||||
layer = container.pop("layer")
|
||||
stub.imprint(layer, {})
|
||||
stub.delete_layer(layer.id)
|
||||
stub.delete_item(layer.id)
|
||||
|
||||
def switch(self, container, representation):
|
||||
self.update(container, representation)
|
||||
BIN
pype/resources/app_icons/aftereffects.png
Normal file
BIN
pype/resources/app_icons/aftereffects.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Loading…
Add table
Add a link
Reference in a new issue