/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 2015 SoftPLC Corporation, Dick Hollenbeck * Copyright (C) 2015 Wayne Stambaugh * Copyright The KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include FOOTPRINT::FOOTPRINT( BOARD* parent ) : BOARD_ITEM_CONTAINER( (BOARD_ITEM*) parent, PCB_FOOTPRINT_T ), m_orient( ANGLE_0 ), m_attributes( 0 ), m_fpStatus( FP_PADS_are_LOCKED ), m_fileFormatVersionAtLoad( 0 ), m_boundingBoxCacheTimeStamp( 0 ), m_textExcludedBBoxCacheTimeStamp( 0 ), m_hullCacheTimeStamp( 0 ), m_duplicatePadNumbersAreJumpers( false ), m_allowMissingCourtyard( false ), m_allowSolderMaskBridges( false ), m_zoneConnection( ZONE_CONNECTION::INHERITED ), m_lastEditTime( 0 ), m_arflag( 0 ), m_link( 0 ), m_initial_comments( nullptr ), m_componentClassCacheProxy( std::make_unique( this ) ) { m_layer = F_Cu; m_embedFonts = false; auto addField = [this]( FIELD_T id, PCB_LAYER_ID layer, bool visible ) { PCB_FIELD* field = new PCB_FIELD( this, id ); field->SetLayer( layer ); field->SetVisible( visible ); m_fields.push_back( field ); }; addField( FIELD_T::REFERENCE, F_SilkS, true ); addField( FIELD_T::VALUE, F_Fab, true ); addField( FIELD_T::DATASHEET, F_Fab, false ); addField( FIELD_T::DESCRIPTION, F_Fab, false ); m_3D_Drawings.clear(); } FOOTPRINT::FOOTPRINT( const FOOTPRINT& aFootprint ) : BOARD_ITEM_CONTAINER( aFootprint ), EMBEDDED_FILES( aFootprint ), m_componentClassCacheProxy( std::make_unique( this ) ) { m_orient = aFootprint.m_orient; m_pos = aFootprint.m_pos; m_fpid = aFootprint.m_fpid; m_attributes = aFootprint.m_attributes; m_fpStatus = aFootprint.m_fpStatus; m_fileFormatVersionAtLoad = aFootprint.m_fileFormatVersionAtLoad; m_cachedBoundingBox = aFootprint.m_cachedBoundingBox; m_boundingBoxCacheTimeStamp = aFootprint.m_boundingBoxCacheTimeStamp; m_cachedTextExcludedBBox = aFootprint.m_cachedTextExcludedBBox; m_textExcludedBBoxCacheTimeStamp = aFootprint.m_textExcludedBBoxCacheTimeStamp; m_cachedHull = aFootprint.m_cachedHull; m_hullCacheTimeStamp = aFootprint.m_hullCacheTimeStamp; m_netTiePadGroups = aFootprint.m_netTiePadGroups; std::ranges::copy( aFootprint.m_jumperPadGroups, std::inserter( m_jumperPadGroups, m_jumperPadGroups.end() ) ); m_duplicatePadNumbersAreJumpers = aFootprint.m_duplicatePadNumbersAreJumpers; m_allowMissingCourtyard = aFootprint.m_allowMissingCourtyard; m_allowSolderMaskBridges = aFootprint.m_allowSolderMaskBridges; m_zoneConnection = aFootprint.m_zoneConnection; m_clearance = aFootprint.m_clearance; m_solderMaskMargin = aFootprint.m_solderMaskMargin; m_solderPasteMargin = aFootprint.m_solderPasteMargin; m_solderPasteMarginRatio = aFootprint.m_solderPasteMarginRatio; m_libDescription = aFootprint.m_libDescription; m_keywords = aFootprint.m_keywords; m_path = aFootprint.m_path; m_sheetname = aFootprint.m_sheetname; m_sheetfile = aFootprint.m_sheetfile; m_filters = aFootprint.m_filters; m_lastEditTime = aFootprint.m_lastEditTime; m_arflag = 0; m_link = aFootprint.m_link; m_privateLayers = aFootprint.m_privateLayers; m_3D_Drawings = aFootprint.m_3D_Drawings; m_initial_comments = aFootprint.m_initial_comments ? new wxArrayString( *aFootprint.m_initial_comments ) : nullptr; m_embedFonts = aFootprint.m_embedFonts; std::map ptrMap; // Copy fields for( PCB_FIELD* field : aFootprint.m_fields ) { if( field->IsMandatory() ) { PCB_FIELD* existingField = GetField( field->GetId() ); ptrMap[field] = existingField; *existingField = *field; existingField->SetParent( this ); } else { PCB_FIELD* newField = static_cast( field->Clone() ); ptrMap[field] = newField; Add( newField ); } } // Copy pads for( PAD* pad : aFootprint.Pads() ) { PAD* newPad = static_cast( pad->Clone() ); ptrMap[ pad ] = newPad; Add( newPad, ADD_MODE::APPEND ); // Append to ensure indexes are identical } // Copy zones for( ZONE* zone : aFootprint.Zones() ) { ZONE* newZone = static_cast( zone->Clone() ); ptrMap[ zone ] = newZone; Add( newZone, ADD_MODE::APPEND ); // Append to ensure indexes are identical // Ensure the net info is OK and especially uses the net info list // living in the current board // Needed when copying a fp from fp editor that has its own board // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net. newZone->SetNetCode( -1 ); } // Copy drawings for( BOARD_ITEM* item : aFootprint.GraphicalItems() ) { BOARD_ITEM* newItem = static_cast( item->Clone() ); ptrMap[ item ] = newItem; Add( newItem, ADD_MODE::APPEND ); // Append to ensure indexes are identical } // Copy groups for( PCB_GROUP* group : aFootprint.Groups() ) { PCB_GROUP* newGroup = static_cast( group->Clone() ); ptrMap[ group ] = newGroup; Add( newGroup, ADD_MODE::APPEND ); // Append to ensure indexes are identical } // Rebuild groups for( PCB_GROUP* group : aFootprint.Groups() ) { PCB_GROUP* newGroup = static_cast( ptrMap[ group ] ); newGroup->GetItems().clear(); for( EDA_ITEM* member : group->GetItems() ) { if( ptrMap.count( member ) ) newGroup->AddItem( ptrMap[ member ] ); } } for( auto& [ name, file ] : aFootprint.EmbeddedFileMap() ) AddFile( new EMBEDDED_FILES::EMBEDDED_FILE( *file ) ); } FOOTPRINT::FOOTPRINT( FOOTPRINT&& aFootprint ) : BOARD_ITEM_CONTAINER( aFootprint ) { *this = std::move( aFootprint ); } FOOTPRINT::~FOOTPRINT() { // Clean up the owned elements delete m_initial_comments; for( PCB_FIELD* f : m_fields ) delete f; m_fields.clear(); for( PAD* p : m_pads ) delete p; m_pads.clear(); for( ZONE* zone : m_zones ) delete zone; m_zones.clear(); for( PCB_GROUP* group : m_groups ) delete group; m_groups.clear(); for( BOARD_ITEM* d : m_drawings ) delete d; m_drawings.clear(); if( BOARD* board = GetBoard() ) board->IncrementTimeStamp(); } void FOOTPRINT::Serialize( google::protobuf::Any &aContainer ) const { using namespace kiapi::board; types::FootprintInstance footprint; footprint.mutable_id()->set_value( m_Uuid.AsStdString() ); footprint.mutable_position()->set_x_nm( GetPosition().x ); footprint.mutable_position()->set_y_nm( GetPosition().y ); footprint.mutable_orientation()->set_value_degrees( GetOrientationDegrees() ); footprint.set_layer( ToProtoEnum( GetLayer() ) ); footprint.set_locked( IsLocked() ? kiapi::common::types::LockedState::LS_LOCKED : kiapi::common::types::LockedState::LS_UNLOCKED ); google::protobuf::Any buf; GetField( FIELD_T::REFERENCE )->Serialize( buf ); buf.UnpackTo( footprint.mutable_reference_field() ); GetField( FIELD_T::VALUE )->Serialize( buf ); buf.UnpackTo( footprint.mutable_value_field() ); GetField( FIELD_T::DATASHEET )->Serialize( buf ); buf.UnpackTo( footprint.mutable_datasheet_field() ); GetField( FIELD_T::DESCRIPTION )->Serialize( buf ); buf.UnpackTo( footprint.mutable_description_field() ); types::FootprintAttributes* attrs = footprint.mutable_attributes(); attrs->set_not_in_schematic( IsBoardOnly() ); attrs->set_exclude_from_position_files( IsExcludedFromPosFiles() ); attrs->set_exclude_from_bill_of_materials( IsExcludedFromBOM() ); attrs->set_exempt_from_courtyard_requirement( AllowMissingCourtyard() ); attrs->set_do_not_populate( IsDNP() ); if( m_attributes & FP_THROUGH_HOLE ) attrs->set_mounting_style( types::FootprintMountingStyle::FMS_THROUGH_HOLE ); else if( m_attributes & FP_SMD ) attrs->set_mounting_style( types::FootprintMountingStyle::FMS_SMD ); else attrs->set_mounting_style( types::FootprintMountingStyle::FMS_UNSPECIFIED ); types::Footprint* def = footprint.mutable_definition(); def->mutable_id()->CopyFrom( kiapi::common::LibIdToProto( GetFPID() ) ); // anchor? def->mutable_attributes()->set_description( GetLibDescription().ToStdString() ); def->mutable_attributes()->set_keywords( GetKeywords().ToStdString() ); // TODO: serialize library mandatory fields types::FootprintDesignRuleOverrides* overrides = def->mutable_overrides(); if( GetLocalClearance().has_value() ) overrides->mutable_copper_clearance()->set_value_nm( *GetLocalClearance() ); if( GetLocalSolderMaskMargin().has_value() ) overrides->mutable_solder_mask()->mutable_solder_mask_margin()->set_value_nm( *GetLocalSolderMaskMargin() ); if( GetLocalSolderPasteMargin().has_value() ) overrides->mutable_solder_paste()->mutable_solder_paste_margin()->set_value_nm( *GetLocalSolderPasteMargin() ); if( GetLocalSolderPasteMarginRatio().has_value() ) overrides->mutable_solder_paste()->mutable_solder_paste_margin_ratio()->set_value( *GetLocalSolderPasteMarginRatio() ); overrides->set_zone_connection( ToProtoEnum( GetLocalZoneConnection() ) ); for( const wxString& group : GetNetTiePadGroups() ) { types::NetTieDefinition* netTie = def->add_net_ties(); wxStringTokenizer tokenizer( group, " " ); while( tokenizer.HasMoreTokens() ) netTie->add_pad_number( tokenizer.GetNextToken().ToStdString() ); } for( PCB_LAYER_ID layer : GetPrivateLayers().Seq() ) def->add_private_layers( ToProtoEnum( layer ) ); for( const PCB_FIELD* item : m_fields ) { if( item->IsMandatory() ) continue; google::protobuf::Any* itemMsg = def->add_items(); item->Serialize( *itemMsg ); } for( const PAD* item : Pads() ) { google::protobuf::Any* itemMsg = def->add_items(); item->Serialize( *itemMsg ); } for( const BOARD_ITEM* item : GraphicalItems() ) { google::protobuf::Any* itemMsg = def->add_items(); item->Serialize( *itemMsg ); } for( const ZONE* item : Zones() ) { google::protobuf::Any* itemMsg = def->add_items(); item->Serialize( *itemMsg ); } for( const FP_3DMODEL& model : Models() ) { google::protobuf::Any* itemMsg = def->add_items(); types::Footprint3DModel modelMsg; modelMsg.set_filename( model.m_Filename.ToUTF8() ); kiapi::common::PackVector3D( *modelMsg.mutable_scale(), model.m_Scale ); kiapi::common::PackVector3D( *modelMsg.mutable_rotation(), model.m_Rotation ); kiapi::common::PackVector3D( *modelMsg.mutable_offset(), model.m_Offset ); modelMsg.set_visible( model.m_Show ); modelMsg.set_opacity( model.m_Opacity ); itemMsg->PackFrom( modelMsg ); } // Serialized only (can't modify this from the API to change the symbol mapping) kiapi::common::PackSheetPath( *footprint.mutable_symbol_path(), m_path ); aContainer.PackFrom( footprint ); } bool FOOTPRINT::Deserialize( const google::protobuf::Any &aContainer ) { using namespace kiapi::board; types::FootprintInstance footprint; if( !aContainer.UnpackTo( &footprint ) ) return false; const_cast( m_Uuid ) = KIID( footprint.id().value() ); SetPosition( VECTOR2I( footprint.position().x_nm(), footprint.position().y_nm() ) ); SetOrientationDegrees( footprint.orientation().value_degrees() ); SetLayer( FromProtoEnum( footprint.layer() ) ); SetLocked( footprint.locked() == kiapi::common::types::LockedState::LS_LOCKED ); google::protobuf::Any buf; types::Field mandatoryField; if( footprint.has_reference_field() ) { mandatoryField = footprint.reference_field(); mandatoryField.mutable_id()->set_id( (int) FIELD_T::REFERENCE ); buf.PackFrom( mandatoryField ); GetField( FIELD_T::REFERENCE )->Deserialize( buf ); } if( footprint.has_value_field() ) { mandatoryField = footprint.value_field(); mandatoryField.mutable_id()->set_id( (int) FIELD_T::VALUE ); buf.PackFrom( mandatoryField ); GetField( FIELD_T::VALUE )->Deserialize( buf ); } if( footprint.has_datasheet_field() ) { mandatoryField = footprint.datasheet_field(); mandatoryField.mutable_id()->set_id( (int) FIELD_T::DATASHEET ); buf.PackFrom( mandatoryField ); GetField( FIELD_T::DATASHEET )->Deserialize( buf ); } if( footprint.has_description_field() ) { mandatoryField = footprint.description_field(); mandatoryField.mutable_id()->set_id( (int) FIELD_T::DESCRIPTION ); buf.PackFrom( mandatoryField ); GetField( FIELD_T::DESCRIPTION )->Deserialize( buf ); } m_attributes = 0; switch( footprint.attributes().mounting_style() ) { case types::FootprintMountingStyle::FMS_THROUGH_HOLE: m_attributes |= FP_THROUGH_HOLE; break; case types::FootprintMountingStyle::FMS_SMD: m_attributes |= FP_SMD; break; default: break; } SetBoardOnly( footprint.attributes().not_in_schematic() ); SetExcludedFromBOM( footprint.attributes().exclude_from_bill_of_materials() ); SetExcludedFromPosFiles( footprint.attributes().exclude_from_position_files() ); SetAllowMissingCourtyard( footprint.attributes().exempt_from_courtyard_requirement() ); SetDNP( footprint.attributes().do_not_populate() ); // Definition SetFPID( kiapi::common::LibIdFromProto( footprint.definition().id() ) ); // TODO: how should anchor be handled? SetLibDescription( footprint.definition().attributes().description() ); SetKeywords( footprint.definition().attributes().keywords() ); const types::FootprintDesignRuleOverrides& overrides = footprint.overrides(); if( overrides.has_copper_clearance() ) SetLocalClearance( overrides.copper_clearance().value_nm() ); else SetLocalClearance( std::nullopt ); if( overrides.has_solder_mask() && overrides.solder_mask().has_solder_mask_margin() ) SetLocalSolderMaskMargin( overrides.solder_mask().solder_mask_margin().value_nm() ); else SetLocalSolderMaskMargin( std::nullopt ); if( overrides.has_solder_paste() ) { const types::SolderPasteOverrides& pasteSettings = overrides.solder_paste(); if( pasteSettings.has_solder_paste_margin() ) SetLocalSolderPasteMargin( pasteSettings.solder_paste_margin().value_nm() ); else SetLocalSolderPasteMargin( std::nullopt ); if( pasteSettings.has_solder_paste_margin_ratio() ) SetLocalSolderPasteMarginRatio( pasteSettings.solder_paste_margin_ratio().value() ); else SetLocalSolderPasteMarginRatio( std::nullopt ); } SetLocalZoneConnection( FromProtoEnum( overrides.zone_connection() ) ); for( const types::NetTieDefinition& netTieMsg : footprint.definition().net_ties() ) { wxString group; for( const std::string& pad : netTieMsg.pad_number() ) group.Append( wxString::Format( wxT( "%s " ), pad ) ); group.Trim(); AddNetTiePadGroup( group ); } LSET privateLayers; for( int layerMsg : footprint.definition().private_layers() ) { auto layer = FromProtoEnum( static_cast( layerMsg ) ); if( layer > UNDEFINED_LAYER ) privateLayers.set( layer ); } SetPrivateLayers( privateLayers ); // Footprint items for( PCB_FIELD* field : m_fields ) { if( !field->IsMandatory() ) Remove( field ); } Pads().clear(); GraphicalItems().clear(); Zones().clear(); Groups().clear(); Models().clear(); for( const google::protobuf::Any& itemMsg : footprint.definition().items() ) { std::optional type = kiapi::common::TypeNameFromAny( itemMsg ); if( !type ) { // Bit of a hack here, but eventually 3D models should be promoted to a first-class // object, at which point they can get their own serialization if( itemMsg.type_url() == "type.googleapis.com/kiapi.board.types.Footprint3DModel" ) { types::Footprint3DModel modelMsg; if( !itemMsg.UnpackTo( &modelMsg ) ) continue; FP_3DMODEL model; model.m_Filename = wxString::FromUTF8( modelMsg.filename() ); model.m_Show = modelMsg.visible(); model.m_Opacity = modelMsg.opacity(); model.m_Scale = kiapi::common::UnpackVector3D( modelMsg.scale() ); model.m_Rotation = kiapi::common::UnpackVector3D( modelMsg.rotation() ); model.m_Offset = kiapi::common::UnpackVector3D( modelMsg.offset() ); Models().push_back( std::move( model ) ); } else { wxLogTrace( traceApi, wxString::Format( wxS( "Attempting to unpack unknown type %s " "from footprint message, skipping" ), itemMsg.type_url() ) ); } continue; } std::unique_ptr item = CreateItemForType( *type, this ); if( item && item->Deserialize( itemMsg ) ) Add( item.release(), ADD_MODE::APPEND ); } return true; } PCB_FIELD* FOOTPRINT::GetField( FIELD_T aFieldType ) { for( PCB_FIELD* field : m_fields ) { if( field->GetId() == aFieldType ) return field; } PCB_FIELD* field = new PCB_FIELD( this, aFieldType ); m_fields.push_back( field ); return field; } const PCB_FIELD* FOOTPRINT::GetField( FIELD_T aFieldType ) const { for( const PCB_FIELD* field : m_fields ) { if( field->GetId() == aFieldType ) return field; } return nullptr; } bool FOOTPRINT::HasField( const wxString& aFieldName ) const { return GetField( aFieldName ) != nullptr; } PCB_FIELD* FOOTPRINT::GetField( const wxString& aFieldName ) const { for( PCB_FIELD* field : m_fields ) { if( field->GetName() == aFieldName ) return field; } return nullptr; } void FOOTPRINT::GetFields( std::vector& aVector, bool aVisibleOnly ) const { aVector.clear(); for( PCB_FIELD* field : m_fields ) { if( aVisibleOnly ) { if( !field->IsVisible() || field->GetText().IsEmpty() ) continue; } aVector.push_back( field ); } std::sort( aVector.begin(), aVector.end(), []( PCB_FIELD* lhs, PCB_FIELD* rhs ) { return lhs->GetOrdinal() < rhs->GetOrdinal(); } ); } int FOOTPRINT::GetNextFieldOrdinal() const { int ordinal = 42; // Arbitrarily larger than any mandatory FIELD_T id for( const PCB_FIELD* field : m_fields ) ordinal = std::max( ordinal, field->GetOrdinal() + 1 ); return ordinal; } void FOOTPRINT::ApplyDefaultSettings( const BOARD& board, bool aStyleFields, bool aStyleText, bool aStyleShapes ) { if( aStyleFields ) { for( PCB_FIELD* field : m_fields ) field->StyleFromSettings( board.GetDesignSettings() ); } for( BOARD_ITEM* item : m_drawings ) { switch( item->Type() ) { case PCB_TEXT_T: case PCB_TEXTBOX_T: if( aStyleText ) item->StyleFromSettings( board.GetDesignSettings() ); break; case PCB_SHAPE_T: if( aStyleShapes && !item->IsOnCopperLayer() ) item->StyleFromSettings( board.GetDesignSettings() ); break; default: break; } } } bool FOOTPRINT::FixUuids() { // replace null UUIDs if any by a valid uuid std::vector< BOARD_ITEM* > item_list; for( PCB_FIELD* field : m_fields ) item_list.push_back( field ); for( PAD* pad : m_pads ) item_list.push_back( pad ); for( BOARD_ITEM* gr_item : m_drawings ) item_list.push_back( gr_item ); // Note: one cannot fix null UUIDs inside the group, but it should not happen // because null uuids can be found in old footprints, therefore without group for( PCB_GROUP* group : m_groups ) item_list.push_back( group ); // Probably notneeded, because old fp do not have zones. But just in case. for( ZONE* zone : m_zones ) item_list.push_back( zone ); bool changed = false; for( BOARD_ITEM* item : item_list ) { if( item->m_Uuid == niluuid ) { const_cast( item->m_Uuid ) = KIID(); changed = true; } } return changed; } FOOTPRINT& FOOTPRINT::operator=( FOOTPRINT&& aOther ) { BOARD_ITEM::operator=( aOther ); m_pos = aOther.m_pos; m_fpid = aOther.m_fpid; m_attributes = aOther.m_attributes; m_fpStatus = aOther.m_fpStatus; m_orient = aOther.m_orient; m_lastEditTime = aOther.m_lastEditTime; m_link = aOther.m_link; m_path = aOther.m_path; m_cachedBoundingBox = aOther.m_cachedBoundingBox; m_boundingBoxCacheTimeStamp = aOther.m_boundingBoxCacheTimeStamp; m_cachedTextExcludedBBox = aOther.m_cachedTextExcludedBBox; m_textExcludedBBoxCacheTimeStamp = aOther.m_textExcludedBBoxCacheTimeStamp; m_cachedHull = aOther.m_cachedHull; m_hullCacheTimeStamp = aOther.m_hullCacheTimeStamp; m_clearance = aOther.m_clearance; m_solderMaskMargin = aOther.m_solderMaskMargin; m_solderPasteMargin = aOther.m_solderPasteMargin; m_solderPasteMarginRatio = aOther.m_solderPasteMarginRatio; m_zoneConnection = aOther.m_zoneConnection; m_netTiePadGroups = aOther.m_netTiePadGroups; m_duplicatePadNumbersAreJumpers = aOther.m_duplicatePadNumbersAreJumpers; std::ranges::copy( aOther.m_jumperPadGroups, std::inserter( m_jumperPadGroups, m_jumperPadGroups.end() ) ); // Move the fields for( PCB_FIELD* field : m_fields ) delete field; m_fields.clear(); for( PCB_FIELD* field : aOther.m_fields ) Add( field ); aOther.m_fields.clear(); // Move the pads for( PAD* pad : m_pads ) delete pad; m_pads.clear(); for( PAD* pad : aOther.Pads() ) Add( pad ); aOther.Pads().clear(); // Move the zones for( ZONE* zone : m_zones ) delete zone; m_zones.clear(); for( ZONE* item : aOther.Zones() ) { Add( item ); // Ensure the net info is OK and especially uses the net info list // living in the current board // Needed when copying a fp from fp editor that has its own board // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net. item->SetNetCode( -1 ); } aOther.Zones().clear(); // Move the drawings for( BOARD_ITEM* item : m_drawings ) delete item; m_drawings.clear(); for( BOARD_ITEM* item : aOther.GraphicalItems() ) Add( item ); aOther.GraphicalItems().clear(); // Move the groups for( PCB_GROUP* group : m_groups ) delete group; m_groups.clear(); for( PCB_GROUP* group : aOther.Groups() ) Add( group ); aOther.Groups().clear(); EMBEDDED_FILES::operator=( std::move( aOther ) ); // Copy auxiliary data m_3D_Drawings = aOther.m_3D_Drawings; m_libDescription = aOther.m_libDescription; m_keywords = aOther.m_keywords; m_privateLayers = aOther.m_privateLayers; m_initial_comments = aOther.m_initial_comments; // Clear the other item's containers since this is a move aOther.m_fields.clear(); aOther.Pads().clear(); aOther.Zones().clear(); aOther.GraphicalItems().clear(); aOther.m_initial_comments = nullptr; return *this; } FOOTPRINT& FOOTPRINT::operator=( const FOOTPRINT& aOther ) { BOARD_ITEM::operator=( aOther ); m_pos = aOther.m_pos; m_fpid = aOther.m_fpid; m_attributes = aOther.m_attributes; m_fpStatus = aOther.m_fpStatus; m_orient = aOther.m_orient; m_lastEditTime = aOther.m_lastEditTime; m_link = aOther.m_link; m_path = aOther.m_path; m_cachedBoundingBox = aOther.m_cachedBoundingBox; m_boundingBoxCacheTimeStamp = aOther.m_boundingBoxCacheTimeStamp; m_cachedTextExcludedBBox = aOther.m_cachedTextExcludedBBox; m_textExcludedBBoxCacheTimeStamp = aOther.m_textExcludedBBoxCacheTimeStamp; m_cachedHull = aOther.m_cachedHull; m_hullCacheTimeStamp = aOther.m_hullCacheTimeStamp; m_clearance = aOther.m_clearance; m_solderMaskMargin = aOther.m_solderMaskMargin; m_solderPasteMargin = aOther.m_solderPasteMargin; m_solderPasteMarginRatio = aOther.m_solderPasteMarginRatio; m_zoneConnection = aOther.m_zoneConnection; m_netTiePadGroups = aOther.m_netTiePadGroups; std::map ptrMap; // Copy fields m_fields.clear(); for( PCB_FIELD* field : aOther.m_fields ) { PCB_FIELD* newField = new PCB_FIELD( *field ); ptrMap[field] = newField; Add( newField ); } // Copy pads m_pads.clear(); for( PAD* pad : aOther.Pads() ) { PAD* newPad = new PAD( *pad ); ptrMap[ pad ] = newPad; Add( newPad ); } // Copy zones m_zones.clear(); for( ZONE* zone : aOther.Zones() ) { ZONE* newZone = static_cast( zone->Clone() ); ptrMap[ zone ] = newZone; Add( newZone ); // Ensure the net info is OK and especially uses the net info list // living in the current board // Needed when copying a fp from fp editor that has its own board // Must be NETINFO_LIST::ORPHANED_ITEM for a keepout that has no net. newZone->SetNetCode( -1 ); } // Copy drawings m_drawings.clear(); for( BOARD_ITEM* item : aOther.GraphicalItems() ) { BOARD_ITEM* newItem = static_cast( item->Clone() ); ptrMap[ item ] = newItem; Add( newItem ); } // Copy groups m_groups.clear(); for( PCB_GROUP* group : aOther.Groups() ) { PCB_GROUP* newGroup = static_cast( group->Clone() ); newGroup->GetItems().clear(); for( EDA_ITEM* member : group->GetItems() ) newGroup->AddItem( ptrMap[ member ] ); Add( newGroup ); } // Copy auxiliary data m_3D_Drawings = aOther.m_3D_Drawings; m_libDescription = aOther.m_libDescription; m_keywords = aOther.m_keywords; m_privateLayers = aOther.m_privateLayers; m_initial_comments = aOther.m_initial_comments ? new wxArrayString( *aOther.m_initial_comments ) : nullptr; EMBEDDED_FILES::operator=( aOther ); return *this; } void FOOTPRINT::CopyFrom( const BOARD_ITEM* aOther ) { wxCHECK( aOther && aOther->Type() == PCB_FOOTPRINT_T, /* void */ ); *this = *static_cast( aOther ); for( PAD* pad : m_pads ) pad->SetDirty(); } void FOOTPRINT::InvalidateGeometryCaches() { m_boundingBoxCacheTimeStamp = 0; m_textExcludedBBoxCacheTimeStamp = 0; m_hullCacheTimeStamp = 0; m_courtyard_cache_back_hash.Clear(); m_courtyard_cache_front_hash.Clear(); } bool FOOTPRINT::IsConflicting() const { return HasFlag( COURTYARD_CONFLICT ); } void FOOTPRINT::GetContextualTextVars( wxArrayString* aVars ) const { aVars->push_back( wxT( "REFERENCE" ) ); aVars->push_back( wxT( "VALUE" ) ); aVars->push_back( wxT( "LAYER" ) ); aVars->push_back( wxT( "FOOTPRINT_LIBRARY" ) ); aVars->push_back( wxT( "FOOTPRINT_NAME" ) ); aVars->push_back( wxT( "SHORT_NET_NAME()" ) ); aVars->push_back( wxT( "NET_NAME()" ) ); aVars->push_back( wxT( "NET_CLASS()" ) ); aVars->push_back( wxT( "PIN_NAME()" ) ); } bool FOOTPRINT::ResolveTextVar( wxString* token, int aDepth ) const { if( GetBoard() && GetBoard()->GetBoardUse() == BOARD_USE::FPHOLDER ) return false; if( token->IsSameAs( wxT( "REFERENCE" ) ) ) { *token = Reference().GetShownText( false, aDepth + 1 ); return true; } else if( token->IsSameAs( wxT( "VALUE" ) ) ) { *token = Value().GetShownText( false, aDepth + 1 ); return true; } else if( token->IsSameAs( wxT( "LAYER" ) ) ) { *token = GetLayerName(); return true; } else if( token->IsSameAs( wxT( "FOOTPRINT_LIBRARY" ) ) ) { *token = m_fpid.GetUniStringLibNickname(); return true; } else if( token->IsSameAs( wxT( "FOOTPRINT_NAME" ) ) ) { *token = m_fpid.GetUniStringLibItemName(); return true; } else if( token->StartsWith( wxT( "SHORT_NET_NAME(" ) ) || token->StartsWith( wxT( "NET_NAME(" ) ) || token->StartsWith( wxT( "NET_CLASS(" ) ) || token->StartsWith( wxT( "PIN_NAME(" ) ) ) { wxString padNumber = token->AfterFirst( '(' ); padNumber = padNumber.BeforeLast( ')' ); for( PAD* pad : Pads() ) { if( pad->GetNumber() == padNumber ) { if( token->StartsWith( wxT( "SHORT_NET_NAME" ) ) ) *token = pad->GetShortNetname(); else if( token->StartsWith( wxT( "NET_NAME" ) ) ) *token = pad->GetNetname(); else if( token->StartsWith( wxT( "NET_CLASS" ) ) ) *token = pad->GetNetClassName(); else *token = pad->GetPinFunction(); return true; } } } else if( PCB_FIELD* field = GetField( *token ) ) { *token = field->GetText(); return true; } if( GetBoard() && GetBoard()->ResolveTextVar( token, aDepth + 1 ) ) return true; return false; } void FOOTPRINT::ClearAllNets() { // Force the ORPHANED dummy net info for all pads. // ORPHANED dummy net does not depend on a board for( PAD* pad : m_pads ) pad->SetNetCode( NETINFO_LIST::ORPHANED ); } void FOOTPRINT::Add( BOARD_ITEM* aBoardItem, ADD_MODE aMode, bool aSkipConnectivity ) { switch( aBoardItem->Type() ) { case PCB_FIELD_T: m_fields.push_back( static_cast( aBoardItem ) ); break; case PCB_TEXT_T: case PCB_DIM_ALIGNED_T: case PCB_DIM_LEADER_T: case PCB_DIM_CENTER_T: case PCB_DIM_RADIAL_T: case PCB_DIM_ORTHOGONAL_T: case PCB_SHAPE_T: case PCB_TEXTBOX_T: case PCB_TABLE_T: case PCB_REFERENCE_IMAGE_T: if( aMode == ADD_MODE::APPEND ) m_drawings.push_back( aBoardItem ); else m_drawings.push_front( aBoardItem ); break; case PCB_PAD_T: if( aMode == ADD_MODE::APPEND ) m_pads.push_back( static_cast( aBoardItem ) ); else m_pads.push_front( static_cast( aBoardItem ) ); break; case PCB_ZONE_T: if( aMode == ADD_MODE::APPEND ) m_zones.push_back( static_cast( aBoardItem ) ); else m_zones.insert( m_zones.begin(), static_cast( aBoardItem ) ); break; case PCB_GROUP_T: if( aMode == ADD_MODE::APPEND ) m_groups.push_back( static_cast( aBoardItem ) ); else m_groups.insert( m_groups.begin(), static_cast( aBoardItem ) ); break; case PCB_MARKER_T: wxFAIL_MSG( wxT( "FOOTPRINT::Add(): Markers go at the board level, even in the footprint editor" ) ); return; case PCB_FOOTPRINT_T: wxFAIL_MSG( wxT( "FOOTPRINT::Add(): Nested footprints not supported" ) ); return; default: wxFAIL_MSG( wxString::Format( wxT( "FOOTPRINT::Add(): BOARD_ITEM type (%d) not handled" ), aBoardItem->Type() ) ); return; } aBoardItem->ClearEditFlags(); aBoardItem->SetParent( this ); } void FOOTPRINT::Remove( BOARD_ITEM* aBoardItem, REMOVE_MODE aMode ) { switch( aBoardItem->Type() ) { case PCB_FIELD_T: for( auto it = m_fields.begin(); it != m_fields.end(); ++it ) { if( *it == aBoardItem ) { m_fields.erase( it ); break; } } break; case PCB_TEXT_T: case PCB_DIM_ALIGNED_T: case PCB_DIM_CENTER_T: case PCB_DIM_ORTHOGONAL_T: case PCB_DIM_RADIAL_T: case PCB_DIM_LEADER_T: case PCB_SHAPE_T: case PCB_TEXTBOX_T: case PCB_TABLE_T: case PCB_REFERENCE_IMAGE_T: for( auto it = m_drawings.begin(); it != m_drawings.end(); ++it ) { if( *it == aBoardItem ) { m_drawings.erase( it ); break; } } break; case PCB_PAD_T: for( auto it = m_pads.begin(); it != m_pads.end(); ++it ) { if( *it == static_cast( aBoardItem ) ) { m_pads.erase( it ); break; } } break; case PCB_ZONE_T: for( auto it = m_zones.begin(); it != m_zones.end(); ++it ) { if( *it == static_cast( aBoardItem ) ) { m_zones.erase( it ); break; } } break; case PCB_GROUP_T: for( auto it = m_groups.begin(); it != m_groups.end(); ++it ) { if( *it == static_cast( aBoardItem ) ) { m_groups.erase( it ); break; } } break; default: { wxString msg; msg.Printf( wxT( "FOOTPRINT::Remove() needs work: BOARD_ITEM type (%d) not handled" ), aBoardItem->Type() ); wxFAIL_MSG( msg ); } } aBoardItem->SetFlags( STRUCT_DELETED ); } double FOOTPRINT::GetArea( int aPadding ) const { BOX2I bbox = GetBoundingBox( false ); double w = std::abs( static_cast( bbox.GetWidth() ) ) + aPadding; double h = std::abs( static_cast( bbox.GetHeight() ) ) + aPadding; return w * h; } int FOOTPRINT::GetLikelyAttribute() const { int smd_count = 0; int tht_count = 0; for( PAD* pad : m_pads ) { switch( pad->GetProperty() ) { case PAD_PROP::FIDUCIAL_GLBL: case PAD_PROP::FIDUCIAL_LOCAL: continue; case PAD_PROP::HEATSINK: case PAD_PROP::CASTELLATED: case PAD_PROP::MECHANICAL: continue; case PAD_PROP::NONE: case PAD_PROP::BGA: case PAD_PROP::TESTPOINT: case PAD_PROP::PRESSFIT: break; } switch( pad->GetAttribute() ) { case PAD_ATTRIB::PTH: tht_count++; break; case PAD_ATTRIB::SMD: if( pad->IsOnCopperLayer() ) smd_count++; break; default: break; } } // Footprints with plated through-hole pads should usually be marked through hole even if they // also have SMD because they might not be auto-placed. Exceptions to this might be shielded if( tht_count > 0 ) return FP_THROUGH_HOLE; if( smd_count > 0 ) return FP_SMD; return 0; } wxString FOOTPRINT::GetTypeName() const { if( ( m_attributes & FP_SMD ) == FP_SMD ) return _( "SMD" ); if( ( m_attributes & FP_THROUGH_HOLE ) == FP_THROUGH_HOLE ) return _( "Through hole" ); return _( "Other" ); } BOX2I FOOTPRINT::GetFpPadsLocalBbox() const { BOX2I bbox; // We want the bounding box of the footprint pads at rot 0, not flipped // Create such a image: FOOTPRINT dummy( *this ); dummy.SetPosition( VECTOR2I( 0, 0 ) ); dummy.SetOrientation( ANGLE_0 ); if( dummy.IsFlipped() ) dummy.Flip( VECTOR2I( 0, 0 ), FLIP_DIRECTION::TOP_BOTTOM ); for( PAD* pad : dummy.Pads() ) bbox.Merge( pad->GetBoundingBox() ); return bbox; } bool FOOTPRINT::TextOnly() const { for( BOARD_ITEM* item : m_drawings ) { if( m_privateLayers.test( item->GetLayer() ) ) continue; if( item->Type() != PCB_FIELD_T && item->Type() != PCB_TEXT_T ) return false; } return true; } const BOX2I FOOTPRINT::GetBoundingBox() const { return GetBoundingBox( true ); } const BOX2I FOOTPRINT::GetBoundingBox( bool aIncludeText ) const { const BOARD* board = GetBoard(); if( board ) { if( aIncludeText ) { if( m_boundingBoxCacheTimeStamp >= board->GetTimeStamp() ) return m_cachedBoundingBox; } else { if( m_textExcludedBBoxCacheTimeStamp >= board->GetTimeStamp() ) return m_cachedTextExcludedBBox; } } std::vector texts; bool isFPEdit = board && board->IsFootprintHolder(); BOX2I bbox( m_pos ); bbox.Inflate( pcbIUScale.mmToIU( 0.25 ) ); // Give a min size to the bbox // Calculate the footprint side PCB_LAYER_ID footprintSide = GetSide(); for( BOARD_ITEM* item : m_drawings ) { if( m_privateLayers.test( item->GetLayer() ) && !isFPEdit ) continue; // We want the bitmap bounding box just in the footprint editor // so it will start with the correct initial zoom if( item->Type() == PCB_REFERENCE_IMAGE_T && !isFPEdit ) continue; // Handle text separately if( item->Type() == PCB_TEXT_T ) { texts.push_back( static_cast( item ) ); continue; } // If we're not including text then drop annotations as well -- unless, of course, it's // an unsided footprint -- in which case it's likely to be nothing *but* annotations. if( !aIncludeText && footprintSide != UNDEFINED_LAYER ) { if( BaseType( item->Type() ) == PCB_DIMENSION_T ) continue; if( item->GetLayer() == Cmts_User || item->GetLayer() == Dwgs_User || item->GetLayer() == Eco1_User || item->GetLayer() == Eco2_User ) { continue; } } bbox.Merge( item->GetBoundingBox() ); } for( PCB_FIELD* field : m_fields ) { // Reference and value get their own processing if( field->IsReference() || field->IsValue() ) continue; texts.push_back( field ); } for( PAD* pad : m_pads ) bbox.Merge( pad->GetBoundingBox() ); for( ZONE* zone : m_zones ) bbox.Merge( zone->GetBoundingBox() ); bool noDrawItems = ( m_drawings.empty() && m_pads.empty() && m_zones.empty() ); // Groups do not contribute to the rect, only their members if( aIncludeText || noDrawItems ) { // Only PCB_TEXT and PCB_FIELD items are independently selectable; PCB_TEXTBOX items go // in with other graphic items above. for( PCB_TEXT* text : texts ) { if( !isFPEdit && m_privateLayers.test( text->GetLayer() ) ) continue; if( text->Type() == PCB_FIELD_T && !text->IsVisible() ) continue; bbox.Merge( text->GetBoundingBox() ); } // This can be further optimized when aIncludeInvisibleText is true, but currently // leaving this as is until it's determined there is a noticeable speed hit. bool valueLayerIsVisible = true; bool refLayerIsVisible = true; if( board ) { // The first "&&" conditional handles the user turning layers off as well as layers // not being present in the current PCB stackup. Values, references, and all // footprint text can also be turned off via the GAL meta-layers, so the 2nd and // 3rd "&&" conditionals handle that. valueLayerIsVisible = board->IsLayerVisible( Value().GetLayer() ) && board->IsElementVisible( LAYER_FP_VALUES ) && board->IsElementVisible( LAYER_FP_TEXT ); refLayerIsVisible = board->IsLayerVisible( Reference().GetLayer() ) && board->IsElementVisible( LAYER_FP_REFERENCES ) && board->IsElementVisible( LAYER_FP_TEXT ); } if( ( Value().IsVisible() && valueLayerIsVisible ) || noDrawItems ) { bbox.Merge( Value().GetBoundingBox() ); } if( ( Reference().IsVisible() && refLayerIsVisible ) || noDrawItems ) { bbox.Merge( Reference().GetBoundingBox() ); } } if( board ) { if( aIncludeText || noDrawItems ) { m_boundingBoxCacheTimeStamp = board->GetTimeStamp(); m_cachedBoundingBox = bbox; } else { m_textExcludedBBoxCacheTimeStamp = board->GetTimeStamp(); m_cachedTextExcludedBBox = bbox; } } return bbox; } const BOX2I FOOTPRINT::GetLayerBoundingBox( const LSET& aLayers ) const { std::vector texts; const BOARD* board = GetBoard(); bool isFPEdit = board && board->IsFootprintHolder(); // Start with an uninitialized bounding box BOX2I bbox; for( BOARD_ITEM* item : m_drawings ) { if( m_privateLayers.test( item->GetLayer() ) && !isFPEdit ) continue; if( ( aLayers & item->GetLayerSet() ).none() ) continue; // We want the bitmap bounding box just in the footprint editor // so it will start with the correct initial zoom if( item->Type() == PCB_REFERENCE_IMAGE_T && !isFPEdit ) continue; bbox.Merge( item->GetBoundingBox() ); } for( PAD* pad : m_pads ) { if( ( aLayers & pad->GetLayerSet() ).none() ) continue; bbox.Merge( pad->GetBoundingBox() ); } for( ZONE* zone : m_zones ) { if( ( aLayers & zone->GetLayerSet() ).none() ) continue; bbox.Merge( zone->GetBoundingBox() ); } return bbox; } SHAPE_POLY_SET FOOTPRINT::GetBoundingHull() const { const BOARD* board = GetBoard(); bool isFPEdit = board && board->IsFootprintHolder(); if( board ) { if( m_hullCacheTimeStamp >= board->GetTimeStamp() ) return m_cachedHull; } SHAPE_POLY_SET rawPolys; for( BOARD_ITEM* item : m_drawings ) { if( !isFPEdit && m_privateLayers.test( item->GetLayer() ) ) continue; if( item->Type() != PCB_FIELD_T && item->Type() != PCB_REFERENCE_IMAGE_T ) { item->TransformShapeToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } // We intentionally exclude footprint fields from the bounding hull. } for( PAD* pad : m_pads ) { pad->Padstack().ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer ) { pad->TransformShapeToPolygon( rawPolys, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } ); // In case hole is larger than pad pad->TransformHoleToPolygon( rawPolys, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } for( ZONE* zone : m_zones ) { for( PCB_LAYER_ID layer : zone->GetLayerSet() ) { const SHAPE_POLY_SET& layerPoly = *zone->GetFilledPolysList( layer ); for( int ii = 0; ii < layerPoly.OutlineCount(); ii++ ) { const SHAPE_LINE_CHAIN& poly = layerPoly.COutline( ii ); rawPolys.AddOutline( poly ); } } } // If there are some graphic items, build the actual hull. // However if no items, create a minimal polygon (can happen if a footprint // is created with no item: it contains only 2 texts. if( rawPolys.OutlineCount() == 0 || rawPolys.FullPointCount() < 3 ) { // generate a small dummy rectangular outline around the anchor const int halfsize = pcbIUScale.mmToIU( 1.0 ); rawPolys.NewOutline(); // add a square: rawPolys.Append( GetPosition().x - halfsize, GetPosition().y - halfsize ); rawPolys.Append( GetPosition().x + halfsize, GetPosition().y - halfsize ); rawPolys.Append( GetPosition().x + halfsize, GetPosition().y + halfsize ); rawPolys.Append( GetPosition().x - halfsize, GetPosition().y + halfsize ); } std::vector convex_hull; BuildConvexHull( convex_hull, rawPolys ); m_cachedHull.RemoveAllContours(); m_cachedHull.NewOutline(); for( const VECTOR2I& pt : convex_hull ) m_cachedHull.Append( pt ); if( board ) m_hullCacheTimeStamp = board->GetTimeStamp(); return m_cachedHull; } SHAPE_POLY_SET FOOTPRINT::GetBoundingHull( PCB_LAYER_ID aLayer ) const { const BOARD* board = GetBoard(); bool isFPEdit = board && board->IsFootprintHolder(); SHAPE_POLY_SET rawPolys; SHAPE_POLY_SET hull; for( BOARD_ITEM* item : m_drawings ) { if( !isFPEdit && m_privateLayers.test( item->GetLayer() ) ) continue; if( item->IsOnLayer( aLayer ) ) { if( item->Type() != PCB_FIELD_T && item->Type() != PCB_REFERENCE_IMAGE_T ) { item->TransformShapeToPolygon( rawPolys, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } // We intentionally exclude footprint fields from the bounding hull. } } for( PAD* pad : m_pads ) { if( pad->IsOnLayer( aLayer ) ) pad->TransformShapeToPolygon( rawPolys, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } for( ZONE* zone : m_zones ) { if( zone->GetIsRuleArea() ) continue; if( zone->IsOnLayer( aLayer ) ) { const std::shared_ptr& layerPoly = zone->GetFilledPolysList( aLayer ); for( int ii = 0; ii < layerPoly->OutlineCount(); ii++ ) rawPolys.AddOutline( layerPoly->COutline( ii ) ); } } std::vector convex_hull; BuildConvexHull( convex_hull, rawPolys ); hull.NewOutline(); for( const VECTOR2I& pt : convex_hull ) hull.Append( pt ); return hull; } void FOOTPRINT::GetMsgPanelInfo( EDA_DRAW_FRAME* aFrame, std::vector& aList ) { wxString msg, msg2; // Don't use GetShownText(); we want to see the variable references here aList.emplace_back( UnescapeString( Reference().GetText() ), UnescapeString( Value().GetText() ) ); if( aFrame->IsType( FRAME_FOOTPRINT_VIEWER ) || aFrame->IsType( FRAME_FOOTPRINT_CHOOSER ) || aFrame->IsType( FRAME_FOOTPRINT_EDITOR ) ) { size_t padCount = GetPadCount( DO_NOT_INCLUDE_NPTH ); aList.emplace_back( _( "Library" ), GetFPID().GetLibNickname().wx_str() ); aList.emplace_back( _( "Footprint Name" ), GetFPID().GetLibItemName().wx_str() ); aList.emplace_back( _( "Pads" ), wxString::Format( wxT( "%zu" ), padCount ) ); aList.emplace_back( wxString::Format( _( "Doc: %s" ), GetLibDescription() ), wxString::Format( _( "Keywords: %s" ), GetKeywords() ) ); return; } // aFrame is the board editor: switch( GetSide() ) { case F_Cu: aList.emplace_back( _( "Board Side" ), _( "Front" ) ); break; case B_Cu: aList.emplace_back( _( "Board Side" ), _( "Back (Flipped)" ) ); break; default: /* unsided: user-layers only, etc. */ break; } auto addToken = []( wxString* aStr, const wxString& aAttr ) { if( !aStr->IsEmpty() ) *aStr += wxT( ", " ); *aStr += aAttr; }; wxString status; wxString attrs; if( IsLocked() ) addToken( &status, _( "Locked" ) ); if( m_fpStatus & FP_is_PLACED ) addToken( &status, _( "autoplaced" ) ); if( m_attributes & FP_BOARD_ONLY ) addToken( &attrs, _( "not in schematic" ) ); if( m_attributes & FP_EXCLUDE_FROM_POS_FILES ) addToken( &attrs, _( "exclude from pos files" ) ); if( m_attributes & FP_EXCLUDE_FROM_BOM ) addToken( &attrs, _( "exclude from BOM" ) ); if( m_attributes & FP_DNP ) addToken( &attrs, _( "DNP" ) ); aList.emplace_back( _( "Status: " ) + status, _( "Attributes:" ) + wxS( " " ) + attrs ); aList.emplace_back( _( "Rotation" ), wxString::Format( wxT( "%.4g" ), GetOrientation().AsDegrees() ) ); if( !m_componentClassCacheProxy->GetComponentClass()->IsEmpty() ) { aList.emplace_back( _( "Component Class" ), m_componentClassCacheProxy->GetComponentClass()->GetHumanReadableName() ); } msg.Printf( _( "Footprint: %s" ), m_fpid.GetUniStringLibId() ); msg2.Printf( _( "3D-Shape: %s" ), m_3D_Drawings.empty() ? _( "" ) : m_3D_Drawings.front().m_Filename ); aList.emplace_back( msg, msg2 ); msg.Printf( _( "Doc: %s" ), m_libDescription ); msg2.Printf( _( "Keywords: %s" ), m_keywords ); aList.emplace_back( msg, msg2 ); } PCB_LAYER_ID FOOTPRINT::GetSide() const { if( const BOARD* board = GetBoard() ) { if( board->IsFootprintHolder() ) return UNDEFINED_LAYER; } // Test pads first; they're the most likely to return a quick answer. for( PAD* pad : m_pads ) { if( ( LSET::SideSpecificMask() & pad->GetLayerSet() ).any() ) return GetLayer(); } for( BOARD_ITEM* item : m_drawings ) { if( LSET::SideSpecificMask().test( item->GetLayer() ) ) return GetLayer(); } for( ZONE* zone : m_zones ) { if( ( LSET::SideSpecificMask() & zone->GetLayerSet() ).any() ) return GetLayer(); } return UNDEFINED_LAYER; } bool FOOTPRINT::IsOnLayer( PCB_LAYER_ID aLayer ) const { // If we have any pads, fall back on normal checking for( PAD* pad : m_pads ) { if( pad->IsOnLayer( aLayer ) ) return true; } for( ZONE* zone : m_zones ) { if( zone->IsOnLayer( aLayer ) ) return true; } for( PCB_FIELD* field : m_fields ) { if( field->IsOnLayer( aLayer ) ) return true; } for( BOARD_ITEM* item : m_drawings ) { if( item->IsOnLayer( aLayer ) ) return true; } return false; } bool FOOTPRINT::HitTestOnLayer( const VECTOR2I& aPosition, PCB_LAYER_ID aLayer, int aAccuracy ) const { for( PAD* pad : m_pads ) { if( pad->IsOnLayer( aLayer ) && pad->HitTest( aPosition, aAccuracy ) ) return true; } for( ZONE* zone : m_zones ) { if( zone->IsOnLayer( aLayer ) && zone->HitTest( aPosition, aAccuracy ) ) return true; } for( BOARD_ITEM* item : m_drawings ) { if( item->Type() != PCB_TEXT_T && item->IsOnLayer( aLayer ) && item->HitTest( aPosition, aAccuracy ) ) { return true; } } return false; } bool FOOTPRINT::HitTestOnLayer( const BOX2I& aRect, bool aContained, PCB_LAYER_ID aLayer, int aAccuracy ) const { std::vector items; for( PAD* pad : m_pads ) { if( pad->IsOnLayer( aLayer ) ) items.push_back( pad ); } for( ZONE* zone : m_zones ) { if( zone->IsOnLayer( aLayer ) ) items.push_back( zone ); } for( BOARD_ITEM* item : m_drawings ) { if( item->Type() != PCB_TEXT_T && item->IsOnLayer( aLayer ) ) items.push_back( item ); } // If we require the elements to be contained in the rect and any of them are not, // we can return false; // Conversely, if we just require any of the elements to have a hit, we can return true // when the first one is found. for( BOARD_ITEM* item : items ) { if( !aContained && item->HitTest( aRect, aContained, aAccuracy ) ) return true; else if( aContained && !item->HitTest( aRect, aContained, aAccuracy ) ) return false; } // If we didn't exit in the loop, that means that we did not return false for aContained or // we did not return true for !aContained. So we can just return the bool with a test of // whether there were any elements or not. return !items.empty() && aContained; } bool FOOTPRINT::HitTest( const VECTOR2I& aPosition, int aAccuracy ) const { BOX2I rect = GetBoundingBox( false ); return rect.Inflate( aAccuracy ).Contains( aPosition ); } bool FOOTPRINT::HitTestAccurate( const VECTOR2I& aPosition, int aAccuracy ) const { return GetBoundingHull().Collide( aPosition, aAccuracy ); } bool FOOTPRINT::HitTest( const BOX2I& aRect, bool aContained, int aAccuracy ) const { BOX2I arect = aRect; arect.Inflate( aAccuracy ); if( aContained ) { return arect.Contains( GetBoundingBox( false ) ); } else { // If the rect does not intersect the bounding box, skip any tests if( !aRect.Intersects( GetBoundingBox( false ) ) ) return false; // If there are no pads, zones, or drawings, allow intersection with text if( m_pads.empty() && m_zones.empty() && m_drawings.empty() ) return GetBoundingBox( true ).Intersects( arect ); // Determine if any elements in the FOOTPRINT intersect the rect for( PAD* pad : m_pads ) { if( pad->HitTest( arect, false, 0 ) ) return true; } for( ZONE* zone : m_zones ) { if( zone->HitTest( arect, false, 0 ) ) return true; } // PCB fields are selectable on their own, so they don't get tested for( BOARD_ITEM* item : m_drawings ) { // Text items are selectable on their own, and are therefore excluded from this // test. TextBox items are NOT selectable on their own, and so MUST be included // here. Bitmaps aren't selectable since they aren't displayed. if( item->Type() != PCB_TEXT_T && item->HitTest( arect, false, 0 ) ) return true; } // Groups are not hit-tested; only their members // No items were hit return false; } } bool FOOTPRINT::HitTest( const SHAPE_LINE_CHAIN& aPoly, bool aContained ) const { using std::ranges::all_of; using std::ranges::any_of; // If there are no pads, zones, or drawings, test footprint text instead. if( m_pads.empty() && m_zones.empty() && m_drawings.empty() ) return KIGEOM::BoxHitTest( aPoly, GetBoundingBox( true ), aContained ); auto hitTest = [&]( const auto* aItem ) { return aItem && aItem->HitTest( aPoly, aContained ); }; // Filter out text items from the drawings, since they are selectable on their own, // and we don't want to select the whole footprint when text is hit. TextBox items are NOT // selectable on their own, so they are not excluded here. auto drawings = m_drawings | std::views::filter( []( const auto* aItem ) { return aItem && aItem->Type() != PCB_TEXT_T; } ); // Test pads, zones and drawings with text excluded. PCB fields are also selectable // on their own, so they don't get tested. Groups are not hit-tested, only their members. // Bitmaps aren't selectable since they aren't displayed. if( aContained ) { // All items must be contained in the selection poly. return all_of( drawings, hitTest ) && all_of( m_pads, hitTest ) && all_of( m_zones, hitTest ); } else { // Any item intersecting the selection poly is sufficient. return any_of( drawings, hitTest ) || any_of( m_pads, hitTest ) || any_of( m_zones, hitTest ); } } PAD* FOOTPRINT::FindPadByNumber( const wxString& aPadNumber, PAD* aSearchAfterMe ) const { bool can_select = aSearchAfterMe ? false : true; for( PAD* pad : m_pads ) { if( !can_select && pad == aSearchAfterMe ) { can_select = true; continue; } if( can_select && pad->GetNumber() == aPadNumber ) return pad; } return nullptr; } PAD* FOOTPRINT::GetPad( const VECTOR2I& aPosition, const LSET& aLayerMask ) { for( PAD* pad : m_pads ) { // ... and on the correct layer. if( !( pad->GetLayerSet() & aLayerMask ).any() ) continue; if( pad->HitTest( aPosition ) ) return pad; } return nullptr; } std::vector FOOTPRINT::GetPads( const wxString& aPadNumber, const PAD* aIgnore ) const { std::vector retv; for( const PAD* pad : m_pads ) { if( ( aIgnore && aIgnore == pad ) || ( pad->GetNumber() != aPadNumber ) ) continue; retv.push_back( pad ); } return retv; } unsigned FOOTPRINT::GetPadCount( INCLUDE_NPTH_T aIncludeNPTH ) const { if( aIncludeNPTH ) return m_pads.size(); unsigned cnt = 0; for( PAD* pad : m_pads ) { if( pad->GetAttribute() == PAD_ATTRIB::NPTH ) continue; cnt++; } return cnt; } std::set FOOTPRINT::GetUniquePadNumbers( INCLUDE_NPTH_T aIncludeNPTH ) const { std::set usedNumbers; // Create a set of used pad numbers for( PAD* pad : m_pads ) { // Skip pads not on copper layers (used to build complex // solder paste shapes for instance) if( ( pad->GetLayerSet() & LSET::AllCuMask() ).none() ) continue; // Skip pads with no name, because they are usually "mechanical" // pads, not "electrical" pads if( pad->GetNumber().IsEmpty() ) continue; if( !aIncludeNPTH ) { // skip NPTH if( pad->GetAttribute() == PAD_ATTRIB::NPTH ) continue; } usedNumbers.insert( pad->GetNumber() ); } return usedNumbers; } unsigned FOOTPRINT::GetUniquePadCount( INCLUDE_NPTH_T aIncludeNPTH ) const { return GetUniquePadNumbers( aIncludeNPTH ).size(); } void FOOTPRINT::Add3DModel( FP_3DMODEL* a3DModel ) { if( nullptr == a3DModel ) return; if( !a3DModel->m_Filename.empty() ) m_3D_Drawings.push_back( *a3DModel ); } bool FOOTPRINT::Matches( const EDA_SEARCH_DATA& aSearchData, void* aAuxData ) const { if( aSearchData.searchMetadata ) { if( EDA_ITEM::Matches( GetFPIDAsString(), aSearchData ) ) return true; if( EDA_ITEM::Matches( GetLibDescription(), aSearchData ) ) return true; if( EDA_ITEM::Matches( GetKeywords(), aSearchData ) ) return true; } return false; } // see footprint.h INSPECT_RESULT FOOTPRINT::Visit( INSPECTOR inspector, void* testData, const std::vector& aScanTypes ) { #if 0 && defined(DEBUG) std::cout << GetClass().mb_str() << ' '; #endif bool drawingsScanned = false; for( KICAD_T scanType : aScanTypes ) { switch( scanType ) { case PCB_FOOTPRINT_T: if( inspector( this, testData ) == INSPECT_RESULT::QUIT ) return INSPECT_RESULT::QUIT; break; case PCB_PAD_T: if( IterateForward( m_pads, inspector, testData, { scanType } ) == INSPECT_RESULT::QUIT ) { return INSPECT_RESULT::QUIT; } break; case PCB_ZONE_T: if( IterateForward( m_zones, inspector, testData, { scanType } ) == INSPECT_RESULT::QUIT ) { return INSPECT_RESULT::QUIT; } break; case PCB_FIELD_T: if( IterateForward( m_fields, inspector, testData, { scanType } ) == INSPECT_RESULT::QUIT ) { return INSPECT_RESULT::QUIT; } break; case PCB_TEXT_T: case PCB_DIM_ALIGNED_T: case PCB_DIM_LEADER_T: case PCB_DIM_CENTER_T: case PCB_DIM_RADIAL_T: case PCB_DIM_ORTHOGONAL_T: case PCB_SHAPE_T: case PCB_TEXTBOX_T: case PCB_TABLE_T: case PCB_TABLECELL_T: if( !drawingsScanned ) { if( IterateForward( m_drawings, inspector, testData, aScanTypes ) == INSPECT_RESULT::QUIT ) { return INSPECT_RESULT::QUIT; } drawingsScanned = true; } break; case PCB_GROUP_T: if( IterateForward( m_groups, inspector, testData, { scanType } ) == INSPECT_RESULT::QUIT ) { return INSPECT_RESULT::QUIT; } break; default: break; } } return INSPECT_RESULT::CONTINUE; } wxString FOOTPRINT::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const { wxString reference = GetReference(); if( reference.IsEmpty() ) reference = _( "" ); return wxString::Format( _( "Footprint %s" ), reference ); } BITMAPS FOOTPRINT::GetMenuImage() const { return BITMAPS::module; } EDA_ITEM* FOOTPRINT::Clone() const { return new FOOTPRINT( *this ); } void FOOTPRINT::RunOnChildren( const std::function& aFunction, RECURSE_MODE aMode ) const { try { for( PCB_FIELD* field : m_fields ) aFunction( field ); for( PAD* pad : m_pads ) aFunction( pad ); for( ZONE* zone : m_zones ) aFunction( zone ); for( PCB_GROUP* group : m_groups ) aFunction( group ); for( BOARD_ITEM* drawing : m_drawings ) { aFunction( drawing ); if( aMode == RECURSE_MODE::RECURSE ) drawing->RunOnChildren( aFunction, RECURSE_MODE::RECURSE ); } } catch( std::bad_function_call& ) { wxFAIL_MSG( wxT( "Error running FOOTPRINT::RunOnChildren" ) ); } } std::vector FOOTPRINT::ViewGetLayers() const { std::vector layers; layers.reserve( 6 ); layers.push_back( LAYER_ANCHOR ); switch( m_layer ) { default: wxASSERT_MSG( false, wxT( "Illegal layer" ) ); // do you really have footprints placed // on other layers? KI_FALLTHROUGH; case F_Cu: layers.push_back( LAYER_FOOTPRINTS_FR ); break; case B_Cu: layers.push_back( LAYER_FOOTPRINTS_BK ); break; } if( IsConflicting() ) layers.push_back( LAYER_CONFLICTS_SHADOW ); // If there are no pads, and only drawings on a silkscreen layer, then report the silkscreen // layer as well so that the component can be edited with the silkscreen layer bool f_silk = false, b_silk = false, non_silk = false; for( BOARD_ITEM* item : m_drawings ) { if( item->GetLayer() == F_SilkS ) f_silk = true; else if( item->GetLayer() == B_SilkS ) b_silk = true; else non_silk = true; } if( ( f_silk || b_silk ) && !non_silk && m_pads.empty() ) { if( f_silk ) layers.push_back( F_SilkS ); if( b_silk ) layers.push_back( B_SilkS ); } return layers; } double FOOTPRINT::ViewGetLOD( int aLayer, const KIGFX::VIEW* aView ) const { if( aLayer == LAYER_CONFLICTS_SHADOW && IsConflicting() ) { // The locked shadow shape is shown only if the footprint itself is visible if( ( m_layer == F_Cu ) && aView->IsLayerVisible( LAYER_FOOTPRINTS_FR ) ) return LOD_SHOW; if( ( m_layer == B_Cu ) && aView->IsLayerVisible( LAYER_FOOTPRINTS_BK ) ) return LOD_SHOW; return LOD_HIDE; } int layer = ( m_layer == F_Cu ) ? LAYER_FOOTPRINTS_FR : ( m_layer == B_Cu ) ? LAYER_FOOTPRINTS_BK : LAYER_ANCHOR; // Currently this is only pertinent for the anchor layer; everything else is drawn from the // children. // The "good" value is experimentally chosen. constexpr double MINIMAL_ZOOM_LEVEL_FOR_VISIBILITY = 1.5; if( aView->IsLayerVisible( layer ) ) return MINIMAL_ZOOM_LEVEL_FOR_VISIBILITY; return LOD_HIDE; } const BOX2I FOOTPRINT::ViewBBox() const { BOX2I area = GetBoundingBox( true ); // Inflate in case clearance lines are drawn around pads, etc. if( const BOARD* board = GetBoard() ) { int biggest_clearance = board->GetMaxClearanceValue(); area.Inflate( biggest_clearance ); } return area; } bool FOOTPRINT::IsLibNameValid( const wxString & aName ) { const wxChar * invalids = StringLibNameInvalidChars( false ); if( aName.find_first_of( invalids ) != std::string::npos ) return false; return true; } const wxChar* FOOTPRINT::StringLibNameInvalidChars( bool aUserReadable ) { // This list of characters is also duplicated in validators.cpp and // lib_id.cpp // TODO: Unify forbidden character lists - Warning, invalid filename characters are not the same // as invalid LIB_ID characters. We will need to separate the FP filenames from FP names before this // can be unified static const wxChar invalidChars[] = wxT("%$<>\t\n\r\"\\/:"); static const wxChar invalidCharsReadable[] = wxT("% $ < > 'tab' 'return' 'line feed' \\ \" / :"); if( aUserReadable ) return invalidCharsReadable; else return invalidChars; } void FOOTPRINT::Move( const VECTOR2I& aMoveVector ) { if( aMoveVector.x == 0 && aMoveVector.y == 0 ) return; VECTOR2I newpos = m_pos + aMoveVector; SetPosition( newpos ); } void FOOTPRINT::Rotate( const VECTOR2I& aRotCentre, const EDA_ANGLE& aAngle ) { if( aAngle == ANGLE_0 ) return; EDA_ANGLE orientation = GetOrientation(); EDA_ANGLE newOrientation = orientation + aAngle; VECTOR2I newpos = m_pos; RotatePoint( newpos, aRotCentre, aAngle ); SetPosition( newpos ); SetOrientation( newOrientation ); for( PCB_FIELD* field : m_fields ) field->KeepUpright(); for( BOARD_ITEM* item : m_drawings ) { if( item->Type() == PCB_TEXT_T ) static_cast( item )->KeepUpright(); } } void FOOTPRINT::SetLayerAndFlip( PCB_LAYER_ID aLayer ) { wxASSERT( aLayer == F_Cu || aLayer == B_Cu ); if( aLayer != GetLayer() ) Flip( GetPosition(), FLIP_DIRECTION::LEFT_RIGHT ); } void FOOTPRINT::Flip( const VECTOR2I& aCentre, FLIP_DIRECTION aFlipDirection ) { // Move footprint to its final position: VECTOR2I finalPos = m_pos; // Now Flip the footprint. // Flipping a footprint is a specific transform: it is not mirrored like a text. // We have to change the side, and ensure the footprint rotation is modified according to the // transform, because this parameter is used in pick and place files, and when updating the // footprint from library. // When flipped around the X axis (Y coordinates changed) orientation is negated // When flipped around the Y axis (X coordinates changed) orientation is 180 - old orient. // Because it is specific to a footprint, we flip around the X axis, and after rotate 180 deg MIRROR( finalPos.y, aCentre.y ); /// Mirror the Y position (around the X axis) SetPosition( finalPos ); // Flip layer BOARD_ITEM::SetLayer( GetBoard()->FlipLayer( GetLayer() ) ); // Calculate the new orientation, and then clear it for pad flipping. EDA_ANGLE newOrientation = -m_orient; newOrientation.Normalize180(); m_orient = ANGLE_0; // Mirror fields to other side of board. for( PCB_FIELD* field : m_fields ) field->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM ); // Mirror pads to other side of board. for( PAD* pad : m_pads ) pad->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM ); // Now set the new orientation. m_orient = newOrientation; // Mirror zones to other side of board. for( ZONE* zone : m_zones ) zone->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM ); // Reverse mirror footprint graphics and texts. for( BOARD_ITEM* item : m_drawings ) item->Flip( m_pos, FLIP_DIRECTION::TOP_BOTTOM ); // Now rotate 180 deg if required if( aFlipDirection == FLIP_DIRECTION::LEFT_RIGHT ) Rotate( aCentre, ANGLE_180 ); m_boundingBoxCacheTimeStamp = 0; m_textExcludedBBoxCacheTimeStamp = 0; m_cachedHull.Mirror( m_pos, aFlipDirection ); // The courtyard caches must be rebuilt after geometry change BuildCourtyardCaches(); } void FOOTPRINT::SetPosition( const VECTOR2I& aPos ) { VECTOR2I delta = aPos - m_pos; m_pos += delta; for( PCB_FIELD* field : m_fields ) field->EDA_TEXT::Offset( delta ); for( PAD* pad : m_pads ) pad->SetPosition( pad->GetPosition() + delta ); for( ZONE* zone : m_zones ) zone->Move( delta ); for( BOARD_ITEM* item : m_drawings ) item->Move( delta ); m_cachedBoundingBox.Move( delta ); m_cachedTextExcludedBBox.Move( delta ); m_cachedHull.Move( delta ); // The geometry work has been conserved by using Move(). But the hashes // need to be updated, otherwise the cached polygons will still be rebuild. m_courtyard_cache_back.Move( delta ); m_courtyard_cache_back_hash = m_courtyard_cache_back.GetHash(); m_courtyard_cache_front.Move( delta ); m_courtyard_cache_front_hash = m_courtyard_cache_front.GetHash(); } void FOOTPRINT::MoveAnchorPosition( const VECTOR2I& aMoveVector ) { /* * Move the reference point of the footprint * the footprints elements (pads, outlines, edges .. ) are moved * but: * - the footprint position is not modified. * - the relative (local) coordinates of these items are modified * - Draw coordinates are updated */ // Update (move) the relative coordinates relative to the new anchor point. VECTOR2I moveVector = aMoveVector; RotatePoint( moveVector, -GetOrientation() ); // Update field local coordinates for( PCB_FIELD* field : m_fields ) field->Move( moveVector ); // Update the pad local coordinates. for( PAD* pad : m_pads ) pad->Move( moveVector ); // Update the draw element coordinates. for( BOARD_ITEM* item : GraphicalItems() ) item->Move( moveVector ); // Update the keepout zones for( ZONE* zone : Zones() ) zone->Move( moveVector ); // Update the 3D models for( FP_3DMODEL& model : Models() ) { model.m_Offset.x += pcbIUScale.IUTomm( moveVector.x ); model.m_Offset.y -= pcbIUScale.IUTomm( moveVector.y ); } m_cachedBoundingBox.Move( moveVector ); m_cachedTextExcludedBBox.Move( moveVector ); m_cachedHull.Move( moveVector ); // The geometry work have been conserved by using Move(). But the hashes // need to be updated, otherwise the cached polygons will still be rebuild. m_courtyard_cache_back.Move( moveVector ); m_courtyard_cache_back_hash = m_courtyard_cache_back.GetHash(); m_courtyard_cache_front.Move( moveVector ); m_courtyard_cache_front_hash = m_courtyard_cache_front.GetHash(); } void FOOTPRINT::SetOrientation( const EDA_ANGLE& aNewAngle ) { EDA_ANGLE angleChange = aNewAngle - m_orient; // change in rotation m_orient = aNewAngle; m_orient.Normalize180(); for( PCB_FIELD* field : m_fields ) field->Rotate( GetPosition(), angleChange ); for( PAD* pad : m_pads ) pad->Rotate( GetPosition(), angleChange ); for( ZONE* zone : m_zones ) zone->Rotate( GetPosition(), angleChange ); for( BOARD_ITEM* item : m_drawings ) item->Rotate( GetPosition(), angleChange ); m_boundingBoxCacheTimeStamp = 0; m_textExcludedBBoxCacheTimeStamp = 0; m_hullCacheTimeStamp = 0; // The courtyard caches need to be rebuilt, as the geometry has changed BuildCourtyardCaches(); } BOARD_ITEM* FOOTPRINT::Duplicate( bool addToParentGroup, BOARD_COMMIT* aCommit ) const { FOOTPRINT* dupe = static_cast( BOARD_ITEM::Duplicate( addToParentGroup, aCommit ) ); dupe->RunOnChildren( [&]( BOARD_ITEM* child ) { const_cast( child->m_Uuid ) = KIID(); }, RECURSE_MODE::RECURSE ); return dupe; } BOARD_ITEM* FOOTPRINT::DuplicateItem( bool addToParentGroup, BOARD_COMMIT* aCommit, const BOARD_ITEM* aItem, bool addToFootprint ) { BOARD_ITEM* new_item = nullptr; switch( aItem->Type() ) { case PCB_PAD_T: { PAD* new_pad = new PAD( *static_cast( aItem ) ); const_cast( new_pad->m_Uuid ) = KIID(); if( addToFootprint ) m_pads.push_back( new_pad ); new_item = new_pad; break; } case PCB_ZONE_T: { ZONE* new_zone = new ZONE( *static_cast( aItem ) ); const_cast( new_zone->m_Uuid ) = KIID(); if( addToFootprint ) m_zones.push_back( new_zone ); new_item = new_zone; break; } case PCB_FIELD_T: case PCB_TEXT_T: { PCB_TEXT* new_text = new PCB_TEXT( *static_cast( aItem ) ); const_cast( new_text->m_Uuid ) = KIID(); if( aItem->Type() == PCB_FIELD_T ) { switch( static_cast( aItem )->GetId() ) { case FIELD_T::REFERENCE: new_text->SetText( wxT( "${REFERENCE}" ) ); break; case FIELD_T::VALUE: new_text->SetText( wxT( "${VALUE}" ) ); break; case FIELD_T::DATASHEET: new_text->SetText( wxT( "${DATASHEET}" ) ); break; default: break; } } if( addToFootprint ) Add( new_text ); new_item = new_text; break; } case PCB_SHAPE_T: { PCB_SHAPE* new_shape = new PCB_SHAPE( *static_cast( aItem ) ); const_cast( new_shape->m_Uuid ) = KIID(); if( addToFootprint ) Add( new_shape ); new_item = new_shape; break; } case PCB_REFERENCE_IMAGE_T: { PCB_REFERENCE_IMAGE* new_image = new PCB_REFERENCE_IMAGE( *static_cast( aItem ) ); const_cast( new_image->m_Uuid ) = KIID(); if( addToFootprint ) Add( new_image ); new_item = new_image; break; } case PCB_TEXTBOX_T: { PCB_TEXTBOX* new_textbox = new PCB_TEXTBOX( *static_cast( aItem ) ); const_cast( new_textbox->m_Uuid ) = KIID(); if( addToFootprint ) Add( new_textbox ); new_item = new_textbox; break; } case PCB_DIM_ALIGNED_T: case PCB_DIM_LEADER_T: case PCB_DIM_CENTER_T: case PCB_DIM_RADIAL_T: case PCB_DIM_ORTHOGONAL_T: { PCB_DIMENSION_BASE* dimension = static_cast( aItem->Duplicate( addToParentGroup, aCommit ) ); if( addToFootprint ) Add( dimension ); new_item = dimension; break; } case PCB_GROUP_T: { PCB_GROUP* group = static_cast( aItem )->DeepDuplicate( addToParentGroup, aCommit ); if( addToFootprint ) { group->RunOnChildren( [&]( BOARD_ITEM* aCurrItem ) { Add( aCurrItem ); }, RECURSE_MODE::RECURSE ); Add( group ); } new_item = group; break; } case PCB_FOOTPRINT_T: // Ignore the footprint itself break; default: // Un-handled item for duplication wxFAIL_MSG( wxT( "Duplication not supported for items of class " ) + aItem->GetClass() ); break; } return new_item; } wxString FOOTPRINT::GetNextPadNumber( const wxString& aLastPadNumber ) const { std::set usedNumbers; // Create a set of used pad numbers for( PAD* pad : m_pads ) usedNumbers.insert( pad->GetNumber() ); // Pad numbers aren't technically reference designators, but the formatting is close enough // for these to give us what we need. wxString prefix = UTIL::GetRefDesPrefix( aLastPadNumber ); int num = GetTrailingInt( aLastPadNumber ); while( usedNumbers.count( wxString::Format( wxT( "%s%d" ), prefix, num ) ) ) num++; return wxString::Format( wxT( "%s%d" ), prefix, num ); } std::optional> FOOTPRINT::GetJumperPadGroup( const wxString& aPadNumber ) const { for( const std::set& group : m_jumperPadGroups ) { if( group.contains( aPadNumber ) ) return group; } return std::nullopt; } void FOOTPRINT::AutoPositionFields() { // Auto-position reference and value BOX2I bbox = GetBoundingBox( false ); bbox.Inflate( pcbIUScale.mmToIU( 0.2 ) ); // Gap between graphics and text if( Reference().GetPosition() == VECTOR2I( 0, 0 ) ) { Reference().SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); Reference().SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); Reference().SetTextAngle( ANGLE_0 ); Reference().SetX( bbox.GetCenter().x ); Reference().SetY( bbox.GetTop() - Reference().GetTextSize().y / 2 ); } if( Value().GetPosition() == VECTOR2I( 0, 0 ) ) { Value().SetHorizJustify( GR_TEXT_H_ALIGN_CENTER ); Value().SetVertJustify( GR_TEXT_V_ALIGN_CENTER ); Value().SetTextAngle( ANGLE_0 ); Value().SetX( bbox.GetCenter().x ); Value().SetY( bbox.GetBottom() + Value().GetTextSize().y / 2 ); } } void FOOTPRINT::IncrementReference( int aDelta ) { const wxString& refdes = GetReference(); SetReference( wxString::Format( wxT( "%s%i" ), UTIL::GetRefDesPrefix( refdes ), GetTrailingInt( refdes ) + aDelta ) ); } // Calculate the area of a PolySet, polygons with hole are allowed. static double polygonArea( SHAPE_POLY_SET& aPolySet ) { // Ensure all outlines are closed, before calculating the SHAPE_POLY_SET area for( int ii = 0; ii < aPolySet.OutlineCount(); ii++ ) { SHAPE_LINE_CHAIN& outline = aPolySet.Outline( ii ); outline.SetClosed( true ); for( int jj = 0; jj < aPolySet.HoleCount( ii ); jj++ ) aPolySet.Hole( ii, jj ).SetClosed( true ); } return aPolySet.Area(); } double FOOTPRINT::GetCoverageArea( const BOARD_ITEM* aItem, const GENERAL_COLLECTOR& aCollector ) { int textMargin = aCollector.GetGuide()->Accuracy(); SHAPE_POLY_SET poly; if( aItem->Type() == PCB_MARKER_T ) { const PCB_MARKER* marker = static_cast( aItem ); SHAPE_LINE_CHAIN markerShape; marker->ShapeToPolygon( markerShape ); return markerShape.Area(); } else if( aItem->Type() == PCB_GROUP_T || aItem->Type() == PCB_GENERATOR_T ) { double combinedArea = 0.0; for( BOARD_ITEM* member : static_cast( aItem )->GetBoardItems() ) combinedArea += GetCoverageArea( member, aCollector ); return combinedArea; } if( aItem->Type() == PCB_FOOTPRINT_T ) { const FOOTPRINT* footprint = static_cast( aItem ); poly = footprint->GetBoundingHull(); } else if( aItem->Type() == PCB_FIELD_T || aItem->Type() == PCB_TEXT_T ) { const PCB_TEXT* text = static_cast( aItem ); text->TransformTextToPolySet( poly, textMargin, ARC_LOW_DEF, ERROR_INSIDE ); } else if( aItem->Type() == PCB_TEXTBOX_T ) { const PCB_TEXTBOX* tb = static_cast( aItem ); tb->TransformTextToPolySet( poly, textMargin, ARC_LOW_DEF, ERROR_INSIDE ); } else if( aItem->Type() == PCB_SHAPE_T ) { // Approximate "linear" shapes with just their width squared, as we don't want to consider // a linear shape as being much bigger than another for purposes of selection filtering // just because it happens to be really long. const PCB_SHAPE* shape = static_cast( aItem ); switch( shape->GetShape() ) { case SHAPE_T::SEGMENT: case SHAPE_T::ARC: case SHAPE_T::BEZIER: return shape->GetWidth() * shape->GetWidth(); case SHAPE_T::RECTANGLE: case SHAPE_T::CIRCLE: case SHAPE_T::POLY: { if( !shape->IsAnyFill() ) return shape->GetWidth() * shape->GetWidth(); KI_FALLTHROUGH; } default: shape->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } } else if( aItem->Type() == PCB_TRACE_T || aItem->Type() == PCB_ARC_T ) { double width = static_cast( aItem )->GetWidth(); return width * width; } else if( aItem->Type() == PCB_PAD_T ) { static_cast( aItem )->Padstack().ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer ) { SHAPE_POLY_SET layerPoly; aItem->TransformShapeToPolygon( layerPoly, aLayer, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); poly.BooleanAdd( layerPoly ); } ); } else { aItem->TransformShapeToPolygon( poly, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } return polygonArea( poly ); } double FOOTPRINT::CoverageRatio( const GENERAL_COLLECTOR& aCollector ) const { int textMargin = aCollector.GetGuide()->Accuracy(); SHAPE_POLY_SET footprintRegion( GetBoundingHull() ); SHAPE_POLY_SET coveredRegion; TransformPadsToPolySet( coveredRegion, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); TransformFPShapesToPolySet( coveredRegion, UNDEFINED_LAYER, textMargin, ARC_LOW_DEF, ERROR_OUTSIDE, true, /* include text */ false, /* include shapes */ false /* include private items */ ); for( int i = 0; i < aCollector.GetCount(); ++i ) { const BOARD_ITEM* item = aCollector[i]; switch( item->Type() ) { case PCB_FIELD_T: case PCB_TEXT_T: case PCB_TEXTBOX_T: case PCB_SHAPE_T: case PCB_TRACE_T: case PCB_ARC_T: case PCB_VIA_T: if( item->GetParent() != this ) { item->TransformShapeToPolygon( coveredRegion, UNDEFINED_LAYER, 0, ARC_LOW_DEF, ERROR_OUTSIDE ); } break; case PCB_FOOTPRINT_T: if( item != this ) { const FOOTPRINT* footprint = static_cast( item ); coveredRegion.AddOutline( footprint->GetBoundingHull().Outline( 0 ) ); } break; default: break; } } coveredRegion.BooleanIntersection( footprintRegion ); double footprintRegionArea = polygonArea( footprintRegion ); double uncoveredRegionArea = footprintRegionArea - polygonArea( coveredRegion ); double coveredArea = footprintRegionArea - uncoveredRegionArea; double ratio = ( coveredArea / footprintRegionArea ); // Test for negative ratio (should not occur). // better to be conservative (this will result in the disambiguate dialog) if( ratio < 0.0 ) return 1.0; return std::min( ratio, 1.0 ); } std::shared_ptr FOOTPRINT::GetEffectiveShape( PCB_LAYER_ID aLayer, FLASHING aFlash ) const { std::shared_ptr shape = std::make_shared(); // There are several possible interpretations here: // 1) the bounding box (without or without invisible items) // 2) just the pads and "edges" (ie: non-text graphic items) // 3) the courtyard // We'll go with (2) for now, unless the caller is clearly looking for (3) if( aLayer == F_CrtYd || aLayer == B_CrtYd ) { const SHAPE_POLY_SET& courtyard = GetCourtyard( aLayer ); if( courtyard.OutlineCount() == 0 ) // malformed/empty polygon return shape; shape->AddShape( new SHAPE_SIMPLE( courtyard.COutline( 0 ) ) ); } else { for( PAD* pad : Pads() ) shape->AddShape( pad->GetEffectiveShape( aLayer, aFlash )->Clone() ); for( BOARD_ITEM* item : GraphicalItems() ) { if( item->Type() == PCB_SHAPE_T ) shape->AddShape( item->GetEffectiveShape( aLayer, aFlash )->Clone() ); } } return shape; } const SHAPE_POLY_SET& FOOTPRINT::GetCourtyard( PCB_LAYER_ID aLayer ) const { std::lock_guard lock( m_courtyard_cache_mutex ); if( m_courtyard_cache_front_hash != m_courtyard_cache_front.GetHash() || m_courtyard_cache_back_hash != m_courtyard_cache_back.GetHash() ) { const_cast(this)->BuildCourtyardCaches(); } return GetCachedCourtyard( aLayer ); } const SHAPE_POLY_SET& FOOTPRINT::GetCachedCourtyard( PCB_LAYER_ID aLayer ) const { if( IsBackLayer( aLayer ) ) return m_courtyard_cache_back; else return m_courtyard_cache_front; } void FOOTPRINT::BuildCourtyardCaches( OUTLINE_ERROR_HANDLER* aErrorHandler ) { m_courtyard_cache_front.RemoveAllContours(); m_courtyard_cache_back.RemoveAllContours(); ClearFlags( MALFORMED_COURTYARDS ); // Build the courtyard area from graphic items on the courtyard. // Only PCB_SHAPE_T have meaning, graphic texts are ignored. // Collect items: std::vector list_front; std::vector list_back; std::map front_width_histogram; std::map back_width_histogram; for( BOARD_ITEM* item : GraphicalItems() ) { if( item->GetLayer() == B_CrtYd && item->Type() == PCB_SHAPE_T ) { PCB_SHAPE* shape = static_cast( item ); list_back.push_back( shape ); back_width_histogram[ shape->GetStroke().GetWidth() ]++; } if( item->GetLayer() == F_CrtYd && item->Type() == PCB_SHAPE_T ) { PCB_SHAPE* shape = static_cast( item ); list_front.push_back( shape ); front_width_histogram[ shape->GetStroke().GetWidth() ]++; } } if( !list_front.size() && !list_back.size() ) return; int maxError = pcbIUScale.mmToIU( 0.005 ); // max error for polygonization int chainingEpsilon = pcbIUScale.mmToIU( 0.02 ); // max dist from one endPt to next startPt if( ConvertOutlineToPolygon( list_front, m_courtyard_cache_front, maxError, chainingEpsilon, true, aErrorHandler ) ) { int width = 0; // Touching courtyards, or courtyards -at- the clearance distance are legal. // Use maxError here because that is the allowed deviation when transforming arcs/circles to // polygons. m_courtyard_cache_front.Inflate( -maxError, CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS, maxError ); m_courtyard_cache_front.CacheTriangulation( false ); auto max = std::max_element( front_width_histogram.begin(), front_width_histogram.end(), []( const std::pair& a, const std::pair& b ) { return a.second < b.second; } ); if( max != front_width_histogram.end() ) width = max->first; if( width == 0 ) width = pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ); if( m_courtyard_cache_front.OutlineCount() > 0 ) m_courtyard_cache_front.Outline( 0 ).SetWidth( width ); } else { SetFlags( MALFORMED_F_COURTYARD ); } if( ConvertOutlineToPolygon( list_back, m_courtyard_cache_back, maxError, chainingEpsilon, true, aErrorHandler ) ) { int width = 0; // Touching courtyards, or courtyards -at- the clearance distance are legal. m_courtyard_cache_back.Inflate( -maxError, CORNER_STRATEGY::CHAMFER_ACUTE_CORNERS, maxError ); m_courtyard_cache_back.CacheTriangulation( false ); auto max = std::max_element( back_width_histogram.begin(), back_width_histogram.end(), []( const std::pair& a, const std::pair& b ) { return a.second < b.second; } ); if( max != back_width_histogram.end() ) width = max->first; if( width == 0 ) width = pcbIUScale.mmToIU( DEFAULT_COURTYARD_WIDTH ); if( m_courtyard_cache_back.OutlineCount() > 0 ) m_courtyard_cache_back.Outline( 0 ).SetWidth( width ); } else { SetFlags( MALFORMED_B_COURTYARD ); } m_courtyard_cache_front_hash = m_courtyard_cache_front.GetHash(); m_courtyard_cache_back_hash = m_courtyard_cache_back.GetHash(); } void FOOTPRINT::BuildNetTieCache() { m_netTieCache.clear(); std::map map = MapPadNumbersToNetTieGroups(); std::map> layer_shapes; std::for_each( m_drawings.begin(), m_drawings.end(), [&]( BOARD_ITEM* item ) { if( item->Type() != PCB_SHAPE_T ) return; for( PCB_LAYER_ID layer : item->GetLayerSet() ) layer_shapes[layer].push_back( static_cast( item ) ); } ); for( size_t ii = 0; ii < m_pads.size(); ++ii ) { PAD* pad = m_pads[ ii ]; bool has_nettie = false; auto it = map.find( pad->GetNumber() ); if( it == map.end() || it->second < 0 ) continue; for( size_t jj = ii + 1; jj < m_pads.size(); ++jj ) { PAD* other = m_pads[ jj ]; auto it2 = map.find( other->GetNumber() ); if( it2 == map.end() || it2->second < 0 ) continue; if( it2->second == it->second ) { m_netTieCache[pad].insert( pad->GetNetCode() ); m_netTieCache[pad].insert( other->GetNetCode() ); m_netTieCache[other].insert( other->GetNetCode() ); m_netTieCache[other].insert( pad->GetNetCode() ); has_nettie = true; } } if( !has_nettie ) continue; for( auto& [ layer, shapes ] : layer_shapes ) { auto pad_shape = pad->GetEffectiveShape( layer ); for( auto other_shape : shapes ) { auto shape = other_shape->GetEffectiveShape( layer ); if( pad_shape->Collide( shape.get() ) ) { std::set& nettie = m_netTieCache[pad]; m_netTieCache[other_shape].insert( nettie.begin(), nettie.end() ); } } } } } std::map FOOTPRINT::MapPadNumbersToNetTieGroups() const { std::map padNumberToGroupIdxMap; for( const PAD* pad : m_pads ) padNumberToGroupIdxMap[ pad->GetNumber() ] = -1; auto processPad = [&]( wxString aPad, int aGroup ) { aPad.Trim( true ).Trim( false ); if( !aPad.IsEmpty() ) padNumberToGroupIdxMap[ aPad ] = aGroup; }; for( int ii = 0; ii < (int) m_netTiePadGroups.size(); ++ii ) { wxString group( m_netTiePadGroups[ ii ] ); bool esc = false; wxString pad; for( wxUniCharRef ch : group ) { if( esc ) { esc = false; pad.Append( ch ); continue; } switch( static_cast( ch ) ) { case '\\': esc = true; break; case ',': processPad( pad, ii ); pad.Clear(); break; default: pad.Append( ch ); break; } } processPad( pad, ii ); } return padNumberToGroupIdxMap; } std::vector FOOTPRINT::GetNetTiePads( PAD* aPad ) const { // First build a map from pad numbers to allowed-shorting-group indexes. This ends up being // something like O(3n), but it still beats O(n^2) for large numbers of pads. std::map padToNetTieGroupMap = MapPadNumbersToNetTieGroups(); int groupIdx = padToNetTieGroupMap[ aPad->GetNumber() ]; std::vector otherPads; if( groupIdx >= 0 ) { for( PAD* pad : m_pads ) { if( padToNetTieGroupMap[ pad->GetNumber() ] == groupIdx ) otherPads.push_back( pad ); } } return otherPads; } void FOOTPRINT::CheckFootprintAttributes( const std::function& aErrorHandler ) { int likelyAttr = ( GetLikelyAttribute() & ( FP_SMD | FP_THROUGH_HOLE ) ); int setAttr = ( GetAttributes() & ( FP_SMD | FP_THROUGH_HOLE ) ); if( setAttr && likelyAttr && setAttr != likelyAttr ) { wxString msg; switch( likelyAttr ) { case FP_THROUGH_HOLE: msg.Printf( _( "(expected 'Through hole'; actual '%s')" ), GetTypeName() ); break; case FP_SMD: msg.Printf( _( "(expected 'SMD'; actual '%s')" ), GetTypeName() ); break; } if( aErrorHandler ) (aErrorHandler)( msg ); } } void FOOTPRINT::CheckPads( UNITS_PROVIDER* aUnitsProvider, const std::function& aErrorHandler ) { if( aErrorHandler == nullptr ) return; for( PAD* pad: Pads() ) { pad->CheckPad( aUnitsProvider, false, [&]( int errorCode, const wxString& msg ) { aErrorHandler( pad, errorCode, msg ); } ); } } void FOOTPRINT::CheckShortingPads( const std::function& aErrorHandler ) { std::unordered_map checkedPairs; for( PAD* pad : Pads() ) { std::vector netTiePads = GetNetTiePads( pad ); for( PAD* other : Pads() ) { if( other == pad ) continue; // store canonical order so we don't collide in both directions (a:b and b:a) PAD* a = pad; PAD* b = other; if( static_cast( a ) > static_cast( b ) ) std::swap( a, b ); if( checkedPairs.find( { a, b } ) == checkedPairs.end() ) { checkedPairs[ { a, b } ] = 1; if( pad->HasDrilledHole() && other->HasDrilledHole() ) { VECTOR2I pos = pad->GetPosition(); if( pad->GetPosition() == other->GetPosition() ) { aErrorHandler( pad, other, DRCE_DRILLED_HOLES_COLOCATED, pos ); } else { std::shared_ptr holeA = pad->GetEffectiveHoleShape(); std::shared_ptr holeB = other->GetEffectiveHoleShape(); if( holeA->Collide( holeB->GetSeg(), 0 ) ) aErrorHandler( pad, other, DRCE_DRILLED_HOLES_TOO_CLOSE, pos ); } } if( pad->SameLogicalPadAs( other ) || alg::contains( netTiePads, other ) ) continue; if( !( ( pad->GetLayerSet() & other->GetLayerSet() ) & LSET::AllCuMask() ).any() ) continue; if( pad->GetBoundingBox().Intersects( other->GetBoundingBox() ) ) { VECTOR2I pos; for( PCB_LAYER_ID l : pad->Padstack().RelevantShapeLayers( other->Padstack() ) ) { SHAPE* padShape = pad->GetEffectiveShape( l ).get(); SHAPE* otherShape = other->GetEffectiveShape( l ).get(); if( padShape->Collide( otherShape, 0, nullptr, &pos ) ) aErrorHandler( pad, other, DRCE_SHORTING_ITEMS, pos ); } } } } } } void FOOTPRINT::CheckNetTies( const std::function& aErrorHandler ) { // First build a map from pad numbers to allowed-shorting-group indexes. This ends up being // something like O(3n), but it still beats O(n^2) for large numbers of pads. std::map padNumberToGroupIdxMap = MapPadNumbersToNetTieGroups(); // Now collect all the footprint items which are on copper layers std::vector copperItems; for( BOARD_ITEM* item : m_drawings ) { if( item->IsOnCopperLayer() ) copperItems.push_back( item ); item->RunOnChildren( [&]( BOARD_ITEM* descendent ) { if( descendent->IsOnCopperLayer() ) copperItems.push_back( descendent ); }, RECURSE_MODE::RECURSE ); } for( ZONE* zone : m_zones ) { if( !zone->GetIsRuleArea() && zone->IsOnCopperLayer() ) copperItems.push_back( zone ); } for( PCB_FIELD* field : m_fields ) { if( field->IsOnCopperLayer() ) copperItems.push_back( field ); } for( PCB_LAYER_ID layer : { F_Cu, In1_Cu, B_Cu } ) { // Next, build a polygon-set for the copper on this layer. We don't really care about // nets here, we just want to end up with a set of outlines describing the distinct // copper polygons of the footprint. SHAPE_POLY_SET copperOutlines; std::map> outlineIdxToPadsMap; for( BOARD_ITEM* item : copperItems ) { if( item->IsOnLayer( layer ) ) item->TransformShapeToPolygon( copperOutlines, layer, 0, GetMaxError(), ERROR_OUTSIDE ); } copperOutlines.Simplify(); // Index each pad to the outline in the set that it is part of. for( const PAD* pad : m_pads ) { for( int ii = 0; ii < copperOutlines.OutlineCount(); ++ii ) { if( pad->GetEffectiveShape( layer )->Collide( &copperOutlines.Outline( ii ), 0 ) ) outlineIdxToPadsMap[ ii ].emplace_back( pad ); } } // Finally, ensure that each outline which contains multiple pads has all its pads // listed in an allowed-shorting group. for( const auto& [ outlineIdx, pads ] : outlineIdxToPadsMap ) { if( pads.size() > 1 ) { const PAD* firstPad = pads[0]; int firstGroupIdx = padNumberToGroupIdxMap[ firstPad->GetNumber() ]; for( size_t ii = 1; ii < pads.size(); ++ii ) { const PAD* thisPad = pads[ii]; int thisGroupIdx = padNumberToGroupIdxMap[ thisPad->GetNumber() ]; if( thisGroupIdx < 0 || thisGroupIdx != firstGroupIdx ) { BOARD_ITEM* shortingItem = nullptr; VECTOR2I pos = ( firstPad->GetPosition() + thisPad->GetPosition() ) / 2; pos = copperOutlines.Outline( outlineIdx ).NearestPoint( pos ); for( BOARD_ITEM* item : copperItems ) { if( item->HitTest( pos, 1 ) ) { shortingItem = item; break; } } if( shortingItem ) aErrorHandler( shortingItem, firstPad, thisPad, pos ); else aErrorHandler( firstPad, thisPad, nullptr, pos ); } } } } } } void FOOTPRINT::CheckNetTiePadGroups( const std::function& aErrorHandler ) { std::set padNumbers; wxString msg; for( const auto& [ padNumber, _ ] : MapPadNumbersToNetTieGroups() ) { const PAD* pad = FindPadByNumber( padNumber ); if( !pad ) { msg.Printf( _( "(net-tie pad group contains unknown pad number %s)" ), padNumber ); aErrorHandler( msg ); } else if( !padNumbers.insert( pad->GetNumber() ).second ) { msg.Printf( _( "(pad %s appears in more than one net-tie pad group)" ), padNumber ); aErrorHandler( msg ); } } } void FOOTPRINT::CheckClippedSilk( const std::function& aErrorHandler ) { auto checkColliding = [&]( BOARD_ITEM* item, BOARD_ITEM* other ) { for( PCB_LAYER_ID silk : { F_SilkS, B_SilkS } ) { PCB_LAYER_ID mask = silk == F_SilkS ? F_Mask : B_Mask; if( !item->IsOnLayer( silk ) || !other->IsOnLayer( mask ) ) continue; std::shared_ptr itemShape = item->GetEffectiveShape( silk ); std::shared_ptr otherShape = other->GetEffectiveShape( mask ); int actual; VECTOR2I pos; if( itemShape->Collide( otherShape.get(), 0, &actual, &pos ) ) aErrorHandler( item, other, pos ); } }; for( BOARD_ITEM* item : m_drawings ) { for( BOARD_ITEM* other : m_drawings ) { if( other != item ) checkColliding( item, other ); } for( PAD* pad : m_pads ) checkColliding( item, pad ); } } void FOOTPRINT::swapData( BOARD_ITEM* aImage ) { wxASSERT( aImage->Type() == PCB_FOOTPRINT_T ); FOOTPRINT* image = static_cast( aImage ); std::swap( *this, *image ); RunOnChildren( [&]( BOARD_ITEM* child ) { child->SetParent( this ); }, RECURSE_MODE::NO_RECURSE ); image->RunOnChildren( [&]( BOARD_ITEM* child ) { child->SetParent( image ); }, RECURSE_MODE::NO_RECURSE ); } bool FOOTPRINT::HasThroughHolePads() const { for( PAD* pad : Pads() ) { if( pad->GetAttribute() != PAD_ATTRIB::SMD ) return true; } return false; } bool FOOTPRINT::operator==( const BOARD_ITEM& aOther ) const { if( aOther.Type() != PCB_FOOTPRINT_T ) return false; const FOOTPRINT& other = static_cast( aOther ); return *this == other; } bool FOOTPRINT::operator==( const FOOTPRINT& aOther ) const { if( m_pads.size() != aOther.m_pads.size() ) return false; for( size_t ii = 0; ii < m_pads.size(); ++ii ) { if( !( *m_pads[ii] == *aOther.m_pads[ii] ) ) return false; } if( m_drawings.size() != aOther.m_drawings.size() ) return false; for( size_t ii = 0; ii < m_drawings.size(); ++ii ) { if( !( *m_drawings[ii] == *aOther.m_drawings[ii] ) ) return false; } if( m_zones.size() != aOther.m_zones.size() ) return false; for( size_t ii = 0; ii < m_zones.size(); ++ii ) { if( !( *m_zones[ii] == *aOther.m_zones[ii] ) ) return false; } // Compare fields in ordinally-sorted order std::vector fields, otherFields; GetFields( fields, false ); aOther.GetFields( otherFields, false ); if( fields.size() != otherFields.size() ) return false; for( size_t ii = 0; ii < fields.size(); ++ii ) { if( fields[ii] ) { if( !( *fields[ii] == *otherFields[ii] ) ) return false; } } return true; } double FOOTPRINT::Similarity( const BOARD_ITEM& aOther ) const { if( aOther.Type() != PCB_FOOTPRINT_T ) return 0.0; const FOOTPRINT& other = static_cast( aOther ); double similarity = 1.0; for( const PAD* pad : m_pads) { const PAD* otherPad = other.FindPadByNumber( pad->GetNumber() ); if( !otherPad ) continue; similarity *= pad->Similarity( *otherPad ); } return similarity; } bool FOOTPRINT::cmp_drawings::operator()( const BOARD_ITEM* itemA, const BOARD_ITEM* itemB ) const { if( itemA->Type() != itemB->Type() ) return itemA->Type() < itemB->Type(); if( itemA->GetLayer() != itemB->GetLayer() ) return itemA->GetLayer() < itemB->GetLayer(); if( itemA->Type() == PCB_SHAPE_T ) { const PCB_SHAPE* dwgA = static_cast( itemA ); const PCB_SHAPE* dwgB = static_cast( itemB ); if( dwgA->GetShape() != dwgB->GetShape() ) return dwgA->GetShape() < dwgB->GetShape(); // GetStart() and GetEnd() have no meaning with polygons. // We cannot use them for sorting polygons if( dwgA->GetShape() != SHAPE_T::POLY ) { if( dwgA->GetStart().x != dwgB->GetStart().x ) return dwgA->GetStart().x < dwgB->GetStart().x; if( dwgA->GetStart().y != dwgB->GetStart().y ) return dwgA->GetStart().y < dwgB->GetStart().y; if( dwgA->GetEnd().x != dwgB->GetEnd().x ) return dwgA->GetEnd().x < dwgB->GetEnd().x; if( dwgA->GetEnd().y != dwgB->GetEnd().y ) return dwgA->GetEnd().y < dwgB->GetEnd().y; } if( dwgA->GetShape() == SHAPE_T::ARC ) { if( dwgA->GetCenter().x != dwgB->GetCenter().x ) return dwgA->GetCenter().x < dwgB->GetCenter().x; if( dwgA->GetCenter().y != dwgB->GetCenter().y ) return dwgA->GetCenter().y < dwgB->GetCenter().y; } else if( dwgA->GetShape() == SHAPE_T::BEZIER ) { if( dwgA->GetBezierC1().x != dwgB->GetBezierC1().x ) return dwgA->GetBezierC1().x < dwgB->GetBezierC1().x; if( dwgA->GetBezierC1().y != dwgB->GetBezierC1().y ) return dwgA->GetBezierC1().y < dwgB->GetBezierC1().y; if( dwgA->GetBezierC2().x != dwgB->GetBezierC2().x ) return dwgA->GetBezierC2().x < dwgB->GetBezierC2().x; if( dwgA->GetBezierC2().y != dwgB->GetBezierC2().y ) return dwgA->GetBezierC2().y < dwgB->GetBezierC2().y; } else if( dwgA->GetShape() == SHAPE_T::POLY ) { if( dwgA->GetPolyShape().TotalVertices() != dwgB->GetPolyShape().TotalVertices() ) return dwgA->GetPolyShape().TotalVertices() < dwgB->GetPolyShape().TotalVertices(); for( int ii = 0; ii < dwgA->GetPolyShape().TotalVertices(); ++ii ) { if( dwgA->GetPolyShape().CVertex( ii ).x != dwgB->GetPolyShape().CVertex( ii ).x ) return dwgA->GetPolyShape().CVertex( ii ).x < dwgB->GetPolyShape().CVertex( ii ).x; if( dwgA->GetPolyShape().CVertex( ii ).y != dwgB->GetPolyShape().CVertex( ii ).y ) return dwgA->GetPolyShape().CVertex( ii ).y < dwgB->GetPolyShape().CVertex( ii ).y; } } if( dwgA->GetWidth() != dwgB->GetWidth() ) return dwgA->GetWidth() < dwgB->GetWidth(); } if( itemA->m_Uuid != itemB->m_Uuid ) return itemA->m_Uuid < itemB->m_Uuid; return itemA < itemB; } bool FOOTPRINT::cmp_pads::operator()( const PAD* aFirst, const PAD* aSecond ) const { if( aFirst->GetNumber() != aSecond->GetNumber() ) return StrNumCmp( aFirst->GetNumber(), aSecond->GetNumber() ) < 0; if( aFirst->GetFPRelativePosition().x != aSecond->GetFPRelativePosition().x ) return aFirst->GetFPRelativePosition().x < aSecond->GetFPRelativePosition().x; if( aFirst->GetFPRelativePosition().y != aSecond->GetFPRelativePosition().y ) return aFirst->GetFPRelativePosition().y < aSecond->GetFPRelativePosition().y; std::optional padCopperMatches; // Pick the "most complex" padstack to iterate const PAD* checkPad = aFirst; if( aSecond->Padstack().Mode() == PADSTACK::MODE::CUSTOM || ( aSecond->Padstack().Mode() == PADSTACK::MODE::FRONT_INNER_BACK && aFirst->Padstack().Mode() == PADSTACK::MODE::NORMAL ) ) { checkPad = aSecond; } checkPad->Padstack().ForEachUniqueLayer( [&]( PCB_LAYER_ID aLayer ) { if( aFirst->GetSize( aLayer ).x != aSecond->GetSize( aLayer ).x ) padCopperMatches = aFirst->GetSize( aLayer ).x < aSecond->GetSize( aLayer ).x; else if( aFirst->GetSize( aLayer ).y != aSecond->GetSize( aLayer ).y ) padCopperMatches = aFirst->GetSize( aLayer ).y < aSecond->GetSize( aLayer ).y; else if( aFirst->GetShape( aLayer ) != aSecond->GetShape( aLayer ) ) padCopperMatches = aFirst->GetShape( aLayer ) < aSecond->GetShape( aLayer ); } ); if( padCopperMatches.has_value() ) return *padCopperMatches; if( aFirst->GetLayerSet() != aSecond->GetLayerSet() ) return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq(); if( aFirst->m_Uuid != aSecond->m_Uuid ) return aFirst->m_Uuid < aSecond->m_Uuid; return aFirst < aSecond; } #if 0 bool FOOTPRINT::cmp_padstack::operator()( const PAD* aFirst, const PAD* aSecond ) const { if( aFirst->GetSize().x != aSecond->GetSize().x ) return aFirst->GetSize().x < aSecond->GetSize().x; if( aFirst->GetSize().y != aSecond->GetSize().y ) return aFirst->GetSize().y < aSecond->GetSize().y; if( aFirst->GetShape() != aSecond->GetShape() ) return aFirst->GetShape() < aSecond->GetShape(); if( aFirst->GetLayerSet() != aSecond->GetLayerSet() ) return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq(); if( aFirst->GetDrillSizeX() != aSecond->GetDrillSizeX() ) return aFirst->GetDrillSizeX() < aSecond->GetDrillSizeX(); if( aFirst->GetDrillSizeY() != aSecond->GetDrillSizeY() ) return aFirst->GetDrillSizeY() < aSecond->GetDrillSizeY(); if( aFirst->GetDrillShape() != aSecond->GetDrillShape() ) return aFirst->GetDrillShape() < aSecond->GetDrillShape(); if( aFirst->GetAttribute() != aSecond->GetAttribute() ) return aFirst->GetAttribute() < aSecond->GetAttribute(); if( aFirst->GetOrientation() != aSecond->GetOrientation() ) return aFirst->GetOrientation() < aSecond->GetOrientation(); if( aFirst->GetSolderMaskExpansion() != aSecond->GetSolderMaskExpansion() ) return aFirst->GetSolderMaskExpansion() < aSecond->GetSolderMaskExpansion(); if( aFirst->GetSolderPasteMargin() != aSecond->GetSolderPasteMargin() ) return aFirst->GetSolderPasteMargin() < aSecond->GetSolderPasteMargin(); if( aFirst->GetLocalSolderMaskMargin() != aSecond->GetLocalSolderMaskMargin() ) return aFirst->GetLocalSolderMaskMargin() < aSecond->GetLocalSolderMaskMargin(); const std::shared_ptr& firstShape = aFirst->GetEffectivePolygon( ERROR_INSIDE ); const std::shared_ptr& secondShape = aSecond->GetEffectivePolygon( ERROR_INSIDE ); if( firstShape->VertexCount() != secondShape->VertexCount() ) return firstShape->VertexCount() < secondShape->VertexCount(); for( int ii = 0; ii < firstShape->VertexCount(); ++ii ) { if( firstShape->CVertex( ii ).x != secondShape->CVertex( ii ).x ) return firstShape->CVertex( ii ).x < secondShape->CVertex( ii ).x; if( firstShape->CVertex( ii ).y != secondShape->CVertex( ii ).y ) return firstShape->CVertex( ii ).y < secondShape->CVertex( ii ).y; } return false; } #endif bool FOOTPRINT::cmp_zones::operator()( const ZONE* aFirst, const ZONE* aSecond ) const { if( aFirst->GetAssignedPriority() != aSecond->GetAssignedPriority() ) return aFirst->GetAssignedPriority() < aSecond->GetAssignedPriority(); if( aFirst->GetLayerSet() != aSecond->GetLayerSet() ) return aFirst->GetLayerSet().Seq() < aSecond->GetLayerSet().Seq(); if( aFirst->Outline()->TotalVertices() != aSecond->Outline()->TotalVertices() ) return aFirst->Outline()->TotalVertices() < aSecond->Outline()->TotalVertices(); for( int ii = 0; ii < aFirst->Outline()->TotalVertices(); ++ii ) { if( aFirst->Outline()->CVertex( ii ).x != aSecond->Outline()->CVertex( ii ).x ) return aFirst->Outline()->CVertex( ii ).x < aSecond->Outline()->CVertex( ii ).x; if( aFirst->Outline()->CVertex( ii ).y != aSecond->Outline()->CVertex( ii ).y ) return aFirst->Outline()->CVertex( ii ).y < aSecond->Outline()->CVertex( ii ).y; } if( aFirst->m_Uuid != aSecond->m_Uuid ) return aFirst->m_Uuid < aSecond->m_Uuid; return aFirst < aSecond; } void FOOTPRINT::TransformPadsToPolySet( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aMaxError, ERROR_LOC aErrorLoc ) const { auto processPad = [&]( const PAD* pad, PCB_LAYER_ID padLayer ) { VECTOR2I clearance( aClearance, aClearance ); switch( aLayer ) { case F_Mask: case B_Mask: clearance.x += pad->GetSolderMaskExpansion( padLayer ); clearance.y += pad->GetSolderMaskExpansion( padLayer ); break; case F_Paste: case B_Paste: clearance += pad->GetSolderPasteMargin( padLayer ); break; default: break; } // Our standard TransformShapeToPolygon() routines can't handle differing x:y clearance // values (which get generated when a relative paste margin is used with an oblong pad). // So we apply this huge hack and fake a larger pad to run the transform on. // Of course being a hack it falls down when dealing with custom shape pads (where the // size is only the size of the anchor), so for those we punt and just use clearance.x. if( ( clearance.x < 0 || clearance.x != clearance.y ) && pad->GetShape( padLayer ) != PAD_SHAPE::CUSTOM ) { VECTOR2I dummySize = pad->GetSize( padLayer ) + clearance + clearance; if( dummySize.x <= 0 || dummySize.y <= 0 ) return; PAD dummy( *pad ); dummy.SetSize( padLayer, dummySize ); dummy.TransformShapeToPolygon( aBuffer, padLayer, 0, aMaxError, aErrorLoc ); } else { pad->TransformShapeToPolygon( aBuffer, padLayer, clearance.x, aMaxError, aErrorLoc ); } }; for( const PAD* pad : m_pads ) { if( !pad->FlashLayer( aLayer ) ) continue; if( aLayer == UNDEFINED_LAYER ) { pad->Padstack().ForEachUniqueLayer( [&]( PCB_LAYER_ID l ) { processPad( pad, l ); } ); } else { processPad( pad, aLayer ); } } } void FOOTPRINT::TransformFPShapesToPolySet( SHAPE_POLY_SET& aBuffer, PCB_LAYER_ID aLayer, int aClearance, int aError, ERROR_LOC aErrorLoc, bool aIncludeText, bool aIncludeShapes, bool aIncludePrivateItems ) const { for( BOARD_ITEM* item : GraphicalItems() ) { if( GetPrivateLayers().test( item->GetLayer() ) && !aIncludePrivateItems ) continue; if( item->Type() == PCB_TEXT_T && aIncludeText ) { PCB_TEXT* text = static_cast( item ); if( aLayer != UNDEFINED_LAYER && text->GetLayer() == aLayer ) text->TransformTextToPolySet( aBuffer, aClearance, aError, aErrorLoc ); } if( item->Type() == PCB_TEXTBOX_T && aIncludeText ) { PCB_TEXTBOX* textbox = static_cast( item ); if( aLayer != UNDEFINED_LAYER && textbox->GetLayer() == aLayer ) { // border if( textbox->IsBorderEnabled() ) textbox->PCB_SHAPE::TransformShapeToPolygon( aBuffer, aLayer, 0, aError, aErrorLoc ); // text textbox->TransformTextToPolySet( aBuffer, 0, aError, aErrorLoc ); } } if( item->Type() == PCB_SHAPE_T && aIncludeShapes ) { const PCB_SHAPE* shape = static_cast( item ); if( aLayer != UNDEFINED_LAYER && shape->GetLayer() == aLayer ) shape->TransformShapeToPolySet( aBuffer, aLayer, 0, aError, aErrorLoc ); } } if( aIncludeText ) { for( const PCB_FIELD* field : m_fields ) { if( field->GetLayer() == aLayer && field->IsVisible() ) field->TransformTextToPolySet( aBuffer, aClearance, aError, aErrorLoc ); } } } std::set FOOTPRINT::GetFonts() const { using PERMISSION = KIFONT::OUTLINE_FONT::EMBEDDING_PERMISSION; std::set fonts; auto processItem = [&]( BOARD_ITEM* item ) { if( EDA_TEXT* text = dynamic_cast( item ) ) { KIFONT::FONT* font = text->GetFont(); if( font && font->IsOutline() ) { KIFONT::OUTLINE_FONT* outlineFont = static_cast( font ); PERMISSION permission = outlineFont->GetEmbeddingPermission(); if( permission == PERMISSION::EDITABLE || permission == PERMISSION::INSTALLABLE ) fonts.insert( outlineFont ); } } }; for( BOARD_ITEM* item : GraphicalItems() ) processItem( item ); for( PCB_FIELD* field : GetFields() ) processItem( field ); return fonts; } void FOOTPRINT::EmbedFonts() { for( KIFONT::OUTLINE_FONT* font : GetFonts() ) { EMBEDDED_FILES::EMBEDDED_FILE* file = GetEmbeddedFiles()->AddFile( font->GetFileName(), false ); file->type = EMBEDDED_FILES::EMBEDDED_FILE::FILE_TYPE::FONT; } } void FOOTPRINT::SetStaticComponentClass( const COMPONENT_CLASS* aClass ) const { m_componentClassCacheProxy->SetStaticComponentClass( aClass ); } const COMPONENT_CLASS* FOOTPRINT::GetStaticComponentClass() const { return m_componentClassCacheProxy->GetStaticComponentClass(); } void FOOTPRINT::RecomputeComponentClass() const { m_componentClassCacheProxy->RecomputeComponentClass(); } const COMPONENT_CLASS* FOOTPRINT::GetComponentClass() const { return m_componentClassCacheProxy->GetComponentClass(); } wxString FOOTPRINT::GetComponentClassAsString() const { if( !m_componentClassCacheProxy->GetComponentClass()->IsEmpty() ) return m_componentClassCacheProxy->GetComponentClass()->GetName(); return wxEmptyString; } void FOOTPRINT::ResolveComponentClassNames( BOARD* aBoard, const std::unordered_set& aComponentClassNames ) { const COMPONENT_CLASS* componentClass = aBoard->GetComponentClassManager().GetEffectiveStaticComponentClass( aComponentClassNames ); SetStaticComponentClass( componentClass ); } void FOOTPRINT::InvalidateComponentClassCache() const { m_componentClassCacheProxy->InvalidateCache(); } static struct FOOTPRINT_DESC { FOOTPRINT_DESC() { ENUM_MAP& zcMap = ENUM_MAP::Instance(); if( zcMap.Choices().GetCount() == 0 ) { zcMap.Undefined( ZONE_CONNECTION::INHERITED ); zcMap.Map( ZONE_CONNECTION::INHERITED, _HKI( "Inherited" ) ) .Map( ZONE_CONNECTION::NONE, _HKI( "None" ) ) .Map( ZONE_CONNECTION::THERMAL, _HKI( "Thermal reliefs" ) ) .Map( ZONE_CONNECTION::FULL, _HKI( "Solid" ) ) .Map( ZONE_CONNECTION::THT_THERMAL, _HKI( "Thermal reliefs for PTH" ) ); } ENUM_MAP& layerEnum = ENUM_MAP::Instance(); if( layerEnum.Choices().GetCount() == 0 ) { layerEnum.Undefined( UNDEFINED_LAYER ); for( PCB_LAYER_ID layer : LSET::AllLayersMask() ) layerEnum.Map( layer, LSET::Name( layer ) ); } wxPGChoices fpLayers; // footprints might be placed only on F.Cu & B.Cu fpLayers.Add( LSET::Name( F_Cu ), F_Cu ); fpLayers.Add( LSET::Name( B_Cu ), B_Cu ); PROPERTY_MANAGER& propMgr = PROPERTY_MANAGER::Instance(); REGISTER_TYPE( FOOTPRINT ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.AddTypeCast( new TYPE_CAST ); propMgr.InheritsAfter( TYPE_HASH( FOOTPRINT ), TYPE_HASH( BOARD_ITEM ) ); propMgr.InheritsAfter( TYPE_HASH( FOOTPRINT ), TYPE_HASH( BOARD_ITEM_CONTAINER ) ); auto layer = new PROPERTY_ENUM( _HKI( "Layer" ), &FOOTPRINT::SetLayerAndFlip, &FOOTPRINT::GetLayer ); layer->SetChoices( fpLayers ); propMgr.ReplaceProperty( TYPE_HASH( BOARD_ITEM ), _HKI( "Layer" ), layer ); propMgr.AddProperty( new PROPERTY( _HKI( "Orientation" ), &FOOTPRINT::SetOrientationDegrees, &FOOTPRINT::GetOrientationDegrees, PROPERTY_DISPLAY::PT_DEGREE ) ); const wxString groupFields = _HKI( "Fields" ); propMgr.AddProperty( new PROPERTY( _HKI( "Reference" ), &FOOTPRINT::SetReference, &FOOTPRINT::GetReferenceAsString ), groupFields ); propMgr.AddProperty( new PROPERTY( _HKI( "Value" ), &FOOTPRINT::SetValue, &FOOTPRINT::GetValueAsString ), groupFields ); propMgr.AddProperty( new PROPERTY( _HKI( "Library Link" ), NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetFPIDAsString ), groupFields ); propMgr.AddProperty( new PROPERTY( _HKI( "Library Description" ), NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetLibDescription ), groupFields ); propMgr.AddProperty( new PROPERTY( _HKI( "Keywords" ), NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetKeywords ), groupFields ); // Note: Also used by DRC engine propMgr.AddProperty( new PROPERTY( _HKI( "Component Class" ), NO_SETTER( FOOTPRINT, wxString ), &FOOTPRINT::GetComponentClassAsString ), groupFields ) .SetIsHiddenFromLibraryEditors(); const wxString groupAttributes = _HKI( "Attributes" ); propMgr.AddProperty( new PROPERTY( _HKI( "Not in Schematic" ), &FOOTPRINT::SetBoardOnly, &FOOTPRINT::IsBoardOnly ), groupAttributes ); propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Position Files" ), &FOOTPRINT::SetExcludedFromPosFiles, &FOOTPRINT::IsExcludedFromPosFiles ), groupAttributes ); propMgr.AddProperty( new PROPERTY( _HKI( "Exclude From Bill of Materials" ), &FOOTPRINT::SetExcludedFromBOM, &FOOTPRINT::IsExcludedFromBOM ), groupAttributes ); propMgr.AddProperty( new PROPERTY( _HKI( "Do not Populate" ), &FOOTPRINT::SetDNP, &FOOTPRINT::IsDNP ), groupAttributes ); const wxString groupOverrides = _HKI( "Overrides" ); propMgr.AddProperty( new PROPERTY( _HKI( "Exempt From Courtyard Requirement" ), &FOOTPRINT::SetAllowMissingCourtyard, &FOOTPRINT::AllowMissingCourtyard ), groupOverrides ); propMgr.AddProperty( new PROPERTY>( _HKI( "Clearance Override" ), &FOOTPRINT::SetLocalClearance, &FOOTPRINT::GetLocalClearance, PROPERTY_DISPLAY::PT_SIZE ), groupOverrides ); propMgr.AddProperty( new PROPERTY>( _HKI( "Solderpaste Margin Override" ), &FOOTPRINT::SetLocalSolderPasteMargin, &FOOTPRINT::GetLocalSolderPasteMargin, PROPERTY_DISPLAY::PT_SIZE ), groupOverrides ); propMgr.AddProperty( new PROPERTY>( _HKI( "Solderpaste Margin Ratio Override" ), &FOOTPRINT::SetLocalSolderPasteMarginRatio, &FOOTPRINT::GetLocalSolderPasteMarginRatio, PROPERTY_DISPLAY::PT_RATIO ), groupOverrides ); propMgr.AddProperty( new PROPERTY_ENUM( _HKI( "Zone Connection Style" ), &FOOTPRINT::SetLocalZoneConnection, &FOOTPRINT::GetLocalZoneConnection ), groupOverrides ); } } _FOOTPRINT_DESC;