feat: 增强FreeCAD自动布线并线路径统计

dev
Zhaowenlong 4 weeks ago
parent b2e9d5b442
commit c777063c08

@ -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): def _orthogonal_points(start_point, end_point, preferred_axis=None):
if _vector_close(start_point, end_point): if _vector_close(start_point, end_point):
return [start_point] return [start_point]
@ -528,6 +554,7 @@ def _route_payload(route_data, collisions, wire_style_id=""):
"algorithm": route_data.get("algorithm", ""), "algorithm": route_data.get("algorithm", ""),
"length_mm": _route_length(points), "length_mm": _route_length(points),
"wire_style_id": str(wire_style_id or "").strip(), "wire_style_id": str(wire_style_id or "").strip(),
"lane": route_data.get("lane", {}),
"points": [_point_payload(point) for point in points], "points": [_point_payload(point) for point in points],
"collision_count": len(collisions), "collision_count": len(collisions),
"collisions": 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() clearance_axis = (opts.get("clearance_axis") or "z").lower()
if clearance_axis not in {"x", "y", "z"}: if clearance_axis not in {"x", "y", "z"}:
clearance_axis = "z" clearance_axis = "z"
lane_axis = (opts.get("lane_axis") or "y").lower() lane = _lane_payload(route_index, opts)
if lane_axis not in {"x", "y", "z"}:
lane_axis = "y"
clearance_value = max( clearance_value = max(
_axis_value(start_exit, clearance_axis), _axis_value(start_exit, clearance_axis),
_axis_value(end_exit, clearance_axis), _axis_value(end_exit, clearance_axis),
) + float(opts.get("clearance", 0.0) or 0.0) ) + 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(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_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 = [] points = []
_append_unique(points, start_origin) _append_unique(points, start_origin)
@ -608,6 +632,7 @@ def build_orthogonal_route(start_terminal, end_terminal, route_index=0, options=
"algorithm": "orthogonal-v1", "algorithm": "orthogonal-v1",
"points": points, "points": points,
"network": {}, "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) carrier_points = RoutingNetwork.path_points(network, path_keys)
if not carrier_points: if not carrier_points:
return None return None
lane = _lane_payload(route_index, opts)
carrier_points = _apply_lane_offset(carrier_points, lane)
points = [] points = []
_append_unique(points, start_origin) _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), "exit_distance": float(end_distance or 0.0),
"obstacle_aware": bool(obstacle_aware), "obstacle_aware": bool(obstacle_aware),
}, },
"lane": lane,
} }
use_obstacle_avoidance = bool(opts.get("avoid_obstacles", True)) use_obstacle_avoidance = bool(opts.get("avoid_obstacles", True))
@ -989,6 +1017,8 @@ def route_between_terminals(
"algorithm": route_data.get("algorithm", ""), "algorithm": route_data.get("algorithm", ""),
"network": route_data.get("network", {}), "network": route_data.get("network", {}),
"points": points, "points": points,
"lane": route_data.get("lane", {}),
"length_mm": _route_length(points),
"collision_count": len(collisions), "collision_count": len(collisions),
"collisions": collisions, "collisions": collisions,
} }
@ -1069,6 +1099,7 @@ def route_all_from_payload(doc, payload, options=None):
"auto_terminal_binding_warnings": terminal_binding_report["warnings"], "auto_terminal_binding_warnings": terminal_binding_report["warnings"],
"routed": 0, "routed": 0,
"collision_warnings": 0, "collision_warnings": 0,
"total_length_mm": 0.0,
"skipped_missing_terminal": 0, "skipped_missing_terminal": 0,
"skipped_invalid": 0, "skipped_invalid": 0,
"missing_endpoint_uuids": [], "missing_endpoint_uuids": [],
@ -1129,11 +1160,14 @@ def route_all_from_payload(doc, payload, options=None):
if result["route_status"] == "CollisionWarning": if result["route_status"] == "CollisionWarning":
report["collision_warnings"] += 1 report["collision_warnings"] += 1
report["routed"] += 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( report["routes"].append(
{ {
"wire_uuid": _wire_item_value(item, "wire_id", "wire_uuid", "id"), "wire_uuid": _wire_item_value(item, "wire_id", "wire_uuid", "id"),
"algorithm": result["algorithm"], "algorithm": result["algorithm"],
"route_status": result["route_status"], "route_status": result["route_status"],
"length_mm": route_length,
"collision_count": result["collision_count"], "collision_count": result["collision_count"],
} }
) )
@ -1155,6 +1189,9 @@ def format_route_all_report(report):
prepared_layout.get("surface_carriers", 0), prepared_layout.get("surface_carriers", 0),
prepared_layout.get("terminal_access_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 [] errors = report.get("errors", []) or []
if errors: if errors:
message += "\n首个错误:{0}".format(str(errors[0])) message += "\n首个错误:{0}".format(str(errors[0]))

@ -302,6 +302,45 @@ class AutoRoutingTest(unittest.TestCase):
self.assertEqual("42", payload["wire_style_id"]) self.assertEqual("42", payload["wire_style_id"])
self.assertGreater(payload["length_mm"], 0.0) 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): def test_route_carrier_styles_make_generated_objects_distinguishable(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()
@ -957,6 +996,38 @@ class AutoRoutingTest(unittest.TestCase):
self.assertEqual("AutoRouteBatch", diagnostic.QetDiagnosticKind) self.assertEqual("AutoRouteBatch", diagnostic.QetDiagnosticKind)
self.assertIn("terminal-missing", diagnostic.QetDiagnosticJson) 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): def test_route_all_report_calls_out_local_unbound_terminals(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()

Loading…
Cancel
Save