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 <ki_exception.h>
30#include <pad.h>
31#include <pcb_group.h>
32#include <project.h>
33#include <zone.h>
35#include <project_pcb.h>
36
37#include <api/common/types/base_types.pb.h>
38
39using namespace kiapi::common::commands;
40using types::CommandStatus;
41using types::DocumentType;
42using types::ItemRequestStatus;
43
44
49
50
66
67
72
73
74tl::expected<bool, ApiResponseStatus> API_HANDLER_FOOTPRINT::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
75{
76 if( aDocument.type() != DocumentType::DOCTYPE_FOOTPRINT )
77 {
78 ApiResponseStatus e;
79 e.set_status( ApiStatusCode::AS_UNHANDLED );
80 return tl::unexpected( e );
81 }
82
83 // An empty library nickname addresses an unsaved new footprint
84 if( aDocument.lib_id().library_nickname().empty() )
85 {
86 BOARD* board = this->board();
87
88 if( !board || !board->GetFirstFootprint() )
89 {
90 ApiResponseStatus e;
91 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
92 e.set_error_message( "no footprint is currently open" );
93 return tl::unexpected( e );
94 }
95
96 return true;
97 }
98
99 LIB_ID target_fp = footprintContext()->GetLoadedFPID();
100
101 if( !target_fp.IsValid() )
102 {
103 ApiResponseStatus e;
104 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
105 e.set_error_message( "no footprint is currently open" );
106 return tl::unexpected( e );
107 }
108
109 std::string actual_lib = target_fp.GetUniStringLibNickname().ToStdString();
110 std::string actual_name = target_fp.GetUniStringLibItemName().ToStdString();
111
112 if( 0 != aDocument.lib_id().library_nickname().compare( actual_lib ) )
113 {
114 ApiResponseStatus e;
115 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
116 e.set_error_message( fmt::format( "the requested library is {} but the actual library is {}",
117 aDocument.lib_id().library_nickname(), actual_lib ) );
118 return tl::unexpected( e );
119 }
120
121 if( 0 != aDocument.lib_id().entry_name().compare( actual_name ) )
122 {
123 ApiResponseStatus e;
124 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
125 e.set_error_message( fmt::format( "the requested footprint name is {} but the actual name is {}",
126 aDocument.lib_id().entry_name(), actual_name ) );
127 return tl::unexpected( e );
128 }
129
130 return true;
131}
132
133
135 const DocumentSpecifier& aDocument )
136{
137 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aDocument ); !documentValidation )
138 return tl::unexpected( documentValidation.error() );
139
140 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
141 return tl::unexpected( *busy );
142
143 FOOTPRINT* editorFootprint = board()->GetFirstFootprint();
144
145 if( !editorFootprint )
146 {
147 ApiResponseStatus e;
148 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
149 e.set_error_message( "no footprint is currently loaded" );
150 return tl::unexpected( e );
151 }
152
153 return editorFootprint;
154}
155
156
159{
160 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "OpenLibraryItem" ) )
161 return tl::unexpected( *headless );
162
163 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
164 {
165 ApiResponseStatus e;
166 e.set_status( ApiStatusCode::AS_UNHANDLED );
167 return tl::unexpected( e );
168 }
169
171
172 wxString libraryName = aCtx.Request.identifier().library_nickname();
173 wxString fpName = aCtx.Request.identifier().entry_name();
174
175 LIB_ID fpid( libraryName, fpName );
176 // preload the footprint to make sure it exists and so that we can make a nice error
177 try
178 {
179 std::unique_ptr<FOOTPRINT> footprint(
180 adapter->LoadFootprintWithOptionalNickname( fpid, true ) );
181
182 if( !footprint )
183 {
184 ApiResponseStatus e;
185 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
186 e.set_error_message( "could not open footprint" );
187 return tl::unexpected( e );
188 }
189 }
190 catch( const IO_ERROR& err )
191 {
192 ApiResponseStatus e;
193 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
194 e.set_error_message( fmt::format( "could not open footprint due to IO error {}",
195 err.Problem().ToStdString() ) );
196 return tl::unexpected( e );
197 }
198
200 return Empty();
201}
202
203
206{
207 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
208 {
209 ApiResponseStatus e;
210 e.set_status( ApiStatusCode::AS_UNHANDLED );
211 return tl::unexpected( e );
212 }
213
214 GetOpenDocumentsResponse response;
215 common::types::DocumentSpecifier doc;
216
218
219 if( !board()->GetFirstFootprint() )
220 return response;
221
222 doc.set_type( DocumentType::DOCTYPE_FOOTPRINT );
223 doc.mutable_lib_id()->set_library_nickname( fpid.GetUniStringLibNickname() );
224 doc.mutable_lib_id()->set_entry_name( fpid.GetUniStringLibItemName() );
225
226 if( !project().IsNullProject() )
227 {
228 doc.mutable_project()->set_name( project().GetProjectName().ToStdString() );
229 doc.mutable_project()->set_path( project().GetProjectDirectory().ToStdString() );
230 }
231
232 response.mutable_documents()->Add( std::move( doc ) );
233 return response;
234}
235
236
239{
240 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
241
242 if( !footprint )
243 return tl::unexpected( footprint.error() );
244
245 if( !footprintContext()->SaveFootprint( *footprint ) )
246 {
247 ApiResponseStatus e;
248 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
249 e.set_error_message( "failed to save footprint" );
250 return tl::unexpected( e );
251 }
252
253 return Empty();
254}
255
256
259{
260 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
261
262 if( !footprint )
263 return tl::unexpected( footprint.error() );
264
265 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
266
267 if( pathStr.IsEmpty() )
268 {
269 ApiResponseStatus e;
270 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
271 e.set_error_message( "path must contain the new footprint name, "
272 "optionally prefixed with a library nickname (e.g. \"lib:name\")" );
273 return tl::unexpected( e );
274 }
275
276 // path can be "NewName" (same library) or "LibNick:NewName" (different library)
277 LIB_ID targetId;
278 targetId.Parse( pathStr );
279
280 wxString libraryName = targetId.GetLibNickname();
281
282 if( libraryName.IsEmpty() )
284
285 wxString newName = targetId.GetLibItemName();
286
287 // Clone the footprint so we don't modify the one being edited
288 std::unique_ptr<FOOTPRINT> copy( static_cast<FOOTPRINT*>( ( *footprint )->Clone() ) );
289 copy->SetFPID( LIB_ID( libraryName, newName ) );
290
291 if( !footprintContext()->SaveFootprintInLibrary( copy.get(), libraryName ) )
292 {
293 ApiResponseStatus e;
294 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
295 e.set_error_message( fmt::format( "failed to save footprint copy '{}' to library '{}'",
296 newName.ToStdString(),
297 libraryName.ToStdString() ) );
298 return tl::unexpected( e );
299 }
300
301 return Empty();
302}
303
304
307{
308 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RevertDocument" ) )
309 return tl::unexpected( *headless );
310
311 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
312 return tl::unexpected( documentValidation.error() );
313
314 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
315 return tl::unexpected( *busy );
316
317 frame()->RevertFootprint( /* aSkipConfirmation = */ true );
318
319 return Empty();
320}
321
322
323// Footprint types that are directly retrievable by GetItems
324static const std::vector<KICAD_T> s_allowedFootprintTypes = {
325 PCB_PAD_T,
336};
337
338
340 const HANDLER_CONTEXT<GetItems>& aCtx )
341{
342 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
343 {
344 ApiResponseStatus e;
345 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
346 e.set_status( ApiStatusCode::AS_UNHANDLED );
347 return tl::unexpected( e );
348 }
349
350 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
351 return tl::unexpected( *busy );
352
353 GetItemsResponse response;
354
355 FOOTPRINT* footprint = board()->GetFirstFootprint();
356 std::vector<BOARD_ITEM*> items;
357 std::set<KICAD_T> typesRequested, typesInserted;
358 bool handledAnything = false;
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( typesInserted.count( type ) )
370 continue;
371
372 switch( type )
373 {
374 case PCB_PAD_T:
375 {
376 handledAnything = true;
377
378 std::copy( footprint->Pads().begin(), footprint->Pads().end(),
379 std::back_inserter( items ) );
380
381 typesInserted.insert( PCB_PAD_T );
382 break;
383 }
384
385 case PCB_FIELD_T:
386 {
387 handledAnything = true;
388
389 std::copy( footprint->GetFields().begin(), footprint->GetFields().end(),
390 std::back_inserter( items ) );
391
392 typesInserted.insert( PCB_FIELD_T );
393 break;
394 }
395
396 case PCB_SHAPE_T:
397 case PCB_TEXT_T:
398 case PCB_TEXTBOX_T:
399 case PCB_BARCODE_T:
400 case PCB_TABLE_T:
401 {
402 handledAnything = true;
403 bool inserted = false;
404
405 for( BOARD_ITEM* item : footprint->GraphicalItems() )
406 {
407 if( item->Type() == type )
408 {
409 items.emplace_back( item );
410 inserted = true;
411 }
412 }
413
414 if( inserted )
415 typesInserted.insert( type );
416
417 break;
418 }
419
420 case PCB_TABLECELL_T:
421 {
422 handledAnything = true;
423 bool inserted = false;
424
425 for( BOARD_ITEM* item : footprint->GraphicalItems() )
426 {
427 if( item->Type() != PCB_TABLE_T )
428 continue;
429
430 item->RunOnChildren(
431 [&]( BOARD_ITEM* child )
432 {
433 items.emplace_back( child );
434 inserted = true;
435 },
437 }
438
439 if( inserted )
440 typesInserted.insert( PCB_TABLECELL_T );
441
442 break;
443 }
444
445 case PCB_DIMENSION_T:
446 {
447 handledAnything = true;
448 bool inserted = false;
449
450 for( BOARD_ITEM* item : footprint->GraphicalItems() )
451 {
452 switch( item->Type() )
453 {
455 case PCB_DIM_CENTER_T:
456 case PCB_DIM_RADIAL_T:
458 case PCB_DIM_LEADER_T:
459 items.emplace_back( item );
460 inserted = true;
461 break;
462 default:
463 break;
464 }
465 }
466
467 // we have to add the dimension subtypes to the requested to get them out
468 typesRequested.insert( { PCB_DIM_ALIGNED_T, PCB_DIM_CENTER_T, PCB_DIM_RADIAL_T,
470
471 if( inserted )
472 {
473 typesInserted.insert( { PCB_DIM_ALIGNED_T, PCB_DIM_CENTER_T, PCB_DIM_RADIAL_T,
475 }
476
477 break;
478 }
479
480 case PCB_ZONE_T:
481 {
482 handledAnything = true;
483
484 std::copy( footprint->Zones().begin(), footprint->Zones().end(),
485 std::back_inserter( items ) );
486
487 typesInserted.insert( PCB_ZONE_T );
488 break;
489 }
490
491 case PCB_GROUP_T:
492 {
493 handledAnything = true;
494
495 std::copy( footprint->Groups().begin(), footprint->Groups().end(),
496 std::back_inserter( items ) );
497
498 typesInserted.insert( PCB_GROUP_T );
499 break;
500 }
501
502 default:
503 break;
504 }
505 }
506
507 if( !handledAnything )
508 {
509 ApiResponseStatus e;
510 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
511 e.set_error_message( "none of the requested types are valid for a Footprint object" );
512 return tl::unexpected( e );
513 }
514
515 for( const BOARD_ITEM* item : items )
516 {
517 if( !typesRequested.count( item->Type() ) )
518 continue;
519
520 google::protobuf::Any itemBuf;
521 item->Serialize( itemBuf );
522 response.mutable_items()->Add( std::move( itemBuf ) );
523 }
524
525 response.set_status( ItemRequestStatus::IRS_OK );
526 return response;
527}
528
529
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:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:712
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:411
std::deque< PAD * > & Pads()
Definition footprint.h:405
GROUPS & Groups()
Definition footprint.h:414
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
DRAWINGS & GraphicalItems()
Definition footprint.h:408
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
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:733
STL namespace.
Class to handle a set of BOARD_ITEMs.
RequestMessageType Request
Definition api_handler.h:54
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_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:98
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:95
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:96
@ 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_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_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ 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
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97