/* * 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. * * RouteState.java * * Created on 8. November 2003, 08:22 */ package interactive; import geometry.planar.FloatPoint; import geometry.planar.IntPoint; import geometry.planar.Point; import java.util.Collection; import java.util.Set; import board.Trace; import board.Via; import board.PolylineTrace; import board.ConductionArea; import board.DrillItem; import board.Item; import board.RoutingBoard; import board.ItemSelectionFilter; /** * Interactive routing state. * * @author Alfons Wirtz */ public class RouteState extends InteractiveState { /** * Returns a new instance of this class or null, * if starting a new route was not possible at p_location. * If p_logfile != null, the creation of the route is stored * in the logfile. **/ public static RouteState get_instance(FloatPoint p_location, InteractiveState p_parent_state, BoardHandling p_board_handling, Logfile p_logfile) { if (!(p_parent_state instanceof MenuState)) { System.out.println("RouteState.get_instance: unexpected parent state"); } p_board_handling.display_layer_messsage(); IntPoint location = p_location.round(); Item picked_item = start_ok(location, p_board_handling); if (picked_item == null) { return null; } int net_count = picked_item.net_count(); if (net_count <= 0) { return null; } int[] route_net_no_arr; if (picked_item instanceof board.Pin && net_count > 1) { // tie pin, remove nets, which are already conneccted to this pin on the current layer. route_net_no_arr = get_route_net_numbers_at_tie_pin((board.Pin) picked_item, p_board_handling.settings.layer); } else { route_net_no_arr = new int[net_count]; for (int i = 0; i < net_count; ++i) { route_net_no_arr[i] = picked_item.get_net_no(i); } } if (route_net_no_arr.length <= 0) { return null; } board.RoutingBoard routing_board = p_board_handling.get_routing_board(); int[] trace_half_widths = new int[routing_board.get_layer_count()]; boolean[] layer_active_arr = new boolean[trace_half_widths.length]; for (int i = 0; i < trace_half_widths.length; ++i) { trace_half_widths[i] = p_board_handling.get_trace_halfwidth(route_net_no_arr[0], i); layer_active_arr[i] = false; for (int j = 0; j < route_net_no_arr.length; ++j) { if (p_board_handling.is_active_routing_layer(route_net_no_arr[j], i)) { layer_active_arr[i] = true; } } } int trace_clearance_class = p_board_handling.get_trace_clearance_class(route_net_no_arr[0]); boolean start_ok = true; if (picked_item instanceof Trace) { Trace picked_trace = (Trace) picked_item; Point picked_corner = picked_trace.nearest_end_point(location); if (picked_corner instanceof IntPoint && p_location.distance(picked_corner.to_float()) < 5 * picked_trace.get_half_width()) { location = (IntPoint) picked_corner; } else { if (picked_trace instanceof PolylineTrace) { FloatPoint nearest_point = ((PolylineTrace) picked_trace).polyline().nearest_point_approx(p_location); location = nearest_point.round(); } if (!routing_board.connect_to_trace(location, picked_trace, picked_trace.get_half_width(), picked_trace.clearance_class_no())) { start_ok = false; } } if (start_ok && !p_board_handling.settings.manual_rule_selection) { // Pick up the half with and the clearance class of the found trace. int[] new_trace_half_widths = new int[trace_half_widths.length]; System.arraycopy(trace_half_widths, 0, new_trace_half_widths, 0, trace_half_widths.length); new_trace_half_widths[picked_trace.get_layer()] = picked_trace.get_half_width(); trace_half_widths = new_trace_half_widths; trace_clearance_class = picked_trace.clearance_class_no(); } } else if (picked_item instanceof DrillItem) { DrillItem drill_item = (DrillItem) picked_item; Point center = drill_item.get_center(); if (center instanceof IntPoint) { location = (IntPoint) center; } } if (!start_ok) { return null; } rules.Net curr_net = routing_board.rules.nets.get(route_net_no_arr[0]); if (curr_net == null) { return null; } // Switch to stitch mode for nets, which are shove fixed. boolean is_stitch_route = p_board_handling.settings.is_stitch_route || curr_net.get_class().is_shove_fixed() || !curr_net.get_class().get_pull_tight(); routing_board.generate_snapshot(); RouteState new_instance; if (is_stitch_route) { new_instance = new StitchRouteState(p_parent_state, p_board_handling, p_logfile); } else { new_instance = new DynamicRouteState(p_parent_state, p_board_handling, p_logfile); } new_instance.routing_target_set = picked_item.get_unconnected_set(-1); new_instance.route = new Route(location, p_board_handling.settings.layer, trace_half_widths, layer_active_arr, route_net_no_arr, trace_clearance_class, p_board_handling.get_via_rule(route_net_no_arr[0]), p_board_handling.settings.push_enabled, p_board_handling.settings.trace_pull_tight_region_width, p_board_handling.settings.trace_pull_tight_accuracy, picked_item, new_instance.routing_target_set, routing_board, is_stitch_route, p_board_handling.settings.automatic_neckdown, p_board_handling.settings.via_snap_to_smd_center, p_board_handling.settings.hilight_routing_obstacle); new_instance.observers_activated = !routing_board.observers_active(); if (new_instance.observers_activated) { routing_board.start_notify_observers(); } p_board_handling.repaint(); if (new_instance != null) { if (new_instance.logfile != null) { new_instance.logfile.start_scope(LogfileScope.CREATING_TRACE, p_location); p_board_handling.hide_ratsnest(); } new_instance.display_default_message(); } return new_instance; } /** * Creates a new instance of RouteState * If p_logfile != null, the creation of the route is stored * in the logfile. */ protected RouteState(InteractiveState p_parent_state, BoardHandling p_board_handling, Logfile p_logfile) { super(p_parent_state, p_board_handling, p_logfile); } /** * Checks starting an interactive route at p_location. * Returns the picked start item of the routing at p_location, * or null, if no such item was found. */ static protected Item start_ok(IntPoint p_location, BoardHandling p_hdlg) { board.RoutingBoard routing_board = p_hdlg.get_routing_board(); /** * look if an already exististing trace ends at p_start_corner * and pick it up in this case. */ Item picked_item = routing_board.pick_nearest_routing_item(p_location, p_hdlg.settings.layer, null); int layer_count = routing_board.get_layer_count(); if (picked_item == null && p_hdlg.settings.select_on_all_visible_layers) { // Nothing found on preferred layer, try the other visible layers. // Prefer the outer layers. picked_item = pick_routing_item(p_location, 0, p_hdlg); if (picked_item == null) { picked_item = pick_routing_item(p_location, layer_count - 1, p_hdlg); } // prefer signal layers if (picked_item == null) { for (int i = 1; i < layer_count - 1; ++i) { if (routing_board.layer_structure.arr[i].is_signal) { picked_item = pick_routing_item(p_location, i, p_hdlg); if (picked_item != null) { break; } } } } if (picked_item == null) { for (int i = 1; i < layer_count - 1; ++i) { if (!routing_board.layer_structure.arr[i].is_signal) { picked_item = pick_routing_item(p_location, i, p_hdlg); if (picked_item != null) { break; } } } } } return picked_item; } static private Item pick_routing_item(IntPoint p_location, int p_layer_no, BoardHandling p_hdlg) { if (p_layer_no == p_hdlg.settings.layer || (p_hdlg.graphics_context.get_layer_visibility(p_layer_no) <= 0)) { return null; } Item picked_item = p_hdlg.get_routing_board().pick_nearest_routing_item(p_location, p_layer_no, null); if (picked_item != null) { p_hdlg.set_layer(picked_item.first_layer()); } return picked_item; } public InteractiveState process_logfile_point(FloatPoint p_point) { return add_corner(p_point); } /** * Action to be taken when a key is pressed (Shortcut). */ public InteractiveState key_typed(char p_key_char) { InteractiveState curr_return_state = this; if (Character.isDigit(p_key_char)) { // change to the p_key_char-ths signal layer board.LayerStructure layer_structure = hdlg.get_routing_board().layer_structure; int d = Character.digit(p_key_char, 10); d = Math.min(d, layer_structure.signal_layer_count()); // Board layers start at 0, keyboard input for layers starts at 1. d = Math.max(d - 1, 0); board.Layer new_layer = layer_structure.get_signal_layer(d); d = layer_structure.get_no(new_layer); if (d >= 0) { change_layer_action(d); } } else if (p_key_char == '+') { // change to the next signal layer board.LayerStructure layer_structure = hdlg.get_routing_board().layer_structure; int current_layer_no = hdlg.settings.layer; for (;;) { ++current_layer_no; if (current_layer_no >= layer_structure.arr.length || layer_structure.arr[current_layer_no].is_signal) { break; } } if (current_layer_no < layer_structure.arr.length) { change_layer_action(current_layer_no); } } else if (p_key_char == '-') { // change to the to the previous signal layer board.LayerStructure layer_structure = hdlg.get_routing_board().layer_structure; int current_layer_no = hdlg.settings.layer; for (;;) { --current_layer_no; if (current_layer_no < 0 || layer_structure.arr[current_layer_no].is_signal) { break; } } if (current_layer_no >= 0) { change_layer_action(current_layer_no); } } else { curr_return_state = super.key_typed(p_key_char); } return curr_return_state; } /** * Append a line to p_location to the trace routed so far. * Returns from state, if the route is completed by connecting * to a target. */ public InteractiveState add_corner(FloatPoint p_location) { boolean route_completed = route.next_corner(p_location); String layer_string = hdlg.get_routing_board().layer_structure.arr[route.nearest_target_layer()].name; hdlg.screen_messages.set_target_layer(layer_string); if (this.logfile != null) { this.logfile.add_corner(p_location); } if (route_completed) { if (this.observers_activated) { hdlg.get_routing_board().end_notify_observers(); this.observers_activated = false; } } InteractiveState result; if (route_completed) { result = this.return_state; hdlg.screen_messages.clear(); for (int curr_net_no : this.route.net_no_arr) { hdlg.update_ratsnest(curr_net_no); } } else { result = this; } hdlg.recalculate_length_violations(); hdlg.repaint(hdlg.get_graphics_update_rectangle()); return result; } public InteractiveState cancel() { Trace tail = hdlg.get_routing_board().get_trace_tail(route.get_last_corner(), hdlg.settings.layer, route.net_no_arr); if (tail != null) { Collection<Item> remove_items = tail.get_connection_items(Item.StopConnectionOption.VIA); if (hdlg.settings.push_enabled) { hdlg.get_routing_board().remove_items_and_pull_tight(remove_items, hdlg.settings.trace_pull_tight_region_width, hdlg.settings.trace_pull_tight_accuracy, false); } else { hdlg.get_routing_board().remove_items(remove_items, false); } } if (this.observers_activated) { hdlg.get_routing_board().end_notify_observers(); this.observers_activated = false; } if (logfile != null) { logfile.start_scope(LogfileScope.CANCEL_SCOPE); } hdlg.screen_messages.clear(); for (int curr_net_no : this.route.net_no_arr) { hdlg.update_ratsnest(curr_net_no); } return this.return_state; } public boolean change_layer_action(int p_new_layer) { boolean result = true; if (p_new_layer >= 0 && p_new_layer < hdlg.get_routing_board().get_layer_count()) { if (this.route != null && !this.route.is_layer_active(p_new_layer)) { String layer_name = hdlg.get_routing_board().layer_structure.arr[p_new_layer].name; hdlg.screen_messages.set_status_message(resources.getString("layer_not_changed_because_layer") + " " + layer_name + " " + resources.getString("is_not_active_for_the_current_net")); } boolean change_layer_succeeded = route.change_layer(p_new_layer); if (change_layer_succeeded) { boolean connected_to_plane = false; // check, if the layer change resulted in a connection to a power plane. int old_layer = hdlg.settings.get_layer(); ItemSelectionFilter selection_filter = new ItemSelectionFilter(ItemSelectionFilter.SelectableChoices.VIAS); Collection<Item> picked_items = hdlg.get_routing_board().pick_items(route.get_last_corner(), old_layer, selection_filter); Via new_via = null; for (Item curr_via : picked_items) { if (curr_via.shares_net_no(route.net_no_arr)) { new_via = (Via) curr_via; break; } } if (new_via != null) { int from_layer; int to_layer; if (old_layer < p_new_layer) { from_layer = old_layer + 1; to_layer = p_new_layer; } else { from_layer = p_new_layer; to_layer = old_layer - 1; } Collection<Item> contacts = new_via.get_normal_contacts(); for (Item curr_item : contacts) { if (curr_item instanceof ConductionArea) { ConductionArea curr_area = (ConductionArea) curr_item; if (curr_area.get_layer() >= from_layer && curr_area.get_layer() <= to_layer) { connected_to_plane = true; break; } } } } if (connected_to_plane) { hdlg.set_interactive_state(this.return_state); for (int curr_net_no : this.route.net_no_arr) { hdlg.update_ratsnest(curr_net_no); } } else { hdlg.set_layer(p_new_layer); String layer_name = hdlg.get_routing_board().layer_structure.arr[p_new_layer].name; hdlg.screen_messages.set_status_message(resources.getString("layer_changed_to") + " " + layer_name); // make the current situation restorable by undo hdlg.get_routing_board().generate_snapshot(); } if (logfile != null) { logfile.start_scope(LogfileScope.CHANGE_LAYER, p_new_layer); } } else { int shove_failing_layer = hdlg.get_routing_board().get_shove_failing_layer(); if (shove_failing_layer >= 0) { String layer_name = hdlg.get_routing_board().layer_structure.arr[hdlg.get_routing_board().get_shove_failing_layer()].name; hdlg.screen_messages.set_status_message(resources.getString("layer_not_changed_because_of_obstacle_on_layer") + " " + layer_name); } else { System.out.println("RouteState.change_layer_action: shove_failing_layer not set"); } result = false; } hdlg.repaint(); } return result; } /** * get nets of p_tie_pin except nets of traces, which are already conneccted to this pin on p_layer. */ static int[] get_route_net_numbers_at_tie_pin(board.Pin p_pin, int p_layer) { Set<Integer> net_number_list = new java.util.TreeSet<Integer>(); for (int i = 0; i < p_pin.net_count(); ++i) { net_number_list.add(p_pin.get_net_no(i)); } Set<Item> contacts = p_pin.get_normal_contacts(); for (Item curr_contact : contacts) { if (curr_contact.first_layer() <= p_layer && curr_contact.last_layer() >= p_layer) { for (int i = 0; i < curr_contact.net_count(); ++i) { net_number_list.remove(curr_contact.get_net_no(i)); } } } int[] result = new int[net_number_list.size()]; int curr_ind = 0; for (Integer curr_net_number : net_number_list) { result[curr_ind] = curr_net_number; ++curr_ind; } return result; } public void draw(java.awt.Graphics p_graphics) { if (route != null) { route.draw(p_graphics, hdlg.graphics_context); } } public void display_default_message() { if (route != null) { rules.Net curr_net = hdlg.get_routing_board().rules.nets.get(route.net_no_arr[0]); hdlg.screen_messages.set_status_message(resources.getString("routing_net") + " " + curr_net.name); } } protected Route route = null; private Set<Item> routing_target_set = null; protected boolean observers_activated = false; }