|
|
|
|
@ -6,6 +6,8 @@ import shutil
@@ -6,6 +6,8 @@ import shutil
|
|
|
|
|
import json |
|
|
|
|
import shlex |
|
|
|
|
import subprocess |
|
|
|
|
import sys |
|
|
|
|
import signal |
|
|
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed |
|
|
|
|
from .oss_redis import ossClient |
|
|
|
|
from .logs import log |
|
|
|
|
@ -21,6 +23,75 @@ elif ENV == 'prod':
@@ -21,6 +23,75 @@ elif ENV == 'prod':
|
|
|
|
|
url = 'https://mp.api.suwa3d.com' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_resource_path(relative_path): |
|
|
|
|
""" |
|
|
|
|
获取资源文件的绝对路径,兼容 PyInstaller 打包后的环境 |
|
|
|
|
参数: |
|
|
|
|
relative_path: 相对于脚本目录的相对路径 |
|
|
|
|
返回: |
|
|
|
|
资源文件的绝对路径 |
|
|
|
|
""" |
|
|
|
|
try: |
|
|
|
|
# PyInstaller 创建临时文件夹,并将路径存储在 _MEIPASS 中 |
|
|
|
|
base_path = sys._MEIPASS |
|
|
|
|
except AttributeError: |
|
|
|
|
# 如果不是打包环境,使用脚本所在目录 |
|
|
|
|
base_path = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
|
|
|
|
|
|
return os.path.join(base_path, relative_path) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_transform_script_path(): |
|
|
|
|
""" |
|
|
|
|
获取 small_machine_transform.py 脚本的路径 |
|
|
|
|
在打包成exe后,如果脚本不在可访问位置,则将其提取到临时目录 |
|
|
|
|
返回: |
|
|
|
|
transform_script_path: 脚本的绝对路径 |
|
|
|
|
""" |
|
|
|
|
# 首先尝试从资源路径获取 |
|
|
|
|
if getattr(sys, 'frozen', False): |
|
|
|
|
# 打包后的环境 |
|
|
|
|
# 尝试从资源路径获取 |
|
|
|
|
resource_path = get_resource_path('utils/small_machine_transform.py') |
|
|
|
|
|
|
|
|
|
# 如果资源路径存在,直接使用 |
|
|
|
|
if os.path.exists(resource_path): |
|
|
|
|
return resource_path |
|
|
|
|
|
|
|
|
|
# 如果不存在,尝试提取到临时目录 |
|
|
|
|
import tempfile |
|
|
|
|
temp_dir = tempfile.gettempdir() |
|
|
|
|
extract_path = os.path.join(temp_dir, 'factory_sliceing_utils', 'small_machine_transform.py') |
|
|
|
|
|
|
|
|
|
# 确保目录存在 |
|
|
|
|
os.makedirs(os.path.dirname(extract_path), exist_ok=True) |
|
|
|
|
|
|
|
|
|
# 如果提取路径不存在,尝试从资源中复制 |
|
|
|
|
if not os.path.exists(extract_path): |
|
|
|
|
# 尝试从多个可能的资源路径查找 |
|
|
|
|
possible_paths = [ |
|
|
|
|
get_resource_path('small_machine_transform.py'), |
|
|
|
|
get_resource_path('utils/small_machine_transform.py'), |
|
|
|
|
] |
|
|
|
|
|
|
|
|
|
for src_path in possible_paths: |
|
|
|
|
if os.path.exists(src_path): |
|
|
|
|
shutil.copy2(src_path, extract_path) |
|
|
|
|
log(f"已将 small_machine_transform.py 提取到: {extract_path}") |
|
|
|
|
return extract_path |
|
|
|
|
|
|
|
|
|
# 如果提取路径存在,直接使用 |
|
|
|
|
if os.path.exists(extract_path): |
|
|
|
|
return extract_path |
|
|
|
|
|
|
|
|
|
# 如果都找不到,返回资源路径(即使不存在,让调用者处理错误) |
|
|
|
|
return resource_path |
|
|
|
|
else: |
|
|
|
|
# 开发环境,直接使用相对路径 |
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
|
return os.path.join(script_dir, 'small_machine_transform.py') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 根据打印ID 获取下载目录 |
|
|
|
|
def getDownloadDirByPrintId(printIds): |
|
|
|
|
# 调用接口获取下载的路径 |
|
|
|
|
@ -317,9 +388,8 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
@@ -317,9 +388,8 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
|
|
|
|
|
|
|
|
|
|
# 通过blender 调用执行 python 文件 |
|
|
|
|
blender_bin_path = findBpyModule() |
|
|
|
|
# 获取 small_machine_transform.py 的绝对路径 |
|
|
|
|
script_dir = os.path.dirname(os.path.abspath(__file__)) |
|
|
|
|
transform_script_path = os.path.join(script_dir, 'small_machine_transform.py') |
|
|
|
|
# 获取 small_machine_transform.py 的绝对路径(兼容打包环境) |
|
|
|
|
transform_script_path = get_transform_script_path() |
|
|
|
|
|
|
|
|
|
# 将 homo_matrix 转换为 JSON 字符串 |
|
|
|
|
homo_matrix_json = json.dumps(homo_matrix) |
|
|
|
|
@ -358,18 +428,86 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
@@ -358,18 +428,86 @@ def _process_single_item(v, arrDownloadPath, dirNewName, dirPath, isSmallMachine
|
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
log(f"开始执行 Blender 转换命令...") |
|
|
|
|
# 使用 subprocess 执行命令,捕获输出和错误 |
|
|
|
|
# 使用 subprocess.Popen 以便更好地控制进程和信号处理 |
|
|
|
|
# 在 Windows 上,Blender 输出可能是 UTF-8,需要指定编码并处理错误 |
|
|
|
|
result = subprocess.run( |
|
|
|
|
# 准备进程启动参数 |
|
|
|
|
startupinfo = None |
|
|
|
|
preexec_fn = None |
|
|
|
|
if platform.system() == 'Windows': |
|
|
|
|
# Windows 上,不设置 CREATE_NEW_PROCESS_GROUP,以便 Ctrl+C 可以传播 |
|
|
|
|
import subprocess as sp |
|
|
|
|
startupinfo = sp.STARTUPINFO() |
|
|
|
|
startupinfo.dwFlags |= sp.STARTF_USESHOWWINDOW |
|
|
|
|
startupinfo.wShowWindow = sp.SW_HIDE |
|
|
|
|
else: |
|
|
|
|
# Unix 系统,设置 preexec_fn 以确保信号处理正确 |
|
|
|
|
preexec_fn = os.setsid |
|
|
|
|
|
|
|
|
|
process = subprocess.Popen( |
|
|
|
|
cmd, |
|
|
|
|
capture_output=True, |
|
|
|
|
stdout=subprocess.PIPE, |
|
|
|
|
stderr=subprocess.PIPE, |
|
|
|
|
text=True, |
|
|
|
|
encoding='utf-8', # 指定 UTF-8 编码 |
|
|
|
|
errors='replace', # 遇到无法解码的字符时替换为占位符,而不是抛出异常 |
|
|
|
|
timeout=300, # 5分钟超时 |
|
|
|
|
check=False # 不自动抛出异常,手动检查返回码 |
|
|
|
|
startupinfo=startupinfo, |
|
|
|
|
preexec_fn=preexec_fn |
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
try: |
|
|
|
|
# 等待进程完成,捕获输出 |
|
|
|
|
stdout, stderr = process.communicate(timeout=300) # 5分钟超时 |
|
|
|
|
returncode = process.returncode |
|
|
|
|
except subprocess.TimeoutExpired: |
|
|
|
|
# 超时,终止进程 |
|
|
|
|
log(f"Blender 命令执行超时,正在终止进程...") |
|
|
|
|
if platform.system() == 'Windows': |
|
|
|
|
process.terminate() |
|
|
|
|
try: |
|
|
|
|
process.wait(timeout=5) |
|
|
|
|
except subprocess.TimeoutExpired: |
|
|
|
|
process.kill() |
|
|
|
|
else: |
|
|
|
|
# Unix 系统,发送 SIGTERM 到进程组 |
|
|
|
|
try: |
|
|
|
|
os.killpg(os.getpgid(process.pid), signal.SIGTERM) |
|
|
|
|
process.wait(timeout=5) |
|
|
|
|
except (subprocess.TimeoutExpired, ProcessLookupError, OSError): |
|
|
|
|
try: |
|
|
|
|
os.killpg(os.getpgid(process.pid), signal.SIGKILL) |
|
|
|
|
except (ProcessLookupError, OSError): |
|
|
|
|
pass |
|
|
|
|
raise |
|
|
|
|
except KeyboardInterrupt: |
|
|
|
|
# 收到中断信号,终止进程 |
|
|
|
|
log(f"收到中断信号,正在终止 Blender 进程...") |
|
|
|
|
if platform.system() == 'Windows': |
|
|
|
|
process.terminate() |
|
|
|
|
try: |
|
|
|
|
process.wait(timeout=5) |
|
|
|
|
except subprocess.TimeoutExpired: |
|
|
|
|
process.kill() |
|
|
|
|
else: |
|
|
|
|
# Unix 系统,发送 SIGTERM 到进程组 |
|
|
|
|
try: |
|
|
|
|
os.killpg(os.getpgid(process.pid), signal.SIGTERM) |
|
|
|
|
process.wait(timeout=5) |
|
|
|
|
except (subprocess.TimeoutExpired, ProcessLookupError, OSError): |
|
|
|
|
try: |
|
|
|
|
os.killpg(os.getpgid(process.pid), signal.SIGKILL) |
|
|
|
|
except (ProcessLookupError, OSError): |
|
|
|
|
pass |
|
|
|
|
raise # 重新抛出,让调用者处理 |
|
|
|
|
|
|
|
|
|
# 创建结果对象(模拟 subprocess.run 的返回值) |
|
|
|
|
class Result: |
|
|
|
|
def __init__(self, returncode, stdout, stderr): |
|
|
|
|
self.returncode = returncode |
|
|
|
|
self.stdout = stdout |
|
|
|
|
self.stderr = stderr |
|
|
|
|
|
|
|
|
|
result = Result(returncode, stdout, stderr) |
|
|
|
|
|
|
|
|
|
log(f"Blender 命令执行完成, 返回码: {result.returncode}") |
|
|
|
|
|
|
|
|
|
# 无论成功失败都输出详细信息 |
|
|
|
|
|