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

814 lines
30 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.
*
* Pin.java
*
* Created on 6. Juni 2003, 08:04
*/
package eu.mihosoft.freerouting.board;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.Shape;
import eu.mihosoft.freerouting.geometry.planar.ConvexShape;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.geometry.planar.Direction;
import eu.mihosoft.freerouting.geometry.planar.Line;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.library.Package;
import eu.mihosoft.freerouting.library.Padstack;
/**
* Class describing the functionality of an electrical Item on the eu.mihosoft.freerouting.board
* with a shape on 1 or several layers.
*
* @author Alfons Wirtz
*/
public class Pin extends DrillItem implements java.io.Serializable
{
/** Creates a new instance of Pin with the input parameters.
* (p_to_layer - p_from_layer + 1) shapes must be provided.
* p_pin_no isthe number of the pin in its component (starting with 0).
*/
Pin(int p_component_no, int p_pin_no, int[] p_net_no_arr, int p_clearance_type, int p_id_no,
FixedState p_fixed_state, BasicBoard p_board)
{
super(null, p_net_no_arr, p_clearance_type, p_id_no, p_component_no, p_fixed_state, p_board);
this.pin_no = p_pin_no;
}
/**
* Calculates the relative location of this pin to its component.
*/
public Vector relative_location()
{
Component component = board.components.get(this.get_component_no());
Package lib_package = component.get_package();
Package.Pin package_pin = lib_package.get_pin(this.pin_no);
Vector rel_location = package_pin.relative_location;
double component_rotation = component.get_rotation_in_degree();
if (!component.placed_on_front() && !board.components.get_flip_style_rotate_first())
{
rel_location = package_pin.relative_location.mirror_at_y_axis();
}
if (component_rotation % 90 == 0)
{
int component_ninety_degree_factor = ((int) component_rotation )/ 90;
if (component_ninety_degree_factor != 0)
{
rel_location = rel_location.turn_90_degree(component_ninety_degree_factor);
}
}
else
{
// rotation may be not exact
FloatPoint location_approx = rel_location.to_float();
location_approx = location_approx.rotate(Math.toRadians(component_rotation), FloatPoint.ZERO);
rel_location = location_approx.round().difference_by(Point.ZERO);
}
if (!component.placed_on_front() && board.components.get_flip_style_rotate_first())
{
rel_location = rel_location.mirror_at_y_axis();
}
return rel_location;
}
public Point get_center()
{
Point pin_center = super.get_center();
if (pin_center == null)
{
// Calculate the pin center.
Component component = board.components.get(this.get_component_no());
pin_center = component.get_location().translate_by(this.relative_location());
// check that the pin center is inside the pin shape and correct it eventually
Padstack padstack = get_padstack();
int from_layer = padstack.from_layer();
int to_layer = padstack.to_layer();
Shape curr_shape = null;
for (int i = 0; i < to_layer - from_layer + 1; ++i)
{
curr_shape = this.get_shape(i);
if (curr_shape != null)
{
break;
}
}
if (curr_shape == null)
{
System.out.println("Pin: At least 1 shape != null expected");
}
else if (!curr_shape.contains_inside(pin_center))
{
pin_center = curr_shape.centre_of_gravity().round();
}
this.set_center(pin_center);
}
return pin_center;
}
public Padstack get_padstack()
{
Component component = board.components.get(get_component_no());
if (component == null)
{
System.out.println("Pin.get_padstack; component not found");
return null;
}
int padstack_no = component.get_package().get_pin(pin_no).padstack_no;
return board.library.padstacks.get(padstack_no);
}
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 Pin(get_component_no(), this.pin_no, curr_net_no_arr, clearance_class_no(),
p_id_no, get_fixed_state(), board);
}
/**
* Return the name of this pin in the package of this component.
*/
public String name()
{
Component component = board.components.get(this.get_component_no());
if (component == null)
{
System.out.println("Pin.name: component not found");
return null;
}
return component.get_package().get_pin(pin_no).name;
}
/**
* Gets index of this pin in the eu.mihosoft.freerouting.library package of the pins component.
*/
public int get_index_in_package()
{
return pin_no;
}
public Shape get_shape(int p_index)
{
Padstack padstack = get_padstack();
if (this.precalculated_shapes == null)
{
// all shapes have to be calculated at once, because otherwise calculation
// of from_layer and to_layer may not be correct
this.precalculated_shapes = new Shape[padstack.to_layer() - padstack.from_layer() + 1];
Component component = board.components.get(this.get_component_no());
if (component == null)
{
System.out.println("Pin.get_shape: component not found");
return null;
}
Package lib_package = component.get_package();
if (lib_package == null)
{
System.out.println("Pin.get_shape: package not found");
return null;
}
Package.Pin package_pin = lib_package.get_pin(this.pin_no);
if (package_pin == null)
{
System.out.println("Pin.get_shape: pin_no out of range");
return null;
}
Vector rel_location = package_pin.relative_location;
double component_rotation = component.get_rotation_in_degree();
boolean mirror_at_y_axis = !component.placed_on_front() && !board.components.get_flip_style_rotate_first();
if (mirror_at_y_axis)
{
rel_location = package_pin.relative_location.mirror_at_y_axis();
}
Vector component_translation = component.get_location().difference_by(Point.ZERO);
for (int index = 0; index < this.precalculated_shapes.length; ++index)
{
int padstack_layer = get_padstack_layer(index);
ConvexShape curr_shape = padstack.get_shape(padstack_layer);
if (curr_shape == null)
{
continue;
}
double pin_rotation = package_pin.rotation_in_degree;
if (pin_rotation % 90 == 0)
{
int pin_ninety_degree_factor = ((int) pin_rotation)/ 90;
if (pin_ninety_degree_factor!= 0)
{
curr_shape = (ConvexShape) curr_shape.turn_90_degree(pin_ninety_degree_factor, Point.ZERO);
}
}
else
{
curr_shape = (ConvexShape) curr_shape.rotate_approx(Math.toRadians(pin_rotation), FloatPoint.ZERO);
}
if (mirror_at_y_axis)
{
curr_shape = (ConvexShape) curr_shape.mirror_vertical(Point.ZERO);
}
// translate the shape first relative to the component
ConvexShape translated_shape = (ConvexShape) curr_shape.translate_by(rel_location);
if (component_rotation % 90 == 0)
{
int component_ninety_degree_factor = ((int) component_rotation )/ 90;
if (component_ninety_degree_factor != 0)
{
translated_shape = (ConvexShape) translated_shape.turn_90_degree(component_ninety_degree_factor, Point.ZERO);
}
}
else
{
translated_shape = (ConvexShape) translated_shape.rotate_approx(Math.toRadians(component_rotation), FloatPoint.ZERO);
}
if (!component.placed_on_front() && board.components.get_flip_style_rotate_first())
{
translated_shape = (ConvexShape) translated_shape.mirror_vertical(Point.ZERO);
}
this.precalculated_shapes[index] = (ConvexShape) translated_shape.translate_by(component_translation);
}
}
return this.precalculated_shapes[p_index];
}
/**
* Returns the layer of the padstack shape corresponding to the shape with index p_index.
*/
int get_padstack_layer(int p_index)
{
Padstack padstack = get_padstack();
Component component = board.components.get(this.get_component_no());
int padstack_layer;
if (component.placed_on_front() || padstack.placed_absolute)
{
padstack_layer = p_index + this.first_layer();
}
else
{
padstack_layer = padstack.board_layer_count() - p_index - this.first_layer() - 1;
}
return padstack_layer;
}
/**
* Calculates the allowed trace exit directions of the shape of this padstack on layer p_layer
* together with the minimal trace line lengths into thei directions.
* Currently only implemented only for box shapes, where traces are allowed to exit
* the pad only on the small sides.
*/
public java.util.Collection<TraceExitRestriction> get_trace_exit_restrictions(int p_layer)
{
java.util.Collection<TraceExitRestriction> result = new java.util.LinkedList<TraceExitRestriction>();
int padstack_layer = this.get_padstack_layer(p_layer - this.first_layer());
double pad_xy_factor = 1.5;
// setting 1.5 to a higher factor may hinder the shove algorithm of the autorouter between
// the pins of SMD components, because the channels can get blocked by the shove_fixed stubs.
Component component = board.components.get(this.get_component_no());
if (component != null)
{
if (component.get_package().pin_count() <= 3)
{
pad_xy_factor *= 2; // allow connection to the longer side also for shorter pads.
}
}
java.util.Collection<Direction> padstack_exit_directions =
this.get_padstack().get_trace_exit_directions(padstack_layer, pad_xy_factor );
if (padstack_exit_directions.isEmpty())
{
return result;
}
if (component == null)
{
return result;
}
Shape curr_shape = this.get_shape(p_layer - this.first_layer());
if (curr_shape == null || !(curr_shape instanceof TileShape))
{
return result;
}
TileShape pad_shape = (TileShape) curr_shape;
double component_rotation = component.get_rotation_in_degree();
Point pin_center = this.get_center();
FloatPoint center_approx = pin_center.to_float();
for (Direction curr_padstack_exit_direction : padstack_exit_directions)
{
Package lib_package = component.get_package();
if (lib_package == null)
{
continue;
}
Package.Pin package_pin = lib_package.get_pin(this.pin_no);
if (package_pin == null)
{
continue;
}
double curr_rotation_in_degree = component_rotation + package_pin.rotation_in_degree;
Direction curr_exit_direction;
if (curr_rotation_in_degree % 45 == 0)
{
int fortyfive_degree_factor = ((int) curr_rotation_in_degree)/ 45;
curr_exit_direction = curr_padstack_exit_direction.turn_45_degree(fortyfive_degree_factor);
}
else
{
double curr_angle_in_radian =
Math.toRadians(curr_rotation_in_degree) + curr_padstack_exit_direction.angle_approx();
curr_exit_direction = Direction.get_instance_approx(curr_angle_in_radian);
}
// calculate the minimum line length from the pin center into curr_exit_direction
int intersecting_border_line_no = pad_shape.intersecting_border_line_no(pin_center, curr_exit_direction);
if (intersecting_border_line_no < 0)
{
System.out.println("Pin.get_trace_exit_restrictions: border line not found");
continue;
}
Line curr_exit_line = new Line(pin_center, curr_exit_direction);
FloatPoint nearest_border_point = curr_exit_line.intersection_approx(pad_shape.border_line(intersecting_border_line_no));
TraceExitRestriction curr_exit_restriction =
new TraceExitRestriction(curr_exit_direction, center_approx.distance(nearest_border_point));
result.add(curr_exit_restriction);
}
return result;
}
/**
* Returns true, if this pin has exit restrictions on some kayer.
*/
public boolean has_trace_exit_restrictions()
{
for (int i = this.first_layer(); i <= this.last_layer(); ++i)
{
java.util.Collection<TraceExitRestriction> curr_exit_restrictions = get_trace_exit_restrictions(i);
if (curr_exit_restrictions.size() > 0)
{
return true;
}
}
return false;
}
/**
* Returns true, if vias throw the pads of this pins are allowed, false, otherwise.
* Currently drills are allowed to SMD-pins.
*/
public boolean drill_allowed()
{
return (this.first_layer() == this.last_layer());
}
public boolean is_obstacle(Item p_other)
{
if (p_other == this || p_other instanceof ObstacleArea)
{
return false;
}
if (!p_other.shares_net(this))
{
return true;
}
if (p_other instanceof Trace)
{
return false;
}
if (this.drill_allowed() && p_other instanceof Via && ((Via) p_other).attach_allowed)
{
return false;
}
return true;
}
public void turn_90_degree(int p_factor, IntPoint p_pole)
{
this.set_center(null);
clear_derived_data();
}
public void rotate_approx(double p_angle_in_degree, FloatPoint p_pole)
{
this.set_center(null);
this.clear_derived_data();
}
public void change_placement_side(IntPoint p_pole)
{
this.set_center(null);
this.clear_derived_data();
}
public void clear_derived_data()
{
super.clear_derived_data();
this.precalculated_shapes = null;
}
/**
* Return all Pins, that can be swapped with this pin.
*/
public java.util.Set<Pin> get_swappable_pins()
{
java.util.Set<Pin> result = new java.util.TreeSet<Pin>();
Component component = this.board.components.get(this.get_component_no());
if (component == null)
{
return result;
}
eu.mihosoft.freerouting.library.LogicalPart logical_part = component.get_logical_part();
if (logical_part == null)
{
return result;
}
eu.mihosoft.freerouting.library.LogicalPart.PartPin this_part_pin = logical_part.get_pin(this.pin_no);
if (this_part_pin == null)
{
return result;
}
if (this_part_pin.gate_pin_swap_code <= 0)
{
return result;
}
// look up all part pins with the same gate_name and the same gate_pin_swap_code
for( int i = 0; i < logical_part.pin_count(); ++i)
{
if (i == this.pin_no)
{
continue;
}
eu.mihosoft.freerouting.library.LogicalPart.PartPin curr_part_pin = logical_part.get_pin(i);
if (curr_part_pin != null && curr_part_pin.gate_pin_swap_code == this_part_pin.gate_pin_swap_code
&& curr_part_pin.gate_name.equals(this_part_pin.gate_name))
{
Pin curr_swappeble_pin = this.board.get_pin(this.get_component_no(), curr_part_pin.pin_no);
if (curr_swappeble_pin != null)
{
result.add(curr_swappeble_pin);
}
else
{
System.out.println("Pin.get_swappable_pins: swappable pin not found");
}
}
}
return result;
}
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.PINS);
}
public java.awt.Color[] get_draw_colors(eu.mihosoft.freerouting.boardgraphics.GraphicsContext p_graphics_context)
{
java.awt.Color[] result;
if (this.net_count() > 0)
{
result = p_graphics_context.get_pin_colors();
}
else
{
// display unconnected pins as obstacles
result = p_graphics_context.get_obstacle_colors();
}
return result;
}
public double get_draw_intensity(eu.mihosoft.freerouting.boardgraphics.GraphicsContext p_graphics_context)
{
return p_graphics_context.get_pin_color_intensity();
}
/**
* Swaps the nets of this pin and p_other.
* Returns false on error.
*/
public boolean swap(Pin p_other)
{
if (this.net_count() > 1 || p_other.net_count() > 1)
{
System.out.println("Pin.swap not yet implemented for pins belonging to more than 1 net ");
return false;
}
int this_net_no;
if (this.net_count() > 0)
{
this_net_no = this.get_net_no(0);
}
else
{
this_net_no = 0;
}
int other_net_no;
if (p_other.net_count() > 0)
{
other_net_no = p_other.get_net_no(0);
}
else
{
other_net_no = 0;
}
this.assign_net_no(other_net_no);
p_other.assign_net_no(this_net_no);
Pin tmp = this.changed_to;
this.changed_to = p_other.changed_to;
p_other.changed_to = tmp;
return true;
}
/**
* Returns the pin, this pin was changed to by pin swapping, or this pin, if it was not swapped.
*/
public Pin get_changed_to()
{
return changed_to;
}
public boolean write(java.io.ObjectOutputStream p_stream)
{
try
{
p_stream.writeObject(this);
}
catch (java.io.IOException e)
{
return false;
}
return true;
}
/** False, if this drillitem is places on the back side of the eu.mihosoft.freerouting.board */
public boolean is_placed_on_front()
{
boolean result = true;
Component component = board.components.get(this.get_component_no());
if (component != null)
{
result = component.placed_on_front();
}
return result;
}
/**
* Returns the smallest width of the pin shape on layer p_layer.
*/
public double get_min_width(int p_layer)
{
int padstack_layer = get_padstack_layer(p_layer - this.first_layer());
Shape padstack_shape = this.get_padstack().get_shape(padstack_layer);
if (padstack_shape == null)
{
System.out.println("Pin.get_min_width: padstack_shape is null");
return 0;
}
eu.mihosoft.freerouting.geometry.planar.IntBox padstack_bounding_box = padstack_shape.bounding_box();
if (padstack_bounding_box == null)
{
System.out.println("Pin.get_min_width: padstack_bounding_box is null");
return 0;
}
return padstack_bounding_box.min_width();
}
/**
* Returns the neckdown half width for traces on p_layer.
* The neckdown width is used, when the pin width is smmaller than the trace width
* to enter or leave the pin with a trace.
*/
public int get_trace_neckdown_halfwidth(int p_layer)
{
double result = Math.max (0.5 * this.get_min_width(p_layer) - 1, 1);
return (int) result;
}
/**
* Returns the largest width of the pin shape on layer p_layer.
*/
public double get_max_width(int p_layer)
{
int padstack_layer = get_padstack_layer(p_layer - this.first_layer());
Shape padstack_shape = this.get_padstack().get_shape(padstack_layer);
if (padstack_shape == null)
{
System.out.println("Pin.get_max_width: padstack_shape is null");
return 0;
}
eu.mihosoft.freerouting.geometry.planar.IntBox padstack_bounding_box = padstack_shape.bounding_box();
if (padstack_bounding_box == null)
{
System.out.println("Pin.get_max_width: padstack_bounding_box is null");
return 0;
}
return padstack_bounding_box.max_width();
}
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("pin") + ": ");
p_window.append(resources.getString("component_2") + " ");
Component component = board.components.get(this.get_component_no());
p_window.append(component.name, resources.getString("component_info"), component);
p_window.append(", " + resources.getString("pin_2") + " ");
p_window.append(component.get_package().get_pin(this.pin_no).name);
p_window.append(", " + resources.getString("padstack") + " ");
eu.mihosoft.freerouting.library.Padstack padstack = this.get_padstack();
p_window.append(padstack.name, resources.getString("padstack_info"), padstack);
p_window.append(" " + resources.getString("at") + " ");
p_window.append(this.get_center().to_float());
this.print_connectable_item_info(p_window, p_locale);
p_window.newline();
}
/**
* Calculates the nearest exit restriction direction for changing p_trace_polyline.
* p_trace_polyline is assumed to start at the pin center.
* Returns null, if there is no matching exit restrictions.
*/
Direction calc_nearest_exit_restriction_direction(Polyline p_trace_polyline, int p_trace_half_width, int p_layer)
{
java.util.Collection<Pin.TraceExitRestriction> trace_exit_restrictions = this.get_trace_exit_restrictions(p_layer);
if (trace_exit_restrictions.isEmpty())
{
return null;
}
Shape pin_shape = this.get_shape(p_layer - this.first_layer());
Point pin_center = this.get_center();
if (!(pin_shape instanceof TileShape))
{
return null;
}
final double edge_to_turn_dist = this.board.rules.get_pin_edge_to_turn_dist();
if (edge_to_turn_dist < 0)
{
return null;
}
TileShape offset_pin_shape = (TileShape)((TileShape)pin_shape).offset(edge_to_turn_dist + p_trace_half_width);
int [][] entries = offset_pin_shape.entrance_points(p_trace_polyline);
if (entries.length == 0)
{
return null;
}
int [] latest_entry_tuple = entries[entries.length - 1];
FloatPoint trace_entry_location_approx =
p_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;
FloatPoint nearest_exit_corner = null;
Direction pin_exit_direction = 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 < p_trace_polyline.corner_count(); ++i )
{
FloatPoint curr_trace_corner = p_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;
pin_exit_direction = curr_exit_restriction.direction;
nearest_exit_corner = curr_exit_corner;
}
}
return pin_exit_direction;
}
/**
* Calculates the nearest trace exit point of the pin on p_layer.
* Returns null, if the pin has no trace exit restrictions.
*/
public FloatPoint nearest_trace_exit_corner(FloatPoint p_from_point, int p_trace_half_width, int p_layer)
{
java.util.Collection<Pin.TraceExitRestriction> trace_exit_restrictions = this.get_trace_exit_restrictions(p_layer);
if (trace_exit_restrictions.isEmpty())
{
return null;
}
Shape pin_shape = this.get_shape(p_layer - this.first_layer());
Point pin_center = this.get_center();
if (!(pin_shape instanceof TileShape))
{
return null;
}
final double edge_to_turn_dist = this.board.rules.get_pin_edge_to_turn_dist();
if (edge_to_turn_dist < 0)
{
return null;
}
TileShape offset_pin_shape = (TileShape)((TileShape)pin_shape).offset(edge_to_turn_dist + p_trace_half_width);
// calculate the nearest legal pin exit point to trace_entry_location_approx
double min_exit_corner_distance = Double.MAX_VALUE;
FloatPoint nearest_exit_corner = null;
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(p_from_point);
if (curr_exit_corner_distance < min_exit_corner_distance)
{
min_exit_corner_distance = curr_exit_corner_distance;
nearest_exit_corner = curr_exit_corner;
}
}
return nearest_exit_corner;
}
/** The number of this pin in its component (starting with 0). */
public final int pin_no;
/**
* The pin, this pin was changed to by swapping or this pin, if no pin swap accured.
*/
private Pin changed_to = this;
private transient Shape[] precalculated_shapes = null;
/**
* Describes an exit restriction from a trace from a pin pad.
*/
public static class TraceExitRestriction
{
/** Creates a new instance of TraceExitRestriction */
public TraceExitRestriction(Direction p_direction, double p_min_length)
{
direction = p_direction;
min_length = p_min_length;
}
public final Direction direction;
public final double min_length;
}
}