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