906 lines
34 KiB
Python
906 lines
34 KiB
Python
from typing import Dict, List, Any, Tuple, Optional
|
|
import math
|
|
import numpy as np
|
|
from OCC.Core.BRepOffsetAPI import BRepOffsetAPI_DraftAngle
|
|
from OCC.Core.BRepAlgoAPI import BRepAlgoAPI_Cut, BRepAlgoAPI_Section, BRepAlgoAPI_Common
|
|
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_Transform
|
|
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox, BRepPrimAPI_MakeHalfSpace
|
|
from OCC.Core.gp import gp_Pln, gp_Dir, gp_Pnt, gp_Trsf, gp_Ax2
|
|
from OCC.Core.TopoDS import TopoDS_Face, topods
|
|
from OCC.Core.BRep import BRep_Tool
|
|
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
|
|
from OCC.Core.GProp import GProp_GProps
|
|
from OCC.Core.BRepGProp import brepgprop
|
|
from OCC.Core.TopExp import TopExp_Explorer
|
|
from OCC.Core.TopAbs import TopAbs_FACE, TopAbs_EDGE
|
|
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface, BRepAdaptor_Curve
|
|
from OCC.Core.Bnd import Bnd_Box
|
|
from OCC.Core.BRepBndLib import brepbndlib
|
|
from OCC.Core.TopLoc import TopLoc_Location
|
|
|
|
from models.schemas import create_mold_cavity_data, create_mold_key_info
|
|
from utils.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class BaseMoldGenerator:
|
|
"""模具生成器基类 - 提供共用方法"""
|
|
|
|
def __init__(self, shrinkage_rate: float = 0.005, draft_angle: float = 2.0,
|
|
material_density: float = 1.05):
|
|
self.shrinkage_rate = shrinkage_rate
|
|
self.draft_angle = draft_angle
|
|
self.material_density = material_density
|
|
|
|
self.ai_parting_detector: Optional[Any] = None
|
|
self.ai_draft_analyzer: Optional[Any] = None
|
|
|
|
def set_ai_model(self, parting_detector: Any = None, draft_analyzer: Any = None):
|
|
self.ai_parting_detector = parting_detector
|
|
self.ai_draft_analyzer = draft_analyzer
|
|
logger.info("AI 模型接口已设置")
|
|
|
|
def _apply_shrinkage_compensation(self, shape: Any) -> Any:
|
|
scale_factor = 1.0 + self.shrinkage_rate
|
|
trsf = gp_Trsf()
|
|
trsf.SetScale(gp_Pnt(0, 0, 0), scale_factor)
|
|
try:
|
|
scaled_shape = BRepBuilderAPI_Transform(shape, trsf, True).Shape()
|
|
logger.info(f"收缩率补偿: {self.shrinkage_rate*100:.2f}%, 缩放因子: {scale_factor:.4f}")
|
|
return scaled_shape
|
|
except Exception as e:
|
|
logger.warning(f"收缩率补偿失败: {e}")
|
|
return shape
|
|
|
|
def _apply_draft_angles(self, shape: Any, parting_surface: Any) -> Any:
|
|
try:
|
|
draft_direction = self._get_draft_direction(parting_surface)
|
|
if draft_direction is None:
|
|
logger.warning("无法确定拔模方向,跳过拔模处理")
|
|
return shape
|
|
|
|
draft_angle_rad = math.radians(self.draft_angle)
|
|
draftable_faces = self._find_draftable_faces(shape, draft_direction)
|
|
|
|
if not draftable_faces:
|
|
logger.info("未找到需要拔模的面,跳过拔模处理")
|
|
return shape
|
|
|
|
logger.info(f"应用拔模角: {self.draft_angle}°, {len(draftable_faces)} 个面")
|
|
|
|
drafted_shape = self._execute_draft(shape, draftable_faces, draft_direction, draft_angle_rad)
|
|
return drafted_shape
|
|
|
|
except Exception as e:
|
|
logger.warning(f"拔模角处理失败,返回原始形状: {e}")
|
|
return shape
|
|
|
|
def _get_draft_direction(self, parting_surface: Any) -> Optional[gp_Dir]:
|
|
try:
|
|
surface = BRepAdaptor_Surface(parting_surface)
|
|
if surface.GetType() == 0:
|
|
return surface.Plane().Position().Direction()
|
|
return gp_Dir(0, 0, 1)
|
|
except Exception:
|
|
return gp_Dir(0, 0, 1)
|
|
|
|
def _find_draftable_faces(self, shape: Any, draft_direction: gp_Dir) -> List[Any]:
|
|
draftable = []
|
|
explorer = TopExp_Explorer(shape, TopAbs_FACE)
|
|
|
|
while explorer.More():
|
|
face = topods.Face(explorer.Current())
|
|
normal = self._get_face_normal(face)
|
|
|
|
if normal is not None:
|
|
dot = abs(normal.Dot(draft_direction))
|
|
angle = math.degrees(math.acos(min(dot, 1.0)))
|
|
if 5.0 < angle < 85.0:
|
|
draftable.append(face)
|
|
|
|
explorer.Next()
|
|
|
|
return draftable
|
|
|
|
def _get_face_normal(self, face: Any) -> Optional[gp_Dir]:
|
|
try:
|
|
surface = BRepAdaptor_Surface(face)
|
|
u = (surface.FirstUParameter() + surface.LastUParameter()) / 2
|
|
v = (surface.FirstVParameter() + surface.LastVParameter()) / 2
|
|
|
|
if surface.GetType() == 0:
|
|
return surface.Plane().Position().Direction()
|
|
|
|
from OCC.Core.BRepLProp import BRepLProp_SLProps
|
|
props = BRepLProp_SLProps(surface, 1, 0.001)
|
|
props.SetParameters(u, v)
|
|
if props.IsNormalDefined():
|
|
return props.Normal()
|
|
|
|
return None
|
|
except Exception:
|
|
return None
|
|
|
|
def _execute_draft(self, shape: Any, faces: List[Any],
|
|
draft_direction: gp_Dir, draft_angle_rad: float) -> Any:
|
|
try:
|
|
draft = BRepOffsetAPI_DraftAngle(shape)
|
|
|
|
for face in faces:
|
|
try:
|
|
normal = self._get_face_normal(face)
|
|
if normal is None:
|
|
continue
|
|
|
|
dot = normal.Dot(draft_direction)
|
|
if dot > 0:
|
|
face_dir = draft_direction
|
|
else:
|
|
face_dir = gp_Dir(-draft_direction.X(), -draft_direction.Y(), -draft_direction.Z())
|
|
|
|
draft.Add(face, face_dir, draft_angle_rad, True)
|
|
except Exception:
|
|
continue
|
|
|
|
draft.Build()
|
|
|
|
if draft.IsDone():
|
|
logger.info(f"拔模角应用成功: {len(faces)} 个面, {self.draft_angle}°")
|
|
return draft.Shape()
|
|
else:
|
|
logger.warning("BRepOffsetAPI_DraftAngle 构建失败,尝试逐面拔模")
|
|
return self._draft_faces_sequentially(shape, faces, draft_direction, draft_angle_rad)
|
|
|
|
except Exception as e:
|
|
logger.warning(f"拔模执行失败: {e}")
|
|
return shape
|
|
|
|
def _draft_faces_sequentially(self, shape: Any, faces: List[Any],
|
|
draft_direction: gp_Dir, draft_angle_rad: float) -> Any:
|
|
current_shape = shape
|
|
success_count = 0
|
|
|
|
for face in faces:
|
|
try:
|
|
draft = BRepOffsetAPI_DraftAngle(current_shape)
|
|
normal = self._get_face_normal(face)
|
|
if normal is None:
|
|
continue
|
|
|
|
dot = normal.Dot(draft_direction)
|
|
if dot > 0:
|
|
face_dir = draft_direction
|
|
else:
|
|
face_dir = gp_Dir(-draft_direction.X(), -draft_direction.Y(), -draft_direction.Z())
|
|
|
|
draft.Add(face, face_dir, draft_angle_rad, True)
|
|
draft.Build()
|
|
|
|
if draft.IsDone():
|
|
current_shape = draft.Shape()
|
|
success_count += 1
|
|
except Exception:
|
|
continue
|
|
|
|
if success_count > 0:
|
|
logger.info(f"逐面拔模完成: {success_count}/{len(faces)} 个面成功")
|
|
else:
|
|
logger.warning("逐面拔模全部失败,返回原始形状")
|
|
|
|
return current_shape
|
|
|
|
def _analyze_product_geometry(self, shape: Any) -> Dict[str, Any]:
|
|
try:
|
|
props = GProp_GProps()
|
|
brepgprop.VolumeProperties(shape, props)
|
|
volume = props.Mass()
|
|
|
|
surface_props = GProp_GProps()
|
|
brepgprop.SurfaceProperties(shape, surface_props)
|
|
surface_area = surface_props.Mass()
|
|
|
|
center = props.CentreOfMass()
|
|
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
|
|
inertia = props.MatrixOfInertia()
|
|
|
|
return {
|
|
"volume": volume,
|
|
"surface_area": surface_area,
|
|
"center_of_mass": [float(center.X()), float(center.Y()), float(center.Z())],
|
|
"bounding_box": {
|
|
"min": [float(xmin), float(ymin), float(zmin)],
|
|
"max": [float(xmax), float(ymax), float(zmax)],
|
|
"center": [float((xmin+xmax)/2), float((ymin+ymax)/2), float((zmin+zmax)/2)],
|
|
"dimensions": [float(xmax-xmin), float(ymax-ymin), float(zmax-zmin)]
|
|
},
|
|
"inertia_matrix": self._get_inertia_matrix(props)
|
|
}
|
|
except Exception as e:
|
|
logger.error(f"产品几何分析失败: {e}")
|
|
raise
|
|
|
|
def _split_cavity_core(self, shape: Any, parting_surface: Any, margin: int = 20) -> Tuple[Any, Any]:
|
|
"""
|
|
用分型面将模具块切分为A板(上模/型腔)和B板(下模/型芯),
|
|
然后从每个板中减去产品形状的对应部分。
|
|
|
|
流程:
|
|
1. 创建完整模具块(产品包围盒 + 全方向余量)
|
|
2. 用分型面将模具块切分为 A板 和 B板
|
|
3. A板 - 产品 = 型腔(凹模)
|
|
4. B板 - 产品 = 型芯(凸模)
|
|
"""
|
|
try:
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
|
|
mold_xmin = xmin - margin
|
|
mold_ymin = ymin - margin
|
|
mold_zmin = zmin - margin
|
|
mold_xmax = xmax + margin
|
|
mold_ymax = ymax + margin
|
|
mold_zmax = zmax + margin
|
|
|
|
mold_block = BRepPrimAPI_MakeBox(
|
|
gp_Pnt(mold_xmin, mold_ymin, mold_zmin),
|
|
gp_Pnt(mold_xmax, mold_ymax, mold_zmax)
|
|
).Shape()
|
|
|
|
parting_plane = self._get_parting_plane(parting_surface, shape)
|
|
if parting_plane is None:
|
|
logger.warning("无法提取分型面平面,使用回退方案")
|
|
center_z = (zmin + zmax) / 2
|
|
parting_plane = gp_Pln(gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1))
|
|
|
|
a_plate, b_plate = self._split_mold_block_by_plane(mold_block, parting_plane)
|
|
|
|
if a_plate is not None and b_plate is not None:
|
|
cavity = self._subtract_product_from_plate(a_plate, shape, "型腔(A板)")
|
|
core = self._subtract_product_from_plate(b_plate, shape, "型芯(B板)")
|
|
|
|
if cavity is not None and core is not None:
|
|
logger.info("型腔/型芯分离完成(分型面切分+布尔减)")
|
|
return cavity, core
|
|
elif cavity is not None:
|
|
logger.warning("型芯生成失败,使用产品形状")
|
|
return cavity, shape
|
|
elif core is not None:
|
|
logger.warning("型腔生成失败,使用模具块")
|
|
return mold_block, core
|
|
|
|
logger.warning("A/B板切分不完全,回退到原方案")
|
|
return self._split_cavity_core_fallback(shape, mold_block)
|
|
|
|
except Exception as e:
|
|
logger.error(f"型腔分离失败: {e}")
|
|
return self._split_cavity_core_fallback(shape, None)
|
|
|
|
def _get_parting_plane(self, parting_surface: Any, shape: Any) -> Optional[gp_Pln]:
|
|
"""从分型面提取平面方程"""
|
|
try:
|
|
surface = BRepAdaptor_Surface(parting_surface)
|
|
if surface.GetType() == 0:
|
|
return surface.Plane()
|
|
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
center_z = (zmin + zmax) / 2
|
|
return gp_Pln(gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1))
|
|
|
|
except Exception as e:
|
|
logger.warning(f"分型面平面提取失败: {e}")
|
|
return None
|
|
|
|
def _split_mold_block_by_plane(self, mold_block: Any,
|
|
parting_plane: gp_Pln) -> Tuple[Any, Any]:
|
|
"""
|
|
用分型面将模具块切分为A板(上模)和B板(下模)
|
|
|
|
方法:使用半空间体与模具块的布尔交集运算
|
|
- A板 = 模具块 ∩ 分型面上方半空间
|
|
- B板 = 模具块 ∩ 分型面下方半空间
|
|
"""
|
|
try:
|
|
plane_origin = parting_plane.Location()
|
|
plane_normal = parting_plane.Axis().Direction()
|
|
|
|
ref_point_above = gp_Pnt(
|
|
plane_origin.X() + plane_normal.X() * 10,
|
|
plane_origin.Y() + plane_normal.Y() * 10,
|
|
plane_origin.Z() + plane_normal.Z() * 10
|
|
)
|
|
ref_point_below = gp_Pnt(
|
|
plane_origin.X() - plane_normal.X() * 10,
|
|
plane_origin.Y() - plane_normal.Y() * 10,
|
|
plane_origin.Z() - plane_normal.Z() * 10
|
|
)
|
|
|
|
half_space_above = BRepPrimAPI_MakeHalfSpace(
|
|
BRepBuilderAPI_MakeFace(parting_plane).Face(),
|
|
ref_point_above
|
|
).Shape()
|
|
|
|
half_space_below = BRepPrimAPI_MakeHalfSpace(
|
|
BRepBuilderAPI_MakeFace(parting_plane).Face(),
|
|
ref_point_below
|
|
).Shape()
|
|
|
|
a_plate_op = BRepAlgoAPI_Common(mold_block, half_space_above)
|
|
a_plate = None
|
|
if a_plate_op.IsDone():
|
|
a_plate = a_plate_op.Shape()
|
|
logger.info("A板(上模)切分成功")
|
|
else:
|
|
logger.warning("A板切分失败")
|
|
|
|
b_plate_op = BRepAlgoAPI_Common(mold_block, half_space_below)
|
|
b_plate = None
|
|
if b_plate_op.IsDone():
|
|
b_plate = b_plate_op.Shape()
|
|
logger.info("B板(下模)切分成功")
|
|
else:
|
|
logger.warning("B板切分失败")
|
|
|
|
return a_plate, b_plate
|
|
|
|
except Exception as e:
|
|
logger.error(f"A/B板分离失败: {e}")
|
|
return None, None
|
|
|
|
def _subtract_product_from_plate(self, plate: Any, product: Any,
|
|
plate_name: str) -> Any:
|
|
"""从模板中减去产品形状,生成型腔或型芯"""
|
|
try:
|
|
cut_op = BRepAlgoAPI_Cut(plate, product)
|
|
if cut_op.IsDone():
|
|
result = cut_op.Shape()
|
|
logger.info(f"{plate_name}减去产品成功")
|
|
return result
|
|
else:
|
|
logger.warning(f"{plate_name}布尔减运算失败")
|
|
return plate
|
|
except Exception as e:
|
|
logger.warning(f"{plate_name}减产品失败: {e}")
|
|
return plate
|
|
|
|
def _split_cavity_core_fallback(self, shape: Any,
|
|
mold_block: Optional[Any] = None) -> Tuple[Any, Any]:
|
|
"""
|
|
分模回退方案:用边界框中心面作为分型面切分模具块。
|
|
"""
|
|
logger.warning("使用分模回退方案")
|
|
|
|
try:
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
|
|
margin = 20
|
|
if mold_block is None:
|
|
mold_block = BRepPrimAPI_MakeBox(
|
|
gp_Pnt(xmin - margin, ymin - margin, zmin - margin),
|
|
gp_Pnt(xmax + margin, ymax + margin, zmax + margin)
|
|
).Shape()
|
|
|
|
center_z = (zmin + zmax) / 2
|
|
parting_plane = gp_Pln(gp_Pnt(0, 0, center_z), gp_Dir(0, 0, 1))
|
|
a_plate, b_plate = self._split_mold_block_by_plane(mold_block, parting_plane)
|
|
|
|
if a_plate is not None and b_plate is not None:
|
|
cavity = self._subtract_product_from_plate(a_plate, shape, "型腔(回退)")
|
|
core = self._subtract_product_from_plate(b_plate, shape, "型芯(回退)")
|
|
if cavity is not None and core is not None:
|
|
logger.info("回退方案型腔/型芯分离完成")
|
|
return cavity, core
|
|
|
|
cavity = self._subtract_product_from_plate(mold_block, shape, "型腔(兜底)")
|
|
return cavity or mold_block, shape
|
|
|
|
except Exception as e:
|
|
logger.error(f"分模回退方案失败: {e}")
|
|
try:
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
margin = 20
|
|
cavity_block = BRepPrimAPI_MakeBox(
|
|
gp_Pnt(xmin - margin, ymin - margin, zmin - margin),
|
|
gp_Pnt(xmax + margin, ymax + margin, zmax + margin)
|
|
).Shape()
|
|
cavity = self._subtract_product_from_plate(cavity_block, shape, "型腔(兜底)")
|
|
return cavity or cavity_block, shape
|
|
except Exception:
|
|
return shape, shape
|
|
|
|
def detect_insert_regions(self, shape: Any, analysis: Dict,
|
|
depth_threshold: float = 30.0,
|
|
aspect_threshold: float = 3.0) -> List[Dict[str, Any]]:
|
|
"""
|
|
检测需要独立镶件的区域
|
|
|
|
镶件判定条件:
|
|
1. 深腔区域(深度超过阈值)
|
|
2. 细长特征(长径比超过阈值)
|
|
3. 易磨损区域(尖锐角落、薄壁)
|
|
4. 精密特征(高精度要求的局部区域)
|
|
|
|
Args:
|
|
shape: 产品形状
|
|
analysis: 几何分析结果
|
|
depth_threshold: 深腔深度阈值 mm
|
|
aspect_threshold: 长径比阈值
|
|
|
|
Returns:
|
|
镶件区域列表
|
|
"""
|
|
inserts = []
|
|
|
|
try:
|
|
bbox = analysis.get("bounding_box", {})
|
|
dims = bbox.get("dimensions", [0, 0, 0])
|
|
center = bbox.get("center", [0, 0, 0])
|
|
|
|
if dims[2] > depth_threshold:
|
|
inserts.append({
|
|
"type": "deep_cavity_insert",
|
|
"location": center,
|
|
"depth": dims[2],
|
|
"reason": f"型腔深度 {dims[2]:.1f}mm 超过阈值 {depth_threshold}mm",
|
|
"insert_type": "core_pin",
|
|
"priority": "high"
|
|
})
|
|
|
|
explorer = TopExp_Explorer(shape, TopAbs_FACE)
|
|
face_idx = 0
|
|
|
|
while explorer.More():
|
|
face = topods.Face(explorer.Current())
|
|
face_idx += 1
|
|
|
|
try:
|
|
surface = BRepAdaptor_Surface(face)
|
|
|
|
face_props = GProp_GProps()
|
|
brepgprop.SurfaceProperties(face, face_props)
|
|
area = face_props.Mass()
|
|
|
|
if area < 1.0 and area > 0.001:
|
|
bbox_face = Bnd_Box()
|
|
brepbndlib.Add(face, bbox_face)
|
|
try:
|
|
fxmin, fymin, fzmin, fxmax, fymax, fzmax = bbox_face.Get()
|
|
f_dims = [fxmax - fxmin, fymax - fymin, fzmax - fzmin]
|
|
max_dim = max(f_dims)
|
|
min_dim = min(f_dims)
|
|
|
|
if min_dim > 0.01 and max_dim / min_dim > aspect_threshold:
|
|
face_center = [
|
|
float((fxmin + fxmax) / 2),
|
|
float((fymin + fymax) / 2),
|
|
float((fzmin + fzmax) / 2)
|
|
]
|
|
|
|
inserts.append({
|
|
"type": "slender_feature_insert",
|
|
"location": face_center,
|
|
"aspect_ratio": max_dim / min_dim,
|
|
"reason": f"细长特征,长径比 {max_dim/min_dim:.1f}",
|
|
"insert_type": "core_pin",
|
|
"priority": "medium",
|
|
"face_index": face_idx
|
|
})
|
|
except Exception:
|
|
pass
|
|
|
|
if surface.GetType() == 1:
|
|
radius = surface.Cylinder().Radius()
|
|
if radius < 3.0 and radius > 0.1:
|
|
cyl_axis = surface.Cylinder().Position().Axis()
|
|
cyl_loc = cyl_axis.Location()
|
|
|
|
inserts.append({
|
|
"type": "small_hole_insert",
|
|
"location": [float(cyl_loc.X()), float(cyl_loc.Y()), float(cyl_loc.Z())],
|
|
"radius": float(radius),
|
|
"reason": f"小孔特征,半径 {radius:.2f}mm",
|
|
"insert_type": "core_pin",
|
|
"priority": "high",
|
|
"face_index": face_idx
|
|
})
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
explorer.Next()
|
|
|
|
if not inserts:
|
|
logger.info("未检测到需要镶件的区域")
|
|
else:
|
|
logger.info(f"检测到 {len(inserts)} 个镶件区域")
|
|
|
|
except Exception as e:
|
|
logger.warning(f"镶件检测失败: {e}")
|
|
|
|
return inserts
|
|
|
|
def _extract_shape_geometry(self, shape: Any, shape_type: str) -> Dict[str, Any]:
|
|
try:
|
|
mesh = BRepMesh_IncrementalMesh(shape, 0.1)
|
|
mesh.Perform()
|
|
|
|
vertices = []
|
|
faces = []
|
|
|
|
explorer = TopExp_Explorer(shape, TopAbs_FACE)
|
|
vertex_index = 0
|
|
|
|
while explorer.More():
|
|
face = explorer.Current()
|
|
location = TopLoc_Location()
|
|
triangulation = BRep_Tool.Triangulation(face, location)
|
|
|
|
if triangulation:
|
|
nb_nodes = triangulation.NbNodes()
|
|
for i in range(1, nb_nodes + 1):
|
|
node = triangulation.Node(i)
|
|
transformed = node.Transformed(location.Transformation())
|
|
vertices.extend([
|
|
float(transformed.X()),
|
|
float(transformed.Y()),
|
|
float(transformed.Z())
|
|
])
|
|
|
|
nb_triangles = triangulation.NbTriangles()
|
|
for i in range(1, nb_triangles + 1):
|
|
triangle = triangulation.Triangle(i)
|
|
idx1 = triangle.Value(1) + vertex_index - 1
|
|
idx2 = triangle.Value(2) + vertex_index - 1
|
|
idx3 = triangle.Value(3) + vertex_index - 1
|
|
faces.extend([int(idx1), int(idx2), int(idx3)])
|
|
|
|
vertex_index += nb_nodes
|
|
|
|
explorer.Next()
|
|
|
|
vertex_count = len(vertices) // 3
|
|
face_count = len(faces) // 3
|
|
|
|
return {
|
|
"type": shape_type,
|
|
"vertices": vertices,
|
|
"faces": faces,
|
|
"vertex_count": vertex_count,
|
|
"face_count": face_count,
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"{shape_type}几何提取失败: {e}")
|
|
return {
|
|
"type": shape_type,
|
|
"vertices": [],
|
|
"faces": [],
|
|
"vertex_count": 0,
|
|
"face_count": 0,
|
|
}
|
|
|
|
def _extract_plane_metadata(self, surface: Any) -> Dict[str, Any]:
|
|
"""从分型面提取平面元数据(法向量、原点、边界)"""
|
|
metadata = {
|
|
"normal": [0.0, 0.0, 1.0],
|
|
"origin": [0.0, 0.0, 0.0],
|
|
"bounds": {"min": [0.0, 0.0, 0.0], "max": [0.0, 0.0, 0.0]},
|
|
}
|
|
try:
|
|
surface_adaptor = BRepAdaptor_Surface(surface)
|
|
if surface_adaptor.GetType() == 0:
|
|
plane = surface_adaptor.Plane()
|
|
axis = plane.Axis()
|
|
normal = axis.Direction()
|
|
origin = plane.Location()
|
|
metadata["normal"] = [float(normal.X()), float(normal.Y()), float(normal.Z())]
|
|
metadata["origin"] = [float(origin.X()), float(origin.Y()), float(origin.Z())]
|
|
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(surface, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
metadata["bounds"] = {
|
|
"min": [float(xmin), float(ymin), float(zmin)],
|
|
"max": [float(xmax), float(ymax), float(zmax)],
|
|
}
|
|
except Exception as e:
|
|
logger.warning(f"提取平面元数据失败: {e}")
|
|
return metadata
|
|
|
|
def _calculate_product_weight(self, analysis: Dict) -> str:
|
|
volume_cm3 = analysis.get("volume", 0) / 1000
|
|
weight_g = volume_cm3 * self.material_density
|
|
return f"{weight_g:.2f} g"
|
|
|
|
def _assess_warpage_risk(self, analysis: Dict) -> str:
|
|
bbox = analysis.get("bounding_box", {}).get("dimensions", [1, 1, 1])
|
|
aspect_ratio = max(bbox) / min(bbox) if min(bbox) > 0 else 1
|
|
|
|
if aspect_ratio > 5:
|
|
return "高 - 建议增加加强筋"
|
|
elif aspect_ratio > 3:
|
|
return "中 - 需优化冷却"
|
|
else:
|
|
return "低"
|
|
|
|
def _get_inertia_matrix(self, props: GProp_GProps) -> List[List[float]]:
|
|
inertia = props.MatrixOfInertia()
|
|
return [
|
|
[inertia.Value(1, 1), inertia.Value(1, 2), inertia.Value(1, 3)],
|
|
[inertia.Value(2, 1), inertia.Value(2, 2), inertia.Value(2, 3)],
|
|
[inertia.Value(3, 1), inertia.Value(3, 2), inertia.Value(3, 3)]
|
|
]
|
|
|
|
def _calculate_parting_line_length(self, parting_line: List) -> float:
|
|
if not parting_line or len(parting_line) < 2:
|
|
return 0.0
|
|
|
|
total_length = 0.0
|
|
for i in range(1, len(parting_line)):
|
|
p1 = np.array(parting_line[i-1])
|
|
p2 = np.array(parting_line[i])
|
|
segment_length = np.linalg.norm(p2 - p1)
|
|
total_length += segment_length
|
|
|
|
return total_length
|
|
|
|
def _calculate_parting_line(self, shape: Any, parting_surface: Any) -> List[List[float]]:
|
|
try:
|
|
section = BRepAlgoAPI_Section(shape, parting_surface)
|
|
section.Build()
|
|
|
|
if not section.IsDone():
|
|
logger.warning("截面运算未完成,使用简化分型线")
|
|
return self._simple_parting_line(shape)
|
|
|
|
edges = []
|
|
explorer = TopExp_Explorer(section.Shape(), TopAbs_EDGE)
|
|
|
|
while explorer.More():
|
|
edge = explorer.Current()
|
|
|
|
curve = BRepAdaptor_Curve(edge)
|
|
first_param = curve.FirstParameter()
|
|
last_param = curve.LastParameter()
|
|
|
|
num_points = max(10, int((last_param - first_param) / 0.5))
|
|
step = (last_param - first_param) / num_points
|
|
|
|
for i in range(num_points + 1):
|
|
param = first_param + i * step
|
|
point = curve.Value(param)
|
|
edges.append([point.X(), point.Y(), point.Z()])
|
|
|
|
explorer.Next()
|
|
|
|
if not edges:
|
|
logger.warning("未找到交线,使用简化分型线")
|
|
return self._simple_parting_line(shape)
|
|
|
|
logger.info(f"计算得到 {len(edges)} 个分型线点")
|
|
return edges
|
|
|
|
except Exception as e:
|
|
logger.error(f"分型线计算失败: {e}")
|
|
return self._simple_parting_line(shape)
|
|
|
|
def _simple_parting_line(self, shape: Any) -> List[List[float]]:
|
|
try:
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
center_z = (zmin + zmax) / 2
|
|
|
|
return [
|
|
[xmin, ymin, center_z],
|
|
[xmax, ymin, center_z],
|
|
[xmax, ymax, center_z],
|
|
[xmin, ymax, center_z],
|
|
[xmin, ymin, center_z]
|
|
]
|
|
except Exception:
|
|
return [[-50, -50, 0], [50, -50, 0], [50, 50, 0], [-50, 50, 0], [-50, -50, 0]]
|
|
|
|
def extend_parting_surface(self, parting_surface: Any, shape: Any,
|
|
extension: float = 30.0) -> Any:
|
|
"""
|
|
将分型面延伸到模具块边界
|
|
|
|
分型面通常只覆盖产品轮廓,需要延伸到模具块边缘
|
|
才能正确分离A板和B板
|
|
|
|
Args:
|
|
parting_surface: 原始分型面
|
|
shape: 产品形状
|
|
extension: 延伸距离 mm
|
|
|
|
Returns:
|
|
延伸后的分型面
|
|
"""
|
|
try:
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(shape, bbox)
|
|
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
|
|
|
|
surface = BRepAdaptor_Surface(parting_surface)
|
|
if surface.GetType() != 0:
|
|
logger.info("分型面非平面,延伸操作跳过")
|
|
return parting_surface
|
|
|
|
plane = surface.Plane()
|
|
origin = plane.Location()
|
|
normal = plane.Axis().Direction()
|
|
|
|
extended_xmin = xmin - extension
|
|
extended_ymin = ymin - extension
|
|
extended_xmax = xmax + extension
|
|
extended_ymax = ymax + extension
|
|
|
|
extended_plane = gp_Pln(origin, normal)
|
|
extended_surface = BRepBuilderAPI_MakeFace(
|
|
extended_plane,
|
|
extended_xmin, extended_xmax,
|
|
extended_ymin, extended_ymax
|
|
).Face()
|
|
|
|
logger.info(f"分型面延伸完成: 延伸距离={extension}mm")
|
|
return extended_surface
|
|
|
|
except Exception as e:
|
|
logger.warning(f"分型面延伸失败: {e}")
|
|
return parting_surface
|
|
|
|
def optimize_parting_line(self, parting_line: List[List[float]],
|
|
smooth_window: int = 5,
|
|
min_segment_length: float = 0.5,
|
|
angle_threshold: float = 150.0) -> List[List[float]]:
|
|
"""
|
|
优化分型线
|
|
|
|
优化内容:
|
|
1. 平滑处理 - 消除噪声点
|
|
2. 去除短线段 - 合并过短的线段
|
|
3. 尖角处理 - 在尖角处添加过渡圆弧
|
|
4. 点密度均匀化 - 重采样使点间距均匀
|
|
|
|
Args:
|
|
parting_line: 原始分型线点列表
|
|
smooth_window: 平滑窗口大小
|
|
min_segment_length: 最小线段长度
|
|
angle_threshold: 尖角判定角度(度)
|
|
|
|
Returns:
|
|
优化后的分型线
|
|
"""
|
|
if len(parting_line) < 3:
|
|
return parting_line
|
|
|
|
try:
|
|
smoothed = self._smooth_parting_line(parting_line, smooth_window)
|
|
|
|
filtered = self._filter_short_segments(smoothed, min_segment_length)
|
|
|
|
optimized = self._round_sharp_corners(filtered, angle_threshold)
|
|
|
|
resampled = self._resample_parting_line(optimized, target_spacing=2.0)
|
|
|
|
logger.info(f"分型线优化: {len(parting_line)} → {len(resampled)} 点")
|
|
return resampled
|
|
|
|
except Exception as e:
|
|
logger.warning(f"分型线优化失败: {e}")
|
|
return parting_line
|
|
|
|
def _smooth_parting_line(self, points: List[List[float]],
|
|
window: int = 5) -> List[List[float]]:
|
|
"""移动平均平滑"""
|
|
if len(points) < window:
|
|
return points
|
|
|
|
arr = np.array(points, dtype=np.float64)
|
|
smoothed = []
|
|
|
|
for i in range(len(arr)):
|
|
start = max(0, i - window // 2)
|
|
end = min(len(arr), i + window // 2 + 1)
|
|
avg = np.mean(arr[start:end], axis=0)
|
|
smoothed.append(avg.tolist())
|
|
|
|
return smoothed
|
|
|
|
def _filter_short_segments(self, points: List[List[float]],
|
|
min_length: float) -> List[List[float]]:
|
|
"""去除过短线段"""
|
|
if not points:
|
|
return points
|
|
|
|
filtered = [points[0]]
|
|
for i in range(1, len(points)):
|
|
dist = np.linalg.norm(np.array(points[i]) - np.array(filtered[-1]))
|
|
if dist >= min_length:
|
|
filtered.append(points[i])
|
|
|
|
return filtered
|
|
|
|
def _round_sharp_corners(self, points: List[List[float]],
|
|
angle_threshold: float) -> List[List[float]]:
|
|
"""在尖角处添加过渡点"""
|
|
if len(points) < 3:
|
|
return points
|
|
|
|
result = [points[0]]
|
|
|
|
for i in range(1, len(points) - 1):
|
|
v1 = np.array(points[i]) - np.array(points[i - 1])
|
|
v2 = np.array(points[i + 1]) - np.array(points[i])
|
|
|
|
len1 = np.linalg.norm(v1)
|
|
len2 = np.linalg.norm(v2)
|
|
|
|
if len1 > 0.001 and len2 > 0.001:
|
|
cos_angle = np.clip(np.dot(v1, v2) / (len1 * len2), -1, 1)
|
|
angle = math.degrees(math.acos(cos_angle))
|
|
|
|
if angle < angle_threshold:
|
|
mid1 = (np.array(points[i - 1]) + np.array(points[i])) / 2
|
|
mid2 = (np.array(points[i]) + np.array(points[i + 1])) / 2
|
|
result.append(mid1.tolist())
|
|
result.append(mid2.tolist())
|
|
else:
|
|
result.append(points[i])
|
|
else:
|
|
result.append(points[i])
|
|
|
|
result.append(points[-1])
|
|
return result
|
|
|
|
def _resample_parting_line(self, points: List[List[float]],
|
|
target_spacing: float) -> List[List[float]]:
|
|
"""重采样使点间距均匀"""
|
|
if len(points) < 2:
|
|
return points
|
|
|
|
arr = np.array(points, dtype=np.float64)
|
|
|
|
cumulative_dist = [0.0]
|
|
for i in range(1, len(arr)):
|
|
dist = np.linalg.norm(arr[i] - arr[i - 1])
|
|
cumulative_dist.append(cumulative_dist[-1] + dist)
|
|
|
|
total_length = cumulative_dist[-1]
|
|
if total_length < target_spacing:
|
|
return points
|
|
|
|
num_points = max(3, int(total_length / target_spacing))
|
|
new_distances = np.linspace(0, total_length, num_points)
|
|
|
|
resampled = []
|
|
for d in new_distances:
|
|
idx = np.searchsorted(cumulative_dist, d) - 1
|
|
idx = max(0, min(idx, len(arr) - 2))
|
|
|
|
seg_start = cumulative_dist[idx]
|
|
seg_end = cumulative_dist[idx + 1]
|
|
seg_length = seg_end - seg_start
|
|
|
|
if seg_length > 0:
|
|
t = (d - seg_start) / seg_length
|
|
else:
|
|
t = 0
|
|
|
|
point = arr[idx] + t * (arr[idx + 1] - arr[idx])
|
|
resampled.append(point.tolist())
|
|
|
|
return resampled
|