freerouting/src/main/java/eu/mihosoft/freerouting/autoroute/MazeSearchAlgo.java

1507 lines
64 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.
*
* MazeSearchAlgo.java
*
* Created on 25. Januar 2004, 13:24
*/
package eu.mihosoft.freerouting.autoroute;
import eu.mihosoft.freerouting.geometry.planar.ConvexShape;
import eu.mihosoft.freerouting.geometry.planar.FloatLine;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
import eu.mihosoft.freerouting.geometry.planar.Line;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.TreeSet;
import java.util.Set;
import eu.mihosoft.freerouting.board.Connectable;
import eu.mihosoft.freerouting.board.ForcedViaAlgo;
import eu.mihosoft.freerouting.board.Item;
import eu.mihosoft.freerouting.board.PolylineTrace;
import eu.mihosoft.freerouting.board.ShapeSearchTree;
import eu.mihosoft.freerouting.board.AngleRestriction;
import eu.mihosoft.freerouting.board.SearchTreeObject;
import eu.mihosoft.freerouting.board.ItemSelectionFilter;
/**
* Class for autorouting an incomplete connection via a maze search algorithm.
*
* @author Alfons Wirtz
*/
public class MazeSearchAlgo
{
/**
* Initializes a new instance of MazeSearchAlgo for secrching a connection
* between p_start_items and p_destination_items.
* Returns null, if the initialisation failed.
*/
public static MazeSearchAlgo get_instance(Set<Item> p_start_items,
Set<Item> p_destination_items, AutorouteEngine p_autoroute_database, AutorouteControl p_ctrl)
{
MazeSearchAlgo new_instance = new MazeSearchAlgo(p_autoroute_database, p_ctrl);
MazeSearchAlgo result;
if (new_instance.init(p_start_items, p_destination_items))
{
result = new_instance;
}
else
{
result = null;
}
return result;
}
/** Creates a new instance of MazeSearchAlgo */
MazeSearchAlgo(AutorouteEngine p_autoroute_engine, AutorouteControl p_ctrl)
{
autoroute_engine = p_autoroute_engine;
ctrl = p_ctrl;
random_generator.setSeed(p_ctrl.ripup_costs); // To get reproducable random numbers in the ripup algorithm.
this.search_tree = p_autoroute_engine.autoroute_search_tree;
maze_expansion_list = new TreeSet<MazeListElement>();
destination_distance =
new DestinationDistance(ctrl.trace_costs, ctrl.layer_active,
ctrl.min_normal_via_cost, ctrl.min_cheap_via_cost);
}
/**
* Does a maze search to find a connection route between the start and the
* destination items. If the algorithm succeeds, the ExpansionDoor and its section number of
* the found destination is returned, from where the whole found connection
* can be backtracked. Otherwise the return value will be null.
*/
public Result find_connection()
{
while (occupy_next_element())
{
}
if (this.destination_door == null)
{
return null;
}
return new Result(this.destination_door, this.section_no_of_destination_door);
}
/**
* Expands the next element in the maze expansion list.
* Returns false, if the expansion list is exhausted or the destination is reached.
*/
public boolean occupy_next_element()
{
if (this.destination_door != null)
{
return false; // destination already reached
}
MazeListElement list_element = null;
MazeSearchElement curr_door_section = null;
// Search the next element, which is not yet expanded.
boolean next_element_found = false;
while (!maze_expansion_list.isEmpty())
{
if (this.autoroute_engine.is_stop_requested())
{
return false;
}
Iterator<MazeListElement> it = maze_expansion_list.iterator();
list_element = it.next();
int curr_section_no = list_element.section_no_of_door;
curr_door_section = list_element.door.get_maze_search_element(curr_section_no);
it.remove();
if (!curr_door_section.is_occupied)
{
next_element_found = true;
break;
}
}
if (!next_element_found)
{
return false;
}
curr_door_section.backtrack_door = list_element.backtrack_door;
curr_door_section.section_no_of_backtrack_door = list_element.section_no_of_backtrack_door;
curr_door_section.room_ripped = list_element.room_ripped;
curr_door_section.adjustment = list_element.adjustment;
if (list_element.door instanceof DrillPage)
{
expand_to_drills_of_page(list_element);
return true;
}
if (list_element.door instanceof TargetItemExpansionDoor)
{
TargetItemExpansionDoor curr_door = (TargetItemExpansionDoor) list_element.door;
if (curr_door.is_destination_door())
{
// The destination is reached.
this.destination_door = curr_door;
this.section_no_of_destination_door = list_element.section_no_of_door;
return false;
}
}
if (ctrl.is_fanout && list_element.door instanceof ExpansionDrill && list_element.backtrack_door instanceof ExpansionDrill)
{
// algorithm completed after the first drill;
this.destination_door = list_element.door;
this.section_no_of_destination_door = list_element.section_no_of_door;
return false;
}
if (ctrl.vias_allowed && list_element.door instanceof ExpansionDrill
&& !(list_element.backtrack_door instanceof ExpansionDrill))
{
expand_to_other_layers(list_element);
}
if (list_element.next_room != null)
{
if (!expand_to_room_doors(list_element))
{
return true; // occupation by ripup is delayed or nothing was expanded
// In case nothing was expanded allow the section to be occupied from
// somewhere else, if the next room is thin.
}
}
curr_door_section.is_occupied = true;
return true;
}
/**
* Expands the other door section of the room.
* Returns true, if the from door section has to be occupied,
* and false, if the occupation for is delayed.
*/
private boolean expand_to_room_doors(MazeListElement p_list_element)
{
// Complete the neigbour rooms to make shure, that the
// doors of this room will not change later on.
int layer_no = p_list_element.next_room.get_layer();
boolean layer_active = ctrl.layer_active[layer_no];
if (!layer_active)
{
if (autoroute_engine.board.layer_structure.arr[layer_no].is_signal)
{
return true;
}
}
double half_width = ctrl.compensated_trace_half_width[layer_no];
boolean curr_door_is_small = false;
if (p_list_element.door instanceof ExpansionDoor)
{
double half_width_add = half_width + AutorouteEngine.TRACE_WIDTH_TOLERANCE;
ExpansionDoor curr_door = (ExpansionDoor) p_list_element.door;
if (this.ctrl.with_neckdown)
{
// try evtl. neckdown at a destination pin
double neck_down_half_width = check_neck_down_at_dest_pin(p_list_element.next_room);
if (neck_down_half_width > 0)
{
half_width_add = Math.min(half_width_add, neck_down_half_width);
half_width = half_width_add;
}
}
curr_door_is_small = door_is_small(curr_door, 2 * half_width_add);
}
this.autoroute_engine.complete_neigbour_rooms(p_list_element.next_room);
FloatPoint shape_entry_middle = p_list_element.shape_entry.a.middle_point(p_list_element.shape_entry.b);
if (this.ctrl.with_neckdown && p_list_element.door instanceof TargetItemExpansionDoor)
{
// try evtl. neckdown at a start pin
Item start_item = ((TargetItemExpansionDoor) p_list_element.door).item;
if (start_item instanceof eu.mihosoft.freerouting.board.Pin)
{
double neckdown_half_width = ((eu.mihosoft.freerouting.board.Pin) start_item).get_trace_neckdown_halfwidth(layer_no);
if (neckdown_half_width > 0)
{
half_width = Math.min(half_width, neckdown_half_width);
}
}
}
boolean next_room_is_thick = true;
if (p_list_element.next_room instanceof ObstacleExpansionRoom)
{
next_room_is_thick = room_shape_is_thick((ObstacleExpansionRoom) p_list_element.next_room);
}
else
{
TileShape next_room_shape = p_list_element.next_room.get_shape();
if (next_room_shape.min_width() < 2 * half_width)
{
next_room_is_thick = false; // to prevent probles with the opposite side
}
else if (!p_list_element.already_checked && p_list_element.door.get_dimension() == 1 && !curr_door_is_small)
{
// The algorithm below works only, if p_location is on the border of p_room_shape.
// That is only the case for 1 dimensional doors.
// For small doors the check is done in check_leaving_via below.
FloatPoint[] nearest_points = next_room_shape.nearest_border_points_approx(shape_entry_middle, 2);
if (nearest_points.length < 2)
{
System.out.println("MazeSearchAlgo.expand_to_room_doors: nearest_points.length == 2 expected");
next_room_is_thick = false;
}
else
{
double curr_dist = nearest_points[1].distance(shape_entry_middle);
next_room_is_thick = (curr_dist > half_width + 1);
}
}
}
if (!layer_active && p_list_element.door instanceof ExpansionDrill)
{
// check for drill to a foreign conduction area on split plane.
Point drill_location = ((ExpansionDrill) p_list_element.door).location;
ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.CONDUCTION);
Set<Item> picked_items = autoroute_engine.board.pick_items(drill_location, layer_no, filter);
for (Item curr_item : picked_items)
{
if (!curr_item.contains_net(ctrl.net_no))
{
return true;
}
}
}
boolean something_expanded = false;
if (expand_to_target_doors(p_list_element, next_room_is_thick, curr_door_is_small, shape_entry_middle))
{
something_expanded = true;
}
if (!layer_active)
{
return true;
}
int ripup_costs = 0;
if (p_list_element.next_room instanceof FreeSpaceExpansionRoom)
{
if (!p_list_element.already_checked)
{
if (curr_door_is_small)
{
boolean enter_through_small_door = false;
if (next_room_is_thick)
{
// check to enter the thick room from a ripped item through a small door (after ripup)
enter_through_small_door = check_leaving_ripped_item(p_list_element);
}
if (!enter_through_small_door)
{
return something_expanded;
}
}
}
}
else if (p_list_element.next_room instanceof ObstacleExpansionRoom)
{
ObstacleExpansionRoom obstacle_room = (ObstacleExpansionRoom) p_list_element.next_room;
if (!p_list_element.already_checked)
{
boolean room_rippable = false;
if (this.ctrl.ripup_allowed)
{
ripup_costs = check_ripup(p_list_element, obstacle_room.get_item(), curr_door_is_small);
room_rippable = (ripup_costs >= 0);
}
if (ripup_costs != ALREADY_RIPPED_COSTS && next_room_is_thick)
{
Item obstacle_item = obstacle_room.get_item();
if (!curr_door_is_small && this.ctrl.max_shove_trace_recursion_depth > 0 && obstacle_item instanceof eu.mihosoft.freerouting.board.PolylineTrace)
{
if (!shove_trace_room(p_list_element, obstacle_room))
{
if (ripup_costs > 0)
{
// delay the occupation by ripup to allow shoving the room by another door sections.
MazeListElement new_element =
new MazeListElement(p_list_element.door, p_list_element.section_no_of_door,
p_list_element.backtrack_door, p_list_element.section_no_of_backtrack_door,
p_list_element.expansion_value + ripup_costs, p_list_element.sorting_value + ripup_costs,
p_list_element.next_room, p_list_element.shape_entry, true,
p_list_element.adjustment, true);
this.maze_expansion_list.add(new_element);
}
return something_expanded;
}
}
}
if (!room_rippable)
{
return true;
}
}
}
for (ExpansionDoor to_door : p_list_element.next_room.get_doors())
{
if (to_door == p_list_element.door)
{
continue;
}
if (expand_to_door(to_door, p_list_element, ripup_costs, next_room_is_thick, MazeSearchElement.Adjustment.NONE))
{
something_expanded = true;
}
}
// Expand also the drill pages intersecting the room.
if (ctrl.vias_allowed && !(p_list_element.door instanceof ExpansionDrill))
{
if ((something_expanded || next_room_is_thick) && p_list_element.next_room instanceof CompleteFreeSpaceExpansionRoom)
{
// avoid setting something_expanded to true when next_room is thin to allow occupying by different sections of the door
Collection<DrillPage> overlapping_drill_pages =
this.autoroute_engine.drill_page_array.overlapping_pages(p_list_element.next_room.get_shape());
{
for (DrillPage to_drill_page : overlapping_drill_pages)
{
expand_to_drill_page(to_drill_page, p_list_element);
something_expanded = true;
}
}
}
else if (p_list_element.next_room instanceof ObstacleExpansionRoom)
{
Item curr_obstacle_item = ((ObstacleExpansionRoom) p_list_element.next_room).get_item();
if (curr_obstacle_item instanceof eu.mihosoft.freerouting.board.Via)
{
eu.mihosoft.freerouting.board.Via curr_via = (eu.mihosoft.freerouting.board.Via) curr_obstacle_item;
ExpansionDrill via_drill_info = curr_via.get_autoroute_drill_info(this.autoroute_engine.autoroute_search_tree);
expand_to_drill(via_drill_info, p_list_element, ripup_costs);
}
}
}
return something_expanded;
}
/** Expand the target doors of the room. Returns true, if at leat 1 target door was expanded */
private boolean expand_to_target_doors(MazeListElement p_list_element,
boolean p_next_room_is_thick, boolean p_curr_door_is_small, FloatPoint p_shape_entry_middle)
{
if (p_curr_door_is_small)
{
boolean enter_through_small_door = false;
if (p_list_element.door instanceof ExpansionDoor)
{
CompleteExpansionRoom from_room = ((ExpansionDoor) p_list_element.door).other_room(p_list_element.next_room);
if (from_room instanceof ObstacleExpansionRoom)
{
// otherwise entering through the small door may fail, because it was not checked.
enter_through_small_door = true;
}
}
if (!enter_through_small_door)
{
return false;
}
}
boolean result = false;
for (TargetItemExpansionDoor to_door : p_list_element.next_room.get_target_doors())
{
if (to_door == p_list_element.door)
{
continue;
}
TileShape target_shape = ((Connectable) to_door.item).get_trace_connection_shape(this.autoroute_engine.autoroute_search_tree,
to_door.tree_entry_no);
FloatPoint connection_point = target_shape.nearest_point_approx(p_shape_entry_middle);
if (!p_next_room_is_thick)
{
// check the line from p_shape_entry_middle to the nearest point.
int[] curr_net_no_arr = new int[1];
curr_net_no_arr[0] = this.ctrl.net_no;
int curr_layer = p_list_element.next_room.get_layer();
IntPoint[] check_points = new IntPoint[2];
check_points[0] = p_shape_entry_middle.round();
check_points[1] = connection_point.round();
if (!check_points[0].equals(check_points[1]))
{
Polyline check_polyline = new Polyline(check_points);
boolean check_ok = autoroute_engine.board.check_forced_trace_polyline(check_polyline,
ctrl.trace_half_width[curr_layer], curr_layer, curr_net_no_arr, ctrl.trace_clearance_class_no,
ctrl.max_shove_trace_recursion_depth, ctrl.max_shove_via_recursion_depth,
ctrl.max_spring_over_recursion_depth);
if (!check_ok)
{
continue;
}
}
}
FloatLine new_shape_entry = new FloatLine(connection_point, connection_point);
if (expand_to_door_section(to_door, 0, new_shape_entry, p_list_element,
0, MazeSearchElement.Adjustment.NONE))
{
result = true;
}
}
return result;
}
/**
* Return true, if at least 1 door ection was expanded.
*/
private boolean expand_to_door(ExpansionDoor p_to_door, MazeListElement p_list_element, int p_add_costs,
boolean p_next_room_is_thick, MazeSearchElement.Adjustment p_adjustment)
{
double half_width = ctrl.compensated_trace_half_width[p_list_element.next_room.get_layer()];
boolean something_expanded = false;
FloatLine[] line_sections = p_to_door.get_section_segments(half_width);
for (int i = 0; i < line_sections.length; ++i)
{
if (p_to_door.section_arr[i].is_occupied)
{
continue;
}
FloatLine new_shape_entry;
if (p_next_room_is_thick)
{
new_shape_entry = line_sections[i];
if (p_to_door.dimension == 1 && line_sections.length == 1 && p_to_door.first_room instanceof CompleteFreeSpaceExpansionRoom && p_to_door.second_room instanceof CompleteFreeSpaceExpansionRoom)
{
// check entering the p_to_door at an acute corner of the shape of p_list_element.next_room
FloatPoint shape_entry_middle = new_shape_entry.a.middle_point(new_shape_entry.b);
TileShape room_shape = p_list_element.next_room.get_shape();
if (room_shape.min_width() < 2 * half_width)
{
return false;
}
FloatPoint[] nearest_points = room_shape.nearest_border_points_approx(shape_entry_middle, 2);
if (nearest_points.length < 2 || nearest_points[1].distance(shape_entry_middle) <= half_width + 1)
{
return false;
}
}
}
else
{
// expand only doors on the opposite side of the room from the shape_entry.
if (p_to_door.dimension == 1 && i == 0 && line_sections[0].b.distance_square(line_sections[0].a) < 1)
{
// p_to_door is small belonging to a via or thin room
continue;
}
new_shape_entry = segment_projection(p_list_element.shape_entry, line_sections[i]);
if (new_shape_entry == null)
{
continue;
}
}
if (expand_to_door_section(p_to_door, i, new_shape_entry, p_list_element,
p_add_costs, p_adjustment))
{
something_expanded = true;
}
}
return something_expanded;
}
/**
* Checks, if the width p_door is big enough for a trace with width p_trace_width.
*/
private boolean door_is_small(ExpansionDoor p_door, double p_trace_width)
{
if (p_door.dimension == 1 || p_door.first_room instanceof CompleteFreeSpaceExpansionRoom && p_door.second_room instanceof CompleteFreeSpaceExpansionRoom)
{
TileShape door_shape = p_door.get_shape();
if (door_shape.is_empty())
{
if (this.autoroute_engine.board.get_test_level().ordinal() >= eu.mihosoft.freerouting.board.TestLevel.ALL_DEBUGGING_OUTPUT.ordinal())
{
System.out.println("MazeSearchAlgo:check_door_width door_shape is empty");
}
return true;
}
double door_length;
AngleRestriction angle_restriction = autoroute_engine.board.rules.get_trace_angle_restriction();
if (angle_restriction == AngleRestriction.NINETY_DEGREE)
{
IntBox door_box = door_shape.bounding_box();
door_length = door_box.max_width();
}
else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
{
IntOctagon door_oct = door_shape.bounding_octagon();
door_length = door_oct.max_width();
}
else
{
FloatLine door_line_segment = door_shape.diagonal_corner_segment();
door_length = door_line_segment.b.distance(door_line_segment.a);
}
if (door_length < p_trace_width)
{
return true;
}
}
return false;
}
/**
* Return true, if the door section was successfully expanded.
*/
private boolean expand_to_door_section(ExpandableObject p_door, int p_section_no,
FloatLine p_shape_entry, MazeListElement p_from_element,
int p_add_costs, MazeSearchElement.Adjustment p_adjustment)
{
if (p_door.get_maze_search_element(p_section_no).is_occupied || p_shape_entry == null)
{
return false;
}
CompleteExpansionRoom next_room = p_door.other_room(p_from_element.next_room);
int layer = p_from_element.next_room.get_layer();
FloatPoint shape_entry_middle = p_shape_entry.a.middle_point(p_shape_entry.b);
double expansion_value = p_from_element.expansion_value + p_add_costs
+ shape_entry_middle.weighted_distance(p_from_element.shape_entry.a.middle_point(p_from_element.shape_entry.b),
ctrl.trace_costs[layer].horizontal, ctrl.trace_costs[layer].vertical);
double sorting_value = expansion_value + this.destination_distance.calculate(shape_entry_middle, layer);
boolean room_ripped = p_add_costs > 0 && p_adjustment == MazeSearchElement.Adjustment.NONE || p_from_element.already_checked && p_from_element.room_ripped;
MazeListElement new_element =
new MazeListElement(p_door, p_section_no, p_from_element.door, p_from_element.section_no_of_door,
expansion_value, sorting_value, next_room, p_shape_entry, room_ripped, p_adjustment, false);
this.maze_expansion_list.add(new_element);
return true;
}
private void expand_to_drill(ExpansionDrill p_drill, MazeListElement p_from_element, int p_add_costs)
{
int layer = p_from_element.next_room.get_layer();
int trace_half_width = this.ctrl.compensated_trace_half_width[layer];
boolean room_shape_is_thin =
p_from_element.next_room.get_shape().min_width() < 2 * trace_half_width;
if (room_shape_is_thin)
{
// expand only drills intersecting the backtrack door
if (p_from_element.backtrack_door == null || !p_drill.get_shape().intersects(p_from_element.backtrack_door.get_shape()))
{
return;
}
}
double via_radius = ctrl.via_radius_arr[layer];
ConvexShape shrinked_drill_shape = p_drill.get_shape().shrink(via_radius);
FloatPoint compare_corner = p_from_element.shape_entry.a.middle_point(p_from_element.shape_entry.b);
if (p_from_element.door instanceof DrillPage && p_from_element.backtrack_door instanceof TargetItemExpansionDoor)
{
// If expansion comes from a pin with trace exit directions the eapansion_value is calculated
// from the nearest trace exit point instead from the center olf the pin.
Item from_item = ((TargetItemExpansionDoor) p_from_element.backtrack_door).item;
if (from_item instanceof eu.mihosoft.freerouting.board.Pin)
{
FloatPoint nearest_exit_corner = ((eu.mihosoft.freerouting.board.Pin) from_item).nearest_trace_exit_corner(p_drill.location.to_float(), trace_half_width, layer);
if (nearest_exit_corner != null)
{
compare_corner = nearest_exit_corner;
}
}
}
FloatPoint nearest_point =
shrinked_drill_shape.nearest_point_approx(compare_corner);
FloatLine shape_entry = new FloatLine(nearest_point, nearest_point);
int section_no = layer - p_drill.first_layer;
double expansion_value = p_from_element.expansion_value + p_add_costs
+ nearest_point.weighted_distance(compare_corner,
ctrl.trace_costs[layer].horizontal, ctrl.trace_costs[layer].vertical);
ExpandableObject new_backtrack_door;
int new_section_no_of_backtrack_door;
if (p_from_element.door instanceof DrillPage)
{
new_backtrack_door = p_from_element.backtrack_door;
new_section_no_of_backtrack_door = p_from_element.section_no_of_backtrack_door;
}
else
{
// Expanded directly through already existing via
// The step expand_to_drill_page is skipped
new_backtrack_door = p_from_element.door;
new_section_no_of_backtrack_door = p_from_element.section_no_of_door;
expansion_value += ctrl.min_normal_via_cost;
}
double sorting_value = expansion_value + this.destination_distance.calculate(nearest_point, layer);
MazeListElement new_element =
new MazeListElement(p_drill, section_no, new_backtrack_door,
new_section_no_of_backtrack_door, expansion_value, sorting_value, null, shape_entry,
p_from_element.room_ripped, MazeSearchElement.Adjustment.NONE, false);
this.maze_expansion_list.add(new_element);
}
/**
* A drill page is inserted between an expansion roomm and the drill to expand
* in order to prevent performance problems with rooms with big shapes containing many drills.
*/
private void expand_to_drill_page(DrillPage p_drill_page, MazeListElement p_from_element)
{
int layer = p_from_element.next_room.get_layer();
FloatPoint from_element_shape_entry_middle = p_from_element.shape_entry.a.middle_point(p_from_element.shape_entry.b);
FloatPoint nearest_point =
p_drill_page.shape.nearest_point(from_element_shape_entry_middle);
double expansion_value = p_from_element.expansion_value + ctrl.min_normal_via_cost;
double sorting_value =
expansion_value + nearest_point.weighted_distance(from_element_shape_entry_middle,
ctrl.trace_costs[layer].horizontal, ctrl.trace_costs[layer].vertical) + this.destination_distance.calculate(nearest_point, layer);
MazeListElement new_element =
new MazeListElement(p_drill_page, layer, p_from_element.door, p_from_element.section_no_of_door,
expansion_value, sorting_value, p_from_element.next_room, p_from_element.shape_entry,
p_from_element.room_ripped, MazeSearchElement.Adjustment.NONE, false);
this.maze_expansion_list.add(new_element);
}
private void expand_to_drills_of_page(MazeListElement p_from_element)
{
int from_room_layer = p_from_element.section_no_of_door;
DrillPage drill_page = (DrillPage) p_from_element.door;
Collection<ExpansionDrill> drill_list = drill_page.get_drills(this.autoroute_engine, this.ctrl.attach_smd_allowed);
for (ExpansionDrill curr_drill : drill_list)
{
int section_no = from_room_layer - curr_drill.first_layer;
if (section_no < 0 || section_no >= curr_drill.room_arr.length)
{
continue;
}
if (curr_drill.room_arr[section_no] == p_from_element.next_room && !curr_drill.get_maze_search_element(section_no).is_occupied)
{
expand_to_drill(curr_drill, p_from_element, 0);
}
}
}
/**
* Tries to expand other layers by inserting a via.
*/
private void expand_to_other_layers(MazeListElement p_list_element)
{
int via_lower_bound = 0;
int via_upper_bound = -1;
ExpansionDrill curr_drill = (ExpansionDrill) p_list_element.door;
int from_layer = curr_drill.first_layer + p_list_element.section_no_of_door;
boolean smd_attached_on_component_side = false;
boolean smd_attached_on_solder_side = false;
boolean room_ripped;
if (curr_drill.room_arr[p_list_element.section_no_of_door] instanceof ObstacleExpansionRoom)
{
// check ripup of an existing via
if (!this.ctrl.ripup_allowed)
{
return;
}
Item curr_obstacle_item =
((ObstacleExpansionRoom) curr_drill.room_arr[p_list_element.section_no_of_door]).get_item();
if (!(curr_obstacle_item instanceof eu.mihosoft.freerouting.board.Via))
{
return;
}
eu.mihosoft.freerouting.library.Padstack curr_obstacle_padstack = ((eu.mihosoft.freerouting.board.Via) curr_obstacle_item).get_padstack();
if (!this.ctrl.via_rule.contains_padstack(curr_obstacle_padstack) || curr_obstacle_item.clearance_class_no() != this.ctrl.via_clearance_class)
{
return;
}
via_lower_bound = curr_obstacle_padstack.from_layer();
via_upper_bound = curr_obstacle_padstack.to_layer();
room_ripped = true;
}
else
{
int[] net_no_arr = new int[1];
net_no_arr[0] = ctrl.net_no;
room_ripped = false;
int via_lower_limit = Math.max(curr_drill.first_layer, ctrl.via_lower_bound);
int via_upper_limit = Math.min(curr_drill.last_layer, ctrl.via_upper_bound);
// Calculate the lower bound of possible vias.
int curr_layer = from_layer;
for (;;)
{
TileShape curr_room_shape = curr_drill.room_arr[curr_layer - curr_drill.first_layer].get_shape();
eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult drill_result =
ForcedViaAlgo.check_layer(ctrl.via_radius_arr[curr_layer], ctrl.via_clearance_class,
ctrl.attach_smd_allowed, curr_room_shape, curr_drill.location, curr_layer, net_no_arr,
ctrl.max_shove_trace_recursion_depth, 0, autoroute_engine.board);
if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE)
{
via_lower_bound = curr_layer + 1;
break;
}
else if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.DRILLABLE_WITH_ATTACH_SMD)
{
if (curr_layer == 0)
{
smd_attached_on_component_side = true;
}
else if (curr_layer == ctrl.layer_count - 1)
{
smd_attached_on_solder_side = true;
}
}
if (curr_layer <= via_lower_limit)
{
via_lower_bound = via_lower_limit;
break;
}
--curr_layer;
}
if (via_lower_bound > curr_drill.first_layer)
{
return;
}
curr_layer = from_layer + 1;
for (;;)
{
if (curr_layer > via_upper_limit)
{
via_upper_bound = via_upper_limit;
break;
}
TileShape curr_room_shape = curr_drill.room_arr[curr_layer - curr_drill.first_layer].get_shape();
eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult drill_result =
ForcedViaAlgo.check_layer(ctrl.via_radius_arr[curr_layer], ctrl.via_clearance_class, ctrl.attach_smd_allowed,
curr_room_shape, curr_drill.location, curr_layer, net_no_arr,
ctrl.max_shove_trace_recursion_depth, 0, autoroute_engine.board);
if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.NOT_DRILLABLE)
{
via_upper_bound = curr_layer - 1;
break;
}
else if (drill_result == eu.mihosoft.freerouting.board.ForcedPadAlgo.CheckDrillResult.DRILLABLE_WITH_ATTACH_SMD)
{
if (curr_layer == ctrl.layer_count - 1)
{
smd_attached_on_solder_side = true;
}
}
++curr_layer;
}
if (via_upper_bound < curr_drill.last_layer)
{
return;
}
}
for (int to_layer = via_lower_bound; to_layer <= via_upper_bound; ++to_layer)
{
if (to_layer == from_layer)
{
continue;
}
// check, there there is a fitting via mask.
int curr_first_layer;
int curr_last_layer;
if (to_layer < from_layer)
{
curr_first_layer = to_layer;
curr_last_layer = from_layer;
}
else
{
curr_first_layer = from_layer;
curr_last_layer = to_layer;
}
boolean mask_found = false;
for (int i = 0; i < ctrl.via_info_arr.length; ++i)
{
AutorouteControl.ViaMask curr_via_info = ctrl.via_info_arr[i];
if (curr_first_layer >= curr_via_info.from_layer && curr_last_layer <= curr_via_info.to_layer && curr_via_info.from_layer >= via_lower_bound && curr_via_info.to_layer <= via_upper_bound)
{
boolean mask_ok = true;
if (curr_via_info.from_layer == 0 && smd_attached_on_component_side || curr_via_info.to_layer == ctrl.layer_count - 1 && smd_attached_on_solder_side)
{
mask_ok = curr_via_info.attach_smd_allowed;
}
if (mask_ok)
{
mask_found = true;
break;
}
}
}
if (!mask_found)
{
continue;
}
MazeSearchElement curr_drill_layer_info = curr_drill.get_maze_search_element(to_layer - curr_drill.first_layer);
if (curr_drill_layer_info.is_occupied)
{
continue;
}
double expansion_value = p_list_element.expansion_value + ctrl.add_via_costs[from_layer].to_layer[to_layer];
FloatPoint shape_entry_middle = p_list_element.shape_entry.a.middle_point(p_list_element.shape_entry.b);
double sorting_value = expansion_value + this.destination_distance.calculate(shape_entry_middle, to_layer);
int curr_room_index = to_layer - curr_drill.first_layer;
MazeListElement new_element =
new MazeListElement(curr_drill, curr_room_index, curr_drill, p_list_element.section_no_of_door,
expansion_value, sorting_value, curr_drill.room_arr[curr_room_index], p_list_element.shape_entry,
room_ripped, MazeSearchElement.Adjustment.NONE, false);
this.maze_expansion_list.add(new_element);
}
}
/**
* Initializes the maze search algorithm.
* Returns false if the initialisation failed.
*/
private boolean init(Set<Item> p_start_items, Set<Item> p_destination_items)
{
reduce_trace_shapes_at_tie_pins(p_start_items, this.ctrl.net_no, this.search_tree);
reduce_trace_shapes_at_tie_pins(p_destination_items, this.ctrl.net_no, this.search_tree);
// process the destination items
boolean destination_ok = false;
Iterator<Item> it = p_destination_items.iterator();
while (it.hasNext())
{
if (this.autoroute_engine.is_stop_requested())
{
return false;
}
Item curr_item = it.next();
ItemAutorouteInfo curr_info = curr_item.get_autoroute_info();
curr_info.set_start_info(false);
for (int i = 0; i < curr_item.tree_shape_count(this.search_tree); ++i)
{
TileShape curr_tree_shape = curr_item.get_tree_shape(this.search_tree, i);
if (curr_tree_shape != null)
{
destination_distance.join(curr_tree_shape.bounding_box(), curr_item.shape_layer(i));
}
}
destination_ok = true;
}
if (!destination_ok && this.ctrl.is_fanout)
{
// detination set is not needed for fanout
IntBox board_bounding_box = this.autoroute_engine.board.bounding_box;
destination_distance.join(board_bounding_box, 0);
destination_distance.join(board_bounding_box, this.ctrl.layer_count - 1);
destination_ok = true;
}
if (!destination_ok)
{
return false;
}
// process the start items
Collection<IncompleteFreeSpaceExpansionRoom> start_rooms = new LinkedList<IncompleteFreeSpaceExpansionRoom>();
it = p_start_items.iterator();
while (it.hasNext())
{
if (this.autoroute_engine.is_stop_requested())
{
return false;
}
Item curr_item = it.next();
ItemAutorouteInfo curr_info = curr_item.get_autoroute_info();
curr_info.set_start_info(true);
if (curr_item instanceof Connectable)
{
for (int i = 0; i < curr_item.tree_shape_count(search_tree); ++i)
{
TileShape contained_shape =
((Connectable) curr_item).get_trace_connection_shape(search_tree, i);
IncompleteFreeSpaceExpansionRoom new_start_room = autoroute_engine.add_incomplete_expansion_room(null, curr_item.shape_layer(i), contained_shape);
start_rooms.add(new_start_room);
}
}
}
// complete the start rooms
Collection<CompleteFreeSpaceExpansionRoom> completed_start_rooms = new LinkedList<CompleteFreeSpaceExpansionRoom>();
if (this.autoroute_engine.maintain_database)
{
// add the completed start rooms carried over from the last eu.mihosoft.freerouting.autoroute to the start rooms.
completed_start_rooms.addAll(this.autoroute_engine.get_rooms_with_target_items(p_start_items));
}
for (IncompleteFreeSpaceExpansionRoom curr_room : start_rooms)
{
if (this.autoroute_engine.is_stop_requested())
{
return false;
}
Collection<CompleteFreeSpaceExpansionRoom> curr_completed_rooms =
autoroute_engine.complete_expansion_room(curr_room);
completed_start_rooms.addAll(curr_completed_rooms);
}
// Put the ItemExpansionDoors of the completed start rooms into
// the maze_expansion_list.
boolean start_ok = false;
for (CompleteFreeSpaceExpansionRoom curr_room : completed_start_rooms)
{
Iterator<TargetItemExpansionDoor> it2 = curr_room.get_target_doors().iterator();
while (it2.hasNext())
{
if (this.autoroute_engine.is_stop_requested())
{
return false;
}
TargetItemExpansionDoor curr_door = it2.next();
if (curr_door.is_destination_door())
{
continue;
}
TileShape connection_shape = ((Connectable) curr_door.item).get_trace_connection_shape(search_tree, curr_door.tree_entry_no);
connection_shape = connection_shape.intersection(curr_door.room.get_shape());
FloatPoint curr_center = connection_shape.centre_of_gravity();
FloatLine shape_entry = new FloatLine(curr_center, curr_center);
double sorting_value = this.destination_distance.calculate(curr_center, curr_room.get_layer());
MazeListElement new_list_element =
new MazeListElement(curr_door, 0, null, 0, 0, sorting_value, curr_room, shape_entry, false,
MazeSearchElement.Adjustment.NONE, false);
maze_expansion_list.add(new_list_element);
start_ok = true;
}
}
return start_ok;
}
/**
* Looks for pins with more than 1 nets and reduces shapes of thraces of foreign nets,
* which are already connected to such a pin, so that the pin center is not blocked for connection.
*/
private static void reduce_trace_shapes_at_tie_pins(Collection<Item> p_item_list, int p_own_net_no, ShapeSearchTree p_autoroute_tree)
{
for (Item curr_item : p_item_list)
{
if ((curr_item instanceof eu.mihosoft.freerouting.board.Pin) && curr_item.net_count() > 1)
{
Collection<Item> pin_contacts = curr_item.get_normal_contacts();
eu.mihosoft.freerouting.board.Pin curr_tie_pin = (eu.mihosoft.freerouting.board.Pin) curr_item;
for (Item curr_contact : pin_contacts)
{
if (!(curr_contact instanceof eu.mihosoft.freerouting.board.PolylineTrace) || curr_contact.contains_net(p_own_net_no))
{
continue;
}
p_autoroute_tree.reduce_trace_shape_at_tie_pin(curr_tie_pin, (eu.mihosoft.freerouting.board.PolylineTrace) curr_contact);
}
}
}
}
private boolean room_shape_is_thick(ObstacleExpansionRoom p_obstacle_room)
{
Item obstacle_item = p_obstacle_room.get_item();
int layer = p_obstacle_room.get_layer();
double obstacle_half_width;
if (obstacle_item instanceof eu.mihosoft.freerouting.board.Trace)
{
obstacle_half_width = ((eu.mihosoft.freerouting.board.Trace) obstacle_item).get_half_width()
+ this.search_tree.clearance_compensation_value(obstacle_item.clearance_class_no(), layer);
}
else if (obstacle_item instanceof eu.mihosoft.freerouting.board.Via)
{
TileShape via_shape = ((eu.mihosoft.freerouting.board.Via) obstacle_item).get_tree_shape_on_layer(this.search_tree, layer);
obstacle_half_width = 0.5 * via_shape.max_width();
}
else
{
System.out.println("MazeSearchAlgo. room_shape_is_thick: unexpected obstacle item");
obstacle_half_width = 0;
}
return obstacle_half_width >= this.ctrl.compensated_trace_half_width[layer];
}
/** Checks, if the room can be ripped and returns the rip up costs,
* which are > 0, if the room is ripped and -1, if no ripup was possible.
* If the previous room was also ripped and contained the same item
* or an item of the same connection, the result will be
* equal to ALREADY_RIPPED_COSTS
*/
private int check_ripup(MazeListElement p_list_element, Item p_obstacle_item, boolean p_door_is_small)
{
if (!p_obstacle_item.is_route())
{
return -1;
}
if (p_door_is_small)
{
// allow entering a via or trace, if its corresponding border segment is smaller than the current trace width
if (!enter_through_small_door(p_list_element, p_obstacle_item))
{
return -1;
}
}
CompleteExpansionRoom previous_room = p_list_element.door.other_room(p_list_element.next_room);
boolean room_was_shoved = p_list_element.adjustment != MazeSearchElement.Adjustment.NONE;
Item previous_item = null;
if (previous_room != null && previous_room instanceof ObstacleExpansionRoom)
{
previous_item = ((ObstacleExpansionRoom) previous_room).get_item();
}
if (room_was_shoved)
{
if (previous_item != null && previous_item != p_obstacle_item && previous_item.shares_net(p_obstacle_item))
{
// The ripped trace may start at a fork.
return -1;
}
}
else if (previous_item == p_obstacle_item)
{
return ALREADY_RIPPED_COSTS;
}
double fanout_via_cost_factor = 1.0;
double cost_factor = 1;
if (p_obstacle_item instanceof eu.mihosoft.freerouting.board.Trace)
{
eu.mihosoft.freerouting.board.Trace obstacle_trace = (eu.mihosoft.freerouting.board.Trace) p_obstacle_item;
cost_factor = obstacle_trace.get_half_width();
if (!this.ctrl.remove_unconnected_vias)
{
// protect traces between SMD-pins and fanout vias
fanout_via_cost_factor = calc_fanout_via_ripup_cost_factor(obstacle_trace);
}
}
else if (p_obstacle_item instanceof eu.mihosoft.freerouting.board.Via)
{
boolean look_if_fanout_via = !this.ctrl.remove_unconnected_vias;
Collection<Item> contact_list = p_obstacle_item.get_normal_contacts();
int contact_count = 0;
for (Item curr_contact : contact_list)
{
if (!(curr_contact instanceof eu.mihosoft.freerouting.board.Trace) || curr_contact.is_user_fixed())
{
return -1;
}
++contact_count;
eu.mihosoft.freerouting.board.Trace obstacle_trace = (eu.mihosoft.freerouting.board.Trace) curr_contact;
cost_factor = Math.max(cost_factor, obstacle_trace.get_half_width());
if (look_if_fanout_via)
{
double curr_fanout_via_cost_factor = calc_fanout_via_ripup_cost_factor(obstacle_trace);
if (curr_fanout_via_cost_factor > 1)
{
fanout_via_cost_factor = curr_fanout_via_cost_factor;
look_if_fanout_via = false;
}
}
}
if (fanout_via_cost_factor <= 1)
{
// not a fanout via
cost_factor *= 0.5 * Math.max(contact_count - 1, 0);
}
}
double ripup_cost = this.ctrl.ripup_costs * cost_factor;
double detour = 1;
if (fanout_via_cost_factor <= 1) // p_obstacle_item does not belong to a fanout
{
Connection obstacle_connection = Connection.get(p_obstacle_item);
if (obstacle_connection != null)
{
detour = obstacle_connection.get_detour();
}
}
boolean randomize = this.ctrl.ripup_pass_no >= 4 && this.ctrl.ripup_pass_no % 3 != 0;
if (randomize)
{
// shuffle the result to avoid repetitive loops
double random_number = this.random_generator.nextDouble();
double random_factor = 0.5 + random_number * random_number;
detour *= random_factor;
}
ripup_cost /= detour;
ripup_cost *= fanout_via_cost_factor;
int result = Math.max((int) ripup_cost, 1);
final int MAX_RIPUP_COSTS = Integer.MAX_VALUE / 100;
return Math.min(result, MAX_RIPUP_COSTS);
}
/**
* Return the addditional cost factor for ripping the trace, if it is connected to a fanout via
* or 1, if no fanout via was found.
*/
private static double calc_fanout_via_ripup_cost_factor(eu.mihosoft.freerouting.board.Trace p_trace)
{
final double FANOUT_COST_CONST = 20000;
Collection<Item> curr_end_contacts;
for (int i = 0; i < 2; ++i)
{
if (i == 0)
{
curr_end_contacts = p_trace.get_start_contacts();
}
else
{
curr_end_contacts = p_trace.get_end_contacts();
}
if (curr_end_contacts.size() != 1)
{
continue;
}
Item curr_trace_contact = curr_end_contacts.iterator().next();
boolean protect_fanout_via = false;
if (curr_trace_contact instanceof eu.mihosoft.freerouting.board.Pin && curr_trace_contact.first_layer() == curr_trace_contact.last_layer())
{
protect_fanout_via = true;
}
else if (curr_trace_contact instanceof PolylineTrace && curr_trace_contact.get_fixed_state() == eu.mihosoft.freerouting.board.FixedState.SHOVE_FIXED)
{
// look for shove fixed exit traces of SMD-pins
PolylineTrace contact_trace = (PolylineTrace) curr_trace_contact;
if (contact_trace.corner_count() == 2)
{
protect_fanout_via = true;
}
}
if (protect_fanout_via)
{
double fanout_via_cost_factor = p_trace.get_half_width() / p_trace.get_length();
fanout_via_cost_factor *= fanout_via_cost_factor;
fanout_via_cost_factor *= FANOUT_COST_CONST;
fanout_via_cost_factor = Math.max(fanout_via_cost_factor, 1);
return fanout_via_cost_factor;
}
}
return 1;
}
/**
* Shoves a trace room and expands the corresponding doors.
* Return false, if no door was expanded. In this case occupation of the door_section by ripup can be delayed
* to allow shoving the room fromm a different door section
*/
private boolean shove_trace_room(MazeListElement p_list_element, ObstacleExpansionRoom p_obstacle_room)
{
if (p_list_element.section_no_of_door != 0
&& p_list_element.section_no_of_door != p_list_element.door.maze_search_element_count() - 1)
{
// No delay of occupation necessesary because inner sections of a door are currently not shoved.
return true;
}
boolean result = false;
if (p_list_element.adjustment != MazeSearchElement.Adjustment.RIGHT)
{
Collection<MazeShoveTraceAlgo.DoorSection> left_to_door_section_list = new java.util.LinkedList<MazeShoveTraceAlgo.DoorSection>();
if (MazeShoveTraceAlgo.check_shove_trace_line(p_list_element, p_obstacle_room,
this.autoroute_engine.board, this.ctrl, false, left_to_door_section_list))
{
result = true;
}
for (MazeShoveTraceAlgo.DoorSection curr_left_door_section : left_to_door_section_list)
{
MazeSearchElement.Adjustment curr_adjustment;
if (curr_left_door_section.door.dimension == 2)
{
// the door is the link door to the next room
curr_adjustment = MazeSearchElement.Adjustment.LEFT;
}
else
{
curr_adjustment = MazeSearchElement.Adjustment.NONE;
}
expand_to_door_section(curr_left_door_section.door, curr_left_door_section.section_no,
curr_left_door_section.section_line, p_list_element,
0, curr_adjustment);
}
}
if (p_list_element.adjustment != MazeSearchElement.Adjustment.LEFT)
{
Collection<MazeShoveTraceAlgo.DoorSection> right_to_door_section_list = new java.util.LinkedList<MazeShoveTraceAlgo.DoorSection>();
if (MazeShoveTraceAlgo.check_shove_trace_line(p_list_element, p_obstacle_room,
this.autoroute_engine.board, this.ctrl, true, right_to_door_section_list))
{
result = true;
}
for (MazeShoveTraceAlgo.DoorSection curr_right_door_section : right_to_door_section_list)
{
MazeSearchElement.Adjustment curr_adjustment;
if (curr_right_door_section.door.dimension == 2)
{
// the door is the link door to the next room
curr_adjustment = MazeSearchElement.Adjustment.RIGHT;
}
else
{
curr_adjustment = MazeSearchElement.Adjustment.NONE;
}
expand_to_door_section(curr_right_door_section.door, curr_right_door_section.section_no,
curr_right_door_section.section_line, p_list_element,
0, curr_adjustment);
}
}
return result;
}
/**
* Checks, if the next roomm contains a destination pin, where evtl. neckdown is necessary.
* Return the neck down width in this case, or 0, if no such pin waas found,
*/
private double check_neck_down_at_dest_pin(CompleteExpansionRoom p_room)
{
Collection<TargetItemExpansionDoor> target_doors = p_room.get_target_doors();
for (TargetItemExpansionDoor curr_target_door : target_doors)
{
if (curr_target_door.item instanceof eu.mihosoft.freerouting.board.Pin)
{
return ((eu.mihosoft.freerouting.board.Pin) curr_target_door.item).get_trace_neckdown_halfwidth(p_room.get_layer());
}
}
return 0;
}
/**
* Returns the perpendicular projection of p_from_segment onto p_to_segment.
* Returns null, if the projection is empty.
*/
private static FloatLine segment_projection(FloatLine p_from_segment, FloatLine p_to_segment)
{
FloatLine check_segment = p_from_segment.adjust_direction(p_to_segment);
FloatLine first_projection = p_to_segment.segment_projection(check_segment);
FloatLine second_projection = p_to_segment.segment_projection_2(check_segment);
FloatLine result;
if (first_projection != null && second_projection != null)
{
FloatPoint result_a;
if (first_projection.a == p_to_segment.a || second_projection.a == p_to_segment.a)
{
result_a = p_to_segment.a;
}
else if (first_projection.a.distance_square(p_to_segment.a) <= second_projection.a.distance_square(p_to_segment.a))
{
result_a = first_projection.a;
}
else
{
result_a = second_projection.a;
}
FloatPoint result_b;
if (first_projection.b == p_to_segment.b || second_projection.b == p_to_segment.b)
{
result_b = p_to_segment.b;
}
else if (first_projection.b.distance_square(p_to_segment.b) <= second_projection.b.distance_square(p_to_segment.b))
{
result_b = first_projection.b;
}
else
{
result_b = second_projection.b;
}
result = new FloatLine(result_a, result_b);
}
else if (first_projection != null)
{
result = first_projection;
}
else
{
result = second_projection;
}
return result;
}
/**
* Checks, if the next room can be entered if the door of p_list_element is small.
* If p_ignore_item != null, p_ignore_item and all other items directly connected to p_ignore_item
* are ignored in the check.
*/
private boolean enter_through_small_door(MazeListElement p_list_element, Item p_ignore_item)
{
if (p_list_element.door.get_dimension() != 1)
{
return false;
}
TileShape door_shape = p_list_element.door.get_shape();
// Get the line of the 1 dimensional door.
Line door_line = null;
FloatPoint prev_corner = door_shape.corner_approx(0);
int corner_count = door_shape.border_line_count();
for (int i = 1; i < corner_count; ++i)
{
// skip lines of lenghth 0
FloatPoint next_corner = door_shape.corner_approx(i);
if (next_corner.distance_square(prev_corner) > 1)
{
door_line = door_shape.border_line(i - 1);
break;
}
prev_corner = next_corner;
}
if (door_line == null)
{
return false;
}
IntPoint door_center = door_shape.centre_of_gravity().round();
int curr_layer = p_list_element.next_room.get_layer();
int check_radius =
this.ctrl.compensated_trace_half_width[curr_layer] + AutorouteEngine.TRACE_WIDTH_TOLERANCE;
// create a perpendicular line segment of length 2 * check_radius through the door center
Line[] line_arr = new Line[3];
line_arr[0] = door_line.translate(check_radius);
line_arr[1] = new Line(door_center, door_line.direction().turn_45_degree(2));
line_arr[2] = door_line.translate(-check_radius);
Polyline check_polyline = new Polyline(line_arr);
TileShape check_shape = check_polyline.offset_shape(check_radius, 0);
int[] ignore_net_nos = new int[1];
ignore_net_nos[0] = this.ctrl.net_no;
Set<SearchTreeObject> overlapping_objects = new TreeSet<SearchTreeObject>();
this.autoroute_engine.autoroute_search_tree.overlapping_objects(check_shape, curr_layer,
ignore_net_nos, overlapping_objects);
for (SearchTreeObject curr_object : overlapping_objects)
{
if (!(curr_object instanceof Item) || curr_object == p_ignore_item)
{
continue;
}
Item curr_item = (Item) curr_object;
if (!curr_item.shares_net(p_ignore_item))
{
return false;
}
Set<Item> curr_contacts = curr_item.get_normal_contacts();
if (!curr_contacts.contains(p_ignore_item))
{
return false;
}
}
return true;
}
/** Checks entering a thick room from a via or trace through a small door (after ripup) */
private boolean check_leaving_ripped_item(MazeListElement p_list_element)
{
if (!(p_list_element.door instanceof ExpansionDoor))
{
return false;
}
ExpansionDoor curr_door = (ExpansionDoor) p_list_element.door;
CompleteExpansionRoom from_room = curr_door.other_room(p_list_element.next_room);
if (!(from_room instanceof ObstacleExpansionRoom))
{
return false;
}
Item curr_item = ((ObstacleExpansionRoom) from_room).get_item();
if (!curr_item.is_route())
{
return false;
}
return enter_through_small_door(p_list_element, curr_item);
}
/**
* The eu.mihosoft.freerouting.autoroute engine of this expansion algorithm.
*/
public final AutorouteEngine autoroute_engine;
final AutorouteControl ctrl;
/**
* The seach tree for expanding. It is the tree compensated for the current net.
*/
private final ShapeSearchTree search_tree;
/**
* The queue of of expanded elements used in this search algorithm.
*/
final java.util.SortedSet<MazeListElement> maze_expansion_list;
/**
* Used for calculating of a good lower bound for the distance between a new MazeExpansionElement
* and the destination set of the expansion.
*/
final DestinationDistance destination_distance;
/**
* The destination door found by the expanding algorithm.
*/
private ExpandableObject destination_door = null;
private int section_no_of_destination_door = 0;
private final java.util.Random random_generator = new java.util.Random();
private static final int ALREADY_RIPPED_COSTS = 1;
/**
* The result type of MazeSearchAlgo.find_connection
*/
public static class Result
{
Result(ExpandableObject p_destination_door, int p_section_no_of_door)
{
destination_door = p_destination_door;
section_no_of_door = p_section_no_of_door;
}
public final ExpandableObject destination_door;
public final int section_no_of_door;
}
/**
* Used for the result of MazeShoveViaAlgo.check_shove_via and MazeShoveThinRoomAlgo.check_shove_thin_room.
*/
static class ShoveResult
{
/** The opposite door to be expanded */
final ExpansionDoor opposite_door;
/** The doors at the adjusted edge of the room shape to be expanded. */
final Collection<ExpansionDoor> side_doors;
/** The passing point of a trace through the from_door after adjustment. */
final FloatPoint from_door_passing_point;
/** The passing point of a trace through the opposite door after adjustment. */
final FloatPoint opposite_door_passing_point;
ShoveResult(ExpansionDoor p_opposite_door, Collection<ExpansionDoor> p_side_doors,
FloatPoint p_from_door_passing_point, FloatPoint p_opposite_door_passing_point)
{
opposite_door = p_opposite_door;
side_doors = p_side_doors;
from_door_passing_point = p_from_door_passing_point;
opposite_door_passing_point = p_opposite_door_passing_point;
}
}
}