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

SourceRectAtTime from multiple layers

Community Beginner ,
Mar 07, 2023 Mar 07, 2023

Copy link to clipboard

Copied

Hello! I have a non-trivial task, the solution of which, as it seems to me, can be useful for many motion designers.

 

The question is:

How can I make an expression for a shape rectangle ("plate") that takes the size and position of not one specific text layer (which is easy to do) but the position and size of several layers (both text and shape) and, depending on their position, would create something like a bounding box around this group of layers with a small margin that can be set through the slider. My Ae version is 2023 v 23.2.1 (build 3)

 

A few limitations:

1) the expression must take for the calculation of the bounding box all the layers that are on the layers panel above the controller layer (which is with index No. 5 in the picture that I will attach below).

2) It must take for calculations new layers that will be added above the controller layer

3) It must be applied not to the size property of the rectangle, but to the path property (this means that the Bezier rectangle, not a procedural one). This limitation exists because I do some more manipulations with the background-rectangle (beveling some corners with inTangents and outTangents)

 

I understand logically (it seems to me) how to solve this problem, but the knowledge of expressions does not yet allow me to write code, and in general I am a humanist and not a techie;)

 

This is how I see the solution to the problem logically:

 

1. The expression finds a layer with an index less than the controller layer and starts the calculation from there. That is, a cycle of the form if the index < 5 (or index of a layer with name "controller" wich will be more accurate) then we start the calculations

2. The expression finds the upper bound of the bounding box by finding the layer with the smallest coordinate on the y-axis.

3. Next, the expression finds the layer with the smallest x-coordinate to find the left border of the bounding box

4. The expression takes for the upper left corner of the bounding box the point with coordinates X from point 3 and the Y from point 2

5. This is where the problems begin. in theory, we need to add SourceRectAtTime for each layer, as well as calculate the height between them and their width. Here I do not understand how to build calculations further.

....

X. the expression adds an indent from the slider to the resulting bounding box (this is easy), we can do this through the offset path

 

I did a simpler solution to a similar problem, the width of the rectangle was adjusted to all layers simply using the width and height sliders (the anchor point was in the upper left corner of the layer), or we can use two nulls for left upper and right bottom corner to manipulate rectangle, but I just want an automated solution.

 

Here is my scene screenshot and a project file:

2023-03-07_15-20-05.png

 

TOPICS
Expressions , How to , Scripting

Views

2.7K

Translate

Translate

Report

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

correct answers 1 Correct answer

Community Expert , Mar 07, 2023 Mar 07, 2023

If you zero out the postion transform for the rectangle, I think this path expression will enclose all the layers above the controller layer:

c = thisComp.layer("controller");

for (i = 1; i < c.index; i++){
  L = thisComp.layer(i);
  r = L.sourceRectAtTime(time,false);
  ul = L.toComp([r.left,r.top]);
  lr = L.toComp([r.left+r.width,r.top+r.height]);
  if (i == 1){
    left = ul[0];
    top = ul[1];
    right = lr[0];
    bottom = lr[1];
  }else{
    left = Math.min(ul[0],left);
    top = Math.
...

Votes

Translate

Translate
Community Expert ,
Mar 07, 2023 Mar 07, 2023

Copy link to clipboard

Copied

Pins & Boxes offers a very easy solution for boxes around multiple layers.

It can create pins on corners and/or edges of any layers and then create boxes around arbitrary collections of pins. It can also deal with parented layers properly and supports animation, i.e. you can still animate the box independently of the content.

 

 

Mathias Möhl - Developer of tools like BeatEdit and Automation Blocks for Premiere Pro and After Effects

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 07, 2023 Mar 07, 2023

Copy link to clipboard

Copied

Thanks for idea, Mathias!

This seems like a good solution, but it doesn't take into account the fact that I need to be able to affect the bounding box additionally (I need to be able to chamfer some corners), among other things, this script creates a lot of extra layers, making the project heavier, and besides, this is a paid solution.
But I will keep it in mind if I fail to solve the problem manually. Thank you!

Votes

Translate

Translate

Report

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
Community Expert ,
Mar 07, 2023 Mar 07, 2023

Copy link to clipboard

Copied

Since the boxes are normal shape layers, you can also add rounded corners easily.

If you just need rounded corners on only some of the corners, you can work with multiple boxes with different corner radius and border size (for example
- one box with rounded corners and a left border of 20px and right border of 0px
- another box without rounded corrners and left border of 0px and right border of 20px
--> now only the left corners of the merged result have visible rounded corners

 

You can get even more fancy:

Mathias Möhl - Developer of tools like BeatEdit and Automation Blocks for Premiere Pro and After Effects

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

Thanks Mathias!

 

But this solution is not quite suitable for me, because:

1) Still need to select layers manually when adding new ones and choose the right pins

2) A lot of extra layers created.

3) It is important for me to work with the path property of the shape and not the size, because round corners rounds all the corners of the shape, and I need to work with certain ones, plus I need not rounding, but a bevel, that is, I need to work through inTangents and outTangents operators.

Dan came up with the perfect solution, it doesn't create unnecessary layers and automatically works no matter how many new layers I add, plus it keeps me able to work with specific angles, because this is the Bezier path.

Votes

Translate

Translate

Report

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
Community Expert ,
Mar 07, 2023 Mar 07, 2023

Copy link to clipboard

Copied

It's possible. I do this kind of thing all the time. There are math functions that will give you the max width of several layers. You can combine sourceRectAtTime() properties to derive the true position of text and shape layers. A shape layer's fill is calculated, but you can add in stroke width and combine the Shape/Transform and position properties to compensate for a lot of things. 

 

My approach is to make a sketch on paper and figure out what values need to be added together and how the position of each layer relates to the other layers in the group. I also simplify things that are going to be repeated for multiple layers by declaring variables like this:

//Layers in stack
L1 = thisComp.layer(index - 3);
L2 = thisComp.layer(index - 2);
L2 = thisComp.layer(index - 1);
b1 = L1.sourceRectAtTime();
b2 = L2.sourceRectAtTime();
b3 = L2.sourceRectAtTime();
w = Math.max(b1.width, b2.width, b3.width)

This much of an expression will find the maximum width of three different layers. Combine that with top and left, and you can figure out a way to calculate the actual position of all 3 layers no matter what the baseline shift or paragraph justification is. 

 

I don't have time to go through your comp right now, but it should be a fairly simple thing to calculate the total height and width of the text and shape layers, throw in some padding, tie the position of every layer to a master layer, and throw in some padding. It will require expressions on the size, transform/Position, and position of the shape layers, the position of all text layers except the one you want to use as the master (moving that text layer moves them all), and the position of all layers except the master. 

 

If I get some time this afternoon I'll take a look at your project and see what I can do to help.

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

Thanks Rick!
Dan came up with a solution that worked perfectly

Votes

Translate

Translate

Report

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
Community Expert ,
Mar 07, 2023 Mar 07, 2023

Copy link to clipboard

Copied

If you zero out the postion transform for the rectangle, I think this path expression will enclose all the layers above the controller layer:

c = thisComp.layer("controller");

for (i = 1; i < c.index; i++){
  L = thisComp.layer(i);
  r = L.sourceRectAtTime(time,false);
  ul = L.toComp([r.left,r.top]);
  lr = L.toComp([r.left+r.width,r.top+r.height]);
  if (i == 1){
    left = ul[0];
    top = ul[1];
    right = lr[0];
    bottom = lr[1];
  }else{
    left = Math.min(ul[0],left);
    top = Math.min(ul[1],top);
    right = Math.max(lr[0],right);
    bottom = Math.max(lr[1],bottom);
  }
}
p = [fromComp([left,top]),fromComp([right,top]),fromComp([right,bottom]),fromComp([left,bottom])];
createPath(p,[],[],true);

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

Thank you so much Dan!
This works exactly as I wanted!
You saved me a lot of time on routine projects where I had to adjust the bounding box manually.
Now I can add this to all of my projects.

 

Votes

Translate

Translate

Report

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
Community Expert ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

I forgot to include your offset slider for a border:

c = thisComp.layer("controller");
offset = c.effect("Offset")("Slider");

for (i = 1; i < c.index; i++){
  L = thisComp.layer(i);
  r = L.sourceRectAtTime(time,false);
  ul = L.toComp([r.left,r.top]);
  lr = L.toComp([r.left+r.width,r.top+r.height]);
  if (i == 1){
    left = ul[0];
    top = ul[1];
    right = lr[0];
    bottom = lr[1];
  }else{
    left = Math.min(ul[0],left);
    top = Math.min(ul[1],top);
    right = Math.max(lr[0],right);
    bottom = Math.max(lr[1],bottom);
  }
}
left -= offset;
top -= offset;
right += offset;
bottom += offset;
p = [fromComp([left,top]),fromComp([right,top]),fromComp([right,bottom]),fromComp([left,bottom])];
createPath(p,[],[],true);

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

Thanks, it works great!
But I made it even simpler) I used the standard offset path effect for the shape layer, which simply expands the borders of the rectangle and its value is attached to the slider. The result is the same visually) 
But from the point of view of calculating the corners, it is more correct to add an offset value to the points of the rectangle if some other calculations and interactions with it are planned.

By the way, how could I add beveling of some corners to this expression?
Right now I'm using this expression which allows me to do beveling corners (I'm attaching an example of what it looks like). This expression also adjusts the length of the rectangle to the size of the text layer (I use it for the lower third layer), but it's the in/outTangent operators part that's important here. 

ctrl = thisComp.layer("controller");
var tl = ctrl.effect("top left")("Slider");
var tr = ctrl.effect("top right")("Slider");
var bl = ctrl.effect("bot left")("Slider");
var br = ctrl.effect("bot right")("Slider");
var p1 = ctrl.effect("padding all")("Slider");
var p2 = ctrl.effect("horisontal margin")("Slider");
var lr = thisComp.layer("txt").sourceRectAtTime();
var h = lr.height+p1;
var w = lr.width+p1;
var t = lr.top+p1;
var l = lr.left+p1;
var newPoint = [];
var newIT = [];
var newOT = [];
	newPoint.push([tl, 0]);
	newPoint.push([w-tr+p2, 0]);
	newPoint.push([w+p2, 0 + tr]);
	newPoint.push([w+p2, h - br]);
	newPoint.push([w-br+p2, h]);
	newPoint.push([bl, h]);
	newPoint.push([0, h-bl]);
	newPoint.push([0, tl]);
	newOT = [[0,0], [0,0], [0,0], [0,0], [0,0], [0,0], [0,0], [0,0]];
	newIT = [[0,0], [0,0], [0,0], [0,0], [0,0], [0,0], [0,0], [0,0]];

createPath(newPoint, newIT, newOT, true);


I borrowed this code from one of the scripts that rounds individual mask corners. But I did not need a rounding, but only a bevel.

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

2023-03-08_23-06-58.png

Votes

Translate

Translate

Report

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
Community Expert ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

Like this, maybe:

c = thisComp.layer("controller");
offset = c.effect("Offset")("Slider");
bevelUL = c.effect("Bevel UL")("Checkbox").value;
bevelUR = c.effect("Bevel UR")("Checkbox").value;
bevelLR = c.effect("Bevel LR")("Checkbox").value;
bevelLL = c.effect("Bevel LL")("Checkbox").value;
bevelAmt = c.effect("Bevel Amt")("Slider");

for (i = 1; i < c.index; i++){
  L = thisComp.layer(i);
  r = L.sourceRectAtTime(time,false);
  ul = L.toComp([r.left,r.top]);
  lr = L.toComp([r.left+r.width,r.top+r.height]);
  if (i == 1){
    left = ul[0];
    top = ul[1];
    right = lr[0];
    bottom = lr[1];
  }else{
    left = Math.min(ul[0],left);
    top = Math.min(ul[1],top);
    right = Math.max(lr[0],right);
    bottom = Math.max(lr[1],bottom);
  }
}
left -= offset;
top -= offset;
right += offset;
bottom += offset;
p = [];
pTemp = fromComp([left,top]);
if (bevelUL){
  p.push(pTemp+[0,bevelAmt],pTemp+[bevelAmt,0]);
}else{
  p.push(pTemp);
}
pTemp = fromComp([right,top]);
if (bevelUR){
  p.push(pTemp-[bevelAmt,0],pTemp+[0,bevelAmt]);
}else{
  p.push(pTemp);
}
pTemp = fromComp([right,bottom]);
if (bevelLR){
  p.push(pTemp-[0,bevelAmt],pTemp-[bevelAmt,0]);
}else{
  p.push(pTemp);
}
pTemp = fromComp([left,bottom]);
if (bevelLL){
  p.push(pTemp+[bevelAmt,0],pTemp-[0,bevelAmt]);
}else{
  p.push(pTemp);
}
createPath(p,[],[],true);

Votes

Translate

Translate

Report

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
Community Beginner ,
Mar 09, 2023 Mar 09, 2023

Copy link to clipboard

Copied

Works great!
It's very interesting how you did the creation of the bevel on the corners, in your example only 4 points are created and not 8 as in mine, and the choice by the checkbox is very smart.
Dan, you are a lifesaver, thank you so much for your time and participation.

Votes

Translate

Translate

Report

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
Community Expert ,
Mar 08, 2023 Mar 08, 2023

Copy link to clipboard

Copied

Brilliant as usual, Dan Ebberts.

 

I'm implementing this idea in a half-dozen complex animation presets I have created for complex MOGRTS I made for training videos for one of my biggest clients. I love the 

for (i = 1; i < c.index; i++)

Never considered that before as a way to look at multiple layers.

Votes

Translate

Translate

Report

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
New Here ,
Apr 27, 2023 Apr 27, 2023

Copy link to clipboard

Copied

Hi @Dan Ebberts this looks like it could be the answer to my issue too. I'm having issues getting it to work though and I'm hoping you wouldn't mind helping. I've got a similar comp setup to the screen shot at the top of the thread, but when using the expression on rectangle size I get a reference error on the createPath. Is there something i need to put in the array?  Thanks 

Votes

Translate

Translate

Report

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
Community Expert ,
Apr 27, 2023 Apr 27, 2023

Copy link to clipboard

Copied

If the expression has createPath() in it, it's meant for a path property, not a size property. Maybe a screen shot would help?

Votes

Translate

Translate

Report

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
New Here ,
Apr 27, 2023 Apr 27, 2023

Copy link to clipboard

Copied

LATEST

Thanks @Dan Ebberts That's the one - I was in rectangle world not path. It's working perfectly. Much appreciated. 

Votes

Translate

Translate

Report

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