#target photoshop app.bringToFront(); // 【约束】本脚本是一次性工具:窗口只在打开期间存在,点“关闭”后即销毁。 // 禁止任何常驻机制(#targetengine、跨次运行的窗口引用/自动关闭旧窗口等)。 // 用非模态 palette 只是为了在窗口打开时还能点选图层(实时读取选择)。 var cID = charIDToTypeID, sID = stringIDToTypeID, tID = typeIDToStringID; var definitions = [ {label: "[bone]", tag: "bone", group: true, layer: true}, {label: "[bone:name]", tag: "bone", value: "name", group: true, layer: true}, {label: "[slot]", tag: "slot", group: true, layer: true}, {label: "[slot:name]", tag: "slot", value: "name", group: true, layer: true}, {label: "[skin]", tag: "skin", group: true, layer: true}, {label: "[skin:name]", tag: "skin", value: "name", group: true, layer: true}, {label: "[scale:number]", tag: "scale", value: "1", group: true, layer: true}, {label: "[folder]", tag: "folder", group: true, layer: true}, {label: "[folder:name]", tag: "folder", value: "name", group: true, layer: true}, {label: "[overlay]", tag: "overlay", group: true, layer: true}, {label: "[trim]", tag: "trim", group: true, layer: true}, {label: "[trim:false]", tag: "trim", fixedValue: "false", group: true, layer: true}, {label: "[mesh]", tag: "mesh", group: true, layer: true}, {label: "[mesh:name]", tag: "mesh", value: "name", group: true, layer: true}, {label: "[ignore]", tag: "ignore", group: true, layer: true}, {label: "[merge]", tag: "merge", group: true, layer: false}, {label: "[name:pattern]", tag: "name", value: "*", group: true, layer: false}, {label: "[path:name]", tag: "path", value: "name", group: false, layer: true} ]; var documentOpen = true; try { activeDocument; } catch (ignored) { documentOpen = false; } if (!documentOpen) { alert("请先打开一个 Photoshop 文档。"); } else { showDialog(); } function showDialog () { // 非模态窗口:打开期间可以继续在图层面板中点选图层/组,按钮点击时实时读取选择 var dialog = new Window("palette", "Spine导出标签"); dialog.alignChildren = "fill"; // 按钮按适用范围分为三组:图层与组共用 / 仅图层可用 / 仅组可用,不再根据选择置灰 var shared = [], layerOnly = [], groupOnly = []; for (var i = 0; i < definitions.length; i++) { var definition = definitions[i]; if (definition.group && definition.layer) shared.push(definition); else if (definition.layer) layerOnly.push(definition); else if (definition.group) groupOnly.push(definition); } addSection(dialog, "图层与组共用", shared); addSection(dialog, "仅图层可用", layerOnly); addSection(dialog, "仅组可用", groupOnly); var clearButton = dialog.add("button", undefined, "清除所有标签"); clearButton.onClick = function () { clearAllTags(); }; var closeButton = dialog.add("button", undefined, "关闭"); closeButton.alignment = "right"; closeButton.onClick = function () { dialog.hide(); dialog.close(); }; dialog.center(); dialog.show(); dialog.active = true; } function addSection (parent, title, list) { if (!list.length) return; var panel = parent.add("panel", undefined, title); panel.margins = [10,15,10,10]; panel.orientation = "column"; panel.alignChildren = "fill"; for (var i = 0; i < list.length; i += 4) { var row = panel.add("group"); row.alignChildren = "fill"; for (var ii = i; ii < Math.min(i + 4, list.length); ii++) { var definition = list[ii]; var button = row.add("button", undefined, definition.label); bindButton(button, definition); } } } function bindButton (button, definition) { button.onClick = function () { // 每次点击时重新读取当前选中的图层/组(窗口打开期间可随时切换选择) var items = getSelectedItems(); if (!items.length) { alert("请先在图层面板中选中一个或多个图层/组。"); return; } var value = definition.fixedValue; if (definition.value) { value = prompt("请输入 " + definition.label + " 的值:", definition.value); if (value == null) return; value = trim(value); if (!value || value.indexOf("]") != -1) { alert(definition.label + " 必须填写值,且不能包含 ]。"); return; } if (definition.tag == "name" && value.indexOf("*") == -1) { alert("[name:pattern] 必须包含星号 (*)。"); return; } if (definition.tag == "scale" && (isNaN(Number(value)) || Number(value) <= 0)) { alert("[scale:number] 必须是大于 0 的数字。"); return; } } if (!addTag(items, definition, value)) { alert("当前选中的对象中没有可应用 " + definition.label + " 的目标。"); } }; } function addTag (items, definition, value) { var tag = "[" + definition.tag + (value != null ? ":" + value : "") + "]"; var re = new RegExp("\\s*\\[" + definition.tag + "(?::[^\\]]+)?\\]", "ig"); var count = 0; for (var i = 0; i < items.length; i++) { var item = items[i]; if ((item.isGroup && !definition.group) || (item.isLayer && !definition.layer)) continue; var name = trim(item.name.replace(re, "")); setName(item.id, (name ? name + " " : "") + tag); item.name = (name ? name + " " : "") + tag; count++; } return count; } function clearAllTags () { var items = getSelectedItems(); if (!items.length) { alert("请先在图层面板中选中一个或多个图层/组。"); return; } clearTags(items); } function clearTags (items) { var re = /\s*\[(?:(?:bone|slot|skin|folder|mesh)(?::[^\]]+)?|(?:scale|name|path):[^\]]+|trim(?::false)?|overlay|ignore|merge)\]/ig; for (var i = 0; i < items.length; i++) { var name = trim(items[i].name.replace(re, "")); setName(items[i].id, name); items[i].name = name; } } function getSelectedItems () { var items = []; try { var ids = getSelectedIDs(); for (var i = 0; i < ids.length; i++) { try { var ref = new ActionReference(); ref.putIdentifier(cID("Lyr "), ids[i]); var desc = executeActionGet(ref); var section = tID(desc.getEnumerationValue(sID("layerSection"))); if (section != "layerSectionEnd") items.push({ id: ids[i], name: desc.getString(sID("name")), isGroup: section == "layerSectionStart", isLayer: section == "layerSectionContent" }); } catch (ignored) {} } } catch (ignored) {} return items; } function getSelectedIDs () { var ids = [], ref = new ActionReference(); ref.putEnumerated(cID("Dcmn"), cID("Ordn"), cID("Trgt")); var desc = executeActionGet(ref); if (desc.hasKey(sID("targetLayers"))) { var selected = desc.getList(sID("targetLayers")); for (var i = 0; i < selected.count; i++) ids.push(getLayerID(selected.getReference(i).getIndex() + 1)); } if (!ids.length) { try { ref = new ActionReference(); ref.putProperty(cID("Prpr"), sID("layerID")); ref.putEnumerated(cID("Lyr "), cID("Ordn"), cID("Trgt")); ids.push(executeActionGet(ref).getInteger(sID("layerID"))); } catch (ignored) {} } return ids; } function getLayerID (index) { var ref = new ActionReference(); ref.putProperty(cID("Prpr"), sID("layerID")); ref.putIndex(cID("Lyr "), index); return executeActionGet(ref).getInteger(sID("layerID")); } function setName (id, name) { var ref = new ActionReference(); ref.putIdentifier(cID("Lyr "), id); var desc = new ActionDescriptor(); desc.putReference(cID("null"), ref); var layer = new ActionDescriptor(); layer.putString(cID("Nm "), name); desc.putObject(cID("T "), cID("Lyr "), layer); executeAction(cID("setd"), desc, DialogModes.NO); } function trim (value) { return value.replace(/^\s+|\s+$/g, ""); }