Create Nulls From Paths EXTENDED

在 AE 2018 中,Adobe 引入了使用表达式控制路径的可能性,与该版本一起出现的是由 Adob​​e 创建的 Create Nulls From Paths。
这是对 After Effects 的路径功能一个很好的补充,但缺少一些功能。这和Create Nulls From Paths师出同源,但它扩展了一些更多的功能。
除了对脚本的一些小改进外,添加的功能还有:从选定的创建路径、手柄控件、使用定位器,可以更精确控制路径,也可以方便制作贝塞尔曲线可视化。
该脚本适用于 AE 2018 及更高版本,由Thor Sarup添加了这些重大改进。


/*
    Create Nulls From Paths.jsx v.0.5
    Attach nulls to shape and mask vertices, and vice-versa.

    Changes:
        - wraps it all in an IIFE
        - fixes call to missing method array.indexOf
*/
/*
    Extended version by crunchycph.dk  -  youtube.com/c/crunchycphdk

    Added features:
        - Add controls for bezier handles
        - Use locators instead of nulls
        - Create path from nulls
            - Uniform bezierepath
            - Auto soft / round corners
            - Close path
        - Save settings
        - Responsive interface

    Possibly upcoming features:
        - Parent nulls to shape layer
        - Remove controls from path
        - Option to create locator master style
*/

/*
    Modified by Thor Sarup
    - Ability to rotate / scale points (handles follow)
    - locked rotation and scaling of handles (only for indicators)
    - sets handles to shy
    - renamed script to "Path tools" to save precious panel space :)
*/

/*
    Update by crunchy 30. oct. 2017
        - Fixed bug when both handle controls and use locators was selected for Create Path From Selected
        - Renamed the plugin back to it's original, since it explains pretty well what this is doing. :)
        - Researched the posibility to only connect selected verticies. But that is not posible since there is nothing like a selectedVerticies property

*/
(function createNullsFromPaths (thisObj) {
    /* Build UI */
    function buildUI(thisObj) {

        var windowTitle = localize("$$$/AE/Script/CreatePathNulls/CreateNullsFromPaths=Create Nulls From Paths Extended");
        var buttonPointsFollowNulls = localize("$$$/AE/Script/CreatePathNulls/PathPointsToNulls=Points Follow Nulls");
        var buttonNullsFollowPoints = localize("$$$/AE/Script/CreatePathNulls/NullsToPathPoints=Nulls Follow Points");
        var buttonTracePath = localize("$$$/AE/Script/CreatePathNulls/TracePath=Trace Path");
        var buttonCreatePathFromSelected = localize("$$$/AE/Script/CreatePathNulls/TracePath=Create Path From Selected");
        var checkHandles = localize("$$$/AE/Script/CreatePathNulls/AddControlsForHandles=Handle Controls");
        var checkUseLocators = localize("$$$/AE/Script/CreatePathNulls/AddControlsForHandles=Use Locators");
        
        var win = (thisObj instanceof Panel)? thisObj : new Window('palette', windowTitle);
            win.spacing = 0;
            win.margins = 4;
            win.myButtonGroup = win.add ("group");
                win.myButtonGroup.spacing = 4;
                win.myButtonGroup.margins = 0;
                win.myButtonGroup.orientation = "row";
                win.buttonPointsFollowNulls = win.myButtonGroup.add ("button", undefined, buttonPointsFollowNulls);
                win.buttonNullsFollowPoints = win.myButtonGroup.add ("button", undefined, buttonNullsFollowPoints);
                win.buttonTracePath = win.myButtonGroup.add ("button", undefined, buttonTracePath);
                win.buttonCreatePathFromSelected = win.myButtonGroup.add ("button", undefined, buttonCreatePathFromSelected);
                win.checkHandles = win.myButtonGroup.add ("checkbox", undefined, checkHandles);
                win.checkHandles.value = savedSettingHandles;
                win.checkUseLocators = win.myButtonGroup.add ("checkbox", undefined, checkUseLocators);
                win.checkUseLocators.value = savedSettingUseLocators;
                win.myButtonGroup.alignment = "center";
                win.myButtonGroup.alignChildren = "center";

            win.checkHandles.onClick = function()
            {
                app.settings.saveSetting("createnullsfrompathsextendedsettings", "handles", String(win.checkHandles.value));
                savedSettingHandles = win.checkHandles.value;
            }
            
            win.checkUseLocators.onClick = function()
            {
                app.settings.saveSetting("createnullsfrompathsextendedsettings", "useLocators", String(win.checkUseLocators.value));
                savedSettingUseLocators = win.checkUseLocators.value;
            }

            win.buttonPointsFollowNulls.onClick = function(){
                linkPointsToNulls();
            }
            win.buttonNullsFollowPoints.onClick = function(){
                linkNullsToPoints();
            }
            win.buttonTracePath.onClick = function(){
                tracePath();
            }
            win.buttonCreatePathFromSelected.onClick = function(){
                showCreatePathFromSelectedWindow();
            }

            win.onResize = function()
            {
                win.myButtonGroup.orientation = win.size[0]>600?"row":"column";
				win.myButtonGroup.alignChildren = win.size[0]>600?"center":"left";
                win.layout.layout(true);
            }

        win.layout.layout(true);

        return win
    }

    // Save settings
    var savedSettingHandles = app.settings.haveSetting("createnullsfrompathsextendedsettings", "handles")?(app.settings.getSetting("createnullsfrompathsextendedsettings", "handles")=="true"?true:false):false;
    var savedSettingUseLocators = app.settings.haveSetting("createnullsfrompathsextendedsettings", "useLocators")?(app.settings.getSetting("createnullsfrompathsextendedsettings", "useLocators")=="true"?true:false):false;
    var savedSettingCornerType = app.settings.haveSetting("createnullsfrompathsextendedsettings", "cornerType")?Number(app.settings.getSetting("createnullsfrompathsextendedsettings", "cornerType")):0;
    var savedSettingSmoothness = app.settings.haveSetting("createnullsfrompathsextendedsettings", "smoothness")?Number(app.settings.getSetting("createnullsfrompathsextendedsettings", "smoothness")):100;
    var savedSettingClosePath = app.settings.haveSetting("createnullsfrompathsextendedsettings", "closePath")?(app.settings.getSetting("createnullsfrompathsextendedsettings", "closePath")=="true"?true:false):false;
    
    // Show the Panel
    var w = buildUI(thisObj);
    if (w.toString() == "[object Panel]") {
        w;
    } else {
        w.show();
    }

    // Create Path From Selected Window
    function showCreatePathFromSelectedWindow()
    {
        var cpfsWindow = new Window("palette");
        cpfsWindow.orientation = "column";
        cpfsWindow.alignChildren = "left";
        cpfsWindow.spacing = 5;

        var row1 = cpfsWindow.add("group");
        row1.orientation = "row";
        row1.alignChildren = "left";

        var row2 = cpfsWindow.add("group");
        row2.orientation = "row";
        row2.alignChildren = "left";

        var row3 = cpfsWindow.add("group");
        row3.orientation = "row";
        row3.alignChildren = "left";

        var row4 = cpfsWindow.add("group");
        row4.orientation = "row";
        row4.alignChildren = "left";

        var buttons = cpfsWindow.add("group");
        buttons.orientation = "row";
        buttons.alignChildren = "right";
        
        var label1 = row1.add("statictext",undefined, "Unique layer name", {"multiline":false});
        var label2 = row2.add("statictext",undefined, "Corner type", {"multiline":false});
        var label3 = row3.add("statictext",undefined, "Smoothness", {"multiline":false});
        var chClosePath = row4.add("checkbox",undefined, "Close path");
        chClosePath.value = savedSettingClosePath;
        
        var inpName = row1.add("editText", undefined, "");
        inpName.size = [200, 24];
        var cbCornerType = row2.add("dropDownList", undefined, ["Sharp", "Smooth", "Bezier"]);
        cbCornerType.size = [100, 24];
        cbCornerType.selection = savedSettingCornerType;
        var inpSmoothness = row3.add("editText", undefined, savedSettingSmoothness);
        inpSmoothness.size = [100, 24];
        
        label3.enabled = cbCornerType.selection.text!="Sharp";
        inpSmoothness.enabled = cbCornerType.selection.text!="Sharp";

        cbCornerType.onChange = function()
        {
            label3.enabled = cbCornerType.selection.text!="Sharp";
            inpSmoothness.enabled = cbCornerType.selection.text!="Sharp";

            app.settings.saveSetting("createnullsfrompathsextendedsettings", "cornerType", cbCornerType.selection.index);
            savedSettingCornerType = cbCornerType.selection.index;
        }

        inpSmoothness.onChange = function()
        {
            app.settings.saveSetting("createnullsfrompathsextendedsettings", "smoothness", Number(inpSmoothness.text));
            savedSettingSmoothness = inpSmoothness.text;
        }

        chClosePath.onClick = function()
        {
            app.settings.saveSetting("createnullsfrompathsextendedsettings", "closePath", String(chClosePath.value));
            savedSettingClosePath = chClosePath.value;
        }
        
        var bCancel = buttons.add("button", undefined, "Cancel");
        var bOk = buttons.add("button", undefined, "Ok");

        bCancel.onClick = function()
        {
            cpfsWindow.close();
        }
        bOk.onClick = function()
        {
            if(inpName.text=="")
            {
                alert("Please enter a unique layer name");
                return;
            }
            cpfsWindow.close();
            createPathFromNSelected(inpName.text, cbCornerType.selection.text, Number(inpSmoothness.text), chClosePath.value);
        }
        
        cpfsWindow.show();
    }


    /* General functions */

    function getActiveComp(){
        var theComp = app.project.activeItem;
        if (theComp == undefined){
            var errorMsg = localize("$$$/AE/Script/CreatePathNulls/ErrorNoComp=Error: Please select a composition.");
            alert(errorMsg);
            return null
        }

        return theComp
    }

    function getSelectedLayers(targetComp){
        var targetLayers = targetComp.selectedLayers;
        return targetLayers
    }

    function createNull(targetComp){
        return targetComp.layers.addNull();
    }

    function createPointLocator(targetComp){
        var pointLocator = targetComp.layers.addShape();
        pointLocator.guideLayer = true;
        pointLocator.property("Scale").expression =
                "scl = transform.scale; \r" +
                "[Math.round(scl[0]*10) == 0 ? 0.1 : scl[0], \r" +
                "Math.round(scl[1]*10) == 0 ? 0.1 : scl[1]];";
        var rectGroup = pointLocator.property("Contents").addProperty("ADBE Vector Group");
        rectGroup.name = "RectGroup";
        
        var rect = rectGroup.property("Contents").addProperty("ADBE Vector Shape - Rect");
        rect.property("Size").setValue([30,30]);
        rect.property("Size").expression =
                "scl = thisLayer.scale; \r" +
                "[value[0] * 100 / (scl[0] < 0 ? -scl[0] : scl[0]), \r" +
                "value[1] * 100 / (scl[1] < 0 ? -scl[1] : scl[1])];";
        
        var rectFill = rectGroup.property("Contents").addProperty("ADBE Vector Graphic - Fill");
        rectFill.property("Color").setValue([232/256, 146/256, 13/256]);
        
        return pointLocator;
    }

    function createHandleLocator(targetComp){
        var handleLocator = targetComp.layers.addShape();
        handleLocator.guideLayer = true;
        //handleLocator.shy = true;
        handleLocator.property("Scale").expression = "[100,100]";
        handleLocator.property("Rotation").expression = "0";

        var ellipseGroup = handleLocator.property("Contents").addProperty("ADBE Vector Group");
        ellipseGroup.name = "EllipseGroup";
        
        var rect = ellipseGroup.property("Contents").addProperty("ADBE Vector Shape - Ellipse");
        rect.property("Size").setValue([20,20]);
        rect.property("Size").expression =
                    "scl = parent.scale; \r" +
                    "[value[0] * 100 / (scl[0] < 0 ? -scl[0] : scl[0]), \r" +
                    "value[1] * 100 / (scl[1] < 0 ? -scl[1] : scl[1])];";
        
        var rectFill = ellipseGroup.property("Contents").addProperty("ADBE Vector Graphic - Fill");
        rectFill.property("Color").setValue([61/256, 162/256, 165/256]);

        var lineGroup = handleLocator.property("Contents").addProperty("ADBE Vector Group");
        lineGroup.name = "LineGroup";
        
        var path = lineGroup.property("Contents").addProperty("ADBE Vector Shape - Group");
        path.property("Path").expression = "createPath([[0,0], -transform.position], [[0,0],[0,0]], [[0,0],[0,0]], false);";
        
        var pathStroke = lineGroup.property("Contents").addProperty("ADBE Vector Graphic - Stroke");
        pathStroke.property("Color").setValue([61/256, 162/256, 165/256]);
        pathStroke.property("Stroke Width").setValue(6);
        pathStroke.property("Stroke Width").expression =
                    "scl = parent.scale[0]; \r" +
                    "value * 100 / (scl < 0 ? -scl : scl);";

        return handleLocator;
    }

    function getSelectedProperties(targetLayer){
        var props = targetLayer.selectedProperties;
        if (props.length < 1){
            return null
        }
        return props
    }

    function forEachLayer(targetLayerArray, doSomething) {
        for (var i = 0, ii = targetLayerArray.length; i < ii; i++){
            doSomething(targetLayerArray[i]);
        }
    }

    function forEachProperty(targetProps, doSomething){
        for (var i = 0, ii = targetProps.length; i < ii; i++){
            doSomething(targetProps[i]);
        }
    }

    function forEachEffect(targetLayer, doSomething){
        for (var i = 1, ii = targetLayer.property("ADBE Effect Parade").numProperties; i <= ii; i++) {
            doSomething(targetLayer.property("ADBE Effect Parade").property(i));
        }
    }

    function matchMatchName(targetEffect,matchNameString){
        if (targetEffect != null && targetEffect.matchName === matchNameString) {
            return targetEffect
        } else {
            return null
        }
    }

    function getPropPath(currentProp,pathHierarchy){
        var pathPath = "";
            while (currentProp.parentProperty !== null){

                if ((currentProp.parentProperty.propertyType === PropertyType.INDEXED_GROUP)) {
                    pathHierarchy.unshift(currentProp.propertyIndex);
                    pathPath = "(" + currentProp.propertyIndex + ")" + pathPath;
                } else {
                    pathPath = "(\"" + currentProp.matchName.toString() + "\")" + pathPath;
                }

                // Traverse up the property tree
                currentProp = currentProp.parentProperty;
            }
        return pathPath
    }

    function getPathPoints(path){
        return path.value.vertices;
    }

    function getPathInTangents(path){
        return path.value.inTangents;
    }

    function getPathOutTangents(path){
        return path.value.outTangents;
    }

    function getCreateControlsForHandles()
    {
        return w.checkHandles.value;
    }

    function getUseLocators()
    {
        return w.checkUseLocators.value;
    }


    /* Project specific code */

    function forEachPath(doSomething){

        var comp = getActiveComp();

        if(comp == null) {
            return
        }

            var selectedLayers = getSelectedLayers(comp);
            if (selectedLayers == null){
                return
            }

            // First store the set of selected paths
            var selectedPaths = [];
            var parentLayers = [];
            forEachLayer(selectedLayers,function(selectedLayer){

                var paths = getSelectedProperties(selectedLayer);
                if (paths == null){
                    return
                }

                forEachProperty(paths,function(path){
                    var isShapePath = matchMatchName(path,"ADBE Vector Shape");
                    var isMaskPath = matchMatchName(path,"ADBE Mask Shape");
                // var isPaintPath = matchMatchName(path,"ADBE Paint Shape"); //Paint and roto strokes not yet supported in scripting
                    if(isShapePath != null || isMaskPath != null ){
                        selectedPaths.push(path);
                        parentLayers.push(selectedLayer);
                    }
                });
            });

            // Then operate on the selection
            if (selectedPaths.length == 0){
                var pathError = localize("$$$/AE/Script/CreatePathNulls/ErrorNoPathsSelected=Error: No paths selected.");

                alert(pathError);
                return
            }

            for (var p = 0; p < selectedPaths.length; p++) {
                    doSomething(comp,parentLayers[p],selectedPaths[p]);
            }

    }

    function linkNullsToPoints(){
        var undoGroup = localize("$$$/AE/Script/CreatePathNulls/LinkNullsToPathPoints=Link Nulls to Path Points");
        app.beginUndoGroup(undoGroup);

        forEachPath(function(comp,selectedLayer,path){
            var createControlsForHandles = getCreateControlsForHandles();
            var pathHierarchy = [];
            var pathPath = getPropPath(path, pathHierarchy);
            // Creation functions to use
            var createPointFunction = getUseLocators() ? createPointLocator : createNull;
            var createHandleFunction = getUseLocators() ? createHandleLocator : createNull;
            var handleScale = getUseLocators() ? 100 : 50;
            // Do things with the path points
            var pathPoints = getPathPoints(path);
            var pathInTangents = getPathInTangents(path);
            var pathOutTangents = getPathOutTangents(path);
            for (var i = 0, ii = pathPoints.length; i < ii; i++){
                var nullName = selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "." + i + "]";
                var nullNameIn = selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "." + i + "][In]";
                var nullNameOut = selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "." + i + "][Out]";
                if(comp.layer(nullName) == undefined){
                    var newNull = createPointFunction(comp);
                    newNull.position.setValue(pathPoints[i]);
                    newNull.position.expression =
                            "var srcLayer = thisComp.layer(\"" + selectedLayer.name + "\"); \r" +
                            "var srcPath = srcLayer" + pathPath + ".points()[" + i + "]; \r" +
                            "srcLayer.toComp(srcPath);";
                    newNull.name = nullName;
                    newNull.label = 10;

                    if(createControlsForHandles)
                    {
                        var newNullIn = createHandleFunction(comp);
                        newNullIn.parent = newNull;
                        newNullIn.position.setValue(pathInTangents[i]);
                        newNullIn.position.expression =
                                "var srcLayer = thisComp.layer(\"" + selectedLayer.name + "\"); \r" +
                                "var srcPath = srcLayer" + pathPath + ".inTangents()[" + i + "]; \r" +
                                "srcPath";
                        newNullIn.name = nullNameIn;
                        newNullIn.label = 14;
                        newNullIn.scale.setValue([handleScale,handleScale]);
                        newNullIn.shy = true;

                        var newNullOut = createHandleFunction(comp);
                        newNullOut.parent = newNull;
                        newNullOut.position.setValue(pathOutTangents[i]);
                        newNullOut.position.expression =
                                "var srcLayer = thisComp.layer(\"" + selectedLayer.name + "\"); \r" +
                                "var srcPath = srcLayer" + pathPath + ".outTangents()[" + i + "]; \r" +
                                "srcPath;";
                        newNullOut.name = nullNameIn;
                        newNullOut.label = 14;
                        newNullOut.scale.setValue([handleScale,handleScale]);
                        newNullOut.shy = true;

                        newNull.moveBefore(newNullOut);
                    }
                }
            }
        });
        app.endUndoGroup();
    }

    function linkPointsToNulls(suppressUndo, layers){
        var undoGroup = localize("$$$/AE/Script/CreatePathNulls/LinkPathPointsToNulls=Link Path Points to Nulls");
        if(!suppressUndo) app.beginUndoGroup(undoGroup);

        forEachPath(function(comp,selectedLayer,path){
            // Get property path to path
            var createControlsForHandles = getCreateControlsForHandles();
            var pathHierarchy = [];
            var pathPath = getPropPath(path, pathHierarchy);
            var nullSet = [];
            var nullSetIn = [];
            var nullSetOut = [];
            // Creation functions to use
            var createPointFunction = getUseLocators() ? createPointLocator : createNull;
            var createHandleFunction = getUseLocators() ? createHandleLocator : createNull;
            var handleScale = getUseLocators() ? 100 : 50;
            // Do things with the path points
            var pathPoints = getPathPoints(path);
            var pathInTangents = getPathInTangents(path);
            var pathOutTangents = getPathOutTangents(path);

            for (var i = 0, ii = pathPoints.length; i < ii; i++){ //For each path point
                var nullName = selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "." + i + "]";
                var nullNameIn = selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "." + i + "][In]";
                var nullNameOut = selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "." + i + "][Out]";

                if(layers)
                {
                    nullName = layers[i].name+"-"+nullName;
                }

                nullSet.push(nullName);
                nullSetIn.push(nullNameIn);
                nullSetOut.push(nullNameOut);

                // Get names of nulls that don't exist yet and create them
                if(comp.layer(nullName) == undefined){

                    if(!layers)
                    {
                        //Create nulls
                        var newNull = createPointFunction(comp);
                    }
                    else
                    {
                        newNull = layers[i];
                    }

                    // Null layer name
                    newNull.name = nullName;
                    newNull.label = 11;
                    

                    if(createControlsForHandles)
                    {
                        var newNullIn = createHandleFunction(comp);
                        var newNullOut = createHandleFunction(comp);
                        newNullIn.parent = newNull;
                        newNullOut.parent = newNull;
                        newNullIn.name = nullNameIn;
                        newNullOut.name = nullNameOut;
                        newNullIn.label = 14;
                        newNullOut.label = 14;
                        newNullIn.scale.setValue([handleScale,handleScale]);
                        newNullOut.scale.setValue([handleScale,handleScale]);
                        newNullOut.shy = newNullIn.shy = true;
                        newNull.moveBefore(newNullOut);
                    }

                    // Set position using layer space transforms, then remove expressions
                    newNull.position.setValue(pathPoints[i]);
                    newNull.position.expression =
                            "var srcLayer = thisComp.layer(\"" + selectedLayer.name + "\"); \r" +
                            "var srcPath = srcLayer" + pathPath + ".points()[" + i + "]; \r" +
                            "srcLayer.toComp(srcPath);";
                    newNull.position.setValue(newNull.position.value);
                    newNull.position.expression = '';

                    if(createControlsForHandles)
                    {
                        newNullIn.position.setValue(pathInTangents[i]);
                        newNullOut.position.setValue(pathOutTangents[i]);
                    }
                }

            }

            // Get any existing Layer Control effects
            var existingEffects = [];
            forEachEffect(selectedLayer,function(targetEffect){
                if(matchMatchName(targetEffect,"ADBE Layer Control") != null) {
                    existingEffects.push(targetEffect.name);
                }
            });

            // Add new layer control effects for each null
            for(var n = 0; n < nullSet.length;n++){
                if(existingEffects.join("|").indexOf(nullSet[n]) != -1){ //If layer control effect exists, relink it to null
                    selectedLayer.property("ADBE Effect Parade")(nullSet[n]).property("ADBE Layer Control-0001").setValue(comp.layer(nullSet[n]).index);
                    if(createControlsForHandles) selectedLayer.property("ADBE Effect Parade")(nullSetIn[n]).property("ADBE Layer Control-0001").setValue(comp.layer(nullSetIn[n]).index);
                    if(createControlsForHandles) selectedLayer.property("ADBE Effect Parade")(nullSetOut[n]).property("ADBE Layer Control-0001").setValue(comp.layer(nullSetOut[n]).index);
                } else {
                    var newControl = selectedLayer.property("ADBE Effect Parade").addProperty("ADBE Layer Control");
                    newControl.name = nullSet[n];
                    newControl.property("ADBE Layer Control-0001").setValue(comp.layer(nullSet[n]).index);

                    if(createControlsForHandles)
                    {
                        var newControlIn = selectedLayer.property("ADBE Effect Parade").addProperty("ADBE Layer Control");
                        newControlIn.name = nullSetIn[n];
                        newControlIn.property("ADBE Layer Control-0001").setValue(comp.layer(nullSetIn[n]).index);

                        var newControlOut = selectedLayer.property("ADBE Effect Parade").addProperty("ADBE Layer Control");
                        newControlOut.name = nullSetOut[n];
                        newControlOut.property("ADBE Layer Control-0001").setValue(comp.layer(nullSetOut[n]).index);
                    }
                }
            }

            // Set path expression that references nulls
            if(createControlsForHandles)
            {
                path.expression =
                        "var nullLayerNames = [\"" + nullSet.join("\",\"") + "\"]; \r" +
                        "var nullLayerNamesIn = [\"" + nullSetIn.join("\",\"") + "\"]; \r" +
                        "var nullLayerNamesOut = [\"" + nullSetOut.join("\",\"") + "\"]; \r" +
                        "var origPath = thisProperty; \r" +
                        "var origPoints = origPath.points(); \r" +
                        "var origInTang = origPath.inTangents(); \r" +
                        "var origOutTang = origPath.outTangents(); \r" +
                        "var getNullLayers = []; \r" +
                        "var getNullLayersIn = []; \r" +
                        "var getNullLayersOut = []; \r" +
                        "for (var i = 0; i < nullLayerNames.length; i++){ \r" +
                        "    try{  \r" +
                        "        getNullLayers.push(effect(nullLayerNames[i])(\"ADBE Layer Control-0001\")); \r" +
                        "        getNullLayersIn.push(effect(nullLayerNamesIn[i])(\"ADBE Layer Control-0001\")); \r" +
                        "        getNullLayersOut.push(effect(nullLayerNamesOut[i])(\"ADBE Layer Control-0001\")); \r" +
                        "    } catch(err) { \r" +
                        "        getNullLayers.push(null); \r" +
                        "    }} \r" +
                        "for (var i = 0; i < getNullLayers.length; i++){ \r" +
                        "    if (getNullLayers[i] != null && getNullLayers[i].index != thisLayer.index){ \r" +
                        "        origPoints[i] = fromCompToSurface(getNullLayers[i].toComp(getNullLayers[i].anchorPoint));  \r" +
                        "        origInTang[i] = fromCompToSurface(getNullLayersIn[i].toComp(getNullLayersIn[i].anchorPoint)) - origPoints[i];  \r" +
                        "        origOutTang[i] = fromCompToSurface(getNullLayersOut[i].toComp(getNullLayersOut[i].anchorPoint)) - origPoints[i];  \r" +
                        "    }} \r" +
                        "createPath(origPoints,origInTang,origOutTang,origPath.isClosed());";
            }
            else
            {
                path.expression =
                        "var nullLayerNames = [\"" + nullSet.join("\",\"") + "\"]; \r" +
                        "var origPath = thisProperty; \r" +
                        "var origPoints = origPath.points(); \r" +
                        "var origInTang = origPath.inTangents(); \r" +
                        "var origOutTang = origPath.outTangents(); \r" +
                        "var getNullLayers = []; \r" +
                        "var getNullLayersIn = []; \r" +
                        "var getNullLayersOut = []; \r" +
                        "for (var i = 0; i < nullLayerNames.length; i++){ \r" +
                        "    try{  \r" +
                        "        getNullLayers.push(effect(nullLayerNames[i])(\"ADBE Layer Control-0001\")); \r" +
                        "    } catch(err) { \r" +
                        "        getNullLayers.push(null); \r" +
                        "    }} \r" +
                        "for (var i = 0; i < getNullLayers.length; i++){ \r" + " if (getNullLayers[i] != null && getNullLayers[i].index != thisLayer.index){ \r" + " origPoints[i] = fromCompToSurface(getNullLayers[i].toComp(getNullLayers[i].anchorPoint)); \r" + " }} \r" + "createPath(origPoints,origInTang,origOutTang,origPath.isClosed());"; } }); if(!suppressUndo) app.endUndoGroup(); } function tracePath(){ var undoGroup = localize("$$$/AE/Script/CreatePathNulls/CreatePathTracerNull=Create Path Tracer Null"); app.beginUndoGroup(undoGroup); var sliderName = localize("$$$/AE/Script/CreatePathNulls/TracerTiming=Tracer Timing"); var checkboxName = localize("$$$/AE/Script/CreatePathNulls/LoopTracer=Loop Tracer"); forEachPath(function(comp,selectedLayer,path){ var pathHierarchy = []; var pathPath = getPropPath(path, pathHierarchy); // Create tracer null var newNull = createNull(comp); // Add expression control effects to the null var nullControl = newNull.property("ADBE Effect Parade").addProperty("Pseudo/ADBE Trace Path"); nullControl.property("Pseudo/ADBE Trace Path-0002").setValue(true); nullControl.property("Pseudo/ADBE Trace Path-0001").setValuesAtTimes([0,1],[0,100]); nullControl.property("Pseudo/ADBE Trace Path-0001").expression = "if(thisProperty.propertyGroup(1)(\"Pseudo/ADBE Trace Path-0002\") == true && thisProperty.numKeys > 1){ \r" +
                        "thisProperty.loopOut(\"cycle\"); \r" +
                        "} else { \r" +
                        "value \r" +
                        "}";
            newNull.position.expression =
                    "var pathLayer = thisComp.layer(\"" + selectedLayer.name + "\"); \r" +
                    "var progress = thisLayer.effect(\"Pseudo/ADBE Trace Path\")(\"Pseudo/ADBE Trace Path-0001\")/100; \r" +
                    "var pathToTrace = pathLayer" + pathPath + "; \r" +
                    "pathLayer.toComp(pathToTrace.pointOnPath(progress));";
            newNull.rotation.expression =
                    "var pathToTrace = thisComp.layer(\"" + selectedLayer.name + "\")" + pathPath + "; \r" +
                    "var progress = thisLayer.effect(\"Pseudo/ADBE Trace Path\")(\"Pseudo/ADBE Trace Path-0001\")/100; \r" +
                    "var pathTan = pathToTrace.tangentOnPath(progress); \r" +
                    "radiansToDegrees(Math.atan2(pathTan[1],pathTan[0]));";
            newNull.name = "Trace " + selectedLayer.name + ": " + path.parentProperty.name + " [" + pathHierarchy.join(".") + "]";
            newNull.label = 10;

        });
        app.endUndoGroup();
    }

    function createPathFromNSelected(layerName, cornerType, smoothness, closePath)
    {
        var undoGroup = localize("$$$/AE/Script/CreatePathNulls/CreatePathTracerNull=Create Path From Selected");
        app.beginUndoGroup(undoGroup);

        var comp = getActiveComp();
        var layers = getSelectedLayers(comp);
        var bottomLayer = layers[0];

        if(layers.length<2)
        {
            alert("Please select two layers or more");
            return;
        }

        var shapeLayer = comp.layers.addShape();
        shapeLayer.name = layerName;
        var shapeGroup = shapeLayer.property("Contents").addProperty("ADBE Vector Group");
        shapeGroup.name = "Group";

        var vertices = [];
        var inTangents = [];
        var outTangents = [];

        var bezierSmoothness = cornerType=="Bezier"?smoothness:0;

        for(var i=0;i<layers.length;i++)
        {
            vertices[i] = [layers[i].position.value[0]-comp.width/2, layers[i].position.value[1]-comp.height/2];
            inTangents[i] = [bezierSmoothness, 0];
            outTangents[i] = [-bezierSmoothness, 0];
            
            bottomLayer = bottomLayer.index<layers[i].index?layers[i]:bottomLayer;
        }
        
        var pathShape = new Shape();
        pathShape.vertices = vertices;
        pathShape.inTangents = inTangents;
        pathShape.outTangents = outTangents;
        pathShape.closed = closePath;

        var pathGroup = shapeGroup.property("Contents").addProperty("ADBE Vector Shape - Group");
        pathGroup.property("Path").setValue(pathShape);

        var pathStroke = shapeGroup.property("Contents").addProperty("ADBE Vector Graphic - Stroke");
        pathStroke.property("Color").setValue([0,0,0]);
        pathStroke.property("Stroke Width").setValue(4);

        var contents = shapeGroup.property("Contents");
        var pathIndex = contents.numProperties-1;

        if(cornerType=="Smooth")
        {
            var roundCorners = shapeGroup.property("Contents").addProperty("ADBE Vector Filter - RC");
            roundCorners.property("Radius").setValue(smoothness);

            pathIndex = contents.numProperties-2;
        }
        
        var pathGroup = contents.property(pathIndex);
        pathGroup.property("Path").selected = true;

        shapeLayer.moveAfter(bottomLayer);

        linkPointsToNulls(true, layers);

        app.endUndoGroup();
    }

})(this);

您的电子邮箱地址不会被公开。 必填项已用*标注

请下载到本地观看!因为pdf是用的阿里云盘预览,可能部分字体加载不了。

请安装油猴脚本,并下载月离的万事屋脚本。详情请查看菜单 导航 - 用户指引

请在右上角三个点 - 下载管理 - 找到刚才的文件,选择允许下载。 或者换个浏览器