/* * 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. * * PullTightAlgo.java * * Created on 19. Juli 2003, 12:42 */ package board; import geometry.planar.FloatPoint; import geometry.planar.IntOctagon; import geometry.planar.IntPoint; import geometry.planar.Line; import geometry.planar.Point; import geometry.planar.Polyline; import geometry.planar.Side; import geometry.planar.TileShape; import java.util.Collection; import java.util.Iterator; import java.util.Set; import datastructures.Signum; import datastructures.Stoppable; import datastructures.TimeLimit; import autoroute.AutorouteControl.ExpansionCostFactor; /** * Class with functionality for optimising traces and vias. * * @author Alfons Wirtz */ public abstract class PullTightAlgo { /** * Returns a new instance of PullTightAlgo. * If p_only_net_no > 0, only traces with net number p_not_no are optimized. * 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. */ static PullTightAlgo get_instance(RoutingBoard p_board, int[] p_only_net_no_arr, IntOctagon p_clip_shape, int p_min_translate_dist, Stoppable p_stoppable_thread, int p_time_limit, Point p_keep_point, int p_keep_point_layer) { PullTightAlgo result; AngleRestriction angle_restriction = p_board.rules.get_trace_angle_restriction(); if (angle_restriction == AngleRestriction.NINETY_DEGREE) { result = new PullTightAlgo90(p_board, p_only_net_no_arr, p_stoppable_thread, p_time_limit, p_keep_point, p_keep_point_layer); } else if (angle_restriction == AngleRestriction.FORTYFIVE_DEGREE) { result = new PullTightAlgo45(p_board, p_only_net_no_arr, p_stoppable_thread, p_time_limit, p_keep_point, p_keep_point_layer); } else { result = new PullTightAlgoAnyAngle(p_board, p_only_net_no_arr, p_stoppable_thread, p_time_limit, p_keep_point, p_keep_point_layer); } result.curr_clip_shape = p_clip_shape; result.min_translate_dist = Math.max(p_min_translate_dist, 100); return result; } /** Creates a new instance of PullTightAlgo */ PullTightAlgo(RoutingBoard p_board, int[] p_only_net_no_arr, Stoppable p_stoppable_thread, int p_time_limit, Point p_keep_point, int p_keep_point_layer) { board = p_board; only_net_no_arr = p_only_net_no_arr; stoppable_thread = p_stoppable_thread; if (p_time_limit > 0) { this.time_limit = new TimeLimit(p_time_limit); } else { this.time_limit = null; } this.keep_point = p_keep_point; this.keep_point_layer = p_keep_point_layer; } /** * Function for optimizing the route in an internal marked area. * If p_clip_shape != null, the optimizing area is restricted to p_clip_shape. * p_trace_cost_arr is used for optimizing vias and may be null. */ void opt_changed_area(ExpansionCostFactor[] p_trace_cost_arr) { if (board.changed_area == null) { return; } boolean something_changed = true; // starting with curr_min_translate_dist big is a try to // avoid fine approximation at the beginning to avoid // problems with dog ears while (something_changed) { something_changed = false; for (int i = 0; i < board.get_layer_count(); ++i) { IntOctagon changed_region = board.changed_area.get_area(i); if (changed_region.is_empty()) { continue; } board.changed_area.set_empty(i); board.join_graphics_update_box(changed_region.bounding_box()); double changed_area_offset = 1.5 * (board.rules.clearance_matrix.max_value(i) + 2 * board.rules.get_max_trace_half_width()); changed_region = changed_region.enlarge(changed_area_offset); // search in the ShapeSearchTree for all overlapping traces // with clip_shape on layer i Collection<SearchTreeObject> items = board.overlapping_objects(changed_region, i); Iterator<SearchTreeObject> it = items.iterator(); while (it.hasNext()) { if (this.is_stop_requested()) { return; } SearchTreeObject curr_ob = it.next(); if (curr_ob instanceof PolylineTrace) { PolylineTrace curr_trace = (PolylineTrace) curr_ob; if (curr_trace.pull_tight(this)) { something_changed = true; if (this.split_traces_at_keep_point()) { break; } } else if (smoothen_end_corners_at_trace_1(curr_trace)) { something_changed = true; break; // because items may be removed } } else if (curr_ob instanceof Via && p_trace_cost_arr != null) { if (OptViaAlgo.opt_via_location(this.board, (Via) curr_ob, p_trace_cost_arr, this.min_translate_dist, 10)) { something_changed = true; } } } } } } /** * Function for optimizing a single trace polygon * p_contact_pins are the pins at the end corners of p_polyline. * Other pins are regarded as obstacles, even if they are of the own net. */ Polyline pull_tight(Polyline p_polyline, int p_layer, int p_half_width, int[] p_net_no_arr, int p_cl_type, Set<Pin> p_contact_pins) { curr_layer = p_layer; ShapeSearchTree search_tree = this.board.search_tree_manager.get_default_tree(); curr_half_width = p_half_width + search_tree.clearance_compensation_value(p_cl_type, p_layer); curr_net_no_arr = p_net_no_arr; curr_cl_type = p_cl_type; contact_pins = p_contact_pins; return pull_tight(p_polyline); } /** * Termitates the pull tight algorithm, if the user has made a stop request. */ protected boolean is_stop_requested() { if (this.stoppable_thread != null && this.stoppable_thread.is_stop_requested()) { return true; } if (this.time_limit == null) { return false; } boolean time_limit_exceeded = this.time_limit.limit_exceeded(); if (time_limit_exceeded && this.board.get_test_level().ordinal() >= TestLevel.CRITICAL_DEBUGGING_OUTPUT.ordinal()) { System.out.println("PullTightAlgo.is_stop_requested: time limit exceeded"); } return time_limit_exceeded; } /** * tries to shorten p_polyline by relocating its lines */ Polyline reposition_lines(Polyline p_polyline) { if (p_polyline.arr.length < 5) { return p_polyline; } for (int i = 2; i < p_polyline.arr.length - 2; ++i) { Line new_line = reposition_line(p_polyline.arr, i); if (new_line != null) { Line[] line_arr = new Line[p_polyline.arr.length]; System.arraycopy(p_polyline.arr, 0, line_arr, 0, line_arr.length); line_arr[i] = new_line; Polyline result = new Polyline(line_arr); return skip_segments_of_length_0(result); } } return p_polyline; } /** * Tries to reposition the line with index p_no to make the polyline consisting * of p_line_arr shorter. */ protected Line reposition_line(Line[] p_line_arr, int p_no) { if (p_line_arr.length - p_no < 3) { return null; } if (curr_clip_shape != null) // check, that the corners of the line to translate are inside // the clip shape { for (int i = -1; i < 1; ++i) { Point curr_corner = p_line_arr[p_no + i].intersection(p_line_arr[p_no + i + 1]); if (curr_clip_shape.is_outside(curr_corner)) { return null; } } } Line translate_line = p_line_arr[p_no]; Point prev_corner = p_line_arr[p_no - 2].intersection(p_line_arr[p_no - 1]); Point next_corner = p_line_arr[p_no + 1].intersection(p_line_arr[p_no + 2]); double prev_dist = translate_line.signed_distance(prev_corner.to_float()); double next_dist = translate_line.signed_distance(next_corner.to_float()); if (Signum.of(prev_dist) != Signum.of(next_dist)) { // the 2 corners are at different sides of translate_line return null; } Point nearest_point; double max_translate_dist; if (Math.abs(prev_dist) < Math.abs(next_dist)) { nearest_point = prev_corner; max_translate_dist = prev_dist; } else { nearest_point = next_corner; max_translate_dist = next_dist; } double translate_dist = max_translate_dist; double delta_dist = max_translate_dist; Side side_of_nearest_point = translate_line.side_of(nearest_point); int sign = Signum.as_int(max_translate_dist); Line new_line = null; Line[] check_lines = new Line[3]; check_lines[0] = p_line_arr[p_no - 1]; check_lines[2] = p_line_arr[p_no + 1]; boolean first_time = true; while (first_time || Math.abs(delta_dist) > min_translate_dist) { boolean check_ok = false; if (first_time && nearest_point instanceof IntPoint) { check_lines[1] = Line.get_instance(nearest_point, translate_line.direction()); } else { check_lines[1] = translate_line.translate(-translate_dist); } if (check_lines[1].equals(translate_line)) { // may happen at first time if nearest_point is not an IntPoint return null; } Side new_line_side_of_nearest_point = check_lines[1].side_of(nearest_point); if (new_line_side_of_nearest_point != side_of_nearest_point && new_line_side_of_nearest_point != Side.COLLINEAR) { // moved a little bit to far at the first time // because of numerical inaccuracy; // may happen if nearest_point is not an IntPoint double shorten_value = sign * 0.5; max_translate_dist -= shorten_value; translate_dist -= shorten_value; delta_dist -= shorten_value; continue; } Polyline tmp = new Polyline(check_lines); if (tmp.arr.length == 3) { TileShape shape_to_check = tmp.offset_shape(curr_half_width, 0); check_ok = board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr, curr_cl_type, this.contact_pins); } delta_dist /= 2; if (check_ok) { new_line = check_lines[1]; if (first_time) { // biggest possible change break; } translate_dist += delta_dist; } else { translate_dist -= delta_dist; } first_time = false; } if (new_line != null && board.changed_area != null) { // mark the changed area board.changed_area.join(check_lines[0].intersection_approx(new_line), curr_layer); board.changed_area.join(check_lines[2].intersection_approx(new_line), curr_layer); board.changed_area.join(p_line_arr[p_no - 1].intersection_approx(p_line_arr[p_no]), curr_layer); board.changed_area.join(p_line_arr[p_no].intersection_approx(p_line_arr[p_no + 1]), curr_layer); } return new_line; } /** * tries to skip linesegments of length 0. * A check is nessesary before skipping because new dog ears * may occur. */ Polyline skip_segments_of_length_0(Polyline p_polyline) { boolean polyline_changed = false; Polyline curr_polyline = p_polyline; for (int i = 1; i < curr_polyline.arr.length - 1; ++i) { boolean try_skip; if (i == 1 || i == curr_polyline.arr.length - 2) // the position of the first corner and the last corner // must be retaimed exactly { Point prev_corner = curr_polyline.corner(i - 1); Point curr_corner = curr_polyline.corner(i); try_skip = curr_corner.equals(prev_corner); } else { FloatPoint prev_corner = curr_polyline.corner_approx(i - 1); FloatPoint curr_corner = curr_polyline.corner_approx(i); try_skip = curr_corner.distance_square(prev_corner) < c_min_corner_dist_square; } if (try_skip) { // check, if skipping the line of length 0 does not // result in a clearance violation Line[] curr_lines = new Line[curr_polyline.arr.length - 1]; System.arraycopy(curr_polyline.arr, 0, curr_lines, 0, i); System.arraycopy(curr_polyline.arr, i + 1, curr_lines, i, curr_lines.length - i); Polyline tmp = new Polyline(curr_lines); boolean check_ok = (tmp.arr.length == curr_lines.length); if (check_ok && !curr_polyline.arr[i].is_multiple_of_45_degree()) { // no check necessary for skipping 45 degree lines, because the check is // performance critical and the line shapes // are intersected with the bounding octagon anyway. if (i > 1) { TileShape shape_to_check = tmp.offset_shape(curr_half_width, i - 2); check_ok = board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr, curr_cl_type, this.contact_pins); } if (check_ok && (i < curr_polyline.arr.length - 2)) { TileShape shape_to_check = tmp.offset_shape(curr_half_width, i - 1); check_ok = board.check_trace_shape(shape_to_check, curr_layer, curr_net_no_arr, curr_cl_type, this.contact_pins); } } if (check_ok) { polyline_changed = true; curr_polyline = tmp; --i; } } } if (!polyline_changed) { return p_polyline; } return curr_polyline; } /** * Smoothens acute angles with contact traces. * Returns true, if something was changed. */ boolean smoothen_end_corners_at_trace(PolylineTrace p_trace) { curr_layer = p_trace.get_layer(); curr_half_width = p_trace.get_half_width(); curr_net_no_arr = p_trace.net_no_arr; curr_cl_type = p_trace.clearance_class_no(); return smoothen_end_corners_at_trace_1(p_trace); } /** * Smoothens acute angles with contact traces. * Returns true, if something was changed. */ private boolean smoothen_end_corners_at_trace_1(PolylineTrace p_trace) { // try to improve the connection to other traces if (p_trace.is_shove_fixed()) { return false; } Set<Pin> saved_contact_pins = this.contact_pins; // to allow the trace to slide to the end point of a contact trace, if the contact trace ends at a pin. this.contact_pins = null; boolean result = false; boolean connection_to_trace_improved = true; PolylineTrace curr_trace = p_trace; while (connection_to_trace_improved) { connection_to_trace_improved = false; Polyline adjusted_polyline = smoothen_end_corners_at_trace_2(curr_trace); if (adjusted_polyline != null) { result = true; connection_to_trace_improved = true; int trace_layer = curr_trace.get_layer(); int curr_cl_class = curr_trace.clearance_class_no(); FixedState curr_fixed_state = curr_trace.get_fixed_state(); board.remove_item(curr_trace); curr_trace = board.insert_trace_without_cleaning(adjusted_polyline, trace_layer, curr_half_width, curr_trace.net_no_arr, curr_cl_class, curr_fixed_state); for (int curr_net_no : curr_trace.net_no_arr) { board.split_traces(adjusted_polyline.first_corner(), trace_layer, curr_net_no); board.split_traces(adjusted_polyline.last_corner(), trace_layer, curr_net_no); board.normalize_traces(curr_net_no); if (split_traces_at_keep_point()) { return true; } } } } this.contact_pins = saved_contact_pins; return result; } /** * Splits the traces containing this.keep_point if this.keep_point != null. * Returns true, if something was split. */ boolean split_traces_at_keep_point() { if (this.keep_point == null) { return false; } ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES); Collection<Item> picked_items = this.board.pick_items(this.keep_point, this.keep_point_layer, filter); for (Item curr_item : picked_items) { Trace[] split_pieces = ((Trace) curr_item).split(this.keep_point); if (split_pieces != null) { return true; } } return false; } /** * Smoothens acute angles with contact traces. * Returns null, if something was changed. */ private Polyline smoothen_end_corners_at_trace_2(PolylineTrace p_trace) { if (p_trace == null || !p_trace.is_on_the_board()) { return null; } Polyline result = smoothen_start_corner_at_trace(p_trace); if (result == null) { result = smoothen_end_corner_at_trace(p_trace); if (result != null && board.changed_area != null) { // mark the changed area board.changed_area.join(result.corner_approx(result.corner_count() - 1), curr_layer); } } else if (board.changed_area != null) { // mark the changed area board.changed_area.join(result.corner_approx(0), curr_layer); } if (result != null) { this.contact_pins = p_trace.touching_pins_at_end_corners(); result = skip_segments_of_length_0(result); } return result; } /** * Wraps around pins of the own net to avoid acid traps. */ protected Polyline avoid_acid_traps(Polyline p_polyline) { if (true) { return p_polyline; } Polyline result = p_polyline; ShoveTraceAlgo shove_trace_algo = new ShoveTraceAlgo(this.board); Polyline new_polyline = shove_trace_algo.spring_over_obstacles(p_polyline, curr_half_width, curr_layer, curr_net_no_arr, curr_cl_type, contact_pins); if (new_polyline != null && new_polyline != p_polyline) { if (this.board.check_polyline_trace(new_polyline, curr_layer, curr_half_width, curr_net_no_arr, curr_cl_type)) { result = new_polyline; } } return result; } abstract Polyline pull_tight(Polyline p_polyline); abstract Polyline smoothen_start_corner_at_trace(PolylineTrace p_trace); abstract Polyline smoothen_end_corner_at_trace(PolylineTrace p_trace); protected final RoutingBoard board; /** If only_net_no > 0, only nets with this net numbers are optimized. */ protected final int[] only_net_no_arr; protected int curr_layer; protected int curr_half_width; protected int[] curr_net_no_arr; protected int curr_cl_type; protected IntOctagon curr_clip_shape; protected Set<Pin> contact_pins; protected int min_translate_dist; protected static final double c_max_cos_angle = 0.999; // with angles to close to 180 degree the algorithm becomes numerically // unstable protected static final double c_min_corner_dist_square = 0.9; /** * If stoppable_thread != null, the agorithm can be requested to be stopped. */ private final Stoppable stoppable_thread; private final TimeLimit time_limit; /** * If keep_point != null, traces containing the keep_point must also * contain the keep_point after optimizing. */ private final Point keep_point; private final int keep_point_layer; }