KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_footprint.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2026 Benjamin Chung ([email protected])
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
22#include <api/api_pcb_utils.h>
23#include <api/api_enums.h>
24#include <api/api_utils.h>
25#include <board.h>
26#include <board_commit.h>
27#include <footprint.h>
29#include <pcb_3d_body.h>
30#include <ki_exception.h>
31#include <pad.h>
32#include <pcb_group.h>
33#include <project.h>
34#include <zone.h>
36#include <project_pcb.h>
37
38#include <api/common/types/base_types.pb.h>
39
40using namespace kiapi::common::commands;
41using types::CommandStatus;
42using types::DocumentType;
43using types::ItemRequestStatus;
44
45
50
51
67
68
73
74
75tl::expected<bool, ApiResponseStatus> API_HANDLER_FOOTPRINT::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
76{
77 if( aDocument.type() != DocumentType::DOCTYPE_FOOTPRINT )
78 {
79 ApiResponseStatus e;
80 e.set_status( ApiStatusCode::AS_UNHANDLED );
81 return tl::unexpected( e );
82 }
83
84 // An empty library nickname addresses an unsaved new footprint
85 if( aDocument.lib_id().library_nickname().empty() )
86 {
87 BOARD* board = this->board();
88
89 if( !board || !board->GetFirstFootprint() )
90 {
91 ApiResponseStatus e;
92 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
93 e.set_error_message( "no footprint is currently open" );
94 return tl::unexpected( e );
95 }
96
97 return true;
98 }
99
100 LIB_ID target_fp = footprintContext()->GetLoadedFPID();
101
102 if( !target_fp.IsValid() )
103 {
104 ApiResponseStatus e;
105 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
106 e.set_error_message( "no footprint is currently open" );
107 return tl::unexpected( e );
108 }
109
110 std::string actual_lib = target_fp.GetUniStringLibNickname().ToStdString();
111 std::string actual_name = target_fp.GetUniStringLibItemName().ToStdString();
112
113 if( 0 != aDocument.lib_id().library_nickname().compare( actual_lib ) )
114 {
115 ApiResponseStatus e;
116 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
117 e.set_error_message( fmt::format( "the requested library is {} but the actual library is {}",
118 aDocument.lib_id().library_nickname(), actual_lib ) );
119 return tl::unexpected( e );
120 }
121
122 if( 0 != aDocument.lib_id().entry_name().compare( actual_name ) )
123 {
124 ApiResponseStatus e;
125 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
126 e.set_error_message( fmt::format( "the requested footprint name is {} but the actual name is {}",
127 aDocument.lib_id().entry_name(), actual_name ) );
128 return tl::unexpected( e );
129 }
130
131 return true;
132}
133
134
136 const DocumentSpecifier& aDocument )
137{
138 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aDocument ); !documentValidation )
139 return tl::unexpected( documentValidation.error() );
140
141 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
142 return tl::unexpected( *busy );
143
144 FOOTPRINT* editorFootprint = board()->GetFirstFootprint();
145
146 if( !editorFootprint )
147 {
148 ApiResponseStatus e;
149 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
150 e.set_error_message( "no footprint is currently loaded" );
151 return tl::unexpected( e );
152 }
153
154 return editorFootprint;
155}
156
157
160{
161 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "OpenLibraryItem" ) )
162 return tl::unexpected( *headless );
163
164 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
165 {
166 ApiResponseStatus e;
167 e.set_status( ApiStatusCode::AS_UNHANDLED );
168 return tl::unexpected( e );
169 }
170
172
173 wxString libraryName = aCtx.Request.identifier().library_nickname();
174 wxString fpName = aCtx.Request.identifier().entry_name();
175
176 LIB_ID fpid( libraryName, fpName );
177 // preload the footprint to make sure it exists and so that we can make a nice error
178 try
179 {
180 std::unique_ptr<FOOTPRINT> footprint(
181 adapter->LoadFootprintWithOptionalNickname( fpid, true ) );
182
183 if( !footprint )
184 {
185 ApiResponseStatus e;
186 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
187 e.set_error_message( "could not open footprint" );
188 return tl::unexpected( e );
189 }
190 }
191 catch( const IO_ERROR& err )
192 {
193 ApiResponseStatus e;
194 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
195 e.set_error_message( fmt::format( "could not open footprint due to IO error {}",
196 err.Problem().ToStdString() ) );
197 return tl::unexpected( e );
198 }
199
201 return Empty();
202}
203
204
207{
208 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
209 {
210 ApiResponseStatus e;
211 e.set_status( ApiStatusCode::AS_UNHANDLED );
212 return tl::unexpected( e );
213 }
214
215 GetOpenDocumentsResponse response;
216 common::types::DocumentSpecifier doc;
217
219
220 if( !board()->GetFirstFootprint() )
221 return response;
222
223 doc.set_type( DocumentType::DOCTYPE_FOOTPRINT );
224 doc.mutable_lib_id()->set_library_nickname( fpid.GetUniStringLibNickname() );
225 doc.mutable_lib_id()->set_entry_name( fpid.GetUniStringLibItemName() );
226
227 if( !project().IsNullProject() )
228 {
229 doc.mutable_project()->set_name( project().GetProjectName().ToStdString() );
230 doc.mutable_project()->set_path( project().GetProjectDirectory().ToStdString() );
231 }
232
233 response.mutable_documents()->Add( std::move( doc ) );
234 return response;
235}
236
237
240{
241 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
242
243 if( !footprint )
244 return tl::unexpected( footprint.error() );
245
246 if( !footprintContext()->SaveFootprint( *footprint ) )
247 {
248 ApiResponseStatus e;
249 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
250 e.set_error_message( "failed to save footprint" );
251 return tl::unexpected( e );
252 }
253
254 return Empty();
255}
256
257
260{
261 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
262
263 if( !footprint )
264 return tl::unexpected( footprint.error() );
265
266 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
267
268 if( pathStr.IsEmpty() )
269 {
270 ApiResponseStatus e;
271 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
272 e.set_error_message( "path must contain the new footprint name, "
273 "optionally prefixed with a library nickname (e.g. \"lib:name\")" );
274 return tl::unexpected( e );
275 }
276
277 // path can be "NewName" (same library) or "LibNick:NewName" (different library)
278 LIB_ID targetId;
279 targetId.Parse( pathStr );
280
281 wxString libraryName = targetId.GetLibNickname();
282
283 if( libraryName.IsEmpty() )
285
286 wxString newName = targetId.GetLibItemName();
287
288 // Clone the footprint so we don't modify the one being edited
289 std::unique_ptr<FOOTPRINT> copy( static_cast<FOOTPRINT*>( ( *footprint )->Clone() ) );
290 copy->SetFPID( LIB_ID( libraryName, newName ) );
291
292 if( !footprintContext()->SaveFootprintInLibrary( copy.get(), libraryName ) )
293 {
294 ApiResponseStatus e;
295 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
296 e.set_error_message( fmt::format( "failed to save footprint copy '{}' to library '{}'",
297 newName.ToStdString(),
298 libraryName.ToStdString() ) );
299 return tl::unexpected( e );
300 }
301
302 return Empty();
303}
304
305
308{
309 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RevertDocument" ) )
310 return tl::unexpected( *headless );
311
312 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
313 return tl::unexpected( documentValidation.error() );
314
315 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
316 return tl::unexpected( *busy );
317
318 frame()->RevertFootprint( /* aSkipConfirmation = */ true );
319
320 return Empty();
321}
322
323
324// Footprint types that are directly retrievable by GetItems
325static const std::vector<KICAD_T> s_allowedFootprintTypes = {
326 PCB_PAD_T,
338};
339
340
342{
343 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
344 {
345 ApiResponseStatus e;
346 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
347 e.set_status( ApiStatusCode::AS_UNHANDLED );
348 return tl::unexpected( e );
349 }
350
351 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
352 return tl::unexpected( *busy );
353
354 GetItemsResponse response;
355
356 FOOTPRINT* footprint = board()->GetFirstFootprint();
357 std::vector<BOARD_ITEM*> items;
358 std::set<KICAD_T> typesRequested, typesHandled;
359
360 std::vector<KICAD_T> requestedTypes = parseRequestedItemTypes( aCtx.Request.types() );
361
362 if( aCtx.Request.types().empty() )
363 requestedTypes.assign( s_allowedFootprintTypes.begin(), s_allowedFootprintTypes.end() );
364
365 for( KICAD_T type : requestedTypes )
366 {
367 typesRequested.emplace( type );
368
369 if( typesHandled.count( type ) )
370 continue;
371
372 switch( type )
373 {
374 case PCB_PAD_T:
375 std::ranges::copy( footprint->Pads(), std::back_inserter( items ) );
376 typesHandled.insert( PCB_PAD_T );
377 break;
378
379 case PCB_FIELD_T:
380 std::ranges::copy( footprint->GetFields(), std::back_inserter( items ) );
381 typesHandled.insert( PCB_FIELD_T );
382 break;
383
384 case PCB_SHAPE_T:
385 case PCB_TEXT_T:
386 case PCB_TEXTBOX_T:
387 case PCB_BARCODE_T:
388 case PCB_TABLE_T:
389 case PCB_DIMENSION_T:
390 for( BOARD_ITEM* item : footprint->GraphicalItems() )
391 {
392 if( item->Type() == type || BaseType( item->Type() ) == type )
393 items.emplace_back( item );
394 }
395
396 typesHandled.insert( type );
397 break;
398
399 case PCB_TABLECELL_T:
400 for( BOARD_ITEM* item : footprint->GraphicalItems() )
401 {
402 if( item->Type() != PCB_TABLE_T )
403 continue;
404
405 item->RunOnChildren(
406 [&]( BOARD_ITEM* child )
407 {
408 items.emplace_back( child );
409 },
411 }
412
413 typesHandled.insert( PCB_TABLECELL_T );
414 break;
415
416 case PCB_ZONE_T:
417 std::ranges::copy( footprint->Zones(), std::back_inserter( items ) );
418 typesHandled.insert( PCB_ZONE_T );
419 break;
420
421 case PCB_GROUP_T:
422 std::ranges::copy( footprint->Groups(), std::back_inserter( items ) );
423 typesHandled.insert( PCB_GROUP_T );
424 break;
425
426 case PCB_3D_BODY_T:
427 for( PCB_3D_BODY* body : footprint->Bodies() )
428 {
429 if( body->GetExtruded()->m_height > 0 )
430 items.push_back( body );
431 }
432
433 typesHandled.insert( PCB_3D_BODY_T );
434 break;
435
436 default:
437 break;
438 }
439 }
440
441 if( typesHandled.empty() )
442 {
443 ApiResponseStatus e;
444 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
445 e.set_error_message( "none of the requested types are valid for a Footprint object" );
446 return tl::unexpected( e );
447 }
448
449 for( const BOARD_ITEM* item : items )
450 {
451 if( !typesRequested.count( item->Type() ) && !typesRequested.count( BaseType( item->Type() ) ) )
452 continue;
453
454 google::protobuf::Any itemBuf;
455 item->Serialize( itemBuf );
456 response.mutable_items()->Add( std::move( itemBuf ) );
457 }
458
459 response.set_status( ItemRequestStatus::IRS_OK );
460 return response;
461}
462
463
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:47
static const std::vector< KICAD_T > s_allowedFootprintTypes
API_HANDLER_BOARD(std::shared_ptr< BOARD_CONTEXT > aContext, EDA_BASE_FRAME *aFrame=nullptr)
std::vector< KICAD_T > parseRequestedItemTypes(const google::protobuf::RepeatedField< int > &aTypes)
PROJECT & project() const
BOARD * board() const
std::optional< ApiResponseStatus > checkForHeadless(const std::string &aCommandName) const
HANDLER_RESULT< bool > validateDocument(const DocumentSpecifier &aDocument)
HANDLER_RESULT< std::optional< KIID > > validateItemHeaderDocument(const kiapi::common::types::ItemHeader &aHeader)
If the header is valid, returns the item container.
virtual std::optional< ApiResponseStatus > checkForBusy()
Checks if the editor can accept commands.
EDA_BASE_FRAME * m_frame
HANDLER_RESULT< commands::GetItemsResponse > handleGetItems(const HANDLER_CONTEXT< commands::GetItems > &aCtx)
FOOTPRINT_EDIT_FRAME * frame() const
HANDLER_RESULT< commands::GetOpenDocumentsResponse > handleGetOpenDocuments(const HANDLER_CONTEXT< commands::GetOpenDocuments > &aCtx)
FOOTPRINT_CONTEXT * footprintContext() const
BOARD_ITEM_CONTAINER * getDefaultContainer() override
tl::expected< bool, ApiResponseStatus > validateDocumentInternal(const DocumentSpecifier &aDocument) const override
HANDLER_RESULT< Empty > handleOpenLibraryItem(const HANDLER_CONTEXT< commands::OpenLibraryItem > &aCtx)
API_HANDLER_FOOTPRINT(FOOTPRINT_EDIT_FRAME *aFrame)
HANDLER_RESULT< Empty > handleSaveDocument(const HANDLER_CONTEXT< commands::SaveDocument > &aCtx)
HANDLER_RESULT< FOOTPRINT * > validateAndGetFootprint(const DocumentSpecifier &aDocument)
HANDLER_RESULT< Empty > handleRevertDocument(const HANDLER_CONTEXT< commands::RevertDocument > &aCtx)
HANDLER_RESULT< Empty > handleSaveCopyOfDocument(const HANDLER_CONTEXT< commands::SaveCopyOfDocument > &aCtx)
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:85
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:713
virtual LIB_ID GetLoadedFPID() const =0
void LoadFootprintFromLibrary(LIB_ID aFPID)
bool RevertFootprint(bool aSkipConfirmation=false)
An interface to the global shared library manager that is schematic-specific and linked to one projec...
FOOTPRINT * LoadFootprintWithOptionalNickname(const LIB_ID &aFootprintId, bool aKeepUUID)
Load a footprint having aFootprintId with possibly an empty nickname.
ZONES & Zones()
Definition footprint.h:361
std::deque< PAD * > & Pads()
Definition footprint.h:355
GROUPS & Groups()
Definition footprint.h:364
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
std::deque< PCB_3D_BODY * > & Bodies()
Definition footprint.h:422
DRAWINGS & GraphicalItems()
Definition footprint.h:358
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString Problem() const
what was the problem?
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:65
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:108
const wxString GetUniStringLibNickname() const
Definition lib_id.h:84
const UTF8 & GetLibItemName() const
Definition lib_id.h:98
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:83
A 3D body attached to a footprint (or, later, placed on a board).
Definition pcb_3d_body.h:93
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
static std::string ToStdString(const wxString &aStr)
@ NO_RECURSE
Definition eda_item.h:52
std::shared_ptr< FOOTPRINT_CONTEXT > CreateFootprintFrameContext(FOOTPRINT_EDIT_FRAME *aFrame)
PROJECT & Prj()
Definition kicad.cpp:737
STL namespace.
Class to handle a set of BOARD_ITEMs.
RequestMessageType Request
Definition api_handler.h:54
constexpr KICAD_T BaseType(const KICAD_T aType)
Return the underlying type of the given type.
Definition typeinfo.h:260
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_3D_BODY_T
class PCB_3D_BODY, a 3D body (extruded or model) on a footprint
Definition typeinfo.h:241
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:92
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86