550 lines
17 KiB
Java
550 lines
17 KiB
Java
/*
|
|
* Copyright (C) 2014 Alfons Wirtz
|
|
* website www.freerouting.net
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License at <http://www.gnu.org/licenses/>
|
|
* for more details.
|
|
*/
|
|
|
|
package eu.mihosoft.freerouting.board;
|
|
|
|
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
|
|
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
|
|
import eu.mihosoft.freerouting.geometry.planar.Point;
|
|
import eu.mihosoft.freerouting.geometry.planar.TileShape;
|
|
|
|
import java.util.Collection;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import java.util.TreeSet;
|
|
|
|
|
|
/**
|
|
*
|
|
* Class describing functionality required for traces in the plane.
|
|
*
|
|
* @author Alfons Wirtz
|
|
*/
|
|
|
|
public abstract class Trace extends Item implements Connectable, java.io.Serializable
|
|
{
|
|
|
|
Trace(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_net_no_arr, p_clearance_type, p_id_no, p_group_no, p_fixed_state, p_board);
|
|
half_width = p_half_width ;
|
|
p_layer = Math.max(p_layer, 0);
|
|
if (p_board != null)
|
|
{
|
|
p_layer = Math.min(p_layer, p_board.get_layer_count() - 1);
|
|
}
|
|
layer = p_layer;
|
|
}
|
|
|
|
/**
|
|
* returns the first corner of the trace
|
|
*/
|
|
public abstract Point first_corner();
|
|
|
|
/**
|
|
* returns the last corner of the trace
|
|
*/
|
|
public abstract Point last_corner();
|
|
|
|
public int first_layer()
|
|
{
|
|
return this.layer;
|
|
}
|
|
|
|
public int last_layer()
|
|
{
|
|
return this.layer;
|
|
}
|
|
|
|
public int get_layer()
|
|
{
|
|
return this.layer;
|
|
}
|
|
|
|
public void set_layer(int p_layer)
|
|
{
|
|
this.layer = p_layer;
|
|
}
|
|
|
|
public int get_half_width()
|
|
{
|
|
return half_width;
|
|
}
|
|
|
|
/**
|
|
* Returns the length of this trace.
|
|
*/
|
|
public abstract double get_length();
|
|
|
|
/**
|
|
* Returns the half with enlarged by the clearance compensation value for the tree
|
|
* with id number p_ttree_id_no
|
|
* Equals get_half_width(), if no clearance compensation is used in this tree.
|
|
*/
|
|
public int get_compensated_half_width(ShapeSearchTree p_search_tree)
|
|
{
|
|
int result = this.half_width + p_search_tree.clearance_compensation_value(clearance_class_no(), this.layer);
|
|
return result;
|
|
}
|
|
|
|
public boolean is_obstacle(Item p_other)
|
|
{
|
|
if (p_other == this || p_other instanceof ViaObstacleArea || p_other instanceof ComponentObstacleArea)
|
|
{
|
|
return false;
|
|
}
|
|
if (p_other instanceof ConductionArea && !((ConductionArea) p_other).get_is_obstacle())
|
|
{
|
|
return false;
|
|
}
|
|
if (!p_other.shares_net(this))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get a list of all items with a connection point on the layer
|
|
* of this trace equal to its first corner.
|
|
*/
|
|
public Set<Item> get_start_contacts()
|
|
{
|
|
return get_normal_contacts(first_corner(), false);
|
|
}
|
|
|
|
/**
|
|
* Get a list of all items with a connection point on the layer
|
|
* of this trace equal to its last corner.
|
|
*/
|
|
public Set<Item> get_end_contacts()
|
|
{
|
|
return get_normal_contacts(last_corner(), false);
|
|
}
|
|
|
|
public Point normal_contact_point(Item p_other)
|
|
{
|
|
return p_other.normal_contact_point(this);
|
|
}
|
|
|
|
public Set<Item> get_normal_contacts()
|
|
{
|
|
Set<Item> result = new TreeSet<Item>();
|
|
Point start_corner = this.first_corner();
|
|
if (start_corner != null)
|
|
{
|
|
result.addAll(get_normal_contacts(start_corner, false));
|
|
}
|
|
Point end_corner = this.last_corner();
|
|
if (end_corner != null)
|
|
{
|
|
result.addAll(get_normal_contacts(end_corner, false));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public boolean is_route()
|
|
{
|
|
return !is_user_fixed() && this.net_count() > 0;
|
|
}
|
|
|
|
/**
|
|
* Returns true, if this trace is not contacted at its first or at its last point.
|
|
*/
|
|
public boolean is_tail()
|
|
{
|
|
Collection<Item> contact_list = this.get_start_contacts();
|
|
if (contact_list.size() == 0)
|
|
{
|
|
return true;
|
|
}
|
|
contact_list = this.get_end_contacts();
|
|
return (contact_list.size() == 0);
|
|
}
|
|
|
|
|
|
public java.awt.Color[] get_draw_colors(eu.mihosoft.freerouting.boardgraphics.GraphicsContext p_graphics_context)
|
|
{
|
|
return p_graphics_context.get_trace_colors(this.is_user_fixed());
|
|
}
|
|
|
|
public int get_draw_priority()
|
|
{
|
|
return eu.mihosoft.freerouting.boardgraphics.Drawable.MAX_DRAW_PRIORITY;
|
|
}
|
|
|
|
public double get_draw_intensity(eu.mihosoft.freerouting.boardgraphics.GraphicsContext p_graphics_context)
|
|
{
|
|
return p_graphics_context.get_trace_color_intensity();
|
|
}
|
|
|
|
/**
|
|
* Get a list of all items having a connection point at p_point
|
|
* on the layer of this trace.
|
|
* If p_ignore_net is false, only contacts to items sharing a net with this trace
|
|
* are calculated. This is the normal case.
|
|
*/
|
|
public Set<Item> get_normal_contacts(Point p_point, boolean p_ignore_net)
|
|
{
|
|
if (p_point == null || !(p_point.equals(this.first_corner()) || p_point.equals(this.last_corner())))
|
|
{
|
|
return new TreeSet<Item>();
|
|
}
|
|
TileShape search_shape = TileShape.get_instance(p_point);
|
|
Set<SearchTreeObject> overlaps = board.overlapping_objects(search_shape, this.layer);
|
|
Set<Item> result = new TreeSet<Item> ();
|
|
for (SearchTreeObject curr_ob : overlaps)
|
|
{
|
|
if (!(curr_ob instanceof Item))
|
|
{
|
|
continue;
|
|
}
|
|
Item curr_item = (Item) curr_ob;
|
|
if (curr_item != this && curr_item.shares_layer(this) && (p_ignore_net || curr_item.shares_net(this)))
|
|
{
|
|
if (curr_item instanceof Trace)
|
|
{
|
|
Trace curr_trace = (Trace) curr_item;
|
|
if (p_point.equals(curr_trace.first_corner())
|
|
|| p_point.equals(curr_trace.last_corner()))
|
|
{
|
|
result.add(curr_item);
|
|
}
|
|
}
|
|
else if (curr_item instanceof DrillItem)
|
|
{
|
|
DrillItem curr_drill_item = (DrillItem) curr_item;
|
|
if(p_point.equals(curr_drill_item.get_center()))
|
|
{
|
|
result.add(curr_item);
|
|
}
|
|
}
|
|
else if (curr_item instanceof ConductionArea)
|
|
{
|
|
ConductionArea curr_area = (ConductionArea) curr_item;
|
|
if (curr_area.get_area().contains(p_point))
|
|
{
|
|
result.add(curr_item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Point normal_contact_point(DrillItem p_drill_item)
|
|
{
|
|
return p_drill_item.normal_contact_point(this);
|
|
}
|
|
|
|
Point normal_contact_point(Trace p_other)
|
|
{
|
|
if (this.layer != p_other.layer)
|
|
{
|
|
return null;
|
|
}
|
|
boolean contact_at_first_corner =
|
|
this.first_corner().equals(p_other.first_corner())
|
|
|| this.first_corner().equals(p_other.last_corner());
|
|
boolean contact_at_last_corner =
|
|
this.last_corner().equals(p_other.first_corner())
|
|
|| this.last_corner().equals(p_other.last_corner());
|
|
Point result;
|
|
if (!(contact_at_first_corner || contact_at_last_corner)
|
|
|| contact_at_first_corner && contact_at_last_corner)
|
|
{
|
|
// no contact point or more than 1 contact point
|
|
result = null;
|
|
}
|
|
else if (contact_at_first_corner)
|
|
{
|
|
result = this.first_corner();
|
|
}
|
|
else // contact at last corner
|
|
{
|
|
result = this.last_corner();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public boolean is_drillable(int p_net_no)
|
|
{
|
|
return this.contains_net(p_net_no);
|
|
}
|
|
|
|
/**
|
|
* looks, if this trace is connectet to the same object
|
|
* at its start and its end point
|
|
*/
|
|
public boolean is_overlap()
|
|
{
|
|
Set<Item> start_contacts = this.get_start_contacts();
|
|
Set<Item> end_contacts = this.get_end_contacts();
|
|
Iterator<Item> it = end_contacts.iterator();
|
|
while (it.hasNext())
|
|
{
|
|
if (start_contacts.contains(it.next()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns true, if it is not allowed to change the location of this item by the push algorithm.
|
|
*/
|
|
public boolean is_shove_fixed()
|
|
{
|
|
if (super.is_shove_fixed())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// check, if the trace belongs to a net, which is not shovable.
|
|
eu.mihosoft.freerouting.rules.Nets nets = this.board.rules.nets;
|
|
for (int curr_net_no : this.net_no_arr)
|
|
{
|
|
if (eu.mihosoft.freerouting.rules.Nets.is_normal_net_no(curr_net_no))
|
|
{
|
|
if (nets.get(curr_net_no).get_class().is_shove_fixed())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* returns the endpoint of this trace with the shortest distance
|
|
* to p_from_point
|
|
*/
|
|
public Point nearest_end_point(Point p_from_point)
|
|
{
|
|
Point p1 = first_corner();
|
|
Point p2 = last_corner();
|
|
FloatPoint from_point = p_from_point.to_float();
|
|
double d1 = from_point.distance(p1.to_float());
|
|
double d2 = from_point.distance(p2.to_float());
|
|
Point result;
|
|
if (d1 < d2)
|
|
{
|
|
result = p1;
|
|
}
|
|
else
|
|
{
|
|
result = p2;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Checks, if this trace can be reached by other items via more than one path
|
|
*/
|
|
public boolean is_cycle()
|
|
{
|
|
if (this.is_overlap())
|
|
{
|
|
return true;
|
|
}
|
|
Set<Item> visited_items = new TreeSet<Item>();
|
|
Collection<Item> start_contacts = this.get_start_contacts();
|
|
// a cycle exists if through expanding the start contact we reach
|
|
// this trace again via an end contact
|
|
for (Item curr_contact : start_contacts)
|
|
{
|
|
// make shure, that all direct neighbours are
|
|
// expanded from here, to block coming back to
|
|
// this trace via a start contact.
|
|
visited_items.add(curr_contact);
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
for (Item curr_contact : start_contacts)
|
|
{
|
|
if (curr_contact.is_cycle_recu(visited_items, this, this, ignore_areas))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public int shape_layer(int p_index)
|
|
{
|
|
return layer;
|
|
}
|
|
|
|
public Point[] get_ratsnest_corners()
|
|
{
|
|
// Use only uncontacted enpoints of the trace.
|
|
// Otherwise the allocated memory in the calculation of the incompletes might become very big.
|
|
int stub_count = 0;
|
|
boolean stub_at_start = false;
|
|
boolean stub_at_end = false;
|
|
if (get_start_contacts().isEmpty())
|
|
{
|
|
++stub_count;
|
|
stub_at_start = true;
|
|
}
|
|
if (get_end_contacts().isEmpty())
|
|
{
|
|
++stub_count;
|
|
stub_at_end = true;
|
|
}
|
|
Point[] result = new Point[stub_count];
|
|
int stub_no = 0;
|
|
if (stub_at_start)
|
|
{
|
|
result[stub_no] = first_corner();
|
|
++stub_no;
|
|
}
|
|
if (stub_at_end)
|
|
{
|
|
result[stub_no] = last_corner();
|
|
}
|
|
for (int i = 0; i < result.length; ++i)
|
|
{
|
|
if (result[i] == null)
|
|
{
|
|
return new Point[0];// Trace is inconsistent
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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 abstract boolean check_connection_to_pin(boolean p_at_start);
|
|
|
|
public boolean is_selected_by_filter(ItemSelectionFilter p_filter)
|
|
{
|
|
if (!this.is_selected_by_fixed_filter(p_filter))
|
|
{
|
|
return false;
|
|
}
|
|
return p_filter.is_selected(ItemSelectionFilter.SelectableChoices.TRACES);
|
|
}
|
|
|
|
|
|
/**
|
|
* Looks up touching pins at the first corner and the last corner of the trace.
|
|
* Used to avoid acid traps.
|
|
*/
|
|
Set<Pin> touching_pins_at_end_corners()
|
|
{
|
|
Set<Pin> result = new TreeSet<Pin>();
|
|
if (this.board == null)
|
|
{
|
|
return result;
|
|
}
|
|
Point curr_end_point = this.first_corner();
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
IntOctagon curr_oct = curr_end_point.surrounding_octagon();
|
|
curr_oct = curr_oct.enlarge(this.half_width);
|
|
Set<Item> curr_overlaps = this.board.overlapping_items_with_clearance(curr_oct, this.layer, new int[0], this.clearance_class_no());
|
|
for (Item curr_item : curr_overlaps)
|
|
{
|
|
if ((curr_item instanceof Pin) && curr_item.shares_net(this))
|
|
{
|
|
result.add((Pin)curr_item);
|
|
}
|
|
}
|
|
curr_end_point = this.last_corner();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public void print_info(ObjectInfoPanel p_window, java.util.Locale p_locale)
|
|
{
|
|
java.util.ResourceBundle resources =
|
|
java.util.ResourceBundle.getBundle("eu.mihosoft.freerouting.board.resources.ObjectInfoPanel", p_locale);
|
|
p_window.append_bold(resources.getString("trace"));
|
|
p_window.append(" " + resources.getString("from"));
|
|
p_window.append(this.first_corner().to_float());
|
|
p_window.append(resources.getString("to"));
|
|
p_window.append(this.last_corner().to_float());
|
|
p_window.append(resources.getString("on_layer") + " ");
|
|
p_window.append(this.board.layer_structure.arr[this.layer].name);
|
|
p_window.append(", " + resources.getString("width") + " ");
|
|
p_window.append(2 * this.half_width);
|
|
p_window.append(", " + resources.getString("length") + " ");
|
|
p_window.append(this.get_length());
|
|
this.print_connectable_item_info(p_window, p_locale);
|
|
p_window.newline();
|
|
}
|
|
|
|
public boolean validate()
|
|
{
|
|
boolean result = super.validate();
|
|
|
|
if (this.first_corner().equals(this.last_corner()))
|
|
{
|
|
System.out.println("Trace.validate: first and last corner are equal");
|
|
result = false;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
/**
|
|
* looks, if this trace can be combined with other traces .
|
|
* Returns true, if somthing has been combined.
|
|
*/
|
|
abstract boolean combine();
|
|
|
|
/**
|
|
* 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.
|
|
* 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 abstract Collection<PolylineTrace> split(IntOctagon p_clip_shape);
|
|
|
|
/**
|
|
* 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 this trace.
|
|
*/
|
|
public abstract Trace[] split(Point p_point);
|
|
|
|
/**
|
|
* Tries to make this trace shorter according to its rules.
|
|
* Returns true if the geometry of the trace was changed.
|
|
*/
|
|
public abstract boolean pull_tight(PullTightAlgo p_pull_tight_algo);
|
|
|
|
|
|
private final int half_width ; // half width of the trace pen
|
|
private int layer ; // eu.mihosoft.freerouting.board layer of the trace
|
|
}
|