init
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
"""
|
||||
数据库迁移脚本 - 添加多上传支持字段
|
||||
|
||||
运行方式: python scripts/migrate_multi_upload.py
|
||||
|
||||
此脚本将:
|
||||
1. 添加 upload_batch 字段到 stp_files 表
|
||||
2. 添加 volume, surface_area, product_weight 快速查询字段到 stp_files 表
|
||||
3. 移除 file_hash 字段的唯一约束(如果存在)
|
||||
4. 为新字段创建索引
|
||||
"""
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from sqlalchemy import text
|
||||
from database.database import async_engine, get_db_session
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
async def check_column_exists(conn, table_name: str, column_name: str) -> bool:
|
||||
"""检查列是否存在"""
|
||||
result = await conn.execute(text("""
|
||||
SELECT column_name
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = :table_name
|
||||
AND column_name = :column_name
|
||||
"""), {"table_name": table_name, "column_name": column_name})
|
||||
return result.fetchone() is not None
|
||||
|
||||
|
||||
async def check_index_exists(conn, index_name: str) -> bool:
|
||||
"""检查索引是否存在"""
|
||||
result = await conn.execute(text("""
|
||||
SELECT indexname
|
||||
FROM pg_indexes
|
||||
WHERE indexname = :index_name
|
||||
"""), {"index_name": index_name})
|
||||
return result.fetchone() is not None
|
||||
|
||||
|
||||
async def check_unique_constraint_exists(conn, table_name: str, column_name: str) -> bool:
|
||||
"""检查唯一约束是否存在"""
|
||||
result = await conn.execute(text("""
|
||||
SELECT conname
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = :table_name::regclass
|
||||
AND contype = 'u'
|
||||
AND conname LIKE :pattern
|
||||
"""), {"table_name": table_name, "pattern": f"%{column_name}%"})
|
||||
return result.fetchone() is not None
|
||||
|
||||
|
||||
async def run_migration():
|
||||
"""执行迁移"""
|
||||
logger.info("开始数据库迁移 - 多上传支持...")
|
||||
|
||||
async with async_engine.begin() as conn:
|
||||
try:
|
||||
await conn.execute(text("SELECT 1"))
|
||||
logger.info("数据库连接成功")
|
||||
except Exception as e:
|
||||
logger.error(f"数据库连接失败: {e}")
|
||||
return False
|
||||
|
||||
async with async_engine.begin() as conn:
|
||||
migration_steps = []
|
||||
|
||||
if not await check_column_exists(conn, "stp_files", "upload_batch"):
|
||||
migration_steps.append("添加 upload_batch 字段")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE stp_files
|
||||
ADD COLUMN upload_batch VARCHAR(36)
|
||||
"""))
|
||||
logger.info("✓ 添加 upload_batch 字段")
|
||||
|
||||
if not await check_column_exists(conn, "stp_files", "volume"):
|
||||
migration_steps.append("添加 volume 字段")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE stp_files
|
||||
ADD COLUMN volume FLOAT
|
||||
"""))
|
||||
logger.info("✓ 添加 volume 字段")
|
||||
|
||||
if not await check_column_exists(conn, "stp_files", "surface_area"):
|
||||
migration_steps.append("添加 surface_area 字段")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE stp_files
|
||||
ADD COLUMN surface_area FLOAT
|
||||
"""))
|
||||
logger.info("✓ 添加 surface_area 字段")
|
||||
|
||||
if not await check_column_exists(conn, "stp_files", "product_weight"):
|
||||
migration_steps.append("添加 product_weight 字段")
|
||||
await conn.execute(text("""
|
||||
ALTER TABLE stp_files
|
||||
ADD COLUMN product_weight FLOAT
|
||||
"""))
|
||||
logger.info("✓ 添加 product_weight 字段")
|
||||
|
||||
if not await check_index_exists(conn, "ix_stp_files_upload_batch"):
|
||||
migration_steps.append("创建 upload_batch 索引")
|
||||
await conn.execute(text("""
|
||||
CREATE INDEX ix_stp_files_upload_batch
|
||||
ON stp_files(upload_batch)
|
||||
"""))
|
||||
logger.info("✓ 创建 upload_batch 索引")
|
||||
|
||||
if not await check_index_exists(conn, "ix_stp_files_file_hash"):
|
||||
migration_steps.append("创建 file_hash 索引")
|
||||
await conn.execute(text("""
|
||||
CREATE INDEX ix_stp_files_file_hash
|
||||
ON stp_files(file_hash)
|
||||
"""))
|
||||
logger.info("✓ 创建 file_hash 索引")
|
||||
|
||||
if not await check_index_exists(conn, "ix_stp_files_original_filename"):
|
||||
migration_steps.append("创建 original_filename 索引")
|
||||
await conn.execute(text("""
|
||||
CREATE INDEX ix_stp_files_original_filename
|
||||
ON stp_files(original_filename)
|
||||
"""))
|
||||
logger.info("✓ 创建 original_filename 索引")
|
||||
|
||||
try:
|
||||
result = await conn.execute(text("""
|
||||
SELECT conname
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'stp_files'::regclass
|
||||
AND contype = 'u'
|
||||
"""))
|
||||
unique_constraints = result.fetchall()
|
||||
|
||||
for constraint in unique_constraints:
|
||||
constraint_name = constraint[0]
|
||||
if 'file_hash' in constraint_name.lower():
|
||||
migration_steps.append(f"删除唯一约束 {constraint_name}")
|
||||
await conn.execute(text(f"""
|
||||
ALTER TABLE stp_files
|
||||
DROP CONSTRAINT {constraint_name}
|
||||
"""))
|
||||
logger.info(f"✓ 删除唯一约束: {constraint_name}")
|
||||
except Exception as e:
|
||||
logger.warning(f"检查唯一约束时出错(可能不存在): {e}")
|
||||
|
||||
if migration_steps:
|
||||
logger.info(f"\n迁移完成,执行了 {len(migration_steps)} 个步骤:")
|
||||
for step in migration_steps:
|
||||
logger.info(f" - {step}")
|
||||
else:
|
||||
logger.info("\n无需迁移,所有字段和索引已存在")
|
||||
|
||||
logger.info("数据库迁移完成!")
|
||||
return True
|
||||
|
||||
|
||||
async def rollback_migration():
|
||||
"""回滚迁移"""
|
||||
logger.info("开始回滚数据库迁移...")
|
||||
|
||||
async with async_engine.begin() as conn:
|
||||
try:
|
||||
if await check_index_exists(conn, "ix_stp_files_upload_batch"):
|
||||
await conn.execute(text("DROP INDEX IF EXISTS ix_stp_files_upload_batch"))
|
||||
logger.info("✓ 删除 upload_batch 索引")
|
||||
|
||||
if await check_index_exists(conn, "ix_stp_files_file_hash"):
|
||||
await conn.execute(text("DROP INDEX IF EXISTS ix_stp_files_file_hash"))
|
||||
logger.info("✓ 删除 file_hash 索引")
|
||||
|
||||
if await check_column_exists(conn, "stp_files", "upload_batch"):
|
||||
await conn.execute(text("ALTER TABLE stp_files DROP COLUMN upload_batch"))
|
||||
logger.info("✓ 删除 upload_batch 字段")
|
||||
|
||||
if await check_column_exists(conn, "stp_files", "volume"):
|
||||
await conn.execute(text("ALTER TABLE stp_files DROP COLUMN volume"))
|
||||
logger.info("✓ 删除 volume 字段")
|
||||
|
||||
if await check_column_exists(conn, "stp_files", "surface_area"):
|
||||
await conn.execute(text("ALTER TABLE stp_files DROP COLUMN surface_area"))
|
||||
logger.info("✓ 删除 surface_area 字段")
|
||||
|
||||
if await check_column_exists(conn, "stp_files", "product_weight"):
|
||||
await conn.execute(text("ALTER TABLE stp_files DROP COLUMN product_weight"))
|
||||
logger.info("✓ 删除 product_weight 字段")
|
||||
|
||||
logger.info("回滚完成!")
|
||||
except Exception as e:
|
||||
logger.error(f"回滚失败: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="数据库迁移脚本 - 多上传支持")
|
||||
parser.add_argument("--rollback", action="store_true", help="回滚迁移")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.rollback:
|
||||
asyncio.run(rollback_migration())
|
||||
else:
|
||||
asyncio.run(run_migration())
|
||||
@@ -0,0 +1,439 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
STP文件几何验证脚本
|
||||
使用FreeCAD命令行验证PythonOCC提取的几何数据是否正确
|
||||
|
||||
使用方法:
|
||||
freecad -c verify_stp.py <stp_file_path>
|
||||
|
||||
或直接运行:
|
||||
python verify_stp.py <stp_file_path>
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目路径
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(PROJECT_ROOT / "src"))
|
||||
|
||||
# 全局结果字典
|
||||
verification_result = {
|
||||
"file": "",
|
||||
"timestamp": "",
|
||||
"freecad": {},
|
||||
"pythonocc": {},
|
||||
"comparison": {},
|
||||
"status": "pending"
|
||||
}
|
||||
|
||||
|
||||
def verify_with_freecad(stp_path):
|
||||
"""使用FreeCAD验证STP文件"""
|
||||
try:
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Mesh
|
||||
import MeshPart
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"FreeCAD 验证")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
# 创建新文档
|
||||
doc = FreeCAD.newDocument("Verification")
|
||||
|
||||
# 导入STP文件
|
||||
Part.insert(stp_path, "Verification")
|
||||
|
||||
# 获取所有形状对象
|
||||
shapes = []
|
||||
for obj in doc.Objects:
|
||||
if hasattr(obj, 'Shape') and not obj.Shape.isNull():
|
||||
shapes.append(obj.Shape)
|
||||
|
||||
if not shapes:
|
||||
print("❌ 未找到有效的几何形状")
|
||||
return None
|
||||
|
||||
# 合并所有形状
|
||||
compound = shapes[0]
|
||||
for s in shapes[1:]:
|
||||
compound = compound.fuse(s)
|
||||
|
||||
# 提取几何数据
|
||||
bbox = compound.BoundBox
|
||||
|
||||
# 计算体积和表面积
|
||||
volume_mm3 = compound.Volume
|
||||
surface_area_mm2 = compound.Area
|
||||
|
||||
# 获取质心
|
||||
com = compound.CenterOfMass
|
||||
|
||||
# 拓扑信息
|
||||
topology = {
|
||||
"faces": len(compound.Faces),
|
||||
"edges": len(compound.Edges),
|
||||
"vertices": len(compound.Vertexes),
|
||||
"wires": len(compound.Wires),
|
||||
"shells": len(compound.Shells),
|
||||
"solids": len(compound.Solids)
|
||||
}
|
||||
|
||||
# 生成网格用于点云验证
|
||||
mesh = MeshPart.meshFromShape(compound, LinearDeflection=0.1, AngularDeflection=0.5)
|
||||
|
||||
result = {
|
||||
"volume_mm3": float(volume_mm3),
|
||||
"volume_cm3": float(volume_mm3 / 1000),
|
||||
"surface_area_mm2": float(surface_area_mm2),
|
||||
"surface_area_cm2": float(surface_area_mm2 / 100),
|
||||
"bounding_box": {
|
||||
"x_min": float(bbox.XMin),
|
||||
"x_max": float(bbox.XMax),
|
||||
"y_min": float(bbox.YMin),
|
||||
"y_max": float(bbox.YMax),
|
||||
"z_min": float(bbox.ZMin),
|
||||
"z_max": float(bbox.ZMax),
|
||||
"x_length": float(bbox.XLength),
|
||||
"y_length": float(bbox.YLength),
|
||||
"z_length": float(bbox.ZLength),
|
||||
"center": [float(bbox.Center.x), float(bbox.Center.y), float(bbox.Center.z)]
|
||||
},
|
||||
"center_of_mass": [float(com.x), float(com.y), float(com.z)],
|
||||
"topology": topology,
|
||||
"mesh": {
|
||||
"points": mesh.CountPoints,
|
||||
"facets": mesh.CountFacets
|
||||
}
|
||||
}
|
||||
|
||||
# 打印结果
|
||||
print(f"体积: {result['volume_cm3']:.4f} cm³ ({result['volume_mm3']:.2f} mm³)")
|
||||
print(f"表面积: {result['surface_area_cm2']:.4f} cm² ({result['surface_area_mm2']:.2f} mm²)")
|
||||
print(f"\n边界框:")
|
||||
print(f" X: {result['bounding_box']['x_length']:.4f} mm ({result['bounding_box']['x_min']:.2f} ~ {result['bounding_box']['x_max']:.2f})")
|
||||
print(f" Y: {result['bounding_box']['y_length']:.4f} mm ({result['bounding_box']['y_min']:.2f} ~ {result['bounding_box']['y_max']:.2f})")
|
||||
print(f" Z: {result['bounding_box']['z_length']:.4f} mm ({result['bounding_box']['z_min']:.2f} ~ {result['bounding_box']['z_max']:.2f})")
|
||||
print(f" 中心: ({result['bounding_box']['center'][0]:.2f}, {result['bounding_box']['center'][1]:.2f}, {result['bounding_box']['center'][2]:.2f})")
|
||||
print(f"\n质心: ({result['center_of_mass'][0]:.4f}, {result['center_of_mass'][1]:.4f}, {result['center_of_mass'][2]:.4f})")
|
||||
print(f"\n拓扑信息:")
|
||||
print(f" 面: {topology['faces']}")
|
||||
print(f" 边: {topology['edges']}")
|
||||
print(f" 顶点: {topology['vertices']}")
|
||||
print(f" 线框: {topology['wires']}")
|
||||
print(f" 壳体: {topology['shells']}")
|
||||
print(f" 实体: {topology['solids']}")
|
||||
print(f"\n网格:")
|
||||
print(f" 点数: {result['mesh']['points']}")
|
||||
print(f" 面数: {result['mesh']['facets']}")
|
||||
|
||||
# 关闭文档
|
||||
FreeCAD.closeDocument("Verification")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ FreeCAD验证失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def verify_with_pythonocc(stp_path):
|
||||
"""使用PythonOCC验证STP文件"""
|
||||
try:
|
||||
from OCC.Core.STEPControl import STEPControl_Reader
|
||||
from OCC.Core.IFSelect import IFSelect_RetDone
|
||||
from OCC.Core.BRepTools import breptools_Read
|
||||
from OCC.Core.TopExp import TopExp_Explorer
|
||||
from OCC.Core.TopAbs import TopAbs_SOLID, TopAbs_FACE, TopAbs_EDGE, TopAbs_VERTEX
|
||||
from OCC.Core.BRepGProp import brepgprop_VolumeProperties, brepgprop_SurfaceProperties
|
||||
from OCC.Core.GProp import GProp_GProps
|
||||
from OCC.Core.Bnd import Bnd_Box
|
||||
from OCC.Core.BRepBndLib import brepbndlib_Add
|
||||
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
|
||||
from OCC.Core.TopLoc import TopLoc_Location
|
||||
from OCC.Core.BRep import BRep_Tool
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"PythonOCC 验证")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
# 读取STP文件
|
||||
reader = STEPControl_Reader()
|
||||
status = reader.ReadFile(stp_path)
|
||||
|
||||
if status != IFSelect_RetDone:
|
||||
print("❌ 无法读取STP文件")
|
||||
return None
|
||||
|
||||
reader.TransferRoots()
|
||||
shape = reader.OneShape()
|
||||
|
||||
# 生成网格
|
||||
mesh_gen = BRepMesh_IncrementalMesh(shape, 0.1)
|
||||
mesh_gen.Perform()
|
||||
|
||||
# 计算体积
|
||||
vol_props = GProp_GProps()
|
||||
brepgprop_VolumeProperties(shape, vol_props)
|
||||
volume_mm3 = vol_props.Mass()
|
||||
com = vol_props.CentreOfMass()
|
||||
|
||||
# 计算表面积
|
||||
surf_props = GProp_GProps()
|
||||
brepgprop_SurfaceProperties(shape, surf_props)
|
||||
surface_area_mm2 = surf_props.Mass()
|
||||
|
||||
# 计算边界框
|
||||
bbox = Bnd_Box()
|
||||
brepbndlib_Add(shape, bbox)
|
||||
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
||||
|
||||
# 拓扑统计
|
||||
def count_topology(shape, top_type):
|
||||
explorer = TopExp_Explorer(shape, top_type)
|
||||
count = 0
|
||||
while explorer.More():
|
||||
count += 1
|
||||
explorer.Next()
|
||||
return count
|
||||
|
||||
topology = {
|
||||
"faces": count_topology(shape, TopAbs_FACE),
|
||||
"edges": count_topology(shape, TopAbs_EDGE),
|
||||
"vertices": count_topology(shape, TopAbs_VERTEX),
|
||||
"solids": count_topology(shape, TopAbs_SOLID)
|
||||
}
|
||||
|
||||
# 统计网格顶点和三角形
|
||||
mesh_points = 0
|
||||
mesh_facets = 0
|
||||
explorer = TopExp_Explorer(shape, TopAbs_FACE)
|
||||
while explorer.More():
|
||||
face = explorer.Current()
|
||||
location = TopLoc_Location()
|
||||
triangulation = BRep_Tool.Triangulation(face, location)
|
||||
if triangulation:
|
||||
mesh_points += triangulation.NbNodes()
|
||||
mesh_facets += triangulation.NbTriangles()
|
||||
explorer.Next()
|
||||
|
||||
result = {
|
||||
"volume_mm3": float(volume_mm3),
|
||||
"volume_cm3": float(volume_mm3 / 1000),
|
||||
"surface_area_mm2": float(surface_area_mm2),
|
||||
"surface_area_cm2": float(surface_area_mm2 / 100),
|
||||
"bounding_box": {
|
||||
"x_min": float(xmin),
|
||||
"x_max": float(xmax),
|
||||
"y_min": float(ymin),
|
||||
"y_max": float(ymax),
|
||||
"z_min": float(zmin),
|
||||
"z_max": float(zmax),
|
||||
"x_length": float(xmax - xmin),
|
||||
"y_length": float(ymax - ymin),
|
||||
"z_length": float(zmax - zmin),
|
||||
"center": [float((xmin + xmax) / 2), float((ymin + ymax) / 2), float((zmin + zmax) / 2)]
|
||||
},
|
||||
"center_of_mass": [float(com.X()), float(com.Y()), float(com.Z())],
|
||||
"topology": topology,
|
||||
"mesh": {
|
||||
"points": mesh_points,
|
||||
"facets": mesh_facets
|
||||
}
|
||||
}
|
||||
|
||||
# 打印结果
|
||||
print(f"体积: {result['volume_cm3']:.4f} cm³ ({result['volume_mm3']:.2f} mm³)")
|
||||
print(f"表面积: {result['surface_area_cm2']:.4f} cm² ({result['surface_area_mm2']:.2f} mm²)")
|
||||
print(f"\n边界框:")
|
||||
print(f" X: {result['bounding_box']['x_length']:.4f} mm ({result['bounding_box']['x_min']:.2f} ~ {result['bounding_box']['x_max']:.2f})")
|
||||
print(f" Y: {result['bounding_box']['y_length']:.4f} mm ({result['bounding_box']['y_min']:.2f} ~ {result['bounding_box']['y_max']:.2f})")
|
||||
print(f" Z: {result['bounding_box']['z_length']:.4f} mm ({result['bounding_box']['z_min']:.2f} ~ {result['bounding_box']['z_max']:.2f})")
|
||||
print(f" 中心: ({result['bounding_box']['center'][0]:.2f}, {result['bounding_box']['center'][1]:.2f}, {result['bounding_box']['center'][2]:.2f})")
|
||||
print(f"\n质心: ({result['center_of_mass'][0]:.4f}, {result['center_of_mass'][1]:.4f}, {result['center_of_mass'][2]:.4f})")
|
||||
print(f"\n拓扑信息:")
|
||||
print(f" 面: {topology['faces']}")
|
||||
print(f" 边: {topology['edges']}")
|
||||
print(f" 顶点: {topology['vertices']}")
|
||||
print(f" 实体: {topology['solids']}")
|
||||
print(f"\n网格:")
|
||||
print(f" 点数: {result['mesh']['points']}")
|
||||
print(f" 面数: {result['mesh']['facets']}")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ PythonOCC验证失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def compare_results(freecad_result, pythonocc_result):
|
||||
"""对比FreeCAD和PythonOCC的结果"""
|
||||
print(f"\n{'='*70}")
|
||||
print(f"对比结果")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
if not freecad_result or not pythonocc_result:
|
||||
print("❌ 无法对比,缺少数据")
|
||||
return None
|
||||
|
||||
comparison = {}
|
||||
|
||||
# 体积对比
|
||||
vol_diff = abs(freecad_result['volume_mm3'] - pythonocc_result['volume_mm3'])
|
||||
vol_diff_percent = (vol_diff / freecad_result['volume_mm3']) * 100 if freecad_result['volume_mm3'] > 0 else 0
|
||||
|
||||
comparison['volume'] = {
|
||||
"freecad": freecad_result['volume_cm3'],
|
||||
"pythonocc": pythonocc_result['volume_cm3'],
|
||||
"difference_mm3": vol_diff,
|
||||
"difference_percent": vol_diff_percent
|
||||
}
|
||||
|
||||
print(f"体积对比:")
|
||||
print(f" FreeCAD: {freecad_result['volume_cm3']:.4f} cm³")
|
||||
print(f" PythonOCC: {pythonocc_result['volume_cm3']:.4f} cm³")
|
||||
print(f" 差异: {vol_diff:.4f} mm³ ({vol_diff_percent:.4f}%)")
|
||||
print(f" 状态: {'✅ 通过' if vol_diff_percent < 1 else '❌ 超差'}")
|
||||
|
||||
# 表面积对比
|
||||
area_diff = abs(freecad_result['surface_area_mm2'] - pythonocc_result['surface_area_mm2'])
|
||||
area_diff_percent = (area_diff / freecad_result['surface_area_mm2']) * 100 if freecad_result['surface_area_mm2'] > 0 else 0
|
||||
|
||||
comparison['surface_area'] = {
|
||||
"freecad": freecad_result['surface_area_cm2'],
|
||||
"pythonocc": pythonocc_result['surface_area_cm2'],
|
||||
"difference_mm2": area_diff,
|
||||
"difference_percent": area_diff_percent
|
||||
}
|
||||
|
||||
print(f"\n表面积对比:")
|
||||
print(f" FreeCAD: {freecad_result['surface_area_cm2']:.4f} cm²")
|
||||
print(f" PythonOCC: {pythonocc_result['surface_area_cm2']:.4f} cm²")
|
||||
print(f" 差异: {area_diff:.4f} mm² ({area_diff_percent:.4f}%)")
|
||||
print(f" 状态: {'✅ 通过' if area_diff_percent < 2 else '❌ 超差'}")
|
||||
|
||||
# 边界框对比
|
||||
bbox_fc = freecad_result['bounding_box']
|
||||
bbox_occ = pythonocc_result['bounding_box']
|
||||
|
||||
x_diff = abs(bbox_fc['x_length'] - bbox_occ['x_length'])
|
||||
y_diff = abs(bbox_fc['y_length'] - bbox_occ['y_length'])
|
||||
z_diff = abs(bbox_fc['z_length'] - bbox_occ['z_length'])
|
||||
|
||||
comparison['bounding_box'] = {
|
||||
"x": {"freecad": bbox_fc['x_length'], "pythonocc": bbox_occ['x_length'], "difference": x_diff},
|
||||
"y": {"freecad": bbox_fc['y_length'], "pythonocc": bbox_occ['y_length'], "difference": y_diff},
|
||||
"z": {"freecad": bbox_fc['z_length'], "pythonocc": bbox_occ['z_length'], "difference": z_diff}
|
||||
}
|
||||
|
||||
print(f"\n边界框对比:")
|
||||
print(f" X轴: FreeCAD={bbox_fc['x_length']:.4f}, PythonOCC={bbox_occ['x_length']:.4f}, 差异={x_diff:.4f} mm")
|
||||
print(f" Y轴: FreeCAD={bbox_fc['y_length']:.4f}, PythonOCC={bbox_occ['y_length']:.4f}, 差异={y_diff:.4f} mm")
|
||||
print(f" Z轴: FreeCAD={bbox_fc['z_length']:.4f}, PythonOCC={bbox_occ['z_length']:.4f}, 差异={z_diff:.4f} mm")
|
||||
|
||||
# 质心对比
|
||||
com_fc = freecad_result['center_of_mass']
|
||||
com_occ = pythonocc_result['center_of_mass']
|
||||
com_diff = [
|
||||
abs(com_fc[0] - com_occ[0]),
|
||||
abs(com_fc[1] - com_occ[1]),
|
||||
abs(com_fc[2] - com_occ[2])
|
||||
]
|
||||
|
||||
comparison['center_of_mass'] = {
|
||||
"freecad": com_fc,
|
||||
"pythonocc": com_occ,
|
||||
"difference": com_diff
|
||||
}
|
||||
|
||||
print(f"\n质心对比:")
|
||||
print(f" FreeCAD: ({com_fc[0]:.4f}, {com_fc[1]:.4f}, {com_fc[2]:.4f})")
|
||||
print(f" PythonOCC: ({com_occ[0]:.4f}, {com_occ[1]:.4f}, {com_occ[2]:.4f})")
|
||||
print(f" 差异: ({com_diff[0]:.4f}, {com_diff[1]:.4f}, {com_diff[2]:.4f}) mm")
|
||||
|
||||
# 拓扑对比
|
||||
topo_fc = freecad_result['topology']
|
||||
topo_occ = pythonocc_result['topology']
|
||||
|
||||
comparison['topology'] = {
|
||||
"faces": {"freecad": topo_fc['faces'], "pythonocc": topo_occ['faces']},
|
||||
"edges": {"freecad": topo_fc['edges'], "pythonocc": topo_occ['edges']},
|
||||
"vertices": {"freecad": topo_fc['vertices'], "pythonocc": topo_occ['vertices']}
|
||||
}
|
||||
|
||||
print(f"\n拓扑对比:")
|
||||
print(f" 面: FreeCAD={topo_fc['faces']}, PythonOCC={topo_occ['faces']}")
|
||||
print(f" 边: FreeCAD={topo_fc['edges']}, PythonOCC={topo_occ['edges']}")
|
||||
print(f" 顶点: FreeCAD={topo_fc['vertices']}, PythonOCC={topo_occ['vertices']}")
|
||||
|
||||
# 总体评估
|
||||
passed = vol_diff_percent < 1 and area_diff_percent < 2
|
||||
|
||||
print(f"\n{'='*70}")
|
||||
print(f"验证结果: {'✅ 通过' if passed else '❌ 失败'}")
|
||||
print(f"{'='*70}\n")
|
||||
|
||||
comparison['overall_status'] = "passed" if passed else "failed"
|
||||
|
||||
return comparison
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python verify_stp.py <stp_file_path>")
|
||||
print(" 或: freecad -c verify_stp.py <stp_file_path>")
|
||||
sys.exit(1)
|
||||
|
||||
stp_path = sys.argv[1]
|
||||
|
||||
if not os.path.exists(stp_path):
|
||||
print(f"❌ 文件不存在: {stp_path}")
|
||||
sys.exit(1)
|
||||
|
||||
# 初始化结果
|
||||
verification_result["file"] = stp_path
|
||||
verification_result["timestamp"] = datetime.now().isoformat()
|
||||
|
||||
print(f"\n{'#'*70}")
|
||||
print(f"# STP文件几何验证报告")
|
||||
print(f"# 文件: {stp_path}")
|
||||
print(f"# 时间: {verification_result['timestamp']}")
|
||||
print(f"{'#'*70}")
|
||||
|
||||
# FreeCAD验证
|
||||
freecad_result = verify_with_freecad(stp_path)
|
||||
verification_result["freecad"] = freecad_result or {}
|
||||
|
||||
# PythonOCC验证
|
||||
pythonocc_result = verify_with_pythonocc(stp_path)
|
||||
verification_result["pythonocc"] = pythonocc_result or {}
|
||||
|
||||
# 对比结果
|
||||
comparison = compare_results(freecad_result, pythonocc_result)
|
||||
verification_result["comparison"] = comparison or {}
|
||||
verification_result["status"] = comparison.get('overall_status', 'failed') if comparison else 'failed'
|
||||
|
||||
# 保存JSON报告
|
||||
report_path = Path(stp_path).stem + "_verification_report.json"
|
||||
with open(report_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(verification_result, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n验证报告已保存: {report_path}")
|
||||
|
||||
return verification_result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
result = main()
|
||||
sys.exit(0 if result['status'] == 'passed' else 1)
|
||||
Reference in New Issue
Block a user