TellmeStpToGlb/utils/network_helper.py
sladro 90ba65e484 feat: 实现基于XCAF的真正装配结构保留转换
- 新增XCAF转换器,直接从STP到GLB保留完整装配层级
- 修复HTTP API与CLI参数不一致问题
- 修复静默吞没错误的问题,遵循快速失败原则
- 清理旧文件,整理测试文件到tests目录
- 添加URL下载支持,可直接转换远程STP文件
- 更新文档,准确描述XCAF装配保留功能

技术改进:
- 使用STEPCAFControl_Reader读取带装配信息的STEP文件
- 通过RWGltf_CafWriter直接导出GLB,无需STL中间格式
- 支持CPU多线程并行三角化
- HTTP API和CLI使用完全一致的转换参数

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-09 16:50:48 +08:00

179 lines
5.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
网络下载工具模块
提供URL验证和文件下载功能
"""
import os
import tempfile
import urllib.request
import urllib.parse
from typing import Callable, Optional, Tuple
from urllib.error import URLError, HTTPError
def is_url(path: str) -> bool:
"""
判断输入是否为URL
Args:
path: 输入路径或URL
Returns:
bool: 是否为有效的HTTP/HTTPS URL
"""
if not path:
return False
try:
parsed = urllib.parse.urlparse(path)
return parsed.scheme.lower() in ['http', 'https'] and bool(parsed.netloc)
except Exception:
return False
def validate_url(url: str) -> Tuple[bool, str]:
"""
验证URL的有效性和可访问性
Args:
url: 要验证的URL
Returns:
Tuple[bool, str]: (是否有效, 错误信息)
"""
if not is_url(url):
return False, "输入不是有效的HTTP/HTTPS URL"
try:
# 发送HEAD请求检查URL可访问性
request = urllib.request.Request(url, method='HEAD')
with urllib.request.urlopen(request, timeout=10) as response:
content_type = response.headers.get('content-type', '').lower()
# 检查是否可能是STP文件
if 'application/octet-stream' not in content_type and 'application/step' not in content_type:
# 如果没有明确的内容类型通过URL扩展名判断
if not url.lower().endswith(('.stp', '.step')):
return False, "URL指向的文件可能不是STP格式"
return True, ""
except HTTPError as e:
return False, f"HTTP错误 {e.code}: {e.reason}"
except URLError as e:
return False, f"URL错误: {e.reason}"
except Exception as e:
return False, f"验证失败: {str(e)}"
def download_file(url: str, progress_callback: Optional[Callable[[int, str], None]] = None) -> str:
"""
下载文件到临时目录
Args:
url: 要下载的文件URL
progress_callback: 进度回调函数
Returns:
str: 下载文件的本地路径
Raises:
Exception: 下载失败时抛出异常
"""
def update_progress(progress: int, message: str) -> None:
"""更新进度"""
if progress_callback:
progress_callback(progress, message)
update_progress(0, f"开始下载文件: {url}")
# 验证URL
is_valid, error_msg = validate_url(url)
if not is_valid:
raise ValueError(f"URL验证失败: {error_msg}")
try:
# 获取文件名和扩展名
parsed_url = urllib.parse.urlparse(url)
filename = os.path.basename(parsed_url.path)
# 如果URL没有文件名生成一个默认名
if not filename or '.' not in filename:
filename = "downloaded_model.stp"
# 确保文件扩展名是STP格式
if not filename.lower().endswith(('.stp', '.step')):
base_name = os.path.splitext(filename)[0]
filename = f"{base_name}.stp"
update_progress(10, f"准备下载文件: {filename}")
# 创建临时文件
temp_dir = tempfile.gettempdir()
temp_path = os.path.join(temp_dir, f"stp2glb_{os.getpid()}_{filename}")
update_progress(15, f"临时文件路径: {temp_path}")
# 下载文件
request = urllib.request.Request(url)
with urllib.request.urlopen(request) as response:
total_size = int(response.headers.get('content-length', 0))
downloaded = 0
with open(temp_path, 'wb') as f:
while True:
chunk = response.read(8192) # 8KB chunks
if not chunk:
break
f.write(chunk)
downloaded += len(chunk)
# 计算并更新进度
if total_size > 0:
progress = 15 + int((downloaded / total_size) * 70) # 15-85%的进度范围
update_progress(progress, f"下载进度: {downloaded / 1024 / 1024:.1f}MB / {total_size / 1024 / 1024:.1f}MB")
else:
# 如果不知道总大小,显示已下载的字节数
update_progress(50, f"已下载: {downloaded / 1024 / 1024:.1f}MB")
# 验证下载的文件
if not os.path.exists(temp_path) or os.path.getsize(temp_path) == 0:
raise ValueError("下载的文件为空或不存在")
file_size_mb = os.path.getsize(temp_path) / 1024 / 1024
update_progress(90, f"文件下载完成: {file_size_mb:.1f}MB")
return temp_path
except HTTPError as e:
raise Exception(f"HTTP下载错误 {e.code}: {e.reason}")
except URLError as e:
raise Exception(f"网络错误: {e.reason}")
except Exception as e:
# 清理可能创建的临时文件
if 'temp_path' in locals() and os.path.exists(temp_path):
try:
os.remove(temp_path)
except Exception as cleanup_error:
# 记录清理失败但继续抛出原始异常
print(f"警告: 无法清理临时文件 {temp_path}: {cleanup_error}")
raise Exception(f"下载失败: {str(e)}")
def cleanup_downloaded_file(file_path: str) -> None:
"""
清理下载的临时文件
Args:
file_path: 要清理的文件路径
"""
if file_path and os.path.exists(file_path):
try:
# 只清理临时目录中的文件,避免误删用户文件
if tempfile.gettempdir() in os.path.abspath(file_path):
os.remove(file_path)
except Exception as e:
raise RuntimeError(f"无法清理下载的临时文件 {file_path}: {str(e)}")