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 LIB_ID target_fp = footprintContext()->GetLoadedFPID();
84
85 if( !target_fp.IsValid() )
86 {
87 ApiResponseStatus e;
88 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
89 e.set_error_message( "no footprint is currently open" );
90 return tl::unexpected( e );
91 }
92
93 std::string actual_lib = target_fp.GetUniStringLibNickname().ToStdString();
94 std::string actual_name = target_fp.GetUniStringLibItemName().ToStdString();
95
96 if( 0 != aDocument.lib_id().library_nickname().compare( actual_lib ) )
97 {
98 ApiResponseStatus e;
99 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
100 e.set_error_message( fmt::format( "the requested library is {} but the actual library is {}",
101 aDocument.lib_id().library_nickname(), actual_lib ) );
102 return tl::unexpected( e );
103 }
104
105 if( 0 != aDocument.lib_id().entry_name().compare( actual_name ) )
106 {
107 ApiResponseStatus e;
108 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
109 e.set_error_message( fmt::format( "the requested footprint name is {} but the actual name is {}",
110 aDocument.lib_id().entry_name(), actual_name ) );
111 return tl::unexpected( e );
112 }
113
114 return true;
115}
116
118 const DocumentSpecifier& aDocument )
119{
120 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
121 return tl::unexpected( *busy );
122
123 HANDLER_RESULT<bool> documentValidation = validateDocument( aDocument );
124
125 if( !documentValidation )
126 return tl::unexpected( documentValidation.error() );
127
128 FOOTPRINT* editorFootprint = board()->GetFirstFootprint();
129
130 if( !editorFootprint )
131 {
132 ApiResponseStatus e;
133 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
134 e.set_error_message( "no footprint is currently loaded" );
135 return tl::unexpected( e );
136 }
137
138 return editorFootprint;
139}
140
143{
144 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
145 {
146 ApiResponseStatus e;
147 e.set_status( ApiStatusCode::AS_UNHANDLED );
148 return tl::unexpected( e );
149 }
150
152
153 wxString libraryName = aCtx.Request.identifier().library_nickname();
154 wxString fpName = aCtx.Request.identifier().entry_name();
155
156 LIB_ID fpid( libraryName, fpName );
157 // preload the footprint to make sure it exists and so that we can make a nice error
158 try
159 {
160 std::unique_ptr<FOOTPRINT> footprint(
161 adapter->LoadFootprintWithOptionalNickname( fpid, true ) );
162
163 if( !footprint )
164 {
165 ApiResponseStatus e;
166 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
167 e.set_error_message( "could not open footprint" );
168 return tl::unexpected( e );
169 }
170 }
171 catch( const IO_ERROR& err )
172 {
173 ApiResponseStatus e;
174 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
175 e.set_error_message( fmt::format( "could not open footprint due to IO error {}",
176 err.Problem().ToStdString() ) );
177 return tl::unexpected( e );
178 }
179
181 return Empty();
182}
183
186{
187 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
188 {
189 ApiResponseStatus e;
190 e.set_status( ApiStatusCode::AS_UNHANDLED );
191 return tl::unexpected( e );
192 }
193
194 GetOpenDocumentsResponse response;
195 common::types::DocumentSpecifier doc;
196
198
199 doc.set_type( DocumentType::DOCTYPE_FOOTPRINT );
200 doc.mutable_lib_id()->set_library_nickname( fpid.GetUniStringLibNickname() );
201 doc.mutable_lib_id()->set_entry_name( fpid.GetUniStringLibItemName() );
202
203 if( !project().IsNullProject() )
204 {
205 doc.mutable_project()->set_name( project().GetProjectName().ToStdString() );
206 doc.mutable_project()->set_path( project().GetProjectDirectory().ToStdString() );
207 }
208
209 response.mutable_documents()->Add( std::move( doc ) );
210 return response;
211}
212
213
216{
217 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
218
219 if( !footprint )
220 return tl::unexpected( footprint.error() );
221
222 if( !footprintContext()->SaveFootprint( *footprint ) )
223 {
224 ApiResponseStatus e;
225 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
226 e.set_error_message( "failed to save footprint" );
227 return tl::unexpected( e );
228 }
229
230 return Empty();
231}
232
233
236{
237 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
238
239 if( !footprint )
240 return tl::unexpected( footprint.error() );
241
242 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
243
244 if( pathStr.IsEmpty() )
245 {
246 ApiResponseStatus e;
247 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
248 e.set_error_message( "path must contain the new footprint name, "
249 "optionally prefixed with a library nickname (e.g. \"lib:name\")" );
250 return tl::unexpected( e );
251 }
252
253 // path can be "NewName" (same library) or "LibNick:NewName" (different library)
254 LIB_ID targetId;
255 targetId.Parse( pathStr );
256
257 wxString libraryName = targetId.GetLibNickname();
258
259 if( libraryName.IsEmpty() )
261
262 wxString newName = targetId.GetLibItemName();
263
264 // Clone the footprint so we don't modify the one being edited
265 std::unique_ptr<FOOTPRINT> copy( static_cast<FOOTPRINT*>( ( *footprint )->Clone() ) );
266 copy->SetFPID( LIB_ID( libraryName, newName ) );
267
268 if( !footprintContext()->SaveFootprintInLibrary( copy.get(), libraryName ) )
269 {
270 ApiResponseStatus e;
271 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
272 e.set_error_message( fmt::format( "failed to save footprint copy '{}' to library '{}'",
273 newName.ToStdString(),
274 libraryName.ToStdString() ) );
275 return tl::unexpected( e );
276 }
277
278 return Empty();
279}
280
281
284{
285 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
286 return tl::unexpected( *busy );
287
288 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
289
290 if( !documentValidation )
291 return tl::unexpected( documentValidation.error() );
292
293 frame()->GetScreen()->SetContentModified( false );
294 frame()->RevertFootprint(); // dialog is suppressed by ^
295
296 return Empty();
297}
298
299
300// Footprint types that are directly retrievable by GetItems
301static const std::vector<KICAD_T> s_allowedFootprintTypes = {
302 PCB_PAD_T,
313};
314
315
317 const HANDLER_CONTEXT<GetItems>& aCtx )
318{
319 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
320 return tl::unexpected( *busy );
321
322 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
323 {
324 ApiResponseStatus e;
325 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
326 e.set_status( ApiStatusCode::AS_UNHANDLED );
327 return tl::unexpected( e );
328 }
329
330 GetItemsResponse response;
331
332 FOOTPRINT* footprint = board()->GetFirstFootprint();
333 std::vector<BOARD_ITEM*> items;
334 std::set<KICAD_T> typesRequested, typesInserted;
335 bool handledAnything = false;
336
337 std::vector<KICAD_T> requestedTypes = parseRequestedItemTypes( aCtx.Request.types() );
338
339 if( aCtx.Request.types().empty() )
340 requestedTypes.assign( s_allowedFootprintTypes.begin(), s_allowedFootprintTypes.end() );
341
342 for( KICAD_T type : requestedTypes )
343 {
344 typesRequested.emplace( type );
345
346 if( typesInserted.count( type ) )
347 continue;
348
349 switch( type )
350 {
351 case PCB_PAD_T:
352 {
353 handledAnything = true;
354
355 std::copy( footprint->Pads().begin(), footprint->Pads().end(),
356 std::back_inserter( items ) );
357
358 typesInserted.insert( PCB_PAD_T );
359 break;
360 }
361
362 case PCB_FIELD_T:
363 {
364 handledAnything = true;
365
366 std::copy( footprint->GetFields().begin(), footprint->GetFields().end(),
367 std::back_inserter( items ) );
368
369 typesInserted.insert( PCB_FIELD_T );
370 break;
371 }
372
373 case PCB_SHAPE_T:
374 case PCB_TEXT_T:
375 case PCB_TEXTBOX_T:
376 case PCB_BARCODE_T:
377 case PCB_TABLE_T:
378 {
379 handledAnything = true;
380 bool inserted = false;
381
382 for( BOARD_ITEM* item : footprint->GraphicalItems() )
383 {
384 if( item->Type() == type )
385 {
386 items.emplace_back( item );
387 inserted = true;
388 }
389 }
390
391 if( inserted )
392 typesInserted.insert( type );
393
394 break;
395 }
396
397 case PCB_TABLECELL_T:
398 {
399 handledAnything = true;
400 bool inserted = false;
401
402 for( BOARD_ITEM* item : footprint->GraphicalItems() )
403 {
404 if( item->Type() != PCB_TABLE_T )
405 continue;
406
407 item->RunOnChildren(
408 [&]( BOARD_ITEM* child )
409 {
410 items.emplace_back( child );
411 inserted = true;
412 },
414 }
415
416 if( inserted )
417 typesInserted.insert( PCB_TABLECELL_T );
418
419 break;
420 }
421
422 case PCB_DIMENSION_T:
423 {
424 handledAnything = true;
425 bool inserted = false;
426
427 for( BOARD_ITEM* item : footprint->GraphicalItems() )
428 {
429 switch( item->Type() )
430 {
432 case PCB_DIM_CENTER_T:
433 case PCB_DIM_RADIAL_T:
435 case PCB_DIM_LEADER_T:
436 items.emplace_back( item );
437 inserted = true;
438 break;
439 default:
440 break;
441 }
442 }
443
444 // we have to add the dimension subtypes to the requested to get them out
445 typesRequested.insert( { PCB_DIM_ALIGNED_T, PCB_DIM_CENTER_T, PCB_DIM_RADIAL_T,
447
448 if( inserted )
449 {
450 typesInserted.insert( { PCB_DIM_ALIGNED_T, PCB_DIM_CENTER_T, PCB_DIM_RADIAL_T,
452 }
453
454 break;
455 }
456
457 case PCB_ZONE_T:
458 {
459 handledAnything = true;
460
461 std::copy( footprint->Zones().begin(), footprint->Zones().end(),
462 std::back_inserter( items ) );
463
464 typesInserted.insert( PCB_ZONE_T );
465 break;
466 }
467
468 case PCB_GROUP_T:
469 {
470 handledAnything = true;
471
472 std::copy( footprint->Groups().begin(), footprint->Groups().end(),
473 std::back_inserter( items ) );
474
475 typesInserted.insert( PCB_GROUP_T );
476 break;
477 }
478
479 default:
480 break;
481 }
482 }
483
484 if( !handledAnything )
485 {
486 ApiResponseStatus e;
487 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
488 e.set_error_message( "none of the requested types are valid for a Footprint object" );
489 return tl::unexpected( e );
490 }
491
492 for( const BOARD_ITEM* item : items )
493 {
494 if( !typesRequested.count( item->Type() ) )
495 continue;
496
497 google::protobuf::Any itemBuf;
498 item->Serialize( itemBuf );
499 response.mutable_items()->Add( std::move( itemBuf ) );
500 }
501
502 response.set_status( ItemRequestStatus::IRS_OK );
503 return response;
504}
505
506
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:45
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
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.
Definition api_handler.h:93
void SetContentModified(bool aModified=true)
Definition base_screen.h:55
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
FOOTPRINT * GetFirstFootprint() const
Get the first footprint on the board or nullptr.
Definition board.h:704
virtual LIB_ID GetLoadedFPID() const =0
void LoadFootprintFromLibrary(LIB_ID aFPID)
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:410
std::deque< PAD * > & Pads()
Definition footprint.h:404
GROUPS & Groups()
Definition footprint.h:413
void GetFields(std::vector< PCB_FIELD * > &aVector, bool aVisibleOnly) const
Populate a std::vector with PCB_TEXTs.
DRAWINGS & GraphicalItems()
Definition footprint.h:407
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
PCB_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
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:727
STL namespace.
Class to handle a set of BOARD_ITEMs.
RequestMessageType Request
Definition api_handler.h:52
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