From 967ad4b7f1d19bd1b41a0c4e1bc9bc849aeab5af Mon Sep 17 00:00:00 2001 From: Zhaowenlong Date: Sat, 30 May 2026 17:58:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E7=AE=80=E5=8C=96=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E5=B8=83=E7=BA=BF=E5=85=B1=E7=BA=BF=E5=86=97=E4=BD=99=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Mod/FreeCADExchange/AutoRouting.py | 77 +++++++++++++++++++ .../freecad_exchange_auto_routing_test.py | 33 ++++++++ 2 files changed, 110 insertions(+) diff --git a/src/Mod/FreeCADExchange/AutoRouting.py b/src/Mod/FreeCADExchange/AutoRouting.py index 3b39765..2da8cad 100644 --- a/src/Mod/FreeCADExchange/AutoRouting.py +++ b/src/Mod/FreeCADExchange/AutoRouting.py @@ -206,6 +206,79 @@ def _append_orthogonal(points, target_point, preferred_axis=None): _append_unique(points, point) +def _collinear_points(first, middle, last): + ax = float(middle.x) - float(first.x) + ay = float(middle.y) - float(first.y) + az = float(middle.z) - float(first.z) + bx = float(last.x) - float(middle.x) + by = float(last.y) - float(middle.y) + bz = float(last.z) - float(middle.z) + cross_x = ay * bz - az * by + cross_y = az * bx - ax * bz + cross_z = ax * by - ay * bx + dot = ax * bx + ay * by + az * bz + return ( + abs(cross_x) <= 0.000001 + and abs(cross_y) <= 0.000001 + and abs(cross_z) <= 0.000001 + and dot >= -0.000001 + ) + + +def _route_point_key(point, tolerance=0.001): + scale = 1.0 / float(tolerance or 0.001) + return ( + int(round(float(point.x) * scale)), + int(round(float(point.y) * scale)), + int(round(float(point.z) * scale)), + ) + + +def _simplify_collinear_points(points, preserved_point_keys=None): + normalized = [_vector(point) for point in points or [] if _is_finite_point(_vector(point))] + if len(normalized) <= 2: + return normalized + preserved_indices = {0, 1, len(normalized) - 2, len(normalized) - 1} + preserved_point_keys = set(preserved_point_keys or []) + simplified = [normalized[0]] + simplified_indices = [0] + for index, point in enumerate(normalized[1:], start=1): + _append_unique(simplified, point) + if len(simplified_indices) < len(simplified): + simplified_indices.append(index) + while len(simplified) >= 3 and _collinear_points( + simplified[-3], + simplified[-2], + simplified[-1], + ): + if ( + simplified_indices[-2] in preserved_indices + or _route_point_key(simplified[-2]) in preserved_point_keys + ): + break + simplified.pop(-2) + simplified_indices.pop(-2) + return simplified + + +def _important_route_node_keys(network, path_keys, path_result): + edges = network.get("edges", {}) if isinstance(network, dict) else {} + important = { + key + for key in path_keys or [] + if len(edges.get(key, []) or []) != 2 + } + segments = path_result.get("segments", []) if isinstance(path_result, dict) else [] + for index in range(1, len(path_keys or []) - 1): + previous_segment = segments[index - 1] if index - 1 < len(segments) else {} + next_segment = segments[index] if index < len(segments) else {} + previous_carrier = (previous_segment.get("carrier") or {}).get("name", "") + next_carrier = (next_segment.get("carrier") or {}).get("name", "") + if previous_carrier != next_carrier: + important.add(path_keys[index]) + return important + + def _offset(point, direction, distance): return App.Vector( float(point.x) + float(direction.x) * float(distance), @@ -716,6 +789,10 @@ def build_network_route(start_terminal, end_terminal, route_index=0, options=Non _append_unique(points, point) _append_orthogonal(points, end_exit) _append_unique(points, end_origin) + points = _simplify_collinear_points( + points, + preserved_point_keys=_important_route_node_keys(network, path_keys, path_result), + ) return { "algorithm": "network-dijkstra-v1", diff --git a/tests/python/freecad_exchange_auto_routing_test.py b/tests/python/freecad_exchange_auto_routing_test.py index c94c519..776fb89 100644 --- a/tests/python/freecad_exchange_auto_routing_test.py +++ b/tests/python/freecad_exchange_auto_routing_test.py @@ -332,6 +332,39 @@ class AutoRoutingTest(unittest.TestCase): self.assertEqual(2, third_payload["lane"]["index"]) self.assertEqual(-12.0, third_payload["lane"]["offset_mm"]) + def test_network_eplan_connection_route_removes_collinear_network_points(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(50, 0, 20), + app.Vector(100, 0, 20), + ], + project_uuid="project-1", + kind="WireDuct", + ) + + result = auto_routing.route_eplan_connection_between_terminals(doc, start, end) + + point_tuples = [(point.x, point.y, point.z) for point in result["points"]] + self.assertNotIn((50.0, 0.0, 20.0), point_tuples) + self.assertEqual( + [ + (0.0, 0.0, 0.0), + (0.0, 0.0, 20.0), + (100.0, 0.0, 20.0), + (100.0, 0.0, 0.0), + ], + point_tuples, + ) + def test_eplan_connection_route_replaces_existing_wire_uuid_when_endpoints_change(self): _install_fake_freecad() terminal_objects, wiring_objects, routing_network, auto_routing = _reload_modules()