feat: 优化FreeCAD自动布线入网点投影

dev
Zhaowenlong 3 weeks ago
parent f74e55b782
commit f903badd22

@ -629,8 +629,8 @@ def build_network_route(start_terminal, end_terminal, route_index=0, options=Non
if network.get("segment_count", 0) <= 0: if network.get("segment_count", 0) <= 0:
return None return None
start_key, start_distance = RoutingNetwork.nearest_node(network, start_exit) start_key, start_distance, start_mode = RoutingNetwork.connect_point_to_network(network, start_exit)
end_key, end_distance = RoutingNetwork.nearest_node(network, end_exit) end_key, end_distance, end_mode = RoutingNetwork.connect_point_to_network(network, end_exit)
if start_key is None or end_key is None: if start_key is None or end_key is None:
return None return None
@ -677,6 +677,8 @@ def build_network_route(start_terminal, end_terminal, route_index=0, options=Non
"nodes": len(network.get("nodes", {})), "nodes": len(network.get("nodes", {})),
"entry_distance": float(start_distance or 0.0), "entry_distance": float(start_distance or 0.0),
"exit_distance": float(end_distance or 0.0), "exit_distance": float(end_distance or 0.0),
"entry_point_mode": start_mode,
"exit_point_mode": end_mode,
"obstacle_aware": bool(obstacle_aware), "obstacle_aware": bool(obstacle_aware),
}, },
"route_track": path_result, "route_track": path_result,

@ -2177,6 +2177,91 @@ def nearest_point_on_network(network, point):
return nearest_node(network, target) return nearest_node(network, target)
def connect_point_to_network(network, point):
"""Connect the closest projected point to a route graph and return key/distance/mode."""
if not isinstance(network, dict):
return None, None, "none"
nodes = network.get("nodes", {}) or {}
edges = network.get("edges", {}) or {}
if not nodes or not edges:
return None, None, "none"
tolerance = float(network.get("tolerance", DEFAULT_NODE_TOLERANCE) or DEFAULT_NODE_TOLERANCE)
target = _vector(point)
best = None
seen = set()
for key, neighbors in edges.items():
start = nodes.get(key)
if start is None:
continue
for next_key, _weight, carrier in neighbors:
pair = tuple(sorted((key, next_key)))
if pair in seen:
continue
seen.add(pair)
end = nodes.get(next_key)
if end is None:
continue
projected = _closest_point_on_segment(target, start, end)
distance = _distance(target, projected)
if best is None or distance < best["distance"]:
best = {
"key": key,
"next_key": next_key,
"carrier": carrier,
"point": projected,
"distance": distance,
}
if best is None:
node_key, distance = nearest_node(network, target)
return node_key, distance, "node" if node_key is not None else "none"
projected_key = _point_key(best["point"], tolerance=tolerance)
if projected_key in nodes:
return projected_key, best["distance"], "node"
start_key = best["key"]
end_key = best["next_key"]
start = nodes[start_key]
end = nodes[end_key]
carrier = best["carrier"]
def remove_edge_once(left_key, right_key, fallback_to_pair=False):
neighbors = list(edges.get(left_key, []) or [])
for index, (candidate_key, _weight, candidate_carrier) in enumerate(neighbors):
if candidate_key == right_key and candidate_carrier is carrier:
del neighbors[index]
edges[left_key] = neighbors
return True
if fallback_to_pair:
for index, (candidate_key, _weight, _candidate_carrier) in enumerate(neighbors):
if candidate_key == right_key:
del neighbors[index]
edges[left_key] = neighbors
return True
return False
removed_forward = remove_edge_once(start_key, end_key)
remove_edge_once(end_key, start_key, fallback_to_pair=removed_forward)
nodes[projected_key] = best["point"]
edges[projected_key] = []
added_segments = 0
for left_key, left_point, right_key, right_point in (
(start_key, start, projected_key, best["point"]),
(projected_key, best["point"], end_key, end),
):
weight = _distance(left_point, right_point)
if weight <= tolerance:
continue
edges[left_key].append((right_key, weight, carrier))
edges[right_key].append((left_key, weight, carrier))
added_segments += 1
network["segment_count"] = max(int(network.get("segment_count", 0) or 0) - 1 + added_segments, 0)
return projected_key, best["distance"], "segment_projection"
def _carrier_track_payload(carrier): def _carrier_track_payload(carrier):
return { return {
"name": getattr(carrier, "Name", ""), "name": getattr(carrier, "Name", ""),

@ -480,6 +480,45 @@ class AutoRoutingTest(unittest.TestCase):
self.assertEqual("network-dijkstra-v1", result["algorithm"]) self.assertEqual("network-dijkstra-v1", result["algorithm"])
self.assertEqual("Routed", result["route_status"]) self.assertEqual("Routed", result["route_status"])
def test_connect_point_to_network_replaces_bridged_edge_without_stale_reverse_edge(self):
_install_fake_freecad()
_terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(50, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
routing_network.create_route_carrier(
doc,
[app.Vector(54, 0, 20), app.Vector(100, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
network = routing_network.build_route_graph(doc)
original_keys = set(network["nodes"].keys())
bridge_keys = {
key
for key, point in network["nodes"].items()
if point.x in {50.0, 54.0}
}
projected_key, _distance, mode = routing_network.connect_point_to_network(network, app.Vector(52, 0, 20))
new_keys = set(network["nodes"].keys()) - original_keys
stale_bridge_edges = [
(left_key, right_key)
for left_key, neighbors in network["edges"].items()
for right_key, _weight, _carrier in neighbors
if left_key in bridge_keys and right_key in bridge_keys
]
self.assertEqual("segment_projection", mode)
self.assertEqual(projected_key, next(iter(new_keys)))
self.assertEqual([], stale_bridge_edges)
self.assertEqual(4, network["segment_count"])
def test_eplan_connection_route_prefers_wire_duct_over_auxiliary_range(self): def test_eplan_connection_route_prefers_wire_duct_over_auxiliary_range(self):
_install_fake_freecad() _install_fake_freecad()
terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules() terminal_objects, _wiring_objects, routing_network, auto_routing = _reload_modules()
@ -719,6 +758,29 @@ class AutoRoutingTest(unittest.TestCase):
end_point = access_carriers[0].Points[-1] end_point = access_carriers[0].Points[-1]
self.assertEqual((50.0, 0.0, 20.0), (end_point.x, end_point.y, end_point.z)) self.assertEqual((50.0, 0.0, 20.0), (end_point.x, end_point.y, end_point.z))
def test_eplan_connection_route_enters_network_at_segment_projection(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(50, 0, 0))
end = _terminal(doc, terminal_objects, "TerminalEnd", "terminal-end", app.Vector(150, 0, 0))
routing_network.create_route_carrier(
doc,
[app.Vector(0, 0, 20), app.Vector(200, 0, 20)],
project_uuid="project-1",
kind="WireDuct",
)
result = auto_routing.route_eplan_connection_between_terminals(doc, start, end)
self.assertEqual("segment_projection", result["network"]["entry_point_mode"])
self.assertEqual("segment_projection", result["network"]["exit_point_mode"])
self.assertNotIn(0.0, [point.x for point in result["points"][1:-1]])
self.assertNotIn(200.0, [point.x for point in result["points"][1:-1]])
self.assertLess(result["length_mm"], 150.0)
def test_generate_routing_path_network_adds_wiring_cut_out_carrier(self): def test_generate_routing_path_network_adds_wiring_cut_out_carrier(self):
_install_fake_freecad() _install_fake_freecad()
terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules() terminal_objects, _wiring_objects, routing_network, _auto_routing = _reload_modules()
@ -1383,7 +1445,11 @@ class AutoRoutingTest(unittest.TestCase):
], ],
} }
report = auto_routing.route_eplan_connections_from_payload(doc, payload) report = auto_routing.route_eplan_connections_from_payload(
doc,
payload,
options={"avoid_obstacles": False},
)
message = auto_routing.format_eplan_connection_route_report(report) message = auto_routing.format_eplan_connection_route_report(report)
self.assertEqual(1, report["route_status_counts"]["Routed"]) self.assertEqual(1, report["route_status_counts"]["Routed"])

Loading…
Cancel
Save