Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

Grouping shapes via scripting

Explorer ,
Jul 15, 2013 Jul 15, 2013

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?

TOPICS
Scripting
2.4K
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Jul 15, 2013 Jul 15, 2013

Very difficult Nik, don't think it's possible.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jul 15, 2013 Jul 15, 2013

I believe I have found a way. But it's too hardcore and definitely needs testing. Will post on progress.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Jul 17, 2013 Jul 17, 2013

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.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Jul 17, 2013 Jul 17, 2013

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);

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jul 17, 2013 Jul 17, 2013

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?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Jul 17, 2013 Jul 17, 2013

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).

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jul 17, 2013 Jul 17, 2013

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 )

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Jul 18, 2013 Jul 18, 2013

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))

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Explorer ,
Jul 18, 2013 Jul 18, 2013

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.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Advocate ,
Jul 18, 2013 Jul 18, 2013
LATEST

Wow! that's an unexpected happy ending!;)

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines