403 lines
17 KiB
Java
403 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2014 Alfons Wirtz
|
|
* website www.freerouting.net
|
|
*
|
|
* Copyright (C) 2017 Michael Hoffer <info@michaelhoffer.de>
|
|
* Website www.freerouting.mihosoft.eu
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License at <http://www.gnu.org/licenses/>
|
|
* for more details.
|
|
*/
|
|
package eu.mihosoft.freerouting.autoroute;
|
|
|
|
import java.util.Iterator;
|
|
import java.util.Collection;
|
|
import java.util.Set;
|
|
import java.util.SortedSet;
|
|
import java.util.TreeSet;
|
|
|
|
import eu.mihosoft.freerouting.datastructures.TimeLimit;
|
|
import eu.mihosoft.freerouting.datastructures.UndoableObjects;
|
|
|
|
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
|
|
import eu.mihosoft.freerouting.geometry.planar.FloatLine;
|
|
|
|
import eu.mihosoft.freerouting.board.Connectable;
|
|
import eu.mihosoft.freerouting.board.Item;
|
|
import eu.mihosoft.freerouting.board.DrillItem;
|
|
import eu.mihosoft.freerouting.board.RoutingBoard;
|
|
|
|
import eu.mihosoft.freerouting.interactive.BoardHandling;
|
|
import eu.mihosoft.freerouting.interactive.InteractiveActionThread;
|
|
import eu.mihosoft.freerouting.logger.FRLogger;
|
|
|
|
/**
|
|
* Handles the sequencing of the batch eu.mihosoft.freerouting.autoroute passes.
|
|
*
|
|
* @author Alfons Wirtz
|
|
*/
|
|
public class BatchAutorouter
|
|
{
|
|
|
|
/**
|
|
* Autoroutes ripup passes until the eu.mihosoft.freerouting.board is completed or the autorouter is stopped by the user,
|
|
* or if p_max_pass_count is exceeded. Is currently used in the optimize via batch pass.
|
|
* Returns the number of oasses to complete the eu.mihosoft.freerouting.board or p_max_pass_count + 1,
|
|
* if the eu.mihosoft.freerouting.board is not completed.
|
|
*/
|
|
public static int autoroute_passes_for_optimizing_item(InteractiveActionThread p_thread,
|
|
int p_max_pass_count, int p_ripup_costs, boolean p_with_prefered_directions)
|
|
{
|
|
BatchAutorouter router_instance = new BatchAutorouter(p_thread, true, p_with_prefered_directions, p_ripup_costs);
|
|
boolean still_unrouted_items = true;
|
|
int curr_pass_no = 1;
|
|
while (still_unrouted_items && !router_instance.is_interrupted && curr_pass_no <= p_max_pass_count)
|
|
{
|
|
if (p_thread.is_stop_requested())
|
|
{
|
|
router_instance.is_interrupted = true;
|
|
}
|
|
still_unrouted_items = router_instance.autoroute_pass(curr_pass_no, false);
|
|
if (still_unrouted_items && !router_instance.is_interrupted)
|
|
{
|
|
p_thread.hdlg.get_settings().autoroute_settings.increment_pass_no();
|
|
}
|
|
++curr_pass_no;
|
|
}
|
|
router_instance.remove_tails(Item.StopConnectionOption.NONE);
|
|
if (!still_unrouted_items)
|
|
{
|
|
--curr_pass_no;
|
|
}
|
|
return curr_pass_no;
|
|
}
|
|
|
|
/**
|
|
* Creates a new batch autorouter.
|
|
*/
|
|
public BatchAutorouter(InteractiveActionThread p_thread, boolean p_remove_unconnected_vias, boolean p_with_preferred_directions,
|
|
int p_start_ripup_costs)
|
|
{
|
|
this.thread = p_thread;
|
|
this.hdlg = p_thread.hdlg;
|
|
this.routing_board = this.hdlg.get_routing_board();
|
|
this.remove_unconnected_vias = p_remove_unconnected_vias;
|
|
if (p_with_preferred_directions)
|
|
{
|
|
this.trace_cost_arr = this.hdlg.get_settings().autoroute_settings.get_trace_cost_arr();
|
|
}
|
|
else
|
|
{
|
|
// remove prefered direction
|
|
this.trace_cost_arr = new AutorouteControl.ExpansionCostFactor[this.routing_board.get_layer_count()];
|
|
for (int i = 0; i < this.trace_cost_arr.length; ++i)
|
|
{
|
|
double curr_min_cost = this.hdlg.get_settings().autoroute_settings.get_preferred_direction_trace_costs(i);
|
|
this.trace_cost_arr[i] = new AutorouteControl.ExpansionCostFactor(curr_min_cost, curr_min_cost);
|
|
}
|
|
}
|
|
|
|
this.start_ripup_costs = p_start_ripup_costs;
|
|
this.retain_autoroute_database = false;
|
|
}
|
|
|
|
/**
|
|
* Autoroutes ripup passes until the eu.mihosoft.freerouting.board is completed or the autorouter is stopped by the user.
|
|
* Returns true if the eu.mihosoft.freerouting.board is completed.
|
|
*/
|
|
public boolean autoroute_passes()
|
|
{
|
|
java.util.ResourceBundle resources =
|
|
java.util.ResourceBundle.getBundle("eu.mihosoft.freerouting.interactive.InteractiveState", hdlg.get_locale());
|
|
boolean still_unrouted_items = true;
|
|
while (still_unrouted_items && !this.is_interrupted)
|
|
{
|
|
if (thread.is_stop_requested())
|
|
{
|
|
this.is_interrupted = true;
|
|
}
|
|
Integer curr_pass_no = hdlg.get_settings().autoroute_settings.get_pass_no();
|
|
String start_message = resources.getString("batch_autorouter") + " " + resources.getString("stop_message") + " " + resources.getString("pass") + " " + curr_pass_no.toString() + ": ";
|
|
hdlg.screen_messages.set_status_message(start_message);
|
|
FRLogger.traceEntry("BatchAutorouter.autoroute_pass("+curr_pass_no+")");
|
|
still_unrouted_items = autoroute_pass(curr_pass_no, true);
|
|
FRLogger.traceExit("BatchAutorouter.autoroute_pass("+curr_pass_no+")");
|
|
if (still_unrouted_items && !is_interrupted)
|
|
{
|
|
hdlg.get_settings().autoroute_settings.increment_pass_no();
|
|
}
|
|
}
|
|
if (!(this.remove_unconnected_vias || still_unrouted_items || this.is_interrupted))
|
|
{
|
|
// clean up the route if the eu.mihosoft.freerouting.board is completed and if fanout is used.
|
|
remove_tails(Item.StopConnectionOption.NONE);
|
|
}
|
|
return !this.is_interrupted;
|
|
}
|
|
|
|
/**
|
|
* Autoroutes one ripup pass of all items of the eu.mihosoft.freerouting.board.
|
|
* Returns false, if the eu.mihosoft.freerouting.board is already completely routed.
|
|
*/
|
|
private boolean autoroute_pass(int p_pass_no, boolean p_with_screen_message)
|
|
{
|
|
try
|
|
{
|
|
Collection<Item> autoroute_item_list = new java.util.LinkedList<Item>();
|
|
Set<Item> handeled_items = new TreeSet<Item>();
|
|
Iterator<UndoableObjects.UndoableObjectNode> it = routing_board.item_list.start_read_object();
|
|
for (;;)
|
|
{
|
|
UndoableObjects.Storable curr_ob = routing_board.item_list.read_object(it);
|
|
if (curr_ob == null)
|
|
{
|
|
break;
|
|
}
|
|
if (curr_ob instanceof Connectable && curr_ob instanceof Item)
|
|
{
|
|
Item curr_item = (Item) curr_ob;
|
|
if (!curr_item.is_route())
|
|
{
|
|
if (!handeled_items.contains(curr_item))
|
|
{
|
|
for (int i = 0; i < curr_item.net_count(); ++i)
|
|
{
|
|
int curr_net_no = curr_item.get_net_no(i);
|
|
Set<Item> connected_set = curr_item.get_connected_set(curr_net_no);
|
|
for (Item curr_connected_item : connected_set)
|
|
{
|
|
if (curr_connected_item.net_count() <= 1)
|
|
{
|
|
handeled_items.add(curr_connected_item);
|
|
}
|
|
}
|
|
int net_item_count = routing_board.connectable_item_count(curr_net_no);
|
|
if (connected_set.size() < net_item_count)
|
|
{
|
|
autoroute_item_list.add(curr_item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (autoroute_item_list.isEmpty())
|
|
{
|
|
this.air_line = null;
|
|
return false;
|
|
}
|
|
int items_to_go_count = autoroute_item_list.size();
|
|
int ripped_item_count = 0;
|
|
int not_found = 0;
|
|
int routed = 0;
|
|
if (p_with_screen_message)
|
|
{
|
|
hdlg.screen_messages.set_batch_autoroute_info(items_to_go_count, routed, ripped_item_count, not_found);
|
|
}
|
|
for (Item curr_item : autoroute_item_list)
|
|
{
|
|
if (this.is_interrupted)
|
|
{
|
|
break;
|
|
}
|
|
for (int i = 0; i < curr_item.net_count(); ++i)
|
|
{
|
|
if (this.thread.is_stop_requested())
|
|
{
|
|
this.is_interrupted = true;
|
|
break;
|
|
}
|
|
routing_board.start_marking_changed_area();
|
|
SortedSet<Item> ripped_item_list = new TreeSet<Item>();
|
|
if (autoroute_item(curr_item, curr_item.get_net_no(i), ripped_item_list, p_pass_no))
|
|
{
|
|
++routed;
|
|
hdlg.repaint();
|
|
}
|
|
else
|
|
{
|
|
++not_found;
|
|
}
|
|
--items_to_go_count;
|
|
ripped_item_count += ripped_item_list.size();
|
|
if (p_with_screen_message)
|
|
{
|
|
hdlg.screen_messages.set_batch_autoroute_info(items_to_go_count, routed, ripped_item_count, not_found);
|
|
}
|
|
}
|
|
}
|
|
if (routing_board.get_test_level() != eu.mihosoft.freerouting.board.TestLevel.ALL_DEBUGGING_OUTPUT)
|
|
{
|
|
Item.StopConnectionOption stop_connection_option;
|
|
if (this.remove_unconnected_vias)
|
|
{
|
|
stop_connection_option = Item.StopConnectionOption.NONE;
|
|
}
|
|
else
|
|
{
|
|
stop_connection_option = Item.StopConnectionOption.FANOUT_VIA;
|
|
}
|
|
remove_tails(stop_connection_option);
|
|
}
|
|
this.air_line = null;
|
|
return true;
|
|
} catch (Exception e)
|
|
{
|
|
this.air_line = null;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void remove_tails(Item.StopConnectionOption p_stop_connection_option)
|
|
{
|
|
routing_board.start_marking_changed_area();
|
|
routing_board.remove_trace_tails(-1, p_stop_connection_option);
|
|
routing_board.opt_changed_area(new int[0], null, this.hdlg.get_settings().get_trace_pull_tight_accuracy(),
|
|
this.trace_cost_arr, this.thread, TIME_LIMIT_TO_PREVENT_ENDLESS_LOOP);
|
|
}
|
|
|
|
private boolean autoroute_item(Item p_item, int p_route_net_no, SortedSet<Item> p_ripped_item_list, int p_ripup_pass_no)
|
|
{
|
|
try
|
|
{
|
|
boolean contains_plane = false;
|
|
eu.mihosoft.freerouting.rules.Net route_net = routing_board.rules.nets.get(p_route_net_no);
|
|
if (route_net != null)
|
|
{
|
|
contains_plane = route_net.contains_plane();
|
|
}
|
|
int curr_via_costs;
|
|
|
|
if (contains_plane)
|
|
{
|
|
curr_via_costs = hdlg.get_settings().autoroute_settings.get_plane_via_costs();
|
|
}
|
|
else
|
|
{
|
|
curr_via_costs = hdlg.get_settings().autoroute_settings.get_via_costs();
|
|
}
|
|
AutorouteControl autoroute_control = new AutorouteControl(this.routing_board, p_route_net_no, hdlg.get_settings(), curr_via_costs, this.trace_cost_arr);
|
|
autoroute_control.ripup_allowed = true;
|
|
autoroute_control.ripup_costs = this.start_ripup_costs * p_ripup_pass_no;
|
|
autoroute_control.remove_unconnected_vias = this.remove_unconnected_vias;
|
|
|
|
Set<Item> unconnected_set = p_item.get_unconnected_set(p_route_net_no);
|
|
if (unconnected_set.size() == 0)
|
|
{
|
|
return true; // p_item is already routed.
|
|
|
|
}
|
|
Set<Item> connected_set = p_item.get_connected_set(p_route_net_no);
|
|
Set<Item> route_start_set;
|
|
Set<Item> route_dest_set;
|
|
if (contains_plane)
|
|
{
|
|
for (Item curr_item : connected_set)
|
|
{
|
|
if (curr_item instanceof eu.mihosoft.freerouting.board.ConductionArea)
|
|
{
|
|
return true; // already connected to plane
|
|
|
|
}
|
|
}
|
|
}
|
|
if (contains_plane)
|
|
{
|
|
route_start_set = connected_set;
|
|
route_dest_set = unconnected_set;
|
|
}
|
|
else
|
|
{
|
|
route_start_set = unconnected_set;
|
|
route_dest_set = connected_set;
|
|
}
|
|
|
|
calc_airline(route_start_set, route_dest_set);
|
|
double max_milliseconds = 100000 * Math.pow(2, p_ripup_pass_no - 1);
|
|
max_milliseconds = Math.min(max_milliseconds, Integer.MAX_VALUE);
|
|
TimeLimit time_limit = new TimeLimit((int) max_milliseconds);
|
|
AutorouteEngine autoroute_engine = routing_board.init_autoroute(p_route_net_no,
|
|
autoroute_control.trace_clearance_class_no, this.thread, time_limit, this.retain_autoroute_database);
|
|
AutorouteEngine.AutorouteResult autoroute_result = autoroute_engine.autoroute_connection(route_start_set, route_dest_set, autoroute_control,
|
|
p_ripped_item_list);
|
|
if (autoroute_result == AutorouteEngine.AutorouteResult.ROUTED)
|
|
{
|
|
routing_board.opt_changed_area(new int[0], null, this.hdlg.get_settings().get_trace_pull_tight_accuracy(), autoroute_control.trace_costs, this.thread, TIME_LIMIT_TO_PREVENT_ENDLESS_LOOP);
|
|
}
|
|
// eu.mihosoft.freerouting.tests.Validate.check("Autoroute ", hdlg.get_routing_board());
|
|
boolean result = autoroute_result == AutorouteEngine.AutorouteResult.ROUTED || autoroute_result == AutorouteEngine.AutorouteResult.ALREADY_CONNECTED;
|
|
return result;
|
|
} catch (Exception e)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the airline of the current autorouted connnection or null,
|
|
* if no such airline exists
|
|
*/
|
|
public FloatLine get_air_line()
|
|
{
|
|
if (this.air_line == null)
|
|
{
|
|
return null;
|
|
}
|
|
if (this.air_line.a == null || this.air_line.b == null)
|
|
{
|
|
return null;
|
|
}
|
|
return this.air_line;
|
|
}
|
|
|
|
private void calc_airline(Collection<Item> p_from_items, Collection<Item> p_to_items)
|
|
{
|
|
FloatPoint from_corner = null;
|
|
FloatPoint to_corner = null;
|
|
double min_distance = Double.MAX_VALUE;
|
|
for (Item curr_from_item : p_from_items)
|
|
{
|
|
if (!(curr_from_item instanceof DrillItem))
|
|
{
|
|
continue;
|
|
}
|
|
FloatPoint curr_from_corner = ((DrillItem) curr_from_item).get_center().to_float();
|
|
for (Item curr_to_item : p_to_items)
|
|
{
|
|
if (!(curr_to_item instanceof DrillItem))
|
|
{
|
|
continue;
|
|
}
|
|
FloatPoint curr_to_corner = ((DrillItem) curr_to_item).get_center().to_float();
|
|
double curr_distance = curr_from_corner.distance_square(curr_to_corner);
|
|
if (curr_distance < min_distance)
|
|
{
|
|
min_distance = curr_distance;
|
|
from_corner = curr_from_corner;
|
|
to_corner = curr_to_corner;
|
|
}
|
|
}
|
|
}
|
|
this.air_line = new FloatLine(from_corner, to_corner);
|
|
}
|
|
private final InteractiveActionThread thread;
|
|
private final BoardHandling hdlg;
|
|
private final RoutingBoard routing_board;
|
|
private boolean is_interrupted = false;
|
|
private final boolean remove_unconnected_vias;
|
|
private final AutorouteControl.ExpansionCostFactor[] trace_cost_arr;
|
|
private final boolean retain_autoroute_database;
|
|
private final int start_ripup_costs;
|
|
/** Used to draw the airline of the current routed incomplete. */
|
|
private FloatLine air_line = null;
|
|
private static final int TIME_LIMIT_TO_PREVENT_ENDLESS_LOOP = 1000;
|
|
}
|