2023-01-29 18:06:05 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
|
|
|
|
* Copyright (C) 2023 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <magic_enum.hpp>
|
|
|
|
|
|
|
|
#include <api/api_handler_pcb.h>
|
2024-01-20 23:35:29 +00:00
|
|
|
#include <api/api_pcb_utils.h>
|
|
|
|
#include <api/api_enums.h>
|
|
|
|
#include <api/api_utils.h>
|
2023-01-29 18:06:05 +00:00
|
|
|
#include <board_commit.h>
|
2024-01-20 23:35:29 +00:00
|
|
|
#include <board_design_settings.h>
|
|
|
|
#include <footprint.h>
|
|
|
|
#include <netinfo.h>
|
|
|
|
#include <pad.h>
|
2023-01-29 18:06:05 +00:00
|
|
|
#include <pcb_edit_frame.h>
|
2024-01-20 23:35:29 +00:00
|
|
|
#include <pcb_group.h>
|
|
|
|
#include <pcb_reference_image.h>
|
|
|
|
#include <pcb_shape.h>
|
|
|
|
#include <pcb_text.h>
|
|
|
|
#include <pcb_textbox.h>
|
2023-01-29 18:06:05 +00:00
|
|
|
#include <pcb_track.h>
|
2024-01-20 23:35:29 +00:00
|
|
|
#include <project.h>
|
2023-01-29 18:06:05 +00:00
|
|
|
#include <tool/tool_manager.h>
|
2024-01-20 23:35:29 +00:00
|
|
|
#include <tools/pcb_actions.h>
|
|
|
|
#include <tools/pcb_selection_tool.h>
|
|
|
|
#include <zone.h>
|
2023-01-29 18:06:05 +00:00
|
|
|
|
|
|
|
#include <api/common/types/base_types.pb.h>
|
|
|
|
|
|
|
|
using namespace kiapi::common::commands;
|
|
|
|
using kiapi::common::types::CommandStatus;
|
|
|
|
using kiapi::common::types::DocumentType;
|
|
|
|
using kiapi::common::types::ItemRequestStatus;
|
|
|
|
|
|
|
|
|
|
|
|
API_HANDLER_PCB::API_HANDLER_PCB( PCB_EDIT_FRAME* aFrame ) :
|
2024-01-20 23:35:29 +00:00
|
|
|
API_HANDLER_EDITOR( aFrame )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
|
|
|
registerHandler<RunAction, RunActionResponse>( &API_HANDLER_PCB::handleRunAction );
|
|
|
|
registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
|
|
|
|
&API_HANDLER_PCB::handleGetOpenDocuments );
|
|
|
|
|
|
|
|
|
|
|
|
registerHandler<GetItems, GetItemsResponse>( &API_HANDLER_PCB::handleGetItems );
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
PCB_EDIT_FRAME* API_HANDLER_PCB::frame() const
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
|
|
|
|
2023-01-29 18:06:05 +00:00
|
|
|
RunActionResponse response;
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( frame()->GetToolManager()->RunAction( aRequest.action(), true ) )
|
2023-01-29 18:06:05 +00:00
|
|
|
response.set_status( RunActionStatus::RAS_OK );
|
|
|
|
else
|
|
|
|
response.set_status( RunActionStatus::RAS_INVALID );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<GetOpenDocumentsResponse> API_HANDLER_PCB::handleGetOpenDocuments(
|
2024-01-20 23:35:29 +00:00
|
|
|
GetOpenDocuments& aMsg, const HANDLER_CONTEXT& )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
|
|
|
if( aMsg.type() != DocumentType::DOCTYPE_PCB )
|
|
|
|
{
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
GetOpenDocumentsResponse response;
|
|
|
|
common::types::DocumentSpecifier doc;
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
wxFileName fn( frame()->GetCurrentFileName() );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
|
|
|
doc.set_type( DocumentType::DOCTYPE_PCB );
|
|
|
|
doc.set_board_filename( fn.GetFullName() );
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
doc.mutable_project()->set_name( frame()->Prj().GetProjectName().ToStdString() );
|
|
|
|
doc.mutable_project()->set_path( frame()->Prj().GetProjectDirectory().ToStdString() );
|
|
|
|
|
2023-01-29 18:06:05 +00:00
|
|
|
response.mutable_documents()->Add( std::move( doc ) );
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
void API_HANDLER_PCB::pushCurrentCommit( const HANDLER_CONTEXT& aCtx, const wxString& aMessage )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
API_HANDLER_EDITOR::pushCurrentCommit( aCtx, aMessage );
|
|
|
|
frame()->Refresh();
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
std::unique_ptr<COMMIT> API_HANDLER_PCB::createCommit()
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
return std::make_unique<BOARD_COMMIT>( frame() );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
std::optional<BOARD_ITEM*> API_HANDLER_PCB::getItemById( const KIID& aId ) const
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
BOARD_ITEM* item = frame()->GetBoard()->GetItem( aId );
|
|
|
|
|
|
|
|
if( item == DELETED_BOARD_ITEM::GetInstance() )
|
|
|
|
return std::nullopt;
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
return item;
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
bool API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
if( aDocument.type() != DocumentType::DOCTYPE_PCB )
|
|
|
|
return false;
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
wxFileName fn( frame()->GetCurrentFileName() );
|
|
|
|
return 0 == aDocument.board_filename().compare( fn.GetFullName() );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>> API_HANDLER_PCB::createItemForType( KICAD_T aType,
|
|
|
|
BOARD_ITEM_CONTAINER* aContainer )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
if( !aContainer )
|
|
|
|
{
|
|
|
|
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 );
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( !created )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
2024-01-20 23:35:29 +00:00
|
|
|
|
|
|
|
return created;
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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 )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
auto containerResult = validateItemHeaderDocument( aHeader );
|
|
|
|
|
|
|
|
if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
|
|
|
// 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 );
|
|
|
|
}
|
2024-01-20 23:35:29 +00:00
|
|
|
else if( !containerResult )
|
|
|
|
{
|
|
|
|
e.CopyFrom( containerResult.error() );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
BOARD_ITEM_CONTAINER* container = board;
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( containerResult->has_value() )
|
|
|
|
{
|
|
|
|
const KIID& containerId = **containerResult;
|
|
|
|
std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx ) );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
for( const google::protobuf::Any& anyItem : aItems )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
ItemStatus status;
|
2023-01-29 18:06:05 +00:00
|
|
|
std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
|
|
|
|
|
|
|
|
if( !type )
|
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
2023-01-29 18:06:05 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>> creationResult =
|
|
|
|
createItemForType( *type, container );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( !creationResult )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
|
|
|
|
status.set_error_message( creationResult.error().error_message() );
|
|
|
|
aItemHandler( status, anyItem );
|
2023-01-29 18:06:05 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
|
|
|
|
|
2023-01-29 18:06:05 +00:00
|
|
|
if( !item->Deserialize( anyItem ) )
|
|
|
|
{
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
e.set_error_message( fmt::format( "could not unpack {} from request",
|
|
|
|
item->GetClass().ToStdString() ) );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
|
|
|
|
|
|
|
|
if( aCreate && optItem )
|
|
|
|
{
|
|
|
|
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 )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
2023-01-29 18:06:05 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( !m_activeClients.count( aCtx.ClientName ) )
|
|
|
|
{
|
|
|
|
pushCurrentCommit( aCtx, aCreate ? _( "Created items via API" )
|
|
|
|
: _( "Added items via API" ) );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
|
|
|
|
return ItemRequestStatus::IRS_OK;
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems( GetItems& aMsg,
|
|
|
|
const HANDLER_CONTEXT& )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2023-01-29 18:06:05 +00:00
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
GetItemsResponse response;
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
BOARD* board = frame()->GetBoard();
|
2023-01-29 18:06:05 +00:00
|
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
std::set<KICAD_T> typesRequested, typesInserted;
|
|
|
|
bool handledAnything = false;
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
for( int typeRaw : aMsg.types() )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
|
|
|
|
KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( type == TYPE_NOT_INIT )
|
2023-01-29 18:06:05 +00:00
|
|
|
continue;
|
|
|
|
|
|
|
|
typesRequested.emplace( type );
|
|
|
|
|
|
|
|
if( typesInserted.count( type ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
switch( type )
|
|
|
|
{
|
|
|
|
case PCB_TRACE_T:
|
|
|
|
case PCB_ARC_T:
|
|
|
|
case PCB_VIA_T:
|
|
|
|
handledAnything = true;
|
|
|
|
std::copy( board->Tracks().begin(), board->Tracks().end(),
|
|
|
|
std::back_inserter( items ) );
|
|
|
|
typesInserted.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
|
|
|
|
break;
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-29 18:06:05 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !handledAnything )
|
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
e.set_error_message( "none of the requested types are valid for a Board object" );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
for( const BOARD_ITEM* item : items )
|
|
|
|
{
|
|
|
|
if( !typesRequested.count( item->Type() ) )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
google::protobuf::Any itemBuf;
|
|
|
|
item->Serialize( itemBuf );
|
|
|
|
response.mutable_items()->Add( std::move( itemBuf ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
response.set_status( ItemRequestStatus::IRS_OK );
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
void API_HANDLER_PCB::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
|
|
|
|
const HANDLER_CONTEXT& aCtx )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
std::vector<BOARD_ITEM*> validatedItems;
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
if( BOARD_ITEM* item = board->GetItem( pair.first ) )
|
|
|
|
{
|
|
|
|
validatedItems.push_back( item );
|
|
|
|
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)
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
COMMIT* commit = getCurrentCommit( aCtx );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
for( BOARD_ITEM* item : validatedItems )
|
|
|
|
commit->Remove( item );
|
|
|
|
|
|
|
|
if( !m_activeClients.count( aCtx.ClientName ) )
|
|
|
|
pushCurrentCommit( aCtx, _( "Deleted items via API" ) );
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
std::optional<EDA_ITEM*> API_HANDLER_PCB::getItemFromDocument( const DocumentSpecifier& aDocument,
|
|
|
|
const KIID& aId )
|
|
|
|
{
|
|
|
|
if( !validateDocument( aDocument ) )
|
|
|
|
return std::nullopt;
|
|
|
|
|
|
|
|
return getItemById( aId );
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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() );
|
|
|
|
|
2024-04-26 12:25:45 +00:00
|
|
|
BoardStackupResponse response;
|
2024-01-20 23:35:29 +00:00
|
|
|
google::protobuf::Any any;
|
|
|
|
|
2024-04-26 12:25:45 +00:00
|
|
|
frame()->GetBoard()->GetStackupOrDefault().Serialize( any );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
any.UnpackTo( response.mutable_stackup() );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
return response;
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
HANDLER_RESULT<GraphicsDefaultsResponse> API_HANDLER_PCB::handleGetGraphicsDefaults(
|
|
|
|
GetGraphicsDefaults& aMsg,
|
|
|
|
const HANDLER_CONTEXT& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
|
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
const BOARD_DESIGN_SETTINGS& bds = frame()->GetBoard()->GetDesignSettings();
|
|
|
|
GraphicsDefaultsResponse response;
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
// 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
|
|
|
|
};
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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] );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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] );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
HANDLER_RESULT<commands::BoundingBoxResponse> API_HANDLER_PCB::handleGetTextExtents(
|
|
|
|
GetTextExtents& aMsg,
|
|
|
|
const HANDLER_CONTEXT& aCtx )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
PCB_TEXT text( frame()->GetBoard() );
|
|
|
|
|
|
|
|
google::protobuf::Any any;
|
|
|
|
any.PackFrom( aMsg.text() );
|
|
|
|
|
|
|
|
if( !text.Deserialize( any ) )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
2024-01-20 23:35:29 +00:00
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
e.set_error_message( "Could not decode text in GetTextExtents message" );
|
2023-01-29 18:06:05 +00:00
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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() )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
|
|
|
|
toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( toSelect.empty() )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
2024-01-20 23:35:29 +00:00
|
|
|
e.set_error_message( fmt::format( "None of the given items exist on the board",
|
|
|
|
aMsg.board().board_filename() ) );
|
2023-01-29 18:06:05 +00:00
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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();
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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() )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
NETCLASS* nc = net->GetNetClass();
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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() );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
return response;
|
|
|
|
}
|
2023-01-29 18:06:05 +00:00
|
|
|
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
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() );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
2023-01-29 18:06:05 +00:00
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
if( aMsg.zones().empty() )
|
2023-01-29 18:06:05 +00:00
|
|
|
{
|
2024-01-20 23:35:29 +00:00
|
|
|
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 );
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|
|
|
|
|
2024-01-20 23:35:29 +00:00
|
|
|
return Empty();
|
2023-01-29 18:06:05 +00:00
|
|
|
}
|