2023-01-29 13:06:05 -05:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
|
2025-01-01 13:30:11 -08:00
|
|
|
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
|
2023-01-29 13:06:05 -05:00
|
|
|
*
|
|
|
|
* 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 18:35:29 -05:00
|
|
|
#include <api/api_pcb_utils.h>
|
|
|
|
#include <api/api_enums.h>
|
|
|
|
#include <api/api_utils.h>
|
2023-01-29 13:06:05 -05:00
|
|
|
#include <board_commit.h>
|
2024-01-20 18:35:29 -05:00
|
|
|
#include <board_design_settings.h>
|
|
|
|
#include <footprint.h>
|
2024-12-01 21:37:13 -05:00
|
|
|
#include <kicad_clipboard.h>
|
2024-01-20 18:35:29 -05:00
|
|
|
#include <netinfo.h>
|
|
|
|
#include <pad.h>
|
2023-01-29 13:06:05 -05:00
|
|
|
#include <pcb_edit_frame.h>
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
#include <pcb_track.h>
|
2025-01-04 09:47:26 -05:00
|
|
|
#include <pcbnew_id.h>
|
2024-01-20 18:35:29 -05:00
|
|
|
#include <project.h>
|
2023-01-29 13:06:05 -05:00
|
|
|
#include <tool/tool_manager.h>
|
2024-01-20 18:35:29 -05:00
|
|
|
#include <tools/pcb_actions.h>
|
|
|
|
#include <tools/pcb_selection_tool.h>
|
|
|
|
#include <zone.h>
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
#include <api/common/types/base_types.pb.h>
|
2024-12-03 21:17:48 -05:00
|
|
|
#include <widgets/appearance_controls.h>
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
using namespace kiapi::common::commands;
|
2024-12-01 21:37:13 -05:00
|
|
|
using types::CommandStatus;
|
|
|
|
using types::DocumentType;
|
|
|
|
using types::ItemRequestStatus;
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
|
|
|
|
API_HANDLER_PCB::API_HANDLER_PCB( PCB_EDIT_FRAME* aFrame ) :
|
2024-01-20 18:35:29 -05:00
|
|
|
API_HANDLER_EDITOR( aFrame )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
|
|
|
registerHandler<RunAction, RunActionResponse>( &API_HANDLER_PCB::handleRunAction );
|
|
|
|
registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
|
|
|
|
&API_HANDLER_PCB::handleGetOpenDocuments );
|
2025-01-04 09:47:26 -05:00
|
|
|
registerHandler<SaveDocument, Empty>( &API_HANDLER_PCB::handleSaveDocument );
|
|
|
|
registerHandler<SaveCopyOfDocument, Empty>( &API_HANDLER_PCB::handleSaveCopyOfDocument );
|
|
|
|
registerHandler<RevertDocument, Empty>( &API_HANDLER_PCB::handleRevertDocument );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
registerHandler<GetItems, GetItemsResponse>( &API_HANDLER_PCB::handleGetItems );
|
|
|
|
|
2025-01-21 19:52:38 -05:00
|
|
|
registerHandler<GetSelection, SelectionResponse>( &API_HANDLER_PCB::handleGetSelection );
|
|
|
|
registerHandler<ClearSelection, Empty>( &API_HANDLER_PCB::handleClearSelection );
|
|
|
|
registerHandler<AddToSelection, SelectionResponse>( &API_HANDLER_PCB::handleAddToSelection );
|
|
|
|
registerHandler<RemoveFromSelection, SelectionResponse>(
|
|
|
|
&API_HANDLER_PCB::handleRemoveFromSelection );
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
registerHandler<GetBoardStackup, BoardStackupResponse>( &API_HANDLER_PCB::handleGetStackup );
|
|
|
|
registerHandler<GetGraphicsDefaults, GraphicsDefaultsResponse>(
|
|
|
|
&API_HANDLER_PCB::handleGetGraphicsDefaults );
|
2024-11-19 22:09:00 -05:00
|
|
|
registerHandler<GetBoundingBox, GetBoundingBoxResponse>(
|
|
|
|
&API_HANDLER_PCB::handleGetBoundingBox );
|
2024-11-19 21:17:22 -05:00
|
|
|
registerHandler<GetPadShapeAsPolygon, PadShapeAsPolygonResponse>(
|
|
|
|
&API_HANDLER_PCB::handleGetPadShapeAsPolygon );
|
2024-11-19 23:44:29 -05:00
|
|
|
registerHandler<GetTitleBlockInfo, types::TitleBlockInfo>(
|
|
|
|
&API_HANDLER_PCB::handleGetTitleBlockInfo );
|
2024-11-28 19:21:18 -05:00
|
|
|
registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
|
|
|
|
&API_HANDLER_PCB::handleExpandTextVariables );
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
registerHandler<InteractiveMoveItems, Empty>( &API_HANDLER_PCB::handleInteractiveMoveItems );
|
|
|
|
registerHandler<GetNets, NetsResponse>( &API_HANDLER_PCB::handleGetNets );
|
2024-12-31 17:51:07 -05:00
|
|
|
registerHandler<GetNetClassForNets, NetClassForNetsResponse>(
|
|
|
|
&API_HANDLER_PCB::handleGetNetClassForNets );
|
2024-01-20 18:35:29 -05:00
|
|
|
registerHandler<RefillZones, Empty>( &API_HANDLER_PCB::handleRefillZones );
|
2024-12-01 21:37:13 -05:00
|
|
|
|
|
|
|
registerHandler<SaveDocumentToString, SavedDocumentResponse>(
|
|
|
|
&API_HANDLER_PCB::handleSaveDocumentToString );
|
|
|
|
registerHandler<SaveSelectionToString, SavedSelectionResponse>(
|
|
|
|
&API_HANDLER_PCB::handleSaveSelectionToString );
|
|
|
|
registerHandler<ParseAndCreateItemsFromString, CreateItemsResponse>(
|
|
|
|
&API_HANDLER_PCB::handleParseAndCreateItemsFromString );
|
2024-12-03 21:17:48 -05:00
|
|
|
registerHandler<GetVisibleLayers, BoardLayers>( &API_HANDLER_PCB::handleGetVisibleLayers );
|
|
|
|
registerHandler<SetVisibleLayers, Empty>( &API_HANDLER_PCB::handleSetVisibleLayers );
|
|
|
|
registerHandler<GetActiveLayer, BoardLayerResponse>( &API_HANDLER_PCB::handleGetActiveLayer );
|
|
|
|
registerHandler<SetActiveLayer, Empty>( &API_HANDLER_PCB::handleSetActiveLayer );
|
2024-12-29 22:02:23 -05:00
|
|
|
registerHandler<GetBoardEditorAppearanceSettings, BoardEditorAppearanceSettings>(
|
|
|
|
&API_HANDLER_PCB::handleGetBoardEditorAppearanceSettings );
|
|
|
|
registerHandler<SetBoardEditorAppearanceSettings, Empty>(
|
|
|
|
&API_HANDLER_PCB::handleSetBoardEditorAppearanceSettings );
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
PCB_EDIT_FRAME* API_HANDLER_PCB::frame() const
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
return static_cast<PCB_EDIT_FRAME*>( m_frame );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<RunActionResponse> API_HANDLER_PCB::handleRunAction(
|
|
|
|
const HANDLER_CONTEXT<RunAction>& aCtx )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2023-01-29 13:06:05 -05:00
|
|
|
RunActionResponse response;
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
if( frame()->GetToolManager()->RunAction( aCtx.Request.action(), true ) )
|
2023-01-29 13:06:05 -05:00
|
|
|
response.set_status( RunActionStatus::RAS_OK );
|
|
|
|
else
|
|
|
|
response.set_status( RunActionStatus::RAS_INVALID );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<GetOpenDocumentsResponse> API_HANDLER_PCB::handleGetOpenDocuments(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<GetOpenDocuments>& aCtx )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
if( aCtx.Request.type() != DocumentType::DOCTYPE_PCB )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
|
|
|
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 18:35:29 -05:00
|
|
|
wxFileName fn( frame()->GetCurrentFileName() );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
doc.set_type( DocumentType::DOCTYPE_PCB );
|
|
|
|
doc.set_board_filename( fn.GetFullName() );
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
doc.mutable_project()->set_name( frame()->Prj().GetProjectName().ToStdString() );
|
|
|
|
doc.mutable_project()->set_path( frame()->Prj().GetProjectDirectory().ToStdString() );
|
|
|
|
|
2023-01-29 13:06:05 -05:00
|
|
|
response.mutable_documents()->Add( std::move( doc ) );
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2025-01-04 09:47:26 -05:00
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleSaveDocument(
|
|
|
|
const HANDLER_CONTEXT<SaveDocument>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
frame()->Files_io_from_id( ID_SAVE_BOARD );
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleSaveCopyOfDocument(
|
|
|
|
const HANDLER_CONTEXT<SaveCopyOfDocument>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
wxFileName boardPath( frame()->Prj().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
|
|
|
|
|
|
|
|
if( !boardPath.IsOk() || !boardPath.IsDirWritable() )
|
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
e.set_error_message( fmt::format( "save path '{}' could not be opened",
|
|
|
|
boardPath.GetFullPath().ToStdString() ) );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( boardPath.FileExists()
|
|
|
|
&& ( !boardPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
|
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
|
|
|
|
boardPath.GetFullPath().ToStdString() ) );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( boardPath.GetExt() != FILEEXT::KiCadPcbFileExtension )
|
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
e.set_error_message( fmt::format( "save path '{}' must have a kicad_pcb extension",
|
|
|
|
boardPath.GetFullPath().ToStdString() ) );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
|
|
|
|
if( board->GetFileName().Matches( boardPath.GetFullPath() ) )
|
|
|
|
{
|
|
|
|
frame()->Files_io_from_id( ID_SAVE_BOARD );
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool includeProject = true;
|
|
|
|
|
|
|
|
if( aCtx.Request.has_options() )
|
|
|
|
includeProject = aCtx.Request.options().include_project();
|
|
|
|
|
|
|
|
frame()->SavePcbCopy( boardPath.GetFullPath(), includeProject, /* aHeadless = */ true );
|
|
|
|
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleRevertDocument(
|
|
|
|
const HANDLER_CONTEXT<RevertDocument>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
wxFileName fn = frame()->Prj().AbsolutePath( frame()->GetBoard()->GetFileName() );
|
|
|
|
|
|
|
|
frame()->GetScreen()->SetContentModified( false );
|
|
|
|
frame()->ReleaseFile();
|
|
|
|
frame()->OpenProjectFiles( std::vector<wxString>( 1, fn.GetFullPath() ), KICTL_REVERT );
|
|
|
|
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
void API_HANDLER_PCB::pushCurrentCommit( const std::string& aClientName, const wxString& aMessage )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
API_HANDLER_EDITOR::pushCurrentCommit( aClientName, aMessage );
|
2024-01-20 18:35:29 -05:00
|
|
|
frame()->Refresh();
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
std::unique_ptr<COMMIT> API_HANDLER_PCB::createCommit()
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
return std::make_unique<BOARD_COMMIT>( frame() );
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
std::optional<BOARD_ITEM*> API_HANDLER_PCB::getItemById( const KIID& aId ) const
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
BOARD_ITEM* item = frame()->GetBoard()->GetItem( aId );
|
|
|
|
|
|
|
|
if( item == DELETED_BOARD_ITEM::GetInstance() )
|
|
|
|
return std::nullopt;
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
return item;
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
bool API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
if( aDocument.type() != DocumentType::DOCTYPE_PCB )
|
|
|
|
return false;
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
wxFileName fn( frame()->GetCurrentFileName() );
|
|
|
|
return 0 == aDocument.board_filename().compare( fn.GetFullName() );
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>> API_HANDLER_PCB::createItemForType( KICAD_T aType,
|
|
|
|
BOARD_ITEM_CONTAINER* aContainer )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
if( !created )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
}
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
return created;
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
HANDLER_RESULT<ItemRequestStatus> API_HANDLER_PCB::handleCreateUpdateItemsInternal( bool aCreate,
|
2024-12-08 20:13:18 -05:00
|
|
|
const std::string& aClientName,
|
2024-01-20 18:35:29 -05:00
|
|
|
const types::ItemHeader &aHeader,
|
|
|
|
const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
|
|
|
|
std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
auto containerResult = validateItemHeaderDocument( aHeader );
|
|
|
|
|
|
|
|
if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
|
2023-01-29 13:06:05 -05: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 18:35:29 -05:00
|
|
|
else if( !containerResult )
|
|
|
|
{
|
|
|
|
e.CopyFrom( containerResult.error() );
|
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
BOARD_ITEM_CONTAINER* container = board;
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
if( containerResult->has_value() )
|
|
|
|
{
|
|
|
|
const KIID& containerId = **containerResult;
|
|
|
|
std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aClientName ) );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
for( const google::protobuf::Any& anyItem : aItems )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
ItemStatus status;
|
2023-01-29 13:06:05 -05:00
|
|
|
std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
|
|
|
|
|
|
|
|
if( !type )
|
|
|
|
{
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-11-29 21:18:38 -05:00
|
|
|
if( type == PCB_DIMENSION_T )
|
|
|
|
{
|
|
|
|
board::types::Dimension dimension;
|
|
|
|
anyItem.UnpackTo( &dimension );
|
|
|
|
|
|
|
|
switch( dimension.dimension_style_case() )
|
|
|
|
{
|
|
|
|
case board::types::Dimension::kAligned: type = PCB_DIM_ALIGNED_T; break;
|
|
|
|
case board::types::Dimension::kOrthogonal: type = PCB_DIM_ORTHOGONAL_T; break;
|
|
|
|
case board::types::Dimension::kRadial: type = PCB_DIM_RADIAL_T; break;
|
|
|
|
case board::types::Dimension::kLeader: type = PCB_DIM_LEADER_T; break;
|
|
|
|
case board::types::Dimension::kCenter: type = PCB_DIM_CENTER_T; break;
|
|
|
|
case board::types::Dimension::DIMENSION_STYLE_NOT_SET: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>> creationResult =
|
|
|
|
createItemForType( *type, container );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
if( !creationResult )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
|
|
|
|
status.set_error_message( creationResult.error().error_message() );
|
|
|
|
aItemHandler( status, anyItem );
|
2023-01-29 13:06:05 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
|
|
|
|
|
2023-01-29 13:06:05 -05: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 18:35:29 -05: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 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2024-12-26 13:27:12 -05:00
|
|
|
if( aCreate && !( board->GetEnabledLayers() & item->GetLayerSet() ).any() )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
status.set_code( ItemStatusCode::ISC_INVALID_DATA );
|
2024-12-26 13:27:12 -05:00
|
|
|
status.set_error_message(
|
|
|
|
"attempted to add item with no overlapping layers with the board" );
|
2024-01-20 18:35:29 -05:00
|
|
|
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 13:06:05 -05:00
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
if( !m_activeClients.count( aClientName ) )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
|
|
|
|
: _( "Added items via API" ) );
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
return ItemRequestStatus::IRS_OK;
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<GetItemsResponse> API_HANDLER_PCB::handleGetItems(
|
|
|
|
const HANDLER_CONTEXT<GetItems>& aCtx )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
if( !validateItemHeaderDocument( aCtx.Request.header() ) )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
|
|
|
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 18:35:29 -05:00
|
|
|
BOARD* board = frame()->GetBoard();
|
2023-01-29 13:06:05 -05:00
|
|
|
std::vector<BOARD_ITEM*> items;
|
|
|
|
std::set<KICAD_T> typesRequested, typesInserted;
|
|
|
|
bool handledAnything = false;
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( int typeRaw : aCtx.Request.types() )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
|
|
|
|
KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
if( type == TYPE_NOT_INIT )
|
2023-01-29 13:06:05 -05: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 18:35:29 -05: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;
|
|
|
|
}
|
|
|
|
|
2024-11-17 16:22:40 -05:00
|
|
|
case PCB_SHAPE_T:
|
2024-11-24 17:25:56 -05:00
|
|
|
case PCB_TEXT_T:
|
|
|
|
case PCB_TEXTBOX_T:
|
2024-11-17 16:22:40 -05:00
|
|
|
{
|
|
|
|
handledAnything = true;
|
|
|
|
bool inserted = false;
|
|
|
|
|
|
|
|
for( BOARD_ITEM* item : board->Drawings() )
|
|
|
|
{
|
2024-11-24 17:25:56 -05:00
|
|
|
if( item->Type() == type )
|
2024-11-17 16:22:40 -05:00
|
|
|
{
|
|
|
|
items.emplace_back( item );
|
|
|
|
inserted = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( inserted )
|
|
|
|
typesInserted.insert( PCB_SHAPE_T );
|
2024-11-17 22:32:35 -05:00
|
|
|
|
|
|
|
break;
|
2024-11-24 00:20:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
case PCB_ZONE_T:
|
|
|
|
{
|
|
|
|
handledAnything = true;
|
|
|
|
|
|
|
|
std::copy( board->Zones().begin(), board->Zones().end(),
|
|
|
|
std::back_inserter( items ) );
|
|
|
|
|
|
|
|
typesInserted.insert( PCB_ZONE_T );
|
|
|
|
break;
|
2024-11-17 16:22:40 -05:00
|
|
|
}
|
|
|
|
|
2023-01-29 13:06:05 -05: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 18:35:29 -05:00
|
|
|
void API_HANDLER_PCB::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
|
2024-12-08 20:13:18 -05:00
|
|
|
const std::string& aClientName )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
std::vector<BOARD_ITEM*> validatedItems;
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
COMMIT* commit = getCurrentCommit( aClientName );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
for( BOARD_ITEM* item : validatedItems )
|
|
|
|
commit->Remove( item );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
if( !m_activeClients.count( aClientName ) )
|
|
|
|
pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
|
2024-01-20 18:35:29 -05:00
|
|
|
}
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
|
|
|
|
|
2025-01-21 19:52:38 -05:00
|
|
|
HANDLER_RESULT<SelectionResponse> API_HANDLER_PCB::handleGetSelection(
|
|
|
|
const HANDLER_CONTEXT<GetSelection>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
if( !validateItemHeaderDocument( aCtx.Request.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::set<KICAD_T> filter;
|
|
|
|
|
|
|
|
for( int typeRaw : aCtx.Request.types() )
|
|
|
|
{
|
|
|
|
auto typeMessage = static_cast<types::KiCadObjectType>( typeRaw );
|
|
|
|
KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
|
|
|
|
|
|
|
|
if( type == TYPE_NOT_INIT )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
filter.insert( type );
|
|
|
|
}
|
|
|
|
|
|
|
|
TOOL_MANAGER* mgr = frame()->GetToolManager();
|
|
|
|
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
|
|
|
|
SelectionResponse response;
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : selectionTool->GetSelection() )
|
|
|
|
{
|
|
|
|
if( filter.empty() || filter.contains( item->Type() ) )
|
|
|
|
item->Serialize( *response.add_items() );
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleClearSelection(
|
|
|
|
const HANDLER_CONTEXT<ClearSelection>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
if( !validateItemHeaderDocument( aCtx.Request.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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
TOOL_MANAGER* mgr = frame()->GetToolManager();
|
|
|
|
mgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<SelectionResponse> API_HANDLER_PCB::handleAddToSelection(
|
|
|
|
const HANDLER_CONTEXT<AddToSelection>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
if( !validateItemHeaderDocument( aCtx.Request.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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
TOOL_MANAGER* mgr = frame()->GetToolManager();
|
|
|
|
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
|
|
|
|
std::vector<EDA_ITEM*> toAdd;
|
|
|
|
|
|
|
|
for( const types::KIID& id : aCtx.Request.items() )
|
|
|
|
{
|
|
|
|
if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
|
|
|
|
toAdd.emplace_back( *item );
|
|
|
|
}
|
|
|
|
|
|
|
|
selectionTool->AddItemsToSel( &toAdd );
|
|
|
|
|
|
|
|
SelectionResponse response;
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : selectionTool->GetSelection() )
|
|
|
|
item->Serialize( *response.add_items() );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<SelectionResponse> API_HANDLER_PCB::handleRemoveFromSelection(
|
|
|
|
const HANDLER_CONTEXT<RemoveFromSelection>& aCtx )
|
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
|
|
|
if( !validateItemHeaderDocument( aCtx.Request.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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
TOOL_MANAGER* mgr = frame()->GetToolManager();
|
|
|
|
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
|
|
|
|
std::vector<EDA_ITEM*> toRemove;
|
|
|
|
|
|
|
|
for( const types::KIID& id : aCtx.Request.items() )
|
|
|
|
{
|
|
|
|
if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
|
|
|
|
toRemove.emplace_back( *item );
|
|
|
|
}
|
|
|
|
|
|
|
|
selectionTool->RemoveItemsFromSel( &toRemove );
|
|
|
|
|
|
|
|
SelectionResponse response;
|
|
|
|
|
|
|
|
for( EDA_ITEM* item : selectionTool->GetSelection() )
|
|
|
|
item->Serialize( *response.add_items() );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<BoardStackupResponse> API_HANDLER_PCB::handleGetStackup(
|
|
|
|
const HANDLER_CONTEXT<GetBoardStackup>& aCtx )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
2024-04-26 15:25:45 +03:00
|
|
|
BoardStackupResponse response;
|
2024-01-20 18:35:29 -05:00
|
|
|
google::protobuf::Any any;
|
|
|
|
|
2024-04-26 15:25:45 +03:00
|
|
|
frame()->GetBoard()->GetStackupOrDefault().Serialize( any );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
any.UnpackTo( response.mutable_stackup() );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2025-01-07 18:28:07 -05:00
|
|
|
// User-settable layer names are not stored in BOARD_STACKUP at the moment
|
|
|
|
for( board::BoardStackupLayer& layer : *response.mutable_stackup()->mutable_layers() )
|
|
|
|
{
|
|
|
|
if( layer.type() == board::BoardStackupLayerType::BSLT_DIELECTRIC )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
PCB_LAYER_ID id = FromProtoEnum<PCB_LAYER_ID>( layer.layer() );
|
|
|
|
wxCHECK2( id != UNDEFINED_LAYER, continue );
|
|
|
|
|
|
|
|
layer.set_user_name( frame()->GetBoard()->GetLayerName( id ) );
|
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
return response;
|
|
|
|
}
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
HANDLER_RESULT<GraphicsDefaultsResponse> API_HANDLER_PCB::handleGetGraphicsDefaults(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<GetGraphicsDefaults>& aCtx )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
const BOARD_DESIGN_SETTINGS& bds = frame()->GetBoard()->GetDesignSettings();
|
|
|
|
GraphicsDefaultsResponse response;
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<GetBoundingBoxResponse> API_HANDLER_PCB::handleGetBoundingBox(
|
|
|
|
const HANDLER_CONTEXT<GetBoundingBox>& aCtx )
|
2024-11-19 22:09:00 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
if( !validateItemHeaderDocument( aCtx.Request.header() ) )
|
2024-11-19 22:09:00 -05:00
|
|
|
{
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
GetBoundingBoxResponse response;
|
2024-12-08 20:13:18 -05:00
|
|
|
bool includeText = aCtx.Request.mode() == BoundingBoxMode::BBM_ITEM_AND_CHILD_TEXT;
|
2024-11-19 22:09:00 -05:00
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( const types::KIID& idMsg : aCtx.Request.items() )
|
2024-11-19 22:09:00 -05:00
|
|
|
{
|
|
|
|
KIID id( idMsg.value() );
|
|
|
|
std::optional<BOARD_ITEM*> optItem = getItemById( id );
|
|
|
|
|
|
|
|
if( !optItem )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
BOARD_ITEM* item = *optItem;
|
|
|
|
BOX2I bbox;
|
|
|
|
|
|
|
|
if( item->Type() == PCB_FOOTPRINT_T )
|
|
|
|
bbox = static_cast<FOOTPRINT*>( item )->GetBoundingBox( includeText );
|
|
|
|
else
|
|
|
|
bbox = item->GetBoundingBox();
|
|
|
|
|
|
|
|
response.add_items()->set_value( idMsg.value() );
|
|
|
|
PackBox2( *response.add_boxes(), bbox );
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-11-19 21:17:22 -05:00
|
|
|
HANDLER_RESULT<PadShapeAsPolygonResponse> API_HANDLER_PCB::handleGetPadShapeAsPolygon(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<GetPadShapeAsPolygon>& aCtx )
|
2024-11-19 21:17:22 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-11-19 21:17:22 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
PadShapeAsPolygonResponse response;
|
2024-12-08 20:13:18 -05:00
|
|
|
PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( aCtx.Request.layer() );
|
2024-11-19 21:17:22 -05:00
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( const types::KIID& padRequest : aCtx.Request.pads() )
|
2024-11-19 21:17:22 -05:00
|
|
|
{
|
|
|
|
KIID id( padRequest.value() );
|
|
|
|
std::optional<BOARD_ITEM*> optPad = getItemById( id );
|
|
|
|
|
|
|
|
if( !optPad || ( *optPad )->Type() != PCB_PAD_T )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
response.add_pads()->set_value( padRequest.value() );
|
|
|
|
|
|
|
|
PAD* pad = static_cast<PAD*>( *optPad );
|
2025-01-01 16:34:30 -05:00
|
|
|
SHAPE_POLY_SET poly;
|
2024-11-19 21:17:22 -05:00
|
|
|
pad->TransformShapeToPolygon( poly, pad->Padstack().EffectiveLayerFor( layer ), 0,
|
|
|
|
ARC_HIGH_DEF, ERROR_INSIDE );
|
|
|
|
|
|
|
|
types::PolygonWithHoles* polyMsg = response.mutable_polygons()->Add();
|
|
|
|
PackPolyLine( *polyMsg->mutable_outline(), poly.COutline( 0 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-11-19 23:44:29 -05:00
|
|
|
HANDLER_RESULT<types::TitleBlockInfo> API_HANDLER_PCB::handleGetTitleBlockInfo(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<GetTitleBlockInfo>& aCtx )
|
2024-11-19 23:44:29 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
2024-11-19 23:44:29 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
const TITLE_BLOCK& block = board->GetTitleBlock();
|
|
|
|
|
|
|
|
types::TitleBlockInfo response;
|
|
|
|
|
|
|
|
response.set_title( block.GetTitle().ToUTF8() );
|
|
|
|
response.set_date( block.GetDate().ToUTF8() );
|
|
|
|
response.set_revision( block.GetRevision().ToUTF8() );
|
|
|
|
response.set_company( block.GetCompany().ToUTF8() );
|
|
|
|
response.set_comment1( block.GetComment( 0 ).ToUTF8() );
|
|
|
|
response.set_comment2( block.GetComment( 1 ).ToUTF8() );
|
|
|
|
response.set_comment3( block.GetComment( 2 ).ToUTF8() );
|
|
|
|
response.set_comment4( block.GetComment( 3 ).ToUTF8() );
|
|
|
|
response.set_comment5( block.GetComment( 4 ).ToUTF8() );
|
|
|
|
response.set_comment6( block.GetComment( 5 ).ToUTF8() );
|
|
|
|
response.set_comment7( block.GetComment( 6 ).ToUTF8() );
|
|
|
|
response.set_comment8( block.GetComment( 7 ).ToUTF8() );
|
|
|
|
response.set_comment9( block.GetComment( 8 ).ToUTF8() );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-11-28 19:21:18 -05:00
|
|
|
HANDLER_RESULT<ExpandTextVariablesResponse> API_HANDLER_PCB::handleExpandTextVariables(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<ExpandTextVariables>& aCtx )
|
2024-11-28 19:21:18 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
2024-11-28 19:21:18 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
ExpandTextVariablesResponse reply;
|
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
|
|
|
|
std::function<bool( wxString* )> textResolver =
|
|
|
|
[&]( wxString* token ) -> bool
|
|
|
|
{
|
|
|
|
// Handles m_board->GetTitleBlock() *and* m_board->GetProject()
|
|
|
|
return board->ResolveTextVar( token, 0 );
|
|
|
|
};
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( const std::string& textMsg : aCtx.Request.text() )
|
2024-11-28 19:21:18 -05:00
|
|
|
{
|
|
|
|
wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver );
|
|
|
|
reply.add_text( text.ToUTF8() );
|
|
|
|
}
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleInteractiveMoveItems(
|
|
|
|
const HANDLER_CONTEXT<InteractiveMoveItems>& aCtx )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
TOOL_MANAGER* mgr = frame()->GetToolManager();
|
|
|
|
std::vector<EDA_ITEM*> toSelect;
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
|
|
|
|
toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
if( toSelect.empty() )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
|
|
|
ApiResponseStatus e;
|
|
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
2024-01-20 18:35:29 -05:00
|
|
|
e.set_error_message( fmt::format( "None of the given items exist on the board",
|
2024-12-08 20:13:18 -05:00
|
|
|
aCtx.Request.board().board_filename() ) );
|
2023-01-29 13:06:05 -05:00
|
|
|
return tl::unexpected( e );
|
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
mgr->RunAction( PCB_ACTIONS::selectionClear );
|
|
|
|
mgr->RunAction<EDA_ITEMS*>( PCB_ACTIONS::selectItems, &toSelect );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
COMMIT* commit = getCurrentCommit( aCtx.ClientName );
|
2024-01-20 18:35:29 -05:00
|
|
|
mgr->PostAction( PCB_ACTIONS::move, commit );
|
|
|
|
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<NetsResponse> API_HANDLER_PCB::handleGetNets( const HANDLER_CONTEXT<GetNets>& aCtx )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-01-20 18:35:29 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
NetsResponse response;
|
|
|
|
BOARD* board = frame()->GetBoard();
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
std::set<wxString> netclassFilter;
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( const std::string& nc : aCtx.Request.netclass_filter() )
|
2024-01-20 18:35:29 -05:00
|
|
|
netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
|
|
|
|
|
|
|
|
for( NETINFO_ITEM* net : board->GetNetInfo() )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05:00
|
|
|
NETCLASS* nc = net->GetNetClass();
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
return response;
|
|
|
|
}
|
2023-01-29 13:06:05 -05:00
|
|
|
|
|
|
|
|
2024-12-31 17:51:07 -05:00
|
|
|
HANDLER_RESULT<NetClassForNetsResponse> API_HANDLER_PCB::handleGetNetClassForNets(
|
|
|
|
const HANDLER_CONTEXT<GetNetClassForNets>& aCtx )
|
|
|
|
{
|
|
|
|
NetClassForNetsResponse response;
|
|
|
|
|
|
|
|
BOARD* board = frame()->GetBoard();
|
|
|
|
NETINFO_LIST nets = board->GetNetInfo();
|
|
|
|
google::protobuf::Any any;
|
|
|
|
|
|
|
|
for( const board::types::Net& net : aCtx.Request.net() )
|
|
|
|
{
|
|
|
|
NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
|
|
|
|
|
|
|
|
if( !netInfo )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
netInfo->GetNetClass()->Serialize( any );
|
|
|
|
auto [pair, rc] = response.mutable_classes()->insert( { net.name(), {} } );
|
|
|
|
any.UnpackTo( &pair->second );
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleRefillZones( const HANDLER_CONTEXT<RefillZones>& aCtx )
|
2024-01-20 18:35:29 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
2023-01-29 13:06:05 -05:00
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
if( aCtx.Request.zones().empty() )
|
2023-01-29 13:06:05 -05:00
|
|
|
{
|
2024-01-20 18:35:29 -05: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 13:06:05 -05:00
|
|
|
}
|
|
|
|
|
2024-01-20 18:35:29 -05:00
|
|
|
return Empty();
|
2023-01-29 13:06:05 -05:00
|
|
|
}
|
2024-12-01 21:37:13 -05:00
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<SavedDocumentResponse> API_HANDLER_PCB::handleSaveDocumentToString(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<SaveDocumentToString>& aCtx )
|
2024-12-01 21:37:13 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
2024-12-01 21:37:13 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
SavedDocumentResponse response;
|
2024-12-08 20:13:18 -05:00
|
|
|
response.mutable_document()->CopyFrom( aCtx.Request.document() );
|
2024-12-01 21:37:13 -05:00
|
|
|
|
|
|
|
CLIPBOARD_IO io;
|
|
|
|
io.SetWriter(
|
|
|
|
[&]( const wxString& aData )
|
|
|
|
{
|
|
|
|
response.set_contents( aData.ToUTF8() );
|
|
|
|
} );
|
|
|
|
|
|
|
|
io.SaveBoard( wxEmptyString, frame()->GetBoard(), nullptr );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<SavedSelectionResponse> API_HANDLER_PCB::handleSaveSelectionToString(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<SaveSelectionToString>& aCtx )
|
2024-12-01 21:37:13 -05:00
|
|
|
{
|
|
|
|
SavedSelectionResponse response;
|
|
|
|
|
|
|
|
TOOL_MANAGER* mgr = frame()->GetToolManager();
|
|
|
|
PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
|
|
|
|
PCB_SELECTION& selection = selectionTool->GetSelection();
|
|
|
|
|
|
|
|
CLIPBOARD_IO io;
|
|
|
|
io.SetWriter(
|
|
|
|
[&]( const wxString& aData )
|
|
|
|
{
|
|
|
|
response.set_contents( aData.ToUTF8() );
|
|
|
|
} );
|
|
|
|
|
|
|
|
io.SetBoard( frame()->GetBoard() );
|
|
|
|
io.SaveSelection( selection, false );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<CreateItemsResponse> API_HANDLER_PCB::handleParseAndCreateItemsFromString(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<ParseAndCreateItemsFromString>& aCtx )
|
2024-12-01 21:37:13 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
|
2024-12-01 21:37:13 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
CreateItemsResponse response;
|
|
|
|
return response;
|
|
|
|
}
|
2024-12-03 21:17:48 -05:00
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<BoardLayers> API_HANDLER_PCB::handleGetVisibleLayers(
|
|
|
|
const HANDLER_CONTEXT<GetVisibleLayers>& aCtx )
|
2024-12-03 21:17:48 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-12-03 21:17:48 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
BoardLayers response;
|
|
|
|
|
|
|
|
for( PCB_LAYER_ID layer : frame()->GetBoard()->GetVisibleLayers() )
|
|
|
|
response.add_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleSetVisibleLayers(
|
|
|
|
const HANDLER_CONTEXT<SetVisibleLayers>& aCtx )
|
2024-12-03 21:17:48 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-12-03 21:17:48 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
LSET visible;
|
|
|
|
LSET enabled = frame()->GetBoard()->GetEnabledLayers();
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
for( int layerIdx : aCtx.Request.layers() )
|
2024-12-03 21:17:48 -05:00
|
|
|
{
|
|
|
|
PCB_LAYER_ID layer =
|
|
|
|
FromProtoEnum<PCB_LAYER_ID>( static_cast<board::types::BoardLayer>( layerIdx ) );
|
|
|
|
|
|
|
|
if( enabled.Contains( layer ) )
|
|
|
|
visible.set( layer );
|
|
|
|
}
|
|
|
|
|
|
|
|
frame()->GetBoard()->SetVisibleLayers( visible );
|
|
|
|
frame()->GetAppearancePanel()->OnBoardChanged();
|
|
|
|
frame()->GetCanvas()->SyncLayersVisibility( frame()->GetBoard() );
|
|
|
|
frame()->Refresh();
|
|
|
|
return Empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<BoardLayerResponse> API_HANDLER_PCB::handleGetActiveLayer(
|
2024-12-08 20:13:18 -05:00
|
|
|
const HANDLER_CONTEXT<GetActiveLayer>& aCtx )
|
2024-12-03 21:17:48 -05:00
|
|
|
{
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-12-03 21:17:48 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
|
|
|
BoardLayerResponse response;
|
|
|
|
response.set_layer(
|
|
|
|
ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( frame()->GetActiveLayer() ) );
|
|
|
|
|
|
|
|
return response;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleSetActiveLayer(
|
|
|
|
const HANDLER_CONTEXT<SetActiveLayer>& aCtx )
|
2024-12-03 21:17:48 -05:00
|
|
|
{
|
|
|
|
if( std::optional<ApiResponseStatus> busy = checkForBusy() )
|
|
|
|
return tl::unexpected( *busy );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
|
2024-12-03 21:17:48 -05:00
|
|
|
|
|
|
|
if( !documentValidation )
|
|
|
|
return tl::unexpected( documentValidation.error() );
|
|
|
|
|
2024-12-08 20:13:18 -05:00
|
|
|
PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID>( aCtx.Request.layer() );
|
2024-12-03 21:17:48 -05:00
|
|
|
|
|
|
|
if( !frame()->GetBoard()->GetEnabledLayers().Contains( layer ) )
|
|
|
|
{
|
|
|
|
ApiResponseStatus err;
|
|
|
|
err.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
|
|
err.set_error_message( fmt::format( "Layer {} is not a valid layer for the given board",
|
|
|
|
magic_enum::enum_name( layer ) ) );
|
|
|
|
return tl::unexpected( err );
|
|
|
|
}
|
|
|
|
|
|
|
|
frame()->SetActiveLayer( layer );
|
|
|
|
return Empty();
|
|
|
|
}
|
2024-12-29 22:02:23 -05:00
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<BoardEditorAppearanceSettings> API_HANDLER_PCB::handleGetBoardEditorAppearanceSettings(
|
|
|
|
const HANDLER_CONTEXT<GetBoardEditorAppearanceSettings>& aCtx )
|
|
|
|
{
|
|
|
|
BoardEditorAppearanceSettings reply;
|
|
|
|
|
|
|
|
// TODO: might be nice to put all these things in one place and have it derive SERIALIZABLE
|
|
|
|
|
|
|
|
const PCB_DISPLAY_OPTIONS& displayOptions = frame()->GetDisplayOptions();
|
|
|
|
|
|
|
|
reply.set_inactive_layer_display( ToProtoEnum<HIGH_CONTRAST_MODE, InactiveLayerDisplayMode>(
|
|
|
|
displayOptions.m_ContrastModeDisplay ) );
|
|
|
|
reply.set_net_color_display(
|
|
|
|
ToProtoEnum<NET_COLOR_MODE, NetColorDisplayMode>( displayOptions.m_NetColorMode ) );
|
|
|
|
|
|
|
|
reply.set_board_flip( frame()->GetCanvas()->GetView()->IsMirroredX()
|
|
|
|
? BoardFlipMode::BFM_FLIPPED_X
|
|
|
|
: BoardFlipMode::BFM_NORMAL );
|
|
|
|
|
|
|
|
PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
|
|
|
|
|
|
|
|
reply.set_ratsnest_display( ToProtoEnum<RATSNEST_MODE, RatsnestDisplayMode>(
|
|
|
|
editorSettings->m_Display.m_RatsnestMode ) );
|
|
|
|
|
|
|
|
return reply;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_PCB::handleSetBoardEditorAppearanceSettings(
|
|
|
|
const HANDLER_CONTEXT<SetBoardEditorAppearanceSettings>& aCtx )
|
|
|
|
{
|
|
|
|
PCB_DISPLAY_OPTIONS options = frame()->GetDisplayOptions();
|
|
|
|
KIGFX::PCB_VIEW* view = frame()->GetCanvas()->GetView();
|
|
|
|
PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
|
|
|
|
const BoardEditorAppearanceSettings& newSettings = aCtx.Request.settings();
|
|
|
|
|
|
|
|
options.m_ContrastModeDisplay =
|
|
|
|
FromProtoEnum<HIGH_CONTRAST_MODE>( newSettings.inactive_layer_display() );
|
|
|
|
options.m_NetColorMode =
|
|
|
|
FromProtoEnum<NET_COLOR_MODE>( newSettings.net_color_display() );
|
|
|
|
|
|
|
|
bool flip = newSettings.board_flip() == BoardFlipMode::BFM_FLIPPED_X;
|
|
|
|
|
|
|
|
if( flip != view->IsMirroredX() )
|
|
|
|
{
|
|
|
|
view->SetMirror( !view->IsMirroredX(), view->IsMirroredY() );
|
|
|
|
view->RecacheAllItems();
|
|
|
|
}
|
|
|
|
|
|
|
|
editorSettings->m_Display.m_RatsnestMode =
|
|
|
|
FromProtoEnum<RATSNEST_MODE>( newSettings.ratsnest_display() );
|
|
|
|
|
|
|
|
frame()->SetDisplayOptions( options );
|
|
|
|
frame()->GetCanvas()->GetView()->UpdateAllLayersColor();
|
|
|
|
frame()->GetCanvas()->Refresh();
|
|
|
|
|
|
|
|
return Empty();
|
|
|
|
}
|