/*
* 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.
*
* OptViaAlgo.java
*
* Created on 31. Maerz 2006, 06:58
*
*/
package board;
import java.util.Collection;
import java.util.Iterator;
import geometry.planar.Point;
import geometry.planar.IntPoint;
import geometry.planar.Vector;
import geometry.planar.Polyline;
import geometry.planar.FloatPoint;
import geometry.planar.FloatLine;
import geometry.planar.Side;
import autoroute.AutorouteControl.ExpansionCostFactor;
/**
* Contains functions for optimizing and improving via locations.
*
* @author Alfons Wirtz
*/
public class OptViaAlgo
{
/**
* Optimizes the location of a via connected to at most 2 traces
* according to the trace costs on the layers of the connected traces
* If p_trace_cost_arr == null, the horizontal and vertical trace costs will be set to 1.
* Returns false, if the via was not changed.
*/
public static boolean opt_via_location(RoutingBoard p_board, Via p_via,
ExpansionCostFactor[] p_trace_cost_arr, int p_trace_pull_tight_accuracy, int p_max_recursion_depth)
{
if (p_via.is_shove_fixed())
{
return false;
}
if (p_max_recursion_depth <= 0)
{
if (p_board.get_test_level().ordinal() >= TestLevel.CRITICAL_DEBUGGING_OUTPUT.ordinal())
{
System.out.println("OptViaAlgo.opt_via_location: probably endless loop") ;
}
return false;
}
Collection<Item> contacts = p_via.get_normal_contacts();
boolean is_plane_or_fanout_via = (contacts.size() == 1);
PolylineTrace first_trace = null;
PolylineTrace second_trace = null;
if (!is_plane_or_fanout_via)
{
if (contacts.size() != 2)
{
return false;
}
Iterator<Item> it = contacts.iterator();
Item curr_item = it.next();
if (curr_item.is_shove_fixed() || !(curr_item instanceof PolylineTrace))
{
if (curr_item instanceof ConductionArea)
{
is_plane_or_fanout_via = true;
}
else
{
return false;
}
}
else
{
first_trace = (PolylineTrace) curr_item;
}
curr_item = it.next();
if (curr_item.is_shove_fixed() || !(curr_item instanceof PolylineTrace))
{
if (curr_item instanceof ConductionArea)
{
is_plane_or_fanout_via = true;
}
else
{
return false;
}
}
else
{
second_trace = (PolylineTrace) curr_item;
}
}
if (is_plane_or_fanout_via)
{
return opt_plane_or_fanout_via(p_board, p_via, p_trace_pull_tight_accuracy, p_max_recursion_depth);
}
Point via_center = p_via.get_center();
int first_layer = first_trace.get_layer();
int second_layer = second_trace.get_layer();
Point first_trace_from_corner;
Point second_trace_from_corner;
// calculate first_trace_from_corner and second_trace_from_corner
if (first_trace.first_corner().equals(via_center))
{
first_trace_from_corner = first_trace.polyline().corner(1);
}
else if (first_trace.last_corner().equals(via_center))
{
first_trace_from_corner
= first_trace.polyline().corner(first_trace.polyline().corner_count() - 2);
}
else
{
System.out.println("OptViaAlgo.opt_via_location: incorrect first contact");
return false;
}
if (second_trace.first_corner().equals(via_center))
{
second_trace_from_corner = second_trace.polyline().corner(1);
}
else if (second_trace.last_corner().equals(via_center))
{
second_trace_from_corner
= second_trace.polyline().corner(second_trace.polyline().corner_count() - 2);
}
else
{
System.out.println("OptViaAlgo.opt_via_location: incorrect second contact");
return false;
}
ExpansionCostFactor first_layer_trace_costs;
ExpansionCostFactor second_layer_trace_costs;
if (p_trace_cost_arr != null)
{
first_layer_trace_costs = p_trace_cost_arr[first_layer];
second_layer_trace_costs = p_trace_cost_arr[second_layer];
}
else
{
first_layer_trace_costs = new ExpansionCostFactor(1, 1);
second_layer_trace_costs = first_layer_trace_costs;
}
Point new_location =
reposition_via(p_board, p_via,
first_trace.get_half_width(), first_trace.clearance_class_no(), first_trace.get_layer(),
first_layer_trace_costs, first_trace_from_corner,
second_trace.get_half_width(), second_trace.clearance_class_no(), second_trace.get_layer(),
second_layer_trace_costs, second_trace_from_corner);
if (new_location == null || new_location.equals(via_center))
{
return false;
}
Vector delta = new_location.difference_by(via_center);
if (!MoveDrillItemAlgo.insert(p_via, delta, 9, 9, null, p_board))
{
System.out.println("OptViaAlgo.opt_via_location: move via failed");
return false;
}
ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES);
Collection<Item> picked_items = p_board.pick_items(new_location, first_trace.get_layer(), filter);
for (Item curr_item : picked_items)
{
((PolylineTrace) curr_item).pull_tight(true, p_trace_pull_tight_accuracy, null);
}
picked_items = p_board.pick_items(new_location, second_trace.get_layer(), filter);
for (Item curr_item : picked_items)
{
((PolylineTrace) curr_item).pull_tight(true, p_trace_pull_tight_accuracy, null);
}
filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.VIAS);
picked_items = p_board.pick_items(new_location, first_trace.get_layer(), filter);
for (Item curr_item : picked_items)
{
opt_via_location(p_board, (Via) curr_item, p_trace_cost_arr,
p_trace_pull_tight_accuracy, p_max_recursion_depth - 1);
break;
}
return true;
}
/**
* Optimisations for vias with only 1 connected Trace (Plane or Fanout Vias).
*/
private static boolean opt_plane_or_fanout_via(RoutingBoard p_board, Via p_via,
int p_trace_pull_tight_accuracy, int p_max_recursion_depth)
{
if (p_max_recursion_depth <= 0)
{
if (p_board.get_test_level().ordinal() >= TestLevel.CRITICAL_DEBUGGING_OUTPUT.ordinal())
{
System.out.println("OptViaAlgo.opt_plane_or_fanout_via: probably endless loop") ;
}
return false;
}
Collection<Item> contact_list = p_via.get_normal_contacts();
if (contact_list.isEmpty())
{
return false;
}
ConductionArea contact_plane = null;
PolylineTrace contact_trace = null;
for (Item curr_contact : contact_list)
{
if (curr_contact instanceof ConductionArea)
{
if (contact_plane != null)
{
return false;
}
contact_plane = (ConductionArea) curr_contact;
}
else if (curr_contact instanceof PolylineTrace)
{
if (curr_contact.is_shove_fixed() || contact_trace != null)
{
return false;
}
contact_trace = (PolylineTrace) curr_contact;
}
else
{
return false;
}
}
if (contact_trace == null)
{
return false;
}
Point via_center = p_via.get_center();
boolean at_first_corner;
if (contact_trace.first_corner().equals(via_center))
{
at_first_corner = true;
}
else if (contact_trace.last_corner().equals(via_center))
{
at_first_corner = false;
}
else
{
System.out.println("OptViaAlgo.opt_plane_or_fanout_via: unconsistant contact");
return false;
}
Polyline trace_polyline = contact_trace.polyline();
Point check_corner;
if (at_first_corner)
{
check_corner = trace_polyline.corner(1);
}
else
{
check_corner = trace_polyline.corner(trace_polyline.corner_count() - 2);
}
IntPoint rounded_check_corner = check_corner.to_float().round();
int trace_half_width = contact_trace.get_half_width();
int trace_layer = contact_trace.get_layer();
int trace_cl_class_no = contact_trace.clearance_class_no();
Point new_via_location =
reposition_via(p_board, p_via, rounded_check_corner, trace_half_width ,
trace_layer, trace_cl_class_no);
if (new_via_location == null && trace_polyline.corner_count() >= 3)
{
// try to project the via to the previous line
Point prev_corner;
if (at_first_corner)
{
prev_corner = trace_polyline.corner(2);
}
else
{
prev_corner = trace_polyline.corner(trace_polyline.corner_count() - 3);
}
FloatPoint float_check_corner = check_corner.to_float();
FloatPoint float_via_center = via_center.to_float();
FloatPoint float_prev_corner = prev_corner.to_float();
if (float_check_corner.scalar_product(float_via_center, float_prev_corner) != 0)
{
FloatLine curr_line = new FloatLine(float_check_corner, float_prev_corner);
Point projection = curr_line.perpendicular_projection(float_via_center).round();
Vector diff_vector = projection.difference_by(via_center);
boolean projection_ok = true;
AngleRestriction angle_restriction = p_board.rules.get_trace_angle_restriction();
if (projection.equals(via_center) ||
angle_restriction == AngleRestriction.NINETY_DEGREE && !diff_vector.is_orthogonal() ||
angle_restriction == AngleRestriction.FORTYFIVE_DEGREE && !diff_vector.is_multiple_of_45_degree())
{
projection_ok = false;
}
if (projection_ok)
{
if(MoveDrillItemAlgo.check(p_via, diff_vector, 0, 0, null, p_board, null))
{
double ok_length =
p_board.check_trace_segment(via_center, projection, trace_layer, p_via.net_no_arr,
trace_half_width, trace_cl_class_no, false);
if (ok_length >= Integer.MAX_VALUE)
{
new_via_location = projection;
}
}
}
}
}
if (new_via_location == null)
{
return false;
}
if (contact_plane != null)
{
// check, that the new location is inside the contact plane
ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.CONDUCTION);
Collection<Item> picked_items = p_board.pick_items(new_via_location, contact_plane.get_layer(), filter);
boolean contact_ok = false;
for (Item curr_item : picked_items)
{
if (curr_item == contact_plane)
{
contact_ok = true;
break;
}
}
if (!contact_ok)
{
return false;
}
}
Vector diff_vector = new_via_location.difference_by(via_center);
if (!MoveDrillItemAlgo.insert(p_via, diff_vector, 9, 9, null, p_board))
{
System.out.println("OptViaAlgo.opt_plane_or_fanout_via: move via failed");
return false;
}
ItemSelectionFilter filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.TRACES);
Collection<Item> picked_items = p_board.pick_items(new_via_location, contact_trace.get_layer(), filter);
for (Item curr_item : picked_items)
{
((PolylineTrace) curr_item).pull_tight(true, p_trace_pull_tight_accuracy, null);
}
if (new_via_location.equals(check_corner))
{
opt_plane_or_fanout_via(p_board, p_via, p_trace_pull_tight_accuracy, p_max_recursion_depth - 1);
}
return true;
}
/**
* Tries to move the via into the direction of p_to_location as far as possible
* Return the new location of the via, or null, if no move was possible.
*/
private static Point reposition_via(RoutingBoard p_board, Via p_via,
IntPoint p_to_location, int p_trace_half_width, int p_trace_layer, int p_trace_cl_class)
{
Point from_location = p_via.get_center();
if (from_location.equals(p_to_location))
{
return null;
}
double ok_length = p_board.check_trace_segment(from_location, p_to_location, p_trace_layer, p_via.net_no_arr,
p_trace_half_width, p_trace_cl_class, false);
if (ok_length <= 0)
{
return null;
}
FloatPoint float_from_location = from_location.to_float();
FloatPoint float_to_location = p_to_location.to_float();
FloatPoint new_float_to_location;
if (ok_length >= Integer.MAX_VALUE)
{
new_float_to_location = float_to_location;
}
else
{
new_float_to_location = float_from_location.change_length(float_to_location, ok_length);
}
Point new_to_location = new_float_to_location.round();
Vector delta = new_to_location.difference_by(from_location);
boolean check_ok =
MoveDrillItemAlgo.check(p_via, delta, 0, 0, null, p_board, null);
if (check_ok)
{
return new_to_location;
}
final double c_min_length = 0.3 * p_trace_half_width + 1;
ok_length = Math.min(ok_length, float_from_location.distance(float_to_location));
double curr_length = ok_length / 2;
ok_length = 0;
Point result = null;
while (curr_length >= c_min_length)
{
Point check_point = float_from_location.change_length(float_to_location, ok_length + curr_length).round();
delta = check_point.difference_by(from_location);
if (MoveDrillItemAlgo.check(p_via, delta, 0, 0, null, p_board, null))
{
ok_length += curr_length;
result = check_point;
}
curr_length /= 2;
}
return result;
}
private static boolean reposition_via(RoutingBoard p_board, Via p_via,
IntPoint p_to_location, int p_trace_half_width_1, int p_trace_layer_1, int p_trace_cl_class_1,
IntPoint p_connect_location, int p_trace_half_width_2, int p_trace_layer_2, int p_trace_cl_class_2)
{
Point from_location = p_via.get_center();
if (from_location.equals(p_to_location))
{
if (p_board.get_test_level() == TestLevel.ALL_DEBUGGING_OUTPUT)
{
System.out.println("OptViaAlgo.reposition_via: from_location equal p_to_location");
}
return false;
}
Vector delta = p_to_location.difference_by(from_location);
if ( p_board.rules.get_trace_angle_restriction() == AngleRestriction.NONE && delta.length_approx() <= 1.5)
{
// PullTightAlgoAnyAngle.reduce_corners mmay not be able to remove the new generated overlap
// because of numerical stability problems
// That would result in an endless loop with removing the generated acute angle in reposition_via.
return false;
}
int[] net_no_arr = p_via.net_no_arr;
double ok_length = p_board.check_trace_segment(from_location, p_to_location, p_trace_layer_1, net_no_arr,
p_trace_half_width_1, p_trace_cl_class_1, false);
if (ok_length < Integer.MAX_VALUE)
{
return false;
}
ok_length = p_board.check_trace_segment(p_to_location, p_connect_location, p_trace_layer_2, net_no_arr,
p_trace_half_width_2, p_trace_cl_class_2, false);
if (ok_length < Integer.MAX_VALUE)
{
return false;
}
if (!MoveDrillItemAlgo.check(p_via, delta, 0, 0, null, p_board, null))
{
return false;
}
return true;
}
/**
* Tries to reposition the via to a better location according to the
* trace costs. Returns null, if no better location was found.
*/
private static Point reposition_via(RoutingBoard p_board, Via p_via,
int p_first_trace_half_width, int p_first_trace_cl_class, int p_first_trace_layer,
ExpansionCostFactor p_first_trace_costs, Point p_first_trace_from_corner,
int p_second_trace_half_width, int p_second_trace_cl_class, int p_second_trace_layer,
ExpansionCostFactor p_second_trace_costs, Point p_second_trace_from_corner)
{
Point via_location = p_via.get_center();
Vector first_delta = p_first_trace_from_corner.difference_by(via_location);
Vector second_delta = p_second_trace_from_corner.difference_by(via_location);
double scalar_product = first_delta.scalar_product(second_delta);
FloatPoint float_via_location = via_location.to_float();
FloatPoint float_first_trace_from_corner = p_first_trace_from_corner.to_float();
FloatPoint float_second_trace_from_corner = p_second_trace_from_corner.to_float();
double first_trace_from_corner_distance = float_via_location.distance(float_first_trace_from_corner);
double second_trace_from_corner_distance = float_via_location.distance(float_second_trace_from_corner);
IntPoint rounded_first_trace_from_corner = float_first_trace_from_corner.round();
IntPoint rounded_second_trace_from_corner = float_second_trace_from_corner.round();
// handle case of overlapping lines first
if (via_location.side_of(p_first_trace_from_corner, p_second_trace_from_corner) == Side.COLLINEAR
&& scalar_product > 0)
{
if (second_trace_from_corner_distance < first_trace_from_corner_distance)
{
return reposition_via(p_board, p_via, rounded_second_trace_from_corner,
p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
}
return reposition_via(p_board, p_via, rounded_first_trace_from_corner,
p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
}
Point result = null;
double curr_weighted_distance_1 =
float_via_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
double curr_weighted_distance_2 =
float_via_location.weighted_distance(float_first_trace_from_corner, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2)
{
// try to move the via in direction of p_first_trace_from_corner
result = reposition_via(p_board, p_via, rounded_first_trace_from_corner,
p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
if (result != null)
{
return result;
}
}
curr_weighted_distance_1 =
float_via_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
curr_weighted_distance_2 =
float_via_location.weighted_distance(float_second_trace_from_corner, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2)
{
// try to move the via in direction of p_second_trace_from_corner
result = reposition_via(p_board, p_via, rounded_second_trace_from_corner,
p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
if (result != null)
{
return result;
}
}
if (scalar_product > 0 && p_board.rules.get_trace_angle_restriction() != AngleRestriction.NINETY_DEGREE)
{
// acute angle
IntPoint to_point_1;
IntPoint to_point_2;
FloatPoint float_to_point_1;
FloatPoint float_to_point_2;
if (first_trace_from_corner_distance < second_trace_from_corner_distance)
{
to_point_1 = rounded_first_trace_from_corner;
float_to_point_1 = float_first_trace_from_corner;
float_to_point_2 =
float_via_location.change_length(float_second_trace_from_corner,
first_trace_from_corner_distance);
to_point_2 = float_to_point_2.round();
}
else
{
float_to_point_1 =
float_via_location.change_length(float_first_trace_from_corner,
second_trace_from_corner_distance);
to_point_1 = float_to_point_1.round();
to_point_2 = rounded_second_trace_from_corner;
float_to_point_2 = float_second_trace_from_corner;
}
curr_weighted_distance_1 =
float_to_point_1.weighted_distance(float_to_point_2, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
curr_weighted_distance_2 =
float_to_point_1.weighted_distance(float_to_point_2, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2)
{
// try moving the via first into the direction of to_point_1
result = reposition_via(p_board, p_via, to_point_1,
p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
if (result == null)
{
result = reposition_via(p_board, p_via, to_point_2,
p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
}
}
else
{
// try moving the via first into the direction of to_point_2
result = reposition_via(p_board, p_via, to_point_2,
p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
if (result == null)
{
result = reposition_via(p_board, p_via, to_point_1,
p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
}
}
if (result != null)
{
return result;
}
}
// try decomposition in axisparallel parts
if (!first_delta.is_orthogonal())
{
FloatPoint float_check_location = new FloatPoint(float_via_location.x, float_first_trace_from_corner.y);
curr_weighted_distance_1 =
float_via_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
curr_weighted_distance_2 =
float_via_location.weighted_distance(float_check_location, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
double curr_weighted_distance_3 =
float_check_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
{
IntPoint check_location = float_check_location.round();
boolean check_ok =
reposition_via(p_board, p_via, check_location, p_second_trace_half_width,
p_second_trace_layer, p_second_trace_cl_class, rounded_first_trace_from_corner,
p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
if (check_ok)
{
return check_location;
}
}
float_check_location = new FloatPoint(float_first_trace_from_corner.x, float_via_location.y);
curr_weighted_distance_2 =
float_via_location.weighted_distance(float_check_location, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
curr_weighted_distance_3 =
float_check_location.weighted_distance(float_first_trace_from_corner, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
{
IntPoint check_location = float_check_location.round();
boolean check_ok =
reposition_via(p_board, p_via, check_location, p_second_trace_half_width,
p_second_trace_layer, p_second_trace_cl_class, rounded_first_trace_from_corner,
p_first_trace_half_width, p_first_trace_layer, p_first_trace_cl_class);
if (check_ok)
{
return check_location;
}
}
}
if (!second_delta.is_orthogonal())
{
FloatPoint float_check_location = new FloatPoint(float_via_location.x, float_second_trace_from_corner.y);
curr_weighted_distance_1 =
float_via_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
curr_weighted_distance_2 =
float_via_location.weighted_distance(float_check_location, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
double curr_weighted_distance_3 =
float_check_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
{
IntPoint check_location = float_check_location.round();
boolean check_ok =
reposition_via(p_board, p_via, check_location, p_first_trace_half_width,
p_first_trace_layer, p_first_trace_cl_class, rounded_second_trace_from_corner,
p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
if (check_ok)
{
return check_location;
}
}
float_check_location = new FloatPoint(float_second_trace_from_corner.x, float_via_location.y);
curr_weighted_distance_2 =
float_via_location.weighted_distance(float_check_location, p_first_trace_costs.horizontal,
p_first_trace_costs.vertical);
curr_weighted_distance_3 =
float_check_location.weighted_distance(float_second_trace_from_corner, p_second_trace_costs.horizontal,
p_second_trace_costs.vertical);
if (curr_weighted_distance_1 > curr_weighted_distance_2 + curr_weighted_distance_3)
{
IntPoint check_location = float_check_location.round();
boolean check_ok =
reposition_via(p_board, p_via, check_location, p_first_trace_half_width,
p_first_trace_layer, p_first_trace_cl_class, rounded_second_trace_from_corner,
p_second_trace_half_width, p_second_trace_layer, p_second_trace_cl_class);
if (check_ok)
{
return check_location;
}
}
}
return null;
}
}