// Connect Vertex to Point (the vertex linker) // An After Effects (CS3) script // to aid in the animating of individual mask vertices // by linking them to other (point) properties. // version 2.0 (created to work with AE CS3 -- I also changed the way it goes about looking at selected layers and properties // by CR Green // based in part on other works by Dan Ebberts, Alejandro Perez, Juan Corcoles // Thanks to them, and as always, thanks to creativecow.net and aenhancers.com /* Note: This is meant to work with a mask layer of the same size of the comp, and that layer should have its default geometric values (size [scale], rotation, position, etc.). The 'vertex guide' layer will be (noticably) off if these values have been changed. */ var theComp = null; var aiPixelAR = 1; var preCheckOK = false; var vGuide = undefined; var guideName = "vertex guide"; var pal = new Window('palette', '',[300,100,620,284]); var theMask = null; var otherProp = null; var workingVertexIndex = 0; var seMaskLayer = null; var sePropLayer = null; var palette = mainGUIWindo(); if (palette != null) { palette.show(); } //////////////////////////////////////////// function moveGuideClick() { mainPreCheck(); if (preCheckOK) { if (theComp.layer(guideName) != undefined) { moveGuideToV(); } } } //////////////////////////////////////////// function moveGuideBackClick() { mainPreCheck(); if (preCheckOK) { if (theComp.layer(guideName) != undefined) { moveGuideToVBack(); } } } //////////////////////////////////////////// function moveGuideToV() { if (! theComp.layer(guideName).enabled) {theComp.layer(guideName).enabled = true;} var myProperty = theMask.property("ADBE Mask Shape"); var myShape = myProperty.value; var vertexLimit = myShape.vertices.length; if (workingVertexIndex == 0) { workingVertexIndex = vertexLimit; } workingVertexIndex--; v = myShape.vertices[workingVertexIndex]; theComp.layer(guideName).Position.setValue(v); reportStuff("\"" + otherProp.name + "\" of layer \"" + sePropLayer.name + "\"\n" + "Mask \"" + theMask.name + "\" of layer \"" + seMaskLayer.name + "\", vertex " + workingVertexIndex); } //////////////////////////////////////////// function moveGuideToVBack() { if (! theComp.layer(guideName).enabled) {theComp.layer(guideName).enabled = true;} var myProperty = theMask.property("ADBE Mask Shape"); var myShape = myProperty.value; var vertexLimit = (myShape.vertices.length - 1); if (workingVertexIndex == vertexLimit) { workingVertexIndex = -1; } workingVertexIndex++; v = myShape.vertices[workingVertexIndex]; theComp.layer(guideName).Position.setValue(v); reportStuff("\"" + otherProp.name + "\" of layer \"" + sePropLayer.name + "\"\n" + "Mask \"" + theMask.name + "\" of layer \"" + seMaskLayer.name + "\", vertex " + workingVertexIndex); } //////////////////////////////////////////// function linkClick() { mainPreCheck(); if (preCheckOK) { if (theComp.layer(guideName) != undefined) { app.beginUndoGroup("Link Vertex to Point"); linkVertexToProp(); app.endUndoGroup(); } else { alert("You have to create a vertex guide layer first."); } } } //////////////////////////////////////////// function linkVertexToProp() { var myIn = seMaskLayer.inPoint; var myOut = seMaskLayer.outPoint; var f = Math.round(myIn/theComp.frameDuration); // frame counter var myProperty = theMask.property("ADBE Mask Shape"); var myShape = myProperty.value; var t,p,vv; while (f <= Math.round(myOut/theComp.frameDuration)){ pal.progArea.text = ("Setting keyframe " + f); pal.hide();//these two lines now needed to fix ae7 pal.show();//these two lines now needed to fix ae7 t = f*theComp.frameDuration; p = otherProp.valueAtTime(t,false); myShape = myProperty.valueAtTime(t,true); vv = myShape.vertices; vv[workingVertexIndex] = [p[0], p[1]]; myShape.vertices = vv; myProperty.setValueAtTime(t,myShape); f++; } // now we get the selection back to where it was theMask.selected = false;//mask shape gets selected when adding keyframes, so we deselect the whole mask first theMask.selected = true;//then select just the mask otherProp.selected = true; /////////////////////////// reportStuff("vertex " + workingVertexIndex + " of mask \"" + theMask.name + "\" of layer \"" + seMaskLayer.name + "\"\n connected to\n" + "\"" + otherProp.name + "\" of layer \"" + sePropLayer.name + "\"."); if (theComp.layer(guideName) != null) {theComp.layer(guideName).enabled = false;} } //////////////////////////////////////////// function mainGUIWindo() { if (pal != null) { pal.mainPnl = pal.add('panel', [10,1,309,111], 'the vertex linker'); pal.progArea = pal.add("statictext", [21,15,255,111], 'Click \"Make Guide\" first,\nmove the guide layer with the \"<\" and \">\" buttons, then click \"Apply\".', {multiline:true} ); pal.guideBtn = pal.add('button', [10,120,99,138], 'Make Guide', {name:'guide'}); pal.huhBtn = pal.add('button', [10,148,99,166], 'Help', {name:'huh'}); pal.backVBtn = pal.add('button', [108,120,202,138], '< Prev. Vertex', {name:'backV'}); pal.nextVBtn = pal.add('button', [210,120,309,138], 'Next Vertex >', {name:'nextV'}); pal.applyBtn = pal.add('button', [108,148,202,166], 'Apply', {name:'apply'}); pal.stopBtn = pal.add('button', [210,148,309,166], 'Close', {name:'stop'}); pal.stopBtn.onClick = function () {this.parent.close(1)}; pal.guideBtn.onClick = newGuideClick; pal.backVBtn.onClick = moveGuideBackClick; pal.nextVBtn.onClick = moveGuideClick; pal.applyBtn.onClick = linkClick; pal.huhBtn.onClick = function() {alert('Helpful Hints:\n The weirdest part of this is selecting just a mask and an appropriate property.\n\n' + ' You must only select a mask and a point property (like position).\n\n If you keep getting an annoying message' + ' about not having the right stuff selected, you should deselect everything (menu: "Edit" item: "DeSelect All"), ' + 'then click on a mask (NOT a mask property) and command-click (Windows: control-click) the point property you want to link the mask vertex to.' + '\n\n Usually, you can also deselect everything by clicking on the timeline area below all the layers.') ; alert('Part 2:\n After the vertex guide is created, you can delete it, or you can keep it and change the look of it (change the ' + 'color, mask, etc.) if that suits you.\n Always use the vertex guide to keep track of the "selected" vertex.' + '\n If the vertex guide is not showing up, it may be targeting an off-screen vertex, so click the < or > buttons to target a' + ' visible vertex.'); alert('IMPORTANT:\n This is meant to be used with a mask layer whose dimensions match the comp. Also, The geometrics ' + '(scale, rotation, position, etc.) of the mask layer should be their default values; if these values have been changed, ' + 'the vertex guide will be off, and you will get bad results.'); }; } return pal; } //////////////////////////////////////////// function putMaskInLayer(theLayer, theVerts, mMode) { var crossHair = theLayer.mask.addProperty("ADBE Mask Atom"); var theShape = new Shape(); theShape.vertices = theVerts; crossHair.maskShape.setValue(theShape); crossHair.maskMode = mMode; } //////////////////////////////////////////// function newGuideClick() { mainPreCheck(); if (preCheckOK) { if (theComp.layer(guideName) == undefined) { app.beginUndoGroup("Vertex Guide Creation"); makeNewVGuide(); app.endUndoGroup(); } else { if (! theComp.layer(guideName).enabled) {theComp.layer(guideName).enabled = true;} } } } //////////////////////////////////////////// function makeNewVGuide() { var guideColor = [1, 0, 0]; var crossHairVertsHoriz = [ [0,49],[0,52],[101,52],[101,49] ]; var crossHairVertsVerti = [ [52,0],[49,0],[49,101],[52,101] ]; var guideSize = 101; // Create a solid vGuide = theComp.layers.addSolid(guideColor, guideName, guideSize, guideSize, aiPixelAR); // Make it a guide layer vGuide.guideLayer = true; putMaskInLayer(vGuide, crossHairVertsHoriz, MaskMode.DIFFERENCE); putMaskInLayer(vGuide, crossHairVertsVerti, MaskMode.DIFFERENCE); moveGuideToV(); ///////////////// in ae7, the selection gets killed when creating the vertex guide, so we deselect guide and reselect mask and point: vGuide.selected = false; theMask.selected = true; otherProp.selected = true; ///////////////// reportStuff("\"" + otherProp.name + "\" of layer \"" + sePropLayer.name + "\"\n" + "Mask \"" + theMask.name + "\" of layer \"" + seMaskLayer.name + "\""); } //////////////////////////////////////////// function reportStuff(stuffToReport) { pal.progArea.text = (stuffToReport); } //////////////////////////////////////////// function mainPreCheck() { preCheckOK = false; if (app.project != null) { if (app.project.activeItem != null) { if (app.project.activeItem instanceof CompItem) { var propSel = app.project.activeItem.selectedProperties; var selLength = propSel.length; if (selLength < 2) {//might want to add || selLength > 2 alert("Please select one mask and one other property."); } else { theComp = app.project.activeItem; for (var thisSelectedProp = 0; thisSelectedProp <= selLength; thisSelectedProp++) { if (propSel[thisSelectedProp].matchName == "ADBE Mask Atom") { theMask = propSel[thisSelectedProp];// grab first mask encountered //get its layer (um ... is this the easiest/most efficient way? i was hoping something like property.layer would work) deepness=propSel[thisSelectedProp].propertyDepth; d = propSel[thisSelectedProp]; for (var i=1;i<=deepness;i++) { d=d.parentProperty; } seMaskLayer = d; break; } } ///////////////// if (theMask != null) { for (var selPropIndex = 0; selPropIndex <= selLength; selPropIndex++) { thisProp = propSel[selPropIndex]; if (thisProp.constructor.name == "Property") { // okay, it's a property, now look for a 2D array (might want to include numbers and 3D arrays if this fails ... ) if (thisProp.value.constructor == Array) { // it's an array, now how many dimensions? propDim = thisProp.value.length; if (propDim >1) { //for now, we'll just accept 2 or greater D properties //now, make sure these are numbers: if ( (thisProp.value[0].constructor == Number) && (thisProp.value[1].constructor == Number) ) { otherProp = thisProp; //get its layer deepness=thisProp.propertyDepth; d = thisProp; for (var i=1;i<=deepness;i++) { d=d.parentProperty; } sePropLayer = d; break; } } } } } if (otherProp != null) { preCheckOK = true; // huzzah! } else { alert("No appropriate property selected."); } } else { alert("No mask selected."); } }// if (app.project.activeItem.selectedProperties.length != 2) } } } }