Update
This commit is contained in:
9
plugins/substancepainter/__init__.py
Normal file
9
plugins/substancepainter/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Substance Painter 插件模块
|
||||
"""
|
||||
|
||||
from .launcher import SubstancePainterLauncher, launch_substance_painter
|
||||
|
||||
__all__ = ['SubstancePainterLauncher', 'launch_substance_painter']
|
||||
435
plugins/substancepainter/launcher.py
Normal file
435
plugins/substancepainter/launcher.py
Normal file
@@ -0,0 +1,435 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Substance Painter 启动器
|
||||
负责设置 SP 库路径和自动配置
|
||||
"""
|
||||
import os
|
||||
import json
|
||||
import subprocess
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import psutil
|
||||
from typing import Dict, Optional
|
||||
from pathlib import Path
|
||||
from .registry_manager import SPRegistryManager
|
||||
|
||||
|
||||
class SubstancePainterLauncher:
|
||||
"""Substance Painter 启动器"""
|
||||
|
||||
def __init__(self, sp_exe_path: str, shelf_path: str, project_name: str = "NexusLauncher"):
|
||||
"""
|
||||
初始化 Substance Painter 启动器
|
||||
|
||||
Args:
|
||||
sp_exe_path: Substance Painter 可执行文件路径
|
||||
shelf_path: 库路径(sp_shelf_path)
|
||||
project_name: 项目名称,用作库名称
|
||||
"""
|
||||
self.sp_exe_path = sp_exe_path
|
||||
self.shelf_path = shelf_path
|
||||
self.project_name = project_name
|
||||
|
||||
# SP 配置文件路径
|
||||
self.sp_config_dir = self._get_sp_config_dir()
|
||||
|
||||
def _get_sp_config_dir(self) -> Optional[Path]:
|
||||
"""获取 Substance Painter 配置目录
|
||||
|
||||
Returns:
|
||||
配置目录路径,如果找不到返回 None
|
||||
"""
|
||||
# 方法1: 使用 USERPROFILE
|
||||
userprofile = os.environ.get('USERPROFILE', '')
|
||||
if userprofile:
|
||||
documents = Path(userprofile) / 'Documents'
|
||||
else:
|
||||
documents = Path.home() / 'Documents'
|
||||
|
||||
# 方法2: 使用注册表获取文档路径
|
||||
try:
|
||||
import winreg
|
||||
key = winreg.OpenKey(
|
||||
winreg.HKEY_CURRENT_USER,
|
||||
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
|
||||
)
|
||||
documents_path, _ = winreg.QueryValueEx(key, "Personal")
|
||||
winreg.CloseKey(key)
|
||||
documents = Path(documents_path)
|
||||
print(f"[SubstancePainter] Documents folder (from registry): {documents}")
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Could not get documents from registry: {e}")
|
||||
|
||||
# 尝试查找 Substance Painter 配置目录
|
||||
sp_dirs = [
|
||||
# SP 2023+
|
||||
documents / 'Adobe' / 'Adobe Substance 3D Painter',
|
||||
# 旧版本
|
||||
documents / 'Allegorithmic' / 'Substance Painter',
|
||||
# 备用路径
|
||||
Path(userprofile) / 'AppData' / 'Local' / 'Adobe' / 'Adobe Substance 3D Painter',
|
||||
Path(userprofile) / 'AppData' / 'Roaming' / 'Adobe' / 'Adobe Substance 3D Painter',
|
||||
]
|
||||
|
||||
print(f"[SubstancePainter] Searching for config directory...")
|
||||
for sp_dir in sp_dirs:
|
||||
print(f"[SubstancePainter] Checking: {sp_dir}")
|
||||
if sp_dir.exists():
|
||||
print(f"[SubstancePainter] ✓ Found config directory: {sp_dir}")
|
||||
|
||||
# 列出配置目录中的文件
|
||||
try:
|
||||
config_files = list(sp_dir.glob('*.json'))
|
||||
if config_files:
|
||||
print(f"[SubstancePainter] Config files found:")
|
||||
for f in config_files:
|
||||
print(f"[SubstancePainter] - {f.name}")
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Could not list config files: {e}")
|
||||
|
||||
return sp_dir
|
||||
|
||||
# 如果找不到,尝试创建默认目录
|
||||
default_dir = documents / 'Adobe' / 'Adobe Substance 3D Painter'
|
||||
print(f"[SubstancePainter] Config directory not found")
|
||||
print(f"[SubstancePainter] Will create default directory: {default_dir}")
|
||||
|
||||
try:
|
||||
default_dir.mkdir(parents=True, exist_ok=True)
|
||||
print(f"[SubstancePainter] ✓ Created config directory")
|
||||
return default_dir
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] ✗ Failed to create config directory: {e}")
|
||||
return None
|
||||
|
||||
def _get_shelf_config_path(self) -> Optional[Path]:
|
||||
"""获取库配置文件路径
|
||||
|
||||
Returns:
|
||||
库配置文件路径,如果找不到返回 None
|
||||
"""
|
||||
if not self.sp_config_dir:
|
||||
return None
|
||||
|
||||
# SP 库配置文件可能的位置
|
||||
possible_configs = [
|
||||
self.sp_config_dir / 'shelf.json',
|
||||
self.sp_config_dir / 'shelves.json',
|
||||
self.sp_config_dir / 'assets.json',
|
||||
self.sp_config_dir / 'preferences.json',
|
||||
]
|
||||
|
||||
# 先检查是否已存在配置文件
|
||||
for config_path in possible_configs:
|
||||
if config_path.exists():
|
||||
print(f"[SubstancePainter] Found existing config: {config_path.name}")
|
||||
return config_path
|
||||
|
||||
# 如果都不存在,使用默认的 shelf.json
|
||||
shelf_config = self.sp_config_dir / 'shelf.json'
|
||||
print(f"[SubstancePainter] Will create new config: {shelf_config.name}")
|
||||
|
||||
return shelf_config
|
||||
|
||||
def _create_default_shelf_config(self) -> dict:
|
||||
"""创建默认的库配置
|
||||
|
||||
Returns:
|
||||
默认库配置字典
|
||||
"""
|
||||
# SP 的库配置格式(基于官方文档)
|
||||
# 库会自动创建标准文件夹结构:
|
||||
# alphas, colorluts, effects, environments, generators, materials, etc.
|
||||
|
||||
# SP 库名称规则:只能包含小写字母、数字、下划线和连字符
|
||||
import re
|
||||
safe_name = self.project_name.lower().replace(" ", "_")
|
||||
safe_name = re.sub(r'[^a-z0-9_-]', '', safe_name)
|
||||
|
||||
return {
|
||||
"libraries": [
|
||||
{
|
||||
"name": safe_name,
|
||||
"path": self.shelf_path.replace("\\", "/"),
|
||||
"default": True
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
def _update_shelf_config(self) -> bool:
|
||||
"""更新 Substance Painter 库配置
|
||||
|
||||
Returns:
|
||||
是否成功更新
|
||||
"""
|
||||
try:
|
||||
shelf_config_path = self._get_shelf_config_path()
|
||||
|
||||
if not shelf_config_path:
|
||||
print(f"[SubstancePainter] Warning: Cannot update shelf config (config path not found)")
|
||||
print(f"[SubstancePainter] SP will start with default library settings")
|
||||
return False # 返回 False 但不影响启动
|
||||
|
||||
# 读取现有配置(如果存在)
|
||||
if shelf_config_path.exists():
|
||||
try:
|
||||
with open(shelf_config_path, 'r', encoding='utf-8') as f:
|
||||
config = json.load(f)
|
||||
print(f"[SubstancePainter] Loaded existing config from: {shelf_config_path.name}")
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Failed to load existing config: {e}")
|
||||
config = self._create_default_shelf_config()
|
||||
else:
|
||||
print(f"[SubstancePainter] Creating new config")
|
||||
config = self._create_default_shelf_config()
|
||||
|
||||
# 确保有 libraries 列表(SP 使用 "libraries" 而不是 "shelves")
|
||||
if "libraries" not in config:
|
||||
config["libraries"] = []
|
||||
|
||||
# 清理项目名称(移除空格和特殊字符,转为小写)
|
||||
# SP 库名称规则:只能包含小写字母、数字、下划线和连字符
|
||||
import re
|
||||
clean_project_name = self.project_name.lower().replace(" ", "_")
|
||||
clean_project_name = re.sub(r'[^a-z0-9_-]', '', clean_project_name)
|
||||
|
||||
# 查找是否已存在同名库
|
||||
existing_library = None
|
||||
for lib in config["libraries"]:
|
||||
if lib.get("name") == clean_project_name:
|
||||
existing_library = lib
|
||||
break
|
||||
|
||||
if existing_library:
|
||||
# 更新现有库
|
||||
print(f"[SubstancePainter] Updating existing library: {clean_project_name}")
|
||||
existing_library["path"] = self.shelf_path.replace("\\", "/")
|
||||
existing_library["default"] = True
|
||||
else:
|
||||
# 添加新库
|
||||
print(f"[SubstancePainter] Adding new library: {clean_project_name}")
|
||||
config["libraries"].append({
|
||||
"name": clean_project_name,
|
||||
"path": self.shelf_path.replace("\\", "/"),
|
||||
"default": True
|
||||
})
|
||||
|
||||
# 将其他库设置为非默认
|
||||
for lib in config["libraries"]:
|
||||
if lib.get("name") != clean_project_name:
|
||||
lib["default"] = False
|
||||
|
||||
# 保存配置
|
||||
shelf_config_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(shelf_config_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"[SubstancePainter] ✓ Shelf config updated successfully")
|
||||
print(f"[SubstancePainter] Library: {self.project_name}")
|
||||
print(f"[SubstancePainter] Path: {self.shelf_path}")
|
||||
print(f"[SubstancePainter] Default: True")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Error updating shelf config: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _create_library_structure(self) -> bool:
|
||||
"""创建 SP 库的标准文件夹结构
|
||||
|
||||
Returns:
|
||||
是否成功创建
|
||||
"""
|
||||
try:
|
||||
# SP 库的标准文件夹结构
|
||||
standard_folders = [
|
||||
'alphas', # Alpha 贴图
|
||||
'brushes', # 笔刷预设
|
||||
'colorluts', # 颜色查找表
|
||||
'effects', # 滤镜/效果
|
||||
'environments', # 环境贴图
|
||||
'generators', # 生成器
|
||||
'materials', # 材质
|
||||
'particles', # 粒子预设
|
||||
'presets', # 预设
|
||||
'shaders', # 着色器
|
||||
'smart-materials', # 智能材质
|
||||
'smart-masks', # 智能蒙版
|
||||
'textures', # 纹理
|
||||
'export-presets', # 导出预设
|
||||
]
|
||||
|
||||
# 确保使用绝对路径
|
||||
base_path = Path(self.shelf_path).resolve()
|
||||
print(f"[SubstancePainter] Library base path: {base_path}")
|
||||
|
||||
# 创建基础目录
|
||||
if not base_path.exists():
|
||||
print(f"[SubstancePainter] Creating library directory...")
|
||||
base_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 创建标准文件夹
|
||||
created_folders = []
|
||||
for folder in standard_folders:
|
||||
folder_path = base_path / folder
|
||||
if not folder_path.exists():
|
||||
folder_path.mkdir(exist_ok=True)
|
||||
created_folders.append(folder)
|
||||
|
||||
if created_folders:
|
||||
print(f"[SubstancePainter] Created {len(created_folders)} standard folders")
|
||||
print(f"[SubstancePainter] ✓ Library structure ready")
|
||||
else:
|
||||
print(f"[SubstancePainter] ✓ Library structure already exists")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Error creating library structure: {e}")
|
||||
return False
|
||||
|
||||
def _setup_environment(self) -> Dict[str, str]:
|
||||
"""设置 Substance Painter 环境变量
|
||||
|
||||
Returns:
|
||||
包含环境变量的字典
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
|
||||
# 创建库文件夹结构
|
||||
self._create_library_structure()
|
||||
|
||||
# 设置环境变量供 SP Python 插件使用
|
||||
# SP 库名称规则:只能包含小写字母、数字、下划线和连字符
|
||||
import re
|
||||
clean_project_name = self.project_name.lower().replace(" ", "_")
|
||||
clean_project_name = re.sub(r'[^a-z0-9_-]', '', clean_project_name)
|
||||
env["NEXUS_SP_LIBRARY_NAME"] = clean_project_name
|
||||
env["NEXUS_SP_LIBRARY_PATH"] = self.shelf_path
|
||||
print(f"[SubstancePainter] Set NEXUS_SP_LIBRARY_NAME: {clean_project_name}")
|
||||
print(f"[SubstancePainter] Set NEXUS_SP_LIBRARY_PATH: {self.shelf_path}")
|
||||
|
||||
# 创建临时插件目录并复制插件
|
||||
try:
|
||||
# 创建临时目录
|
||||
temp_plugin_dir = Path(tempfile.gettempdir()) / "NexusLauncher_SP_Plugins"
|
||||
temp_plugin_dir.mkdir(exist_ok=True)
|
||||
|
||||
# 复制插件文件
|
||||
plugin_source = Path(__file__).parent / "sp_api_plugin.py"
|
||||
plugin_dest = temp_plugin_dir / "sp_api_plugin.py"
|
||||
|
||||
if plugin_source.exists():
|
||||
shutil.copy2(plugin_source, plugin_dest)
|
||||
print(f"[SubstancePainter] Copied plugin to: {plugin_dest}")
|
||||
|
||||
# 设置 SP 插件路径
|
||||
env["SUBSTANCE_PAINTER_PLUGINS_PATH"] = str(temp_plugin_dir)
|
||||
print(f"[SubstancePainter] Set SUBSTANCE_PAINTER_PLUGINS_PATH: {temp_plugin_dir}")
|
||||
else:
|
||||
print(f"[SubstancePainter] Warning: Plugin source not found: {plugin_source}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Warning: Failed to setup plugin: {e}")
|
||||
|
||||
return env
|
||||
|
||||
def _is_sp_running(self) -> bool:
|
||||
"""检查 SP 是否正在运行"""
|
||||
for proc in psutil.process_iter(['name']):
|
||||
try:
|
||||
if 'Adobe Substance 3D Painter' in proc.info['name']:
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _kill_sp(self):
|
||||
"""关闭所有 SP 进程"""
|
||||
print(f"[SubstancePainter] Closing existing SP instances...")
|
||||
for proc in psutil.process_iter(['name', 'pid']):
|
||||
try:
|
||||
if 'Adobe Substance 3D Painter' in proc.info['name']:
|
||||
proc.kill()
|
||||
print(f"[SubstancePainter] Killed process: {proc.info['pid']}")
|
||||
except:
|
||||
pass
|
||||
time.sleep(2) # 等待进程完全关闭
|
||||
|
||||
def launch(self) -> bool:
|
||||
"""启动 Substance Painter
|
||||
|
||||
Returns:
|
||||
是否成功启动
|
||||
"""
|
||||
try:
|
||||
# 检查 SP 可执行文件是否存在
|
||||
if not os.path.exists(self.sp_exe_path):
|
||||
print(f"[SubstancePainter] Error: Executable not found: {self.sp_exe_path}")
|
||||
return False
|
||||
|
||||
print(f"[SubstancePainter] ========================================")
|
||||
print(f"[SubstancePainter] Launching Substance Painter")
|
||||
print(f"[SubstancePainter] ========================================")
|
||||
print(f"[SubstancePainter] Project: {self.project_name}")
|
||||
print(f"[SubstancePainter] Shelf Path: {self.shelf_path}")
|
||||
print(f"")
|
||||
|
||||
# 步骤 1: 清理旧的项目库
|
||||
print(f"[SubstancePainter] Step 1: Cleaning old project libraries...")
|
||||
SPRegistryManager.remove_project_libraries()
|
||||
print(f"")
|
||||
|
||||
# 步骤 2: 添加当前项目库
|
||||
print(f"[SubstancePainter] Step 2: Adding current project library...")
|
||||
self._create_library_structure() # 确保库文件夹存在
|
||||
success = SPRegistryManager.add_project_library(self.project_name, self.shelf_path, set_as_default=True)
|
||||
if not success:
|
||||
print(f"[SubstancePainter] Warning: Failed to add library to registry")
|
||||
print(f"")
|
||||
|
||||
# 步骤 3: 检查是否需要重启 SP
|
||||
if self._is_sp_running():
|
||||
print(f"[SubstancePainter] Step 3: SP is running, restarting to apply changes...")
|
||||
self._kill_sp()
|
||||
time.sleep(2)
|
||||
else:
|
||||
print(f"[SubstancePainter] Step 3: No restart needed")
|
||||
print(f"")
|
||||
|
||||
# 步骤 4: 设置环境变量
|
||||
env = self._setup_environment()
|
||||
|
||||
# 步骤 5: 启动 Substance Painter
|
||||
print(f"[SubstancePainter] Step 4: Starting application...")
|
||||
subprocess.Popen([self.sp_exe_path], env=env)
|
||||
|
||||
print(f"[SubstancePainter] ✓ Substance Painter launched successfully")
|
||||
print(f"[SubstancePainter] ========================================")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[SubstancePainter] Error: Failed to launch: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def launch_substance_painter(sp_exe_path: str, shelf_path: str, project_name: str = "NexusLauncher") -> bool:
|
||||
"""启动 Substance Painter 的便捷函数
|
||||
|
||||
Args:
|
||||
sp_exe_path: Substance Painter 可执行文件路径
|
||||
shelf_path: 库路径(sp_shelf_path)
|
||||
project_name: 项目名称,用作库名称
|
||||
|
||||
Returns:
|
||||
是否成功启动
|
||||
"""
|
||||
launcher = SubstancePainterLauncher(sp_exe_path, shelf_path, project_name)
|
||||
return launcher.launch()
|
||||
252
plugins/substancepainter/registry_manager.py
Normal file
252
plugins/substancepainter/registry_manager.py
Normal file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Substance Painter 注册表管理器
|
||||
用于在启动前配置库,关闭后清理库
|
||||
"""
|
||||
|
||||
import winreg
|
||||
import re
|
||||
import os
|
||||
|
||||
|
||||
class SPRegistryManager:
|
||||
"""SP 注册表管理器"""
|
||||
|
||||
REGISTRY_KEY = r"SOFTWARE\Adobe\Adobe Substance 3D Painter\Shelf\pathInfos"
|
||||
SHELF_KEY = r"SOFTWARE\Adobe\Adobe Substance 3D Painter\Shelf"
|
||||
|
||||
@staticmethod
|
||||
def normalize_library_name(name):
|
||||
"""规范化库名称(小写、下划线、连字符)"""
|
||||
safe_name = name.lower().replace(' ', '_')
|
||||
safe_name = re.sub(r'[^a-z0-9_-]', '', safe_name)
|
||||
return safe_name
|
||||
|
||||
@staticmethod
|
||||
def get_all_libraries():
|
||||
"""获取所有库配置
|
||||
|
||||
Returns:
|
||||
list: [(id, name, path, disabled), ...]
|
||||
"""
|
||||
try:
|
||||
reg_conn = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
key = winreg.OpenKey(reg_conn, SPRegistryManager.REGISTRY_KEY, 0, winreg.KEY_READ)
|
||||
|
||||
libraries = []
|
||||
sub_key_count = winreg.QueryInfoKey(key)[0]
|
||||
|
||||
for i in range(sub_key_count):
|
||||
sub_key_name = winreg.EnumKey(key, i)
|
||||
sub_key = winreg.OpenKey(reg_conn, f"{SPRegistryManager.REGISTRY_KEY}\\{sub_key_name}", 0, winreg.KEY_READ)
|
||||
|
||||
try:
|
||||
name = winreg.QueryValueEx(sub_key, "name")[0]
|
||||
path = winreg.QueryValueEx(sub_key, "path")[0]
|
||||
disabled = winreg.QueryValueEx(sub_key, "disabled")[0]
|
||||
libraries.append((int(sub_key_name), name, path, disabled))
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
winreg.CloseKey(sub_key)
|
||||
|
||||
winreg.CloseKey(key)
|
||||
return libraries
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Registry] Error reading libraries: {e}")
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def remove_project_libraries():
|
||||
"""删除所有项目库(保留系统库)
|
||||
|
||||
系统库:your_assets, starter_assets, system_fonts, user_fonts
|
||||
"""
|
||||
system_libs = ['your_assets', 'starter_assets', 'system_fonts', 'user_fonts']
|
||||
|
||||
try:
|
||||
libraries = SPRegistryManager.get_all_libraries()
|
||||
to_remove = []
|
||||
|
||||
for lib_id, name, path, disabled in libraries:
|
||||
if name not in system_libs:
|
||||
to_remove.append(lib_id)
|
||||
print(f"[Registry] Will remove: {name} (ID: {lib_id})")
|
||||
|
||||
if not to_remove:
|
||||
print(f"[Registry] No project libraries to remove")
|
||||
return True
|
||||
|
||||
# 删除项目库
|
||||
reg_conn = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
for lib_id in to_remove:
|
||||
try:
|
||||
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, f"{SPRegistryManager.REGISTRY_KEY}\\{lib_id}")
|
||||
print(f"[Registry] Removed library ID: {lib_id}")
|
||||
except Exception as e:
|
||||
print(f"[Registry] Error removing {lib_id}: {e}")
|
||||
|
||||
# 重新编号剩余的库(确保连续)
|
||||
SPRegistryManager._reindex_libraries()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Registry] Error removing libraries: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _reindex_libraries():
|
||||
"""重新编号库,确保 ID 连续"""
|
||||
try:
|
||||
libraries = SPRegistryManager.get_all_libraries()
|
||||
libraries.sort(key=lambda x: x[0]) # 按 ID 排序
|
||||
|
||||
reg_conn = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
|
||||
# 如果已经连续,不需要重新编号
|
||||
expected_ids = list(range(1, len(libraries) + 1))
|
||||
actual_ids = [lib[0] for lib in libraries]
|
||||
|
||||
if expected_ids == actual_ids:
|
||||
print(f"[Registry] Library IDs are already continuous")
|
||||
return
|
||||
|
||||
print(f"[Registry] Reindexing libraries...")
|
||||
|
||||
# 先将所有库移到临时 ID
|
||||
temp_offset = 1000
|
||||
for lib_id, name, path, disabled in libraries:
|
||||
old_key_path = f"{SPRegistryManager.REGISTRY_KEY}\\{lib_id}"
|
||||
new_key_path = f"{SPRegistryManager.REGISTRY_KEY}\\{lib_id + temp_offset}"
|
||||
|
||||
# 读取旧值
|
||||
old_key = winreg.OpenKey(reg_conn, old_key_path, 0, winreg.KEY_READ)
|
||||
name_val = winreg.QueryValueEx(old_key, "name")[0]
|
||||
path_val = winreg.QueryValueEx(old_key, "path")[0]
|
||||
disabled_val = winreg.QueryValueEx(old_key, "disabled")[0]
|
||||
winreg.CloseKey(old_key)
|
||||
|
||||
# 创建新键
|
||||
new_key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, new_key_path)
|
||||
winreg.SetValueEx(new_key, "name", 0, winreg.REG_SZ, name_val)
|
||||
winreg.SetValueEx(new_key, "path", 0, winreg.REG_SZ, path_val)
|
||||
winreg.SetValueEx(new_key, "disabled", 0, winreg.REG_SZ, disabled_val)
|
||||
winreg.CloseKey(new_key)
|
||||
|
||||
# 删除旧键
|
||||
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, old_key_path)
|
||||
|
||||
# 再将临时 ID 移到正确的连续 ID
|
||||
for new_id, (old_id, name, path, disabled) in enumerate(libraries, start=1):
|
||||
temp_id = old_id + temp_offset
|
||||
temp_key_path = f"{SPRegistryManager.REGISTRY_KEY}\\{temp_id}"
|
||||
final_key_path = f"{SPRegistryManager.REGISTRY_KEY}\\{new_id}"
|
||||
|
||||
# 读取临时键
|
||||
temp_key = winreg.OpenKey(reg_conn, temp_key_path, 0, winreg.KEY_READ)
|
||||
name_val = winreg.QueryValueEx(temp_key, "name")[0]
|
||||
path_val = winreg.QueryValueEx(temp_key, "path")[0]
|
||||
disabled_val = winreg.QueryValueEx(temp_key, "disabled")[0]
|
||||
winreg.CloseKey(temp_key)
|
||||
|
||||
# 创建最终键
|
||||
final_key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, final_key_path)
|
||||
winreg.SetValueEx(final_key, "name", 0, winreg.REG_SZ, name_val)
|
||||
winreg.SetValueEx(final_key, "path", 0, winreg.REG_SZ, path_val)
|
||||
winreg.SetValueEx(final_key, "disabled", 0, winreg.REG_SZ, disabled_val)
|
||||
winreg.CloseKey(final_key)
|
||||
|
||||
# 删除临时键
|
||||
winreg.DeleteKey(winreg.HKEY_CURRENT_USER, temp_key_path)
|
||||
|
||||
# 更新 size
|
||||
key = winreg.OpenKeyEx(reg_conn, SPRegistryManager.REGISTRY_KEY, 0, winreg.KEY_SET_VALUE)
|
||||
winreg.SetValueEx(key, "size", 0, winreg.REG_DWORD, len(libraries))
|
||||
winreg.CloseKey(key)
|
||||
|
||||
print(f"[Registry] Reindexing complete, new size: {len(libraries)}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Registry] Error reindexing: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
@staticmethod
|
||||
def add_project_library(library_name, library_path, set_as_default=True):
|
||||
"""添加项目库
|
||||
|
||||
Args:
|
||||
library_name: 库名称
|
||||
library_path: 库路径
|
||||
set_as_default: 是否设置为默认
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
safe_name = SPRegistryManager.normalize_library_name(library_name)
|
||||
normalized_path = library_path.replace('\\', '/')
|
||||
|
||||
print(f"[Registry] Adding library: {safe_name}")
|
||||
print(f"[Registry] Path: {normalized_path}")
|
||||
|
||||
# 检查是否已存在
|
||||
libraries = SPRegistryManager.get_all_libraries()
|
||||
for lib_id, name, path, disabled in libraries:
|
||||
if name == safe_name:
|
||||
print(f"[Registry] Library already exists: {name}")
|
||||
return True
|
||||
|
||||
# 找到下一个 ID
|
||||
if libraries:
|
||||
next_id = max(lib[0] for lib in libraries) + 1
|
||||
else:
|
||||
next_id = 1
|
||||
|
||||
# 创建新库
|
||||
reg_conn = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
new_key_path = f"{SPRegistryManager.REGISTRY_KEY}\\{next_id}"
|
||||
new_key = winreg.CreateKey(winreg.HKEY_CURRENT_USER, new_key_path)
|
||||
winreg.SetValueEx(new_key, "name", 0, winreg.REG_SZ, safe_name)
|
||||
winreg.SetValueEx(new_key, "path", 0, winreg.REG_SZ, normalized_path)
|
||||
winreg.SetValueEx(new_key, "disabled", 0, winreg.REG_SZ, "false")
|
||||
winreg.CloseKey(new_key)
|
||||
|
||||
# 更新 size
|
||||
key = winreg.OpenKeyEx(reg_conn, SPRegistryManager.REGISTRY_KEY, 0, winreg.KEY_SET_VALUE)
|
||||
winreg.SetValueEx(key, "size", 0, winreg.REG_DWORD, len(libraries) + 1)
|
||||
winreg.CloseKey(key)
|
||||
|
||||
print(f"[Registry] Library added with ID: {next_id}")
|
||||
|
||||
# 设置为默认库
|
||||
if set_as_default:
|
||||
shelf_key = winreg.OpenKeyEx(reg_conn, SPRegistryManager.SHELF_KEY, 0, winreg.KEY_SET_VALUE)
|
||||
winreg.SetValueEx(shelf_key, "writableShelf", 0, winreg.REG_SZ, safe_name)
|
||||
winreg.CloseKey(shelf_key)
|
||||
print(f"[Registry] Set as default library")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[Registry] Error adding library: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def needs_restart():
|
||||
"""检查是否需要重启 SP 以应用更改
|
||||
|
||||
Returns:
|
||||
bool: 是否需要重启
|
||||
"""
|
||||
# 如果 SP 正在运行,注册表更改需要重启才能生效
|
||||
import psutil
|
||||
for proc in psutil.process_iter(['name']):
|
||||
if 'Adobe Substance 3D Painter' in proc.info['name']:
|
||||
return True
|
||||
return False
|
||||
318
plugins/substancepainter/sp_api_plugin.py
Normal file
318
plugins/substancepainter/sp_api_plugin.py
Normal file
@@ -0,0 +1,318 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Substance Painter API 插件
|
||||
通过 Python API 自动添加项目库
|
||||
|
||||
此文件会被复制到临时目录,在 SP 启动时自动执行
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def setup_project_library_via_registry(library_name, library_path):
|
||||
"""通过注册表设置项目库(Windows)
|
||||
|
||||
Args:
|
||||
library_name: 库名称
|
||||
library_path: 库路径
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
import winreg
|
||||
import re
|
||||
|
||||
# SP 库名称规则:只能包含小写字母、数字、下划线和连字符
|
||||
safe_library_name = library_name.lower().replace(' ', '_')
|
||||
safe_library_name = re.sub(r'[^a-z0-9_-]', '', safe_library_name)
|
||||
|
||||
print(f"[NexusLauncher] Setting up library via registry: {library_name}")
|
||||
if safe_library_name != library_name:
|
||||
print(f"[NexusLauncher] Normalized name: {safe_library_name}")
|
||||
print(f"[NexusLauncher] Path: {library_path}")
|
||||
|
||||
# 检查路径是否存在
|
||||
if not os.path.exists(library_path):
|
||||
print(f"[NexusLauncher] Error: Library path does not exist: {library_path}")
|
||||
return False
|
||||
|
||||
# 规范化路径
|
||||
normalized_path = library_path.replace('\\', '/')
|
||||
|
||||
# 注册表路径
|
||||
registry_key_name = r"SOFTWARE\Adobe\Adobe Substance 3D Painter\Shelf\pathInfos"
|
||||
|
||||
# 连接到注册表
|
||||
reg_connection = winreg.ConnectRegistry(None, winreg.HKEY_CURRENT_USER)
|
||||
|
||||
try:
|
||||
# 打开父键
|
||||
key = winreg.OpenKey(reg_connection, registry_key_name, 0, winreg.KEY_READ)
|
||||
except FileNotFoundError:
|
||||
print(f"[NexusLauncher] Registry key not found. Please open SP settings once to create it.")
|
||||
return False
|
||||
|
||||
# 检查是否已存在同名库
|
||||
sub_key_count = winreg.QueryInfoKey(key)[0]
|
||||
shelf_exists = False
|
||||
shelf_number = 0
|
||||
|
||||
for x in range(sub_key_count):
|
||||
sub_key_name = winreg.EnumKey(key, x)
|
||||
shelf_number = max(shelf_number, int(sub_key_name))
|
||||
|
||||
sub_key = winreg.OpenKey(reg_connection, registry_key_name + "\\" + sub_key_name, 0, winreg.KEY_READ)
|
||||
try:
|
||||
existing_name = winreg.QueryValueEx(sub_key, "name")[0]
|
||||
existing_path = winreg.QueryValueEx(sub_key, "path")[0]
|
||||
|
||||
if existing_name == safe_library_name:
|
||||
print(f"[NexusLauncher] Library already exists: {existing_name} at {existing_path}")
|
||||
shelf_exists = True
|
||||
winreg.CloseKey(sub_key)
|
||||
break
|
||||
finally:
|
||||
winreg.CloseKey(sub_key)
|
||||
|
||||
if not shelf_exists:
|
||||
# 添加新库
|
||||
shelf_number += 1
|
||||
print(f"[NexusLauncher] Adding new library with ID: {shelf_number}")
|
||||
|
||||
# 创建新键
|
||||
new_key = winreg.CreateKey(key, str(shelf_number))
|
||||
winreg.SetValueEx(new_key, "disabled", 0, winreg.REG_SZ, "false")
|
||||
winreg.SetValueEx(new_key, "name", 0, winreg.REG_SZ, safe_library_name)
|
||||
winreg.SetValueEx(new_key, "path", 0, winreg.REG_SZ, normalized_path)
|
||||
winreg.CloseKey(new_key)
|
||||
|
||||
# 更新计数
|
||||
try:
|
||||
count = winreg.QueryValueEx(key, "size")[0]
|
||||
new_count = count + 1
|
||||
except:
|
||||
# 如果读取失败,使用当前最大的 shelf_number + 1
|
||||
new_count = shelf_number + 1
|
||||
|
||||
winreg.CloseKey(key)
|
||||
|
||||
key = winreg.OpenKeyEx(reg_connection, registry_key_name, 0, winreg.KEY_SET_VALUE)
|
||||
winreg.SetValueEx(key, "size", 0, winreg.REG_DWORD, new_count)
|
||||
print(f"[NexusLauncher] Updated size to: {new_count}")
|
||||
|
||||
print(f"[NexusLauncher] ✓ Library added to registry successfully")
|
||||
|
||||
# 设置为默认库(writableShelf)
|
||||
try:
|
||||
shelf_key = r"SOFTWARE\Adobe\Adobe Substance 3D Painter\Shelf"
|
||||
default_key = winreg.OpenKeyEx(reg_connection, shelf_key, 0, winreg.KEY_SET_VALUE)
|
||||
winreg.SetValueEx(default_key, "writableShelf", 0, winreg.REG_SZ, safe_library_name)
|
||||
winreg.CloseKey(default_key)
|
||||
print(f"[NexusLauncher] ✓ Set as default library (writableShelf)")
|
||||
except Exception as e:
|
||||
print(f"[NexusLauncher] Warning: Could not set as default: {e}")
|
||||
|
||||
winreg.CloseKey(key)
|
||||
else:
|
||||
winreg.CloseKey(key)
|
||||
|
||||
print(f"[NexusLauncher] ========================================")
|
||||
print(f"[NexusLauncher] ✓ Library setup complete")
|
||||
print(f"[NexusLauncher] Note: Restart SP to see the changes")
|
||||
print(f"[NexusLauncher] ========================================")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[NexusLauncher] Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def setup_project_library(library_name, library_path, set_as_default=True):
|
||||
"""设置项目库
|
||||
|
||||
Args:
|
||||
library_name: 库名称
|
||||
library_path: 库路径
|
||||
set_as_default: 是否设置为默认库
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
# 延迟导入,因为只有在 SP 中才能导入
|
||||
import substance_painter.resource
|
||||
|
||||
# SP 库名称规则:只能包含小写字母、数字、下划线和连字符
|
||||
# 将名称转换为小写并替换无效字符
|
||||
safe_library_name = library_name.lower().replace(' ', '_')
|
||||
# 移除其他无效字符
|
||||
import re
|
||||
safe_library_name = re.sub(r'[^a-z0-9_-]', '', safe_library_name)
|
||||
|
||||
print(f"[NexusLauncher] Setting up library: {library_name}")
|
||||
if safe_library_name != library_name:
|
||||
print(f"[NexusLauncher] Normalized name: {safe_library_name} (SP naming rules)")
|
||||
print(f"[NexusLauncher] Path: {library_path}")
|
||||
|
||||
# 检查路径是否存在
|
||||
if not os.path.exists(library_path):
|
||||
print(f"[NexusLauncher] Error: Library path does not exist: {library_path}")
|
||||
return False
|
||||
|
||||
# 获取所有现有的 Shelf
|
||||
existing_shelves = substance_painter.resource.Shelves.all()
|
||||
print(f"[NexusLauncher] Found {len(existing_shelves)} existing shelves")
|
||||
|
||||
# 检查是否已存在同名或同路径的库
|
||||
shelf_exists = False
|
||||
for shelf in existing_shelves:
|
||||
shelf_name = shelf.name()
|
||||
shelf_path = shelf.path()
|
||||
print(f"[NexusLauncher] Existing shelf: {shelf_name} -> {shelf_path}")
|
||||
|
||||
# 规范化路径进行比较
|
||||
from os.path import normpath
|
||||
normalized_shelf_path = normpath(shelf_path).lower()
|
||||
normalized_library_path = normpath(library_path).lower()
|
||||
|
||||
if shelf_name == safe_library_name or normalized_shelf_path == normalized_library_path:
|
||||
print(f"[NexusLauncher] Library already exists: {shelf_name} at {shelf_path}")
|
||||
shelf_exists = True
|
||||
existing_shelf = shelf
|
||||
break
|
||||
|
||||
if not shelf_exists:
|
||||
# 添加新库
|
||||
print(f"[NexusLauncher] Adding new library...")
|
||||
try:
|
||||
# 探索可用的 API 方法
|
||||
print(f"[NexusLauncher] Available Shelf methods: {[m for m in dir(substance_painter.resource.Shelf) if not m.startswith('_')]}")
|
||||
print(f"[NexusLauncher] Available Shelves methods: {[m for m in dir(substance_painter.resource.Shelves) if not m.startswith('_')]}")
|
||||
|
||||
# 检查是否有项目打开(添加库需要关闭项目)
|
||||
try:
|
||||
import substance_painter.project
|
||||
if substance_painter.project.is_open():
|
||||
print(f"[NexusLauncher] ERROR: A project is currently open!")
|
||||
print(f"[NexusLauncher] According to SP API docs, no project should be open when adding shelves")
|
||||
print(f"[NexusLauncher] Please close the project and try again")
|
||||
print(f"[NexusLauncher] Or add the library manually: Edit -> Settings -> Libraries")
|
||||
return False
|
||||
else:
|
||||
print(f"[NexusLauncher] ✓ No project is open, safe to add shelf")
|
||||
except Exception as e:
|
||||
print(f"[NexusLauncher] Warning: Could not check project status: {e}")
|
||||
|
||||
# 规范化路径为正斜杠格式(SP 可能需要这个)
|
||||
normalized_path = library_path.replace('\\', '/')
|
||||
print(f"[NexusLauncher] Normalized path: {normalized_path}")
|
||||
|
||||
# 使用 Shelves.add 方法(使用规范化的名称)
|
||||
print(f"[NexusLauncher] Calling Shelves.add('{safe_library_name}', '{normalized_path}')")
|
||||
new_shelf = substance_painter.resource.Shelves.add(safe_library_name, normalized_path)
|
||||
print(f"[NexusLauncher] ✓ Library added successfully")
|
||||
|
||||
# 刷新资源
|
||||
substance_painter.resource.Shelves.refresh_all()
|
||||
print(f"[NexusLauncher] ✓ Resources refreshed")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[NexusLauncher] Error adding library: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
else:
|
||||
print(f"[NexusLauncher] Library already configured")
|
||||
|
||||
# 设置为默认库(如果需要)
|
||||
if set_as_default:
|
||||
# 注意:SP API 可能没有直接设置默认库的方法
|
||||
# 这需要通过修改配置文件来实现
|
||||
print(f"[NexusLauncher] Note: Default library setting may require manual configuration")
|
||||
|
||||
print(f"[NexusLauncher] ========================================")
|
||||
print(f"[NexusLauncher] ✓ Library setup complete")
|
||||
print(f"[NexusLauncher] ========================================")
|
||||
|
||||
return True
|
||||
|
||||
except ImportError as e:
|
||||
print(f"[NexusLauncher] Error: Cannot import substance_painter module")
|
||||
print(f"[NexusLauncher] This script must be run inside Substance Painter")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"[NexusLauncher] Error: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
|
||||
def get_library_info_from_env():
|
||||
"""从环境变量获取库信息
|
||||
|
||||
Returns:
|
||||
tuple: (library_name, library_path) 或 (None, None)
|
||||
"""
|
||||
# NexusLauncher 会设置这些环境变量
|
||||
library_name = os.environ.get('NEXUS_SP_LIBRARY_NAME')
|
||||
library_path = os.environ.get('NEXUS_SP_LIBRARY_PATH')
|
||||
|
||||
if library_name and library_path:
|
||||
return library_name, library_path
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print(f"[NexusLauncher] ========================================")
|
||||
print(f"[NexusLauncher] Substance Painter Library Setup Plugin")
|
||||
print(f"[NexusLauncher] ========================================")
|
||||
print(f"[NexusLauncher] Plugin loaded from: {__file__}")
|
||||
print(f"[NexusLauncher] Python version: {sys.version}")
|
||||
|
||||
# 检查环境变量
|
||||
print(f"[NexusLauncher] Checking environment variables...")
|
||||
library_name = os.environ.get('NEXUS_SP_LIBRARY_NAME')
|
||||
library_path = os.environ.get('NEXUS_SP_LIBRARY_PATH')
|
||||
print(f"[NexusLauncher] NEXUS_SP_LIBRARY_NAME: {library_name}")
|
||||
print(f"[NexusLauncher] NEXUS_SP_LIBRARY_PATH: {library_path}")
|
||||
|
||||
if not library_name or not library_path:
|
||||
print(f"[NexusLauncher] Error: Library info not found in environment variables")
|
||||
print(f"[NexusLauncher] Please launch SP through NexusLauncher")
|
||||
return False
|
||||
|
||||
# 优先使用注册表方法(更可靠)
|
||||
print(f"[NexusLauncher] Method 1: Using Windows Registry...")
|
||||
success = setup_project_library_via_registry(library_name, library_path)
|
||||
|
||||
if not success:
|
||||
print(f"[NexusLauncher] Method 2: Using Python API...")
|
||||
success = setup_project_library(library_name, library_path, set_as_default=True)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
# SP 插件入口点
|
||||
def start_plugin():
|
||||
"""SP 插件启动入口点"""
|
||||
main()
|
||||
|
||||
def close_plugin():
|
||||
"""SP 插件关闭入口点"""
|
||||
pass
|
||||
|
||||
# 如果作为插件运行
|
||||
if __name__ == "__plugin__":
|
||||
start_plugin()
|
||||
|
||||
# 如果作为脚本运行
|
||||
elif __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user