You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

4464 lines
135 KiB

2 months ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
2 months ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
7 months ago
5 years ago
5 years ago
7 months ago
7 months ago
7 months ago
3 months ago
3 months ago
5 years ago
5 years ago
5 years ago
5 years ago
7 months ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
7 months ago
5 years ago
14 years ago
11 years ago
5 years ago
5 years ago
5 years ago
* KIWAY Milestone A): Make major modules into DLL/DSOs. ! The initial testing of this commit should be done using a Debug build so that all the wxASSERT()s are enabled. Also, be sure and keep enabled the USE_KIWAY_DLLs option. The tree won't likely build without it. Turning it off is senseless anyways. If you want stable code, go back to a prior version, the one tagged with "stable". * Relocate all functionality out of the wxApp derivative into more finely targeted purposes: a) DLL/DSO specific b) PROJECT specific c) EXE or process specific d) configuration file specific data e) configuration file manipulations functions. All of this functionality was blended into an extremely large wxApp derivative and that was incompatible with the desire to support multiple concurrently loaded DLL/DSO's ("KIFACE")s and multiple concurrently open projects. An amazing amount of organization come from simply sorting each bit of functionality into the proper box. * Switch to wxConfigBase from wxConfig everywhere except instantiation. * Add classes KIWAY, KIFACE, KIFACE_I, SEARCH_STACK, PGM_BASE, PGM_KICAD, PGM_SINGLE_TOP, * Remove "Return" prefix on many function names. * Remove obvious comments from CMakeLists.txt files, and from else() and endif()s. * Fix building boost for use in a DSO on linux. * Remove some of the assumptions in the CMakeLists.txt files that windows had to be the host platform when building windows binaries. * Reduce the number of wxStrings being constructed at program load time via static construction. * Pass wxConfigBase* to all SaveSettings() and LoadSettings() functions so that these functions are useful even when the wxConfigBase comes from another source, as is the case in the KICAD_MANAGER_FRAME. * Move the setting of the KIPRJMOD environment variable into class PROJECT, so that it can be moved into a project variable soon, and out of FP_LIB_TABLE. * Add the KIWAY_PLAYER which is associated with a particular PROJECT, and all its child wxFrames and wxDialogs now have a Kiway() member function which returns a KIWAY& that that window tree branch is in support of. This is like wxWindows DNA in that child windows get this member with proper value at time of construction. * Anticipate some of the needs for milestones B) and C) and make code adjustments now in an effort to reduce work in those milestones. * No testing has been done for python scripting, since milestone C) has that being largely reworked and re-thought-out.
12 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
10 months ago
10 months ago
10 months ago
5 years ago
10 months ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
11 years ago
10 months ago
5 years ago
5 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  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 The 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. #include <magic_enum.hpp>
  27. #include <unordered_set>
  28. #include <wx/log.h>
  29. #include <wx/debug.h>
  30. #include <bitmaps.h>
  31. #include <board.h>
  32. #include <board_design_settings.h>
  33. #include <confirm.h>
  34. #include <convert_basic_shapes_to_polygon.h>
  35. #include <convert_shape_list_to_polygon.h>
  36. #include <component_classes/component_class.h>
  37. #include <component_classes/component_class_cache_proxy.h>
  38. #include <drc/drc_item.h>
  39. #include <embedded_files.h>
  40. #include <font/font.h>
  41. #include <font/outline_font.h>
  42. #include <footprint.h>
  43. #include <geometry/convex_hull.h>
  44. #include <geometry/shape_segment.h>
  45. #include <geometry/shape_simple.h>
  46. #include <geometry/geometry_utils.h>
  47. #include <i18n_utility.h>
  48. #include <lset.h>
  49. #include <macros.h>
  50. #include <pad.h>
  51. #include <pcb_dimension.h>
  52. #include <pcb_edit_frame.h>
  53. #include <pcb_field.h>
  54. #include <pcb_group.h>
  55. #include <pcb_marker.h>
  56. #include <pcb_point.h>
  57. #include <pcb_reference_image.h>
  58. #include <pcb_textbox.h>
  59. #include <pcb_track.h>
  60. #include <refdes_utils.h>
  61. #include <string_utils.h>
  62. #include <view/view.h>
  63. #include <zone.h>
  64. #include <google/protobuf/any.pb.h>
  65. #include <api/board/board_types.pb.h>
  66. #include <api/api_enums.h>
  67. #include <api/api_utils.h>
  68. #include <api/api_pcb_utils.h>
  69. FOOTPRINT::FOOTPRINT( BOARD* parent ) :
  70. BOARD_ITEM_CONTAINER( (BOARD_ITEM*) parent, PCB_FOOTPRINT_T ),
  71. m_orient( ANGLE_0 ),
  72. m_attributes( 0 ),
  73. m_fpStatus( FP_PADS_are_LOCKED ),
  74. m_fileFormatVersionAtLoad( 0 ),
  75. m_boundingBoxCacheTimeStamp( 0 ),
  76. m_textExcludedBBoxCacheTimeStamp( 0 ),
  77. m_hullCacheTimeStamp( 0 ),
  78. m_duplicatePadNumbersAreJumpers( false ),
  79. m_allowMissingCourtyard( false ),
  80. m_allowSolderMaskBridges( false ),
  81. m_zoneConnection( ZONE_CONNECTION::INHERITED ),
  82. m_stackupLayers( LSET{ F_Cu, In1_Cu, B_Cu } ),
  83. m_stackupMode( FOOTPRINT_STACKUP::EXPAND_INNER_LAYERS ),
  84. m_lastEditTime( 0 ),
  85. m_arflag( 0 ),
  86. m_link( 0 ),
  87. m_initial_comments( nullptr ),
  88. m_componentClassCacheProxy( std::make_unique<COMPONENT_CLASS_CACHE_PROXY>( this ) )
  89. {
  90. m_layer = F_Cu;
  91. m_embedFonts = false;
  92. auto addField =
  93. [this]( FIELD_T id, PCB_LAYER_ID layer, bool visible )
  94. {
  95. PCB_FIELD* field = new PCB_FIELD( this, id );
  96. field->SetLayer( layer );
  97. field->SetVisible( visible );
  98. m_fields.push_back( field );
  99. };
  100. addField( FIELD_T::REFERENCE, F_SilkS, true );
  101. addField( FIELD_T::VALUE, F_Fab, true );
  102. addField( FIELD_T::DATASHEET, F_Fab, false );
  103. addField( FIELD_T::DESCRIPTION, F_Fab, false );
  104. m_3D_Drawings.clear();
  105. }
  106. FOOTPRINT::FOOTPRINT( const FOOTPRINT& aFootprint ) :
  107. BOARD_ITEM_CONTAINER( aFootprint ),
  108. EMBEDDED_FILES( aFootprint ),
  109. m_componentClassCacheProxy( std::make_unique<COMPONENT_CLASS_CACHE_PROXY>( this ) )
  110. {
  111. m_orient = aFootprint.m_orient;
  112. m_pos = aFootprint.m_pos;
  113. m_fpid = aFootprint.m_fpid;
  114. m_attributes = aFootprint.m_attributes;
  115. m_fpStatus = aFootprint.m_fpStatus;
  116. m_fileFormatVersionAtLoad = aFootprint.m_fileFormatVersionAtLoad;
  117. m_cachedBoundingBox = aFootprint.m_cachedBoundingBox;
  118. m_boundingBoxCacheTimeStamp = aFootprint.m_boundingBoxCacheTimeStamp;
  119. m_cachedTextExcludedBBox = aFootprint.m_cachedTextExcludedBBox;
  120. m_textExcludedBBoxCacheTimeStamp = aFootprint.m_textExcludedBBoxCacheTimeStamp;
  121. m_cachedHull = aFootprint.m_cachedHull;
  122. m_hullCacheTimeStamp = aFootprint.m_hullCacheTimeStamp;
  123. m_netTiePadGroups = aFootprint.m_netTiePadGroups;
  124. std::ranges::copy( aFootprint.m_jumperPadGroups,
  125. std::inserter( m_jumperPadGroups, m_jumperPadGroups.end() ) );
  126. m_duplicatePadNumbersAreJumpers = aFootprint.m_duplicatePadNumbersAreJumpers;
  127. m_allowMissingCourtyard = aFootprint.m_allowMissingCourtyard;
  128. m_allowSolderMaskBridges = aFootprint.m_allowSolderMaskBridges;
  129. m_zoneConnection = aFootprint.m_zoneConnection;
  130. m_clearance = aFootprint.m_clearance;
  131. m_solderMaskMargin = aFootprint.m_solderMaskMargin;
  132. m_solderPasteMargin = aFootprint.m_solderPasteMargin;
  133. m_solderPasteMarginRatio = aFootprint.m_solderPasteMarginRatio;
  134. m_stackupLayers = aFootprint.m_stackupLayers;
  135. m_stackupMode = aFootprint.m_stackupMode;
  136. m_libDescription = aFootprint.m_libDescription;
  137. m_keywords = aFootprint.m_keywords;
  138. m_path = aFootprint.m_path;
  139. m_sheetname = aFootprint.m_sheetname;
  140. m_sheetfile = aFootprint.m_sheetfile;
  141. m_filters = aFootprint.m_filters;
  142. m_lastEditTime = aFootprint.m_lastEditTime;
  143. m_arflag = 0;
  144. m_link = aFootprint.m_link;
  145. m_privateLayers = aFootprint.m_privateLayers;
  146. m_3D_Drawings = aFootprint.m_3D_Drawings;
  147. m_initial_comments = aFootprint.m_initial_comments ? new wxArrayString( *aFootprint.m_initial_comments )
  148. : nullptr;
  149. m_embedFonts = aFootprint.m_embedFonts;
  150. std::map<EDA_ITEM*, EDA_ITEM*> ptrMap;
  151. // Copy fields
  152. for( PCB_FIELD* field : aFootprint.m_fields )
  153. {
  154. if( field->IsMandatory() )
  155. {
  156. PCB_FIELD* existingField = GetField( field->GetId() );
  157. ptrMap[field] = existingField;
  158. *existingField = *field;
  159. existingField->SetParent( this );
  160. }
  161. else
  162. {
  163. PCB_FIELD* newField = static_cast<PCB_FIELD*>( field->Clone() );
  164. ptrMap[field] = newField;
  165. Add( newField );
  166. }
  167. }
  168. // Copy pads
  169. for( PAD* pad : aFootprint.Pads() )
  170. {
  171. PAD* newPad = static_cast<PAD*>( pad->Clone() );
  172. ptrMap[ pad ] = newPad;
  173. Add( newPad, ADD_MODE::APPEND ); // Append to ensure indexes are identical
  174. }
  175. // Copy zones
  176. for( ZONE* zone : aFootprint.Zones() )
  177. {
  178. ZONE* newZone = static_cast<ZONE*>( zone->Clone() );
  179. ptrMap[ zone ] = newZone;
  180. Add( newZone, ADD_MODE::APPEND ); // Append to ensure indexes are identical
  181. // Ensure the net info is OK and especially uses the net info list
  182. // living in the current board
  183. // Needed when copying a fp from fp editor that has its own board
  184. // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net.
  185. newZone->SetNetCode( -1 );
  186. }
  187. // Copy drawings
  188. for( BOARD_ITEM* item : aFootprint.GraphicalItems() )
  189. {
  190. BOARD_ITEM* newItem = static_cast<BOARD_ITEM*>( item->Clone() );
  191. ptrMap[ item ] = newItem;
  192. Add( newItem, ADD_MODE::APPEND ); // Append to ensure indexes are identical
  193. }
  194. // Copy groups
  195. for( PCB_GROUP* group : aFootprint.Groups() )
  196. {
  197. PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( group->Clone() );
  198. ptrMap[ group ] = newGroup;
  199. Add( newGroup, ADD_MODE::APPEND ); // Append to ensure indexes are identical
  200. }
  201. for( PCB_POINT* point : aFootprint.Points() )
  202. {
  203. PCB_POINT* newPoint = static_cast<PCB_POINT*>( point->Clone() );
  204. ptrMap[ point ] = newPoint;
  205. Add( newPoint, ADD_MODE::APPEND ); // Append to ensure indexes are identical
  206. }
  207. // Rebuild groups
  208. for( PCB_GROUP* group : aFootprint.Groups() )
  209. {
  210. PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( ptrMap[ group ] );
  211. newGroup->GetItems().clear();
  212. for( EDA_ITEM* member : group->GetItems() )
  213. {
  214. if( ptrMap.count( member ) )
  215. newGroup->AddItem( ptrMap[ member ] );
  216. }
  217. }
  218. for( auto& [ name, file ] : aFootprint.EmbeddedFileMap() )
  219. AddFile( new EMBEDDED_FILES::EMBEDDED_FILE( *file ) );
  220. }
  221. FOOTPRINT::FOOTPRINT( FOOTPRINT&& aFootprint ) :
  222. BOARD_ITEM_CONTAINER( aFootprint )
  223. {
  224. *this = std::move( aFootprint );
  225. }
  226. FOOTPRINT::~FOOTPRINT()
  227. {
  228. // Clean up the owned elements
  229. delete m_initial_comments;
  230. for( PCB_FIELD* f : m_fields )
  231. delete f;
  232. m_fields.clear();
  233. for( PAD* p : m_pads )
  234. delete p;
  235. m_pads.clear();
  236. for( ZONE* zone : m_zones )
  237. delete zone;
  238. m_zones.clear();
  239. for( PCB_GROUP* group : m_groups )
  240. delete group;
  241. m_groups.clear();
  242. for( PCB_POINT* point : m_points )
  243. delete point;
  244. m_points.clear();
  245. for( BOARD_ITEM* d : m_drawings )
  246. delete d;
  247. m_drawings.clear();
  248. if( BOARD* board = GetBoard() )
  249. board->IncrementTimeStamp();
  250. }
  251. void FOOTPRINT::Serialize( google::protobuf::Any &aContainer ) const
  252. {
  253. using namespace kiapi::board;
  254. types::FootprintInstance footprint;
  255. footprint.mutable_id()->set_value( m_Uuid.AsStdString() );
  256. footprint.mutable_position()->set_x_nm( GetPosition().x );
  257. footprint.mutable_position()->set_y_nm( GetPosition().y );
  258. footprint.mutable_orientation()->set_value_degrees( GetOrientationDegrees() );
  259. footprint.set_layer( ToProtoEnum<PCB_LAYER_ID, types::BoardLayer>( GetLayer() ) );
  260. footprint.set_locked( IsLocked() ? kiapi::common::types::LockedState::LS_LOCKED
  261. : kiapi::common::types::LockedState::LS_UNLOCKED );
  262. google::protobuf::Any buf;
  263. GetField( FIELD_T::REFERENCE )->Serialize( buf );
  264. buf.UnpackTo( footprint.mutable_reference_field() );
  265. GetField( FIELD_T::VALUE )->Serialize( buf );
  266. buf.UnpackTo( footprint.mutable_value_field() );
  267. GetField( FIELD_T::DATASHEET )->Serialize( buf );
  268. buf.UnpackTo( footprint.mutable_datasheet_field() );
  269. GetField( FIELD_T::DESCRIPTION )->Serialize( buf );
  270. buf.UnpackTo( footprint.mutable_description_field() );
  271. types::FootprintAttributes* attrs = footprint.mutable_attributes();
  272. attrs->set_not_in_schematic( IsBoardOnly() );
  273. attrs->set_exclude_from_position_files( IsExcludedFromPosFiles() );
  274. attrs->set_exclude_from_bill_of_materials( IsExcludedFromBOM() );
  275. attrs->set_exempt_from_courtyard_requirement( AllowMissingCourtyard() );
  276. attrs->set_do_not_populate( IsDNP() );
  277. if( m_attributes & FP_THROUGH_HOLE )
  278. attrs->set_mounting_style( types::FootprintMountingStyle::FMS_THROUGH_HOLE );
  279. else if( m_attributes & FP_SMD )
  280. attrs->set_mounting_style( types::FootprintMountingStyle::FMS_SMD );
  281. else
  282. attrs->set_mounting_style( types::FootprintMountingStyle::FMS_UNSPECIFIED );
  283. types::Footprint* def = footprint.mutable_definition();
  284. def->mutable_id()->CopyFrom( kiapi::common::LibIdToProto( GetFPID() ) );
  285. // anchor?
  286. def->mutable_attributes()->set_description( GetLibDescription().ToStdString() );
  287. def->mutable_attributes()->set_keywords( GetKeywords().ToStdString() );
  288. // TODO: serialize library mandatory fields
  289. types::FootprintDesignRuleOverrides* overrides = def->mutable_overrides();
  290. if( GetLocalClearance().has_value() )
  291. overrides->mutable_copper_clearance()->set_value_nm( *GetLocalClearance() );
  292. if( GetLocalSolderMaskMargin().has_value() )
  293. overrides->mutable_solder_mask()->mutable_solder_mask_margin()->set_value_nm( *GetLocalSolderMaskMargin() );
  294. if( GetLocalSolderPasteMargin().has_value() )
  295. overrides->mutable_solder_paste()->mutable_solder_paste_margin()->set_value_nm( *GetLocalSolderPasteMargin() );
  296. if( GetLocalSolderPasteMarginRatio().has_value() )
  297. overrides->mutable_solder_paste()->mutable_solder_paste_margin_ratio()->set_value( *GetLocalSolderPasteMarginRatio() );
  298. overrides->set_zone_connection(
  299. ToProtoEnum<ZONE_CONNECTION, types::ZoneConnectionStyle>( GetLocalZoneConnection() ) );
  300. for( const wxString& group : GetNetTiePadGroups() )
  301. {
  302. types::NetTieDefinition* netTie = def->add_net_ties();
  303. wxStringTokenizer tokenizer( group, " " );
  304. while( tokenizer.HasMoreTokens() )
  305. netTie->add_pad_number( tokenizer.GetNextToken().ToStdString() );
  306. }
  307. for( PCB_LAYER_ID layer : GetPrivateLayers().Seq() )
  308. def->add_private_layers( ToProtoEnum<PCB_LAYER_ID, types::BoardLayer>( layer ) );
  309. for( const PCB_FIELD* item : m_fields )
  310. {
  311. if( item->IsMandatory() )
  312. continue;
  313. google::protobuf::Any* itemMsg = def->add_items();
  314. item->Serialize( *itemMsg );
  315. }
  316. for( const PAD* item : Pads() )
  317. {
  318. google::protobuf::Any* itemMsg = def->add_items();
  319. item->Serialize( *itemMsg );
  320. }
  321. for( const BOARD_ITEM* item : GraphicalItems() )
  322. {
  323. google::protobuf::Any* itemMsg = def->add_items();
  324. item->Serialize( *itemMsg );
  325. }
  326. for( const ZONE* item : Zones() )
  327. {
  328. google::protobuf::Any* itemMsg = def->add_items();
  329. item->Serialize( *itemMsg );
  330. }
  331. for( const FP_3DMODEL& model : Models() )
  332. {
  333. google::protobuf::Any* itemMsg = def->add_items();
  334. types::Footprint3DModel modelMsg;
  335. modelMsg.set_filename( model.m_Filename.ToUTF8() );
  336. kiapi::common::PackVector3D( *modelMsg.mutable_scale(), model.m_Scale );
  337. kiapi::common::PackVector3D( *modelMsg.mutable_rotation(), model.m_Rotation );
  338. kiapi::common::PackVector3D( *modelMsg.mutable_offset(), model.m_Offset );
  339. modelMsg.set_visible( model.m_Show );
  340. modelMsg.set_opacity( model.m_Opacity );
  341. itemMsg->PackFrom( modelMsg );
  342. }
  343. // Serialized only (can't modify this from the API to change the symbol mapping)
  344. kiapi::common::PackSheetPath( *footprint.mutable_symbol_path(), m_path );
  345. aContainer.PackFrom( footprint );
  346. }
  347. bool FOOTPRINT::Deserialize( const google::protobuf::Any &aContainer )
  348. {
  349. using namespace kiapi::board;
  350. types::FootprintInstance footprint;
  351. if( !aContainer.UnpackTo( &footprint ) )
  352. return false;
  353. const_cast<KIID&>( m_Uuid ) = KIID( footprint.id().value() );
  354. SetPosition( VECTOR2I( footprint.position().x_nm(), footprint.position().y_nm() ) );
  355. SetOrientationDegrees( footprint.orientation().value_degrees() );
  356. SetLayer( FromProtoEnum<PCB_LAYER_ID, types::BoardLayer>( footprint.layer() ) );
  357. SetLocked( footprint.locked() == kiapi::common::types::LockedState::LS_LOCKED );
  358. google::protobuf::Any buf;
  359. types::Field mandatoryField;
  360. if( footprint.has_reference_field() )
  361. {
  362. mandatoryField = footprint.reference_field();
  363. mandatoryField.mutable_id()->set_id( (int) FIELD_T::REFERENCE );
  364. buf.PackFrom( mandatoryField );
  365. GetField( FIELD_T::REFERENCE )->Deserialize( buf );
  366. }
  367. if( footprint.has_value_field() )
  368. {
  369. mandatoryField = footprint.value_field();
  370. mandatoryField.mutable_id()->set_id( (int) FIELD_T::VALUE );
  371. buf.PackFrom( mandatoryField );
  372. GetField( FIELD_T::VALUE )->Deserialize( buf );
  373. }
  374. if( footprint.has_datasheet_field() )
  375. {
  376. mandatoryField = footprint.datasheet_field();
  377. mandatoryField.mutable_id()->set_id( (int) FIELD_T::DATASHEET );
  378. buf.PackFrom( mandatoryField );
  379. GetField( FIELD_T::DATASHEET )->Deserialize( buf );
  380. }
  381. if( footprint.has_description_field() )
  382. {
  383. mandatoryField = footprint.description_field();
  384. mandatoryField.mutable_id()->set_id( (int) FIELD_T::DESCRIPTION );
  385. buf.PackFrom( mandatoryField );
  386. GetField( FIELD_T::DESCRIPTION )->Deserialize( buf );
  387. }
  388. m_attributes = 0;
  389. switch( footprint.attributes().mounting_style() )
  390. {
  391. case types::FootprintMountingStyle::FMS_THROUGH_HOLE:
  392. m_attributes |= FP_THROUGH_HOLE;
  393. break;
  394. case types::FootprintMountingStyle::FMS_SMD:
  395. m_attributes |= FP_SMD;
  396. break;
  397. default:
  398. break;
  399. }
  400. SetBoardOnly( footprint.attributes().not_in_schematic() );
  401. SetExcludedFromBOM( footprint.attributes().exclude_from_bill_of_materials() );
  402. SetExcludedFromPosFiles( footprint.attributes().exclude_from_position_files() );
  403. SetAllowMissingCourtyard( footprint.attributes().exempt_from_courtyard_requirement() );
  404. SetDNP( footprint.attributes().do_not_populate() );
  405. // Definition
  406. SetFPID( kiapi::common::LibIdFromProto( footprint.definition().id() ) );
  407. // TODO: how should anchor be handled?
  408. SetLibDescription( footprint.definition().attributes().description() );
  409. SetKeywords( footprint.definition().attributes().keywords() );
  410. const types::FootprintDesignRuleOverrides& overrides = footprint.overrides();
  411. if( overrides.has_copper_clearance() )
  412. SetLocalClearance( overrides.copper_clearance().value_nm() );
  413. else
  414. SetLocalClearance( std::nullopt );
  415. if( overrides.has_solder_mask() && overrides.solder_mask().has_solder_mask_margin() )
  416. SetLocalSolderMaskMargin( overrides.solder_mask().solder_mask_margin().value_nm() );
  417. else
  418. SetLocalSolderMaskMargin( std::nullopt );
  419. if( overrides.has_solder_paste() )
  420. {
  421. const types::SolderPasteOverrides& pasteSettings = overrides.solder_paste();
  422. if( pasteSettings.has_solder_paste_margin() )
  423. SetLocalSolderPasteMargin( pasteSettings.solder_paste_margin().value_nm() );
  424. else
  425. SetLocalSolderPasteMargin( std::nullopt );
  426. if( pasteSettings.has_solder_paste_margin_ratio() )
  427. SetLocalSolderPasteMarginRatio( pasteSettings.solder_paste_margin_ratio().value() );
  428. else
  429. SetLocalSolderPasteMarginRatio( std::nullopt );
  430. }
  431. SetLocalZoneConnection( FromProtoEnum<ZONE_CONNECTION>( overrides.zone_connection() ) );
  432. for( const types::NetTieDefinition& netTieMsg : footprint.definition().net_ties() )
  433. {
  434. wxString group;
  435. for( const std::string& pad : netTieMsg.pad_number() )
  436. group.Append( wxString::Format( wxT( "%s " ), pad ) );
  437. group.Trim();
  438. AddNetTiePadGroup( group );
  439. }
  440. LSET privateLayers;
  441. for( int layerMsg : footprint.definition().private_layers() )
  442. {
  443. auto layer = FromProtoEnum<PCB_LAYER_ID, types::BoardLayer>( static_cast<types::BoardLayer>( layerMsg ) );
  444. if( layer > UNDEFINED_LAYER )
  445. privateLayers.set( layer );
  446. }
  447. SetPrivateLayers( privateLayers );
  448. // Footprint items
  449. for( PCB_FIELD* field : m_fields )
  450. {
  451. if( !field->IsMandatory() )
  452. Remove( field );
  453. }
  454. Pads().clear();
  455. GraphicalItems().clear();
  456. Zones().clear();
  457. Groups().clear();
  458. Models().clear();
  459. Points().clear();
  460. for( const google::protobuf::Any& itemMsg : footprint.definition().items() )
  461. {
  462. std::optional<KICAD_T> type = kiapi::common::TypeNameFromAny( itemMsg );
  463. if( !type )
  464. {
  465. // Bit of a hack here, but eventually 3D models should be promoted to a first-class
  466. // object, at which point they can get their own serialization
  467. if( itemMsg.type_url() == "type.googleapis.com/kiapi.board.types.Footprint3DModel" )
  468. {
  469. types::Footprint3DModel modelMsg;
  470. if( !itemMsg.UnpackTo( &modelMsg ) )
  471. continue;
  472. FP_3DMODEL model;
  473. model.m_Filename = wxString::FromUTF8( modelMsg.filename() );
  474. model.m_Show = modelMsg.visible();
  475. model.m_Opacity = modelMsg.opacity();
  476. model.m_Scale = kiapi::common::UnpackVector3D( modelMsg.scale() );
  477. model.m_Rotation = kiapi::common::UnpackVector3D( modelMsg.rotation() );
  478. model.m_Offset = kiapi::common::UnpackVector3D( modelMsg.offset() );
  479. Models().push_back( std::move( model ) );
  480. }
  481. else
  482. {
  483. wxLogTrace( traceApi, wxString::Format( wxS( "Attempting to unpack unknown type %s "
  484. "from footprint message, skipping" ),
  485. itemMsg.type_url() ) );
  486. }
  487. continue;
  488. }
  489. std::unique_ptr<BOARD_ITEM> item = CreateItemForType( *type, this );
  490. if( item && item->Deserialize( itemMsg ) )
  491. Add( item.release(), ADD_MODE::APPEND );
  492. }
  493. return true;
  494. }
  495. PCB_FIELD* FOOTPRINT::GetField( FIELD_T aFieldType )
  496. {
  497. for( PCB_FIELD* field : m_fields )
  498. {
  499. if( field->GetId() == aFieldType )
  500. return field;
  501. }
  502. PCB_FIELD* field = new PCB_FIELD( this, aFieldType );
  503. m_fields.push_back( field );
  504. return field;
  505. }
  506. const PCB_FIELD* FOOTPRINT::GetField( FIELD_T aFieldType ) const
  507. {
  508. for( const PCB_FIELD* field : m_fields )
  509. {
  510. if( field->GetId() == aFieldType )
  511. return field;
  512. }
  513. return nullptr;
  514. }
  515. bool FOOTPRINT::HasField( const wxString& aFieldName ) const
  516. {
  517. return GetField( aFieldName ) != nullptr;
  518. }
  519. PCB_FIELD* FOOTPRINT::GetField( const wxString& aFieldName ) const
  520. {
  521. for( PCB_FIELD* field : m_fields )
  522. {
  523. if( field->GetName() == aFieldName )
  524. return field;
  525. }
  526. return nullptr;
  527. }
  528. void FOOTPRINT::GetFields( std::vector<PCB_FIELD*>& aVector, bool aVisibleOnly ) const
  529. {
  530. aVector.clear();
  531. for( PCB_FIELD* field : m_fields )
  532. {
  533. if( aVisibleOnly )
  534. {
  535. if( !field->IsVisible() || field->GetText().IsEmpty() )
  536. continue;
  537. }
  538. aVector.push_back( field );
  539. }
  540. std::sort( aVector.begin(), aVector.end(),
  541. []( PCB_FIELD* lhs, PCB_FIELD* rhs )
  542. {
  543. return lhs->GetOrdinal() < rhs->GetOrdinal();
  544. } );
  545. }
  546. int FOOTPRINT::GetNextFieldOrdinal() const
  547. {
  548. int ordinal = 42; // Arbitrarily larger than any mandatory FIELD_T id
  549. for( const PCB_FIELD* field : m_fields )
  550. ordinal = std::max( ordinal, field->GetOrdinal() + 1 );
  551. return ordinal;
  552. }
  553. void FOOTPRINT::ApplyDefaultSettings( const BOARD& board, bool aStyleFields, bool aStyleText,
  554. bool aStyleShapes )
  555. {
  556. if( aStyleFields )
  557. {
  558. for( PCB_FIELD* field : m_fields )
  559. field->StyleFromSettings( board.GetDesignSettings() );
  560. }
  561. for( BOARD_ITEM* item : m_drawings )
  562. {
  563. switch( item->Type() )
  564. {
  565. case PCB_TEXT_T:
  566. case PCB_TEXTBOX_T:
  567. if( aStyleText )
  568. item->StyleFromSettings( board.GetDesignSettings() );
  569. break;
  570. case PCB_SHAPE_T:
  571. if( aStyleShapes && !item->IsOnCopperLayer() )
  572. item->StyleFromSettings( board.GetDesignSettings() );
  573. break;
  574. default:
  575. break;
  576. }
  577. }
  578. }
  579. bool FOOTPRINT::FixUuids()
  580. {
  581. // replace null UUIDs if any by a valid uuid
  582. std::vector< BOARD_ITEM* > item_list;
  583. for( PCB_FIELD* field : m_fields )
  584. item_list.push_back( field );
  585. for( PAD* pad : m_pads )
  586. item_list.push_back( pad );
  587. for( BOARD_ITEM* gr_item : m_drawings )
  588. item_list.push_back( gr_item );
  589. // Note: one cannot fix null UUIDs inside the group, but it should not happen
  590. // because null uuids can be found in old footprints, therefore without group
  591. for( PCB_GROUP* group : m_groups )
  592. item_list.push_back( group );
  593. // Probably not needed, because old fp do not have zones. But just in case.
  594. for( ZONE* zone : m_zones )
  595. item_list.push_back( zone );
  596. // Ditto
  597. for( PCB_POINT* point : m_points )
  598. item_list.push_back( point );
  599. bool changed = false;
  600. for( BOARD_ITEM* item : item_list )
  601. {
  602. if( item->m_Uuid == niluuid )
  603. {
  604. const_cast<KIID&>( item->m_Uuid ) = KIID();
  605. changed = true;
  606. }
  607. }
  608. return changed;
  609. }
  610. FOOTPRINT& FOOTPRINT::operator=( FOOTPRINT&& aOther )
  611. {
  612. BOARD_ITEM::operator=( aOther );
  613. m_pos = aOther.m_pos;
  614. m_fpid = aOther.m_fpid;
  615. m_attributes = aOther.m_attributes;
  616. m_fpStatus = aOther.m_fpStatus;
  617. m_orient = aOther.m_orient;
  618. m_lastEditTime = aOther.m_lastEditTime;
  619. m_link = aOther.m_link;
  620. m_path = aOther.m_path;
  621. m_cachedBoundingBox = aOther.m_cachedBoundingBox;
  622. m_boundingBoxCacheTimeStamp = aOther.m_boundingBoxCacheTimeStamp;
  623. m_cachedTextExcludedBBox = aOther.m_cachedTextExcludedBBox;
  624. m_textExcludedBBoxCacheTimeStamp = aOther.m_textExcludedBBoxCacheTimeStamp;
  625. m_cachedHull = aOther.m_cachedHull;
  626. m_hullCacheTimeStamp = aOther.m_hullCacheTimeStamp;
  627. m_clearance = aOther.m_clearance;
  628. m_solderMaskMargin = aOther.m_solderMaskMargin;
  629. m_solderPasteMargin = aOther.m_solderPasteMargin;
  630. m_solderPasteMarginRatio = aOther.m_solderPasteMarginRatio;
  631. m_zoneConnection = aOther.m_zoneConnection;
  632. m_netTiePadGroups = aOther.m_netTiePadGroups;
  633. m_duplicatePadNumbersAreJumpers = aOther.m_duplicatePadNumbersAreJumpers;
  634. std::ranges::copy( aOther.m_jumperPadGroups,
  635. std::inserter( m_jumperPadGroups, m_jumperPadGroups.end() ) );
  636. // Move the fields
  637. for( PCB_FIELD* field : m_fields )
  638. delete field;
  639. m_fields.clear();
  640. for( PCB_FIELD* field : aOther.m_fields )
  641. Add( field );
  642. aOther.m_fields.clear();
  643. // Move the pads
  644. for( PAD* pad : m_pads )
  645. delete pad;
  646. m_pads.clear();
  647. for( PAD* pad : aOther.Pads() )
  648. Add( pad );
  649. aOther.Pads().clear();
  650. // Move the zones
  651. for( ZONE* zone : m_zones )
  652. delete zone;
  653. m_zones.clear();
  654. for( ZONE* item : aOther.Zones() )
  655. {
  656. Add( item );
  657. // Ensure the net info is OK and especially uses the net info list
  658. // living in the current board
  659. // Needed when copying a fp from fp editor that has its own board
  660. // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net.
  661. item->SetNetCode( -1 );
  662. }
  663. aOther.Zones().clear();
  664. // Move the drawings
  665. for( BOARD_ITEM* item : m_drawings )
  666. delete item;
  667. m_drawings.clear();
  668. for( BOARD_ITEM* item : aOther.GraphicalItems() )
  669. Add( item );
  670. aOther.GraphicalItems().clear();
  671. // Move the groups
  672. for( PCB_GROUP* group : m_groups )
  673. delete group;
  674. m_groups.clear();
  675. for( PCB_GROUP* group : aOther.Groups() )
  676. Add( group );
  677. aOther.Groups().clear();
  678. // Move the points
  679. for( PCB_POINT* point : m_points )
  680. delete point;
  681. m_groups.clear();
  682. for( PCB_POINT* point : aOther.Points() )
  683. Add( point );
  684. aOther.Points().clear();
  685. EMBEDDED_FILES::operator=( std::move( aOther ) );
  686. // Copy auxiliary data
  687. m_3D_Drawings = aOther.m_3D_Drawings;
  688. m_libDescription = aOther.m_libDescription;
  689. m_keywords = aOther.m_keywords;
  690. m_privateLayers = aOther.m_privateLayers;
  691. m_initial_comments = aOther.m_initial_comments;
  692. // Clear the other item's containers since this is a move
  693. aOther.m_fields.clear();
  694. aOther.Pads().clear();
  695. aOther.Zones().clear();
  696. aOther.GraphicalItems().clear();
  697. aOther.m_initial_comments = nullptr;
  698. return *this;
  699. }
  700. FOOTPRINT& FOOTPRINT::operator=( const FOOTPRINT& aOther )
  701. {
  702. BOARD_ITEM::operator=( aOther );
  703. m_pos = aOther.m_pos;
  704. m_fpid = aOther.m_fpid;
  705. m_attributes = aOther.m_attributes;
  706. m_fpStatus = aOther.m_fpStatus;
  707. m_orient = aOther.m_orient;
  708. m_lastEditTime = aOther.m_lastEditTime;
  709. m_link = aOther.m_link;
  710. m_path = aOther.m_path;
  711. m_cachedBoundingBox = aOther.m_cachedBoundingBox;
  712. m_boundingBoxCacheTimeStamp = aOther.m_boundingBoxCacheTimeStamp;
  713. m_cachedTextExcludedBBox = aOther.m_cachedTextExcludedBBox;
  714. m_textExcludedBBoxCacheTimeStamp = aOther.m_textExcludedBBoxCacheTimeStamp;
  715. m_cachedHull = aOther.m_cachedHull;
  716. m_hullCacheTimeStamp = aOther.m_hullCacheTimeStamp;
  717. m_clearance = aOther.m_clearance;
  718. m_solderMaskMargin = aOther.m_solderMaskMargin;
  719. m_solderPasteMargin = aOther.m_solderPasteMargin;
  720. m_solderPasteMarginRatio = aOther.m_solderPasteMarginRatio;
  721. m_zoneConnection = aOther.m_zoneConnection;
  722. m_netTiePadGroups = aOther.m_netTiePadGroups;
  723. std::map<EDA_ITEM*, EDA_ITEM*> ptrMap;
  724. // Copy fields
  725. m_fields.clear();
  726. for( PCB_FIELD* field : aOther.m_fields )
  727. {
  728. PCB_FIELD* newField = new PCB_FIELD( *field );
  729. ptrMap[field] = newField;
  730. Add( newField );
  731. }
  732. // Copy pads
  733. m_pads.clear();
  734. for( PAD* pad : aOther.Pads() )
  735. {
  736. PAD* newPad = new PAD( *pad );
  737. ptrMap[ pad ] = newPad;
  738. Add( newPad );
  739. }
  740. // Copy zones
  741. m_zones.clear();
  742. for( ZONE* zone : aOther.Zones() )
  743. {
  744. ZONE* newZone = static_cast<ZONE*>( zone->Clone() );
  745. ptrMap[ zone ] = newZone;
  746. Add( newZone );
  747. // Ensure the net info is OK and especially uses the net info list
  748. // living in the current board
  749. // Needed when copying a fp from fp editor that has its own board
  750. // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net.
  751. newZone->SetNetCode( -1 );
  752. }
  753. // Copy drawings
  754. m_drawings.clear();
  755. for( BOARD_ITEM* item : aOther.GraphicalItems() )
  756. {
  757. BOARD_ITEM* newItem = static_cast<BOARD_ITEM*>( item->Clone() );
  758. ptrMap[ item ] = newItem;
  759. Add( newItem );
  760. }
  761. // Copy groups
  762. m_groups.clear();
  763. for( PCB_GROUP* group : aOther.Groups() )
  764. {
  765. PCB_GROUP* newGroup = static_cast<PCB_GROUP*>( group->Clone() );
  766. newGroup->GetItems().clear();
  767. for( EDA_ITEM* member : group->GetItems() )
  768. newGroup->AddItem( ptrMap[ member ] );
  769. Add( newGroup );
  770. }
  771. // Copy drawings
  772. m_points.clear();
  773. for( PCB_POINT* point : aOther.Points() )
  774. {
  775. BOARD_ITEM* newItem = static_cast<BOARD_ITEM*>( point->Clone() );
  776. ptrMap[ point ] = newItem;
  777. Add( newItem );
  778. }
  779. // Copy auxiliary data
  780. m_3D_Drawings = aOther.m_3D_Drawings;
  781. m_libDescription = aOther.m_libDescription;
  782. m_keywords = aOther.m_keywords;
  783. m_privateLayers = aOther.m_privateLayers;
  784. m_initial_comments = aOther.m_initial_comments ?
  785. new wxArrayString( *aOther.m_initial_comments ) : nullptr;
  786. EMBEDDED_FILES::operator=( aOther );
  787. return *this;
  788. }
  789. void FOOTPRINT::CopyFrom( const BOARD_ITEM* aOther )
  790. {
  791. wxCHECK( aOther && aOther->Type() == PCB_FOOTPRINT_T, /* void */ );
  792. *this = *static_cast<const FOOTPRINT*>( aOther );
  793. for( PAD* pad : m_pads )
  794. pad->SetDirty();
  795. }
  796. void FOOTPRINT::InvalidateGeometryCaches()
  797. {
  798. m_boundingBoxCacheTimeStamp = 0;
  799. m_textExcludedBBoxCacheTimeStamp = 0;
  800. m_hullCacheTimeStamp = 0;
  801. m_courtyard_cache_back_hash.Clear();
  802. m_courtyard_cache_front_hash.Clear();
  803. }
  804. bool FOOTPRINT::IsConflicting() const
  805. {
  806. return HasFlag( COURTYARD_CONFLICT );
  807. }
  808. void FOOTPRINT::GetContextualTextVars( wxArrayString* aVars ) const
  809. {
  810. aVars->push_back( wxT( "REFERENCE" ) );
  811. aVars->push_back( wxT( "VALUE" ) );
  812. aVars->push_back( wxT( "LAYER" ) );
  813. aVars->push_back( wxT( "FOOTPRINT_LIBRARY" ) );
  814. aVars->push_back( wxT( "FOOTPRINT_NAME" ) );
  815. aVars->push_back( wxT( "SHORT_NET_NAME(<pad_number>)" ) );
  816. aVars->push_back( wxT( "NET_NAME(<pad_number>)" ) );
  817. aVars->push_back( wxT( "NET_CLASS(<pad_number>)" ) );
  818. aVars->push_back( wxT( "PIN_NAME(<pad_number>)" ) );
  819. }
  820. bool FOOTPRINT::ResolveTextVar( wxString* token, int aDepth ) const
  821. {
  822. if( GetBoard() && GetBoard()->GetBoardUse() == BOARD_USE::FPHOLDER )
  823. return false;
  824. if( token->IsSameAs( wxT( "REFERENCE" ) ) )
  825. {
  826. *token = Reference().GetShownText( false, aDepth + 1 );
  827. return true;
  828. }
  829. else if( token->IsSameAs( wxT( "VALUE" ) ) )
  830. {
  831. *token = Value().GetShownText( false, aDepth + 1 );
  832. return true;
  833. }
  834. else if( token->IsSameAs( wxT( "LAYER" ) ) )
  835. {
  836. *token = GetLayerName();
  837. return true;
  838. }
  839. else if( token->IsSameAs( wxT( "FOOTPRINT_LIBRARY" ) ) )
  840. {
  841. *token = m_fpid.GetUniStringLibNickname();
  842. return true;
  843. }
  844. else if( token->IsSameAs( wxT( "FOOTPRINT_NAME" ) ) )
  845. {
  846. *token = m_fpid.GetUniStringLibItemName();
  847. return true;
  848. }
  849. else if( token->StartsWith( wxT( "SHORT_NET_NAME(" ) )
  850. || token->StartsWith( wxT( "NET_NAME(" ) )
  851. || token->StartsWith( wxT( "NET_CLASS(" ) )
  852. || token->StartsWith( wxT( "PIN_NAME(" ) ) )
  853. {
  854. wxString padNumber = token->AfterFirst( '(' );
  855. padNumber = padNumber.BeforeLast( ')' );
  856. for( PAD* pad : Pads() )
  857. {
  858. if( pad->GetNumber() == padNumber )
  859. {
  860. if( token->StartsWith( wxT( "SHORT_NET_NAME" ) ) )
  861. *token = pad->GetShortNetname();
  862. else if( token->StartsWith( wxT( "NET_NAME" ) ) )
  863. *token = pad->GetNetname();
  864. else if( token->StartsWith( wxT( "NET_CLASS" ) ) )
  865. *token = pad->GetNetClassName();
  866. else
  867. *token = pad->GetPinFunction();
  868. return true;
  869. }
  870. }
  871. }
  872. else if( PCB_FIELD* field = GetField( *token ) )
  873. {
  874. *token = field->GetText();
  875. return true;
  876. }
  877. if( GetBoard() && GetBoard()->ResolveTextVar( token, aDepth + 1 ) )
  878. return true;
  879. return false;
  880. }
  881. void FOOTPRINT::ClearAllNets()
  882. {
  883. // Force the ORPHANED dummy net info for all pads.
  884. // ORPHANED dummy net does not depend on a board
  885. for( PAD* pad : m_pads )
  886. pad->SetNetCode( NETINFO_LIST::ORPHANED );
  887. }
  888. void FOOTPRINT::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode, bool aSkipConnectivity )
  889. {
  890. switch( aBoardItem->Type() )
  891. {
  892. case PCB_FIELD_T:
  893. m_fields.push_back( static_cast<PCB_FIELD*>( aBoardItem ) );
  894. break;
  895. case PCB_TEXT_T:
  896. case PCB_DIM_ALIGNED_T:
  897. case PCB_DIM_LEADER_T:
  898. case PCB_DIM_CENTER_T:
  899. case PCB_DIM_RADIAL_T:
  900. case PCB_DIM_ORTHOGONAL_T:
  901. case PCB_SHAPE_T:
  902. case PCB_TEXTBOX_T:
  903. case PCB_TABLE_T:
  904. case PCB_REFERENCE_IMAGE_T:
  905. if( aMode == ADD_MODE::APPEND )
  906. m_drawings.push_back( aBoardItem );
  907. else
  908. m_drawings.push_front( aBoardItem );
  909. break;
  910. case PCB_PAD_T:
  911. if( aMode == ADD_MODE::APPEND )
  912. m_pads.push_back( static_cast<PAD*>( aBoardItem ) );
  913. else
  914. m_pads.push_front( static_cast<PAD*>( aBoardItem ) );
  915. break;
  916. case PCB_ZONE_T:
  917. if( aMode == ADD_MODE::APPEND )
  918. m_zones.push_back( static_cast<ZONE*>( aBoardItem ) );
  919. else
  920. m_zones.insert( m_zones.begin(), static_cast<ZONE*>( aBoardItem ) );
  921. break;
  922. case PCB_GROUP_T:
  923. if( aMode == ADD_MODE::APPEND )
  924. m_groups.push_back( static_cast<PCB_GROUP*>( aBoardItem ) );
  925. else
  926. m_groups.insert( m_groups.begin(), static_cast<PCB_GROUP*>( aBoardItem ) );
  927. break;
  928. case PCB_MARKER_T:
  929. wxFAIL_MSG( wxT( "FOOTPRINT::Add(): Markers go at the board level, even in the footprint editor" ) );
  930. return;
  931. case PCB_FOOTPRINT_T:
  932. wxFAIL_MSG( wxT( "FOOTPRINT::Add(): Nested footprints not supported" ) );
  933. return;
  934. case PCB_POINT_T:
  935. if( aMode == ADD_MODE::APPEND )
  936. m_points.push_back( static_cast<PCB_POINT*>( aBoardItem ) );
  937. else
  938. m_points.insert( m_points.begin(), static_cast<PCB_POINT*>( aBoardItem ) );
  939. break;
  940. default:
  941. wxFAIL_MSG( wxString::Format( wxT( "FOOTPRINT::Add(): BOARD_ITEM type (%d) not handled" ),
  942. aBoardItem->Type() ) );
  943. return;
  944. }
  945. aBoardItem->ClearEditFlags();
  946. aBoardItem->SetParent( this );
  947. }
  948. void FOOTPRINT::Remove( BOARD_ITEM* aBoardItem, REMOVE_MODE aMode )
  949. {
  950. switch( aBoardItem->Type() )
  951. {
  952. case PCB_FIELD_T:
  953. for( auto it = m_fields.begin(); it != m_fields.end(); ++it )
  954. {
  955. if( *it == aBoardItem )
  956. {
  957. m_fields.erase( it );
  958. break;
  959. }
  960. }
  961. break;
  962. case PCB_TEXT_T:
  963. case PCB_DIM_ALIGNED_T:
  964. case PCB_DIM_CENTER_T:
  965. case PCB_DIM_ORTHOGONAL_T:
  966. case PCB_DIM_RADIAL_T:
  967. case PCB_DIM_LEADER_T:
  968. case PCB_SHAPE_T:
  969. case PCB_TEXTBOX_T:
  970. case PCB_TABLE_T:
  971. case PCB_REFERENCE_IMAGE_T:
  972. for( auto it = m_drawings.begin(); it != m_drawings.end(); ++it )
  973. {
  974. if( *it == aBoardItem )
  975. {
  976. m_drawings.erase( it );
  977. break;
  978. }
  979. }
  980. break;
  981. case PCB_PAD_T:
  982. for( auto it = m_pads.begin(); it != m_pads.end(); ++it )
  983. {
  984. if( *it == static_cast<PAD*>( aBoardItem ) )
  985. {
  986. m_pads.erase( it );
  987. break;
  988. }
  989. }
  990. break;
  991. case PCB_ZONE_T:
  992. for( auto it = m_zones.begin(); it != m_zones.end(); ++it )
  993. {
  994. if( *it == static_cast<ZONE*>( aBoardItem ) )
  995. {
  996. m_zones.erase( it );
  997. break;
  998. }
  999. }
  1000. break;
  1001. case PCB_GROUP_T:
  1002. for( auto it = m_groups.begin(); it != m_groups.end(); ++it )
  1003. {
  1004. if( *it == static_cast<PCB_GROUP*>( aBoardItem ) )
  1005. {
  1006. m_groups.erase( it );
  1007. break;
  1008. }
  1009. }
  1010. break;
  1011. case PCB_POINT_T:
  1012. for( auto it = m_points.begin(); it != m_points.end(); ++it )
  1013. {
  1014. if( *it == static_cast<PCB_POINT*>( aBoardItem ) )
  1015. {
  1016. m_points.erase( it );
  1017. break;
  1018. }
  1019. }
  1020. break;
  1021. default:
  1022. {
  1023. wxString msg;
  1024. msg.Printf( wxT( "FOOTPRINT::Remove() needs work: BOARD_ITEM type (%d) not handled" ),
  1025. aBoardItem->Type() );
  1026. wxFAIL_MSG( msg );
  1027. }
  1028. }
  1029. aBoardItem->SetFlags( STRUCT_DELETED );
  1030. }
  1031. double FOOTPRINT::GetArea( int aPadding ) const
  1032. {
  1033. BOX2I bbox = GetBoundingBox( false );
  1034. double w = std::abs( static_cast<double>( bbox.GetWidth() ) ) + aPadding;
  1035. double h = std::abs( static_cast<double>( bbox.GetHeight() ) ) + aPadding;
  1036. return w * h;
  1037. }
  1038. int FOOTPRINT::GetLikelyAttribute() const
  1039. {
  1040. int smd_count = 0;
  1041. int tht_count = 0;
  1042. for( PAD* pad : m_pads )
  1043. {
  1044. switch( pad->GetProperty() )
  1045. {
  1046. case PAD_PROP::FIDUCIAL_GLBL:
  1047. case PAD_PROP::FIDUCIAL_LOCAL:
  1048. continue;
  1049. case PAD_PROP::HEATSINK:
  1050. case PAD_PROP::CASTELLATED:
  1051. case PAD_PROP::MECHANICAL:
  1052. continue;
  1053. case PAD_PROP::NONE:
  1054. case PAD_PROP::BGA:
  1055. case PAD_PROP::TESTPOINT:
  1056. case PAD_PROP::PRESSFIT:
  1057. break;
  1058. }
  1059. switch( pad->GetAttribute() )
  1060. {
  1061. case PAD_ATTRIB::PTH:
  1062. tht_count++;
  1063. break;
  1064. case PAD_ATTRIB::SMD:
  1065. if( pad->IsOnCopperLayer() )
  1066. smd_count++;
  1067. break;
  1068. default:
  1069. break;
  1070. }
  1071. }
  1072. // Footprints with plated through-hole pads should usually be marked through hole even if they
  1073. // also have SMD because they might not be auto-placed. Exceptions to this might be shielded
  1074. if( tht_count > 0 )
  1075. return FP_THROUGH_HOLE;
  1076. if( smd_count > 0 )
  1077. return FP_SMD;
  1078. return 0;
  1079. }
  1080. wxString FOOTPRINT::GetTypeName() const
  1081. {
  1082. if( ( m_attributes & FP_SMD ) == FP_SMD )
  1083. return _( "SMD" );
  1084. if( ( m_attributes & FP_THROUGH_HOLE ) == FP_THROUGH_HOLE )
  1085. return _( "Through hole" );
  1086. return _( "Other" );
  1087. }
  1088. BOX2I FOOTPRINT::GetFpPadsLocalBbox() const
  1089. {
  1090. BOX2I bbox;
  1091. // We want the bounding box of the footprint pads at rot 0, not flipped
  1092. // Create such a image:
  1093. FOOTPRINT dummy( *this );
  1094. dummy.SetPosition( VECTOR2I( 0, 0 ) );
  1095. dummy.SetOrientation( ANGLE_0 );
  1096. if( dummy.IsFlipped() )
  1097. dummy.Flip( VECTOR2I( 0, 0 ), FLIP_DIRECTION::TOP_BOTTOM );
  1098. for( PAD* pad : dummy.Pads() )
  1099. bbox.Merge( pad->GetBoundingBox() );
  1100. return bbox;
  1101. }
  1102. bool FOOTPRINT::TextOnly() const
  1103. {
  1104. for( BOARD_ITEM* item : m_drawings )
  1105. {
  1106. if( m_privateLayers.test( item->GetLayer() ) )
  1107. continue;
  1108. if( item->Type() != PCB_FIELD_T && item->Type() != PCB_TEXT_T )
  1109. return false;
  1110. }
  1111. return true;
  1112. }
  1113. const BOX2I FOOTPRINT::GetBoundingBox() const
  1114. {
  1115. return GetBoundingBox( true );
  1116. }
  1117. const BOX2I FOOTPRINT::GetBoundingBox( bool aIncludeText ) const
  1118. {
  1119. const BOARD* board = GetBoard();
  1120. if( board )
  1121. {
  1122. if( aIncludeText )
  1123. {
  1124. if( m_boundingBoxCacheTimeStamp >= board->GetTimeStamp() )
  1125. return m_cachedBoundingBox;
  1126. }
  1127. else
  1128. {
  1129. if( m_textExcludedBBoxCacheTimeStamp >= board->GetTimeStamp() )
  1130. return m_cachedTextExcludedBBox;
  1131. }
  1132. }
  1133. std::vector<PCB_TEXT*> texts;
  1134. bool isFPEdit = board && board->IsFootprintHolder();
  1135. BOX2I bbox( m_pos );
  1136. bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) ); // Give a min size to the bbox
  1137. // Calculate the footprint side
  1138. PCB_LAYER_ID footprintSide = GetSide();
  1139. for( BOARD_ITEM* item : m_drawings )
  1140. {
  1141. if( m_privateLayers.test( item->GetLayer() ) && !isFPEdit )
  1142. continue;
  1143. // We want the bitmap bounding box just in the footprint editor
  1144. // so it will start with the correct initial zoom
  1145. if( item->Type() == PCB_REFERENCE_IMAGE_T && !isFPEdit )
  1146. continue;
  1147. // Handle text separately
  1148. if( item->Type() == PCB_TEXT_T )
  1149. {
  1150. texts.push_back( static_cast<PCB_TEXT*>( item ) );
  1151. continue;
  1152. }
  1153. // If we're not including text then drop annotations as well -- unless, of course, it's
  1154. // an unsided footprint -- in which case it's likely to be nothing *but* annotations.
  1155. if( !aIncludeText && footprintSide != UNDEFINED_LAYER )
  1156. {
  1157. if( BaseType( item->Type() ) == PCB_DIMENSION_T )
  1158. continue;
  1159. if( item->GetLayer() == Cmts_User || item->GetLayer() == Dwgs_User
  1160. || item->GetLayer() == Eco1_User || item->GetLayer() == Eco2_User )
  1161. {
  1162. continue;
  1163. }
  1164. }
  1165. bbox.Merge( item->GetBoundingBox() );
  1166. }
  1167. for( PCB_FIELD* field : m_fields )
  1168. {
  1169. // Reference and value get their own processing
  1170. if( field->IsReference() || field->IsValue() )
  1171. continue;
  1172. texts.push_back( field );
  1173. }
  1174. for( PAD* pad : m_pads )
  1175. bbox.Merge( pad->GetBoundingBox() );
  1176. for( ZONE* zone : m_zones )
  1177. bbox.Merge( zone->GetBoundingBox() );
  1178. for( PCB_POINT* point : m_points )
  1179. bbox.Merge( point->GetBoundingBox() );
  1180. bool noDrawItems = ( m_drawings.empty() && m_pads.empty() && m_zones.empty() );
  1181. // Groups do not contribute to the rect, only their members
  1182. if( aIncludeText || noDrawItems )
  1183. {
  1184. // Only PCB_TEXT and PCB_FIELD items are independently selectable; PCB_TEXTBOX items go
  1185. // in with other graphic items above.
  1186. for( PCB_TEXT* text : texts )
  1187. {
  1188. if( !isFPEdit && m_privateLayers.test( text->GetLayer() ) )
  1189. continue;
  1190. if( text->Type() == PCB_FIELD_T && !text->IsVisible() )
  1191. continue;
  1192. bbox.Merge( text->GetBoundingBox() );
  1193. }
  1194. // This can be further optimized when aIncludeInvisibleText is true, but currently
  1195. // leaving this as is until it's determined there is a noticeable speed hit.
  1196. bool valueLayerIsVisible = true;
  1197. bool refLayerIsVisible = true;
  1198. if( board )
  1199. {
  1200. // The first "&&" conditional handles the user turning layers off as well as layers
  1201. // not being present in the current PCB stackup. Values, references, and all
  1202. // footprint text can also be turned off via the GAL meta-layers, so the 2nd and
  1203. // 3rd "&&" conditionals handle that.
  1204. valueLayerIsVisible = board->IsLayerVisible( Value().GetLayer() )
  1205. && board->IsElementVisible( LAYER_FP_VALUES )
  1206. && board->IsElementVisible( LAYER_FP_TEXT );
  1207. refLayerIsVisible = board->IsLayerVisible( Reference().GetLayer() )
  1208. && board->IsElementVisible( LAYER_FP_REFERENCES )
  1209. && board->IsElementVisible( LAYER_FP_TEXT );
  1210. }
  1211. if( ( Value().IsVisible() && valueLayerIsVisible ) || noDrawItems )
  1212. {
  1213. bbox.Merge( Value().GetBoundingBox() );
  1214. }
  1215. if( ( Reference().IsVisible() && refLayerIsVisible ) || noDrawItems )
  1216. {
  1217. bbox.Merge( Reference().GetBoundingBox() );
  1218. }
  1219. }
  1220. if( board )
  1221. {
  1222. if( aIncludeText || noDrawItems )
  1223. {
  1224. m_boundingBoxCacheTimeStamp = board->GetTimeStamp();
  1225. m_cachedBoundingBox = bbox;
  1226. }
  1227. else
  1228. {
  1229. m_textExcludedBBoxCacheTimeStamp = board->GetTimeStamp();
  1230. m_cachedTextExcludedBBox = bbox;
  1231. }
  1232. }
  1233. return bbox;
  1234. }
  1235. const BOX2I FOOTPRINT::GetLayerBoundingBox( const LSET& aLayers ) const
  1236. {
  1237. std::vector<PCB_TEXT*> texts;
  1238. const BOARD* board = GetBoard();
  1239. bool isFPEdit = board && board->IsFootprintHolder();
  1240. // Start with an uninitialized bounding box
  1241. BOX2I bbox;
  1242. for( BOARD_ITEM* item : m_drawings )
  1243. {
  1244. if( m_privateLayers.test( item->GetLayer() ) && !isFPEdit )
  1245. continue;
  1246. if( ( aLayers & item->GetLayerSet() ).none() )
  1247. continue;
  1248. // We want the bitmap bounding box just in the footprint editor
  1249. // so it will start with the correct initial zoom
  1250. if( item->Type() == PCB_REFERENCE_IMAGE_T && !isFPEdit )
  1251. continue;
  1252. bbox.Merge( item->GetBoundingBox() );
  1253. }
  1254. for( PAD* pad : m_pads )
  1255. {
  1256. if( ( aLayers & pad->GetLayerSet() ).none() )
  1257. continue;
  1258. bbox.Merge( pad->GetBoundingBox() );
  1259. }
  1260. for( ZONE* zone : m_zones )
  1261. {
  1262. if( ( aLayers & zone->GetLayerSet() ).none() )
  1263. continue;
  1264. bbox.Merge( zone->GetBoundingBox() );
  1265. }
  1266. for( PCB_POINT* point : m_points )
  1267. {
  1268. if( m_privateLayers.test( point->GetLayer() ) && !isFPEdit )
  1269. continue;
  1270. if( ( aLayers & point->GetLayerSet() ).none() )
  1271. continue;
  1272. bbox.Merge( point->GetBoundingBox() );
  1273. }
  1274. return bbox;
  1275. }
  1276. SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const
  1277. {
  1278. const BOARD* board = GetBoard();
  1279. bool isFPEdit = board && board->IsFootprintHolder();
  1280. if( board )
  1281. {
  1282. if( m_hullCacheTimeStamp >= board->GetTimeStamp() )
  1283. return m_cachedHull;
  1284. }
  1285. SHAPE_POLY_SET rawPolys;
  1286. for( BOARD_ITEM* item : m_drawings )
  1287. {
  1288. if( !isFPEdit && m_privateLayers.test( item->GetLayer() ) )
  1289. continue;
  1290. if( item->Type() != PCB_FIELD_T && item->Type() != PCB_REFERENCE_IMAGE_T )
  1291. {
  1292. item->TransformShapeToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF,
  1293. ERROR_OUTSIDE );
  1294. }
  1295. // We intentionally exclude footprint fields from the bounding hull.
  1296. }
  1297. for( PAD* pad : m_pads )
  1298. {
  1299. pad->Padstack().ForEachUniqueLayer(
  1300. [&]( PCB_LAYER_ID aLayer )
  1301. {
  1302. pad->TransformShapeToPolygon( rawPolys, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  1303. } );
  1304. // In case hole is larger than pad
  1305. pad->TransformHoleToPolygon( rawPolys, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  1306. }
  1307. for( ZONE* zone : m_zones )
  1308. {
  1309. for( PCB_LAYER_ID layer : zone->GetLayerSet() )
  1310. {
  1311. const SHAPE_POLY_SET& layerPoly = *zone->GetFilledPolysList( layer );
  1312. for( int ii = 0; ii < layerPoly.OutlineCount(); ii++ )
  1313. {
  1314. const SHAPE_LINE_CHAIN& poly = layerPoly.COutline( ii );
  1315. rawPolys.AddOutline( poly );
  1316. }
  1317. }
  1318. }
  1319. // If there are some graphic items, build the actual hull.
  1320. // However if no items, create a minimal polygon (can happen if a footprint
  1321. // is created with no item: it contains only 2 texts.
  1322. if( rawPolys.OutlineCount() == 0 || rawPolys.FullPointCount() < 3 )
  1323. {
  1324. // generate a small dummy rectangular outline around the anchor
  1325. const int halfsize = pcbIUScale.mmToIU( 1.0 );
  1326. rawPolys.NewOutline();
  1327. // add a square:
  1328. rawPolys.Append( GetPosition().x - halfsize, GetPosition().y - halfsize );
  1329. rawPolys.Append( GetPosition().x + halfsize, GetPosition().y - halfsize );
  1330. rawPolys.Append( GetPosition().x + halfsize, GetPosition().y + halfsize );
  1331. rawPolys.Append( GetPosition().x - halfsize, GetPosition().y + halfsize );
  1332. }
  1333. std::vector<VECTOR2I> convex_hull;
  1334. BuildConvexHull( convex_hull, rawPolys );
  1335. m_cachedHull.RemoveAllContours();
  1336. m_cachedHull.NewOutline();
  1337. for( const VECTOR2I& pt : convex_hull )
  1338. m_cachedHull.Append( pt );
  1339. if( board )
  1340. m_hullCacheTimeStamp = board->GetTimeStamp();
  1341. return m_cachedHull;
  1342. }
  1343. SHAPE_POLY_SET FOOTPRINT::GetBoundingHull( PCB_LAYER_ID aLayer ) const
  1344. {
  1345. const BOARD* board = GetBoard();
  1346. bool isFPEdit = board && board->IsFootprintHolder();
  1347. SHAPE_POLY_SET rawPolys;
  1348. SHAPE_POLY_SET hull;
  1349. for( BOARD_ITEM* item : m_drawings )
  1350. {
  1351. if( !isFPEdit && m_privateLayers.test( item->GetLayer() ) )
  1352. continue;
  1353. if( item->IsOnLayer( aLayer ) )
  1354. {
  1355. if( item->Type() != PCB_FIELD_T && item->Type() != PCB_REFERENCE_IMAGE_T )
  1356. {
  1357. item->TransformShapeToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF,
  1358. ERROR_OUTSIDE );
  1359. }
  1360. // We intentionally exclude footprint fields from the bounding hull.
  1361. }
  1362. }
  1363. for( PAD* pad : m_pads )
  1364. {
  1365. if( pad->IsOnLayer( aLayer ) )
  1366. pad->TransformShapeToPolygon( rawPolys, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  1367. }
  1368. for( ZONE* zone : m_zones )
  1369. {
  1370. if( zone->GetIsRuleArea() )
  1371. continue;
  1372. if( zone->IsOnLayer( aLayer ) )
  1373. {
  1374. const std::shared_ptr<SHAPE_POLY_SET>& layerPoly = zone->GetFilledPolysList( aLayer );
  1375. for( int ii = 0; ii < layerPoly->OutlineCount(); ii++ )
  1376. rawPolys.AddOutline( layerPoly->COutline( ii ) );
  1377. }
  1378. }
  1379. std::vector<VECTOR2I> convex_hull;
  1380. BuildConvexHull( convex_hull, rawPolys );
  1381. hull.NewOutline();
  1382. for( const VECTOR2I& pt : convex_hull )
  1383. hull.Append( pt );
  1384. return hull;
  1385. }
  1386. void FOOTPRINT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector<MSG_PANEL_ITEM>& aList )
  1387. {
  1388. wxString msg, msg2;
  1389. // Don't use GetShownText(); we want to see the variable references here
  1390. aList.emplace_back( UnescapeString( Reference().GetText() ),
  1391. UnescapeString( Value().GetText() ) );
  1392. if( aFrame->IsType( FRAME_FOOTPRINT_VIEWER )
  1393. || aFrame->IsType( FRAME_FOOTPRINT_CHOOSER )
  1394. || aFrame->IsType( FRAME_FOOTPRINT_EDITOR ) )
  1395. {
  1396. size_t padCount = GetPadCount( DO_NOT_INCLUDE_NPTH );
  1397. aList.emplace_back( _( "Library" ), GetFPID().GetLibNickname().wx_str() );
  1398. aList.emplace_back( _( "Footprint Name" ), GetFPID().GetLibItemName().wx_str() );
  1399. aList.emplace_back( _( "Pads" ), wxString::Format( wxT( "%zu" ), padCount ) );
  1400. aList.emplace_back( wxString::Format( _( "Doc: %s" ), GetLibDescription() ),
  1401. wxString::Format( _( "Keywords: %s" ), GetKeywords() ) );
  1402. return;
  1403. }
  1404. // aFrame is the board editor:
  1405. switch( GetSide() )
  1406. {
  1407. case F_Cu: aList.emplace_back( _( "Board Side" ), _( "Front" ) ); break;
  1408. case B_Cu: aList.emplace_back( _( "Board Side" ), _( "Back (Flipped)" ) ); break;
  1409. default: /* unsided: user-layers only, etc. */ break;
  1410. }
  1411. auto addToken = []( wxString* aStr, const wxString& aAttr )
  1412. {
  1413. if( !aStr->IsEmpty() )
  1414. *aStr += wxT( ", " );
  1415. *aStr += aAttr;
  1416. };
  1417. wxString status;
  1418. wxString attrs;
  1419. if( IsLocked() )
  1420. addToken( &status, _( "Locked" ) );
  1421. if( m_fpStatus & FP_is_PLACED )
  1422. addToken( &status, _( "autoplaced" ) );
  1423. if( m_attributes & FP_BOARD_ONLY )
  1424. addToken( &attrs, _( "not in schematic" ) );
  1425. if( m_attributes & FP_EXCLUDE_FROM_POS_FILES )
  1426. addToken( &attrs, _( "exclude from pos files" ) );
  1427. if( m_attributes & FP_EXCLUDE_FROM_BOM )
  1428. addToken( &attrs, _( "exclude from BOM" ) );
  1429. if( m_attributes & FP_DNP )
  1430. addToken( &attrs, _( "DNP" ) );
  1431. aList.emplace_back( _( "Status: " ) + status, _( "Attributes:" ) + wxS( " " ) + attrs );
  1432. aList.emplace_back( _( "Rotation" ), wxString::Format( wxT( "%.4g" ),
  1433. GetOrientation().AsDegrees() ) );
  1434. if( !m_componentClassCacheProxy->GetComponentClass()->IsEmpty() )
  1435. {
  1436. aList.emplace_back(
  1437. _( "Component Class" ),
  1438. m_componentClassCacheProxy->GetComponentClass()->GetHumanReadableName() );
  1439. }
  1440. msg.Printf( _( "Footprint: %s" ), m_fpid.GetUniStringLibId() );
  1441. msg2.Printf( _( "3D-Shape: %s" ), m_3D_Drawings.empty() ? _( "<none>" )
  1442. : m_3D_Drawings.front().m_Filename );
  1443. aList.emplace_back( msg, msg2 );
  1444. msg.Printf( _( "Doc: %s" ), m_libDescription );
  1445. msg2.Printf( _( "Keywords: %s" ), m_keywords );
  1446. aList.emplace_back( msg, msg2 );
  1447. }
  1448. PCB_LAYER_ID FOOTPRINT::GetSide() const
  1449. {
  1450. if( const BOARD* board = GetBoard() )
  1451. {
  1452. if( board->IsFootprintHolder() )
  1453. return UNDEFINED_LAYER;
  1454. }
  1455. // Test pads first; they're the most likely to return a quick answer.
  1456. for( PAD* pad : m_pads )
  1457. {
  1458. if( ( LSET::SideSpecificMask() & pad->GetLayerSet() ).any() )
  1459. return GetLayer();
  1460. }
  1461. for( BOARD_ITEM* item : m_drawings )
  1462. {
  1463. if( LSET::SideSpecificMask().test( item->GetLayer() ) )
  1464. return GetLayer();
  1465. }
  1466. for( ZONE* zone : m_zones )
  1467. {
  1468. if( ( LSET::SideSpecificMask() & zone->GetLayerSet() ).any() )
  1469. return GetLayer();
  1470. }
  1471. return UNDEFINED_LAYER;
  1472. }
  1473. bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer ) const
  1474. {
  1475. // If we have any pads, fall back on normal checking
  1476. for( PAD* pad : m_pads )
  1477. {
  1478. if( pad->IsOnLayer( aLayer ) )
  1479. return true;
  1480. }
  1481. for( ZONE* zone : m_zones )
  1482. {
  1483. if( zone->IsOnLayer( aLayer ) )
  1484. return true;
  1485. }
  1486. for( PCB_FIELD* field : m_fields )
  1487. {
  1488. if( field->IsOnLayer( aLayer ) )
  1489. return true;
  1490. }
  1491. for( BOARD_ITEM* item : m_drawings )
  1492. {
  1493. if( item->IsOnLayer( aLayer ) )
  1494. return true;
  1495. }
  1496. return false;
  1497. }
  1498. bool FOOTPRINT::HitTestOnLayer( const VECTOR2I& aPosition, PCB_LAYER_ID aLayer, int aAccuracy ) const
  1499. {
  1500. for( PAD* pad : m_pads )
  1501. {
  1502. if( pad->IsOnLayer( aLayer ) && pad->HitTest( aPosition, aAccuracy ) )
  1503. return true;
  1504. }
  1505. for( ZONE* zone : m_zones )
  1506. {
  1507. if( zone->IsOnLayer( aLayer ) && zone->HitTest( aPosition, aAccuracy ) )
  1508. return true;
  1509. }
  1510. for( BOARD_ITEM* item : m_drawings )
  1511. {
  1512. if( item->Type() != PCB_TEXT_T && item->IsOnLayer( aLayer )
  1513. && item->HitTest( aPosition, aAccuracy ) )
  1514. {
  1515. return true;
  1516. }
  1517. }
  1518. return false;
  1519. }
  1520. bool FOOTPRINT::HitTestOnLayer( const BOX2I& aRect, bool aContained, PCB_LAYER_ID aLayer, int aAccuracy ) const
  1521. {
  1522. std::vector<BOARD_ITEM*> items;
  1523. for( PAD* pad : m_pads )
  1524. {
  1525. if( pad->IsOnLayer( aLayer ) )
  1526. items.push_back( pad );
  1527. }
  1528. for( ZONE* zone : m_zones )
  1529. {
  1530. if( zone->IsOnLayer( aLayer ) )
  1531. items.push_back( zone );
  1532. }
  1533. for( BOARD_ITEM* item : m_drawings )
  1534. {
  1535. if( item->Type() != PCB_TEXT_T && item->IsOnLayer( aLayer ) )
  1536. items.push_back( item );
  1537. }
  1538. // If we require the elements to be contained in the rect and any of them are not,
  1539. // we can return false;
  1540. // Conversely, if we just require any of the elements to have a hit, we can return true
  1541. // when the first one is found.
  1542. for( BOARD_ITEM* item : items )
  1543. {
  1544. if( !aContained && item->HitTest( aRect, aContained, aAccuracy ) )
  1545. return true;
  1546. else if( aContained && !item->HitTest( aRect, aContained, aAccuracy ) )
  1547. return false;
  1548. }
  1549. // If we didn't exit in the loop, that means that we did not return false for aContained or
  1550. // we did not return true for !aContained. So we can just return the bool with a test of
  1551. // whether there were any elements or not.
  1552. return !items.empty() && aContained;
  1553. }
  1554. bool FOOTPRINT::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const
  1555. {
  1556. BOX2I rect = GetBoundingBox( false );
  1557. return rect.Inflate( aAccuracy ).Contains( aPosition );
  1558. }
  1559. bool FOOTPRINT::HitTestAccurate( const VECTOR2I& aPosition, int aAccuracy ) const
  1560. {
  1561. return GetBoundingHull().Collide( aPosition, aAccuracy );
  1562. }
  1563. bool FOOTPRINT::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const
  1564. {
  1565. BOX2I arect = aRect;
  1566. arect.Inflate( aAccuracy );
  1567. if( aContained )
  1568. {
  1569. return arect.Contains( GetBoundingBox( false ) );
  1570. }
  1571. else
  1572. {
  1573. // If the rect does not intersect the bounding box, skip any tests
  1574. if( !aRect.Intersects( GetBoundingBox( false ) ) )
  1575. return false;
  1576. // If there are no pads, zones, or drawings, allow intersection with text
  1577. if( m_pads.empty() && m_zones.empty() && m_drawings.empty() )
  1578. return GetBoundingBox( true ).Intersects( arect );
  1579. // Determine if any elements in the FOOTPRINT intersect the rect
  1580. for( PAD* pad : m_pads )
  1581. {
  1582. if( pad->HitTest( arect, false, 0 ) )
  1583. return true;
  1584. }
  1585. for( ZONE* zone : m_zones )
  1586. {
  1587. if( zone->HitTest( arect, false, 0 ) )
  1588. return true;
  1589. }
  1590. for( PCB_POINT* point : m_points )
  1591. {
  1592. if( point->HitTest( arect, false, 0 ) )
  1593. return true;
  1594. }
  1595. // PCB fields are selectable on their own, so they don't get tested
  1596. for( BOARD_ITEM* item : m_drawings )
  1597. {
  1598. // Text items are selectable on their own, and are therefore excluded from this
  1599. // test. TextBox items are NOT selectable on their own, and so MUST be included
  1600. // here. Bitmaps aren't selectable since they aren't displayed.
  1601. if( item->Type() != PCB_TEXT_T && item->HitTest( arect, false, 0 ) )
  1602. return true;
  1603. }
  1604. // Groups are not hit-tested; only their members
  1605. // No items were hit
  1606. return false;
  1607. }
  1608. }
  1609. bool FOOTPRINT::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const
  1610. {
  1611. using std::ranges::all_of;
  1612. using std::ranges::any_of;
  1613. // If there are no pads, zones, or drawings, test footprint text instead.
  1614. if( m_pads.empty() && m_zones.empty() && m_drawings.empty() )
  1615. return KIGEOM::BoxHitTest( aPoly, GetBoundingBox( true ), aContained );
  1616. auto hitTest =
  1617. [&]( const auto* aItem )
  1618. {
  1619. return aItem && aItem->HitTest( aPoly, aContained );
  1620. };
  1621. // Filter out text items from the drawings, since they are selectable on their own,
  1622. // and we don't want to select the whole footprint when text is hit. TextBox items are NOT
  1623. // selectable on their own, so they are not excluded here.
  1624. auto drawings = m_drawings | std::views::filter( []( const auto* aItem )
  1625. {
  1626. return aItem && aItem->Type() != PCB_TEXT_T;
  1627. } );
  1628. // Test pads, zones and drawings with text excluded. PCB fields are also selectable
  1629. // on their own, so they don't get tested. Groups are not hit-tested, only their members.
  1630. // Bitmaps aren't selectable since they aren't displayed.
  1631. if( aContained )
  1632. {
  1633. // All items must be contained in the selection poly.
  1634. return all_of( drawings, hitTest )
  1635. && all_of( m_pads, hitTest )
  1636. && all_of( m_zones, hitTest );
  1637. }
  1638. else
  1639. {
  1640. // Any item intersecting the selection poly is sufficient.
  1641. return any_of( drawings, hitTest )
  1642. || any_of( m_pads, hitTest )
  1643. || any_of( m_zones, hitTest );
  1644. }
  1645. }
  1646. PAD* FOOTPRINT::FindPadByNumber( const wxString& aPadNumber, PAD* aSearchAfterMe ) const
  1647. {
  1648. bool can_select = aSearchAfterMe ? false : true;
  1649. for( PAD* pad : m_pads )
  1650. {
  1651. if( !can_select && pad == aSearchAfterMe )
  1652. {
  1653. can_select = true;
  1654. continue;
  1655. }
  1656. if( can_select && pad->GetNumber() == aPadNumber )
  1657. return pad;
  1658. }
  1659. return nullptr;
  1660. }
  1661. PAD* FOOTPRINT::GetPad( const VECTOR2I& aPosition, const LSET& aLayerMask )
  1662. {
  1663. for( PAD* pad : m_pads )
  1664. {
  1665. // ... and on the correct layer.
  1666. if( !( pad->GetLayerSet() & aLayerMask ).any() )
  1667. continue;
  1668. if( pad->HitTest( aPosition ) )
  1669. return pad;
  1670. }
  1671. return nullptr;
  1672. }
  1673. std::vector<const PAD*> FOOTPRINT::GetPads( const wxString& aPadNumber, const PAD* aIgnore ) const
  1674. {
  1675. std::vector<const PAD*> retv;
  1676. for( const PAD* pad : m_pads )
  1677. {
  1678. if( ( aIgnore && aIgnore == pad ) || ( pad->GetNumber() != aPadNumber ) )
  1679. continue;
  1680. retv.push_back( pad );
  1681. }
  1682. return retv;
  1683. }
  1684. unsigned FOOTPRINT::GetPadCount( INCLUDE_NPTH_T aIncludeNPTH ) const
  1685. {
  1686. if( aIncludeNPTH )
  1687. return m_pads.size();
  1688. unsigned cnt = 0;
  1689. for( PAD* pad : m_pads )
  1690. {
  1691. if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
  1692. continue;
  1693. cnt++;
  1694. }
  1695. return cnt;
  1696. }
  1697. std::set<wxString> FOOTPRINT::GetUniquePadNumbers( INCLUDE_NPTH_T aIncludeNPTH ) const
  1698. {
  1699. std::set<wxString> usedNumbers;
  1700. // Create a set of used pad numbers
  1701. for( PAD* pad : m_pads )
  1702. {
  1703. // Skip pads not on copper layers (used to build complex
  1704. // solder paste shapes for instance)
  1705. if( ( pad->GetLayerSet() & LSET::AllCuMask() ).none() )
  1706. continue;
  1707. // Skip pads with no name, because they are usually "mechanical"
  1708. // pads, not "electrical" pads
  1709. if( pad->GetNumber().IsEmpty() )
  1710. continue;
  1711. if( !aIncludeNPTH )
  1712. {
  1713. // skip NPTH
  1714. if( pad->GetAttribute() == PAD_ATTRIB::NPTH )
  1715. continue;
  1716. }
  1717. usedNumbers.insert( pad->GetNumber() );
  1718. }
  1719. return usedNumbers;
  1720. }
  1721. unsigned FOOTPRINT::GetUniquePadCount( INCLUDE_NPTH_T aIncludeNPTH ) const
  1722. {
  1723. return GetUniquePadNumbers( aIncludeNPTH ).size();
  1724. }
  1725. void FOOTPRINT::Add3DModel( FP_3DMODEL* a3DModel )
  1726. {
  1727. if( nullptr == a3DModel )
  1728. return;
  1729. if( !a3DModel->m_Filename.empty() )
  1730. m_3D_Drawings.push_back( *a3DModel );
  1731. }
  1732. bool FOOTPRINT::Matches( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) const
  1733. {
  1734. if( aSearchData.searchMetadata )
  1735. {
  1736. if( EDA_ITEM::Matches( GetFPIDAsString(), aSearchData ) )
  1737. return true;
  1738. if( EDA_ITEM::Matches( GetLibDescription(), aSearchData ) )
  1739. return true;
  1740. if( EDA_ITEM::Matches( GetKeywords(), aSearchData ) )
  1741. return true;
  1742. }
  1743. return false;
  1744. }
  1745. // see footprint.h
  1746. INSPECT_RESULT FOOTPRINT::Visit( INSPECTOR inspector, void* testData,
  1747. const std::vector<KICAD_T>& aScanTypes )
  1748. {
  1749. #if 0 && defined(DEBUG)
  1750. std::cout << GetClass().mb_str() << ' ';
  1751. #endif
  1752. bool drawingsScanned = false;
  1753. for( KICAD_T scanType : aScanTypes )
  1754. {
  1755. switch( scanType )
  1756. {
  1757. case PCB_FOOTPRINT_T:
  1758. if( inspector( this, testData ) == INSPECT_RESULT::QUIT )
  1759. return INSPECT_RESULT::QUIT;
  1760. break;
  1761. case PCB_PAD_T:
  1762. if( IterateForward<PAD*>( m_pads, inspector, testData, { scanType } )
  1763. == INSPECT_RESULT::QUIT )
  1764. {
  1765. return INSPECT_RESULT::QUIT;
  1766. }
  1767. break;
  1768. case PCB_ZONE_T:
  1769. if( IterateForward<ZONE*>( m_zones, inspector, testData, { scanType } )
  1770. == INSPECT_RESULT::QUIT )
  1771. {
  1772. return INSPECT_RESULT::QUIT;
  1773. }
  1774. break;
  1775. case PCB_FIELD_T:
  1776. if( IterateForward<PCB_FIELD*>( m_fields, inspector, testData, { scanType } )
  1777. == INSPECT_RESULT::QUIT )
  1778. {
  1779. return INSPECT_RESULT::QUIT;
  1780. }
  1781. break;
  1782. case PCB_TEXT_T:
  1783. case PCB_DIM_ALIGNED_T:
  1784. case PCB_DIM_LEADER_T:
  1785. case PCB_DIM_CENTER_T:
  1786. case PCB_DIM_RADIAL_T:
  1787. case PCB_DIM_ORTHOGONAL_T:
  1788. case PCB_SHAPE_T:
  1789. case PCB_TEXTBOX_T:
  1790. case PCB_TABLE_T:
  1791. case PCB_TABLECELL_T:
  1792. if( !drawingsScanned )
  1793. {
  1794. if( IterateForward<BOARD_ITEM*>( m_drawings, inspector, testData, aScanTypes )
  1795. == INSPECT_RESULT::QUIT )
  1796. {
  1797. return INSPECT_RESULT::QUIT;
  1798. }
  1799. drawingsScanned = true;
  1800. }
  1801. break;
  1802. case PCB_GROUP_T:
  1803. if( IterateForward<PCB_GROUP*>( m_groups, inspector, testData, { scanType } )
  1804. == INSPECT_RESULT::QUIT )
  1805. {
  1806. return INSPECT_RESULT::QUIT;
  1807. }
  1808. break;
  1809. case PCB_POINT_T:
  1810. if( IterateForward<PCB_POINT*>( m_points, inspector, testData, { scanType } )
  1811. == INSPECT_RESULT::QUIT )
  1812. {
  1813. return INSPECT_RESULT::QUIT;
  1814. }
  1815. break;
  1816. default:
  1817. break;
  1818. }
  1819. }
  1820. return INSPECT_RESULT::CONTINUE;
  1821. }
  1822. wxString FOOTPRINT::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const
  1823. {
  1824. wxString reference = GetReference();
  1825. if( reference.IsEmpty() )
  1826. reference = _( "<no reference designator>" );
  1827. return wxString::Format( _( "Footprint %s" ), reference );
  1828. }
  1829. BITMAPS FOOTPRINT::GetMenuImage() const
  1830. {
  1831. return BITMAPS::module;
  1832. }
  1833. EDA_ITEM* FOOTPRINT::Clone() const
  1834. {
  1835. return new FOOTPRINT( *this );
  1836. }
  1837. void FOOTPRINT::RunOnChildren( const std::function<void( BOARD_ITEM* )>& aFunction, RECURSE_MODE aMode ) const
  1838. {
  1839. try
  1840. {
  1841. for( PCB_FIELD* field : m_fields )
  1842. aFunction( field );
  1843. for( PAD* pad : m_pads )
  1844. aFunction( pad );
  1845. for( ZONE* zone : m_zones )
  1846. aFunction( zone );
  1847. for( PCB_GROUP* group : m_groups )
  1848. aFunction( group );
  1849. for( PCB_POINT* point : m_points )
  1850. aFunction( point );
  1851. for( BOARD_ITEM* drawing : m_drawings )
  1852. {
  1853. aFunction( drawing );
  1854. if( aMode == RECURSE_MODE::RECURSE )
  1855. drawing->RunOnChildren( aFunction, RECURSE_MODE::RECURSE );
  1856. }
  1857. }
  1858. catch( std::bad_function_call& )
  1859. {
  1860. wxFAIL_MSG( wxT( "Error running FOOTPRINT::RunOnChildren" ) );
  1861. }
  1862. }
  1863. std::vector<int> FOOTPRINT::ViewGetLayers() const
  1864. {
  1865. std::vector<int> layers;
  1866. layers.reserve( 6 );
  1867. layers.push_back( LAYER_ANCHOR );
  1868. switch( m_layer )
  1869. {
  1870. default:
  1871. wxASSERT_MSG( false, wxT( "Illegal layer" ) ); // do you really have footprints placed
  1872. // on other layers?
  1873. KI_FALLTHROUGH;
  1874. case F_Cu:
  1875. layers.push_back( LAYER_FOOTPRINTS_FR );
  1876. break;
  1877. case B_Cu:
  1878. layers.push_back( LAYER_FOOTPRINTS_BK );
  1879. break;
  1880. }
  1881. if( IsConflicting() )
  1882. layers.push_back( LAYER_CONFLICTS_SHADOW );
  1883. // If there are no pads, and only drawings on a silkscreen layer, then report the silkscreen
  1884. // layer as well so that the component can be edited with the silkscreen layer
  1885. bool f_silk = false, b_silk = false, non_silk = false;
  1886. for( BOARD_ITEM* item : m_drawings )
  1887. {
  1888. if( item->GetLayer() == F_SilkS )
  1889. f_silk = true;
  1890. else if( item->GetLayer() == B_SilkS )
  1891. b_silk = true;
  1892. else
  1893. non_silk = true;
  1894. }
  1895. if( ( f_silk || b_silk ) && !non_silk && m_pads.empty() )
  1896. {
  1897. if( f_silk )
  1898. layers.push_back( F_SilkS );
  1899. if( b_silk )
  1900. layers.push_back( B_SilkS );
  1901. }
  1902. return layers;
  1903. }
  1904. double FOOTPRINT::ViewGetLOD( int aLayer, const KIGFX::VIEW* aView ) const
  1905. {
  1906. if( aLayer == LAYER_CONFLICTS_SHADOW && IsConflicting() )
  1907. {
  1908. // The locked shadow shape is shown only if the footprint itself is visible
  1909. if( ( m_layer == F_Cu ) && aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) )
  1910. return LOD_SHOW;
  1911. if( ( m_layer == B_Cu ) && aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) )
  1912. return LOD_SHOW;
  1913. return LOD_HIDE;
  1914. }
  1915. int layer = ( m_layer == F_Cu ) ? LAYER_FOOTPRINTS_FR :
  1916. ( m_layer == B_Cu ) ? LAYER_FOOTPRINTS_BK : LAYER_ANCHOR;
  1917. // Currently this is only pertinent for the anchor layer; everything else is drawn from the
  1918. // children.
  1919. // The "good" value is experimentally chosen.
  1920. constexpr double MINIMAL_ZOOM_LEVEL_FOR_VISIBILITY = 1.5;
  1921. if( aView->IsLayerVisible( layer ) )
  1922. return MINIMAL_ZOOM_LEVEL_FOR_VISIBILITY;
  1923. return LOD_HIDE;
  1924. }
  1925. const BOX2I FOOTPRINT::ViewBBox() const
  1926. {
  1927. BOX2I area = GetBoundingBox( true );
  1928. // Inflate in case clearance lines are drawn around pads, etc.
  1929. if( const BOARD* board = GetBoard() )
  1930. {
  1931. int biggest_clearance = board->GetMaxClearanceValue();
  1932. area.Inflate( biggest_clearance );
  1933. }
  1934. return area;
  1935. }
  1936. bool FOOTPRINT::IsLibNameValid( const wxString & aName )
  1937. {
  1938. const wxChar * invalids = StringLibNameInvalidChars( false );
  1939. if( aName.find_first_of( invalids ) != std::string::npos )
  1940. return false;
  1941. return true;
  1942. }
  1943. const wxChar* FOOTPRINT::StringLibNameInvalidChars( bool aUserReadable )
  1944. {
  1945. // This list of characters is also duplicated in validators.cpp and
  1946. // lib_id.cpp
  1947. // TODO: Unify forbidden character lists - Warning, invalid filename characters are not the same
  1948. // as invalid LIB_ID characters. We will need to separate the FP filenames from FP names before this
  1949. // can be unified
  1950. static const wxChar invalidChars[] = wxT("%$<>\t\n\r\"\\/:");
  1951. static const wxChar invalidCharsReadable[] = wxT("% $ < > 'tab' 'return' 'line feed' \\ \" / :");
  1952. if( aUserReadable )
  1953. return invalidCharsReadable;
  1954. else
  1955. return invalidChars;
  1956. }
  1957. void FOOTPRINT::Move( const VECTOR2I& aMoveVector )
  1958. {
  1959. if( aMoveVector.x == 0 && aMoveVector.y == 0 )
  1960. return;
  1961. VECTOR2I newpos = m_pos + aMoveVector;
  1962. SetPosition( newpos );
  1963. }
  1964. void FOOTPRINT::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle )
  1965. {
  1966. if( aAngle == ANGLE_0 )
  1967. return;
  1968. EDA_ANGLE orientation = GetOrientation();
  1969. EDA_ANGLE newOrientation = orientation + aAngle;
  1970. VECTOR2I newpos = m_pos;
  1971. RotatePoint( newpos, aRotCentre, aAngle );
  1972. SetPosition( newpos );
  1973. SetOrientation( newOrientation );
  1974. for( PCB_FIELD* field : m_fields )
  1975. field->KeepUpright();
  1976. for( BOARD_ITEM* item : m_drawings )
  1977. {
  1978. if( item->Type() == PCB_TEXT_T )
  1979. static_cast<PCB_TEXT*>( item )->KeepUpright();
  1980. }
  1981. }
  1982. void FOOTPRINT::SetLayerAndFlip( PCB_LAYER_ID aLayer )
  1983. {
  1984. wxASSERT( aLayer == F_Cu || aLayer == B_Cu );
  1985. if( aLayer != GetLayer() )
  1986. Flip( GetPosition(), FLIP_DIRECTION::LEFT_RIGHT );
  1987. }
  1988. void FOOTPRINT::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection )
  1989. {
  1990. // Move footprint to its final position:
  1991. VECTOR2I finalPos = m_pos;
  1992. // Now Flip the footprint.
  1993. // Flipping a footprint is a specific transform: it is not mirrored like a text.
  1994. // We have to change the side, and ensure the footprint rotation is modified according to the
  1995. // transform, because this parameter is used in pick and place files, and when updating the
  1996. // footprint from library.
  1997. // When flipped around the X axis (Y coordinates changed) orientation is negated
  1998. // When flipped around the Y axis (X coordinates changed) orientation is 180 - old orient.
  1999. // Because it is specific to a footprint, we flip around the X axis, and after rotate 180 deg
  2000. MIRROR( finalPos.y, aCentre.y ); /// Mirror the Y position (around the X axis)
  2001. SetPosition( finalPos );
  2002. // Flip layer
  2003. BOARD_ITEM::SetLayer( GetBoard()->FlipLayer( GetLayer() ) );
  2004. // Calculate the new orientation, and then clear it for pad flipping.
  2005. EDA_ANGLE newOrientation = -m_orient;
  2006. newOrientation.Normalize180();
  2007. m_orient = ANGLE_0;
  2008. // Mirror fields to other side of board.
  2009. for( PCB_FIELD* field : m_fields )
  2010. field->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM );
  2011. // Mirror pads to other side of board.
  2012. for( PAD* pad : m_pads )
  2013. pad->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM );
  2014. // Now set the new orientation.
  2015. m_orient = newOrientation;
  2016. // Mirror zones to other side of board.
  2017. for( ZONE* zone : m_zones )
  2018. zone->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM );
  2019. // Reverse mirror footprint graphics and texts.
  2020. for( BOARD_ITEM* item : m_drawings )
  2021. item->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM );
  2022. // Points move but don't flip layer
  2023. for( PCB_POINT* point : m_points )
  2024. point->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM );
  2025. // Now rotate 180 deg if required
  2026. if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT )
  2027. Rotate( aCentre, ANGLE_180 );
  2028. m_boundingBoxCacheTimeStamp = 0;
  2029. m_textExcludedBBoxCacheTimeStamp = 0;
  2030. m_cachedHull.Mirror( m_pos, aFlipDirection );
  2031. // The courtyard caches must be rebuilt after geometry change
  2032. BuildCourtyardCaches();
  2033. }
  2034. void FOOTPRINT::SetPosition( const VECTOR2I& aPos )
  2035. {
  2036. VECTOR2I delta = aPos - m_pos;
  2037. m_pos += delta;
  2038. for( PCB_FIELD* field : m_fields )
  2039. field->EDA_TEXT::Offset( delta );
  2040. for( PAD* pad : m_pads )
  2041. pad->SetPosition( pad->GetPosition() + delta );
  2042. for( ZONE* zone : m_zones )
  2043. zone->Move( delta );
  2044. for( BOARD_ITEM* item : m_drawings )
  2045. item->Move( delta );
  2046. for( PCB_POINT* point : m_points )
  2047. point->Move( delta );
  2048. m_cachedBoundingBox.Move( delta );
  2049. m_cachedTextExcludedBBox.Move( delta );
  2050. m_cachedHull.Move( delta );
  2051. // The geometry work has been conserved by using Move(). But the hashes
  2052. // need to be updated, otherwise the cached polygons will still be rebuild.
  2053. m_courtyard_cache_back.Move( delta );
  2054. m_courtyard_cache_back_hash = m_courtyard_cache_back.GetHash();
  2055. m_courtyard_cache_front.Move( delta );
  2056. m_courtyard_cache_front_hash = m_courtyard_cache_front.GetHash();
  2057. }
  2058. void FOOTPRINT::MoveAnchorPosition( const VECTOR2I& aMoveVector )
  2059. {
  2060. /*
  2061. * Move the reference point of the footprint
  2062. * the footprints elements (pads, outlines, edges .. ) are moved
  2063. * but:
  2064. * - the footprint position is not modified.
  2065. * - the relative (local) coordinates of these items are modified
  2066. * - Draw coordinates are updated
  2067. */
  2068. // Update (move) the relative coordinates relative to the new anchor point.
  2069. VECTOR2I moveVector = aMoveVector;
  2070. RotatePoint( moveVector, -GetOrientation() );
  2071. // Update field local coordinates
  2072. for( PCB_FIELD* field : m_fields )
  2073. field->Move( moveVector );
  2074. // Update the pad local coordinates.
  2075. for( PAD* pad : m_pads )
  2076. pad->Move( moveVector );
  2077. // Update the draw element coordinates.
  2078. for( BOARD_ITEM* item : GraphicalItems() )
  2079. item->Move( moveVector );
  2080. // Update the keepout zones
  2081. for( ZONE* zone : Zones() )
  2082. zone->Move( moveVector );
  2083. // Update the 3D models
  2084. for( FP_3DMODEL& model : Models() )
  2085. {
  2086. model.m_Offset.x += pcbIUScale.IUTomm( moveVector.x );
  2087. model.m_Offset.y -= pcbIUScale.IUTomm( moveVector.y );
  2088. }
  2089. m_cachedBoundingBox.Move( moveVector );
  2090. m_cachedTextExcludedBBox.Move( moveVector );
  2091. m_cachedHull.Move( moveVector );
  2092. // The geometry work have been conserved by using Move(). But the hashes
  2093. // need to be updated, otherwise the cached polygons will still be rebuild.
  2094. m_courtyard_cache_back.Move( moveVector );
  2095. m_courtyard_cache_back_hash = m_courtyard_cache_back.GetHash();
  2096. m_courtyard_cache_front.Move( moveVector );
  2097. m_courtyard_cache_front_hash = m_courtyard_cache_front.GetHash();
  2098. }
  2099. void FOOTPRINT::SetOrientation( const EDA_ANGLE& aNewAngle )
  2100. {
  2101. EDA_ANGLE angleChange = aNewAngle - m_orient; // change in rotation
  2102. m_orient = aNewAngle;
  2103. m_orient.Normalize180();
  2104. for( PCB_FIELD* field : m_fields )
  2105. field->Rotate( GetPosition(), angleChange );
  2106. for( PAD* pad : m_pads )
  2107. pad->Rotate( GetPosition(), angleChange );
  2108. for( ZONE* zone : m_zones )
  2109. zone->Rotate( GetPosition(), angleChange );
  2110. for( BOARD_ITEM* item : m_drawings )
  2111. item->Rotate( GetPosition(), angleChange );
  2112. for( PCB_POINT* point : m_points )
  2113. point->Rotate( GetPosition(), angleChange );
  2114. m_boundingBoxCacheTimeStamp = 0;
  2115. m_textExcludedBBoxCacheTimeStamp = 0;
  2116. m_hullCacheTimeStamp = 0;
  2117. // The courtyard caches need to be rebuilt, as the geometry has changed
  2118. BuildCourtyardCaches();
  2119. }
  2120. BOARD_ITEM* FOOTPRINT::Duplicate( bool addToParentGroup, BOARD_COMMIT* aCommit ) const
  2121. {
  2122. FOOTPRINT* dupe = static_cast<FOOTPRINT*>( BOARD_ITEM::Duplicate( addToParentGroup, aCommit ) );
  2123. dupe->RunOnChildren( [&]( BOARD_ITEM* child )
  2124. {
  2125. const_cast<KIID&>( child->m_Uuid ) = KIID();
  2126. },
  2127. RECURSE_MODE::RECURSE );
  2128. return dupe;
  2129. }
  2130. BOARD_ITEM* FOOTPRINT::DuplicateItem( bool addToParentGroup, BOARD_COMMIT* aCommit,
  2131. const BOARD_ITEM* aItem, bool addToFootprint )
  2132. {
  2133. BOARD_ITEM* new_item = nullptr;
  2134. switch( aItem->Type() )
  2135. {
  2136. case PCB_PAD_T:
  2137. {
  2138. PAD* new_pad = new PAD( *static_cast<const PAD*>( aItem ) );
  2139. const_cast<KIID&>( new_pad->m_Uuid ) = KIID();
  2140. if( addToFootprint )
  2141. m_pads.push_back( new_pad );
  2142. new_item = new_pad;
  2143. break;
  2144. }
  2145. case PCB_ZONE_T:
  2146. {
  2147. ZONE* new_zone = new ZONE( *static_cast<const ZONE*>( aItem ) );
  2148. const_cast<KIID&>( new_zone->m_Uuid ) = KIID();
  2149. if( addToFootprint )
  2150. m_zones.push_back( new_zone );
  2151. new_item = new_zone;
  2152. break;
  2153. }
  2154. case PCB_POINT_T:
  2155. {
  2156. PCB_POINT* new_point = new PCB_POINT( *static_cast<const PCB_POINT*>( aItem ) );
  2157. const_cast<KIID&>( new_point->m_Uuid ) = KIID();
  2158. if( addToFootprint )
  2159. m_points.push_back( new_point );
  2160. new_item = new_point;
  2161. break;
  2162. }
  2163. case PCB_FIELD_T:
  2164. case PCB_TEXT_T:
  2165. {
  2166. PCB_TEXT* new_text = new PCB_TEXT( *static_cast<const PCB_TEXT*>( aItem ) );
  2167. const_cast<KIID&>( new_text->m_Uuid ) = KIID();
  2168. if( aItem->Type() == PCB_FIELD_T )
  2169. {
  2170. switch( static_cast<const PCB_FIELD*>( aItem )->GetId() )
  2171. {
  2172. case FIELD_T::REFERENCE: new_text->SetText( wxT( "${REFERENCE}" ) ); break;
  2173. case FIELD_T::VALUE: new_text->SetText( wxT( "${VALUE}" ) ); break;
  2174. case FIELD_T::DATASHEET: new_text->SetText( wxT( "${DATASHEET}" ) ); break;
  2175. default: break;
  2176. }
  2177. }
  2178. if( addToFootprint )
  2179. Add( new_text );
  2180. new_item = new_text;
  2181. break;
  2182. }
  2183. case PCB_SHAPE_T:
  2184. {
  2185. PCB_SHAPE* new_shape = new PCB_SHAPE( *static_cast<const PCB_SHAPE*>( aItem ) );
  2186. const_cast<KIID&>( new_shape->m_Uuid ) = KIID();
  2187. if( addToFootprint )
  2188. Add( new_shape );
  2189. new_item = new_shape;
  2190. break;
  2191. }
  2192. case PCB_REFERENCE_IMAGE_T:
  2193. {
  2194. PCB_REFERENCE_IMAGE* new_image = new PCB_REFERENCE_IMAGE( *static_cast<const PCB_REFERENCE_IMAGE*>( aItem ) );
  2195. const_cast<KIID&>( new_image->m_Uuid ) = KIID();
  2196. if( addToFootprint )
  2197. Add( new_image );
  2198. new_item = new_image;
  2199. break;
  2200. }
  2201. case PCB_TEXTBOX_T:
  2202. {
  2203. PCB_TEXTBOX* new_textbox = new PCB_TEXTBOX( *static_cast<const PCB_TEXTBOX*>( aItem ) );
  2204. const_cast<KIID&>( new_textbox->m_Uuid ) = KIID();
  2205. if( addToFootprint )
  2206. Add( new_textbox );
  2207. new_item = new_textbox;
  2208. break;
  2209. }
  2210. case PCB_DIM_ALIGNED_T:
  2211. case PCB_DIM_LEADER_T:
  2212. case PCB_DIM_CENTER_T:
  2213. case PCB_DIM_RADIAL_T:
  2214. case PCB_DIM_ORTHOGONAL_T:
  2215. {
  2216. PCB_DIMENSION_BASE* dimension = static_cast<PCB_DIMENSION_BASE*>( aItem->Duplicate( addToParentGroup,
  2217. aCommit ) );
  2218. if( addToFootprint )
  2219. Add( dimension );
  2220. new_item = dimension;
  2221. break;
  2222. }
  2223. case PCB_GROUP_T:
  2224. {
  2225. PCB_GROUP* group = static_cast<const PCB_GROUP*>( aItem )->DeepDuplicate( addToParentGroup, aCommit );
  2226. if( addToFootprint )
  2227. {
  2228. group->RunOnChildren(
  2229. [&]( BOARD_ITEM* aCurrItem )
  2230. {
  2231. Add( aCurrItem );
  2232. },
  2233. RECURSE_MODE::RECURSE );
  2234. Add( group );
  2235. }
  2236. new_item = group;
  2237. break;
  2238. }
  2239. case PCB_FOOTPRINT_T:
  2240. // Ignore the footprint itself
  2241. break;
  2242. default:
  2243. // Un-handled item for duplication
  2244. wxFAIL_MSG( wxT( "Duplication not supported for items of class " ) + aItem->GetClass() );
  2245. break;
  2246. }
  2247. return new_item;
  2248. }
  2249. wxString FOOTPRINT::GetNextPadNumber( const wxString& aLastPadNumber ) const
  2250. {
  2251. std::set<wxString> usedNumbers;
  2252. // Create a set of used pad numbers
  2253. for( PAD* pad : m_pads )
  2254. usedNumbers.insert( pad->GetNumber() );
  2255. // Pad numbers aren't technically reference designators, but the formatting is close enough
  2256. // for these to give us what we need.
  2257. wxString prefix = UTIL::GetRefDesPrefix( aLastPadNumber );
  2258. int num = GetTrailingInt( aLastPadNumber );
  2259. while( usedNumbers.count( wxString::Format( wxT( "%s%d" ), prefix, num ) ) )
  2260. num++;
  2261. return wxString::Format( wxT( "%s%d" ), prefix, num );
  2262. }
  2263. std::optional<const std::set<wxString>> FOOTPRINT::GetJumperPadGroup( const wxString& aPadNumber ) const
  2264. {
  2265. for( const std::set<wxString>& group : m_jumperPadGroups )
  2266. {
  2267. if( group.contains( aPadNumber ) )
  2268. return group;
  2269. }
  2270. return std::nullopt;
  2271. }
  2272. void FOOTPRINT::AutoPositionFields()
  2273. {
  2274. // Auto-position reference and value
  2275. BOX2I bbox = GetBoundingBox( false );
  2276. bbox.Inflate( pcbIUScale.mmToIU( 0.2 ) ); // Gap between graphics and text
  2277. if( Reference().GetPosition() == VECTOR2I( 0, 0 ) )
  2278. {
  2279. Reference().SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
  2280. Reference().SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
  2281. Reference().SetTextAngle( ANGLE_0 );
  2282. Reference().SetX( bbox.GetCenter().x );
  2283. Reference().SetY( bbox.GetTop() - Reference().GetTextSize().y / 2 );
  2284. }
  2285. if( Value().GetPosition() == VECTOR2I( 0, 0 ) )
  2286. {
  2287. Value().SetHorizJustify( GR_TEXT_H_ALIGN_CENTER );
  2288. Value().SetVertJustify( GR_TEXT_V_ALIGN_CENTER );
  2289. Value().SetTextAngle( ANGLE_0 );
  2290. Value().SetX( bbox.GetCenter().x );
  2291. Value().SetY( bbox.GetBottom() + Value().GetTextSize().y / 2 );
  2292. }
  2293. }
  2294. void FOOTPRINT::IncrementReference( int aDelta )
  2295. {
  2296. const wxString& refdes = GetReference();
  2297. SetReference( wxString::Format( wxT( "%s%i" ),
  2298. UTIL::GetRefDesPrefix( refdes ),
  2299. GetTrailingInt( refdes ) + aDelta ) );
  2300. }
  2301. // Calculate the area of a PolySet, polygons with hole are allowed.
  2302. static double polygonArea( SHAPE_POLY_SET& aPolySet )
  2303. {
  2304. // Ensure all outlines are closed, before calculating the SHAPE_POLY_SET area
  2305. for( int ii = 0; ii < aPolySet.OutlineCount(); ii++ )
  2306. {
  2307. SHAPE_LINE_CHAIN& outline = aPolySet.Outline( ii );
  2308. outline.SetClosed( true );
  2309. for( int jj = 0; jj < aPolySet.HoleCount( ii ); jj++ )
  2310. aPolySet.Hole( ii, jj ).SetClosed( true );
  2311. }
  2312. return aPolySet.Area();
  2313. }
  2314. double FOOTPRINT::GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLECTOR& aCollector )
  2315. {
  2316. int textMargin = aCollector.GetGuide()->Accuracy();
  2317. SHAPE_POLY_SET poly;
  2318. if( aItem->Type() == PCB_MARKER_T )
  2319. {
  2320. const PCB_MARKER* marker = static_cast<const PCB_MARKER*>( aItem );
  2321. SHAPE_LINE_CHAIN markerShape;
  2322. marker->ShapeToPolygon( markerShape );
  2323. return markerShape.Area();
  2324. }
  2325. else if( aItem->Type() == PCB_GROUP_T || aItem->Type() == PCB_GENERATOR_T )
  2326. {
  2327. double combinedArea = 0.0;
  2328. for( BOARD_ITEM* member : static_cast<const PCB_GROUP*>( aItem )->GetBoardItems() )
  2329. combinedArea += GetCoverageArea( member, aCollector );
  2330. return combinedArea;
  2331. }
  2332. if( aItem->Type() == PCB_FOOTPRINT_T )
  2333. {
  2334. const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( aItem );
  2335. poly = footprint->GetBoundingHull();
  2336. }
  2337. else if( aItem->Type() == PCB_FIELD_T || aItem->Type() == PCB_TEXT_T )
  2338. {
  2339. const PCB_TEXT* text = static_cast<const PCB_TEXT*>( aItem );
  2340. text->TransformTextToPolySet( poly, textMargin, ARC_LOW_DEF, ERROR_INSIDE );
  2341. }
  2342. else if( aItem->Type() == PCB_TEXTBOX_T )
  2343. {
  2344. const PCB_TEXTBOX* tb = static_cast<const PCB_TEXTBOX*>( aItem );
  2345. tb->TransformTextToPolySet( poly, textMargin, ARC_LOW_DEF, ERROR_INSIDE );
  2346. }
  2347. else if( aItem->Type() == PCB_SHAPE_T )
  2348. {
  2349. // Approximate "linear" shapes with just their width squared, as we don't want to consider
  2350. // a linear shape as being much bigger than another for purposes of selection filtering
  2351. // just because it happens to be really long.
  2352. const PCB_SHAPE* shape = static_cast<const PCB_SHAPE*>( aItem );
  2353. switch( shape->GetShape() )
  2354. {
  2355. case SHAPE_T::SEGMENT:
  2356. case SHAPE_T::ARC:
  2357. case SHAPE_T::BEZIER:
  2358. return shape->GetWidth() * shape->GetWidth();
  2359. case SHAPE_T::RECTANGLE:
  2360. case SHAPE_T::CIRCLE:
  2361. case SHAPE_T::POLY:
  2362. {
  2363. if( !shape->IsAnyFill() )
  2364. return shape->GetWidth() * shape->GetWidth();
  2365. KI_FALLTHROUGH;
  2366. }
  2367. default:
  2368. shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  2369. }
  2370. }
  2371. else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T )
  2372. {
  2373. double width = static_cast<const PCB_TRACK*>( aItem )->GetWidth();
  2374. return width * width;
  2375. }
  2376. else if( aItem->Type() == PCB_PAD_T )
  2377. {
  2378. static_cast<const PAD*>( aItem )->Padstack().ForEachUniqueLayer(
  2379. [&]( PCB_LAYER_ID aLayer )
  2380. {
  2381. SHAPE_POLY_SET layerPoly;
  2382. aItem->TransformShapeToPolygon( layerPoly, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  2383. poly.BooleanAdd( layerPoly );
  2384. } );
  2385. }
  2386. else
  2387. {
  2388. aItem->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  2389. }
  2390. return polygonArea( poly );
  2391. }
  2392. double FOOTPRINT::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const
  2393. {
  2394. int textMargin = aCollector.GetGuide()->Accuracy();
  2395. SHAPE_POLY_SET footprintRegion( GetBoundingHull() );
  2396. SHAPE_POLY_SET coveredRegion;
  2397. TransformPadsToPolySet( coveredRegion, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE );
  2398. TransformFPShapesToPolySet( coveredRegion, UNDEFINED_LAYER, textMargin, ARC_LOW_DEF,
  2399. ERROR_OUTSIDE,
  2400. true, /* include text */
  2401. false, /* include shapes */
  2402. false /* include private items */ );
  2403. for( int i = 0; i < aCollector.GetCount(); ++i )
  2404. {
  2405. const BOARD_ITEM* item = aCollector[i];
  2406. switch( item->Type() )
  2407. {
  2408. case PCB_FIELD_T:
  2409. case PCB_TEXT_T:
  2410. case PCB_TEXTBOX_T:
  2411. case PCB_SHAPE_T:
  2412. case PCB_TRACE_T:
  2413. case PCB_ARC_T:
  2414. case PCB_VIA_T:
  2415. if( item->GetParent() != this )
  2416. {
  2417. item->TransformShapeToPolygon( coveredRegion, UNDEFINED_LAYER, 0, ARC_LOW_DEF,
  2418. ERROR_OUTSIDE );
  2419. }
  2420. break;
  2421. case PCB_FOOTPRINT_T:
  2422. if( item != this )
  2423. {
  2424. const FOOTPRINT* footprint = static_cast<const FOOTPRINT*>( item );
  2425. coveredRegion.AddOutline( footprint->GetBoundingHull().Outline( 0 ) );
  2426. }
  2427. break;
  2428. default:
  2429. break;
  2430. }
  2431. }
  2432. coveredRegion.BooleanIntersection( footprintRegion );
  2433. double footprintRegionArea = polygonArea( footprintRegion );
  2434. double uncoveredRegionArea = footprintRegionArea - polygonArea( coveredRegion );
  2435. double coveredArea = footprintRegionArea - uncoveredRegionArea;
  2436. // Avoid div-by-zero (this will result in the disambiguate dialog)
  2437. if( footprintRegionArea == 0 )
  2438. return 1.0;
  2439. double ratio = coveredArea / footprintRegionArea;
  2440. // Test for negative ratio (should not occur).
  2441. // better to be conservative (this will result in the disambiguate dialog)
  2442. if( ratio < 0.0 )
  2443. return 1.0;
  2444. return std::min( ratio, 1.0 );
  2445. }
  2446. std::shared_ptr<SHAPE> FOOTPRINT::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const
  2447. {
  2448. std::shared_ptr<SHAPE_COMPOUND> shape = std::make_shared<SHAPE_COMPOUND>();
  2449. // There are several possible interpretations here:
  2450. // 1) the bounding box (without or without invisible items)
  2451. // 2) just the pads and "edges" (ie: non-text graphic items)
  2452. // 3) the courtyard
  2453. // We'll go with (2) for now, unless the caller is clearly looking for (3)
  2454. if( aLayer == F_CrtYd || aLayer == B_CrtYd )
  2455. {
  2456. const SHAPE_POLY_SET& courtyard = GetCourtyard( aLayer );
  2457. if( courtyard.OutlineCount() == 0 ) // malformed/empty polygon
  2458. return shape;
  2459. shape->AddShape( new SHAPE_SIMPLE( courtyard.COutline( 0 ) ) );
  2460. }
  2461. else
  2462. {
  2463. for( PAD* pad : Pads() )
  2464. shape->AddShape( pad->GetEffectiveShape( aLayer, aFlash )->Clone() );
  2465. for( BOARD_ITEM* item : GraphicalItems() )
  2466. {
  2467. if( item->Type() == PCB_SHAPE_T )
  2468. shape->AddShape( item->GetEffectiveShape( aLayer, aFlash )->Clone() );
  2469. }
  2470. }
  2471. return shape;
  2472. }
  2473. const SHAPE_POLY_SET& FOOTPRINT::GetCourtyard( PCB_LAYER_ID aLayer ) const
  2474. {
  2475. std::lock_guard<std::mutex> lock( m_courtyard_cache_mutex );
  2476. if( m_courtyard_cache_front_hash != m_courtyard_cache_front.GetHash()
  2477. || m_courtyard_cache_back_hash != m_courtyard_cache_back.GetHash() )
  2478. {
  2479. const_cast<FOOTPRINT*>(this)->BuildCourtyardCaches();
  2480. }
  2481. return GetCachedCourtyard( aLayer );
  2482. }
  2483. const SHAPE_POLY_SET& FOOTPRINT::GetCachedCourtyard( PCB_LAYER_ID aLayer ) const
  2484. {
  2485. if( IsBackLayer( aLayer ) )
  2486. return m_courtyard_cache_back;
  2487. else
  2488. return m_courtyard_cache_front;
  2489. }
  2490. void FOOTPRINT::BuildCourtyardCaches( OUTLINE_ERROR_HANDLER* aErrorHandler )
  2491. {
  2492. m_courtyard_cache_front.RemoveAllContours();
  2493. m_courtyard_cache_back.RemoveAllContours();
  2494. ClearFlags( MALFORMED_COURTYARDS );
  2495. // Build the courtyard area from graphic items on the courtyard.
  2496. // Only PCB_SHAPE_T have meaning, graphic texts are ignored.
  2497. // Collect items:
  2498. std::vector<PCB_SHAPE*> list_front;
  2499. std::vector<PCB_SHAPE*> list_back;
  2500. std::map<int, int> front_width_histogram;
  2501. std::map<int, int> back_width_histogram;
  2502. for( BOARD_ITEM* item : GraphicalItems() )
  2503. {
  2504. if( item->GetLayer() == B_CrtYd && item->Type() == PCB_SHAPE_T )
  2505. {
  2506. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  2507. list_back.push_back( shape );
  2508. back_width_histogram[ shape->GetStroke().GetWidth() ]++;
  2509. }
  2510. if( item->GetLayer() == F_CrtYd && item->Type() == PCB_SHAPE_T )
  2511. {
  2512. PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  2513. list_front.push_back( shape );
  2514. front_width_histogram[ shape->GetStroke().GetWidth() ]++;
  2515. }
  2516. }
  2517. if( !list_front.size() && !list_back.size() )
  2518. return;
  2519. int maxError = pcbIUScale.mmToIU( 0.005 ); // max error for polygonization
  2520. int chainingEpsilon = pcbIUScale.mmToIU( 0.02 ); // max dist from one endPt to next startPt
  2521. if( ConvertOutlineToPolygon( list_front, m_courtyard_cache_front, maxError, chainingEpsilon,
  2522. true, aErrorHandler ) )
  2523. {
  2524. int width = 0;
  2525. // Touching courtyards, or courtyards -at- the clearance distance are legal.
  2526. // Use maxError here because that is the allowed deviation when transforming arcs/circles to
  2527. // polygons.
  2528. m_courtyard_cache_front.Inflate( -maxError, CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS, maxError );
  2529. m_courtyard_cache_front.CacheTriangulation( false );
  2530. auto max = std::max_element( front_width_histogram.begin(), front_width_histogram.end(),
  2531. []( const std::pair<int, int>& a, const std::pair<int, int>& b )
  2532. {
  2533. return a.second < b.second;
  2534. } );
  2535. if( max != front_width_histogram.end() )
  2536. width = max->first;
  2537. if( width == 0 )
  2538. width = pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH );
  2539. if( m_courtyard_cache_front.OutlineCount() > 0 )
  2540. m_courtyard_cache_front.Outline( 0 ).SetWidth( width );
  2541. }
  2542. else
  2543. {
  2544. SetFlags( MALFORMED_F_COURTYARD );
  2545. }
  2546. if( ConvertOutlineToPolygon( list_back, m_courtyard_cache_back, maxError, chainingEpsilon, true,
  2547. aErrorHandler ) )
  2548. {
  2549. int width = 0;
  2550. // Touching courtyards, or courtyards -at- the clearance distance are legal.
  2551. m_courtyard_cache_back.Inflate( -maxError, CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS, maxError );
  2552. m_courtyard_cache_back.CacheTriangulation( false );
  2553. auto max = std::max_element( back_width_histogram.begin(), back_width_histogram.end(),
  2554. []( const std::pair<int, int>& a, const std::pair<int, int>& b )
  2555. {
  2556. return a.second < b.second;
  2557. } );
  2558. if( max != back_width_histogram.end() )
  2559. width = max->first;
  2560. if( width == 0 )
  2561. width = pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH );
  2562. if( m_courtyard_cache_back.OutlineCount() > 0 )
  2563. m_courtyard_cache_back.Outline( 0 ).SetWidth( width );
  2564. }
  2565. else
  2566. {
  2567. SetFlags( MALFORMED_B_COURTYARD );
  2568. }
  2569. m_courtyard_cache_front_hash = m_courtyard_cache_front.GetHash();
  2570. m_courtyard_cache_back_hash = m_courtyard_cache_back.GetHash();
  2571. }
  2572. void FOOTPRINT::BuildNetTieCache()
  2573. {
  2574. m_netTieCache.clear();
  2575. std::map<wxString, int> map = MapPadNumbersToNetTieGroups();
  2576. std::map<PCB_LAYER_ID, std::vector<PCB_SHAPE*>> layer_shapes;
  2577. BOARD* board = GetBoard();
  2578. std::for_each( m_drawings.begin(), m_drawings.end(),
  2579. [&]( BOARD_ITEM* item )
  2580. {
  2581. if( item->Type() != PCB_SHAPE_T )
  2582. return;
  2583. for( PCB_LAYER_ID layer : item->GetLayerSet() )
  2584. {
  2585. if( !IsCopperLayer( layer ) )
  2586. continue;
  2587. if( board && !board->GetEnabledLayers().Contains( layer ) )
  2588. continue;
  2589. layer_shapes[layer].push_back( static_cast<PCB_SHAPE*>( item ) );
  2590. }
  2591. } );
  2592. for( size_t ii = 0; ii < m_pads.size(); ++ii )
  2593. {
  2594. PAD* pad = m_pads[ ii ];
  2595. bool has_nettie = false;
  2596. auto it = map.find( pad->GetNumber() );
  2597. if( it == map.end() || it->second < 0 )
  2598. continue;
  2599. for( size_t jj = ii + 1; jj < m_pads.size(); ++jj )
  2600. {
  2601. PAD* other = m_pads[ jj ];
  2602. auto it2 = map.find( other->GetNumber() );
  2603. if( it2 == map.end() || it2->second < 0 )
  2604. continue;
  2605. if( it2->second == it->second )
  2606. {
  2607. m_netTieCache[pad].insert( pad->GetNetCode() );
  2608. m_netTieCache[pad].insert( other->GetNetCode() );
  2609. m_netTieCache[other].insert( other->GetNetCode() );
  2610. m_netTieCache[other].insert( pad->GetNetCode() );
  2611. has_nettie = true;
  2612. }
  2613. }
  2614. if( !has_nettie )
  2615. continue;
  2616. for( auto& [ layer, shapes ] : layer_shapes )
  2617. {
  2618. auto pad_shape = pad->GetEffectiveShape( layer );
  2619. for( auto other_shape : shapes )
  2620. {
  2621. auto shape = other_shape->GetEffectiveShape( layer );
  2622. if( pad_shape->Collide( shape.get() ) )
  2623. {
  2624. std::set<int>& nettie = m_netTieCache[pad];
  2625. m_netTieCache[other_shape].insert( nettie.begin(), nettie.end() );
  2626. }
  2627. }
  2628. }
  2629. }
  2630. }
  2631. std::map<wxString, int> FOOTPRINT::MapPadNumbersToNetTieGroups() const
  2632. {
  2633. std::map<wxString, int> padNumberToGroupIdxMap;
  2634. for( const PAD* pad : m_pads )
  2635. padNumberToGroupIdxMap[ pad->GetNumber() ] = -1;
  2636. auto processPad =
  2637. [&]( wxString aPad, int aGroup )
  2638. {
  2639. aPad.Trim( true ).Trim( false );
  2640. if( !aPad.IsEmpty() )
  2641. padNumberToGroupIdxMap[ aPad ] = aGroup;
  2642. };
  2643. for( int ii = 0; ii < (int) m_netTiePadGroups.size(); ++ii )
  2644. {
  2645. wxString group( m_netTiePadGroups[ ii ] );
  2646. bool esc = false;
  2647. wxString pad;
  2648. for( wxUniCharRef ch : group )
  2649. {
  2650. if( esc )
  2651. {
  2652. esc = false;
  2653. pad.Append( ch );
  2654. continue;
  2655. }
  2656. switch( static_cast<unsigned char>( ch ) )
  2657. {
  2658. case '\\':
  2659. esc = true;
  2660. break;
  2661. case ',':
  2662. processPad( pad, ii );
  2663. pad.Clear();
  2664. break;
  2665. default:
  2666. pad.Append( ch );
  2667. break;
  2668. }
  2669. }
  2670. processPad( pad, ii );
  2671. }
  2672. return padNumberToGroupIdxMap;
  2673. }
  2674. std::vector<PAD*> FOOTPRINT::GetNetTiePads( PAD* aPad ) const
  2675. {
  2676. // First build a map from pad numbers to allowed-shorting-group indexes. This ends up being
  2677. // something like O(3n), but it still beats O(n^2) for large numbers of pads.
  2678. std::map<wxString, int> padToNetTieGroupMap = MapPadNumbersToNetTieGroups();
  2679. int groupIdx = padToNetTieGroupMap[ aPad->GetNumber() ];
  2680. std::vector<PAD*> otherPads;
  2681. if( groupIdx >= 0 )
  2682. {
  2683. for( PAD* pad : m_pads )
  2684. {
  2685. if( padToNetTieGroupMap[ pad->GetNumber() ] == groupIdx )
  2686. otherPads.push_back( pad );
  2687. }
  2688. }
  2689. return otherPads;
  2690. }
  2691. void FOOTPRINT::CheckFootprintAttributes( const std::function<void( const wxString& )>& aErrorHandler )
  2692. {
  2693. int likelyAttr = ( GetLikelyAttribute() & ( FP_SMD | FP_THROUGH_HOLE ) );
  2694. int setAttr = ( GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE ) );
  2695. if( setAttr && likelyAttr && setAttr != likelyAttr )
  2696. {
  2697. wxString msg;
  2698. switch( likelyAttr )
  2699. {
  2700. case FP_THROUGH_HOLE:
  2701. msg.Printf( _( "(expected 'Through hole'; actual '%s')" ), GetTypeName() );
  2702. break;
  2703. case FP_SMD:
  2704. msg.Printf( _( "(expected 'SMD'; actual '%s')" ), GetTypeName() );
  2705. break;
  2706. }
  2707. if( aErrorHandler )
  2708. (aErrorHandler)( msg );
  2709. }
  2710. }
  2711. void FOOTPRINT::CheckPads( UNITS_PROVIDER* aUnitsProvider,
  2712. const std::function<void( const PAD*, int,
  2713. const wxString& )>& aErrorHandler )
  2714. {
  2715. if( aErrorHandler == nullptr )
  2716. return;
  2717. for( PAD* pad: Pads() )
  2718. {
  2719. pad->CheckPad( aUnitsProvider, false,
  2720. [&]( int errorCode, const wxString& msg )
  2721. {
  2722. aErrorHandler( pad, errorCode, msg );
  2723. } );
  2724. }
  2725. }
  2726. void FOOTPRINT::CheckShortingPads( const std::function<void( const PAD*, const PAD*,
  2727. int aErrorCode,
  2728. const VECTOR2I& )>& aErrorHandler )
  2729. {
  2730. std::unordered_map<PTR_PTR_CACHE_KEY, int> checkedPairs;
  2731. for( PAD* pad : Pads() )
  2732. {
  2733. std::vector<PAD*> netTiePads = GetNetTiePads( pad );
  2734. for( PAD* other : Pads() )
  2735. {
  2736. if( other == pad )
  2737. continue;
  2738. // store canonical order so we don't collide in both directions (a:b and b:a)
  2739. PAD* a = pad;
  2740. PAD* b = other;
  2741. if( static_cast<void*>( a ) > static_cast<void*>( b ) )
  2742. std::swap( a, b );
  2743. if( checkedPairs.find( { a, b } ) == checkedPairs.end() )
  2744. {
  2745. checkedPairs[ { a, b } ] = 1;
  2746. if( pad->HasDrilledHole() && other->HasDrilledHole() )
  2747. {
  2748. VECTOR2I pos = pad->GetPosition();
  2749. if( pad->GetPosition() == other->GetPosition() )
  2750. {
  2751. aErrorHandler( pad, other, DRCE_DRILLED_HOLES_COLOCATED, pos );
  2752. }
  2753. else
  2754. {
  2755. std::shared_ptr<SHAPE_SEGMENT> holeA = pad->GetEffectiveHoleShape();
  2756. std::shared_ptr<SHAPE_SEGMENT> holeB = other->GetEffectiveHoleShape();
  2757. if( holeA->Collide( holeB->GetSeg(), 0 ) )
  2758. aErrorHandler( pad, other, DRCE_DRILLED_HOLES_TOO_CLOSE, pos );
  2759. }
  2760. }
  2761. if( pad->SameLogicalPadAs( other ) || alg::contains( netTiePads, other ) )
  2762. continue;
  2763. if( !( ( pad->GetLayerSet() & other->GetLayerSet() ) & LSET::AllCuMask() ).any() )
  2764. continue;
  2765. if( pad->GetBoundingBox().Intersects( other->GetBoundingBox() ) )
  2766. {
  2767. VECTOR2I pos;
  2768. for( PCB_LAYER_ID l : pad->Padstack().RelevantShapeLayers( other->Padstack() ) )
  2769. {
  2770. SHAPE* padShape = pad->GetEffectiveShape( l ).get();
  2771. SHAPE* otherShape = other->GetEffectiveShape( l ).get();
  2772. if( padShape->Collide( otherShape, 0, nullptr, &pos ) )
  2773. aErrorHandler( pad, other, DRCE_SHORTING_ITEMS, pos );
  2774. }
  2775. }
  2776. }
  2777. }
  2778. }
  2779. }
  2780. void FOOTPRINT::CheckNetTies( const std::function<void( const BOARD_ITEM* aItem,
  2781. const BOARD_ITEM* bItem,
  2782. const BOARD_ITEM* cItem,
  2783. const VECTOR2I& )>& aErrorHandler )
  2784. {
  2785. // First build a map from pad numbers to allowed-shorting-group indexes. This ends up being
  2786. // something like O(3n), but it still beats O(n^2) for large numbers of pads.
  2787. std::map<wxString, int> padNumberToGroupIdxMap = MapPadNumbersToNetTieGroups();
  2788. // Now collect all the footprint items which are on copper layers
  2789. std::vector<BOARD_ITEM*> copperItems;
  2790. for( BOARD_ITEM* item : m_drawings )
  2791. {
  2792. if( item->IsOnCopperLayer() )
  2793. copperItems.push_back( item );
  2794. item->RunOnChildren(
  2795. [&]( BOARD_ITEM* descendent )
  2796. {
  2797. if( descendent->IsOnCopperLayer() )
  2798. copperItems.push_back( descendent );
  2799. },
  2800. RECURSE_MODE::RECURSE );
  2801. }
  2802. for( ZONE* zone : m_zones )
  2803. {
  2804. if( !zone->GetIsRuleArea() && zone->IsOnCopperLayer() )
  2805. copperItems.push_back( zone );
  2806. }
  2807. for( PCB_FIELD* field : m_fields )
  2808. {
  2809. if( field->IsOnCopperLayer() )
  2810. copperItems.push_back( field );
  2811. }
  2812. for( PCB_LAYER_ID layer : { F_Cu, In1_Cu, B_Cu } )
  2813. {
  2814. // Next, build a polygon-set for the copper on this layer. We don't really care about
  2815. // nets here, we just want to end up with a set of outlines describing the distinct
  2816. // copper polygons of the footprint.
  2817. SHAPE_POLY_SET copperOutlines;
  2818. std::map<int, std::vector<const PAD*>> outlineIdxToPadsMap;
  2819. for( BOARD_ITEM* item : copperItems )
  2820. {
  2821. if( item->IsOnLayer( layer ) )
  2822. item->TransformShapeToPolygon( copperOutlines, layer, 0, GetMaxError(), ERROR_OUTSIDE );
  2823. }
  2824. copperOutlines.Simplify();
  2825. // Index each pad to the outline in the set that it is part of.
  2826. for( const PAD* pad : m_pads )
  2827. {
  2828. for( int ii = 0; ii < copperOutlines.OutlineCount(); ++ii )
  2829. {
  2830. if( pad->GetEffectiveShape( layer )->Collide( &copperOutlines.Outline( ii ), 0 ) )
  2831. outlineIdxToPadsMap[ ii ].emplace_back( pad );
  2832. }
  2833. }
  2834. // Finally, ensure that each outline which contains multiple pads has all its pads
  2835. // listed in an allowed-shorting group.
  2836. for( const auto& [ outlineIdx, pads ] : outlineIdxToPadsMap )
  2837. {
  2838. if( pads.size() > 1 )
  2839. {
  2840. const PAD* firstPad = pads[0];
  2841. int firstGroupIdx = padNumberToGroupIdxMap[ firstPad->GetNumber() ];
  2842. for( size_t ii = 1; ii < pads.size(); ++ii )
  2843. {
  2844. const PAD* thisPad = pads[ii];
  2845. int thisGroupIdx = padNumberToGroupIdxMap[ thisPad->GetNumber() ];
  2846. if( thisGroupIdx < 0 || thisGroupIdx != firstGroupIdx )
  2847. {
  2848. BOARD_ITEM* shortingItem = nullptr;
  2849. VECTOR2I pos = ( firstPad->GetPosition() + thisPad->GetPosition() ) / 2;
  2850. pos = copperOutlines.Outline( outlineIdx ).NearestPoint( pos );
  2851. for( BOARD_ITEM* item : copperItems )
  2852. {
  2853. if( item->HitTest( pos, 1 ) )
  2854. {
  2855. shortingItem = item;
  2856. break;
  2857. }
  2858. }
  2859. if( shortingItem )
  2860. aErrorHandler( shortingItem, firstPad, thisPad, pos );
  2861. else
  2862. aErrorHandler( firstPad, thisPad, nullptr, pos );
  2863. }
  2864. }
  2865. }
  2866. }
  2867. }
  2868. }
  2869. void FOOTPRINT::CheckNetTiePadGroups( const std::function<void( const wxString& )>& aErrorHandler )
  2870. {
  2871. std::set<wxString> padNumbers;
  2872. wxString msg;
  2873. for( const auto& [ padNumber, _ ] : MapPadNumbersToNetTieGroups() )
  2874. {
  2875. const PAD* pad = FindPadByNumber( padNumber );
  2876. if( !pad )
  2877. {
  2878. msg.Printf( _( "(net-tie pad group contains unknown pad number %s)" ), padNumber );
  2879. aErrorHandler( msg );
  2880. }
  2881. else if( !padNumbers.insert( pad->GetNumber() ).second )
  2882. {
  2883. msg.Printf( _( "(pad %s appears in more than one net-tie pad group)" ), padNumber );
  2884. aErrorHandler( msg );
  2885. }
  2886. }
  2887. }
  2888. void FOOTPRINT::CheckClippedSilk( const std::function<void( BOARD_ITEM* aItemA,
  2889. BOARD_ITEM* aItemB,
  2890. const VECTOR2I& aPt )>& aErrorHandler )
  2891. {
  2892. auto checkColliding =
  2893. [&]( BOARD_ITEM* item, BOARD_ITEM* other )
  2894. {
  2895. for( PCB_LAYER_ID silk : { F_SilkS, B_SilkS } )
  2896. {
  2897. PCB_LAYER_ID mask = silk == F_SilkS ? F_Mask : B_Mask;
  2898. if( !item->IsOnLayer( silk ) || !other->IsOnLayer( mask ) )
  2899. continue;
  2900. std::shared_ptr<SHAPE> itemShape = item->GetEffectiveShape( silk );
  2901. std::shared_ptr<SHAPE> otherShape = other->GetEffectiveShape( mask );
  2902. int actual;
  2903. VECTOR2I pos;
  2904. if( itemShape->Collide( otherShape.get(), 0, &actual, &pos ) )
  2905. aErrorHandler( item, other, pos );
  2906. }
  2907. };
  2908. for( BOARD_ITEM* item : m_drawings )
  2909. {
  2910. for( BOARD_ITEM* other : m_drawings )
  2911. {
  2912. if( other != item )
  2913. checkColliding( item, other );
  2914. }
  2915. for( PAD* pad : m_pads )
  2916. checkColliding( item, pad );
  2917. }
  2918. }
  2919. void FOOTPRINT::swapData( BOARD_ITEM* aImage )
  2920. {
  2921. wxASSERT( aImage->Type() == PCB_FOOTPRINT_T );
  2922. FOOTPRINT* image = static_cast<FOOTPRINT*>( aImage );
  2923. std::swap( *this, *image );
  2924. RunOnChildren(
  2925. [&]( BOARD_ITEM* child )
  2926. {
  2927. child->SetParent( this );
  2928. },
  2929. RECURSE_MODE::NO_RECURSE );
  2930. image->RunOnChildren(
  2931. [&]( BOARD_ITEM* child )
  2932. {
  2933. child->SetParent( image );
  2934. },
  2935. RECURSE_MODE::NO_RECURSE );
  2936. }
  2937. bool FOOTPRINT::HasThroughHolePads() const
  2938. {
  2939. for( PAD* pad : Pads() )
  2940. {
  2941. if( pad->GetAttribute() != PAD_ATTRIB::SMD )
  2942. return true;
  2943. }
  2944. return false;
  2945. }
  2946. bool FOOTPRINT::operator==( const BOARD_ITEM& aOther ) const
  2947. {
  2948. if( aOther.Type() != PCB_FOOTPRINT_T )
  2949. return false;
  2950. const FOOTPRINT& other = static_cast<const FOOTPRINT&>( aOther );
  2951. return *this == other;
  2952. }
  2953. bool FOOTPRINT::operator==( const FOOTPRINT& aOther ) const
  2954. {
  2955. if( m_pads.size() != aOther.m_pads.size() )
  2956. return false;
  2957. for( size_t ii = 0; ii < m_pads.size(); ++ii )
  2958. {
  2959. if( !( *m_pads[ii] == *aOther.m_pads[ii] ) )
  2960. return false;
  2961. }
  2962. if( m_drawings.size() != aOther.m_drawings.size() )
  2963. return false;
  2964. for( size_t ii = 0; ii < m_drawings.size(); ++ii )
  2965. {
  2966. if( !( *m_drawings[ii] == *aOther.m_drawings[ii] ) )
  2967. return false;
  2968. }
  2969. if( m_zones.size() != aOther.m_zones.size() )
  2970. return false;
  2971. for( size_t ii = 0; ii < m_zones.size(); ++ii )
  2972. {
  2973. if( !( *m_zones[ii] == *aOther.m_zones[ii] ) )
  2974. return false;
  2975. }
  2976. if( m_points.size() != aOther.m_points.size() )
  2977. return false;
  2978. // Compare fields in ordinally-sorted order
  2979. std::vector<PCB_FIELD*> fields, otherFields;
  2980. GetFields( fields, false );
  2981. aOther.GetFields( otherFields, false );
  2982. if( fields.size() != otherFields.size() )
  2983. return false;
  2984. for( size_t ii = 0; ii < fields.size(); ++ii )
  2985. {
  2986. if( fields[ii] )
  2987. {
  2988. if( !( *fields[ii] == *otherFields[ii] ) )
  2989. return false;
  2990. }
  2991. }
  2992. return true;
  2993. }
  2994. double FOOTPRINT::Similarity( const BOARD_ITEM& aOther ) const
  2995. {
  2996. if( aOther.Type() != PCB_FOOTPRINT_T )
  2997. return 0.0;
  2998. const FOOTPRINT& other = static_cast<const FOOTPRINT&>( aOther );
  2999. double similarity = 1.0;
  3000. for( const PAD* pad : m_pads)
  3001. {
  3002. const PAD* otherPad = other.FindPadByNumber( pad->GetNumber() );
  3003. if( !otherPad )
  3004. continue;
  3005. similarity *= pad->Similarity( *otherPad );
  3006. }
  3007. return similarity;
  3008. }
  3009. /**
  3010. * Compare two points, returning std::nullopt if they are identical.
  3011. */
  3012. static constexpr std::optional<bool> cmp_points_opt( const VECTOR2I& aPtA, const VECTOR2I& aPtB )
  3013. {
  3014. if( aPtA.x != aPtB.x )
  3015. return aPtA.x < aPtB.x;
  3016. if( aPtA.y != aPtB.y )
  3017. return aPtA.y < aPtB.y;
  3018. return std::nullopt;
  3019. }
  3020. bool FOOTPRINT::cmp_drawings::operator()( const BOARD_ITEM* itemA, const BOARD_ITEM* itemB ) const
  3021. {
  3022. if( itemA->Type() != itemB->Type() )
  3023. return itemA->Type() < itemB->Type();
  3024. if( itemA->GetLayer() != itemB->GetLayer() )
  3025. return itemA->GetLayer() < itemB->GetLayer();
  3026. switch( itemA->Type() )
  3027. {
  3028. case PCB_SHAPE_T:
  3029. {
  3030. const PCB_SHAPE* dwgA = static_cast<const PCB_SHAPE*>( itemA );
  3031. const PCB_SHAPE* dwgB = static_cast<const PCB_SHAPE*>( itemB );
  3032. if( dwgA->GetShape() != dwgB->GetShape() )
  3033. return dwgA->GetShape() < dwgB->GetShape();
  3034. // GetStart() and GetEnd() have no meaning with polygons.
  3035. // We cannot use them for sorting polygons
  3036. if( dwgA->GetShape() != SHAPE_T::POLY )
  3037. {
  3038. if( std::optional<bool> cmp = cmp_points_opt( dwgA->GetStart(), dwgB->GetStart() ) )
  3039. return *cmp;
  3040. if( std::optional<bool> cmp = cmp_points_opt( dwgA->GetEnd(), dwgB->GetEnd() ) )
  3041. return *cmp;
  3042. }
  3043. if( dwgA->GetShape() == SHAPE_T::ARC )
  3044. {
  3045. if( std::optional<bool> cmp = cmp_points_opt( dwgA->GetCenter(), dwgB->GetCenter() ) )
  3046. return *cmp;
  3047. }
  3048. else if( dwgA->GetShape() == SHAPE_T::BEZIER )
  3049. {
  3050. if( std::optional<bool> cmp = cmp_points_opt( dwgA->GetBezierC1(), dwgB->GetBezierC1() ) )
  3051. return *cmp;
  3052. if( std::optional<bool> cmp = cmp_points_opt( dwgA->GetBezierC2(), dwgB->GetBezierC2() ) )
  3053. return *cmp;
  3054. }
  3055. else if( dwgA->GetShape() == SHAPE_T::POLY )
  3056. {
  3057. if( dwgA->GetPolyShape().TotalVertices() != dwgB->GetPolyShape().TotalVertices() )
  3058. return dwgA->GetPolyShape().TotalVertices() < dwgB->GetPolyShape().TotalVertices();
  3059. for( int ii = 0; ii < dwgA->GetPolyShape().TotalVertices(); ++ii )
  3060. {
  3061. if( std::optional<bool> cmp =
  3062. cmp_points_opt( dwgA->GetPolyShape().CVertex( ii ), dwgB->GetPolyShape().CVertex( ii ) ) )
  3063. {
  3064. return *cmp;
  3065. }
  3066. }
  3067. }
  3068. if( dwgA->GetWidth() != dwgB->GetWidth() )
  3069. return dwgA->GetWidth() < dwgB->GetWidth();
  3070. break;
  3071. }
  3072. case PCB_TEXT_T:
  3073. {
  3074. const PCB_TEXT& textA = static_cast<const PCB_TEXT&>( *itemA );
  3075. const PCB_TEXT& textB = static_cast<const PCB_TEXT&>( *itemB );
  3076. if( std::optional<bool> cmp = cmp_points_opt( textA.GetPosition(), textB.GetPosition() ) )
  3077. return *cmp;
  3078. if( textA.GetTextAngle() != textB.GetTextAngle() )
  3079. return textA.GetTextAngle() < textB.GetTextAngle();
  3080. if( std::optional<bool> cmp = cmp_points_opt( textA.GetTextSize(), textB.GetTextSize() ) )
  3081. return *cmp;
  3082. if( textA.GetTextThickness() != textB.GetTextThickness() )
  3083. return textA.GetTextThickness() < textB.GetTextThickness();
  3084. if( textA.IsBold() != textB.IsBold() )
  3085. return textA.IsBold() < textB.IsBold();
  3086. if( textA.IsItalic() != textB.IsItalic() )
  3087. return textA.IsItalic() < (int) textB.IsItalic();
  3088. if( textA.IsMirrored() != textB.IsMirrored() )
  3089. return textA.IsMirrored() < textB.IsMirrored();
  3090. if( textA.GetLineSpacing() != textB.GetLineSpacing() )
  3091. return textA.GetLineSpacing() < textB.GetLineSpacing();
  3092. if( textA.GetText() != textB.GetText() )
  3093. return textA.GetText().Cmp( textB.GetText() ) < 0;
  3094. break;
  3095. }
  3096. default:
  3097. {
  3098. // These items don't have their own specific sorting criteria.
  3099. break;
  3100. }
  3101. }
  3102. if( itemA->m_Uuid != itemB->m_Uuid )
  3103. return itemA->m_Uuid < itemB->m_Uuid;
  3104. return itemA < itemB;
  3105. }
  3106. bool FOOTPRINT::cmp_pads::operator()( const PAD* aFirst, const PAD* aSecond ) const
  3107. {
  3108. if( aFirst->GetNumber() != aSecond->GetNumber() )
  3109. return StrNumCmp( aFirst->GetNumber(), aSecond->GetNumber() ) < 0;
  3110. if( std::optional<bool> cmp = cmp_points_opt( aFirst->GetFPRelativePosition(), aSecond->GetFPRelativePosition() ) )
  3111. return *cmp;
  3112. std::optional<bool> padCopperMatches;
  3113. // Pick the "most complex" padstack to iterate
  3114. const PAD* checkPad = aFirst;
  3115. if( aSecond->Padstack().Mode() == PADSTACK::MODE::CUSTOM
  3116. || ( aSecond->Padstack().Mode() == PADSTACK::MODE::FRONT_INNER_BACK &&
  3117. aFirst->Padstack().Mode() == PADSTACK::MODE::NORMAL ) )
  3118. {
  3119. checkPad = aSecond;
  3120. }
  3121. checkPad->Padstack().ForEachUniqueLayer(
  3122. [&]( PCB_LAYER_ID aLayer )
  3123. {
  3124. if( aFirst->GetSize( aLayer ).x != aSecond->GetSize( aLayer ).x )
  3125. padCopperMatches = aFirst->GetSize( aLayer ).x < aSecond->GetSize( aLayer ).x;
  3126. else if( aFirst->GetSize( aLayer ).y != aSecond->GetSize( aLayer ).y )
  3127. padCopperMatches = aFirst->GetSize( aLayer ).y < aSecond->GetSize( aLayer ).y;
  3128. else if( aFirst->GetShape( aLayer ) != aSecond->GetShape( aLayer ) )
  3129. padCopperMatches = aFirst->GetShape( aLayer ) < aSecond->GetShape( aLayer );
  3130. } );
  3131. if( padCopperMatches.has_value() )
  3132. return *padCopperMatches;
  3133. if( aFirst->GetLayerSet() != aSecond->GetLayerSet() )
  3134. return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq();
  3135. if( aFirst->m_Uuid != aSecond->m_Uuid )
  3136. return aFirst->m_Uuid < aSecond->m_Uuid;
  3137. return aFirst < aSecond;
  3138. }
  3139. #if 0
  3140. bool FOOTPRINT::cmp_padstack::operator()( const PAD* aFirst, const PAD* aSecond ) const
  3141. {
  3142. if( aFirst->GetSize().x != aSecond->GetSize().x )
  3143. return aFirst->GetSize().x < aSecond->GetSize().x;
  3144. if( aFirst->GetSize().y != aSecond->GetSize().y )
  3145. return aFirst->GetSize().y < aSecond->GetSize().y;
  3146. if( aFirst->GetShape() != aSecond->GetShape() )
  3147. return aFirst->GetShape() < aSecond->GetShape();
  3148. if( aFirst->GetLayerSet() != aSecond->GetLayerSet() )
  3149. return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq();
  3150. if( aFirst->GetDrillSizeX() != aSecond->GetDrillSizeX() )
  3151. return aFirst->GetDrillSizeX() < aSecond->GetDrillSizeX();
  3152. if( aFirst->GetDrillSizeY() != aSecond->GetDrillSizeY() )
  3153. return aFirst->GetDrillSizeY() < aSecond->GetDrillSizeY();
  3154. if( aFirst->GetDrillShape() != aSecond->GetDrillShape() )
  3155. return aFirst->GetDrillShape() < aSecond->GetDrillShape();
  3156. if( aFirst->GetAttribute() != aSecond->GetAttribute() )
  3157. return aFirst->GetAttribute() < aSecond->GetAttribute();
  3158. if( aFirst->GetOrientation() != aSecond->GetOrientation() )
  3159. return aFirst->GetOrientation() < aSecond->GetOrientation();
  3160. if( aFirst->GetSolderMaskExpansion() != aSecond->GetSolderMaskExpansion() )
  3161. return aFirst->GetSolderMaskExpansion() < aSecond->GetSolderMaskExpansion();
  3162. if( aFirst->GetSolderPasteMargin() != aSecond->GetSolderPasteMargin() )
  3163. return aFirst->GetSolderPasteMargin() < aSecond->GetSolderPasteMargin();
  3164. if( aFirst->GetLocalSolderMaskMargin() != aSecond->GetLocalSolderMaskMargin() )
  3165. return aFirst->GetLocalSolderMaskMargin() < aSecond->GetLocalSolderMaskMargin();
  3166. const std::shared_ptr<SHAPE_POLY_SET>& firstShape = aFirst->GetEffectivePolygon( ERROR_INSIDE );
  3167. const std::shared_ptr<SHAPE_POLY_SET>& secondShape = aSecond->GetEffectivePolygon( ERROR_INSIDE );
  3168. if( firstShape->VertexCount() != secondShape->VertexCount() )
  3169. return firstShape->VertexCount() < secondShape->VertexCount();
  3170. for( int ii = 0; ii < firstShape->VertexCount(); ++ii )
  3171. {
  3172. if( std::optional<bool> cmp = cmp_points_opt( firstShape->CVertex( ii ), secondShape->CVertex( ii ) ) )
  3173. {
  3174. return *cmp;
  3175. }
  3176. }
  3177. return false;
  3178. }
  3179. #endif
  3180. bool FOOTPRINT::cmp_zones::operator()( const ZONE* aFirst, const ZONE* aSecond ) const
  3181. {
  3182. if( aFirst->GetAssignedPriority() != aSecond->GetAssignedPriority() )
  3183. return aFirst->GetAssignedPriority() < aSecond->GetAssignedPriority();
  3184. if( aFirst->GetLayerSet() != aSecond->GetLayerSet() )
  3185. return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq();
  3186. if( aFirst->Outline()->TotalVertices() != aSecond->Outline()->TotalVertices() )
  3187. return aFirst->Outline()->TotalVertices() < aSecond->Outline()->TotalVertices();
  3188. for( int ii = 0; ii < aFirst->Outline()->TotalVertices(); ++ii )
  3189. {
  3190. if( std::optional<bool> cmp =
  3191. cmp_points_opt( aFirst->Outline()->CVertex( ii ), aSecond->Outline()->CVertex( ii ) ) )
  3192. {
  3193. return *cmp;
  3194. }
  3195. }
  3196. if( aFirst->m_Uuid != aSecond->m_Uuid )
  3197. return aFirst->m_Uuid < aSecond->m_Uuid;
  3198. return aFirst < aSecond;
  3199. }
  3200. void FOOTPRINT::TransformPadsToPolySet( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
  3201. int aMaxError, ERROR_LOC aErrorLoc ) const
  3202. {
  3203. auto processPad =
  3204. [&]( const PAD* pad, PCB_LAYER_ID padLayer )
  3205. {
  3206. VECTOR2I clearance( aClearance, aClearance );
  3207. switch( aLayer )
  3208. {
  3209. case F_Mask:
  3210. case B_Mask:
  3211. clearance.x += pad->GetSolderMaskExpansion( padLayer );
  3212. clearance.y += pad->GetSolderMaskExpansion( padLayer );
  3213. break;
  3214. case F_Paste:
  3215. case B_Paste:
  3216. clearance += pad->GetSolderPasteMargin( padLayer );
  3217. break;
  3218. default:
  3219. break;
  3220. }
  3221. // Our standard TransformShapeToPolygon() routines can't handle differing x:y clearance
  3222. // values (which get generated when a relative paste margin is used with an oblong pad).
  3223. // So we apply this huge hack and fake a larger pad to run the transform on.
  3224. // Of course being a hack it falls down when dealing with custom shape pads (where the
  3225. // size is only the size of the anchor), so for those we punt and just use clearance.x.
  3226. if( ( clearance.x < 0 || clearance.x != clearance.y )
  3227. && pad->GetShape( padLayer ) != PAD_SHAPE::CUSTOM )
  3228. {
  3229. VECTOR2I dummySize = pad->GetSize( padLayer ) + clearance + clearance;
  3230. if( dummySize.x <= 0 || dummySize.y <= 0 )
  3231. return;
  3232. PAD dummy( *pad );
  3233. dummy.SetSize( padLayer, dummySize );
  3234. dummy.TransformShapeToPolygon( aBuffer, padLayer, 0, aMaxError, aErrorLoc );
  3235. }
  3236. else
  3237. {
  3238. pad->TransformShapeToPolygon( aBuffer, padLayer, clearance.x, aMaxError, aErrorLoc );
  3239. }
  3240. };
  3241. for( const PAD* pad : m_pads )
  3242. {
  3243. if( !pad->FlashLayer( aLayer ) )
  3244. continue;
  3245. if( aLayer == UNDEFINED_LAYER )
  3246. {
  3247. pad->Padstack().ForEachUniqueLayer(
  3248. [&]( PCB_LAYER_ID l )
  3249. {
  3250. processPad( pad, l );
  3251. } );
  3252. }
  3253. else
  3254. {
  3255. processPad( pad, aLayer );
  3256. }
  3257. }
  3258. }
  3259. void FOOTPRINT::TransformFPShapesToPolySet( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance,
  3260. int aError, ERROR_LOC aErrorLoc, bool aIncludeText,
  3261. bool aIncludeShapes, bool aIncludePrivateItems ) const
  3262. {
  3263. for( BOARD_ITEM* item : GraphicalItems() )
  3264. {
  3265. if( GetPrivateLayers().test( item->GetLayer() ) && !aIncludePrivateItems )
  3266. continue;
  3267. if( item->Type() == PCB_TEXT_T && aIncludeText )
  3268. {
  3269. PCB_TEXT* text = static_cast<PCB_TEXT*>( item );
  3270. if( aLayer != UNDEFINED_LAYER && text->GetLayer() == aLayer )
  3271. text->TransformTextToPolySet( aBuffer, aClearance, aError, aErrorLoc );
  3272. }
  3273. if( item->Type() == PCB_TEXTBOX_T && aIncludeText )
  3274. {
  3275. PCB_TEXTBOX* textbox = static_cast<PCB_TEXTBOX*>( item );
  3276. if( aLayer != UNDEFINED_LAYER && textbox->GetLayer() == aLayer )
  3277. {
  3278. // border
  3279. if( textbox->IsBorderEnabled() )
  3280. textbox->PCB_SHAPE::TransformShapeToPolygon( aBuffer, aLayer, 0, aError, aErrorLoc );
  3281. // text
  3282. textbox->TransformTextToPolySet( aBuffer, 0, aError, aErrorLoc );
  3283. }
  3284. }
  3285. if( item->Type() == PCB_SHAPE_T && aIncludeShapes )
  3286. {
  3287. const PCB_SHAPE* shape = static_cast<PCB_SHAPE*>( item );
  3288. if( aLayer != UNDEFINED_LAYER && shape->GetLayer() == aLayer )
  3289. shape->TransformShapeToPolySet( aBuffer, aLayer, 0, aError, aErrorLoc );
  3290. }
  3291. }
  3292. if( aIncludeText )
  3293. {
  3294. for( const PCB_FIELD* field : m_fields )
  3295. {
  3296. if( field->GetLayer() == aLayer && field->IsVisible() )
  3297. field->TransformTextToPolySet( aBuffer, aClearance, aError, aErrorLoc );
  3298. }
  3299. }
  3300. }
  3301. std::set<KIFONT::OUTLINE_FONT*> FOOTPRINT::GetFonts() const
  3302. {
  3303. using PERMISSION = KIFONT::OUTLINE_FONT::EMBEDDING_PERMISSION;
  3304. std::set<KIFONT::OUTLINE_FONT*> fonts;
  3305. auto processItem =
  3306. [&]( BOARD_ITEM* item )
  3307. {
  3308. if( EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( item ) )
  3309. {
  3310. KIFONT::FONT* font = text->GetFont();
  3311. if( font && font->IsOutline() )
  3312. {
  3313. KIFONT::OUTLINE_FONT* outlineFont = static_cast<KIFONT::OUTLINE_FONT*>( font );
  3314. PERMISSION permission = outlineFont->GetEmbeddingPermission();
  3315. if( permission == PERMISSION::EDITABLE || permission == PERMISSION::INSTALLABLE )
  3316. fonts.insert( outlineFont );
  3317. }
  3318. }
  3319. };
  3320. for( BOARD_ITEM* item : GraphicalItems() )
  3321. processItem( item );
  3322. for( PCB_FIELD* field : GetFields() )
  3323. processItem( field );
  3324. return fonts;
  3325. }
  3326. void FOOTPRINT::EmbedFonts()
  3327. {
  3328. for( KIFONT::OUTLINE_FONT* font : GetFonts() )
  3329. {
  3330. EMBEDDED_FILES::EMBEDDED_FILE* file = GetEmbeddedFiles()->AddFile( font->GetFileName(), false );
  3331. file->type = EMBEDDED_FILES::EMBEDDED_FILE::FILE_TYPE::FONT;
  3332. }
  3333. }
  3334. void FOOTPRINT::SetStaticComponentClass( const COMPONENT_CLASS* aClass ) const
  3335. {
  3336. m_componentClassCacheProxy->SetStaticComponentClass( aClass );
  3337. }
  3338. const COMPONENT_CLASS* FOOTPRINT::GetStaticComponentClass() const
  3339. {
  3340. return m_componentClassCacheProxy->GetStaticComponentClass();
  3341. }
  3342. void FOOTPRINT::RecomputeComponentClass() const
  3343. {
  3344. m_componentClassCacheProxy->RecomputeComponentClass();
  3345. }
  3346. const COMPONENT_CLASS* FOOTPRINT::GetComponentClass() const
  3347. {
  3348. return m_componentClassCacheProxy->GetComponentClass();
  3349. }
  3350. wxString FOOTPRINT::GetComponentClassAsString() const
  3351. {
  3352. if( !m_componentClassCacheProxy->GetComponentClass()->IsEmpty() )
  3353. return m_componentClassCacheProxy->GetComponentClass()->GetName();
  3354. return wxEmptyString;
  3355. }
  3356. void FOOTPRINT::ResolveComponentClassNames( BOARD* aBoard,
  3357. const std::unordered_set<wxString>& aComponentClassNames )
  3358. {
  3359. const COMPONENT_CLASS* componentClass =
  3360. aBoard->GetComponentClassManager().GetEffectiveStaticComponentClass( aComponentClassNames );
  3361. SetStaticComponentClass( componentClass );
  3362. }
  3363. void FOOTPRINT::InvalidateComponentClassCache() const
  3364. {
  3365. m_componentClassCacheProxy->InvalidateCache();
  3366. }
  3367. void FOOTPRINT::SetStackupMode( FOOTPRINT_STACKUP aMode )
  3368. {
  3369. m_stackupMode = aMode;
  3370. if( m_stackupMode == FOOTPRINT_STACKUP::EXPAND_INNER_LAYERS )
  3371. {
  3372. // Reset the stackup layers to the default values
  3373. m_stackupLayers = LSET{ F_Cu, In1_Cu, B_Cu };
  3374. }
  3375. }
  3376. void FOOTPRINT::SetStackupLayers( LSET aLayers )
  3377. {
  3378. wxCHECK2( m_stackupMode == FOOTPRINT_STACKUP::CUSTOM_LAYERS, /*void*/ );
  3379. if( m_stackupMode == FOOTPRINT_STACKUP::CUSTOM_LAYERS )
  3380. m_stackupLayers = std::move( aLayers );
  3381. }
  3382. static struct FOOTPRINT_DESC
  3383. {
  3384. FOOTPRINT_DESC()
  3385. {
  3386. ENUM_MAP<ZONE_CONNECTION>& zcMap = ENUM_MAP<ZONE_CONNECTION>::Instance();
  3387. if( zcMap.Choices().GetCount() == 0 )
  3388. {
  3389. zcMap.Undefined( ZONE_CONNECTION::INHERITED );
  3390. zcMap.Map( ZONE_CONNECTION::INHERITED, _HKI( "Inherited" ) )
  3391. .Map( ZONE_CONNECTION::NONE, _HKI( "None" ) )
  3392. .Map( ZONE_CONNECTION::THERMAL, _HKI( "Thermal reliefs" ) )
  3393. .Map( ZONE_CONNECTION::FULL, _HKI( "Solid" ) )
  3394. .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) );
  3395. }
  3396. ENUM_MAP<PCB_LAYER_ID>& layerEnum = ENUM_MAP<PCB_LAYER_ID>::Instance();
  3397. if( layerEnum.Choices().GetCount() == 0 )
  3398. {
  3399. layerEnum.Undefined( UNDEFINED_LAYER );
  3400. for( PCB_LAYER_ID layer : LSET::AllLayersMask() )
  3401. layerEnum.Map( layer, LSET::Name( layer ) );
  3402. }
  3403. wxPGChoices fpLayers; // footprints might be placed only on F.Cu & B.Cu
  3404. fpLayers.Add( LSET::Name( F_Cu ), F_Cu );
  3405. fpLayers.Add( LSET::Name( B_Cu ), B_Cu );
  3406. PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance();
  3407. REGISTER_TYPE( FOOTPRINT );
  3408. propMgr.AddTypeCast( new TYPE_CAST<FOOTPRINT, BOARD_ITEM> );
  3409. propMgr.AddTypeCast( new TYPE_CAST<FOOTPRINT, BOARD_ITEM_CONTAINER> );
  3410. propMgr.InheritsAfter( TYPE_HASH( FOOTPRINT ), TYPE_HASH( BOARD_ITEM ) );
  3411. propMgr.InheritsAfter( TYPE_HASH( FOOTPRINT ), TYPE_HASH( BOARD_ITEM_CONTAINER ) );
  3412. auto layer = new PROPERTY_ENUM<FOOTPRINT, PCB_LAYER_ID>( _HKI( "Layer" ),
  3413. &FOOTPRINT::SetLayerAndFlip, &FOOTPRINT::GetLayer );
  3414. layer->SetChoices( fpLayers );
  3415. propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ), layer );
  3416. propMgr.AddProperty( new PROPERTY<FOOTPRINT, double>( _HKI( "Orientation" ),
  3417. &FOOTPRINT::SetOrientationDegrees, &FOOTPRINT::GetOrientationDegrees,
  3418. PROPERTY_DISPLAY::PT_DEGREE ) );
  3419. const wxString groupFields = _HKI( "Fields" );
  3420. propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Reference" ),
  3421. &FOOTPRINT::SetReference, &FOOTPRINT::GetReferenceAsString ),
  3422. groupFields );
  3423. propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Value" ),
  3424. &FOOTPRINT::SetValue, &FOOTPRINT::GetValueAsString ),
  3425. groupFields );
  3426. propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Library Link" ),
  3427. NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetFPIDAsString ),
  3428. groupFields );
  3429. propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Library Description" ),
  3430. NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetLibDescription ),
  3431. groupFields );
  3432. propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Keywords" ),
  3433. NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetKeywords ),
  3434. groupFields );
  3435. // Note: Also used by DRC engine
  3436. propMgr.AddProperty( new PROPERTY<FOOTPRINT, wxString>( _HKI( "Component Class" ),
  3437. NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetComponentClassAsString ),
  3438. groupFields )
  3439. .SetIsHiddenFromLibraryEditors();
  3440. const wxString groupAttributes = _HKI( "Attributes" );
  3441. propMgr.AddProperty( new PROPERTY<FOOTPRINT, bool>( _HKI( "Not in Schematic" ),
  3442. &FOOTPRINT::SetBoardOnly, &FOOTPRINT::IsBoardOnly ), groupAttributes );
  3443. propMgr.AddProperty( new PROPERTY<FOOTPRINT, bool>( _HKI( "Exclude From Position Files" ),
  3444. &FOOTPRINT::SetExcludedFromPosFiles, &FOOTPRINT::IsExcludedFromPosFiles ),
  3445. groupAttributes );
  3446. propMgr.AddProperty( new PROPERTY<FOOTPRINT, bool>( _HKI( "Exclude From Bill of Materials" ),
  3447. &FOOTPRINT::SetExcludedFromBOM, &FOOTPRINT::IsExcludedFromBOM ),
  3448. groupAttributes );
  3449. propMgr.AddProperty( new PROPERTY<FOOTPRINT, bool>( _HKI( "Do not Populate" ),
  3450. &FOOTPRINT::SetDNP, &FOOTPRINT::IsDNP ),
  3451. groupAttributes );
  3452. const wxString groupOverrides = _HKI( "Overrides" );
  3453. propMgr.AddProperty( new PROPERTY<FOOTPRINT, bool>( _HKI( "Exempt From Courtyard Requirement" ),
  3454. &FOOTPRINT::SetAllowMissingCourtyard, &FOOTPRINT::AllowMissingCourtyard ),
  3455. groupOverrides );
  3456. propMgr.AddProperty( new PROPERTY<FOOTPRINT, std::optional<int>>( _HKI( "Clearance Override" ),
  3457. &FOOTPRINT::SetLocalClearance, &FOOTPRINT::GetLocalClearance,
  3458. PROPERTY_DISPLAY::PT_SIZE ),
  3459. groupOverrides );
  3460. propMgr.AddProperty( new PROPERTY<FOOTPRINT, std::optional<int>>( _HKI( "Solderpaste Margin Override" ),
  3461. &FOOTPRINT::SetLocalSolderPasteMargin, &FOOTPRINT::GetLocalSolderPasteMargin,
  3462. PROPERTY_DISPLAY::PT_SIZE ),
  3463. groupOverrides );
  3464. propMgr.AddProperty( new PROPERTY<FOOTPRINT, std::optional<double>>( _HKI( "Solderpaste Margin Ratio Override" ),
  3465. &FOOTPRINT::SetLocalSolderPasteMarginRatio,
  3466. &FOOTPRINT::GetLocalSolderPasteMarginRatio,
  3467. PROPERTY_DISPLAY::PT_RATIO ),
  3468. groupOverrides );
  3469. propMgr.AddProperty( new PROPERTY_ENUM<FOOTPRINT, ZONE_CONNECTION>( _HKI( "Zone Connection Style" ),
  3470. &FOOTPRINT::SetLocalZoneConnection, &FOOTPRINT::GetLocalZoneConnection ),
  3471. groupOverrides );
  3472. }
  3473. } _FOOTPRINT_DESC;