freerouting/src/main/java/eu/mihosoft/freerouting/board/Trace.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
}