From 97af2af7797594fa1dff63a63c430e933c30081d Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 19 Aug 2025 13:57:07 -0400 Subject: [PATCH] design blocks / multichannel: basics of placing DB layout --- include/eda_item_flags.h | 2 +- pcbnew/tools/multichannel_tool.cpp | 38 ++++++ pcbnew/tools/multichannel_tool.h | 1 + pcbnew/tools/pcb_actions.cpp | 8 ++ pcbnew/tools/pcb_actions.h | 1 + pcbnew/tools/pcb_control.cpp | 173 ++++++++++++++++++++++++++++ pcbnew/tools/pcb_control.h | 1 + pcbnew/tools/pcb_selection_tool.cpp | 1 + 8 files changed, 224 insertions(+), 1 deletion(-) diff --git a/include/eda_item_flags.h b/include/eda_item_flags.h index efd72eb0cc..13232bc1dc 100644 --- a/include/eda_item_flags.h +++ b/include/eda_item_flags.h @@ -66,7 +66,7 @@ #define SHOW_ELEC_TYPE (1UL << 25) ///< Show pin electrical type #define BRIGHTENED (1UL << 26) ///< item is drawn with a bright contour -// 27 is unused +#define MCT_SKIP_STRUCT (1 << 27) ///< flag used by the multichannel tool to mark items that should be skipped #define UR_TRANSIENT (1UL << 28) ///< indicates the item is owned by the undo/redo stack diff --git a/pcbnew/tools/multichannel_tool.cpp b/pcbnew/tools/multichannel_tool.cpp index 3bb3949863..de6f7cf3e1 100644 --- a/pcbnew/tools/multichannel_tool.cpp +++ b/pcbnew/tools/multichannel_tool.cpp @@ -526,6 +526,44 @@ int MULTICHANNEL_TOOL::CheckRACompatibility( ZONE *aRefZone ) } +int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, RULE_AREA& aRefArea, RULE_AREA& aTargetArea ) +{ + RULE_AREA_COMPAT_DATA compat; + + if( !resolveConnectionTopology( &aRefArea, &aTargetArea, compat ) ) + { + if( Pgm().IsGUI() ) + { + auto errMsg = wxString::Format( _( "Rule Area topologies do not match: %s" ), compat.m_errorMsg ); + frame()->ShowInfoBarError( errMsg, true ); + } + return -1; + } + + BOARD_COMMIT commit( GetManager(), true ); + + if( !copyRuleAreaContents( compat.m_matchingComponents, &commit, &aRefArea, &aTargetArea, m_areas.m_options, + compat.m_affectedItems, compat.m_groupableItems ) ) + { + auto errMsg = wxString::Format( _( "Copy Rule Area contents failed between rule areas '%s' and '%s'." ), + m_areas.m_refRA->m_area->GetZoneName(), aTargetArea.m_area->GetZoneName() ); + + commit.Revert(); + + if( Pgm().IsGUI() ) + { + frame()->ShowInfoBarError( errMsg, true ); + } + + return -1; + } + + commit.Push( _( "Repeat layout" ) ); + + return 0; +} + + int MULTICHANNEL_TOOL::RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone ) { int totalCopied = 0; diff --git a/pcbnew/tools/multichannel_tool.h b/pcbnew/tools/multichannel_tool.h index 0d8b5e1a68..1161ac9a17 100644 --- a/pcbnew/tools/multichannel_tool.h +++ b/pcbnew/tools/multichannel_tool.h @@ -101,6 +101,7 @@ public: RULE_AREAS_DATA* GetData() { return &m_areas; } int AutogenerateRuleAreas( const TOOL_EVENT& aEvent ); int RepeatLayout( const TOOL_EVENT& aEvent, ZONE* aRefZone ); + int RepeatLayout( const TOOL_EVENT& aEvent, RULE_AREA& aRefArea, RULE_AREA& aTargetArea ); void QuerySheetsAndComponentClasses(); void FindExistingRuleAreas(); int CheckRACompatibility( ZONE *aRefZone ); diff --git a/pcbnew/tools/pcb_actions.cpp b/pcbnew/tools/pcb_actions.cpp index d69a1d204c..2956002c21 100644 --- a/pcbnew/tools/pcb_actions.cpp +++ b/pcbnew/tools/pcb_actions.cpp @@ -463,6 +463,14 @@ TOOL_ACTION PCB_ACTIONS::placeLinkedDesignBlock( TOOL_ACTION_ARGS() .Icon( BITMAPS::add_component ) .Flags( AF_ACTIVATE ) ); +TOOL_ACTION PCB_ACTIONS::applyDesignBlockLayout( TOOL_ACTION_ARGS() + .Name( "pcbnew.InteractiveDrawing.applyDesignBlockLayout" ) + .Scope( AS_GLOBAL ) + .FriendlyName( _( "Apply Design Block Layout" ) ) + .Tooltip( _( "Apply linked design block layout to selected group" ) ) + .Icon( BITMAPS::add_component ) + .Flags( AF_ACTIVATE ) ); + TOOL_ACTION PCB_ACTIONS::saveToLinkedDesignBlock( TOOL_ACTION_ARGS() .Name( "pcbnew.InteractiveDrawing.saveToLinkedDesignBlock" ) .Scope( AS_GLOBAL ) diff --git a/pcbnew/tools/pcb_actions.h b/pcbnew/tools/pcb_actions.h index ebbc53a882..46eb6d25ac 100644 --- a/pcbnew/tools/pcb_actions.h +++ b/pcbnew/tools/pcb_actions.h @@ -456,6 +456,7 @@ public: // Design Block management static TOOL_ACTION placeDesignBlock; static TOOL_ACTION placeLinkedDesignBlock; + static TOOL_ACTION applyDesignBlockLayout; static TOOL_ACTION saveToLinkedDesignBlock; static TOOL_ACTION showDesignBlockPanel; static TOOL_ACTION saveBoardAsDesignBlock; diff --git a/pcbnew/tools/pcb_control.cpp b/pcbnew/tools/pcb_control.cpp index 8e96df1eac..21bebe8524 100644 --- a/pcbnew/tools/pcb_control.cpp +++ b/pcbnew/tools/pcb_control.cpp @@ -45,6 +45,8 @@ #include #include #include +#include +#include #include #include #include @@ -73,6 +75,7 @@ #include #include #include +#include #include #include #include @@ -1374,6 +1377,175 @@ int PCB_CONTROL::AppendDesignBlock( const TOOL_EVENT& aEvent ) return ret; } +int PCB_CONTROL::ApplyDesignBlockLayout( const TOOL_EVENT& aEvent ) +{ + PCB_EDIT_FRAME* editFrame = dynamic_cast( m_frame ); + + if( !editFrame ) + return 1; + + BOARD* brd = board(); + + if( !brd ) + return 1; + + // Need to have a group selected and it needs to have a linked design block + PCB_SELECTION_TOOL* selTool = m_toolMgr->GetTool(); + PCB_SELECTION selection = selTool->GetSelection(); + + if( selection.Size() != 1 || selection[0]->Type() != PCB_GROUP_T ) + return 1; + + PCB_GROUP* group = static_cast( selection[0] ); + + if( !group->HasDesignBlockLink() ) + return 1; + + std::set originalItems; + // Apply MCT_SKIP_STRUCT to every EDA_ITEM on the board so we know what is not part of the design block + // Can't use SKIP_STRUCT as that is used and cleared by the temporary board appending + brd->Visit( []( EDA_ITEM* item, void* ) + { + item->SetFlags( MCT_SKIP_STRUCT ); + return INSPECT_RESULT::CONTINUE; + }, + nullptr, GENERAL_COLLECTOR::AllBoardItems ); + + int ret = PlaceLinkedDesignBlock( aEvent ); + + // If we succeeded in placing the linked design block, we're ready to apply the multichannel tool + if( ret == 0 ) + { + // Make a lambda for the bounding box of all the components + auto generateBoundingBox = [&]( std::unordered_set aItems ) + { + std::vector bbCorners; + bbCorners.reserve( aItems.size() * 4 ); + + for( auto item : aItems ) + { + const BOX2I bb = item->GetBoundingBox().GetInflated( 100000 ); + 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 ); + }; + + // Build an outline that is the entire board editor since a design block can + // have anything within this space + SHAPE_LINE_CHAIN wholeEditorOutline; + wholeEditorOutline.Append( VECTOR2I( -INT_MAX, -INT_MAX ) ); + wholeEditorOutline.Append( VECTOR2I( INT_MAX, -INT_MAX ) ); + wholeEditorOutline.Append( VECTOR2I( INT_MAX, INT_MAX ) ); + wholeEditorOutline.Append( VECTOR2I( -INT_MAX, INT_MAX ) ); + wholeEditorOutline.SetClosed( true ); + + // Build a rule area that contains all the components in the design block, + // meaning all items without SKIP_STRUCT set. + RULE_AREA dbRA; + + dbRA.m_sourceType = PLACEMENT_SOURCE_T::GROUP_PLACEMENT; + dbRA.m_generateEnabled = true; + + // Add all components that aren't marked MCT_SKIP_STRUCT to ra.m_components + std::unordered_set allDbItems; + brd->Visit( + [&]( EDA_ITEM* item, void* data ) + { + if( !item->HasFlag( MCT_SKIP_STRUCT ) ) + { + allDbItems.insert( item ); + + if( item->Type() == PCB_FOOTPRINT_T ) + dbRA.m_components.insert( static_cast( item ) ); + } + return INSPECT_RESULT::CONTINUE; + }, + nullptr, GENERAL_COLLECTOR::AllBoardItems ); + + dbRA.m_area = new ZONE( board() ); + //dbRA.m_area->SetZoneName( wxString::Format( wxT( "design-block-source-%s" ), group->GetDesignBlockLibId().GetUniStringLibId() ) ); + dbRA.m_area->SetIsRuleArea( true ); + dbRA.m_area->SetLayerSet( LSET::AllCuMask() ); + dbRA.m_area->SetPlacementAreaEnabled( true ); + dbRA.m_area->SetDoNotAllowZoneFills( false ); + dbRA.m_area->SetDoNotAllowVias( false ); + dbRA.m_area->SetDoNotAllowTracks( false ); + dbRA.m_area->SetDoNotAllowPads( false ); + dbRA.m_area->SetDoNotAllowFootprints( false ); + dbRA.m_area->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT ); + dbRA.m_area->SetPlacementAreaSource( group->GetDesignBlockLibId().GetUniStringLibId() ); + dbRA.m_area->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH ); + dbRA.m_area->AddPolygon( generateBoundingBox( allDbItems ) ); + dbRA.m_center = dbRA.m_area->Outline()->COutline( 0 ).Centre(); + + // Create the destination rule area for the group + RULE_AREA destRA; + + destRA.m_sourceType = PLACEMENT_SOURCE_T::GROUP_PLACEMENT; + + // Add all the design block group footprints to the destination rule area + for( EDA_ITEM* item : group->GetItems() ) + { + if( item->Type() == PCB_FOOTPRINT_T ) + { + FOOTPRINT* fp = static_cast( item ); + + // If the footprint is locked, we can't place it + if( fp->IsLocked() ) + { + wxString msg; + msg.Printf( _( "Footprint %s is locked and cannot be placed." ), fp->GetReference() ); + m_frame->GetInfoBar()->ShowMessageFor( msg, 5000, wxICON_WARNING ); + return 1; + } + + destRA.m_components.insert( fp ); + } + } + + destRA.m_area = new ZONE( board() ); + destRA.m_area->SetZoneName( + wxString::Format( wxT( "design-block-dest-%s" ), group->GetDesignBlockLibId().GetUniStringLibId() ) ); + destRA.m_area->SetIsRuleArea( true ); + destRA.m_area->SetLayerSet( LSET::AllCuMask() ); + destRA.m_area->SetPlacementAreaEnabled( true ); + destRA.m_area->SetDoNotAllowZoneFills( false ); + destRA.m_area->SetDoNotAllowVias( false ); + destRA.m_area->SetDoNotAllowTracks( false ); + destRA.m_area->SetDoNotAllowPads( false ); + destRA.m_area->SetDoNotAllowFootprints( false ); + destRA.m_area->SetPlacementAreaSourceType( PLACEMENT_SOURCE_T::GROUP_PLACEMENT ); + destRA.m_area->SetPlacementAreaSource( group->GetName() ); + destRA.m_area->SetHatchStyle( ZONE_BORDER_DISPLAY_STYLE::NO_HATCH ); + destRA.m_area->AddPolygon( generateBoundingBox( group->GetItems() ) ); + destRA.m_center = destRA.m_area->Outline()->COutline( 0 ).Centre(); + + // Use the multichannel tool to repeat the layout + MULTICHANNEL_TOOL* mct = m_toolMgr->GetTool(); + + ret = mct->RepeatLayout( aEvent, dbRA, destRA ); + + delete dbRA.m_area; + delete destRA.m_area; + } + + // We're done, remove SKIP_STRUCT + brd->Visit( []( EDA_ITEM* item, void* ) + { + item->ClearFlags( MCT_SKIP_STRUCT ); + return INSPECT_RESULT::CONTINUE; + }, + nullptr, GENERAL_COLLECTOR::AllBoardItems ); + + return ret; +} int PCB_CONTROL::PlaceLinkedDesignBlock( const TOOL_EVENT& aEvent ) { @@ -2732,6 +2904,7 @@ void PCB_CONTROL::setTransitions() // Append control Go( &PCB_CONTROL::AppendDesignBlock, PCB_ACTIONS::placeDesignBlock.MakeEvent() ); + Go( &PCB_CONTROL::ApplyDesignBlockLayout, PCB_ACTIONS::applyDesignBlockLayout.MakeEvent() ); Go( &PCB_CONTROL::PlaceLinkedDesignBlock, PCB_ACTIONS::placeLinkedDesignBlock.MakeEvent() ); Go( &PCB_CONTROL::SaveToLinkedDesignBlock, PCB_ACTIONS::saveToLinkedDesignBlock.MakeEvent() ); Go( &PCB_CONTROL::AppendBoardFromFile, PCB_ACTIONS::appendBoard.MakeEvent() ); diff --git a/pcbnew/tools/pcb_control.h b/pcbnew/tools/pcb_control.h index a7d49a38db..72c61877e6 100644 --- a/pcbnew/tools/pcb_control.h +++ b/pcbnew/tools/pcb_control.h @@ -107,6 +107,7 @@ public: int Paste( const TOOL_EVENT& aEvent ); int AppendBoardFromFile( const TOOL_EVENT& aEvent ); int AppendDesignBlock( const TOOL_EVENT& aEvent ); + int ApplyDesignBlockLayout( const TOOL_EVENT& aEvent ); int PlaceLinkedDesignBlock( const TOOL_EVENT& aEvent ); int SaveToLinkedDesignBlock( const TOOL_EVENT& aEvent ); int AppendBoard( PCB_IO& pi, const wxString& fileName, DESIGN_BLOCK* aDesignBlock = nullptr ); diff --git a/pcbnew/tools/pcb_selection_tool.cpp b/pcbnew/tools/pcb_selection_tool.cpp index 29431296bf..3d7798aabf 100644 --- a/pcbnew/tools/pcb_selection_tool.cpp +++ b/pcbnew/tools/pcb_selection_tool.cpp @@ -198,6 +198,7 @@ bool PCB_SELECTION_TOOL::Init() menu.AddItem( ACTIONS::cancelInteractive, activeToolCondition, 1 ); menu.AddItem( ACTIONS::groupEnter, groupEnterCondition, 1 ); menu.AddItem( ACTIONS::groupLeave, inGroupCondition, 1 ); + menu.AddItem( PCB_ACTIONS::applyDesignBlockLayout, groupEnterCondition, 1 ); menu.AddItem( PCB_ACTIONS::placeLinkedDesignBlock, groupEnterCondition, 1 ); menu.AddItem( PCB_ACTIONS::saveToLinkedDesignBlock, groupEnterCondition, 1 ); menu.AddItem( PCB_ACTIONS::clearHighlight, haveHighlight, 1 );