fix: 修复层级保留功能的导入错误并重新实现
- 移除有问题的OCC.Core导入,避免模块依赖问题 - 重新实现基于trimesh的层级保留功能,使用连通组件分离 - 添加智能回退机制,如果分离失败自动使用标准转换 - 扩展CLI和API接口支持层级保留参数 - 更新文档说明新的实现方式 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
5102bd57e8
commit
7bd0624829
23
CLAUDE.md
23
CLAUDE.md
@ -33,7 +33,14 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
### CLI模式转换
|
||||
```bash
|
||||
# 标准转换(默认)
|
||||
python main.py input.stp output.glb
|
||||
|
||||
# 层级保留转换(实验性功能)
|
||||
python main.py input.stp output.glb --hierarchy
|
||||
|
||||
# 显示帮助信息
|
||||
python main.py --help
|
||||
```
|
||||
|
||||
### 启动Web服务
|
||||
@ -53,11 +60,16 @@ uvicorn app:app --host 0.0.0.0 --port 8000 --workers 4
|
||||
# 健康检查
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# 提交转换任务
|
||||
# 提交标准转换任务
|
||||
curl -X POST http://localhost:8000/api/v1/convert \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"input_path": "/path/to/input.stp", "output_path": "/path/to/output.glb"}'
|
||||
|
||||
# 提交层级保留转换任务
|
||||
curl -X POST http://localhost:8000/api/v1/convert \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"input_path": "/path/to/input.stp", "output_path": "/path/to/output.glb", "options": {"preserve_hierarchy": true}}'
|
||||
|
||||
# 查询任务状态
|
||||
curl http://localhost:8000/api/v1/status/{task_id}
|
||||
```
|
||||
@ -79,6 +91,7 @@ python main.py test.stp test.glb
|
||||
2. **进度追踪**: 实时更新转换进度(0-100%)
|
||||
3. **任务管理**: 内存存储任务状态,支持查询和管理
|
||||
4. **RESTful API**: 标准HTTP接口,支持跨语言调用
|
||||
5. **层级保留**: 支持保留STP装配体的层级结构(实验性功能)
|
||||
|
||||
### 转换配置
|
||||
- **STL设置**: 二进制模式,线性偏差0.01,角度偏差0.1
|
||||
@ -86,6 +99,14 @@ python main.py test.stp test.glb
|
||||
- 智能缩放:超过1000单位的模型自动缩放到50单位以内
|
||||
- 自动居中:模型质心移动到原点,便于Blender查看
|
||||
|
||||
### 层级保留功能(实验性)
|
||||
- **连通组件分离**: 基于trimesh自动分离STL文件中的独立网格组件
|
||||
- **独立网格**: 为每个分离组件创建独立的网格对象
|
||||
- **场景构建**: 使用trimesh.Scene保持组件分离关系
|
||||
- **智能回退**: 如果未发现分离组件,自动回退到标准转换模式
|
||||
- **组件命名**: 自动为分离组件分配有序名称(Part_001, Part_002等)
|
||||
- **API兼容**: 避免复杂的OCC.Core依赖,使用稳定的trimesh API
|
||||
|
||||
### 错误处理
|
||||
- 完整的文件验证和网格检查
|
||||
- 异常捕获和错误信息反馈
|
||||
|
||||
@ -50,7 +50,8 @@ async def convert_file(request: ConvertRequestSchema):
|
||||
# 创建转换选项
|
||||
options = ConvertOptions(
|
||||
auto_scale=request.options.auto_scale,
|
||||
auto_center=request.options.auto_center
|
||||
auto_center=request.options.auto_center,
|
||||
preserve_hierarchy=request.options.preserve_hierarchy
|
||||
)
|
||||
|
||||
# 创建任务
|
||||
|
||||
@ -12,6 +12,7 @@ class ConvertOptionsSchema(BaseModel):
|
||||
"""转换选项模式"""
|
||||
auto_scale: bool = Field(default=True, description="是否自动缩放")
|
||||
auto_center: bool = Field(default=True, description="是否自动居中")
|
||||
preserve_hierarchy: bool = Field(default=False, description="是否保留装配体层级结构")
|
||||
|
||||
|
||||
class ConvertRequestSchema(BaseModel):
|
||||
|
||||
@ -92,6 +92,110 @@ class StpToGlbConverter:
|
||||
self._update_progress(0, "开始转换...")
|
||||
self.stp_to_stl(stp_path, stl_tmp)
|
||||
self.stl_to_glb(stl_tmp, glb_path, auto_scale, auto_center)
|
||||
finally:
|
||||
# 清理临时文件
|
||||
if os.path.isfile(stl_tmp):
|
||||
os.remove(stl_tmp)
|
||||
|
||||
def _split_mesh_components(self, stl_path: str, auto_scale: bool = True,
|
||||
auto_center: bool = True) -> trimesh.Scene:
|
||||
"""基于trimesh分离连通组件来模拟层级结构"""
|
||||
self._update_progress(70, "加载STL文件...")
|
||||
|
||||
if not os.path.isfile(stl_path):
|
||||
raise FileNotFoundError(f"找不到STL文件:{stl_path}")
|
||||
|
||||
# 加载STL文件
|
||||
mesh = trimesh.load(stl_path)
|
||||
|
||||
if mesh.is_empty:
|
||||
raise ValueError(f"STL文件为空或无效:{stl_path}")
|
||||
|
||||
self._update_progress(75, "分析网格连通组件...")
|
||||
|
||||
# 尝试分离连通组件
|
||||
try:
|
||||
# 分离不连通的网格部分
|
||||
components = mesh.split(only_watertight=False)
|
||||
|
||||
if len(components) > 1:
|
||||
self._update_progress(80, f"发现 {len(components)} 个分离组件")
|
||||
|
||||
# 创建场景,每个组件作为独立零件
|
||||
scene = trimesh.Scene()
|
||||
|
||||
for i, component in enumerate(components):
|
||||
if not component.is_empty and len(component.vertices) > 0:
|
||||
part_name = f'Part_{i:03d}'
|
||||
scene.add_geometry(component, node_name=part_name)
|
||||
self._update_progress(80 + int(5 * i / len(components)),
|
||||
f"添加组件 {part_name}")
|
||||
|
||||
# 对整个场景进行优化
|
||||
if auto_scale or auto_center:
|
||||
self._update_progress(87, "优化场景...")
|
||||
bounds = scene.bounds
|
||||
if bounds is not None:
|
||||
max_dimension = max(bounds[1] - bounds[0])
|
||||
|
||||
if auto_scale and max_dimension > 1000:
|
||||
scale_factor = 50.0 / max_dimension
|
||||
scene.apply_scale(scale_factor)
|
||||
|
||||
if auto_center:
|
||||
scene.apply_translation(-scene.centroid)
|
||||
|
||||
return scene
|
||||
else:
|
||||
self._update_progress(80, "未发现分离组件,使用单一网格")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self._update_progress(80, f"组件分离失败: {e}")
|
||||
return None
|
||||
|
||||
def convert_with_hierarchy(self, stp_path: str, glb_path: str,
|
||||
auto_scale: bool = True, auto_center: bool = True) -> None:
|
||||
"""完整的STP到GLB转换流程,尝试保持组件分离结构"""
|
||||
stl_tmp = os.path.splitext(glb_path)[0] + ".tmp.stl"
|
||||
|
||||
try:
|
||||
self._update_progress(0, "开始层级转换...")
|
||||
|
||||
# 首先执行标准的STP到STL转换
|
||||
self.stp_to_stl(stp_path, stl_tmp)
|
||||
|
||||
# 尝试分离网格组件
|
||||
self._update_progress(65, "尝试分离网格组件...")
|
||||
scene = self._split_mesh_components(stl_tmp, auto_scale, auto_center)
|
||||
|
||||
if scene is not None:
|
||||
# 成功分离组件,导出场景
|
||||
self._update_progress(90, "导出分离组件GLB文件...")
|
||||
|
||||
try:
|
||||
scene.export(glb_path)
|
||||
if os.path.getsize(glb_path) == 0:
|
||||
raise ValueError("生成的GLB文件为空")
|
||||
|
||||
# 计算组件数量
|
||||
component_count = len([name for name in scene.geometry.keys()])
|
||||
self._update_progress(100, f"层级转换完成,包含 {component_count} 个分离组件")
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"GLB导出失败:{e}")
|
||||
else:
|
||||
# 没有发现分离组件,回退到标准转换
|
||||
self._update_progress(85, "未发现分离组件,使用标准转换...")
|
||||
self.stl_to_glb(stl_tmp, glb_path, auto_scale, auto_center)
|
||||
|
||||
except Exception as e:
|
||||
# 如果层级转换失败,提供回退选项
|
||||
self._update_progress(50, f"层级转换失败: {e},尝试标准转换...")
|
||||
try:
|
||||
self.convert(stp_path, glb_path, auto_scale, auto_center)
|
||||
except Exception as fallback_error:
|
||||
raise ValueError(f"转换失败: {fallback_error}")
|
||||
finally:
|
||||
# 清理临时文件
|
||||
if os.path.isfile(stl_tmp):
|
||||
|
||||
@ -22,6 +22,7 @@ class ConvertOptions:
|
||||
"""转换选项"""
|
||||
auto_scale: bool = True
|
||||
auto_center: bool = True
|
||||
preserve_hierarchy: bool = False
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
41
main.py
41
main.py
@ -6,15 +6,35 @@ stp2glb.py 一键 STP → STL → GLB
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
from core.converter import StpToGlbConverter
|
||||
|
||||
|
||||
def main():
|
||||
"""CLI主函数,保持向后兼容"""
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("用法: python main.py input.stp output.glb")
|
||||
|
||||
stp_file, glb_file = sys.argv[1], sys.argv[2]
|
||||
"""CLI主函数,支持层级保留参数"""
|
||||
# 兼容原有的简单用法
|
||||
if len(sys.argv) == 3 and not any(arg.startswith('-') for arg in sys.argv[1:]):
|
||||
stp_file, glb_file = sys.argv[1], sys.argv[2]
|
||||
preserve_hierarchy = False
|
||||
else:
|
||||
# 使用argparse处理参数
|
||||
parser = argparse.ArgumentParser(description='STP到GLB转换工具')
|
||||
parser.add_argument('input', help='输入的STP文件路径')
|
||||
parser.add_argument('output', help='输出的GLB文件路径')
|
||||
parser.add_argument('--hierarchy', '--preserve-hierarchy',
|
||||
action='store_true',
|
||||
help='保留装配体层级结构(实验性功能)')
|
||||
parser.add_argument('--no-scale', action='store_true',
|
||||
help='禁用自动缩放')
|
||||
parser.add_argument('--no-center', action='store_true',
|
||||
help='禁用自动居中')
|
||||
|
||||
args = parser.parse_args()
|
||||
stp_file = args.input
|
||||
glb_file = args.output
|
||||
preserve_hierarchy = args.hierarchy
|
||||
auto_scale = not args.no_scale
|
||||
auto_center = not args.no_center
|
||||
|
||||
# 创建转换器实例
|
||||
converter = StpToGlbConverter()
|
||||
@ -27,8 +47,15 @@ def main():
|
||||
converter.set_progress_callback(show_progress)
|
||||
|
||||
try:
|
||||
# 执行转换
|
||||
converter.convert(stp_file, glb_file)
|
||||
# 根据参数选择转换方法
|
||||
if preserve_hierarchy:
|
||||
print("🔧 使用层级保留模式转换...")
|
||||
converter.convert_with_hierarchy(stp_file, glb_file,
|
||||
auto_scale=auto_scale if 'auto_scale' in locals() else True,
|
||||
auto_center=auto_center if 'auto_center' in locals() else True)
|
||||
else:
|
||||
converter.convert(stp_file, glb_file)
|
||||
|
||||
print(f"✅ 完成:{glb_file}")
|
||||
except Exception as e:
|
||||
sys.exit(f"❌ 转换失败:{e}")
|
||||
|
||||
@ -60,15 +60,27 @@ class TaskManager:
|
||||
converter.set_progress_callback(progress_callback)
|
||||
|
||||
try:
|
||||
# 执行转换
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
converter.convert,
|
||||
task.input_path,
|
||||
task.output_path,
|
||||
task.options.auto_scale,
|
||||
task.options.auto_center
|
||||
)
|
||||
# 根据选项选择转换方法
|
||||
if task.options.preserve_hierarchy:
|
||||
# 执行层级保留转换
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
converter.convert_with_hierarchy,
|
||||
task.input_path,
|
||||
task.output_path,
|
||||
task.options.auto_scale,
|
||||
task.options.auto_center
|
||||
)
|
||||
else:
|
||||
# 执行标准转换
|
||||
await asyncio.get_event_loop().run_in_executor(
|
||||
None,
|
||||
converter.convert,
|
||||
task.input_path,
|
||||
task.output_path,
|
||||
task.options.auto_scale,
|
||||
task.options.auto_center
|
||||
)
|
||||
|
||||
# 标记完成
|
||||
task.status = TaskStatus.COMPLETED
|
||||
|
||||
Loading…
Reference in New Issue
Block a user