Files
geMoldInsight/src/core/mold_generator.py
T

512 lines
19 KiB
Python
Raw Normal View History

2026-04-19 23:41:35 +08:00
from typing import Dict, List, Any, Tuple, Optional
2026-02-11 22:40:35 +08:00
import numpy as np
2026-04-19 23:41:35 +08:00
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace
from OCC.Core.gp import gp_Pln, gp_Dir, gp_Pnt
from OCC.Core.TopoDS import TopoDS_Face
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
2026-03-11 01:05:18 +08:00
from OCC.Core.TopExp import TopExp_Explorer
2026-04-19 23:41:35 +08:00
from OCC.Core.TopAbs import TopAbs_FACE
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.BRepBndLib import brepbndlib_Add
2026-02-11 22:40:35 +08:00
from models.schemas import create_mold_cavity_data, create_mold_key_info
from utils.logger import get_logger
2026-04-19 23:41:35 +08:00
from core.base_mold_generator import BaseMoldGenerator
2026-04-23 00:49:22 +08:00
from core.side_action_designer import SideActionDesigner
2026-02-11 22:40:35 +08:00
logger = get_logger(__name__)
2026-04-19 23:41:35 +08:00
class MoldCavityGenerator(BaseMoldGenerator):
2026-02-11 22:40:35 +08:00
"""模具型腔生成器 - 基于产品模型生成Cavity和Core"""
2026-02-15 00:42:56 +08:00
def __init__(self, shrinkage_rate: float = 0.005, draft_angle: float = 2.0,
material_density: float = 1.05):
2026-04-19 23:41:35 +08:00
super().__init__(shrinkage_rate, draft_angle, material_density)
2026-02-15 00:42:56 +08:00
self.material_densities = {
"ABS": 1.05,
"PP": 0.90,
"PC": 1.20,
"PE": 0.95,
"PS": 1.05,
"PA": 1.14,
"POM": 1.42,
"PMMA": 1.18
}
2026-02-11 22:40:35 +08:00
self.parting_line_tolerance = 0.1
self.max_draft_angle = 5.0
2026-04-23 00:49:22 +08:00
self.side_action_designer = SideActionDesigner()
2026-02-11 22:40:35 +08:00
2026-02-15 00:42:56 +08:00
def set_material(self, material: str):
"""设置产品材料"""
if material in self.material_densities:
self.material_density = self.material_densities[material]
logger.info(f"材料设置为 {material}, 密度: {self.material_density} g/cm³")
else:
logger.warning(f"未知材料 {material}, 使用默认密度 {self.material_density} g/cm³")
2026-02-11 22:40:35 +08:00
def generate_mold_cavities(self, product_shape: Any) -> Dict[str, Any]:
"""
从产品的3D模型生成型腔和型芯
Returns:
{
2026-04-19 23:41:35 +08:00
"cavity": cavity_shape,
"core": core_shape,
"parting_surface": parting_surface,
"parting_line": parting_line
2026-02-11 22:40:35 +08:00
}
"""
logger.info("开始生成模具型腔...")
try:
analysis = self._analyze_product_geometry(product_shape)
2026-04-23 00:49:22 +08:00
parting_result = self._detect_primary_parting(product_shape, analysis)
parting_surface = parting_result["surface"]
parting_line = self.optimize_parting_line(parting_result["line"])
parting_direction = parting_result["direction"]
side_action_result = self.side_action_designer.analyze_and_design(
shape=product_shape,
parting_direction=parting_direction,
mold_size=self._calculate_mold_size(analysis),
parting_surface=parting_surface,
)
undercut_regions = self._build_undercut_regions(
side_action_result.get("undercut_analysis", {})
2026-02-11 22:40:35 +08:00
)
scaled_shape = self._apply_shrinkage_compensation(product_shape)
drafted_shape = self._apply_draft_angles(scaled_shape, parting_surface)
cavity, core = self._split_cavity_core(drafted_shape, parting_surface)
logger.info("模具型腔生成完成")
return {
"cavity": cavity,
"core": core,
"parting_surface": parting_surface,
"parting_line": parting_line,
2026-04-23 00:49:22 +08:00
"analysis": analysis,
"undercut_regions": undercut_regions,
"side_actions": side_action_result,
2026-02-11 22:40:35 +08:00
}
except Exception as e:
logger.error(f"模具型腔生成失败: {e}")
raise
def generate_detailed_cavity_json(self, cavity_data: Dict) -> Dict[str, Any]:
"""
生成详细的型腔三维JSON数据
Returns:
包含完整几何信息的JSON结构
"""
cavity = cavity_data["cavity"]
core = cavity_data["core"]
parting_surface = cavity_data["parting_surface"]
analysis = cavity_data["analysis"]
cavity_geometry = self._extract_shape_geometry(cavity, "cavity")
core_geometry = self._extract_shape_geometry(core, "core")
parting_geometry = self._extract_parting_surface_geometry(
parting_surface
)
detailed_json = {
"metadata": {
"version": "2.0",
"generated_at": str(np.datetime64('now')),
"shrinkage_rate": self.shrinkage_rate,
"draft_angle": self.draft_angle,
"unit": "mm"
},
"product_analysis": {
2026-04-19 23:41:35 +08:00
"bounding_box": analysis.get("bounding_box", {}),
"volume": analysis.get("volume", 0),
"surface_area": analysis.get("surface_area", 0),
"center_of_mass": analysis.get("center_of_mass", [0, 0, 0])
2026-02-11 22:40:35 +08:00
},
"mold_cavities": {
"cavity": cavity_geometry,
"core": core_geometry
},
"parting_surface": parting_geometry,
2026-04-23 00:49:22 +08:00
"quality_checks": {
"undercut_regions": cavity_data.get("undercut_regions", []),
"side_actions": cavity_data.get("side_actions", {}),
},
2026-02-11 22:40:35 +08:00
"manufacturing_info": {
"estimated_mold_size": self._calculate_mold_size(analysis),
"estimated_clamping_force": self._calculate_clamping_force(analysis),
"recommended_material": self._get_recommended_material()
}
}
return detailed_json
def generate_cavity_key_info(self, cavity_data: Dict) -> Dict[str, Any]:
"""
生成模具型腔的关键信息
Returns:
关键参数摘要
"""
analysis = cavity_data["analysis"]
key_info = {
"mold_parameters": {
"shrinkage_rate": f"{self.shrinkage_rate * 100:.2f}%",
"draft_angle": f"{self.draft_angle}°",
"parting_line_length": self._calculate_parting_line_length(
cavity_data["parting_line"]
),
"cavity_depth": analysis.get("bounding_box", {}).get("dimensions", [0, 0, 0])[2]
},
"geometric_characteristics": {
"product_volume": f"{analysis.get('volume', 0) / 1000:.2f} cm³",
"product_weight": self._calculate_product_weight(analysis),
"wall_thickness_range": self._estimate_wall_thickness(analysis),
"complexity_score": self._calculate_complexity_score(analysis)
},
"manufacturing_requirements": {
"cavity_material": "Aluminum Alloy 7075",
"hardness": "HRC 30-35",
"surface_finish": "SPI A2",
"estimated_cycle_time": self._estimate_cycle_time(analysis),
"recommended_injection_pressure": "80-120 MPa"
},
"quality_considerations": {
2026-04-23 00:49:22 +08:00
"undercut_count": len(cavity_data.get("undercut_regions", [])),
"side_action_summary": cavity_data.get("side_actions", {}).get("summary", {}),
2026-02-11 22:40:35 +08:00
"potential_weld_lines": self._identify_weld_line_risk(analysis),
"sink_mark_areas": self._identify_sink_mark_risk(analysis),
"warpage_risk": self._assess_warpage_risk(analysis)
}
}
return key_info
# ==================== 内部方法 ====================
def _detect_parting_surface(self, shape: Any, analysis: Dict) -> Tuple[Any, List]:
2026-03-11 01:05:18 +08:00
"""
检测分型面和分型线
2026-04-19 23:41:35 +08:00
2026-03-11 01:05:18 +08:00
优先级:
1. AI 模型检测(如果已设置)
2. 基于法向量分析的几何方法
3. 简化方法(基于边界框)
"""
try:
2026-04-23 00:49:22 +08:00
parting_result = self._detect_primary_parting(shape, analysis)
logger.info(
f"使用 {parting_result['method']} 方法检测分型面,"
f"置信度={parting_result['confidence']:.3f}"
2026-03-11 01:05:18 +08:00
)
2026-04-23 00:49:22 +08:00
return parting_result["surface"], self.optimize_parting_line(parting_result["line"])
2026-04-19 23:41:35 +08:00
2026-03-11 01:05:18 +08:00
except Exception as e:
logger.warning(f"法向量分析失败,使用简化方法:{e}")
2026-04-19 23:41:35 +08:00
2026-03-11 01:05:18 +08:00
logger.info("使用简化方法检测分型面")
2026-04-19 23:41:35 +08:00
return self._simple_parting_surface(shape, analysis)
2026-04-30 16:06:15 +08:00
def _detect_primary_parting(self, shape: Any, analysis: Dict) -> Dict[str, Any]:
"""检测主分型面(AI优先 → 几何法向量 → 简化回退)"""
if self.ai_parting_detector is not None:
try:
ai_result = self.ai_parting_detector.detect(shape, analysis)
if ai_result is not None:
surface, line = self._create_parting_surface_from_ai(ai_result, analysis, shape)
return {
"surface": surface,
"line": line,
"direction": ai_result.get("normal", [0, 0, 1]),
"method": ai_result.get("method", "ai"),
"confidence": ai_result.get("confidence", 0.8),
}
except Exception as e:
logger.warning(f"AI 分型面检测失败: {e}")
try:
normal_dir = self._analyze_face_normals(shape)
parting_plane = self._create_optimal_parting_plane(shape, analysis, normal_dir)
dims = analysis.get("bounding_box", {}).get("dimensions", [100, 100, 100])
span = max(dims) * 1.5 + 30
parting_surface = BRepBuilderAPI_MakeFace(
parting_plane, -span, span, -span, span
).Face()
parting_surface = self.extend_parting_surface(parting_surface, shape, extension=30.0)
parting_line = self._calculate_parting_line(shape, parting_surface)
return {
"surface": parting_surface,
"line": parting_line,
"direction": [float(normal_dir.X()), float(normal_dir.Y()), float(normal_dir.Z())],
"method": "face_normal_analysis",
"confidence": 0.85,
}
except Exception as e:
logger.warning(f"法向量分析失败,使用简化方法:{e}")
surface, line = self._simple_parting_surface(shape, analysis)
return {
"surface": surface,
"line": line,
"direction": [0, 0, 1],
"method": "simple",
"confidence": 0.6,
}
2026-04-23 00:49:22 +08:00
def _build_undercut_regions(self, undercut_analysis: Dict[str, Any]) -> List[Dict[str, Any]]:
"""将侧向机构分析结果转换为兼容旧结构的倒扣区域列表。"""
undercut_faces = undercut_analysis.get("undercut_faces", [])
regions = []
for face in undercut_faces:
regions.append({
"type": "negative_draft",
"location": face.get("center", [0, 0, 0]),
"severity": face.get("severity", "medium"),
"area": face.get("area", 0),
"is_outer": face.get("is_outer", False),
"face_index": face.get("face_index"),
})
logger.info(f"转换得到 {len(regions)} 个兼容倒扣区域")
return regions
2026-04-19 23:41:35 +08:00
def _analyze_face_normals(self, shape: Any) -> gp_Dir:
"""
分析产品表面的法向量分布,找出最优分型方向
原理:
- 统计所有面的法向量
- 选择法向量变化最小的方向作为分型方向
- 避免倒扣(undercut)区域
"""
face_normals = []
explorer = TopExp_Explorer(shape, TopAbs_FACE)
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
while explorer.More():
face = TopoDS_Face(explorer.Current())
surface = BRepAdaptor_Surface(face)
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
try:
if surface.GetType() == 0:
normal = surface.Plane().Position().Direction()
else:
bbox = Bnd_Box()
brepbndlib_Add(face, bbox)
normal = gp_Dir(0, 0, 1)
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
face_normals.append(normal)
except Exception as e:
logger.debug(f"面法向量计算失败:{e}")
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
explorer.Next()
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
if not face_normals:
return gp_Dir(0, 0, 1)
avg_x = sum(n.X() for n in face_normals) / len(face_normals)
avg_y = sum(n.Y() for n in face_normals) / len(face_normals)
avg_z = sum(n.Z() for n in face_normals) / len(face_normals)
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
length = np.sqrt(avg_x**2 + avg_y**2 + avg_z**2)
if length > 0.001:
return gp_Dir(avg_x/length, avg_y/length, avg_z/length)
else:
return gp_Dir(0, 0, 1)
def _create_optimal_parting_plane(self, shape: Any, analysis: Dict,
direction: gp_Dir) -> gp_Pln:
2026-03-08 02:14:07 +08:00
"""
2026-04-19 23:41:35 +08:00
创建最优分型面
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
Args:
shape: 产品形状
analysis: 几何分析结果
direction: 分型方向(法向量)
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
Returns:
gp_Pln: 分型面方程
"""
bbox = analysis["bounding_box"]
center = bbox["center"]
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
parting_plane = gp_Pln(
gp_Pnt(center[0], center[1], center[2]),
direction
)
2026-02-11 22:40:35 +08:00
2026-04-19 23:41:35 +08:00
logger.info(f"创建分型面:原点=({center[0]:.2f}, {center[1]:.2f}, {center[2]:.2f}), "
f"法向量=({direction.X():.3f}, {direction.Y():.3f}, {direction.Z():.3f})")
return parting_plane
def _simple_parting_surface(self, shape: Any, analysis: Dict) -> Tuple[Any, List]:
"""简化的分型面检测(回退方案)"""
bbox = analysis["bounding_box"]
center_z = bbox["center"][2]
parting_plane = gp_Pln(
gp_Pnt(0, 0, center_z),
gp_Dir(0, 0, 1)
)
parting_surface = BRepBuilderAPI_MakeFace(
parting_plane,
bbox["min"][0] - 10, bbox["max"][0] + 10,
bbox["min"][1] - 10, bbox["max"][1] + 10
).Face()
parting_line = self._simple_parting_line(shape)
return parting_surface, parting_line
def _create_parting_surface_from_ai(self, ai_result: Dict,
analysis: Dict, shape: Any = None) -> Tuple[Any, List]:
"""
从 AI 模型结果创建分型面(预留接口)
Args:
ai_result: AI 模型输出,应包含:
- origin: [x, y, z] 平面原点
- normal: [nx, ny, nz] 法向量
analysis: 几何分析结果
shape: 产品形状(用于计算分型线)
Returns:
(parting_surface, parting_line)
"""
origin = ai_result.get("origin", [0, 0, 0])
normal = ai_result.get("normal", [0, 0, 1])
parting_plane = gp_Pln(
gp_Pnt(origin[0], origin[1], origin[2]),
gp_Dir(normal[0], normal[1], normal[2])
)
parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face()
if "parting_line" in ai_result:
parting_line = ai_result["parting_line"]
elif shape is not None:
parting_line = self._calculate_parting_line(shape, parting_surface)
else:
parting_line = []
logger.info(f"从 AI 结果创建分型面:原点={origin}, 法向量={normal}")
return parting_surface, parting_line
2026-02-11 22:40:35 +08:00
def _extract_parting_surface_geometry(self, surface: Any) -> Dict[str, Any]:
"""提取分型面几何数据"""
2026-04-23 00:49:22 +08:00
metadata = self._extract_plane_metadata(surface)
2026-02-11 22:40:35 +08:00
return {
"type": "plane",
2026-04-23 00:49:22 +08:00
"normal": metadata["normal"],
"origin": metadata["origin"],
"bounds": metadata["bounds"],
2026-02-11 22:40:35 +08:00
}
def _calculate_mold_size(self, analysis: Dict) -> Dict[str, float]:
"""估算模具尺寸"""
product_bbox = analysis["bounding_box"]["dimensions"]
2026-04-19 23:41:35 +08:00
margin = 30
2026-02-11 22:40:35 +08:00
return {
"length": product_bbox[0] + 2 * margin,
"width": product_bbox[1] + 2 * margin,
2026-04-19 23:41:35 +08:00
"height": product_bbox[2] + 2 * margin + 100,
2026-02-11 22:40:35 +08:00
"margin": margin
}
def _calculate_clamping_force(self, analysis: Dict) -> str:
"""估算锁模力"""
2026-04-19 23:41:35 +08:00
volume_cm3 = analysis.get("volume", 0) / 1000
2026-02-11 22:40:35 +08:00
if volume_cm3 < 10:
return "50-100 吨"
elif volume_cm3 < 100:
return "150-300 吨"
elif volume_cm3 < 500:
return "400-600 吨"
else:
return "800+ 吨"
def _get_recommended_material(self) -> str:
2026-02-15 00:42:56 +08:00
"""推荐模具材料"""
2026-02-11 22:40:35 +08:00
return "Aluminum Alloy 7075 (铝合金模具)"
def _estimate_wall_thickness(self, analysis: Dict) -> str:
"""估算壁厚范围"""
volume = analysis.get("volume", 0)
surface_area = analysis.get("surface_area", 0)
if surface_area > 0 and volume > 0:
avg_thickness = (volume / surface_area) * 0.6
return f"{avg_thickness * 0.7:.2f} - {avg_thickness * 1.3:.2f} mm"
elif volume > 0:
bbox_dims = analysis.get("bounding_box", {}).get("dimensions", [1, 1, 1])
bbox_volume = bbox_dims[0] * bbox_dims[1] * bbox_dims[2]
if bbox_volume > 0:
efficiency = volume / bbox_volume
avg_thickness = (bbox_dims[0] + bbox_dims[1]) / 2 * efficiency
return f"{avg_thickness * 0.7:.2f} - {avg_thickness * 1.3:.2f} mm"
return "2.0 - 4.0 mm (默认)"
def _calculate_complexity_score(self, analysis: Dict) -> float:
"""计算复杂度评分(0-10)"""
volume = analysis.get("volume", 0)
surface_area = analysis.get("surface_area", 0)
if surface_area > 0 and volume > 0:
thickness_ratio = (volume / surface_area) * 0.6
complexity = min(thickness_ratio / 5.0, 10.0)
return round(complexity, 1)
elif volume > 0:
bbox_dims = analysis.get("bounding_box", {}).get("dimensions", [100, 100, 100])
bbox_volume = bbox_dims[0] * bbox_dims[1] * bbox_dims[2]
if bbox_volume > 0:
volume_ratio = volume / bbox_volume
complexity = (1.0 - volume_ratio) * 10
return round(min(max(complexity, 0), 10), 1)
return 5.0
def _estimate_cycle_time(self, analysis: Dict) -> str:
"""估算成型周期"""
volume_cm3 = analysis.get("volume", 0) / 1000
if volume_cm3 < 10:
return "15-25 秒"
elif volume_cm3 < 50:
return "25-40 秒"
elif volume_cm3 < 200:
return "40-60 秒"
else:
return "60-90 秒"
def _identify_weld_line_risk(self, analysis: Dict) -> str:
"""识别熔接痕风险"""
complexity = self._calculate_complexity_score(analysis)
if complexity > 7:
return "高 - 建议优化浇口位置"
elif complexity > 4:
return "中 - 需仿真验证"
else:
return "低"
def _identify_sink_mark_risk(self, analysis: Dict) -> str:
"""识别缩痕风险"""
return "中 - 建议壁厚均匀性检查"