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