1 /*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr
5 * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6 * Copyright (C) 2015 Wayne Stambaugh <stambaughw@gmail.com>
7 * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, you may find one here:
21 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
22 * or you may search the http://www.gnu.org website for the version 2 license,
23 * or you may write to the Free Software Foundation, Inc.,
24 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
25 */
26
27 #include <core/mirror.h>
28 #include <confirm.h>
29 #include <refdes_utils.h>
30 #include <bitmaps.h>
31 #include <unordered_set>
32 #include <string_utils.h>
33 #include <pcb_edit_frame.h>
34 #include <board.h>
35 #include <board_design_settings.h>
36 #include <fp_shape.h>
37 #include <macros.h>
38 #include <pad.h>
39 #include <pcb_text.h>
40 #include <pcb_marker.h>
41 #include <pcb_group.h>
42 #include <pcb_track.h>
43 #include <footprint.h>
44 #include <zone.h>
45 #include <view/view.h>
46 #include <geometry/shape_null.h>
47 #include <i18n_utility.h>
48 #include <convert_shape_list_to_polygon.h>
49 #include <geometry/convex_hull.h>
50
FOOTPRINT(BOARD * parent)51 FOOTPRINT::FOOTPRINT( BOARD* parent ) :
52 BOARD_ITEM_CONTAINER((BOARD_ITEM*) parent, PCB_FOOTPRINT_T ),
53 m_boundingBoxCacheTimeStamp( 0 ),
54 m_visibleBBoxCacheTimeStamp( 0 ),
55 m_textExcludedBBoxCacheTimeStamp( 0 ),
56 m_hullCacheTimeStamp( 0 ),
57 m_initial_comments( nullptr )
58 {
59 m_attributes = 0;
60 m_layer = F_Cu;
61 m_orient = 0;
62 m_fpStatus = FP_PADS_are_LOCKED;
63 m_arflag = 0;
64 m_rot90Cost = m_rot180Cost = 0;
65 m_link = 0;
66 m_lastEditTime = 0;
67 m_localClearance = 0;
68 m_localSolderMaskMargin = 0;
69 m_localSolderPasteMargin = 0;
70 m_localSolderPasteMarginRatio = 0.0;
71 m_zoneConnection = ZONE_CONNECTION::INHERITED; // Use zone setting by default
72 m_thermalWidth = 0; // Use zone setting by default
73 m_thermalGap = 0; // Use zone setting by default
74
75 // These are special and mandatory text fields
76 m_reference = new FP_TEXT( this, FP_TEXT::TEXT_is_REFERENCE );
77 m_value = new FP_TEXT( this, FP_TEXT::TEXT_is_VALUE );
78
79 m_3D_Drawings.clear();
80 }
81
82
FOOTPRINT(const FOOTPRINT & aFootprint)83 FOOTPRINT::FOOTPRINT( const FOOTPRINT& aFootprint ) :
84 BOARD_ITEM_CONTAINER( aFootprint )
85 {
86 m_pos = aFootprint.m_pos;
87 m_fpid = aFootprint.m_fpid;
88 m_attributes = aFootprint.m_attributes;
89 m_fpStatus = aFootprint.m_fpStatus;
90 m_orient = aFootprint.m_orient;
91 m_rot90Cost = aFootprint.m_rot90Cost;
92 m_rot180Cost = aFootprint.m_rot180Cost;
93 m_lastEditTime = aFootprint.m_lastEditTime;
94 m_link = aFootprint.m_link;
95 m_path = aFootprint.m_path;
96
97 m_cachedBoundingBox = aFootprint.m_cachedBoundingBox;
98 m_boundingBoxCacheTimeStamp = aFootprint.m_boundingBoxCacheTimeStamp;
99 m_cachedVisibleBBox = aFootprint.m_cachedVisibleBBox;
100 m_visibleBBoxCacheTimeStamp = aFootprint.m_visibleBBoxCacheTimeStamp;
101 m_cachedTextExcludedBBox = aFootprint.m_cachedTextExcludedBBox;
102 m_textExcludedBBoxCacheTimeStamp = aFootprint.m_textExcludedBBoxCacheTimeStamp;
103 m_cachedHull = aFootprint.m_cachedHull;
104 m_hullCacheTimeStamp = aFootprint.m_hullCacheTimeStamp;
105
106 m_localClearance = aFootprint.m_localClearance;
107 m_localSolderMaskMargin = aFootprint.m_localSolderMaskMargin;
108 m_localSolderPasteMargin = aFootprint.m_localSolderPasteMargin;
109 m_localSolderPasteMarginRatio = aFootprint.m_localSolderPasteMarginRatio;
110 m_zoneConnection = aFootprint.m_zoneConnection;
111 m_thermalWidth = aFootprint.m_thermalWidth;
112 m_thermalGap = aFootprint.m_thermalGap;
113
114 std::map<BOARD_ITEM*, BOARD_ITEM*> ptrMap;
115
116 // Copy reference and value.
117 m_reference = new FP_TEXT( *aFootprint.m_reference );
118 m_reference->SetParent( this );
119 ptrMap[ aFootprint.m_reference ] = m_reference;
120
121 m_value = new FP_TEXT( *aFootprint.m_value );
122 m_value->SetParent( this );
123 ptrMap[ aFootprint.m_value ] = m_value;
124
125 // Copy pads
126 for( PAD* pad : aFootprint.Pads() )
127 {
128 PAD* newPad = static_cast<PAD*>( pad->Clone() );
129 ptrMap[ pad ] = newPad;
130 Add( newPad, ADD_MODE::APPEND ); // Append to ensure indexes are identical
131 }
132
133 // Copy zones
134 for( FP_ZONE* zone : aFootprint.Zones() )
135 {
136 FP_ZONE* newZone = static_cast<FP_ZONE*>( zone->Clone() );
137 ptrMap[ zone ] = newZone;
138 Add( newZone, ADD_MODE::APPEND ); // Append to ensure indexes are identical
139
140 // Ensure the net info is OK and especially uses the net info list
141 // living in the current board
142 // Needed when copying a fp from fp editor that has its own board
143 // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net.
144 newZone->SetNetCode( -1 );
145 }
146
147 // Copy drawings
148 for( BOARD_ITEM* item : aFootprint.GraphicalItems() )
149 {
150 BOARD_ITEM* newItem = static_cast<BOARD_ITEM*>( item->Clone() );
151 ptrMap[ item ] = newItem;
152 Add( newItem, ADD_MODE::APPEND ); // Append to ensure indexes are identical
153 }
154
155 // Copy groups
156 for( PCB_GROUP* group : aFootprint.Groups() )
157 {
158 PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( group->Clone() );
159 ptrMap[ group ] = newGroup;
160 Add( newGroup, ADD_MODE::APPEND ); // Append to ensure indexes are identical
161 }
162
163 // Rebuild groups
164 for( PCB_GROUP* group : aFootprint.Groups() )
165 {
166 PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( ptrMap[ group ] );
167
168 newGroup->GetItems().clear();
169
170 for( BOARD_ITEM* member : group->GetItems() )
171 {
172 if( ptrMap.count( member ) )
173 newGroup->AddItem( ptrMap[ member ] );
174 }
175 }
176
177 // Copy auxiliary data: 3D_Drawings info
178 m_3D_Drawings = aFootprint.m_3D_Drawings;
179
180 m_doc = aFootprint.m_doc;
181 m_keywords = aFootprint.m_keywords;
182 m_properties = aFootprint.m_properties;
183
184 m_arflag = 0;
185
186 m_initial_comments = aFootprint.m_initial_comments ?
187 new wxArrayString( *aFootprint.m_initial_comments ) : nullptr;
188 }
189
190
FOOTPRINT(FOOTPRINT && aFootprint)191 FOOTPRINT::FOOTPRINT( FOOTPRINT&& aFootprint ) :
192 BOARD_ITEM_CONTAINER( aFootprint )
193 {
194 *this = std::move( aFootprint );
195 }
196
197
~FOOTPRINT()198 FOOTPRINT::~FOOTPRINT()
199 {
200 // Clean up the owned elements
201 delete m_reference;
202 delete m_value;
203 delete m_initial_comments;
204
205 for( PAD* p : m_pads )
206 delete p;
207
208 m_pads.clear();
209
210 for( FP_ZONE* zone : m_fp_zones )
211 delete zone;
212
213 m_fp_zones.clear();
214
215 for( PCB_GROUP* group : m_fp_groups )
216 delete group;
217
218 m_fp_groups.clear();
219
220 for( BOARD_ITEM* d : m_drawings )
221 delete d;
222
223 m_drawings.clear();
224 }
225
226
FixUuids()227 bool FOOTPRINT::FixUuids()
228 {
229 // replace null UUIDs if any by a valid uuid
230 std::vector< BOARD_ITEM* > item_list;
231
232 item_list.push_back( m_reference );
233 item_list.push_back( m_value );
234
235 for( PAD* pad : m_pads )
236 item_list.push_back( pad );
237
238 for( BOARD_ITEM* gr_item : m_drawings )
239 item_list.push_back( gr_item );
240
241 // Note: one cannot fix null UUIDs inside the group, but it should not happen
242 // because null uuids can be found in old footprints, therefore without group
243 for( PCB_GROUP* group : m_fp_groups )
244 item_list.push_back( group );
245
246 // Probably notneeded, because old fp do not have zones. But just in case.
247 for( FP_ZONE* zone : m_fp_zones )
248 item_list.push_back( zone );
249
250 bool changed = false;
251
252 for( BOARD_ITEM* item : item_list )
253 {
254 if( item->m_Uuid == niluuid )
255 {
256 const_cast<KIID&>( item->m_Uuid ) = KIID();
257 changed = true;
258 }
259 }
260
261 return changed;
262 }
263
264
operator =(FOOTPRINT && aOther)265 FOOTPRINT& FOOTPRINT::operator=( FOOTPRINT&& aOther )
266 {
267 BOARD_ITEM::operator=( aOther );
268
269 m_pos = aOther.m_pos;
270 m_fpid = aOther.m_fpid;
271 m_attributes = aOther.m_attributes;
272 m_fpStatus = aOther.m_fpStatus;
273 m_orient = aOther.m_orient;
274 m_rot90Cost = aOther.m_rot90Cost;
275 m_rot180Cost = aOther.m_rot180Cost;
276 m_lastEditTime = aOther.m_lastEditTime;
277 m_link = aOther.m_link;
278 m_path = aOther.m_path;
279
280 m_cachedBoundingBox = aOther.m_cachedBoundingBox;
281 m_boundingBoxCacheTimeStamp = aOther.m_boundingBoxCacheTimeStamp;
282 m_cachedVisibleBBox = aOther.m_cachedVisibleBBox;
283 m_visibleBBoxCacheTimeStamp = aOther.m_visibleBBoxCacheTimeStamp;
284 m_cachedTextExcludedBBox = aOther.m_cachedTextExcludedBBox;
285 m_textExcludedBBoxCacheTimeStamp = aOther.m_textExcludedBBoxCacheTimeStamp;
286 m_cachedHull = aOther.m_cachedHull;
287 m_hullCacheTimeStamp = aOther.m_hullCacheTimeStamp;
288
289 m_localClearance = aOther.m_localClearance;
290 m_localSolderMaskMargin = aOther.m_localSolderMaskMargin;
291 m_localSolderPasteMargin = aOther.m_localSolderPasteMargin;
292 m_localSolderPasteMarginRatio = aOther.m_localSolderPasteMarginRatio;
293 m_zoneConnection = aOther.m_zoneConnection;
294 m_thermalWidth = aOther.m_thermalWidth;
295 m_thermalGap = aOther.m_thermalGap;
296
297 // Move reference and value
298 m_reference = aOther.m_reference;
299 m_reference->SetParent( this );
300 m_value = aOther.m_value;
301 m_value->SetParent( this );
302
303
304 // Move the pads
305 m_pads.clear();
306
307 for( PAD* pad : aOther.Pads() )
308 Add( pad );
309
310 aOther.Pads().clear();
311
312 // Move the zones
313 m_fp_zones.clear();
314
315 for( FP_ZONE* item : aOther.Zones() )
316 {
317 Add( item );
318
319 // Ensure the net info is OK and especially uses the net info list
320 // living in the current board
321 // Needed when copying a fp from fp editor that has its own board
322 // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net.
323 item->SetNetCode( -1 );
324 }
325
326 aOther.Zones().clear();
327
328 // Move the drawings
329 m_drawings.clear();
330
331 for( BOARD_ITEM* item : aOther.GraphicalItems() )
332 Add( item );
333
334 aOther.GraphicalItems().clear();
335
336 // Move the groups
337 m_fp_groups.clear();
338
339 for( PCB_GROUP* group : aOther.Groups() )
340 Add( group );
341
342 aOther.Groups().clear();
343
344 // Copy auxiliary data: 3D_Drawings info
345 m_3D_Drawings.clear();
346 m_3D_Drawings = aOther.m_3D_Drawings;
347 m_doc = aOther.m_doc;
348 m_keywords = aOther.m_keywords;
349 m_properties = aOther.m_properties;
350
351 m_initial_comments = aOther.m_initial_comments;
352
353 // Clear the other item's containers since this is a move
354 aOther.Pads().clear();
355 aOther.Zones().clear();
356 aOther.GraphicalItems().clear();
357 aOther.m_value = nullptr;
358 aOther.m_reference = nullptr;
359 aOther.m_initial_comments = nullptr;
360
361 return *this;
362 }
363
364
operator =(const FOOTPRINT & aOther)365 FOOTPRINT& FOOTPRINT::operator=( const FOOTPRINT& aOther )
366 {
367 BOARD_ITEM::operator=( aOther );
368
369 m_pos = aOther.m_pos;
370 m_fpid = aOther.m_fpid;
371 m_attributes = aOther.m_attributes;
372 m_fpStatus = aOther.m_fpStatus;
373 m_orient = aOther.m_orient;
374 m_rot90Cost = aOther.m_rot90Cost;
375 m_rot180Cost = aOther.m_rot180Cost;
376 m_lastEditTime = aOther.m_lastEditTime;
377 m_link = aOther.m_link;
378 m_path = aOther.m_path;
379
380 m_cachedBoundingBox = aOther.m_cachedBoundingBox;
381 m_boundingBoxCacheTimeStamp = aOther.m_boundingBoxCacheTimeStamp;
382 m_cachedVisibleBBox = aOther.m_cachedVisibleBBox;
383 m_visibleBBoxCacheTimeStamp = aOther.m_visibleBBoxCacheTimeStamp;
384 m_cachedTextExcludedBBox = aOther.m_cachedTextExcludedBBox;
385 m_textExcludedBBoxCacheTimeStamp = aOther.m_textExcludedBBoxCacheTimeStamp;
386 m_cachedHull = aOther.m_cachedHull;
387 m_hullCacheTimeStamp = aOther.m_hullCacheTimeStamp;
388
389 m_localClearance = aOther.m_localClearance;
390 m_localSolderMaskMargin = aOther.m_localSolderMaskMargin;
391 m_localSolderPasteMargin = aOther.m_localSolderPasteMargin;
392 m_localSolderPasteMarginRatio = aOther.m_localSolderPasteMarginRatio;
393 m_zoneConnection = aOther.m_zoneConnection;
394 m_thermalWidth = aOther.m_thermalWidth;
395 m_thermalGap = aOther.m_thermalGap;
396
397 // Copy reference and value
398 *m_reference = *aOther.m_reference;
399 m_reference->SetParent( this );
400 *m_value = *aOther.m_value;
401 m_value->SetParent( this );
402
403 std::map<BOARD_ITEM*, BOARD_ITEM*> ptrMap;
404
405 // Copy pads
406 m_pads.clear();
407
408 for( PAD* pad : aOther.Pads() )
409 {
410 PAD* newPad = new PAD( *pad );
411 ptrMap[ pad ] = newPad;
412 Add( newPad );
413 }
414
415 // Copy zones
416 m_fp_zones.clear();
417
418 for( FP_ZONE* zone : aOther.Zones() )
419 {
420 FP_ZONE* newZone = static_cast<FP_ZONE*>( zone->Clone() );
421 ptrMap[ zone ] = newZone;
422 Add( newZone );
423
424 // Ensure the net info is OK and especially uses the net info list
425 // living in the current board
426 // Needed when copying a fp from fp editor that has its own board
427 // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net.
428 newZone->SetNetCode( -1 );
429 }
430
431 // Copy drawings
432 m_drawings.clear();
433
434 for( BOARD_ITEM* item : aOther.GraphicalItems() )
435 {
436 BOARD_ITEM* newItem = static_cast<BOARD_ITEM*>( item->Clone() );
437 ptrMap[ item ] = newItem;
438 Add( newItem );
439 }
440
441 // Copy groups
442 m_fp_groups.clear();
443
444 for( PCB_GROUP* group : aOther.Groups() )
445 {
446 PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( group->Clone() );
447 newGroup->GetItems().clear();
448
449 for( BOARD_ITEM* member : group->GetItems() )
450 newGroup->AddItem( ptrMap[ member ] );
451
452 Add( newGroup );
453 }
454
455 // Copy auxiliary data: 3D_Drawings info
456 m_3D_Drawings.clear();
457 m_3D_Drawings = aOther.m_3D_Drawings;
458 m_doc = aOther.m_doc;
459 m_keywords = aOther.m_keywords;
460 m_properties = aOther.m_properties;
461
462 m_initial_comments = aOther.m_initial_comments ?
463 new wxArrayString( *aOther.m_initial_comments ) : nullptr;
464
465 return *this;
466 }
467
468
GetContextualTextVars(wxArrayString * aVars) const469 void FOOTPRINT::GetContextualTextVars( wxArrayString* aVars ) const
470 {
471 aVars->push_back( wxT( "REFERENCE" ) );
472 aVars->push_back( wxT( "VALUE" ) );
473 aVars->push_back( wxT( "LAYER" ) );
474 }
475
476
ResolveTextVar(wxString * token,int aDepth) const477 bool FOOTPRINT::ResolveTextVar( wxString* token, int aDepth ) const
478 {
479 if( token->IsSameAs( wxT( "REFERENCE" ) ) )
480 {
481 *token = m_reference->GetShownText( aDepth + 1 );
482 return true;
483 }
484 else if( token->IsSameAs( wxT( "VALUE" ) ) )
485 {
486 *token = m_value->GetShownText( aDepth + 1 );
487 return true;
488 }
489 else if( token->IsSameAs( wxT( "LAYER" ) ) )
490 {
491 *token = GetLayerName();
492 return true;
493 }
494 else if( m_properties.count( *token ) )
495 {
496 *token = m_properties.at( *token );
497 return true;
498 }
499
500 return false;
501 }
502
503
ClearAllNets()504 void FOOTPRINT::ClearAllNets()
505 {
506 // Force the ORPHANED dummy net info for all pads.
507 // ORPHANED dummy net does not depend on a board
508 for( PAD* pad : m_pads )
509 pad->SetNetCode( NETINFO_LIST::ORPHANED );
510 }
511
512
Add(BOARD_ITEM * aBoardItem,ADD_MODE aMode)513 void FOOTPRINT::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode )
514 {
515 switch( aBoardItem->Type() )
516 {
517 case PCB_FP_TEXT_T:
518 // Only user text can be added this way.
519 wxASSERT( static_cast<FP_TEXT*>( aBoardItem )->GetType() == FP_TEXT::TEXT_is_DIVERS );
520 KI_FALLTHROUGH;
521
522 case PCB_FP_SHAPE_T:
523 if( aMode == ADD_MODE::APPEND )
524 m_drawings.push_back( aBoardItem );
525 else
526 m_drawings.push_front( aBoardItem );
527 break;
528
529 case PCB_PAD_T:
530 if( aMode == ADD_MODE::APPEND )
531 m_pads.push_back( static_cast<PAD*>( aBoardItem ) );
532 else
533 m_pads.push_front( static_cast<PAD*>( aBoardItem ) );
534 break;
535
536 case PCB_FP_ZONE_T:
537 if( aMode == ADD_MODE::APPEND )
538 m_fp_zones.push_back( static_cast<FP_ZONE*>( aBoardItem ) );
539 else
540 m_fp_zones.insert( m_fp_zones.begin(), static_cast<FP_ZONE*>( aBoardItem ) );
541 break;
542
543 case PCB_GROUP_T:
544 if( aMode == ADD_MODE::APPEND )
545 m_fp_groups.push_back( static_cast<PCB_GROUP*>( aBoardItem ) );
546 else
547 m_fp_groups.insert( m_fp_groups.begin(), static_cast<PCB_GROUP*>( aBoardItem ) );
548 break;
549
550 default:
551 {
552 wxString msg;
553 msg.Printf( wxT( "FOOTPRINT::Add() needs work: BOARD_ITEM type (%d) not handled" ),
554 aBoardItem->Type() );
555 wxFAIL_MSG( msg );
556
557 return;
558 }
559 }
560
561 aBoardItem->ClearEditFlags();
562 aBoardItem->SetParent( this );
563 }
564
565
Remove(BOARD_ITEM * aBoardItem,REMOVE_MODE aMode)566 void FOOTPRINT::Remove( BOARD_ITEM* aBoardItem, REMOVE_MODE aMode )
567 {
568 switch( aBoardItem->Type() )
569 {
570 case PCB_FP_TEXT_T:
571 // Only user text can be removed this way.
572 wxCHECK_RET(
573 static_cast<FP_TEXT*>( aBoardItem )->GetType() == FP_TEXT::TEXT_is_DIVERS,
574 "Please report this bug: Invalid remove operation on required text" );
575 KI_FALLTHROUGH;
576
577 case PCB_FP_SHAPE_T:
578 for( auto it = m_drawings.begin(); it != m_drawings.end(); ++it )
579 {
580 if( *it == aBoardItem )
581 {
582 m_drawings.erase( it );
583 break;
584 }
585 }
586
587 break;
588
589 case PCB_PAD_T:
590 for( auto it = m_pads.begin(); it != m_pads.end(); ++it )
591 {
592 if( *it == static_cast<PAD*>( aBoardItem ) )
593 {
594 m_pads.erase( it );
595 break;
596 }
597 }
598
599 break;
600
601 case PCB_FP_ZONE_T:
602 for( auto it = m_fp_zones.begin(); it != m_fp_zones.end(); ++it )
603 {
604 if( *it == static_cast<FP_ZONE*>( aBoardItem ) )
605 {
606 m_fp_zones.erase( it );
607 break;
608 }
609 }
610
611 break;
612
613 case PCB_GROUP_T:
614 for( auto it = m_fp_groups.begin(); it != m_fp_groups.end(); ++it )
615 {
616 if( *it == static_cast<PCB_GROUP*>( aBoardItem ) )
617 {
618 m_fp_groups.erase( it );
619 break;
620 }
621 }
622
623 break;
624
625 default:
626 {
627 wxString msg;
628 msg.Printf( wxT( "FOOTPRINT::Remove() needs work: BOARD_ITEM type (%d) not handled" ),
629 aBoardItem->Type() );
630 wxFAIL_MSG( msg );
631 }
632 }
633
634 aBoardItem->SetFlags( STRUCT_DELETED );
635
636 PCB_GROUP* parentGroup = aBoardItem->GetParentGroup();
637
638 if( parentGroup && !( parentGroup->GetFlags() & STRUCT_DELETED ) )
639 parentGroup->RemoveItem( aBoardItem );
640 }
641
642
GetArea(int aPadding) const643 double FOOTPRINT::GetArea( int aPadding ) const
644 {
645 EDA_RECT bbox = GetBoundingBox( false, false );
646
647 double w = std::abs( static_cast<double>( bbox.GetWidth() ) ) + aPadding;
648 double h = std::abs( static_cast<double>( bbox.GetHeight() ) ) + aPadding;
649 return w * h;
650 }
651
652
GetLikelyAttribute() const653 int FOOTPRINT::GetLikelyAttribute() const
654 {
655 int smd_count = 0;
656 int tht_count = 0;
657
658 for( PAD* pad : m_pads )
659 {
660 switch( pad->GetProperty() )
661 {
662 case PAD_PROP::FIDUCIAL_GLBL:
663 case PAD_PROP::FIDUCIAL_LOCAL:
664 continue;
665
666 case PAD_PROP::HEATSINK:
667 case PAD_PROP::CASTELLATED:
668 continue;
669
670 case PAD_PROP::NONE:
671 case PAD_PROP::BGA:
672 case PAD_PROP::TESTPOINT:
673 break;
674 }
675
676 switch( pad->GetAttribute() )
677 {
678 case PAD_ATTRIB::PTH:
679 tht_count++;
680 break;
681
682 case PAD_ATTRIB::SMD:
683 smd_count++;
684 break;
685
686 default:
687 break;
688 }
689 }
690
691 if( tht_count > 0 )
692 return FP_THROUGH_HOLE;
693
694 if( smd_count > 0 )
695 return FP_SMD;
696
697 return 0;
698 }
699
700
GetTypeName() const701 wxString FOOTPRINT::GetTypeName() const
702 {
703 if( ( m_attributes & FP_SMD ) == FP_SMD )
704 return _( "SMD" );
705
706 if( ( m_attributes & FP_THROUGH_HOLE ) == FP_THROUGH_HOLE )
707 return _( "Through hole" );
708
709 return _( "Other" );
710 }
711
712
GetFpPadsLocalBbox() const713 EDA_RECT FOOTPRINT::GetFpPadsLocalBbox() const
714 {
715 EDA_RECT area;
716
717 // We want the bounding box of the footprint pads at rot 0, not flipped
718 // Create such a image:
719 FOOTPRINT dummy( *this );
720
721 dummy.SetPosition( wxPoint( 0, 0 ) );
722
723 if( dummy.IsFlipped() )
724 dummy.Flip( wxPoint( 0, 0 ) , false );
725
726 if( dummy.GetOrientation() )
727 dummy.SetOrientation( 0 );
728
729 for( PAD* pad : dummy.Pads() )
730 area.Merge( pad->GetBoundingBox() );
731
732 return area;
733 }
734
735
GetBoundingBox() const736 const EDA_RECT FOOTPRINT::GetBoundingBox() const
737 {
738 return GetBoundingBox( true, true );
739 }
740
741
GetBoundingBox(bool aIncludeText,bool aIncludeInvisibleText) const742 const EDA_RECT FOOTPRINT::GetBoundingBox( bool aIncludeText, bool aIncludeInvisibleText ) const
743 {
744 const BOARD* board = GetBoard();
745
746 if( board )
747 {
748 if( aIncludeText && aIncludeInvisibleText )
749 {
750 if( m_boundingBoxCacheTimeStamp >= board->GetTimeStamp() )
751 return m_cachedBoundingBox;
752 }
753 else if( aIncludeText )
754 {
755 if( m_visibleBBoxCacheTimeStamp >= board->GetTimeStamp() )
756 return m_cachedVisibleBBox;
757 }
758 else
759 {
760 if( m_textExcludedBBoxCacheTimeStamp >= board->GetTimeStamp() )
761 return m_cachedTextExcludedBBox;
762 }
763 }
764
765 EDA_RECT area;
766
767 area.SetOrigin( m_pos );
768 area.SetEnd( m_pos );
769 area.Inflate( Millimeter2iu( 0.25 ) ); // Give a min size to the area
770
771 for( BOARD_ITEM* item : m_drawings )
772 {
773 if( item->Type() == PCB_FP_SHAPE_T )
774 area.Merge( item->GetBoundingBox() );
775 }
776
777 for( PAD* pad : m_pads )
778 area.Merge( pad->GetBoundingBox() );
779
780 for( FP_ZONE* zone : m_fp_zones )
781 area.Merge( zone->GetBoundingBox() );
782
783 bool noDrawItems = ( m_drawings.empty() && m_pads.empty() && m_fp_zones.empty() );
784
785 // Groups do not contribute to the rect, only their members
786 if( aIncludeText || noDrawItems )
787 {
788 for( BOARD_ITEM* item : m_drawings )
789 {
790 if( item->Type() == PCB_FP_TEXT_T )
791 area.Merge( item->GetBoundingBox() );
792 }
793
794 // This can be further optimized when aIncludeInvisibleText is true, but currently
795 // leaving this as is until it's determined there is a noticeable speed hit.
796 bool valueLayerIsVisible = true;
797 bool refLayerIsVisible = true;
798
799 if( board )
800 {
801 // The first "&&" conditional handles the user turning layers off as well as layers
802 // not being present in the current PCB stackup. Values, references, and all
803 // footprint text can also be turned off via the GAL meta-layers, so the 2nd and
804 // 3rd "&&" conditionals handle that.
805 valueLayerIsVisible = board->IsLayerVisible( m_value->GetLayer() )
806 && board->IsElementVisible( LAYER_MOD_VALUES )
807 && board->IsElementVisible( LAYER_MOD_TEXT );
808
809 refLayerIsVisible = board->IsLayerVisible( m_reference->GetLayer() )
810 && board->IsElementVisible( LAYER_MOD_REFERENCES )
811 && board->IsElementVisible( LAYER_MOD_TEXT );
812 }
813
814
815 if( ( m_value->IsVisible() && valueLayerIsVisible )
816 || aIncludeInvisibleText || noDrawItems )
817 area.Merge( m_value->GetBoundingBox() );
818
819 if( ( m_reference->IsVisible() && refLayerIsVisible )
820 || aIncludeInvisibleText || noDrawItems )
821 area.Merge( m_reference->GetBoundingBox() );
822 }
823
824 if( board )
825 {
826 if( ( aIncludeText && aIncludeInvisibleText ) || noDrawItems )
827 {
828 m_boundingBoxCacheTimeStamp = board->GetTimeStamp();
829 m_cachedBoundingBox = area;
830 }
831 else if( aIncludeText )
832 {
833 m_visibleBBoxCacheTimeStamp = board->GetTimeStamp();
834 m_cachedVisibleBBox = area;
835 }
836 else
837 {
838 m_textExcludedBBoxCacheTimeStamp = board->GetTimeStamp();
839 m_cachedTextExcludedBBox = area;
840 }
841 }
842
843 return area;
844 }
845
846
GetBoundingHull() const847 SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const
848 {
849 const BOARD* board = GetBoard();
850
851 if( board )
852 {
853 if( m_hullCacheTimeStamp >= board->GetTimeStamp() )
854 return m_cachedHull;
855 }
856
857 SHAPE_POLY_SET rawPolys;
858 SHAPE_POLY_SET hull;
859
860 for( BOARD_ITEM* item : m_drawings )
861 {
862 if( item->Type() == PCB_FP_SHAPE_T )
863 {
864 item->TransformShapeWithClearanceToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF,
865 ERROR_OUTSIDE );
866 }
867
868 // We intentionally exclude footprint text from the bounding hull.
869 }
870
871 for( PAD* pad : m_pads )
872 {
873 pad->TransformShapeWithClearanceToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF,
874 ERROR_OUTSIDE );
875 // In case hole is larger than pad
876 pad->TransformHoleWithClearanceToPolygon( rawPolys, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
877 }
878
879 for( FP_ZONE* zone : m_fp_zones )
880 {
881 for( PCB_LAYER_ID layer : zone->GetLayerSet().Seq() )
882 {
883 SHAPE_POLY_SET layerPoly = zone->GetFilledPolysList( layer );
884
885 for( int ii = 0; ii < layerPoly.OutlineCount(); ii++ )
886 {
887 const SHAPE_LINE_CHAIN& poly = layerPoly.COutline( ii );
888 rawPolys.AddOutline( poly );
889 }
890 }
891 }
892
893 // If there are some graphic items, build the actual hull.
894 // However if no items, create a minimal polygon (can happen if a footprint
895 // is created with no item: it contains only 2 texts.
896 if( rawPolys.OutlineCount() == 0 )
897 {
898 // generate a small dummy rectangular outline around the anchor
899 const int halfsize = Millimeter2iu( 1.0 );
900
901 rawPolys.NewOutline();
902
903 // add a square:
904 rawPolys.Append( GetPosition().x - halfsize, GetPosition().y - halfsize );
905 rawPolys.Append( GetPosition().x + halfsize, GetPosition().y - halfsize );
906 rawPolys.Append( GetPosition().x + halfsize, GetPosition().y + halfsize );
907 rawPolys.Append( GetPosition().x - halfsize, GetPosition().y + halfsize );
908 }
909
910 std::vector<wxPoint> convex_hull;
911 BuildConvexHull( convex_hull, rawPolys );
912
913 m_cachedHull.RemoveAllContours();
914 m_cachedHull.NewOutline();
915
916 for( const wxPoint& pt : convex_hull )
917 m_cachedHull.Append( pt );
918
919 if( board )
920 m_hullCacheTimeStamp = board->GetTimeStamp();
921
922 return m_cachedHull;
923 }
924
925
GetMsgPanelInfo(EDA_DRAW_FRAME * aFrame,std::vector<MSG_PANEL_ITEM> & aList)926 void FOOTPRINT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
927 {
928 wxString msg, msg2;
929
930 aList.emplace_back( m_reference->GetShownText(), m_value->GetShownText() );
931
932 if( aFrame->IsType( FRAME_FOOTPRINT_VIEWER )
933 || aFrame->IsType( FRAME_FOOTPRINT_VIEWER_MODAL )
934 || aFrame->IsType( FRAME_FOOTPRINT_EDITOR ) )
935 {
936 wxDateTime date( static_cast<time_t>( m_lastEditTime ) );
937
938 // Date format: see http://www.cplusplus.com/reference/ctime/strftime
939 if( m_lastEditTime && date.IsValid() )
940 msg = date.Format( wxT( "%b %d, %Y" ) ); // Abbreviated_month_name Day, Year
941 else
942 msg = _( "Unknown" );
943
944 aList.emplace_back( _( "Last Change" ), msg );
945 }
946 else if( aFrame->IsType( FRAME_PCB_EDITOR ) )
947 {
948 aList.emplace_back( _( "Board Side" ), IsFlipped() ? _( "Back (Flipped)" ) : _( "Front" ) );
949 }
950
951 auto addToken = []( wxString* aStr, const wxString& aAttr )
952 {
953 if( !aStr->IsEmpty() )
954 *aStr += wxT( ", " );
955
956 *aStr += aAttr;
957 };
958
959 wxString status;
960 wxString attrs;
961
962 if( aFrame->GetName() == PCB_EDIT_FRAME_NAME && IsLocked() )
963 addToken( &status, _( "Locked" ) );
964
965 if( m_fpStatus & FP_is_PLACED )
966 addToken( &status, _( "autoplaced" ) );
967
968 if( m_attributes & FP_BOARD_ONLY )
969 addToken( &attrs, _( "not in schematic" ) );
970
971 if( m_attributes & FP_EXCLUDE_FROM_POS_FILES )
972 addToken( &attrs, _( "exclude from pos files" ) );
973
974 if( m_attributes & FP_EXCLUDE_FROM_BOM )
975 addToken( &attrs, _( "exclude from BOM" ) );
976
977 aList.emplace_back( _( "Status: " ) + status, _( "Attributes:" ) + wxS( " " ) + attrs );
978
979 aList.emplace_back( _( "Rotation" ), wxString::Format( "%.4g", GetOrientationDegrees() ) );
980
981 msg.Printf( _( "Footprint: %s" ), m_fpid.GetUniStringLibId() );
982 msg2.Printf( _( "3D-Shape: %s" ), m_3D_Drawings.empty() ? _( "<none>" )
983 : m_3D_Drawings.front().m_Filename );
984 aList.emplace_back( msg, msg2 );
985
986 msg.Printf( _( "Doc: %s" ), m_doc );
987 msg2.Printf( _( "Keywords: %s" ), m_keywords );
988 aList.emplace_back( msg, msg2 );
989 }
990
991
IsOnLayer(PCB_LAYER_ID aLayer) const992 bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer ) const
993 {
994 // If we have any pads, fall back on normal checking
995 if( !m_pads.empty() )
996 return m_layer == aLayer;
997
998 // No pads? Check if this entire footprint exists on the given layer
999 for( FP_ZONE* zone : m_fp_zones )
1000 {
1001 if( !zone->IsOnLayer( aLayer ) )
1002 return false;
1003 }
1004
1005 for( BOARD_ITEM* item : m_drawings )
1006 {
1007 if( !item->IsOnLayer( aLayer ) )
1008 return false;
1009 }
1010
1011 return true;
1012 }
1013
1014
HitTest(const wxPoint & aPosition,int aAccuracy) const1015 bool FOOTPRINT::HitTest( const wxPoint& aPosition, int aAccuracy ) const
1016 {
1017 EDA_RECT rect = GetBoundingBox( false, false );
1018 return rect.Inflate( aAccuracy ).Contains( aPosition );
1019 }
1020
1021
HitTestAccurate(const wxPoint & aPosition,int aAccuracy) const1022 bool FOOTPRINT::HitTestAccurate( const wxPoint& aPosition, int aAccuracy ) const
1023 {
1024 return GetBoundingHull().Collide( aPosition, aAccuracy );
1025 }
1026
1027
HitTest(const EDA_RECT & aRect,bool aContained,int aAccuracy) const1028 bool FOOTPRINT::HitTest( const EDA_RECT& aRect, bool aContained, int aAccuracy ) const
1029 {
1030 EDA_RECT arect = aRect;
1031 arect.Inflate( aAccuracy );
1032
1033 if( aContained )
1034 {
1035 return arect.Contains( GetBoundingBox( false, false ) );
1036 }
1037 else
1038 {
1039 // If the rect does not intersect the bounding box, skip any tests
1040 if( !aRect.Intersects( GetBoundingBox( false, false ) ) )
1041 return false;
1042
1043 // The empty footprint dummy rectangle intersects the selection area.
1044 if( m_pads.empty() && m_fp_zones.empty() && m_drawings.empty() )
1045 return GetBoundingBox( true, false ).Intersects( arect );
1046
1047 // Determine if any elements in the FOOTPRINT intersect the rect
1048 for( PAD* pad : m_pads )
1049 {
1050 if( pad->HitTest( arect, false, 0 ) )
1051 return true;
1052 }
1053
1054 for( FP_ZONE* zone : m_fp_zones )
1055 {
1056 if( zone->HitTest( arect, false, 0 ) )
1057 return true;
1058 }
1059
1060 for( BOARD_ITEM* item : m_drawings )
1061 {
1062 if( item->Type() != PCB_FP_TEXT_T && item->HitTest( arect, false, 0 ) )
1063 return true;
1064 }
1065
1066 // Groups are not hit-tested; only their members
1067
1068 // No items were hit
1069 return false;
1070 }
1071 }
1072
1073
FindPadByNumber(const wxString & aPadNumber,PAD * aSearchAfterMe) const1074 PAD* FOOTPRINT::FindPadByNumber( const wxString& aPadNumber, PAD* aSearchAfterMe ) const
1075 {
1076 bool can_select = aSearchAfterMe ? false : true;
1077
1078 for( PAD* pad : m_pads )
1079 {
1080 if( !can_select && pad == aSearchAfterMe )
1081 {
1082 can_select = true;
1083 continue;
1084 }
1085
1086 if( can_select && pad->GetNumber() == aPadNumber )
1087 return pad;
1088 }
1089
1090 return nullptr;
1091 }
1092
1093
GetPad(const wxPoint & aPosition,LSET aLayerMask)1094 PAD* FOOTPRINT::GetPad( const wxPoint& aPosition, LSET aLayerMask )
1095 {
1096 for( PAD* pad : m_pads )
1097 {
1098 // ... and on the correct layer.
1099 if( !( pad->GetLayerSet() & aLayerMask ).any() )
1100 continue;
1101
1102 if( pad->HitTest( aPosition ) )
1103 return pad;
1104 }
1105
1106 return nullptr;
1107 }
1108
1109
GetTopLeftPad()1110 PAD* FOOTPRINT::GetTopLeftPad()
1111 {
1112 PAD* topLeftPad = m_pads.front();
1113
1114 for( PAD* p : m_pads )
1115 {
1116 wxPoint pnt = p->GetPosition(); // GetPosition() returns the center of the pad
1117
1118 if( ( pnt.x < topLeftPad->GetPosition().x ) ||
1119 ( topLeftPad->GetPosition().x == pnt.x && pnt.y < topLeftPad->GetPosition().y ) )
1120 {
1121 topLeftPad = p;
1122 }
1123 }
1124
1125 return topLeftPad;
1126 }
1127
1128
GetPadCount(INCLUDE_NPTH_T aIncludeNPTH) const1129 unsigned FOOTPRINT::GetPadCount( INCLUDE_NPTH_T aIncludeNPTH ) const
1130 {
1131 if( aIncludeNPTH )
1132 return m_pads.size();
1133
1134 unsigned cnt = 0;
1135
1136 for( PAD* pad : m_pads )
1137 {
1138 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
1139 continue;
1140
1141 cnt++;
1142 }
1143
1144 return cnt;
1145 }
1146
1147
GetUniquePadCount(INCLUDE_NPTH_T aIncludeNPTH) const1148 unsigned FOOTPRINT::GetUniquePadCount( INCLUDE_NPTH_T aIncludeNPTH ) const
1149 {
1150 std::set<wxString> usedNumbers;
1151
1152 // Create a set of used pad numbers
1153 for( PAD* pad : m_pads )
1154 {
1155 // Skip pads not on copper layers (used to build complex
1156 // solder paste shapes for instance)
1157 if( ( pad->GetLayerSet() & LSET::AllCuMask() ).none() )
1158 continue;
1159
1160 // Skip pads with no name, because they are usually "mechanical"
1161 // pads, not "electrical" pads
1162 if( pad->GetNumber().IsEmpty() )
1163 continue;
1164
1165 if( !aIncludeNPTH )
1166 {
1167 // skip NPTH
1168 if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
1169 continue;
1170 }
1171
1172 usedNumbers.insert( pad->GetNumber() );
1173 }
1174
1175 return usedNumbers.size();
1176 }
1177
1178
Add3DModel(FP_3DMODEL * a3DModel)1179 void FOOTPRINT::Add3DModel( FP_3DMODEL* a3DModel )
1180 {
1181 if( nullptr == a3DModel )
1182 return;
1183
1184 if( !a3DModel->m_Filename.empty() )
1185 m_3D_Drawings.push_back( *a3DModel );
1186 }
1187
1188
1189 // see footprint.h
Visit(INSPECTOR inspector,void * testData,const KICAD_T scanTypes[])1190 SEARCH_RESULT FOOTPRINT::Visit( INSPECTOR inspector, void* testData, const KICAD_T scanTypes[] )
1191 {
1192 KICAD_T stype;
1193 SEARCH_RESULT result = SEARCH_RESULT::CONTINUE;
1194 const KICAD_T* p = scanTypes;
1195 bool done = false;
1196
1197 #if 0 && defined(DEBUG)
1198 std::cout << GetClass().mb_str() << ' ';
1199 #endif
1200
1201 while( !done )
1202 {
1203 stype = *p;
1204
1205 switch( stype )
1206 {
1207 case PCB_FOOTPRINT_T:
1208 result = inspector( this, testData ); // inspect me
1209 ++p;
1210 break;
1211
1212 case PCB_PAD_T:
1213 result = IterateForward<PAD*>( m_pads, inspector, testData, p );
1214 ++p;
1215 break;
1216
1217 case PCB_FP_ZONE_T:
1218 result = IterateForward<FP_ZONE*>( m_fp_zones, inspector, testData, p );
1219 ++p;
1220 break;
1221
1222 case PCB_FP_TEXT_T:
1223 result = inspector( m_reference, testData );
1224
1225 if( result == SEARCH_RESULT::QUIT )
1226 break;
1227
1228 result = inspector( m_value, testData );
1229
1230 if( result == SEARCH_RESULT::QUIT )
1231 break;
1232
1233 // Intentionally fall through since m_Drawings can hold PCB_FP_SHAPE_T also
1234 KI_FALLTHROUGH;
1235
1236 case PCB_FP_SHAPE_T:
1237 result = IterateForward<BOARD_ITEM*>( m_drawings, inspector, testData, p );
1238
1239 // skip over any types handled in the above call.
1240 for( ; ; )
1241 {
1242 switch( stype = *++p )
1243 {
1244 case PCB_FP_TEXT_T:
1245 case PCB_FP_SHAPE_T:
1246 continue;
1247
1248 default:
1249 ;
1250 }
1251
1252 break;
1253 }
1254
1255 break;
1256
1257 case PCB_GROUP_T:
1258 result = IterateForward<PCB_GROUP*>( m_fp_groups, inspector, testData, p );
1259 ++p;
1260 break;
1261
1262 default:
1263 done = true;
1264 break;
1265 }
1266
1267 if( result == SEARCH_RESULT::QUIT )
1268 break;
1269 }
1270
1271 return result;
1272 }
1273
1274
GetSelectMenuText(EDA_UNITS aUnits) const1275 wxString FOOTPRINT::GetSelectMenuText( EDA_UNITS aUnits ) const
1276 {
1277 wxString reference = GetReference();
1278
1279 if( reference.IsEmpty() )
1280 reference = _( "<no reference designator>" );
1281
1282 return wxString::Format( _( "Footprint %s" ), reference );
1283 }
1284
1285
GetMenuImage() const1286 BITMAPS FOOTPRINT::GetMenuImage() const
1287 {
1288 return BITMAPS::module;
1289 }
1290
1291
Clone() const1292 EDA_ITEM* FOOTPRINT::Clone() const
1293 {
1294 return new FOOTPRINT( *this );
1295 }
1296
1297
RunOnChildren(const std::function<void (BOARD_ITEM *)> & aFunction) const1298 void FOOTPRINT::RunOnChildren( const std::function<void ( BOARD_ITEM*)>& aFunction ) const
1299 {
1300 try
1301 {
1302 for( PAD* pad : m_pads )
1303 aFunction( static_cast<BOARD_ITEM*>( pad ) );
1304
1305 for( FP_ZONE* zone : m_fp_zones )
1306 aFunction( static_cast<FP_ZONE*>( zone ) );
1307
1308 for( PCB_GROUP* group : m_fp_groups )
1309 aFunction( static_cast<PCB_GROUP*>( group ) );
1310
1311 for( BOARD_ITEM* drawing : m_drawings )
1312 aFunction( static_cast<BOARD_ITEM*>( drawing ) );
1313
1314 aFunction( static_cast<BOARD_ITEM*>( m_reference ) );
1315 aFunction( static_cast<BOARD_ITEM*>( m_value ) );
1316 }
1317 catch( std::bad_function_call& )
1318 {
1319 wxFAIL_MSG( "Error running FOOTPRINT::RunOnChildren" );
1320 }
1321 }
1322
1323
GetAllDrawingLayers(int aLayers[],int & aCount,bool aIncludePads) const1324 void FOOTPRINT::GetAllDrawingLayers( int aLayers[], int& aCount, bool aIncludePads ) const
1325 {
1326 std::unordered_set<int> layers;
1327
1328 for( BOARD_ITEM* item : m_drawings )
1329 layers.insert( static_cast<int>( item->GetLayer() ) );
1330
1331 if( aIncludePads )
1332 {
1333 for( PAD* pad : m_pads )
1334 {
1335 int pad_layers[KIGFX::VIEW::VIEW_MAX_LAYERS], pad_layers_count;
1336 pad->ViewGetLayers( pad_layers, pad_layers_count );
1337
1338 for( int i = 0; i < pad_layers_count; i++ )
1339 layers.insert( pad_layers[i] );
1340 }
1341 }
1342
1343 aCount = layers.size();
1344 int i = 0;
1345
1346 for( int layer : layers )
1347 aLayers[i++] = layer;
1348 }
1349
1350
ViewGetLayers(int aLayers[],int & aCount) const1351 void FOOTPRINT::ViewGetLayers( int aLayers[], int& aCount ) const
1352 {
1353 aCount = 2;
1354 aLayers[0] = LAYER_ANCHOR;
1355
1356 switch( m_layer )
1357 {
1358 default:
1359 wxASSERT_MSG( false, "Illegal layer" ); // do you really have footprints placed on
1360 // other layers?
1361 KI_FALLTHROUGH;
1362
1363 case F_Cu:
1364 aLayers[1] = LAYER_MOD_FR;
1365 break;
1366
1367 case B_Cu:
1368 aLayers[1] = LAYER_MOD_BK;
1369 break;
1370 }
1371
1372 // If there are no pads, and only drawings on a silkscreen layer, then report the silkscreen
1373 // layer as well so that the component can be edited with the silkscreen layer
1374 bool f_silk = false, b_silk = false, non_silk = false;
1375
1376 for( BOARD_ITEM* item : m_drawings )
1377 {
1378 if( item->GetLayer() == F_SilkS )
1379 f_silk = true;
1380 else if( item->GetLayer() == B_SilkS )
1381 b_silk = true;
1382 else
1383 non_silk = true;
1384 }
1385
1386 if( ( f_silk || b_silk ) && !non_silk && m_pads.empty() )
1387 {
1388 if( f_silk )
1389 aLayers[ aCount++ ] = F_SilkS;
1390
1391 if( b_silk )
1392 aLayers[ aCount++ ] = B_SilkS;
1393 }
1394 }
1395
1396
ViewGetLOD(int aLayer,KIGFX::VIEW * aView) const1397 double FOOTPRINT::ViewGetLOD( int aLayer, KIGFX::VIEW* aView ) const
1398 {
1399 int layer = ( m_layer == F_Cu ) ? LAYER_MOD_FR :
1400 ( m_layer == B_Cu ) ? LAYER_MOD_BK : LAYER_ANCHOR;
1401
1402 // Currently this is only pertinent for the anchor layer; everything else is drawn from the
1403 // children.
1404 // The "good" value is experimentally chosen.
1405 #define MINIMAL_ZOOM_LEVEL_FOR_VISIBILITY 1.5
1406
1407 if( aView->IsLayerVisible( layer ) )
1408 return MINIMAL_ZOOM_LEVEL_FOR_VISIBILITY;
1409
1410 return std::numeric_limits<double>::max();
1411 }
1412
1413
ViewBBox() const1414 const BOX2I FOOTPRINT::ViewBBox() const
1415 {
1416 EDA_RECT area = GetBoundingBox( true, true );
1417
1418 // Add the Clearance shape size: (shape around the pads when the clearance is shown. Not
1419 // optimized, but the draw cost is small (perhaps smaller than optimization).
1420 const BOARD* board = GetBoard();
1421
1422 if( board )
1423 {
1424 int biggest_clearance = board->GetDesignSettings().GetBiggestClearanceValue();
1425 area.Inflate( biggest_clearance );
1426 }
1427
1428 return area;
1429 }
1430
1431
IsLibNameValid(const wxString & aName)1432 bool FOOTPRINT::IsLibNameValid( const wxString & aName )
1433 {
1434 const wxChar * invalids = StringLibNameInvalidChars( false );
1435
1436 if( aName.find_first_of( invalids ) != std::string::npos )
1437 return false;
1438
1439 return true;
1440 }
1441
1442
StringLibNameInvalidChars(bool aUserReadable)1443 const wxChar* FOOTPRINT::StringLibNameInvalidChars( bool aUserReadable )
1444 {
1445 // This list of characters is also duplicated in validators.cpp and
1446 // lib_id.cpp
1447 // TODO: Unify forbidden character lists
1448 static const wxChar invalidChars[] = wxT("%$<>\t\n\r\"\\/:");
1449 static const wxChar invalidCharsReadable[] = wxT("% $ < > 'tab' 'return' 'line feed' \\ \" / :");
1450
1451 if( aUserReadable )
1452 return invalidCharsReadable;
1453 else
1454 return invalidChars;
1455 }
1456
1457
Move(const wxPoint & aMoveVector)1458 void FOOTPRINT::Move( const wxPoint& aMoveVector )
1459 {
1460 wxPoint newpos = m_pos + aMoveVector;
1461 SetPosition( newpos );
1462 }
1463
1464
Rotate(const wxPoint & aRotCentre,double aAngle)1465 void FOOTPRINT::Rotate( const wxPoint& aRotCentre, double aAngle )
1466 {
1467 double orientation = GetOrientation();
1468 double newOrientation = orientation + aAngle;
1469 wxPoint newpos = m_pos;
1470 RotatePoint( &newpos, aRotCentre, aAngle );
1471 SetPosition( newpos );
1472 SetOrientation( newOrientation );
1473
1474 m_reference->KeepUpright( orientation, newOrientation );
1475 m_value->KeepUpright( orientation, newOrientation );
1476
1477 for( BOARD_ITEM* item : m_drawings )
1478 {
1479 if( item->Type() == PCB_FP_TEXT_T )
1480 static_cast<FP_TEXT*>( item )->KeepUpright( orientation, newOrientation );
1481 }
1482
1483 m_boundingBoxCacheTimeStamp = 0;
1484 m_visibleBBoxCacheTimeStamp = 0;
1485 m_textExcludedBBoxCacheTimeStamp = 0;
1486 m_hullCacheTimeStamp = 0;
1487 }
1488
1489
Flip(const wxPoint & aCentre,bool aFlipLeftRight)1490 void FOOTPRINT::Flip( const wxPoint& aCentre, bool aFlipLeftRight )
1491 {
1492 // Move footprint to its final position:
1493 wxPoint finalPos = m_pos;
1494
1495 // Now Flip the footprint.
1496 // Flipping a footprint is a specific transform: it is not mirrored like a text.
1497 // We have to change the side, and ensure the footprint rotation is modified according to the
1498 // transform, because this parameter is used in pick and place files, and when updating the
1499 // footprint from library.
1500 // When flipped around the X axis (Y coordinates changed) orientation is negated
1501 // When flipped around the Y axis (X coordinates changed) orientation is 180 - old orient.
1502 // Because it is specific to a footprint, we flip around the X axis, and after rotate 180 deg
1503
1504 MIRROR( finalPos.y, aCentre.y ); /// Mirror the Y position (around the X axis)
1505
1506 SetPosition( finalPos );
1507
1508 // Flip layer
1509 SetLayer( FlipLayer( GetLayer() ) );
1510
1511 // Reverse mirror orientation.
1512 m_orient = -m_orient;
1513
1514 NORMALIZE_ANGLE_180( m_orient );
1515
1516 // Mirror pads to other side of board.
1517 for( PAD* pad : m_pads )
1518 pad->Flip( m_pos, false );
1519
1520 // Mirror zones to other side of board.
1521 for( ZONE* zone : m_fp_zones )
1522 zone->Flip( m_pos, false );
1523
1524 // Mirror reference and value.
1525 m_reference->Flip( m_pos, false );
1526 m_value->Flip( m_pos, false );
1527
1528 // Reverse mirror footprint graphics and texts.
1529 for( BOARD_ITEM* item : m_drawings )
1530 {
1531 switch( item->Type() )
1532 {
1533 case PCB_FP_SHAPE_T:
1534 static_cast<FP_SHAPE*>( item )->Flip( m_pos, false );
1535 break;
1536
1537 case PCB_FP_TEXT_T:
1538 static_cast<FP_TEXT*>( item )->Flip( m_pos, false );
1539 break;
1540
1541 default:
1542 wxMessageBox( wxT( "FOOTPRINT::Flip() error: Unknown Draw Type" ) );
1543 break;
1544 }
1545 }
1546
1547 // Now rotate 180 deg if required
1548 if( aFlipLeftRight )
1549 Rotate( aCentre, 1800.0 );
1550
1551 m_boundingBoxCacheTimeStamp = 0;
1552 m_visibleBBoxCacheTimeStamp = 0;
1553 m_textExcludedBBoxCacheTimeStamp = 0;
1554
1555 m_cachedHull.Mirror( aFlipLeftRight, !aFlipLeftRight, m_pos );
1556
1557 std::swap( m_poly_courtyard_front, m_poly_courtyard_back );
1558 }
1559
1560
SetPosition(const wxPoint & aPos)1561 void FOOTPRINT::SetPosition( const wxPoint& aPos )
1562 {
1563 wxPoint delta = aPos - m_pos;
1564
1565 m_pos += delta;
1566
1567 m_reference->EDA_TEXT::Offset( delta );
1568 m_value->EDA_TEXT::Offset( delta );
1569
1570 for( PAD* pad : m_pads )
1571 pad->SetPosition( pad->GetPosition() + delta );
1572
1573 for( ZONE* zone : m_fp_zones )
1574 zone->Move( delta );
1575
1576 for( BOARD_ITEM* item : m_drawings )
1577 {
1578 switch( item->Type() )
1579 {
1580 case PCB_FP_SHAPE_T:
1581 {
1582 FP_SHAPE* shape = static_cast<FP_SHAPE*>( item );
1583 shape->SetDrawCoord();
1584 break;
1585 }
1586
1587 case PCB_FP_TEXT_T:
1588 {
1589 FP_TEXT* text = static_cast<FP_TEXT*>( item );
1590 text->EDA_TEXT::Offset( delta );
1591 break;
1592 }
1593
1594 default:
1595 wxMessageBox( wxT( "Draw type undefined." ) );
1596 break;
1597 }
1598 }
1599
1600 m_cachedBoundingBox.Move( delta );
1601 m_cachedVisibleBBox.Move( delta );
1602 m_cachedTextExcludedBBox.Move( delta );
1603 m_cachedHull.Move( delta );
1604 }
1605
1606
MoveAnchorPosition(const wxPoint & aMoveVector)1607 void FOOTPRINT::MoveAnchorPosition( const wxPoint& aMoveVector )
1608 {
1609 /* Move the reference point of the footprint
1610 * the footprints elements (pads, outlines, edges .. ) are moved
1611 * but:
1612 * - the footprint position is not modified.
1613 * - the relative (local) coordinates of these items are modified
1614 * - Draw coordinates are updated
1615 */
1616
1617
1618 // Update (move) the relative coordinates relative to the new anchor point.
1619 wxPoint moveVector = aMoveVector;
1620 RotatePoint( &moveVector, -GetOrientation() );
1621
1622 // Update of the reference and value.
1623 m_reference->SetPos0( m_reference->GetPos0() + moveVector );
1624 m_reference->SetDrawCoord();
1625 m_value->SetPos0( m_value->GetPos0() + moveVector );
1626 m_value->SetDrawCoord();
1627
1628 // Update the pad local coordinates.
1629 for( PAD* pad : m_pads )
1630 {
1631 pad->SetPos0( pad->GetPos0() + moveVector );
1632 pad->SetDrawCoord();
1633 }
1634
1635 // Update the draw element coordinates.
1636 for( BOARD_ITEM* item : GraphicalItems() )
1637 {
1638 switch( item->Type() )
1639 {
1640 case PCB_FP_SHAPE_T:
1641 {
1642 FP_SHAPE* shape = static_cast<FP_SHAPE*>( item );
1643 shape->Move( moveVector );
1644 }
1645 break;
1646
1647 case PCB_FP_TEXT_T:
1648 {
1649 FP_TEXT* text = static_cast<FP_TEXT*>( item );
1650 text->SetPos0( text->GetPos0() + moveVector );
1651 text->SetDrawCoord();
1652 }
1653 break;
1654
1655 default:
1656 break;
1657 }
1658 }
1659
1660 // Update the keepout zones
1661 for( ZONE* zone : Zones() )
1662 {
1663 zone->Move( moveVector );
1664 }
1665
1666 // Update the 3D models
1667 for( FP_3DMODEL& model : Models() )
1668 {
1669 model.m_Offset.x += Iu2Millimeter( moveVector.x );
1670 model.m_Offset.y -= Iu2Millimeter( moveVector.y );
1671 }
1672
1673 m_cachedBoundingBox.Move( moveVector );
1674 m_cachedVisibleBBox.Move( moveVector );
1675 m_cachedTextExcludedBBox.Move( moveVector );
1676 m_cachedHull.Move( moveVector );
1677 }
1678
1679
SetOrientation(double aNewAngle)1680 void FOOTPRINT::SetOrientation( double aNewAngle )
1681 {
1682 double angleChange = aNewAngle - m_orient; // change in rotation
1683
1684 NORMALIZE_ANGLE_180( aNewAngle );
1685
1686 m_orient = aNewAngle;
1687
1688 for( PAD* pad : m_pads )
1689 {
1690 pad->SetOrientation( pad->GetOrientation() + angleChange );
1691 pad->SetDrawCoord();
1692 }
1693
1694 for( ZONE* zone : m_fp_zones )
1695 {
1696 zone->Rotate( GetPosition(), angleChange );
1697 }
1698
1699 // Update of the reference and value.
1700 m_reference->SetDrawCoord();
1701 m_value->SetDrawCoord();
1702
1703 // Displace contours and text of the footprint.
1704 for( BOARD_ITEM* item : m_drawings )
1705 {
1706 if( item->Type() == PCB_FP_SHAPE_T )
1707 {
1708 static_cast<FP_SHAPE*>( item )->SetDrawCoord();
1709 }
1710 else if( item->Type() == PCB_FP_TEXT_T )
1711 {
1712 static_cast<FP_TEXT*>( item )->SetDrawCoord();
1713 }
1714 }
1715
1716 m_boundingBoxCacheTimeStamp = 0;
1717 m_visibleBBoxCacheTimeStamp = 0;
1718 m_textExcludedBBoxCacheTimeStamp = 0;
1719
1720 m_cachedHull.Rotate( -DECIDEG2RAD( angleChange ), GetPosition() );
1721 }
1722
1723
Duplicate() const1724 BOARD_ITEM* FOOTPRINT::Duplicate() const
1725 {
1726 FOOTPRINT* dupe = (FOOTPRINT*) Clone();
1727 const_cast<KIID&>( dupe->m_Uuid ) = KIID();
1728
1729 dupe->RunOnChildren( [&]( BOARD_ITEM* child )
1730 {
1731 const_cast<KIID&>( child->m_Uuid ) = KIID();
1732 });
1733
1734 return static_cast<BOARD_ITEM*>( dupe );
1735 }
1736
1737
DuplicateItem(const BOARD_ITEM * aItem,bool aAddToFootprint)1738 BOARD_ITEM* FOOTPRINT::DuplicateItem( const BOARD_ITEM* aItem, bool aAddToFootprint )
1739 {
1740 BOARD_ITEM* new_item = nullptr;
1741 FP_ZONE* new_zone = nullptr;
1742
1743 switch( aItem->Type() )
1744 {
1745 case PCB_PAD_T:
1746 {
1747 PAD* new_pad = new PAD( *static_cast<const PAD*>( aItem ) );
1748 const_cast<KIID&>( new_pad->m_Uuid ) = KIID();
1749
1750 if( aAddToFootprint )
1751 m_pads.push_back( new_pad );
1752
1753 new_item = new_pad;
1754 break;
1755 }
1756
1757 case PCB_FP_ZONE_T:
1758 {
1759 new_zone = new FP_ZONE( *static_cast<const FP_ZONE*>( aItem ) );
1760 const_cast<KIID&>( new_zone->m_Uuid ) = KIID();
1761
1762 if( aAddToFootprint )
1763 m_fp_zones.push_back( new_zone );
1764
1765 new_item = new_zone;
1766 break;
1767 }
1768
1769 case PCB_FP_TEXT_T:
1770 {
1771 FP_TEXT* new_text = new FP_TEXT( *static_cast<const FP_TEXT*>( aItem ) );
1772 const_cast<KIID&>( new_text->m_Uuid ) = KIID();
1773
1774 if( new_text->GetType() == FP_TEXT::TEXT_is_REFERENCE )
1775 {
1776 new_text->SetText( wxT( "${REFERENCE}" ) );
1777 new_text->SetType( FP_TEXT::TEXT_is_DIVERS );
1778 }
1779 else if( new_text->GetType() == FP_TEXT::TEXT_is_VALUE )
1780 {
1781 new_text->SetText( wxT( "${VALUE}" ) );
1782 new_text->SetType( FP_TEXT::TEXT_is_DIVERS );
1783 }
1784
1785 if( aAddToFootprint )
1786 Add( new_text );
1787
1788 new_item = new_text;
1789
1790 break;
1791 }
1792
1793 case PCB_FP_SHAPE_T:
1794 {
1795 FP_SHAPE* new_shape = new FP_SHAPE( *static_cast<const FP_SHAPE*>( aItem ) );
1796 const_cast<KIID&>( new_shape->m_Uuid ) = KIID();
1797
1798 if( aAddToFootprint )
1799 Add( new_shape );
1800
1801 new_item = new_shape;
1802 break;
1803 }
1804
1805 case PCB_GROUP_T:
1806 new_item = static_cast<const PCB_GROUP*>( aItem )->DeepDuplicate();
1807 break;
1808
1809 case PCB_FOOTPRINT_T:
1810 // Ignore the footprint itself
1811 break;
1812
1813 default:
1814 // Un-handled item for duplication
1815 wxFAIL_MSG( "Duplication not supported for items of class " + aItem->GetClass() );
1816 break;
1817 }
1818
1819 return new_item;
1820 }
1821
1822
GetNextPadNumber(const wxString & aLastPadNumber) const1823 wxString FOOTPRINT::GetNextPadNumber( const wxString& aLastPadNumber ) const
1824 {
1825 std::set<wxString> usedNumbers;
1826
1827 // Create a set of used pad numbers
1828 for( PAD* pad : m_pads )
1829 usedNumbers.insert( pad->GetNumber() );
1830
1831 // Pad numbers aren't technically reference designators, but the formatting is close enough
1832 // for these to give us what we need.
1833 wxString prefix = UTIL::GetRefDesPrefix( aLastPadNumber );
1834 int num = GetTrailingInt( aLastPadNumber );
1835
1836 while( usedNumbers.count( wxString::Format( "%s%d", prefix, num ) ) )
1837 num++;
1838
1839 return wxString::Format( "%s%d", prefix, num );
1840 }
1841
1842
IncrementReference(int aDelta)1843 void FOOTPRINT::IncrementReference( int aDelta )
1844 {
1845 const wxString& refdes = GetReference();
1846
1847 SetReference( wxString::Format( wxT( "%s%i" ),
1848 UTIL::GetRefDesPrefix( refdes ),
1849 GetTrailingInt( refdes ) + aDelta ) );
1850 }
1851
1852
1853 // Calculate the area of a PolySet, polygons with hole are allowed.
polygonArea(SHAPE_POLY_SET & aPolySet)1854 static double polygonArea( SHAPE_POLY_SET& aPolySet )
1855 {
1856 // Ensure all outlines are closed, before calculating the SHAPE_POLY_SET area
1857 for( int ii = 0; ii < aPolySet.OutlineCount(); ii++ )
1858 {
1859 SHAPE_LINE_CHAIN& outline = aPolySet.Outline( ii );
1860 outline.SetClosed( true );
1861
1862 for( int jj = 0; jj < aPolySet.HoleCount( ii ); jj++ )
1863 aPolySet.Hole( ii, jj ).SetClosed( true );
1864 }
1865
1866 return aPolySet.Area();
1867 }
1868
1869
GetCoverageArea(const BOARD_ITEM * aItem,const GENERAL_COLLECTOR & aCollector)1870 double FOOTPRINT::GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLECTOR& aCollector )
1871 {
1872 int textMargin = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() );
1873 SHAPE_POLY_SET poly;
1874
1875 if( aItem->Type() == PCB_MARKER_T )
1876 {
1877 const PCB_MARKER* marker = static_cast<const PCB_MARKER*>( aItem );
1878 SHAPE_LINE_CHAIN markerShape;
1879
1880 marker->ShapeToPolygon( markerShape );
1881 return markerShape.Area();
1882 }
1883 else if( aItem->Type() == PCB_GROUP_T )
1884 {
1885 double combinedArea = 0.0;
1886
1887 for( BOARD_ITEM* member : static_cast<const PCB_GROUP*>( aItem )->GetItems() )
1888 combinedArea += GetCoverageArea( member, aCollector );
1889
1890 return combinedArea;
1891 }
1892 if( aItem->Type() == PCB_FOOTPRINT_T )
1893 {
1894 const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
1895
1896 poly = footprint->GetBoundingHull();
1897 }
1898 else if( aItem->Type() == PCB_FP_TEXT_T )
1899 {
1900 const FP_TEXT* text = static_cast<const FP_TEXT*>( aItem );
1901
1902 text->TransformTextShapeWithClearanceToPolygon( poly, UNDEFINED_LAYER, textMargin,
1903 ARC_LOW_DEF, ERROR_OUTSIDE );
1904 }
1905 else if( aItem->Type() == PCB_SHAPE_T )
1906 {
1907 // Approximate "linear" shapes with just their width squared, as we don't want to consider
1908 // a linear shape as being much bigger than another for purposes of selection filtering
1909 // just because it happens to be really long.
1910
1911 const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
1912
1913 switch( shape->GetShape() )
1914 {
1915 case SHAPE_T::SEGMENT:
1916 case SHAPE_T::ARC:
1917 case SHAPE_T::BEZIER:
1918 return shape->GetWidth() * shape->GetWidth();
1919
1920 case SHAPE_T::RECT:
1921 case SHAPE_T::CIRCLE:
1922 case SHAPE_T::POLY:
1923 {
1924 if( !shape->IsFilled() )
1925 return shape->GetWidth() * shape->GetWidth();
1926
1927 KI_FALLTHROUGH;
1928 }
1929
1930 default:
1931 aItem->TransformShapeWithClearanceToPolygon( poly, UNDEFINED_LAYER, 0,
1932 ARC_LOW_DEF, ERROR_OUTSIDE );
1933 }
1934 }
1935 else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
1936 {
1937 double width = static_cast<const PCB_TRACK*>( aItem )->GetWidth();
1938 return width * width;
1939 }
1940 else
1941 {
1942 aItem->TransformShapeWithClearanceToPolygon( poly, UNDEFINED_LAYER, 0,
1943 ARC_LOW_DEF, ERROR_OUTSIDE );
1944 }
1945
1946 return polygonArea( poly );
1947 }
1948
1949
CoverageRatio(const GENERAL_COLLECTOR & aCollector) const1950 double FOOTPRINT::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const
1951 {
1952 int textMargin = KiROUND( 5 * aCollector.GetGuide()->OnePixelInIU() );
1953
1954 SHAPE_POLY_SET footprintRegion( GetBoundingHull() );
1955 SHAPE_POLY_SET coveredRegion;
1956
1957 TransformPadsWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, 0, ARC_LOW_DEF,
1958 ERROR_OUTSIDE );
1959
1960 TransformFPShapesWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, textMargin,
1961 ARC_LOW_DEF, ERROR_OUTSIDE,
1962 true, /* include text */
1963 false /* include shapes */ );
1964
1965 for( int i = 0; i < aCollector.GetCount(); ++i )
1966 {
1967 const BOARD_ITEM* item = aCollector[i];
1968
1969 switch( item->Type() )
1970 {
1971 case PCB_FP_TEXT_T:
1972 case PCB_FP_SHAPE_T:
1973 if( item->GetParent() != this )
1974 {
1975 item->TransformShapeWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, 0,
1976 ARC_LOW_DEF, ERROR_OUTSIDE );
1977 }
1978 break;
1979
1980 case PCB_TEXT_T:
1981 case PCB_SHAPE_T:
1982 case PCB_TRACE_T:
1983 case PCB_ARC_T:
1984 case PCB_VIA_T:
1985 item->TransformShapeWithClearanceToPolygon( coveredRegion, UNDEFINED_LAYER, 0,
1986 ARC_LOW_DEF, ERROR_OUTSIDE );
1987 break;
1988
1989 case PCB_FOOTPRINT_T:
1990 if( item != this )
1991 {
1992 const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( item );
1993 coveredRegion.AddOutline( footprint->GetBoundingHull().Outline( 0 ) );
1994 }
1995 break;
1996
1997 default:
1998 break;
1999 }
2000 }
2001
2002 double footprintRegionArea = polygonArea( footprintRegion );
2003 double uncoveredRegionArea = footprintRegionArea - polygonArea( coveredRegion );
2004 double coveredArea = footprintRegionArea - uncoveredRegionArea;
2005 double ratio = ( coveredArea / footprintRegionArea );
2006
2007 // Test for negative ratio (should not occur).
2008 // better to be conservative (this will result in the disambiguate dialog)
2009 if( ratio < 0.0 )
2010 return 1.0;
2011
2012 return std::min( ratio, 1.0 );
2013 }
2014
2015
GetEffectiveShape(PCB_LAYER_ID aLayer) const2016 std::shared_ptr<SHAPE> FOOTPRINT::GetEffectiveShape( PCB_LAYER_ID aLayer ) const
2017 {
2018 std::shared_ptr<SHAPE_COMPOUND> shape = std::make_shared<SHAPE_COMPOUND>();
2019
2020 // There are several possible interpretations here:
2021 // 1) the bounding box (without or without invisible items)
2022 // 2) just the pads and "edges" (ie: non-text graphic items)
2023 // 3) the courtyard
2024
2025 // We'll go with (2) for now....
2026
2027 for( PAD* pad : Pads() )
2028 shape->AddShape( pad->GetEffectiveShape( aLayer )->Clone() );
2029
2030 for( BOARD_ITEM* item : GraphicalItems() )
2031 {
2032 if( item->Type() == PCB_FP_SHAPE_T )
2033 shape->AddShape( item->GetEffectiveShape( aLayer )->Clone() );
2034 }
2035
2036 return shape;
2037 }
2038
2039
BuildPolyCourtyards(OUTLINE_ERROR_HANDLER * aErrorHandler)2040 void FOOTPRINT::BuildPolyCourtyards( OUTLINE_ERROR_HANDLER* aErrorHandler )
2041 {
2042 m_poly_courtyard_front.RemoveAllContours();
2043 m_poly_courtyard_back.RemoveAllContours();
2044 ClearFlags( MALFORMED_COURTYARDS );
2045
2046 // Build the courtyard area from graphic items on the courtyard.
2047 // Only PCB_FP_SHAPE_T have meaning, graphic texts are ignored.
2048 // Collect items:
2049 std::vector<PCB_SHAPE*> list_front;
2050 std::vector<PCB_SHAPE*> list_back;
2051
2052 for( BOARD_ITEM* item : GraphicalItems() )
2053 {
2054 if( item->GetLayer() == B_CrtYd && item->Type() == PCB_FP_SHAPE_T )
2055 list_back.push_back( static_cast<PCB_SHAPE*>( item ) );
2056
2057 if( item->GetLayer() == F_CrtYd && item->Type() == PCB_FP_SHAPE_T )
2058 list_front.push_back( static_cast<PCB_SHAPE*>( item ) );
2059 }
2060
2061 if( !list_front.size() && !list_back.size() )
2062 return;
2063
2064 int errorMax = Millimeter2iu( 0.02 ); // max error for polygonization
2065 int chainingEpsilon = Millimeter2iu( 0.02 ); // max dist from one endPt to next startPt
2066
2067 if( ConvertOutlineToPolygon( list_front, m_poly_courtyard_front, errorMax, chainingEpsilon,
2068 aErrorHandler ) )
2069 {
2070 // Touching courtyards, or courtyards -at- the clearance distance are legal.
2071 m_poly_courtyard_front.Inflate( -1, SHAPE_POLY_SET::CHAMFER_ACUTE_CORNERS );
2072
2073 m_poly_courtyard_front.CacheTriangulation( false );
2074 }
2075 else
2076 {
2077 SetFlags( MALFORMED_F_COURTYARD );
2078 }
2079
2080 if( ConvertOutlineToPolygon( list_back, m_poly_courtyard_back, errorMax, chainingEpsilon,
2081 aErrorHandler ) )
2082 {
2083 // Touching courtyards, or courtyards -at- the clearance distance are legal.
2084 m_poly_courtyard_back.Inflate( -1, SHAPE_POLY_SET::CHAMFER_ACUTE_CORNERS );
2085
2086 m_poly_courtyard_back.CacheTriangulation( false );
2087 }
2088 else
2089 {
2090 SetFlags( MALFORMED_B_COURTYARD );
2091 }
2092 }
2093
2094
CheckFootprintAttributes(const std::function<void (const wxString & msg)> * aErrorHandler)2095 void FOOTPRINT::CheckFootprintAttributes( const std::function<void( const wxString& msg )>* aErrorHandler )
2096 {
2097
2098 int likelyAttr = GetLikelyAttribute();
2099 int setAttr = ( GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE ) );
2100
2101 // This is only valid if the footprint doesn't have FP_SMD and FP_THROUGH_HOLE set
2102 // Which is, unfortunately, possible in theory but not in the UI (I think)
2103 if( aErrorHandler && likelyAttr != setAttr )
2104 {
2105 wxString msg;
2106
2107 if( likelyAttr == FP_THROUGH_HOLE )
2108 {
2109 msg.Printf( _( "Expected \"Through hole\" type but set to \"%s\"" ), GetTypeName() );
2110 }
2111 else if( likelyAttr == FP_SMD )
2112 {
2113 msg.Printf( _( "Expected \"SMD\" type but set to \"%s\"" ), GetTypeName() );
2114 }
2115 else
2116 {
2117 msg.Printf( _( "Expected \"Other\" type but set to \"%s\"" ), GetTypeName() );
2118 }
2119
2120 msg = "(" + msg + ")";
2121
2122 (*aErrorHandler)( msg );
2123 }
2124 }
2125
CheckFootprintTHPadNoHoles(const std::function<void (const wxString & msg,const wxPoint & position)> * aErrorHandler)2126 void FOOTPRINT::CheckFootprintTHPadNoHoles(
2127 const std::function<void( const wxString& msg, const wxPoint& position )>*
2128 aErrorHandler )
2129 {
2130 if( aErrorHandler == nullptr )
2131 return;
2132
2133 for( const PAD* pad: Pads() )
2134 {
2135
2136 if( pad->GetAttribute() != PAD_ATTRIB::PTH
2137 && pad->GetAttribute() != PAD_ATTRIB::NPTH )
2138 continue;
2139
2140 if( pad->GetDrillSizeX() < 1 || pad->GetDrillSizeX() < 1 )
2141 {
2142 wxString msg;
2143 msg.Printf( _( "(pad \"%s\")" ), pad->GetNumber() );
2144
2145 (*aErrorHandler)( msg, pad->GetPosition() );
2146 }
2147 }
2148 }
2149
2150
SwapData(BOARD_ITEM * aImage)2151 void FOOTPRINT::SwapData( BOARD_ITEM* aImage )
2152 {
2153 wxASSERT( aImage->Type() == PCB_FOOTPRINT_T );
2154
2155 std::swap( *((FOOTPRINT*) this), *((FOOTPRINT*) aImage) );
2156 }
2157
2158
HasThroughHolePads() const2159 bool FOOTPRINT::HasThroughHolePads() const
2160 {
2161 for( PAD* pad : Pads() )
2162 {
2163 if( pad->GetAttribute() != PAD_ATTRIB::SMD )
2164 return true;
2165 }
2166
2167 return false;
2168 }
2169
2170
operator ()(const BOARD_ITEM * aFirst,const BOARD_ITEM * aSecond) const2171 bool FOOTPRINT::cmp_drawings::operator()( const BOARD_ITEM* aFirst,
2172 const BOARD_ITEM* aSecond ) const
2173 {
2174 if( aFirst->Type() != aSecond->Type() )
2175 return aFirst->Type() < aSecond->Type();
2176
2177 if( aFirst->GetLayer() != aSecond->GetLayer() )
2178 return aFirst->GetLayer() < aSecond->GetLayer();
2179
2180 if( aFirst->Type() == PCB_FP_SHAPE_T )
2181 {
2182 const FP_SHAPE* dwgA = static_cast<const FP_SHAPE*>( aFirst );
2183 const FP_SHAPE* dwgB = static_cast<const FP_SHAPE*>( aSecond );
2184
2185 if( dwgA->GetShape() != dwgB->GetShape() )
2186 return dwgA->GetShape() < dwgB->GetShape();
2187 }
2188
2189 if( aFirst->m_Uuid != aSecond->m_Uuid ) // shopuld be always the case foer valid boards
2190 return aFirst->m_Uuid < aSecond->m_Uuid;
2191
2192 return aFirst < aSecond;
2193 }
2194
2195
operator ()(const PAD * aFirst,const PAD * aSecond) const2196 bool FOOTPRINT::cmp_pads::operator()( const PAD* aFirst, const PAD* aSecond ) const
2197 {
2198 if( aFirst->GetNumber() != aSecond->GetNumber() )
2199 return StrNumCmp( aFirst->GetNumber(), aSecond->GetNumber() ) < 0;
2200
2201 if( aFirst->m_Uuid != aSecond->m_Uuid ) // shopuld be always the case foer valid boards
2202 return aFirst->m_Uuid < aSecond->m_Uuid;
2203
2204 return aFirst < aSecond;
2205 }
2206
2207
TransformPadsWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,PCB_LAYER_ID aLayer,int aClearance,int aMaxError,ERROR_LOC aErrorLoc,bool aSkipNPTHPadsWihNoCopper,bool aSkipPlatedPads,bool aSkipNonPlatedPads) const2208 void FOOTPRINT::TransformPadsWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
2209 PCB_LAYER_ID aLayer, int aClearance,
2210 int aMaxError, ERROR_LOC aErrorLoc,
2211 bool aSkipNPTHPadsWihNoCopper,
2212 bool aSkipPlatedPads,
2213 bool aSkipNonPlatedPads ) const
2214 {
2215 for( const PAD* pad : m_pads )
2216 {
2217 if( aLayer != UNDEFINED_LAYER && !pad->IsOnLayer(aLayer) )
2218 continue;
2219
2220 if( !pad->FlashLayer( aLayer ) && IsCopperLayer( aLayer ) )
2221 continue;
2222
2223 // NPTH pads are not drawn on layers if the shape size and pos is the same
2224 // as their hole:
2225 if( aSkipNPTHPadsWihNoCopper && pad->GetAttribute() == PAD_ATTRIB::NPTH )
2226 {
2227 if( pad->GetDrillSize() == pad->GetSize() && pad->GetOffset() == wxPoint( 0, 0 ) )
2228 {
2229 switch( pad->GetShape() )
2230 {
2231 case PAD_SHAPE::CIRCLE:
2232 if( pad->GetDrillShape() == PAD_DRILL_SHAPE_CIRCLE )
2233 continue;
2234
2235 break;
2236
2237 case PAD_SHAPE::OVAL:
2238 if( pad->GetDrillShape() != PAD_DRILL_SHAPE_CIRCLE )
2239 continue;
2240
2241 break;
2242
2243 default:
2244 break;
2245 }
2246 }
2247 }
2248
2249 const bool isPlated = ( ( aLayer == F_Cu ) && pad->FlashLayer( F_Mask ) ) ||
2250 ( ( aLayer == B_Cu ) && pad->FlashLayer( B_Mask ) );
2251
2252 if( aSkipPlatedPads && isPlated )
2253 continue;
2254
2255 if( aSkipNonPlatedPads && !isPlated )
2256 continue;
2257
2258 wxSize clearance( aClearance, aClearance );
2259
2260 switch( aLayer )
2261 {
2262 case F_Mask:
2263 case B_Mask:
2264 clearance.x += pad->GetSolderMaskMargin();
2265 clearance.y += pad->GetSolderMaskMargin();
2266 break;
2267
2268 case F_Paste:
2269 case B_Paste:
2270 clearance += pad->GetSolderPasteMargin();
2271 break;
2272
2273 default:
2274 break;
2275 }
2276
2277 // Our standard TransformShapeWithClearanceToPolygon() routines can't handle differing
2278 // x:y clearance values (which get generated when a relative paste margin is used with
2279 // an oblong pad). So we apply this huge hack and fake a larger pad to run the transform
2280 // on.
2281 // Of course being a hack it falls down when dealing with custom shape pads (where the
2282 // size is only the size of the anchor), so for those we punt and just use clearance.x.
2283
2284 if( ( clearance.x < 0 || clearance.x != clearance.y )
2285 && pad->GetShape() != PAD_SHAPE::CUSTOM )
2286 {
2287 wxSize dummySize = pad->GetSize() + clearance + clearance;
2288
2289 if( dummySize.x <= 0 || dummySize.y <= 0 )
2290 continue;
2291
2292 PAD dummy( *pad );
2293 dummy.SetSize( dummySize );
2294 dummy.TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, 0,
2295 aMaxError, aErrorLoc );
2296 }
2297 else
2298 {
2299 pad->TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, clearance.x,
2300 aMaxError, aErrorLoc );
2301 }
2302 }
2303 }
2304
2305
TransformFPShapesWithClearanceToPolygon(SHAPE_POLY_SET & aCornerBuffer,PCB_LAYER_ID aLayer,int aClearance,int aError,ERROR_LOC aErrorLoc,bool aIncludeText,bool aIncludeShapes) const2306 void FOOTPRINT::TransformFPShapesWithClearanceToPolygon( SHAPE_POLY_SET& aCornerBuffer,
2307 PCB_LAYER_ID aLayer, int aClearance,
2308 int aError, ERROR_LOC aErrorLoc,
2309 bool aIncludeText,
2310 bool aIncludeShapes ) const
2311 {
2312 std::vector<FP_TEXT*> texts; // List of FP_TEXT to convert
2313
2314 for( BOARD_ITEM* item : GraphicalItems() )
2315 {
2316 if( item->Type() == PCB_FP_TEXT_T && aIncludeText )
2317 {
2318 FP_TEXT* text = static_cast<FP_TEXT*>( item );
2319
2320 if( aLayer != UNDEFINED_LAYER && text->GetLayer() == aLayer && text->IsVisible() )
2321 texts.push_back( text );
2322 }
2323
2324 if( item->Type() == PCB_FP_SHAPE_T && aIncludeShapes )
2325 {
2326 const FP_SHAPE* outline = static_cast<FP_SHAPE*>( item );
2327
2328 if( aLayer != UNDEFINED_LAYER && outline->GetLayer() == aLayer )
2329 {
2330 outline->TransformShapeWithClearanceToPolygon( aCornerBuffer, aLayer, 0,
2331 aError, aErrorLoc );
2332 }
2333 }
2334 }
2335
2336 if( aIncludeText )
2337 {
2338 if( Reference().GetLayer() == aLayer && Reference().IsVisible() )
2339 texts.push_back( &Reference() );
2340
2341 if( Value().GetLayer() == aLayer && Value().IsVisible() )
2342 texts.push_back( &Value() );
2343 }
2344
2345 for( const FP_TEXT* text : texts )
2346 {
2347 text->TransformTextShapeWithClearanceToPolygon( aCornerBuffer, aLayer, aClearance,
2348 aError, aErrorLoc );
2349 }
2350 }
2351
2352
2353 static struct FOOTPRINT_DESC
2354 {
FOOTPRINT_DESCFOOTPRINT_DESC2355 FOOTPRINT_DESC()
2356 {
2357 ENUM_MAP<PCB_LAYER_ID>& layerEnum = ENUM_MAP<PCB_LAYER_ID>::Instance();
2358
2359 if( layerEnum.Choices().GetCount() == 0 )
2360 {
2361 layerEnum.Undefined( UNDEFINED_LAYER );
2362
2363 for( LSEQ seq = LSET::AllLayersMask().Seq(); seq; ++seq )
2364 layerEnum.Map( *seq, LSET::Name( *seq ) );
2365 }
2366
2367 wxPGChoices fpLayers; // footprints might be placed only on F.Cu & B.Cu
2368 fpLayers.Add( LSET::Name( F_Cu ), F_Cu );
2369 fpLayers.Add( LSET::Name( B_Cu ), B_Cu );
2370
2371 PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
2372 REGISTER_TYPE( FOOTPRINT );
2373 propMgr.AddTypeCast( new TYPE_CAST<FOOTPRINT, BOARD_ITEM> );
2374 propMgr.AddTypeCast( new TYPE_CAST<FOOTPRINT, BOARD_ITEM_CONTAINER> );
2375 propMgr.InheritsAfter( TYPE_HASH( FOOTPRINT ), TYPE_HASH( BOARD_ITEM ) );
2376 propMgr.InheritsAfter( TYPE_HASH( FOOTPRINT ), TYPE_HASH( BOARD_ITEM_CONTAINER ) );
2377
2378 auto layer = new PROPERTY_ENUM<FOOTPRINT, PCB_LAYER_ID, BOARD_ITEM>( _HKI( "Layer" ),
2379 &FOOTPRINT::SetLayer, &FOOTPRINT::GetLayer );
2380 layer->SetChoices( fpLayers );
2381 propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ), layer );
2382
2383 propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Reference" ),
2384 &FOOTPRINT::SetReference, &FOOTPRINT::GetReference ) );
2385 propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Value" ),
2386 &FOOTPRINT::SetValue, &FOOTPRINT::GetValue ) );
2387 propMgr.AddProperty( new PROPERTY<FOOTPRINT, double>( _HKI( "Orientation" ),
2388 &FOOTPRINT::SetOrientationDegrees, &FOOTPRINT::GetOrientationDegrees,
2389 PROPERTY_DISPLAY::DEGREE ) );
2390 propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Clearance Override" ),
2391 &FOOTPRINT::SetLocalClearance, &FOOTPRINT::GetLocalClearance,
2392 PROPERTY_DISPLAY::DISTANCE ) );
2393 propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Solderpaste Margin Override" ),
2394 &FOOTPRINT::SetLocalSolderPasteMargin, &FOOTPRINT::GetLocalSolderPasteMargin,
2395 PROPERTY_DISPLAY::DISTANCE ) );
2396 propMgr.AddProperty( new PROPERTY<FOOTPRINT,
2397 double>( _HKI( "Solderpaste Margin Ratio Override" ),
2398 &FOOTPRINT::SetLocalSolderPasteMarginRatio,
2399 &FOOTPRINT::GetLocalSolderPasteMarginRatio ) );
2400 propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Thermal Relief Width" ),
2401 &FOOTPRINT::SetThermalWidth,
2402 &FOOTPRINT::GetThermalWidth,
2403 PROPERTY_DISPLAY::DISTANCE ) );
2404 propMgr.AddProperty( new PROPERTY<FOOTPRINT, int>( _HKI( "Thermal Relief Gap" ),
2405 &FOOTPRINT::SetThermalGap,
2406 &FOOTPRINT::GetThermalGap,
2407 PROPERTY_DISPLAY::DISTANCE ) );
2408 // TODO zone connection, FPID?
2409 }
2410 } _FOOTPRINT_DESC;
2411