hele_two头像
关注
Photoshop JSX自动化:批量生成蜂窝六角格及单元格编号(适配PS CS6以上版本)封面图

Photoshop JSX自动化:批量生成蜂窝六角格及单元格编号(适配PS CS6以上版本)

摘要

PS手工绘制大量蜂窝六边形网格、逐个录入编号耗时久,坐标难以对齐,极易出错。本文采用Photoshop ExtendScript(JSX)开发自动化脚本,实现错位蜂窝正六边形网格批量生成,针对Photoshop API兼容性问题做专项适配,解决大量开发调试遇到的报错。脚本集成进度条提示、参数输入面板,并做了代码性能优化,将全部编号合并至单一图层,提高生成效率的同时方便后期统一位移调整。


一、需求背景与方案对比

(一)业务需求

  1. 在PS画布批量生成标准错位排布蜂窝正六边形网格;
  2. 每个六边形中心自动填充对应编号;
  3. 可自定义六边形尺寸、画布起始坐标、网格行列数量;
  4. 所有文本编号合并为单个图层,支持整体位置微调;
  5. 大批量渲染时显示进度窗口,避免脚本假死无法判断运行状态。

(二)多种实现方案对比

方案优点缺点
纯手动绘制复制无需脚本,上手简单网格量大时效率极低,坐标错位、编号容易混乱
动作+PS变量零代码仅适合矩形阵列,无法实现蜂窝错位六角布局
JSX原生脚本(本文方案)PS原生运行、坐标精准需要编写脚本,不同PS版本API存在兼容性差异

二、核心原理基础

(一)蜂窝六角网格数学模型

请阅读我之前写的一篇文章SDL2画六角格地板第一部分数学基础部分。

(二)PS JSX稳定绘图技术路线

  1. 图像绘制稳定方案:手动构造锚点数组,通过PathItem绘制多边形路径。
  2. 文字图层性能方案:批量创建文字图层后栅格合并单层,杜绝数百上千个独立文本图层造成软件卡顿。

三、完整模块化脚本实现

(一)前置准备

查阅photoshop-cc-javascript-references官方文档,需要在脚本开始做以下工作:

  1. 脚本开始第一行#target photoshop为固定格式
  2. PS画布单位统一设置为点POINTS
    在这里插入图片描述
    涉及到UnitsTypeUnits2个常量:

在这里插入图片描述

app.preferences.rulerUnits = Units.POINTS; //画图系统使用的尺寸单位为点
app.preferences.typeUnits = TypeUnits.POINTS; //文字系统使用的尺寸单位为点
  1. 设置显示对话框的样式,有ALLERRORNO3种样式,一般简单的选择NO
    在这里插入图片描述

    app.displayDialogs = DialogModes.NO; 
    

(二)脚本模块划分

1.参数配置模块

//========配置参数=========
var CELLCOUNT = 5; //一个方里格纵向放五个六角格
var lineWidth = 8; //边框粗细
var fontSize = hexRadius / 4; //字号
var canvasW = 10200; //画布宽度
var canvasH = 6800; //画布高度
var startOffsetX = 200; //整体起点X
var startOffsetY = 600; //整体起点Y
var perRowHexCount = 10; //默认每行六边形数量10个
var maxCount = 10; //默认10行
//颜色
var strokeRGB = [0,0,0]; //黑色
var textRGB = [0,0,0]; //字体黑色
var fillRGB = null; //填充色
//分辨率DPI
var dpi = 300;

//========全局变量=========
//六边形蜂窝坐标计算常量
var hexHeight = 100; //默认100
var hexRadius = hexHeight / Math.sqrt(3);
var hexWidth = hexRadius * 2;
var xStep = hexWidth + hexRadius; //一行中相邻2块之间的距离
var yStep = hexHeight / 2; //一列中相邻2块之间的距离
var oddRowShift = hexRadius * 1.5; //奇数行与偶数行的水平错位距离

2.几何计算模块

  • 根据圆心生成正六边形锚点数组
    //生成正6边形6个顶点完整锚点数组(中心cx,cy,外接圆半径r)
    function getHexAnchorData(cx, cy, r) {
        //alert("生成六边形锚点");
        var anchors =[];
        var startRad = 0; //起始偏移角度
        for (var a = 0; a < 6; a++) {
            var rad = startRad + Math.PI / 3 * a; //旋转60度找下一个锚点坐标
            var px = cx + r * Math.cos(rad);
            var py = cy + r * Math.sin(rad);
            //UnitValue到number的转换
            if (px instanceof UnitValue) px = px.value;
            if (py instanceof UnitValue) py = py.value;
            var pt = [px, py];
            var pif = new PathPointInfo(); //构造PathPointInfo,用于在PathItems中加入PathPoint,这是画锚点前的固定格式,参见P142-145
            pif.anchor = pt;
            pif.leftDirection = pt; //直线点:锚点、左右手柄坐标完全一致
            pif.rightDirection = pt;
            pif.kind = PointKind.CORNERPOINT;
            anchors.push(pif);
        }
        return anchors;
    }
    
  • 蜂窝网格所有六边形圆心坐标批量计算
    首先处理第一行的圆心,参数配置模块的xStep是关键。接着下一行的圆心yStepoddRowShift是关键,横向下偏移oddRowShift,纵向上偏移yStep。在六角格绘制模块和编号绘制模块有具体体现。

3.绘图模块

  • 单个六边形路径绘制
    //给定一个图层,给出圆心和半径
    function createHexShape(hexLayer, cx, cy, r) {
        // 新建图层
        doc.activeLayer = hexLayer;
        doc.pathItems.removeAll(); //清空旧路径
        //获取六边形新锚点
        var anchors = getHexAnchorData(cx, cy, r);
        //创建闭合路径
        var spi = new SubPathInfo(); //P173-175
        spi.entireSubPath = anchors;
        spi.closed = true;
        spi.operation = ShapeOperation.SHAPEXOR;
        var tempPath = doc.pathItems.add("temp_",[spi]);
        //描边
        app.foregroundColor.rgb.red = strokeRGB[0];
        app.foregroundColor.rgb.green = strokeRGB[1];
        app.foregroundColor.rgb.blue = strokeRGB[2];
        tempPath.strokePath(ToolType.PENCIL, lineWidth);
        //删除临时路径
        tempPath.remove();
    }
    
  • 单行六边形绘制
    不断重新绘制单个六边形
    //重复绘制六角格
    for (var idx = 0; idx < maxCount; ) { //遍历所有六角格序号
    	var rowIndex = Math.floor(idx / perRowHexCount); //行序号
    	var colIndex = idx % perRowHexCount; //列序号
    	//奇数行横向错位偏移
    	var rowShift = (rowIndex % 2 === 1) ? oddRowShift : 0;
    	var centerX = startOffsetX + colIndex * xStep + rowShift;
    	var centerY = startOffsetY + rowIndex * yStep;
    	
    	if (rowIndex === 0) { //绘制第一行六边形
    		createHexShape(hexLayer, centerX, centerY, hexRadius);
    		idx++;
    	}
        ...
        ...
        ...
    }
    
  • 多行六边形绘制
    直接复制第1行六边形那个图层,不用再重新计算锚点和描边了,这样可以提高绘制性能。
    //重复绘制六角格
    for (var idx = 0; idx < maxCount; ) {
    	var rowIndex = Math.floor(idx / perRowHexCount);
    	var colIndex = idx % perRowHexCount;
    	//奇数行横向错位偏移
    	var rowShift = (rowIndex % 2 === 1) ? oddRowShift : 0;
    	var centerX = startOffsetX + colIndex * xStep + rowShift;
    	var centerY = startOffsetY + rowIndex * yStep;
    	
    	if (rowIndex === 0) { //绘制第一行六边形
    		createHexShape(hexLayer, centerX, centerY, hexRadius);
    		idx++;
    	}
    	else {
    		var copyLayer = hexLayer.duplicate();
    		copyLayer.translate(new UnitValue(rowShift, "pt"), new UnitValue(rowIndex * yStep, "pt"));
    		idx += perRowHexCount;
    	}
    }
    
  • 单个编号绘制函数
    var textLayerList = []; //第一行编号图层组
    var textLayer0 = mainGroup.artLayers.add(); //第1个编号图层
    textLayer0.name = "编号";
    textLayer0.kind = LayerKind.TEXT;
    textLayer0.textItem.size = fontSize;
    textLayer0.textItem.kind = TextType.POINTTEXT;
    textLayer0.textItem.justification = Justification.CENTER;
    textLayer0.textItem.color.rgb.red = textRGB[0];
    textLayer0.textItem.color.rgb.green = textRGB[1];
    textLayer0.textItem.color.rgb.blue = textRGB[2];
    
  • 单行编号绘制函数
    与多行六边行绘制原理类似,只不过这次是水平复制的。
    //第一行编码绘制函数
    function createFirstRowHexCode(textLayer, centerX, centerY, label) {
        var copyLayer = textLayer.duplicate();
        copyLayer.name = label;
        var textItem = copyLayer.textItem;
        textItem.contents = label;
        textItem.position = Array(centerX, centerY);
        return copyLayer;
    }
    //重复绘制编号
    for (var idx = 0; idx < maxCount;) {
        var rowIndex = Math.floor(idx / perRowHexCount);
        var colIndex = idx % perRowHexCount;
        //奇数行横向错位偏移
        var rowShift = (rowIndex % 2 === 1) ? oddRowShift : 0;
        var centerX = startOffsetX + colIndex * xStep + rowShift;
        var centerY = startOffsetY + rowIndex * yStep;
    
        if (rowIndex === 0) { //绘制第一行编号
            textLayerList.push(createFirstRowHexCode(textLayer0, centerX, centerY - yStep + fontSize, (rowIndex+1)+","+colIndex )); //绘制形如1,0这样的编号
            idx++;
        }
        ...
        ...
        ...
    }
    
  • 多行编号绘制函数
    将第一行的编号内容进行逐个改变,然后整体复制合并成1个图层,再平移至对应行
    //其余单行编码绘制函数
    function createSingleRowHexCode(textLayer, label) {
        var copyLayer = textLayer.duplicate();
        copyLayer.name = label;
        copyLayer.textItem.contents = label;
        return copyLayer;
    }
    //重复绘制编号
    for (var idx = 0; idx < maxCount;) {
        var rowIndex = Math.floor(idx / perRowHexCount);
        var colIndex = idx % perRowHexCount;
        //奇数行横向错位偏移
        var rowShift = (rowIndex % 2 === 1) ? oddRowShift : 0;
        var centerX = startOffsetX + colIndex * xStep + rowShift;
        var centerY = startOffsetY + rowIndex * yStep;
    
        if (rowIndex === 0) { //绘制第一行编号
            ...
            ...
            ...
        } else { //绘制一行编号
            if (colIndex === 0) { //另起一行
                var plainLayer = mainGroup.artLayers.add(); //作为merge的最底层
                plainLayer.name = rowIndex + "_";
                plainLayer.moveToBeginning(mainGroup);
            }
            for (var i = 0; i < perRowHexCount; i++) {
                var copyLayer = createSingleRowHexCode(textLayerList[i], (rowIndex+1)+","+i);
                copyLayer.moveToBeginning(mainGroup);
                copyLayer.merge(); //向下合并
            }
            plainLayer.translate(new UnitValue(rowShift, "pt"), new UnitValue(rowIndex * yStep, "pt")); //整体位移
            idx += perRowHexCount;
        }
    }
    
    
    其中mainGroup是图层组,通过mainGroup = doc.layerSets.add(); mainGroup.name = "编号图层组";生成。

4. 通用工具模块

(1) 获取格子大小
一开始定的配置参数都是死的,怎样通过鼠标选取一定范围,然后对应生成相应大小的六角格呢?我在初始化函数中做了实现:

//初始化函数
function initParam() {
	//获取选中矩形框大小,参考P90、P166
	var bl = doc.selection.bounds[0]; //left
	var bt = doc.selection.bounds[1]; //top
	var br = doc.selection.bounds[2]; //right
	var bb = doc.selection.bounds[3]; //bottom
	
	//初始化参数
	hexHeight = (bb - bt) / CELLCOUNT;
	hexRadius = hexHeight / Math.sqrt(3);
	hexWidth = hexRadius * 2;
	xStep = hexWidth + hexRadius;
	yStep = hexHeight / 2;
	oddRowShift = hexRadius * 1.5;
	fontSize = hexRadius / 4;
	startOffsetX = bl + hexWidth / 2; //整体起点X
	startOffsetY = bt + hexHeight / 2; //整体起点Y
	perRowHexCount = Math.floor((doc.width - startOffsetX) / xStep) + 1; //每行六边形数量
	maxCount = (Math.floor((doc.height - startOffsetY) / yStep) + 1) * perRowHexCount; //最大行数
	doc.selection.deselect(); //放弃选取
}

(2) 自定义弹窗输入框
在这里插入图片描述

var num = prompt("请输入1方里格纵向上被六角格分几块?",5); //默认5块

在这里插入图片描述

var go_on = confirm("即将绘制六角格,期间PS不会进行响应,请勿操作,等待运行完成提示,确认后继续!");

(3) 可视化进度条ProgressBar封装
在这里插入图片描述

初始化:

//创建进度条窗口
var win = new Window("palette", "六角格绘制进度", [120,120,420,200]);
win.orientation = "column";
win.alignItems = "fill";
win.spacing = 10;
var tipText = win.add("statictext", [10,0,250,20], "处理六角格中...0%");
var bar = win.add("progressbar",[10,20,280,60], 0, maxCount); //默认最大值maxCount

动态更新,只需在for循环重复绘制图形时加入:

//更新进度
bar.value = idx;
tipText.text = "处理六角格中..." + Math.round((idx / maxCount) * 100) + "%";
win.update();

四、完整操作流程

  1. Photoshop新建画布;

  2. 选择画笔,设置粗细和类型;
    在这里插入图片描述

  3. 使用矩形选择工具选择需要开始绘制六角格的起始格子;
    在这里插入图片描述

  4. 运行脚本,根据脚本提示完成绘制操作。
    在这里插入图片描述

  5. 生成结果:六边形独立图层,所有编号合并单一图层,可整体移动调整位置。
    在这里插入图片描述

五、常见问题FAQ

  1. 报错:ShapeTool没有构造函数
    原因:PS2020不支持直接实例化形状工具;解决方案:基于锚点数组手动构建六边形路径。
  2. 报错:subPathItems.add 不是函数
    原因:PS路径API不支持动态追加锚点;必须一次性传入完整锚点数组创建路径。
  3. 大量文字图层导致PS卡顿
    解决方案:全部文字渲染完成后栅格化,合并至同一个图层。
  4. UnitValue单位对象判断
    typename无法直接调用,封装专用判断函数区分像素单位与普通数值。

六、总结

本套JSX自动化脚本适合工程点位示意图、蜂窝布局图纸、网格编号图纸等场景。相比于人工绘图,极大降低重复性工作。同时也要注意Photoshop ExtendScript存在明显版本差异,跨PS版本运行需要针对性修改API。后续可以继续拓展功能,从文件中读取编号,依次填入对应的六角格中,在附录完整代码中被我注释掉了。

附件

参考

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/hele_two/article/details/164340704

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--