This commit is contained in:
2026-05-02 00:39:04 +08:00
parent f9acdb767b
commit d1a3f22785
16 changed files with 1181 additions and 113 deletions
+33 -5
View File
@@ -398,14 +398,42 @@ class BaseMoldGenerator:
core = self._subtract_product_from_plate(b_plate, shape, "B板(回退)")
return cavity or a_plate, core or b_plate
logger.warning("回退方案也失败,使用最简方法")
cavity_op = BRepAlgoAPI_Cut(mold_block, shape)
cavity = cavity_op.Shape() if cavity_op.IsDone() else mold_block
return cavity, shape
logger.warning("回退方案也失败,使用最简A/B板切分(避免返回产品本体)")
center_z = (zmin + zmax) / 2
margin = 20
a_plate = BRepPrimAPI_MakeBox(
gp_Pnt(xmin - margin, ymin - margin, center_z),
gp_Pnt(xmax + margin, ymax + margin, zmax + margin)
).Shape()
b_plate = BRepPrimAPI_MakeBox(
gp_Pnt(xmin - margin, ymin - margin, zmin - margin),
gp_Pnt(xmax + margin, ymax + margin, center_z)
).Shape()
cavity = self._subtract_product_from_plate(a_plate, shape, "A板(最简)")
core = self._subtract_product_from_plate(b_plate, shape, "B板(最简)")
return cavity or a_plate, core or b_plate
except Exception as e:
logger.error(f"分模回退方案失败: {e}")
return shape, shape
# 最后兜底也返回模具半板,而不是产品本体,避免预览出现“三份产品”
try:
bbox = Bnd_Box()
brepbndlib.Add(shape, bbox)
xmin, ymin, zmin, xmax, ymax, zmax = bbox.Get()
center_z = (zmin + zmax) / 2
margin = 20
a_plate = BRepPrimAPI_MakeBox(
gp_Pnt(xmin - margin, ymin - margin, center_z),
gp_Pnt(xmax + margin, ymax + margin, zmax + margin)
).Shape()
b_plate = BRepPrimAPI_MakeBox(
gp_Pnt(xmin - margin, ymin - margin, zmin - margin),
gp_Pnt(xmax + margin, ymax + margin, center_z)
).Shape()
return a_plate, b_plate
except Exception:
return shape, shape
def detect_insert_regions(self, shape: Any, analysis: Dict,
depth_threshold: float = 30.0,
+44
View File
@@ -113,6 +113,7 @@ class MultiSchemeMoldPlanner:
undercut_regions = generator._build_undercut_regions(
side_action_result.get("undercut_analysis", {})
)
mold_structure = self._determine_mold_structure(analysis, undercut_regions)
scaled_shape = generator._apply_shrinkage_compensation(shape)
drafted_shape = generator._apply_draft_angles(scaled_shape, parting_surface)
@@ -150,6 +151,9 @@ class MultiSchemeMoldPlanner:
cavity_data["metadata"]["scheme_reason"] = candidate["reason"]
cavity_data["metadata"]["scheme_offset_ratio"] = candidate.get("offset_ratio", 0.0)
cavity_data["metadata"]["scheme_offset_label"] = candidate.get("offset_label", "中面")
cavity_data["metadata"]["mold_structure_type"] = mold_structure["mold_structure_type"]
cavity_data["metadata"]["core_required"] = mold_structure["core_required"]
cavity_data["metadata"]["structure_decision_reason"] = mold_structure["decision_reason"]
return {
"scheme_id": candidate["scheme_id"],
@@ -161,6 +165,9 @@ class MultiSchemeMoldPlanner:
"normal_alignment_score": candidate.get("normal_alignment_score"),
"offset_ratio": candidate.get("offset_ratio", 0.0),
"offset_label": candidate.get("offset_label", "中面"),
"mold_structure_type": mold_structure["mold_structure_type"],
"core_required": mold_structure["core_required"],
"decision_reason": mold_structure["decision_reason"],
"parting": {
"axis": candidate["axis"],
"direction": parting_direction,
@@ -273,3 +280,40 @@ class MultiSchemeMoldPlanner:
axis: round(value / total * 100, 2)
for axis, value in stats.items()
}
@staticmethod
def _determine_mold_structure(analysis: Dict[str, Any], undercut_regions: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
判定是否需要独立模芯。
规则为工程启发式:
- 实心度高 + 平均厚度占比高 + 无明显倒扣:倾向两板半腔(无独立凸芯)
- 否则:采用型腔+模芯结构
"""
dims = analysis.get("bounding_box", {}).get("dimensions", [0.0, 0.0, 0.0])
valid_dims = [float(d) for d in dims if float(d) > 1e-6]
min_dim = min(valid_dims) if valid_dims else 1.0
bbox_volume = 1.0
for dim in valid_dims[:3]:
bbox_volume *= dim
if bbox_volume <= 0:
bbox_volume = 1.0
volume = float(analysis.get("volume", 0.0))
surface_area = float(analysis.get("surface_area", 0.0))
solid_ratio = max(0.0, min(volume / bbox_volume, 1.0))
avg_wall = (2.0 * volume / surface_area) if surface_area > 1e-6 else min_dim
wall_ratio = max(0.0, min(avg_wall / max(min_dim, 1e-6), 1.0))
undercut_count = len(undercut_regions or [])
core_required = not (solid_ratio > 0.62 and wall_ratio > 0.38 and undercut_count == 0)
mold_structure_type = "cavity_core" if core_required else "two_half_cavity"
decision_reason = (
f"solid_ratio={solid_ratio:.2f}, wall_ratio={wall_ratio:.2f}, "
f"undercut_count={undercut_count}"
)
return {
"core_required": core_required,
"mold_structure_type": mold_structure_type,
"decision_reason": decision_reason,
}
+119 -2
View File
@@ -1,4 +1,4 @@
from typing import Dict, Any, List
from typing import Dict, Any, List, Optional, Tuple
import re
@@ -19,8 +19,19 @@ class PartingSchemeScorer:
)
scored_scheme = dict(scheme)
fallback = self._assess_fallback(scored_scheme, score_breakdown)
scored_scheme["score_breakdown"] = score_breakdown
scored_scheme["score"] = total_score
scored_scheme["is_fallback"] = fallback["is_fallback"]
scored_scheme["fallback_reason"] = fallback["fallback_reason"]
scored_scheme["dfm_violations"] = self._build_dfm_violations(scored_scheme)
scored_scheme["dfm_violation_count"] = len(scored_scheme["dfm_violations"])
scored_scheme["confidence_score"] = self._build_confidence_score(
total_score,
score_breakdown,
fallback["is_fallback"],
scored_scheme["dfm_violation_count"],
)
scored_scheme["summary"] = self._build_summary(scored_scheme)
scored.append(scored_scheme)
@@ -95,6 +106,98 @@ class PartingSchemeScorer:
"risk": round(risk, 2),
}
def _assess_fallback(self, scheme: Dict[str, Any], score_breakdown: Dict[str, float]) -> Dict[str, Any]:
cavity_data = scheme.get("cavity_data", {})
mold_cavities = cavity_data.get("mold_cavities", {})
cavity_mesh = mold_cavities.get("cavity", {})
core_mesh = mold_cavities.get("core", {})
core_required = bool(scheme.get("core_required", True))
cavity_v = int(cavity_mesh.get("vertex_count", 0) or 0)
core_v = int(core_mesh.get("vertex_count", 0) or 0)
reasons = []
if cavity_v <= 0:
reasons.append("型腔网格为空")
if core_required and core_v <= 0:
reasons.append("型芯网格为空")
if float(score_breakdown.get("manufacturability", 0.0)) < 70.0:
reasons.append("可制造性评分偏低")
return {
"is_fallback": len(reasons) > 0,
"fallback_reason": ";".join(reasons) if reasons else "",
}
@staticmethod
def _build_confidence_score(
total_score: float,
score_breakdown: Dict[str, float],
is_fallback: bool,
dfm_violation_count: int = 0,
) -> float:
confidence = float(total_score)
confidence += (float(score_breakdown.get("manufacturability", 0.0)) - 70.0) * 0.25
confidence += (float(score_breakdown.get("parting_quality", 0.0)) - 70.0) * 0.15
if is_fallback:
confidence -= 18.0
confidence -= min(max(dfm_violation_count, 0) * 3.0, 15.0)
return round(max(20.0, min(99.0, confidence)), 2)
def _build_dfm_violations(self, scheme: Dict[str, Any]) -> List[Dict[str, str]]:
cavity_data = scheme.get("cavity_data", {})
manufacturing_info = cavity_data.get("manufacturing_info", {})
key_info = scheme.get("key_info", {})
geometric = key_info.get("geometric_characteristics", {})
quality = key_info.get("quality_considerations", {})
metadata = cavity_data.get("metadata", {})
violations: List[Dict[str, str]] = []
wall_min, wall_max = self._parse_wall_range(
geometric.get("wall_thickness_range", "")
)
if wall_min is not None and wall_min < 1.2:
violations.append({
"rule": "最小壁厚",
"level": "high",
"message": f"最小壁厚 {wall_min:.2f}mm 偏薄,可能导致短射/强度不足",
})
if wall_max is not None and wall_max > 6.0:
violations.append({
"rule": "最大壁厚",
"level": "medium",
"message": f"最大壁厚 {wall_max:.2f}mm 偏厚,存在缩痕与冷却不均风险",
})
draft_angle = self._parse_first_number(metadata.get("draft_angle"))
if draft_angle and draft_angle < 1.0:
violations.append({
"rule": "拔模角",
"level": "medium",
"message": f"拔模角 {draft_angle:.2f}° 偏小,脱模阻力较大",
})
warpage = str(quality.get("warpage_risk", "")).lower()
if "high" in warpage or "高" in warpage:
violations.append({
"rule": "翘曲风险",
"level": "high",
"message": "当前方案翘曲风险高,建议优化壁厚与浇口位置",
})
clamping_force = self._parse_first_number(
manufacturing_info.get("estimated_clamping_force")
)
if clamping_force > 1200:
violations.append({
"rule": "锁模力",
"level": "medium",
"message": f"预估锁模力 {clamping_force:.0f} 吨,设备适配窗口较窄",
})
return violations
def _build_summary(self, scheme: Dict[str, Any]) -> str:
cavity_data = scheme.get("cavity_data", {})
quality_checks = cavity_data.get("quality_checks", {})
@@ -106,8 +209,10 @@ class PartingSchemeScorer:
axis = scheme.get("parting", {}).get("axis", "Z")
offset_label = scheme.get("offset_label", "中面")
clamping_force = manufacturing_info.get("estimated_clamping_force", "自动计算")
structure_type = scheme.get("mold_structure_type", "cavity_core")
structure_text = "型腔+模芯" if structure_type == "cavity_core" else "两板半腔(无独立模芯)"
return (
f"{axis} 轴开模,分型面位置 {offset_label},倒扣 {undercut_count} 处,"
f"{axis} 轴开模,结构 {structure_text},分型面位置 {offset_label},倒扣 {undercut_count} 处,"
f"滑块 {slider_count} 组,斜顶 {lifter_count} 组,"
f"预估锁模力 {clamping_force}"
)
@@ -127,3 +232,15 @@ class PartingSchemeScorer:
return 0.0
matches = re.findall(r"\d+(?:\.\d+)?", str(value))
return float(matches[0]) if matches else 0.0
@staticmethod
def _parse_wall_range(value: Any) -> Tuple[Optional[float], Optional[float]]:
if value is None:
return None, None
nums = re.findall(r"\d+(?:\.\d+)?", str(value))
if not nums:
return None, None
if len(nums) == 1:
v = float(nums[0])
return v, v
return float(nums[0]), float(nums[1])