1404 lines
56 KiB
Java
1404 lines
56 KiB
Java
/*
|
|
* Copyright (C) 2014 Alfons Wirtz
|
|
* website www.freerouting.net
|
|
*
|
|
* 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.board;
|
|
|
|
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
|
|
import eu.mihosoft.freerouting.geometry.planar.IntBox;
|
|
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
|
|
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
|
|
import eu.mihosoft.freerouting.geometry.planar.LineSegment;
|
|
import eu.mihosoft.freerouting.geometry.planar.Point;
|
|
import eu.mihosoft.freerouting.geometry.planar.Polyline;
|
|
import eu.mihosoft.freerouting.geometry.planar.PolylineShape;
|
|
import eu.mihosoft.freerouting.geometry.planar.TileShape;
|
|
import eu.mihosoft.freerouting.geometry.planar.Vector;
|
|
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import java.util.SortedSet;
|
|
import java.util.TreeSet;
|
|
|
|
import eu.mihosoft.freerouting.datastructures.UndoableObjects;
|
|
import eu.mihosoft.freerouting.datastructures.Stoppable;
|
|
import eu.mihosoft.freerouting.datastructures.TimeLimit;
|
|
import eu.mihosoft.freerouting.datastructures.ShapeTree.TreeEntry;
|
|
|
|
import eu.mihosoft.freerouting.rules.ViaInfo;
|
|
import eu.mihosoft.freerouting.rules.BoardRules;
|
|
|
|
import eu.mihosoft.freerouting.autoroute.AutorouteControl;
|
|
import eu.mihosoft.freerouting.autoroute.AutorouteEngine;
|
|
import eu.mihosoft.freerouting.autoroute.AutorouteControl.ExpansionCostFactor;
|
|
import eu.mihosoft.freerouting.autoroute.CompleteFreeSpaceExpansionRoom;
|
|
|
|
/**
|
|
*
|
|
* Contains higher level functions of a eu.mihosoft.freerouting.board
|
|
*
|
|
* @author Alfons Wirtz
|
|
*/
|
|
public class RoutingBoard extends BasicBoard implements java.io.Serializable
|
|
{
|
|
|
|
/**
|
|
* Creates a new instance of a routing Board with surrrounding box
|
|
* p_bounding_box
|
|
* Rules contains the restrictions to obey when inserting items.
|
|
* Among other things it may contain a clearance matrix.
|
|
*/
|
|
public RoutingBoard(IntBox p_bounding_box, LayerStructure p_layer_structure, PolylineShape[] p_outline_shapes,
|
|
int p_outline_cl_class_no, BoardRules p_rules, Communication p_board_communication, TestLevel p_test_level)
|
|
{
|
|
super(p_bounding_box, p_layer_structure, p_outline_shapes, p_outline_cl_class_no,
|
|
p_rules, p_board_communication, p_test_level);
|
|
}
|
|
|
|
/**
|
|
* Maintains the autorouter database after p_item is inserted, changed, or deleted.
|
|
*/
|
|
public void additional_update_after_change(Item p_item)
|
|
{
|
|
if (p_item == null)
|
|
{
|
|
return;
|
|
}
|
|
if (this.autoroute_engine == null || !this.autoroute_engine.maintain_database)
|
|
{
|
|
return;
|
|
}
|
|
// Invalidate the free space expansion rooms touching a shape of p_item.
|
|
int shape_count = p_item.tree_shape_count(this.autoroute_engine.autoroute_search_tree);
|
|
for (int i = 0; i < shape_count; ++i)
|
|
{
|
|
TileShape curr_shape = p_item.get_tree_shape(this.autoroute_engine.autoroute_search_tree, i);
|
|
this.autoroute_engine.invalidate_drill_pages(curr_shape);
|
|
int curr_layer = p_item.shape_layer(i);
|
|
Collection<SearchTreeObject> overlaps =
|
|
this.autoroute_engine.autoroute_search_tree.overlapping_objects(curr_shape, curr_layer);
|
|
for (SearchTreeObject curr_object : overlaps)
|
|
{
|
|
if (curr_object instanceof CompleteFreeSpaceExpansionRoom)
|
|
{
|
|
this.autoroute_engine.remove_complete_expansion_room((CompleteFreeSpaceExpansionRoom) curr_object);
|
|
}
|
|
}
|
|
}
|
|
p_item.clear_autoroute_info();
|
|
}
|
|
|
|
/**
|
|
* Removes the items in p_item_list and pulls the nearby rubbertraces tight.
|
|
* Returns false, if some items could not be removed, because they were fixed.
|
|
*/
|
|
public boolean remove_items_and_pull_tight(Collection<Item> p_item_list, int p_tidy_width,
|
|
int p_pull_tight_accuracy, boolean p_with_delete_fixed)
|
|
{
|
|
boolean result = true;
|
|
IntOctagon tidy_region;
|
|
boolean calculate_tidy_region;
|
|
if (p_tidy_width < Integer.MAX_VALUE)
|
|
{
|
|
tidy_region = IntOctagon.EMPTY;
|
|
calculate_tidy_region = (p_tidy_width > 0);
|
|
}
|
|
else
|
|
{
|
|
tidy_region = null;
|
|
calculate_tidy_region = false;
|
|
}
|
|
start_marking_changed_area();
|
|
Set<Integer> changed_nets = new TreeSet<Integer>();
|
|
Iterator<Item> it = p_item_list.iterator();
|
|
while (it.hasNext())
|
|
{
|
|
Item curr_item = it.next();
|
|
if (!p_with_delete_fixed && curr_item.is_delete_fixed() || curr_item.is_user_fixed())
|
|
{
|
|
result = false;
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < curr_item.tile_shape_count(); ++i)
|
|
{
|
|
TileShape curr_shape = curr_item.get_tile_shape(i);
|
|
changed_area.join(curr_shape, curr_item.shape_layer(i));
|
|
if (calculate_tidy_region)
|
|
{
|
|
tidy_region = tidy_region.union(curr_shape.bounding_octagon());
|
|
}
|
|
}
|
|
remove_item(curr_item);
|
|
for (int i = 0; i < curr_item.net_count(); ++i)
|
|
{
|
|
changed_nets.add(curr_item.get_net_no(i));
|
|
}
|
|
}
|
|
}
|
|
for (Integer curr_net_no : changed_nets)
|
|
{
|
|
this.combine_traces(curr_net_no);
|
|
}
|
|
if (calculate_tidy_region)
|
|
{
|
|
tidy_region = tidy_region.enlarge(p_tidy_width);
|
|
}
|
|
opt_changed_area(new int[0], tidy_region, p_pull_tight_accuracy, null, null, PULL_TIGHT_TIME_LIMIT);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* starts marking the changed areas for optimizing traces
|
|
*/
|
|
public void start_marking_changed_area()
|
|
{
|
|
if (changed_area == null)
|
|
{
|
|
changed_area = new ChangedArea(get_layer_count());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* enlarges the changed area on p_layer, so that it contains p_point
|
|
*/
|
|
public void join_changed_area(FloatPoint p_point, int p_layer)
|
|
{
|
|
if (changed_area != null)
|
|
{
|
|
changed_area.join(p_point, p_layer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* marks the whole eu.mihosoft.freerouting.board as changed
|
|
*/
|
|
public void mark_all_changed_area()
|
|
{
|
|
start_marking_changed_area();
|
|
FloatPoint[] board_corners = new FloatPoint[4];
|
|
board_corners[0] = bounding_box.ll.to_float();
|
|
board_corners[1] = new FloatPoint(bounding_box.ur.x, bounding_box.ll.y);
|
|
board_corners[2] = bounding_box.ur.to_float();
|
|
board_corners[3] = new FloatPoint(bounding_box.ll.x, bounding_box.ur.y);
|
|
for (int i = 0; i < get_layer_count(); ++i)
|
|
{
|
|
for (int j = 0; j < 4; ++j)
|
|
{
|
|
join_changed_area(board_corners[j], i);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Optimizes the route in the internally marked area.
|
|
* If p_net_no > 0, only traces with net number p_net_no are optimized.
|
|
* If p_clip_shape != null the optimizing is restricted to p_clip_shape.
|
|
* p_trace_cost_arr is used for optimizing vias and may be null.
|
|
* If p_stoppable_thread != null, the agorithm can be requested to be stopped.
|
|
* If p_time_limit > 0; the algorithm will be stopped after p_time_limit Milliseconds.
|
|
*/
|
|
public void opt_changed_area(int[] p_only_net_no_arr, IntOctagon p_clip_shape, int p_accuracy, ExpansionCostFactor[] p_trace_cost_arr,
|
|
Stoppable p_stoppable_thread, int p_time_limit)
|
|
{
|
|
opt_changed_area(p_only_net_no_arr, p_clip_shape, p_accuracy, p_trace_cost_arr,
|
|
p_stoppable_thread, p_time_limit, null, 0);
|
|
}
|
|
|
|
/**
|
|
* Optimizes the route in the internally marked area.
|
|
* If p_net_no > 0, only traces with net number p_net_no are optimized.
|
|
* If p_clip_shape != null the optimizing is restricted to p_clip_shape.
|
|
* p_trace_cost_arr is used for optimizing vias and may be null.
|
|
* If p_stoppable_thread != null, the agorithm can be requested to be stopped.
|
|
* If p_time_limit > 0; the algorithm will be stopped after p_time_limit Milliseconds.
|
|
* If p_keep_point != null, traces on layer p_keep_point_layer containing p_keep_point
|
|
* will also contain this point after optimizing.
|
|
*/
|
|
public void opt_changed_area(int[] p_only_net_no_arr, IntOctagon p_clip_shape, int p_accuracy, ExpansionCostFactor[] p_trace_cost_arr,
|
|
Stoppable p_stoppable_thread, int p_time_limit, Point p_keep_point, int p_keep_point_layer)
|
|
{
|
|
if (changed_area == null)
|
|
{
|
|
return;
|
|
}
|
|
if (p_clip_shape != IntOctagon.EMPTY)
|
|
{
|
|
PullTightAlgo pull_tight_algo =
|
|
PullTightAlgo.get_instance(this, p_only_net_no_arr, p_clip_shape,
|
|
p_accuracy, p_stoppable_thread, p_time_limit, p_keep_point, p_keep_point_layer);
|
|
pull_tight_algo.opt_changed_area(p_trace_cost_arr);
|
|
}
|
|
join_graphics_update_box(changed_area.surrounding_box());
|
|
changed_area = null;
|
|
}
|
|
|
|
/**
|
|
* Checks if a rectangular boxed trace line segment with the input parameters can
|
|
* be inserted without conflict. If a conflict exists,
|
|
* The result length is the maximal line length from p_line.a to p_line.b,
|
|
* which can be inserted without conflict (Integer.MAX_VALUE, if no conflict exists).
|
|
* If p_only_not_shovable_obstacles, unfixed traces and vias are ignored.
|
|
*/
|
|
public double check_trace_segment(Point p_from_point, Point p_to_point, int p_layer, int[] p_net_no_arr,
|
|
int p_trace_half_width, int p_cl_class_no, boolean p_only_not_shovable_obstacles)
|
|
{
|
|
if (p_from_point.equals(p_to_point))
|
|
{
|
|
return 0;
|
|
}
|
|
Polyline curr_polyline = new Polyline(p_from_point, p_to_point);
|
|
LineSegment curr_line_segment = new LineSegment(curr_polyline, 1);
|
|
return check_trace_segment(curr_line_segment, p_layer, p_net_no_arr,
|
|
p_trace_half_width, p_cl_class_no, p_only_not_shovable_obstacles);
|
|
}
|
|
|
|
/**
|
|
* Checks if a trace shape around the input parameters can
|
|
* be inserted without conflict. If a conflict exists,
|
|
* The result length is the maximal line length from p_line.a to p_line.b,
|
|
* which can be inserted without conflict (Integer.MAX_VALUE, if no conflict exists).
|
|
* If p_only_not_shovable_obstacles, unfixed traces and vias are ignored.
|
|
*/
|
|
public double check_trace_segment(LineSegment p_line_segment, int p_layer, int[] p_net_no_arr,
|
|
int p_trace_half_width, int p_cl_class_no, boolean p_only_not_shovable_obstacles)
|
|
{
|
|
Polyline check_polyline = p_line_segment.to_polyline();
|
|
if (check_polyline.arr.length != 3)
|
|
{
|
|
return 0;
|
|
}
|
|
TileShape shape_to_check = check_polyline.offset_shape(p_trace_half_width, 0);
|
|
FloatPoint from_point = p_line_segment.start_point_approx();
|
|
FloatPoint to_point = p_line_segment.end_point_approx();
|
|
double line_length = to_point.distance(from_point);
|
|
double ok_length = Integer.MAX_VALUE;
|
|
ShapeSearchTree default_tree = this.search_tree_manager.get_default_tree();
|
|
|
|
Collection<TreeEntry> obstacle_entries = default_tree.overlapping_tree_entries_with_clearance(shape_to_check, p_layer, p_net_no_arr, p_cl_class_no);
|
|
|
|
for (TreeEntry curr_obstacle_entry : obstacle_entries)
|
|
{
|
|
|
|
if (!(curr_obstacle_entry.object instanceof Item))
|
|
{
|
|
continue;
|
|
}
|
|
Item curr_obstacle = (Item) curr_obstacle_entry.object;
|
|
if (p_only_not_shovable_obstacles && curr_obstacle.is_route() && !curr_obstacle.is_shove_fixed())
|
|
{
|
|
continue;
|
|
}
|
|
TileShape curr_obstacle_shape = curr_obstacle_entry.object.get_tree_shape(default_tree, curr_obstacle_entry.shape_index_in_object);
|
|
TileShape curr_offset_shape;
|
|
FloatPoint nearest_obstacle_point;
|
|
double shorten_value;
|
|
if (default_tree.is_clearance_compensation_used())
|
|
{
|
|
curr_offset_shape = shape_to_check;
|
|
shorten_value = p_trace_half_width + rules.clearance_matrix.clearance_compensation_value(curr_obstacle.clearance_class_no(), p_layer);
|
|
}
|
|
else
|
|
{
|
|
int clearance_value = this.clearance_value(curr_obstacle.clearance_class_no(), p_cl_class_no, p_layer);
|
|
curr_offset_shape = (TileShape) shape_to_check.offset(clearance_value);
|
|
shorten_value = p_trace_half_width + clearance_value;
|
|
}
|
|
TileShape intersection = curr_obstacle_shape.intersection(curr_offset_shape);
|
|
if (intersection.is_empty())
|
|
{
|
|
continue;
|
|
}
|
|
nearest_obstacle_point = intersection.nearest_point_approx(from_point);
|
|
|
|
double projection = from_point.scalar_product(to_point, nearest_obstacle_point) / line_length;
|
|
|
|
projection = Math.max(0.0, projection - shorten_value - 1);
|
|
|
|
if (projection < ok_length)
|
|
{
|
|
ok_length = projection;
|
|
if (ok_length <= 0)
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ok_length;
|
|
}
|
|
|
|
/**
|
|
* Checks, if p_item can be translated by p_vector without
|
|
* producing overlaps or clearance violations.
|
|
*/
|
|
public boolean check_move_item(Item p_item, Vector p_vector, Collection<Item> p_ignore_items)
|
|
{
|
|
int net_count = p_item.net_no_arr.length;
|
|
if (net_count > 1)
|
|
{
|
|
return false; //not yet implemented
|
|
}
|
|
int contact_count = 0;
|
|
// the connected items must remain connected after moving
|
|
if (p_item instanceof Connectable)
|
|
{
|
|
contact_count = p_item.get_all_contacts().size();
|
|
}
|
|
if (p_item instanceof Trace && contact_count > 0)
|
|
{
|
|
return false;
|
|
}
|
|
if (p_ignore_items != null)
|
|
{
|
|
p_ignore_items.add(p_item);
|
|
}
|
|
for (int i = 0; i < p_item.tile_shape_count(); ++i)
|
|
{
|
|
TileShape moved_shape = (TileShape) p_item.get_tile_shape(i).translate_by(p_vector);
|
|
if (!moved_shape.is_contained_in(bounding_box))
|
|
{
|
|
return false;
|
|
}
|
|
Set<Item> obstacles =
|
|
this.overlapping_items_with_clearance(moved_shape, p_item.shape_layer(i), p_item.net_no_arr,
|
|
p_item.clearance_class_no());
|
|
for (Item curr_item : obstacles)
|
|
{
|
|
if (p_ignore_items != null)
|
|
{
|
|
if (!p_ignore_items.contains(curr_item))
|
|
{
|
|
if (curr_item.is_obstacle(p_item))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else if (curr_item != p_item)
|
|
{
|
|
if (curr_item.is_obstacle(p_item))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks, if the net number of p_item can be changed without producing clearance violations.
|
|
*/
|
|
public boolean check_change_net(Item p_item, int p_new_net_no)
|
|
{
|
|
int[] net_no_arr = new int[1];
|
|
net_no_arr[0] = p_new_net_no;
|
|
for (int i = 0; i < p_item.tile_shape_count(); ++i)
|
|
{
|
|
TileShape curr_shape = p_item.get_tile_shape(i);
|
|
Set<Item> obstacles =
|
|
this.overlapping_items_with_clearance(curr_shape, p_item.shape_layer(i),
|
|
net_no_arr, p_item.clearance_class_no());
|
|
for (SearchTreeObject curr_ob : obstacles)
|
|
{
|
|
if (curr_ob != p_item && curr_ob instanceof Connectable && !((Connectable) curr_ob).contains_net(p_new_net_no))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Translates p_drill_item by p_vector and shoves obstacle
|
|
* traces aside. Returns false, if that was not possible without creating
|
|
* clearance violations. In this case the database may be damaged, so that an undo
|
|
* becomes necessesary.
|
|
*/
|
|
public boolean move_drill_item(DrillItem p_drill_item, Vector p_vector,
|
|
int p_max_recursion_depth, int p_max_via_recursion_depth,
|
|
int p_tidy_width, int p_pull_tight_accuracy, int p_pull_tight_time_limit)
|
|
{
|
|
clear_shove_failing_obstacle();
|
|
// unfix the connected shove fixed traces.
|
|
Collection<Item> contact_list = p_drill_item.get_normal_contacts();
|
|
Iterator<Item> it = contact_list.iterator();
|
|
while (it.hasNext())
|
|
{
|
|
Item curr_contact = it.next();
|
|
if (curr_contact.get_fixed_state() == FixedState.SHOVE_FIXED)
|
|
{
|
|
curr_contact.set_fixed_state(FixedState.UNFIXED);
|
|
}
|
|
}
|
|
|
|
IntOctagon tidy_region;
|
|
boolean calculate_tidy_region;
|
|
if (p_tidy_width < Integer.MAX_VALUE)
|
|
{
|
|
tidy_region = IntOctagon.EMPTY;
|
|
calculate_tidy_region = (p_tidy_width > 0);
|
|
}
|
|
else
|
|
{
|
|
tidy_region = null;
|
|
calculate_tidy_region = false;
|
|
}
|
|
int[] net_no_arr = p_drill_item.net_no_arr;
|
|
start_marking_changed_area();
|
|
if (!MoveDrillItemAlgo.insert(p_drill_item, p_vector,
|
|
p_max_recursion_depth, p_max_via_recursion_depth, tidy_region, this))
|
|
{
|
|
return false;
|
|
}
|
|
if (calculate_tidy_region)
|
|
{
|
|
tidy_region = tidy_region.enlarge(p_tidy_width);
|
|
}
|
|
int[] opt_net_no_arr;
|
|
if (p_max_recursion_depth <= 0)
|
|
{
|
|
opt_net_no_arr = net_no_arr;
|
|
}
|
|
else
|
|
{
|
|
opt_net_no_arr = new int[0];
|
|
}
|
|
opt_changed_area(opt_net_no_arr, tidy_region, p_pull_tight_accuracy, null, null, p_pull_tight_time_limit);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks, if there is an item near by sharing a net with p_net_no_arr, from where a routing can start,
|
|
* or where the routig can connect to.
|
|
* If p_from_item != null, items, which are connected to p_from_item, are
|
|
* ignored.
|
|
* Returns null, if no item is found,
|
|
* If p_layer < 0, the layer is ignored
|
|
*/
|
|
public Item pick_nearest_routing_item(Point p_location, int p_layer, Item p_from_item)
|
|
{
|
|
TileShape point_shape = TileShape.get_instance(p_location);
|
|
Collection<Item> found_items = overlapping_items(point_shape, p_layer);
|
|
FloatPoint pick_location = p_location.to_float();
|
|
double min_dist = Integer.MAX_VALUE;
|
|
Item nearest_item = null;
|
|
Set<Item> ignore_set = null;
|
|
Iterator<Item> it = found_items.iterator();
|
|
while (it.hasNext())
|
|
{
|
|
Item curr_item = it.next();
|
|
if (!curr_item.is_connectable())
|
|
{
|
|
continue;
|
|
}
|
|
boolean candidate_found = false;
|
|
double curr_dist = 0;
|
|
if (curr_item instanceof PolylineTrace)
|
|
{
|
|
PolylineTrace curr_trace = (PolylineTrace) curr_item;
|
|
if (p_layer < 0 || curr_trace.get_layer() == p_layer)
|
|
{
|
|
if (nearest_item instanceof DrillItem)
|
|
{
|
|
continue; // prefer drill items
|
|
}
|
|
int trace_radius = curr_trace.get_half_width();
|
|
curr_dist = curr_trace.polyline().distance(pick_location);
|
|
if (curr_dist < min_dist && curr_dist <= trace_radius)
|
|
{
|
|
candidate_found = true;
|
|
}
|
|
}
|
|
}
|
|
else if (curr_item instanceof DrillItem)
|
|
{
|
|
DrillItem curr_drill_item = (DrillItem) curr_item;
|
|
if (p_layer < 0 || curr_drill_item.is_on_layer(p_layer))
|
|
{
|
|
FloatPoint drill_item_center = curr_drill_item.get_center().to_float();
|
|
curr_dist = drill_item_center.distance(pick_location);
|
|
if (curr_dist < min_dist || nearest_item instanceof Trace)
|
|
{
|
|
candidate_found = true;
|
|
}
|
|
}
|
|
}
|
|
else if (curr_item instanceof ConductionArea)
|
|
{
|
|
ConductionArea curr_area = (ConductionArea) curr_item;
|
|
if ((p_layer < 0 || curr_area.get_layer() == p_layer) && nearest_item == null)
|
|
{
|
|
candidate_found = true;
|
|
curr_dist = Integer.MAX_VALUE;
|
|
}
|
|
}
|
|
if (candidate_found)
|
|
{
|
|
if (p_from_item != null)
|
|
{
|
|
if (ignore_set == null)
|
|
{
|
|
// calculated here to avoid unnessery calculations for performance reasoss.
|
|
ignore_set = p_from_item.get_connected_set(-1);
|
|
}
|
|
if (ignore_set.contains(curr_item))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
min_dist = curr_dist;
|
|
nearest_item = curr_item;
|
|
}
|
|
}
|
|
return nearest_item;
|
|
}
|
|
|
|
/**
|
|
* Shoves aside traces, so that a via with the input parameters can be
|
|
* inserted without clearance violations. If the shove failed, the database may be damaged, so that an undo
|
|
* becomes necessesary. Returns false, if the forced via failed.
|
|
*/
|
|
public boolean forced_via(ViaInfo p_via_info, Point p_location, int[] p_net_no_arr,
|
|
int p_trace_clearance_class_no, int[] p_trace_pen_halfwidth_arr,
|
|
int p_max_recursion_depth, int p_max_via_recursion_depth,
|
|
int p_tidy_width, int p_pull_tight_accuracy, int p_pull_tight_time_limit)
|
|
{
|
|
clear_shove_failing_obstacle();
|
|
this.start_marking_changed_area();
|
|
boolean result = ForcedViaAlgo.insert(p_via_info, p_location, p_net_no_arr,
|
|
p_trace_clearance_class_no, p_trace_pen_halfwidth_arr,
|
|
p_max_recursion_depth, p_max_via_recursion_depth, this);
|
|
if (result)
|
|
{
|
|
IntOctagon tidy_clip_shape;
|
|
if (p_tidy_width < Integer.MAX_VALUE)
|
|
{
|
|
tidy_clip_shape = p_location.surrounding_octagon().enlarge(p_tidy_width);
|
|
}
|
|
else
|
|
{
|
|
tidy_clip_shape = null;
|
|
}
|
|
int[] opt_net_no_arr;
|
|
if (p_max_recursion_depth <= 0)
|
|
{
|
|
opt_net_no_arr = p_net_no_arr;
|
|
}
|
|
else
|
|
{
|
|
opt_net_no_arr = new int[0];
|
|
}
|
|
this.opt_changed_area(opt_net_no_arr, tidy_clip_shape,
|
|
p_pull_tight_accuracy, null, null, p_pull_tight_time_limit);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Tries to insert a trace line with the input parameters from
|
|
* p_from_corner to p_to_corner while shoving aside obstacle traces
|
|
* and vias. Returns the last point between p_from_corner and p_to_corner,
|
|
* to which the shove succeeded.
|
|
* Returns null, if the check was inaccurate and an error accured while
|
|
* inserting, so that the database may be damaged and an undo necessary.
|
|
* p_search_tree is the shape search tree used in the algorithm.
|
|
*/
|
|
public Point insert_forced_trace_segment(Point p_from_corner,
|
|
Point p_to_corner, int p_half_width, int p_layer, int[] p_net_no_arr,
|
|
int p_clearance_class_no, int p_max_recursion_depth, int p_max_via_recursion_depth,
|
|
int p_max_spring_over_recursion_depth, int p_tidy_width,
|
|
int p_pull_tight_accuracy, boolean p_with_check, TimeLimit p_time_limit)
|
|
{
|
|
if (p_from_corner.equals(p_to_corner))
|
|
{
|
|
return p_to_corner;
|
|
}
|
|
Polyline insert_polyline = new Polyline(p_from_corner, p_to_corner);
|
|
Point ok_point = insert_forced_trace_polyline(insert_polyline, p_half_width, p_layer, p_net_no_arr,
|
|
p_clearance_class_no, p_max_recursion_depth, p_max_via_recursion_depth,
|
|
p_max_spring_over_recursion_depth, p_tidy_width,
|
|
p_pull_tight_accuracy, p_with_check, p_time_limit);
|
|
Point result;
|
|
if (ok_point == insert_polyline.first_corner())
|
|
{
|
|
result = p_from_corner;
|
|
}
|
|
else if (ok_point == insert_polyline.last_corner())
|
|
{
|
|
result = p_to_corner;
|
|
}
|
|
else
|
|
{
|
|
result = ok_point;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks, if a trace polyline with the input parameters can be inserted
|
|
* while shoving aside obstacle traces and vias.
|
|
*/
|
|
public boolean check_forced_trace_polyline(Polyline p_polyline, int p_half_width, int p_layer, int[] p_net_no_arr,
|
|
int p_clearance_class_no, int p_max_recursion_depth, int p_max_via_recursion_depth,
|
|
int p_max_spring_over_recursion_depth)
|
|
{
|
|
ShapeSearchTree search_tree = search_tree_manager.get_default_tree();
|
|
int compensated_half_width = p_half_width + search_tree.clearance_compensation_value(p_clearance_class_no, p_layer);
|
|
TileShape[] trace_shapes = p_polyline.offset_shapes(compensated_half_width,
|
|
0, p_polyline.arr.length - 1);
|
|
boolean orthogonal_mode = (rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE);
|
|
ShoveTraceAlgo shove_trace_algo = new ShoveTraceAlgo(this);
|
|
for (int i = 0; i < trace_shapes.length; ++i)
|
|
{
|
|
TileShape curr_trace_shape = trace_shapes[i];
|
|
if (orthogonal_mode)
|
|
{
|
|
curr_trace_shape = curr_trace_shape.bounding_box();
|
|
}
|
|
CalcFromSide from_side = new CalcFromSide(p_polyline, i + 1, curr_trace_shape);
|
|
|
|
boolean check_shove_ok = shove_trace_algo.check(curr_trace_shape, from_side, null, p_layer,
|
|
p_net_no_arr, p_clearance_class_no, p_max_recursion_depth,
|
|
p_max_via_recursion_depth, p_max_spring_over_recursion_depth, null);
|
|
if (!check_shove_ok)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Tries to insert a trace polyline with the input parameters from
|
|
* while shoving aside obstacle traces and vias. Returns the last corner
|
|
* on the polyline, to which the shove succeeded.
|
|
* Returns null, if the check was inaccurate and an error accured while
|
|
* inserting, so that the database may be damaged and an undo necessary.
|
|
*/
|
|
public Point insert_forced_trace_polyline(Polyline p_polyline, int p_half_width, int p_layer, int[] p_net_no_arr,
|
|
int p_clearance_class_no, int p_max_recursion_depth, int p_max_via_recursion_depth,
|
|
int p_max_spring_over_recursion_depth, int p_tidy_width,
|
|
int p_pull_tight_accuracy, boolean p_with_check, TimeLimit p_time_limit)
|
|
{
|
|
clear_shove_failing_obstacle();
|
|
Point from_corner = p_polyline.first_corner();
|
|
Point to_corner = p_polyline.last_corner();
|
|
if (from_corner.equals(to_corner))
|
|
{
|
|
return to_corner;
|
|
}
|
|
if (!(from_corner instanceof IntPoint && to_corner instanceof IntPoint))
|
|
{
|
|
System.out.println("RoutingBoard.insert_forced_trace_segment: only implemented for IntPoints");
|
|
return from_corner;
|
|
}
|
|
start_marking_changed_area();
|
|
// Check, if there ends a item of the same net at p_from_corner.
|
|
// If so, its geometry will be used to cut off dog ears of the check shape.
|
|
Trace picked_trace = null;
|
|
ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES);
|
|
Set<Item> picked_items = this.pick_items(from_corner, p_layer, filter);
|
|
if (picked_items.size() == 1)
|
|
{
|
|
Trace curr_picked_trace = (Trace) picked_items.iterator().next();
|
|
if (curr_picked_trace.nets_equal(p_net_no_arr) && curr_picked_trace.get_half_width() == p_half_width && curr_picked_trace.clearance_class_no() == p_clearance_class_no && (curr_picked_trace instanceof PolylineTrace))
|
|
{
|
|
// can combine with the picked trace
|
|
picked_trace = curr_picked_trace;
|
|
}
|
|
}
|
|
ShapeSearchTree search_tree = search_tree_manager.get_default_tree();
|
|
int compensated_half_width = p_half_width + search_tree.clearance_compensation_value(p_clearance_class_no, p_layer);
|
|
ShoveTraceAlgo shove_trace_algo = new ShoveTraceAlgo(this);
|
|
Polyline new_polyline = shove_trace_algo.spring_over_obstacles(p_polyline,
|
|
compensated_half_width, p_layer, p_net_no_arr, p_clearance_class_no, null);
|
|
if (new_polyline == null)
|
|
{
|
|
return from_corner;
|
|
}
|
|
Polyline combined_polyline;
|
|
if (picked_trace == null)
|
|
{
|
|
combined_polyline = new_polyline;
|
|
}
|
|
else
|
|
{
|
|
PolylineTrace combine_trace = (PolylineTrace) picked_trace;
|
|
combined_polyline = new_polyline.combine(combine_trace.polyline());
|
|
}
|
|
if (combined_polyline.arr.length < 3)
|
|
{
|
|
return from_corner;
|
|
}
|
|
int start_shape_no = combined_polyline.arr.length - new_polyline.arr.length;
|
|
// calculate the last shapes of combined_polyline for checking
|
|
TileShape[] trace_shapes = combined_polyline.offset_shapes(compensated_half_width,
|
|
start_shape_no, combined_polyline.arr.length - 1);
|
|
int last_shape_no = trace_shapes.length;
|
|
boolean orthogonal_mode = (rules.get_trace_angle_restriction() == AngleRestriction.NINETY_DEGREE);
|
|
for (int i = 0; i < trace_shapes.length; ++i)
|
|
{
|
|
TileShape curr_trace_shape = trace_shapes[i];
|
|
if (orthogonal_mode)
|
|
{
|
|
curr_trace_shape = curr_trace_shape.bounding_box();
|
|
}
|
|
CalcFromSide from_side = new CalcFromSide(combined_polyline,
|
|
combined_polyline.corner_count() - trace_shapes.length - 1 + i, curr_trace_shape);
|
|
if (p_with_check)
|
|
{
|
|
boolean check_shove_ok = shove_trace_algo.check(curr_trace_shape, from_side, null, p_layer,
|
|
p_net_no_arr, p_clearance_class_no, p_max_recursion_depth,
|
|
p_max_via_recursion_depth, p_max_spring_over_recursion_depth, p_time_limit);
|
|
if (!check_shove_ok)
|
|
{
|
|
last_shape_no = i;
|
|
break;
|
|
}
|
|
}
|
|
boolean insert_ok = shove_trace_algo.insert(curr_trace_shape, from_side, p_layer, p_net_no_arr,
|
|
p_clearance_class_no, null, p_max_recursion_depth,
|
|
p_max_via_recursion_depth, p_max_spring_over_recursion_depth);
|
|
if (!insert_ok)
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
Point new_corner = to_corner;
|
|
if (last_shape_no < trace_shapes.length)
|
|
{
|
|
// the shove with index last_shape_no failed.
|
|
// Sample the shove line to a shorter shove distance and try again.
|
|
TileShape last_trace_shape = trace_shapes[last_shape_no];
|
|
if (orthogonal_mode)
|
|
{
|
|
last_trace_shape = last_trace_shape.bounding_box();
|
|
}
|
|
int sample_width = 2 * this.get_min_trace_half_width();
|
|
FloatPoint last_corner = new_polyline.corner_approx(last_shape_no + 1);
|
|
FloatPoint prev_last_corner = new_polyline.corner_approx(last_shape_no);
|
|
double last_segment_length = last_corner.distance(prev_last_corner);
|
|
if (last_segment_length > 100 * sample_width)
|
|
{
|
|
// to many cycles to sample
|
|
return from_corner;
|
|
}
|
|
int shape_index = combined_polyline.corner_count() - trace_shapes.length - 1 + last_shape_no;
|
|
if (last_segment_length > sample_width)
|
|
{
|
|
new_polyline =
|
|
new_polyline.shorten(new_polyline.arr.length - (trace_shapes.length - last_shape_no - 1), sample_width);
|
|
Point curr_last_corner = new_polyline.last_corner();
|
|
if (!(curr_last_corner instanceof IntPoint))
|
|
{
|
|
System.out.println("insert_forced_trace_segment: IntPoint expected");
|
|
return from_corner;
|
|
}
|
|
new_corner = curr_last_corner;
|
|
if (picked_trace == null)
|
|
{
|
|
combined_polyline = new_polyline;
|
|
}
|
|
else
|
|
{
|
|
PolylineTrace combine_trace = (PolylineTrace) picked_trace;
|
|
combined_polyline = new_polyline.combine(combine_trace.polyline());
|
|
}
|
|
if (combined_polyline.arr.length < 3)
|
|
{
|
|
return new_corner;
|
|
}
|
|
shape_index = combined_polyline.arr.length - 3;
|
|
last_trace_shape = combined_polyline.offset_shape(compensated_half_width, shape_index);
|
|
if (orthogonal_mode)
|
|
{
|
|
last_trace_shape = last_trace_shape.bounding_box();
|
|
}
|
|
}
|
|
CalcFromSide from_side = new CalcFromSide(combined_polyline, shape_index, last_trace_shape);
|
|
boolean check_shove_ok = shove_trace_algo.check(last_trace_shape, from_side, null, p_layer,
|
|
p_net_no_arr, p_clearance_class_no, p_max_recursion_depth,
|
|
p_max_via_recursion_depth, p_max_spring_over_recursion_depth, p_time_limit);
|
|
if (!check_shove_ok)
|
|
{
|
|
return from_corner;
|
|
}
|
|
boolean insert_ok = shove_trace_algo.insert(last_trace_shape, from_side, p_layer,
|
|
p_net_no_arr, p_clearance_class_no, null, p_max_recursion_depth,
|
|
p_max_via_recursion_depth, p_max_spring_over_recursion_depth);
|
|
if (!insert_ok)
|
|
{
|
|
System.out.println("shove trace failed");
|
|
return null;
|
|
}
|
|
}
|
|
// insert the new trace segment
|
|
for (int i = 0; i < new_polyline.corner_count(); ++i)
|
|
{
|
|
join_changed_area(new_polyline.corner_approx(i), p_layer);
|
|
}
|
|
PolylineTrace new_trace = insert_trace_without_cleaning(new_polyline, p_layer, p_half_width, p_net_no_arr, p_clearance_class_no, FixedState.UNFIXED);
|
|
new_trace.combine();
|
|
|
|
IntOctagon tidy_region = null;
|
|
if (p_tidy_width < Integer.MAX_VALUE)
|
|
{
|
|
tidy_region = new_corner.surrounding_octagon().enlarge(p_tidy_width);
|
|
}
|
|
int[] opt_net_no_arr;
|
|
if (p_max_recursion_depth <= 0)
|
|
{
|
|
opt_net_no_arr = p_net_no_arr;
|
|
}
|
|
else
|
|
{
|
|
opt_net_no_arr = new int[0];
|
|
}
|
|
PullTightAlgo pull_tight_algo =
|
|
PullTightAlgo.get_instance(this, opt_net_no_arr, tidy_region,
|
|
p_pull_tight_accuracy, null, -1, new_corner, p_layer);
|
|
|
|
// Remove evtl. generated cycles because otherwise pull_tight may not work correctly.
|
|
if (new_trace.normalize(changed_area.get_area(p_layer)))
|
|
{
|
|
|
|
pull_tight_algo.split_traces_at_keep_point();
|
|
// otherwise the new corner may no more be contained in the new trace after optimizing
|
|
ItemSelectionFilter item_filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES);
|
|
Set<Item> curr_picked_items = this.pick_items(new_corner, p_layer, item_filter);
|
|
new_trace = null;
|
|
if (!curr_picked_items.isEmpty())
|
|
{
|
|
Item found_trace = curr_picked_items.iterator().next();
|
|
if (found_trace instanceof PolylineTrace)
|
|
{
|
|
new_trace = (PolylineTrace) found_trace;
|
|
}
|
|
}
|
|
}
|
|
|
|
// To avoid, that a separate handling for moving backwards in the own trace line
|
|
// becomes necessesary, pull tight is called here.
|
|
if (p_tidy_width > 0 && new_trace != null)
|
|
{
|
|
new_trace.pull_tight(pull_tight_algo);
|
|
}
|
|
return new_corner;
|
|
}
|
|
|
|
/**
|
|
* Initialises the eu.mihosoft.freerouting.autoroute database for routing a connection.
|
|
* If p_retain_autoroute_database, the eu.mihosoft.freerouting.autoroute database is retained and maintained after
|
|
* the algorithm for performance reasons.
|
|
*/
|
|
public AutorouteEngine init_autoroute(int p_net_no, int p_trace_clearance_class_no,
|
|
Stoppable p_stoppable_thread, TimeLimit p_time_limit, boolean p_retain_autoroute_database)
|
|
{
|
|
if (this.autoroute_engine == null || !p_retain_autoroute_database || this.autoroute_engine.autoroute_search_tree.compensated_clearance_class_no != p_trace_clearance_class_no)
|
|
{
|
|
this.autoroute_engine = new AutorouteEngine(this, p_trace_clearance_class_no, p_retain_autoroute_database);
|
|
}
|
|
this.autoroute_engine.init_connection(p_net_no, p_stoppable_thread, p_time_limit);
|
|
return this.autoroute_engine;
|
|
}
|
|
|
|
/**
|
|
* Clears the eu.mihosoft.freerouting.autoroute database in case it was retained.
|
|
*/
|
|
public void finish_autoroute()
|
|
{
|
|
if (this.autoroute_engine != null)
|
|
{
|
|
this.autoroute_engine.clear();
|
|
}
|
|
this.autoroute_engine = null;
|
|
}
|
|
|
|
/**
|
|
* Routes automatically p_item to another item of the same net, to which it
|
|
* is not yet electrically connected.
|
|
* Returns an enum of type AutorouteEngine.AutorouteResult
|
|
*/
|
|
public AutorouteEngine.AutorouteResult autoroute(Item p_item, eu.mihosoft.freerouting.interactive.Settings p_settings, int p_via_costs, Stoppable p_stoppable_thread, TimeLimit p_time_limit)
|
|
{
|
|
if (!(p_item instanceof Connectable) || p_item.net_count() == 0)
|
|
{
|
|
return AutorouteEngine.AutorouteResult.ALREADY_CONNECTED;
|
|
}
|
|
if (p_item.net_count() > 1)
|
|
{
|
|
System.out.println("RoutingBoard.eu.mihosoft.freerouting.autoroute: net_count > 1 not yet implemented");
|
|
}
|
|
int route_net_no = p_item.get_net_no(0);
|
|
AutorouteControl ctrl_settings = new AutorouteControl(this, route_net_no, p_settings, p_via_costs, p_settings.autoroute_settings.get_trace_cost_arr());
|
|
ctrl_settings.remove_unconnected_vias = false;
|
|
Set<Item> route_start_set = p_item.get_connected_set(route_net_no);
|
|
eu.mihosoft.freerouting.rules.Net route_net = rules.nets.get(route_net_no);
|
|
if (route_net != null && route_net.contains_plane())
|
|
{
|
|
for (Item curr_item : route_start_set)
|
|
{
|
|
if (curr_item instanceof eu.mihosoft.freerouting.board.ConductionArea)
|
|
{
|
|
return AutorouteEngine.AutorouteResult.ALREADY_CONNECTED; // already connected to plane
|
|
}
|
|
}
|
|
}
|
|
Set<Item> route_dest_set = p_item.get_unconnected_set(route_net_no);
|
|
if (route_dest_set.size() == 0)
|
|
{
|
|
return AutorouteEngine.AutorouteResult.ALREADY_CONNECTED; // p_item is already routed.
|
|
}
|
|
SortedSet<Item> ripped_item_list = new TreeSet<Item>();
|
|
AutorouteEngine curr_autoroute_engine = init_autoroute(p_item.get_net_no(0),
|
|
ctrl_settings.trace_clearance_class_no, p_stoppable_thread, p_time_limit, false);
|
|
AutorouteEngine.AutorouteResult result =
|
|
curr_autoroute_engine.autoroute_connection(route_start_set, route_dest_set, ctrl_settings, ripped_item_list);
|
|
if (result == AutorouteEngine.AutorouteResult.ROUTED)
|
|
{
|
|
final int time_limit_to_prevent_endless_loop = 1000;
|
|
opt_changed_area(new int[0], null, p_settings.get_trace_pull_tight_accuracy(), ctrl_settings.trace_costs, p_stoppable_thread, time_limit_to_prevent_endless_loop);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Autoroutes from the input pin until the first via, in case the pin and its connected set
|
|
* has only 1 layer. Ripup is allowed if p_ripup_costs is >= 0.
|
|
* Returns an enum of type AutorouteEngine.AutorouteResult
|
|
*/
|
|
public AutorouteEngine.AutorouteResult fanout(Pin p_pin, eu.mihosoft.freerouting.interactive.Settings p_settings, int p_ripup_costs,
|
|
Stoppable p_stoppable_thread, TimeLimit p_time_limit)
|
|
{
|
|
if (p_pin.first_layer() != p_pin.last_layer() || p_pin.net_count() != 1)
|
|
{
|
|
return AutorouteEngine.AutorouteResult.ALREADY_CONNECTED;
|
|
}
|
|
int pin_net_no = p_pin.get_net_no(0);
|
|
int pin_layer = p_pin.first_layer();
|
|
Set<Item> pin_connected_set = p_pin.get_connected_set(pin_net_no);
|
|
for (Item curr_item : pin_connected_set)
|
|
{
|
|
if (curr_item.first_layer() != pin_layer || curr_item.last_layer() != pin_layer)
|
|
{
|
|
return AutorouteEngine.AutorouteResult.ALREADY_CONNECTED;
|
|
}
|
|
}
|
|
Set<Item> unconnected_set = p_pin.get_unconnected_set(pin_net_no);
|
|
if (unconnected_set.isEmpty())
|
|
{
|
|
return AutorouteEngine.AutorouteResult.ALREADY_CONNECTED;
|
|
}
|
|
AutorouteControl ctrl_settings = new AutorouteControl(this, pin_net_no, p_settings);
|
|
ctrl_settings.is_fanout = true;
|
|
ctrl_settings.remove_unconnected_vias = false;
|
|
if (p_ripup_costs >= 0)
|
|
{
|
|
ctrl_settings.ripup_allowed = true;
|
|
ctrl_settings.ripup_costs = p_ripup_costs;
|
|
}
|
|
SortedSet<Item> ripped_item_list = new TreeSet<Item>();
|
|
AutorouteEngine curr_autoroute_engine = init_autoroute(pin_net_no,
|
|
ctrl_settings.trace_clearance_class_no, p_stoppable_thread, p_time_limit, false);
|
|
AutorouteEngine.AutorouteResult result =
|
|
curr_autoroute_engine.autoroute_connection(pin_connected_set,
|
|
unconnected_set, ctrl_settings, ripped_item_list);
|
|
if (result == AutorouteEngine.AutorouteResult.ROUTED)
|
|
{
|
|
final int time_limit_to_prevent_endless_loop = 1000;
|
|
opt_changed_area(new int[0], null, p_settings.get_trace_pull_tight_accuracy(), ctrl_settings.trace_costs, p_stoppable_thread, time_limit_to_prevent_endless_loop);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Inserts a trace from p_from_point to the nearest point on p_to_trace.
|
|
* Returns false, if that is not possible without clearance violation.
|
|
*/
|
|
public boolean connect_to_trace(IntPoint p_from_point, Trace p_to_trace,
|
|
int p_pen_half_width, int p_cl_type)
|
|
{
|
|
|
|
Point first_corner = p_to_trace.first_corner();
|
|
|
|
Point last_corner = p_to_trace.last_corner();
|
|
|
|
int[] net_no_arr = p_to_trace.net_no_arr;
|
|
|
|
if (!(p_to_trace instanceof PolylineTrace))
|
|
{
|
|
return false; // not yet implemented
|
|
}
|
|
PolylineTrace to_trace = (PolylineTrace) p_to_trace;
|
|
if (to_trace.polyline().contains(p_from_point))
|
|
{
|
|
// no connection line necessary
|
|
return true;
|
|
}
|
|
LineSegment projection_line = to_trace.polyline().projection_line(p_from_point);
|
|
if (projection_line == null)
|
|
{
|
|
return false;
|
|
}
|
|
Polyline connection_line = projection_line.to_polyline();
|
|
if (connection_line == null || connection_line.arr.length != 3)
|
|
{
|
|
return false;
|
|
}
|
|
int trace_layer = p_to_trace.get_layer();
|
|
if (!this.check_polyline_trace(connection_line, trace_layer, p_pen_half_width,
|
|
p_to_trace.net_no_arr, p_cl_type))
|
|
{
|
|
return false;
|
|
}
|
|
if (this.changed_area != null)
|
|
{
|
|
for (int i = 0; i < connection_line.corner_count(); ++i)
|
|
{
|
|
this.changed_area.join(connection_line.corner_approx(i), trace_layer);
|
|
}
|
|
}
|
|
|
|
this.insert_trace(connection_line, trace_layer, p_pen_half_width, net_no_arr, p_cl_type, FixedState.UNFIXED);
|
|
if (!p_from_point.equals(first_corner))
|
|
{
|
|
Trace tail = this.get_trace_tail(first_corner, trace_layer, net_no_arr);
|
|
if (tail != null && !tail.is_user_fixed())
|
|
{
|
|
this.remove_item(tail);
|
|
}
|
|
}
|
|
if (!p_from_point.equals(last_corner))
|
|
{
|
|
Trace tail = this.get_trace_tail(last_corner, trace_layer, net_no_arr);
|
|
if (tail != null && !tail.is_user_fixed())
|
|
{
|
|
this.remove_item(tail);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks, if the list p_items contains traces, which have no contact at their
|
|
* start or end point. Trace with net number p_except_net_no are ignored.
|
|
*/
|
|
public boolean contains_trace_tails(Collection<Item> p_items, int[] p_except_net_no_arr)
|
|
{
|
|
Iterator<Item> it = p_items.iterator();
|
|
while (it.hasNext())
|
|
{
|
|
Item curr_ob = it.next();
|
|
if (curr_ob instanceof Trace)
|
|
{
|
|
Trace curr_trace = (Trace) curr_ob;
|
|
if (!curr_trace.nets_equal(p_except_net_no_arr))
|
|
{
|
|
if (curr_trace.is_tail())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Removes all trace tails of the input net.
|
|
* If p_net_no <= 0, the tails of all nets are removed.
|
|
* Returns true, if something was removed.
|
|
*/
|
|
public boolean remove_trace_tails(int p_net_no, Item.StopConnectionOption p_stop_connection_option)
|
|
{
|
|
SortedSet<Item> stub_set = new TreeSet<Item>();
|
|
Collection<Item> board_items = this.get_items();
|
|
for (Item curr_item : board_items)
|
|
{
|
|
if (!curr_item.is_route())
|
|
{
|
|
continue;
|
|
}
|
|
if (curr_item.net_count() != 1)
|
|
{
|
|
continue;
|
|
}
|
|
if (p_net_no > 0 && curr_item.get_net_no(0) != p_net_no)
|
|
{
|
|
continue;
|
|
}
|
|
if (curr_item.is_tail())
|
|
{
|
|
if (curr_item instanceof Via)
|
|
{
|
|
if (p_stop_connection_option == Item.StopConnectionOption.VIA)
|
|
{
|
|
continue;
|
|
}
|
|
if (p_stop_connection_option == Item.StopConnectionOption.FANOUT_VIA)
|
|
{
|
|
if (curr_item.is_fanout_via(null))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
stub_set.add(curr_item);
|
|
}
|
|
}
|
|
SortedSet<Item> stub_connections = new TreeSet<Item>();
|
|
for (Item curr_item : stub_set)
|
|
{
|
|
int item_contact_count = curr_item.get_normal_contacts().size();
|
|
if (item_contact_count == 1)
|
|
{
|
|
stub_connections.addAll(curr_item.get_connection_items(p_stop_connection_option));
|
|
}
|
|
else
|
|
{
|
|
// the connected items are no stubs for example if a via is only connected on 1 layer,
|
|
// but to several traces.
|
|
stub_connections.add(curr_item);
|
|
}
|
|
}
|
|
if (stub_connections.isEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
this.remove_items(stub_connections, false);
|
|
this.combine_traces(p_net_no);
|
|
return true;
|
|
}
|
|
|
|
public void clear_all_item_temporary_autoroute_data()
|
|
{
|
|
Iterator<UndoableObjects.UndoableObjectNode> it = this.item_list.start_read_object();
|
|
for (;;)
|
|
{
|
|
Item curr_item = (Item) item_list.read_object(it);
|
|
if (curr_item == null)
|
|
{
|
|
break;
|
|
}
|
|
curr_item.clear_autoroute_info();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets, if all conduction areas on the eu.mihosoft.freerouting.board are obstacles for route of foreign nets.
|
|
*/
|
|
public void change_conduction_is_obstacle(boolean p_value)
|
|
{
|
|
if (this.rules.get_ignore_conduction() != p_value)
|
|
{
|
|
return; // no muultiply
|
|
}
|
|
boolean something_changed = false;
|
|
// Change the is_obstacle property of all conduction areas of the eu.mihosoft.freerouting.board.
|
|
Iterator<UndoableObjects.UndoableObjectNode> it = item_list.start_read_object();
|
|
for (;;)
|
|
{
|
|
Item curr_item = (Item) item_list.read_object(it);
|
|
if (curr_item == null)
|
|
{
|
|
break;
|
|
}
|
|
if (curr_item instanceof ConductionArea)
|
|
{
|
|
ConductionArea curr_conduction_area = (ConductionArea) curr_item;
|
|
Layer curr_layer = layer_structure.arr[curr_conduction_area.get_layer()];
|
|
if (curr_layer.is_signal && curr_conduction_area.get_is_obstacle() != p_value)
|
|
{
|
|
curr_conduction_area.set_is_obstacle(p_value);
|
|
something_changed = true;
|
|
}
|
|
}
|
|
}
|
|
this.rules.set_ignore_conduction(!p_value);
|
|
if (something_changed)
|
|
{
|
|
this.search_tree_manager.reinsert_tree_items();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Tries to educe the nets of traces and vias, so that the nets are a subset of the nets of the contact
|
|
* items. This is applied to traces and vias with more than 1 net connected to tie pins.
|
|
* Returns true, if the nets of some items were reduced.
|
|
*/
|
|
public boolean reduce_nets_of_route_items()
|
|
{
|
|
boolean result = false;
|
|
boolean something_changed = true;
|
|
while (something_changed)
|
|
{
|
|
something_changed = false;
|
|
Iterator<UndoableObjects.UndoableObjectNode> it = item_list.start_read_object();
|
|
for (;;)
|
|
{
|
|
UndoableObjects.Storable curr_ob = item_list.read_object(it);
|
|
if (curr_ob == null)
|
|
{
|
|
break;
|
|
}
|
|
Item curr_item = (Item) curr_ob;
|
|
if (curr_item.net_no_arr.length <= 1 || curr_item.get_fixed_state() == FixedState.SYSTEM_FIXED)
|
|
{
|
|
continue;
|
|
}
|
|
if (curr_ob instanceof Via)
|
|
{
|
|
Collection<Item> contacts = curr_item.get_normal_contacts();
|
|
for (int curr_net_no : curr_item.net_no_arr)
|
|
{
|
|
for (Item curr_contact : contacts)
|
|
{
|
|
if (!curr_contact.contains_net(curr_net_no))
|
|
{
|
|
curr_item.remove_from_net(curr_net_no);
|
|
something_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (something_changed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
else if (curr_ob instanceof Trace)
|
|
{
|
|
Trace curr_trace = (Trace) curr_ob;
|
|
Collection<Item> contacts = curr_trace.get_start_contacts();
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
for (int curr_net_no : curr_item.net_no_arr)
|
|
{
|
|
boolean pin_found = false;
|
|
for (Item curr_contact : contacts)
|
|
{
|
|
if (curr_contact instanceof Pin)
|
|
{
|
|
pin_found = true;
|
|
if (!curr_contact.contains_net(curr_net_no))
|
|
{
|
|
curr_item.remove_from_net(curr_net_no);
|
|
something_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!pin_found) // at tie pins traces may have different nets
|
|
{
|
|
for (Item curr_contact : contacts)
|
|
{
|
|
if (!(curr_contact instanceof Pin) && !curr_contact.contains_net(curr_net_no))
|
|
{
|
|
curr_item.remove_from_net(curr_net_no);
|
|
something_changed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (something_changed)
|
|
{
|
|
break;
|
|
}
|
|
contacts = curr_trace.get_end_contacts();
|
|
}
|
|
if (something_changed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (something_changed)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Returns the obstacle responsible for the last shove to fail.
|
|
*/
|
|
public Item get_shove_failing_obstacle()
|
|
{
|
|
return shove_failing_obstacle;
|
|
}
|
|
|
|
void set_shove_failing_obstacle(Item p_item)
|
|
{
|
|
shove_failing_obstacle = p_item;
|
|
}
|
|
|
|
public int get_shove_failing_layer()
|
|
{
|
|
return shove_failing_layer;
|
|
}
|
|
|
|
void set_shove_failing_layer(int p_layer)
|
|
{
|
|
shove_failing_layer = p_layer;
|
|
}
|
|
|
|
private void clear_shove_failing_obstacle()
|
|
{
|
|
shove_failing_obstacle = null;
|
|
shove_failing_layer = -1;
|
|
}
|
|
|
|
/**
|
|
* Sets, if the eu.mihosoft.freerouting.autoroute database has to be maintained outside the outoroute algorithm
|
|
* while changing items on rhe eu.mihosoft.freerouting.board.
|
|
*/
|
|
void set_maintaining_autoroute_database(boolean p_value)
|
|
{
|
|
if (p_value)
|
|
{
|
|
|
|
}
|
|
else
|
|
{
|
|
this.autoroute_engine = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns, if the eu.mihosoft.freerouting.autoroute database is maintained outside the outoroute algorithm
|
|
* while changing items on rhe eu.mihosoft.freerouting.board.
|
|
*/
|
|
boolean is_maintaining_autoroute_database()
|
|
{
|
|
return this.autoroute_engine != null;
|
|
}
|
|
/**
|
|
* Contains the database for the autorouzte algorithm.
|
|
*/
|
|
private transient AutorouteEngine autoroute_engine = null;
|
|
/** the area marked for optimizing the route */
|
|
transient ChangedArea changed_area;
|
|
private transient Item shove_failing_obstacle = null;
|
|
private transient int shove_failing_layer = -1;
|
|
/** The time limit in milliseconds for the pull tight algorithm */
|
|
private static final int PULL_TIGHT_TIME_LIMIT = 2000;
|
|
}
|