freerouting/src/main/java/eu/mihosoft/freerouting/board/PolylineTrace.java

1408 lines
52 KiB
Java

/*
* Copyright (C) 2014 Alfons Wirtz
* website www.freerouting.net
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License at <http://www.gnu.org/licenses/>
* for more details.
*/
package eu.mihosoft.freerouting.board;
import eu.mihosoft.freerouting.datastructures.Signum;
import eu.mihosoft.freerouting.datastructures.Stoppable;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
import eu.mihosoft.freerouting.geometry.planar.Line;
import eu.mihosoft.freerouting.geometry.planar.LineSegment;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.Shape;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.Direction;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import java.awt.Color;
import java.awt.Graphics;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import eu.mihosoft.freerouting.boardgraphics.GraphicsContext;
/**
*
* Objects of class Trace, whose geometry is described by a Polyline
*
*
* @author Alfons Wirtz
*/
public class PolylineTrace extends Trace implements java.io.Serializable
{
/**
* creates a new instance of a PolylineTrace with the input data
*/
public PolylineTrace(Polyline p_polyline, int p_layer, int p_half_width,
int[] p_net_no_arr, int p_clearance_type, int p_id_no,
int p_group_no, FixedState p_fixed_state, BasicBoard p_board)
{
super(p_layer, p_half_width, p_net_no_arr, p_clearance_type,
p_id_no, p_group_no, p_fixed_state, p_board);
if (p_polyline.arr.length < 3)
{
System.out.println("PolylineTrace: p_polyline.arr.length >= 3 expected");
}
lines = p_polyline;
}
public Item copy(int p_id_no)
{
int[] curr_net_no_arr = new int[this.net_count()];
for (int i = 0; i < curr_net_no_arr.length; ++i)
{
curr_net_no_arr[i] = get_net_no(i);
}
return new PolylineTrace(lines, get_layer(), get_half_width(), curr_net_no_arr, clearance_class_no(),
p_id_no, get_component_no(), get_fixed_state(), board);
}
/**
* checks, if this trace is on layer p_layer
*/
public boolean is_on_layer(int p_layer)
{
return get_layer() == p_layer;
}
/**
* returns the first corner of this trace, which is the intersection
* of the first and second lines of its polyline
*/
public Point first_corner()
{
return lines.corner(0);
}
/**
* returns the last corner of this trace, which is the intersection
* of the last two lines of its polyline
*/
public Point last_corner()
{
return lines.corner(lines.arr.length - 2);
}
/**
* returns the number of corners of this trace, which is the
* number of lines of its polyline minus one
*/
public int corner_count()
{
return lines.arr.length - 1;
}
public double get_length()
{
return lines.length_approx();
}
public IntBox bounding_box()
{
IntBox result = this.lines.bounding_box();
return result.offset(this.get_half_width());
}
public void draw(Graphics p_g, GraphicsContext p_graphics_context, Color[] p_color_arr, double p_intensity)
{
if (p_graphics_context == null)
{
return;
}
int layer = this.get_layer();
Color color = p_color_arr[layer];
double display_width = get_half_width();
double intensity = p_intensity * p_graphics_context.get_layer_visibility(layer);
p_graphics_context.draw(lines.corner_approx_arr(), display_width, color, p_g, intensity);
}
/**
* Returns the polyline of this trace.
*/
public Polyline polyline()
{
return lines;
}
protected TileShape[] calculate_tree_shapes(ShapeSearchTree p_search_tree)
{
return p_search_tree.calculate_tree_shapes(this);
}
/**
* returns the count of tile shapes of this polyline
*/
public int tile_shape_count()
{
return Math.max(lines.arr.length - 2, 0);
}
public void translate_by(Vector p_vector)
{
lines = lines.translate_by(p_vector);
this.clear_derived_data();
}
public void turn_90_degree(int p_factor, IntPoint p_pole)
{
lines = lines.turn_90_degree(p_factor, p_pole);
this.clear_derived_data();
}
public void rotate_approx(double p_angle_in_degree, FloatPoint p_pole)
{
this.lines = this.lines.rotate_approx(Math.toRadians(p_angle_in_degree), p_pole);
}
public void change_placement_side(IntPoint p_pole)
{
lines = lines.mirror_vertical(p_pole);
if (this.board != null)
{
this.set_layer(board.get_layer_count() - this.get_layer() - 1);
}
this.clear_derived_data();
}
/**
* Looks, if other traces can be combined with this trace.
* Returns true, if somthing has been combined.
* This trace will be the combined trace, so that only other traces may be deleted.
*/
public boolean combine()
{
if (!this.is_on_the_board())
{
return false;
}
boolean something_changed;
if (this.combine_at_start(true))
{
something_changed = true;
this.combine();
}
else if (this.combine_at_end(true))
{
something_changed = true;
this.combine();
}
else
{
something_changed = false;
}
if (something_changed)
{
// let the observers syncronize the changes
board.communication.observers.notify_changed(this);
board.additional_update_after_change(this);
}
return something_changed;
}
/**
* looks, if this trace can be combined at its first point with
* an other trace. Returns true, if somthing was combined.
* The corners of the other trace will be inserted in front of thie trace.
* In case of combine the other trace will be deleted and this trace will
* remain.
*/
private boolean combine_at_start(boolean p_ignore_areas)
{
Point start_corner = first_corner();
Collection<Item> contacts = get_normal_contacts(start_corner, false);
if (p_ignore_areas)
{
// remove conduction areas from the list
Iterator<Item> it = contacts.iterator();
while (it.hasNext())
{
if (it.next() instanceof ConductionArea)
{
it.remove();
}
}
}
if (contacts.size() != 1)
{
return false;
}
PolylineTrace other_trace = null;
boolean trace_found = false;
boolean reverse_order = false;
Iterator<Item> it = contacts.iterator();
while (it.hasNext())
{
Item curr_ob = it.next();
if (curr_ob instanceof PolylineTrace)
{
other_trace = (PolylineTrace) curr_ob;
if (other_trace.get_layer() == get_layer() && other_trace.nets_equal(this) && other_trace.get_half_width() == get_half_width() && other_trace.get_fixed_state() == this.get_fixed_state())
{
if (start_corner.equals(other_trace.last_corner()))
{
trace_found = true;
break;
}
else if (start_corner.equals(other_trace.first_corner()))
{
reverse_order = true;
trace_found = true;
break;
}
}
}
}
if (!trace_found)
{
return false;
}
board.item_list.save_for_undo(this);
// create the lines of the joined polyline
Line[] this_lines = lines.arr;
Line[] other_lines;
if (reverse_order)
{
other_lines = new Line[other_trace.lines.arr.length];
for (int i = 0; i < other_lines.length; ++i)
{
other_lines[i] = other_trace.lines.arr[other_lines.length - 1 - i].opposite();
}
}
else
{
other_lines = other_trace.lines.arr;
}
boolean skip_line =
other_lines[other_lines.length - 2].is_equal_or_opposite(this_lines[1]);
int new_line_count = this_lines.length + other_lines.length - 2;
if (skip_line)
{
--new_line_count;
}
Line[] new_lines = new Line[new_line_count];
System.arraycopy(other_lines, 0, new_lines, 0, other_lines.length - 1);
int join_pos = other_lines.length - 1;
if (skip_line)
{
--join_pos;
}
System.arraycopy(this_lines, 1, new_lines, join_pos, this_lines.length - 1);
Polyline joined_polyline = new Polyline(new_lines);
if (joined_polyline.arr.length != new_line_count)
{
// consecutive parallel lines where skipped at the join location
// combine without performance optimation
board.search_tree_manager.remove(this);
this.lines = joined_polyline;
this.clear_derived_data();
board.search_tree_manager.insert(this);
}
else
{
// reuse the tree entries for better performance
// create the changed line shape at the join location
int to_no = other_lines.length;
if (skip_line)
{
--to_no;
}
board.search_tree_manager.merge_entries_in_front(other_trace, this, joined_polyline,
other_lines.length - 3, to_no);
other_trace.clear_search_tree_entries();
this.lines = joined_polyline;
}
if (this.lines.arr.length < 3)
{
board.remove_item(this);
}
board.remove_item(other_trace);
if (board instanceof RoutingBoard)
{
((RoutingBoard) board).join_changed_area(start_corner.to_float(), get_layer());
}
return true;
}
/**
* looks, if this trace can be combined at its last point with
* another trace. Returns true, if somthing was combined.
* The corners of the other trace will be inserted at the end of thie trace.
* In case of combine the other trace will be deleted and this trace will
* remain.
*/
private boolean combine_at_end(boolean p_ignore_areas)
{
Point end_corner = last_corner();
Collection<Item> contacts = get_normal_contacts(end_corner, false);
if (p_ignore_areas)
{
// remove conduction areas from the list
Iterator<Item> it = contacts.iterator();
while (it.hasNext())
{
if (it.next() instanceof ConductionArea)
{
it.remove();
}
}
}
if (contacts.size() != 1)
{
return false;
}
PolylineTrace other_trace = null;
boolean trace_found = false;
boolean reverse_order = false;
Iterator<Item> it = contacts.iterator();
while (it.hasNext())
{
Item curr_ob = it.next();
if (curr_ob instanceof PolylineTrace)
{
other_trace = (PolylineTrace) curr_ob;
if (other_trace.get_layer() == get_layer() && other_trace.nets_equal(this) && other_trace.get_half_width() == get_half_width() && other_trace.get_fixed_state() == this.get_fixed_state())
{
if (end_corner.equals(other_trace.first_corner()))
{
trace_found = true;
break;
}
else if (end_corner.equals(other_trace.last_corner()))
{
reverse_order = true;
trace_found = true;
break;
}
}
}
}
if (!trace_found)
{
return false;
}
board.item_list.save_for_undo(this);
// create the lines of the joined polyline
Line[] this_lines = lines.arr;
Line[] other_lines;
if (reverse_order)
{
other_lines = new Line[other_trace.lines.arr.length];
for (int i = 0; i < other_lines.length; ++i)
{
other_lines[i] = other_trace.lines.arr[other_lines.length - 1 - i].opposite();
}
}
else
{
other_lines = other_trace.lines.arr;
}
boolean skip_line =
this_lines[this_lines.length - 2].is_equal_or_opposite(other_lines[1]);
int new_line_count = this_lines.length + other_lines.length - 2;
if (skip_line)
{
--new_line_count;
}
Line[] new_lines = new Line[new_line_count];
System.arraycopy(this_lines, 0, new_lines, 0, this_lines.length - 1);
int join_pos = this_lines.length - 1;
if (skip_line)
{
--join_pos;
}
System.arraycopy(other_lines, 1, new_lines, join_pos, other_lines.length - 1);
Polyline joined_polyline = new Polyline(new_lines);
if (joined_polyline.arr.length != new_line_count)
{
// consecutive parallel lines where skipped at the join location
// combine without performance optimation
board.search_tree_manager.remove(this);
this.clear_search_tree_entries();
this.lines = joined_polyline;
this.clear_derived_data();
board.search_tree_manager.insert(this);
}
else
{
// reuse tree entries for better performance
// create the changed line shape at the join location
int to_no = this_lines.length;
if (skip_line)
{
--to_no;
}
board.search_tree_manager.merge_entries_at_end(other_trace, this, joined_polyline, this_lines.length - 3, to_no);
other_trace.clear_search_tree_entries();
this.lines = joined_polyline;
}
if (this.lines.arr.length < 3)
{
board.remove_item(this);
}
board.remove_item(other_trace);
if (board instanceof RoutingBoard)
{
((RoutingBoard) board).join_changed_area(end_corner.to_float(), get_layer());
}
return true;
}
/**
* Looks up traces intersecting with this trace and splits them at the intersection points.
* In case of an overlaps, the traces are split at their first and their last common point.
* Returns the pieces resulting from splitting.
* Found cycles are removed.
* If nothing is split, the result will contain just this Trace.
* If p_clip_shape != null, the split may be resticted to p_clip_shape.
*/
public Collection<PolylineTrace> split(IntOctagon p_clip_shape)
{
Collection<PolylineTrace> result = new LinkedList<PolylineTrace>();
if (!this.nets_normal())
{
// only normal nets are split
result.add(this);
return result;
}
boolean own_trace_split = false;
ShapeSearchTree default_tree = board.search_tree_manager.get_default_tree();
for (int i = 0; i < this.lines.arr.length - 2; ++i)
{
if (p_clip_shape != null)
{
LineSegment curr_segment = new LineSegment(this.lines, i + 1);
if (!p_clip_shape.intersects(curr_segment.bounding_box()))
{
continue;
}
}
TileShape curr_shape = this.get_tree_shape(default_tree, i);
LineSegment curr_line_segment = new LineSegment(this.lines, i + 1);
Collection<ShapeSearchTree.TreeEntry> overlapping_tree_entries = new LinkedList<ShapeSearchTree.TreeEntry>();
// look for intersecting traces with the i-th line segment
default_tree.overlapping_tree_entries(curr_shape, get_layer(), overlapping_tree_entries);
Iterator<ShapeSearchTree.TreeEntry> it = overlapping_tree_entries.iterator();
while (it.hasNext())
{
if (!this.is_on_the_board())
{
// this trace has been deleted in a cleanup operation
return result;
}
ShapeSearchTree.TreeEntry found_entry = it.next();
if (!(found_entry.object instanceof Item))
{
continue;
}
Item found_item = (Item) found_entry.object;
if (found_item == this)
{
if (found_entry.shape_index_in_object >= i - 1 && found_entry.shape_index_in_object <= i + 1)
{
// don't split own trace at this line or at neighbour lines
continue;
}
// try to handle intermediate segments of length 0 by comparing end corners
if (i < found_entry.shape_index_in_object)
{
if (lines.corner(i + 1).equals(lines.corner(found_entry.shape_index_in_object)))
{
continue;
}
}
else if (found_entry.shape_index_in_object < i)
{
if (lines.corner(found_entry.shape_index_in_object + 1).equals(lines.corner(i)))
{
continue;
}
}
}
if (!found_item.shares_net(this))
{
continue;
}
if (found_item instanceof PolylineTrace)
{
PolylineTrace found_trace = (PolylineTrace) found_item;
LineSegment found_line_segment =
new LineSegment(found_trace.lines, found_entry.shape_index_in_object + 1);
Line[] intersecting_lines = found_line_segment.intersection(curr_line_segment);
Collection<PolylineTrace> split_pieces = new LinkedList<PolylineTrace>();
// try splitting the found trace first
boolean found_trace_split = false;
if (found_trace != this)
{
for (int j = 0; j < intersecting_lines.length; ++j)
{
int line_no = found_entry.shape_index_in_object + 1;
PolylineTrace[] curr_split_pieces = found_trace.split(line_no, intersecting_lines[j]);
if (curr_split_pieces != null)
{
for (int k = 0; k < 2; ++k)
{
if (curr_split_pieces[k] != null)
{
found_trace_split = true;
split_pieces.add(curr_split_pieces[k]);
}
}
if (found_trace_split)
{
// reread the overlapping tree entries and reset the iterator,
// because the eu.mihosoft.freerouting.board has changed
default_tree.overlapping_tree_entries(curr_shape, get_layer(), overlapping_tree_entries);
it = overlapping_tree_entries.iterator();
break;
}
}
}
if (!found_trace_split)
{
split_pieces.add(found_trace);
}
}
// now try splitting the own trace
intersecting_lines = curr_line_segment.intersection(found_line_segment);
for (int j = 0; j < intersecting_lines.length; ++j)
{
PolylineTrace[] curr_split_pieces = split(i + 1, intersecting_lines[j]);
if (curr_split_pieces != null)
{
own_trace_split = true;
// this trace was split itself into 2.
if (curr_split_pieces[0] != null)
{
result.addAll(curr_split_pieces[0].split(p_clip_shape));
}
if (curr_split_pieces[1] != null)
{
result.addAll(curr_split_pieces[1].split(p_clip_shape));
}
break;
}
}
if (found_trace_split || own_trace_split)
{
// something was split,
// remove cycles containing a split piece
Iterator<PolylineTrace> it2 = split_pieces.iterator();
for (int j = 0; j < 2; ++j)
{
while (it2.hasNext())
{
PolylineTrace curr_piece = it2.next();
board.remove_if_cycle(curr_piece);
}
// remove cycles in the own split pieces last
// to preserve them, if possible
it2 = result.iterator();
}
}
if (own_trace_split)
{
break;
}
}
else if (found_item instanceof DrillItem)
{
DrillItem curr_drill_item = (DrillItem) found_item;
Point split_point = curr_drill_item.get_center();
if (curr_line_segment.contains(split_point))
{
Direction split_line_direction = curr_line_segment.get_line().direction().turn_45_degree(2);
Line split_line = new Line(split_point, split_line_direction);
split(i + 1, split_line);
}
}
else if (!this.is_user_fixed() && (found_item instanceof ConductionArea))
{
boolean ignore_areas = false;
if (this.net_no_arr.length > 0)
{
eu.mihosoft.freerouting.rules.Net curr_net = this.board.rules.nets.get(this.net_no_arr[0]);
if (curr_net != null && curr_net.get_class() != null)
{
ignore_areas = curr_net.get_class().get_ignore_cycles_with_areas();
}
}
if (!ignore_areas && this.get_start_contacts().contains(found_item) &&
this.get_end_contacts().contains(found_item))
{
// this trace can be removed because of cycle with conduction area
board.remove_item(this);
return result;
}
}
}
if (own_trace_split)
{
break;
}
}
if (!own_trace_split)
{
result.add(this);
}
if (result.size() > 1)
{
for (Item curr_item : result)
{
board.additional_update_after_change(curr_item);
}
}
return result;
}
/**
* Checks, if the intersection of the p_line_no-th line of this trace with p_line is inside
* the pad of a pin. In this case the trace will be split only, if the intersection
* is at the center of the pin.
* Extending the function to vias leaded to broken connection problems wenn the autorouter connected to a trace.
*/
private boolean split_inside_drill_pad_prohibited(int p_line_no, Line p_line)
{
if (this.board == null)
{
return false;
}
Point intersection = this.lines.arr[p_line_no].intersection(p_line);
java.util.Collection<Item> overlap_items = this.board.pick_items(intersection, this.get_layer(), null);
boolean pad_found = false;
for (Item curr_item : overlap_items)
{
if (!curr_item.shares_net(this))
{
continue;
}
if (curr_item instanceof Pin)
{
DrillItem curr_drill_item = (DrillItem) curr_item;
if (curr_drill_item.get_center().equals(intersection))
{
return false; // split always at the center of a drill item.
}
pad_found = true;
}
else if (curr_item instanceof Trace)
{
Trace curr_trace = (Trace) curr_item;
if (curr_trace != this && curr_trace.first_corner().equals(intersection) || curr_trace.last_corner().equals(intersection))
{
return false;
}
}
}
return pad_found;
}
/**
* Splits this trace into two at p_point.
* Returns the 2 pieces of the splitted trace, or null if nothing was splitted because for example
* p_point is not located on a line segment of the p_polyline of this trace.
*/
public Trace[] split(Point p_point)
{
for (int i = 0; i < this.lines.arr.length - 2; ++i)
{
LineSegment curr_line_segment = new LineSegment(this.lines, i + 1);
if (curr_line_segment.contains(p_point))
{
Direction split_line_direction = curr_line_segment.get_line().direction().turn_45_degree(2);
Line split_line = new Line(p_point, split_line_direction);
Trace[] result = split(i + 1, split_line);
if (result != null)
{
return result;
}
}
}
return null;
}
/**
* Splits this trace at the line with number p_line_no
* into two by inserting p_endline as concluding line of the first split piece
* and as the start line of the second split piece.
* Returns the 2 pieces of the splitted trace, or null, if nothing was splitted.
*/
private PolylineTrace[] split(int p_line_no, Line p_new_end_line)
{
if (!this.is_on_the_board())
{
return null;
}
Polyline[] split_polylines = lines.split(p_line_no, p_new_end_line);
if (split_polylines == null)
{
return null;
}
if (split_polylines.length != 2)
{
System.out.println("PolylineTrace.split: array of length 2 expected for split_polylines");
return null;
}
if (split_inside_drill_pad_prohibited(p_line_no, p_new_end_line))
{
return null;
}
board.remove_item(this);
PolylineTrace[] result = new PolylineTrace[2];
result[0] = board.insert_trace_without_cleaning(split_polylines[0], get_layer(), get_half_width(),
net_no_arr, clearance_class_no(), get_fixed_state());
result[1] = board.insert_trace_without_cleaning(split_polylines[1], get_layer(), get_half_width(),
net_no_arr, clearance_class_no(), get_fixed_state());
return result;
}
/**
* Splits this trace and overlapping traces, and combines this trace.
* Returns true, if something was changed.
* If p_clip_shape != null, splitting is restricted to p_clip_shape.
*/
public boolean normalize(IntOctagon p_clip_shape)
{
boolean observers_activated = false;
BasicBoard routing_board = this.board;
if (this.board != null)
{
// Let the observers know the trace changes.
observers_activated = !routing_board.observers_active();
if (observers_activated)
{
routing_board.start_notify_observers();
}
}
Collection<PolylineTrace> split_pieces = this.split(p_clip_shape);
boolean result = (split_pieces.size() != 1);
Iterator<PolylineTrace> it = split_pieces.iterator();
while (it.hasNext())
{
PolylineTrace curr_split_trace = it.next();
if (curr_split_trace.is_on_the_board())
{
boolean trace_combined = curr_split_trace.combine();
if (curr_split_trace.corner_count() == 2 && curr_split_trace.first_corner().equals(curr_split_trace.last_corner()))
{
// remove trace with only 1 corner
board.remove_item(curr_split_trace);
result = true;
}
else if (trace_combined)
{
curr_split_trace.normalize(p_clip_shape);
result = true;
}
}
}
if (observers_activated)
{
routing_board.end_notify_observers();
}
return result;
}
/**
* Tries to shorten this trace without creating clearance violations
* Returns true, if the trace was changed.
*/
public boolean pull_tight(PullTightAlgo p_pull_tight_algo)
{
if (!this.is_on_the_board())
{
// This trace may have been deleted in a trace split for example
return false;
}
if (this.is_shove_fixed())
{
return false;
}
if (!this.nets_normal())
{
return false;
}
if (p_pull_tight_algo.only_net_no_arr.length > 0 && !this.nets_equal(p_pull_tight_algo.only_net_no_arr))
{
return false;
}
if (this.net_no_arr.length > 0)
{
if (!this.board.rules.nets.get(this.net_no_arr[0]).get_class().get_pull_tight())
{
return false;
}
}
Polyline new_lines =
p_pull_tight_algo.pull_tight(lines, get_layer(), get_half_width(), net_no_arr, clearance_class_no(),
this.touching_pins_at_end_corners());
if (new_lines != lines)
{
change(new_lines);
return true;
}
AngleRestriction angle_restriction = this.board.rules.get_trace_angle_restriction();
if (angle_restriction != AngleRestriction.NINETY_DEGREE && this.board.rules.get_pin_edge_to_turn_dist() > 0)
{
if (this.swap_connection_to_pin(true))
{
pull_tight(p_pull_tight_algo);
return true;
}
if (this.swap_connection_to_pin(false))
{
pull_tight(p_pull_tight_algo);
return true;
}
// optimize algorithm could not improve the trace, try to remove acid traps
if (this.correct_connection_to_pin(true, angle_restriction))
{
pull_tight(p_pull_tight_algo);
return true;
}
if (this.correct_connection_to_pin(false, angle_restriction))
{
pull_tight(p_pull_tight_algo);
return true;
}
}
return false;
}
/**
* Tries to pull this trace tight without creating clearance violations
* Returns true, if the trace was changed.
*/
public boolean pull_tight(boolean p_own_net_only, int p_pull_tight_accuracy, Stoppable p_stoppable_thread)
{
if (!(this.board instanceof RoutingBoard))
{
return false;
}
int[] opt_net_no_arr;
if (p_own_net_only)
{
opt_net_no_arr = this.net_no_arr;
}
else
{
opt_net_no_arr = new int[0];
}
PullTightAlgo pull_tight_algo =
PullTightAlgo.get_instance((RoutingBoard) this.board, opt_net_no_arr,
null, p_pull_tight_accuracy, p_stoppable_thread, -1, null, -1);
return pull_tight(pull_tight_algo);
}
/**
* Tries to smoothen the end corners of this trace, which are at a fork with other traces.
*/
public boolean smoothen_end_corners_fork(boolean p_own_net_only, int p_pull_tight_accuracy, Stoppable p_stoppable_thread)
{
if (!(this.board instanceof RoutingBoard))
{
return false;
}
int[] opt_net_no_arr;
if (p_own_net_only)
{
opt_net_no_arr = this.net_no_arr;
}
else
{
opt_net_no_arr = new int[0];
}
PullTightAlgo pull_tight_algo =
PullTightAlgo.get_instance((RoutingBoard) this.board, opt_net_no_arr,
null, p_pull_tight_accuracy, p_stoppable_thread, -1, null, -1);
return pull_tight_algo.smoothen_end_corners_at_trace(this);
}
public TileShape get_trace_connection_shape(ShapeSearchTree p_search_tree, int p_index)
{
if (p_index < 0 || p_index >= this.tile_shape_count())
{
System.out.println("PolylineTrace.get_trace_connection_shape p_index out of range");
return null;
}
LineSegment curr_line_segment = new LineSegment(this.lines, p_index + 1);
TileShape result = curr_line_segment.to_simplex().simplify();
return result;
}
public boolean write(java.io.ObjectOutputStream p_stream)
{
try
{
p_stream.writeObject(this);
} catch (java.io.IOException e)
{
return false;
}
return true;
}
/**
* changes the geometry of this trace to p_new_polyline
*/
void change(Polyline p_new_polyline)
{
if (!this.is_on_the_board())
{
// Just change the polyline of this trace.
lines = p_new_polyline;
return;
}
board.additional_update_after_change(this);
// The precalculated tile shapes must not be cleared here here because they are used and modified
// in ShapeSearchTree.change_entries.
board.item_list.save_for_undo(this);
// for performance reasons there is some effort to reuse
// ShapeTree entries of the old trace in the changed trace
// look for the first line in p_new_polyline different from
// the lines of the existung trace
int last_index = Math.min(p_new_polyline.arr.length, lines.arr.length);
int index_of_first_different_line = last_index;
for (int i = 0; i < last_index; ++i)
{
if (p_new_polyline.arr[i] != lines.arr[i])
{
index_of_first_different_line = i;
break;
}
}
if (index_of_first_different_line == last_index)
{
return; // both polylines are equal, no change nessesary
}
// look for the last line in p_new_polyline different from
// the lines of the existung trace
int index_of_last_different_line = -1;
for (int i = 1; i <= last_index; ++i)
{
if (p_new_polyline.arr[p_new_polyline.arr.length - i] !=
lines.arr[lines.arr.length - i])
{
index_of_last_different_line = p_new_polyline.arr.length - i;
break;
}
}
if (index_of_last_different_line < 0)
{
return; // both polylines are equal, no change nessesary
}
int keep_at_start_count = Math.max(index_of_first_different_line - 2, 0);
int keep_at_end_count = Math.max(p_new_polyline.arr.length - index_of_last_different_line - 3, 0);
board.search_tree_manager.change_entries(this, p_new_polyline, keep_at_start_count, keep_at_end_count);
lines = p_new_polyline;
// let the observers syncronize the changes
board.communication.observers.notify_changed(this);
IntOctagon clip_shape = null;
if (board instanceof RoutingBoard)
{
ChangedArea changed_area = ((RoutingBoard) board).changed_area;
if (changed_area != null)
{
clip_shape = changed_area.get_area(this.get_layer());
}
}
this.normalize(clip_shape);
}
/**
* checks, that the connection restrictions to the contact pins
* are satisfied. If p_at_start, the start of this trace is checked,
* else the end. Returns false, if a pin is at that end, where
* the connection is checked and the connection is not ok.
*/
public boolean check_connection_to_pin(boolean p_at_start)
{
if (this.board == null)
{
return true;
}
if (this.corner_count() < 2)
{
return true;
}
Collection<Item> contact_list;
if (p_at_start)
{
contact_list = this.get_start_contacts();
}
else
{
contact_list = this.get_end_contacts();
}
Pin contact_pin = null;
for (Item curr_contact : contact_list)
{
if (curr_contact instanceof Pin)
{
contact_pin = (Pin) curr_contact;
break;
}
}
if (contact_pin == null)
{
return true;
}
Collection<Pin.TraceExitRestriction> trace_exit_restrictions = contact_pin.get_trace_exit_restrictions(this.get_layer());
if (trace_exit_restrictions.isEmpty())
{
return true;
}
Point end_corner;
Point prev_end_corner;
if (p_at_start)
{
end_corner = this.first_corner();
prev_end_corner = this.lines.corner(1);
}
else
{
end_corner = this.last_corner();
prev_end_corner = this.lines.corner(this.lines.corner_count() - 2);
}
Direction trace_end_direction = Direction.get_instance(end_corner, prev_end_corner);
if (trace_end_direction == null)
{
return true;
}
Pin.TraceExitRestriction matching_exit_restriction = null;
for (Pin.TraceExitRestriction curr_exit_restriction : trace_exit_restrictions)
{
if (curr_exit_restriction.direction.equals(trace_end_direction))
{
matching_exit_restriction = curr_exit_restriction;
break;
}
}
if (matching_exit_restriction == null)
{
return false;
}
final double edge_to_turn_dist = this.board.rules.get_pin_edge_to_turn_dist();
if (edge_to_turn_dist < 0)
{
return false;
}
double end_line_length = end_corner.to_float().distance(prev_end_corner.to_float());
double curr_clearance = board.clearance_value(this.clearance_class_no(), contact_pin.clearance_class_no(), this.get_layer());
double add_width = Math.max(edge_to_turn_dist, curr_clearance + 1);
double preserve_length = matching_exit_restriction.min_length + this.get_half_width() + add_width;
if (preserve_length > end_line_length)
{
return false;
}
return true;
}
/**
* Tries to correct a connection restriction of this trace.
* If p_at_start, the start of the trace polygon is corrected, else the end.
*Returns true, if this trace was changed.
*/
public boolean correct_connection_to_pin(boolean p_at_start, AngleRestriction p_angle_restriction)
{
if (this.check_connection_to_pin(p_at_start))
{
return false;
}
Polyline trace_polyline;
Collection<Item> contact_list;
if (p_at_start)
{
trace_polyline = this.polyline();
contact_list = this.get_start_contacts();
}
else
{
trace_polyline = this.polyline().reverse();
contact_list = this.get_end_contacts();
}
Pin contact_pin = null;
for (Item curr_contact : contact_list)
{
if (curr_contact instanceof Pin)
{
contact_pin = (Pin) curr_contact;
break;
}
}
if (contact_pin == null)
{
return false;
}
Collection<Pin.TraceExitRestriction> trace_exit_restrictions = contact_pin.get_trace_exit_restrictions(this.get_layer());
if (trace_exit_restrictions.isEmpty())
{
return false;
}
Shape pin_shape = contact_pin.get_shape(this.get_layer() - contact_pin.first_layer());
if (!(pin_shape instanceof TileShape))
{
return false;
}
Point pin_center = contact_pin.get_center();
final double edge_to_turn_dist = this.board.rules.get_pin_edge_to_turn_dist();
if (edge_to_turn_dist < 0)
{
return false;
}
double curr_clearance = board.clearance_value(this.clearance_class_no(), contact_pin.clearance_class_no(), this.get_layer());
double add_width = Math.max(edge_to_turn_dist, curr_clearance + 1);
TileShape offset_pin_shape = (TileShape) ((TileShape) pin_shape).offset(this.get_half_width() + add_width);
if (p_angle_restriction == AngleRestriction.NINETY_DEGREE || offset_pin_shape.is_IntBox())
{
offset_pin_shape = offset_pin_shape.bounding_box();
}
else if (p_angle_restriction == AngleRestriction.FORTYFIVE_DEGREE)
{
offset_pin_shape = offset_pin_shape.bounding_octagon();
}
int[][] entries = offset_pin_shape.entrance_points(trace_polyline);
if (entries.length == 0)
{
return false;
}
int[] latest_entry_tuple = entries[entries.length - 1];
FloatPoint trace_entry_location_approx =
trace_polyline.arr[latest_entry_tuple[0]].intersection_approx(offset_pin_shape.border_line(latest_entry_tuple[1]));
// calculate the nearest legal pin exit point to trace_entry_location_approx
double min_exit_corner_distance = Double.MAX_VALUE;
Line nearest_pin_exit_ray = null;
int nearest_border_line_no = -1;
Direction pin_exit_direction = null;
FloatPoint nearest_exit_corner = null;
final double TOLERANCE = 1;
for (Pin.TraceExitRestriction curr_exit_restriction : trace_exit_restrictions)
{
int curr_intersecting_border_line_no = offset_pin_shape.intersecting_border_line_no(pin_center, curr_exit_restriction.direction);
Line curr_pin_exit_ray = new Line(pin_center, curr_exit_restriction.direction);
FloatPoint curr_exit_corner = curr_pin_exit_ray.intersection_approx(offset_pin_shape.border_line(curr_intersecting_border_line_no));
double curr_exit_corner_distance = curr_exit_corner.distance_square(trace_entry_location_approx);
boolean new_nearest_corner_found = false;
if (curr_exit_corner_distance + TOLERANCE < min_exit_corner_distance)
{
new_nearest_corner_found = true;
}
else if (curr_exit_corner_distance < min_exit_corner_distance + TOLERANCE)
{
// the distances are near equal, compare to the previous corners of p_trace_polyline
for (int i = 1; i < trace_polyline.corner_count(); ++i)
{
FloatPoint curr_trace_corner = trace_polyline.corner_approx(i);
double curr_trace_corner_distance = curr_trace_corner.distance_square(curr_exit_corner);
double old_trace_corner_distance = curr_trace_corner.distance_square(nearest_exit_corner);
if (curr_trace_corner_distance + TOLERANCE < old_trace_corner_distance)
{
new_nearest_corner_found = true;
break;
}
else if (curr_trace_corner_distance > old_trace_corner_distance + TOLERANCE)
{
break;
}
}
}
if (new_nearest_corner_found)
{
min_exit_corner_distance = curr_exit_corner_distance;
nearest_pin_exit_ray = curr_pin_exit_ray;
nearest_border_line_no = curr_intersecting_border_line_no;
pin_exit_direction = curr_exit_restriction.direction;
nearest_exit_corner = curr_exit_corner;
}
}
// append the polygon piece around the border of the pin shape.
Line[] curr_lines;
int corner_count = offset_pin_shape.border_line_count();
int clock_wise_side_diff =
(nearest_border_line_no - latest_entry_tuple[1] + corner_count) % corner_count;
int counter_clock_wise_side_diff =
(latest_entry_tuple[1] - nearest_border_line_no + corner_count) % corner_count;
int curr_border_line_no = nearest_border_line_no;
if (counter_clock_wise_side_diff <= clock_wise_side_diff)
{
curr_lines = new Line[counter_clock_wise_side_diff + 3];
for (int i = 0; i <= counter_clock_wise_side_diff; ++i)
{
curr_lines[i + 1] = offset_pin_shape.border_line(curr_border_line_no);
curr_border_line_no = (curr_border_line_no + 1) % corner_count;
}
}
else
{
curr_lines = new Line[clock_wise_side_diff + 3];
for (int i = 0; i <= clock_wise_side_diff; ++i)
{
curr_lines[i + 1] = offset_pin_shape.border_line(curr_border_line_no);
curr_border_line_no = (curr_border_line_no - 1 + corner_count) % corner_count;
}
}
curr_lines[0] = nearest_pin_exit_ray;
curr_lines[curr_lines.length - 1] = trace_polyline.arr[latest_entry_tuple[0]];
Polyline border_polyline = new Polyline(curr_lines);
if (!this.board.check_polyline_trace(border_polyline, this.get_layer(),
this.get_half_width(), this.net_no_arr, this.clearance_class_no()))
{
return false;
}
Line[] cut_lines = new Line[trace_polyline.arr.length - latest_entry_tuple[0] + 1];
cut_lines[0] = curr_lines[curr_lines.length - 2];
for (int i = 1; i < cut_lines.length; ++i)
{
cut_lines[i] = trace_polyline.arr[latest_entry_tuple[0] + i - 1];
}
Polyline cut_polyline = new Polyline(cut_lines);
Polyline changed_polyline;
if (cut_polyline.first_corner().equals(cut_polyline.last_corner()))
{
changed_polyline = border_polyline;
}
else
{
changed_polyline = border_polyline.combine(cut_polyline);
}
if (!p_at_start)
{
changed_polyline = changed_polyline.reverse();
}
this.change(changed_polyline);
// create an shove_fixed exit line.
curr_lines = new Line[3];
curr_lines[0] = new Line(pin_center, pin_exit_direction.turn_45_degree(2));
curr_lines[1] = nearest_pin_exit_ray;
curr_lines[2] = offset_pin_shape.border_line(nearest_border_line_no);
Polyline exit_line_segment = new Polyline(curr_lines);
this.board.insert_trace(exit_line_segment, this.get_layer(), this.get_half_width(), this.net_no_arr,
this.clearance_class_no(), FixedState.SHOVE_FIXED);
return true;
}
/**
* Looks, if an other pin connection restriction fits better than the current connection restriction
* and changes this trace in this case.
* If p_at_start, the start of the trace polygon is changed, else the end.
* Returns true, if this trace was changed.
*/
public boolean swap_connection_to_pin(boolean p_at_start)
{
Polyline trace_polyline;
Collection<Item> contact_list;
if (p_at_start)
{
trace_polyline = this.polyline();
contact_list = this.get_start_contacts();
}
else
{
trace_polyline = this.polyline().reverse();
contact_list = this.get_end_contacts();
}
if (contact_list.size() != 1)
{
return false;
}
Item curr_contact = contact_list.iterator().next();
if (!(curr_contact.get_fixed_state() == FixedState.SHOVE_FIXED && (curr_contact instanceof PolylineTrace)))
{
return false;
}
PolylineTrace contact_trace = (PolylineTrace) curr_contact;
Polyline contact_polyline = contact_trace.polyline();
Line contact_last_line = contact_polyline.arr[contact_polyline.arr.length - 2];
// look, if this trace has a sharp angle with the contact trace.
Line first_line = trace_polyline.arr[1];
// check for sharp angle
boolean check_swap = contact_last_line.direction().projection(first_line.direction()) == Signum.NEGATIVE;
if (!check_swap)
{
double half_width = this.get_half_width();
if (trace_polyline.arr.length > 3 &&
trace_polyline.corner_approx(0).distance_square(trace_polyline.corner_approx(1)) <= half_width * half_width)
{
// check also for sharp angle with the second line
check_swap =
(contact_last_line.direction().projection(trace_polyline.arr[2].direction()) == Signum.NEGATIVE);
}
}
if (!check_swap)
{
return false;
}
Pin contact_pin = null;
Collection<Item> curr_contacts = contact_trace.get_start_contacts();
for (Item tmp_contact : curr_contacts)
{
if (tmp_contact instanceof Pin)
{
contact_pin = (Pin) tmp_contact;
break;
}
}
if (contact_pin == null)
{
return false;
}
Polyline combined_polyline = contact_polyline.combine(trace_polyline);
Direction nearest_pin_exit_direction =
contact_pin.calc_nearest_exit_restriction_direction(combined_polyline, this.get_half_width(), this.get_layer());
if (nearest_pin_exit_direction == null || nearest_pin_exit_direction.equals(contact_polyline.arr[1].direction()))
{
return false; // direction would not be changed
}
contact_trace.set_fixed_state(this.get_fixed_state());
this.combine();
return true;
}
// primary data
private Polyline lines;
}