Update
This commit is contained in:
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()
|
||||
Reference in New Issue
Block a user