From dabd8cdab89ad5d2f405967c218d114a7d8111c5 Mon Sep 17 00:00:00 2001 From: Jon Evans Date: Sun, 31 Aug 2025 22:18:43 -0400 Subject: [PATCH] API: Add ability to set enabled board layers and copper layer count (cherry picked from commit ca0207016e9f91d1bd8ed1c5fadf87096583611e) --- api/proto/board/board_commands.proto | 33 +++++++ pcbnew/api/api_handler_pcb.cpp | 106 ++++++++++++++++++++++ pcbnew/api/api_handler_pcb.h | 6 ++ pcbnew/board.cpp | 126 ++++++++++++++++++++++++++ pcbnew/board.h | 12 +++ pcbnew/dialogs/panel_setup_layers.cpp | 124 +------------------------ 6 files changed, 286 insertions(+), 121 deletions(-) diff --git a/api/proto/board/board_commands.proto b/api/proto/board/board_commands.proto index e5438182a1..a24a787fcf 100644 --- a/api/proto/board/board_commands.proto +++ b/api/proto/board/board_commands.proto @@ -42,12 +42,45 @@ message BoardStackupResponse kiapi.board.BoardStackup stackup = 1; } +// Changes the stackup for the given board according to the contents of the message (**not yet implemented**) +// WARNING: any existing content on layers that are removed by this call is deleted. This operation cannot be undone. +// Returns BoardStackupResponse with the updated stackup, in normalized form message UpdateBoardStackup { kiapi.common.types.DocumentSpecifier board = 1; kiapi.board.BoardStackup stackup = 2; } +message GetBoardEnabledLayers +{ + kiapi.common.types.DocumentSpecifier board = 1; +} + +message BoardEnabledLayersResponse +{ + // The number of copper layers enabled in this board. + uint32 copper_layer_count = 1; + + // A list of all layers enabled in this board, including copper layers and ones which cannot be disabled. + repeated kiapi.board.types.BoardLayer layers = 2; +} + +// Changes which layers are enabled in the board stackup +// WARNING: any existing content on layers that are removed by this call is deleted. This operation cannot be undone. +// Returns BoardEnabledLayersResponse with the updated layer set. +message SetBoardEnabledLayers +{ + kiapi.common.types.DocumentSpecifier board = 1; + + // The number of copper layers to enable in the board. Currently, this must be an even number >= 2. + uint32 copper_layer_count = 2; + + // The non-copper layers to enable. Note that any copper layers in this list are ignored; copper layers are enabled + // by setting copper_layer_count. Note that the F/B.Courtyard, Edge.Cuts, and Margin layers cannot be disabled and + // will be present in the board even if they are omitted from this list. + repeated kiapi.board.types.BoardLayer layers = 3; +} + message GetGraphicsDefaults { kiapi.common.types.DocumentSpecifier board = 1; diff --git a/pcbnew/api/api_handler_pcb.cpp b/pcbnew/api/api_handler_pcb.cpp index 34b2eef67e..3e0a957e67 100644 --- a/pcbnew/api/api_handler_pcb.cpp +++ b/pcbnew/api/api_handler_pcb.cpp @@ -73,6 +73,10 @@ API_HANDLER_PCB::API_HANDLER_PCB( PCB_EDIT_FRAME* aFrame ) : &API_HANDLER_PCB::handleRemoveFromSelection ); registerHandler( &API_HANDLER_PCB::handleGetStackup ); + registerHandler( + &API_HANDLER_PCB::handleGetBoardEnabledLayers ); + registerHandler( + &API_HANDLER_PCB::handleSetBoardEnabledLayers ); registerHandler( &API_HANDLER_PCB::handleGetGraphicsDefaults ); registerHandler( @@ -902,6 +906,108 @@ HANDLER_RESULT API_HANDLER_PCB::handleGetStackup( } +HANDLER_RESULT API_HANDLER_PCB::handleGetBoardEnabledLayers( + const HANDLER_CONTEXT& aCtx ) +{ + HANDLER_RESULT documentValidation = validateDocument( aCtx.Request.board() ); + + if( !documentValidation ) + return tl::unexpected( documentValidation.error() ); + + BoardEnabledLayersResponse response; + + BOARD* board = frame()->GetBoard(); + int copperLayerCount = board->GetCopperLayerCount(); + + response.set_copper_layer_count( copperLayerCount ); + + LSET enabled = board->GetEnabledLayers(); + + // The Rescue layer is an internal detail and should be hidden from the API + enabled.reset( Rescue ); + + // Just in case this is out of sync; the API should always return the expected copper layers + enabled |= LSET::AllCuMask( copperLayerCount ); + + board::PackLayerSet( *response.mutable_layers(), enabled ); + + return response; +} + + +HANDLER_RESULT API_HANDLER_PCB::handleSetBoardEnabledLayers( + const HANDLER_CONTEXT& aCtx ) +{ + HANDLER_RESULT documentValidation = validateDocument( aCtx.Request.board() ); + + if( !documentValidation ) + return tl::unexpected( documentValidation.error() ); + + if( aCtx.Request.copper_layer_count() % 2 != 0 ) + { + ApiResponseStatus e; + e.set_status( ApiStatusCode::AS_BAD_REQUEST ); + e.set_error_message( "copper_layer_count must be an even number" ); + return tl::unexpected( e ); + } + + if( aCtx.Request.copper_layer_count() > MAX_CU_LAYERS ) + { + ApiResponseStatus e; + e.set_status( ApiStatusCode::AS_BAD_REQUEST ); + e.set_error_message( fmt::format( "copper_layer_count must be below %d", MAX_CU_LAYERS ) ); + return tl::unexpected( e ); + } + + int copperLayerCount = static_cast( aCtx.Request.copper_layer_count() ); + LSET enabled = board::UnpackLayerSet( aCtx.Request.layers() ); + + // Sanitize the input + enabled |= LSET( { Edge_Cuts, Margin, F_CrtYd, B_CrtYd } ); + enabled &= ~LSET::AllCuMask(); + enabled |= LSET::AllCuMask( copperLayerCount ); + + BOARD* board = frame()->GetBoard(); + + LSET previousEnabled = board->GetEnabledLayers(); + LSET changedLayers = enabled ^ previousEnabled; + + board->SetEnabledLayers( enabled ); + board->SetVisibleLayers( board->GetVisibleLayers() | changedLayers ); + + LSEQ removedLayers; + + for( PCB_LAYER_ID layer_id : previousEnabled ) + { + if( !enabled[layer_id] && board->HasItemsOnLayer( layer_id ) ) + removedLayers.push_back( layer_id ); + } + + bool modified = false; + + if( !removedLayers.empty() ) + { + m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectionClear ); + + for( PCB_LAYER_ID layer_id : removedLayers ) + modified |= board->RemoveAllItemsOnLayer( layer_id ); + } + + if( enabled != previousEnabled ) + frame()->UpdateUserInterface(); + + if( modified ) + frame()->OnModify(); + + BoardEnabledLayersResponse response; + + response.set_copper_layer_count( copperLayerCount ); + board::PackLayerSet( *response.mutable_layers(), enabled ); + + return response; +} + + HANDLER_RESULT API_HANDLER_PCB::handleGetGraphicsDefaults( const HANDLER_CONTEXT& aCtx ) { diff --git a/pcbnew/api/api_handler_pcb.h b/pcbnew/api/api_handler_pcb.h index c08c3c1237..4f2c5cbd0e 100644 --- a/pcbnew/api/api_handler_pcb.h +++ b/pcbnew/api/api_handler_pcb.h @@ -92,6 +92,12 @@ private: HANDLER_RESULT handleGetStackup( const HANDLER_CONTEXT& aCtx ); + HANDLER_RESULT handleGetBoardEnabledLayers( + const HANDLER_CONTEXT& aCtx ); + + HANDLER_RESULT handleSetBoardEnabledLayers( + const HANDLER_CONTEXT& aCtx ); + HANDLER_RESULT handleGetGraphicsDefaults( const HANDLER_CONTEXT& aCtx ); diff --git a/pcbnew/board.cpp b/pcbnew/board.cpp index 7a896ff58a..a77ccd3bd9 100644 --- a/pcbnew/board.cpp +++ b/pcbnew/board.cpp @@ -1525,6 +1525,132 @@ void BOARD::RemoveAll( std::initializer_list aTypes ) } +bool BOARD::HasItemsOnLayer( PCB_LAYER_ID aLayer ) +{ + PCB_LAYER_COLLECTOR collector; + + collector.SetLayerId( aLayer ); + collector.Collect( this, GENERAL_COLLECTOR::BoardLevelItems ); + + if( collector.GetCount() != 0 ) + { + // Skip items owned by footprints and footprints when building + // the actual list of removed layers: these items are not removed + for( int i = 0; i < collector.GetCount(); i++ ) + { + BOARD_ITEM* item = collector[i]; + + if( item->Type() == PCB_FOOTPRINT_T || item->GetParentFootprint() ) + continue; + + // Vias are on multiple adjacent layers, but only the top and + // the bottom layers are stored. So there are issues only if one + // is on a removed layer + if( item->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( item ); + + if( via->GetViaType() == VIATYPE::THROUGH ) + continue; + else + { + PCB_LAYER_ID top_layer; + PCB_LAYER_ID bottom_layer; + via->LayerPair( &top_layer, &bottom_layer ); + + if( top_layer != aLayer && bottom_layer != aLayer ) + continue; + } + } + + return true; + } + } + + return false; +} + + +bool BOARD::RemoveAllItemsOnLayer( PCB_LAYER_ID aLayer ) +{ + bool modified = false; + bool removedItemLayers = false; + PCB_LAYER_COLLECTOR collector; + + collector.SetLayerId( aLayer ); + collector.Collect( this, GENERAL_COLLECTOR::BoardLevelItems ); + + for( int i = 0; i < collector.GetCount(); i++ ) + { + BOARD_ITEM* item = collector[i]; + + // Do not remove/change an item owned by a footprint + if( item->GetParentFootprint() ) + continue; + + // Do not remove footprints + if( item->Type() == PCB_FOOTPRINT_T ) + continue; + + // Note: vias are specific. They are only on copper layers, and + // do not use a layer set, only store the copper top and the copper bottom. + // So reinit the layer set does not work with vias + if( item->Type() == PCB_VIA_T ) + { + PCB_VIA* via = static_cast( item ); + + if( via->GetViaType() == VIATYPE::THROUGH ) + { + removedItemLayers = true; + continue; + } + else if( via->IsOnLayer( aLayer ) ) + { + PCB_LAYER_ID top_layer; + PCB_LAYER_ID bottom_layer; + via->LayerPair( &top_layer, &bottom_layer ); + + if( top_layer == aLayer || bottom_layer == aLayer ) + { + // blind/buried vias with a top or bottom layer on a removed layer + // are removed. Perhaps one could just modify the top/bottom layer, + // but I am not sure this is better. + Remove( item ); + delete item; + modified = true; + } + + removedItemLayers = true; + } + } + else if( item->IsOnLayer( aLayer ) ) + { + LSET layers = item->GetLayerSet(); + + layers.reset( aLayer ); + + if( layers.any() ) + { + item->SetLayerSet( layers ); + } + else + { + Remove( item ); + delete item; + modified = true; + } + + removedItemLayers = true; + } + } + + if( removedItemLayers ) + BuildConnectivity(); + + return modified; +} + + wxString BOARD::GetItemDescription( UNITS_PROVIDER* aUnitsProvider, bool aFull ) const { return wxString::Format( _( "PCB" ) ); diff --git a/pcbnew/board.h b/pcbnew/board.h index c76c40e8c2..ced31669b7 100644 --- a/pcbnew/board.h +++ b/pcbnew/board.h @@ -448,6 +448,18 @@ public: PCB_GENERATOR_T, PCB_FOOTPRINT_T, PCB_TRACE_T, PCB_SHAPE_T } ); + bool HasItemsOnLayer( PCB_LAYER_ID aLayer ); + + /** + * Removes all owned items other than footprints existing on the given board layer, and modifies + * the stackup for multilayer items to remove the given layer where applicable. + * Used when removing an existing layer from the board via Board Setup or the API. + * Caller is responsible for clearing the selection before calling this. + * @param aLayer should be a layer enabled for this board + * @return true if any items were removed or modified + */ + bool RemoveAllItemsOnLayer( PCB_LAYER_ID aLayer ); + /** * Remove all teardrop zones with the STRUCT_DELETED flag set. This avoids O(n^2) traversal * over the zone list. diff --git a/pcbnew/dialogs/panel_setup_layers.cpp b/pcbnew/dialogs/panel_setup_layers.cpp index 75e16cacb9..81ed0b758b 100644 --- a/pcbnew/dialogs/panel_setup_layers.cpp +++ b/pcbnew/dialogs/panel_setup_layers.cpp @@ -898,84 +898,12 @@ bool PANEL_SETUP_LAYERS::TransferDataFromWindow() // Delete all objects on layers that have been removed. Leaving them in copper layers // can (will?) result in DRC errors and it pollutes the board file with cruft. - bool hasRemovedBoardItemLayers = false; - if( !removedLayers.empty() ) { m_frame->GetToolManager()->RunAction( ACTIONS::selectionClear ); - PCB_LAYER_COLLECTOR collector; - for( PCB_LAYER_ID layer_id : removedLayers ) - { - collector.SetLayerId( layer_id ); - collector.Collect( m_pcb, GENERAL_COLLECTOR::BoardLevelItems ); - - // Bye-bye items on removed layer. - for( int i = 0; i < collector.GetCount(); i++ ) - { - BOARD_ITEM* item = collector[i]; - - // Do not remove/change an item owned by a footprint - if( item->GetParentFootprint() ) - continue; - - // Do not remove footprints - if( item->Type() == PCB_FOOTPRINT_T ) - continue; - - // Note: vias are specific. They are only on copper layers, and - // do not use a layer set, only store the copper top and the copper bottom. - // So reinit the layer set does not work with vias - if( item->Type() == PCB_VIA_T ) - { - PCB_VIA* via = static_cast( item ); - - if( via->GetViaType() == VIATYPE::THROUGH ) - { - hasRemovedBoardItemLayers = true; - continue; - } - else if( via->IsOnLayer( layer_id ) ) - { - PCB_LAYER_ID top_layer; - PCB_LAYER_ID bottom_layer; - via->LayerPair( &top_layer, &bottom_layer ); - - if( top_layer == layer_id || bottom_layer == layer_id ) - { - // blind/buried vias with a top or bottom layer on a removed layer - // are removed. Perhaps one could just modify the top/bottom layer, - // but I am not sure this is better. - m_pcb->Remove( item ); - delete item; - modified = true; - } - - hasRemovedBoardItemLayers = true; - } - } - else if( item->IsOnLayer( layer_id ) ) - { - LSET layers = item->GetLayerSet(); - - layers.reset( layer_id ); - - if( layers.any() ) - { - item->SetLayerSet( layers ); - } - else - { - m_pcb->Remove( item ); - delete item; - modified = true; - } - - hasRemovedBoardItemLayers = true; - } - } - } + modified |= m_pcb->RemoveAllItemsOnLayer( layer_id ); // Undo state may have copies of pointers deleted above m_frame->ClearUndoRedoList(); @@ -983,11 +911,6 @@ bool PANEL_SETUP_LAYERS::TransferDataFromWindow() modified |= transferDataFromWindow(); - // If some board items are deleted: Rebuild the connectivity, because it is likely some - // tracks and vias were removed - if( hasRemovedBoardItemLayers ) - m_pcb->BuildConnectivity(); - if( modified ) m_frame->OnModify(); @@ -1066,51 +989,10 @@ LSEQ PANEL_SETUP_LAYERS::getRemovedLayersWithItems() if( newLayers == curLayers ) // Return an empty list if no change return removedLayers; - PCB_LAYER_COLLECTOR collector; - for( PCB_LAYER_ID layer_id : curLayers ) { - if( !newLayers[layer_id] ) - { - collector.SetLayerId( layer_id ); - collector.Collect( m_pcb, GENERAL_COLLECTOR::BoardLevelItems ); - - if( collector.GetCount() != 0 ) - { - // Skip items owned by footprints and footprints when building - // the actual list of removed layers: these items are not removed - for( int i = 0; i < collector.GetCount(); i++ ) - { - BOARD_ITEM* item = collector[i]; - - if( item->Type() == PCB_FOOTPRINT_T || item->GetParentFootprint() ) - continue; - - // Vias are on multiple adjacent layers, but only the top and - // the bottom layers are stored. So there are issues only if one - // is on a removed layer - if( item->Type() == PCB_VIA_T ) - { - PCB_VIA* via = static_cast( item ); - - if( via->GetViaType() == VIATYPE::THROUGH ) - continue; - else - { - PCB_LAYER_ID top_layer; - PCB_LAYER_ID bottom_layer; - via->LayerPair( &top_layer, &bottom_layer ); - - if( top_layer != layer_id && bottom_layer != layer_id ) - continue; - } - } - - removedLayers.push_back( layer_id ); - break; - } - } - } + if( !newLayers[layer_id] && m_pcb->HasItemsOnLayer( layer_id ) ) + removedLayers.push_back( layer_id ); } return removedLayers;