Add QA tests and expand serialization for API

This commit is contained in:
Jon Evans
2024-04-02 19:51:18 -04:00
parent 6bd02cae6d
commit 1dbe78c68b
106 changed files with 9643 additions and 736 deletions
+472 -263
View File
@@ -21,10 +21,26 @@
#include <magic_enum.hpp>
#include <api/api_handler_pcb.h>
#include <api/api_pcb_utils.h>
#include <api/api_enums.h>
#include <api/api_utils.h>
#include <board_commit.h>
#include <board_design_settings.h>
#include <footprint.h>
#include <netinfo.h>
#include <pad.h>
#include <pcb_edit_frame.h>
#include <pcb_group.h>
#include <pcb_reference_image.h>
#include <pcb_shape.h>
#include <pcb_text.h>
#include <pcb_textbox.h>
#include <pcb_track.h>
#include <project.h>
#include <tool/tool_manager.h>
#include <tools/pcb_actions.h>
#include <tools/pcb_selection_tool.h>
#include <zone.h>
#include <api/common/types/base_types.pb.h>
@@ -33,34 +49,44 @@ using kiapi::common::types::CommandStatus;
using kiapi::common::types::DocumentType;
using kiapi::common::types::ItemRequestStatus;
static const wxString s_defaultCommitMessage = wxS( "Modification from API" );
API_HANDLER_PCB::API_HANDLER_PCB( PCB_EDIT_FRAME* aFrame ) :
API_HANDLER(),
m_frame( aFrame ),
m_transactionInProgress( false )
API_HANDLER_EDITOR( aFrame )
{
registerHandler<RunAction, RunActionResponse>( &API_HANDLER_PCB::handleRunAction );
registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
&API_HANDLER_PCB::handleGetOpenDocuments );
registerHandler<BeginCommit, BeginCommitResponse>( &API_HANDLER_PCB::handleBeginCommit );
registerHandler<EndCommit, EndCommitResponse>( &API_HANDLER_PCB::handleEndCommit );
registerHandler<CreateItems, CreateItemsResponse>( &API_HANDLER_PCB::handleCreateItems );
registerHandler<GetItems, GetItemsResponse>( &API_HANDLER_PCB::handleGetItems );
registerHandler<UpdateItems, UpdateItemsResponse>( &API_HANDLER_PCB::handleUpdateItems );
registerHandler<DeleteItems, DeleteItemsResponse>( &API_HANDLER_PCB::handleDeleteItems );
registerHandler<GetBoardStackup, BoardStackupResponse>( &API_HANDLER_PCB::handleGetStackup );
registerHandler<GetGraphicsDefaults, GraphicsDefaultsResponse>(
&API_HANDLER_PCB::handleGetGraphicsDefaults );
registerHandler<GetTextExtents, commands::BoundingBoxResponse>(
&API_HANDLER_PCB::handleGetTextExtents );
registerHandler<InteractiveMoveItems, Empty>( &API_HANDLER_PCB::handleInteractiveMoveItems );
registerHandler<GetNets, NetsResponse>( &API_HANDLER_PCB::handleGetNets );
registerHandler<RefillZones, Empty>( &API_HANDLER_PCB::handleRefillZones );
}
HANDLER_RESULT<RunActionResponse> API_HANDLER_PCB::handleRunAction( RunAction& aRequest )
PCB_EDIT_FRAME* API_HANDLER_PCB::frame() const
{
return static_cast<PCB_EDIT_FRAME*>( m_frame );
}
HANDLER_RESULT<RunActionResponse> API_HANDLER_PCB::handleRunAction( RunAction& aRequest,
const HANDLER_CONTEXT& )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
RunActionResponse response;
if( m_frame->GetToolManager()->RunAction( aRequest.action(), true ) )
if( frame()->GetToolManager()->RunAction( aRequest.action(), true ) )
response.set_status( RunActionStatus::RAS_OK );
else
response.set_status( RunActionStatus::RAS_INVALID );
@@ -70,7 +96,7 @@ HANDLER_RESULT<RunActionResponse> API_HANDLER_PCB::handleRunAction( RunAction& a
HANDLER_RESULT<GetOpenDocumentsResponse> API_HANDLER_PCB::handleGetOpenDocuments(
GetOpenDocuments& aMsg )
GetOpenDocuments& aMsg, const HANDLER_CONTEXT& )
{
if( aMsg.type() != DocumentType::DOCTYPE_PCB )
{
@@ -83,166 +109,178 @@ HANDLER_RESULT<GetOpenDocumentsResponse> API_HANDLER_PCB::handleGetOpenDocuments
GetOpenDocumentsResponse response;
common::types::DocumentSpecifier doc;
wxFileName fn( m_frame->GetCurrentFileName() );
wxFileName fn( frame()->GetCurrentFileName() );
doc.set_type( DocumentType::DOCTYPE_PCB );
doc.set_board_filename( fn.GetFullName() );
doc.mutable_project()->set_name( frame()->Prj().GetProjectName().ToStdString() );
doc.mutable_project()->set_path( frame()->Prj().GetProjectDirectory().ToStdString() );
response.mutable_documents()->Add( std::move( doc ) );
return response;
}
HANDLER_RESULT<BeginCommitResponse> API_HANDLER_PCB::handleBeginCommit( BeginCommit& aMsg )
void API_HANDLER_PCB::pushCurrentCommit( const HANDLER_CONTEXT& aCtx, const wxString& aMessage )
{
BeginCommitResponse response;
if( m_commit )
{
// TODO: right now there is no way for m_transactionInProgress to be true here, but
// we should still check it as a safety measure and return a specific error
//if( !m_transactionInProgress )
m_commit->Revert();
}
m_commit.reset( new BOARD_COMMIT( m_frame ) );
// TODO: return an opaque ID for this new commit to make this more robust
m_transactionInProgress = true;
return response;
API_HANDLER_EDITOR::pushCurrentCommit( aCtx, aMessage );
frame()->Refresh();
}
HANDLER_RESULT<EndCommitResponse> API_HANDLER_PCB::handleEndCommit( EndCommit& aMsg )
std::unique_ptr<COMMIT> API_HANDLER_PCB::createCommit()
{
EndCommitResponse response;
// TODO: return more specific error if m_transactionInProgress is false
if( !m_transactionInProgress )
{
// Make sure we don't get stuck with a commit we can never push
m_commit.reset();
response.set_result( CommitResult::CR_NO_COMMIT );
return response;
}
if( !m_commit )
{
response.set_result( CommitResult::CR_NO_COMMIT );
return response;
}
pushCurrentCommit( aMsg.message() );
m_transactionInProgress = false;
response.set_result( CommitResult::CR_OK );
return response;
return std::make_unique<BOARD_COMMIT>( frame() );
}
BOARD_COMMIT* API_HANDLER_PCB::getCurrentCommit()
std::optional<BOARD_ITEM*> API_HANDLER_PCB::getItemById( const KIID& aId ) const
{
if( !m_commit )
m_commit.reset( new BOARD_COMMIT( m_frame ) );
BOARD_ITEM* item = frame()->GetBoard()->GetItem( aId );
return m_commit.get();
if( item == DELETED_BOARD_ITEM::GetInstance() )
return std::nullopt;
return item;
}
void API_HANDLER_PCB::pushCurrentCommit( const std::string& aMessage )
bool API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
{
wxCHECK( m_commit, /* void */ );
wxString msg( aMessage.c_str(), wxConvUTF8 );
if( msg.IsEmpty() )
msg = s_defaultCommitMessage;
m_commit->Push( msg );
m_commit.reset();
m_frame->Refresh();
}
bool API_HANDLER_PCB::validateItemHeaderDocument( const common::types::ItemHeader& aHeader )
{
// TODO: this should return a more complex error type.
// We should provide detailed feedback when a header fails validation, and distinguish between
// "skip this handler" and "this is the right handler, but the request is invalid"
if( !aHeader.has_document() || aHeader.document().type() != DocumentType::DOCTYPE_PCB )
if( aDocument.type() != DocumentType::DOCTYPE_PCB )
return false;
wxFileName fn( m_frame->GetCurrentFileName() );
return aHeader.document().board_filename().compare( fn.GetFullName() ) == 0;
wxFileName fn( frame()->GetCurrentFileName() );
return 0 == aDocument.board_filename().compare( fn.GetFullName() );
}
std::unique_ptr<BOARD_ITEM> API_HANDLER_PCB::createItemForType( KICAD_T aType,
BOARD_ITEM_CONTAINER* aContainer )
HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>> API_HANDLER_PCB::createItemForType( KICAD_T aType,
BOARD_ITEM_CONTAINER* aContainer )
{
switch( aType )
if( !aContainer )
{
case PCB_TRACE_T: return std::make_unique<PCB_TRACK>( aContainer );
case PCB_ARC_T: return std::make_unique<PCB_ARC>( aContainer );
case PCB_VIA_T: return std::make_unique<PCB_VIA>( aContainer );
default: return nullptr;
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( "Tried to create an item in a null container" );
return tl::unexpected( e );
}
if( aType == PCB_PAD_T && !dynamic_cast<FOOTPRINT*>( aContainer ) )
{
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format( "Tried to create a pad in {}, which is not a footprint",
aContainer->GetFriendlyName().ToStdString() ) );
return tl::unexpected( e );
}
else if( aType == PCB_FOOTPRINT_T && !dynamic_cast<BOARD*>( aContainer ) )
{
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format( "Tried to create a footprint in {}, which is not a board",
aContainer->GetFriendlyName().ToStdString() ) );
return tl::unexpected( e );
}
std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
if( !created )
{
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
magic_enum::enum_name( aType ) ) );
return tl::unexpected( e );
}
return created;
}
HANDLER_RESULT<CreateItemsResponse> API_HANDLER_PCB::handleCreateItems( CreateItems& aMsg )
HANDLER_RESULT<ItemRequestStatus> API_HANDLER_PCB::handleCreateUpdateItemsInternal( bool aCreate,
const HANDLER_CONTEXT& aCtx,
const types::ItemHeader &aHeader,
const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
{
ApiResponseStatus e;
if( !validateItemHeaderDocument( aMsg.header() ) )
auto containerResult = validateItemHeaderDocument( aHeader );
if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
{
// No message needed for AS_UNHANDLED; this is an internal flag for the API server
e.set_status( ApiStatusCode::AS_UNHANDLED );
return tl::unexpected( e );
}
BOARD* board = m_frame->GetBoard();
BOARD_ITEM_SET boardItems = board->GetItemSet();
std::map<KIID, BOARD_ITEM*> itemUuidMap;
std::for_each( boardItems.begin(), boardItems.end(),
[&]( BOARD_ITEM* aItem )
{
itemUuidMap[aItem->m_Uuid] = aItem;
} );
BOARD_COMMIT* commit = getCurrentCommit();
CreateItemsResponse response;
for( const google::protobuf::Any& anyItem : aMsg.items() )
else if( !containerResult )
{
ItemCreationResult itemResult;
e.CopyFrom( containerResult.error() );
return tl::unexpected( e );
}
BOARD* board = frame()->GetBoard();
BOARD_ITEM_CONTAINER* container = board;
if( containerResult->has_value() )
{
const KIID& containerId = **containerResult;
std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
if( optItem )
{
container = dynamic_cast<BOARD_ITEM_CONTAINER*>( *optItem );
if( !container )
{
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format(
"The requested container {} is not a valid board item container",
containerId.AsStdString() ) );
return tl::unexpected( e );
}
}
else
{
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format(
"The requested container {} does not exist in this document",
containerId.AsStdString() ) );
return tl::unexpected( e );
}
}
BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx ) );
for( const google::protobuf::Any& anyItem : aItems )
{
ItemStatus status;
std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
if( !type )
{
itemResult.set_status( ItemCreationStatus::ICS_INVALID_TYPE );
response.mutable_created_items()->Add( std::move( itemResult ) );
status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
status.set_error_message( fmt::format( "Could not decode a valid type from {}",
anyItem.type_url() ) );
aItemHandler( status, anyItem );
continue;
}
std::unique_ptr<BOARD_ITEM> item = createItemForType( *type, board );
HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>> creationResult =
createItemForType( *type, container );
if( !item )
if( !creationResult )
{
itemResult.set_status( ItemCreationStatus::ICS_INVALID_TYPE );
e.set_error_message( fmt::format( "item type {} not supported for board",
magic_enum::enum_name( *type ) ) );
response.mutable_created_items()->Add( std::move( itemResult ) );
status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
status.set_error_message( creationResult.error().error_message() );
aItemHandler( status, anyItem );
continue;
}
std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
if( !item->Deserialize( anyItem ) )
{
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
@@ -251,28 +289,70 @@ HANDLER_RESULT<CreateItemsResponse> API_HANDLER_PCB::handleCreateItems( CreateIt
return tl::unexpected( e );
}
if( itemUuidMap.count( item->m_Uuid ) )
std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
if( aCreate && optItem )
{
itemResult.set_status( ItemCreationStatus::ICS_EXISTING );
response.mutable_created_items()->Add( std::move( itemResult ) );
status.set_code( ItemStatusCode::ISC_EXISTING );
status.set_error_message( fmt::format( "an item with UUID {} already exists",
item->m_Uuid.AsStdString() ) );
aItemHandler( status, anyItem );
continue;
}
else if( !aCreate && !optItem )
{
status.set_code( ItemStatusCode::ISC_NONEXISTENT );
status.set_error_message( fmt::format( "an item with UUID {} does not exist",
item->m_Uuid.AsStdString() ) );
aItemHandler( status, anyItem );
continue;
}
itemResult.set_status( ItemCreationStatus::ICS_OK );
item->Serialize( *itemResult.mutable_item() );
commit->Add( item.release() );
if( aCreate && !board->GetEnabledLayers().Contains( item->GetLayer() ) )
{
status.set_code( ItemStatusCode::ISC_INVALID_DATA );
status.set_error_message( fmt::format( "attempted to add item on disabled layer {}",
LayerName( item->GetLayer() ).ToStdString() ) );
aItemHandler( status, anyItem );
continue;
}
response.mutable_created_items()->Add( std::move( itemResult ) );
status.set_code( ItemStatusCode::ISC_OK );
google::protobuf::Any newItem;
if( aCreate )
{
item->Serialize( newItem );
commit->Add( item.release() );
}
else
{
BOARD_ITEM* boardItem = *optItem;
commit->Modify( boardItem );
boardItem->SwapItemData( item.get() );
boardItem->Serialize( newItem );
}
aItemHandler( status, newItem );
}
pushCurrentCommit( "Added items via API" );
response.set_status( ItemRequestStatus::IRS_OK );
return response;
if( !m_activeClients.count( aCtx.ClientName ) )
{
pushCurrentCommit( aCtx, aCreate ? _( "Created items via API" )
: _( "Added items via API" ) );
}
return ItemRequestStatus::IRS_OK;
}
HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems( GetItems& aMsg )
HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems( GetItems& aMsg,
const HANDLER_CONTEXT& )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
if( !validateItemHeaderDocument( aMsg.header() ) )
{
ApiResponseStatus e;
@@ -283,18 +363,17 @@ HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems( GetItems& aMsg
GetItemsResponse response;
BOARD* board = m_frame->GetBoard();
BOARD* board = frame()->GetBoard();
std::vector<BOARD_ITEM*> items;
std::set<KICAD_T> typesRequested, typesInserted;
bool handledAnything = false;
for( const common::types::ItemType& typeMessage : aMsg.types() )
for( int typeRaw : aMsg.types() )
{
KICAD_T type;
auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
if( std::optional<KICAD_T> opt_type = magic_enum::enum_cast<KICAD_T>( typeMessage.type() ) )
type = *opt_type;
else
if( type == TYPE_NOT_INIT )
continue;
typesRequested.emplace( type );
@@ -313,6 +392,31 @@ HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems( GetItems& aMsg
typesInserted.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
break;
case PCB_PAD_T:
{
handledAnything = true;
for( FOOTPRINT* fp : board->Footprints() )
{
std::copy( fp->Pads().begin(), fp->Pads().end(),
std::back_inserter( items ) );
}
typesInserted.insert( PCB_PAD_T );
break;
}
case PCB_FOOTPRINT_T:
{
handledAnything = true;
std::copy( board->Footprints().begin(), board->Footprints().end(),
std::back_inserter( items ) );
typesInserted.insert( PCB_FOOTPRINT_T );
break;
}
default:
break;
}
@@ -341,154 +445,259 @@ HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems( GetItems& aMsg
}
HANDLER_RESULT<UpdateItemsResponse> API_HANDLER_PCB::handleUpdateItems( UpdateItems& aMsg )
void API_HANDLER_PCB::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
const HANDLER_CONTEXT& aCtx )
{
ApiResponseStatus e;
if( !validateItemHeaderDocument( aMsg.header() ) )
{
// No message needed for AS_UNHANDLED; this is an internal flag for the API server
e.set_status( ApiStatusCode::AS_UNHANDLED );
return tl::unexpected( e );
}
BOARD* board = m_frame->GetBoard();
BOARD_ITEM_SET boardItems = board->GetItemSet();
std::map<KIID, BOARD_ITEM*> itemUuidMap;
std::for_each( boardItems.begin(), boardItems.end(),
[&]( BOARD_ITEM* aItem )
{
itemUuidMap[aItem->m_Uuid] = aItem;
} );
BOARD_COMMIT* commit = getCurrentCommit();
UpdateItemsResponse response;
for( const google::protobuf::Any& anyItem : aMsg.items() )
{
ItemUpdateResult itemResult;
std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
if( !type )
{
itemResult.set_status( ItemUpdateStatus::IUS_INVALID_TYPE );
response.mutable_updated_items()->Add( std::move( itemResult ) );
continue;
}
std::unique_ptr<BOARD_ITEM> temporaryItem = createItemForType( *type, board );
if( !temporaryItem )
{
itemResult.set_status( ItemUpdateStatus::IUS_INVALID_TYPE );
response.mutable_updated_items()->Add( std::move( itemResult ) );
continue;
}
if( !temporaryItem->Deserialize( anyItem ) )
{
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format( "could not unpack {} from request",
magic_enum::enum_name( *type ) ) );
return tl::unexpected( e );
}
if( !itemUuidMap.count( temporaryItem->m_Uuid ) )
{
itemResult.set_status( ItemUpdateStatus::IUS_NONEXISTENT );
response.mutable_updated_items()->Add( std::move( itemResult ) );
continue;
}
BOARD_ITEM* boardItem = itemUuidMap[temporaryItem->m_Uuid];
boardItem->SwapItemData( temporaryItem.get() );
itemResult.set_status( ItemUpdateStatus::IUS_OK );
boardItem->Serialize( *itemResult.mutable_item() );
commit->Modify( boardItem );
itemResult.set_status( ItemUpdateStatus::IUS_OK );
response.mutable_updated_items()->Add( std::move( itemResult ) );
}
response.set_status( ItemRequestStatus::IRS_OK );
pushCurrentCommit( "Updated items via API" );
return response;
}
HANDLER_RESULT<DeleteItemsResponse> API_HANDLER_PCB::handleDeleteItems( DeleteItems& aMsg )
{
if( !validateItemHeaderDocument( aMsg.header() ) )
{
ApiResponseStatus e;
// No message needed for AS_UNHANDLED; this is an internal flag for the API server
e.set_status( ApiStatusCode::AS_UNHANDLED );
return tl::unexpected( e );
}
std::map<KIID, ItemDeletionStatus> itemsToDelete;
for( const common::types::KIID& kiidBuf : aMsg.item_ids() )
{
if( !kiidBuf.value().empty() )
{
KIID kiid( kiidBuf.value() );
itemsToDelete[kiid] = ItemDeletionStatus::IDS_NONEXISTENT;
}
}
if( itemsToDelete.empty() )
{
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( "no valid items to delete were given" );
return tl::unexpected( e );
}
BOARD* board = m_frame->GetBoard();
// This is somewhat inefficient on paper, but the total number of items on a board is
// not computationally-speaking very high even on what we'd consider a large design.
// If this ends up not being the case, we should consider doing something like refactoring
// BOARD to contain all items in a contiguous memory arena and constructing views over it
// when we want to filter to just tracks, etc.
BOARD_ITEM_SET items = board->GetItemSet();
BOARD* board = frame()->GetBoard();
std::vector<BOARD_ITEM*> validatedItems;
for( BOARD_ITEM* item : items )
for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
{
if( itemsToDelete.count( item->m_Uuid ) )
if( BOARD_ITEM* item = board->GetItem( pair.first ) )
{
validatedItems.push_back( item );
itemsToDelete[item->m_Uuid] = ItemDeletionStatus::IDS_OK;
aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_OK;
}
// Note: we don't currently support locking items from API modification, but here is where
// to add it in the future (and return IDS_IMMUTABLE)
}
BOARD_COMMIT* commit = getCurrentCommit();
COMMIT* commit = getCurrentCommit( aCtx );
for( BOARD_ITEM* item : validatedItems )
commit->Remove( item );
if( !m_transactionInProgress )
pushCurrentCommit( "Deleted items via API" );
if( !m_activeClients.count( aCtx.ClientName ) )
pushCurrentCommit( aCtx, _( "Deleted items via API" ) );
}
DeleteItemsResponse response;
for( const auto& [id, status] : itemsToDelete )
std::optional<EDA_ITEM*> API_HANDLER_PCB::getItemFromDocument( const DocumentSpecifier& aDocument,
const KIID& aId )
{
if( !validateDocument( aDocument ) )
return std::nullopt;
return getItemById( aId );
}
HANDLER_RESULT<BoardStackupResponse> API_HANDLER_PCB::handleGetStackup( GetBoardStackup& aMsg,
const HANDLER_CONTEXT& aCtx )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
if( !documentValidation )
return tl::unexpected( documentValidation.error() );
const BOARD* board = frame()->GetBoard();
BoardStackupResponse response;
google::protobuf::Any any;
const BOARD_DESIGN_SETTINGS& bds = board->GetDesignSettings();
if( frame()->GetBoard()->GetDesignSettings().m_HasStackup )
{
ItemDeletionResult result;
result.mutable_id()->set_value( id.AsStdString() );
result.set_status( status );
const BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
stackup.Serialize( any );
}
else
{
BOARD_STACKUP stackup;
stackup.BuildDefaultStackupList( &bds, board->GetCopperLayerCount() );
stackup.Serialize( any );
}
response.set_status( ItemRequestStatus::IRS_OK );
any.UnpackTo( response.mutable_stackup() );
return response;
}
HANDLER_RESULT<GraphicsDefaultsResponse> API_HANDLER_PCB::handleGetGraphicsDefaults(
GetGraphicsDefaults& aMsg,
const HANDLER_CONTEXT& aCtx )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
if( !documentValidation )
return tl::unexpected( documentValidation.error() );
const BOARD_DESIGN_SETTINGS& bds = frame()->GetBoard()->GetDesignSettings();
GraphicsDefaultsResponse response;
// TODO: This should change to be an enum class
constexpr std::array<kiapi::board::BoardLayerClass, LAYER_CLASS_COUNT> classOrder = {
kiapi::board::BLC_SILKSCREEN,
kiapi::board::BLC_COPPER,
kiapi::board::BLC_EDGES,
kiapi::board::BLC_COURTYARD,
kiapi::board::BLC_FABRICATION,
kiapi::board::BLC_OTHER
};
for( int i = 0; i < LAYER_CLASS_COUNT; ++i )
{
kiapi::board::BoardLayerGraphicsDefaults* l = response.mutable_defaults()->add_layers();
l->set_layer( classOrder[i] );
l->mutable_line_thickness()->set_value_nm( bds.m_LineThickness[i] );
kiapi::common::types::TextAttributes* text = l->mutable_text();
text->mutable_size()->set_x_nm( bds.m_TextSize[i].x );
text->mutable_size()->set_y_nm( bds.m_TextSize[i].y );
text->mutable_stroke_width()->set_value_nm( bds.m_TextThickness[i] );
text->set_italic( bds.m_TextItalic[i] );
text->set_keep_upright( bds.m_TextUpright[i] );
}
return response;
}
HANDLER_RESULT<commands::BoundingBoxResponse> API_HANDLER_PCB::handleGetTextExtents(
GetTextExtents& aMsg,
const HANDLER_CONTEXT& aCtx )
{
PCB_TEXT text( frame()->GetBoard() );
google::protobuf::Any any;
any.PackFrom( aMsg.text() );
if( !text.Deserialize( any ) )
{
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( "Could not decode text in GetTextExtents message" );
return tl::unexpected( e );
}
commands::BoundingBoxResponse response;
BOX2I bbox = text.GetTextBox();
EDA_ANGLE angle = text.GetTextAngle();
if( !angle.IsZero() )
bbox = bbox.GetBoundingBoxRotated( text.GetTextPos(), text.GetTextAngle() );
response.mutable_position()->set_x_nm( bbox.GetPosition().x );
response.mutable_position()->set_y_nm( bbox.GetPosition().y );
response.mutable_size()->set_x_nm( bbox.GetSize().x );
response.mutable_size()->set_y_nm( bbox.GetSize().y );
return response;
}
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleInteractiveMoveItems( InteractiveMoveItems& aMsg,
const HANDLER_CONTEXT& aCtx )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
if( !documentValidation )
return tl::unexpected( documentValidation.error() );
TOOL_MANAGER* mgr = frame()->GetToolManager();
std::vector<EDA_ITEM*> toSelect;
for( const kiapi::common::types::KIID& id : aMsg.items() )
{
if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
}
if( toSelect.empty() )
{
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
e.set_error_message( fmt::format( "None of the given items exist on the board",
aMsg.board().board_filename() ) );
return tl::unexpected( e );
}
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
mgr->RunAction( PCB_ACTIONS::selectionClear );
mgr->RunAction<EDA_ITEMS*>( PCB_ACTIONS::selectItems, &toSelect );
COMMIT* commit = getCurrentCommit( aCtx );
mgr->PostAction( PCB_ACTIONS::move, commit );
return Empty();
}
HANDLER_RESULT<NetsResponse> API_HANDLER_PCB::handleGetNets( GetNets& aMsg,
const HANDLER_CONTEXT& aCtx )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
if( !documentValidation )
return tl::unexpected( documentValidation.error() );
NetsResponse response;
BOARD* board = frame()->GetBoard();
std::set<wxString> netclassFilter;
for( const std::string& nc : aMsg.netclass_filter() )
netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
for( NETINFO_ITEM* net : board->GetNetInfo() )
{
NETCLASS* nc = net->GetNetClass();
if( !netclassFilter.empty() && nc && !netclassFilter.count( nc->GetName() ) )
continue;
board::types::Net* netProto = response.add_nets();
netProto->set_name( net->GetNetname() );
netProto->mutable_code()->set_value( net->GetNetCode() );
}
return response;
}
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleRefillZones( RefillZones& aMsg,
const HANDLER_CONTEXT& aCtx )
{
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
return tl::unexpected( *busy );
HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
if( !documentValidation )
return tl::unexpected( documentValidation.error() );
if( aMsg.zones().empty() )
{
TOOL_MANAGER* mgr = frame()->GetToolManager();
frame()->CallAfter( [mgr]()
{
mgr->RunAction( PCB_ACTIONS::zoneFillAll );
} );
}
else
{
// TODO
ApiResponseStatus e;
e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
return tl::unexpected( e );
}
return Empty();
}