x
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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])
|
||||
|
||||
Reference in New Issue
Block a user