# -*- coding: utf-8 -*-
"""
step-to-stl.py —— 真实 STEP 批量转 STL 网格 (桌面软件窗口内 3D 预览用)

内核: OpenCASCADE 的 Python 绑定 (cadquery/OCP, 开源 LGPL)。
用途: 把 catalog\\step\\ 里的真实数模 (体积 > 20KB 的 .step/.stp, 占位几何 ~7KB 自动跳过)
     转成 catalog\\mesh\\ 下同名 .stl 网格。桌面 3D 标签页发现有网格就在窗口内直渲。

断点续跑: 同名 STL 已存在且不比 STEP 旧 → 跳过。
以后新增真实 STEP 后重跑本脚本即可 (或用 NX journal 的 exportStl 自动导出)。

用法: python step-to-stl.py
"""
import os
import sys

BASE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STEP_DIR = os.path.join(BASE, 'catalog', 'step')
MESH_DIR = os.path.join(BASE, 'catalog', 'mesh')
REAL_MIN_BYTES = 20 * 1024      # 20KB 以上算真实数模 (占位几何 ~7KB)
LIN_DEFLECTION = 1.5            # 网格线性偏差 (mm): 越小越精细、文件越大 (1.5mm 屏幕预览足够, 保证流畅旋转)


def convert(step_path, stl_path):
    from cadquery.occ_impl.shapes import Shape
    from OCP.STEPControl import STEPControl_Reader
    from OCP.StlAPI import StlAPI_Writer
    from OCP.BRepMesh import BRepMesh_IncrementalMesh
    from OCP.IFSelect import IFSelect_RetDone

    reader = STEPControl_Reader()
    if reader.ReadFile(str(step_path)) != IFSelect_RetDone:
        raise RuntimeError('STEP 读取失败: %s' % step_path)
    reader.TransferRoots()
    shape = Shape(reader.OneShape())
    BRepMesh_IncrementalMesh(shape.wrapped, LIN_DEFLECTION, False, 0.5, True).Perform()
    writer = StlAPI_Writer()
    writer.SetASCIIMode(False)   # 二进制 STL (比 ASCII 小 ~3 倍)
    if not writer.Write(shape.wrapped, str(stl_path)):
        raise RuntimeError('STL 写出失败: %s' % stl_path)


def main():
    if not os.path.isdir(MESH_DIR):
        os.makedirs(MESH_DIR)
    step_files = [f for f in os.listdir(STEP_DIR)
                  if f.lower().endswith(('.step', '.stp'))]
    done = skip = 0
    errors = []
    for name in sorted(step_files):
        step_path = os.path.join(STEP_DIR, name)
        if os.path.getsize(step_path) < REAL_MIN_BYTES:
            continue  # 占位几何, 不转
        stl_path = os.path.join(MESH_DIR, os.path.splitext(name)[0] + '.stl')
        # 跳过条件: STL 存在、比 STEP 新、且不是占位网格 (<50KB 视为占位, 强制重转)
        if (os.path.exists(stl_path) and os.path.getsize(stl_path) > 50 * 1024
                and os.path.getmtime(stl_path) >= os.path.getmtime(step_path)):
            skip += 1
            continue
        try:
            convert(step_path, stl_path)
            done += 1
            print('  OK %-24s -> %s (%.1f KB)' % (name, os.path.basename(stl_path),
                                                   os.path.getsize(stl_path) / 1024.0))
        except Exception as e:
            errors.append('%s: %s' % (name, e))
            print('FAIL %s: %s' % (name, e))
    print('完成: 转换 %d, 跳过 %d, 失败 %d → %s' % (done, skip, len(errors), MESH_DIR))
    if errors:
        sys.exit(1)


if __name__ == '__main__':
    main()
