// ==================== |-----| ====================
// ==================== | api | ====================
// ==================== |-----| ====================
// Include all Duik methods and DuAEF Extension
/**
* <h3>A collection of tools for exporting and importing stuff in After Effects</h3>
* <p>DuIO requires <i>DuAEF</i>, the <i>Duduf After Effects Framework</i>. Two builds of the <i>DuIO API</i> are available:<br />
* <ul><li><code>DuIO_api.jsxinc</code> does not include <i>DuAEF</i>, and can be used to compine multiple <i>Duduf APIs</i> with a single copy of <i>DuAEF</i>.<br />
* Be careful to grab the right version of <i>DuAEF</i> in this case.</li>
* <li><code>DuAEF_DuIO_api.jsxinc</code> includes all dependencies, with <i>DuAEF</i>, and is easier to include in your scripts.</li></ul></p>
* @example
* // Encapsulate everything to avoid global variables!
* // The parameter is either undefined (stand alone script) or the panel containing the ui (ScriptUI)
* (function(thisObj)
* {
* // If you only need Duik, just include DuAEF_Duik_api
* #include "DuAEF_DuIO_api.jsxinc";
*
* // Running the init() method of DuAEF is required to setup everything properly.
* DuAEF.init( "YourScriptName", "1.0.0", "YourCompanyName" );
*
* // These info can be used by the framework to improve UX, but they're optional
* DuESF.chatURL = 'http://chat.rxlab.info'; // A link to a live-chat server like Discord or Slack...
* DuESF.bugReportURL = 'https://github.com/RxLaboratory/DuAEF_Dugr/issues/new/choose'; // A link to a bug report form
* DuESF.featureRequestURL = 'https://github.com/RxLaboratory/DuAEF_Dugr/issues/new/choose'; // A link to a feature request form
* DuESF.aboutURL = 'http://rxlaboratory.org/tools/dugr'; // A link to the webpage about your script
* DuESF.docURL = 'http://dugr.rxlab.guide'; // A link to the documentation of the script
* DuESF.scriptAbout = 'Duduf Groups: group After Effects layers!'; // A short string describing your script
* DuESF.companyURL = 'https://rxlaboratory.org'; // A link to your company's website
* DuESF.rxVersionURL = 'http://version.rxlab.io' // A link to an RxVersion server to check for updates
*
* // Build your UI here, declare your methods, etc.
*
* // This will be our main panel
* var ui = DuScriptUI.scriptPanel( thisObj, true, true, new File($.fileName) );
* ui.addCommonSettings(); // Automatically adds the language settings, location of the settings file, etc
*
* DuScriptUI.staticText( ui.settingsGroup, "Hello world of settings!" ); // Adds a static text to the settings panel
* DuScriptUI.staticText( ui.mainGroup, "Hello worlds!" ); // Adds a static text to the main panel
*
* // When you're ready to display everything
* DuScriptUI.showUI(ui);
*
* // Note that if you don't have a UI or if you don't use DuScriptUI to show it,
* // you HAVE TO run this method before running any other function:
* // DuAEF.enterRunTime();
*
* })(this);
* @example
* // Encapsulate everything to avoid global variables!
* // The parameter is either undefined (stand alone script) or the panel containing the ui (ScriptUI)
* (function(thisObj)
* {
* // If you need to combine Duik and other APIs like DuIO or DuGR
* // Include DuAEF first, and then stand-alone APIs
* #include "DuAEF.jsxinc";
* #include "DuGR_api.jsxinc";
* #include "Duik_api.jsxinc";
* #include "DuIO_api.jsxinc";
*
* // Running the init() method of DuAEF is required to setup everything properly.
* DuAEF.init( "YourScriptName", "1.0.0", "YourCompanyName" );
*
* // These info can be used by the framework to improve UX, but they're optional
* DuESF.chatURL = 'http://chat.rxlab.info'; // A link to a live-chat server like Discord or Slack...
* DuESF.bugReportURL = 'https://github.com/RxLaboratory/DuAEF_Dugr/issues/new/choose'; // A link to a bug report form
* DuESF.featureRequestURL = 'https://github.com/RxLaboratory/DuAEF_Dugr/issues/new/choose'; // A link to a feature request form
* DuESF.aboutURL = 'http://rxlaboratory.org/tools/dugr'; // A link to the webpage about your script
* DuESF.docURL = 'http://dugr.rxlab.guide'; // A link to the documentation of the script
* DuESF.scriptAbout = 'Duduf Groups: group After Effects layers!'; // A short string describing your script
* DuESF.companyURL = 'https://rxlaboratory.org'; // A link to your company's website
* DuESF.rxVersionURL = 'http://version.rxlab.io' // A link to an RxVersion server to check for updates
*
* // Build your UI here, declare your methods, etc.
*
* // This will be our main panel
* var ui = DuScriptUI.scriptPanel( thisObj, true, true, new File($.fileName) );
* ui.addCommonSettings(); // Automatically adds the language settings, location of the settings file, etc
*
* DuScriptUI.staticText( ui.settingsGroup, "Hello world of settings!" ); // Adds a static text to the settings panel
* DuScriptUI.staticText( ui.mainGroup, "Hello worlds!" ); // Adds a static text to the main panel
*
* // When you're ready to display everything
* DuScriptUI.showUI(ui);
*
* // Note that if you don't have a UI or if you don't use DuScriptUI to show it,
* // you HAVE TO run this method before running any other function:
* // DuAEF.enterRunTime();
*
* })(this);
* @namespace
* @author Nicolas Dufresne and contributors
* @copyright 2008 - 2021 Nicolas Dufresne, RxLaboratory
* @version 3.0.0
* @requires DuAEF>=1.0.0
* @category DuIO
* @license GPL-3.0 <br />
* DuIO is free software: you can redistribute it and/or modify<br />
* it under the terms of the GNU General Public License as published by<br />
* the Free Software Foundation, either version 3 of the License, or<br />
* (at your option) any later version.<br />
*<br />
* DuIO is distributed in the hope that it will be useful,<br />
* but WITHOUT ANY WARRANTY; without even the implied warranty of<br />
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the<br />
* GNU General Public License for more details.<br />
*<br />
* You should have received a copy of the GNU General Public License<br />
* along with DuIO. If not, see {@link http://www.gnu.org/licenses/}.
*/
var DuIO = {};
// ==================== |---------| ====================
// ==================== | strings | ====================
// ==================== |---------| ====================
DuScriptUI.String.IMPORT_ANIMATION = "Import animation";// ==================== |-----------| ====================
// ==================== | animation | ====================
// ==================== |-----------| ====================
/**
* Animation IO
* @namespace
* @category DuIO
*/
DuIO.Animation = {};
/**
* Pastes the animation previously copied by {@link Duik.Animation.copy} to the selected properties.
* @param {DuAELayerAnimation[]} data The animation data to paste
* @param {CompItem} [comp] The composition.
* @param {Boolean} [replace=false] - Whether to completely erase and replace the current animation
* @param {Boolean} [offset=false] - Whether to offset the animation from the current value
* @param {Boolean} [reverse=false] - Whether to reverse the animation
* @param {string[]} [matchNames=[]] A filter to apply only on specific property types
* @param {Boolean} [keyframesOnly=true] - If false, the value of properties without keyframes will be set too.
* @param {Boolean} [expressions=false] - If true, the expression is set too.
* @param {Boolean} [dontMoveAncestors=false] - When set to true, the transform (position, rotation) values for ancestor layers (the ones without parent) will be offset to 0 before applying the animation.
*/
DuIO.Animation.paste = function ( layers, data, replace, offset, reverse, matchNames, keyframesOnly, expressions, dontMoveAncestors )
{
var comp = DuAEProject.getActiveComp();
if (!comp) return;
if (typeof layers === 'undefined')
{
layers = DuAEComp.getSelectedLayers();
if (layers.length == 0) layers = comp.layers;
}
if (layers.length == 0) return;
//options
var time = comp.time;
if (data.firstKeyFrameTime) time -= data.firstKeyFrameTime;
replace = def( replace, false );
offset = def( offset, false );
reverse = def( reverse, false );
matchNames = def(matchNames, []);
keyframesOnly = def( keyframesOnly, true);
expressions = def( expressions, !keyframesOnly);
dontMoveAncestors = def(dontMoveAncestors, false);
DuAE.beginUndoGroup(DuScriptUI.String.PASTE_KEYFRAMES, false);
DuAEProject.setProgressMode(true);
var remaining = DuAELayer.setAnims( layers, data, time, undefined, expressions, keyframesOnly, replace, matchNames, offset, reverse, dontMoveAncestors );
if (remaining.length > 0)
{
var ui_pasteLayerPicker = DuScriptUI.layerPickerDialog("Missing layers");
for (var i = 0, num = remaining.length; i < num; i++)
{
ui_pasteLayerPicker.addSelector(remaining[i]._index + ' | ' + remaining[i]._name);
//try to preselect by name
var ok = false;
for (var j = 1, numLayers = comp.numLayers; j <= numLayers; j++)
{
var l = comp.layer(j);
if (l.name.toLowerCase() == remaining[i]._name.toLowerCase())
{
ui_pasteLayerPicker.layerPicker.selectors[i].setCurrentIndex(j);
ok = true;
break;
}
}
if (!ok && remaining[i]._index > 0 && remaining[i]._index <= comp.numLayers) ui_pasteLayerPicker.layerPicker.selectors[i].setCurrentIndex(remaining[i]._index);
}
ui_pasteLayerPicker.onAccept = function () {
DuAE.beginUndoGroup(DuScriptUI.String.PASTE_KEYFRAMES, false);
DuAEProject.setProgressMode(true);
DuAELayer.setAllAnims(
ui_pasteLayerPicker.getLayers(),
remaining,
time,
undefined,
expressions,
keyframesOnly,
replace,
matchNames,
offset,
reverse
);
DuAEProject.setProgressMode(false);
DuAE.endUndoGroup(DuScriptUI.String.PASTE_KEYFRAMES);
};
DuAEProject.setProgressMode(false);
DuScriptUI.showUI(ui_pasteLayerPicker);
}
DuAEProject.setProgressMode(false);
DuAE.endUndoGroup(DuScriptUI.String.PASTE_KEYFRAMES);
}
/**
* Copies all the animations on selected layers, and saves them to a Json file.
* @param {File} file The file to save the data
* @param {Layer[]|DuList<Layer>|LayerCollection} [layers] An array of Layers or LayerCollection with the animation. Selected layers from the current comp if omitted.
* @param {Boolean} [selectedKeysOnly] Wether to copy only selected keys or not. If omitted, will be true if there are selected keyframes, false otherwise.
* @return {DuAELayerAnimation[]} The animations
*/
DuIO.Animation.toJson = function( file, layers, selectedKeysOnly)
{
layers = def( layers, DuAEComp.getSelectedLayers() );
if (layers.length == 0) return null;
var comp = layers[0].containingComp;
//wether to store only selected keys if there are any
selectedKeysOnly = def( selectedKeysOnly, DuAELayer.haveSelectedKeys(layers) );
//end time of the animation to store
var endTime = comp.workAreaDuration + comp.workAreaStart;
//start time of the animation to store
var startTime;
// If there are selected keys, look for the time of the first one
if (selectedKeysOnly) startTime = DuAELayer.firstKeyFrameTime(layers,true);
else startTime = comp.workAreaStart;
var data = {};
data.duio = {};
data.duio.version = DuESF.scriptVersion.fullVersion;
data.duio.animation = true;
data.duio.rig = false;
//copy the animation
var anims = DuAELayer.getAnims( layers, selectedKeysOnly, [startTime, endTime] );
//clean data
data.layers = DuIO.Animation.cleanExportData(anims);
//save data
DuFile.saveJSON(data,file);
return anims;
}
/**
* Loads an animation file and applies it on the layers
* @param {File} jsonFile The file containing the animation
* @param {Layer[]|DuList<Layer>|LayerCollection} [layers] The layers. If omitted, will get the selected layers from the current comp, or all layers if none are selected.
* @param {Boolean} [keyframesOnly=false] If true, will load only keyframes, but not static values or expressions.
* @param {string[]} [matchNames=[]] A filter for the properties to load. If empty, will load all properties.
* @param {Boolean} [offset=false] Whether to offset the current values or use absolute values.
* @param {Boolean} [reverse=false] Whether to reverse the keyframes in time.
*/
DuIO.Animation.fromJson = function( jsonFile, layers, keyframesOnly, matchNames, offset, reverse )
{
if (!jsonFile.exists) return;
keyframesOnly = def( keyframesOnly, false );
DuAE.beginUndoGroup( DuScriptUI.String.IMPORT_ANIMATION, false );
DuAEProject.setProgressMode(true);
var data = DuFile.parseJSON( jsonFile );
//TODO version check
var anim = data.layers;
//clean data
anim = DuIO.Animation.cleanImportData(anim);
DuIO.Animation.paste( layers, anim, false, offset, reverse, matchNames, keyframesOnly, undefined, true );
DuAEProject.setProgressMode(false);
DuAE.endUndoGroup( DuScriptUI.String.IMPORT_ANIMATION );
}
/**
* Cleans data from an After Effects animation before exporting it.<br />
* This is a low-level function which you may need only if building your own export formats.<br />
* It cleans the data returned by the "getAnim" functions (see {@link DuAELayer.getAnim}, {@link DuAELayer.getAnims}, {@link DuAEProperty.getAnim}) to be able to store it in a text file.
* @param {object} data The animation
* @return {object} The data cleaned
*/
DuIO.Animation.cleanExportData = function (data)
{
var newData = data;
if (newData.keys)
{
for (var k = 0, num = newData.keys.length ; k < num; k++)
{
newData.keys[k]._inInterpolationType = DuIO.Animation.keyframeInterpolationTypeToName(newData.keys[k]._inInterpolationType);
newData.keys[k]._outInterpolationType = DuIO.Animation.keyframeInterpolationTypeToName(newData.keys[k]._outInterpolationType);
}
}
if (newData.anims)
{
for (var a = 0, num = newData.anims.length ; a < num; a++)
{
newData.anims[a] = DuIO.Animation.cleanExportData(newData.anims[a]);
}
}
if (newData instanceof Array)
{
for (var a = 0, num = newData.length ; a < num; a++)
{
newData[a] = DuIO.Animation.cleanExportData(newData[a]);
}
}
return newData;
}
/**
* Cleans data from a JSON before loading it.<br />
* This is a low-level function which you may need only if building your own import formats.<br />
* It cleans the data stored after having used {@link DuAEF.Interchange.animation.cleanAnimExportData}<br />
* to be able to set the animations using the "setAnim" functions (see {@link DuAEF.DuAE.Layer.setAnim}, {@link DuAEF.DuAE.Property.setAnim}).
* @param {object} data The animation
* @return {object} The data cleaned
*/
DuIO.Animation.cleanImportData = function (data)
{
var newData = data;
if (newData.keys)
{
for (var k = 0, num = newData.keys.length ; k < num; k++)
{
newData.keys[k] = DuIO.Animation.cleanKeyframeImportData(newData.keys[k]);
}
}
if (newData.anims)
{
for (var a = 0, num = newData.anims.length ; a < num; a++)
{
newData.anims[a] = DuIO.Animation.cleanImportData(newData.anims[a]);
}
}
if (newData instanceof Array)
{
for (var a = 0, num = newData.length ; a < num; a++)
{
newData[a] = DuIO.Animation.cleanImportData(newData[a]);
}
}
return newData;
}
/**
* Cleans data from a JSON before loading it.<br />
* This is a low-level function which you may need only if building your own import formats.<br />
* It cleans the data stored after having used {@link DuAEF.Interchange.animation.cleanAnimExportData}<br />
* to be able to set the animations using the "setAnim" functions (see {@link DuAEF.DuAE.Layer.setAnim}, {@link DuAEF.DuAE.Property.setAnim}).
* @memberof DuAEF.Interchange.animation
* @param {object} keyframeData The keyframe data which is being loaded
* @return {object} The data cleaned
*/
DuIO.Animation.cleanKeyframeImportData = function (keyframeData)
{
var newData = keyframeData;
newData._inInterpolationType = DuIO.Animation.keyframeInterpolationNameToType(newData._inInterpolationType);
newData._outInterpolationType = DuIO.Animation.keyframeInterpolationNameToType(newData._outInterpolationType);
for (var i = 0, num = newData.inEase.length; i < num; i++)
{
var inInfluence = newData.inEase[i].influence;
if (inInfluence < 0.1) inInfluence = 0.1;
if (inInfluence > 100) inInfluence = 100;
var outInfluence = newData.outEase[i].influence;
if (outInfluence < 0.1) outInfluence = 0.1;
if (outInfluence > 100) outInfluence = 100;
newData.inEase[i] = new KeyframeEase(newData.inEase[i].speed, inInfluence);
newData.outEase[i] = new KeyframeEase(newData.outEase[i].speed, outInfluence);
}
// If the value is a path
if (typeof newData.value.vertices !== 'undefined')
{
var s = new Shape();
var v = newData.value;
s.closed = v.closed;
s.featherInterps = v.featherInterps;
s.featherRadii = v.featherRadii;
s.featherRelCornerAngles = v.featherRelCornerAngles;
s.featherRelSegLocs = v.featherRelSegLocs;
s.featherSegLocs = v.featherSegLocs;
s.featherTensions = v.featherTensions;
s.featherTypes = v.featherTypes;
s.inTangents = v.inTangents;
s.outTangents = v.outTangents;
s.vertices = v.vertices;
newData.value = s;
}
return newData;
}
/**
* Gets the name of an After Effects interpolation type.<br />
* This is a low-level function which you should not need.<br />
* It used by {@link DuIO.Animation.cleanAnimExportData} to store interpolation with their names.
* @param {KeyframeInterpolationType} type The After Effects interpolation type
* @return {string} The interpolation name or empty string if not found
*/
DuIO.Animation.keyframeInterpolationTypeToName = function (type)
{
if (type == KeyframeInterpolationType.LINEAR) return 'linear';
if (type == KeyframeInterpolationType.BEZIER) return 'bezier';
if (type == KeyframeInterpolationType.HOLD) return 'hold';
else return '';
}
/**
* Gets the After Effects interpolation type with its name.<br />
* This is a low-level function which you should not need.<br />
* It used by {@link DuAEF.Interchange.cleanAnimImportData} to set interpolation from their names.
* @memberof DuAEF.Interchange.animation
* @param {string} name The interpolation name
* @return {KeyframeInterpolationType} The interpolation type or null if not found
*/
DuIO.Animation.keyframeInterpolationNameToType = function (name)
{
if (!name) return KeyframeInterpolationType.LINEAR;
name = name.toString();
if (name.toLowerCase() == 'linear' || name == '6612') return KeyframeInterpolationType.LINEAR;
else if (name.toLowerCase() == 'bezier' || name == '6613') return KeyframeInterpolationType.BEZIER;
else if (name.toLowerCase() == 'hold' || name == '6614') return KeyframeInterpolationType.HOLD;
else return null;
}
Source