后端模块拆分
This commit is contained in:
@@ -0,0 +1,533 @@
|
||||
"""
|
||||
侧壁/倒扣面滑块机构检测与设计模块
|
||||
|
||||
功能:
|
||||
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度)"""
|
||||
return 8.0
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user