mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
AfterEffects: set frame range and resolution (#4983)
* OP-5660 - adding menu buttons to Set frame range in AE * OP-5660 - refactored location of scripts set_settings should be in lib as it is used elsewhere, but launch_logic and lib created circular dependency. Moved main to launch logic as it is actually handling launching. * OP-5660 - added set_settings to creator When instance gets created, set frame range and resolution from DB. * OP-5660 - minor fix * OP-5660 - updated extension zip * OP-5660 - updated documentation * OP-5660 - fixed missing exception * OP-5660 - fixed argument * OP-5560 - fix imports * OP-5660 - updated extension * OP-5660 - add js alert message for buttons * OP-5660 - repacked extension Without Anastasyi showed success, but extension wasn't loaded. * OP-5660 - make log message nicer * OP-5660 - added log if workfile not saved * OP-5660 - provide defaults to limit None exception * OP-5660 - updated error message
This commit is contained in:
parent
e178244d46
commit
136af34a71
15 changed files with 414 additions and 212 deletions
|
|
@ -4,9 +4,8 @@ Anything that isn't defined here is INTERNAL and unreliable for external use.
|
|||
|
||||
"""
|
||||
|
||||
from .launch_logic import (
|
||||
from .ws_stub import (
|
||||
get_stub,
|
||||
stub,
|
||||
)
|
||||
|
||||
from .pipeline import (
|
||||
|
|
@ -18,7 +17,8 @@ from .pipeline import (
|
|||
from .lib import (
|
||||
maintained_selection,
|
||||
get_extension_manifest_path,
|
||||
get_asset_settings
|
||||
get_asset_settings,
|
||||
set_settings
|
||||
)
|
||||
|
||||
from .plugin import (
|
||||
|
|
@ -27,9 +27,8 @@ from .plugin import (
|
|||
|
||||
|
||||
__all__ = [
|
||||
# launch_logic
|
||||
# ws_stub
|
||||
"get_stub",
|
||||
"stub",
|
||||
|
||||
# pipeline
|
||||
"ls",
|
||||
|
|
@ -39,6 +38,7 @@ __all__ = [
|
|||
"maintained_selection",
|
||||
"get_extension_manifest_path",
|
||||
"get_asset_settings",
|
||||
"set_settings",
|
||||
|
||||
# plugin
|
||||
"AfterEffectsLoader"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.24"
|
||||
ExtensionBundleName="openpype" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionManifest Version="8.0" ExtensionBundleId="com.openpype.AE.panel" ExtensionBundleVersion="1.0.25"
|
||||
ExtensionBundleName="com.openpype.AE.panel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<ExtensionList>
|
||||
<Extension Id="com.openpype.AE.panel" Version="1.0" />
|
||||
</ExtensionList>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
|
||||
<link rel="stylesheet" href="css/topcoat-desktop-dark.min.css"/>
|
||||
<link id="hostStyle" rel="stylesheet" href="css/styles.css"/>
|
||||
|
||||
|
|
@ -25,11 +25,11 @@
|
|||
|
||||
<title></title>
|
||||
<script src="js/libs/jquery-2.0.2.min.js"></script>
|
||||
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#workfiles-button").bind("click", function() {
|
||||
|
||||
|
||||
RPC.call('AfterEffects.workfiles_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
|
|
@ -37,7 +37,7 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#loader-button").bind("click", function() {
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#publish-button").bind("click", function() {
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#sceneinventory-button").bind("click", function() {
|
||||
|
|
@ -70,7 +70,40 @@
|
|||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#setresolution-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.setresolution_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#setframes-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.setframes_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#setall-button").bind("click", function() {
|
||||
RPC.call('AfterEffects.setall_route').then(function (data) {
|
||||
}, function (error) {
|
||||
alert(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<script type=text/javascript>
|
||||
$(function() {
|
||||
$("a#experimental-button").bind("click", function() {
|
||||
|
|
@ -80,25 +113,28 @@
|
|||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
</head>
|
||||
|
||||
<body class="hostElt">
|
||||
|
||||
<div id="content">
|
||||
|
||||
<div>
|
||||
<div id="content">
|
||||
|
||||
<div>
|
||||
<div></div><a href=# id=workfiles-button><button class="hostFontSize">Workfiles...</button></a></div>
|
||||
<div><a href=# id=loader-button><button class="hostFontSize">Load...</button></a></div>
|
||||
<div><a href=# id=publish-button><button class="hostFontSize">Publish...</button></a></div>
|
||||
<div><a href=# id=sceneinventory-button><button class="hostFontSize">Manage...</button></a></div>
|
||||
<div><a href=# id=setresolution-button><button class="hostFontSize">Set Resolution</button></a></div>
|
||||
<div><a href=# id=setframes-button><button class="hostFontSize">Set Frame Range</button></a></div>
|
||||
<div><a href=# id=setall-button><button class="hostFontSize">Apply All Settings</button></a></div>
|
||||
<div><a href=# id=experimental-button><button class="hostFontSize">Experimental Tools...</button></a></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <script src="js/libs/PlayerDebugMode"></script> -->
|
||||
<script src="js/libs/wsrpc.js"></script>
|
||||
<script src="js/libs/loglevel.min.js"></script>
|
||||
|
|
@ -107,6 +143,6 @@
|
|||
<script src="js/themeManager.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ indent: 4, maxerr: 50 */
|
|||
|
||||
|
||||
var csInterface = new CSInterface();
|
||||
|
||||
|
||||
log.warn("script start");
|
||||
|
||||
WSRPC.DEBUG = false;
|
||||
|
|
@ -14,7 +14,7 @@ WSRPC.TRACE = false;
|
|||
async function startUp(url){
|
||||
promis = runEvalScript("getEnv('" + url + "')");
|
||||
|
||||
var res = await promis;
|
||||
var res = await promis;
|
||||
log.warn("res: " + res);
|
||||
|
||||
promis = runEvalScript("getEnv('OPENPYPE_DEBUG')");
|
||||
|
|
@ -56,7 +56,7 @@ function get_extension_version(){
|
|||
}
|
||||
|
||||
function main(websocket_url){
|
||||
// creates connection to 'websocket_url', registers routes
|
||||
// creates connection to 'websocket_url', registers routes
|
||||
var default_url = 'ws://localhost:8099/ws/';
|
||||
|
||||
if (websocket_url == ''){
|
||||
|
|
@ -66,7 +66,7 @@ function main(websocket_url){
|
|||
|
||||
RPC.connect();
|
||||
|
||||
log.warn("connected");
|
||||
log.warn("connected");
|
||||
|
||||
RPC.addRoute('AfterEffects.open', function (data) {
|
||||
log.warn('Server called client route "open":', data);
|
||||
|
|
@ -88,7 +88,7 @@ function main(websocket_url){
|
|||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.get_active_document_name', function (data) {
|
||||
log.warn('Server called client route ' +
|
||||
log.warn('Server called client route ' +
|
||||
'"get_active_document_name":', data);
|
||||
return runEvalScript("getActiveDocumentName()")
|
||||
.then(function(result){
|
||||
|
|
@ -98,7 +98,7 @@ function main(websocket_url){
|
|||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.get_active_document_full_name', function (data){
|
||||
log.warn('Server called client route ' +
|
||||
log.warn('Server called client route ' +
|
||||
'"get_active_document_full_name":', data);
|
||||
return runEvalScript("getActiveDocumentFullName()")
|
||||
.then(function(result){
|
||||
|
|
@ -118,7 +118,7 @@ function main(websocket_url){
|
|||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
RPC.addRoute('AfterEffects.get_selected_items', function (data) {
|
||||
log.warn('Server called client route "get_selected_items":', data);
|
||||
return runEvalScript("getSelectedItems(" + data.comps + "," +
|
||||
|
|
@ -194,23 +194,25 @@ function main(websocket_url){
|
|||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.get_work_area', function (data) {
|
||||
log.warn('Server called client route "get_work_area":', data);
|
||||
return runEvalScript("getWorkArea(" + data.item_id + ")")
|
||||
RPC.addRoute('AfterEffects.get_comp_properties', function (data) {
|
||||
log.warn('Server called client route "get_comp_properties":', data);
|
||||
return runEvalScript("getCompProperties(" + data.item_id + ")")
|
||||
.then(function(result){
|
||||
log.warn("getWorkArea: " + result);
|
||||
log.warn("get_comp_properties: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.set_work_area', function (data) {
|
||||
RPC.addRoute('AfterEffects.set_comp_properties', function (data) {
|
||||
log.warn('Server called client route "set_work_area":', data);
|
||||
return runEvalScript("setWorkArea(" + data.item_id + ',' +
|
||||
return runEvalScript("setCompProperties(" + data.item_id + ',' +
|
||||
data.start + ',' +
|
||||
data.duration + ',' +
|
||||
data.frame_rate + ")")
|
||||
data.frame_rate + ',' +
|
||||
data.width + ',' +
|
||||
data.height + ")")
|
||||
.then(function(result){
|
||||
log.warn("getWorkArea: " + result);
|
||||
log.warn("set_comp_properties: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
|
@ -255,7 +257,7 @@ function main(websocket_url){
|
|||
|
||||
RPC.addRoute('AfterEffects.import_background', function (data) {
|
||||
log.warn('Server called client route "import_background":', data);
|
||||
return runEvalScript("importBackground(" + data.comp_id + ", " +
|
||||
return runEvalScript("importBackground(" + data.comp_id + ", " +
|
||||
"'" + data.comp_name + "', " +
|
||||
JSON.stringify(data.files) + ")")
|
||||
.then(function(result){
|
||||
|
|
@ -266,7 +268,7 @@ function main(websocket_url){
|
|||
|
||||
RPC.addRoute('AfterEffects.reload_background', function (data) {
|
||||
log.warn('Server called client route "reload_background":', data);
|
||||
return runEvalScript("reloadBackground(" + data.comp_id + ", " +
|
||||
return runEvalScript("reloadBackground(" + data.comp_id + ", " +
|
||||
"'" + data.comp_name + "', " +
|
||||
JSON.stringify(data.files) + ")")
|
||||
.then(function(result){
|
||||
|
|
@ -314,6 +316,16 @@ function main(websocket_url){
|
|||
log.warn('Server called client route "close":', data);
|
||||
return runEvalScript("close()");
|
||||
});
|
||||
|
||||
RPC.addRoute('AfterEffects.print_msg', function (data) {
|
||||
log.warn('Server called client route "print_msg":', data);
|
||||
var escaped_msg = EscapeStringForJSX(data.msg);
|
||||
return runEvalScript("printMsg('" + escaped_msg +"')")
|
||||
.then(function(result){
|
||||
log.warn("print_msg: " + result);
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** main entry point **/
|
||||
|
|
@ -323,17 +335,17 @@ startUp("WEBSOCKET_URL");
|
|||
'use strict';
|
||||
|
||||
var csInterface = new CSInterface();
|
||||
|
||||
|
||||
|
||||
|
||||
function init() {
|
||||
|
||||
|
||||
themeManager.init();
|
||||
|
||||
|
||||
$("#btn_test").click(function () {
|
||||
csInterface.evalScript('sayHello()');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
init();
|
||||
|
||||
}());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true,
|
||||
indent: 4, maxerr: 50 */
|
||||
/*global $, Folder*/
|
||||
#include "../js/libs/json.js";
|
||||
//@include "../js/libs/json.js"
|
||||
|
||||
/* All public API function should return JSON! */
|
||||
|
||||
|
|
@ -29,13 +29,13 @@ function getEnv(variable){
|
|||
function getMetadata(){
|
||||
/**
|
||||
* Returns payload in 'Label' field of project's metadata
|
||||
*
|
||||
*
|
||||
**/
|
||||
if (ExternalObject.AdobeXMPScript === undefined){
|
||||
ExternalObject.AdobeXMPScript =
|
||||
new ExternalObject('lib:AdobeXMPScript');
|
||||
}
|
||||
|
||||
|
||||
var proj = app.project;
|
||||
var meta = new XMPMeta(app.project.xmpPacket);
|
||||
var schemaNS = XMPMeta.getNamespaceURI("xmp");
|
||||
|
|
@ -53,7 +53,7 @@ function getMetadata(){
|
|||
function imprint(payload){
|
||||
/**
|
||||
* Stores payload in 'Label' field of project's metadata
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* payload (string): json content
|
||||
*/
|
||||
|
|
@ -61,14 +61,14 @@ function imprint(payload){
|
|||
ExternalObject.AdobeXMPScript =
|
||||
new ExternalObject('lib:AdobeXMPScript');
|
||||
}
|
||||
|
||||
|
||||
var proj = app.project;
|
||||
var meta = new XMPMeta(app.project.xmpPacket);
|
||||
var schemaNS = XMPMeta.getNamespaceURI("xmp");
|
||||
var label = "xmp:Label";
|
||||
|
||||
meta.setProperty(schemaNS, label, payload);
|
||||
|
||||
|
||||
app.project.xmpPacket = meta.serialize();
|
||||
|
||||
}
|
||||
|
|
@ -116,14 +116,14 @@ function getItems(comps, folders, footages){
|
|||
/**
|
||||
* Returns JSON representation of compositions and
|
||||
* if 'collectLayers' then layers in comps too.
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comps (bool): return selected compositions
|
||||
* folders (bool): return folders
|
||||
* footages (bool): return FootageItem
|
||||
* Returns:
|
||||
* (list) of JSON items
|
||||
*/
|
||||
*/
|
||||
var items = []
|
||||
for (i = 1; i <= app.project.items.length; ++i){
|
||||
var item = app.project.items[i];
|
||||
|
|
@ -142,14 +142,14 @@ function getItems(comps, folders, footages){
|
|||
function getSelectedItems(comps, folders, footages){
|
||||
/**
|
||||
* Returns list of selected items from Project menu
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comps (bool): return selected compositions
|
||||
* folders (bool): return folders
|
||||
* footages (bool): return FootageItem
|
||||
* Returns:
|
||||
* (list) of JSON items
|
||||
*/
|
||||
*/
|
||||
var items = []
|
||||
for (i = 0; i < app.project.selection.length; ++i){
|
||||
var item = app.project.selection[i];
|
||||
|
|
@ -166,9 +166,9 @@ function getSelectedItems(comps, folders, footages){
|
|||
|
||||
function _getItem(item, comps, folders, footages){
|
||||
/**
|
||||
* Auxiliary function as project items and selections
|
||||
* Auxiliary function as project items and selections
|
||||
* are indexed in different way :/
|
||||
* Refactor
|
||||
* Refactor
|
||||
*/
|
||||
var item_type = '';
|
||||
if (item instanceof FolderItem){
|
||||
|
|
@ -189,7 +189,7 @@ function _getItem(item, comps, folders, footages){
|
|||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var item = {"name": item.name,
|
||||
"id": item.id,
|
||||
"type": item_type};
|
||||
|
|
@ -200,7 +200,7 @@ function importFile(path, item_name, import_options){
|
|||
/**
|
||||
* Imports file (image tested for now) as a FootageItem.
|
||||
* Creates new composition
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* path (string): absolute path to image file
|
||||
* item_name (string): label for composition
|
||||
|
|
@ -218,7 +218,7 @@ function importFile(path, item_name, import_options){
|
|||
app.beginUndoGroup("Import File");
|
||||
fp = new File(path);
|
||||
if (fp.exists){
|
||||
try {
|
||||
try {
|
||||
im_opt = new ImportOptions(fp);
|
||||
importAsType = import_options["ImportAsType"];
|
||||
|
||||
|
|
@ -234,18 +234,18 @@ function importFile(path, item_name, import_options){
|
|||
}
|
||||
if (importAsType.indexOf('PROJECT') > 0){
|
||||
im_opt.importAs = ImportAsType.PROJECT;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
if ('sequence' in import_options){
|
||||
im_opt.sequence = true;
|
||||
}
|
||||
|
||||
|
||||
comp = app.project.importFile(im_opt);
|
||||
|
||||
if (app.project.selection.length == 2 &&
|
||||
app.project.selection[0] instanceof FolderItem){
|
||||
comp.parentFolder = app.project.selection[0]
|
||||
comp.parentFolder = app.project.selection[0]
|
||||
}
|
||||
} catch (error) {
|
||||
return _prepareError(error.toString() + importOptions.file.fsName);
|
||||
|
|
@ -283,14 +283,14 @@ function setLabelColor(comp_id, color_idx){
|
|||
function replaceItem(comp_id, path, item_name){
|
||||
/**
|
||||
* Replaces loaded file with new file and updates name
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* comp_id (int): id of composition, not a index!
|
||||
* path (string): absolute path to new file
|
||||
* item_name (string): new composition name
|
||||
*/
|
||||
app.beginUndoGroup("Replace File");
|
||||
|
||||
|
||||
fp = new File(path);
|
||||
if (!fp.exists){
|
||||
return _prepareError("File " + path + " not found.");
|
||||
|
|
@ -303,7 +303,7 @@ function replaceItem(comp_id, path, item_name){
|
|||
}else{
|
||||
item.replace(fp);
|
||||
}
|
||||
|
||||
|
||||
item.name = item_name;
|
||||
} catch (error) {
|
||||
return _prepareError(error.toString() + path);
|
||||
|
|
@ -319,7 +319,7 @@ function replaceItem(comp_id, path, item_name){
|
|||
function renameItem(item_id, new_name){
|
||||
/**
|
||||
* Renames item with 'item_id' to 'new_name'
|
||||
*
|
||||
*
|
||||
* Args:
|
||||
* item_id (int): id to search item
|
||||
* new_name (str)
|
||||
|
|
@ -335,7 +335,7 @@ function renameItem(item_id, new_name){
|
|||
function deleteItem(item_id){
|
||||
/**
|
||||
* Delete any 'item_id'
|
||||
*
|
||||
*
|
||||
* Not restricted only to comp, it could delete
|
||||
* any item with 'id'
|
||||
*/
|
||||
|
|
@ -347,38 +347,76 @@ function deleteItem(item_id){
|
|||
}
|
||||
}
|
||||
|
||||
function getWorkArea(comp_id){
|
||||
function getCompProperties(comp_id){
|
||||
/**
|
||||
* Returns information about workarea - are that will be
|
||||
* rendered. All calculation will be done in OpenPype,
|
||||
* easier to modify without redeploy of extension.
|
||||
*
|
||||
* Returns information about composition - are that will be
|
||||
* rendered.
|
||||
*
|
||||
* Returns
|
||||
* (dict)
|
||||
*/
|
||||
var item = app.project.itemByID(comp_id);
|
||||
if (item){
|
||||
return JSON.stringify({
|
||||
"workAreaStart": item.displayStartFrame,
|
||||
"workAreaDuration": item.duration,
|
||||
"frameRate": item.frameRate});
|
||||
}else{
|
||||
var comp = app.project.itemByID(comp_id);
|
||||
if (!comp){
|
||||
return _prepareError("There is no composition with "+ comp_id);
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
"id": comp.id,
|
||||
"name": comp.name,
|
||||
"frameStart": comp.displayStartFrame,
|
||||
"framesDuration": comp.duration * comp.frameRate,
|
||||
"frameRate": comp.frameRate,
|
||||
"width": comp.width,
|
||||
"height": comp.height});
|
||||
}
|
||||
|
||||
function setWorkArea(comp_id, workAreaStart, workAreaDuration, frameRate){
|
||||
function setCompProperties(comp_id, frameStart, framesCount, frameRate,
|
||||
width, height){
|
||||
/**
|
||||
* Sets work area info from outside (from Ftrack via OpenPype)
|
||||
*/
|
||||
var item = app.project.itemByID(comp_id);
|
||||
if (item){
|
||||
item.displayStartTime = workAreaStart;
|
||||
item.duration = workAreaDuration;
|
||||
item.frameRate = frameRate;
|
||||
}else{
|
||||
var comp = app.project.itemByID(comp_id);
|
||||
if (!comp){
|
||||
return _prepareError("There is no composition with "+ comp_id);
|
||||
}
|
||||
|
||||
app.beginUndoGroup('change comp properties');
|
||||
if (frameStart && framesCount && frameRate){
|
||||
comp.displayStartFrame = frameStart;
|
||||
comp.duration = framesCount / frameRate;
|
||||
comp.frameRate = frameRate;
|
||||
}
|
||||
if (width && height){
|
||||
var widthOld = comp.width;
|
||||
var widthNew = width;
|
||||
var widthDelta = widthNew - widthOld;
|
||||
|
||||
var heightOld = comp.height;
|
||||
var heightNew = height;
|
||||
var heightDelta = heightNew - heightOld;
|
||||
|
||||
var offset = [widthDelta / 2, heightDelta / 2];
|
||||
|
||||
comp.width = widthNew;
|
||||
comp.height = heightNew;
|
||||
|
||||
for (var i = 1, il = comp.numLayers; i <= il; i++) {
|
||||
var layer = comp.layer(i);
|
||||
var positionProperty = layer.property('ADBE Transform Group').property('ADBE Position');
|
||||
|
||||
if (positionProperty.numKeys > 0) {
|
||||
for (var j = 1, jl = positionProperty.numKeys; j <= jl; j++) {
|
||||
var keyValue = positionProperty.keyValue(j);
|
||||
positionProperty.setValueAtKey(j, keyValue + offset);
|
||||
}
|
||||
} else {
|
||||
var positionValue = positionProperty.value;
|
||||
positionProperty.setValue(positionValue + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.endUndoGroup();
|
||||
}
|
||||
|
||||
function save(){
|
||||
|
|
@ -504,7 +542,7 @@ function addItemAsLayerToComp(comp_id, item_id, found_comp){
|
|||
* Args:
|
||||
* comp_id (int): id of target composition
|
||||
* item_id (int): FootageItem.id
|
||||
* found_comp (CompItem, optional): to limit querying if
|
||||
* found_comp (CompItem, optional): to limit quering if
|
||||
* comp already found previously
|
||||
*/
|
||||
var comp = found_comp || app.project.itemByID(comp_id);
|
||||
|
|
@ -749,7 +787,7 @@ function render(target_folder, comp_id){
|
|||
|
||||
var om1 = app.project.renderQueue.item(i).outputModule(1);
|
||||
var file_name = File.decode( om1.file.name ).replace('℗', ''); // Name contains special character, space?
|
||||
|
||||
|
||||
var omItem1_settable_str = app.project.renderQueue.item(i).outputModule(1).getSettings( GetSettingsFormat.STRING_SETTABLE );
|
||||
|
||||
var targetFolder = new Folder(target_folder);
|
||||
|
|
@ -763,7 +801,7 @@ function render(target_folder, comp_id){
|
|||
render_item.render = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
app.beginSuppressDialogs();
|
||||
app.project.renderQueue.render();
|
||||
|
|
@ -779,6 +817,10 @@ function getAppVersion(){
|
|||
return _prepareSingleValue(app.version);
|
||||
}
|
||||
|
||||
function printMsg(msg){
|
||||
alert(msg);
|
||||
}
|
||||
|
||||
function _prepareSingleValue(value){
|
||||
return JSON.stringify({"result": value})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,77 @@
|
|||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import collections
|
||||
import logging
|
||||
import asyncio
|
||||
import functools
|
||||
import traceback
|
||||
|
||||
|
||||
from wsrpc_aiohttp import (
|
||||
WebSocketRoute,
|
||||
WebSocketAsync
|
||||
)
|
||||
|
||||
from qtpy import QtCore
|
||||
from qtpy import QtCore, QtWidgets
|
||||
|
||||
from openpype.lib import Logger
|
||||
from openpype.pipeline import legacy_io
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.tests.lib import is_in_tests
|
||||
from openpype.pipeline import install_host, legacy_io
|
||||
from openpype.modules import ModulesManager
|
||||
from openpype.tools.adobe_webserver.app import WebServerTool
|
||||
|
||||
from .ws_stub import AfterEffectsServerStub
|
||||
from .ws_stub import get_stub
|
||||
from .lib import set_settings
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
class ConnectionNotEstablishedYet(Exception):
|
||||
pass
|
||||
def safe_excepthook(*args):
|
||||
traceback.print_exception(*args)
|
||||
|
||||
|
||||
def get_stub():
|
||||
"""
|
||||
Convenience function to get server RPC stub to call methods directed
|
||||
for host (Photoshop).
|
||||
It expects already created connection, started from client.
|
||||
Currently created when panel is opened (PS: Window>Extensions>Avalon)
|
||||
:return: <PhotoshopClientStub> where functions could be called from
|
||||
"""
|
||||
ae_stub = AfterEffectsServerStub()
|
||||
if not ae_stub.client:
|
||||
raise ConnectionNotEstablishedYet("Connection is not created yet")
|
||||
def main(*subprocess_args):
|
||||
"""Main entrypoint to AE launching, called from pre hook."""
|
||||
sys.excepthook = safe_excepthook
|
||||
|
||||
return ae_stub
|
||||
from openpype.hosts.aftereffects.api import AfterEffectsHost
|
||||
|
||||
host = AfterEffectsHost()
|
||||
install_host(host)
|
||||
|
||||
def stub():
|
||||
return get_stub()
|
||||
os.environ["OPENPYPE_LOG_NO_COLORS"] = "False"
|
||||
app = QtWidgets.QApplication([])
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
launcher = ProcessLauncher(subprocess_args)
|
||||
launcher.start()
|
||||
|
||||
if os.environ.get("HEADLESS_PUBLISH"):
|
||||
manager = ModulesManager()
|
||||
webpublisher_addon = manager["webpublisher"]
|
||||
|
||||
launcher.execute_in_main_thread(
|
||||
functools.partial(
|
||||
webpublisher_addon.headless_publish,
|
||||
log,
|
||||
"CloseAE",
|
||||
is_in_tests()
|
||||
)
|
||||
)
|
||||
|
||||
elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True):
|
||||
save = False
|
||||
if os.getenv("WORKFILES_SAVE_AS"):
|
||||
save = True
|
||||
|
||||
launcher.execute_in_main_thread(
|
||||
lambda: host_tools.show_tool_by_name("workfiles", save=save)
|
||||
)
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
def show_tool_by_name(tool_name):
|
||||
|
|
@ -55,6 +83,7 @@ def show_tool_by_name(tool_name):
|
|||
|
||||
|
||||
class ProcessLauncher(QtCore.QObject):
|
||||
"""Launches webserver, connects to it, runs main thread."""
|
||||
route_name = "AfterEffects"
|
||||
_main_thread_callbacks = collections.deque()
|
||||
|
||||
|
|
@ -296,6 +325,15 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
async def sceneinventory_route(self):
|
||||
self._tool_route("sceneinventory")
|
||||
|
||||
async def setresolution_route(self):
|
||||
self._settings_route(False, True)
|
||||
|
||||
async def setframes_route(self):
|
||||
self._settings_route(True, False)
|
||||
|
||||
async def setall_route(self):
|
||||
self._settings_route(True, True)
|
||||
|
||||
async def experimental_tools_route(self):
|
||||
self._tool_route("experimental_tools")
|
||||
|
||||
|
|
@ -309,3 +347,13 @@ class AfterEffectsRoute(WebSocketRoute):
|
|||
|
||||
# Required return statement.
|
||||
return "nothing"
|
||||
|
||||
def _settings_route(self, frames, resolution):
|
||||
partial_method = functools.partial(set_settings,
|
||||
frames,
|
||||
resolution)
|
||||
|
||||
ProcessLauncher.execute_in_main_thread(partial_method)
|
||||
|
||||
# Required return statement.
|
||||
return "nothing"
|
||||
|
|
|
|||
|
|
@ -1,69 +1,17 @@
|
|||
import os
|
||||
import sys
|
||||
import re
|
||||
import json
|
||||
import contextlib
|
||||
import traceback
|
||||
import logging
|
||||
from functools import partial
|
||||
|
||||
from qtpy import QtWidgets
|
||||
|
||||
from openpype.pipeline import install_host
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype.tests.lib import is_in_tests
|
||||
from .launch_logic import ProcessLauncher, get_stub
|
||||
from openpype.pipeline.context_tools import get_current_context
|
||||
from openpype.client import get_asset_by_name
|
||||
from .ws_stub import get_stub
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
|
||||
def safe_excepthook(*args):
|
||||
traceback.print_exception(*args)
|
||||
|
||||
|
||||
def main(*subprocess_args):
|
||||
sys.excepthook = safe_excepthook
|
||||
|
||||
from openpype.hosts.aftereffects.api import AfterEffectsHost
|
||||
|
||||
host = AfterEffectsHost()
|
||||
install_host(host)
|
||||
|
||||
os.environ["OPENPYPE_LOG_NO_COLORS"] = "False"
|
||||
app = QtWidgets.QApplication([])
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
|
||||
launcher = ProcessLauncher(subprocess_args)
|
||||
launcher.start()
|
||||
|
||||
if os.environ.get("HEADLESS_PUBLISH"):
|
||||
manager = ModulesManager()
|
||||
webpublisher_addon = manager["webpublisher"]
|
||||
|
||||
launcher.execute_in_main_thread(
|
||||
partial(
|
||||
webpublisher_addon.headless_publish,
|
||||
log,
|
||||
"CloseAE",
|
||||
is_in_tests()
|
||||
)
|
||||
)
|
||||
|
||||
elif os.environ.get("AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH", True):
|
||||
save = False
|
||||
if os.getenv("WORKFILES_SAVE_AS"):
|
||||
save = True
|
||||
|
||||
launcher.execute_in_main_thread(
|
||||
lambda: host_tools.show_tool_by_name("workfiles", save=save)
|
||||
)
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_selection():
|
||||
"""Maintain selection during context."""
|
||||
|
|
@ -145,13 +93,13 @@ def get_asset_settings(asset_doc):
|
|||
|
||||
"""
|
||||
asset_data = asset_doc["data"]
|
||||
fps = asset_data.get("fps")
|
||||
frame_start = asset_data.get("frameStart")
|
||||
frame_end = asset_data.get("frameEnd")
|
||||
handle_start = asset_data.get("handleStart")
|
||||
handle_end = asset_data.get("handleEnd")
|
||||
resolution_width = asset_data.get("resolutionWidth")
|
||||
resolution_height = asset_data.get("resolutionHeight")
|
||||
fps = asset_data.get("fps", 0)
|
||||
frame_start = asset_data.get("frameStart", 0)
|
||||
frame_end = asset_data.get("frameEnd", 0)
|
||||
handle_start = asset_data.get("handleStart", 0)
|
||||
handle_end = asset_data.get("handleEnd", 0)
|
||||
resolution_width = asset_data.get("resolutionWidth", 0)
|
||||
resolution_height = asset_data.get("resolutionHeight", 0)
|
||||
duration = (frame_end - frame_start + 1) + handle_start + handle_end
|
||||
|
||||
return {
|
||||
|
|
@ -164,3 +112,49 @@ def get_asset_settings(asset_doc):
|
|||
"resolutionHeight": resolution_height,
|
||||
"duration": duration
|
||||
}
|
||||
|
||||
|
||||
def set_settings(frames, resolution, comp_ids=None, print_msg=True):
|
||||
"""Sets number of frames and resolution to selected comps.
|
||||
|
||||
Args:
|
||||
frames (bool): True if set frame info
|
||||
resolution (bool): True if set resolution
|
||||
comp_ids (list): specific composition ids, if empty
|
||||
it tries to look for currently selected
|
||||
print_msg (bool): True throw JS alert with msg
|
||||
"""
|
||||
frame_start = frames_duration = fps = width = height = None
|
||||
current_context = get_current_context()
|
||||
|
||||
asset_doc = get_asset_by_name(current_context["project_name"],
|
||||
current_context["asset_name"])
|
||||
settings = get_asset_settings(asset_doc)
|
||||
|
||||
msg = ''
|
||||
if frames:
|
||||
frame_start = settings["frameStart"] - settings["handleStart"]
|
||||
frames_duration = settings["duration"]
|
||||
fps = settings["fps"]
|
||||
msg += f"frame start:{frame_start}, duration:{frames_duration}, "\
|
||||
f"fps:{fps}"
|
||||
if resolution:
|
||||
width = settings["resolutionWidth"]
|
||||
height = settings["resolutionHeight"]
|
||||
msg += f"width:{width} and height:{height}"
|
||||
|
||||
stub = get_stub()
|
||||
if not comp_ids:
|
||||
comps = stub.get_selected_items(True, False, False)
|
||||
comp_ids = [comp.id for comp in comps]
|
||||
if not comp_ids:
|
||||
stub.print_msg("Select at least one composition to apply settings.")
|
||||
return
|
||||
|
||||
for comp_id in comp_ids:
|
||||
msg = f"Setting for comp {comp_id} " + msg
|
||||
log.debug(msg)
|
||||
stub.set_comp_properties(comp_id, frame_start, frames_duration,
|
||||
fps, width, height)
|
||||
if print_msg:
|
||||
stub.print_msg(msg)
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ from openpype.lib import Logger, register_event_callback
|
|||
from openpype.pipeline import (
|
||||
register_loader_plugin_path,
|
||||
register_creator_plugin_path,
|
||||
deregister_loader_plugin_path,
|
||||
deregister_creator_plugin_path,
|
||||
AVALON_CONTAINER_ID,
|
||||
legacy_io,
|
||||
)
|
||||
from openpype.pipeline.load import any_outdated_containers
|
||||
import openpype.hosts.aftereffects
|
||||
|
|
@ -23,7 +20,8 @@ from openpype.host import (
|
|||
IPublishHost
|
||||
)
|
||||
|
||||
from .launch_logic import get_stub, ConnectionNotEstablishedYet
|
||||
from .launch_logic import get_stub
|
||||
from .ws_stub import ConnectionNotEstablishedYet
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,10 @@ from wsrpc_aiohttp import WebSocketAsync
|
|||
from openpype.tools.adobe_webserver.app import WebServerTool
|
||||
|
||||
|
||||
class ConnectionNotEstablishedYet(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@attr.s
|
||||
class AEItem(object):
|
||||
"""
|
||||
|
|
@ -24,8 +28,8 @@ class AEItem(object):
|
|||
# all imported elements, single for
|
||||
# regular image, array for Backgrounds
|
||||
members = attr.ib(factory=list)
|
||||
workAreaStart = attr.ib(default=None)
|
||||
workAreaDuration = attr.ib(default=None)
|
||||
frameStart = attr.ib(default=None)
|
||||
framesDuration = attr.ib(default=None)
|
||||
frameRate = attr.ib(default=None)
|
||||
file_name = attr.ib(default=None)
|
||||
instance_id = attr.ib(default=None) # New Publisher
|
||||
|
|
@ -355,42 +359,50 @@ class AfterEffectsServerStub():
|
|||
|
||||
return self._handle_return(res)
|
||||
|
||||
def get_work_area(self, item_id):
|
||||
""" Get work are information for render purposes
|
||||
def get_comp_properties(self, comp_id):
|
||||
""" Get composition information for render purposes
|
||||
|
||||
Returns startFrame, frameDuration, fps, width, height.
|
||||
|
||||
Args:
|
||||
item_id (int):
|
||||
comp_id (int):
|
||||
|
||||
Returns:
|
||||
(AEItem)
|
||||
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.get_work_area',
|
||||
item_id=item_id
|
||||
('AfterEffects.get_comp_properties',
|
||||
item_id=comp_id
|
||||
))
|
||||
|
||||
records = self._to_records(self._handle_return(res))
|
||||
if records:
|
||||
return records.pop()
|
||||
|
||||
def set_work_area(self, item, start, duration, frame_rate):
|
||||
def set_comp_properties(self, comp_id, start, duration, frame_rate,
|
||||
width, height):
|
||||
"""
|
||||
Set work area to predefined values (from Ftrack).
|
||||
Work area directs what gets rendered.
|
||||
Beware of rounding, AE expects seconds, not frames directly.
|
||||
|
||||
Args:
|
||||
item (dict):
|
||||
start (float): workAreaStart in seconds
|
||||
duration (float): in seconds
|
||||
comp_id (int):
|
||||
start (int): workAreaStart in frames
|
||||
duration (int): in frames
|
||||
frame_rate (float): frames in seconds
|
||||
width (int): resolution width
|
||||
height (int): resolution height
|
||||
"""
|
||||
res = self.websocketserver.call(self.client.call
|
||||
('AfterEffects.set_work_area',
|
||||
item_id=item.id,
|
||||
('AfterEffects.set_comp_properties',
|
||||
item_id=comp_id,
|
||||
start=start,
|
||||
duration=duration,
|
||||
frame_rate=frame_rate))
|
||||
frame_rate=frame_rate,
|
||||
width=width,
|
||||
height=height))
|
||||
return self._handle_return(res)
|
||||
|
||||
def save(self):
|
||||
|
|
@ -554,6 +566,12 @@ class AfterEffectsServerStub():
|
|||
|
||||
return self._handle_return(res)
|
||||
|
||||
def print_msg(self, msg):
|
||||
"""Triggers Javascript alert dialog."""
|
||||
self.websocketserver.call(self.client.call
|
||||
('AfterEffects.print_msg',
|
||||
msg=msg))
|
||||
|
||||
def _handle_return(self, res):
|
||||
"""Wraps return, throws ValueError if 'error' key is present."""
|
||||
if res and isinstance(res, str) and res != "undefined":
|
||||
|
|
@ -608,8 +626,8 @@ class AfterEffectsServerStub():
|
|||
d.get('name'),
|
||||
d.get('type'),
|
||||
d.get('members'),
|
||||
d.get('workAreaStart'),
|
||||
d.get('workAreaDuration'),
|
||||
d.get('frameStart'),
|
||||
d.get('framesDuration'),
|
||||
d.get('frameRate'),
|
||||
d.get('file_name'),
|
||||
d.get("instance_id"),
|
||||
|
|
@ -618,3 +636,18 @@ class AfterEffectsServerStub():
|
|||
|
||||
ret.append(item)
|
||||
return ret
|
||||
|
||||
|
||||
def get_stub():
|
||||
"""
|
||||
Convenience function to get server RPC stub to call methods directed
|
||||
for host (Photoshop).
|
||||
It expects already created connection, started from client.
|
||||
Currently created when panel is opened (PS: Window>Extensions>Avalon)
|
||||
:return: <PhotoshopClientStub> where functions could be called from
|
||||
"""
|
||||
ae_stub = AfterEffectsServerStub()
|
||||
if not ae_stub.client:
|
||||
raise ConnectionNotEstablishedYet("Connection is not created yet")
|
||||
|
||||
return ae_stub
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from openpype.pipeline import (
|
|||
CreatorError
|
||||
)
|
||||
from openpype.hosts.aftereffects.api.pipeline import cache_and_get_instances
|
||||
from openpype.hosts.aftereffects.api.lib import set_settings
|
||||
from openpype.lib import prepare_template_data
|
||||
from openpype.pipeline.create import SUBSET_NAME_ALLOWED_SYMBOLS
|
||||
|
||||
|
|
@ -32,6 +33,14 @@ class RenderCreator(Creator):
|
|||
|
||||
def create(self, subset_name_from_ui, data, pre_create_data):
|
||||
stub = api.get_stub() # only after After Effects is up
|
||||
|
||||
try:
|
||||
_ = stub.get_active_document_full_name()
|
||||
except ValueError:
|
||||
raise CreatorError(
|
||||
"Please save workfile via Workfile app first!"
|
||||
)
|
||||
|
||||
if pre_create_data.get("use_selection"):
|
||||
comps = stub.get_selected_items(
|
||||
comps=True, folders=False, footages=False
|
||||
|
|
@ -41,8 +50,8 @@ class RenderCreator(Creator):
|
|||
|
||||
if not comps:
|
||||
raise CreatorError(
|
||||
"Nothing to create. Select composition "
|
||||
"if 'useSelection' or create at least "
|
||||
"Nothing to create. Select composition in Project Bin if "
|
||||
"'Use selection' is toggled or create at least "
|
||||
"one composition."
|
||||
)
|
||||
use_composition_name = (pre_create_data.get("use_composition_name") or
|
||||
|
|
@ -87,10 +96,14 @@ class RenderCreator(Creator):
|
|||
self._add_instance_to_context(new_instance)
|
||||
|
||||
stub.rename_item(comp.id, subset_name)
|
||||
set_settings(True, True, [comp.id], print_msg=False)
|
||||
|
||||
def get_pre_create_attr_defs(self):
|
||||
output = [
|
||||
BoolDef("use_selection", default=True, label="Use selection"),
|
||||
BoolDef("use_selection",
|
||||
tooltip="Composition for publishable instance should be "
|
||||
"selected by default.",
|
||||
default=True, label="Use selection"),
|
||||
BoolDef("use_composition_name",
|
||||
label="Use composition name in subset"),
|
||||
UISeparatorDef(),
|
||||
|
|
|
|||
|
|
@ -66,19 +66,19 @@ class CollectAERender(publish.AbstractCollectRender):
|
|||
|
||||
comp_id = int(inst.data["members"][0])
|
||||
|
||||
work_area_info = CollectAERender.get_stub().get_work_area(comp_id)
|
||||
comp_info = CollectAERender.get_stub().get_comp_properties(
|
||||
comp_id)
|
||||
|
||||
if not work_area_info:
|
||||
if not comp_info:
|
||||
self.log.warning("Orphaned instance, deleting metadata")
|
||||
inst_id = inst.get("instance_id") or str(comp_id)
|
||||
inst_id = inst.data.get("instance_id") or str(comp_id)
|
||||
CollectAERender.get_stub().remove_instance(inst_id)
|
||||
continue
|
||||
|
||||
frame_start = work_area_info.workAreaStart
|
||||
frame_end = round(work_area_info.workAreaStart +
|
||||
float(work_area_info.workAreaDuration) *
|
||||
float(work_area_info.frameRate)) - 1
|
||||
fps = work_area_info.frameRate
|
||||
frame_start = comp_info.frameStart
|
||||
frame_end = round(comp_info.frameStart +
|
||||
comp_info.framesDuration) - 1
|
||||
fps = comp_info.frameRate
|
||||
# TODO add resolution when supported by extension
|
||||
|
||||
task_name = inst.data.get("task") # legacy
|
||||
|
|
|
|||
|
|
@ -81,9 +81,10 @@ def main(argv):
|
|||
|
||||
host_name = os.environ["AVALON_APP"].lower()
|
||||
if host_name == "photoshop":
|
||||
# TODO refactor launch logic according to AE
|
||||
from openpype.hosts.photoshop.api.lib import main
|
||||
elif host_name == "aftereffects":
|
||||
from openpype.hosts.aftereffects.api.lib import main
|
||||
from openpype.hosts.aftereffects.api.launch_logic import main
|
||||
elif host_name == "harmony":
|
||||
from openpype.hosts.harmony.api.lib import main
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -15,18 +15,18 @@ sidebar_label: AfterEffects
|
|||
|
||||
## Setup
|
||||
|
||||
To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}hosts/aftereffects/api/extension.zxp`.
|
||||
To install the extension, download, install [Anastasyi's Extension Manager](https://install.anastasiy.com/). Open Anastasyi's Extension Manager and select AfterEffects in menu. Then go to `{path to pype}hosts/aftereffects/api/extension.zxp`.
|
||||
|
||||
Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself.
|
||||
Drag extension.zxp and drop it to Anastasyi's Extension Manager. The extension will install itself.
|
||||
|
||||
## Implemented functionality
|
||||
|
||||
AfterEffects implementation currently allows you to import and add various media to composition (image plates, renders, audio files, video files etc.)
|
||||
and send prepared composition for rendering to Deadline or render locally.
|
||||
and send prepared composition for rendering to Deadline or render locally.
|
||||
|
||||
## Usage
|
||||
|
||||
When you launch AfterEffects you will be met with the Workfiles app. If don't
|
||||
When you launch AfterEffects you will be met with the Workfiles app. If don't
|
||||
have any previous workfiles, you can just close this window.
|
||||
|
||||
Workfiles tools takes care of saving your .AEP files in the correct location and under
|
||||
|
|
@ -34,7 +34,7 @@ a correct name. You should use it instead of standard file saving dialog.
|
|||
|
||||
In AfterEffects you'll find the tools in the `OpenPype` extension:
|
||||
|
||||
 <!-- same menu as in PS -->
|
||||

|
||||
|
||||
You can show the extension panel by going to `Window` > `Extensions` > `OpenPype`.
|
||||
|
||||
|
|
@ -58,6 +58,9 @@ Name of publishable instance (eg. subset name) could be configured with a templa
|
|||
|
||||
Trash icon under the list of instances allows to delete any selected `render` instance.
|
||||
|
||||
Frame information (frame start, duration, fps) and resolution (width and height) is applied to selected composition from Asset Management System (Ftrack or DB) automatically!
|
||||
(Eg. number of rendered frames is controlled by settings inserted from supervisor. Artist can override this by disabling validation only in special cases.)
|
||||
|
||||
Workfile instance will be automatically recreated though. If you do not want to publish it, use pill toggle on the instance item.
|
||||
|
||||
If you would like to modify publishable instance, click on `Publish` tab at the top. This would allow you to change name of publishable
|
||||
|
|
@ -67,7 +70,7 @@ Publisher allows publishing into different context, just click on any instance,
|
|||
|
||||
#### RenderQueue
|
||||
|
||||
AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module.
|
||||
AE's Render Queue is required for publishing locally or on a farm. Artist needs to configure expected result format (extension, resolution) in the Render Queue in an Output module.
|
||||
Currently its expected to have only single render item per composition in the Render Queue.
|
||||
|
||||
|
||||
|
|
@ -151,3 +154,25 @@ You can switch to a previous version of the image or update to the latest.
|
|||
|
||||

|
||||

|
||||
|
||||
|
||||
### Setting section
|
||||
|
||||
Composition properties should be controlled by state in Asset Management System (Ftrack etc). Extension provides couple of buttons to trigger this propagation.
|
||||
|
||||
#### Set Resolution
|
||||
|
||||
Set width and height from AMS to composition.
|
||||
|
||||
#### Set Frame Range
|
||||
|
||||
Start frame and duration in workarea is set according to the settings in AMS. Handles are incorporated (not inclusive).
|
||||
It is expected that composition(s) is selected first before pushing this button!
|
||||
|
||||
#### Apply All Settings
|
||||
|
||||
Both previous settings are triggered at same time.
|
||||
|
||||
### Experimental tools
|
||||
|
||||
Currently empty. Could contain special tools available only for specific hosts for early access testing.
|
||||
|
|
|
|||
BIN
website/docs/assets/aftereffects_extension.png
Normal file
BIN
website/docs/assets/aftereffects_extension.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Loading…
Add table
Add a link
Reference in a new issue