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

512 lines
22 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.
*
* ForcedPadAlgo.java
*
* Created on 1. September 2003, 08:28
*/
package eu.mihosoft.freerouting.board;
import eu.mihosoft.freerouting.datastructures.TimeLimit;
import eu.mihosoft.freerouting.geometry.planar.Direction;
import eu.mihosoft.freerouting.geometry.planar.FloatPoint;
import eu.mihosoft.freerouting.geometry.planar.IntPoint;
import eu.mihosoft.freerouting.geometry.planar.IntBox;
import eu.mihosoft.freerouting.geometry.planar.IntOctagon;
import eu.mihosoft.freerouting.geometry.planar.Point;
import eu.mihosoft.freerouting.geometry.planar.Vector;
import eu.mihosoft.freerouting.geometry.planar.Line;
import eu.mihosoft.freerouting.geometry.planar.Polyline;
import eu.mihosoft.freerouting.geometry.planar.TileShape;
import java.util.Collection;
/**
* Class with functions for checking and inserting pads with eventually
* shoving aside obstacle traces.
*
* @author Alfons Wirtz
*/
public class ForcedPadAlgo
{
/** Creates a new instance of ForcedPadAlgo */
public ForcedPadAlgo(RoutingBoard p_board)
{
board = p_board;
}
/**
* Checks, if possible obstacle traces can be shoved aside, so that a
* pad with the input parameters can be inserted without clearance violations.
* Returns false, if the check failed.
* If p_ignore_items != null, items in this list are not checked,
* If p_check_only_front only trace obstacles in the direction from p_from_side are checked
* for performance reasons. This is the cave when moving drill_items
*/
public CheckDrillResult check_forced_pad(TileShape p_pad_shape, CalcFromSide p_from_side, int p_layer, int[] p_net_no_arr,
int p_cl_type, boolean p_copper_sharing_allowed, Collection<Item> p_ignore_items, int p_max_recursion_depth, int p_max_via_recursion_depth,
boolean p_check_only_front, TimeLimit p_time_limit)
{
if (!p_pad_shape.is_contained_in(board.get_bounding_box()))
{
this.board.set_shove_failing_obstacle(board.get_outline());
return CheckDrillResult.NOT_DRILLABLE;
}
ShapeSearchTree search_tree = this.board.search_tree_manager.get_default_tree();
ShapeTraceEntries shape_entries =
new ShapeTraceEntries(p_pad_shape, p_layer, p_net_no_arr, p_cl_type, p_from_side, board);
Collection<Item> obstacles = search_tree.overlapping_items_with_clearance(p_pad_shape, p_layer, new int[0], p_cl_type);
if (p_ignore_items != null)
{
obstacles.removeAll(p_ignore_items);
}
boolean obstacles_shovable = shape_entries.store_items(obstacles, true, p_copper_sharing_allowed);
if (!obstacles_shovable)
{
this.board.set_shove_failing_obstacle(shape_entries.get_found_obstacle());
return CheckDrillResult.NOT_DRILLABLE;
}
// check, if the obstacle vias can be shoved
for (Via curr_shove_via : shape_entries.shove_via_list)
{
if (p_max_via_recursion_depth <= 0)
{
this.board.set_shove_failing_obstacle(curr_shove_via);
return CheckDrillResult.NOT_DRILLABLE;
}
IntPoint [] new_via_center =
MoveDrillItemAlgo.try_shove_via_points(p_pad_shape, p_layer, curr_shove_via, p_cl_type, false, board);
if (new_via_center.length <= 0)
{
this.board.set_shove_failing_obstacle(curr_shove_via);
return CheckDrillResult.NOT_DRILLABLE;
}
Vector delta = new_via_center[0].difference_by(curr_shove_via.get_center());
Collection<Item> ignore_items = new java.util.LinkedList<Item>();
if (!MoveDrillItemAlgo.check(curr_shove_via, delta,
p_max_recursion_depth, p_max_via_recursion_depth - 1, ignore_items,
this.board, p_time_limit))
{
return CheckDrillResult.NOT_DRILLABLE;
}
}
CheckDrillResult result = CheckDrillResult.DRILLABLE;
if (p_copper_sharing_allowed)
{
for (Item curr_obstacle : obstacles)
{
if (curr_obstacle instanceof Pin)
{
result = CheckDrillResult.DRILLABLE_WITH_ATTACH_SMD;
break;
}
}
}
int trace_piece_count = shape_entries.substitute_trace_count();
if (trace_piece_count == 0)
{
return result;
}
if (p_max_recursion_depth <= 0)
{
this.board.set_shove_failing_obstacle(shape_entries.get_found_obstacle());
return CheckDrillResult.NOT_DRILLABLE;
}
if (shape_entries.stack_depth() > 1)
{
this.board.set_shove_failing_obstacle(shape_entries.get_found_obstacle());
return CheckDrillResult.NOT_DRILLABLE;
}
ShoveTraceAlgo shove_trace_algo = new ShoveTraceAlgo(board);
boolean is_orthogonal_mode = p_pad_shape instanceof IntBox;
for (;;)
{
PolylineTrace curr_substitute_trace =
shape_entries.next_substitute_trace_piece();
if (curr_substitute_trace == null)
{
break;
}
for (int i = 0; i < curr_substitute_trace.tile_shape_count(); ++i)
{
Line curr_line = curr_substitute_trace.polyline().arr[i + 1];
Direction curr_dir = curr_line.direction();
boolean is_in_front;
if (p_check_only_front)
{
is_in_front = in_front_of_pad(curr_line, p_pad_shape,
p_from_side.no, curr_substitute_trace.get_half_width(), true);
}
else
{
is_in_front = true;
}
if (is_in_front)
{
CalcShapeAndFromSide curr = new CalcShapeAndFromSide(curr_substitute_trace, i, is_orthogonal_mode, true);
if (!shove_trace_algo.check(curr.shape, curr.from_side, curr_dir, p_layer,
curr_substitute_trace.net_no_arr, curr_substitute_trace.clearance_class_no(),
p_max_recursion_depth - 1, p_max_via_recursion_depth, 0, p_time_limit))
{
return CheckDrillResult.NOT_DRILLABLE;
}
}
}
}
return result;
}
/**
* Shoves aside traces, so that a pad with the input parameters can be
* inserted without clearance violations. Returns false, if the shove
* failed. In this case the database may be damaged, so that an undo
* becomes necessesary.
*/
boolean forced_pad(TileShape p_pad_shape, CalcFromSide p_from_side,
int p_layer, int[] p_net_no_arr, int p_cl_type, boolean p_copper_sharing_allowed, Collection<Item> p_ignore_items, int p_max_recursion_depth, int p_max_via_recursion_depth)
{
if (p_pad_shape.is_empty())
{
System.out.println("ShoveTraceAux.forced_pad: p_pad_shape is empty");
return true;
}
if (!p_pad_shape.is_contained_in(board.get_bounding_box()))
{
this.board.set_shove_failing_obstacle(board.get_outline());
return false;
}
if (!MoveDrillItemAlgo.shove_vias(p_pad_shape, p_from_side, p_layer, p_net_no_arr, p_cl_type,
p_ignore_items, p_max_recursion_depth, p_max_via_recursion_depth, false, this.board))
{
return false;
}
ShapeSearchTree search_tree = this.board.search_tree_manager.get_default_tree();
ShapeTraceEntries shape_entries =
new ShapeTraceEntries(p_pad_shape, p_layer, p_net_no_arr, p_cl_type, p_from_side, board);
Collection<Item> obstacles = search_tree.overlapping_items_with_clearance(p_pad_shape, p_layer, new int[0], p_cl_type);
if (p_ignore_items != null)
{
obstacles.removeAll(p_ignore_items);
}
boolean obstacles_shovable =
shape_entries.store_items(obstacles, true, p_copper_sharing_allowed)
&& shape_entries.shove_via_list.isEmpty();
if (!obstacles_shovable)
{
this.board.set_shove_failing_obstacle(shape_entries.get_found_obstacle());
return false;
}
int trace_piece_count = shape_entries.substitute_trace_count();
if (trace_piece_count == 0)
{
return true;
}
if (p_max_recursion_depth <= 0)
{
this.board.set_shove_failing_obstacle(shape_entries.get_found_obstacle());
return false;
}
boolean tails_exist_before = board.contains_trace_tails(obstacles, p_net_no_arr);
shape_entries.cutout_traces(obstacles);
boolean is_orthogonal_mode = p_pad_shape instanceof IntBox;
ShoveTraceAlgo shove_trace_algo = new ShoveTraceAlgo(this.board);
for (;;)
{
PolylineTrace curr_substitute_trace =
shape_entries.next_substitute_trace_piece();
if (curr_substitute_trace == null)
{
break;
}
if (curr_substitute_trace.first_corner().equals(curr_substitute_trace.last_corner()))
{
continue;
}
int[] curr_net_no_arr = curr_substitute_trace.net_no_arr;
for (int i = 0; i < curr_substitute_trace.tile_shape_count(); ++i)
{
CalcShapeAndFromSide curr =
new CalcShapeAndFromSide(curr_substitute_trace, i, is_orthogonal_mode, false);
if (!shove_trace_algo.insert(curr.shape, curr.from_side,
p_layer, curr_net_no_arr, curr_substitute_trace.clearance_class_no(),
p_ignore_items, p_max_recursion_depth - 1, p_max_via_recursion_depth, 0))
{
return false;
}
}
for (int i = 0; i < curr_substitute_trace.corner_count(); ++i)
{
board.join_changed_area(
curr_substitute_trace.polyline().corner_approx(i), p_layer);
}
Point [] end_corners = null;
if (!tails_exist_before)
{
end_corners = new Point[2];
end_corners[0] = curr_substitute_trace.first_corner();
end_corners[1] = curr_substitute_trace.last_corner();
}
board.insert_item(curr_substitute_trace);
IntOctagon opt_area;
if (board.changed_area != null)
{
opt_area = board.changed_area.get_area(p_layer);
}
else
{
opt_area = null;
}
curr_substitute_trace.normalize(opt_area);
if (!tails_exist_before)
{
for (int i = 0; i < 2; ++i)
{
Trace tail = board.get_trace_tail(end_corners[i], p_layer, curr_net_no_arr);
if (tail != null)
{
board.remove_items(tail.get_connection_items(Item.StopConnectionOption.VIA), false);
for (int curr_net_no : curr_net_no_arr )
{
board.combine_traces(curr_net_no);
}
}
}
}
}
return true;
}
/**
* Looks for a side of p_shape, so that a trace line from the shape center
* to the nearest point on this side does not conflict with any obstacles.
*/
CalcFromSide calc_from_side(TileShape p_shape, Point p_shape_center, int p_layer, int p_offset, int p_cl_class)
{
int [] empty_arr = new int [0];
TileShape offset_shape = (TileShape) p_shape.offset(p_offset);
for (int i = 0; i < offset_shape.border_line_count(); ++i)
{
TileShape check_shape =
calc_check_chape_for_from_side(p_shape, p_shape_center, offset_shape.border_line(i));
if(board.check_trace_shape(check_shape, p_layer, empty_arr, p_cl_class, null))
{
return new CalcFromSide(i, null);
}
}
// try second check without clearance
for (int i = 0; i < offset_shape.border_line_count(); ++i)
{
TileShape check_shape =
calc_check_chape_for_from_side(p_shape, p_shape_center, offset_shape.border_line(i));
if(board.check_trace_shape(check_shape, p_layer, empty_arr, 0, null))
{
return new CalcFromSide(i, null);
}
}
return CalcFromSide.NOT_CALCULATED;
}
private static TileShape calc_check_chape_for_from_side(TileShape p_shape,
Point p_shape_center, Line p_border_line)
{
FloatPoint shape_center = p_shape_center.to_float();
FloatPoint offset_projection = shape_center.projection_approx(p_border_line);
// Make shure, that direction restrictions are retained.
Line [] line_arr = new Line[3];
Direction curr_dir = p_border_line.direction();
line_arr[0] = new Line(p_shape_center, curr_dir);
line_arr[1] = new Line(p_shape_center, curr_dir.turn_45_degree(2));
line_arr[2] = new Line(offset_projection.round(), curr_dir);
Polyline check_line = new Polyline(line_arr);
return check_line.offset_shape(1, 0);
}
/**
* Checks, if p_line is in frone of p_pad_shape when shoving from p_from_side
*/
private static boolean in_front_of_pad(Line p_line, TileShape p_pad_shape, int p_from_side,
int p_width, boolean p_with_sides)
{
if (!p_pad_shape.is_IntOctagon())
{
// only implemented for octagons
return true;
}
IntOctagon pad_octagon = p_pad_shape.bounding_octagon();
if (!(p_line.a instanceof IntPoint && p_line.b instanceof IntPoint))
{
// not implemented
return true;
}
IntPoint line_a = (IntPoint) p_line.a;
IntPoint line_b = (IntPoint) p_line.b;
double diag_width = p_width * Math.sqrt(2);
boolean result;
switch (p_from_side)
{
case 0:
result = Math.min(line_a.y, line_b.y) >= pad_octagon.uy + p_width ||
Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width ||
Math.min(line_a.x + line_a.y, line_b.x + line_b.x)
>= pad_octagon.urx + diag_width;
if (p_with_sides && !result)
{
result = Math.max(line_a.x, line_b.x) <= pad_octagon.lx - p_width
&& Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width ||
Math.min(line_a.x, line_b.x) >= pad_octagon.rx + p_width &&
Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
>= pad_octagon.urx + diag_width;
}
break;
case 1:
result = Math.min(line_a.y, line_b.y) >= pad_octagon.uy + p_width ||
Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width ||
Math.max(line_a.x, line_b.x) <= pad_octagon.lx - p_width;
if (p_with_sides && !result)
{
result = Math.min(line_a.x, line_b.x) <= pad_octagon.lx - p_width
&& Math.max(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width ||
Math.max(line_a.y, line_b.y) >= pad_octagon.uy + p_width &&
Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
>= pad_octagon.urx + diag_width;
}
break;
case 2:
result = Math.max(line_a.x, line_b.x) <= pad_octagon.lx - p_width ||
Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width ||
Math.max(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width;
if (p_with_sides && !result)
{
result = Math.max(line_a.y, line_b.y) <= pad_octagon.ly - p_width
&& Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width ||
Math.min(line_a.y, line_b.y) >= pad_octagon.uy + p_width &&
Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width;
}
break;
case 3:
result = Math.max(line_a.x, line_b.x) <= pad_octagon.lx - p_width ||
Math.max(line_a.y, line_b.y) <= pad_octagon.ly - p_width ||
Math.max(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width;
if (p_with_sides && !result)
{
result = Math.min(line_a.y, line_b.y) <= pad_octagon.ly - p_width
&& Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width ||
Math.min(line_a.x, line_b.x) <= pad_octagon.lx - p_width
&& Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width;
}
break;
case 4:
result = Math.max(line_a.y, line_b.y) <= pad_octagon.ly - p_width ||
Math.max(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width ||
Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width;
if (p_with_sides && !result)
{
result = Math.min(line_a.x, line_b.x) >= pad_octagon.rx + p_width
&& Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width ||
Math.max(line_a.x, line_b.x) <= pad_octagon.lx - p_width &&
Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width;
}
break;
case 5:
result = Math.max(line_a.y, line_b.y) <= pad_octagon.ly - p_width ||
Math.min(line_a.x, line_b.x) >= pad_octagon.rx + p_width ||
Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width;
if (p_with_sides && !result)
{
result = Math.max(line_a.x, line_b.x) >= pad_octagon.rx + p_width
&& Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
>= pad_octagon.urx + diag_width ||
Math.min(line_a.y, line_b.y) <= pad_octagon.ly - p_width &&
Math.max(line_a.x + line_a.y, line_b.x + line_b.y)
<= pad_octagon.llx - diag_width;
}
break;
case 6:
result = Math.min(line_a.x, line_b.x) >= pad_octagon.rx + p_width ||
Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
>= pad_octagon.urx + diag_width ||
Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width;
if (p_with_sides && !result)
{
result = Math.max(line_a.y, line_b.y) <= pad_octagon.ly - p_width
&& Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width ||
Math.min(line_a.y, line_b.y) >= pad_octagon.uy + p_width
&& Math.max(line_a.x + line_a.y, line_b.x + line_b.y)
>= pad_octagon.urx + diag_width;
}
break;
case 7:
result = Math.min(line_a.y, line_b.y) >= pad_octagon.uy + p_width ||
Math.min(line_a.x + line_a.y, line_b.x + line_b.y)
>= pad_octagon.urx + diag_width ||
Math.min(line_a.x, line_b.x) >= pad_octagon.rx + p_width;
if (p_with_sides && !result)
{
result = Math.max(line_a.y, line_b.y) >= pad_octagon.uy + p_width
&& Math.max(line_a.x - line_a.y, line_b.x - line_b.y)
<= pad_octagon.ulx - diag_width ||
Math.max(line_a.x, line_b.x) >= pad_octagon.rx + p_width
&& Math.min(line_a.x - line_a.y, line_b.x - line_b.y)
>= pad_octagon.lrx + diag_width;
}
break;
default:
{
System.out.println("ForcedPadAlgo.in_front_of_pad: p_from_side out of range");
result = true;
}
}
return result;
}
private final RoutingBoard board;
public enum CheckDrillResult
{
DRILLABLE, DRILLABLE_WITH_ATTACH_SMD, NOT_DRILLABLE
}
}