feat: AI套图分层方案 + Gemini集成 - 4种图案类型处理 + 正片叠底 + 宽高比 + 模型选择

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-07 16:59:56 +08:00
parent 12395d8eca
commit dae906aba7
277 changed files with 15009 additions and 19922 deletions

View File

@@ -1,56 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="com.designer.adminpanel" ExtensionBundleVersion="1.0.0" Version="8.0">
<ExtensionList>
<Extension Id="com.designer.adminpanel" Version="1.0.0"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<!-- Photoshop CC 2017-2024+ -->
<Host Name="PHSP" Version="[18.0,99.9]"/>
<Host Name="PHXS" Version="[18.0,99.9]"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="8.0"/>
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="com.designer.adminpanel">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<CEFCommandLine>
<Parameter>--enable-nodejs</Parameter>
<Parameter>--mixed-context</Parameter>
</CEFCommandLine>
</Resources>
<Lifecycle>
<AutoVisible>true</AutoVisible>
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>Designer Admin Panel</Menu>
<Geometry>
<Size>
<Height>800</Height>
<Width>1200</Width>
</Size>
<MinSize>
<Height>400</Height>
<Width>600</Width>
</MinSize>
</Geometry>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>

View File

@@ -1,146 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Designer Admin Panel</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
#frame {
width: 100%;
height: 100%;
border: 0;
display: block;
}
/* 加载动画 */
#loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
font-family: Arial, sans-serif;
color: #666;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#frame.loaded ~ #loading {
display: none;
}
</style>
</head>
<body>
<!-- 加载指示器 -->
<div id="loading">
<div class="spinner"></div>
<p>加载中...</p>
</div>
<!-- 主页面 iframe -->
<iframe
id="frame"
src="https://app.aidg168.uk"
allow="clipboard-read; clipboard-write"
></iframe>
<script>
const frame = document.getElementById('frame');
const loading = document.getElementById('loading');
let loadTimeout;
// iframe 加载完成
frame.addEventListener('load', function() {
clearTimeout(loadTimeout);
this.classList.add('loaded');
console.log('✅ 页面加载成功');
});
// iframe 加载错误
frame.addEventListener('error', function(e) {
clearTimeout(loadTimeout);
loading.innerHTML = '<div style="color: red; padding: 20px; text-align: center;">' +
'<h3>❌ 加载失败</h3>' +
'<p>无法连接到https://app.aidg168.uk</p>' +
'<p>请检查网络连接或更换 URL</p>' +
'<button onclick="location.reload()" style="padding: 10px 20px; cursor: pointer;">重试</button>' +
'</div>';
console.error('❌ iframe 加载错误:', e);
});
// 20秒超时处理
loadTimeout = setTimeout(function() {
if (!frame.classList.contains('loaded')) {
loading.innerHTML = '<div style="color: orange; padding: 20px; text-align: center;">' +
'<h3>⏱️ 加载超时</h3>' +
'<p>网页加载时间过长(超过 20 秒)</p>' +
'<p>当前 URL: https://app.aidg168.uk</p>' +
'<button onclick="location.reload()" style="padding: 10px 20px; cursor: pointer; margin: 5px;">重试</button>' +
'<button onclick="testUrl()" style="padding: 10px 20px; cursor: pointer; margin: 5px;">测试连接</button>' +
'</div>';
console.warn('⏱️ iframe 加载超时');
}
}, 20000);
// 测试 URL 是否可访问
function testUrl() {
loading.innerHTML = '<div style="padding: 20px; text-align: center;">' +
'<div class="spinner"></div>' +
'<p>正在测试连接...</p></div>';
fetch('https://app.aidg168.uk', { mode: 'no-cors' })
.then(() => {
alert('✅ 网站可以访问,正在重新加载...');
location.reload();
})
.catch(err => {
loading.innerHTML = '<div style="color: red; padding: 20px; text-align: center;">' +
'<h3>❌ 网站无法访问</h3>' +
'<p>错误: ' + err.message + '</p>' +
'<button onclick="location.reload()" style="padding: 10px 20px; cursor: pointer;">返回</button>' +
'</div>';
});
}
// 与 iframe 通信(可选)
window.addEventListener("message", function(e) {
if (e.origin === "https://app.aidg168.uk") {
console.log("📨 收到消息:", e.data);
}
});
function sendToIframe(data) {
frame.contentWindow.postMessage(data, "https://app.aidg168.uk");
}
// 调试信息
console.log('🚀 AdminPanel 已启动');
console.log('📍 加载 URL:', frame.src);
</script>
</body>
</html>

View File

@@ -1,41 +0,0 @@
@echo off
chcp 65001 >nul
echo ========================================
echo CEP 扩展调试模式检查工具
echo ========================================
echo.
echo [1] 检查当前 CEP 调试模式状态...
reg query "HKEY_CURRENT_USER\Software\Adobe\CSXS.8" /v PlayerDebugMode 2>nul
if %errorlevel% neq 0 (
echo ❌ 未找到 CSXS.8 调试模式配置
) else (
echo ✅ CSXS.8 配置已存在
)
echo.
echo [2] 启用 CEP 调试模式...
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.8" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.9" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.10" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
reg add "HKEY_CURRENT_USER\Software\Adobe\CSXS.11" /v PlayerDebugMode /t REG_SZ /d 1 /f >nul
echo ✅ CEP 调试模式已启用CSXS 8/9/10/11
echo.
echo [3] 检查插件安装位置...
set "EXT_DIR=%APPDATA%\Adobe\CEP\extensions\AdminPanel"
if exist "%EXT_DIR%\" (
echo ✅ 插件已安装到: %EXT_DIR%
dir "%EXT_DIR%" /B
) else (
echo ❌ 插件未安装,请运行安装命令
)
echo.
echo ========================================
echo 操作完成!
echo 请 [完全关闭 Photoshop] 后重新打开
echo ========================================
pause

223
AI改图-双图.py Normal file
View File

@@ -0,0 +1,223 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
双图合成处理 - 基于 multi_image_merge 方法
使用 Gemini 3 Pro Image Preview 模型进行双图创意合成
特点:
- 支持本地图片和网络图片 URL 两种输入方式
- 支持自定义提示词
- 创意合成、场景融合
使用方法:
1. 安装依赖pip install openai requests
2. 在下方配置区填入您的 API Key
3. 修改文件底部的参数变量
4. 运行python 双图合成处理.py
获取 API Keyhttps://api.laozhang.ai/token
"""
import base64
import re
import os
from openai import OpenAI
# ========== 配置区(请填入您的 API Key==========
API_KEY = "sk-KZ9TGgZAWnzY1T3c90B277F72e184c26A53f22440e08E86e" # 替换为您的 API Key
BASE_URL = "https://api.laozhang.ai/v1"
# 模型选择
MODEL = "gemini-3-pro-image-preview" # 最新版,支持更高质量
# MODEL = "gemini-2.5-flash-image" # 稳定版,价格更低($0.025 vs $0.05
# ================================================
def extract_and_save_image(content: str, filename: str) -> bool:
"""从响应内容中提取 base64 图片并保存"""
# 匹配 markdown 格式的 base64 图片
match = re.search(r'!\[.*?\]\((data:image/\w+;base64,([^)]+))\)', content)
if match:
base64_data = match.group(2)
# 确保 base64 填充正确
padding = 4 - len(base64_data) % 4
if padding != 4:
base64_data += '=' * padding
image_data = base64.b64decode(base64_data)
with open(filename, 'wb') as f:
f.write(image_data)
return True
return False
def is_url(path: str) -> bool:
"""判断路径是否为 URL"""
return path.startswith('http://') or path.startswith('https://')
def multi_image_merge(image1_path: str, image2_path: str, prompt: str, output_filename: str = None) -> bool:
"""
多图合成
参数:
image1_path: 第一张图片路径(本地文件路径或 URL
image2_path: 第二张图片路径(本地文件路径或 URL
prompt: 合成提示词
output_filename: 输出文件名(可选)
返回: 是否成功
"""
print("\n" + "="*60)
print("🎨 双图合成处理")
print("="*60)
client = OpenAI(api_key=API_KEY, base_url=BASE_URL)
# 构建 content 数组
content = [{"type": "text", "text": prompt}]
# 处理第一张图片
if is_url(image1_path):
# 网络图片 URL
content.append({"type": "image_url", "image_url": {"url": image1_path}})
print(f"🖼️ 图片1 (URL): {image1_path[:50]}...")
else:
# 本地图片 Base64
if not os.path.exists(image1_path):
print(f"❌ 错误图片1不存在: {image1_path}")
return False
with open(image1_path, "rb") as f:
image1_b64 = base64.b64encode(f.read()).decode("utf-8")
content.append({
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image1_b64}"
}
})
print(f"🖼️ 图片1 (本地): {image1_path}")
# 处理第二张图片
if is_url(image2_path):
# 网络图片 URL
content.append({"type": "image_url", "image_url": {"url": image2_path}})
print(f"🖼️ 图片2 (URL): {image2_path[:50]}...")
else:
# 本地图片 Base64
if not os.path.exists(image2_path):
print(f"❌ 错误图片2不存在: {image2_path}")
return False
with open(image2_path, "rb") as f:
image2_b64 = base64.b64encode(f.read()).decode("utf-8")
content.append({
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image2_b64}"
}
})
print(f"🖼️ 图片2 (本地): {image2_path}")
print(f"📝 提示词: {prompt}")
print("📡 发送请求...")
try:
response = client.chat.completions.create(
model=MODEL,
messages=[
{
"role": "user",
"content": content
}
]
)
result_content = response.choices[0].message.content
# 确定输出文件名
if output_filename:
output_file = output_filename
else:
output_file = "result_multi_merge.png"
if extract_and_save_image(result_content, output_file):
print(f"✅ 成功!图片已保存: {output_file}")
return True
else:
print(f"⚠️ 未找到图片数据")
return False
except Exception as e:
print(f"❌ 错误: {str(e)}")
return False
def main():
"""主函数"""
print("="*60)
print("🎨 双图合成处理")
print(f"模型: {MODEL}")
print("="*60)
# 检查 API Key
if API_KEY == "sk-YOUR_API_KEY" or not API_KEY:
print("\n❌ 请先配置您的 API Key")
print(" 获取地址: https://api.laozhang.ai/token")
print(" 然后修改脚本顶部的 API_KEY 变量")
return
# 检查图片是否存在(仅本地文件)
if not is_url(IMAGE1_PATH) and not os.path.exists(IMAGE1_PATH):
print(f"\n❌ 错误图片1文件不存在: {IMAGE1_PATH}")
print(" 请检查文件路径是否正确")
return
if not is_url(IMAGE2_PATH) and not os.path.exists(IMAGE2_PATH):
print(f"\n❌ 错误图片2文件不存在: {IMAGE2_PATH}")
print(" 请检查文件路径是否正确")
return
print(f"\n📋 配置信息:")
print(f" 图片1: {IMAGE1_PATH}")
print(f" 图片2: {IMAGE2_PATH}")
print(f" 提示词: {PROMPT}")
print()
# 执行合成
result = multi_image_merge(
image1_path=IMAGE1_PATH,
image2_path=IMAGE2_PATH,
prompt=PROMPT,
output_filename=OUTPUT_FILENAME
)
if result:
print("\n" + "="*60)
print("🎉 合成完成!")
print("="*60)
else:
print("\n" + "="*60)
print("❌ 合成失败,请检查错误信息")
print("="*60)
# ========== 参数配置区(请修改以下参数)==========
# 1. 第一张图片路径(支持本地文件路径或网络 URL
IMAGE1_PATH = "5.png" # 本地文件示例: "1.png" 或 "C:/images/photo1.jpg"
# IMAGE1_PATH = "https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?w=800&q=80" # URL 示例
# 2. 第二张图片路径(支持本地文件路径或网络 URL
IMAGE2_PATH = "6.png" # 本地文件示例: "2.png" 或 "C:/images/photo2.jpg"
# IMAGE2_PATH = "https://images.unsplash.com/photo-1560806887-1e4cd0b6cbd6?w=800&q=80" # URL 示例
# 3. 合成提示词(描述如何合成这两张图片)
PROMPT = "把第2张图的衣股花型图案替换成图1的花型图案" # 修改为您想要的合成效果描述
# 4. 输出文件名(可选,留空则使用默认名称)
OUTPUT_FILENAME = None # 例如: "合成结果.png" 或 None使用默认名称
# ================================================
if __name__ == "__main__":
main()

View File

@@ -13,7 +13,7 @@ const config: ICepConfig = {
"panels": [ "panels": [
{ {
"name": "AdminPanel-dev", "name": "AdminPanel-dev",
"displayName": "管理面板", "displayName": "ps套版",
"main": "./index.html", "main": "./index.html",
"width": 280, "width": 280,
"height": 600, "height": 600,

View File

@@ -7,6 +7,7 @@
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="/CSInterface.js"></script>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

View File

@@ -78,6 +78,13 @@ export class CEP {
this.writeDebug() this.writeDebug()
this.copyJson2() this.copyJson2()
// 确保 CEP 扩展目录存在
const cepExtDir = getAdobeCepDir()
if (!fs.existsSync(cepExtDir)) {
fs.mkdirSync(cepExtDir, { recursive: true })
console.log('[CEP] 已创建 CEP 扩展目录:', cepExtDir)
}
// 创建符号链接或直接复制 // 创建符号链接或直接复制
if (!fs.existsSync(this.cepLink)) { if (!fs.existsSync(this.cepLink)) {
try { try {
@@ -85,8 +92,8 @@ export class CEP {
console.log('[CEP] 符号链接已创建') console.log('[CEP] 符号链接已创建')
} catch (error: any) { } catch (error: any) {
// 权限不足时,改用复制 // 权限不足时,改用复制
if (error.code === 'EPERM') { if (error.code === 'EPERM' || error.code === 'ENOENT') {
console.warn('[CEP] 符号链接权限不足,改用复制方式') console.warn('[CEP] 符号链接创建失败,改用复制方式')
this.copyToCepDir() this.copyToCepDir()
} else { } else {
throw error throw error
@@ -160,7 +167,7 @@ export class CEP {
} }
// 3. 复制并修正 HTML 路径 // 3. 复制并修正 HTML 路径
const builtHtmlPath = path.join(this.dist, 'src/launcher/index.html') const builtHtmlPath = path.join(this.dist, 'index.html')
const targetHtmlPath = path.join(this.cepOutput, 'index.html') const targetHtmlPath = path.join(this.cepOutput, 'index.html')
if (fs.existsSync(builtHtmlPath)) { if (fs.existsSync(builtHtmlPath)) {

View File

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

Before

Width:  |  Height:  |  Size: 475 B

After

Width:  |  Height:  |  Size: 475 B

View File

Before

Width:  |  Height:  |  Size: 846 B

After

Width:  |  Height:  |  Size: 846 B

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -5,8 +5,8 @@ export function joinHtml(name:string,server:string){
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>${name}</title> <title>${name}</title>
<script> <script>
// 直接跳转到目标网址(带时间戳防止缓存) // 跳转到开发服务器地址
window.location.href = "https://app.aidg168.uk/?_t=" + Date.now(); window.location.href = "${server}";
</script> </script>
</head> </head>
<body> <body>

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -5,10 +5,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { useTheme } from '@/hooks/useTheme' // 主题初始化已在 main.ts 中完成
// 初始化主题
const { isDark } = useTheme()
</script> </script>
<style> <style>

View File

@@ -1,5 +1,4 @@
import { isNodeJSEnabled } from "./tool" import { isNodeJSEnabled } from "./tool"
console.error('isNodeJSEnabled()'+isNodeJSEnabled());
/** /**
* cep_node 为ps内置的全局变量 * cep_node 为ps内置的全局变量

View File

@@ -107,7 +107,30 @@ export const updateTheme = () => {
const isLight = brightness > 128; const isLight = brightness > 128;
applyTheme(bgColorHex, isLight, skinInfo.baseFontSize); applyTheme(bgColorHex, isLight, skinInfo.baseFontSize);
// 发送主题给 iframe
const iframe = document.querySelector('iframe');
if (iframe && iframe.contentWindow) {
const message: PSThemeMessage = {
type: 'PS_THEME',
theme: {
bgColor: bgColorHex,
isLight: isLight,
fontSize: skinInfo.baseFontSize
}
}; };
iframe.contentWindow.postMessage(message, '*');
}
};
/**
* 处理来自子 iframe 的请求
*/
function handleChildMessage(event: MessageEvent) {
if (event.data && event.data.type === 'REQUEST_THEME') {
updateTheme();
}
}
/** /**
* 处理来自父窗口的主题消息iframe 模式) * 处理来自父窗口的主题消息iframe 模式)
@@ -138,6 +161,8 @@ export const initThemeListener = () => {
logger.log('[Theme] CEP 模式 - 直接监听主题变化'); logger.log('[Theme] CEP 模式 - 直接监听主题变化');
updateTheme(); updateTheme();
cep.addEventListener('com.adobe.csxs.events.ThemeColorChanged', updateTheme); cep.addEventListener('com.adobe.csxs.events.ThemeColorChanged', updateTheme);
// 监听来自 iframe 的请求
window.addEventListener('message', handleChildMessage);
} else { } else {
// 纯浏览器模式 // 纯浏览器模式
logger.log('[Theme] 浏览器模式 - 使用默认主题'); logger.log('[Theme] 浏览器模式 - 使用默认主题');

View File

@@ -1,26 +1,13 @@
<template> <template>
<div class="iframe-container"> <div class="iframe-container">
<!-- 顶部工具栏 --> <!-- 顶部工具栏 (已移除) -->
<div class="toolbar">
<a-button size="small" @click="goBack">
<template #icon><icon-arrow-left /></template>
返回
</a-button>
<span class="url-display">{{ currentUrl }}</span>
<a-button size="small" @click="reload">
<template #icon><icon-refresh /></template>
刷新
</a-button>
<a-button size="small" @click="openExternal">
<template #icon><icon-export /></template>
外部打开
</a-button>
</div>
<!-- 加载指示器 --> <!-- 加载指示器 -->
<div v-if="loading" class="loading-overlay"> <div v-if="loading" class="loading-overlay">
<a-spin size="32" /> <a-spin size="32" />
<p>加载中...</p> <p>加载中...</p>
<p style="font-size:12px;color:#999;margin-top:10px;">目标地址: {{ currentUrl }}</p>
<p style="font-size:12px;color:#999;">开发环境: {{ isDev ? '是' : '否' }}</p>
</div> </div>
<!-- iframe --> <!-- iframe -->
@@ -36,39 +23,21 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { updateTheme } from '@/utils/theme'
import { IconArrowLeft, IconRefresh, IconExport } from '@arco-design/web-vue/es/icon'
const router = useRouter()
const frameRef = ref<HTMLIFrameElement | null>(null) const frameRef = ref<HTMLIFrameElement | null>(null)
const loading = ref(true) const loading = ref(true)
const isDev = ref(typeof __DEV__ !== 'undefined' ? __DEV__ : false)
const currentUrl = ref( const currentUrl = ref(
localStorage.getItem('adminPanelUrl') || ( localStorage.getItem('adminPanelUrl') || 'http://localhost:5173'
__DEV__
? 'http://localhost:5173'
: 'https://app.aidg168.uk'
) )
)
function goBack() {
router.push('/')
}
function reload() {
loading.value = true
if (frameRef.value) {
frameRef.value.src = currentUrl.value
}
}
function openExternal() {
window.open(currentUrl.value, '_blank')
}
function onFrameLoad() { function onFrameLoad() {
loading.value = false loading.value = false
console.log('✅ iframe 加载完成') console.log('✅ iframe 加载完成')
// iframe 加载完成后立即同步主题
updateTheme()
} }
function onFrameError(e: Event) { function onFrameError(e: Event) {
@@ -94,24 +63,6 @@ onMounted(() => {
background: var(--ps-bg, #1e1e1e); background: var(--ps-bg, #1e1e1e);
} }
.toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--color-bg-2, #2d2d2d);
border-bottom: 1px solid var(--ps-border, #444);
}
.url-display {
flex: 1;
font-size: 12px;
color: var(--ps-text, #aaa);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.main-frame { .main-frame {
flex: 1; flex: 1;
width: 100%; width: 100%;

View File

@@ -19,7 +19,7 @@ export default defineConfig(({ command }) => {
legacy({ legacy({
targets: ['chrome 41', 'not IE 11'] targets: ['chrome 41', 'not IE 11']
}), }),
!isBuild && cepPlugin(), // 开发时自动复制到 PS 扩展目录 cepPlugin(), // 开发时自动复制到 PS 扩展目录,构建时生成 CEP 结构
vue(), vue(),
tsconfigPaths(), tsconfigPaths(),
], ],

225
PSMARK代码块/44222.jsx Normal file
View File

@@ -0,0 +1,225 @@

贴图换图()
function 贴图换图() {
建立快照();
var folder = Folder.selectDialog("请选择一个文件夹");
var 模特文档 = app.activeDocument;
if (folder) {
// 在选择的文件夹中创建一个新的子文件夹
var targetFolder = new Folder(folder.fullName + "/贴图生成");
if (!targetFolder.exists) {
targetFolder.create();
}
var fileNames = [];
var files = folder.getFiles();
// 获取文件夹中的所有文件名
for (var i = 0; i < files.length; i++) {
if (files[i] instanceof File) {
fileNames.push(files[i].name);
}
}
// 获取“贴图位置”图层组中的所有图层名称
var layerSet = 模特文档.layerSets.getByName("贴图位置");
var layerNames = [];
for (var j = 0; j < layerSet.artLayers.length; j++) {
layerNames.push(layerSet.artLayers[j].name);
}
// 遍历“贴图位置”图层组中的每个图层
for (var k = 0; k < layerNames.length; k++) {
var layname = layerNames[k];
var 贴图位置 = app.activeDocument.layerSets.getByName("贴图位置").layers.getByName(layname);
app.activeDocument.activeLayer = 贴图位置;
// 依次调取文件夹中的文件
if (k < fileNames.length) {
var file = new File(folder + "/" + fileNames[k]);
if (file.exists) {
try {
app.open(file);
app.activeDocument.flatten();
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
预设图案(当前文档名称); // 需要自定义实现
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument = 模特文档;
载入选区(); // 需要自定义实现
填充图案(当前文档名称); // 需要自定义实现
} catch (e) {
alert("无法打开文件: " + fileNames[k]);
}
// 保存文件为 TIF 格式
}
}
}
var 当前文档 = app.activeDocument; // 获取当前文档
var 当前文档名称 = 当前文档.name; // 获取当前文档名称(包含扩展名)
// 去除文件扩展名并添加 ".tif" 后缀
var 文件名 = 当前文档名称.replace(/\.[^\.]+$/, "") + ".tif";
// 创建保存路径的 File 对象
var saveFile = new File(当前文档.path + "/" + 文件名);
// 设置 Tiff 保存选项
var tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.imageCompression = TIFFEncoding.NONE; // 无压缩
tiffSaveOptions.alphaChannels = true; // 保留 alpha 通道
tiffSaveOptions.layers = true; // 保留图层
// 另存为 TIF 格式
当前文档.saveAs(saveFile, tiffSaveOptions, true, Extension.LOWERCASE);
alert("换图完成");
}
}
// 其他函数保持不变
function generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 150);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 取消链接蒙版() //取消链接蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putBoolean(stringIDToTypeID("userMaskLinked"), false);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 栅格化图层() //栅格化图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("rasterizeLayer"), d, DialogModes.NO);
}
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 位移(水平,垂直) //位移
{
var d = new ActionDescriptor();
d.putInteger(stringIDToTypeID("horizontal"), 水平);
d.putInteger(stringIDToTypeID("vertical"), 垂直);
d.putEnumerated(stringIDToTypeID("fill"), stringIDToTypeID("fillMode"), stringIDToTypeID("wrap"));
executeAction(stringIDToTypeID("offset"), d, DialogModes.NO);
}
function 历史记录回退到快照1() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("snapshotClass"), "快照 1");
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 建立快照() //打开
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("snapshotClass"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("historyState"), stringIDToTypeID("currentHistoryState"));
d.putReference(stringIDToTypeID("from"), r1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}

1001
PSMARK代码块/JSX17.jsx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
//JSX监听某个事件
alert("当前监听事件个数:"+app.notifiers.length,"提示:")
try
{
arg_num = arguments.length;
}
catch(e)
{
arguments = []; //初始赋值为空
}
eventName = "recordMeasurements";
//开启监听
enable_notifier(eventName, $.fileName);
//取消监听
//~ disable_notifier(eventName, $.fileName);
//这里进行监控调用事件
if (arguments.length >= 2)
{
//alert(arguments.length);
//~ alert(arguments[0],"动作参数1"); //动作描述符 AR
//~ alert(arguments[1],"动作参数2"); //动作事件ID
alert("事件名:"+typeIDToStringID(arguments[1])+"\n"+"事件ID:"+arguments[1],"提示:");
//main(arguments[0], arguments[1]);
}
function enable_notifier(event_name, script_name, event_class)
{
try
{
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
if (!app.notifiersEnabled) app.notifiersEnabled = true;
return true;
}
}
app.notifiers.add(event_name, File(script_name), event_class);
app.notifiersEnabled = true;
return true;
}
catch (e) { _alert(e); return false; }
}
function disable_notifier(event_name, script_name, event_class)
{
try
{
var ret = false;
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
app.notifiers[i].remove();
ret = true;
}
}
if (!app.notifiers.length) app.notifiersEnabled = false;
return ret;
}
catch (e) { _alert(e); return false; }
}

View File

@@ -0,0 +1,627 @@
// photoshopscripts.wordpress.com
////////////////////////////////////
// Split to Layers 1.0 //
// 2012, David Jensen //
// //
// With help from //
// pfaffenbichler and xbytor //
// at ps-scripts.com //
////////////////////////////////////
#target photoshop
//更改以下 5 个值中的任何一个以自定义脚本的默认选项:
var showOptionsDialog = true; //设置为 false 以禁用对用户的提示.
var tolerance = 2; // 将被忽略的透明像素的最大间隙,设置默认值.
var confirmThreshold = 20; // 如果脚本要制作大量图层,提示用户确认这是可以的.
var suffix = "-"; // 将此添加到新图层的图层名称中. 设置为空不添加.
var addCount = true; // 在每个新层的末尾添加一个增量数字.
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
function 裁片分解()
{
var layerNamePreview=activeDocument.activeLayer.name + suffix;
if (addCount === true){
layerNamePreview += "1";
}
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
bounds = activeDocument.activeLayer.bounds
var emptyLayer=false;
if (Number(bounds[0]) == 0 && Number(bounds[1]) == 0 && Number(bounds[2]) == 0 && Number(bounds[3]) == 0) {emptyLayer = true};
try{
if (activeDocument.activeLayer.kind != undefined && activeDocument.activeLayer.isBackgroundLayer == false && emptyLayer == false){
activeDocument.suspendHistory("Separate", "main()");
//~ app.doForcedProgress("PSMark-裁片分解","PSMark_main()");
//~ app.doProgress("PSMark-裁片分解","PSMark_main()");
//~ app.doForcedProgress("PSMark-裁片分解","main()");
}else{
alert( "未选择支持的图层类型.");
}
}catch(err){
alert(err)
}
app.preferences.rulerUnits = originalRulerUnits;
}
function PSMark_main()
{
app.activeDocument.suspendHistory("Separate", "main()");
}
function main() {
baseLayer=activeDocument.activeLayer;
activeDocument.quickMaskMode = false;
activeDocument.selection.deselect()
var layerName = activeDocument.activeLayer.name
//if a selection can't be made, stop running the script
var idCpTL = charIDToTypeID("CpTL");
executeAction(idCpTL, undefined, DialogModes.NO);
activeDocument.activeLayer.rasterize(RasterizeType.ENTIRELAYER)
try{
var idDlt = charIDToTypeID( "Dlt " );
var desc120 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref112 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idChnl = charIDToTypeID( "Chnl" );
var idMsk = charIDToTypeID( "Msk " );
ref112.putEnumerated( idChnl, idChnl, idMsk );
desc120.putReference( idnull, ref112 );
var idAply = charIDToTypeID( "Aply" );
desc120.putBoolean( idAply, true );
executeAction( idDlt, desc120, DialogModes.NO );
}catch(e){}
activeDocument.activeLayer.name = layerName;
baseLayer=activeDocument.activeLayer;
makeSelection();
var idMk = charIDToTypeID("Mk ");
var desc642 = new ActionDescriptor();
var idNw = charIDToTypeID("Nw ");
var idDcmn = charIDToTypeID("Dcmn");
desc642.putClass(idNw, idDcmn);
var idUsng = charIDToTypeID("Usng");
var ref535 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref535.putEnumerated(idChnl, idOrdn, idTrgt);
desc642.putReference(idUsng, ref535);
executeAction(idMk, desc642, DialogModes.NO);
newDoc = activeDocument;
// =======================================================
activeDocument.resizeImage("200%", "200%", undefined, ResampleMethod.NEARESTNEIGHBOR);
// =======================================================
var idsetd = charIDToTypeID("setd");
var desc934 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref535 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref535.putProperty(idChnl, idfsel);
desc934.putReference(idnull, ref535);
var idT = charIDToTypeID("T ");
var ref536 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref536.putEnumerated(idChnl, idOrdn, idTrgt);
desc934.putReference(idT, ref536);
executeAction(idsetd, desc934, DialogModes.NO);
var idMk = charIDToTypeID("Mk ");
var desc403 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref288 = new ActionReference();
var idPath = charIDToTypeID("Path");
ref288.putClass(idPath);
desc403.putReference(idnull, ref288);
var idFrom = charIDToTypeID("From");
var ref289 = new ActionReference();
var idcsel = charIDToTypeID("csel");
var idfsel = charIDToTypeID("fsel");
var idfsel = charIDToTypeID("fsel");
ref289.putProperty(idcsel, idfsel);
desc403.putReference(idFrom, ref289);
var idTlrn = charIDToTypeID("Tlrn");
var idPxl = charIDToTypeID("#Pxl");
desc403.putUnitDouble(idTlrn, idPxl, 0.500000);
executeAction(idMk, desc403, DialogModes.NO);
var subPathsLength = activeDocument.pathItems[0].subPathItems.length
if (subPathsLength>confirmThreshold){
var answer = confirm("基于"+subPathsLength+ "个拆分对象将创建图层. 你想继续吗?",true)
if (answer === false){
newDoc.close(SaveOptions.DONOTSAVECHANGES);
activeDocument.quickMaskMode = false;
activeDocument.selection.deselect();
return 0;
}
}
// =======================================================
activeDocument.resizeImage("50%", "50%", undefined, ResampleMethod.NEARESTNEIGHBOR)
var pathInfo = collectPathInfoFromDesc(activeDocument, activeDocument.pathItems[activeDocument.pathItems.length - 1])
// =======================================================
newDoc.close(SaveOptions.DONOTSAVECHANGES)
// =======================================================
activeDocument.quickMaskMode = false
// =======================================================
//make channel
// =======================================================
var idMk = charIDToTypeID("Mk ");
var desc6 = new ActionDescriptor();
var idNw = charIDToTypeID("Nw ");
var desc7 = new ActionDescriptor();
var idNm = charIDToTypeID("Nm ");
desc7.putString(idNm, "ContiguityMask");
var idClrI = charIDToTypeID("ClrI");
var idMskI = charIDToTypeID("MskI");
var idMskA = charIDToTypeID("MskA");
desc7.putEnumerated(idClrI, idMskI, idMskA);
var idClr = charIDToTypeID("Clr ");
var desc8 = new ActionDescriptor();
var idRd = charIDToTypeID("Rd ");
desc8.putDouble(idRd, 255.000000);
var idGrn = charIDToTypeID("Grn ");
desc8.putDouble(idGrn, 0.000000);
var idBl = charIDToTypeID("Bl ");
desc8.putDouble(idBl, 0.000000);
var idRGBC = charIDToTypeID("RGBC");
desc7.putObject(idClr, idRGBC, desc8);
var idOpct = charIDToTypeID("Opct");
desc7.putInteger(idOpct, 50);
var idChnl = charIDToTypeID("Chnl");
desc6.putObject(idNw, idChnl, desc7);
var idUsng = charIDToTypeID("Usng");
var ref5 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref5.putProperty(idChnl, idfsel);
desc6.putReference(idUsng, ref5);
executeAction(idMk, desc6, DialogModes.NO);
doc_name = app.activeDocument.name.replace(/(?:\.[^.]*$|$)/, '');
var layerCount = 1
for (i = 0; i < subPathsLength; i++) {
//deselect
var idsetd = charIDToTypeID("setd");
var desc279 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref137 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref137.putProperty(idChnl, idfsel);
desc279.putReference(idnull, ref137);
var idT = charIDToTypeID("T ");
var idOrdn = charIDToTypeID("Ordn");
var idNone = charIDToTypeID("None");
desc279.putEnumerated(idT, idOrdn, idNone);
executeAction(idsetd, desc279, DialogModes.NO);
///select alpha channel
var idslct = charIDToTypeID("slct");
var desc315 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref175 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
ref175.putName(idChnl, "ContiguityMask");
desc315.putReference(idnull, ref175);
executeAction(idslct, desc315, DialogModes.NO);
//use magic wand
var idsetd = charIDToTypeID("setd");
var desc263 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref123 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref123.putProperty(idChnl, idfsel);
desc263.putReference(idnull, ref123);
var idT = charIDToTypeID("T ");
var desc264 = new ActionDescriptor();
var idHrzn = charIDToTypeID("Hrzn");
var idRlt = charIDToTypeID("#Rlt");
desc264.putUnitDouble(idHrzn, idRlt, pathInfo[i][0][0]);
var idVrtc = charIDToTypeID("Vrtc");
var idRlt = charIDToTypeID("#Rlt");
desc264.putUnitDouble(idVrtc, idRlt, pathInfo[i][0][1]);
var idPnt = charIDToTypeID("Pnt ");
desc263.putObject(idT, idPnt, desc264);
var idTlrn = charIDToTypeID("Tlrn");
desc263.putInteger(idTlrn, 1);
executeAction(idsetd, desc263, DialogModes.NO);
var idslct = charIDToTypeID("slct");
var desc346 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref205 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idRGB = charIDToTypeID("RGB ");
ref205.putEnumerated(idChnl, idChnl, idRGB);
desc346.putReference(idnull, ref205);
var idMkVs = charIDToTypeID("MkVs");
desc346.putBoolean(idMkVs, false);
executeAction(idslct, desc346, DialogModes.NO);
try {
// =======================================================
var idCpTL = charIDToTypeID("CpTL");
executeAction(idCpTL, undefined, DialogModes.NO);
try {
var idrasterizeLayer = stringIDToTypeID("rasterizeLayer");
var desc924 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref721 = new ActionReference();
var idLyr = charIDToTypeID("Lyr ");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref721.putEnumerated(idLyr, idOrdn, idTrgt);
desc924.putReference(idnull, ref721);
var idWhat = charIDToTypeID("What");
var idrasterizeItem = stringIDToTypeID("rasterizeItem");
var idvectorMask = stringIDToTypeID("vectorMask");
desc924.putEnumerated(idWhat, idrasterizeItem, idvectorMask);
executeAction(idrasterizeLayer, desc924, DialogModes.NO);
} catch (err) {}
try {
var idIntr = charIDToTypeID("Intr");
var desc864 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref658 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref658.putEnumerated(idChnl, idOrdn, idTrgt);
desc864.putReference(idnull, ref658);
var idWith = charIDToTypeID("With");
var ref659 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref659.putProperty(idChnl, idfsel);
desc864.putReference(idWith, ref659);
executeAction(idIntr, desc864, DialogModes.NO);
// =======================================================
var idDlt = charIDToTypeID("Dlt ");
var desc865 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref660 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref660.putEnumerated(idChnl, idOrdn, idTrgt);
desc865.putReference(idnull, ref660);
executeAction(idDlt, desc865, DialogModes.NO);
// =======================================================
var idMk = charIDToTypeID("Mk ");
var desc866 = new ActionDescriptor();
var idNw = charIDToTypeID("Nw ");
var idChnl = charIDToTypeID("Chnl");
desc866.putClass(idNw, idChnl);
var idAt = charIDToTypeID("At ");
var ref661 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idMsk = charIDToTypeID("Msk ");
ref661.putEnumerated(idChnl, idChnl, idMsk);
desc866.putReference(idAt, ref661);
var idUsng = charIDToTypeID("Usng");
var idUsrM = charIDToTypeID("UsrM");
var idRvlS = charIDToTypeID("RvlS");
desc866.putEnumerated(idUsng, idUsrM, idRvlS);
executeAction(idMk, desc866, DialogModes.NO);
} catch (err) {}
var finalSuffix = suffix;
if (addCount===true)
{
finalSuffix += layerCount;
}
//~ activeDocument.activeLayer.name = layerName + finalSuffix;
//分解的图层命名
activeDocument.activeLayer.name = "Mark" +"-" + doc_name + finalSuffix;
layerCount++;
//~ $.writeln(100*(layerCount-1)/subPathsLength);
//~ $.writeln("subPathsLength:"+subPathsLength);
//~ app.updateProgress(100*(layerCount-1)/subPathsLength,100);
activeDocument.activeLayer=baseLayer;
}
catch (e) {}
}
var idsetd = charIDToTypeID("setd");
var desc1045 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref578 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref578.putProperty(idChnl, idfsel);
desc1045.putReference(idnull, ref578);
var idT = charIDToTypeID("T ");
var idOrdn = charIDToTypeID("Ordn");
var idNone = charIDToTypeID("None");
desc1045.putEnumerated(idT, idOrdn, idNone);
executeAction(idsetd, desc1045, DialogModes.NO);
// =======================================================
var idDlt = charIDToTypeID("Dlt ");
var desc694 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref323 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
ref323.putName(idChnl, "ContiguityMask");
desc694.putReference(idnull, ref323);
executeAction(idDlt, desc694, DialogModes.NO);
activeDocument.activeLayer.remove();
var idHd = charIDToTypeID("Hd ");
var desc736 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var list22 = new ActionList();
var ref541 = new ActionReference();
var idLyr = charIDToTypeID("Lyr ");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref541.putEnumerated(idLyr, idOrdn, idTrgt);
list22.putReference(ref541);
desc736.putList(idnull, list22);
executeAction(idHd, desc736, DialogModes.NO);
}
// pfaffenbichler and xbytor //
// at ps-scripts.com //
// created this function //
function collectPathInfoFromDesc(myDocument, thePath) {
var myDocument = app.activeDocument;
// based of functions from xbytors stdlib;
var ref = new ActionReference();
for (var l = 0; l < myDocument.pathItems.length; l++) {
var thisPath = myDocument.pathItems[l];
if (thisPath == thePath && thisPath.name == "Work Path") {
ref.putProperty(cTID("Path"), cTID("WrPt"));
};
if (thisPath == thePath && thisPath.name != "Work Path" && thisPath.kind != PathKind.VECTORMASK) {
ref.putIndex(cTID("Path"), l + 1);
};
if (thisPath == thePath && thisPath.kind == PathKind.VECTORMASK) {
var idPath = charIDToTypeID("Path");
var idPath = charIDToTypeID("Path");
var idvectorMask = stringIDToTypeID("vectorMask");
ref.putEnumerated(idPath, idPath, idvectorMask);
};
};
var desc = app.executeActionGet(ref);
var pname = desc.getString(cTID('PthN'));
// create new array;
var theArray = new Array;
var pathComponents = desc.getObjectValue(cTID("PthC")).getList(sTID('pathComponents'));
// for subpathitems;
for (var m = 0; m < pathComponents.count; m++) {
var listKey = pathComponents.getObjectValue(m).getList(sTID("subpathListKey"));
// for subpathitems count;
for (var n = 0; n < listKey.count; n++) {
var points = listKey.getObjectValue(n).getList(sTID('points'));
// get first point;
var anchorObj = points.getObjectValue(0).getObjectValue(sTID("anchor"));
var anchor = [anchorObj.getUnitDoubleValue(sTID('horizontal')), anchorObj.getUnitDoubleValue(sTID('vertical'))];
var thisPoint = [anchor];
theArray.push(thisPoint);
};
};
// by xbytor, thanks to him;
function cTID(s) {
return cTID[s] || cTID[s] = app.charIDToTypeID(s);
};
function sTID(s) {
return sTID[s] || sTID[s] = app.stringIDToTypeID(s);
};
// reset;
return theArray;
};
function makePreviewSelection(){
makeSelection()
app.refresh()
activeDocument.quickMaskMode = false;
}
function makeSelection(){
try{
var idsetd = charIDToTypeID("setd");
var desc922 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref529 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref529.putProperty(idChnl, idfsel);
desc922.putReference(idnull, ref529);
var idT = charIDToTypeID("T ");
var ref530 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idTrsp = charIDToTypeID("Trsp");
ref530.putEnumerated(idChnl, idChnl, idTrsp);
desc922.putReference(idT, ref530);
executeAction(idsetd, desc922, DialogModes.NO);
} catch (err) {
return false;
}
try {
var idIntr = charIDToTypeID("Intr");
var desc846 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref639 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idMsk = charIDToTypeID("Msk ");
ref639.putEnumerated(idChnl, idChnl, idMsk);
desc846.putReference(idnull, ref639);
var idWith = charIDToTypeID("With");
var ref640 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref640.putProperty(idChnl, idfsel);
desc846.putReference(idWith, ref640);
executeAction(idIntr, desc846, DialogModes.NO);
} catch (err) {}
try {
// =======================================================
var idIntW = charIDToTypeID("IntW");
var desc787 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref572 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref572.putProperty(idChnl, idfsel);
desc787.putReference(idnull, ref572);
var idT = charIDToTypeID("T ");
var ref573 = new ActionReference();
var idPath = charIDToTypeID("Path");
var idPath = charIDToTypeID("Path");
var idvectorMask = stringIDToTypeID("vectorMask");
ref573.putEnumerated(idPath, idPath, idvectorMask);
var idLyr = charIDToTypeID("Lyr ");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref573.putEnumerated(idLyr, idOrdn, idTrgt);
desc787.putReference(idT, ref573);
var idVrsn = charIDToTypeID("Vrsn");
desc787.putInteger(idVrsn, 1);
var idvectorMaskParams = stringIDToTypeID("vectorMaskParams");
desc787.putBoolean(idvectorMaskParams, true);
executeAction(idIntW, desc787, DialogModes.NO);
} catch (err) {}
if (tolerance >= 2) {
activeDocument.selection.expand(Math.floor(tolerance / 2))
}
activeDocument.quickMaskMode = true;
var idThrs = charIDToTypeID("Thrs");
var desc479 = new ActionDescriptor();
var idLvl = charIDToTypeID("Lvl ");
desc479.putInteger(idLvl, 1);
executeAction(idThrs, desc479, DialogModes.NO);
if (tolerance % 2 == 1) {
var idMtnB = charIDToTypeID("MtnB");
var desc213 = new ActionDescriptor();
var idAngl = charIDToTypeID("Angl");
desc213.putInteger(idAngl, 0);
var idDstn = charIDToTypeID("Dstn");
var idPxl = charIDToTypeID("#Pxl");
desc213.putUnitDouble(idDstn, idPxl, 1.000000);
executeAction(idMtnB, desc213, DialogModes.NO);
// =======================================================
var idMtnB = charIDToTypeID("MtnB");
var desc214 = new ActionDescriptor();
var idAngl = charIDToTypeID("Angl");
desc214.putInteger(idAngl, 90);
var idDstn = charIDToTypeID("Dstn");
var idPxl = charIDToTypeID("#Pxl");
desc214.putUnitDouble(idDstn, idPxl, 1.000000);
executeAction(idMtnB, desc214, DialogModes.NO);
// =======================================================
var idThrs = charIDToTypeID("Thrs");
var desc215 = new ActionDescriptor();
var idLvl = charIDToTypeID("Lvl ");
desc215.putInteger(idLvl, 1);
executeAction(idThrs, desc215, DialogModes.NO);
}
}

View File

@@ -0,0 +1,673 @@
// photoshopscripts.wordpress.com
////////////////////////////////////
// Split to Layers 1.0 //
// 2012, David Jensen //
// //
// With help from //
// pfaffenbichler and xbytor //
// at ps-scripts.com //
////////////////////////////////////
#target photoshop
//更改以下 5 个值中的任何一个以自定义脚本的默认选项:
var showOptionsDialog = true; //设置为 false 以禁用对用户的提示.
var tolerance = 2; // 将被忽略的透明像素的最大间隙,设置默认值.
var confirmThreshold = 20; // 如果脚本要制作大量图层,提示用户确认这是可以的.
var suffix = "-"; // 将此添加到新图层的图层名称中. 设置为空不添加.
var addCount = true; // 在每个新层的末尾添加一个增量数字.
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
///////////////////////////////////////
var layerNamePreview=activeDocument.activeLayer.name + suffix;
if (addCount === true){
layerNamePreview += "1";
}
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.POINTS;
bounds = activeDocument.activeLayer.bounds
var emptyLayer=false;
if (Number(bounds[0]) == 0 && Number(bounds[1]) == 0 && Number(bounds[2]) == 0 && Number(bounds[3]) == 0) {emptyLayer = true};
try{
if (activeDocument.activeLayer.kind != undefined && activeDocument.activeLayer.isBackgroundLayer == false && emptyLayer == false){
app.doForcedProgress("PSMark-裁片分解","PSMark_main()");
}else{
alert( "未选择支持的图层类型.");
}
}catch(err){
alert(err)
}
app.preferences.rulerUnits = originalRulerUnits;
function PSMark_main()
{
app.activeDocument.suspendHistory("Separate", "main()");
}
function main() {
var ok=createDialog();
if (ok===2){
activeDocument.selection.deselect()
return false;
}
baseLayer=activeDocument.activeLayer;
activeDocument.quickMaskMode = false;
activeDocument.selection.deselect()
var layerName = activeDocument.activeLayer.name
//if a selection can't be made, stop running the script
var idCpTL = charIDToTypeID("CpTL");
executeAction(idCpTL, undefined, DialogModes.NO);
activeDocument.activeLayer.rasterize(RasterizeType.ENTIRELAYER)
try{
var idDlt = charIDToTypeID( "Dlt " );
var desc120 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref112 = new ActionReference();
var idChnl = charIDToTypeID( "Chnl" );
var idChnl = charIDToTypeID( "Chnl" );
var idMsk = charIDToTypeID( "Msk " );
ref112.putEnumerated( idChnl, idChnl, idMsk );
desc120.putReference( idnull, ref112 );
var idAply = charIDToTypeID( "Aply" );
desc120.putBoolean( idAply, true );
executeAction( idDlt, desc120, DialogModes.NO );
}catch(e){}
activeDocument.activeLayer.name = layerName
baseLayer=activeDocument.activeLayer
makeSelection()
var idMk = charIDToTypeID("Mk ");
var desc642 = new ActionDescriptor();
var idNw = charIDToTypeID("Nw ");
var idDcmn = charIDToTypeID("Dcmn");
desc642.putClass(idNw, idDcmn);
var idUsng = charIDToTypeID("Usng");
var ref535 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref535.putEnumerated(idChnl, idOrdn, idTrgt);
desc642.putReference(idUsng, ref535);
executeAction(idMk, desc642, DialogModes.NO);
newDoc = activeDocument
// =======================================================
activeDocument.resizeImage("200%", "200%", undefined, ResampleMethod.NEARESTNEIGHBOR)
// =======================================================
var idsetd = charIDToTypeID("setd");
var desc934 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref535 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref535.putProperty(idChnl, idfsel);
desc934.putReference(idnull, ref535);
var idT = charIDToTypeID("T ");
var ref536 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref536.putEnumerated(idChnl, idOrdn, idTrgt);
desc934.putReference(idT, ref536);
executeAction(idsetd, desc934, DialogModes.NO);
var idMk = charIDToTypeID("Mk ");
var desc403 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref288 = new ActionReference();
var idPath = charIDToTypeID("Path");
ref288.putClass(idPath);
desc403.putReference(idnull, ref288);
var idFrom = charIDToTypeID("From");
var ref289 = new ActionReference();
var idcsel = charIDToTypeID("csel");
var idfsel = charIDToTypeID("fsel");
var idfsel = charIDToTypeID("fsel");
ref289.putProperty(idcsel, idfsel);
desc403.putReference(idFrom, ref289);
var idTlrn = charIDToTypeID("Tlrn");
var idPxl = charIDToTypeID("#Pxl");
desc403.putUnitDouble(idTlrn, idPxl, 0.500000);
executeAction(idMk, desc403, DialogModes.NO);
var subPathsLength = activeDocument.pathItems[0].subPathItems.length
if (subPathsLength>confirmThreshold){
var answer = confirm("基于"+subPathsLength+ "个拆分对象将创建图层. 你想继续吗?",true)
if (answer === false){
newDoc.close(SaveOptions.DONOTSAVECHANGES);
activeDocument.quickMaskMode = false;
activeDocument.selection.deselect();
return 0;
}
}
// =======================================================
activeDocument.resizeImage("50%", "50%", undefined, ResampleMethod.NEARESTNEIGHBOR)
var pathInfo = collectPathInfoFromDesc(activeDocument, activeDocument.pathItems[activeDocument.pathItems.length - 1])
// =======================================================
newDoc.close(SaveOptions.DONOTSAVECHANGES)
// =======================================================
activeDocument.quickMaskMode = false
// =======================================================
//make channel
// =======================================================
var idMk = charIDToTypeID("Mk ");
var desc6 = new ActionDescriptor();
var idNw = charIDToTypeID("Nw ");
var desc7 = new ActionDescriptor();
var idNm = charIDToTypeID("Nm ");
desc7.putString(idNm, "ContiguityMask");
var idClrI = charIDToTypeID("ClrI");
var idMskI = charIDToTypeID("MskI");
var idMskA = charIDToTypeID("MskA");
desc7.putEnumerated(idClrI, idMskI, idMskA);
var idClr = charIDToTypeID("Clr ");
var desc8 = new ActionDescriptor();
var idRd = charIDToTypeID("Rd ");
desc8.putDouble(idRd, 255.000000);
var idGrn = charIDToTypeID("Grn ");
desc8.putDouble(idGrn, 0.000000);
var idBl = charIDToTypeID("Bl ");
desc8.putDouble(idBl, 0.000000);
var idRGBC = charIDToTypeID("RGBC");
desc7.putObject(idClr, idRGBC, desc8);
var idOpct = charIDToTypeID("Opct");
desc7.putInteger(idOpct, 50);
var idChnl = charIDToTypeID("Chnl");
desc6.putObject(idNw, idChnl, desc7);
var idUsng = charIDToTypeID("Usng");
var ref5 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref5.putProperty(idChnl, idfsel);
desc6.putReference(idUsng, ref5);
executeAction(idMk, desc6, DialogModes.NO);
var layerCount = 1
for (i = 0; i < subPathsLength; i++) {
//deselect
var idsetd = charIDToTypeID("setd");
var desc279 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref137 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref137.putProperty(idChnl, idfsel);
desc279.putReference(idnull, ref137);
var idT = charIDToTypeID("T ");
var idOrdn = charIDToTypeID("Ordn");
var idNone = charIDToTypeID("None");
desc279.putEnumerated(idT, idOrdn, idNone);
executeAction(idsetd, desc279, DialogModes.NO);
///select alpha channel
var idslct = charIDToTypeID("slct");
var desc315 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref175 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
ref175.putName(idChnl, "ContiguityMask");
desc315.putReference(idnull, ref175);
executeAction(idslct, desc315, DialogModes.NO);
//use magic wand
var idsetd = charIDToTypeID("setd");
var desc263 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref123 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref123.putProperty(idChnl, idfsel);
desc263.putReference(idnull, ref123);
var idT = charIDToTypeID("T ");
var desc264 = new ActionDescriptor();
var idHrzn = charIDToTypeID("Hrzn");
var idRlt = charIDToTypeID("#Rlt");
desc264.putUnitDouble(idHrzn, idRlt, pathInfo[i][0][0]);
var idVrtc = charIDToTypeID("Vrtc");
var idRlt = charIDToTypeID("#Rlt");
desc264.putUnitDouble(idVrtc, idRlt, pathInfo[i][0][1]);
var idPnt = charIDToTypeID("Pnt ");
desc263.putObject(idT, idPnt, desc264);
var idTlrn = charIDToTypeID("Tlrn");
desc263.putInteger(idTlrn, 1);
executeAction(idsetd, desc263, DialogModes.NO);
var idslct = charIDToTypeID("slct");
var desc346 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref205 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idRGB = charIDToTypeID("RGB ");
ref205.putEnumerated(idChnl, idChnl, idRGB);
desc346.putReference(idnull, ref205);
var idMkVs = charIDToTypeID("MkVs");
desc346.putBoolean(idMkVs, false);
executeAction(idslct, desc346, DialogModes.NO);
try {
// =======================================================
var idCpTL = charIDToTypeID("CpTL");
executeAction(idCpTL, undefined, DialogModes.NO);
try {
var idrasterizeLayer = stringIDToTypeID("rasterizeLayer");
var desc924 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref721 = new ActionReference();
var idLyr = charIDToTypeID("Lyr ");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref721.putEnumerated(idLyr, idOrdn, idTrgt);
desc924.putReference(idnull, ref721);
var idWhat = charIDToTypeID("What");
var idrasterizeItem = stringIDToTypeID("rasterizeItem");
var idvectorMask = stringIDToTypeID("vectorMask");
desc924.putEnumerated(idWhat, idrasterizeItem, idvectorMask);
executeAction(idrasterizeLayer, desc924, DialogModes.NO);
} catch (err) {}
try {
var idIntr = charIDToTypeID("Intr");
var desc864 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref658 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref658.putEnumerated(idChnl, idOrdn, idTrgt);
desc864.putReference(idnull, ref658);
var idWith = charIDToTypeID("With");
var ref659 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref659.putProperty(idChnl, idfsel);
desc864.putReference(idWith, ref659);
executeAction(idIntr, desc864, DialogModes.NO);
// =======================================================
var idDlt = charIDToTypeID("Dlt ");
var desc865 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref660 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref660.putEnumerated(idChnl, idOrdn, idTrgt);
desc865.putReference(idnull, ref660);
executeAction(idDlt, desc865, DialogModes.NO);
// =======================================================
var idMk = charIDToTypeID("Mk ");
var desc866 = new ActionDescriptor();
var idNw = charIDToTypeID("Nw ");
var idChnl = charIDToTypeID("Chnl");
desc866.putClass(idNw, idChnl);
var idAt = charIDToTypeID("At ");
var ref661 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idMsk = charIDToTypeID("Msk ");
ref661.putEnumerated(idChnl, idChnl, idMsk);
desc866.putReference(idAt, ref661);
var idUsng = charIDToTypeID("Usng");
var idUsrM = charIDToTypeID("UsrM");
var idRvlS = charIDToTypeID("RvlS");
desc866.putEnumerated(idUsng, idUsrM, idRvlS);
executeAction(idMk, desc866, DialogModes.NO);
} catch (err) {}
var finalSuffix=suffix;
if (addCount===true)finalSuffix += layerCount;
activeDocument.activeLayer.name = layerName + finalSuffix;
layerCount++;
activeDocument.activeLayer=baseLayer;
} catch (e) {}
}
var idsetd = charIDToTypeID("setd");
var desc1045 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref578 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref578.putProperty(idChnl, idfsel);
desc1045.putReference(idnull, ref578);
var idT = charIDToTypeID("T ");
var idOrdn = charIDToTypeID("Ordn");
var idNone = charIDToTypeID("None");
desc1045.putEnumerated(idT, idOrdn, idNone);
executeAction(idsetd, desc1045, DialogModes.NO);
// =======================================================
var idDlt = charIDToTypeID("Dlt ");
var desc694 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref323 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
ref323.putName(idChnl, "ContiguityMask");
desc694.putReference(idnull, ref323);
executeAction(idDlt, desc694, DialogModes.NO);
activeDocument.activeLayer.remove();
var idHd = charIDToTypeID("Hd ");
var desc736 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var list22 = new ActionList();
var ref541 = new ActionReference();
var idLyr = charIDToTypeID("Lyr ");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref541.putEnumerated(idLyr, idOrdn, idTrgt);
list22.putReference(ref541);
desc736.putList(idnull, list22);
executeAction(idHd, desc736, DialogModes.NO);
}
// pfaffenbichler and xbytor //
// at ps-scripts.com //
// created this function //
function collectPathInfoFromDesc(myDocument, thePath) {
var myDocument = app.activeDocument;
// based of functions from xbytors stdlib;
var ref = new ActionReference();
for (var l = 0; l < myDocument.pathItems.length; l++) {
var thisPath = myDocument.pathItems[l];
if (thisPath == thePath && thisPath.name == "Work Path") {
ref.putProperty(cTID("Path"), cTID("WrPt"));
};
if (thisPath == thePath && thisPath.name != "Work Path" && thisPath.kind != PathKind.VECTORMASK) {
ref.putIndex(cTID("Path"), l + 1);
};
if (thisPath == thePath && thisPath.kind == PathKind.VECTORMASK) {
var idPath = charIDToTypeID("Path");
var idPath = charIDToTypeID("Path");
var idvectorMask = stringIDToTypeID("vectorMask");
ref.putEnumerated(idPath, idPath, idvectorMask);
};
};
var desc = app.executeActionGet(ref);
var pname = desc.getString(cTID('PthN'));
// create new array;
var theArray = new Array;
var pathComponents = desc.getObjectValue(cTID("PthC")).getList(sTID('pathComponents'));
// for subpathitems;
for (var m = 0; m < pathComponents.count; m++) {
var listKey = pathComponents.getObjectValue(m).getList(sTID("subpathListKey"));
// for subpathitems count;
for (var n = 0; n < listKey.count; n++) {
var points = listKey.getObjectValue(n).getList(sTID('points'));
// get first point;
var anchorObj = points.getObjectValue(0).getObjectValue(sTID("anchor"));
var anchor = [anchorObj.getUnitDoubleValue(sTID('horizontal')), anchorObj.getUnitDoubleValue(sTID('vertical'))];
var thisPoint = [anchor];
theArray.push(thisPoint);
};
};
// by xbytor, thanks to him;
function cTID(s) {
return cTID[s] || cTID[s] = app.charIDToTypeID(s);
};
function sTID(s) {
return sTID[s] || sTID[s] = app.stringIDToTypeID(s);
};
// reset;
return theArray;
};
function makePreviewSelection(){
makeSelection()
app.refresh()
activeDocument.quickMaskMode = false;
}
function makeSelection(){
try{
var idsetd = charIDToTypeID("setd");
var desc922 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref529 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref529.putProperty(idChnl, idfsel);
desc922.putReference(idnull, ref529);
var idT = charIDToTypeID("T ");
var ref530 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idTrsp = charIDToTypeID("Trsp");
ref530.putEnumerated(idChnl, idChnl, idTrsp);
desc922.putReference(idT, ref530);
executeAction(idsetd, desc922, DialogModes.NO);
} catch (err) {
return false;
}
try {
var idIntr = charIDToTypeID("Intr");
var desc846 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref639 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idChnl = charIDToTypeID("Chnl");
var idMsk = charIDToTypeID("Msk ");
ref639.putEnumerated(idChnl, idChnl, idMsk);
desc846.putReference(idnull, ref639);
var idWith = charIDToTypeID("With");
var ref640 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref640.putProperty(idChnl, idfsel);
desc846.putReference(idWith, ref640);
executeAction(idIntr, desc846, DialogModes.NO);
} catch (err) {}
try {
// =======================================================
var idIntW = charIDToTypeID("IntW");
var desc787 = new ActionDescriptor();
var idnull = charIDToTypeID("null");
var ref572 = new ActionReference();
var idChnl = charIDToTypeID("Chnl");
var idfsel = charIDToTypeID("fsel");
ref572.putProperty(idChnl, idfsel);
desc787.putReference(idnull, ref572);
var idT = charIDToTypeID("T ");
var ref573 = new ActionReference();
var idPath = charIDToTypeID("Path");
var idPath = charIDToTypeID("Path");
var idvectorMask = stringIDToTypeID("vectorMask");
ref573.putEnumerated(idPath, idPath, idvectorMask);
var idLyr = charIDToTypeID("Lyr ");
var idOrdn = charIDToTypeID("Ordn");
var idTrgt = charIDToTypeID("Trgt");
ref573.putEnumerated(idLyr, idOrdn, idTrgt);
desc787.putReference(idT, ref573);
var idVrsn = charIDToTypeID("Vrsn");
desc787.putInteger(idVrsn, 1);
var idvectorMaskParams = stringIDToTypeID("vectorMaskParams");
desc787.putBoolean(idvectorMaskParams, true);
executeAction(idIntW, desc787, DialogModes.NO);
} catch (err) {}
if (tolerance >= 2) {
activeDocument.selection.expand(Math.floor(tolerance / 2))
}
activeDocument.quickMaskMode = true;
var idThrs = charIDToTypeID("Thrs");
var desc479 = new ActionDescriptor();
var idLvl = charIDToTypeID("Lvl ");
desc479.putInteger(idLvl, 1);
executeAction(idThrs, desc479, DialogModes.NO);
if (tolerance % 2 == 1) {
var idMtnB = charIDToTypeID("MtnB");
var desc213 = new ActionDescriptor();
var idAngl = charIDToTypeID("Angl");
desc213.putInteger(idAngl, 0);
var idDstn = charIDToTypeID("Dstn");
var idPxl = charIDToTypeID("#Pxl");
desc213.putUnitDouble(idDstn, idPxl, 1.000000);
executeAction(idMtnB, desc213, DialogModes.NO);
// =======================================================
var idMtnB = charIDToTypeID("MtnB");
var desc214 = new ActionDescriptor();
var idAngl = charIDToTypeID("Angl");
desc214.putInteger(idAngl, 90);
var idDstn = charIDToTypeID("Dstn");
var idPxl = charIDToTypeID("#Pxl");
desc214.putUnitDouble(idDstn, idPxl, 1.000000);
executeAction(idMtnB, desc214, DialogModes.NO);
// =======================================================
var idThrs = charIDToTypeID("Thrs");
var desc215 = new ActionDescriptor();
var idLvl = charIDToTypeID("Lvl ");
desc215.putInteger(idLvl, 1);
executeAction(idThrs, desc215, DialogModes.NO);
}
}
function createDialog(){
var dlg = new Window('dialog', 'PNG素材拆分图层');
dlg.alignChildren ='left';
dlg.gap = dlg.add('group')
dlg.gap.orientation= 'row';
dlg.gap.txt=dlg.gap.add('statictext', undefined,'间隙大于多少时拆分?');
dlg.gap.input=dlg.gap.add('edittext', undefined,tolerance);
dlg.gap.input.preferredSize = [20,20];
dlg.gap.txt2=dlg.gap.add('statictext', undefined,'像素');
dlg.gap.btnPreview= dlg.gap.add('button', undefined,'蒙版预览');
dlg.gap.btnPreview.preferredSize = [55,20]
dlg.naming = dlg.add('panel',undefined,'图层重命名')
dlg.naming.alignChildren ='left';
dlg.naming.suffix = dlg.naming.add('group')
dlg.naming.suffix.orientation= 'row';
dlg.naming.suffix.txt=dlg.naming.suffix.add('statictext', undefined,'后缀:');
dlg.naming.suffix.input=dlg.naming.suffix.add('edittext', undefined,suffix);
dlg.naming.suffix.input.preferredSize = [60,20];
dlg.naming.suffix.chkbox = dlg.naming.suffix.add('checkbox', undefined, '添加序号')
dlg.naming.suffix.chkbox.value=addCount;
dlg.naming.txtPreview = dlg.naming.add('statictext', undefined, layerNamePreview)
dlg.naming.txtPreview.preferredSize = [200,20];
dlg.btnPnl= dlg.add('group');
dlg.btnPnl.alignment ='right';
dlg.btnPnl.okBtn = dlg.btnPnl.add('button', undefined, '确定', {name:'ok'});
dlg.btnPnl.okBtn.active=true;
dlg.btnPnl.cancelBtn = dlg.btnPnl.add('button', undefined, '取消', {name:'cancel'});
dlg.naming.suffix.input.onChanging= function(){
layerNamePreview=activeDocument.activeLayer.name + dlg.naming.suffix.input.text
if (dlg.naming.suffix.chkbox.value === true){
layerNamePreview += "1"
}
dlg.naming.txtPreview.text =layerNamePreview
}
dlg.naming.suffix.chkbox.onClick = function(){
layerNamePreview=activeDocument.activeLayer.name + dlg.naming.suffix.input.text
if (dlg.naming.suffix.chkbox.value === true){
layerNamePreview += "1"
}
dlg.naming.txtPreview.text = layerNamePreview;
}
dlg.gap.input.onChanging = function() {
if (parseInt(dlg.gap.input.text) == 1){
dlg.gap.txt2.text = '像素'
}else{
dlg.gap.txt2.text = '像素'
}
tolerance = parseInt (dlg.gap.input.text)
}
dlg.gap.btnPreview.onClick = function() {
makePreviewSelection()
}
x=dlg.show();
tolerance = parseInt (dlg.gap.input.text)
suffix = dlg.naming.suffix.input.text
addCount=dlg.naming.suffix.chkbox.value
return x;
}

View File

@@ -1,6 +1,4 @@
dxf22_jscode = """ 
//PS //PS
function 模特换衣功能(){ function 模特换衣功能(){
@@ -104,6 +102,10 @@ var button5 = group4.add("button");
//~ sPath = decodeURI(File($.fileName).path);
//~ edittext1.text = sPath + "// -38.psd";
//~ edittext2.text = sPath + "/";
//~ edittext3.text = sPath + "/";
@@ -199,11 +201,11 @@ function main(模板路径,素材目录,导出目录)
if(cbox2.value) // if(cbox2.value) //
{ {
结构导出目录 = 导出目录+"/" + getRelativePath(decodeURI(File(scpsd_path).path), 素材目录); 结构导出目录 = 导出目录+"\\" + getRelativePath(decodeURI(File(scpsd_path).path), 素材目录);
if(Folder(结构导出目录).exists==false){Folder(结构导出目录).create();} if(Folder(结构导出目录).exists==false){Folder(结构导出目录).create();}
保存路径 = 结构导出目录+"/"+素材名+".jpg"; 保存路径 = 结构导出目录+"\\"+素材名+".jpg";
$.writeln(保存路径); $.writeln(保存路径);
if(cbox3.value) // if(cbox3.value) //
@@ -225,7 +227,7 @@ function main(模板路径,素材目录,导出目录)
} }
else else
{ {
保存JPG(导出目录+"/"+素材名+".jpg"); 保存JPG(导出目录+"\\"+素材名+".jpg");
} }
@@ -449,5 +451,3 @@ function 按切片导出图片(保存路径,替换词)
dialog.show(); dialog.show();
} }
"""

View File

@@ -0,0 +1,340 @@

var dialog = new Window("dialog");
dialog.text = "打印拼贴软件 使用前请看规则";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "S/O样自动连晒";
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "宽高设定cm:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var statictext2 = group2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "宽度";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.preferredSize.width = 60;
var statictext3 = group2.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "宽度";
var edittext2 = group2.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 60;
// GROUP3
// ======
var group3 = panel1.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext4 = group3.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "字体属性:";
var dropdown1_array = ["文件名"];
var dropdown1 = group3.add("dropdownlist", undefined, undefined, {name: "dropdown1", items: dropdown1_array});
dropdown1.selection = 0;
dropdown1.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel1.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
var statictext5 = group4.add("statictext", undefined, undefined, {name: "statictext5"});
statictext5.text = "字体方向:";
var dropdown2_array = ["以下对齐"];
var dropdown2 = group4.add("dropdownlist", undefined, undefined, {name: "dropdown2", items: dropdown2_array});
dropdown2.selection = 0;
dropdown2.preferredSize.width = 100;
// GROUP5
// ======
var group5 = panel1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
var statictext6 = group5.add("statictext", undefined, undefined, {name: "statictext6"});
statictext6.text = "字体大小:";
var edittext3 = group5.add('edittext {properties: {name: "edittext3"}}');
edittext3.text ="60"
edittext3.preferredSize.width = 60;
// PANEL1
// ======
var group6 = panel1.add("group", undefined, {name: "group6"});
group6.orientation = "row";
group6.alignChildren = ["left","center"];
group6.spacing = 10;
group6.margins = 0;
var statictext7 = group6.add("statictext", undefined, undefined, {name: "statictext7"});
statictext7.text = "文件路径:";
var button1 = group6.add("button", undefined, undefined, {name: "button1"});
button1.text = "输出文件夹目录";
button1.preferredSize.width = 100;
// GROUP1
// ======
var progressbar1 = group1.add("progressbar", undefined, undefined, {name: "progressbar1"});
progressbar1.maxvalue = 100;
progressbar1.value = 50;
progressbar1.preferredSize.width = 50;
progressbar1.preferredSize.height = 4;
// GROUP6
// ======
var group6 = dialog.add("group", undefined, {name: "group6"});
group6.orientation = "column";
group6.alignChildren = ["fill","top"];
group6.spacing = 10;
group6.margins = 0;
var ok = group6.add("button", undefined, undefined, {name: "ok"});
ok.text = "执行";
ok.alignment = ["center","top"];
var cancel = group6.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
cancel.alignment = ["fill","top"];
var button3 = group6.add("button", undefined, undefined, {name: "button3"});
button3.text = "关于";
button3.alignment = ["right","top"];
var button4 = group6.add("button", undefined, undefined, {name: "button4"});
button4.text = "规则";
button4.alignment = ["right","top"];
button1.onClick=function(){
button1.text =Folder.selectDialog ("输出文件夹目录").fsName;
}
button3.onClick = function () {
alert("by jimi 2022.8.9 裁片自动排版软件18620223577",dialog.text+"----关于");
}
button4.onClick = function () {
alert("须在D盘建立一个名为*输入*的文件夹",dialog.text+"----使用须知");
}
//处理函数
ok .onClick=function(){
//~ //~ if (myEditText!="") {
var myFolder=new Folder(button1.text)
//打开文件夹中的每一个文件开始处理
var myFiles=myFolder.getFiles ("*.tif*")
for(i=0 ;i< myFiles.length;i++){
//开始处理每一个文件
//断判是文件格式
if(myFiles[i] instanceof File)
app.open(myFiles[i])
var document =app.activeDocument
//合并图层
document.flatten()
//预设图案
var 当前文档名称=activeDocument.name
var idMk = charIDToTypeID( "Mk " );
var desc14 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref3 = new ActionReference();
var idPtrn = charIDToTypeID( "Ptrn" );
ref3.putClass( idPtrn );
desc14.putReference( idnull, ref3 );
var idUsng = charIDToTypeID( "Usng" );
var ref4 = new ActionReference();
var idPrpr = charIDToTypeID( "Prpr" );
var idfsel = charIDToTypeID( "fsel" );
ref4.putProperty( idPrpr, idfsel );
var idDcmn = charIDToTypeID( "Dcmn" );
var idOrdn = charIDToTypeID( "Ordn" );
var idTrgt = charIDToTypeID( "Trgt" );
ref4.putEnumerated( idDcmn, idOrdn, idTrgt );
desc14.putReference( idUsng, ref4 );
var idNm = charIDToTypeID( "Nm " );
desc14.putString( idNm, """当前文档名称""" );
executeAction( idMk, desc14, DialogModes.NO );
//
//重设宽高
//记录宽高以调整宽高
//-.../-.--/-.-./..-./..../-.--
var sWidth=Number (edittext1.text); //宽;
var sHeight=Number (edittext2.text); //宽;
var fontsz=Number (edittext3.text); //宽;
//将图片裁切成指定宽高
app.activeDocument.crop([UnitValue("0px"),UnitValue("0px"),UnitValue(sWidth,"cm"),UnitValue(sHeight,"cm")]);
//填充当前文档图案
var idMk = charIDToTypeID( "Mk " );
var desc111 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref20 = new ActionReference();
var idcontentLayer = stringIDToTypeID( "contentLayer" );
ref20.putClass( idcontentLayer );
desc111.putReference( idnull, ref20 );
var idUsng = charIDToTypeID( "Usng" );
var desc112 = new ActionDescriptor();
var idType = charIDToTypeID( "Type" );
var desc113 = new ActionDescriptor();
var idPtrn = charIDToTypeID( "Ptrn" );
var desc114 = new ActionDescriptor();
var idNm = charIDToTypeID( "Nm " );
desc114.putString( idNm, """当前文档名称""" );
var idIdnt = charIDToTypeID( "Idnt" );
desc114.putString( idIdnt, """当前文档名称""" );
var idPtrn = charIDToTypeID( "Ptrn" );
desc113.putObject( idPtrn, idPtrn, desc114 );
var idpatternLayer = stringIDToTypeID( "patternLayer" );
desc112.putObject( idType, idpatternLayer, desc113 );
var idcontentLayer = stringIDToTypeID( "contentLayer" );
desc111.putObject( idUsng, idcontentLayer, desc112 );
executeAction( idMk, desc111, DialogModes.NO );
//记录字体大小并修改
//r textsz= Number(dialog.group5.statictext6.edittext3.text); //宽;
//新建图层
activeDocument.artLayers.add()
//将新建图层变成文本图层
activeDocument.activeLayer.kind=LayerKind.TEXT
//将文本内容改为当前文档name
activeDocument.activeLayer.textItem.contents=activeDocument.name
//字体大小
activeDocument.activeLayer.textItem.size= fontsz
//文字字体
activeDocument.activeLayer.textItem.font="微软雅黑"
//文字层边界
x=activeDocument.width-activeDocument.activeLayer.bounds[2]
//文字层边界
y=activeDocument.activeLayer.bounds[1]
//对齐
activeDocument.activeLayer.translate(x,-y)
//偏移
activeDocument.activeLayer.translate(UnitValue("-1cm"),UnitValue("+0.5cm"))
//电白
var myCopyLayer=activeDocument.activeLayer.duplicate (activeDocument.activeLayer,ElementPlacement.PLACEAFTER)
myCopyLayer.rasterize(RasterizeType.ENTIRELAYER)
//T填充颜色参数
var myColor=new SolidColor()
myColor.rgb.red=255
myColor.rgb.green=255
myColor.rgb.blue=255
activeDocument.activeLayer=myCopyLayer
fillType=myColor
mode=ColorBlendMode.NORMAL
oacity=100
preserveTransparency=true
activeDocument.selection.fill(fillType,mode,oacity,preserveTransparency)
//白边
activeDocument.activeLayer.applyMinimum(10)
document.flatten()
//保存输出
//r CSyFolder=File(button2.text)
saveIn=File("d:/输入/"+activeDocument.name);
tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
asCopy=true
app.activeDocument.saveAs(saveIn,tifSaveOpt,asCopy);
activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
dialog.show();

View File

@@ -0,0 +1,10 @@
function 执行动作(actionName, actionSetName) {
try {
app.doAction(actionName, actionSetName);
} catch (e) {
alert("动作调用出错: " + e.message);
}
}
// 使用示例:
执行动作("某个动作名称", "某个动作集名称");

View File

@@ -0,0 +1,11 @@
function offset_47921752929688() //位移
{
var d = new ActionDescriptor();
d.putInteger(stringIDToTypeID("horizontal"), 300);
d.putInteger(stringIDToTypeID("vertical"), 300);
d.putEnumerated(stringIDToTypeID("fill"), stringIDToTypeID("fillMode"), stringIDToTypeID("wrap"));
executeAction(stringIDToTypeID("offset"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,62 @@

function 历史记录保留() {
app.activeDocument.suspendHistory("历史记录保留", "历史记录保留()");
}
function 历史记录保留() {
主文档 = app.activeDocument;
主文档名称 = 主文档.name;
// 遍历当前打开的文档
for (var i = 0; i < app.documents.length; i++) {
var document = app.documents[i];
var documentName = document.name;
// 判断文档名称是否与主文档名称不相同
if (documentName !== 主文档名称) {
// 设置当前文档为活动文档
app.activeDocument = document;
取消选择()
历史记录快照保留()
}
}
app.activeDocument = 主文档
}
function 历史记录快照保留() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("snapshotClass"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("historyState"), stringIDToTypeID("currentHistoryState"));
d.putReference(stringIDToTypeID("from"), r1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 取消选择() //取消选择
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("to"), stringIDToTypeID("ordinal"), stringIDToTypeID("none"));
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,142 @@

function 关闭事件监听(文件路径) {
try
{
arg_num = arguments.length;
}
catch(e)
{
arguments = []; //初始赋值为空
}
eventName = "set";
//开启监听
//enable_notifier(eventName,文件路径);
//
//取消监听
disable_notifier(eventName,文件路径);
//这里进行监控调用事件
if (arguments.length >= 2)
{
//alert(arguments.length);
//~ alert(arguments[0],"动作参数1"); //动作描述符 AR
//~ alert(arguments[1],"动作参数2"); //动作事件ID
moveToGuideCenter();
//main(arguments[0], arguments[1]);
}
function moveToGuideCenter() {
if (app.documents.length === 0 || !app.activeDocument.selection.bounds) {
alert("没有打开的文档或没有有效的选区。");
return;
}
var doc = app.activeDocument;
var guides = doc.guides;
var xGuide = null;
var yGuide = null;
// 找到 X 方向和 Y 方向上的参考线
for (var i = 0; i < guides.length; i++) {
var guide = guides[i];
if (guide.direction === Direction.HORIZONTAL && yGuide === null) {
yGuide = guide;
} else if (guide.direction === Direction.VERTICAL && xGuide === null) {
xGuide = guide;
}
}
if (xGuide === null || yGuide === null) {
alert("必须确保存在一条水平和一条垂直参考线。");
return;
}
var intersectionX = xGuide.coordinate;
var intersectionY = yGuide.coordinate;
var bounds = doc.selection.bounds;
var xCenter = bounds[0] + (bounds[2] - bounds[0]) / 2;
var yCenter = bounds[1] + (bounds[3] - bounds[1]) / 2;
var deltaX = intersectionX - xCenter;
var deltaY = intersectionY - yCenter;
// 取消当前选区
doc.selection.deselect();
// 移动选区图层
var selectedLayer = doc.activeLayer;
selectedLayer.translate(deltaX, deltaY);
move_60275268554688()
}
function move_60275268554688() // һ
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function enable_notifier(event_name, script_name, event_class)
{
try
{
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
if (!app.notifiersEnabled) app.notifiersEnabled = true;
return true;
}
}
app.notifiers.add(event_name, File(script_name), event_class);
app.notifiersEnabled = true;
return true;
}
catch (e) { _alert(e); return false; }
}
function disable_notifier(event_name, script_name, event_class)
{
try
{
var ret = false;
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
app.notifiers[i].remove();
ret = true;
}
}
if (!app.notifiers.length) app.notifiersEnabled = false;
return ret;
}
catch (e) { _alert(e); return false; }
}
}

View File

@@ -0,0 +1,37 @@
app.preferences.rulerUnits = Units.MM;
var doc = app.activeDocument;
var 扩展毫米数 = 80;
// 获取文档中的所有图层
var allLayers = doc.layers;
// 创建一个数组来存储子图层的名称
var 子图层名称数组 = [];
// 循环遍历所有图层
for (var i = 0; i < allLayers.length; i++) {
// 检查图层是否是图层组
if (allLayers[i] instanceof LayerSet && allLayers[i].name === "填充底图") {
// 获取图层组中的所有子图层
var subLayers = allLayers[i].layers;
// 将子图层的名称添加到数组中,并在名称后面加上"-填充底图"
for (var j = 0; j < subLayers.length; j++) {
子图层名称数组.push('"' + subLayers[j].name + '-填充底图"');
}
// 手动创建JSON格式的字符串
var jsonStr = '[' + 子图层名称数组.join(', ') + ']';
// 创建一个文件对象指向桌面
var desktop = Folder.desktop;
var file = new File(desktop + "/子图层名称.json");
// 打开文件写入JSON字符串然后关闭文件
file.open('w');
file.write(jsonStr);
file.close();
alert("子图层名称已保存到桌面的JSON文件中");
}
}

View File

@@ -0,0 +1,12 @@
function switchToDocument(docName) {
var docs = app.documents;
for (var i = 0; i < docs.length; i++) {
if (docs[i].name == docName) {
app.activeDocument = docs[i];
break;
}
}
}
// 使用示例
switchToDocument("xxx.psd");

View File

@@ -0,0 +1,42 @@

function 删除图层3()
{
app.activeDocument.suspendHistory("delete layer", "删除图层2()");
}
function 删除图层2()
{
图层选择()
删除图层()
}
function 图层选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "图层 2");
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(3);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
///////////////////////////////////////////////////////////////////////////////
function 删除图层() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var list = new ActionList();
list.putInteger(3);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,31 @@
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
// 调用函数
缩小字体图层至文档一半();

View File

@@ -0,0 +1,189 @@
创建并处理文本图层();
function 创建并处理文本图层() {
// 新建图层
var textLayer = activeDocument.artLayers.add();
// 将新建图层变成文本图层
textLayer.kind = LayerKind.TEXT;
// 将文本内容改为当前文档name
textLayer.textItem.contents = activeDocument.name;
// 字体大小固定值
var 固定字体大小 = 30; // 例如30像素
textLayer.textItem.size = 固定字体大小;
// 文字字体固定值
textLayer.textItem.font = "微软雅黑";
// 计算并调整文本位置
var x = activeDocument.width - textLayer.bounds[2];
var y = textLayer.bounds[1];
textLayer.translate(x, -y);
// 偏移
textLayer.translate(UnitValue("-1cm"), UnitValue("+0.5cm"));
// 复制并栅格化图层
var copyLayer = textLayer.duplicate();
copyLayer.rasterize(RasterizeType.ENTIRELAYER);
// 设置固定颜色值
var 固定颜色 = new SolidColor();
固定颜色.rgb.red = 255; // 红色分量
固定颜色.rgb.green = 255; // 绿色分量
固定颜色.rgb.blue = 255; // 蓝色分量
activeDocument.activeLayer = copyLayer;
activeDocument.selection.fill(固定颜色, ColorBlendMode.NORMAL, 100, true);
// 白边
activeDocument.activeLayer.applyMinimum(10);
后移一层()
向上选择()
向下合并()
名称更改字体()
缩小字体图层至文档一半()
多选背景()
底对齐()
水平居中对齐()
}
// 调用函数
function 后移一层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 向上选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(19);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 名称更改字体() //名称更改
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("name"), "字体");
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
function 多选背景() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "背景");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(1);
list.putInteger(13);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,124 @@
function 取消事件监听() {
try
{
arg_num = arguments.length;
}
catch(e)
{
arguments = []; //初始赋值为空
}
eventName = "set";
//开启监听
//enable_notifier(eventName, $.fileName);
//取消监听
disable_notifier(eventName, $.fileName);
//这里进行监控调用事件
if (arguments.length >= 2)
{
//alert(arguments.length);
//~ alert(arguments[0],"动作参数1"); //动作描述符 AR
//~ alert(arguments[1],"动作参数2"); //动作事件ID
//alert("事件名:"+typeIDToStringID(arguments[1])+"\n"+"事件ID:"+arguments[1],"提示:");
moveToGuideCenter();
//main(arguments[0], arguments[1]);
}
function moveToGuideCenter() {
if (app.documents.length === 0 || !app.activeDocument.selection.bounds) {
alert("没有打开的文档或没有有效的选区。");
return;
}
var doc = app.activeDocument;
var guides = doc.guides;
var xGuide = null;
var yGuide = null;
// 找到 X 方向和 Y 方向上的参考线
for (var i = 0; i < guides.length; i++) {
var guide = guides[i];
if (guide.direction === Direction.HORIZONTAL && yGuide === null) {
yGuide = guide;
} else if (guide.direction === Direction.VERTICAL && xGuide === null) {
xGuide = guide;
}
}
if (xGuide === null || yGuide === null) {
alert("必须确保存在一条水平和一条垂直参考线。");
return;
}
var intersectionX = xGuide.coordinate;
var intersectionY = yGuide.coordinate;
var bounds = doc.selection.bounds;
var xCenter = bounds[0] + (bounds[2] - bounds[0]) / 2;
var yCenter = bounds[1] + (bounds[3] - bounds[1]) / 2;
var deltaX = intersectionX - xCenter;
var deltaY = intersectionY - yCenter;
// 取消当前选区
doc.selection.deselect();
// 移动选区图层
var selectedLayer = doc.activeLayer;
selectedLayer.translate(deltaX, deltaY);
}
function enable_notifier(event_name, script_name, event_class)
{
try
{
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
if (!app.notifiersEnabled) app.notifiersEnabled = true;
return true;
}
}
app.notifiers.add(event_name, File(script_name), event_class);
app.notifiersEnabled = true;
return true;
}
catch (e) { _alert(e); return false; }
}
function disable_notifier(event_name, script_name, event_class)
{
try
{
var ret = false;
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
app.notifiers[i].remove();
ret = true;
}
}
if (!app.notifiers.length) app.notifiersEnabled = false;
return ret;
}
catch (e) { _alert(e); return false; }
}
}

View File

@@ -0,0 +1,15 @@
function 图层选择(sizelayername)
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), sizelayername);
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(10);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,62 @@
var desktop = Folder.desktop;
var file = new File(desktop + "/名称数据.json");
if (file.open('r')) {
var content = file.read();
file.close();
// 移除双引号
var content1 = content.replace(/"/g, '');
// 移除[]并使用逗号分割字符串
var 名称数组 = content1.replace(/^\s*\[|\]\s*$/g, '').split(',');
for (var k = 0; k < 名称数组.length; k++) {
var 名称 = 名称数组[k].replace(/^\s+|\s+$/g, ''); // 使用正则替代trim函数移除前后空格
alert(名称);
var matches = 名称.match(/\(([^)]+)\)/);
if (matches) {
var 图案名称 = matches[1];
var 素材填充 = app.activeDocument.layerSets.getByName("填充底图").layers.getByName(图案名称);
app.activeDocument.activeLayer = 素材填充;
载入选区();
填充图案(名称);
}
}
} else {
alert("找不到名称数据文件查看是否写入!");
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 填充图案(名称) //新建图案填充图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,24 @@
function saveAsPSD(saveFolder) {
var doc = app.activeDocument; // 获取当前文档
if (doc == null) {
alert("没有打开的文档!");
return;
}
var fileName = doc.name.replace(/\.[^\.]+$/, ''); // 移除文件扩展名
var saveFile = File(saveFolder + "/" + fileName + ".psd"); // 创建保存路径
var d = new ActionDescriptor();
var d1 = new ActionDescriptor();
d1.putBoolean(stringIDToTypeID("maximizeCompatibility"), true);
d.putObject(stringIDToTypeID("as"), stringIDToTypeID("photoshop35Format"), d1);
d.putPath(stringIDToTypeID("in"), new File(saveFile));
d.putInteger(stringIDToTypeID("documentID"), 2027);
d.putBoolean(stringIDToTypeID("lowerCase"), true);
d.putEnumerated(stringIDToTypeID("saveStage"), stringIDToTypeID("saveStageType"), stringIDToTypeID("saveSucceeded"));
executeAction(stringIDToTypeID("save"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,996 @@
function 定位码批量化替换外链新(){
var dialog = new Window("dialog");
dialog.text = "定位码批量快速换图 ";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.preferredSize.width = 183;
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "文件夹选择";
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "大货齐码裁片模板路径:";
var button1 = panel1.add("button", undefined, undefined, {name: "button1"});
button1.text = "路径选择";
button1.preferredSize.width = 300;
var statictext2 = panel1.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "待套花样路径选择:";
var button2 = panel1.add("button", undefined, undefined, {name: "button2"});
button2.text = "路径选择";
button2.preferredSize.width = 300;
var statictext3 = panel1.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "缓存切片裁片路径选择:";
var button3 = panel1.add("button", undefined, undefined, {name: "button3"});
button3.text = "路径选择";
button3.preferredSize.width = 300;
var statictext4= panel1.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "大货成品路径选择:";
var button4 = panel1.add("button", undefined, undefined, {name: "button4"});
button4.text = "路径选择";
button4.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = panel1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "款号修改:";
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
// PANEL4
// ======
var statictext11 = panel2.add("statictext", undefined, undefined, {name: "statictext11"});
statictext11.text = "是否拼合裁片组:";
var checkbox1 =panel2.add("checkbox", undefined, undefined, {name: "checkbox1"});
checkbox1.value = true;
checkbox1.text = "拼合";
var statictext13 = panel2.add("statictext", undefined, undefined, {name: "statictext13"});
statictext13.text = "存储位置:";
var checkbox3 =panel2.add("checkbox", undefined, undefined, {name: "checkbox3"});
checkbox3.value = false;
checkbox3.text = "模板位置";
// GROUP2
// ======
var group2 = dialog.add("group", undefined, {name: "group2"});
group2.orientation = "column";
group2.alignChildren = ["fill","top"];
group2.spacing = 10;
group2.margins = 0;
var ok = group2.add("button", undefined, undefined, {name: "ok"});
ok.text = "执行";
var cancel = group2.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
var button8 = group2.add("button", undefined, undefined, {name: "button8"});
button8.text = "关于我们";
button1.onClick=function(){
button1.text =Folder.selectDialog ("大货齐码裁片模板路径:").fsName;
}
button2.onClick=function(){
button2.text =Folder.selectDialog ("待套花样路径选择:").fsName;
}
button3.onClick=function(){
button3.text =Folder.selectDialog ("缓存切片裁片路径选择:").fsName;
}
button4.onClick=function(){
button4.text =Folder.selectDialog ("大货成品路径选择:").fsName;
}
button8.onClick = function () {
alert("自由花型工作室 17520145271 脚本开发 裁片排版 花型开发 ",dialog.text+"----关于我们");
}
ok .onClick=function(){
var 大货齐码裁片模板路径 = new Folder(button1.text);
var 待套花样路径选择 =new Folder(button2.text);
var 大货文件存放位置 =new Folder(button4.text);
var 缓存切片裁片路径选择 = new Folder(button3.text);
var myFiles1 =大货齐码裁片模板路径.getFiles("*.tif*");
var myFiles2 =待套花样路径选择.getFiles("*.tif*");
for (var i = 0; i <myFiles2.length; i++)
{
app.open(myFiles2[i])
文档名称1=activeDocument.name.replace(/(?:\.[^.]*$|$)/, '');
var 大货成品文件夹 =文档名称1
//花样标准化(80);
// 文档另存(缓存切片裁片路径选择);
花样图层导出为TIF透明底(缓存切片裁片路径选择)
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES)
var parentFolderPath =大货文件存放位置
var folderNames = 文档名称1
var folderPath = parentFolderPath + "/" + 文档名称1;
var folderObj = new Folder(folderPath);
$.writeln(folderObj);
folderObj.create();
for (var j = 0; j <myFiles1.length; j++) {
app.open(myFiles1[j])
更换当前文档裁片组外链(缓存切片裁片路径选择);
// 图层选择();
// app.activeDocument.activeLayer.textItem.contents = 文档名称1;
选择裁片图层();
// if (checkbox1.value == true) {
合并图层();
预设图案填充(缓存切片裁片路径选择)
另存为(folderObj,文档名称1)
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
function 预设图案填充(缓存切片裁片路径选择)
{
var file = new File(缓存切片裁片路径选择 + "/名称数据.json");
if (file.open('r')) {
var content = file.read();
file.close();
// 移除双引号
var content1 = content.replace(/"/g, '');
// 移除[]并使用逗号分割字符串
var 名称数组 = content1.replace(/^\s*\[|\]\s*$/g, '').split(',');
for (var k = 0; k < 名称数组.length; k++) {
var 名称 = 名称数组[k].replace(/^\s+|\s+$/g, ''); // 使用正则替代trim函数移除前后空格
//alert(名称);
var matches = 名称.match(/\(([^)]+)\)/);
if (matches) {
var 图案名称 = matches[1];
var 素材填充 = app.activeDocument.layerSets.getByName("填充底图").layers.getByName(图案名称);
app.activeDocument.activeLayer = 素材填充;
载入选区();
填充图案(名称);
}
}
} else {
alert("找不到名称数据文件查看是否写入");
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 填充图案(名称) //新建图案填充图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 填充图案(图案名称) //新建图案填充图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 图案名称);
d3.putString(stringIDToTypeID("ID"), 图案名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 花样图层导出为TIF透明底(导出目录) {
app.preferences.rulerUnits = Units.MM;
var doc = app.activeDocument;
var 扩展毫米数=80
// 获取文档中的所有图层
var allLayers = doc.layers;
var 名称数组 = [];
// 循环遍历所有图层
for (var i = 0; i < allLayers.length; i++) {
// 检查图层是否是图层组
if (allLayers[i] instanceof LayerSet) {
// 获取图层组中的所有子图层
subLayers = allLayers[i].layers;
图层组名称=allLayers[i].name
文档名称=app.activeDocument.name
文档名称去除后缀 = 文档名称.replace(/\.[^\.]+$/, "");
// 检查图层组是否包含子图层
if (subLayers.length > 0) {
// 获取图层组中最后一个子图层的名称
var lastSubLayer = subLayers[subLayers.length - 1];
var lastSubLayerName = lastSubLayer.name;
FastSubLayer = subLayers[0];
FastSubLayername= FastSubLayer.name
// 输出图层组名称和最后一个子图层的名称
if (图层组名称 === "填充底图") {
for (var y = 0; y < subLayers.length; y++) {
// alert(subLayers[j].name);
填充底图组内部循环名称 = subLayers[y].name
填充底图循环素材 = app.activeDocument.layerSets.getByName("填充底图").layers.getByName(填充底图组内部循环名称);
app.activeDocument.activeLayer = 填充底图循环素材;
新建文档()
// 合并图层()
载入选区()
裁剪()
名称=文档名称去除后缀+"-("+填充底图组内部循环名称 +")-填充底图"
名称数组.push('"' + 名称 + '"');
制作图案预设(名称)
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
// 执行某一个操作(例如,设置图层组的可见性)
// alert("测试")
}
// 手动创建JSON格式的字符串
var jsonStr = '[' + 名称数组.join(', ') + ']';
// 创建一个文件对象指向桌面
var file = new File(导出目录 + "/名称数据.json");
// 打开文件写入JSON字符串然后关闭文件
file.open('w');
file.write(jsonStr);
file.close();
} else {
// 执行别的操作(例如,隐藏其他图层组)
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(lastSubLayerName);
app.activeDocument.activeLayer = 空白裁片模板;
新建图层()
app.activeDocument.activeLayer.name="最大白边值"
裁片边界 = lastSubLayer.bounds;
扩展值 = 毫米转像素(扩展毫米数); //50cm
裁片边界_左 = 毫米转像素(裁片边界[0]) - 扩展值;
裁片边界_上 = 毫米转像素(裁片边界[1]) - 扩展值;
裁片边界_右 = 毫米转像素(裁片边界[2]) + 扩展值;
裁片边界_下 = 毫米转像素(裁片边界[3]) + 扩展值;
var selRegion = [
[裁片边界_左,裁片边界_上],
[裁片边界_右,裁片边界_上],
[裁片边界_右,裁片边界_下],
[裁片边界_左,裁片边界_下]
];
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
背景切换();
恢复白底();
填充();
隐藏图层();
裁片图层组 = app.activeDocument.layerSets.getByName(图层组名称)
app.activeDocument.activeLayer = 裁片图层组 ;
新建文档()
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(lastSubLayerName);
app.activeDocument.activeLayer = 空白裁片模板;
删除图层()
最大白边值=app.activeDocument.layerSets.getByName(图层组名称).layers.getByName("最大白边值");
app.activeDocument.activeLayer = 最大白边值;
app.activeDocument.crop(最大白边值.bounds, 0);
// 保存为TIF
var 文件路径 = 导出目录 + "/" + 图层组名称 + ".tif";
tiffOptions = new TiffSaveOptions();
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
最大白边值=app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(FastSubLayername);
app.activeDocument.activeLayer = 最大白边值;
//上移图层()
多选图层()
转换为智能对象()
添加图层蒙版()
// 向下合并()
转换为智能对象()
// 重新链接到文件(文件路径)
}
}
}
}
}
function 恢复白底() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("exchange"), d, DialogModes.NO);
}
function 制作图案预设(名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 合并图层() //
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 背景切换() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 裁剪() //裁剪
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("delete"), true);
executeAction(stringIDToTypeID("crop"), d, DialogModes.NO);
}
function 重新链接到文件(文件路径) //重新链接到文件
{
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(文件路径));
d.putInteger(stringIDToTypeID("layerID"), 94);
executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);
}
function 转换为智能对象() //转换为智能对象
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("newPlacedLayer"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 上移图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(11);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 毫米转像素(毫米)
{
//厘米转像素
doc_w = app.activeDocument.width;
//用户设定的厘米数 支持小数
user_mm = UnitValue(毫米,"mm");
user_px = user_mm.as("px")*app.activeDocument.resolution/72;
return user_px;
}
function 删除图层() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var list = new ActionList();
list.putInteger(22);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 多选图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "最大白边值");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelectionContinuous"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(115);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putBoolean(stringIDToTypeID("group"), true);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("layer"), d1);
d.putInteger(stringIDToTypeID("layerID"), 22);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 隐藏图层() //
{
var d = new ActionDescriptor();
var list = new ActionList();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
list.putReference(r);
d.putList(stringIDToTypeID("null"), list);
executeAction(stringIDToTypeID("hide"), d, DialogModes.NO);
}
function 新建文档() //复制图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("document"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putInteger(stringIDToTypeID("version"), 5);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 更换当前文档裁片组外链(缓存切片裁片路径选择)
{
try
{
裁片组 = app.activeDocument.layerSets.getByName("裁片").layers;
}
catch(e)
{
alert("找不到裁片组");
}
for(var k=0;k<裁片组.length;k++)
{
裁片 = 裁片组[k];
app.activeDocument.activeLayer = 裁片;
if(裁片.kind == LayerKind.SMARTOBJECT)
{
更换链接智能对象路径(缓存切片裁片路径选择);
}
}
}
function 更换链接智能对象路径(缓存切片裁片路径选择)
{
//获取当前图层外链的智能对象路径
//先获取链接的文件名
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
//~ r.putName(charIDToTypeID("Lyr "), "左袖口"); //按名称查找
descLayer = executeActionGet(r);
res = descLayer.getObjectValue(stringIDToTypeID("smartObject"));
链接文件名 = res.getString(stringIDToTypeID("fileReference"));
//$.writeln(链接文件名);
//~ 链接文件路径 = res.getPath(stringIDToTypeID("link"));
//~ $.writeln(链接文件路径);
图片路径 = 缓存切片裁片路径选择 + "/" + 链接文件名;
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(图片路径));
executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);
}
function 另存为(folderObj,文档名称1)
{
文档名称=activeDocument.name.replace(/(?:\.[^.]*$|$)/, '');
saveIn=File(folderObj+ "/"+文档名称1+"-"+文档名称);
tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
asCopy=true
app.activeDocument.saveAs(saveIn,tifSaveOpt,asCopy);
}
function 图层选择() //a
{
try {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "款号");
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(74);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
catch (e) {
//alert("找不到款号图层",dialog.text+"----关于");
}
}
///////////////////////////////////////////////////////////////////////////////
function 合并图层() //合并图层
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 选择裁片图层() //
{
try {
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "裁片");
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(74);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
catch (e) {
alert("找不到裁片图层",dialog.text+"----关于");
}
}
function 存储在原来位置(myFolder)
{
tiffOptions = new TiffSaveOptions();
try
{
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
}
catch(e)
{
alert(文件太大无法保存)
}
}
function 花样标准化(扩展毫米数)
{
var 扩展毫米数=80
app.preferences.rulerUnits = Units.MM;
裁片组 = app.activeDocument.layerSets;
for(var z=0;z<裁片组.length;z++)
{
当前裁片组 = 裁片组[z];
花样图层 = 当前裁片组.layers[0];
裁片图层 = 当前裁片组.layers[1];
裁片边界 = 裁片图层.bounds;
//~ alert(毫米转像素(50))
//~ 扩展值 = 毫米转像素(50); //50cm
扩展值 = 毫米转像素(扩展毫米数); //50cm
裁片边界_左 = 毫米转像素(裁片边界[0]) - 扩展值;
裁片边界_上 = 毫米转像素(裁片边界[1]) - 扩展值;
裁片边界_右 = 毫米转像素(裁片边界[2]) + 扩展值;
裁片边界_下 = 毫米转像素(裁片边界[3]) + 扩展值;
//左上右下点XY坐标
var selRegion = [
[裁片边界_左,裁片边界_上],
[裁片边界_右,裁片边界_上],
[裁片边界_右,裁片边界_下],
[裁片边界_左,裁片边界_下]
];
app.activeDocument.activeLayer = 花样图层;
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
按选区添加蒙版();
//制作一个白底衬底图
//新建一个图层
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 198);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
白底图层 = app.activeDocument.activeLayer;
白底图层.name = "白底";
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
var c = new SolidColor();
c.rgb.hexValue = "FFFFFF";
app.activeDocument.selection.fill(c);
花样图层.grouped = false;
白底图层.move(花样图层,ElementPlacement.PLACEAFTER);
app.activeDocument.activeLayer = 花样图层;
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "白底");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelectionContinuous"));
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
app.activeDocument.activeLayer.merge(); //合并当前选择图层
app.activeDocument.activeLayer.grouped = true;
}
function 毫米转像素(毫米)
{
//厘米转像素
doc_w = app.activeDocument.width;
//用户设定的厘米数 支持小数
user_mm = UnitValue(毫米,"mm");
user_px = user_mm.as("px")*app.activeDocument.resolution/72;
return user_px;
}
function 按选区添加蒙版() //先创建出选区 然后按选区添加出一个蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
}
function 文档另存(缓存切片裁片路径选择){
var 导出目录 =缓存切片裁片路径选择;
var 裁片组 = app.activeDocument.layerSets;
for(var i=0;i<裁片组.length;i++)
{
app.activeDocument.duplicate("temp");
复制文档裁片组 = app.activeDocument.layerSets;
当前裁片组 = 复制文档裁片组[i];
当前裁片组名 = 当前裁片组.name;
花样图层 = 当前裁片组.layers[0];
裁片图层 = 当前裁片组.layers[1];
app.activeDocument.activeLayer = 花样图层;
//把花样图层导出
花样图层.grouped = false; //取消图层链接
仅当前图层可见();
//按花样图层大小裁剪文档
app.activeDocument.crop(花样图层.bounds,0);
//拼合图像只保留花样图层
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("flattenImage"), d, DialogModes.NO);
//保存为TIF
var 文件路径 = 导出目录 + "/" + 当前裁片组名 + ".tif";
tiffOptions = new TiffSaveOptions();
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
function 仅当前图层可见()
{
var d = new ActionDescriptor();
var list = new ActionList();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
list.putReference(r);
d.putList(stringIDToTypeID("null"), list);
d.putBoolean(stringIDToTypeID("toggleOptionsPalette"), true);
executeAction(stringIDToTypeID("show"), d, DialogModes.NO);
}
}
}
dialog.show()
}

View File

@@ -0,0 +1,569 @@

function 快速定位码链接() {
app.activeDocument.suspendHistory("快速定位码链接", "花样图层导出()");
}
function 花样图层导出() {
var 导出目录 = Folder.selectDialog("选择外链素材目录");
if (!导出目录) {
alert("未选择导出目录。操作已取消。");
return;
}
花样图层导出为TIF透明底(导出目录)
// 花样图层导出为TIF(导出目录);
}
function 花样图层导出为TIF透明底(导出目录) {
app.preferences.rulerUnits = Units.MM;
var doc = app.activeDocument;
var 扩展毫米数=80
// 获取文档中的所有图层
var allLayers = doc.layers;
var 名称数组 = [];
// 循环遍历所有图层
for (var i = 0; i < allLayers.length; i++) {
// 检查图层是否是图层组
if (allLayers[i] instanceof LayerSet) {
// 获取图层组中的所有子图层
subLayers = allLayers[i].layers;
图层组名称=allLayers[i].name
文档名称=app.activeDocument.name
文档名称去除后缀 = 文档名称.replace(/\.[^\.]+$/, "");
// 检查图层组是否包含子图层
if (subLayers.length > 0) {
// 获取图层组中最后一个子图层的名称
var lastSubLayer = subLayers[subLayers.length - 1];
var lastSubLayerName = lastSubLayer.name;
var lastSubLayerName = lastSubLayer.name;
FastSubLayer = subLayers[0];
FastSubLayername= FastSubLayer.name
//alert(SastSubLayerName)
// 输出图层组名称和最后一个子图层的名称
if (图层组名称 === "填充底图") {
for (var j = 0; j < subLayers.length; j++) {
// alert(subLayers[j].name);
填充底图组内部循环名称 = subLayers[j].name
填充底图循环素材 = app.activeDocument.layerSets.getByName("填充底图").layers.getByName(填充底图组内部循环名称);
app.activeDocument.activeLayer = 填充底图循环素材;
新建文档()
// 合并图层()
载入选区()
裁剪()
名称=文档名称去除后缀+"-("+填充底图组内部循环名称 +")-填充底图"
名称数组.push('"' + 名称 + '"');
制作图案预设(名称)
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
// 执行某一个操作(例如,设置图层组的可见性)
// alert("测试")
}
// 手动创建JSON格式的字符串
var jsonStr = '[' + 名称数组.join(', ') + ']';
// 创建一个文件对象指向桌面
var file = new File(导出目录 + "/名称数据.json");
// 打开文件写入JSON字符串然后关闭文件
file.open('w');
file.write(jsonStr);
file.close();
} else {
// 执行别的操作(例如,隐藏其他图层组)
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(lastSubLayerName);
app.activeDocument.activeLayer = 空白裁片模板;
新建图层()
app.activeDocument.activeLayer.name="最大白边值"
裁片边界 = lastSubLayer.bounds;
扩展值 = 毫米转像素(扩展毫米数); //50cm
裁片边界_左 = 毫米转像素(裁片边界[0]) - 扩展值;
裁片边界_上 = 毫米转像素(裁片边界[1]) - 扩展值;
裁片边界_右 = 毫米转像素(裁片边界[2]) + 扩展值;
裁片边界_下 = 毫米转像素(裁片边界[3]) + 扩展值;
var selRegion = [
[裁片边界_左,裁片边界_上],
[裁片边界_右,裁片边界_上],
[裁片边界_右,裁片边界_下],
[裁片边界_左,裁片边界_下]
];
app.activeDocument.selection.select(selRegion, SelectionType.REPLACE);
背景切换();
恢复白底();
填充();
隐藏图层();
裁片图层组 = app.activeDocument.layerSets.getByName(图层组名称)
app.activeDocument.activeLayer = 裁片图层组 ;
新建文档()
空白裁片模板 = app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(lastSubLayerName);
app.activeDocument.activeLayer = 空白裁片模板;
删除图层()
最大白边值=app.activeDocument.layerSets.getByName(图层组名称).layers.getByName("最大白边值");
app.activeDocument.activeLayer = 最大白边值;
app.activeDocument.crop(最大白边值.bounds, 0);
// 保存为TIF
var 文件路径 = 导出目录 + "/" + 图层组名称 + ".tif";
tiffOptions = new TiffSaveOptions();
app.activeDocument.saveAs(new File(文件路径), tiffOptions);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument=doc
最大白边值=app.activeDocument.layerSets.getByName(图层组名称).layers.getByName(FastSubLayername);
app.activeDocument.activeLayer = 最大白边值;
//上移图层()
多选图层()
转换为智能对象()
释放剪贴蒙版()
栅格化图层()
选择下一图层()
新建图层()
背景切换();
恢复白底();
填充();
选择上一图层()
添加图层蒙版()
向下合并()
转换为智能对象()
重新链接到文件(文件路径)
创建剪贴蒙版()
}
}
}
}
}
function 恢复白底() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("exchange"), d, DialogModes.NO);
}
function 创建剪贴蒙版() //创建剪贴蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("groupEvent"), d, DialogModes.NO);
}
function 制作图案预设(名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function mergeLayersNew_72799682617188() //
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 合并图层() //
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 背景切换() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 图层可见性show() //图层可见性
{
var d = new ActionDescriptor();
var list = new ActionList();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
list.putReference(r);
d.putList(stringIDToTypeID("null"), list);
executeAction(stringIDToTypeID("show"), d, DialogModes.NO);
}
function 裁剪() //裁剪
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("delete"), true);
executeAction(stringIDToTypeID("crop"), d, DialogModes.NO);
}
function 重新链接到文件(文件路径) //重新链接到文件
{
var d = new ActionDescriptor();
d.putPath(stringIDToTypeID("null"), new File(文件路径));
d.putInteger(stringIDToTypeID("layerID"), 94);
executeAction(stringIDToTypeID("placedLayerRelinkToFile"), d, DialogModes.NO);
}
function 转换为智能对象() //转换为智能对象
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("newPlacedLayer"), d, DialogModes.NO);
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 上移图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(11);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 毫米转像素(毫米)
{
//厘米转像素
doc_w = app.activeDocument.width;
//用户设定的厘米数 支持小数
user_mm = UnitValue(毫米,"mm");
user_px = user_mm.as("px")*app.activeDocument.resolution/72;
return user_px;
}
function 删除图层() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var list = new ActionList();
list.putInteger(22);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("delete"), d, DialogModes.NO);
}
function 多选图层(SastSubLayerName) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "最大白边值");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelectionContinuous"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(115);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putBoolean(stringIDToTypeID("group"), true);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("layer"), d1);
d.putInteger(stringIDToTypeID("layerID"), 22);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 释放剪贴蒙版() //释放剪贴蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("ungroup"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 栅格化图层() //栅格化图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("rasterizeLayer"), d, DialogModes.NO);
}
function 选择下一图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("backwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(314);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 选择上一图层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(369);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 新建图层() //新建图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("layer"));
d.putReference(stringIDToTypeID("null"), r);
d.putInteger(stringIDToTypeID("layerID"), 373);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 恢复白底() //删除图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("exchange"), d, DialogModes.NO);
}
function 背景切换() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("color"), stringIDToTypeID("colors"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("reset"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 填充() //填充
{
var d = new ActionDescriptor();
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("fillContents"), stringIDToTypeID("foregroundColor"));
d.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
executeAction(stringIDToTypeID("fill"), d, DialogModes.NO);
}
function 隐藏图层() //
{
var d = new ActionDescriptor();
var list = new ActionList();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
list.putReference(r);
d.putList(stringIDToTypeID("null"), list);
executeAction(stringIDToTypeID("hide"), d, DialogModes.NO);
}
function 新建文档() //复制图层
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("document"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putInteger(stringIDToTypeID("version"), 5);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,30 @@

底对齐()
水平居中对齐()
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,124 @@
function 开启事件监听() {
try
{
arg_num = arguments.length;
}
catch(e)
{
arguments = []; //初始赋值为空
}
eventName = "set";
//开启监听
enable_notifier(eventName, $.fileName);
alert("开启成功")
//取消监听
//disable_notifier(eventName, $.fileName);
//这里进行监控调用事件
if (arguments.length >= 2)
{
//alert(arguments.length);
//~ alert(arguments[0],"动作参数1"); //动作描述符 AR
//~ alert(arguments[1],"动作参数2"); //动作事件ID
//alert("事件名:"+typeIDToStringID(arguments[1])+"\n"+"事件ID:"+arguments[1],"提示:");
moveToGuideCenter();
//main(arguments[0], arguments[1]);
}
function moveToGuideCenter() {
if (app.documents.length === 0 || !app.activeDocument.selection.bounds) {
alert("没有打开的文档或没有有效的选区。");
return;
}
var doc = app.activeDocument;
var guides = doc.guides;
var xGuide = null;
var yGuide = null;
// 找到 X 方向和 Y 方向上的参考线
for (var i = 0; i < guides.length; i++) {
var guide = guides[i];
if (guide.direction === Direction.HORIZONTAL && yGuide === null) {
yGuide = guide;
} else if (guide.direction === Direction.VERTICAL && xGuide === null) {
xGuide = guide;
}
}
if (xGuide === null || yGuide === null) {
alert("必须确保存在一条水平和一条垂直参考线。");
return;
}
var intersectionX = xGuide.coordinate;
var intersectionY = yGuide.coordinate;
var bounds = doc.selection.bounds;
var xCenter = bounds[0] + (bounds[2] - bounds[0]) / 2;
var yCenter = bounds[1] + (bounds[3] - bounds[1]) / 2;
var deltaX = intersectionX - xCenter;
var deltaY = intersectionY - yCenter;
// 取消当前选区
doc.selection.deselect();
// 移动选区图层
var selectedLayer = doc.activeLayer;
selectedLayer.translate(deltaX, deltaY);
}
function enable_notifier(event_name, script_name, event_class)
{
try
{
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
if (!app.notifiersEnabled) app.notifiersEnabled = true;
return true;
}
}
app.notifiers.add(event_name, File(script_name), event_class);
app.notifiersEnabled = true;
return true;
}
catch (e) { _alert(e); return false; }
}
function disable_notifier(event_name, script_name, event_class)
{
try
{
var ret = false;
for (var i = 0; i < app.notifiers.length; i++)
{
if (app.notifiers[i].event == event_name &&
File(app.notifiers[i].eventFile).fsName.toLowerCase() == File(script_name).fsName.toLowerCase())
{
app.notifiers[i].remove();
ret = true;
}
}
if (!app.notifiers.length) app.notifiersEnabled = false;
return ret;
}
catch (e) { _alert(e); return false; }
}
}

View File

@@ -0,0 +1,51 @@
function 删除图层3()
{
app.activeDocument.suspendHistory("Establish a mask", "批量建立蒙版()");
}
function 批量建立蒙版(){
var doc = app.activeDocument; // 获取当前文档
// 遍历所有图层
for (var i = 0; i < doc.artLayers.length; i++) {
var layer = doc.artLayers[i];
// alert(layer.name); // 显示图层名称
var layname=layer.name
var 图层名称选择 = app.activeDocument.layers.getByName(layname);
app.activeDocument.activeLayer = 图层名称选择;
载入选区();
添加图层蒙版();
}
function 载入选区() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("transparencyEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 添加图层蒙版() //添加图层蒙版
{
var d = new ActionDescriptor();
d.putClass(stringIDToTypeID("new"), stringIDToTypeID("channel"));
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("at"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("userMaskEnabled"), stringIDToTypeID("revealSelection"));
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
}

View File

@@ -0,0 +1,18 @@
// 获取Photoshop的当前文档
var 当前文档 = app.activeDocument;
try {
// 获取名为"贴图位置"的图层组
var layerSet = 当前文档.layerSets.getByName("贴图位置");
// 遍历图层组中的每个图层
for (var i = 0; i < layerSet.artLayers.length; i++) {
var layer = layerSet.artLayers[i];
// 在这里执行对每个图层的操作
// 例如,打印图层的名字
alert("图层名: " + layer.name);
}
} catch (e) {
alert("发生错误或找不到图层组: " + e);
}

View File

@@ -0,0 +1,16 @@
function 删除图层3()
{
app.activeDocument.suspendHistory("Combine all masks", "拼合所有蒙版()");
}
function 拼合所有蒙版() //拼合所有蒙版
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
executeAction(stringIDToTypeID("e805a6ee-6d75-4b62-b6fe-f5873b5fdf20"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,8 @@

function 名称重新赋予(layername,angle,size) //
{
var doc = app.activeDocument;
var selectedLayer = doc.activeLayer;
selectedLayer.name =layername+"-"+"Mark"+"_"+angle+"_"+size
}
//layername+"-"+"Mark"+"_"+angle+"_"+size

View File

@@ -0,0 +1,55 @@

// 找到图层组后,显示文件夹选择框
var 文件夹 = Folder.selectDialog("请选择要打开文件的文件夹");
if (文件夹) {
var 文件列表 = 文件夹.getFiles();
for (var i = 0; i < 文件列表.length; i++) {
var 文件 = 文件列表[i];
// 确保文件是图像文件
if (文件 instanceof File && 文件.name.match(/\.(jpg|jpeg|png|gif|psd)$/i)) {
原始文档 = app.activeDocument;
app.open(文件);
图像大小();
预设图案(当前文档名称);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
app.activeDocument =原始文档;
}
}
}
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 200);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}

View File

@@ -0,0 +1,45 @@

function 图层排序改名(){
var doc = app.activeDocument;
// 函数:获取图层的面积
function getLayerArea(layer) {
var bounds = layer.bounds;
var width = bounds[2].value - bounds[0].value;
var height = bounds[3].value - bounds[1].value;
return width * height;
}
// 创建一个包含所有图层及其面积的数组
var layers = [];
for (var i = 0; i < doc.layers.length; i++) {
var layer = doc.layers[i];
var area = getLayerArea(layer);
layers.push({ layer: layer, area: area });
}
// 按面积对图层进行排序
layers.sort(function(a, b) {
return b.area - a.area; // 从大到小排序
});
// 正则表达式,用于移除图层名称中的最后一个字符
var regex = /(.+)-\d+$/;
// 重新排列图层,并重命名
for (var i = 0; i < layers.length; i++) {
// 移动图层到文档顶部
layers[i].layer.move(doc, ElementPlacement.PLACEATBEGINNING);
// 获取原始图层名称,并使用正则表达式处理
var originalName = layers[i].layer.name;
var newName = originalName.replace(regex, "$1");
// 添加新的排序编号
layers[i].layer.name = newName + "-" + (i + 1);
}
}

View File

@@ -1,6 +1,5 @@
dxf26_jscode = """ //
// 模特换图()
function 模特换图(){ function 模特换图(){
建立快照() 建立快照()
var folder = Folder.selectDialog("请选择一个文件夹"); var folder = Folder.selectDialog("请选择一个文件夹");
@@ -63,7 +62,7 @@ if (folder) {
// //
历史记录回退到快照1() 历史记录回退到快照1()
} catch (e) { } catch (e) {
alert("无法打开文件: " ); alert("无法打开文件: " + file.name + "\n错误信息: " + e);
} }
} }
@@ -215,4 +214,3 @@ function 建立快照() //打开
} }
} }
"""

View File

@@ -0,0 +1,363 @@
// 设置单位为像素
自动米样拼贴()
function 自动米样拼贴(){
var dialog = new Window("dialog");
dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "幅宽(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
var dialog = new Window("dialog");
dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档拼贴";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "幅宽(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
//function 创建拼贴图(导入文件夹路径, 文档宽度厘米) {
// 假设这些值是从某处获取或者用户输入的
var 导入文件夹路径 =new Folder (edittext1.text);
var 文档宽度厘米 = Number(edittext2.text);
var 文档高度厘米 = 6000; // 例如50厘米
var 分辨率 = 150; // 分辨率设置为150 DPI
// 转换厘米到像素
var 厘米到像素的转换系数 = 2.54;
var 文档宽度像素 = 文档宽度厘米 / 厘米到像素的转换系数 * 分辨率;
var 文档高度像素 = 文档高度厘米 / 厘米到像素的转换系数 * 分辨率;
var 导入文件夹 = new Folder(导入文件夹路径);
var 文件列表 = 导入文件夹.getFiles("*.*");
var 文件名数组 = new Array();
var 文件宽度数组 = new Array();
var 文件高度数组 = new Array();
// 读取文件列表并获取文件信息
for (var 索引1 = 0; 索引1 < 文件列表.length; 索引1++) {
var 文档 = app.open(文件列表[索引1]);
文件名数组[索引1] = 文档.fullName;
文件宽度数组[索引1] = parseInt(文档.width);
文件高度数组[索引1] = parseInt(文档.height);
文档.close();
}
// 根据高度对文件进行排序
for (var 索引1 = 文件高度数组.length - 1; 索引1 > 0; --索引1) {
for (var 索引2 = 0; 索引2 < 索引1; ++索引2) {
if (文件高度数组[索引2] > 文件高度数组[索引2 + 1])
交换数组元素(索引2, 索引2 + 1);
}
}
function 交换数组元素(索引1, 索引2) {
var 临时 = 文件高度数组[索引1];
文件高度数组[索引1] = 文件高度数组[索引2];
文件高度数组[索引2] = 临时;
var 临时2 = 文件宽度数组[索引1];
文件宽度数组[索引1] = 文件宽度数组[索引2];
文件宽度数组[索引2] = 临时2;
var 临时3 = 文件名数组[索引1];
文件名数组[索引1] = 文件名数组[索引2];
文件名数组[索引2] = 临时3;
}
// 创建新文档并添加图片
var 新文档 = app.documents.add(文档宽度像素, 文档高度像素, 分辨率, "拼贴", NewDocumentMode.CMYK, DocumentFill.WHITE);
var 当前顶部 = 0;
var 当前左侧 = 0;
for (var 索引1 = 0; 索引1 < 文件名数组.length; 索引1++) {
var 文档 = app.open(文件名数组[索引1]);
文档.selection.selectAll();
文档.selection.copy();
文档.close();
if ((当前左侧 + 文件宽度数组[索引1]) > 文档宽度像素) {
当前左侧 = 0;
当前顶部 += 文件高度数组[索引1 - 1];
}
var 边界 = [
[当前左侧, 当前顶部],
[当前左侧 + 文件宽度数组[索引1], 当前顶部],
[当前左侧 + 文件宽度数组[索引1], 当前顶部 + 文件高度数组[索引1]],
[当前左侧, 当前顶部 + 文件高度数组[索引1]]
];
新文档.selection.select(边界, SelectionType.REPLACE, 0, true);
新文档.paste();
当前左侧 += 文件宽度数组[索引1];
}
// 选择输出文件夹并保存图片
var 输出文件夹 = new Folder(edittext1.text + "/拼贴");
if (!输出文件夹.exists) 输出文件夹.create();
// 设置TIFF保存选项
var tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW; // 使用LZW压缩
tiffSaveOptions.byteOrder = ByteOrder.IBM; // 设置字节顺序为IBM大端序
// 定义保存的文件路径和名称
var 文件 = new File(输出文件夹 + "/combined.tif");
// 保存当前文档为TIFF格式
新文档.saveAs(文件, tiffSaveOptions, true, Extension.LOWERCASE);
裁切透明像素();
// 关闭文档,不保存更改
//新文档.close(SaveOptions.DONOTSAVECHANGES);
dialog.close();
alert("拼贴完成")
}
function 裁切透明像素() {
var 文档 = app.activeDocument; // 获取当前活动的文档
// 裁切透明像素
// TrimType.TOPLEFT 表示从图像的左上角开始裁切
// 第二个参数 true 表示裁切顶部的透明像素
// 第三个参数 true 表示裁切左侧的透明像素
// 第四个参数 true 表示裁切底部的透明像素
// 第五个参数 true 表示裁切右侧的透明像素
文档.trim(TrimType.TOPLEFT, true, true, true, true);
}
dialog.show();
}

View File

@@ -0,0 +1,459 @@

var dialog = new Window("dialog");
dialog.text = "S/O样自动连晒";
//dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "宽度(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
var statictext4 = group3.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "高度(cm):";
var edittext3 = group3.add('edittext {properties: {name: "edittext3"}}');
edittext3.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
var 素材图片文件夹 = new Folder(edittext1.text);
var 遍历tiff = 素材图片文件夹.getFiles("*.*");
// 新建文件夹
var 新文件夹 = new Folder(edittext1.text + "/SO小样拼贴");
新文件夹.create();
var 新加字文件夹 = new Folder(edittext1.text + "/SO小样拼贴加字");
新加字文件夹.create();
宽度=Number (edittext2.text);
高度=Number (edittext3.text);
for (var i = 0; i < 遍历tiff.length; i++) {
if (遍历tiff[i] instanceof File) {
app.open(遍历tiff[i]);
app.activeDocument.flatten();
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
图像大小()
预设图案(当前文档名称);
app.activeDocument.crop([UnitValue("0px"), UnitValue("0px"), UnitValue(宽度, "cm"), UnitValue(高度, "cm")]);
填充图案(当前文档名称);
app.activeDocument.flatten();
画布扩展()
// 保存新的图像文件到新建的文件夹中
var 保存路径 = 新文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
创建并处理文本图层();
app.activeDocument.flatten();
var 保存路径 = 新加字文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
alert("小样连晒完成")
// 创建并保存拼贴图像(新文件夹, 幅宽)
};
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 图像大小()
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 150);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("automaticInterpolation"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 画布扩展() //
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 14.4);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 14.4);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("color"));
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("red"), 255);
d1.putDouble(stringIDToTypeID("green"), 255);
d1.putDouble(stringIDToTypeID("blue"), 255);
d.putObject(stringIDToTypeID("canvasExtensionColor"), stringIDToTypeID("RGBColor"), d1);
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 创建并处理文本图层() {
// 新建图层
var textLayer = activeDocument.artLayers.add();
// 将新建图层变成文本图层
textLayer.kind = LayerKind.TEXT;
// 将文本内容改为当前文档name
textLayer.textItem.contents = activeDocument.name;
// 字体大小固定值
var 固定字体大小 = 30; // 例如30像素
textLayer.textItem.size = 固定字体大小;
// 文字字体固定值
textLayer.textItem.font = "微软雅黑";
// 计算并调整文本位置
var x = activeDocument.width - textLayer.bounds[2];
var y = textLayer.bounds[1];
textLayer.translate(x, -y);
// 偏移
textLayer.translate(UnitValue("-1cm"), UnitValue("+0.5cm"));
// 复制并栅格化图层
var copyLayer = textLayer.duplicate();
copyLayer.rasterize(RasterizeType.ENTIRELAYER);
// 设置固定颜色值
var 固定颜色 = new SolidColor();
固定颜色.rgb.red = 255; // 红色分量
固定颜色.rgb.green = 255; // 绿色分量
固定颜色.rgb.blue = 255; // 蓝色分量
activeDocument.activeLayer = copyLayer;
activeDocument.selection.fill(固定颜色, ColorBlendMode.NORMAL, 100, true);
// 白边
activeDocument.activeLayer.applyMinimum(10);
后移一层()
向上选择()
向下合并()
名称更改字体()
缩小字体图层至文档一半()
多选背景()
底对齐()
水平居中对齐()
}
// 调用函数
function 后移一层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 向上选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(19);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 名称更改字体() //名称更改
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("name"), "字体");
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
function 多选背景() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "背景");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(1);
list.putInteger(13);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
// 设置单位为像素
dialog.show();

View File

@@ -0,0 +1,489 @@
新的米样缩放()
function 新的米样缩放()
{
var dialog = new Window("dialog");
dialog.text = "S/O样自动缩放";
//dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "宽度(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
var statictext4 = group3.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "高度(cm):";
var edittext3 = group3.add('edittext {properties: {name: "edittext3"}}');
edittext3.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
var 素材图片文件夹 = new Folder(edittext1.text);
var 遍历tiff = 素材图片文件夹.getFiles("*.*");
// 新建文件夹
var 新文件夹 = new Folder(edittext1.text + "/SO小样拼贴");
新文件夹.create();
var 新加字文件夹 = new Folder(edittext1.text + "/SO小样拼贴加字");
新加字文件夹.create();
宽度=Number (edittext2.text);
高度=Number (edittext3.text);
for (var i = 0; i < 遍历tiff.length; i++) {
if (遍历tiff[i] instanceof File) {
app.open(遍历tiff[i]);
app.activeDocument.flatten();
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
resizeImageToCm(宽度, 高度,200);
/*
图像大小()
预设图案(当前文档名称);
app.activeDocument.crop([UnitValue("0px"), UnitValue("0px"), UnitValue(宽度, "cm"), UnitValue(高度, "cm")]);
填充图案(当前文档名称);
app.activeDocument.flatten();
画布扩展()
*/
// 保存新的图像文件到新建的文件夹中
var 保存路径 = 新文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
创建并处理文本图层();
app.activeDocument.flatten();
var 保存路径 = 新加字文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
//alert("小样连晒完成")
dialog.close();
// 创建并保存拼贴图像(新文件夹, 幅宽)
};
function resizeImageToCm(widthCm, heightCm, resolution) {
// 将厘米转换为像素
var widthInPixels = widthCm * (resolution / 2.54);
var heightInPixels = heightCm * (resolution / 2.54);
// 调整图像大小
app.activeDocument.resizeImage(widthInPixels, heightInPixels, resolution, ResampleMethod.NEARESTNEIGHBOR);
}
// 使用示例:将图像大小调整为 10cm x 15cm分辨率为 300 像素/英寸
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 200);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 画布扩展() //
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 14.4);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 14.4);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("color"));
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("red"), 255);
d1.putDouble(stringIDToTypeID("green"), 255);
d1.putDouble(stringIDToTypeID("blue"), 255);
d.putObject(stringIDToTypeID("canvasExtensionColor"), stringIDToTypeID("RGBColor"), d1);
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 创建并处理文本图层() {
// 新建图层
var textLayer = activeDocument.artLayers.add();
// 将新建图层变成文本图层
textLayer.kind = LayerKind.TEXT;
// 将文本内容改为当前文档name
textLayer.textItem.contents = activeDocument.name;
// 字体大小固定值
var 固定字体大小 = 30; // 例如30像素
textLayer.textItem.size = 固定字体大小;
// 文字字体固定值
textLayer.textItem.font = "微软雅黑";
// 计算并调整文本位置
var x = activeDocument.width - textLayer.bounds[2];
var y = textLayer.bounds[1];
textLayer.translate(x, -y);
// 偏移
textLayer.translate(UnitValue("-1cm"), UnitValue("+0.5cm"));
// 复制并栅格化图层
var copyLayer = textLayer.duplicate();
copyLayer.rasterize(RasterizeType.ENTIRELAYER);
// 设置固定颜色值
var 固定颜色 = new SolidColor();
固定颜色.rgb.red = 255; // 红色分量
固定颜色.rgb.green = 255; // 绿色分量
固定颜色.rgb.blue = 255; // 蓝色分量
activeDocument.activeLayer = copyLayer;
activeDocument.selection.fill(固定颜色, ColorBlendMode.NORMAL, 100, true);
// 白边
activeDocument.activeLayer.applyMinimum(10);
后移一层()
向上选择()
向下合并()
名称更改字体()
缩小字体图层至文档一半()
多选背景()
底对齐()
水平居中对齐()
}
// 调用函数
function 后移一层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 向上选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(19);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 名称更改字体() //名称更改
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("name"), "字体");
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
function 多选背景() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "背景");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(1);
list.putInteger(13);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
// 设置单位为像素
dialog.show();
}

View File

@@ -0,0 +1,461 @@

var dialog = new Window("dialog");
dialog.text = "S/O样自动连晒";
//dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "宽度(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
var statictext4 = group3.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = "高度(cm):";
var edittext3 = group3.add('edittext {properties: {name: "edittext3"}}');
edittext3.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
var 素材图片文件夹 = new Folder(edittext1.text);
var 遍历tiff = 素材图片文件夹.getFiles("*.*");
// 新建文件夹
var 新文件夹 = new Folder(edittext1.text + "/SO小样拼贴");
新文件夹.create();
var 新加字文件夹 = new Folder(edittext1.text + "/SO小样拼贴加字");
新加字文件夹.create();
宽度=Number (edittext2.text);
高度=Number (edittext3.text);
for (var i = 0; i < 遍历tiff.length; i++) {
if (遍历tiff[i] instanceof File) {
app.open(遍历tiff[i]);
app.activeDocument.flatten();
var 当前文档 = app.activeDocument;
var 当前文档名称 = 当前文档.name;
图像大小()
预设图案(当前文档名称);
app.activeDocument.crop([UnitValue("0px"), UnitValue("0px"), UnitValue(宽度, "cm"), UnitValue(高度, "cm")]);
填充图案(当前文档名称);
app.activeDocument.flatten();
画布扩展()
// 保存新的图像文件到新建的文件夹中
var 保存路径 = 新文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
创建并处理文本图层();
app.activeDocument.flatten();
var 保存路径 = 新加字文件夹 + "/" + 当前文档名称;
var tifSaveOpt = new TiffSaveOptions();
tifSaveOpt.imageCompression = TIFFEncoding.TIFFLZW;
tifSaveOpt.byteOrder = ByteOrder.IBM;
var asCopy = true;
app.activeDocument.saveAs(new File(保存路径), tifSaveOpt, asCopy);
app.activeDocument.close(SaveOptions.DONOTSAVECHANGES);
}
}
alert("小样连晒完成")
// 创建并保存拼贴图像(新文件夹, 幅宽)
};
function 预设图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("pattern"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("property"), stringIDToTypeID("selection"));
r1.putEnumerated(stringIDToTypeID("document"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("using"), r1);
d.putString(stringIDToTypeID("name"), 当前文档名称);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 填充图案(当前文档名称) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putClass(stringIDToTypeID("contentLayer"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
var d2 = new ActionDescriptor();
var d3 = new ActionDescriptor();
d3.putString(stringIDToTypeID("name"), 当前文档名称);
d2.putObject(stringIDToTypeID("pattern"), stringIDToTypeID("pattern"), d3);
d1.putObject(stringIDToTypeID("type"), stringIDToTypeID("patternLayer"), d2);
d.putObject(stringIDToTypeID("using"), stringIDToTypeID("contentLayer"), d1);
executeAction(stringIDToTypeID("make"), d, DialogModes.NO);
}
function 图像大小() //图像大小
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("resolution"), stringIDToTypeID("densityUnit"), 200);
d.putBoolean(stringIDToTypeID("scaleStyles"), true);
d.putBoolean(stringIDToTypeID("constrainProportions"), true);
d.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID("nearestNeighbor"));
executeAction(stringIDToTypeID("imageSize"), d, DialogModes.NO);
}
function 画布扩展() //
{
var d = new ActionDescriptor();
d.putBoolean(stringIDToTypeID("relative"), true);
d.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("distanceUnit"), 14.4);
d.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("distanceUnit"), 14.4);
d.putEnumerated(stringIDToTypeID("horizontal"), stringIDToTypeID("horizontalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("vertical"), stringIDToTypeID("verticalLocation"), stringIDToTypeID("center"));
d.putEnumerated(stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("canvasExtensionColorType"), stringIDToTypeID("color"));
var d1 = new ActionDescriptor();
d1.putDouble(stringIDToTypeID("red"), 255);
d1.putDouble(stringIDToTypeID("green"), 255);
d1.putDouble(stringIDToTypeID("blue"), 255);
d.putObject(stringIDToTypeID("canvasExtensionColor"), stringIDToTypeID("RGBColor"), d1);
executeAction(stringIDToTypeID("canvasSize"), d, DialogModes.NO);
}
function 创建并处理文本图层() {
// 新建图层
var textLayer = activeDocument.artLayers.add();
// 将新建图层变成文本图层
textLayer.kind = LayerKind.TEXT;
// 将文本内容改为当前文档name
textLayer.textItem.contents = activeDocument.name;
// 字体大小固定值
var 固定字体大小 = 30; // 例如30像素
textLayer.textItem.size = 固定字体大小;
// 文字字体固定值
textLayer.textItem.font = "微软雅黑";
// 计算并调整文本位置
var x = activeDocument.width - textLayer.bounds[2];
var y = textLayer.bounds[1];
textLayer.translate(x, -y);
// 偏移
textLayer.translate(UnitValue("-1cm"), UnitValue("+0.5cm"));
// 复制并栅格化图层
var copyLayer = textLayer.duplicate();
copyLayer.rasterize(RasterizeType.ENTIRELAYER);
// 设置固定颜色值
var 固定颜色 = new SolidColor();
固定颜色.rgb.red = 255; // 红色分量
固定颜色.rgb.green = 255; // 绿色分量
固定颜色.rgb.blue = 255; // 蓝色分量
activeDocument.activeLayer = copyLayer;
activeDocument.selection.fill(固定颜色, ColorBlendMode.NORMAL, 100, true);
// 白边
activeDocument.activeLayer.applyMinimum(10);
后移一层()
向上选择()
向下合并()
名称更改字体()
缩小字体图层至文档一半()
多选背景()
底对齐()
水平居中对齐()
}
// 调用函数
function 后移一层() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("previous"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("move"), d, DialogModes.NO);
}
function 向上选择() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("forwardEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(19);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 向下合并() //向下合并
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 名称更改字体() //名称更改
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putString(stringIDToTypeID("name"), "字体");
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layer"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 缩小字体图层至文档一半() {
var 文档 = app.activeDocument;
var 字体图层 = null;
// 遍历文档中的图层以找到名为"字体"的图层
for (var i = 0; i < 文档.artLayers.length; i++) {
if (文档.artLayers[i].name === "字体") {
字体图层 = 文档.artLayers[i];
break;
}
}
if (字体图层 !== null) {
// 获取文档的宽度的一半
var 目标宽度 = 文档.width / 2;
// 获取图层的当前宽度
var 图层宽度 = 字体图层.bounds[2] - 字体图层.bounds[0];
// 计算缩放比例
var 缩放比例 = 目标宽度 / 图层宽度 * 100;
// 缩放图层
字体图层.resize(缩放比例, 缩放比例, AnchorPosition.MIDDLECENTER);
} else {
alert("未找到名为'字体'的图层");
}
}
function 多选背景() //自由变换
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "背景");
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("selectionModifier"), stringIDToTypeID("selectionModifierType"), stringIDToTypeID("addToSelection"));
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(1);
list.putInteger(13);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 底对齐() //底对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSBottoms"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
function 水平居中对齐() //水平居中对齐
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
d.putEnumerated(stringIDToTypeID("using"), stringIDToTypeID("alignDistributeSelector"), stringIDToTypeID("ADSCentersH"));
d.putBoolean(stringIDToTypeID("alignToCanvas"), false);
executeAction(stringIDToTypeID("align"), d, DialogModes.NO);
}
// 设置单位为像素
dialog.show();

View File

@@ -0,0 +1,306 @@
/////////////////////////////////////////////////////////////////////////////////////////
// 检查是否有打开的文档
function 码标添加2() {
app.activeDocument.suspendHistory("码标添加", "码标放置()");
}
function 码标放置() {
app.activeDocument.layerSets.add().name = "码标";
app.preferences.rulerUnits = Units.PIXELS;
if (app.documents.length > 0) {
var doc = app.activeDocument; // 获取当前文档
// 查找名为"裁片"的组
var cropGroup = doc.layerSets.getByName("裁片"); // 将“裁片”替换为您的组名称
if (cropGroup) {
// 遍历裁片组内的所有图层
for (var i = 0; i < cropGroup.layers.length; i++) {
var layer = cropGroup.layers[i];
//alert("图层名称: " + layer.name);
当前图层名称=layer.name
parts = layer.name.split("_")
// alert(parts[2])
大货成品图层 = app.activeDocument.layerSets.getByName("裁片").layers.getByName(当前图层名称);
app.activeDocument.activeLayer = 大货成品图层;
切换mask();
载入选区蒙版()
大货成品图层边距 = 获取当前选区四边距();
右边距=大货成品图层边距.right
var currentDocument = app.activeDocument;
var height = currentDocument.height.value;
var 上边距新 = 0;
var 左边距新 = 右边距 - 1;
var 下边距新 = height;
var 右边距新 = 右边距 + 1;
新建选区(上边距新, 左边距新, 下边距新, 右边距新);
选取交叉()
获取码标记点 = 获取当前选区四边距();
码标记点下标记 = 获取码标记点.bottom
码标记点左标记 = 获取码标记点.left
码标x中心坐标=码标记点左标记
码标y中心坐标=码标记点下标记
$.writeln(码标x中心坐标);
$.writeln(码标y中心坐标);
载入选区蒙版()
收缩45像素()
收缩45像素成品图层边距 = 获取当前选区四边距();
收缩45像素右边距=收缩45像素成品图层边距.right
var 收缩45像素上边距新 = 0;
var 收缩45像素左边距新 = 0 ;
var 收缩45像素下边距新 = height;
var 收缩45像素右边距新 = 收缩45像素右边距-1 ;
//$.writeln(收缩45像素上边距新);
//$ .writeln(收缩45像素左边距新);
//$.writeln(收缩45像素下边距新);
//$.writeln(收缩45像素右边距新);
减去选区(收缩45像素上边距新, 收缩45像素左边距新, 收缩45像素下边距新, 收缩45像素右边距新);
//选取交叉()
收缩45像素获取码标记点 = 获取当前选区四边距();
收缩45像素码标记点y标记 = 收缩45像素获取码标记点.bottom
收缩45像素码标记点x标记 = 收缩45像素获取码标记点.left
$.writeln(收缩45像素码标记点x标记);
$.writeln(收缩45像素码标记点y标记);
获取中心点1=获取中心点(收缩45像素码标记点x标记, 收缩45像素码标记点y标记, 码标x中心坐标, 码标y中心坐标)
$.writeln("中心点坐标: x = " + 获取中心点1.x + ", y = " + 获取中心点1.y);
var 码标高度转毫米y = pixelsToMillimeters(获取中心点1.y);
var 码标宽度转毫米x = pixelsToMillimeters(获取中心点1.x );
//alert(码标高度转毫米)
//alert(码标宽度转毫米)
var fileName = currentDocument.name;
// 去掉文件名的后缀名
var fileNameWithoutExtension = fileName.split('.').slice(0, -1).join('.');
var textLayer = currentDocument.artLayers.add();
textLayer.kind = LayerKind.TEXT;
// 设置文本图层的文本内容
textLayer.textItem.contents = fileNameWithoutExtension
textLayer.textItem.size = 10
var cmykColor = new SolidColor();
cmykColor.cmyk.cyan = 50; // 青色通道值
cmykColor.cmyk.magenta = 40; // 品红色通道值
cmykColor.cmyk.yellow = 50; // 黄色通道值
cmykColor.cmyk.black = 70; // 黑色通道值
// 将文本图层的颜色设置为上面创建的CMYK颜色
textLayer.textItem.color = cmykColor;
app.preferences.rulerUnits = Units.MM;
当前图层 = app.activeDocument.activeLayer;
当前图层的底边 = 当前图层.bounds[3];
当前图层的上边 = 当前图层.bounds[1];
当前图层的高度 = 当前图层的底边 - 当前图层的上边;
当前图层的左边 = 当前图层.bounds[0];
当前图层的右边 = 当前图层.bounds[2];
当前图层的宽度 = 当前图层的右边 - 当前图层的左边;
当前图层的高度的一半 = 当前图层的高度 / 2;
当前图层的宽度的一半 = 当前图层的宽度 / 2;
当前图层的高度中心 = 当前图层的上边 + 当前图层的高度的一半;
当前图层的宽度中心 = 当前图层的左边 + 当前图层的宽度的一半;
//app.activeDocument.activeLayer.translate(Number(当前图层的宽度中心) - Number(码标宽度转毫米), Number(当前图层的高度中心) - Number(码标高度转毫米));
app.activeDocument.activeLayer.translate(Number(码标宽度转毫米x) - Number(当前图层的宽度中心), Number(码标高度转毫米y) - Number(当前图层的高度中心));
app.preferences.rulerUnits = Units.PIXELS;
app.activeDocument.activeLayer.move(app.activeDocument.layerSets.getByName("码标"), ElementPlacement.INSIDE);
}
} else {
alert("找不到名为“裁片”的组。");
}
} else {
alert("没有打开的文档。");
}
}
码标 = app.activeDocument.layerSets.getByName("码标")
app.activeDocument.activeLayer = 码标;
描边()
function 减去选区(上边距, 左边距, 下边距, 右边距) //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("top"), stringIDToTypeID("pixelsUnit"), 上边距);
d1.putUnitDouble(stringIDToTypeID("left"), stringIDToTypeID("pixelsUnit"), 左边距);
d1.putUnitDouble(stringIDToTypeID("bottom"), stringIDToTypeID("pixelsUnit"), 下边距);
d1.putUnitDouble(stringIDToTypeID("right"), stringIDToTypeID("pixelsUnit"), 右边距);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("rectangle"), d1);
executeAction(stringIDToTypeID("subtractFrom"), d, DialogModes.NO);
}
function 收缩45像素() //
{
var d = new ActionDescriptor();
d.putUnitDouble(stringIDToTypeID("by"), stringIDToTypeID("pixelsUnit"), 45);
d.putBoolean(stringIDToTypeID("selectionModifyEffectAtCanvasBounds"), false);
executeAction(stringIDToTypeID("contract"), d, DialogModes.NO);
}
function 获取中心点(x1, y1, x2, y2) {
var centerX = (x1 + x2) / 2;
var centerY = (y1 + y2) / 2;
return { x: centerX, y: centerY };
}
function 切换mask() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("channel"), stringIDToTypeID("mask"));
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 选择组() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putName(stringIDToTypeID("layer"), "码标");
d.putReference(stringIDToTypeID("null"), r);
d.putBoolean(stringIDToTypeID("makeVisible"), false);
var list = new ActionList();
list.putInteger(153);
d.putList(stringIDToTypeID("layerID"), list);
executeAction(stringIDToTypeID("select"), d, DialogModes.NO);
}
function 合并组() //合并组
{
var d = new ActionDescriptor();
executeAction(stringIDToTypeID("mergeLayersNew"), d, DialogModes.NO);
}
function 新建选区(上边距, 左边距, 下边距, 右边距) {
var currentDocument = app.activeDocument;
var top = 上边距;
var left = 左边距;
var bottom = 下边距;
var right = 右边距;
var selectionRegion = Array(Array(left, top), Array(right, top), Array(right, bottom), Array(left, bottom));
currentDocument.selection.select(selectionRegion);
}
function 载入选区蒙版() //载入选区
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("to"), r1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
function 获取当前选区四边距() {
var currentDocument = app.activeDocument;
var selectionBounds = currentDocument.selection.bounds;
var top = selectionBounds[1].value;
var left = selectionBounds[0].value;
var bottom = selectionBounds[3].value;
var right = selectionBounds[2].value;
return {
top: top,
left: left,
bottom: bottom,
right: right
};
}
function 描边() //描边
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putProperty(stringIDToTypeID("property"), stringIDToTypeID("layerEffects"));
r.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var d1 = new ActionDescriptor();
d1.putUnitDouble(stringIDToTypeID("scale"), stringIDToTypeID("percentUnit"), 208.333290947808);
var d2 = new ActionDescriptor();
d2.putBoolean(stringIDToTypeID("enabled"), true);
d2.putBoolean(stringIDToTypeID("present"), true);
d2.putBoolean(stringIDToTypeID("showInDialog"), true);
d2.putEnumerated(stringIDToTypeID("style"), stringIDToTypeID("frameStyle"), stringIDToTypeID("outsetFrame"));
d2.putEnumerated(stringIDToTypeID("paintType"), stringIDToTypeID("frameFill"), stringIDToTypeID("solidColor"));
d2.putEnumerated(stringIDToTypeID("mode"), stringIDToTypeID("blendMode"), stringIDToTypeID("normal"));
d2.putUnitDouble(stringIDToTypeID("opacity"), stringIDToTypeID("percentUnit"), 100);
d2.putUnitDouble(stringIDToTypeID("size"), stringIDToTypeID("pixelsUnit"), 2);
var d3 = new ActionDescriptor();
d3.putDouble(stringIDToTypeID("cyan"), 0);
d3.putDouble(stringIDToTypeID("magenta"), 0);
d3.putDouble(stringIDToTypeID("yellowColor"), 0);
d3.putDouble(stringIDToTypeID("black"), 0);
d2.putObject(stringIDToTypeID("color"), stringIDToTypeID("CMYKColorClass"), d3);
d2.putBoolean(stringIDToTypeID("overprint"), false);
d1.putObject(stringIDToTypeID("frameFX"), stringIDToTypeID("frameFX"), d2);
d.putObject(stringIDToTypeID("to"), stringIDToTypeID("layerEffects"), d1);
executeAction(stringIDToTypeID("set"), d, DialogModes.NO);
}
// 将像素转换为毫米
function pixelsToMillimeters(pixels) {
// 获取当前文档
var doc = app.activeDocument;
// 获取图像的分辨率(像素/英寸)
var resolution = doc.resolution;
// 计算像素转换为毫米
var inches = pixels / resolution;
var millimeters = inches * 25.4;
return millimeters.toFixed(2); // 保留两位小数
}
function 选取交叉() //
{
var d = new ActionDescriptor();
var r = new ActionReference();
r.putEnumerated(stringIDToTypeID("channel"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
d.putReference(stringIDToTypeID("null"), r);
var r1 = new ActionReference();
r1.putProperty(stringIDToTypeID("channel"), stringIDToTypeID("selection"));
d.putReference(stringIDToTypeID("with"), r1);
executeAction(charIDToTypeID("Intr"), d, DialogModes.NO);
}

View File

@@ -1,5 +1,5 @@
dxf24_jscode = """ 
自动米样拼贴()
// //
@@ -254,7 +254,7 @@ ok.onClick = function () {
// //
var 导入文件夹路径 =new Folder (edittext1.text); var 导入文件夹路径 =new Folder (edittext1.text);
var 文档宽度厘米 = Number(edittext2.text); var 文档宽度厘米 = Number(edittext2.text);
var 文档高度厘米 = 300; // 50 var 文档高度厘米 = 600; // 50
var 分辨率 = 200; // 150 DPI var 分辨率 = 200; // 150 DPI
// //
@@ -365,6 +365,3 @@ alert("拼贴完成")
dialog.show(); dialog.show();
} }
"""

View File

@@ -1,10 +1,8 @@
dxf27_jscode = """ 
新的米样缩放()
@@ -497,4 +495,3 @@ dialog.show();
} }
"""

View File

@@ -0,0 +1,361 @@
// 设置单位为像素
var dialog = new Window("dialog");
dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "幅宽(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
var dialog = new Window("dialog");
dialog.text = "SO自动米样拼贴";
dialog.orientation = "row";
dialog.alignChildren = ["left","top"];
dialog.spacing = 10;
dialog.margins = 16;
// GROUP1
// ======
var group1 = dialog.add("group", undefined, {name: "group1"});
group1.orientation = "column";
group1.alignChildren = ["fill","top"];
group1.spacing = 10;
group1.margins = 0;
// PANEL1
// ======
var panel1 = group1.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = "源图像";
panel1.preferredSize.width = 388;
panel1.preferredSize.height = 205;
panel1.orientation = "column";
panel1.alignChildren = ["left","top"];
panel1.spacing = 10;
panel1.margins = 10;
var statictext1 = panel1.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = "使用:";
// GROUP2
// ======
var group2 = panel1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left","center"];
group2.spacing = 10;
group2.margins = 0;
var button1 = group2.add("button", undefined, undefined, {name: "button1"});
button1.text = "选取";
var edittext1 = group2.add('edittext {properties: {name: "edittext1"}}');
edittext1.text = "[选择图像文件夹]";
edittext1.preferredSize.width = 300;
// PANEL2
// ======
var panel2 = group1.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = "SO小样文档拼贴";
panel2.preferredSize.height = 160;
panel2.orientation = "column";
panel2.alignChildren = ["left","top"];
panel2.spacing = 10;
panel2.margins = 10;
var statictext2 = panel2.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = "设定:";
// GROUP3
// ======
var group3 = panel2.add("group", undefined, {name: "group3"});
group3.orientation = "row";
group3.alignChildren = ["left","center"];
group3.spacing = 10;
group3.margins = 0;
var statictext3 = group3.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = "幅宽(cm):";
var edittext2 = group3.add('edittext {properties: {name: "edittext2"}}');
edittext2.preferredSize.width = 100;
// GROUP4
// ======
var group4 = panel2.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left","center"];
group4.spacing = 10;
group4.margins = 0;
// GROUP5
// ======
var group5 = group1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left","center"];
group5.spacing = 10;
group5.margins = 0;
// PANEL3
// ======
// GROUP7
// ======
var group7 = dialog.add("group", undefined, {name: "group7"});
group7.orientation = "column";
group7.alignChildren = ["fill","top"];
group7.spacing = 10;
group7.margins = 0;
var ok = group7.add("button", undefined, undefined, {name: "ok"});
ok.text = "确认";
var cancel = group7.add("button", undefined, undefined, {name: "cancel"});
cancel.text = "取消";
button1.onClick = function () {
// 打开文件夹选择对话框
var selectedFolder = Folder.selectDialog("选择图像文件夹");
// 检查用户是否取消了选择
if (selectedFolder) {
// 将选择的文件夹路径显示在输入框中
edittext1.text = selectedFolder.fsName;
}
};
ok.onClick = function () {
app.preferences.rulerUnits = Units.PIXELS;
//function 创建拼贴图(导入文件夹路径, 文档宽度厘米) {
// 假设这些值是从某处获取或者用户输入的
var 导入文件夹路径 =new Folder (edittext1.text);
var 文档宽度厘米 = Number(edittext2.text);
var 文档高度厘米 = 300; // 例如50厘米
var 分辨率 = 150; // 分辨率设置为150 DPI
// 转换厘米到像素
var 厘米到像素的转换系数 = 2.54;
var 文档宽度像素 = 文档宽度厘米 / 厘米到像素的转换系数 * 分辨率;
var 文档高度像素 = 文档高度厘米 / 厘米到像素的转换系数 * 分辨率;
var 导入文件夹 = new Folder(导入文件夹路径);
var 文件列表 = 导入文件夹.getFiles("*.*");
var 文件名数组 = new Array();
var 文件宽度数组 = new Array();
var 文件高度数组 = new Array();
// 读取文件列表并获取文件信息
for (var 索引1 = 0; 索引1 < 文件列表.length; 索引1++) {
var 文档 = app.open(文件列表[索引1]);
文件名数组[索引1] = 文档.fullName;
文件宽度数组[索引1] = parseInt(文档.width);
文件高度数组[索引1] = parseInt(文档.height);
文档.close();
}
// 根据高度对文件进行排序
for (var 索引1 = 文件高度数组.length - 1; 索引1 > 0; --索引1) {
for (var 索引2 = 0; 索引2 < 索引1; ++索引2) {
if (文件高度数组[索引2] > 文件高度数组[索引2 + 1])
交换数组元素(索引2, 索引2 + 1);
}
}
function 交换数组元素(索引1, 索引2) {
var 临时 = 文件高度数组[索引1];
文件高度数组[索引1] = 文件高度数组[索引2];
文件高度数组[索引2] = 临时;
var 临时2 = 文件宽度数组[索引1];
文件宽度数组[索引1] = 文件宽度数组[索引2];
文件宽度数组[索引2] = 临时2;
var 临时3 = 文件名数组[索引1];
文件名数组[索引1] = 文件名数组[索引2];
文件名数组[索引2] = 临时3;
}
// 创建新文档并添加图片
var 新文档 = app.documents.add(文档宽度像素, 文档高度像素, 分辨率, "拼贴", NewDocumentMode.CMYK, DocumentFill.WHITE);
var 当前顶部 = 0;
var 当前左侧 = 0;
for (var 索引1 = 0; 索引1 < 文件名数组.length; 索引1++) {
var 文档 = app.open(文件名数组[索引1]);
文档.selection.selectAll();
文档.selection.copy();
文档.close();
if ((当前左侧 + 文件宽度数组[索引1]) > 文档宽度像素) {
当前左侧 = 0;
当前顶部 += 文件高度数组[索引1 - 1];
}
var 边界 = [
[当前左侧, 当前顶部],
[当前左侧 + 文件宽度数组[索引1], 当前顶部],
[当前左侧 + 文件宽度数组[索引1], 当前顶部 + 文件高度数组[索引1]],
[当前左侧, 当前顶部 + 文件高度数组[索引1]]
];
新文档.selection.select(边界, SelectionType.REPLACE, 0, true);
新文档.paste();
当前左侧 += 文件宽度数组[索引1];
}
// 选择输出文件夹并保存图片
var 输出文件夹 = new Folder(edittext1.text + "/拼贴");
if (!输出文件夹.exists) 输出文件夹.create();
// 设置TIFF保存选项
var tiffSaveOptions = new TiffSaveOptions();
tiffSaveOptions.imageCompression = TIFFEncoding.TIFFLZW; // 使用LZW压缩
tiffSaveOptions.byteOrder = ByteOrder.IBM; // 设置字节顺序为IBM大端序
// 定义保存的文件路径和名称
var 文件 = new File(输出文件夹 + "/combined.tif");
// 保存当前文档为TIFF格式
新文档.saveAs(文件, tiffSaveOptions, true, Extension.LOWERCASE);
裁切透明像素();
// 关闭文档,不保存更改
//新文档.close(SaveOptions.DONOTSAVECHANGES);
dialog.close();
alert("拼贴完成")
}
function 裁切透明像素() {
var 文档 = app.activeDocument; // 获取当前活动的文档
// 裁切透明像素
// TrimType.TOPLEFT 表示从图像的左上角开始裁切
// 第二个参数 true 表示裁切顶部的透明像素
// 第三个参数 true 表示裁切左侧的透明像素
// 第四个参数 true 表示裁切底部的透明像素
// 第五个参数 true 表示裁切右侧的透明像素
文档.trim(TrimType.TOPLEFT, true, true, true, true);
}
dialog.show();

View File

@@ -1,7 +1,7 @@
dxf23_jscode = """ 
自动连晒()
@@ -471,4 +471,3 @@ dialog.show();
"""

View File

@@ -0,0 +1,49 @@

function 自由变换(水平位置, 垂直位置, 水平偏移, 垂直偏移, 宽度百分比, 高度百分比, 保持宽高比, 插值方法,角度) {
try {
var 描述符 = new ActionDescriptor();
var 引用 = new ActionReference();
// 引用当前选中的图层
引用.putEnumerated(stringIDToTypeID("layer"), stringIDToTypeID("ordinal"), stringIDToTypeID("targetEnum"));
描述符.putReference(stringIDToTypeID("null"), 引用);
// 设置自由变换的中心状态为独立
描述符.putEnumerated(stringIDToTypeID("freeTransformCenterState"), stringIDToTypeID("quadCenterState"), stringIDToTypeID("QCSIndependent"));
// 设置位置
var 位置描述符 = new ActionDescriptor();
位置描述符.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 水平位置);
位置描述符.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 垂直位置);
描述符.putObject(stringIDToTypeID("position"), stringIDToTypeID("point"), 位置描述符);
// 设置偏移
var 偏移描述符 = new ActionDescriptor();
偏移描述符.putUnitDouble(stringIDToTypeID("horizontal"), stringIDToTypeID("pixelsUnit"), 水平偏移);
偏移描述符.putUnitDouble(stringIDToTypeID("vertical"), stringIDToTypeID("pixelsUnit"), 垂直偏移);
描述符.putObject(stringIDToTypeID("offset"), stringIDToTypeID("offset"), 偏移描述符);
// 设置宽度和高度
描述符.putUnitDouble(stringIDToTypeID("width"), stringIDToTypeID("percentUnit"), 宽度百分比);
描述符.putUnitDouble(stringIDToTypeID("height"), stringIDToTypeID("percentUnit"), 高度百分比);
// 设置是否保持宽高比
描述符.putBoolean(stringIDToTypeID("linked"), 保持宽高比);
// 设置插值方法
描述符.putEnumerated(charIDToTypeID("Intr"), stringIDToTypeID("interpolationType"), stringIDToTypeID(插值方法));
描述符.putUnitDouble(stringIDToTypeID("angle"), stringIDToTypeID("angleUnit"), 角度);
// 执行变换
executeAction(stringIDToTypeID("transform"), 描述符, DialogModes.NO);
} catch (e) {
if (e.number != 8007) {
alert("行号: " + e.line + e, "错误", true);
throw(e);
}
}
// 调用示例
自由变换(水平位置, 垂直位置, 水平偏移, 垂直偏移, 宽度百分比, 高度百分比, true, 插值方法,角度);

View File

@@ -0,0 +1,28 @@
// 创建一个文件对象指向桌面上的子图层名称.json文件
var desktop = Folder.desktop;
var file = new File(desktop + "/名称数据.json");
// 打开文件以进行读取
if (file.open('r')) {
var content = file.read();
file.close();
// 从JSON格式的字符串中删除前后的方括号并根据逗号分割为数组
var 名称数组 = content.replace(/^\s*\[|\]\s*$/g, '').split(/"\s*,\s*"/);
// 循环遍历数组并逐个弹出名称
for (var i = 0; i < 名称数组.length; i++) {
// 从每个名称中删除前后的双引号
var 名称 = 名称数组[i].replace(/^"|"$/g, '');
// 弹出整个名称
// alert("完整名称: " + 名称);
// 使用正则表达式提取括号内的内容
var matches = 名称.match(/\(([^)]+)\)/);
var 图案名称=matches [1]
//alert("图案名称: " + 图案名称);
}
} else {
alert("无法打开文件!");
}

View File

@@ -0,0 +1,50 @@
// 检查是否有活动文档
if (app.documents.length > 0) {
var doc = app.activeDocument; // 获取活动文档
// 递归函数来构建所有图层的字符串表示
function buildLayersString(layerSet, indent) {
var result = "";
var currentIndent = Array(indent + 1).join(" "); // 根据缩进级别构建缩进字符串
for (var i = 0; i < layerSet.layers.length; i++) {
var layer = layerSet.layers[i];
var layerName = layer.name.replace(/"/g, '\\"'); // 转义引号
if (layer.typename === 'ArtLayer') {
// 构建图层的字符串表示
result += '\n' + currentIndent + '\"图层: ' + layerName + '\": null';
} else { // LayerSet
// 构建图层组的字符串表示,并递归处理子图层
var childResult = buildLayersString(layer, indent + 1);
if (childResult !== "") {
result += '\n' + currentIndent + '\"图层组: ' + layerName + '\": {' + childResult + '}';
}
}
if (i < layerSet.layers.length - 1) {
result += ','; // 如果不是最后一个图层或图层组,添加逗号
}
}
return result;
}
var layersString = "{" + buildLayersString(doc, 0) + "\n}";
// 删除最后一个逗号
layersString = layersString.replace(/,\s*$/, '');
// 创建文件并写入数据
var desktopPath = Folder.desktop.fsName; // 获取桌面路径
var filePath = desktopPath + "/photoshop_layers.json"; // 文件完整路径
var file = new File(filePath);
// 打开文件进行写入并使用GBK编码
file.open("w");
file.encoding = "GBK";
file.write(layersString);
file.close();
alert("文件已保存至桌面: photoshop_layers.json");
} else {
alert("没有打开的文档");
}

View File

@@ -0,0 +1,44 @@
// 确保Photoshop中有打开的文档
if (app.documents.length == 0) {
$.writeln("没有打开的文档!");
} else {
// 获取当前激活的文档
var doc = app.activeDocument;
// 指定要遍历的图层组名称
var targetLayerSetName = "组 1"; // 请替换为您的图层组名称
// 查找并遍历指定的图层组
var targetLayerSet = findLayerSet(doc, targetLayerSetName);
if (targetLayerSet != null) {
traverseLayers(targetLayerSet, "");
} else {
$.writeln("未找到名为 '" + targetLayerSetName + "' 的图层组!");
}
}
// 函数:查找指定名称的图层组
function findLayerSet(doc, name) {
for (var i = 0; i < doc.layerSets.length; i++) {
if (doc.layerSets[i].name == name) {
return doc.layerSets[i];
}
}
return null;
}
// 函数:遍历图层
function traverseLayers(layerSet, indent) {
// 遍历图层组中的所有图层
for (var i = 0; i < layerSet.layers.length; i++) {
var layer = layerSet.layers[i];
// 在控制台打印图层名称及其在图层组中的位置
$.writeln(indent + layer.name);
// 如果是图层组,递归遍历该组内的图层
if (layer.typename == "LayerSet") {
traverseLayers(layer, indent + " ");
}
}
}

32
PltService/Dockerfile Normal file
View File

@@ -0,0 +1,32 @@
# PLT 处理微服务 Dockerfile
# 用于部署到阿里云 SAE
FROM python:3.10-slim
# 设置工作目录
WORKDIR /app
# 安装系统依赖OpenCV 需要)
RUN apt-get update && apt-get install -y \
libgl1-mesa-glx \
libglib2.0-0 \
libsm6 \
libxext6 \
libxrender-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/
# 复制代码
COPY pltreader.py .
COPY main.py .
# 暴露端口SAE 默认使用 8080
EXPOSE 8080
# 启动命令
CMD ["python", "main.py"]

112
PltService/README.md Normal file
View File

@@ -0,0 +1,112 @@
# PLT 裁片处理微服务
独立的 PLT 文件处理服务,可部署到阿里云 SAE。
## 本地运行
```bash
# 安装依赖
pip install -r requirements.txt
# 启动服务
python main.py
```
服务启动后访问http://localhost:8080
## Docker 构建
```bash
# 构建镜像
docker build -t plt-service:latest .
# 运行容器
docker run -p 8080:8080 plt-service:latest
```
## 部署到阿里云 SAE
### 1. 构建并推送镜像到阿里云容器镜像服务
```bash
# 登录阿里云容器镜像服务
docker login --username=<你的阿里云账号> registry.cn-hangzhou.aliyuncs.com
# 构建镜像
docker build -t registry.cn-hangzhou.aliyuncs.com/<命名空间>/plt-service:v1.0 .
# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/<命名空间>/plt-service:v1.0
```
### 2. 在 SAE 创建应用
1. 进入阿里云 SAE 控制台
2. 创建应用 → 选择"镜像部署"
3. 填写镜像地址:`registry.cn-hangzhou.aliyuncs.com/<命名空间>/plt-service:v1.0`
4. 配置规格:
- CPU: 2核
- 内存: 4GB
- 最小实例数: 0无请求时不收费
- 最大实例数: 5
5. 完成创建
### 3. 配置公网访问
在 SAE 应用详情 → 基本信息 → SLB 设置 → 添加公网 SLB
## API 接口
### 健康检查
```
GET /health
```
### 处理 PLT 文件
```
POST /process
Content-Type: multipart/form-data
参数:
- file: PLT 文件
- size_labels: 尺码标签,如 ["S","M","L","XL","2XL"]
- dpi: 输出分辨率(默认 150
- rotation: 旋转角度0/90/-90/180
```
响应示例:
```json
{
"success": true,
"total_groups": 5,
"groups": [
{
"group_id": 1,
"pieces": [
{
"size": "S",
"image_base64": "data:image/png;base64,...",
"width_px": 500,
"height_px": 300,
"width_cm": 25.5,
"height_cm": 15.3,
"center_x_cm": 12.75,
"center_y_cm": 7.65,
"left_cm": 0,
"top_cm": 0
}
]
}
]
}
```
## 费用估算(阿里云 SAE
| 场景 | 费用 |
|------|------|
| 处理 1 个 PLT30秒 | ¥0.04 |
| 每天 100 个 PLT | ¥4/天 |
| 无请求时 | ¥0最小实例设为0 |

261
PltService/main.py Normal file
View File

@@ -0,0 +1,261 @@
# -*- coding: utf-8 -*-
"""
PLT 裁片处理微服务
独立部署到阿里云 SAE
"""
import os
import io
import base64
import json
from typing import List, Optional
from fastapi import FastAPI, UploadFile, File, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
import numpy as np
import cv2
from PIL import Image
from shapely import affinity
from scipy.optimize import linear_sum_assignment
from pltreader import PltReader
app = FastAPI(title="PLT Processing Service")
# CORS 配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ==================== 数据模型 ====================
class PieceInfo(BaseModel):
"""单个裁片信息"""
size: str
image_base64: str
width_px: int
height_px: int
width_cm: float
height_cm: float
center_x_cm: float
center_y_cm: float
left_cm: float
top_cm: float
class GroupInfo(BaseModel):
"""裁片分组信息"""
group_id: int
pieces: List[PieceInfo]
class ProcessPltResponse(BaseModel):
"""API 响应"""
success: bool
total_groups: int
groups: List[GroupInfo]
# ==================== 辅助函数 ====================
def parse_plt_file(file_content: str, tolerance: int = 10):
"""解析 PLT 文件内容"""
reader = PltReader(io.StringIO(file_content))
output = reader.get_output(tolerance=tolerance)
return output
def apply_rotation_to_image(pil_img: Image.Image, rotation: int) -> Image.Image:
"""应用旋转变换"""
if rotation == 90:
return pil_img.rotate(-90, expand=True)
elif rotation == -90:
return pil_img.rotate(90, expand=True)
elif rotation == 180:
return pil_img.rotate(180, expand=True)
return pil_img
def calculate_piece_coordinates(polygon, bounds, plt_to_cm, rotation=0):
"""计算裁片坐标(厘米单位)"""
min_x, min_y, max_x, max_y = bounds
centroid = polygon.centroid
orig_center_x = (centroid.x - min_x) * plt_to_cm
orig_center_y = (centroid.y - min_y) * plt_to_cm
piece_bounds = polygon.bounds
orig_piece_width = (piece_bounds[2] - piece_bounds[0]) * plt_to_cm
orig_piece_height = (piece_bounds[3] - piece_bounds[1]) * plt_to_cm
orig_canvas_width = (max_x - min_x) * plt_to_cm
orig_canvas_height = (max_y - min_y) * plt_to_cm
if rotation == 90:
center_x = orig_center_y
center_y = orig_canvas_width - orig_center_x
piece_width, piece_height = orig_piece_height, orig_piece_width
elif rotation == -90:
center_x = orig_canvas_height - orig_center_y
center_y = orig_center_x
piece_width, piece_height = orig_piece_height, orig_piece_width
elif rotation == 180:
center_x = orig_canvas_width - orig_center_x
center_y = orig_canvas_height - orig_center_y
piece_width, piece_height = orig_piece_width, orig_piece_height
else:
center_x, center_y = orig_center_x, orig_center_y
piece_width, piece_height = orig_piece_width, orig_piece_height
left_cm = center_x - piece_width / 2
top_cm = center_y - piece_height / 2
return {
"center_x_cm": round(center_x, 2),
"center_y_cm": round(center_y, 2),
"left_cm": round(left_cm, 2),
"top_cm": round(top_cm, 2),
"width_cm": round(piece_width, 2),
"height_cm": round(piece_height, 2)
}
# ==================== API 接口 ====================
@app.get("/health")
async def health_check():
"""健康检查"""
return {"status": "healthy", "service": "plt-processor"}
@app.post("/process", response_model=ProcessPltResponse)
async def process_plt(
file: UploadFile = File(..., description="PLT 文件"),
size_labels: str = Form(..., description="尺码标签 JSON 数组"),
dpi: int = Form(150, description="输出图片分辨率DPI"),
rotation: int = Form(0, description="强制旋转角度 (0/90/-90/180)")
):
"""
PLT 裁片处理接口
"""
try:
# 1. 解析参数
try:
size_labels_list = json.loads(size_labels)
except:
raise HTTPException(status_code=400, detail="size_labels 格式错误")
size_num = len(size_labels_list)
scale_factor = dpi / 1016
plt_to_cm = 2.54 / 1016
# 2. 读取文件
file_content = (await file.read()).decode('utf-8', errors='ignore')
# 3. 解析 PLT
print(f"[PLT] 正在处理文件: {file.filename}")
output = parse_plt_file(file_content)
# 4. 获取尺码聚类
clusters = output.get_single_size_info(size_num=size_num)
# 5. 建立匹配关系
base_cluster = [piece for piece in clusters[0] if piece["parent"] is None]
match_result = {piece["index"]: [piece["index"]] for piece in base_cluster}
for size_index in range(1, len(clusters)):
compare_cluster = [piece for piece in clusters[size_index] if piece["parent"] is None]
cost_matrix = np.zeros((len(base_cluster), len(compare_cluster)))
for i in range(len(base_cluster)):
for j in range(len(compare_cluster)):
poly1 = base_cluster[i]["data"]
poly2 = compare_cluster[j]["data"]
poly1 = affinity.translate(poly1, xoff=-poly1.centroid.x, yoff=-poly1.centroid.y)
poly2 = affinity.translate(poly2, xoff=-poly2.centroid.x, yoff=-poly2.centroid.y)
target_area = 10000
scale1 = (target_area / poly1.area) ** 0.5
scale2 = (target_area / poly2.area) ** 0.5
poly1 = affinity.scale(poly1, xfact=scale1, yfact=scale1, origin=(0, 0))
poly2 = affinity.scale(poly2, xfact=scale2, yfact=scale2, origin=(0, 0))
dist_min = float('inf')
for angle in [0, 90, 180, 270]:
rotated = affinity.rotate(poly2, angle, origin='centroid')
dist = poly1.hausdorff_distance(rotated)
if dist < dist_min:
dist_min = dist
cost_matrix[i, j] = dist_min
row_ind, col_ind = linear_sum_assignment(cost_matrix)
for i, j in zip(row_ind, col_ind):
base_index = base_cluster[i]['index']
matched_index = compare_cluster[j]['index']
match_result[base_index].append(matched_index)
# 6. 生成图片
all_nodes = {node["index"]: node for node in output.nodes}
from shapely.geometry import MultiPolygon as ShapelyMultiPolygon
all_polygons = [node["data"] for node in output.nodes]
global_bounds = ShapelyMultiPolygon(all_polygons).bounds
groups = []
for group_id, (base_index, matched_indices) in enumerate(match_result.items(), start=1):
pieces = []
for size_idx, piece_index in enumerate(matched_indices):
size_label = size_labels_list[size_idx]
node = all_nodes[piece_index]
nodes_to_draw = [node] + node['child']
img = output._draw_nodes(nodes_to_draw, scale_factor, show_id=False)
img_rgba = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
pil_img = Image.fromarray(img_rgba)
pil_img = apply_rotation_to_image(pil_img, rotation)
buffer = io.BytesIO()
pil_img.save(buffer, format='PNG', dpi=(dpi, dpi))
buffer.seek(0)
base64_str = base64.b64encode(buffer.read()).decode('utf-8')
image_base64 = f"data:image/png;base64,{base64_str}"
coords = calculate_piece_coordinates(
node["data"],
global_bounds,
plt_to_cm,
rotation
)
piece_info = PieceInfo(
size=size_label,
image_base64=image_base64,
width_px=pil_img.width,
height_px=pil_img.height,
**coords
)
pieces.append(piece_info)
groups.append(GroupInfo(group_id=group_id, pieces=pieces))
print(f"[PLT] 处理完成,共 {len(groups)} 组裁片")
return ProcessPltResponse(
success=True,
total_groups=len(groups),
groups=groups
)
except Exception as e:
print(f"[PLT] 处理失败: {str(e)}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", 8080))
uvicorn.run(app, host="0.0.0.0", port=port)

620
PltService/pltreader.py Normal file
View File

@@ -0,0 +1,620 @@
'''
参考资料:
https://www.gnu.org/software/hp2xx/hp2xx.html
https://zhuanlan.zhihu.com/p/622090369
'''
import os
import typing
import string
import re
import warnings
from enum import Enum
import shapely
import shapely.ops
from shapely.geometry import LineString, Polygon, MultiPolygon, MultiLineString
import numpy as np
import cv2
import itertools
import logging
from shapely.validation import make_valid
SHOW_PLT_WARNINGS = False
# 如果没配置日志则配置日志
if not logging.getLogger().handlers:
logging.basicConfig(level=logging.INFO, format='[%(asctime)s][%(name)s][%(levelname)s] %(message)s')
def assert_warning(condition, message):
if SHOW_PLT_WARNINGS and (not condition):
logging.warning(message)
class PltCommand(Enum):
IN = 0
PU = 1
PD = 2
PA = 3
AbstractSyntaxTree = typing.List[typing.Dict]
class VirtualMachineOutput(object):
'''虚拟机执行后的结果'''
MIN_FILTER_AREA_FACTOR = 0.0068 ** 2
MAX_FILTER_AREA_FACTOR = 0.8 ** 2
def __init__(self, polygons :typing.List[Polygon], dispensable: typing.List[LineString]) -> None:
for poly in polygons:
assert isinstance(poly, Polygon)
for line in dispensable:
assert isinstance(line, LineString)
# 过滤结果
# 计算画布大小
min_x, min_y, max_x, max_y = MultiPolygon(polygons).bounds
filter_polygons = []
for polygon in polygons:
if (max_x - min_x) * (max_y - min_y) * VirtualMachineOutput.MIN_FILTER_AREA_FACTOR < polygon.area < (max_x - min_x) * (max_y - min_y) * VirtualMachineOutput.MAX_FILTER_AREA_FACTOR:
filter_polygons.append(polygon)
else:
boundary = polygon.boundary
if isinstance(boundary, LineString):
dispensable.append(boundary)
elif isinstance(boundary, MultiLineString):
dispensable.extend(list(boundary.geoms))
logging.info(f"过滤了{len(polygons) - len(filter_polygons)}个多边形")
polygons = filter_polygons
# 计算多边形的包含关系,构建多边形树
self.nodes = [{"data":polygon, "child":[], "parent":None, "index":index} for index, polygon in enumerate(polygons)]
self.dispensable = dispensable
# 找每个node的最佳parent
for node1 in self.nodes:
best_parent = None
for node2 in self.nodes:
if node1 == node2:
continue
if node1["index"] == 8 and node2["index"] == 9:
pass
# 判断是否包含
#if node2["data"].contains(node1["data"]):
if node2["data"].area > node1["data"].area:
intersection = node2["data"].intersection(node1["data"])
if intersection.area > node1["data"].area * 0.99:
if best_parent is None:
best_parent = node2
else:
if best_parent["data"].contains(node2["data"]):
best_parent = node2
node1["parent"] = best_parent
# 填充child
self.tree = []
for node in self.nodes:
if node["parent"] is not None:
node["parent"]["child"].append(node)
else:
self.tree.append(node)
# 构建广度优先遍历
self.bfs = self.tree.copy()
index = 0
while index < len(self.bfs):
self.bfs.extend(self.bfs[index]["child"])
index += 1
def _draw_nodes(self, nodes: typing.List, scale_factor: float, show_id: bool = True) -> np.ndarray:
# 计算画布大小
min_x, min_y, max_x, max_y = MultiPolygon([node["data"] for node in nodes]).bounds
max_x = int(np.ceil(max_x * scale_factor))
max_y = int(np.ceil(max_y * scale_factor))
min_x = int(np.floor(min_x * scale_factor))
min_y = int(np.floor(min_y * scale_factor))
max_x -= min_x
max_y -= min_y
#print("画布大小", max_x, max_y)
result_img = np.zeros((max_y, max_x, 4), dtype=np.uint8)
nodes = sorted(nodes, key=lambda node: self.bfs.index(node))
for node in nodes:
pts = (np.asarray(node["data"].exterior.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
pts[:,1] = max_y - pts[:,1]
cv2.fillPoly(result_img, [pts], (255,255,255,255))
cv2.polylines(result_img, [pts], True, (0,0,0,255), 2)
# 画无关紧要的东西
multiPolygon = MultiPolygon([node["data"] for node in nodes])
multiPolygon = make_valid(multiPolygon.buffer(0))
for shape in self.dispensable:
if multiPolygon.contains(shape):
pts = (np.asarray(shape.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
pts[:,1] = max_y - pts[:,1]
cv2.polylines(result_img, [pts], False, (0,0,0,255), 1)
# 画parent为None的id
if show_id:
for node in nodes:
if node["parent"] is None:
centroid = node["data"].centroid
text_pos = (int((centroid.x * scale_factor) - min_x), int(max_y - (centroid.y * scale_factor) - min_y))
cv2.putText(result_img, str(node["index"]), text_pos, cv2.FONT_HERSHEY_SIMPLEX, 3, (255,0,0,255), 5)
return result_img
def full_sheet(self, scale_factor: float = 0.1) -> np.ndarray:
'''整幅输出'''
return self._draw_nodes(self.bfs, scale_factor)
def debug_full_sheet(self, scale_factor: float = 0.1) -> np.ndarray:
"""
用随机颜色画出所有多边形和被过滤掉的线段
"""
# 计算画布大小画布大小应该考虑dispensable
all_shapes = [node["data"] for node in self.nodes] + self.dispensable
temp = []
for shape in all_shapes:
if isinstance(shape, LineString):
convex_hull = shape.convex_hull
if isinstance(convex_hull, Polygon):
temp.append(convex_hull)
else:
temp.append(shape)
min_x, min_y, max_x, max_y = MultiPolygon(temp).bounds
max_x = int(np.ceil(max_x * scale_factor))
max_y = int(np.ceil(max_y * scale_factor))
min_x = int(np.floor(min_x * scale_factor))
min_y = int(np.floor(min_y * scale_factor))
max_x -= min_x
max_y -= min_y
result_img = np.zeros((max_y, max_x, 4), dtype=np.uint8)
# 画多边形
for node in self.nodes:
#overlay = result_img.copy()
color = tuple(np.random.randint(0,256,size=3).tolist() + [255])
pts = (np.asarray(node["data"].exterior.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
pts[:,1] = max_y - pts[:,1]
cv2.fillPoly(result_img, [pts], color)
if node["parent"] is None:
cv2.polylines(result_img, [pts], True, (0,0,255,255), 1)
else:
cv2.polylines(result_img, [pts], True, (0,0,0,255), 1)
#alpha = 0.5
#cv2.addWeighted(overlay, alpha, result_img, 1 - alpha, 0, result_img)
# 画被过滤掉的线段
for shape in self.dispensable:
#overlay = result_img.copy()
color = tuple(np.random.randint(0,256,size=3).tolist() + [255])
pts = (np.asarray(shape.coords) * scale_factor - [min_x, min_y]).astype(np.int32)
pts[:,1] = max_y - pts[:,1]
cv2.polylines(result_img, [pts], False, color, 1)
#cv2.addWeighted(overlay, alpha, result_img, 1 - alpha, 0, result_img)
# 画parent为None的id
for node in self.nodes:
if node["parent"] is None:
centroid = node["data"].centroid
text_pos = (int((centroid.x * scale_factor) - min_x), int(max_y - (centroid.y * scale_factor) - min_y))
cv2.putText(result_img, str(node["index"]), text_pos, cv2.FONT_HERSHEY_SIMPLEX, 3, (0,0,255,255), 5)
return result_img
def _distance_between_cluster(self, cluster1, cluster2):
min_distance = float("inf")
for node1 in cluster1:
for node2 in cluster2:
assert isinstance(node1["data"], Polygon)
assert isinstance(node2["data"], Polygon)
d = node1["data"].distance(node2["data"])
if d < min_distance:
min_distance = d
return min_distance
def get_single_size_info(self, size_num: int) -> typing.List[typing.List]:
'''获取单码信息'''
logging.info(f"开始进行距离聚类,目标聚类数目: {size_num}")
# 计算所有多边形两两之间的距离
distances: list[tuple[float, int, int]] = []
for i in range(len(self.nodes)):
for j in range(i + 1, len(self.nodes)):
poly1 = self.nodes[i]["data"]
poly2 = self.nodes[j]["data"]
dist = poly1.distance(poly2)
distances.append((dist, i, j))
distances.sort(key=lambda x: x[0])
# 使用并查集进行聚类
parent = list(range(len(self.nodes)))
def find(i):
if parent[i] != i:
parent[i] = find(parent[i]) # 路径压缩
return parent[i]
def union(i, j):
root_i = find(i)
root_j = find(j)
parent[root_i] = root_j
return root_i != root_j
# 进行聚类直到聚类数目达到size_num
current_cluster_count = len(self.nodes)
for _, i, j in distances:
if current_cluster_count <= size_num:
break
if union(i, j):
current_cluster_count -= 1
# 构建聚类结果
clusters_dict = {}
for i in range(len(self.nodes)):
root = find(i)
if root not in clusters_dict:
clusters_dict[root] = []
clusters_dict[root].append(self.nodes[i])
clusters = list(clusters_dict.values())
logging.info(f"距离聚类得到{len(clusters)}个聚类")
# 排序
sorted_clusters = sorted(clusters, key=lambda x: min([node["index"] for node in x]))
return list(sorted_clusters)
def single_size(self, size_num: int, scale_factor: float = 0.1) -> typing.List[np.ndarray]:
'''单码输出'''
sorted_clusters = self.get_single_size_info(size_num)
result = []
for cluster in sorted_clusters:
result.append(self._draw_nodes(cluster, scale_factor))
return result
def single_piece(self, scale_factor: float = 0.1) -> typing.List[np.ndarray]:
'''单片输出'''
result = []
for node in self.nodes:
# 如果是顶层则新建一副图像
if node["parent"] is None:
nodes = [node] + node['child']
result.append(self._draw_nodes(nodes, scale_factor))
return result
class _VirtualMachine(object):
'''运行PLT文件的虚拟机'''
def __init__(self, tolerance) -> None:
self.tolerance = tolerance
def __run_v2(self, abstract_syntax_tree: AbstractSyntaxTree) -> VirtualMachineOutput:
current_x, current_y = 0, 0
pen_state = 'UP'
raw_segments: typing.List[typing.List[typing.Tuple[float, float]]] = []
current_path: typing.List[typing.Tuple[float, float]] = []
for code in abstract_syntax_tree:
try:
if code["cmd"] == PltCommand.IN:
logging.info("初始化")
elif code["cmd"] == PltCommand.PA:
new_x = float(code["args"][0])
new_y = float(code["args"][1])
if pen_state == 'DOWN':
if not current_path:
current_path.append((current_x, current_y))
current_path.append((new_x, new_y))
elif pen_state == 'UP':
pass
# 更新当前位置
current_x, current_y = new_x, new_y
elif code["cmd"] == PltCommand.PU:
pen_state = 'UP'
if current_path:
assert len(current_path) > 1 and len(set(current_path)) > 1
raw_segments.append(current_path)
current_path = []
elif code["cmd"] == PltCommand.PD:
pen_state = 'DOWN'
except Exception as e:
assert_warning(False, f"[code {code}] 执行指令异常: {e}")
# 处理最后一条路径
if current_path:
assert len(current_path) > 1 and len(set(current_path)) > 1
raw_segments.append(current_path)
current_path = []
merged_geometry = shapely.ops.linemerge(raw_segments)
final_lines: typing.List[LineString] = []
if isinstance(merged_geometry, LineString):
final_lines.append(merged_geometry)
elif isinstance(merged_geometry, MultiLineString):
final_lines.extend(merged_geometry.geoms)
return self.__build_polygons_and_dispensable(final_lines)
def run(self, abstract_syntax_tree: AbstractSyntaxTree, version: int = 2) -> VirtualMachineOutput:
if version == 1:
logging.warning("版本1已过时 请使用版本2")
return self.__run_v1(abstract_syntax_tree)
elif version == 2:
return self.__run_v2(abstract_syntax_tree)
else:
raise ValueError(f"不支持的版本号: {version}")
def __run_v1(self, abstract_syntax_tree: AbstractSyntaxTree) -> VirtualMachineOutput:
context = abstract_syntax_tree.copy()
current_x = 0
current_y = 0
self.pen_state = 'UP'
lines :typing.List[typing.List[typing.Tuple]] = []
lines_flag :typing.List[int] = []
while len(context) != 0:
code = context[0]
try:
if code["cmd"] == PltCommand.IN:
logging.info("初始化")
elif code["cmd"] == PltCommand.PA:
if self.pen_state == 'UP':
pass
elif self.pen_state == 'DOWN':
new_x = float(code["args"][0])
new_y = float(code["args"][1])
# 判断是否与已有的线相连接
for i in range(len(lines) - 1, -1, -1):
if lines_flag[i] == 1:
continue
if lines[i][0] == (current_x, current_y):
lines[i].insert(0, (new_x, new_y))
if lines[i][0] == lines[i][-1]:
lines_flag[i] = 1
break
elif lines[i][0] == (new_x, new_y):
lines[i].insert(0, (current_x, current_y))
if lines[i][0] == lines[i][-1]:
lines_flag[i] = 1
break
elif lines[i][-1] == (current_x, current_y):
lines[i].append((new_x, new_y))
if lines[i][0] == lines[i][-1]:
lines_flag[i] = 1
break
elif lines[i][-1] == (new_x, new_y):
lines[i].append((current_x, current_y))
if lines[i][0] == lines[i][-1]:
lines_flag[i] = 1
break
else:
# 不与已有的线相连接
lines.append([(current_x, current_y), (new_x, new_y)])
lines_flag.append(0)
current_x = float(code["args"][0])
current_y = float(code["args"][1])
elif code["cmd"] == PltCommand.PU:
self.pen_state = 'UP'
elif code["cmd"] == PltCommand.PD:
self.pen_state = 'DOWN'
except Exception as e:
assert_warning(False, f"[code {code}] 执行指令异常: {e}")
finally:
context = context[1:]
# 创建LineString :typing.List[LineString] = []
lineStrings :typing.List[LineString] = []
lineStrings = [LineString(line) for line in lines if len(set(line)) > 1]
'''
import matplotlib.pyplot as plt
for line in lineStrings:
x, y = line.xy
plt.plot(x, y)
plt.show()
'''
return self.__build_polygons_and_dispensable(lineStrings)
def __build_polygons_and_dispensable(self, lineStrings: typing.List[LineString]) -> VirtualMachineOutput:
# 为非常近的线搭桥
tree = shapely.STRtree(lineStrings)
bridges = []
# 遍历所有线段的端点,寻找需要搭桥的地方
for line in lineStrings:
endpoints = [shapely.Point(line.coords[0]), shapely.Point(line.coords[-1])]
for p in endpoints:
for idx in tree.query(p.buffer(self.tolerance)):
other_line = lineStrings[idx]
if other_line == line:
continue
dist = p.distance(other_line)
if 0 < dist <= self.tolerance:
_, p_target = shapely.ops.nearest_points(p, other_line)
bridge = LineString([p, p_target])
bridges.append(bridge)
lineStrings.extend(bridges)
# 节点化并提取闭合区域
all_shapes = shapely.ops.unary_union(lineStrings)
assert isinstance(all_shapes, MultiLineString)
polygons :typing.List[Polygon] = list(shapely.ops.polygonize(all_shapes))
# 合并重叠多边形
merged_polygons = shapely.unary_union(polygons)
assert isinstance(merged_polygons, MultiPolygon)
polygons = list(merged_polygons.geoms)
# 只保留外环
polygons = [Polygon(poly.exterior) for poly in polygons]
# 找到多余的线段
dispensable :typing.List[LineString] = []
closed_rings = [poly.exterior for poly in polygons]
if closed_rings:
leftovers = all_shapes.difference(shapely.ops.unary_union(closed_rings))
assert isinstance(leftovers, shapely.MultiLineString)
for shape in leftovers.geoms:
dispensable.append(shape)
else:
dispensable = list(all_shapes.geoms)
for i in range(len(polygons)):
polygons[i] = polygons[i].buffer(0)
logging.info(f"共生成多边形{len(polygons)}个,线段{len(dispensable)}")
output = VirtualMachineOutput(polygons, dispensable)
return output
class _Preprocessor(object):
'''PLT文件预处理器'''
def __init__(self) -> None:
pass
def get_abstract_syntax_tree(self, src_content: str) -> AbstractSyntaxTree:
content = src_content
# 换行符转分号 某些文件的语句末尾没有分号
content = content.replace("\n", ";")
content = content.replace(" ", ",")
# 删除所有空白字符
for space in string.whitespace:
content = content.replace(space, "")
result = []
# 遍历每一行命令
for line_index, line in enumerate(content.split(";")):
if line == "":
continue
# 提取开头的英文
pattern = r'^[a-zA-Z]+'
match = re.search(pattern, line)
if match is None:
assert_warning(False, f"[line {line_index}] 未找到命令: {line}")
continue
# 获取对应的指令
command_str = match.group().upper()
try:
command = PltCommand[command_str]
except KeyError:
assert_warning(False, f"[line {line_index}] 不支持的指令: {command_str}")
continue
# 获取参数
if len(command_str) == len(line):
stack = []
else:
stack = line[len(command_str):].split(",")
if command == PltCommand.IN:
result.append({'cmd': PltCommand.IN, 'args': []})
elif command == PltCommand.PA:
for i in range(len(stack) // 2):
result.append({'cmd': PltCommand.PA, 'args': [stack[0], stack[1]]})
stack = stack[2:]
elif command == PltCommand.PU:
result.append({'cmd': PltCommand.PU, 'args': []})
for i in range(len(stack) // 2):
result.append({'cmd': PltCommand.PA, 'args': [stack[0], stack[1]]})
stack = stack[2:]
elif command == PltCommand.PD:
result.append({'cmd': PltCommand.PD, 'args': []})
for i in range(len(stack) // 2):
result.append({'cmd': PltCommand.PA, 'args': [stack[0], stack[1]]})
stack = stack[2:]
assert_warning(len(stack) == 0, f"[line {line_index}] 栈非空: {stack}")
return result
class PltReader(object):
'''PLT文件读取器'''
def __init__(self, pltfile: typing.TextIO) -> None:
self.pltfile = pltfile
logging.info("正在预处理PLT文件...")
preprocessor = _Preprocessor()
self.abstract_syntax_tree = preprocessor.get_abstract_syntax_tree(self.pltfile.read())
logging.info("预处理完成")
def get_output(self, tolerance=5, version: int = 2) -> VirtualMachineOutput:
machine = _VirtualMachine(tolerance=tolerance)
output = machine.run(self.abstract_syntax_tree, version)
return output
if __name__ == "__main__":
filepath = "标准.plt"
with open(filepath, 'r') as f:
reader = PltReader(f)
output = reader.get_output()
debug_full_sheet_path = "debug_full_sheet.png"
cv2.imwrite(debug_full_sheet_path, output.debug_full_sheet(scale_factor=72/1016))
print(f"调试整幅输出已写入{debug_full_sheet_path}")
full_sheet_path = "full_sheet.png"
cv2.imwrite(full_sheet_path, output.full_sheet(scale_factor=72/1016))
print(f"整幅输出已写入{full_sheet_path}")
single_size_path = "single_size"
for index, img in enumerate(output.single_size(size_num=5, scale_factor=72/1016)):
cv2.imwrite(os.path.join(single_size_path, f"{index}.png"), img)
print(f"单码输出已写入{single_size_path}")
single_piece_path = "single_piece"
for index, img in enumerate(output.single_piece(scale_factor=72/1016)):
cv2.imwrite(os.path.join(single_piece_path, f"{index}.png"), img)
print(f"单片输出已写入{single_piece_path}")

View File

@@ -0,0 +1,8 @@
fastapi==0.109.0
uvicorn==0.27.0
python-multipart==0.0.6
numpy==1.26.3
opencv-python-headless==4.9.0.80
shapely==2.0.2
scipy==1.12.0
Pillow==10.2.0

1
Server/.python-version Normal file
View File

@@ -0,0 +1 @@
3.14

View File

@@ -0,0 +1,340 @@
# PLT 裁片处理 API 集成完成
## ✅ 已完成的工作
### 1. **核心文件创建**
-`Server/app/api/v1/algorithm.py` - PLT处理API接口
-`Server/pltreader.py` - 已复制到Server目录
-`Server/app/main.py` - 已注册algorithm路由
### 2. **依赖包更新**
- ✅ 添加 `scipy` - 用于匈牙利算法匹配
- ✅ 添加 `Pillow` - 用于图像处理和Base64编码
- ✅ 已有 `shapely`, `numpy`, `opencv-python-headless`
### 3. **API功能特性**
#### 核心功能
1. **PLT文件解析** - 自动识别裁片轮廓
2. **按尺码分组** - 自动匹配不同尺码的相同裁片
3. **生成透明PNG** - 每个裁片独立输出带Alpha通道
4. **精确坐标计算** - 返回厘米单位的中心点和左上角坐标
5. **旋转支持** - 可指定0°/90°/-90°/180°旋转
6. **双文件匹配** - 可上传两个PLT文件进行轮廓对比
#### 安全特性
- ✅ JWT Token 认证保护
- ✅ 需要登录才能访问
- ✅ 集成现有用户系统
---
## 📋 API 接口说明
### 接口地址
```
POST /api/v1/algorithm/process_plt
```
### 请求方式
`multipart/form-data`
### 请求头
```
Authorization: Bearer <access_token>
```
### 请求参数
| 参数名 | 类型 | 必填 | 说明 | 示例 |
|--------|------|------|------|------|
| `file` | File | **是** | 标准PLT文件 | `standard.plt` |
| `rotated_file` | File | 否 | 旋转后的PLT文件用于匹配分析 | `rotated.plt` |
| `size_labels` | String | **是** | 尺码标签JSON数组 | `["S","M","L","XL","2XL"]` |
| `dpi` | Integer | 否 | 输出分辨率默认150 | `150` |
| `rotation` | Integer | 否 | 旋转角度0/90/-90/180默认0 | `90` |
### 响应示例
```json
{
"success": true,
"total_groups": 5,
"groups": [
{
"group_id": 1,
"pieces": [
{
"size": "S",
"image_base64": "data:image/png;base64,iVBORw0KGgo...",
"width_px": 800,
"height_px": 600,
"width_cm": 13.54,
"height_cm": 10.16,
"center_x_cm": 25.4,
"center_y_cm": 30.2,
"left_cm": 18.63,
"top_cm": 35.28
},
{
"size": "M",
"image_base64": "data:image/png;base64,iVBORw0KGgo...",
"width_px": 820,
"height_px": 615,
"width_cm": 13.89,
"height_cm": 10.41,
"center_x_cm": 26.1,
"center_y_cm": 30.8,
"left_cm": 19.15,
"top_cm": 35.60
}
]
}
],
"match_analysis": [
{
"size": "S",
"matches": [
{
"standard_id": 0,
"rotated_id": 3,
"distance": 0.7032,
"angle": 0.0
}
]
}
]
}
```
---
## 🧪 测试步骤
### 1. 安装依赖
```bash
cd Server
pip install -r requirements.txt
```
### 2. 启动服务
```bash
cd Server
python -m uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
### 3. 运行测试脚本
```bash
cd Server
python test_plt_api.py
```
测试脚本会自动:
1. 登录获取Token
2. 上传PLT文件进行处理
3. 显示匹配结果
---
## 🔧 技术实现细节
### 1. **裁片识别算法**
- 使用 Shapely 进行几何计算
- 自动构建多边形包含关系树
- 过滤噪声面积阈值0.68%² ~ 80%²)
### 2. **尺码匹配算法**
- **Hausdorff距离** - 衡量轮廓相似度
- **匈牙利算法** - 最优匹配分配
- 自动处理旋转0°/90°/180°/270°
- 质心对齐 + 面积归一化
### 3. **坐标系统**
- PLT单位1016单位 = 1英寸 = 2.54cm
- 原点:左下角
- Y轴向上为正数学坐标系
### 4. **图像处理**
- OpenCV绘制裁片轮廓
- 自动添加Alpha通道透明背景
- Pillow处理旋转和Base64编码
- DPI元数据写入PNG文件
---
## 📝 使用示例 (Python)
```python
import requests
import json
# 1. 登录
login_response = requests.post(
"http://localhost:8000/api/v1/auth/login",
json={
"username": "admin",
"password": "123456",
"device_id": "test-device"
}
)
token = login_response.json()["access_token"]
# 2. 处理PLT文件
with open("standard.plt", "rb") as f1:
files = [
("file", ("standard.plt", f1, "application/octet-stream"))
]
data = {
"size_labels": json.dumps(["S", "M", "L", "XL", "2XL"]),
"dpi": 150,
"rotation": 90 # 顺时针旋转90度
}
headers = {"Authorization": f"Bearer {token}"}
response = requests.post(
"http://localhost:8000/api/v1/algorithm/process_plt",
files=files,
data=data,
headers=headers
)
result = response.json()
print(f"共生成 {result['total_groups']} 组裁片")
# 3. 保存图片
for group in result["groups"]:
for piece in group["pieces"]:
# 提取Base64数据
base64_data = piece["image_base64"].split(",")[1]
# 解码并保存
import base64
img_data = base64.b64decode(base64_data)
with open(f"{piece['size']}-{group['group_id']}.png", "wb") as img_file:
img_file.write(img_data)
print(f"已保存: {piece['size']}-{group['group_id']}.png")
print(f" 位置: ({piece['left_cm']}, {piece['top_cm']}) cm")
print(f" 尺寸: {piece['width_cm']} x {piece['height_cm']} cm")
```
---
## 🎯 Photoshop插件集成要点
### 1. **创建画布**
```javascript
// 根据API返回的尺寸创建画布
var canvasWidth = 100; // cm根据实际需求
var canvasHeight = 150; // cm
var dpi = 150;
var doc = app.documents.add(
UnitValue(canvasWidth, "cm"),
UnitValue(canvasHeight, "cm"),
dpi,
"PLT裁片",
NewDocumentMode.RGB,
DocumentFill.TRANSPARENT
);
```
### 2. **加载Base64图片**
```javascript
function loadBase64Image(base64String, layerName) {
// 去除前缀
var base64Data = base64String.split(",")[1];
// 保存为临时文件
var tempFile = new File(Folder.temp + "/temp_piece.png");
tempFile.encoding = "BINARY";
tempFile.open("w");
tempFile.write(decode64(base64Data));
tempFile.close();
// 打开并复制到主文档
var tempDoc = app.open(tempFile);
tempDoc.activeLayer.copy();
app.activeDocument = mainDoc;
var newLayer = mainDoc.paste();
newLayer.name = layerName;
tempDoc.close(SaveOptions.DONOTSAVECHANGES);
tempFile.remove();
return newLayer;
}
```
### 3. **定位图层**
```javascript
function positionLayer(layer, leftCm, topCm, dpi) {
// 厘米转像素
var leftPx = leftCm * dpi / 2.54;
var topPx = topCm * dpi / 2.54;
// 移动到指定位置
var deltaX = leftPx - layer.bounds[0];
var deltaY = topPx - layer.bounds[1];
layer.translate(deltaX, deltaY);
}
```
---
## ⚠️ 注意事项
1. **文件编码** - PLT文件必须是文本格式如果是二进制格式会导致解析失败
2. **尺码数量** - `size_labels` 的数量必须与PLT文件中的实际尺码数量一致
3. **内存占用** - 处理大型PLT文件>1000个裁片可能需要较长时间
4. **坐标系** - Photoshop的Y轴向下如需适配可能需要翻转Y坐标
---
## 🐛 故障排查
### 问题1: 导入错误 `cannot import name 'algorithm'`
**解决**:
```bash
# 确保已重启uvicorn服务
python -m uvicorn app.main:app --reload
```
### 问题2: `ModuleNotFoundError: No module named 'scipy'`
**解决**:
```bash
pip install scipy Pillow
```
### 问题3: PLT文件解析失败
**检查**:
- 文件是否是文本格式(用记事本能打开)
- 文件编码是否正确建议UTF-8或GBK
- 文件是否包含有效的PLT命令IN, PU, PD, PA等
---
## 📚 相关文档
- `Server/docs/API_PLT_Processing.md` - 详细API文档
- `Server/test_plt_api.py` - 测试示例代码
- `pltreader.py` - PLT解析器源码
- `run.py` - 命令行工具示例
---
## 🎉 完成!
现在你可以启动服务器并测试API了
```bash
cd D:\main\DesignerCEP\Server
python -m uvicorn app.main:app --reload
```
然后在浏览器访问文档:
```
http://localhost:8000/docs
```
祝使用愉快!如有问题请随时反馈。

View File

@@ -1,6 +1,6 @@
import os import os
import shutil import shutil
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, status, Form from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, status, Form, Header
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.db import get_db from app.db import get_db
from app.models.group import PluginGroup as DBPluginGroup from app.models.group import PluginGroup as DBPluginGroup
@@ -8,21 +8,21 @@ from app.models.user import User
from app.schemas.group import PluginGroupCreate, PluginGroupUpdate, PluginGroup from app.schemas.group import PluginGroupCreate, PluginGroupUpdate, PluginGroup
from app.schemas.admin import UserInfo from app.schemas.admin import UserInfo
from app.core.config import settings from app.core.config import settings
from typing import List from typing import List, Optional
router = APIRouter() router = APIRouter()
# Hardcoded admin token for simplicity as per requirements # 从配置读取管理员令牌
ADMIN_TOKEN = "admin-secret-token" ADMIN_TOKEN = settings.ADMIN_TOKEN
def verify_admin(token: str = Form(...)): def verify_admin(token: str = Form(...)):
if token != ADMIN_TOKEN: if token != ADMIN_TOKEN:
raise HTTPException(status_code=403, detail="Admin permission required") raise HTTPException(status_code=403, detail="需要管理员权限")
def get_admin_dep(x_admin_token: str = None): def verify_admin_header(x_admin_token: Optional[str] = Header(None, alias="X-Admin-Token")):
# Alternative using header """通过 Header 验证管理员身份"""
if x_admin_token != ADMIN_TOKEN: if x_admin_token != ADMIN_TOKEN:
raise HTTPException(status_code=403, detail="Admin permission required") raise HTTPException(status_code=403, detail="需要管理员权限")
# Ensure archives directory exists # Ensure archives directory exists
ARCHIVES_DIR = "archives" ARCHIVES_DIR = "archives"
@@ -31,11 +31,11 @@ os.makedirs(ARCHIVES_DIR, exist_ok=True)
@router.post("/upload_version") @router.post("/upload_version")
async def upload_version( async def upload_version(
file: UploadFile = File(...), file: UploadFile = File(...),
# token: str = Form(...), # Simple auth token: str = Form(...),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
# if token != ADMIN_TOKEN: if token != ADMIN_TOKEN:
# raise HTTPException(status_code=403, detail="Invalid admin token") raise HTTPException(status_code=403, detail="Invalid admin token")
file_location = os.path.join(ARCHIVES_DIR, file.filename) file_location = os.path.join(ARCHIVES_DIR, file.filename)
with open(file_location, "wb+") as file_object: with open(file_location, "wb+") as file_object:
@@ -44,7 +44,7 @@ async def upload_version(
return {"code": 200, "message": f"File '{file.filename}' uploaded successfully", "filename": file.filename} return {"code": 200, "message": f"File '{file.filename}' uploaded successfully", "filename": file.filename}
@router.get("/archives") @router.get("/archives")
async def list_archives(): async def list_archives(_: None = Depends(verify_admin_header)):
if not os.path.exists(ARCHIVES_DIR): if not os.path.exists(ARCHIVES_DIR):
return [] return []
files = os.listdir(ARCHIVES_DIR) files = os.listdir(ARCHIVES_DIR)
@@ -53,7 +53,7 @@ async def list_archives():
return files return files
@router.post("/groups", response_model=PluginGroup) @router.post("/groups", response_model=PluginGroup)
async def create_group(group: PluginGroupCreate, db: Session = Depends(get_db)): async def create_group(group: PluginGroupCreate, db: Session = Depends(get_db), _: None = Depends(verify_admin_header)):
db_group = DBPluginGroup(**group.model_dump()) db_group = DBPluginGroup(**group.model_dump())
db.add(db_group) db.add(db_group)
db.commit() db.commit()
@@ -61,11 +61,11 @@ async def create_group(group: PluginGroupCreate, db: Session = Depends(get_db)):
return db_group return db_group
@router.get("/groups", response_model=List[PluginGroup]) @router.get("/groups", response_model=List[PluginGroup])
async def list_groups(db: Session = Depends(get_db)): async def list_groups(db: Session = Depends(get_db), _: None = Depends(verify_admin_header)):
return db.query(DBPluginGroup).all() return db.query(DBPluginGroup).all()
@router.put("/groups/{group_id}", response_model=PluginGroup) @router.put("/groups/{group_id}", response_model=PluginGroup)
async def update_group(group_id: int, group_update: PluginGroupUpdate, db: Session = Depends(get_db)): async def update_group(group_id: int, group_update: PluginGroupUpdate, db: Session = Depends(get_db), _: None = Depends(verify_admin_header)):
db_group = db.query(DBPluginGroup).filter(DBPluginGroup.id == group_id).first() db_group = db.query(DBPluginGroup).filter(DBPluginGroup.id == group_id).first()
if not db_group: if not db_group:
raise HTTPException(status_code=404, detail="Group not found") raise HTTPException(status_code=404, detail="Group not found")
@@ -79,11 +79,11 @@ async def update_group(group_id: int, group_update: PluginGroupUpdate, db: Sessi
return db_group return db_group
@router.get("/users", response_model=List[UserInfo]) @router.get("/users", response_model=List[UserInfo])
async def list_users(db: Session = Depends(get_db)): async def list_users(db: Session = Depends(get_db), _: None = Depends(verify_admin_header)):
return db.query(User).all() return db.query(User).all()
@router.put("/users/{user_id}/group") @router.put("/users/{user_id}/group")
async def update_user_group(user_id: int, group_id: int, db: Session = Depends(get_db)): async def update_user_group(user_id: int, group_id: int, db: Session = Depends(get_db), _: None = Depends(verify_admin_header)):
user = db.query(User).filter(User.id == user_id).first() user = db.query(User).filter(User.id == user_id).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
@@ -97,7 +97,7 @@ async def update_user_group(user_id: int, group_id: int, db: Session = Depends(g
return {"code": 200, "message": "User group updated"} return {"code": 200, "message": "User group updated"}
@router.put("/users/{user_id}/permissions") @router.put("/users/{user_id}/permissions")
async def update_user_permissions(user_id: int, permissions: str = Form(...), db: Session = Depends(get_db)): async def update_user_permissions(user_id: int, permissions: str = Form(...), db: Session = Depends(get_db), _: None = Depends(verify_admin_header)):
user = db.query(User).filter(User.id == user_id).first() user = db.query(User).filter(User.id == user_id).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")

View File

@@ -10,6 +10,7 @@ from pydantic import BaseModel
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.db import get_db from app.db import get_db
from app.models.business import FeatureConfig, VipConfig, CheckInConfig from app.models.business import FeatureConfig, VipConfig, CheckInConfig
from app.core.config import settings
router = APIRouter() router = APIRouter()
@@ -51,8 +52,7 @@ class CheckInConfigUpdate(BaseModel):
def verify_admin_token(token: str): def verify_admin_token(token: str):
"""验证管理员Token""" """验证管理员Token"""
expected_token = "admin-secret-token" if token != settings.ADMIN_TOKEN:
if token != expected_token:
raise HTTPException(status_code=401, detail="管理员Token无效") raise HTTPException(status_code=401, detail="管理员Token无效")
# ==================== 功能配置管理 ==================== # ==================== 功能配置管理 ====================

1270
Server/app/api/v1/ai_chat.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,239 @@
# -*- coding: utf-8 -*-
"""
AI 工具定义
将 PS 操作注册为 function calling 的 tool schema
前端收到 tool_calls 后执行 JSX把结果回传
"""
# ==================== 工具 SchemaOpenAI function calling 格式)====================
PS_TOOLS = [
{
"type": "function",
"function": {
"name": "get_document_info",
"description": "获取当前 Photoshop 文档信息(名称、尺寸、分辨率)",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "get_layer_structure",
"description": "获取当前文档的图层结构树(所有顶层组和子图层)",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "create_layer",
"description": "在 Photoshop 中创建新图层",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "新图层的名称"
}
},
"required": ["name"]
}
}
},
{
"type": "function",
"function": {
"name": "rename_layer",
"description": "重命名当前选中的图层",
"parameters": {
"type": "object",
"properties": {
"new_name": {
"type": "string",
"description": "新名称"
}
},
"required": ["new_name"]
}
}
},
{
"type": "function",
"function": {
"name": "delete_layer",
"description": "删除当前选中的图层",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "duplicate_layer",
"description": "复制当前选中的图层",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "align_layers",
"description": "对齐当前选中的图层",
"parameters": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["left", "right", "centerH", "top", "bottom", "centerV"],
"description": "对齐方式left=左对齐, right=右对齐, centerH=水平居中, top=顶对齐, bottom=底对齐, centerV=垂直居中"
}
},
"required": ["type"]
}
}
},
{
"type": "function",
"function": {
"name": "merge_layers",
"description": "合并当前选中的图层",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "create_layer_group",
"description": "创建图层组(如果不存在)",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "图层组名称"
}
},
"required": ["name"]
}
}
},
{
"type": "function",
"function": {
"name": "move_layer_to_group",
"description": "将当前图层移入指定图层组",
"parameters": {
"type": "object",
"properties": {
"group_name": {
"type": "string",
"description": "目标图层组名称"
}
},
"required": ["group_name"]
}
}
},
# ==================== 套图工具 ====================
{
"type": "function",
"function": {
"name": "identify_pieces",
"description": "识别裁片部位。截取当前PS画布并分析每个裁片图层的形状和位置用视觉AI识别哪个图层是前片、后片、袖子等。应在套图前先调用以建立图层名到裁片部位的对应关系。",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "generate_garment_preview",
"description": "【阶段1-生成预览】将用户上传的成衣照片中的花样,填充到 PS 文档的裁片轮廓上,生成一张带轮廓的花样预览图。预览图会显示在聊天中,供用户确认效果。需要用户已上传成衣图片。",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
{
"type": "function",
"function": {
"name": "extract_and_apply_all_pieces",
"description": "【阶段2-提取套图】用户确认预览 OK 后调用。根据 identify_pieces 的分析结果对每个裁片执行不同操作solid=PS纯色填充fill_pattern=AI提取花型铺满theme_pattern=底层纯色填充+上层AI提取主题图案(白底+正片叠底)mixed_pattern=底层AI提取花型+上层AI提取主题图案(白底+正片叠底)。",
"parameters": {
"type": "object",
"properties": {
"pieces": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string", "description": "裁片图层名称"},
"type": {"type": "string", "enum": ["solid", "fill_pattern", "theme_pattern", "mixed_pattern"], "description": "图案类型solid=纯色fill_pattern=花型铺满theme_pattern=主题图案+纯色底mixed_pattern=主题图案+花型底纹"},
"color": {"type": "string", "description": "颜色 hex 值。solid 和 theme_pattern 必须提供theme_pattern 的 color 是底色)"},
"description": {"type": "string", "description": "图案内容描述"}
},
"required": ["name", "type"]
},
"description": "裁片列表type 和 color 来自 identify_pieces 的分析结果"
}
},
"required": ["pieces"]
}
}
},
{
"type": "function",
"function": {
"name": "verify_pattern_result",
"description": "验证套图效果。自动截取当前 PS 画布与原始成衣对比,由视觉 AI 评分(1-10)并给出改进建议。应在套图完成后调用。",
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
},
]
# 工具名称映射(中文显示用)
TOOL_DISPLAY_NAMES = {
"get_document_info": "查看文档信息",
"get_layer_structure": "查看图层结构",
"create_layer": "新建图层",
"rename_layer": "重命名图层",
"delete_layer": "删除图层",
"duplicate_layer": "复制图层",
"align_layers": "对齐图层",
"merge_layers": "合并图层",
"create_layer_group": "创建图层组",
"move_layer_to_group": "移入图层组",
"identify_pieces": "识别裁片部位",
"generate_garment_preview": "生成花样预览",
"extract_and_apply_all_pieces": "提取并套图",
"verify_pattern_result": "验证套图效果",
}

View File

@@ -0,0 +1,506 @@
# -*- coding: utf-8 -*-
"""
PLT 裁片处理接口 (Photoshop 插件专用)
功能:解析 PLT 文件,生成裁片图片和坐标信息
优化版:提升匹配计算速度 + 并行图片生成
"""
import os
import io
import base64
import json
import numpy as np
import cv2
from typing import List, Optional, Dict, Tuple
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends
from pydantic import BaseModel
from sqlalchemy.orm import Session
from PIL import Image
from shapely import affinity
from scipy.optimize import linear_sum_assignment
from concurrent.futures import ThreadPoolExecutor, as_completed
import asyncio
# 导入自定义模块
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))))
from pltreader import PltReader
from app.db import get_db
from app.core.security import get_current_user
from app.core.qiniu_storage import qiniu_storage
router = APIRouter()
# ==================== 数据模型 ====================
class PieceInfo(BaseModel):
"""单个裁片信息"""
size: str
image_base64: Optional[str] = None # base64 图片(未启用云存储时使用)
image_url: Optional[str] = None # 云存储 URL启用七牛云时使用
width_px: int
height_px: int
width_cm: float
height_cm: float
center_x_cm: float
center_y_cm: float
left_cm: float
top_cm: float
class GroupInfo(BaseModel):
"""裁片分组信息"""
group_id: int
pieces: List[PieceInfo]
class MatchInfo(BaseModel):
"""双文件匹配结果"""
standard_id: int
rotated_id: int
distance: float
angle: float
class SizeMatchInfo(BaseModel):
"""每个尺码的匹配结果"""
size: str
matches: List[MatchInfo]
class ProcessPltResponse(BaseModel):
"""API 响应"""
success: bool
total_groups: int
groups: List[GroupInfo]
match_analysis: Optional[List[SizeMatchInfo]] = None
# ==================== 优化的辅助函数 ====================
def parse_plt_file(file_content: str, tolerance: int = 10):
"""解析 PLT 文件内容"""
reader = PltReader(io.StringIO(file_content))
return reader.get_output(tolerance=tolerance)
def normalize_polygon(poly):
"""
预处理多边形:质心对齐 + 面积归一化
返回: (归一化多边形, 原始面积, 原始周长)
"""
area = poly.area
perimeter = poly.length
if area <= 0:
return poly, 0, 0
# 质心对齐
poly = affinity.translate(poly, xoff=-poly.centroid.x, yoff=-poly.centroid.y)
# 缩放到统一面积
target_area = 10000
scale = (target_area / area) ** 0.5
poly = affinity.scale(poly, xfact=scale, yfact=scale, origin=(0, 0))
return poly, area, perimeter
def fast_shape_similarity(poly1_data: Tuple, poly2_data: Tuple) -> Tuple[float, int]:
"""
快速形状相似度计算
poly_data: (归一化多边形, 原始面积, 原始周长)
返回: (距离, 最佳角度)
"""
poly1, area1, peri1 = poly1_data
poly2, area2, peri2 = poly2_data
# 面积比初筛(面积差异太大的直接跳过)
if area1 > 0 and area2 > 0:
area_ratio = min(area1, area2) / max(area1, area2)
if area_ratio < 0.5: # 面积差异超过50%
return float('inf'), 0
# 只尝试 0° 和 180°服装裁片通常只需要这两个角度
dist_min = float('inf')
best_angle = 0
for angle in [0, 180]:
try:
rotated = affinity.rotate(poly2, angle, origin='centroid') if angle != 0 else poly2
# 使用 symmetric_difference 面积作为距离度量(比 hausdorff 快很多)
diff_area = poly1.symmetric_difference(rotated).area
dist = diff_area / max(poly1.area, 1)
except:
dist = float('inf')
if dist < dist_min:
dist_min = dist
best_angle = angle
return dist_min, best_angle
def batch_normalize_polygons(pieces: List[Dict]) -> List[Tuple]:
"""批量预处理多边形"""
return [normalize_polygon(p["data"]) for p in pieces]
def compute_matching_matrix(base_polys: List[Tuple], compare_polys: List[Tuple]) -> np.ndarray:
"""计算匹配成本矩阵"""
n, m = len(base_polys), len(compare_polys)
cost_matrix = np.zeros((n, m))
for i in range(n):
for j in range(m):
dist, _ = fast_shape_similarity(base_polys[i], compare_polys[j])
cost_matrix[i, j] = dist
return cost_matrix
def apply_rotation_to_image(pil_img: Image.Image, rotation: int) -> Image.Image:
"""应用旋转变换"""
if rotation == 90:
return pil_img.rotate(-90, expand=True)
elif rotation == -90:
return pil_img.rotate(90, expand=True)
elif rotation == 180:
return pil_img.rotate(180, expand=True)
return pil_img
def calculate_piece_coordinates(polygon, bounds, plt_to_cm, rotation=0) -> Dict:
"""计算裁片坐标(厘米单位)"""
min_x, min_y, max_x, max_y = bounds
centroid = polygon.centroid
orig_center_x = (centroid.x - min_x) * plt_to_cm
orig_center_y = (centroid.y - min_y) * plt_to_cm
piece_bounds = polygon.bounds
orig_piece_width = (piece_bounds[2] - piece_bounds[0]) * plt_to_cm
orig_piece_height = (piece_bounds[3] - piece_bounds[1]) * plt_to_cm
orig_canvas_width = (max_x - min_x) * plt_to_cm
orig_canvas_height = (max_y - min_y) * plt_to_cm
if rotation == 90:
center_x = orig_center_y
center_y = orig_canvas_width - orig_center_x
piece_width, piece_height = orig_piece_height, orig_piece_width
elif rotation == -90:
center_x = orig_canvas_height - orig_center_y
center_y = orig_center_x
piece_width, piece_height = orig_piece_height, orig_piece_width
elif rotation == 180:
center_x = orig_canvas_width - orig_center_x
center_y = orig_canvas_height - orig_center_y
piece_width, piece_height = orig_piece_width, orig_piece_height
else:
center_x, center_y = orig_center_x, orig_center_y
piece_width, piece_height = orig_piece_width, orig_piece_height
left_cm = center_x - piece_width / 2
top_cm = center_y - piece_height / 2
return {
"center_x_cm": round(center_x, 2),
"center_y_cm": round(center_y, 2),
"left_cm": round(left_cm, 2),
"top_cm": round(top_cm, 2),
"width_cm": round(piece_width, 2),
"height_cm": round(piece_height, 2)
}
def get_size_matching(clusters: List, size_num: int) -> Dict[int, List[int]]:
"""
获取尺码间匹配关系(优化版)
返回: {base_index: [matched_indices_per_size]}
"""
# 提取各尺码的父节点
size_parents = []
for cluster in clusters:
parents = [p for p in cluster if p["parent"] is None]
size_parents.append(parents)
# 预处理第一个尺码的多边形
base_pieces = size_parents[0]
base_polys = batch_normalize_polygons(base_pieces)
# 初始化匹配结果
match_result = {p["index"]: [p["index"]] for p in base_pieces}
# 依次匹配其他尺码
for size_idx in range(1, len(size_parents)):
compare_pieces = size_parents[size_idx]
compare_polys = batch_normalize_polygons(compare_pieces)
# 计算成本矩阵
cost_matrix = compute_matching_matrix(base_polys, compare_polys)
# 匈牙利算法匹配
row_ind, col_ind = linear_sum_assignment(cost_matrix)
for i, j in zip(row_ind, col_ind):
base_index = base_pieces[i]['index']
matched_index = compare_pieces[j]['index']
match_result[base_index].append(matched_index)
return match_result
def generate_single_piece_image(args: Tuple) -> Dict:
"""生成单个裁片图片(用于并行处理)"""
output, node, scale_factor, rotation, dpi, size_label, group_id, global_bounds, plt_to_cm = args
try:
nodes_to_draw = [node] + node['child']
# 绘制图像
img = output._draw_nodes(nodes_to_draw, scale_factor, show_id=False)
img_rgba = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA)
pil_img = Image.fromarray(img_rgba)
# 应用旋转
pil_img = apply_rotation_to_image(pil_img, rotation)
# 转换为 Base64
buffer = io.BytesIO()
pil_img.save(buffer, format='PNG', dpi=(dpi, dpi))
buffer.seek(0)
base64_str = base64.b64encode(buffer.read()).decode('utf-8')
image_base64 = f"data:image/png;base64,{base64_str}"
# 计算坐标
coords = calculate_piece_coordinates(node["data"], global_bounds, plt_to_cm, rotation)
return {
"success": True,
"group_id": group_id,
"size_label": size_label,
"image_base64": image_base64,
"width_px": pil_img.width,
"height_px": pil_img.height,
**coords
}
except Exception as e:
return {"success": False, "error": str(e), "group_id": group_id, "size_label": size_label}
def get_match_result_between_files(standard_clusters, rotated_clusters) -> List[List[Dict]]:
"""计算两个文件间的匹配结果(优化版)"""
all_size_matches = []
for standard_cluster, rotated_cluster in zip(standard_clusters, rotated_clusters):
standard_parents = [p for p in standard_cluster if p["parent"] is None]
rotated_parents = [p for p in rotated_cluster if p["parent"] is None]
if len(standard_parents) != len(rotated_parents):
continue
n = len(standard_parents)
# 批量预处理
std_polys = batch_normalize_polygons(standard_parents)
rot_polys = batch_normalize_polygons(rotated_parents)
# 计算成本矩阵和角度矩阵
cost_matrix = np.zeros((n, n))
rotation_matrix = np.zeros((n, n))
for i in range(n):
for j in range(n):
dist, angle = fast_shape_similarity(std_polys[i], rot_polys[j])
cost_matrix[i, j] = dist
rotation_matrix[i, j] = angle
# 匈牙利算法匹配
row_ind, col_ind = linear_sum_assignment(cost_matrix)
matches = []
for i, j in zip(row_ind, col_ind):
matches.append({
"standard_id": standard_parents[i]["index"],
"rotated_id": rotated_parents[j]["index"],
"distance": round(cost_matrix[i, j], 4),
"angle": rotation_matrix[i, j]
})
all_size_matches.append(matches)
return all_size_matches
# ==================== API 接口 ====================
@router.post("/algorithm/process_plt", response_model=ProcessPltResponse)
async def process_plt(
file: UploadFile = File(..., description="标准 PLT 文件"),
rotated_file: Optional[UploadFile] = File(None, description="旋转后的 PLT 文件(可选)"),
size_labels: str = Form(..., description="尺码标签 JSON 数组"),
dpi: int = Form(150, description="输出图片分辨率DPI"),
rotation: int = Form(0, description="强制旋转角度 (0/90/-90/180)"),
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""PLT 裁片处理接口(优化版)"""
try:
# 1. 解析参数
try:
size_labels_list = json.loads(size_labels)
except:
raise HTTPException(status_code=400, detail="size_labels 格式错误")
size_num = len(size_labels_list)
scale_factor = dpi / 1016
plt_to_cm = 2.54 / 1016
# 2. 读取并解析 PLT 文件
print(f"[PLT API] 用户 {current_username} 正在处理: {file.filename}")
file_content = (await file.read()).decode('utf-8', errors='ignore')
output = parse_plt_file(file_content)
# 3. 获取尺码聚类
clusters = output.get_single_size_info(size_num=size_num)
# 4. 获取尺码间匹配关系(优化版)
match_result = get_size_matching(clusters, size_num)
# 5. 构建索引映射
all_nodes = {node["index"]: node for node in output.nodes}
# 6. 计算全局边界
from shapely.geometry import MultiPolygon as ShapelyMultiPolygon
all_polygons = [node["data"] for node in output.nodes]
global_bounds = ShapelyMultiPolygon(all_polygons).bounds
# 7. 并行生成图片和坐标数据
print(f"[PLT API] 开始并行生成图片...")
# 准备所有任务参数
tasks = []
for group_id, (base_index, matched_indices) in enumerate(match_result.items(), start=1):
for size_idx, piece_index in enumerate(matched_indices):
size_label = size_labels_list[size_idx]
node = all_nodes[piece_index]
tasks.append((
output, node, scale_factor, rotation, dpi,
size_label, group_id, global_bounds, plt_to_cm
))
# 使用线程池并行处理CPU 核心数)
max_workers = min(os.cpu_count() or 4, 8) # 最多8线程
results = []
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_task = {executor.submit(generate_single_piece_image, task): task for task in tasks}
for future in as_completed(future_to_task):
result = future.result()
if result["success"]:
results.append(result)
# 上传图片到七牛云(如果启用)
image_urls = {} # key: (group_id, size_label) -> url
if qiniu_storage.enabled:
# 获取用户ID用于文件隔离
from app.models.user import User
user = db.query(User).filter(User.username == current_username).first()
user_id = user.id if user else None
print(f"[PLT API] 正在上传图片到七牛云,共 {len(results)} 张,用户: {current_username}(id={user_id})...")
upload_tasks = []
for r in results:
base64_data = r["image_base64"]
name = f"g{r['group_id']}_{r['size_label']}"
upload_tasks.append((base64_data, name))
# 上传到 plt/{日期}/u{用户ID}/ 目录
upload_results = qiniu_storage.upload_batch(upload_tasks, key_prefix="plt", user_id=user_id)
success_count = 0
for i, (success, url_or_base64, name) in enumerate(upload_results):
if success:
r = results[i]
image_urls[(r["group_id"], r["size_label"])] = url_or_base64
success_count += 1
print(f"[PLT API] 七牛云上传完成,成功 {success_count}/{len(results)}")
# 按 group_id 分组
groups_dict: Dict[int, List] = {}
for r in results:
gid = r["group_id"]
key = (gid, r["size_label"])
# 优先使用云存储 URL
if key in image_urls:
piece = PieceInfo(
size=r["size_label"],
image_url=image_urls[key],
image_base64=None,
width_px=r["width_px"],
height_px=r["height_px"],
center_x_cm=r["center_x_cm"],
center_y_cm=r["center_y_cm"],
left_cm=r["left_cm"],
top_cm=r["top_cm"],
width_cm=r["width_cm"],
height_cm=r["height_cm"]
)
else:
piece = PieceInfo(
size=r["size_label"],
image_base64=r["image_base64"],
image_url=None,
width_px=r["width_px"],
height_px=r["height_px"],
center_x_cm=r["center_x_cm"],
center_y_cm=r["center_y_cm"],
left_cm=r["left_cm"],
top_cm=r["top_cm"],
width_cm=r["width_cm"],
height_cm=r["height_cm"]
)
if gid not in groups_dict:
groups_dict[gid] = []
groups_dict[gid].append(piece)
# 构建 groups 列表
groups_list = []
for gid, pieces in groups_dict.items():
# 按尺码排序
sorted_pieces = sorted(pieces, key=lambda p: size_labels_list.index(p.size) if p.size in size_labels_list else 999)
# 计算该组的面积(用第一个裁片的面积作为代表)
area = sorted_pieces[0].width_cm * sorted_pieces[0].height_cm if sorted_pieces else 0
groups_list.append((gid, sorted_pieces, area))
# 按面积从大到小排序,重新分配 group_id
groups_list.sort(key=lambda x: -x[2]) # 面积降序
groups = [
GroupInfo(group_id=new_id, pieces=pieces)
for new_id, (_, pieces, _) in enumerate(groups_list, start=1)
]
print(f"[PLT API] 图片生成完成,共 {len(results)}")
# 8. 处理双文件匹配(可选)
match_analysis = None
if rotated_file:
print(f"[PLT API] 双文件匹配: {file.filename} vs {rotated_file.filename}")
rotated_content = (await rotated_file.read()).decode('utf-8', errors='ignore')
rotated_output = parse_plt_file(rotated_content)
rotated_clusters = rotated_output.get_single_size_info(size_num=size_num)
all_matches = get_match_result_between_files(clusters, rotated_clusters)
match_analysis = [
SizeMatchInfo(size=size_labels_list[i], matches=[MatchInfo(**m) for m in matches])
for i, matches in enumerate(all_matches)
]
# 9. 返回结果
print(f"[PLT API] 处理完成,共 {len(groups)} 组裁片")
return ProcessPltResponse(
success=True,
total_groups=len(groups),
groups=groups,
match_analysis=match_analysis
)
except HTTPException:
raise
except Exception as e:
print(f"[PLT API] 处理失败: {str(e)}")
import traceback
traceback.print_exc()
raise HTTPException(status_code=500, detail=f"处理失败: {str(e)}")

View File

@@ -3,10 +3,12 @@
记录和分析用户操作 记录和分析用户操作
""" """
from fastapi import APIRouter, HTTPException from fastapi import APIRouter, HTTPException, Depends, Header
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional, Any from typing import Optional, Any
from datetime import datetime from datetime import datetime
from app.core.security import get_current_user
from app.core.config import settings
router = APIRouter() router = APIRouter()
@@ -14,28 +16,28 @@ router = APIRouter()
action_logs = [] action_logs = []
class ActionLog(BaseModel): class ActionLog(BaseModel):
username: str
device_id: str
action: str action: str
details: Optional[Any] = None details: Optional[Any] = None
timestamp: int timestamp: int
session_id: str session_id: str
device_id: Optional[str] = None
class ActionLogResponse(BaseModel): class ActionLogResponse(BaseModel):
success: bool success: bool
message: str message: str
@router.post("/log", response_model=ActionLogResponse) @router.post("/log", response_model=ActionLogResponse)
async def log_action(log: ActionLog): async def log_action(log: ActionLog, current_username: str = Depends(get_current_user)):
""" """记录用户行为(仅记录当前登录用户的操作)"""
记录用户行为
"""
try: try:
# 添加服务器时间
log_entry = { log_entry = {
**log.dict(), "username": current_username,
"action": log.action,
"details": log.details,
"timestamp": log.timestamp,
"session_id": log.session_id,
"device_id": log.device_id,
"server_time": datetime.now().isoformat(), "server_time": datetime.now().isoformat(),
"ip": "unknown" # 可以从请求中获取
} }
action_logs.append(log_entry) action_logs.append(log_entry)
@@ -44,40 +46,37 @@ async def log_action(log: ActionLog):
if len(action_logs) > 10000: if len(action_logs) > 10000:
action_logs.pop(0) action_logs.pop(0)
# 可以在这里添加异常检测逻辑
# 例如:检测同一用户短时间内的大量操作
return ActionLogResponse(success=True, message="已记录") return ActionLogResponse(success=True, message="已记录")
except Exception as e: except Exception as e:
return ActionLogResponse(success=False, message=str(e)) return ActionLogResponse(success=False, message=str(e))
@router.get("/stats/{username}") @router.get("/stats/me")
async def get_user_stats(username: str): async def get_my_stats(current_username: str = Depends(get_current_user)):
""" """获取当前用户的统计信息"""
获取用户统计信息 user_logs = [log for log in action_logs if log.get("username") == current_username]
"""
user_logs = [log for log in action_logs if log.get("username") == username]
# 统计各操作类型的次数 action_counts: dict[str, int] = {}
action_counts = {}
for log in user_logs: for log in user_logs:
action = log.get("action", "unknown") action = log.get("action", "unknown")
action_counts[action] = action_counts.get(action, 0) + 1 action_counts[action] = action_counts.get(action, 0) + 1
return { return {
"username": username, "username": current_username,
"total_actions": len(user_logs), "total_actions": len(user_logs),
"action_counts": action_counts, "action_counts": action_counts,
"recent_actions": user_logs[-10:] # 最近 10 条 "recent_actions": user_logs[-10:]
} }
@router.get("/recent") @router.get("/recent")
async def get_recent_logs(limit: int = 100): async def get_recent_logs(
""" limit: int = 100,
获取最近的操作日志(管理员用) admin_token: Optional[str] = Header(None, alias="X-Admin-Token")
""" ):
"""获取最近的操作日志(仅管理员)"""
if admin_token != settings.ADMIN_TOKEN:
raise HTTPException(status_code=403, detail="需要管理员权限")
return { return {
"total": len(action_logs), "total": len(action_logs),
"logs": action_logs[-limit:] "logs": action_logs[-limit:]
} }

View File

@@ -55,13 +55,14 @@ async def reset_password(body: ResetPasswordRequest, db: Session = Depends(get_d
return auth_service.reset_password(db, body.token, body.new_password, body.email) return auth_service.reset_password(db, body.token, body.new_password, body.email)
@router.post("/logout") @router.post("/logout")
async def logout(body: UserLogout, db: Session = Depends(get_db)): async def logout(body: UserLogout, db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
# 登出接口:将指定设备会话置为非活跃 # 登出接口:只允许登出自己的会话
return auth_service.logout(db, body.username, body.device_id) return auth_service.logout(db, current_username, body.device_id)
@router.get("/online-time/{username}") @router.get("/online-time")
async def get_online_time(username: str, db: Session = Depends(get_db)): async def get_online_time(db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
# 在线时长统计:累计历史会话的时长(秒),以及当前活跃会话的实时时长(秒) # 在线时长统计:只查自己的数据
username = current_username
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == username).first()
if not user: if not user:
return {"username": username, "total_seconds": 0, "active_seconds": 0} return {"username": username, "total_seconds": 0, "active_seconds": 0}
@@ -104,8 +105,8 @@ async def get_online_time(username: str, db: Session = Depends(get_db)):
return {"username": username, "total_seconds": total_seconds, "active_seconds": active_seconds} return {"username": username, "total_seconds": total_seconds, "active_seconds": active_seconds}
@router.post("/heartbeat") @router.post("/heartbeat")
async def heartbeat(body: UserHeartbeat, db: Session = Depends(get_db)): async def heartbeat(body: UserHeartbeat, db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
# 心跳接口:更新会话的最近在线时间 # 心跳接口:更新自己的会话
return auth_service.heartbeat(db, body.username, body.device_id) return auth_service.heartbeat(db, current_username, body.device_id)

View File

@@ -12,21 +12,17 @@ from sqlalchemy import func
from app.db import get_db from app.db import get_db
from app.models.user import User from app.models.user import User
from app.models.business import CheckInConfig, VipConfig, CheckInRecord, PointsHistory from app.models.business import CheckInConfig, VipConfig, CheckInRecord, PointsHistory
from app.core.security import get_current_user
router = APIRouter() router = APIRouter()
# ==================== 数据模型 ====================
class CheckInRequest(BaseModel):
username: str
# ==================== 签到功能 ==================== # ==================== 签到功能 ====================
@router.post("/checkin/daily") @router.post("/checkin/daily")
async def daily_checkin(data: CheckInRequest, db: Session = Depends(get_db)): async def daily_checkin(db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
"""每日签到""" """每日签到"""
# 1. 获取用户信息 # 1. 获取用户信息
user = db.query(User).filter(User.username == data.username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -85,7 +81,7 @@ async def daily_checkin(data: CheckInRequest, db: Session = Depends(get_db)):
# 7. 记录签到记录 # 7. 记录签到记录
checkin_record = CheckInRecord( checkin_record = CheckInRecord(
user_id=user.id, user_id=user.id,
username=data.username, username=current_username,
check_in_date=today, check_in_date=today,
points_earned=points_earned, points_earned=points_earned,
consecutive_days=consecutive_days, consecutive_days=consecutive_days,
@@ -96,7 +92,7 @@ async def daily_checkin(data: CheckInRequest, db: Session = Depends(get_db)):
# 8. 记录积分历史 # 8. 记录积分历史
points_history = PointsHistory( points_history = PointsHistory(
user_id=user.id, user_id=user.id,
username=data.username, username=current_username,
type='checkin', type='checkin',
amount=points_earned, amount=points_earned,
balance=new_balance, balance=new_balance,
@@ -145,9 +141,9 @@ async def get_checkin_config(db: Session = Depends(get_db)):
} }
@router.get("/checkin/status") @router.get("/checkin/status")
async def get_checkin_status(username: str, db: Session = Depends(get_db)): async def get_checkin_status(db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
"""获取签到状态""" """获取签到状态"""
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -160,14 +156,15 @@ async def get_checkin_status(username: str, db: Session = Depends(get_db)):
"today_checked": today_checked, "today_checked": today_checked,
"consecutive_days": user.consecutive_check_in if user.consecutive_check_in else 0, "consecutive_days": user.consecutive_check_in if user.consecutive_check_in else 0,
"total_days": user.total_check_in_days if user.total_check_in_days else 0, "total_days": user.total_check_in_days if user.total_check_in_days else 0,
"total_points": user.points if user.points else 0,
"last_check_in_date": user.last_check_in_date.isoformat() if user.last_check_in_date else None "last_check_in_date": user.last_check_in_date.isoformat() if user.last_check_in_date else None
} }
} }
@router.get("/checkin/calendar/{year}/{month}") @router.get("/checkin/calendar/{year}/{month}")
async def get_checkin_calendar(username: str, year: int, month: int, db: Session = Depends(get_db)): async def get_checkin_calendar(year: int, month: int, db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
"""获取签到日历""" """获取签到日历"""
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -199,9 +196,9 @@ async def get_checkin_calendar(username: str, year: int, month: int, db: Session
} }
@router.get("/checkin/history") @router.get("/checkin/history")
async def get_checkin_history(username: str, page: int = 1, limit: int = 10, db: Session = Depends(get_db)): async def get_checkin_history(page: int = 1, limit: int = 10, db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
"""签到记录""" """签到记录"""
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -232,23 +229,3 @@ async def get_checkin_history(username: str, page: int = 1, limit: int = 10, db:
"records": result_records "records": result_records
} }
} }
@router.get("/checkin/config")
async def get_checkin_config(db: Session = Depends(get_db)):
"""获取签到奖励规则 (公开接口)"""
configs = db.query(CheckInConfig).filter(CheckInConfig.enabled == True).order_by(CheckInConfig.consecutive_days.asc()).all()
data = []
for c in configs:
data.append({
"consecutive_days": c.consecutive_days,
"base_points": c.base_points,
"bonus_points": c.bonus_points,
"total_points": c.total_points
})
return {
"code": 200,
"data": data
}

View File

@@ -11,20 +11,19 @@ from sqlalchemy.orm import Session
from app.db import get_db from app.db import get_db
from app.models.user import User from app.models.user import User
from app.models.business import FeatureConfig, VipConfig, PointsHistory from app.models.business import FeatureConfig, VipConfig, PointsHistory
from app.core.security import get_current_user
router = APIRouter() router = APIRouter()
# ==================== 数据模型 ==================== # ==================== 数据模型 ====================
class UseFeatureRequest(BaseModel): class UseFeatureRequest(BaseModel):
username: str
feature_key: str feature_key: str
device_id: str
# ==================== 通用功能使用 ==================== # ==================== 通用功能使用 ====================
@router.post("/feature/use") @router.post("/feature/use")
async def use_feature(data: UseFeatureRequest, db: Session = Depends(get_db)): async def use_feature(data: UseFeatureRequest, db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
""" """
通用功能使用接口 通用功能使用接口
逻辑: 逻辑:
@@ -38,7 +37,7 @@ async def use_feature(data: UseFeatureRequest, db: Session = Depends(get_db)):
raise HTTPException(status_code=400, detail="功能不存在或已禁用") raise HTTPException(status_code=400, detail="功能不存在或已禁用")
# 2. 获取用户信息 # 2. 获取用户信息
user = db.query(User).filter(User.username == data.username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -100,7 +99,7 @@ async def use_feature(data: UseFeatureRequest, db: Session = Depends(get_db)):
# 记录积分历史 # 记录积分历史
points_history = PointsHistory( points_history = PointsHistory(
user_id=user.id, user_id=user.id,
username=data.username, username=current_username,
type='consume', type='consume',
amount=-points_cost, amount=-points_cost,
balance=new_balance, balance=new_balance,

View File

@@ -1,120 +0,0 @@
"""
服务器端计算 Demo - 简单数学计算
演示:前端获取图层名称 → 后端计算数学表达式 → 返回结果
"""
from fastapi import APIRouter, Header, HTTPException
from pydantic import BaseModel
import re
import logging
from datetime import datetime
from typing import Optional
from app.core.api_keys import validate_api_key, get_key_info
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger(__name__)
router = APIRouter()
class CalculateRequest(BaseModel):
"""计算请求"""
expression: str # 数学表达式,如 "87-98"
class CalculateResult(BaseModel):
"""计算结果"""
success: bool
expression: str
result: float = None
message: str
@router.post("/calculate", response_model=CalculateResult)
async def calculate_expression(
request: CalculateRequest,
x_api_key: Optional[str] = Header(None) # 可选的 API Key 验证
):
"""
🔒 服务器端数学计算(核心算法)
客户端只能拿到计算结果,看不到算法
示例:前端发送 "87-98" → 后端计算 → 返回 -11
"""
# ==================== 📝 日志:打印请求 ====================
logger.info("="*60)
logger.info("📥 收到计算请求")
logger.info(f" 时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
logger.info(f" 表达式: {request.expression}")
logger.info(f" API Key: {x_api_key if x_api_key else '未提供'}")
logger.info("="*60)
# ==================== 🔐 API Key 验证 ====================
if not validate_api_key(x_api_key):
logger.warning(f"❌ API Key 验证失败: {x_api_key}")
raise HTTPException(status_code=403, detail="无效的 API Key")
# 获取 Key 信息
key_info = get_key_info(x_api_key)
logger.info(f"✅ API Key 验证通过 | 名称: {key_info['name']} | 权限: {key_info['permissions']}")
try:
# 🔒 核心算法在这里(客户端看不到)
# 可以是复杂的数学模型、AI 推理等
expression = request.expression.strip()
# ==================== 🛡️ 安全检查 ====================
logger.info(f"🛡️ 安全检查: 验证表达式格式...")
# 只允许数字和基本运算符
if not re.match(r'^[\d\s\+\-\*\/\(\)\.]+$', expression):
logger.warning(f"❌ 表达式包含非法字符: {expression}")
return CalculateResult(
success=False,
expression=expression,
message="只支持基本数学运算(+、-、*、/"
)
logger.info("✅ 表达式格式验证通过")
# ==================== 🔒 核心算法执行 ====================
logger.info("🔒 开始执行核心算法...")
# 这里可以放你的核心算法
# 示例:简单计算
result = eval(expression)
logger.info(f"✅ 计算完成: {expression} = {result}")
# ==================== 📤 日志:打印输出 ====================
response = CalculateResult(
success=True,
expression=expression,
result=float(result),
message=f"计算成功: {expression} = {result}"
)
logger.info("="*60)
logger.info("📤 返回计算结果")
logger.info(f" 成功: {response.success}")
logger.info(f" 表达式: {response.expression}")
logger.info(f" 结果: {response.result}")
logger.info(f" 消息: {response.message}")
logger.info("="*60)
return response
except Exception as e:
logger.error(f"❌ 计算失败: {str(e)}")
logger.error("="*60)
return CalculateResult(
success=False,
expression=request.expression,
message=f"计算失败: {str(e)}"
)

View File

@@ -7,7 +7,6 @@ from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
from app.core.security import get_current_user from app.core.security import get_current_user
from app.db import get_db_session
import json import json
router = APIRouter() router = APIRouter()
@@ -74,14 +73,14 @@ class JSXExecuteResponse(BaseModel):
@router.post("/execute", response_model=JSXExecuteResponse) @router.post("/execute", response_model=JSXExecuteResponse)
async def execute_jsx( async def execute_jsx(
request: JSXExecuteRequest, request: JSXExecuteRequest,
current_user: dict = Depends(get_current_user) current_user: str = Depends(get_current_user)
): ):
""" """
服务器端生成 JSX 代码 服务器端生成 JSX 代码
客户端只能通过 API 获取,无法直接看到核心逻辑 客户端只能通过 API 获取,无法直接看到核心逻辑
""" """
try: try:
username = current_user.get("username") username = current_user # get_current_user 返回用户名字符串
# 1. 验证用户和设备 # 1. 验证用户和设备
# ... (从数据库检查用户是否有权限、设备是否绑定) # ... (从数据库检查用户是否有权限、设备是否绑定)
@@ -118,7 +117,7 @@ async def execute_jsx(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/templates") @router.get("/templates")
async def list_templates(current_user: dict = Depends(get_current_user)): async def list_templates(current_user: str = Depends(get_current_user)):
""" """
列出可用的 JSX 模板(不返回具体代码) 列出可用的 JSX 模板(不返回具体代码)
""" """

340
Server/app/api/v1/logs.py Normal file
View File

@@ -0,0 +1,340 @@
# -*- coding: utf-8 -*-
"""
日志和历史记录API
"""
from typing import List, Optional
from datetime import datetime, timedelta
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from sqlalchemy.orm import Session, joinedload
from sqlalchemy import desc
from app.db import get_db
from app.core.security import get_current_user
from app.models.logs import PltProcessRecord, UserActionLog, PltPiece
from app.models.user import User
router = APIRouter()
# ==================== 数据模型 ====================
class ActionLogCreate(BaseModel):
"""创建操作日志"""
session_id: Optional[str] = None
action_type: str
action_params: Optional[dict] = None
page: Optional[str] = None
result: str = "success"
error_message: Optional[str] = None
duration_ms: Optional[int] = None
class ActionLogResponse(BaseModel):
"""操作日志响应"""
id: int
action_type: str
action_params: Optional[dict]
page: Optional[str]
result: str
error_message: Optional[str]
duration_ms: Optional[int]
created_at: datetime
class Config:
from_attributes = True
class PltPieceCreate(BaseModel):
"""裁片信息"""
group_id: int
size: str
image_url: Optional[str] = None
width_px: int = 0
height_px: int = 0
width_cm: float = 0
height_cm: float = 0
left_cm: float = 0
top_cm: float = 0
center_x_cm: float = 0
center_y_cm: float = 0
class PltPieceResponse(BaseModel):
"""裁片响应"""
id: int
group_id: int
size: str
image_url: Optional[str]
width_px: int
height_px: int
width_cm: float
height_cm: float
left_cm: float
top_cm: float
center_x_cm: float
center_y_cm: float
class Config:
from_attributes = True
class PltRecordCreate(BaseModel):
"""创建PLT处理记录"""
filename: Optional[str] = None
file_size: Optional[int] = None
rotated_filename: Optional[str] = None
size_labels: Optional[List[str]] = None
dpi: int = 150
rotation: int = 0
total_groups: int = 0
total_pieces: int = 0
process_time_ms: Optional[int] = None
status: str = "success"
error_message: Optional[str] = None
pieces: Optional[List[PltPieceCreate]] = None # 裁片详情
class PltRecordResponse(BaseModel):
"""PLT处理记录响应"""
id: int
filename: Optional[str]
file_size: Optional[int]
rotated_filename: Optional[str]
size_labels: Optional[List[str]]
dpi: int
rotation: int
total_groups: int
total_pieces: int
process_time_ms: Optional[int]
status: str
created_at: datetime
class Config:
from_attributes = True
class PltRecordDetailResponse(PltRecordResponse):
"""PLT处理记录详情响应包含裁片"""
pieces: List[PltPieceResponse] = []
class SessionLogsResponse(BaseModel):
"""会话日志响应给AI用"""
user_id: int
username: str
current_page: Optional[str]
recent_actions: List[ActionLogResponse]
recent_plt_records: List[PltRecordResponse]
error_count: int
# ==================== API端点 ====================
@router.post("/logs/action", response_model=ActionLogResponse)
async def create_action_log(
log_data: ActionLogCreate,
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""记录用户操作日志"""
# 获取用户ID
user = db.query(User).filter(User.username == current_username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 创建日志
log = UserActionLog(
user_id=user.id,
session_id=log_data.session_id,
action_type=log_data.action_type,
action_params=log_data.action_params,
page=log_data.page,
result=log_data.result,
error_message=log_data.error_message,
duration_ms=log_data.duration_ms
)
db.add(log)
db.commit()
db.refresh(log)
return log
@router.post("/plt/record", response_model=PltRecordResponse)
async def create_plt_record(
record_data: PltRecordCreate,
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""记录PLT处理记录包含裁片详情"""
user = db.query(User).filter(User.username == current_username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
record = PltProcessRecord(
user_id=user.id,
filename=record_data.filename,
file_size=record_data.file_size,
rotated_filename=record_data.rotated_filename,
size_labels=record_data.size_labels,
dpi=record_data.dpi,
rotation=record_data.rotation,
total_groups=record_data.total_groups,
total_pieces=record_data.total_pieces,
process_time_ms=record_data.process_time_ms,
status=record_data.status,
error_message=record_data.error_message
)
db.add(record)
db.flush() # 获取 record.id
# 保存裁片详情
if record_data.pieces:
for piece_data in record_data.pieces:
piece = PltPiece(
record_id=record.id,
group_id=piece_data.group_id,
size=piece_data.size,
image_url=piece_data.image_url,
width_px=piece_data.width_px,
height_px=piece_data.height_px,
width_cm=piece_data.width_cm,
height_cm=piece_data.height_cm,
left_cm=piece_data.left_cm,
top_cm=piece_data.top_cm,
center_x_cm=piece_data.center_x_cm,
center_y_cm=piece_data.center_y_cm
)
db.add(piece)
db.commit()
db.refresh(record)
return record
@router.get("/plt/history/{record_id}", response_model=PltRecordDetailResponse)
async def get_plt_record_detail(
record_id: int,
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取PLT处理记录详情包含裁片"""
user = db.query(User).filter(User.username == current_username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
record = db.query(PltProcessRecord)\
.options(joinedload(PltProcessRecord.pieces))\
.filter(PltProcessRecord.id == record_id)\
.filter(PltProcessRecord.user_id == user.id)\
.first()
if not record:
raise HTTPException(status_code=404, detail="记录不存在")
return record
@router.get("/plt/history", response_model=List[PltRecordResponse])
async def get_plt_history(
limit: int = Query(20, ge=1, le=100),
days: int = Query(7, ge=1, le=30), # 保留天数默认7天
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取用户的PLT处理历史默认保留7天"""
user = db.query(User).filter(User.username == current_username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
# 只返回指定天数内的记录
since = datetime.now() - timedelta(days=days)
records = db.query(PltProcessRecord)\
.filter(PltProcessRecord.user_id == user.id)\
.filter(PltProcessRecord.created_at >= since)\
.order_by(desc(PltProcessRecord.created_at))\
.limit(limit)\
.all()
return records
@router.get("/logs/recent", response_model=List[ActionLogResponse])
async def get_recent_logs(
limit: int = Query(50, ge=1, le=200),
action_type: Optional[str] = None,
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取用户最近的操作日志"""
user = db.query(User).filter(User.username == current_username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
query = db.query(UserActionLog).filter(UserActionLog.user_id == user.id)
if action_type:
query = query.filter(UserActionLog.action_type.like(f"{action_type}%"))
logs = query.order_by(desc(UserActionLog.created_at)).limit(limit).all()
return logs
@router.get("/logs/session", response_model=SessionLogsResponse)
async def get_session_context(
hours: int = Query(24, ge=1, le=168),
current_username: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取会话上下文给AI助手用"""
user = db.query(User).filter(User.username == current_username).first()
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
since = datetime.now() - timedelta(hours=hours)
# 最近的操作日志
recent_actions = db.query(UserActionLog)\
.filter(UserActionLog.user_id == user.id)\
.filter(UserActionLog.created_at >= since)\
.order_by(desc(UserActionLog.created_at))\
.limit(50)\
.all()
# 最近的PLT处理记录
recent_plt = db.query(PltProcessRecord)\
.filter(PltProcessRecord.user_id == user.id)\
.filter(PltProcessRecord.created_at >= since)\
.order_by(desc(PltProcessRecord.created_at))\
.limit(10)\
.all()
# 错误数量
error_count = db.query(UserActionLog)\
.filter(UserActionLog.user_id == user.id)\
.filter(UserActionLog.created_at >= since)\
.filter(UserActionLog.result == "error")\
.count()
# 当前页面(最近一次进入页面的记录)
last_page_action = db.query(UserActionLog)\
.filter(UserActionLog.user_id == user.id)\
.filter(UserActionLog.action_type == "page.enter")\
.order_by(desc(UserActionLog.created_at))\
.first()
current_page = last_page_action.action_params.get("page") if last_page_action and last_page_action.action_params else None
return SessionLogsResponse(
user_id=user.id,
username=current_username,
current_page=current_page,
recent_actions=recent_actions,
recent_plt_records=recent_plt,
error_count=error_count
)

View File

@@ -8,6 +8,7 @@ from fastapi import APIRouter, Header, HTTPException
from datetime import date, timedelta from datetime import date, timedelta
import pymysql import pymysql
from app.core.database import get_db_connection from app.core.database import get_db_connection
from app.core.config import settings
router = APIRouter() router = APIRouter()
@@ -15,8 +16,7 @@ router = APIRouter()
def verify_admin_token(token: str): def verify_admin_token(token: str):
"""验证管理员Token""" """验证管理员Token"""
expected_token = "admin-secret-token" if token != settings.ADMIN_TOKEN:
if token != expected_token:
raise HTTPException(status_code=401, detail="管理员Token无效") raise HTTPException(status_code=401, detail="管理员Token无效")
# ==================== 统计功能 ==================== # ==================== 统计功能 ====================

View File

@@ -8,17 +8,16 @@ from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from sqlalchemy import func
from app.db import get_db from app.db import get_db
from app.models.user import User from app.models.user import User
from app.models.business import PointsHistory from app.models.business import PointsHistory
from app.core.security import get_current_user
router = APIRouter() router = APIRouter()
# ==================== 数据模型 ==================== # ==================== 数据模型 ====================
class UserProfileUpdate(BaseModel): class UserProfileUpdate(BaseModel):
username: str
nickname: Optional[str] = None nickname: Optional[str] = None
avatar: Optional[str] = None avatar: Optional[str] = None
email: Optional[str] = None email: Optional[str] = None
@@ -26,13 +25,12 @@ class UserProfileUpdate(BaseModel):
# ==================== 用户资料管理 ==================== # ==================== 用户资料管理 ====================
@router.get("/user/profile") @router.get("/user/profile")
async def get_user_profile(username: str, db: Session = Depends(get_db)): async def get_user_profile(db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
"""获取用户资料""" """获取用户资料"""
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
# 转换为字典以便序列化
user_data = { user_data = {
"username": user.username, "username": user.username,
"nickname": user.nickname, "nickname": user.nickname,
@@ -54,9 +52,9 @@ async def get_user_profile(username: str, db: Session = Depends(get_db)):
} }
@router.put("/user/profile") @router.put("/user/profile")
async def update_user_profile(data: UserProfileUpdate, db: Session = Depends(get_db)): async def update_user_profile(data: UserProfileUpdate, db: Session = Depends(get_db), current_username: str = Depends(get_current_user)):
"""更新用户资料""" """更新用户资料"""
user = db.query(User).filter(User.username == data.username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -78,14 +76,14 @@ async def update_user_profile(data: UserProfileUpdate, db: Session = Depends(get
@router.get("/points/history") @router.get("/points/history")
async def get_points_history( async def get_points_history(
username: str,
type: Optional[str] = None, type: Optional[str] = None,
page: int = 1, page: int = 1,
limit: int = 20, limit: int = 20,
db: Session = Depends(get_db) db: Session = Depends(get_db),
current_username: str = Depends(get_current_user)
): ):
"""获取积分历史""" """获取积分历史"""
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == current_username).first()
if not user: if not user:
raise HTTPException(status_code=404, detail="用户不存在") raise HTTPException(status_code=404, detail="用户不存在")
@@ -117,4 +115,3 @@ async def get_points_history(
"records": result_records "records": result_records
} }
} }

View File

@@ -5,20 +5,37 @@ class Settings(BaseSettings):
PROJECT_NAME: str = "DesignerCEP Backend" PROJECT_NAME: str = "DesignerCEP Backend"
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
DATABASE_URL: str = "sqlite:///./designercep.db" DATABASE_URL: str = "sqlite:///./designercep.db"
SECRET_KEY: str = "change-me" SECRET_KEY: str = "" # 必须从 .env 读取
ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080 ACCESS_TOKEN_EXPIRE_MINUTES: int = 10080
ALLOWED_ORIGINS: str = "*" ALLOWED_ORIGINS: str = "*"
ADMIN_TOKEN: str = "admin-token" ADMIN_TOKEN: str = "" # 必须从 .env 读取
# Email Configuration # Email Configuration
SMTP_HOST: str = "smtp.gmail.com" SMTP_HOST: str = "smtp.gmail.com"
SMTP_PORT: int = 587 SMTP_PORT: int = 587
SMTP_USER: str = "ly1104803132@gmail.com" SMTP_USER: str = ""
SMTP_PASSWORD: str = "wsfrpnmkojpsqdkk" SMTP_PASSWORD: str = "" # 必须从 .env 读取
EMAILS_FROM_EMAIL: str = "ly1104803132@gmail.com" EMAILS_FROM_EMAIL: str = ""
EMAILS_FROM_NAME: str = "Designer" EMAILS_FROM_NAME: str = "Designer"
# 七牛云对象存储
QINIU_ACCESS_KEY: str = ""
QINIU_SECRET_KEY: str = ""
QINIU_BUCKET: str = ""
QINIU_DOMAIN: str = ""
# AI 配置 — 通义千问 QwenDashScope
AI_API_KEY: str = "" # LLM API KeyOpenAI / DeepSeek 等)
AI_BASE_URL: str = "" # LLM API 地址(留空则用 OpenAI 默认)
AI_MODEL: str = "qwen3-max-2026-01-23" # 默认模型通义千问3深度思考
AI_VISION_MODEL: str = "qwen-vl-max-latest" # 视觉分析模型(看图分析成衣)
AI_IMAGE_EDIT_MODEL: str = "qwen-image-edit-max-2026-01-16" # 图片编辑/生成模型
# AI 配置 — Gemini第三方代理
GEMINI_API_KEY: str = ""
GEMINI_BASE_URL: str = "https://api.apiqik.online"
model_config = SettingsConfigDict(env_file=".env", extra="ignore") model_config = SettingsConfigDict(env_file=".env", extra="ignore")
settings = Settings() settings = Settings()

View File

@@ -0,0 +1,154 @@
# -*- coding: utf-8 -*-
"""
七牛云对象存储工具
"""
import base64
import hashlib
from datetime import datetime
from typing import Optional, Tuple
from qiniu import Auth, put_data, BucketManager
from app.core.config import settings
# ==================== 配置 ====================
# 从 settings 读取(通过 pydantic-settings 加载 .env
QINIU_ACCESS_KEY = settings.QINIU_ACCESS_KEY
QINIU_SECRET_KEY = settings.QINIU_SECRET_KEY
QINIU_BUCKET = settings.QINIU_BUCKET
QINIU_DOMAIN = settings.QINIU_DOMAIN # 例如: http://cdn.example.com
# 是否启用七牛云存储
QINIU_ENABLED = bool(QINIU_ACCESS_KEY and QINIU_SECRET_KEY and QINIU_BUCKET and QINIU_DOMAIN)
class QiniuStorage:
"""七牛云存储工具类"""
def __init__(self):
self.enabled = QINIU_ENABLED
if self.enabled:
self.auth = Auth(QINIU_ACCESS_KEY, QINIU_SECRET_KEY)
self.bucket = QINIU_BUCKET
self.domain = QINIU_DOMAIN.rstrip('/')
print(f"✅ 七牛云存储已启用Bucket: {self.bucket}")
else:
self.auth = None
self.bucket = None
self.domain = None
print("⚠️ 七牛云存储未配置,图片将使用 base64 返回")
def upload_base64(
self,
base64_data: str,
key_prefix: str = "plt",
user_id: Optional[int] = None
) -> Tuple[bool, str]:
"""
上传 base64 图片到七牛云
Args:
base64_data: base64 编码的图片数据(可带或不带 data:image/png;base64, 前缀)
key_prefix: 文件名前缀
user_id: 用户ID用于分目录
Returns:
(success, url_or_error): 成功返回 URL失败返回错误信息
"""
if not self.enabled:
return False, "七牛云存储未启用"
try:
# 去掉 base64 前缀
if ',' in base64_data:
base64_data = base64_data.split(',')[1]
# 解码
image_data = base64.b64decode(base64_data)
# 生成文件名: plt/2024/02/05/u123/时间戳_哈希.png
now = datetime.now()
date_path = now.strftime("%Y/%m/%d")
timestamp = now.strftime("%H%M%S") # 时分秒
content_hash = hashlib.md5(image_data).hexdigest()[:8]
if user_id:
key = f"{key_prefix}/{date_path}/u{user_id}/{timestamp}_{content_hash}.png"
else:
key = f"{key_prefix}/{date_path}/{timestamp}_{content_hash}.png"
# 生成上传凭证
token = self.auth.upload_token(self.bucket, key, 3600)
# 上传
ret, info = put_data(token, key, image_data)
if info.status_code == 200:
url = f"{self.domain}/{key}"
return True, url
else:
print(f"[七牛云] 上传失败: status={info.status_code}, error={info.error}, text={info.text_body}")
return False, f"上传失败: {info.error}"
except Exception as e:
print(f"[七牛云] 上传异常: {str(e)}")
return False, f"上传异常: {str(e)}"
def upload_batch(
self,
images: list, # [(base64_data, name), ...]
key_prefix: str = "plt",
user_id: Optional[int] = None
) -> list:
"""
批量上传图片
Returns:
[(success, url_or_base64, name), ...]
"""
results = []
success_count = 0
fail_count = 0
for base64_data, name in images:
if self.enabled:
success, result = self.upload_base64(base64_data, key_prefix, user_id)
if success:
results.append((True, result, name))
success_count += 1
else:
# 上传失败,回退到 base64
print(f"[七牛云] 上传失败 {name}: {result}")
results.append((False, base64_data, name))
fail_count += 1
else:
# 未启用,返回原始 base64
results.append((False, base64_data, name))
if fail_count > 0:
print(f"[七牛云] 批量上传结果: 成功 {success_count}, 失败 {fail_count}")
return results
def delete(self, key: str) -> bool:
"""删除文件"""
if not self.enabled:
return False
try:
bucket_manager = BucketManager(self.auth)
ret, info = bucket_manager.delete(self.bucket, key)
return info.status_code == 200
except Exception:
return False
def get_url(self, key: str) -> str:
"""获取文件 URL"""
if not self.enabled or not key:
return ""
return f"{self.domain}/{key}"
# 全局实例
qiniu_storage = QiniuStorage()

View File

@@ -34,10 +34,6 @@ def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(
验证 JWT Token 并返回当前用户名 (sub) 验证 JWT Token 并返回当前用户名 (sub)
增加 Session 强校验:检查数据库中 Session 是否活跃 增加 Session 强校验:检查数据库中 Session 是否活跃
""" """
print("="*60)
print("[get_current_user] 开始验证 Token")
print(f" - Token (前30字符): {token[:30]}...")
credentials_exception = HTTPException( credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的认证凭据", detail="无效的认证凭据",
@@ -50,41 +46,24 @@ def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(
username: str = payload.get("sub") username: str = payload.get("sub")
device_id: str = payload.get("device_id") device_id: str = payload.get("device_id")
print(f"[get_current_user] ✓ Token 解析成功")
print(f" - username: {username}")
print(f" - device_id: {device_id}")
print(f" - exp: {payload.get('exp')}")
if username is None: if username is None:
print("[get_current_user] ✗ username 为空")
raise credentials_exception raise credentials_exception
except jwt.ExpiredSignatureError: except jwt.ExpiredSignatureError:
print("[get_current_user] ✗ Token 已过期")
raise credentials_exception raise credentials_exception
except jwt.InvalidTokenError as e: except jwt.InvalidTokenError:
print(f"[get_current_user] ✗ Token 无效: {e}")
raise credentials_exception raise credentials_exception
except jwt.PyJWTError as e: except jwt.PyJWTError:
print(f"[get_current_user] ✗ Token 解析失败: {e}")
raise credentials_exception raise credentials_exception
# 2. Session 强校验 (如果有 device_id) # 2. Session 强校验 (如果有 device_id)
# 如果 Token 是旧版本没有 device_id可以选择放行或拒绝。为了安全建议逐步拒绝。
# 这里我们假设所有新 Token 都有 device_id
if device_id: if device_id:
print(f"[get_current_user] 开始 Session 强校验")
# 查询 User ID
from app.models.user import User from app.models.user import User
user = db.query(User).filter(User.username == username).first() user = db.query(User).filter(User.username == username).first()
if not user: if not user:
print(f"[get_current_user] ✗ 用户不存在: {username}")
raise credentials_exception raise credentials_exception
print(f"[get_current_user] ✓ 用户存在: user_id={user.id}")
# 查询活跃 Session # 查询活跃 Session
print(f"[get_current_user] 查询活跃 Session: user_id={user.id}, device_id={device_id}")
session = db.query(UserSession).filter( session = db.query(UserSession).filter(
UserSession.user_id == user.id, UserSession.user_id == user.id,
UserSession.device_id == device_id, UserSession.device_id == device_id,
@@ -92,24 +71,10 @@ def get_current_user(token: str = Depends(oauth2_scheme), db: Session = Depends(
).first() ).first()
if not session: if not session:
# Session 不存在或已失效(被踢下线/登出)
print(f"[get_current_user] ✗ Session 不存在或已失效")
# 调试:列出该用户的所有 Session
all_sessions = db.query(UserSession).filter(UserSession.user_id == user.id).all()
print(f"[get_current_user] 该用户共有 {len(all_sessions)} 个 Session:")
for s in all_sessions:
print(f" - session_id={s.id}, device_id={s.device_id}, active={s.active}")
raise HTTPException( raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, status_code=status.HTTP_401_UNAUTHORIZED,
detail="会话已失效或在其他设备登录", detail="会话已失效或在其他设备登录",
headers={"WWW-Authenticate": "Bearer"}, headers={"WWW-Authenticate": "Bearer"},
) )
print(f"[get_current_user] ✓ Session 验证通过: session_id={session.id}")
else:
print(f"[get_current_user] ⚠️ Token 中没有 device_id跳过 Session 校验")
print("="*60)
return username return username

View File

@@ -8,7 +8,15 @@ SQLALCHEMY_DATABASE_URL = getattr(settings, "DATABASE_URL", "sqlite:///./designe
# 创建数据库引擎 # 创建数据库引擎
engine = create_engine( engine = create_engine(
SQLALCHEMY_DATABASE_URL, SQLALCHEMY_DATABASE_URL,
connect_args={"check_same_thread": False} if SQLALCHEMY_DATABASE_URL.startswith("sqlite") else {} pool_pre_ping=True, # 每次连接前 ping 一下,防止连接断开
pool_recycle=3600, # 1小时回收连接
pool_size=10, # 连接池大小
max_overflow=20, # 最大溢出连接数
connect_args={
"connect_timeout": 60, # 增加连接超时
"read_timeout": 60, # 增加读取超时
"write_timeout": 60 # 增加写入超时
} if not SQLALCHEMY_DATABASE_URL.startswith("sqlite") else {"check_same_thread": False}
) )
# 会话工厂与 ORM 基类 # 会话工厂与 ORM 基类
@@ -29,6 +37,8 @@ def init_db():
from app.models.group import PluginGroup from app.models.group import PluginGroup
from app.models.session import UserSession from app.models.session import UserSession
from app.models.business import FeatureConfig, VipConfig, CheckInConfig, CheckInRecord, PointsHistory from app.models.business import FeatureConfig, VipConfig, CheckInConfig, CheckInRecord, PointsHistory
from app.models.logs import PltProcessRecord, UserActionLog, PltPiece
from app.models.chat import ChatSession, ChatMessage
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
ensure_migrations() ensure_migrations()
seed_data() seed_data()

View File

@@ -2,9 +2,8 @@ from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
import os import os
from pathlib import Path
from app.core.config import settings from app.core.config import settings
from app.api.v1 import auth, client, admin, analytics, jsx_demo, admin_config, feature, checkin, user_profile, stats from app.api.v1 import auth, client, admin, analytics, admin_config, feature, checkin, user_profile, stats, algorithm, logs, ai_chat
from app.db import init_db from app.db import init_db
from datetime import datetime from datetime import datetime
@@ -59,7 +58,6 @@ app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["aut
app.include_router(client.router, prefix=f"{settings.API_V1_STR}/client", tags=["client"]) app.include_router(client.router, prefix=f"{settings.API_V1_STR}/client", tags=["client"])
app.include_router(admin.router, prefix=f"{settings.API_V1_STR}/admin", tags=["admin"]) app.include_router(admin.router, prefix=f"{settings.API_V1_STR}/admin", tags=["admin"])
app.include_router(analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"]) app.include_router(analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"])
app.include_router(jsx_demo.router, prefix=f"{settings.API_V1_STR}/jsx_demo", tags=["jsx_demo"])
# 新增路由 # 新增路由
app.include_router(admin_config.router, prefix=settings.API_V1_STR, tags=["admin-config"]) app.include_router(admin_config.router, prefix=settings.API_V1_STR, tags=["admin-config"])
@@ -67,6 +65,9 @@ app.include_router(feature.router, prefix=settings.API_V1_STR, tags=["feature"])
app.include_router(checkin.router, prefix=settings.API_V1_STR, tags=["checkin"]) app.include_router(checkin.router, prefix=settings.API_V1_STR, tags=["checkin"])
app.include_router(user_profile.router, prefix=settings.API_V1_STR, tags=["user-profile"]) app.include_router(user_profile.router, prefix=settings.API_V1_STR, tags=["user-profile"])
app.include_router(stats.router, prefix=settings.API_V1_STR, tags=["stats"]) app.include_router(stats.router, prefix=settings.API_V1_STR, tags=["stats"])
app.include_router(algorithm.router, prefix=settings.API_V1_STR, tags=["algorithm"])
app.include_router(logs.router, prefix=settings.API_V1_STR, tags=["logs"])
app.include_router(ai_chat.router, prefix=settings.API_V1_STR, tags=["ai-chat"])
# Health Check # Health Check
@app.get("/health") @app.get("/health")
@@ -77,26 +78,6 @@ def health_check():
if IS_DEV: if IS_DEV:
# Mount archives directory for download # Mount archives directory for download
app.mount("/download", StaticFiles(directory="archives"), name="download") app.mount("/download", StaticFiles(directory="archives"), name="download")
# Mount Shell directory (登录页面)
# shell_dir = Path(__file__).parent.parent / "Designer"
# if shell_dir.exists():
# app.mount("/shell", StaticFiles(directory=str(shell_dir), html=True), name="shell")
# print(f"✓ Shell 已挂载 (Dev): {shell_dir}")
# else:
# # print(f"⚠️ Shell 目录不存在: {shell_dir}")
# # print(" 请先运行: cd Designer && npm run build:shell")
# pass
# Mount DesignerCache directory to serve Core application files
# designer_cache = Path.home() / "AppData" / "Roaming" / "DesignerCache"
# if designer_cache.exists():
# app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
# print(f"✓ Core 已挂载 (Dev): {designer_cache}")
# else:
# # Create directory if it doesn't exist
# designer_cache.mkdir(parents=True, exist_ok=True)
# app.mount("/core", StaticFiles(directory=str(designer_cache), html=True), name="core")
else: else:
print(" Production Mode: Static files are NOT mounted by FastAPI (handled by Caddy/Nginx).") print(" Production Mode: Static files are NOT mounted by FastAPI (handled by Caddy/Nginx).")

31
Server/app/models/chat.py Normal file
View File

@@ -0,0 +1,31 @@
"""
AI 对话模型
- ChatSession: 对话会话(按用户隔离)
- ChatMessage: 对话消息
"""
from sqlalchemy import Column, Integer, String, DateTime, Text, ForeignKey, func
from app.db import Base
class ChatSession(Base):
"""对话会话表 — 每个用户可以有多个对话"""
__tablename__ = "chat_sessions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
username = Column(String(64), nullable=False, index=True)
title = Column(String(200), default="新对话") # 对话标题(取首条消息摘要)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class ChatMessage(Base):
"""对话消息表"""
__tablename__ = "chat_messages"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(Integer, ForeignKey("chat_sessions.id", ondelete="CASCADE"), nullable=False, index=True)
role = Column(String(20), nullable=False) # user / assistant / system
content = Column(Text, nullable=False) # 消息内容
tool_calls = Column(Text, nullable=True) # 工具调用 JSON预留
created_at = Column(DateTime(timezone=True), server_default=func.now())

94
Server/app/models/logs.py Normal file
View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
PLT处理记录和用户操作日志模型
"""
from sqlalchemy import Column, Integer, String, DateTime, func, ForeignKey, Text, JSON, Float
from sqlalchemy.orm import relationship
from app.db import Base
class PltProcessRecord(Base):
"""PLT处理记录表"""
__tablename__ = "plt_process_records"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
# 文件信息
filename = Column(String(255), nullable=True)
file_size = Column(Integer, nullable=True) # 字节
rotated_filename = Column(String(255), nullable=True) # 旋转对比文件名
# 处理参数
size_labels = Column(JSON, nullable=True) # ["S", "M", "L", ...]
dpi = Column(Integer, default=150)
rotation = Column(Integer, default=0)
# 处理结果
total_groups = Column(Integer, default=0) # 裁片组数
total_pieces = Column(Integer, default=0) # 裁片总数
process_time_ms = Column(Integer, nullable=True) # 处理耗时(毫秒)
# 状态
status = Column(String(20), default='success') # success, error
error_message = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
# 关联
user = relationship("User", backref="plt_records")
pieces = relationship("PltPiece", back_populates="record", cascade="all, delete-orphan")
class PltPiece(Base):
"""PLT裁片详情表"""
__tablename__ = "plt_pieces"
id = Column(Integer, primary_key=True, index=True)
record_id = Column(Integer, ForeignKey("plt_process_records.id", ondelete="CASCADE"), nullable=False, index=True)
# 裁片信息
group_id = Column(Integer, nullable=False) # 组号
size = Column(String(20), nullable=False) # 尺码
image_url = Column(String(500), nullable=True) # 七牛云 URL
# 尺寸信息
width_px = Column(Integer, default=0)
height_px = Column(Integer, default=0)
width_cm = Column(Float, default=0)
height_cm = Column(Float, default=0)
left_cm = Column(Float, default=0)
top_cm = Column(Float, default=0)
center_x_cm = Column(Float, default=0)
center_y_cm = Column(Float, default=0)
# 关联
record = relationship("PltProcessRecord", back_populates="pieces")
class UserActionLog(Base):
"""用户操作日志表"""
__tablename__ = "user_action_logs"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
# 会话追踪
session_id = Column(String(64), nullable=True, index=True)
# 操作信息
action_type = Column(String(50), nullable=False, index=True) # plt.upload, plt.process, etc.
action_params = Column(JSON, nullable=True) # 操作参数
page = Column(String(50), nullable=True) # 所在页面
# 结果
result = Column(String(20), default='success') # success, error
error_message = Column(Text, nullable=True)
# 时间
duration_ms = Column(Integer, nullable=True) # 操作耗时
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
# 关联
user = relationship("User", backref="action_logs")

Some files were not shown because too many files have changed in this diff Show More