refactor: remove legacy fitting box repair

This commit is contained in:
chenpei 2026-09-08 10:21:37 +08:00
parent 88cd1dc427
commit c6a8953e24

View File

@ -146,13 +146,13 @@ def normalize_visual_review(
def enrich_hardware_feedback_with_verified_placements(
graph: dict, compiled: dict, feedback: list[dict], *, compile_candidate,
) -> list[dict]:
"""Attach atomic, compiler-proven placement candidates to mounting errors.
"""Attach the only legal v2 repair contract to hardware feedback.
The candidates are not applied automatically because multiple physically
valid mounting faces can exist. The image model must select the candidate
supported by the source views, but it cannot repair only the declaration
while leaving the hardware body hundreds of millimetres away.
Graph v2 has no generic fitting geometry. All locations are explicit
template mounting centres; moving a placeholder AABB is therefore never
a legal repair, even as a fallback.
"""
del compiled, compile_candidate # Kept in the call signature for probe compatibility.
result = copy.deepcopy(feedback)
parts = {str(row.get("id") or ""): row for row in graph.get("parts") or []}
template_instances = {
@ -160,14 +160,8 @@ def enrich_hardware_feedback_with_verified_placements(
for row in graph.get("instances") or []
if (parts.get(str(row.get("part_id") or "")) or {}).get("hardware_template_id")
}
solved = {str(row.get("id")): row for row in compiled.get("instances") or []}
current_issue_count = len(compiled.get("issues") or [])
envelope = list(compiled.get("occupied_envelope_mm") or [0, 0, 0])
for item in result:
if (
item.get("code") != "hardware_mounting_geometry"
or item.get("compatible_mounting_values")
):
if item.get("code") != "hardware_mounting_geometry":
continue
instance_id = str(item.get("instance_id") or "")
if instance_id in template_instances:
@ -182,82 +176,12 @@ def enrich_hardware_feedback_with_verified_placements(
)
item["repair_contract"] = "template_mounting_centers_only"
continue
host_id = str(item.get("host_instance_id") or "")
hardware, host = solved.get(instance_id), solved.get(host_id)
path = str(item.get("path") or "")
match = re.search(r"/mountings/(\d+)$", path)
axis_name = str((host or {}).get("thickness_axis") or "")
if not hardware or not host or not match or axis_name not in "xyz":
continue
mounting_index = int(match.group(1))
axis = "xyz".index(axis_name)
hardware_size = float(hardware["size_mm"][axis])
host_low = float(host["position_mm"][axis])
host_thickness = float(host["size_mm"][axis])
host_high = host_low + host_thickness
current_position = float(hardware["position_mm"][axis])
candidate_specs = []
for face in (f"{axis_name}-", f"{axis_name}+"):
surface_position = host_low - hardware_size if face.endswith("-") else host_high
candidate_specs.append((face, "surface", 0.0, surface_position))
if hardware_size + 0.01 >= host_thickness:
through_position = host_high - hardware_size if face.endswith("-") else host_low
candidate_specs.append((face, "through", host_thickness, through_position))
if item.get("kind") == "mortised":
depth = float(item.get("declared_embed_depth_mm") or 0.0)
if 0.6 < depth < min(host_thickness, hardware_size) - 0.6:
candidate_specs.extend([
(f"{axis_name}-", "mortised", depth, host_low - (hardware_size - depth)),
(f"{axis_name}+", "mortised", depth, host_high - depth),
])
candidates = []
seen = set()
for face, kind, depth, position in candidate_specs:
signature = (face, kind, round(depth, 6), round(position, 6))
if signature in seen:
continue
seen.add(signature)
patches = [
{"op": "replace", "path": f"/instances/{instance_id}/placement/{axis_name}",
"value": {"mode": "absolute", "value_mm": round(position, 6)}},
{"op": "replace", "path": f"/instances/{instance_id}/mountings/{mounting_index}/kind", "value": kind},
{"op": "replace", "path": f"/instances/{instance_id}/mountings/{mounting_index}/face", "value": face},
{"op": "replace", "path": f"/instances/{instance_id}/mountings/{mounting_index}/embed_depth_mm", "value": round(depth, 6)},
]
try:
candidate_graph, _ = apply_graph_patch(graph, patches)
candidate_compiled = compile_candidate(candidate_graph)
except Exception:
continue
candidate_issues = list(candidate_compiled.get("issues") or [])
if len(candidate_issues) >= current_issue_count:
continue
if any(
row.get("code") == "hardware_mounting_geometry" and row.get("path") == path
for row in candidate_issues
):
continue
outside = 0.0
if len(envelope) == 3:
outside = max(0.0, -position) + max(0.0, position + hardware_size - float(envelope[axis]))
candidates.append({
"patches": patches,
"candidate_compile_issue_count": len(candidate_issues),
"candidate_issue_codes": sorted({str(row.get("code")) for row in candidate_issues}),
"outside_occupied_envelope_mm": round(outside, 6),
"movement_mm": round(abs(position - current_position), 6),
"validation": "full_graph_recompile_strict_issue_reduction",
})
candidates.sort(key=lambda row: (
row["candidate_compile_issue_count"], row["outside_occupied_envelope_mm"],
row["movement_mm"], json.dumps(row["patches"], sort_keys=True),
))
if candidates:
item["compatible_placement_patches"] = candidates[:4]
item["correction"] = (
"compatible_mounting_values为空,必须从compatible_placement_patches中回看原图选择一组"
"完整原子补丁;不得只修改kind/face/embed_depth_mm而不移动五金实体。"
)
item.pop("compatible_placement_patches", None)
item["repair_contract"] = "hardware_template_required"
item["correction"] = (
"先将五金part映射到允许的hardware_family和hardware_template_id,再按模板补齐全部"
"mounting slots与孔型中心;v2禁止任何通用包围盒位置候选。"
)
return result