分模候选方案
This commit is contained in:
@@ -0,0 +1,275 @@
|
||||
from typing import Dict, Any, List, Optional
|
||||
|
||||
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace
|
||||
from OCC.Core.BRepGProp import brepgprop
|
||||
from OCC.Core.GProp import GProp_GProps
|
||||
from OCC.Core.gp import gp_Dir, gp_Pln, gp_Pnt
|
||||
from OCC.Core.TopAbs import TopAbs_FACE
|
||||
from OCC.Core.TopExp import TopExp_Explorer
|
||||
from OCC.Core.TopoDS import TopoDS_Face
|
||||
|
||||
from core.mold_generator import MoldCavityGenerator
|
||||
from core.aluminum_foam_mold import AluminumFoamMoldGenerator
|
||||
from core.parting_candidate_generator import PartingCandidateGenerator
|
||||
from core.parting_scheme_scorer import PartingSchemeScorer
|
||||
from utils.logger import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class MultiSchemeMoldPlanner:
|
||||
"""针对单个产品生成最多三套候选分模方案并排序。"""
|
||||
|
||||
def __init__(self):
|
||||
self.candidate_generator = PartingCandidateGenerator()
|
||||
self.scheme_scorer = PartingSchemeScorer()
|
||||
self.mold_generator = MoldCavityGenerator(shrinkage_rate=0.005)
|
||||
self.aluminum_foam_generator = AluminumFoamMoldGenerator(
|
||||
shrinkage_rate=0.015,
|
||||
draft_angle=3.0,
|
||||
)
|
||||
|
||||
def generate_plan(
|
||||
self,
|
||||
shape: Any,
|
||||
material: Dict[str, Any],
|
||||
is_foam_material: bool = False,
|
||||
max_schemes: int = 3,
|
||||
) -> Dict[str, Any]:
|
||||
generator = self.aluminum_foam_generator if is_foam_material else self.mold_generator
|
||||
generator.set_material(material["name"])
|
||||
|
||||
analysis = generator._analyze_product_geometry(shape)
|
||||
analysis["axis_normal_stats"] = self._collect_axis_normal_stats(generator, shape)
|
||||
candidates = self.candidate_generator.generate_candidates(
|
||||
analysis=analysis,
|
||||
is_foam_material=is_foam_material,
|
||||
max_candidates=max_schemes,
|
||||
)
|
||||
|
||||
schemes = []
|
||||
for candidate in candidates:
|
||||
for offset_variant in self._build_offset_variants(candidate, is_foam_material):
|
||||
try:
|
||||
scheme = self._build_scheme(
|
||||
generator=generator,
|
||||
shape=shape,
|
||||
analysis=analysis,
|
||||
candidate=offset_variant,
|
||||
is_foam_material=is_foam_material,
|
||||
)
|
||||
if scheme is not None:
|
||||
schemes.append(scheme)
|
||||
except Exception as exc:
|
||||
logger.warning(f"候选方案 {offset_variant.get('scheme_id')} 生成失败: {exc}")
|
||||
|
||||
if not schemes:
|
||||
raise ValueError("未能生成任何可用分模方案")
|
||||
|
||||
scored_schemes = self.scheme_scorer.score_schemes(schemes)[:max_schemes]
|
||||
for idx, scheme in enumerate(scored_schemes, start=1):
|
||||
scheme["raw_scheme_id"] = scheme.get("scheme_id")
|
||||
scheme["scheme_id"] = f"scheme_{idx}"
|
||||
if scheme.get("cavity_data", {}).get("metadata") is not None:
|
||||
scheme["cavity_data"]["metadata"]["scheme_id"] = scheme["scheme_id"]
|
||||
best_scheme = scored_schemes[0]
|
||||
|
||||
return {
|
||||
"best_scheme_id": best_scheme["scheme_id"],
|
||||
"candidate_schemes": scored_schemes,
|
||||
"global_summary": {
|
||||
"scheme_count": len(scored_schemes),
|
||||
"recommended_reason": best_scheme.get("summary", ""),
|
||||
},
|
||||
}
|
||||
|
||||
def _build_scheme(
|
||||
self,
|
||||
generator: Any,
|
||||
shape: Any,
|
||||
analysis: Dict[str, Any],
|
||||
candidate: Dict[str, Any],
|
||||
is_foam_material: bool,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
parting_surface = self._build_parting_surface(
|
||||
generator,
|
||||
analysis,
|
||||
candidate["direction"],
|
||||
shape,
|
||||
candidate.get("offset_ratio", 0.0),
|
||||
candidate.get("opening_span_mm"),
|
||||
)
|
||||
parting_line = generator.optimize_parting_line(
|
||||
generator._calculate_parting_line(shape, parting_surface)
|
||||
)
|
||||
|
||||
parting_direction = candidate["direction"]
|
||||
side_action_result = generator.side_action_designer.analyze_and_design(
|
||||
shape=shape,
|
||||
parting_direction=parting_direction,
|
||||
mold_size=generator._calculate_mold_size(analysis),
|
||||
parting_surface=parting_surface,
|
||||
)
|
||||
undercut_regions = generator._build_undercut_regions(
|
||||
side_action_result.get("undercut_analysis", {})
|
||||
)
|
||||
|
||||
scaled_shape = generator._apply_shrinkage_compensation(shape)
|
||||
drafted_shape = generator._apply_draft_angles(scaled_shape, parting_surface)
|
||||
cavity, core = generator._split_cavity_core(drafted_shape, parting_surface)
|
||||
|
||||
cavity_result = {
|
||||
"cavity": cavity,
|
||||
"core": core,
|
||||
"parting_surface": parting_surface,
|
||||
"parting_line": parting_line,
|
||||
"analysis": analysis,
|
||||
"undercut_regions": undercut_regions,
|
||||
"side_actions": side_action_result,
|
||||
}
|
||||
|
||||
if is_foam_material:
|
||||
cavity_result["mold_block"] = generator._generate_mold_block(cavity, analysis)
|
||||
cavity_result["parting_surfaces"] = {
|
||||
"primary_surface": parting_surface,
|
||||
"primary_line": parting_line,
|
||||
"primary_direction": parting_direction,
|
||||
"method": candidate["method"],
|
||||
"offset_ratio": candidate.get("offset_ratio", 0.0),
|
||||
}
|
||||
cavity_result["material"] = generator.foam_material
|
||||
cavity_result["shrinkage_applied"] = generator.shrinkage_rate
|
||||
cavity_result["draft_angle_applied"] = generator.draft_angle
|
||||
|
||||
cavity_data = generator.generate_detailed_cavity_json(cavity_result)
|
||||
key_info = generator.generate_cavity_key_info(cavity_result)
|
||||
cavity_data.setdefault("metadata", {})
|
||||
cavity_data["metadata"]["scheme_id"] = candidate["scheme_id"]
|
||||
cavity_data["metadata"]["scheme_method"] = candidate["method"]
|
||||
cavity_data["metadata"]["scheme_axis"] = candidate["axis"]
|
||||
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", "中面")
|
||||
|
||||
return {
|
||||
"scheme_id": candidate["scheme_id"],
|
||||
"method": candidate["method"],
|
||||
"axis": candidate["axis"],
|
||||
"title": candidate["title"],
|
||||
"reason": candidate["reason"],
|
||||
"priority_score": candidate.get("priority_score"),
|
||||
"normal_alignment_score": candidate.get("normal_alignment_score"),
|
||||
"offset_ratio": candidate.get("offset_ratio", 0.0),
|
||||
"offset_label": candidate.get("offset_label", "中面"),
|
||||
"parting": {
|
||||
"axis": candidate["axis"],
|
||||
"direction": parting_direction,
|
||||
"line": parting_line,
|
||||
"surface": cavity_data.get("parting_surface", {}),
|
||||
},
|
||||
"undercut_regions": undercut_regions,
|
||||
"side_actions": side_action_result,
|
||||
"cavity_data": cavity_data,
|
||||
"key_info": key_info,
|
||||
}
|
||||
|
||||
def _build_parting_surface(
|
||||
self,
|
||||
generator: Any,
|
||||
analysis: Dict[str, Any],
|
||||
direction_vector: List[float],
|
||||
shape: Any,
|
||||
offset_ratio: float = 0.0,
|
||||
opening_span_mm: Optional[float] = None,
|
||||
) -> Any:
|
||||
center = analysis.get("bounding_box", {}).get("center", [0, 0, 0])
|
||||
dims = analysis.get("bounding_box", {}).get("dimensions", [100, 100, 100])
|
||||
span = max(dims) * 1.5 + 30
|
||||
opening_span = opening_span_mm or max(dims)
|
||||
offset_distance = float(opening_span) * float(offset_ratio)
|
||||
origin = [
|
||||
center[0] + direction_vector[0] * offset_distance,
|
||||
center[1] + direction_vector[1] * offset_distance,
|
||||
center[2] + direction_vector[2] * offset_distance,
|
||||
]
|
||||
|
||||
plane = gp_Pln(
|
||||
gp_Pnt(origin[0], origin[1], origin[2]),
|
||||
gp_Dir(direction_vector[0], direction_vector[1], direction_vector[2]),
|
||||
)
|
||||
|
||||
parting_surface = BRepBuilderAPI_MakeFace(
|
||||
plane,
|
||||
-span,
|
||||
span,
|
||||
-span,
|
||||
span,
|
||||
).Face()
|
||||
|
||||
return generator.extend_parting_surface(parting_surface, shape, extension=30.0)
|
||||
|
||||
def _build_offset_variants(
|
||||
self,
|
||||
candidate: Dict[str, Any],
|
||||
is_foam_material: bool,
|
||||
) -> List[Dict[str, Any]]:
|
||||
opening_span = float(candidate.get("opening_span_mm", 0.0))
|
||||
if opening_span <= 0:
|
||||
return [dict(candidate)]
|
||||
|
||||
ratios = [0.0, -0.12, 0.12]
|
||||
if is_foam_material and candidate.get("axis") == "Z":
|
||||
ratios = [0.0, -0.08, 0.08]
|
||||
|
||||
variants = []
|
||||
for ratio in ratios:
|
||||
variant = dict(candidate)
|
||||
label = "中面"
|
||||
id_label = "center"
|
||||
if ratio < 0:
|
||||
label = "偏下" if candidate.get("axis") == "Z" else "负向偏移"
|
||||
id_label = "neg"
|
||||
elif ratio > 0:
|
||||
label = "偏上" if candidate.get("axis") == "Z" else "正向偏移"
|
||||
id_label = "pos"
|
||||
|
||||
variant["scheme_id"] = f"{candidate.get('axis', 'A').lower()}_{id_label}_{abs(ratio):.2f}"
|
||||
variant["offset_ratio"] = ratio
|
||||
variant["offset_label"] = label
|
||||
variant["reason"] = f"{candidate.get('reason', '')},分型面位置: {label}"
|
||||
variants.append(variant)
|
||||
|
||||
return variants
|
||||
|
||||
def _collect_axis_normal_stats(self, generator: Any, shape: Any) -> 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 = generator._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 as exc:
|
||||
logger.debug(f"统计面法向失败: {exc}")
|
||||
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user