Copy link to clipboard
Copied
Hi everyone.
I'd like to do "Ctrl-G" for shapes via scripting, but I can't realize how to do it. Could anyone suggest a way to do this?
Copy link to clipboard
Copied
Very difficult Nik, don't think it's possible.
Copy link to clipboard
Copied
I believe I have found a way. But it's too hardcore and definitely needs testing. Will post on progress.
Copy link to clipboard
Copied
Nik, I may be wrong but it seems (somewhat) simple, if your solution actually is "hardcore" you may not be on the right path.
I'll try to post some sample code later on, tell me if you need me to.
Copy link to clipboard
Copied
OK, I had some time to put together this script, I'll go trhough it quickly so tell me if you would rather like a written tutorial.
So it's all pretty simple but there are a few steps:
1) In this case I'm using the selected shapes, even if they're on separate layers. So you need first to loop trough all the selected properties to keep only Shapes:
// Get the selected Shapes in array
for (var i=0; i<comp.selectedLayers.length; i++)
{
// Current layer
layer = comp.selectedLayers;
// Only care about shape layers
if (layer instanceof ShapeLayer)
{
for (var j=0; j<layer.selectedProperties.length; j++)
{
// Current property
property = layer.selectedProperties
; // Only care about Shape objects
if (property.matchName == matchName_shape)
{
// Store the shape
shapes[shapes.length] = readProperty(property);
property.enabled = false;
}
}
// If we don't have a base shape layer yet
// store the current one if we've got shapes in the array
if (firstLayer == null && shapes.length)
{
firstLayer = layer;
}
}
}
Here I store in firstLayer the first Shape Layer found, that's the layer on which I'll create the group. The matchName_... variables are the default match names used by After Effects.
2) You can see I'm not storing the shapes themselves but I use the readProperty function given below. That's because as soon as you'll create a new property the object referencing the selected properties may not be defined anymore.
The readProperty function recursively reads the matchName, name and value of the properties constituting the shape and it's all stored in an array.
function readProperty(prop)
{
var data = new Array();
data.matchName = prop.matchName;
data.name = prop.name;
if (prop.numProperties)
{
for (var i=1; i<= prop.numProperties; i++)
{
data[data.length] = readProperty(prop(i));
}
}
else
{
data.value = prop.value;
}
return data;
}
3) Now we can create the group on firstLayer, which is in fact a shape object:
if (shapeRoot.canAddProperty(matchName_shape))
{
shapeGroup = shapeRoot.addProperty(matchName_shape);
shapeGroup.name = "Shape Group";
shapeGroup("ADBE Vector Materials Group").remove();
...
4) And we can now create clones of the shapes inside that group:
for (var i=0; i<shapes.length; i++)
{
// Create a new shape inside the group
cloneProperty(shapes, shapeGroup(matchName_group));
}
5) So the cloneProperty function simply reads recursively the array created in readProperty and creates new properties accordingly:
// Utility function to create properties recursively
function cloneProperty(data, parent)
{
var clone;
try
{
if (parent.canAddProperty(data.matchName))
{
clone = parent.addProperty(data.matchName);
try { clone.name = data.name; } catch (err) { ; }
}
else
{
clone = parent(data.matchName);
}
if (clone)
{
if (data.length)
{
for (var i=0; i<= data.length; i++)
{
cloneProperty(data, clone);
}
}
else
{
try { clone.setValue(data.value); } catch (err) { ; }
}
}
else
{
alert("Could not add prop: " + data.matchName + " in " + parent.name);
}
}
catch(err)
{
// There must be something wrong in my loop since this point should never be reached, but it is ...
}
}
As I wrote at the end, there must be a problem with the read or clone functions since I had to add a try/catch otherwise the loop was going too deep.
6) Not done here: if you want to delete the original shapes you'll need to look for them using their names I guess, since everytime you'll delete one you won't be able to use a reference to this property previously created. In this case I just toggle their visibility.
___________________________________________________________________
That's about it, here's the full script that you can run to try all this out:
function groupShapesTest(comp)
{
function readProperty(prop)
{
var data = new Array();
data.matchName = prop.matchName;
data.name = prop.name;
if (prop.numProperties)
{
for (var i=1; i<= prop.numProperties; i++)
{
data[data.length] = readProperty(prop(i));
}
}
else
{
data.value = prop.value;
}
return data;
}
// Utility function to create properties recursively
function cloneProperty(data, parent)
{
var clone;
try
{
if (parent.canAddProperty(data.matchName))
{
clone = parent.addProperty(data.matchName);
try { clone.name = data.name; } catch (err) { ; }
}
else
{
clone = parent(data.matchName);
}
if (clone)
{
if (data.length)
{
for (var i=0; i<= data.length; i++)
{
cloneProperty(data, clone);
}
}
else
{
try { clone.setValue(data.value); } catch (err) { ; }
}
}
else
{
alert("Could not add prop: " + data.matchName + " in " + parent.name);
}
}
catch(err)
{
// There must be something wrong in my loop since this point should never be reached, but it is ...
}
}
if (comp && comp instanceof CompItem)
{
// Open the undo group
app.beginUndoGroup("Group Shapes");
// Put a try/catch to avoid any Undo group mismatch
try
{
// Adobe's matchname for all the Shape stuff
var matchName_shape = "ADBE Vector Group";
var matchName_root = "ADBE Root Vectors Group";
var matchName_group = "ADBE Vectors Group";
var firstLayer = null;
var layer;
var property;
var shapes = new Array();
// Get the selected Shapes in array
for (var i=0; i<comp.selectedLayers.length; i++)
{
// Current layer
layer = comp.selectedLayers;
// Only care about shape layers
if (layer instanceof ShapeLayer)
{
for (var j=0; j<layer.selectedProperties.length; j++)
{
// Current property
property = layer.selectedProperties
; // Only care about Shape objects
if (property.matchName == matchName_shape)
{
// Store the shape
shapes[shapes.length] = readProperty(property);
property.enabled = false;
}
}
// If we don't have a base shape layer yet
// store the current one if we've got shapes in the array
if (firstLayer == null && shapes.length)
{
firstLayer = layer;
}
}
}
if (firstLayer)
{
// Now that we have the selected properties,
// Create a new group
var shapeRoot = firstLayer(matchName_root);
var shapeGroup;
if (shapeRoot.canAddProperty(matchName_shape))
{
shapeGroup = shapeRoot.addProperty(matchName_shape);
shapeGroup.name = "Shape Group";
shapeGroup("ADBE Vector Materials Group").remove();
// Now that we have the group,
// Loop through the shapes and creates copies inside the group
for (var i=0; i<shapes.length; i++)
{
// Create a new shape inside the group
cloneProperty(shapes, shapeGroup(matchName_group));
}
}
else
{
alert("ERR > Could not create shape group");
}
// Close the undo group
app.endUndoGroup();
}
else
{
alert("MSG > Please select at least 1 shape.");
}
}
catch (err)
{
var file = err.fileName.split("/");
alert( err.name + " while running the script \"" + file[file.length-1]+ "\" (" + err.line + "):\n" + err.message, "Script Error");
}
}
else
{
alert("MSG > Please open a comp and select at least 1 shape.");
}
}
groupShapesTest(app.project.activeItem);
Copy link to clipboard
Copied
I am sencirely sorry, but you consider this an easy way?
Well I had created a simple recursive function that works for now but has 4 major setbacks, which may be solved in future
https://github.com/ae-scripting/scripting-snippets/blob/master/shape-duplicator.jsx
The main troubles are dashes and gaps and gradients, others are pretty solvable.
Anyway there's no native way to do this simplest action. Why, Adobe? Why?
Copy link to clipboard
Copied
Well I only think the idea is simple: get the properties of the shapes and re-create them using simple recursive functions. If you look at the (incomplete) code I posted this doesn't take many lines, most of it is looking at the selected properties and safety stuff. I did forget about the keyframes though, sorry about that, so a big chunk is indeed missing from my code.
You'll think I'm playing on words but I'm not trying to: I never thought it was easy to do (not hardcore either though) and I agree the resulting script is a bit long, and yes, it's pretty weird there isn't a utility function to do that in the API.
About the limitations, obviously nothing can be done for gradients and dashes but for the keyframes I think you're not setting the ease, which causes your other 2 issues. You need to get the ease in/out which should be an array of KeyframeEase objects if I remember properly:
easeIn = property.keyInTemporalEase(key);
easeOut = property.keyOutTemporalEase(key);
Then need to set these with the setTemporalEaseAtKey function.
I'm not sure and I'm just guessing but that may be why you can't set the tangents (because there's no ease yet).
Copy link to clipboard
Copied
As for dashes - I really can do it manually line by line, but somehow I can't do it loop. Strange.
Gradients - yeah, I believe I can't copy them. But if I can't the whole meaning of my efforts is lost.
Thanks for the easing tip. BTW, fork if you want )
Copy link to clipboard
Copied
That may a stupid question but I feel I should have asked this in my first post: since you're apparently trying to do exactly what Ctrl+G does why do this using a script function?
You could let Ae do it for you:
- first loop through the selected layers and unselect them (.selected = false) to make sure that no property is selected.
- then select all the shapes you want to group (.selected = true)
- then run "Group Shapes" (app.executeCommand(3741))
Copy link to clipboard
Copied
Oh, man... I've been known for solving simple problems hard way, but this way the biggest waste of time for me )
I couldn't find this command code in the first place. Thanks.
Copy link to clipboard
Copied
Wow! that's an unexpected happy ending!;)
Get ready! An upgraded Adobe Community experience is coming in January.
Learn more