/* * This program source code file is part of KiCad, a free EDA CAD application. * * 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 "multichannel_tool.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MULTICHANNEL_EXTRA_DEBUG static const wxString traceMultichannelTool = wxT( "MULTICHANNEL_TOOL" ); MULTICHANNEL_TOOL::MULTICHANNEL_TOOL() : PCB_TOOL_BASE( "pcbnew.Multichannel" ) { } MULTICHANNEL_TOOL::~MULTICHANNEL_TOOL() { } void MULTICHANNEL_TOOL::setTransitions() { Go( &MULTICHANNEL_TOOL::AutogenerateRuleAreas, PCB_ACTIONS::generatePlacementRuleAreas.MakeEvent() ); Go( &MULTICHANNEL_TOOL::repeatLayout, PCB_ACTIONS::repeatLayout.MakeEvent() ); } bool MULTICHANNEL_TOOL::identifyComponentsInRuleArea( ZONE* aRuleArea, std::set& aComponents ) { PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER ); PCBEXPR_UCODE ucode; PCBEXPR_CONTEXT ctx, preflightCtx; auto reportError = [&]( const wxString& aMessage, int aOffset ) { wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage ); }; ctx.SetErrorCallback( reportError ); preflightCtx.SetErrorCallback( reportError ); compiler.SetErrorCallback( reportError ); //compiler.SetDebugReporter( m_reporter ); wxLogTrace( traceMultichannelTool, wxT( "rule area '%s'"), aRuleArea->GetZoneName() ); wxString ruleText; switch( aRuleArea->GetRuleAreaPlacementSourceType() ) { case RULE_AREA_PLACEMENT_SOURCE_TYPE::SHEETNAME: { ruleText = wxT( "A.memberOfSheetOrChildren('" ) + aRuleArea->GetRuleAreaPlacementSource() + wxT( "')" ); break; } case RULE_AREA_PLACEMENT_SOURCE_TYPE::COMPONENT_CLASS: ruleText = wxT( "A.hasComponentClass('" ) + aRuleArea->GetRuleAreaPlacementSource() + wxT( "')" ); break; case RULE_AREA_PLACEMENT_SOURCE_TYPE::GROUP: ruleText = wxT( "A.memberOfGroup('" ) + aRuleArea->GetRuleAreaPlacementSource() + wxT( "')" ); break; } auto ok = compiler.Compile( ruleText, &ucode, &preflightCtx ); if( !ok ) { return false; } for( FOOTPRINT* fp : board()->Footprints() ) { ctx.SetItems( fp, fp ); auto val = ucode.Run( &ctx ); if( val->AsDouble() != 0.0 ) { wxLogTrace( traceMultichannelTool, wxT( " - %s [sheet %s]" ), fp->GetReference(), fp->GetSheetname() ); aComponents.insert( fp ); } } return true; } bool MULTICHANNEL_TOOL::findOtherItemsInRuleArea( ZONE* aRuleArea, std::set& aItems ) { std::vector result; PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER ); PCBEXPR_UCODE ucode; PCBEXPR_CONTEXT ctx, preflightCtx; auto reportError = [&]( const wxString& aMessage, int aOffset ) { wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage ); }; ctx.SetErrorCallback( reportError ); preflightCtx.SetErrorCallback( reportError ); compiler.SetErrorCallback( reportError ); bool restoreBlankName = false; if( aRuleArea->GetZoneName().IsEmpty() ) { restoreBlankName = true; aRuleArea->SetZoneName( aRuleArea->m_Uuid.AsString() ); } wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ), aRuleArea->GetZoneName() ); if( !compiler.Compile( ruleText, &ucode, &preflightCtx ) ) { if( restoreBlankName ) aRuleArea->SetZoneName( wxEmptyString ); return false; } auto testAndAdd = [&]( BOARD_ITEM* aItem ) { ctx.SetItems( aItem, aItem ); auto val = ucode.Run( &ctx ); if( val->AsDouble() != 0.0 ) aItems.insert( aItem ); }; for( ZONE* zone : board()->Zones() ) { if( zone == aRuleArea ) continue; testAndAdd( zone ); } for( BOARD_ITEM* drawing : board()->Drawings() ) testAndAdd( drawing ); for( PCB_GROUP* group : board()->Groups() ) { // A group is cloned in its entirety if *all* children are contained bool addGroup = true; group->RunOnChildren( [&]( BOARD_ITEM* aItem ) { if( aItem->IsType( { PCB_ZONE_T, PCB_SHAPE_T, PCB_DIMENSION_T } ) ) { ctx.SetItems( aItem, aItem ); auto val = ucode.Run( &ctx ); if( val->AsDouble() == 0.0 ) addGroup = false; } }, RECURSE_MODE::RECURSE ); if( addGroup ) aItems.insert( group ); } if( restoreBlankName ) aRuleArea->SetZoneName( wxEmptyString ); return true; } std::set MULTICHANNEL_TOOL::queryComponentsInSheet( wxString aSheetName ) const { std::set rv; if( aSheetName.EndsWith( wxT( "/" ) ) ) aSheetName.RemoveLast(); for( auto& fp : board()->Footprints() ) { auto sn = fp->GetSheetname(); if( sn.EndsWith( wxT( "/" ) ) ) sn.RemoveLast(); if( sn == aSheetName ) { rv.insert( fp ); } } return rv; } std::set MULTICHANNEL_TOOL::queryComponentsInComponentClass( const wxString& aComponentClassName ) const { std::set rv; for( auto& fp : board()->Footprints() ) { if( fp->GetComponentClass()->ContainsClassName( aComponentClassName ) ) rv.insert( fp ); } return rv; } std::set MULTICHANNEL_TOOL::queryComponentsInGroup( const wxString& aGroupName ) const { std::set rv; for( auto& group : board()->Groups() ) { if( group->GetName() == aGroupName ) { for( EDA_ITEM* item : group->GetItems() ) { if( item->Type() == PCB_FOOTPRINT_T ) rv.insert( static_cast( item ) ); } } } return rv; } const SHAPE_LINE_CHAIN MULTICHANNEL_TOOL::buildRAOutline( std::set& aFootprints, int aMargin ) { std::vector bbCorners; bbCorners.reserve( aFootprints.size() * 4 ); for( auto fp : aFootprints ) { const BOX2I bb = fp->GetBoundingBox( false ).GetInflated( aMargin ); KIGEOM::CollectBoxCorners( bb, bbCorners ); } std::vector hullVertices; BuildConvexHull( hullVertices, bbCorners ); SHAPE_LINE_CHAIN hull( hullVertices ); // Make the newly computed convex hull use only 90 degree segments return KIGEOM::RectifyPolygon( hull ); } void MULTICHANNEL_TOOL::QuerySheetsAndComponentClasses() { using PathAndName = std::pair; std::set uniqueSheets; std::set uniqueComponentClasses; std::set uniqueGroups; m_areas.m_areas.clear(); for( const FOOTPRINT* fp : board()->Footprints() ) { uniqueSheets.insert( PathAndName( fp->GetSheetname(), fp->GetSheetfile() ) ); const COMPONENT_CLASS* compClass = fp->GetComponentClass(); for( const COMPONENT_CLASS* singleClass : compClass->GetConstituentClasses() ) uniqueComponentClasses.insert( singleClass->GetName() ); if( fp->GetParentGroup() && !fp->GetParentGroup()->GetName().IsEmpty() ) uniqueGroups.insert( fp->GetParentGroup()->GetName() ); } for( const PathAndName& sheet : uniqueSheets ) { RULE_AREA ent; ent.m_sourceType = RULE_AREA_PLACEMENT_SOURCE_TYPE::SHEETNAME; ent.m_generateEnabled = false; ent.m_sheetPath = sheet.first; ent.m_sheetName = sheet.second; ent.m_components = queryComponentsInSheet( ent.m_sheetPath ); m_areas.m_areas.push_back( ent ); wxLogTrace( traceMultichannelTool, wxT("found sheet '%s' @ '%s' s %d\n"), ent.m_sheetName, ent.m_sheetPath, (int)m_areas.m_areas.size() ); } for( const wxString& compClass : uniqueComponentClasses ) { RULE_AREA ent; ent.m_sourceType = RULE_AREA_PLACEMENT_SOURCE_TYPE::COMPONENT_CLASS; ent.m_generateEnabled = false; ent.m_componentClass = compClass; ent.m_components = queryComponentsInComponentClass( ent.m_componentClass ); m_areas.m_areas.push_back( ent ); wxLogTrace( traceMultichannelTool, wxT( "found component class '%s' s %d\n" ), ent.m_componentClass, static_cast( m_areas.m_areas.size() ) ); } for( const wxString& groupName : uniqueGroups ) { RULE_AREA ent; ent.m_sourceType = RULE_AREA_PLACEMENT_SOURCE_TYPE::GROUP; ent.m_generateEnabled = false; ent.m_groupName = groupName; ent.m_components = queryComponentsInGroup( ent.m_groupName ); m_areas.m_areas.push_back( ent ); wxLogTrace( traceMultichannelTool, wxT( "found group '%s' s %d\n" ), ent.m_componentClass, static_cast( m_areas.m_areas.size() ) ); } } void MULTICHANNEL_TOOL::FindExistingRuleAreas() { m_areas.m_areas.clear(); for( ZONE* zone : board()->Zones() ) { if( !zone->GetIsRuleArea() ) continue; if( !zone->GetRuleAreaPlacementEnabled() ) continue; RULE_AREA area; area.m_existsAlready = true; area.m_area = zone; identifyComponentsInRuleArea( zone, area.m_raFootprints ); area.m_ruleName = zone->GetZoneName(); area.m_center = zone->Outline()->COutline( 0 ).Centre(); m_areas.m_areas.push_back( area ); wxLogTrace( traceMultichannelTool, wxT("RA '%s', %d footprints\n"), area.m_ruleName, (int) area.m_raFootprints.size() ); } wxLogTrace( traceMultichannelTool, wxT("Total RAs found: %d\n"), (int) m_areas.m_areas.size() ); } RULE_AREA* MULTICHANNEL_TOOL::findRAByName( const wxString& aName ) { for( RULE_AREA& ra : m_areas.m_areas ) { if( ra.m_ruleName == aName ) return &ra; } return nullptr; } void MULTICHANNEL_TOOL::UpdatePickedItem( const EDA_ITEM* aItem ) { m_toolMgr->RunAction( PCB_ACTIONS::repeatLayout ); } int MULTICHANNEL_TOOL::repeatLayout( const TOOL_EVENT& aEvent ) { std::vector refRAs; auto isSelectedItemAnRA = []( EDA_ITEM* aItem ) -> ZONE* { if( !aItem || aItem->Type() != PCB_ZONE_T ) return nullptr; ZONE* zone = static_cast( aItem ); if( !zone->GetIsRuleArea() ) return nullptr; if( !zone->GetRuleAreaPlacementEnabled() ) return nullptr; return zone; }; for( EDA_ITEM* item : selection() ) { if( auto zone = isSelectedItemAnRA( item ) ) { refRAs.push_back(zone); } else if ( item->Type() == PCB_GROUP_T ) { PCB_GROUP *group = static_cast( item ); for( EDA_ITEM* grpItem : group->GetItems() ) { if( auto grpZone = isSelectedItemAnRA( grpItem ) ) { refRAs.push_back( grpZone ); } } } } if( refRAs.size() != 1 ) { m_toolMgr->RunAction( PCB_ACTIONS::selectItemInteractively, PCB_PICKER_TOOL::INTERACTIVE_PARAMS { this, _( "Select a reference Rule Area to copy from..." ), [&]( EDA_ITEM* aItem ) { return isSelectedItemAnRA( aItem ) != nullptr; } } ); return 0; } FindExistingRuleAreas(); int status = CheckRACompatibility( refRAs.front() ); if( status < 0 ) return status; if( m_areas.m_areas.size() <= 1 ) { frame()->ShowInfoBarError( _( "No Rule Areas to repeat layout to have been found." ), true ); return 0; } DIALOG_MULTICHANNEL_REPEAT_LAYOUT dialog( frame(), this ); int ret = dialog.ShowModal(); if( ret != wxID_OK ) return 0; return RepeatLayout( aEvent, refRAs.front() ); } int MULTICHANNEL_TOOL::CheckRACompatibility( ZONE *aRefZone ) { m_areas.m_refRA = nullptr; for( RULE_AREA& ra : m_areas.m_areas ) { if( ra.m_area == aRefZone ) { m_areas.m_refRA = &ra; break; } } if( !m_areas.m_refRA ) return -1; m_areas.m_compatMap.clear(); for( RULE_AREA& ra : m_areas.m_areas ) { if( ra.m_area == m_areas.m_refRA->m_area ) continue; m_areas.m_compatMap[&ra] = RULE_AREA_COMPAT_DATA(); resolveConnectionTopology( m_areas.m_refRA, &ra, m_areas.m_compatMap[&ra] ); } return 0; } int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone ) { int totalCopied = 0; BOARD_COMMIT commit( GetManager(), true ); for( auto& targetArea : m_areas.m_compatMap ) { if( !targetArea.second.m_doCopy ) { wxLogTrace( traceMultichannelTool, wxT("skipping copy to RA '%s' (disabled in dialog)\n"), targetArea.first->m_ruleName ); continue; } if( !targetArea.second.m_isOk ) continue; if( !copyRuleAreaContents( targetArea.second.m_matchingComponents, &commit, m_areas.m_refRA, targetArea.first, m_areas.m_options, targetArea.second.m_affectedItems, targetArea.second.m_groupableItems ) ) { auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ), m_areas.m_refRA->m_area->GetZoneName(), targetArea.first->m_area->GetZoneName() ); commit.Revert(); if( Pgm().IsGUI() ) { frame()->ShowInfoBarError( errMsg, true ); } return -1; } totalCopied++; } commit.Push( _( "Repeat layout" ) ); if( m_areas.m_options.m_groupItems ) { BOARD_COMMIT grpCommit( GetManager(), true ); for( auto& targetArea : m_areas.m_compatMap ) { pruneExistingGroups( grpCommit, targetArea.second.m_affectedItems ); PCB_GROUP* grp = new PCB_GROUP( board() ); grpCommit.Add( grp ); for( BOARD_ITEM* item : targetArea.second.m_groupableItems ) { grpCommit.Stage( item, CHT_GROUP ); } } grpCommit.Push( _( "Group repeated items" ) ); } if( Pgm().IsGUI() ) { frame()->ShowInfoBarMsg( wxString::Format( _( "Copied to %d Rule Areas." ), totalCopied ), true ); } return 0; } wxString MULTICHANNEL_TOOL::stripComponentIndex( const wxString& aRef ) const { wxString rv; // fixme: i'm pretty sure this can be written in a simpler way, but I really suck at figuring // out which wx's built in functions would do it for me. And I hate regexps :-) for( auto k : aRef ) { if( !k.IsAscii() ) break; char c; k.GetAsChar( &c ); if( ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || ( c == '_' ) ) rv.Append( k ); else break; } return rv; } int MULTICHANNEL_TOOL::findRoutedConnections( std::set& aOutput, std::shared_ptr aConnectivity, const SHAPE_POLY_SET& aRAPoly, RULE_AREA* aRA, FOOTPRINT* aFp, const REPEAT_LAYOUT_OPTIONS& aOpts ) const { std::set conns; for( PAD* pad : aFp->Pads() ) { auto connectedItems = aConnectivity->GetConnectedItems( pad, EXCLUDE_ZONES | IGNORE_NETS ); for( BOARD_CONNECTED_ITEM* item : connectedItems ) conns.insert( item ); } int count = 0; for( BOARD_ITEM* item : conns ) { // fixme: respect layer sets assigned to each RA if( item->Type() == PCB_PAD_T ) continue; std::shared_ptr effShape = item->GetEffectiveShape( item->GetLayer() ); if( effShape->Collide( &aRAPoly, 0 ) ) { aOutput.insert( item ); count++; } } // The user also will consider tracks and vias that are inside the source area but // not connected to any of the source pads to count as "routing" (e.g. stitching vias) PCBEXPR_COMPILER compiler( new PCBEXPR_UNIT_RESOLVER ); PCBEXPR_UCODE ucode; PCBEXPR_CONTEXT ctx, preflightCtx; auto reportError = [&]( const wxString& aMessage, int aOffset ) { wxLogTrace( traceMultichannelTool, wxT( "ERROR: %s"), aMessage ); }; ctx.SetErrorCallback( reportError ); preflightCtx.SetErrorCallback( reportError ); compiler.SetErrorCallback( reportError ); bool restoreBlankName = false; if( aRA->m_area->GetZoneName().IsEmpty() ) { restoreBlankName = true; aRA->m_area->SetZoneName( aRA->m_area->m_Uuid.AsString() ); } wxString ruleText = wxString::Format( wxT( "A.enclosedByArea('%s')" ), aRA->m_area->GetZoneName() ); auto testAndAdd = [&]( BOARD_ITEM* aItem ) { if( aOutput.contains( aItem ) ) return; ctx.SetItems( aItem, aItem ); auto val = ucode.Run( &ctx ); if( val->AsDouble() != 0.0 ) { aOutput.insert( aItem ); count++; } }; if( compiler.Compile( ruleText, &ucode, &preflightCtx ) ) { for( PCB_TRACK* track : board()->Tracks() ) testAndAdd( track ); } if( restoreBlankName ) aRA->m_area->SetZoneName( wxEmptyString ); return count; } bool MULTICHANNEL_TOOL::copyRuleAreaContents( TMATCH::COMPONENT_MATCHES& aMatches, BOARD_COMMIT* aCommit, RULE_AREA* aRefArea, RULE_AREA* aTargetArea, REPEAT_LAYOUT_OPTIONS aOpts, std::unordered_set& aAffectedItems, std::unordered_set& aGroupableItems ) { // copy RA shapes first SHAPE_LINE_CHAIN refOutline = aRefArea->m_area->Outline()->COutline( 0 ); SHAPE_LINE_CHAIN targetOutline = aTargetArea->m_area->Outline()->COutline( 0 ); VECTOR2I disp = aTargetArea->m_center - aRefArea->m_center; SHAPE_POLY_SET refPoly; refPoly.AddOutline( refOutline ); refPoly.CacheTriangulation( false ); SHAPE_POLY_SET targetPoly; SHAPE_LINE_CHAIN newTargetOutline( refOutline ); newTargetOutline.Move( disp ); targetPoly.AddOutline( newTargetOutline ); targetPoly.CacheTriangulation( false ); auto connectivity = board()->GetConnectivity(); aCommit->Modify( aTargetArea->m_area ); aAffectedItems.insert( aTargetArea->m_area ); aGroupableItems.insert( aTargetArea->m_area ); if( aOpts.m_copyRouting ) { std::set refRouting; std::set targetRouting; wxLogTrace( traceMultichannelTool, wxT("copying routing: %d fps\n"), (int) aMatches.size() ); for( auto& fpPair : aMatches ) { findRoutedConnections( targetRouting, connectivity, targetPoly, aTargetArea, fpPair.second, aOpts ); findRoutedConnections( refRouting, connectivity, refPoly, aRefArea, fpPair.first, aOpts ); wxLogTrace( traceMultichannelTool, wxT("target-routes %d\n"), (int) targetRouting.size() ); } for( BOARD_ITEM* item : targetRouting ) { if( item->IsLocked() && !aOpts.m_includeLockedItems ) continue; // item already removed if( aCommit->GetStatus( item ) != 0 ) continue; if( aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) ) { aAffectedItems.insert( item ); aCommit->Remove( item ); } } for( BOARD_ITEM* item : refRouting ) { if( !aRefArea->m_area->GetLayerSet().Contains( item->GetLayer() ) ) continue; if( !aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) ) continue; BOARD_ITEM* copied = static_cast( item->Clone() ); if( item->Type() == PCB_VIA_T ) { fixupNet( static_cast( item ), static_cast( copied ), aMatches ); } copied->Move( disp ); copied->SetParentGroup( nullptr ); const_cast( copied->m_Uuid ) = KIID(); aGroupableItems.insert( copied ); aCommit->Add( copied ); } } if( aOpts.m_copyOtherItems ) { std::set sourceItems; std::set targetItems; findOtherItemsInRuleArea( aRefArea->m_area, sourceItems ); findOtherItemsInRuleArea( aTargetArea->m_area, targetItems ); for( BOARD_ITEM* item : targetItems ) { if( item->Type() == PCB_TEXT_T && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T ) continue; if( item->IsLocked() && !aOpts.m_includeLockedItems ) continue; // item already removed if( aCommit->GetStatus( item ) != 0 ) continue; if( item->Type() != PCB_ZONE_T ) { if( aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) ) { aAffectedItems.insert( item ); aCommit->Remove( item ); } } else { ZONE* zone = static_cast( item ); // Check all zone layers are included in the rule area bool layerMismatch = false; LSET zoneLayers = zone->GetLayerSet(); for( const PCB_LAYER_ID& layer : zoneLayers ) { if( !aTargetArea->m_area->GetLayerSet().Contains( layer ) ) layerMismatch = true; } if( !layerMismatch ) { aAffectedItems.insert( zone ); aCommit->Remove( zone ); } } } for( BOARD_ITEM* item : sourceItems ) { if( item->Type() == PCB_TEXT_T && item->GetParent() && item->GetParent()->Type() == PCB_FOOTPRINT_T ) continue; BOARD_ITEM* copied = nullptr; if( item->Type() != PCB_ZONE_T ) { if( !aRefArea->m_area->GetLayerSet().Contains( item->GetLayer() ) ) continue; if( !aTargetArea->m_area->GetLayerSet().Contains( item->GetLayer() ) ) continue; if( item->Type() == PCB_GROUP_T ) { copied = static_cast( item )->DeepClone(); } else { copied = static_cast( item->Clone() ); } } else { ZONE* zone = static_cast( item ); // Check all zone layers are included in the rule area bool layerMismatch = false; LSET zoneLayers = zone->GetLayerSet(); for( const PCB_LAYER_ID& layer : zoneLayers ) { if( !aRefArea->m_area->GetLayerSet().Contains( layer ) || !aTargetArea->m_area->GetLayerSet().Contains( layer ) ) { layerMismatch = true; } } if( layerMismatch ) continue; ZONE* targetZone = static_cast( item->Clone() ); fixupNet( zone, targetZone, aMatches ); copied = targetZone; } if( copied ) { copied->ClearFlags(); copied->SetParentGroup( nullptr ); const_cast( copied->m_Uuid ) = KIID(); copied->Move( disp ); aGroupableItems.insert( copied ); aCommit->Add( copied ); } } } aTargetArea->m_area->RemoveAllContours(); aTargetArea->m_area->AddPolygon( newTargetOutline ); aTargetArea->m_area->UnHatchBorder(); aTargetArea->m_area->HatchBorder(); if( aOpts.m_copyPlacement ) { for( auto& fpPair : aMatches ) { FOOTPRINT* refFP = fpPair.first; FOOTPRINT* targetFP = fpPair.second; if( !aRefArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) ) { wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (ref layer)\n" ), refFP->GetReference() ); continue; } if( !aTargetArea->m_area->GetLayerSet().Contains( refFP->GetLayer() ) ) { wxLogTrace( traceMultichannelTool, wxT( "discard ref:%s (target layer)\n" ), refFP->GetReference() ); continue; } // Ignore footprints outside of the rule area if( !refFP->GetEffectiveShape( refFP->GetLayer() )->Collide( &refPoly, 0 ) ) continue; if( targetFP->IsLocked() && !aOpts.m_includeLockedItems ) continue; aCommit->Modify( targetFP ); targetFP->SetLayerAndFlip( refFP->GetLayer() ); targetFP->SetOrientation( refFP->GetOrientation() ); VECTOR2I targetPos = refFP->GetPosition() + disp; targetFP->SetPosition( targetPos ); for( PCB_FIELD* refField : refFP->GetFields() ) { if( !refField->IsVisible() ) continue; PCB_FIELD* targetField = targetFP->GetField( refField->GetName() ); wxCHECK2( targetField, continue ); targetField->SetAttributes( refField->GetAttributes() ); targetField->SetPosition( refField->GetPosition() + disp ); targetField->SetIsKnockout( refField->IsKnockout() ); } aAffectedItems.insert( targetFP ); aGroupableItems.insert( targetFP ); } } return true; } /** * @brief Attempts to modify the assigned net of copied items, especially intended for zones and vias * */ void MULTICHANNEL_TOOL::fixupNet( BOARD_CONNECTED_ITEM* aRef, BOARD_CONNECTED_ITEM* aTarget, TMATCH::COMPONENT_MATCHES& aComponentMatches ) { auto connectivity = board()->GetConnectivity(); const std::vector refConnectedPads = connectivity->GetNetItems( aRef->GetNetCode(), { PCB_PAD_T } ); for( const BOARD_CONNECTED_ITEM* refConItem : refConnectedPads ) { if( refConItem->Type() != PCB_PAD_T ) continue; const PAD* refPad = static_cast( refConItem ); FOOTPRINT* sourceFootprint = refPad->GetParentFootprint(); if( aComponentMatches.contains( sourceFootprint ) ) { const FOOTPRINT* targetFootprint = aComponentMatches[sourceFootprint]; std::vector targetFpPads = targetFootprint->GetPads( refPad->GetNumber() ); if( !targetFpPads.empty() ) { int targetNetCode = targetFpPads[0]->GetNet()->GetNetCode(); aTarget->SetNetCode( targetNetCode ); break; } } } } bool MULTICHANNEL_TOOL::resolveConnectionTopology( RULE_AREA* aRefArea, RULE_AREA* aTargetArea, RULE_AREA_COMPAT_DATA& aMatches ) { using namespace TMATCH; std::unique_ptr cgRef ( CONNECTION_GRAPH::BuildFromFootprintSet( aRefArea->m_raFootprints ) ); std::unique_ptr cgTarget ( CONNECTION_GRAPH::BuildFromFootprintSet( aTargetArea->m_raFootprints ) ); auto status = cgRef->FindIsomorphism( cgTarget.get(), aMatches.m_matchingComponents ); switch( status ) { case CONNECTION_GRAPH::ST_OK: aMatches.m_isOk = true; aMatches.m_errorMsg = _("OK"); break; case CONNECTION_GRAPH::ST_EMPTY: aMatches.m_isOk = false; aMatches.m_errorMsg = _("One or both of the areas has no components assigned."); break; case CONNECTION_GRAPH::ST_COMPONENT_COUNT_MISMATCH: aMatches.m_isOk = false; aMatches.m_errorMsg = _("Component count mismatch"); break; case CONNECTION_GRAPH::ST_ITERATION_COUNT_EXCEEDED: aMatches.m_isOk = false; aMatches.m_errorMsg = _("Iteration count exceeded (timeout)"); break; case CONNECTION_GRAPH::ST_TOPOLOGY_MISMATCH: aMatches.m_isOk = false; aMatches.m_errorMsg = _("Topology mismatch"); break; default: break; } return ( status == TMATCH::CONNECTION_GRAPH::ST_OK ); } bool MULTICHANNEL_TOOL::pruneExistingGroups( COMMIT& aCommit, const std::unordered_set& aItemsToRemove ) { std::deque pending ( board()->Groups() ); while( !pending.empty() ) { auto grp = pending.front(); pending.pop_front(); std::unordered_set& grpItems = grp->GetItems(); size_t n_erased = 0; for( EDA_ITEM* refItem : grpItems ) { if( refItem->Type() == PCB_GROUP_T ) pending.push_back( static_cast(refItem) ); for( BOARD_ITEM* testItem : aItemsToRemove ) { if( refItem->m_Uuid == testItem->m_Uuid ) { aCommit.Stage( refItem, CHT_UNGROUP ); n_erased++; } } } if( n_erased == grpItems.size() ) { aCommit.Stage( grp, CHT_REMOVE ); } } return false; } int MULTICHANNEL_TOOL::AutogenerateRuleAreas( const TOOL_EVENT& aEvent ) { if( Pgm().IsGUI() ) { QuerySheetsAndComponentClasses(); if( m_areas.m_areas.size() <= 1 ) { frame()->ShowInfoBarError( _( "Cannot auto-generate any placement areas because the " "schematic has only one or no hierarchical sheet(s) or " "component classes." ), true ); return 0; } DIALOG_MULTICHANNEL_GENERATE_RULE_AREAS dialog( frame(), this ); int ret = dialog.ShowModal(); if( ret != wxID_OK ) return 0; } for( ZONE* zone : board()->Zones() ) { if( !zone->GetIsRuleArea() ) continue; if( !zone->GetRuleAreaPlacementEnabled() ) continue; std::set components; identifyComponentsInRuleArea( zone, components ); if( components.empty() ) continue; for( RULE_AREA& ra : m_areas.m_areas ) { if( components == ra.m_components ) { if( zone->GetRuleAreaPlacementSourceType() == RULE_AREA_PLACEMENT_SOURCE_TYPE::SHEETNAME ) { wxLogTrace( traceMultichannelTool, wxT( "Placement rule area for sheet '%s' already exists as '%s'\n" ), ra.m_sheetPath, zone->GetZoneName() ); } else if( zone->GetRuleAreaPlacementSourceType() == RULE_AREA_PLACEMENT_SOURCE_TYPE::COMPONENT_CLASS ) { wxLogTrace( traceMultichannelTool, wxT( "Placement rule area for component class '%s' already exists " "as '%s'\n" ), ra.m_componentClass, zone->GetZoneName() ); } else { wxLogTrace( traceMultichannelTool, wxT( "Placement rule area for group '%s' already exists " "as '%s'\n" ), ra.m_groupName, zone->GetZoneName() ); } ra.m_oldArea = zone; ra.m_existsAlready = true; } } } wxLogTrace( traceMultichannelTool, wxT( "%d placement areas found\n" ), (int) m_areas.m_areas.size() ); BOARD_COMMIT commit( GetManager(), true ); for( RULE_AREA& ra : m_areas.m_areas ) { if( !ra.m_generateEnabled ) continue; if( ra.m_existsAlready && !m_areas.m_replaceExisting ) continue; if( ra.m_components.empty() ) continue; SHAPE_LINE_CHAIN raOutline = buildRAOutline( ra.m_components, 100000 ); std::unique_ptr newZone( new ZONE( board() ) ); if( ra.m_sourceType == RULE_AREA_PLACEMENT_SOURCE_TYPE::SHEETNAME ) { newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_sheetPath ) ); } else if( ra.m_sourceType == RULE_AREA_PLACEMENT_SOURCE_TYPE::COMPONENT_CLASS ) { newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_componentClass ) ); } else { newZone->SetZoneName( wxString::Format( wxT( "auto-placement-area-%s" ), ra.m_groupName ) ); } wxLogTrace( traceMultichannelTool, wxT( "Generated rule area '%s' (%d components)\n" ), newZone->GetZoneName(), (int) ra.m_components.size() ); newZone->SetIsRuleArea( true ); newZone->SetLayerSet( LSET::AllCuMask() ); newZone->SetRuleAreaPlacementEnabled( true ); newZone->SetDoNotAllowZoneFills( false ); newZone->SetDoNotAllowVias( false ); newZone->SetDoNotAllowTracks( false ); newZone->SetDoNotAllowPads( false ); newZone->SetDoNotAllowFootprints( false ); if( ra.m_sourceType == RULE_AREA_PLACEMENT_SOURCE_TYPE::SHEETNAME ) { newZone->SetRuleAreaPlacementSourceType( RULE_AREA_PLACEMENT_SOURCE_TYPE::SHEETNAME ); newZone->SetRuleAreaPlacementSource( ra.m_sheetPath ); } else if( ra.m_sourceType == RULE_AREA_PLACEMENT_SOURCE_TYPE::COMPONENT_CLASS ) { newZone->SetRuleAreaPlacementSourceType( RULE_AREA_PLACEMENT_SOURCE_TYPE::COMPONENT_CLASS ); newZone->SetRuleAreaPlacementSource( ra.m_componentClass ); } else { newZone->SetRuleAreaPlacementSourceType( RULE_AREA_PLACEMENT_SOURCE_TYPE::GROUP ); newZone->SetRuleAreaPlacementSource( ra.m_groupName ); } newZone->AddPolygon( raOutline ); newZone->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH ); if( ra.m_existsAlready ) { commit.Remove( ra.m_oldArea ); } ra.m_area = newZone.get(); commit.Add( newZone.release() ); } commit.Push( _( "Auto-generate placement rule areas" ) ); // fixme: handle corner cases where the items belonging to a Rule Area already // belong to other groups. if( m_areas.m_options.m_groupItems ) { // fixme: sth gets weird when creating new zones & grouping them within a single COMMIT BOARD_COMMIT grpCommit( GetManager(), true ); for( RULE_AREA& ra : m_areas.m_areas ) { if( !ra.m_generateEnabled ) continue; if( ra.m_existsAlready && !m_areas.m_replaceExisting ) continue; std::unordered_set toPrune; std::copy( ra.m_components.begin(), ra.m_components.end(), std::inserter( toPrune, toPrune.begin() ) ); if( ra.m_existsAlready ) toPrune.insert( ra.m_area ); pruneExistingGroups( grpCommit, toPrune ); PCB_GROUP* grp = new PCB_GROUP( board() ); grpCommit.Add( grp ); grpCommit.Stage( ra.m_area, CHT_GROUP ); for( FOOTPRINT* fp : ra.m_components ) { grpCommit.Stage( fp, CHT_GROUP ); } } grpCommit.Push( _( "Group components with their placement rule areas" ) ); } return true; }