- 新增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>
255 lines
9.6 KiB
Python
255 lines
9.6 KiB
Python
#!/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() |