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 along
18 * with this program. If not, see <http://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_BAD_REQUEST );
80 e.set_error_message( "the requested document is not a footprint" );
81 return tl::unexpected( e );
82 }
83
84 LIB_ID target_fp = footprintContext()->GetLoadedFPID();
85 std::string actual_lib = target_fp.GetUniStringLibNickname().ToStdString();
86 std::string actual_name = target_fp.GetUniStringLibItemName().ToStdString();
87 if( 0 != aDocument.lib_id().library_nickname().compare( actual_lib ) )
88 {
89 ApiResponseStatus e;
90 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
91 e.set_error_message( fmt::format( "the requested library is {} but the actual library is {}",
92 aDocument.lib_id().library_nickname(), actual_lib ) );
93 return tl::unexpected( e );
94 }
95
96 if( 0 != aDocument.lib_id().entry_name().compare( actual_name ) )
97 {
98 ApiResponseStatus e;
99 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
100 e.set_error_message( fmt::format( "the requested footprint name is {} but the actual name is {}",
101 aDocument.lib_id().entry_name(), actual_name ) );
102 return tl::unexpected( e );
103 }
104
105 return true;
106}
107
109 const DocumentSpecifier& aDocument )
110{
111 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
112 return tl::unexpected( *busy );
113
114 HANDLER_RESULT<bool> documentValidation = validateDocument( aDocument );
115
116 if( !documentValidation )
117 return tl::unexpected( documentValidation.error() );
118
119 FOOTPRINT* editorFootprint = board()->GetFirstFootprint();
120
121 if( !editorFootprint )
122 {
123 ApiResponseStatus e;
124 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
125 e.set_error_message( "no footprint is currently loaded" );
126 return tl::unexpected( e );
127 }
128
129 return editorFootprint;
130}
131
134{
135 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
136 {
137 ApiResponseStatus e;
138 e.set_status( ApiStatusCode::AS_UNHANDLED );
139 return tl::unexpected( e );
140 }
141
143
144 wxString libraryName = aCtx.Request.identifier().library_nickname();
145 wxString fpName = aCtx.Request.identifier().entry_name();
146
147 LIB_ID fpid( libraryName, fpName );
148 // preload the footprint to make sure it exists and so that we can make a nice error
149 try
150 {
151 std::unique_ptr<FOOTPRINT> footprint(
152 adapter->LoadFootprintWithOptionalNickname( fpid, true ) );
153
154 if( !footprint )
155 {
156 ApiResponseStatus e;
157 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
158 e.set_error_message( "could not open footprint" );
159 return tl::unexpected( e );
160 }
161 }
162 catch( const IO_ERROR& err )
163 {
164 ApiResponseStatus e;
165 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
166 e.set_error_message( fmt::format( "could not open footprint due to IO error {}",
167 err.Problem().ToStdString() ) );
168 return tl::unexpected( e );
169 }
170
172 return Empty();
173}
174
177{
178 if( aCtx.Request.type() != DocumentType::DOCTYPE_FOOTPRINT )
179 {
180 ApiResponseStatus e;
181 e.set_status( ApiStatusCode::AS_UNHANDLED );
182 return tl::unexpected( e );
183 }
184
185 GetOpenDocumentsResponse response;
186 common::types::DocumentSpecifier doc;
187
189
190 doc.set_type( DocumentType::DOCTYPE_FOOTPRINT );
191 doc.mutable_lib_id()->set_library_nickname( fpid.GetUniStringLibNickname() );
192 doc.mutable_lib_id()->set_entry_name( fpid.GetUniStringLibItemName() );
193
194 if( !project().IsNullProject() )
195 {
196 doc.mutable_project()->set_name( project().GetProjectName().ToStdString() );
197 doc.mutable_project()->set_path( project().GetProjectDirectory().ToStdString() );
198 }
199
200 response.mutable_documents()->Add( std::move( doc ) );
201 return response;
202}
203
204
207{
208 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
209
210 if( !footprint )
211 return tl::unexpected( footprint.error() );
212
213 if( !footprintContext()->SaveFootprint( *footprint ) )
214 {
215 ApiResponseStatus e;
216 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
217 e.set_error_message( "failed to save footprint" );
218 return tl::unexpected( e );
219 }
220
221 return Empty();
222}
223
224
227{
228 HANDLER_RESULT<FOOTPRINT*> footprint = validateAndGetFootprint( aCtx.Request.document() );
229
230 if( !footprint )
231 return tl::unexpected( footprint.error() );
232
233 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
234
235 if( pathStr.IsEmpty() )
236 {
237 ApiResponseStatus e;
238 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
239 e.set_error_message( "path must contain the new footprint name, "
240 "optionally prefixed with a library nickname (e.g. \"lib:name\")" );
241 return tl::unexpected( e );
242 }
243
244 // path can be "NewName" (same library) or "LibNick:NewName" (different library)
245 LIB_ID targetId;
246 targetId.Parse( pathStr );
247
248 wxString libraryName = targetId.GetLibNickname();
249
250 if( libraryName.IsEmpty() )
252
253 wxString newName = targetId.GetLibItemName();
254
255 // Clone the footprint so we don't modify the one being edited
256 std::unique_ptr<FOOTPRINT> copy( static_cast<FOOTPRINT*>( ( *footprint )->Clone() ) );
257 copy->SetFPID( LIB_ID( libraryName, newName ) );
258
259 if( !footprintContext()->SaveFootprintInLibrary( copy.get(), libraryName ) )
260 {
261 ApiResponseStatus e;
262 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
263 e.set_error_message( fmt::format( "failed to save footprint copy '{}' to library '{}'",
264 newName.ToStdString(),
265 libraryName.ToStdString() ) );
266 return tl::unexpected( e );
267 }
268
269 return Empty();
270}
271
272
275{
276 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
277 return tl::unexpected( *busy );
278
279 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
280
281 if( !documentValidation )
282 return tl::unexpected( documentValidation.error() );
283
284 frame()->GetScreen()->SetContentModified( false );
285 frame()->RevertFootprint(); // dialog is suppressed by ^
286
287 return Empty();
288}
289
290
292 const HANDLER_CONTEXT<GetItems>& aCtx )
293{
294 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
295 return tl::unexpected( *busy );
296
297 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
298 {
299 ApiResponseStatus e;
300 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
301 e.set_status( ApiStatusCode::AS_UNHANDLED );
302 return tl::unexpected( e );
303 }
304
305 GetItemsResponse response;
306
307 FOOTPRINT* footprint = board()->GetFirstFootprint();
308 std::vector<BOARD_ITEM*> items;
309 std::set<KICAD_T> typesRequested, typesInserted;
310 bool handledAnything = false;
311
312 for( int typeRaw : aCtx.Request.types() )
313 {
314 auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
315 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
316
317 if( type == TYPE_NOT_INIT )
318 continue;
319
320 typesRequested.emplace( type );
321
322 if( typesInserted.count( type ) )
323 continue;
324
325 switch( type )
326 {
327 case PCB_PAD_T:
328 {
329 handledAnything = true;
330
331 std::copy( footprint->Pads().begin(), footprint->Pads().end(),
332 std::back_inserter( items ) );
333
334 typesInserted.insert( PCB_PAD_T );
335 break;
336 }
337
338 case PCB_SHAPE_T:
339 case PCB_TEXT_T:
340 case PCB_TEXTBOX_T:
341 case PCB_BARCODE_T:
342 {
343 handledAnything = true;
344 bool inserted = false;
345
346 for( BOARD_ITEM* item : footprint->GraphicalItems() )
347 {
348 if( item->Type() == type )
349 {
350 items.emplace_back( item );
351 inserted = true;
352 }
353 }
354
355 if( inserted )
356 typesInserted.insert( type );
357
358 break;
359 }
360
361 case PCB_DIMENSION_T:
362 {
363 handledAnything = true;
364 bool inserted = false;
365
366 for( BOARD_ITEM* item : footprint->GraphicalItems() )
367 {
368 switch( item->Type() )
369 {
371 case PCB_DIM_CENTER_T:
372 case PCB_DIM_RADIAL_T:
374 case PCB_DIM_LEADER_T:
375 items.emplace_back( item );
376 inserted = true;
377 break;
378 default:
379 break;
380 }
381 }
382
383 // we have to add the dimension subtypes to the requested to get them out
384 typesRequested.insert( { PCB_DIM_ALIGNED_T, PCB_DIM_CENTER_T, PCB_DIM_RADIAL_T,
386
387 if( inserted )
388 {
389 typesInserted.insert( { PCB_DIM_ALIGNED_T, PCB_DIM_CENTER_T, PCB_DIM_RADIAL_T,
391 }
392
393 break;
394 }
395
396 case PCB_ZONE_T:
397 {
398 handledAnything = true;
399
400 std::copy( footprint->Zones().begin(), footprint->Zones().end(),
401 std::back_inserter( items ) );
402
403 typesInserted.insert( PCB_ZONE_T );
404 break;
405 }
406
407 case PCB_GROUP_T:
408 {
409 handledAnything = true;
410
411 std::copy( footprint->Groups().begin(), footprint->Groups().end(),
412 std::back_inserter( items ) );
413
414 typesInserted.insert( PCB_GROUP_T );
415 break;
416 }
417
418 default:
419 break;
420 }
421 }
422
423 if( !handledAnything )
424 {
425 ApiResponseStatus e;
426 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
427 e.set_error_message( "none of the requested types are valid for a Footprint object" );
428 return tl::unexpected( e );
429 }
430
431 for( const BOARD_ITEM* item : items )
432 {
433 if( !typesRequested.count( item->Type() ) )
434 continue;
435
436 google::protobuf::Any itemBuf;
437 item->Serialize( itemBuf );
438 response.mutable_items()->Add( std::move( itemBuf ) );
439 }
440
441 response.set_status( ItemRequestStatus::IRS_OK );
442 return response;
443}
444
445
KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:47
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:45
API_HANDLER_BOARD(std::shared_ptr< BOARD_CONTEXT > aContext, EDA_BASE_FRAME *aFrame=nullptr)
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:59
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:524
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:383
std::deque< PAD * > & Pads()
Definition footprint.h:377
GROUPS & Groups()
Definition footprint.h:386
DRAWINGS & GraphicalItems()
Definition footprint.h:380
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:49
int Parse(const UTF8 &aId, bool aFix=false)
Parse LIB_ID with the information from aId.
Definition lib_id.cpp:52
const wxString GetUniStringLibItemName() const
Get strings for display messages in dialogs.
Definition lib_id.h:112
const wxString GetUniStringLibNickname() const
Definition lib_id.h:88
const UTF8 & GetLibItemName() const
Definition lib_id.h:102
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:87
PCB_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
static FOOTPRINT_LIBRARY_ADAPTER * FootprintLibAdapter(PROJECT *aProject)
std::shared_ptr< FOOTPRINT_CONTEXT > CreateFootprintFrameContext(FOOTPRINT_EDIT_FRAME *aFrame)
PROJECT & Prj()
Definition kicad.cpp:669
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:75
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:85
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:103
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:100
@ TYPE_NOT_INIT
Definition typeinfo.h:78
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:101
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:108
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:90
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:105
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:89
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:98
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:99
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:84
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:97
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:102