Free Function Friday Ep.41 – fillCompWithShape

July 01st 2016 08:00:32am

IMPORTANT NOTE: This function is built to only handle one mask shape, even though getMaskPaths is capable of gathering multiple mask paths.

Welcome to Free Function Friday. Today marks the final episode in the series. It’s hard to believe that when I first started drafting up the idea for Free Function Friday in March of 2014, that it would be this large. Recording of episodes started in September of 2015, at which point I had already designed the opening title template, and was churning out title cards each night after work. I had built a stockpile of episodes to prepare for the weekly release schedule. There were twenty-four episodes all set for release when the series premiered on October 2, 2015. It was a mad dash after that point, with some episodes being recorded and posted just hours before public release.

So much has changed in my life during this series, I’ve moved, gotten married, changed jobs, and have worked countless hours putting this whole series together. While this is the end of the series, it is far from the end of my contribution to the community. I have big stuff already being planned as I type this, but for the moment I will rest and take in the sunshine.

Everyone in the After Effects community has been amazing, and I thank you from the bottom of my heart for all of the support, and collective participation that has been made. I hope you have learned and continue to keep learning.

Ok, enough of the babbling, and on to the final episode. fillCompWithShape is a more aggressive function than we’ve built in the past. So much so that the tutorial is just under and hour. Apologies, it could have gone way longer though easily.

We go though a few different areas of shape path data. Now this is a slight bit different than the mask shape data we dealt with in getMaskPaths and setMaskPaths, as we are creating actual Shape Layers for this one. There are three nested functions being created for this function as well, but don’t worry they are very small.

What fillCompWithShape effectively does is take mask shape data, along with a few other arguments and randomly copies the shape path within the comp 2D space as Shape Layers. You can supply a copy count, color palette, and set various layer attributes as the new Shape Layer is created. I originally used this code in a high profile project to create a series of animated triangles earlier in the year. This code is a paired down simplified version, that is a bit easier to customize hopefully.

Source Code:

var curComp = app.project.activeItem;

if(curComp instanceof CompItem){
	var layObj = curComp.layer(1); //Assumes active comp has one layer with one mask on it.
	var shapeData = getMaskShapeData(layObj);
	if(shapeData != null){
		var colorPalette = [];
		for(var r=0; r < 20; r++){
			colorPalette.push([Math.random(), Math.random(), Math.random()]);
		}
		var copies = 50;
		
		app.beginUndoGroup("Make shapes");
			fillCompWithShape(curComp, shapeData, colorPalette, copies);
		app.endUndoGroup();
	}
}

function fillCompWithShape(curComp, shapeData, colorPalette, copies){
	var compW, compH, myXPositions, myYPositions, colorPaletteLen, myColorResults, newShapeLayer, newShape, myVerts, shapeGrp;
	if(curComp instanceof CompItem){
		if(copies != null && typeof copies == "number"){
			compW = curComp.width;
			compH = curComp.height;
			myXPositions = getRandPosVals(copies, [0, compW]);
			myYPositions = getRandPosVals(copies, [0, compH]);
			colorPaletteLen = colorPalette.length;
			myColorResults = getRandPosVals(copies, [0, colorPaletteLen]);
			for(var c=0; c < copies; c++){
				newShapeLayer = curComp.layers.addShape();
				newShape = new Shape();
				myVerts = shapeData.vertices;
				newShape.vertices = myVerts;
				newShape.inTangents = shapeData.inTangents;
				newShape.outTangents = shapeData.outTangents;
				newShape.closed = shapeData.closed;
				newShape.featherTypes = shapeData.featherTypes;
				newShape.featherSegLocs = shapeData.featherSegLocs;
				newShape.featherRelSegLocs = shapeData.featherRelSegLocs;
				newShape.featherRadii = shapeData.featherRadii;
				newShape.featherInterps = shapeData.featherInterps;
				newShape.featherRelCornerAngles = shapeData.featherRelCornerAngles;
				newShape.featherTensions = shapeData.featherTensions;
				newShape.closed = true;
				
				shapeGrp = newShapeLayer.content.addProperty("ADBE Vector Shape - Group");
				shapeGrp.path.setValue(newShape);
				
				//Layer specifics (just uncomment the lines to turn feature on)
 				//newShapeLayer.blendingMode = BlendingMode.OVERLAY;
 				//newShapeLayer.moveToEnd();
 				//newShapeLayer.motionBlur = true;

				var layAnchor = newShapeLayer.property("ADBE Transform Group").property("ADBE Anchor Point");
				var layPos = newShapeLayer.property("ADBE Transform Group").property("ADBE Position");
				
				var centroids = getCentroid(myVerts);
				var xCentroid = centroids[0];
				var yCentroid = centroids[1];
				
				layAnchor.setValue([xCentroid, yCentroid]);
				layPos.setValue([myXPositions[c], myYPositions[c]]);
				
				var fillGrp = newShapeLayer.content.addProperty("ADBE Vector Graphic - Fill");
				fillGrp.color.setValue(colorPalette[Math.floor(myColorResults[c])]);
				fillGrp.opacity.setValue(50.0);
			}
		}
	}

	function getCentroid(verts){
		var vertsLen, x, y;
		x = 0;
		y = 0;
		vertsLen = verts.length;
		for(var v=0; v < vertsLen; v++){
			x += verts[v][0];
			y += verts[v][1];
		}
		return [x/vertsLen, y/vertsLen];
	}

	function randomValRange(min, max){
		return Math.random()*(max-min)+min;
	}

	function getRandPosVals(count, range){
		var r = new Array();
		for(var i=0; i < count; i++){
			r.push(randomValRange(range[0], range[1]));
		}
		return r;
	}
}

function getMaskShapeData(layObj){
	var mask, shapeData, path;
	mask = layObj.property("ADBE Mask Parade").property("ADBE Mask Atom");
	if(layObj instanceof ShapeLayer){
		path = layObj.property("ADBE Root Vectors Group").property("ADBE Vector Group").property("ADBE Vectors Group").property("ADBE Vector Shape - Group").property("ADBE Vector Shape").value;
		return {'closed': path.closed, 'featherInterps': path.featherInterps, 'featherRadii': path.featherRadii, 'featherRelCornerAngles': path.featherRelCornerAngles, 'featherRelSegLocs': path.featherRelSegLocs, 'featherSegLocs': path.featherSegLocs, 'featherTensions': path.featherTensions, 'featherTypes': path.featherTypes, 'inTangents': path.inTangents, 'outTangents': path.outTangents, 'vertices': path.vertices};
	}else if(layObj instanceof AVLayer && mask != null){
		path = layObj.property("ADBE Mask Parade").property("ADBE Mask Atom").property("ADBE Mask Shape").value;
		return {'closed': path.closed, 'featherInterps': path.featherInterps, 'featherRadii': path.featherRadii, 'featherRelCornerAngles': path.featherRelCornerAngles, 'featherRelSegLocs': path.featherRelSegLocs, 'featherSegLocs': path.featherSegLocs, 'featherTensions': path.featherTensions, 'featherTypes': path.featherTypes, 'inTangents': path.inTangents, 'outTangents': path.outTangents, 'vertices': path.vertices};
	}else{
		return null;
	}
}
Checkout more:
Free Function Friday Ep.15 – getAllTextLayers
After Effects ExtendScript Training: Ep. 9
Houdini | Noise
CG Eyes Element3D Rig
Free Function Friday Ep.8 – moveItemToFolder