/*
* 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 interactive;
import datastructures.TimeLimit;
import geometry.planar.Area;
import geometry.planar.FloatPoint;
import geometry.planar.IntBox;
import geometry.planar.IntOctagon;
import geometry.planar.IntPoint;
import geometry.planar.Vector;
import geometry.planar.Point;
import geometry.planar.Polyline;
import geometry.planar.Ellipse;
import java.awt.Graphics;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import library.Padstack;
import rules.ViaRule;
import rules.ViaInfo;
import rules.Net;
import board.AngleRestriction;
import board.Trace;
import board.ConductionArea;
import board.DrillItem;
import board.Item;
import board.PolylineTrace;
import board.RoutingBoard;
import board.ItemSelectionFilter;
import board.TestLevel;
import board.Unit;
import boardgraphics.GraphicsContext;
/**
*
* Functionality for interactive routing.
*
* @author Alfons Wirtz
*/
public class Route
{
/**
* Starts routing a connection.
* p_pen_half_width_arr is provided because it may be different from
* the half width array in p_board.rules.
*/
public Route(Point p_start_corner, int p_layer, int[] p_pen_half_width_arr, boolean[] p_layer_active_arr, int[] p_net_no_arr,
int p_clearance_class, ViaRule p_via_rule, boolean p_push_enabled,
int p_trace_tidy_width, int p_pull_tight_accuracy, Item p_start_item, Set<Item> p_target_set,
RoutingBoard p_board, boolean p_is_stitch_mode, boolean p_with_neckdown, boolean p_via_snap_to_smd_center,
boolean p_hilight_shove_failing_obstacle)
{
board = p_board;
layer = p_layer;
if (p_push_enabled)
{
max_shove_trace_recursion_depth = 20;
max_shove_via_recursion_depth = 8;
max_spring_over_recursion_depth = 5;
}
else
{
max_shove_trace_recursion_depth = 0;
max_shove_via_recursion_depth = 0;
max_spring_over_recursion_depth = 0;
}
trace_tidy_width = p_trace_tidy_width;
pull_tight_accuracy = p_pull_tight_accuracy;
prev_corner = p_start_corner;
net_no_arr = p_net_no_arr;
pen_half_width_arr = p_pen_half_width_arr;
layer_active = p_layer_active_arr;
clearance_class = p_clearance_class;
via_rule = p_via_rule;
start_item = p_start_item;
target_set = p_target_set;
is_stitch_mode = p_is_stitch_mode;
with_neckdown = p_with_neckdown;
via_snap_to_smd_center = p_via_snap_to_smd_center;
hilight_shove_failing_obstacle = p_hilight_shove_failing_obstacle;
if (p_board.get_test_level() == TestLevel.RELEASE_VERSION)
{
this.pull_tight_time_limit = PULL_TIGHT_TIME_LIMIT;
}
else
{
this.pull_tight_time_limit = 0;
}
calculate_target_points_and_areas();
swap_pin_infos = calculate_swap_pin_infos();
}
/**
* Append a line to the trace routed so far.
* Return true, if the route is completed by connecting
* to a target.
*/
public boolean next_corner(FloatPoint p_corner)
{
if (!this.layer_active[this.layer])
{
return false;
}
IntPoint curr_corner = p_corner.round();
if (!(board.contains(prev_corner) && board.contains(curr_corner) && board.layer_structure.arr[this.layer].is_signal))
{
return false;
}
if (curr_corner.equals(prev_corner))
{
return false;
}
if (nearest_target_item instanceof DrillItem)
{
DrillItem target = (DrillItem) nearest_target_item;
if (this.prev_corner.equals(target.get_center()))
{
return true; // connection already completed at prev_corner.
}
}
this.shove_failing_obstacle = null;
AngleRestriction angle_restriction = this.board.rules.get_trace_angle_restriction();
if (angle_restriction != AngleRestriction.NONE && !(prev_corner instanceof IntPoint))
{
return false;
}
if (angle_restriction == AngleRestriction.NINETY_DEGREE)
{
curr_corner = curr_corner.orthogonal_projection((IntPoint) prev_corner);
}
else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
{
curr_corner = curr_corner.fortyfive_degree_projection((IntPoint) prev_corner);
}
Item end_routing_item = board.pick_nearest_routing_item(prev_corner, this.layer, null);
// look for a nearby item of this net, which is not connected to end_routing_item.
nearest_target_item = board.pick_nearest_routing_item(curr_corner, this.layer, end_routing_item);
TimeLimit check_forced_trace_time_limit;
if (is_stitch_mode || this.board.get_test_level() != TestLevel.RELEASE_VERSION)
{
// because no check before inserting in this case
check_forced_trace_time_limit = null;
}
else
{
check_forced_trace_time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
}
// tests.Validate.check("before insert", board);
Point ok_point = board.insert_forced_trace_segment(prev_corner,
curr_corner, pen_half_width_arr[layer], layer, net_no_arr, clearance_class,
max_shove_trace_recursion_depth, max_shove_via_recursion_depth, max_spring_over_recursion_depth,
trace_tidy_width, pull_tight_accuracy, !is_stitch_mode, check_forced_trace_time_limit);
// tests.Validate.check("after insert", board);
if (ok_point == prev_corner && this.with_neckdown)
{
ok_point = try_neckdown_at_start(curr_corner);
}
if (ok_point == prev_corner && this.with_neckdown)
{
ok_point = try_neckdown_at_end(this.prev_corner, curr_corner);
}
if (ok_point == null)
{
// database may be damaged, restore previous situation
board.undo(null);
// end routing in case it is dynamic
return (!is_stitch_mode);
}
if (ok_point == prev_corner)
{
set_shove_failing_obstacle(board.get_shove_failing_obstacle());
return false;
}
this.prev_corner = ok_point;
// check, if a target is reached
boolean route_completed = false;
if (ok_point == curr_corner)
{
route_completed = connect_to_target(curr_corner);
}
IntOctagon tidy_clip_shape;
if (trace_tidy_width == Integer.MAX_VALUE)
{
tidy_clip_shape = null;
}
else if (trace_tidy_width == 0)
{
tidy_clip_shape = IntOctagon.EMPTY;
}
else
{
tidy_clip_shape = ok_point.surrounding_octagon().enlarge(trace_tidy_width);
}
int[] opt_net_no_arr;
if (max_shove_trace_recursion_depth <= 0)
{
opt_net_no_arr = net_no_arr;
}
else
{
opt_net_no_arr = new int[0];
}
if (route_completed)
{
this.board.reduce_nets_of_route_items();
for (int curr_net_no : this.net_no_arr)
{
this.board.combine_traces(curr_net_no);
}
}
else
{
calc_nearest_target_point(this.prev_corner.to_float());
}
board.opt_changed_area(opt_net_no_arr, tidy_clip_shape, pull_tight_accuracy,
null, null, pull_tight_time_limit, ok_point, layer);
return route_completed;
}
/**
* Changing the layer in interactive route and inserting a via.
* Returns false, if changing the layer was not possible.
*/
public boolean change_layer(int p_to_layer)
{
if (this.layer == p_to_layer)
{
return true;
}
if (p_to_layer < 0 || p_to_layer >= this.layer_active.length)
{
System.out.println("Route.change_layer: p_to_layer out of range");
return false;
}
if (!this.layer_active[p_to_layer])
{
return false;
}
if (this.via_rule == null)
{
return false;
}
this.shove_failing_obstacle = null;
if (this.via_snap_to_smd_center)
{
boolean snapped_to_smd_center = snap_to_smd_center(p_to_layer);
if (!snapped_to_smd_center)
{
snap_to_smd_center(this.layer);
}
}
boolean result = true;
int min_layer = Math.min(this.layer, p_to_layer);
int max_layer = Math.max(this.layer, p_to_layer);
boolean via_found = false;
for (int i = 0; i < this.via_rule.via_count(); ++i)
{
ViaInfo curr_via_info = this.via_rule.get_via(i);
Padstack curr_via_padstack = curr_via_info.get_padstack();
if (min_layer < curr_via_padstack.from_layer() || max_layer > curr_via_padstack.to_layer())
{
continue;
}
// make the current situation restorable by undo
board.generate_snapshot();
result = board.forced_via(curr_via_info, this.prev_corner, this.net_no_arr, clearance_class,
pen_half_width_arr, max_shove_trace_recursion_depth,
0, this.trace_tidy_width, this.pull_tight_accuracy, pull_tight_time_limit);
if (result)
{
via_found = true;
break;
}
set_shove_failing_obstacle(board.get_shove_failing_obstacle());
board.undo(null);
}
if (via_found)
{
this.layer = p_to_layer;
}
return result;
}
/**
* Snaps to the center of an smd pin, if the location location on p_layer is inside an smd pin of the own net,
*/
private boolean snap_to_smd_center(int p_layer)
{
ItemSelectionFilter selection_filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.PINS);
java.util.Collection<Item> picked_items = board.pick_items(this.prev_corner, p_layer, selection_filter);
board.Pin found_smd_pin = null;
for (Item curr_item : picked_items)
{
if (curr_item instanceof board.Pin && curr_item.shares_net_no(this.net_no_arr))
{
board.Pin curr_pin = (board.Pin) curr_item;
if (curr_pin.first_layer() == p_layer && curr_pin.last_layer() == p_layer)
{
found_smd_pin = curr_pin;
break;
}
}
}
if (found_smd_pin == null)
{
return false;
}
Point pin_center = found_smd_pin.get_center();
if (!(pin_center instanceof IntPoint))
{
return false;
}
IntPoint to_corner = (IntPoint) pin_center;
if (this.connect(this.prev_corner, to_corner))
{
this.prev_corner = to_corner;
}
return true;
}
/**
* If p_from_point is already on a target item, a connection
* to the target is made and true returned.
*/
private boolean connect_to_target(IntPoint p_from_point)
{
if (nearest_target_item != null && target_set != null && !target_set.contains(nearest_target_item))
{
nearest_target_item = null;
}
if (nearest_target_item == null || !nearest_target_item.shares_net_no(this.net_no_arr))
{
return false;
}
boolean route_completed = false;
Point connection_point = null;
if (nearest_target_item instanceof DrillItem)
{
DrillItem target = (DrillItem) nearest_target_item;
connection_point = target.get_center();
}
else if (nearest_target_item instanceof PolylineTrace)
{
return board.connect_to_trace(p_from_point, (PolylineTrace) nearest_target_item,
this.pen_half_width_arr[layer], this.clearance_class);
}
else if (nearest_target_item instanceof ConductionArea)
{
connection_point = p_from_point;
}
if (connection_point != null && connection_point instanceof IntPoint)
{
route_completed = connect(p_from_point, (IntPoint) connection_point);
}
return route_completed;
}
/**
* Tries to make a trace connection from p_from_point to p_to_point according to the angle restriction.
* Returns true, if the connection succeeded.
*/
private boolean connect(Point p_from_point, IntPoint p_to_point)
{
Point[] corners = angled_connection(p_from_point, p_to_point);
boolean connection_succeeded = true;
for (int i = 1; i < corners.length; ++i)
{
Point from_corner = corners[i - 1];
Point to_corner = corners[i];
TimeLimit time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
while (!from_corner.equals(to_corner))
{
Point curr_ok_point = board.insert_forced_trace_segment(from_corner,
to_corner, pen_half_width_arr[layer], this.layer, net_no_arr,
clearance_class, max_shove_trace_recursion_depth,
max_shove_via_recursion_depth, max_spring_over_recursion_depth,
trace_tidy_width, pull_tight_accuracy, !is_stitch_mode, time_limit);
if (curr_ok_point == null)
{
// database may be damaged, restore previous situation
board.undo(null);
return true;
}
if (curr_ok_point.equals(from_corner) && this.with_neckdown)
{
curr_ok_point = try_neckdown_at_end(from_corner, to_corner);
}
if (curr_ok_point.equals(from_corner))
{
this.prev_corner = from_corner;
connection_succeeded = false;
break;
}
from_corner = curr_ok_point;
}
}
return connection_succeeded;
}
/** Calculates the nearest layer of the nearest target item
* to this.layer.
*/
public int nearest_target_layer()
{
if (nearest_target_item == null)
{
return this.layer;
}
int result;
int first_layer = nearest_target_item.first_layer();
int last_layer = nearest_target_item.last_layer();
if (this.layer < first_layer)
{
result = first_layer;
}
else if (this.layer > last_layer)
{
result = last_layer;
}
else
{
result = this.layer;
}
return result;
}
/**
* Returns all pins, which can be reached by a pin swap from a srtart or target pin.
*/
private Set<SwapPinInfo> calculate_swap_pin_infos()
{
Set<SwapPinInfo> result = new java.util.TreeSet<SwapPinInfo>();
if (this.target_set == null)
{
return result;
}
for (Item curr_item : this.target_set)
{
if (curr_item instanceof board.Pin)
{
Collection<board.Pin> curr_swapppable_pins = ((board.Pin) curr_item).get_swappable_pins();
for (board.Pin curr_swappable_pin : curr_swapppable_pins)
{
result.add(new SwapPinInfo(curr_swappable_pin));
}
}
}
// add the from item, if it is a pin
ItemSelectionFilter selection_filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.PINS);
java.util.Collection<Item> picked_items = board.pick_items(this.prev_corner, this.layer, selection_filter);
for (Item curr_item : picked_items)
{
if (curr_item instanceof board.Pin)
{
Collection<board.Pin> curr_swapppable_pins = ((board.Pin) curr_item).get_swappable_pins();
for (board.Pin curr_swappable_pin : curr_swapppable_pins)
{
result.add(new SwapPinInfo(curr_swappable_pin));
}
}
}
return result;
}
/**
* Hilights the targets and draws the incomplete.
*/
public void draw(Graphics p_graphics, GraphicsContext p_graphics_context)
{
if (this.hilight_shove_failing_obstacle && this.shove_failing_obstacle != null)
{
this.shove_failing_obstacle.draw(p_graphics, p_graphics_context, p_graphics_context.get_violations_color(), 1);
}
if (target_set == null || net_no_arr.length < 1)
{
return;
}
Net curr_net = board.rules.nets.get(net_no_arr[0]);
if (curr_net == null)
{
return;
}
java.awt.Color highlight_color = p_graphics_context.get_hilight_color();
double highligt_color_intensity = p_graphics_context.get_hilight_color_intensity();
// hilight the swapppable pins and their incompletes
for (SwapPinInfo curr_info : this.swap_pin_infos)
{
curr_info.pin.draw(p_graphics, p_graphics_context, highlight_color, 0.3 * highligt_color_intensity);
if (curr_info.incomplete != null)
{
// draw the swap pin incomplete
FloatPoint[] draw_points = new FloatPoint[2];
draw_points[0] = curr_info.incomplete.a;
draw_points[1] = curr_info.incomplete.b;
java.awt.Color draw_color = p_graphics_context.get_incomplete_color();
p_graphics_context.draw(draw_points, 1, draw_color, p_graphics, highligt_color_intensity);
}
}
// hilight the target set
for (Item curr_item : target_set)
{
if (!(curr_item instanceof ConductionArea))
{
curr_item.draw(p_graphics, p_graphics_context, highlight_color, highligt_color_intensity);
}
}
FloatPoint from_corner = this.prev_corner.to_float();
if (nearest_target_point != null && prev_corner != null)
{
boolean curr_length_matching_ok = true; // used for drawing the incomplete as violation
double max_trace_length = curr_net.get_class().get_maximum_trace_length();
double min_trace_length = curr_net.get_class().get_minimum_trace_length();
double length_matching_color_intensity = p_graphics_context.get_length_matching_area_color_intensity();
if (max_trace_length > 0 || min_trace_length > 0 && length_matching_color_intensity > 0)
{
// draw the length matching area
double trace_length_add = from_corner.distance(this.prev_corner.to_float());
// trace_length_add is != 0 only in stitching mode.
if (max_trace_length <= 0)
{
// max_trace_length not provided. Create an ellipse containing the whole board.
max_trace_length = 0.3 * geometry.planar.Limits.CRIT_INT;
}
double curr_max_trace_length = max_trace_length - (curr_net.get_trace_length() + trace_length_add);
double curr_min_trace_length = min_trace_length - (curr_net.get_trace_length() + trace_length_add);
double incomplete_length = nearest_target_point.distance(from_corner);
if (incomplete_length < curr_max_trace_length && min_trace_length <= max_trace_length)
{
Vector delta = nearest_target_point.round().difference_by(prev_corner);
double rotation = delta.angle_approx();
FloatPoint center = from_corner.middle_point(nearest_target_point);
double bigger_radius = 0.5 * curr_max_trace_length;
// dist_focus_to_center^2 = bigger_radius^2 - smaller_radius^2
double smaller_radius = 0.5 * Math.sqrt(curr_max_trace_length * curr_max_trace_length - incomplete_length * incomplete_length);
int ellipse_count;
if (min_trace_length <= 0 || incomplete_length >= curr_min_trace_length)
{
ellipse_count = 1;
}
else
{
// display an ellipse ring.
ellipse_count = 2;
}
Ellipse[] ellipse_arr = new Ellipse[ellipse_count];
ellipse_arr[0] = new Ellipse(center, rotation, bigger_radius, smaller_radius);
IntBox bounding_box = new IntBox(prev_corner.to_float().round(), nearest_target_point.round());
bounding_box = bounding_box.offset(curr_max_trace_length - incomplete_length);
board.join_graphics_update_box(bounding_box);
if (ellipse_count == 2)
{
bigger_radius = 0.5 * curr_min_trace_length;
smaller_radius = 0.5 * Math.sqrt(curr_min_trace_length * curr_min_trace_length - incomplete_length * incomplete_length);
ellipse_arr[1] = new Ellipse(center, rotation, bigger_radius, smaller_radius);
}
p_graphics_context.fill_ellipse_arr(ellipse_arr, p_graphics,
p_graphics_context.get_length_matching_area_color(), length_matching_color_intensity);
}
else
{
curr_length_matching_ok = false;
}
}
// draw the incomplete
FloatPoint[] draw_points = new FloatPoint[2];
draw_points[0] = from_corner;
draw_points[1] = nearest_target_point;
java.awt.Color draw_color = p_graphics_context.get_incomplete_color();
double draw_width = Math.min (this.board.communication.get_resolution(Unit.MIL), 100); // problem with low resolution on Kicad
if (!curr_length_matching_ok)
{
draw_color = p_graphics_context.get_violations_color();
draw_width *= 3;
}
p_graphics_context.draw(draw_points, draw_width, draw_color, p_graphics, highligt_color_intensity);
if (this.nearest_target_item != null && !this.nearest_target_item.is_on_layer(this.layer))
{
// draw a marker to indicate the layer change.
NetIncompletes.draw_layer_change_marker(draw_points[0], 4 * pen_half_width_arr[0], p_graphics, p_graphics_context);
}
}
}
/**
* Makes a connection polygon from p_from_point to p_to_point
* whose lines fulfill the angle restriction.
*/
private Point[] angled_connection(Point p_from_point, Point p_to_point)
{
IntPoint add_corner = null;
if (p_from_point instanceof IntPoint && p_to_point instanceof IntPoint)
{
AngleRestriction angle_restriction = this.board.rules.get_trace_angle_restriction();
if (angle_restriction == AngleRestriction.NINETY_DEGREE)
{
add_corner = ((IntPoint) p_from_point).ninety_degree_corner((IntPoint) p_to_point, true);
}
else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
{
add_corner = ((IntPoint) p_from_point).fortyfive_degree_corner((IntPoint) p_to_point, true);
}
}
int new_corner_count = 2;
if (add_corner != null)
{
++new_corner_count;
}
Point[] result = new Point[new_corner_count];
result[0] = p_from_point;
if (add_corner != null)
{
result[1] = add_corner;
}
result[result.length - 1] = p_to_point;
return result;
}
/**
* Calculates a list of the center points of DrillItems,
* end points of traces and areas of ConductionAreas in the target set.
*/
private void calculate_target_points_and_areas()
{
target_points = new LinkedList<TargetPoint>();
target_traces_and_areas = new LinkedList<Item>();
if (target_set == null)
{
return;
}
Iterator<Item> it = target_set.iterator();
while (it.hasNext())
{
Item curr_ob = it.next();
if (curr_ob instanceof DrillItem)
{
Point curr_point = ((DrillItem) curr_ob).get_center();
target_points.add(new TargetPoint(curr_point.to_float(), curr_ob));
}
else if (curr_ob instanceof Trace || curr_ob instanceof ConductionArea)
{
target_traces_and_areas.add(curr_ob);
}
}
}
public Point get_last_corner()
{
return prev_corner;
}
public boolean is_layer_active(int p_layer)
{
if (p_layer < 0 || p_layer >= layer_active.length)
{
return false;
}
return layer_active[p_layer];
}
/**
* The nearest point is used for drowing the incomplete
*/
void calc_nearest_target_point(FloatPoint p_from_point)
{
double min_dist = Double.MAX_VALUE;
FloatPoint nearest_point = null;
Item nearest_item = null;
for (TargetPoint curr_target_point : target_points)
{
double curr_dist = p_from_point.distance(curr_target_point.location);
if (curr_dist < min_dist)
{
min_dist = curr_dist;
nearest_point = curr_target_point.location;
nearest_item = curr_target_point.item;
}
}
Iterator<Item> it = target_traces_and_areas.iterator();
while (it.hasNext())
{
Item curr_item = it.next();
if (curr_item instanceof PolylineTrace)
{
PolylineTrace curr_trace = (PolylineTrace) curr_item;
Polyline curr_polyline = curr_trace.polyline();
if (curr_polyline.bounding_box().distance(p_from_point) < min_dist)
{
FloatPoint curr_nearest_point = curr_polyline.nearest_point_approx(p_from_point);
double curr_dist = p_from_point.distance(curr_nearest_point);
if (curr_dist < min_dist)
{
min_dist = curr_dist;
nearest_point = curr_nearest_point;
nearest_item = curr_trace;
}
}
}
else if (curr_item instanceof ConductionArea && curr_item.tile_shape_count() > 0)
{
ConductionArea curr_conduction_area = (ConductionArea) curr_item;
Area curr_area = curr_conduction_area.get_area();
if (curr_area.bounding_box().distance(p_from_point) < min_dist)
{
FloatPoint curr_nearest_point = curr_area.nearest_point_approx(p_from_point);
double curr_dist = p_from_point.distance(curr_nearest_point);
if (curr_dist < min_dist)
{
min_dist = curr_dist;
nearest_point = curr_nearest_point;
nearest_item = curr_conduction_area;
}
}
}
}
if (nearest_point == null)
{
return; // target set is empty
}
nearest_target_point = nearest_point;
nearest_target_item = nearest_item;
// join the graphics update box by the nearest item, so that the incomplete
// is completely displayed.
board.join_graphics_update_box(nearest_item.bounding_box());
}
private void set_shove_failing_obstacle(Item p_item)
{
this.shove_failing_obstacle = p_item;
if (p_item != null)
{
this.board.join_graphics_update_box(p_item.bounding_box());
}
}
/**
* If the routed starts at a pin and the route failed with the normal trace width,
* another try with the smalllest pin width is done.
* Returns the ok_point of the try, which is this.prev_point, if the try failed.
*/
private Point try_neckdown_at_start(IntPoint p_to_corner)
{
if (!(this.start_item instanceof board.Pin))
{
return this.prev_corner;
}
board.Pin start_pin = (board.Pin) this.start_item;
if (!start_pin.is_on_layer(this.layer))
{
return this.prev_corner;
}
FloatPoint pin_center = start_pin.get_center().to_float();
double curr_clearance =
this.board.rules.clearance_matrix.value(this.clearance_class, start_pin.clearance_class_no(), this.layer);
double pin_neck_down_distance =
2 * (0.5 * start_pin.get_max_width(this.layer) + curr_clearance);
if (pin_center.distance(this.prev_corner.to_float()) >= pin_neck_down_distance)
{
return this.prev_corner;
}
int neck_down_halfwidth = start_pin.get_trace_neckdown_halfwidth(this.layer);
if (neck_down_halfwidth >= this.pen_half_width_arr[this.layer])
{
return this.prev_corner;
}
// check, that the neck_down started inside the pin shape
if (!this.prev_corner.equals(start_pin.get_center()))
{
Item picked_item = this.board.pick_nearest_routing_item(this.prev_corner, this.layer, null);
if (picked_item instanceof Trace)
{
if (((Trace) picked_item).get_half_width() > neck_down_halfwidth)
{
return this.prev_corner;
}
}
}
TimeLimit time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
Point ok_point = board.insert_forced_trace_segment(prev_corner,
p_to_corner, neck_down_halfwidth, layer, net_no_arr, clearance_class, max_shove_trace_recursion_depth,
max_shove_via_recursion_depth, max_spring_over_recursion_depth, trace_tidy_width,
pull_tight_accuracy, !is_stitch_mode, time_limit);
return ok_point;
}
/**
* If the routed ends at a pin and the route failed with the normal trace width,
* another try with the smalllest pin width is done.
* Returns the ok_point of the try, which is p_from_corner, if the try failed.
*/
private Point try_neckdown_at_end(Point p_from_corner, Point p_to_corner)
{
if (!(this.nearest_target_item instanceof board.Pin))
{
return p_from_corner;
}
board.Pin target_pin = (board.Pin) this.nearest_target_item;
if (!target_pin.is_on_layer(this.layer))
{
return p_from_corner;
}
FloatPoint pin_center = target_pin.get_center().to_float();
double curr_clearance =
this.board.rules.clearance_matrix.value(this.clearance_class, target_pin.clearance_class_no(), this.layer);
double pin_neck_down_distance =
2 * (0.5 * target_pin.get_max_width(this.layer) + curr_clearance);
if (pin_center.distance(p_from_corner.to_float()) >= pin_neck_down_distance)
{
return p_from_corner;
}
int neck_down_halfwidth = target_pin.get_trace_neckdown_halfwidth(this.layer);
if (neck_down_halfwidth >= this.pen_half_width_arr[this.layer])
{
return p_from_corner;
}
TimeLimit time_limit = new TimeLimit(CHECK_FORCED_TRACE_TIME_LIMIT);
Point ok_point = board.insert_forced_trace_segment(p_from_corner,
p_to_corner, neck_down_halfwidth, layer, net_no_arr, clearance_class,
max_shove_trace_recursion_depth, max_shove_via_recursion_depth,
max_spring_over_recursion_depth, trace_tidy_width,
pull_tight_accuracy, !is_stitch_mode, time_limit);
return ok_point;
}
/** The net numbers used for routing */
final int[] net_no_arr;
private Point prev_corner;
private int layer;
private final Item start_item;
private final Set<Item> target_set;
/** Pins, which can be reached by a pin swap by a target pin. */
private final Set<SwapPinInfo> swap_pin_infos;
private Collection<TargetPoint> target_points; // from drill_items
private Collection<Item> target_traces_and_areas; // from traces and conduction areas
private FloatPoint nearest_target_point;
private Item nearest_target_item;
private final int[] pen_half_width_arr;
private final boolean[] layer_active;
private final int clearance_class;
private final ViaRule via_rule;
private final int max_shove_trace_recursion_depth;
private final int max_shove_via_recursion_depth;
private final int max_spring_over_recursion_depth;
private final int trace_tidy_width;
private final int pull_tight_accuracy;
private final RoutingBoard board;
private final boolean is_stitch_mode;
private final boolean with_neckdown;
private final boolean via_snap_to_smd_center;
private final boolean hilight_shove_failing_obstacle;
private final int pull_tight_time_limit;
private Item shove_failing_obstacle = null;
/** The time limit in milliseconds for the pull tight algorithm */
private static final int CHECK_FORCED_TRACE_TIME_LIMIT = 3000;
/** The time limit in milliseconds for the pull tight algorithm */
private static final int PULL_TIGHT_TIME_LIMIT = 2000;
private static class TargetPoint
{
TargetPoint(FloatPoint p_location, Item p_item)
{
location = p_location;
item = p_item;
}
final FloatPoint location;
final Item item;
}
private class SwapPinInfo implements Comparable<SwapPinInfo>
{
SwapPinInfo(board.Pin p_pin)
{
pin = p_pin;
incomplete = null;
if (p_pin.is_connected() || p_pin.net_count() != 1)
{
return;
}
// calculate the incomplete of p_pin
FloatPoint pin_center = p_pin.get_center().to_float();
double min_dist = Double.MAX_VALUE;
FloatPoint nearest_point = null;
Collection<Item> net_items = board.get_connectable_items(p_pin.get_net_no(0));
for (Item curr_item : net_items)
{
if (curr_item == this.pin || !(curr_item instanceof DrillItem))
{
continue;
}
FloatPoint curr_point = ((DrillItem) curr_item).get_center().to_float();
double curr_dist = pin_center.distance_square(curr_point);
if (curr_dist < min_dist)
{
min_dist = curr_dist;
nearest_point = curr_point;
}
}
if (nearest_point != null)
{
incomplete = new geometry.planar.FloatLine(pin_center, nearest_point);
}
}
public int compareTo(SwapPinInfo p_other)
{
return this.pin.compareTo(p_other.pin);
}
final board.Pin pin;
geometry.planar.FloatLine incomplete;
}
}