feat: 增强一级关系识别与保持关系策略
This commit is contained in:
@@ -45,6 +45,32 @@ def _write_obround_slot_model(path: Path) -> None:
|
||||
_write_step(shape, path)
|
||||
|
||||
|
||||
def _write_cross_obround_slot_model(path: Path) -> None:
|
||||
plate = BRepPrimAPI_MakeBox(40.0, 40.0, 6.0).Shape()
|
||||
|
||||
def capsule_tool_x() -> object:
|
||||
axis_1 = gp_Ax2(gp_Pnt(14.0, 20.0, -1.0), gp_Dir(0.0, 0.0, 1.0))
|
||||
axis_2 = gp_Ax2(gp_Pnt(26.0, 20.0, -1.0), gp_Dir(0.0, 0.0, 1.0))
|
||||
cylinder_1 = BRepPrimAPI_MakeCylinder(axis_1, 3.0, 8.0).Shape()
|
||||
cylinder_2 = BRepPrimAPI_MakeCylinder(axis_2, 3.0, 8.0).Shape()
|
||||
connector = BRepPrimAPI_MakeBox(gp_Pnt(14.0, 17.0, -1.0), 12.0, 6.0, 8.0).Shape()
|
||||
fuse_1 = _finalize_boolean_result(BRepAlgoAPI_Fuse(cylinder_1, connector), "verify cross slot x tool fuse")
|
||||
return _finalize_boolean_result(BRepAlgoAPI_Fuse(fuse_1, cylinder_2), "verify cross slot x tool second fuse")
|
||||
|
||||
def capsule_tool_y() -> object:
|
||||
axis_1 = gp_Ax2(gp_Pnt(20.0, 14.0, -1.0), gp_Dir(0.0, 0.0, 1.0))
|
||||
axis_2 = gp_Ax2(gp_Pnt(20.0, 26.0, -1.0), gp_Dir(0.0, 0.0, 1.0))
|
||||
cylinder_1 = BRepPrimAPI_MakeCylinder(axis_1, 3.0, 8.0).Shape()
|
||||
cylinder_2 = BRepPrimAPI_MakeCylinder(axis_2, 3.0, 8.0).Shape()
|
||||
connector = BRepPrimAPI_MakeBox(gp_Pnt(17.0, 14.0, -1.0), 6.0, 12.0, 8.0).Shape()
|
||||
fuse_1 = _finalize_boolean_result(BRepAlgoAPI_Fuse(cylinder_1, connector), "verify cross slot y tool fuse")
|
||||
return _finalize_boolean_result(BRepAlgoAPI_Fuse(fuse_1, cylinder_2), "verify cross slot y tool second fuse")
|
||||
|
||||
cut_x = _finalize_boolean_result(BRepAlgoAPI_Cut(plate, capsule_tool_x()), "verify cross slot x cut")
|
||||
cut_xy = _finalize_boolean_result(BRepAlgoAPI_Cut(cut_x, capsule_tool_y()), "verify cross slot y cut")
|
||||
_write_step(cut_xy, path)
|
||||
|
||||
|
||||
def _slot_face_ids(model: StepModel) -> list[int]:
|
||||
face_ids: list[int] = []
|
||||
for face_id in range(len(model.faces)):
|
||||
@@ -151,6 +177,69 @@ def _obround_total_lengths(model: StepModel) -> list[tuple[int, int, float, floa
|
||||
return rows
|
||||
|
||||
|
||||
def _complex_slot_face_ids(model: StepModel) -> list[int]:
|
||||
face_ids: list[int] = []
|
||||
for face_id in range(len(model.faces)):
|
||||
info = model.face_info(face_id)
|
||||
if info.get("surface") != "cylinder":
|
||||
continue
|
||||
feature = model.feature_info(face_id)
|
||||
if feature.get("slot_kind") == "partial-cylindrical-groove" and feature.get("slot_status") == "blocked":
|
||||
face_ids.append(face_id)
|
||||
return face_ids
|
||||
|
||||
|
||||
def _run_complex_slot_guard() -> None:
|
||||
with tempfile.TemporaryDirectory(prefix="geom_param_cross_slot_guard_") as temp_dir:
|
||||
model_path = Path(temp_dir) / "cross_obround_slot.step"
|
||||
_write_cross_obround_slot_model(model_path)
|
||||
model = StepModel.load(model_path)
|
||||
face_ids = _complex_slot_face_ids(model)
|
||||
if len(face_ids) < 4:
|
||||
raise SystemExit(f"cross obround slot should expose blocked slot ends, got {face_ids}")
|
||||
face_id = face_ids[0]
|
||||
feature = model.feature_info(face_id)
|
||||
blockers = str(feature.get("slot_blockers") or feature.get("recognition_blockers") or "")
|
||||
if "交叉槽" not in blockers and "多槽组" not in blockers and "复杂草图槽" not in blockers:
|
||||
raise SystemExit(f"cross slot blocker should explain the unsupported reason: {feature}")
|
||||
if "复杂槽" not in str(feature.get("feature_type") or ""):
|
||||
raise SystemExit(f"cross slot should be labeled as a complex slot: {feature.get('feature_type')}")
|
||||
|
||||
candidates = model.editable_feature_candidates(limit=80, detailed=False)
|
||||
forbidden_actions = {
|
||||
"resize_cylinder",
|
||||
"resize_slot_width",
|
||||
"resize_slot_depth",
|
||||
"resize_slot_arc_length",
|
||||
"resize_slot_angular_span",
|
||||
}
|
||||
leaked = [
|
||||
(item.get("operation_key"), item.get("target_id"))
|
||||
for item in candidates
|
||||
if int(item.get("target_id", -1)) in face_ids and item.get("operation_key") in forbidden_actions
|
||||
]
|
||||
if leaked:
|
||||
raise SystemExit(f"cross slot should not leak editable slot entries: {leaked}")
|
||||
|
||||
width_plan = model.cylindrical_slot_resize_plan(face_id, 7.0, "width")
|
||||
angle_plan = model.cylindrical_slot_angular_span_plan(face_id, math.pi * 0.75)
|
||||
current_center = _slot_axis_center(model, face_id)
|
||||
axis_plan = model.cylindrical_slot_axis_move_plan(face_id, (current_center[0] + 1.0, current_center[1], current_center[2]))
|
||||
total_plan = model.cylindrical_slot_total_length_plan(face_id, 16.0)
|
||||
for label, plan in (
|
||||
("width", width_plan),
|
||||
("angle", angle_plan),
|
||||
("axis", axis_plan),
|
||||
("total_length", total_plan),
|
||||
):
|
||||
if plan.get("status") != "blocked":
|
||||
raise SystemExit(f"cross slot {label} plan should be blocked: {plan}")
|
||||
text = f"{plan.get('message')} {plan.get('blockers')}"
|
||||
if "交叉槽" not in text and "多槽组" not in text and "复杂草图槽" not in text:
|
||||
raise SystemExit(f"cross slot {label} plan should explain unsupported complex slot: {plan}")
|
||||
print(f"complex_slot_guard=blocked_faces {face_ids}")
|
||||
|
||||
|
||||
def _nearest_obround_total_length(model: StepModel, target_total_length: float) -> tuple[int, int, float, float, float]:
|
||||
best: tuple[int, int, float, float, float] | None = None
|
||||
for face_id, pair_face_id, total_length, center_distance in _obround_total_lengths(model):
|
||||
@@ -404,6 +493,7 @@ def main() -> int:
|
||||
"obround_axis_center",
|
||||
"total_length",
|
||||
"center_distance",
|
||||
"complex_slot_guard",
|
||||
],
|
||||
help="Slot metric to verify.",
|
||||
)
|
||||
@@ -418,8 +508,8 @@ def main() -> int:
|
||||
parser.add_argument("--tolerance", type=float, default=2e-4)
|
||||
args = parser.parse_args()
|
||||
|
||||
cases = (
|
||||
[
|
||||
if args.mode == "all":
|
||||
cases = [
|
||||
("width", args.width),
|
||||
("depth", args.depth),
|
||||
("arc_length", args.arc_length),
|
||||
@@ -428,12 +518,16 @@ def main() -> int:
|
||||
("obround_axis_center", args.obround_axis_center),
|
||||
("total_length", args.total_length),
|
||||
("center_distance", args.center_distance),
|
||||
("complex_slot_guard", 0.0),
|
||||
]
|
||||
if args.mode == "all"
|
||||
else [(args.mode, getattr(args, args.mode.replace("-", "_")))]
|
||||
)
|
||||
elif args.mode == "complex_slot_guard":
|
||||
cases = [("complex_slot_guard", 0.0)]
|
||||
else:
|
||||
cases = [(args.mode, getattr(args, args.mode.replace("-", "_")))]
|
||||
for mode, target in cases:
|
||||
if mode == "total_length":
|
||||
if mode == "complex_slot_guard":
|
||||
_run_complex_slot_guard()
|
||||
elif mode == "total_length":
|
||||
_run_total_length_case(float(target), args.tolerance)
|
||||
elif mode == "center_distance":
|
||||
_run_center_distance_case(float(target), args.tolerance)
|
||||
|
||||
Reference in New Issue
Block a user