diff --git a/src/Mod/FreeCADExchange/AutoRouting.py b/src/Mod/FreeCADExchange/AutoRouting.py index 5ecafff..395ae5a 100644 --- a/src/Mod/FreeCADExchange/AutoRouting.py +++ b/src/Mod/FreeCADExchange/AutoRouting.py @@ -140,6 +140,32 @@ def _with_axis(point, axis, value): ) +def _lane_payload(route_index, options): + opts = options or {} + lane_axis = (opts.get("lane_axis") or "y").lower() + if lane_axis not in {"x", "y", "z"}: + lane_axis = "y" + lane_index = int(route_index or 0) + lane_spacing = float(opts.get("lane_spacing", 0.0) or 0.0) + return { + "index": lane_index, + "axis": lane_axis, + "spacing_mm": lane_spacing, + "offset_mm": float(lane_index) * lane_spacing, + } + + +def _apply_lane_offset(points, lane): + offset = float((lane or {}).get("offset_mm", 0.0) or 0.0) + if abs(offset) <= 0.000001: + return list(points or []) + axis = (lane or {}).get("axis", "y") + return [ + _with_axis(point, axis, _axis_value(point, axis) + offset) + for point in list(points or []) + ] + + def _orthogonal_points(start_point, end_point, preferred_axis=None): if _vector_close(start_point, end_point): return [start_point] @@ -528,6 +554,7 @@ def _route_payload(route_data, collisions, wire_style_id=""): "algorithm": route_data.get("algorithm", ""), "length_mm": _route_length(points), "wire_style_id": str(wire_style_id or "").strip(), + "lane": route_data.get("lane", {}), "points": [_point_payload(point) for point in points], "collision_count": len(collisions), "collisions": collisions, @@ -581,20 +608,17 @@ def build_orthogonal_route(start_terminal, end_terminal, route_index=0, options= clearance_axis = (opts.get("clearance_axis") or "z").lower() if clearance_axis not in {"x", "y", "z"}: clearance_axis = "z" - lane_axis = (opts.get("lane_axis") or "y").lower() - if lane_axis not in {"x", "y", "z"}: - lane_axis = "y" + lane = _lane_payload(route_index, opts) clearance_value = max( _axis_value(start_exit, clearance_axis), _axis_value(end_exit, clearance_axis), ) + float(opts.get("clearance", 0.0) or 0.0) - lane_offset = float(route_index or 0) * float(opts.get("lane_spacing", 0.0) or 0.0) lane_point = _with_axis(start_exit, clearance_axis, clearance_value) - lane_point = _with_axis(lane_point, lane_axis, _axis_value(lane_point, lane_axis) + lane_offset) + lane_point = _with_axis(lane_point, lane["axis"], _axis_value(lane_point, lane["axis"]) + lane["offset_mm"]) end_lane = _with_axis(end_exit, clearance_axis, clearance_value) - end_lane = _with_axis(end_lane, lane_axis, _axis_value(end_lane, lane_axis) + lane_offset) + end_lane = _with_axis(end_lane, lane["axis"], _axis_value(end_lane, lane["axis"]) + lane["offset_mm"]) points = [] _append_unique(points, start_origin) @@ -608,6 +632,7 @@ def build_orthogonal_route(start_terminal, end_terminal, route_index=0, options= "algorithm": "orthogonal-v1", "points": points, "network": {}, + "lane": lane, } @@ -655,6 +680,8 @@ def build_network_route(start_terminal, end_terminal, route_index=0, options=Non carrier_points = RoutingNetwork.path_points(network, path_keys) if not carrier_points: return None + lane = _lane_payload(route_index, opts) + carrier_points = _apply_lane_offset(carrier_points, lane) points = [] _append_unique(points, start_origin) @@ -677,6 +704,7 @@ def build_network_route(start_terminal, end_terminal, route_index=0, options=Non "exit_distance": float(end_distance or 0.0), "obstacle_aware": bool(obstacle_aware), }, + "lane": lane, } use_obstacle_avoidance = bool(opts.get("avoid_obstacles", True)) @@ -989,6 +1017,8 @@ def route_between_terminals( "algorithm": route_data.get("algorithm", ""), "network": route_data.get("network", {}), "points": points, + "lane": route_data.get("lane", {}), + "length_mm": _route_length(points), "collision_count": len(collisions), "collisions": collisions, } @@ -1069,6 +1099,7 @@ def route_all_from_payload(doc, payload, options=None): "auto_terminal_binding_warnings": terminal_binding_report["warnings"], "routed": 0, "collision_warnings": 0, + "total_length_mm": 0.0, "skipped_missing_terminal": 0, "skipped_invalid": 0, "missing_endpoint_uuids": [], @@ -1129,11 +1160,14 @@ def route_all_from_payload(doc, payload, options=None): if result["route_status"] == "CollisionWarning": report["collision_warnings"] += 1 report["routed"] += 1 + route_length = float(result.get("length_mm", 0.0) or 0.0) + report["total_length_mm"] += route_length report["routes"].append( { "wire_uuid": _wire_item_value(item, "wire_id", "wire_uuid", "id"), "algorithm": result["algorithm"], "route_status": result["route_status"], + "length_mm": route_length, "collision_count": result["collision_count"], } ) @@ -1155,6 +1189,9 @@ def format_route_all_report(report): prepared_layout.get("surface_carriers", 0), prepared_layout.get("terminal_access_carriers", 0), ) + total_length_mm = float(report.get("total_length_mm", 0.0) or 0.0) + if total_length_mm > 0.0: + message += "\n自动布线总长度:{0:.1f} mm。".format(total_length_mm) errors = report.get("errors", []) or [] if errors: message += "\n首个错误:{0}".format(str(errors[0])) diff --git a/tests/python/freecad_exchange_auto_routing_test.py b/tests/python/freecad_exchange_auto_routing_test.py index ec181ce..420a136 100644 --- a/tests/python/freecad_exchange_auto_routing_test.py +++ b/tests/python/freecad_exchange_auto_routing_test.py @@ -302,6 +302,45 @@ class AutoRoutingTest(unittest.TestCase): self.assertEqual("42", payload["wire_style_id"]) self.assertGreater(payload["length_mm"], 0.0) + def test_network_auto_route_offsets_lane_by_route_index(self): + _install_fake_freecad() + terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() + app = sys.modules["FreeCAD"] + doc = FakeDocument() + terminal_objects.ensure_root_group(doc, "project-1") + start = _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) + end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) + routing_network.create_route_carrier( + doc, + [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], + project_uuid="project-1", + kind="WireDuct", + ) + + first = auto_routing.route_between_terminals( + doc, + start, + end, + route_index=0, + wire_uuid="wire-1", + options={"lane_spacing": 12.0, "lane_axis": "y"}, + ) + second = auto_routing.route_between_terminals( + doc, + start, + end, + route_index=1, + wire_uuid="wire-2", + options={"lane_spacing": 12.0, "lane_axis": "y"}, + ) + payload = json.loads(second["wire"].QetAutoRouteDiagnosticsJson) + + self.assertTrue(any(abs(point.y - 0.0) <= 0.001 for point in first["points"][1:-1])) + self.assertTrue(any(abs(point.y - 12.0) <= 0.001 for point in second["points"][1:-1])) + self.assertEqual(1, payload["lane"]["index"]) + self.assertEqual("y", payload["lane"]["axis"]) + self.assertEqual(12.0, payload["lane"]["offset_mm"]) + def test_route_carrier_styles_make_generated_objects_distinguishable(self): _install_fake_freecad() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() @@ -957,6 +996,38 @@ class AutoRoutingTest(unittest.TestCase): self.assertEqual("AutoRouteBatch", diagnostic.QetDiagnosticKind) self.assertIn("terminal-missing", diagnostic.QetDiagnosticJson) + def test_route_all_reports_total_auto_route_length(self): + _install_fake_freecad() + terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() + app = sys.modules["FreeCAD"] + doc = FakeDocument() + terminal_objects.ensure_root_group(doc, "project-1") + _terminal(doc, terminal_objects, "TerminalStart", "terminal-start", app.Vector(0, 0, 0)) + _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(100, 0, 0)) + routing_network.create_route_carrier( + doc, + [app.Vector(0, 0, 20), app.Vector(100, 0, 20)], + project_uuid="project-1", + kind="WireDuct", + ) + payload = { + "project_uuid": "project-1", + "wires": [ + { + "wire_id": "wire-1", + "start_terminal_uuid": "terminal-start", + "end_terminal_uuid": "terminal-end", + } + ], + } + + report = auto_routing.route_all_from_payload(doc, payload) + message = auto_routing.format_route_all_report(report) + + self.assertGreater(report["total_length_mm"], 0.0) + self.assertEqual(report["total_length_mm"], report["routes"][0]["length_mm"]) + self.assertIn("总长度", message) + def test_route_all_report_calls_out_local_unbound_terminals(self): _install_fake_freecad() terminal_objects, _wiring_objects, _routing_network, auto_routing = _reload_modules()