From c6a8953e242796b2346d3ebe36fcdeb43699b233 Mon Sep 17 00:00:00 2001 From: chenpei Date: Tue, 8 Sep 2026 10:21:37 +0800 Subject: [PATCH] refactor: remove legacy fitting box repair --- .../probe_structure_graph.py | 100 +++--------------- 1 file changed, 12 insertions(+), 88 deletions(-) diff --git a/server_patch/ai_3d_model/ai-solid-wood-cost/probe_structure_graph.py b/server_patch/ai_3d_model/ai-solid-wood-cost/probe_structure_graph.py index ae95f6f..44cba1d 100644 --- a/server_patch/ai_3d_model/ai-solid-wood-cost/probe_structure_graph.py +++ b/server_patch/ai_3d_model/ai-solid-wood-cost/probe_structure_graph.py @@ -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