Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
#663 - AfterEffects init commit
Browse files Browse the repository at this point in the history
  • Loading branch information
kalisp committed Oct 24, 2020
1 parent 4315d2f commit 1d2973c
Show file tree
Hide file tree
Showing 3 changed files with 246 additions and 0 deletions.
74 changes: 74 additions & 0 deletions pype/hosts/aftereffects/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import os
import sys

from avalon import api, io
from avalon.vendor import Qt
from pype import lib
import pyblish.api


def check_inventory():
if not lib.any_outdated():
return

host = api.registered_host()
outdated_containers = []
for container in host.ls():
representation = container['representation']
representation_doc = io.find_one(
{
"_id": io.ObjectId(representation),
"type": "representation"
},
projection={"parent": True}
)
if representation_doc and not lib.is_latest(representation_doc):
outdated_containers.append(container)

# Warn about outdated containers.
print("Starting new QApplication..")
app = Qt.QtWidgets.QApplication(sys.argv)

message_box = Qt.QtWidgets.QMessageBox()
message_box.setIcon(Qt.QtWidgets.QMessageBox.Warning)
msg = "There are outdated containers in the scene."
message_box.setText(msg)
message_box.exec_()

# Garbage collect QApplication.
del app


def application_launch():
check_inventory()


def install():
print("Installing Pype config...")

plugins_directory = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
"plugins",
"aftereffects"
)

pyblish.api.register_plugin_path(
os.path.join(plugins_directory, "publish")
)
api.register_plugin_path(
api.Loader, os.path.join(plugins_directory, "load")
)
api.register_plugin_path(
api.Creator, os.path.join(plugins_directory, "create")
)

pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
)

api.on("application.launched", application_launch)


def on_pyblish_instance_toggled(instance, old_value, new_value):
"""Toggle layer visibility on instance toggles."""
instance[0].Visible = new_value
64 changes: 64 additions & 0 deletions pype/modules/websocket_server/hosts/aftereffects.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
from pype.api import Logger
from wsrpc_aiohttp import WebSocketRoute
import functools

import avalon.aftereffects as aftereffects

log = Logger().get_logger("WebsocketServer")


class AfterEffects(WebSocketRoute):
"""
One route, mimicking external application (like Harmony, etc).
All functions could be called from client.
'do_notify' function calls function on the client - mimicking
notification after long running job on the server or similar
"""
instance = None

def init(self, **kwargs):
# Python __init__ must be return "self".
# This method might return anything.
log.debug("someone called AfterEffects route")
self.instance = self
return kwargs

# server functions
async def ping(self):
log.debug("someone called AfterEffects route ping")

# This method calls function on the client side
# client functions

async def read(self):
log.debug("aftereffects.read client calls server server calls "
"aftereffects client")
return await self.socket.call('aftereffects.read')

# panel routes for tools
async def creator_route(self):
self._tool_route("creator")

async def workfiles_route(self):
self._tool_route("workfiles")

async def loader_route(self):
self._tool_route("loader")

async def publish_route(self):
self._tool_route("publish")

async def sceneinventory_route(self):
self._tool_route("sceneinventory")

async def projectmanager_route(self):
self._tool_route("projectmanager")

def _tool_route(self, tool_name):
"""The address accessed when clicking on the buttons."""
partial_method = functools.partial(aftereffects.show, tool_name)

aftereffects.execute_in_main_thread(partial_method)

# Required return statement.
return "nothing"
108 changes: 108 additions & 0 deletions pype/modules/websocket_server/stubs/aftereffects_server_stub.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from pype.modules.websocket_server import WebSocketServer
"""
Stub handling connection from server to client.
Used anywhere solution is calling client methods.
"""
import json
from collections import namedtuple


class AfterEffectsServerStub():
"""
Stub for calling function on client (Photoshop js) side.
Expects that client is already connected (started when avalon menu
is opened).
'self.websocketserver.call' is used as async wrapper
"""

def __init__(self):
self.websocketserver = WebSocketServer.get_instance()
self.client = self.websocketserver.get_client()

def open(self, path):
"""
Open file located at 'path' (local).
Args:
path(string): file path locally
Returns: None
"""
self.websocketserver.call(self.client.call
('Photoshop.open', path=path)
)

def read(self, layer, layers_meta=None):
"""
Parses layer metadata from Headline field of active document
Args:
layer: <namedTuple Layer("id":XX, "name":"YYY")
layers_meta: full list from Headline (for performance in loops)
Returns:
"""
if layers_meta is None:
layers_meta = self.get_layers_metadata()

return layers_meta.get(str(layer.id))

def imprint(self, layer, data, all_layers=None, layers_meta=None):
"""
Save layer metadata to Headline field of active document
Args:
layer (namedtuple): Layer("id": XXX, "name":'YYY')
data(string): json representation for single layer
all_layers (list of namedtuples): for performance, could be
injected for usage in loop, if not, single call will be
triggered
layers_meta(string): json representation from Headline
(for performance - provide only if imprint is in
loop - value should be same)
Returns: None
"""
if not layers_meta:
layers_meta = self.get_layers_metadata()
# json.dumps writes integer values in a dictionary to string, so
# anticipating it here.
if str(layer.id) in layers_meta and layers_meta[str(layer.id)]:
if data:
layers_meta[str(layer.id)].update(data)
else:
layers_meta.pop(str(layer.id))
else:
layers_meta[str(layer.id)] = data

# Ensure only valid ids are stored.
if not all_layers:
all_layers = self.get_layers()
layer_ids = [layer.id for layer in all_layers]
cleaned_data = {}

for id in layers_meta:
if int(id) in layer_ids:
cleaned_data[id] = layers_meta[id]

payload = json.dumps(cleaned_data, indent=4)

self.websocketserver.call(self.client.call
('Photoshop.imprint', payload=payload)
)

def close(self):
self.client.close()

def _to_records(self, res):
"""
Converts string json representation into list of named tuples for
dot notation access to work.
Returns: <list of named tuples>
res(string): - json representation
"""
try:
layers_data = json.loads(res)
except json.decoder.JSONDecodeError:
raise ValueError("Received broken JSON {}".format(res))
ret = []
# convert to namedtuple to use dot donation
if isinstance(layers_data, dict): # TODO refactore
layers_data = [layers_data]
for d in layers_data:
ret.append(namedtuple('Layer', d.keys())(*d.values()))
return ret

0 comments on commit 1d2973c

Please sign in to comment.