550 lines
20 KiB
Python
550 lines
20 KiB
Python
"""
|
|
侧壁/倒扣面滑块机构检测与设计模块
|
|
|
|
功能:
|
|
1. 倒扣区域检测 - 识别无法直接脱模的侧壁凹槽
|
|
2. 滑块机构设计 - 侧向分型抽芯机构
|
|
3. 斜顶机构设计 - 内侧倒扣的斜顶脱模机构
|
|
4. 机构运动学分析 - 抽芯行程、脱模角度计算
|
|
|
|
倒扣检测原理:
|
|
- 分型方向确定后,检查每个面的法向量
|
|
- 如果面的法向量与脱模方向的点积为负(面朝向脱模反方向)
|
|
且该面不在分型面上,则判定为倒扣面
|
|
- 根据倒扣面的位置(外侧/内侧)选择滑块或斜顶
|
|
|
|
滑块 vs 斜顶:
|
|
- 滑块:外侧倒扣,沿导滑槽侧向运动
|
|
- 斜顶:内侧倒扣,沿斜导柱内侧运动
|
|
"""
|
|
|
|
from typing import Dict, List, Any, Optional, Tuple
|
|
import math
|
|
import numpy as np
|
|
from OCC.Core.TopoDS import TopoDS_Shape, TopoDS_Face
|
|
from shared.utils.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
class UndercutDetector:
|
|
"""倒扣区域检测器"""
|
|
|
|
def detect_undercuts(self, shape: TopoDS_Shape, parting_direction: List[float],
|
|
parting_surface: Optional[TopoDS_Face] = None) -> Dict[str, Any]:
|
|
"""
|
|
检测产品中的倒扣区域
|
|
|
|
Args:
|
|
shape: OCC 产品形状
|
|
parting_direction: 分型方向 [nx, ny, nz]
|
|
parting_surface: 分型面(可选)
|
|
|
|
Returns:
|
|
{
|
|
"undercut_faces": List[Dict],
|
|
"slider_regions": List[Dict],
|
|
"lifter_regions": List[Dict],
|
|
"total_undercut_area": float,
|
|
"requires_slider": bool,
|
|
"requires_lifter": bool,
|
|
"complexity": str
|
|
}
|
|
"""
|
|
try:
|
|
from OCC.Core.TopExp import TopExp_Explorer
|
|
from OCC.Core.TopAbs import TopAbs_FACE
|
|
from OCC.Core.TopoDS import TopoDS_Face, topods
|
|
from OCC.Core.BRepAdaptor import BRepAdaptor_Surface
|
|
from OCC.Core.GProp import GProp_GProps
|
|
from OCC.Core.BRepGProp import brepgprop
|
|
from OCC.Core.Bnd import Bnd_Box
|
|
from OCC.Core.BRepBndLib import brepbndlib
|
|
from OCC.Core.gp import gp_Dir
|
|
|
|
dir_vec = np.array(parting_direction, dtype=np.float64)
|
|
dir_norm = np.linalg.norm(dir_vec)
|
|
if dir_norm < 1e-6:
|
|
dir_vec = np.array([0, 0, 1])
|
|
else:
|
|
dir_vec /= dir_norm
|
|
|
|
parting_dir = gp_Dir(dir_vec[0], dir_vec[1], dir_vec[2])
|
|
|
|
undercut_faces = []
|
|
slider_regions = []
|
|
lifter_regions = []
|
|
total_undercut_area = 0.0
|
|
|
|
parting_z = 0.0
|
|
if parting_surface is not None:
|
|
try:
|
|
surface = BRepAdaptor_Surface(parting_surface)
|
|
if surface.GetType() == 0:
|
|
parting_z = surface.Plane().Location().Z()
|
|
except Exception:
|
|
pass
|
|
|
|
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)
|
|
u = (surface.FirstUParameter() + surface.LastUParameter()) / 2
|
|
v = (surface.FirstVParameter() + surface.LastVParameter()) / 2
|
|
|
|
face_normal = None
|
|
if surface.GetType() == 0:
|
|
face_normal = surface.Plane().Position().Direction()
|
|
else:
|
|
from OCC.Core.BRepLProp import BRepLProp_SLProps
|
|
props = BRepLProp_SLProps(surface, 1, 0.001)
|
|
props.SetParameters(u, v)
|
|
if props.IsNormalDefined():
|
|
face_normal = props.Normal()
|
|
|
|
if face_normal is None:
|
|
explorer.Next()
|
|
continue
|
|
|
|
dot = face_normal.Dot(parting_dir)
|
|
|
|
face_props = GProp_GProps()
|
|
brepgprop.SurfaceProperties(face, face_props)
|
|
area = face_props.Mass()
|
|
center = face_props.CentreOfMass()
|
|
|
|
bbox = Bnd_Box()
|
|
brepbndlib.Add(face, bbox)
|
|
try:
|
|
fxmin, fymin, fzmin, fxmax, fymax, fzmax = bbox.Get()
|
|
except Exception:
|
|
fxmin, fymin, fzmin, fxmax, fymax, fzmax = 0, 0, 0, 0, 0, 0
|
|
|
|
if dot < -0.1:
|
|
face_center_z = center.Z()
|
|
is_outer = face_center_z >= parting_z
|
|
|
|
undercut_info = {
|
|
"face_index": face_idx,
|
|
"normal": [face_normal.X(), face_normal.Y(), face_normal.Z()],
|
|
"dot_product": float(dot),
|
|
"area": float(area),
|
|
"center": [float(center.X()), float(center.Y()), float(center.Z())],
|
|
"bbox": {
|
|
"min": [float(fxmin), float(fymin), float(fzmin)],
|
|
"max": [float(fxmax), float(fymax), float(fzmax)]
|
|
},
|
|
"severity": "high" if dot < -0.5 else "medium",
|
|
"is_outer": is_outer,
|
|
}
|
|
undercut_faces.append(undercut_info)
|
|
total_undercut_area += area
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
explorer.Next()
|
|
|
|
for uf in undercut_faces:
|
|
normal = np.array(uf["normal"])
|
|
lateral_component = normal - np.dot(normal, dir_vec) * dir_vec
|
|
lateral_norm = np.linalg.norm(lateral_component)
|
|
|
|
if lateral_norm > 0.01:
|
|
slide_direction = lateral_component / lateral_norm
|
|
else:
|
|
slide_direction = np.array([1, 0, 0])
|
|
|
|
mechanism = {
|
|
"face_indices": [uf["face_index"]],
|
|
"slide_direction": slide_direction.tolist(),
|
|
"area": uf["area"],
|
|
"center": uf["center"],
|
|
"severity": uf["severity"],
|
|
}
|
|
|
|
if uf["is_outer"]:
|
|
slider_regions.append(mechanism)
|
|
else:
|
|
lifter_regions.append(mechanism)
|
|
|
|
requires_slider = len(slider_regions) > 0
|
|
requires_lifter = len(lifter_regions) > 0
|
|
|
|
total_count = len(slider_regions) + len(lifter_regions)
|
|
if total_count == 0:
|
|
complexity = "simple"
|
|
elif total_count <= 2:
|
|
complexity = "moderate"
|
|
elif total_count <= 4:
|
|
complexity = "complex"
|
|
else:
|
|
complexity = "very_complex"
|
|
|
|
result = {
|
|
"undercut_faces": undercut_faces,
|
|
"slider_regions": slider_regions,
|
|
"lifter_regions": lifter_regions,
|
|
"total_undercut_area": total_undercut_area,
|
|
"requires_slider": requires_slider,
|
|
"requires_lifter": requires_lifter,
|
|
"complexity": complexity,
|
|
"parting_direction": parting_direction,
|
|
}
|
|
|
|
logger.info(f"倒扣检测完成: {len(undercut_faces)} 个倒扣面, "
|
|
f"{len(slider_regions)} 个滑块, {len(lifter_regions)} 个斜顶, "
|
|
f"复杂度={complexity}")
|
|
|
|
return result
|
|
|
|
except Exception as e:
|
|
logger.error(f"倒扣检测失败: {e}")
|
|
return {
|
|
"undercut_faces": [],
|
|
"slider_regions": [],
|
|
"lifter_regions": [],
|
|
"total_undercut_area": 0,
|
|
"requires_slider": False,
|
|
"requires_lifter": False,
|
|
"complexity": "unknown",
|
|
"parting_direction": parting_direction,
|
|
}
|
|
|
|
|
|
class SliderMechanismDesigner:
|
|
"""滑块机构设计器"""
|
|
|
|
def design_slider(self, slider_region: Dict, mold_size: Dict,
|
|
parting_direction: List[float]) -> Dict[str, Any]:
|
|
"""
|
|
设计滑块机构
|
|
|
|
Args:
|
|
slider_region: 倒扣区域信息
|
|
mold_size: 模具尺寸
|
|
parting_direction: 分型方向
|
|
|
|
Returns:
|
|
滑块机构设计方案
|
|
"""
|
|
center = slider_region["center"]
|
|
area = slider_region["area"]
|
|
slide_dir = slider_region["slide_direction"]
|
|
|
|
slide_stroke = self._calculate_slide_stroke(slider_region, mold_size)
|
|
|
|
slide_angle = self._calculate_slide_angle(slide_dir, parting_direction)
|
|
|
|
slide_block_size = self._calculate_slide_block_size(area, slide_stroke)
|
|
|
|
guide_type = self._select_guide_type(slide_stroke, slide_angle)
|
|
|
|
return {
|
|
"type": "slider",
|
|
"location": center,
|
|
"slide_direction": slide_dir,
|
|
"slide_stroke": slide_stroke,
|
|
"slide_angle": slide_angle,
|
|
"block_size": slide_block_size,
|
|
"guide_type": guide_type,
|
|
"locking_mechanism": self._select_locking(slide_angle),
|
|
"actuation": "pneumatic" if slide_stroke > 50 else "mechanical",
|
|
"components": self._generate_components(slide_block_size, guide_type),
|
|
"manufacturing_notes": self._generate_slider_notes(slide_angle, slide_stroke),
|
|
}
|
|
|
|
def _calculate_slide_stroke(self, region: Dict, mold_size: Dict) -> float:
|
|
"""计算抽芯行程"""
|
|
bbox = region.get("bbox", {})
|
|
if "max" in bbox and "min" in bbox:
|
|
max_dim = max(
|
|
abs(bbox["max"][0] - bbox["min"][0]),
|
|
abs(bbox["max"][1] - bbox["min"][1]),
|
|
abs(bbox["max"][2] - bbox["min"][2])
|
|
)
|
|
else:
|
|
max_dim = 10.0
|
|
|
|
stroke = max_dim + 5.0
|
|
return round(max(stroke, 10.0), 1)
|
|
|
|
def _calculate_slide_angle(self, slide_dir: List[float],
|
|
parting_dir: List[float]) -> float:
|
|
"""计算滑块倾斜角度"""
|
|
s = np.array(slide_dir)
|
|
p = np.array(parting_dir)
|
|
|
|
s_norm = np.linalg.norm(s)
|
|
p_norm = np.linalg.norm(p)
|
|
|
|
if s_norm < 1e-6 or p_norm < 1e-6:
|
|
return 90.0
|
|
|
|
cos_angle = np.clip(np.dot(s, p) / (s_norm * p_norm), -1, 1)
|
|
angle = math.degrees(math.acos(abs(cos_angle)))
|
|
return round(angle, 1)
|
|
|
|
def _calculate_slide_block_size(self, area: float, stroke: float) -> Dict[str, float]:
|
|
"""计算滑块尺寸"""
|
|
width = max(math.sqrt(area) * 1.5, 15.0)
|
|
height = max(math.sqrt(area) * 1.2, 12.0)
|
|
length = stroke + width * 0.5
|
|
|
|
return {
|
|
"width": round(width, 1),
|
|
"height": round(height, 1),
|
|
"length": round(length, 1),
|
|
}
|
|
|
|
def _select_guide_type(self, stroke: float, angle: float) -> str:
|
|
"""选择导滑方式"""
|
|
if stroke > 80:
|
|
return "T_slot_guide"
|
|
elif angle > 20:
|
|
return "angled_guide_pin"
|
|
else:
|
|
return "dovetail_guide"
|
|
|
|
def _select_locking(self, angle: float) -> str:
|
|
"""选择锁紧方式"""
|
|
if angle > 25:
|
|
return "wedge_block"
|
|
else:
|
|
return "lock_block"
|
|
|
|
def _generate_components(self, block_size: Dict, guide_type: str) -> List[Dict]:
|
|
"""生成滑块组件清单"""
|
|
components = [
|
|
{"name": "slide_block", "material": "P20", "hardness": "HRC 28-32"},
|
|
{"name": "guide_strip", "material": "bronze", "hardness": "HB 80-100"},
|
|
{"name": "wear_plate", "material": "T8", "hardness": "HRC 45-50"},
|
|
{"name": "return_spring", "material": "spring_steel", "spec": "standard"},
|
|
]
|
|
|
|
if guide_type == "T_slot_guide":
|
|
components.append({"name": "T_slot_insert", "material": "P20", "hardness": "HRC 28-32"})
|
|
elif guide_type == "angled_guide_pin":
|
|
components.append({"name": "guide_pin", "material": "SUJ2", "hardness": "HRC 58-62"})
|
|
elif guide_type == "dovetail_guide":
|
|
components.append({"name": "dovetail_block", "material": "P20", "hardness": "HRC 28-32"})
|
|
|
|
return components
|
|
|
|
def _generate_slider_notes(self, angle: float, stroke: float) -> List[str]:
|
|
"""生成滑块加工注意事项"""
|
|
notes = []
|
|
if angle > 25:
|
|
notes.append("滑块角度较大,需确保锁紧可靠")
|
|
if stroke > 50:
|
|
notes.append("抽芯行程较长,建议使用气动抽芯")
|
|
if stroke > 80:
|
|
notes.append("大行程抽芯,需校核导滑槽强度")
|
|
notes.append("滑块需设置限位装置,防止脱出")
|
|
notes.append("配合面需做耐磨处理")
|
|
return notes
|
|
|
|
|
|
class LifterMechanismDesigner:
|
|
"""斜顶机构设计器"""
|
|
|
|
def design_lifter(self, lifter_region: Dict, mold_size: Dict,
|
|
parting_direction: List[float]) -> Dict[str, Any]:
|
|
"""
|
|
设计斜顶机构
|
|
|
|
Args:
|
|
lifter_region: 内侧倒扣区域信息
|
|
mold_size: 模具尺寸
|
|
parting_direction: 分型方向
|
|
|
|
Returns:
|
|
斜顶机构设计方案
|
|
"""
|
|
center = lifter_region["center"]
|
|
area = lifter_region["area"]
|
|
|
|
lifter_angle = self._calculate_lifter_angle(lifter_region)
|
|
|
|
lifter_stroke = self._calculate_lifter_stroke(lifter_region)
|
|
|
|
lifter_size = self._calculate_lifter_size(area, lifter_stroke, lifter_angle)
|
|
|
|
return {
|
|
"type": "lifter",
|
|
"location": center,
|
|
"lifter_angle": lifter_angle,
|
|
"lifter_stroke": lifter_stroke,
|
|
"block_size": lifter_size,
|
|
"guide_type": "angled_hole",
|
|
"return_mechanism": "spring_return",
|
|
"components": self._generate_lifter_components(lifter_size),
|
|
"manufacturing_notes": self._generate_lifter_notes(lifter_angle),
|
|
}
|
|
|
|
def _calculate_lifter_angle(self, region: Dict) -> float:
|
|
"""基于倒扣深度和估算顶出行程计算斜顶角度(通常 5-15 度)。
|
|
|
|
angle = atan(undercut_depth / estimated_stroke)
|
|
"""
|
|
bbox = region.get("bbox", {})
|
|
if "max" in bbox and "min" in bbox:
|
|
lateral = max(
|
|
abs(bbox["max"][0] - bbox["min"][0]),
|
|
abs(bbox["max"][1] - bbox["min"][1]),
|
|
abs(bbox["max"][2] - bbox["min"][2]),
|
|
)
|
|
else:
|
|
lateral = 5.0
|
|
|
|
undercut_depth = lateral * 0.4
|
|
estimated_stroke = max(undercut_depth * 3, 20.0)
|
|
angle = math.degrees(math.atan(undercut_depth / estimated_stroke))
|
|
return round(max(min(angle, 15.0), 5.0), 1)
|
|
|
|
def _calculate_lifter_stroke(self, region: Dict) -> float:
|
|
"""计算斜顶行程"""
|
|
bbox = region.get("bbox", {})
|
|
if "max" in bbox and "min" in bbox:
|
|
max_dim = max(
|
|
abs(bbox["max"][i] - bbox["min"][i]) for i in range(3)
|
|
)
|
|
else:
|
|
max_dim = 5.0
|
|
|
|
return round(max(max_dim + 3.0, 8.0), 1)
|
|
|
|
def _calculate_lifter_size(self, area: float, stroke: float,
|
|
angle: float) -> Dict[str, float]:
|
|
"""计算斜顶尺寸"""
|
|
width = max(math.sqrt(area) * 1.2, 10.0)
|
|
height = stroke / math.sin(math.radians(angle)) if angle > 0 else stroke * 3
|
|
thickness = max(width * 0.6, 8.0)
|
|
|
|
return {
|
|
"width": round(width, 1),
|
|
"height": round(height, 1),
|
|
"thickness": round(thickness, 1),
|
|
}
|
|
|
|
def _generate_lifter_components(self, size: Dict) -> List[Dict]:
|
|
"""生成斜顶组件清单"""
|
|
return [
|
|
{"name": "lifter_body", "material": "P20", "hardness": "HRC 28-32"},
|
|
{"name": "guide_pin", "material": "SUJ2", "hardness": "HRC 58-62"},
|
|
{"name": "return_spring", "material": "spring_steel", "spec": "standard"},
|
|
{"name": "wear_bushing", "material": "bronze", "hardness": "HB 80-100"},
|
|
]
|
|
|
|
def _generate_lifter_notes(self, angle: float) -> List[str]:
|
|
"""生成斜顶加工注意事项"""
|
|
notes = []
|
|
if angle > 12:
|
|
notes.append("斜顶角度偏大,需校核脱模力")
|
|
notes.append("斜顶导滑孔需精确加工")
|
|
notes.append("斜顶头部需做耐磨处理")
|
|
notes.append("需设置限位防止斜顶脱出")
|
|
return notes
|
|
|
|
|
|
class SideActionDesigner:
|
|
"""侧向分型机构综合设计器"""
|
|
|
|
def __init__(self):
|
|
self.undercut_detector = UndercutDetector()
|
|
self.slider_designer = SliderMechanismDesigner()
|
|
self.lifter_designer = LifterMechanismDesigner()
|
|
|
|
def analyze_and_design(self, shape: TopoDS_Shape, parting_direction: List[float],
|
|
mold_size: Dict, parting_surface: Optional[TopoDS_Face] = None) -> Dict[str, Any]:
|
|
"""
|
|
综合分析倒扣并设计侧向分型机构
|
|
|
|
Returns:
|
|
{
|
|
"undercut_analysis": Dict,
|
|
"slider_mechanisms": List[Dict],
|
|
"lifter_mechanisms": List[Dict],
|
|
"summary": Dict,
|
|
"recommendations": List[str]
|
|
}
|
|
"""
|
|
logger.info("开始侧向分型机构分析...")
|
|
|
|
undercut_result = self.undercut_detector.detect_undercuts(
|
|
shape, parting_direction, parting_surface
|
|
)
|
|
|
|
slider_mechanisms = []
|
|
for region in undercut_result["slider_regions"]:
|
|
slider = self.slider_designer.design_slider(
|
|
region, mold_size, parting_direction
|
|
)
|
|
slider_mechanisms.append(slider)
|
|
|
|
lifter_mechanisms = []
|
|
for region in undercut_result["lifter_regions"]:
|
|
lifter = self.lifter_designer.design_lifter(
|
|
region, mold_size, parting_direction
|
|
)
|
|
lifter_mechanisms.append(lifter)
|
|
|
|
total_mechanisms = len(slider_mechanisms) + len(lifter_mechanisms)
|
|
|
|
summary = {
|
|
"total_undercut_faces": len(undercut_result["undercut_faces"]),
|
|
"total_slider_count": len(slider_mechanisms),
|
|
"total_lifter_count": len(lifter_mechanisms),
|
|
"total_mechanism_count": total_mechanisms,
|
|
"complexity": undercut_result["complexity"],
|
|
}
|
|
|
|
recommendations = self._generate_overall_recommendations(summary, undercut_result)
|
|
|
|
result = {
|
|
"undercut_analysis": undercut_result,
|
|
"slider_mechanisms": slider_mechanisms,
|
|
"lifter_mechanisms": lifter_mechanisms,
|
|
"summary": summary,
|
|
"recommendations": recommendations,
|
|
}
|
|
|
|
logger.info(f"侧向分型机构设计完成: {len(slider_mechanisms)} 个滑块, "
|
|
f"{len(lifter_mechanisms)} 个斜顶")
|
|
|
|
return result
|
|
|
|
def _generate_overall_recommendations(self, summary: Dict,
|
|
undercut: Dict) -> List[str]:
|
|
"""生成总体建议"""
|
|
recs = []
|
|
|
|
if summary["total_mechanism_count"] == 0:
|
|
recs.append("无倒扣区域,模具结构简单,无需侧向分型机构")
|
|
return recs
|
|
|
|
if summary["total_slider_count"] > 0:
|
|
recs.append(f"需要 {summary['total_slider_count']} 个滑块机构处理外侧倒扣")
|
|
|
|
if summary["total_lifter_count"] > 0:
|
|
recs.append(f"需要 {summary['total_lifter_count']} 个斜顶机构处理内侧倒扣")
|
|
|
|
if summary["total_slider_count"] > 0:
|
|
recs.append("如存在大行程滑块,建议优先评估气动抽芯回路并预留稳定供气")
|
|
|
|
if summary["complexity"] == "very_complex":
|
|
recs.append("侧向分型机构复杂,建议评估是否可通过产品修改简化")
|
|
recs.append("考虑使用二次分型或旋转脱模替代方案")
|
|
|
|
if summary["total_mechanism_count"] > 3:
|
|
recs.append("侧向机构较多,建议优化模具结构减少机构数量")
|
|
|
|
recs.append("所有侧向机构需做运动仿真验证干涉")
|
|
|
|
return recs
|