1 /*
2  * KiRouter - a push-and-(sometimes-)shove PCB router
3  *
4  * Copyright (C) 2013  CERN
5  * Copyright (C) 2016-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  * Author: Tomasz Wlostowski <tomasz.wlostowski@cern.ch>
7  *
8  * This program is free software: you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation, either version 3 of the License, or (at your
11  * option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 
23 #include <functional>
24 using namespace std::placeholders;
25 
26 #include <pcb_painter.h>
27 #include <pcbnew_settings.h>
28 
29 #include <tools/pcb_grid_helper.h>
30 #include <wx/log.h>
31 
32 #include "pns_kicad_iface.h"
33 #include "pns_tool_base.h"
34 #include "pns_arc.h"
35 #include "pns_solid.h"
36 
37 
38 using namespace KIGFX;
39 
40 namespace PNS {
41 
42 
TOOL_BASE(const std::string & aToolName)43 TOOL_BASE::TOOL_BASE( const std::string& aToolName ) :
44     PCB_TOOL_BASE( aToolName )
45 {
46     m_gridHelper = nullptr;
47     m_iface = nullptr;
48     m_router = nullptr;
49     m_cancelled = false;
50 
51     m_startItem = nullptr;
52     m_startHighlight = false;
53 
54     m_endItem = nullptr;
55     m_gridHelper = nullptr;
56 
57     m_cancelled = false;
58 }
59 
60 
~TOOL_BASE()61 TOOL_BASE::~TOOL_BASE()
62 {
63     delete m_gridHelper;
64     delete m_iface;
65     delete m_router;
66 }
67 
68 
Reset(RESET_REASON aReason)69 void TOOL_BASE::Reset( RESET_REASON aReason )
70 {
71     delete m_gridHelper;
72     delete m_iface;
73     delete m_router;
74 
75     m_iface = new PNS_KICAD_IFACE;
76     m_iface->SetBoard( board() );
77     m_iface->SetView( getView() );
78     m_iface->SetHostTool( this );
79     m_iface->SetDisplayOptions( &( frame()->GetDisplayOptions() ) );
80 
81     m_router = new ROUTER;
82     m_router->SetInterface( m_iface );
83     m_router->ClearWorld();
84     m_router->SyncWorld();
85 
86     m_router->UpdateSizes( m_savedSizes );
87 
88     PCBNEW_SETTINGS* settings = frame()->GetPcbNewSettings();
89 
90     if( !settings->m_PnsSettings )
91         settings->m_PnsSettings = std::make_unique<ROUTING_SETTINGS>( settings, "tools.pns" );
92 
93     m_router->LoadSettings( settings->m_PnsSettings.get() );
94 
95     m_gridHelper = new PCB_GRID_HELPER( m_toolMgr, frame()->GetMagneticItemsSettings() );
96 }
97 
98 
pickSingleItem(const VECTOR2I & aWhere,int aNet,int aLayer,bool aIgnorePads,const std::vector<ITEM * > aAvoidItems)99 ITEM* TOOL_BASE::pickSingleItem( const VECTOR2I& aWhere, int aNet, int aLayer, bool aIgnorePads,
100 								 const std::vector<ITEM*> aAvoidItems )
101 {
102     int tl = aLayer > 0 ? aLayer : getView()->GetTopLayer();
103 
104     static const int candidateCount = 5;
105     ITEM* prioritized[candidateCount];
106     SEG::ecoord dist[candidateCount];
107 
108     for( int i = 0; i < candidateCount; i++ )
109     {
110         prioritized[i] = nullptr;
111         dist[i] = VECTOR2I::ECOORD_MAX;
112     }
113 
114     ITEM_SET candidates = m_router->QueryHoverItems( aWhere );
115 
116     if( candidates.Empty() )
117         candidates = m_router->QueryHoverItems( aWhere, true );
118 
119     for( ITEM* item : candidates.Items() )
120     {
121         if( !item->IsRoutable() )
122             continue;
123 
124         if( !IsCopperLayer( item->Layers().Start() ) )
125             continue;
126 
127         if( !m_iface->IsAnyLayerVisible( item->Layers() ) )
128             continue;
129 
130         if( alg::contains( aAvoidItems, item ) )
131             continue;
132 
133         // fixme: this causes flicker with live loop removal...
134         //if( item->Parent() && !item->Parent()->ViewIsVisible() )
135         //    continue;
136 
137         if( aNet <= 0 || item->Net() == aNet )
138         {
139             if( item->OfKind( ITEM::VIA_T | ITEM::SOLID_T ) )
140             {
141                 if( item->OfKind( ITEM::SOLID_T ) && aIgnorePads )
142                     continue;
143 
144                 SEG::ecoord d = ( item->Shape()->Centre() - aWhere ).SquaredEuclideanNorm();
145 
146                 if( d < dist[2] )
147                 {
148                     prioritized[2] = item;
149                     dist[2] = d;
150                 }
151 
152                 if( item->Layers().Overlaps( tl ) && d < dist[0] )
153                 {
154                     prioritized[0] = item;
155                     dist[0] = d;
156                 }
157             }
158             else    // ITEM::SEGMENT_T | ITEM::ARC_T
159             {
160                 LINKED_ITEM* li = static_cast<LINKED_ITEM*>( item );
161                 SEG::ecoord  d = std::min( ( li->Anchor( 0 ) - aWhere ).SquaredEuclideanNorm(),
162                                            ( li->Anchor( 1 ) - aWhere ).SquaredEuclideanNorm() );
163 
164                 if( d < dist[3] )
165                 {
166                     prioritized[3] = item;
167                     dist[3] = d;
168                 }
169 
170                 if( item->Layers().Overlaps( tl ) && d < dist[1] )
171                 {
172                     prioritized[1] = item;
173                     dist[1] = d;
174                 }
175             }
176         }
177         else if ( item->Net() == 0 && m_router->Settings().Mode() == RM_MarkObstacles )
178         {
179             // Allow unconnected items as last resort in RM_MarkObstacles mode
180             if( item->OfKind( ITEM::SOLID_T ) && aIgnorePads )
181                 continue;
182 
183             if( item->Layers().Overlaps( tl ) )
184                 prioritized[4] = item;
185         }
186     }
187 
188     ITEM* rv = nullptr;
189 
190     bool highContrast = ( displayOptions().m_ContrastModeDisplay != HIGH_CONTRAST_MODE::NORMAL );
191 
192     for( int i = 0; i < candidateCount; i++ )
193     {
194         ITEM* item = prioritized[i];
195 
196         if( highContrast && item && !item->Layers().Overlaps( tl ) )
197             item = nullptr;
198 
199         if( item && ( aLayer < 0 || item->Layers().Overlaps( aLayer ) ) )
200         {
201             rv = item;
202             break;
203         }
204     }
205 
206     if( rv )
207     {
208         wxLogTrace( "PNS", "%s, layer : %d, tl: %d", rv->KindStr().c_str(), rv->Layers().Start(),
209                     tl );
210     }
211 
212     return rv;
213 }
214 
215 
highlightNet(bool aEnabled,int aNetcode)216 void TOOL_BASE::highlightNet( bool aEnabled, int aNetcode )
217 {
218     RENDER_SETTINGS* rs = getView()->GetPainter()->GetSettings();
219 
220     if( aNetcode >= 0 && aEnabled )
221     {
222         // If the user has previously set the current net to be highlighted,
223         // we assume they want to keep it highlighted after routing
224         m_startHighlight = ( rs->IsHighlightEnabled()
225                                 && rs->GetHighlightNetCodes().count( aNetcode ) );
226 
227         rs->SetHighlight( true, aNetcode );
228     }
229     else
230     {
231         if( !m_startHighlight )
232             rs->SetHighlight( false );
233 
234         m_startHighlight = false;
235     }
236 
237     getView()->UpdateAllLayersColor();
238 }
239 
240 
checkSnap(ITEM * aItem)241 bool TOOL_BASE::checkSnap( ITEM *aItem )
242 {
243     // Sync PNS engine settings with the general PCB editor options.
244     auto& pnss = m_router->Settings();
245 
246     // If we're dragging a track segment, don't try to snap to items on the same copper layer.
247     // This way we avoid 'flickery' behaviour for short segments when the snap algo is trying to
248     // snap to the corners of the segments next to the one being dragged.
249     if( m_startItem && aItem && m_router->GetState() == ROUTER::DRAG_SEGMENT
250         && aItem->Layer() == m_startItem->Layer() && aItem->OfKind( ITEM::SEGMENT_T )
251         && m_startItem->OfKind( ITEM::SEGMENT_T ) )
252         return false;
253 
254     pnss.SetSnapToPads(
255             frame()->GetMagneticItemsSettings()->pads == MAGNETIC_OPTIONS::CAPTURE_CURSOR_IN_TRACK_TOOL ||
256             frame()->GetMagneticItemsSettings()->pads == MAGNETIC_OPTIONS::CAPTURE_ALWAYS );
257 
258     pnss.SetSnapToTracks(
259             frame()->GetMagneticItemsSettings()->tracks == MAGNETIC_OPTIONS::CAPTURE_CURSOR_IN_TRACK_TOOL
260             || frame()->GetMagneticItemsSettings()->tracks == MAGNETIC_OPTIONS::CAPTURE_ALWAYS );
261 
262     if( aItem )
263     {
264         if( aItem->OfKind( ITEM::VIA_T | ITEM::SEGMENT_T | ITEM::ARC_T )  )
265             return pnss.GetSnapToTracks();
266         else if( aItem->OfKind( ITEM::SOLID_T ) )
267             return pnss.GetSnapToPads();
268     }
269 
270     return false;
271 }
272 
273 
updateStartItem(const TOOL_EVENT & aEvent,bool aIgnorePads)274 void TOOL_BASE::updateStartItem( const TOOL_EVENT& aEvent, bool aIgnorePads )
275 {
276     int      tl = getView()->GetTopLayer();
277     VECTOR2I cp = controls()->GetCursorPosition( !aEvent.Modifier( MD_SHIFT ) );
278     VECTOR2I p;
279     GAL*     gal = m_toolMgr->GetView()->GetGAL();
280 
281     controls()->ForceCursorPosition( false );
282     m_gridHelper->SetUseGrid( gal->GetGridSnapping() && !aEvent.DisableGridSnapping()  );
283     m_gridHelper->SetSnap( !aEvent.Modifier( MD_SHIFT ) );
284 
285     if( aEvent.IsMotion() || aEvent.IsClick() )
286         p = aEvent.Position();
287     else
288         p = cp;
289 
290     m_startItem = pickSingleItem( aEvent.IsClick() ? cp : p, -1, -1, aIgnorePads );
291 
292     if( !m_gridHelper->GetUseGrid() && m_startItem && !m_startItem->Layers().Overlaps( tl ) )
293         m_startItem = nullptr;
294 
295     m_startSnapPoint = snapToItem( m_startItem, p );
296     controls()->ForceCursorPosition( true, m_startSnapPoint );
297 }
298 
299 
updateEndItem(const TOOL_EVENT & aEvent)300 void TOOL_BASE::updateEndItem( const TOOL_EVENT& aEvent )
301 {
302     int  layer;
303     GAL* gal = m_toolMgr->GetView()->GetGAL();
304 
305     m_gridHelper->SetUseGrid( gal->GetGridSnapping() && !aEvent.DisableGridSnapping()  );
306     m_gridHelper->SetSnap( !aEvent.Modifier( MD_SHIFT ) );
307 
308     controls()->ForceCursorPosition( false );
309     VECTOR2I mousePos = controls()->GetMousePosition();
310 
311     if( m_router->Settings().Mode() != RM_MarkObstacles &&
312         ( m_router->GetCurrentNets().empty() || m_router->GetCurrentNets().front() < 0 ) )
313     {
314         m_endSnapPoint = snapToItem( nullptr, mousePos );
315         controls()->ForceCursorPosition( true, m_endSnapPoint );
316         m_endItem = nullptr;
317 
318         return;
319     }
320 
321     if( m_router->IsPlacingVia() )
322         layer = -1;
323     else
324         layer = m_router->GetCurrentLayer();
325 
326     ITEM* endItem = nullptr;
327 
328     std::vector<int> nets = m_router->GetCurrentNets();
329 
330     for( int net : nets )
331     {
332         endItem = pickSingleItem( mousePos, net, layer, false, { m_startItem } );
333 
334         if( endItem )
335             break;
336     }
337 
338     if( m_gridHelper->GetSnap() && checkSnap( endItem ) )
339     {
340         m_endItem = endItem;
341         m_endSnapPoint = snapToItem( endItem, mousePos );
342     }
343     else
344     {
345         m_endItem = nullptr;
346         m_endSnapPoint = m_gridHelper->Align( mousePos );
347     }
348 
349     controls()->ForceCursorPosition( true, m_endSnapPoint );
350 
351     if( m_endItem )
352     {
353         wxLogTrace( "PNS", "%s, layer : %d",
354                     m_endItem->KindStr().c_str(),
355                     m_endItem->Layers().Start() );
356     }
357 }
358 
359 
Router() const360 ROUTER *TOOL_BASE::Router() const
361 {
362     return m_router;
363 }
364 
365 
snapToItem(ITEM * aItem,const VECTOR2I & aP)366 const VECTOR2I TOOL_BASE::snapToItem( ITEM* aItem, const VECTOR2I& aP )
367 {
368     if( !aItem || !m_iface->IsItemVisible( aItem ) )
369     {
370         return m_gridHelper->Align( aP );
371     }
372 
373     switch( aItem->Kind() )
374     {
375     case ITEM::SOLID_T:
376         return static_cast<SOLID*>( aItem )->Pos();
377 
378     case ITEM::VIA_T:
379         return static_cast<VIA*>( aItem )->Pos();
380 
381     case ITEM::SEGMENT_T:
382     case ITEM::ARC_T:
383     {
384         LINKED_ITEM* li = static_cast<LINKED_ITEM*>( aItem );
385         VECTOR2I     A = li->Anchor( 0 );
386         VECTOR2I     B = li->Anchor( 1 );
387         SEG::ecoord  w_sq = SEG::Square( li->Width() / 2 );
388         SEG::ecoord  distA_sq = ( aP - A ).SquaredEuclideanNorm();
389         SEG::ecoord  distB_sq = ( aP - B ).SquaredEuclideanNorm();
390 
391         if( distA_sq < w_sq || distB_sq < w_sq )
392         {
393             return ( distA_sq < distB_sq ) ? A : B;
394         }
395         else if( aItem->Kind() == ITEM::SEGMENT_T )
396         {
397             // TODO(snh): Clean this up
398             SEGMENT* seg = static_cast<SEGMENT*>( li );
399             return m_gridHelper->AlignToSegment( aP, seg->Seg() );
400         }
401         else if( aItem->Kind() == ITEM::ARC_T )
402         {
403             ARC* arc = static_cast<ARC*>( li );
404             return m_gridHelper->AlignToArc( aP, *static_cast<const SHAPE_ARC*>( arc->Shape() ) );
405         }
406 
407         break;
408     }
409 
410     default:
411         break;
412     }
413 
414     return m_gridHelper->Align( aP );
415 }
416 
417 }
418