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:
root 2025-08-02 16:55:22 +08:00
parent 5102bd57e8
commit 7bd0624829
7 changed files with 185 additions and 18 deletions

View File

@ -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
### 错误处理
- 完整的文件验证和网格检查
- 异常捕获和错误信息反馈

View File

@ -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
)
# 创建任务

View File

@ -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):

View File

@ -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):

View File

@ -22,6 +22,7 @@ class ConvertOptions:
"""转换选项"""
auto_scale: bool = True
auto_center: bool = True
preserve_hierarchy: bool = False
@dataclass

41
main.py
View File

@ -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}")

View File

@ -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