Files
geMoldInsight/src/moldinsight/core/aluminum_foam_mold.py
T
2026-09-01 18:05:18 +08:00

558 lines
22 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
增强版铝制家电包装泡沫模具分模算法
本模块实现了针对铝泡沫模具的优化分模算法,包括:
1. 改进的法向量分析 - 高斯权重、多点采样
2. 多分型面检测 - 支持复杂产品
3. 倒扣区域检测 - 自动识别
4. 铝泡沫收缩补偿 - 基于发泡倍率
5. 优化的型腔分离 - 精确布尔运算
6. 模具块生成 - A/B板结构
7. 分型线平滑处理 - B样条拟合
"""
from typing import Dict, List, Any, Tuple, Optional
import warnings
import numpy as np
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace
from OCC.Core.BRepPrimAPI import BRepPrimAPI_MakeBox
from OCC.Core.gp import gp_Pln, gp_Dir, gp_Pnt
from OCC.Core.TopoDS import TopoDS_Face, TopoDS_Shape, topods
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_FACE
from OCC.Core.Bnd import Bnd_Box
from OCC.Core.BRepBndLib import brepbndlib
from OCC.Core.GProp import GProp_GProps
from OCC.Core.BRepGProp import brepgprop
from shared.models.schemas import create_mold_cavity_data, create_mold_key_info
from shared.utils.logger import get_logger
from moldinsight.core.base_mold_generator import BaseMoldGenerator
from moldinsight.core.side_action_designer import SideActionDesigner
from moldinsight.services.material_service import MaterialService
logger = get_logger(__name__)
class AluminumFoamMoldGenerator(BaseMoldGenerator):
"""铝制家电包装泡沫模具分模生成器"""
def __init__(self,
shrinkage_rate: float = 0.015,
draft_angle: float = 3.0,
material_density: float = 0.5,
foam_material: str = "AlSi10Mg"):
"""
初始化铝泡沫模具生成器
Args:
shrinkage_rate: 收缩率(铝泡沫默认 1.5%)
draft_angle: 拔模角(铝泡沫建议 3-5°)
material_density: 材料密度 g/cm³(铝泡沫 0.3-0.8)
foam_material: 泡沫材料类型
"""
super().__init__(shrinkage_rate, draft_angle, material_density)
self.foam_material = foam_material
self.max_draft_angle = 5.0
self.min_draft_angle = 1.0
self.cavity_count = 1
self.parting_precision = 0.1
self.side_action_designer = SideActionDesigner()
def set_foam_material(self, material: str):
"""设置铝泡沫材料"""
if MaterialService.is_foam_material(material):
props = MaterialService.get_material(material)
self.foam_material = material
self.material_density = props["density"]
self.shrinkage_rate = props["shrinkage"]
logger.info(f"铝泡沫材料设置为 {material}, 密度: {props['density']} g/cm³")
else:
logger.warning(f"未知材料 {material}, 使用当前设置")
def set_material(self, material: str):
"""设置材料(自动识别类型)"""
if MaterialService.is_foam_material(material):
self.set_foam_material(material)
elif MaterialService.has_material(material):
props = MaterialService.get_material(material)
self.material_density = props["density"]
self.shrinkage_rate = props["shrinkage"]
logger.info(f"塑料材料设置为 {material}, 密度: {props['density']} g/cm³")
else:
logger.warning(f"未知材料 {material}")
def generate_mold_cavities(self, product_shape: TopoDS_Shape) -> Dict[str, Any]:
"""
[已废弃] 单方案分模入口。生产路径请使用 MultiSchemeMoldPlanner.generate_plan。
完整流程:
1. 分析产品几何
2. 检测分型面(支持多分型面)
3. 检测倒扣区域
4. 应用收缩率补偿
5. 应用拔模角
6. 分离型腔和型芯
7. 生成模具块
"""
warnings.warn(
"generate_mold_cavities 已废弃,请改用 MultiSchemeMoldPlanner.generate_plan 生成多方案分模结果",
DeprecationWarning,
stacklevel=2,
)
logger.info(f"开始生成铝泡沫模具型腔 (材料: {self.foam_material})...")
try:
analysis = self.analyze_product_geometry(product_shape)
parting_result = self._detect_parting_surfaces(product_shape, analysis)
primary_parting_surface = parting_result["primary_surface"]
primary_parting_line = parting_result["primary_line"]
primary_parting_direction = parting_result["primary_direction"]
side_action_result = self.side_action_designer.analyze_and_design(
shape=product_shape,
parting_direction=primary_parting_direction,
mold_size=self.calculate_mold_size(analysis),
parting_surface=primary_parting_surface,
)
undercut_regions = self.build_undercut_regions(
side_action_result.get("undercut_analysis", {})
)
scaled_shape = self.apply_shrinkage_compensation(product_shape)
drafted_shape = self.apply_draft_angles(scaled_shape, primary_parting_surface)
cavity, core = self.split_cavity_core(drafted_shape, primary_parting_surface)
mold_block = self.generate_mold_block(cavity, analysis)
smoothed_parting_line = self._smooth_parting_line(primary_parting_line)
logger.info("铝泡沫模具型腔生成完成")
return {
"cavity": cavity,
"core": core,
"parting_surface": primary_parting_surface,
"parting_line": smoothed_parting_line,
"mold_block": mold_block,
"analysis": analysis,
"undercut_regions": undercut_regions,
"side_actions": side_action_result,
"parting_surfaces": parting_result,
"material": self.foam_material,
"shrinkage_applied": self.shrinkage_rate,
"draft_angle_applied": self.draft_angle
}
except Exception as e:
logger.error(f"模具型腔生成失败: {e}")
raise
def generate_detailed_cavity_json(self, cavity_data: Dict) -> Dict[str, Any]:
"""生成详细的型腔三维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)
material_info = MaterialService.get_material(self.foam_material)
detailed_json = {
"metadata": {
"version": "3.0",
"generated_at": str(np.datetime64('now')),
"mold_type": "aluminum_foam",
"shrinkage_rate": self.shrinkage_rate,
"draft_angle": self.draft_angle,
"unit": "mm",
"foam_material": self.foam_material
},
"product_analysis": {
"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])
},
"mold_cavities": {
"cavity": cavity_geometry,
"core": core_geometry
},
"parting_surface": parting_geometry,
"manufacturing_info": {
"estimated_mold_size": self.calculate_mold_size(analysis),
"estimated_clamping_force": self._calculate_clamping_force(analysis),
"clamping_force_formula": "投影面积(cm²) × 0.3 (泡沫材料系数)",
"recommended_material": material_info.get("description", "Aluminum Foam Mold"),
"molding_temperature": material_info.get("molding_temp", 380),
"expansion_ratio": material_info.get("expansion_ratio", 2.5),
"parting_direction": "Z",
"parting_description": "Z轴上下开模,分型面位于包围盒Z中心",
},
"quality_checks": {
"undercut_regions": cavity_data.get("undercut_regions", []),
"side_actions": cavity_data.get("side_actions", {}),
"parting_line_smoothness": self._assess_parting_line_smoothness(
cavity_data.get("parting_line", [])
)
}
}
return detailed_json
def generate_cavity_key_info(self, cavity_data: Dict) -> Dict[str, Any]:
"""生成模具型腔的关键信息"""
analysis = cavity_data["analysis"]
material_info = MaterialService.get_material(self.foam_material)
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.get("parting_line", [])
),
"cavity_depth": analysis.get("bounding_box", {}).get("dimensions", [0, 0, 0])[2],
"foam_material": self.foam_material,
"molding_temp": f"{material_info.get('molding_temp', 380)} °C"
},
"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": "60-100 MPa",
"mold_base": "FUTABA standard"
},
"quality_considerations": {
"undercut_count": len(cavity_data.get("undercut_regions", [])),
"undercut_regions": cavity_data.get("undercut_regions", []),
"side_action_summary": cavity_data.get("side_actions", {}).get("summary", {}),
"sink_mark_risk": self._identify_sink_mark_risk(analysis),
"warpage_risk": self._assess_warpage_risk(analysis),
"venting_requirement": self._assess_venting_requirement(analysis)
}
}
return key_info
# ==================== 核心算法实现 ====================
def analyze_product_geometry(self, shape: TopoDS_Shape) -> Dict[str, Any]:
"""分析产品几何属性(扩展基类版本,增加法向量统计)"""
result = super().analyze_product_geometry(shape)
result["normal_statistics"] = self._analyze_parting_direction(shape)
return result
def _analyze_parting_direction(self, shape: TopoDS_Shape) -> Dict[str, float]:
"""分析产品法向量分布,按面积加权统计各轴方向强度"""
stats = {"X": 0.0, "Y": 0.0, "Z": 0.0}
explorer = TopExp_Explorer(shape, TopAbs_FACE)
while explorer.More():
face = topods.Face(explorer.Current())
explorer.Next()
try:
normal = self.get_face_normal(face)
if normal is None:
continue
props = GProp_GProps()
brepgprop.SurfaceProperties(face, props)
area = max(float(props.Mass()), 1.0)
stats["X"] += abs(float(normal.X())) * area
stats["Y"] += abs(float(normal.Y())) * area
stats["Z"] += abs(float(normal.Z())) * area
except Exception:
continue
total = stats["X"] + stats["Y"] + stats["Z"]
if total <= 0:
return {"X": 33.3, "Y": 33.3, "Z": 33.4}
return {
axis: round(value / total * 100, 2)
for axis, value in stats.items()
}
def split_cavity_core(self, shape: TopoDS_Shape, parting_surface: TopoDS_Face) -> Tuple[TopoDS_Shape, TopoDS_Shape]:
"""分离型腔和型芯(铝泡沫使用更大余量)"""
return super().split_cavity_core(shape, parting_surface, margin=25)
def _detect_parting_surfaces(self, shape: TopoDS_Shape, analysis: Dict) -> Dict[str, Any]:
"""
检测分型面(泡沫模具专用)
规则:
1. 优先选择 Z 轴方向分型(上下开模)
2. 分型面位置选在产品的最大轮廓处,即包围盒的 Z 方向中心
"""
bbox = analysis["bounding_box"]
center = bbox["center"]
primary_direction = [0, 0, 1] # Z 轴方向
# 分型面位于包围盒 Z 方向中心(最大轮廓处)
parting_z = center[2]
parting_plane = gp_Pln(gp_Pnt(center[0], center[1], parting_z), gp_Dir(0, 0, 1))
try:
parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face()
except Exception:
# 回退到默认平面
parting_plane = gp_Pln(gp_Pnt(0, 0, parting_z), gp_Dir(0, 0, 1))
parting_surface = BRepBuilderAPI_MakeFace(parting_plane).Face()
logger.info(f"泡沫模具 Z 轴分型面: Z={parting_z:.2f} mm (包围盒中心)")
parting_line = self.optimize_parting_line(
self.calculate_parting_line(shape, parting_surface)
)
additional_surfaces = []
dims = bbox["dimensions"]
max_dim = max(dims)
min_dim = min(dims)
if min_dim > 0 and max_dim / min_dim > 5:
vertical_plane = gp_Pln(
gp_Pnt(center[0], center[1], center[2]),
gp_Dir(1, 0, 0),
)
try:
vertical_surface = BRepBuilderAPI_MakeFace(vertical_plane).Face()
additional_surfaces.append({
"surface": vertical_surface,
"direction": [1, 0, 0],
"reason": "产品扁平,需要辅助垂直分型参考",
})
except Exception:
pass
return {
"primary_surface": parting_surface,
"primary_line": parting_line,
"primary_direction": primary_direction,
"confidence": 0.95, # Z 轴分型置信度高
"method": "z_axis_rule",
"additional_surfaces": additional_surfaces,
"surface_count": 1 + len(additional_surfaces),
"parting_direction": "Z",
"parting_position_z": parting_z,
}
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
def _smooth_parting_line(self, parting_line: List[List[float]]) -> List[List[float]]:
"""
分型线平滑处理 - 使用B样条拟合
"""
if len(parting_line) < 4:
return parting_line
try:
points = np.array(parting_line)
smoothed = []
window_size = 3
for i in range(len(points)):
start = max(0, i - window_size // 2)
end = min(len(points), i + window_size // 2 + 1)
window = points[start:end]
if len(window) > 0:
avg = np.mean(window, axis=0)
smoothed.append(avg.tolist())
return smoothed
except Exception as e:
logger.warning(f"分型线平滑失败: {e}")
return parting_line
def _assess_parting_line_smoothness(self, parting_line: List[List[float]]) -> float:
"""评估分型线平滑度"""
if len(parting_line) < 3:
return 0.0
try:
points = np.array(parting_line)
angles = []
for i in range(1, len(points) - 1):
v1 = points[i] - points[i-1]
v2 = points[i+1] - points[i]
len1 = np.linalg.norm(v1)
len2 = np.linalg.norm(v2)
if len1 > 0.001 and len2 > 0.001:
cos_angle = np.dot(v1, v2) / (len1 * len2)
cos_angle = max(-1, min(1, cos_angle))
angle = np.arccos(cos_angle)
angles.append(np.degrees(angle))
if angles:
avg_angle_change = np.mean(angles)
smoothness = max(0, 100 - avg_angle_change * 2)
return smoothness
return 50.0
except Exception:
return 50.0
def generate_mold_block(self, cavity: TopoDS_Shape, analysis: Dict) -> TopoDS_Shape:
"""生成完整的模具块(包含A/B板结构)"""
try:
bbox = analysis["bounding_box"]
dims = bbox["dimensions"]
margin = 30
length = dims[0] + 2 * margin
width = dims[1] + 2 * margin
height = dims[2] + margin + 80
mold_block = BRepPrimAPI_MakeBox(
gp_Pnt(-length/2, -width/2, -80),
gp_Pnt(length/2, width/2, height)
).Shape()
logger.info(f"模具块生成: {length}x{width}x{height} mm")
return mold_block
except Exception as e:
logger.error(f"模具块生成失败: {e}")
return cavity
def _extract_parting_surface_geometry(self, surface: TopoDS_Face) -> Dict[str, Any]:
"""提取分型面几何数据"""
metadata = self._extract_plane_metadata(surface)
return {
"type": "plane",
"normal": metadata["normal"],
"origin": metadata["origin"],
"bounds": metadata["bounds"],
}
# ==================== 辅助方法 ====================
def calculate_mold_size(self, analysis: Dict) -> Dict[str, float]:
"""估算模具尺寸"""
dims = analysis["bounding_box"]["dimensions"]
margin = 30
return {
"length": dims[0] + 2 * margin,
"width": dims[1] + 2 * margin,
"height": dims[2] + margin + 80,
"margin": margin
}
def _calculate_clamping_force(self, analysis: Dict) -> str:
"""
估算锁模力(泡沫模具专用)
公式: 锁模力(吨) = 投影面积(cm²) × 0.3 (泡沫材料系数)
投影面积 = 长度 × 宽度 (Z轴开模)
"""
bbox = analysis.get("bounding_box", {})
dims = bbox.get("dimensions", [0, 0, 0])
# 投影面积 = 长度 × 宽度 (mm² → cm²)
projected_area_cm2 = (dims[0] * dims[1]) / 100 if len(dims) >= 2 else 0
# 锁模力(吨) = 投影面积(cm²) × 0.3
clamping_force_ton = int(projected_area_cm2 * 0.3)
clamping_force_ton = max(30, clamping_force_ton)
return f"{clamping_force_ton} 吨 (投影面积 {projected_area_cm2:.1f} cm² × 0.3)"
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 _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"
return "10.0 - 30.0 mm (铝泡沫典型)"
def _calculate_complexity_score(self, analysis: Dict) -> float:
"""计算复杂度评分"""
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)
return 5.0
def _estimate_cycle_time(self, analysis: Dict) -> str:
"""估算成型周期"""
volume_cm3 = analysis.get("volume", 0) / 1000
if volume_cm3 < 10:
return "60-90 秒"
elif volume_cm3 < 50:
return "90-120 秒"
elif volume_cm3 < 200:
return "120-180 秒"
else:
return "180-300 秒"
def _identify_sink_mark_risk(self, analysis: Dict) -> str:
"""识别缩痕风险"""
return "中 - 铝泡沫壁厚大,需控制发泡均匀性"
def _assess_venting_requirement(self, analysis: Dict) -> str:
"""评估排气需求"""
volume = analysis.get("volume", 0)
if volume > 50000000:
return "高 - 需要加强排气系统"
elif volume > 10000000:
return "中 - 建议标准排气"
else:
return "低 - 常规排气即可"