推荐“素材”关键词

设计资源

精选设计教程与资源分享
Adobe脚本一键处理元素随机排列

用脚本一键处理:类似于下图中的同一形状元素在特定区域内,随机缩放大小,随机位置排列

使用方法:

1、画出两形状,小的在上面,大的在下面;

2、同时选中两个形状,按 CTRL + F12,找到你保存的脚本(在本页最下面)打开,调整参数(可参考下图),点击开始精准填充。

3、效果(不满意可以撤销后再来一遍重新调整参数,或者直接在此效果上手动调节)


【将以下代码全部完整复制,保存到记事本,另存为所有文档,后缀名改成 .jsx 即可】

/* Script: 随机填充
  Author: sucaizip.com
  Version: V5.0
*/
(function() {
    // --- 0. 基础检查 ---
    if (app.documents.length === 0 || app.selection.length < 2) {
        alert('错误:请至少选择 2 个对象!\n\n1. 上方对象:填充物 (矢量)\n2. 下方对象:容器 (矢量形状)\n\n提示:对于圆形/曲线容器,建议先执行[对象>路径>添加锚点]以提高边缘精度。');
        return;
    }
    var scriptName = 'Fillinger_V5_Precision',
        settingFile = {
            name: scriptName + '__setting.json',
            folder: Folder.myDocuments + '/LA_AI_Scripts/'
        };
    // --- 1. 构建界面 ---
    var win = new Window('dialog', '精准形状填充 V5');
    win.orientation = 'column';
    win.alignChildren = ['fill', 'fill'];
    win.spacing = 10;
    win.margins = 16;
    // A. 间距面板
    var panelGap = win.add('panel', undefined, '1. 间距设置');
    panelGap.orientation = 'column';
    panelGap.alignChildren = ['left', 'top'];
    var grpGap = panelGap.add('group');
    grpGap.add('statictext', undefined, '间距倍数:');
    var inputGap = grpGap.add('edittext', undefined, '1.5');
    inputGap.characters = 6;
    grpGap.add('statictext', undefined, '(1.0=紧贴, >1=空隙)');
    // B. 缩放面板
    var panelScale = win.add('panel', undefined, '2. 随机大小');
    panelScale.orientation = 'column';
    panelScale.alignChildren = ['left', 'top'];
    var chkScale = panelScale.add('checkbox', undefined, '启用随机缩放');
    chkScale.value = false;
    var grpScaleRange = panelScale.add('group');
    grpScaleRange.enabled = false;
    grpScaleRange.add('statictext', undefined, '最小:');
    var inputMinScale = grpScaleRange.add('edittext', undefined, '0.5');
    inputMinScale.characters = 5;
    grpScaleRange.add('statictext', undefined, ' 最大:');
    var inputMaxScale = grpScaleRange.add('edittext', undefined, '1.5');
    inputMaxScale.characters = 5;
    chkScale.onClick = function() { grpScaleRange.enabled = chkScale.value; }
    // C. 旋转面板
    var panelRotate = win.add('panel', undefined, '3. 旋转设置');
    panelRotate.alignChildren = 'left';
    var grpRot = panelRotate.add('group');
    var radRotRandom = grpRot.add('radiobutton', undefined, '随机 360°');
    var radRotFixed = grpRot.add('radiobutton', undefined, '固定角度:');
    var inputRotate = grpRot.add('edittext', undefined, '0');
    inputRotate.characters = 5;
    inputRotate.enabled = false;
    radRotRandom.value = true;
    radRotRandom.onClick = function() { inputRotate.enabled = false; }
    radRotFixed.onClick = function() { inputRotate.enabled = true; }
    // D. 按钮
    var grpOptions = win.add('group');
    grpOptions.alignment = 'left';
    var chkDelContainer = grpOptions.add('checkbox', undefined, '完成后删除容器');
    var grpBtns = win.add('group');
    grpBtns.alignment = 'center';
    var btnCancel = grpBtns.add('button', undefined, '取消');
    var btnOk = grpBtns.add('button', undefined, '开始精准填充');
    btnCancel.onClick = function() { win.close(); }
    btnOk.onClick = function() {
        if (isNaN(Number(inputGap.text))) { alert("间距需为数字"); return; }
        win.close();
        runScript();
    }
    win.center();
    win.show();
    // --- 2. 核心算法 ---
    // 算法:射线法判断点是否在多边形内
    // 参数:pt=[x,y], vs=[[x,y], [x,y]...] (多边形顶点数组)
    function isPointInPolygon(pt, vs) {
        var x = pt[0], y = pt[1];
        var inside = false;
        for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
            var xi = vs[i][0], yi = vs[i][1];
            var xj = vs[j][0], yj = vs[j][1];
            var intersect = ((yi > y) != (yj > y)) &&
                (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
            if (intersect) inside = !inside;
        }
        return inside;
    }
    // 提取路径的所有锚点坐标
    function getPathPoints(pathItem) {
        var pts = [];
        var pPoints = pathItem.pathPoints;
        for (var i = 0; i < pPoints.length; i++) {
            pts.push([pPoints[i].anchor[0], pPoints[i].anchor[1]]);
        }
        return pts;
    }
    // --- 3. 主逻辑 ---
    function runScript() {
        var gapMultiplier = Number(inputGap.text);
        var useScale = chkScale.value;
        var minScale = Number(inputMinScale.text);
        var maxScale = Number(inputMaxScale.text);
        // 排序与提取
        var items = [];
        for (var i = 0; i < app.selection.length; i++) items.push(app.selection[i]);
        items.sort(function(a, b) { return b.top - a.top; });
        var container = items.pop();
        var seeds = items;
        if (container.typename === "RasterItem") { alert("容器必须是路径!"); return; }
        // --- 准备容器数据 ---
        // 我们需要解析容器的几何形状。
        // 为了处理复合路径(如甜甜圈),我们需要提取所有子路径。
        var polyList = []; // 存放所有多边形轮廓 [[pt,pt...], [pt,pt...]]
        if (container.typename === "CompoundPathItem") {
            for (var i = 0; i < container.pathItems.length; i++) {
                polyList.push(getPathPoints(container.pathItems[i]));
            }
        } else if (container.typename === "PathItem") {
            polyList.push(getPathPoints(container));
        } else {
            alert("容器类型不支持 (请取消编组或扩展外观)"); return;
        }
        // 准备边界盒 (作为第一轮粗筛)
        var lb = container.geometricBounds;
        var cLeft = lb[0], cTop = lb[1], cRight = lb[2], cBottom = lb[3];
        var cWidth = cRight - cLeft;
        var cHeight = cTop - cBottom;
        // 准备结果组
        var resultGroup = app.activeDocument.groupItems.add();
        resultGroup.move(container, ElementPlacement.PLACEBEFORE);
        var seedRef = seeds[0];
        var baseSize = Math.max(seedRef.width, seedRef.height);
        var placedItems = []; // [x, y, radius]
        // 增加尝试次数,因为现在很多点会被“形状外”的条件剔除
        var maxAttempts = 5000;
        // 进度条
        var progWin = new Window('palette', '精准计算中...');
        var pBar = progWin.add('progressbar', undefined, 0, maxAttempts);
        pBar.preferredSize.width = 300;
        progWin.center();
        progWin.show();
        for (var i = 0; i < maxAttempts; i++) {
            // 1. 随机参数
            var currentScale = useScale ? (minScale + Math.random() * (maxScale - minScale)) : 1.0;
            var currentRadius = (baseSize * currentScale) / 2;
            // 2. 在外框矩形内随机取点
            var rndX = cLeft + Math.random() * cWidth;
            var rndY = cTop - Math.random() * cHeight;
            var pt = [rndX, rndY];
            // 3. 【关键步骤】检查点是否在精准形状内部
            var isInsideShape = false;
            // 奇偶规则 (Even-Odd Rule) 处理复合路径
            // 简单来说:在实心区域内,包含该点的子路径数量通常是奇数(1个)。
            // 在中空区域(洞)内,包含该点的子路径数量通常是偶数(2个:外圈+内圈)。
            var insideCount = 0;
            for (var p = 0; p < polyList.length; p++) {
                if (isPointInPolygon(pt, polyList[p])) {
                    insideCount++;
                }
            }
            // 如果是普通路径,count=1即为内部。如果是复合路径,奇数层为实体。
            // 大多数情况下,insideCount % 2 !== 0 是通用的。
            if (insideCount % 2 !== 0) {
                isInsideShape = true;
            }
// 如果中心点不在形状内,直接跳过
if (!isInsideShape) {
    if (i % 10 === 0) i--;
    continue;
}
// ====== 新增:边界不外溢检测(外接圆采样点都必须在容器内)======
// currentRadius = (baseSize * currentScale) / 2; // 你上面已经算好了
var r = currentRadius;
// 采样 8 个方向 + 可选 4 个更密的点(更严谨)
var offsets = [
    [ r, 0], [-r, 0], [0, r], [0, -r],
    [ r*0.7071, r*0.7071], [ r*0.7071, -r*0.7071],
    [-r*0.7071, r*0.7071], [-r*0.7071, -r*0.7071],
    // 更严谨可再加一圈(建议保留)
    [ r*0.5, r*0.8660], [ r*0.5, -r*0.8660],
    [-r*0.5, r*0.8660], [-r*0.5, -r*0.8660]
];
var allInside = true;
for (var t = 0; t < offsets.length; t++) {
    var testPt = [rndX + offsets[t][0], rndY + offsets[t][1]];
    // 复用你原来的 even-odd 逻辑:insideCount%2!==0 为实心区
    var insideCount2 = 0;
    for (var pp = 0; pp < polyList.length; pp++) {
        if (isPointInPolygon(testPt, polyList[pp])) insideCount2++;
    }
    if (insideCount2 % 2 === 0) {
        allInside = false;
        break;
    }
}
if (!allInside) {
    if (i % 10 === 0) i--;
    continue;
}
// ===============================================================
            // 4. 碰撞检测 (检查是否与已有物体重叠)
            var fits = true;
            for (var j = 0; j < placedItems.length; j++) {
                var other = placedItems[j];
                var dist = Math.sqrt(Math.pow(rndX - other[0], 2) + Math.pow(rndY - other[1], 2));
                var minSafeDist = (currentRadius + other[2]) * gapMultiplier;
                if (dist < minSafeDist) {
                    fits = false;
                    break;
                }
            }
            // 5. 放置
            if (fits) {
                placedItems.push([rndX, rndY, currentRadius, currentScale]);
            }
            if (i % 50 === 0) pBar.value = i;
        }
        progWin.close();
        // --- 生成 ---
        if (placedItems.length === 0) {
            alert("未生成对象。\n可能原因:容器太小、间距太大,或未检测到内部区域。\n提示:请给容器[添加锚点]后重试。");
            return;
        }
        for (var k = 0; k < placedItems.length; k++) {
            var data = placedItems[k];
            var newItem = seedRef.duplicate();
            if (data[3] !== 1.0) newItem.resize(data[3] * 100, data[3] * 100);
            newItem.position = [data[0] - newItem.width/2, data[1] + newItem.height/2];
            newItem.move(resultGroup, ElementPlacement.PLACEATEND);
            if (radRotRandom.value) newItem.rotate(Math.random() * 360);
            else if (inputRotate.text !== "0") newItem.rotate(Number(inputRotate.text));
        }
        if (chkDelContainer.value) container.remove();
    }
})();