TellmeStpToGlb/core/converter_xcaf.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

255 lines
9.6 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
"""
基于XCAF的STP到GLB转换器
使用OpenCascade的XDE/OCAF直接保留装配结构
"""
import os
from typing import Optional, Callable
# 检测可用的OCCT模块
XCAF_AVAILABLE = False
RWGLTF_AVAILABLE = False
try:
# 尝试导入XCAF相关模块
from OCC.Core.XCAFApp import XCAFApp_Application
from OCC.Core.TDocStd import TDocStd_Document
from OCC.Core.STEPCAFControl import STEPCAFControl_Reader
from OCC.Core.TDF import TDF_LabelSequence
from OCC.Core.IFSelect import IFSelect_RetDone
from OCC.Core.XCAFDoc import XCAFDoc_DocumentTool
XCAF_AVAILABLE = True
except ImportError:
pass
try:
# 尝试导入RWGltf模块
from OCC.Core.RWGltf import RWGltf_CafWriter
RWGLTF_AVAILABLE = True
except ImportError:
pass
class XCAFConverter:
"""基于XCAF的转换器真正保留装配结构"""
def __init__(self):
self.progress_callback: Optional[Callable[[int, str], None]] = None
def set_progress_callback(self, callback: Callable[[int, str], None]) -> None:
"""设置进度回调函数"""
self.progress_callback = callback
def _update_progress(self, progress: int, message: str) -> None:
"""更新进度"""
if self.progress_callback:
self.progress_callback(progress, message)
def is_available(self) -> bool:
"""检查XCAF转换是否可用"""
return XCAF_AVAILABLE and RWGLTF_AVAILABLE
def get_availability_status(self) -> dict:
"""获取可用性状态详情"""
return {
"xcaf_available": XCAF_AVAILABLE,
"rwgltf_available": RWGLTF_AVAILABLE,
"fully_available": self.is_available(),
"message": self._get_availability_message()
}
def _get_availability_message(self) -> str:
"""获取可用性消息"""
if self.is_available():
return "XCAF转换器完全可用"
elif XCAF_AVAILABLE and not RWGLTF_AVAILABLE:
return "XCAF可用但RWGltf不可用需要使用备用方案"
elif not XCAF_AVAILABLE:
return "XCAF模块不可用请检查pythonocc安装"
else:
return "XCAF转换器不可用"
def convert_with_xcaf(self, stp_path: str, glb_path: str,
embed_textures: bool = True,
y_up: bool = True,
linear_deflection: float = 0.01,
angular_deflection: float = 0.1) -> None:
"""
使用XCAF直接从STEP转换到GLB保留完整装配结构
Args:
stp_path: STEP文件路径
glb_path: 输出GLB文件路径
embed_textures: 是否嵌入纹理
y_up: 是否使用Y向上坐标系
linear_deflection: 线性偏差(用于三角化)
angular_deflection: 角度偏差(用于三角化)
"""
if not self.is_available():
raise RuntimeError(f"XCAF转换器不可用: {self._get_availability_message()}")
self._update_progress(0, "初始化XCAF文档...")
# 1. 创建XCAF文档
app = XCAFApp_Application.GetApplication()
doc = TDocStd_Document("MDTV-XCAF")
app.NewDocument("MDTV-XCAF", doc)
self._update_progress(10, "读取STEP文件保留装配结构...")
# 2. 使用STEPCAFControl_Reader读取STEP文件
reader = STEPCAFControl_Reader()
reader.SetNameMode(True) # 读取名称
reader.SetColorMode(True) # 读取颜色
reader.SetLayerMode(True) # 读取层
reader.SetMatMode(True) # 读取材质
# 读取文件
status = reader.ReadFile(stp_path)
if status != IFSelect_RetDone:
raise RuntimeError(f"无法读取STEP文件: {stp_path}")
self._update_progress(30, "传输装配数据到XCAF文档...")
# 传输到文档
if not reader.Transfer(doc):
raise RuntimeError("无法传输STEP数据到XCAF文档")
# 获取装配信息
doc_tool = XCAFDoc_DocumentTool.ShapeTool(doc.Main())
free_shapes = TDF_LabelSequence()
doc_tool.GetFreeShapes(free_shapes)
shape_count = free_shapes.Length()
self._update_progress(50, f"发现 {shape_count} 个顶级装配...")
# 2.5 三角化所有形状(关键步骤!)
self._update_progress(60, "三角化模型(生成网格)...")
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.TDF import TDF_Label
from OCC.Core.TopoDS import TopoDS_Shape
# 使用传入的三角化参数
self._update_progress(62, f"三角化参数: 线性={linear_deflection}, 角度={angular_deflection}")
# 遍历所有标签并三角化
def triangulate_label(label):
"""递归三角化标签及其子标签"""
if doc_tool.IsShape(label):
shape = doc_tool.GetShape(label)
if shape and not shape.IsNull():
# 对形状进行三角化
mesh = BRepMesh_IncrementalMesh(
shape,
linear_deflection,
False, # is_relative
angular_deflection,
True # in_parallel
)
mesh.Perform()
if not mesh.IsDone():
print(f"警告: 无法三角化形状")
# 递归处理子标签
from OCC.Core.TDF import TDF_ChildIterator
child_it = TDF_ChildIterator(label)
while child_it.More():
triangulate_label(child_it.Value())
child_it.Next()
# 三角化所有根形状
for i in range(free_shapes.Length()):
label = free_shapes.Value(i + 1) # 注意索引从1开始
triangulate_label(label)
self._update_progress(65, "三角化完成")
# 3. 使用RWGltf_CafWriter导出GLB
self._update_progress(70, "导出为GLB格式保留层级...")
# 构造函数theFile: str, theIsBinary: bool
writer = RWGltf_CafWriter(glb_path, True) # True = 二进制GLB格式
# 设置嵌入纹理(正确的方法名)
if embed_textures:
writer.SetToEmbedTexturesInGlb(True)
# 设置坐标系转换(如果需要)
if y_up:
# Y-up是glTF的默认坐标系通常不需要特殊处理
# 如果需要自定义,可以使用:
# from OCC.Core.RWMesh import RWMesh_CoordinateSystemConverter
# converter = RWMesh_CoordinateSystemConverter()
# writer.SetCoordinateSystemConverter(converter)
pass
# 执行导出
# Perform有两个重载版本
# 1. Perform(theDocument, theRootLabels, theLabelFilter, theFileInfo, theProgress)
# 2. Perform(theDocument, theFileInfo, theProgress)
# 我们使用简单版本
from OCC.Core.TColStd import TColStd_IndexedDataMapOfStringString
from OCC.Core.Message import Message_ProgressRange
file_info = TColStd_IndexedDataMapOfStringString()
progress = Message_ProgressRange()
if not writer.Perform(doc, file_info, progress):
raise RuntimeError(f"无法导出GLB文件: {glb_path}")
self._update_progress(90, "验证输出文件...")
# 验证输出
if not os.path.exists(glb_path):
raise RuntimeError("GLB文件未生成")
if os.path.getsize(glb_path) == 0:
raise RuntimeError("生成的GLB文件为空")
self._update_progress(100, f"XCAF转换完成保留了 {shape_count} 个装配")
def convert_with_fallback(self, stp_path: str, glb_path: str,
**kwargs) -> None:
"""
带回退的转换方法
如果XCAF不可用抛出明确的错误提示
"""
if XCAF_AVAILABLE and RWGLTF_AVAILABLE:
# 完整的XCAF转换
return self.convert_with_xcaf(stp_path, glb_path, **kwargs)
elif XCAF_AVAILABLE and not RWGLTF_AVAILABLE:
# 有XCAF但没有RWGltf可以实现自定义导出器方案B
raise NotImplementedError(
"RWGltf_CafWriter不可用。"
"可选方案:\n"
"1. 升级pythonocc到包含OCCT 7.6+的版本\n"
"2. 使用自定义glTF导出器需要额外开发\n"
"3. 使用外部工具进行转换"
)
else:
# XCAF完全不可用
raise RuntimeError(
"XCAF模块不可用无法使用装配结构保留功能。\n"
"请检查pythonocc安装或使用传统STL管线。"
)
def check_xcaf_availability():
"""检查XCAF转换器可用性的工具函数"""
converter = XCAFConverter()
status = converter.get_availability_status()
print("XCAF转换器状态检查:")
print(f" XCAF模块: {'' if status['xcaf_available'] else ''}")
print(f" RWGltf模块: {'' if status['rwgltf_available'] else ''}")
print(f" 完全可用: {'' if status['fully_available'] else ''}")
print(f" 消息: {status['message']}")
return status['fully_available']
if __name__ == "__main__":
# 测试可用性
check_xcaf_availability()