KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_board.cpp
Go to the documentation of this file.
1
2/*
3 * This program source code file is part of KiCad, a free EDA CAD application.
4 *
5 * Copyright (C) 2023 Jon Evans <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software: you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the
10 * Free Software Foundation, either version 3 of the License, or (at your
11 * option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22#include <set>
23#include <fmt/ranges.h>
24#include <magic_enum.hpp>
25
26#include <common.h>
28#include <api/api_pcb_utils.h>
29#include <api/api_enums.h>
30#include <api/api_utils.h>
31#include <board_commit.h>
33#include <footprint.h>
34#include <kicad_clipboard.h>
35#include <pad.h>
36#include <pcb_base_edit_frame.h>
37#include <pcb_field.h>
38#include <pcb_group.h>
40#include <pcb_track.h>
41#include <pcb_table.h>
42#include <pcb_tablecell.h>
43#include <pcb_3d_body.h>
44
45#include <layer_ids.h>
46#include <project.h>
47#include <tool/common_tools.h>
48#include <tool/tool_manager.h>
49#include <tools/pcb_actions.h>
52
53#include <api/common/types/base_types.pb.h>
54
55using namespace kiapi::common::commands;
56using types::CommandStatus;
57
58using types::DocumentType;
59using types::ItemRequestStatus;
60
61
62API_HANDLER_BOARD::API_HANDLER_BOARD( std::shared_ptr<BOARD_CONTEXT> aContext,
63 EDA_BASE_FRAME* aFrame ) :
64 API_HANDLER_EDITOR( aFrame ),
65 m_context( std::move( aContext ) )
66{
67 wxCHECK( m_context, /* void */ );
68
70
72
79
94
97
108}
109
110
111std::optional<ApiResponseStatus> API_HANDLER_BOARD::checkForHeadless(
112 const std::string& aCommandName ) const
113{
114 if( m_frame )
115 return std::nullopt;
116
117 ApiResponseStatus e;
118 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
119 e.set_error_message( fmt::format( "{} is not available in headless mode", aCommandName ) );
120 return e;
121}
122
123
128
129
130void API_HANDLER_BOARD::pushCurrentCommit( const std::string& aClientName,
131 const wxString& aMessage )
132{
133 API_HANDLER_EDITOR::pushCurrentCommit( aClientName, aMessage );
134 onModified();
135}
136
137
138std::unique_ptr<COMMIT> API_HANDLER_BOARD::createCommit()
139{
140 if( m_frame )
141 return std::make_unique<BOARD_COMMIT>( static_cast<EDA_DRAW_FRAME*>( m_frame ) );
142
143 bool isFootprintEditor = thisDocumentType() == kiapi::common::types::DOCTYPE_FOOTPRINT;
144
145 return std::make_unique<BOARD_COMMIT>( toolManager(), !isFootprintEditor, isFootprintEditor );
146}
147
148
149std::optional<BOARD_ITEM*> API_HANDLER_BOARD::getItemById( const KIID& aId ) const
150{
151 BOARD_ITEM* item = board()->ResolveItem( aId, true );
152
153 if( !item )
154 return std::nullopt;
155
156 return item;
157}
158
159
161 BOARD_ITEM_CONTAINER* aContainer )
162{
163 if( !aContainer )
164 {
165 ApiResponseStatus e;
166 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
167 e.set_error_message( "Tried to create an item in a null container" );
168 return tl::unexpected( e );
169 }
170
171 if( aType == PCB_PAD_T && !dynamic_cast<FOOTPRINT*>( aContainer ) )
172 {
173 ApiResponseStatus e;
174 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
175 e.set_error_message( fmt::format( "Tried to create a pad in {}, which is not a footprint",
176 aContainer->GetFriendlyName().ToStdString() ) );
177 return tl::unexpected( e );
178 }
179 else if( aType == PCB_FOOTPRINT_T && !dynamic_cast<BOARD*>( aContainer ) )
180 {
181 ApiResponseStatus e;
182 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
183 e.set_error_message( fmt::format( "Tried to create a footprint in {}, which is not a board",
184 aContainer->GetFriendlyName().ToStdString() ) );
185 return tl::unexpected( e );
186 }
187
188 if( dynamic_cast<FOOTPRINT*>( aContainer ) )
189 {
190 static const std::set<KICAD_T> s_footprintItemTypes = {
195 };
196
197 if( !s_footprintItemTypes.contains( aType ) )
198 {
199 ApiResponseStatus e;
200 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
201 e.set_error_message( fmt::format( "items of type {} cannot be created in a footprint",
202 magic_enum::enum_name( aType ) ) );
203 return tl::unexpected( e );
204 }
205 }
206
207 std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
208
209 if( !created )
210 {
211 ApiResponseStatus e;
212 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
213 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
214 magic_enum::enum_name( aType ) ) );
215 return tl::unexpected( e );
216 }
217
218 return created;
219}
220
221
222void API_HANDLER_BOARD::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
223 const std::string& aClientName )
224{
225 BOARD* board = this->board();
226 std::vector<BOARD_ITEM*> validatedItems;
227
228 for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
229 {
230 if( BOARD_ITEM* item = board->ResolveItem( pair.first, true ) )
231 {
232 // A footprint without its mandatory fields is not a state the editor can load or
233 // render; the const field accessors return nullptr and callers dereference them
234 if( item->Type() == PCB_FIELD_T && static_cast<PCB_FIELD*>( item )->IsMandatory() )
235 {
236 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_IMMUTABLE;
237 continue;
238 }
239
240 validatedItems.push_back( item );
241 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_OK;
242 }
243
244 // Note: we don't currently support locking items from API modification, but here is where
245 // to add it in the future (and return IDS_IMMUTABLE)
246 }
247
248 COMMIT* commit = getCurrentCommit( aClientName );
249
250 for( BOARD_ITEM* item : validatedItems )
251 {
252 if( item->Type() == PCB_TABLECELL_T )
253 {
254 // Cells are owned by their table; the commit removal path doesn't handle them.
255 // Match the GUI delete: clear the cell contents (generated-table cells have no user text).
256 if( IsGeneratedTableCell( item ) )
257 continue;
258
259 commit->Modify( item );
260 static_cast<PCB_TABLECELL*>( item )->SetText( wxEmptyString );
261 }
262 else if( item->Type() == PCB_GENERATOR_T )
263 {
264 TOOL_MANAGER* mgr = toolManager();
265
266 if( ensureGeneratorTool() )
267 {
269 static_cast<PCB_GENERATOR*>( item ) );
270 }
271 else
272 {
273 commit->Remove( item );
274 }
275 }
276 else
277 {
278 commit->Remove( item );
279 }
280 }
281
282 if( !m_activeClients.count( aClientName ) )
283 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
284}
285
286
288{
289 TOOL_MANAGER* mgr = toolManager();
290
291 if( !mgr->FindTool( GENERATOR_TOOL_NAME ) )
292 {
293 mgr->RegisterTool( new GENERATOR_TOOL );
295 }
296
297 return mgr->GetTool<GENERATOR_TOOL>();
298}
299
300
302 const std::vector<PCB_GENERATOR*>& aGenerators,
303 std::function<void( const KIID&, ItemStatus )> aResultHandler ) const
304{
305 BOARD* board = this->board();
306
307 for( PCB_GENERATOR* generator : aGenerators )
308 {
309 ItemStatus status;
310
311 try
312 {
313 generator->EditStart( aTool, board, aCommit );
314 bool ok = generator->Update( aTool, board, aCommit );
315 generator->EditFinish( aTool, board, aCommit );
316
317 status.set_code( ok ? ItemStatusCode::ISC_OK : ItemStatusCode::ISC_INVALID_DATA );
318
319 if( !ok )
320 status.set_error_message( "the generator reported an update failure" );
321 }
322 catch( const std::exception& exc )
323 {
324 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
325 status.set_error_message( fmt::format( "regeneration exception: {}", exc.what() ) );
326 }
327
328 if( aResultHandler )
329 aResultHandler( generator->m_Uuid, status );
330 }
331}
332
333
335 const DocumentSpecifier& aDocument, const KIID& aId )
336{
337 if( !validateDocument( aDocument ) )
338 return std::nullopt;
339
340 return getItemById( aId );
341}
342
343
345 const std::string& aClientName,
346 const types::ItemHeader &aHeader,
347 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
348 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
349{
350 ApiResponseStatus e;
351
352 auto containerResult = validateItemHeaderDocument( aHeader );
353
354 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
355 {
356 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
357 e.set_status( ApiStatusCode::AS_UNHANDLED );
358 return tl::unexpected( e );
359 }
360 else if( !containerResult )
361 {
362 e.CopyFrom( containerResult.error() );
363 return tl::unexpected( e );
364 }
365
366 BOARD* board = this->board();
368
369 if( containerResult->has_value() )
370 {
371 const KIID& containerId = **containerResult;
372 std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
373
374 if( optItem )
375 {
376 container = dynamic_cast<BOARD_ITEM_CONTAINER*>( *optItem );
377
378 if( !container )
379 {
380 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
381 e.set_error_message( fmt::format( "The requested container {} is not a valid board item container",
382 containerId.AsStdString() ) );
383 return tl::unexpected( e );
384 }
385 }
386 else
387 {
388 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
389 e.set_error_message( fmt::format( "The requested container {} does not exist in this document",
390 containerId.AsStdString() ) );
391 return tl::unexpected( e );
392 }
393 }
394
395 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aClientName ) );
396 int commitCheckpoint = commit->Checkpoint();
397
398 for( const google::protobuf::Any& anyItem : aItems )
399 {
400 ItemStatus status;
401 std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
402
403 if( !type )
404 {
405 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
406 status.set_error_message( fmt::format( "Could not decode a valid type from {}", anyItem.type_url() ) );
407 aItemHandler( status, anyItem );
408 continue;
409 }
410
411 if( type == PCB_DIMENSION_T )
412 {
413 board::types::Dimension dimension;
414 anyItem.UnpackTo( &dimension );
415
416 switch( dimension.dimension_style_case() )
417 {
418 case board::types::Dimension::kAligned: type = PCB_DIM_ALIGNED_T; break;
419 case board::types::Dimension::kOrthogonal: type = PCB_DIM_ORTHOGONAL_T; break;
420 case board::types::Dimension::kRadial: type = PCB_DIM_RADIAL_T; break;
421 case board::types::Dimension::kLeader: type = PCB_DIM_LEADER_T; break;
422 case board::types::Dimension::kCenter: type = PCB_DIM_CENTER_T; break;
423 case board::types::Dimension::DIMENSION_STYLE_NOT_SET: break;
424 }
425 }
426
427 if( *type == PCB_SHAPE_T )
428 {
429 board::types::BoardGraphicShape shape;
430 anyItem.UnpackTo( &shape );
431
432 if( shape.has_pad_custom_shape_options() )
433 {
434 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
435 status.set_error_message( "pad_custom_shape_options are only valid on shapes inside a pad's "
436 "padstack custom shapes" );
437 aItemHandler( status, anyItem );
438 continue;
439 }
440 }
441
443 [&]() -> HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>
444 {
445 if( *type == PCB_GENERATOR_T )
446 {
447 std::optional<wxString> generatorType = GeneratorTypeFromAny( anyItem );
448
449 if( !generatorType )
450 {
451 ItemStatus genStatus;
452 genStatus.set_code( ItemStatusCode::ISC_INVALID_TYPE );
453 genStatus.set_error_message( fmt::format( "could not decode a generator from {}",
454 anyItem.type_url() ) );
455 aItemHandler( genStatus, anyItem );
456 return HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>( std::unique_ptr<BOARD_ITEM>() );
457 }
458
459 std::unique_ptr<BOARD_ITEM> genItem = CreateGeneratorForType( *generatorType, container );
460
461 if( !genItem )
462 {
463 ItemStatus genStatus;
464 genStatus.set_code( ItemStatusCode::ISC_INVALID_TYPE );
465 genStatus.set_error_message( fmt::format( "generator type {} is not registered",
466 generatorType->ToStdString() ) );
467 aItemHandler( genStatus, anyItem );
468 return HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>( std::unique_ptr<BOARD_ITEM>() );
469 }
470
471 return HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>( std::move( genItem ) );
472 }
473
474 return createItemForType( *type, container );
475 }();
476
477 if( !creationResult || !creationResult.value() )
478 {
479 if( !creationResult )
480 {
481 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
482 status.set_error_message( creationResult.error().error_message() );
483 aItemHandler( status, anyItem );
484 }
485
486 continue;
487 }
488
489 std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
490
491 bool unpacked = false;
492
493 if( item->Type() == PCB_GENERATOR_T )
494 unpacked = item->Deserialize( anyItem );
495 else if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( item.get() ) )
496 unpacked = group->DeserializeGroup( anyItem, commit );
497 else
498 unpacked = item->Deserialize( anyItem );
499
500 if( !unpacked )
501 {
502 commit->RevertToCheckpoint( commitCheckpoint );
503
504 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
505 e.set_error_message( fmt::format( "could not unpack {} from request",
506 item->GetClass().ToStdString() ) );
507 return tl::unexpected( e );
508 }
509
510 if( std::vector<wxString> removed = item->RemoveConflictingCustomProperties(); !removed.empty() )
511 {
512 auto as_str =
513 []( const wxString& aIn )
514 {
515 return std::string( aIn.ToUTF8() );
516 };
517
518 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
519 status.set_error_message( fmt::format( "Invalid custom properties for item {}: property name(s) '{}' "
520 "already in use",
521 item->m_Uuid.AsStdString(),
522 fmt::join( std::views::transform( removed, as_str ), ", " ) ) );
523
524 aItemHandler( status, anyItem );
525 continue;
526 }
527
528 std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
529
530 if( aCreate && optItem )
531 {
532 status.set_code( ItemStatusCode::ISC_EXISTING );
533 status.set_error_message( fmt::format( "an item with UUID {} already exists",
534 item->m_Uuid.AsStdString() ) );
535 aItemHandler( status, anyItem );
536 continue;
537 }
538 else if( !aCreate && !optItem )
539 {
540 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
541 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
542 item->m_Uuid.AsStdString() ) );
543 aItemHandler( status, anyItem );
544 continue;
545 }
546
547 if( !aCreate && ( *optItem )->Type() != item->Type() )
548 {
549 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
550 status.set_error_message( fmt::format( "item {} is of type {}, not {}",
551 item->m_Uuid.AsStdString(),
552 magic_enum::enum_name( ( *optItem )->Type() ),
553 magic_enum::enum_name( item->Type() ) ) );
554 aItemHandler( status, anyItem );
555 continue;
556 }
557
558 if( aCreate
559 && !item->FitsEnabledLayers( board->GetEnabledLayers(), board->GetCopperLayerCount() ) )
560 {
561 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
562 status.set_error_message( "attempted to add item with no overlapping layers with the board" );
563 aItemHandler( status, anyItem );
564 continue;
565 }
566
567 status.set_code( ItemStatusCode::ISC_OK );
568 google::protobuf::Any newItem;
569
570 if( item->Type() == PCB_GROUP_T )
571 static_cast<PCB_GROUP*>( item.get() )->FinalizeGroupDeserialization();
572
573 if( aCreate )
574 {
575 if( item->Type() == PCB_TABLECELL_T )
576 {
577 PCB_TABLE* table = dynamic_cast<PCB_TABLE*>( container );
578
579 if( !table )
580 {
581 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
582 status.set_error_message( "a table cell must target a table container" );
583 aItemHandler( status, anyItem );
584 continue;
585 }
586
587 PCB_TABLECELL* cell = static_cast<PCB_TABLECELL*>( item.release() );
588 commit->Modify( table );
589 table->AddCell( cell );
590 cell->Serialize( newItem );
591 }
592 else
593 {
594 if( item->Type() == PCB_FOOTPRINT_T || item->Type() == PCB_TABLE_T )
595 {
596 // Ensure children have unique identifiers; in case the API client created
597 // this new item by cloning an existing one and only changing the parent UUID.
598 item->RunOnChildren(
599 []( BOARD_ITEM* aChild )
600 {
601 aChild->ResetUuid();
602 },
603 RECURSE );
604 }
605
606 BOARD_ITEM* newBoardItem = item.get();
607 item->Serialize( newItem );
608 commit->Add( item.release() );
609
610 if( newBoardItem->Type() == PCB_GENERATOR_T )
611 {
612 if( GENERATOR_TOOL* genTool = ensureGeneratorTool() )
613 regenerateGenerators( genTool, commit, { static_cast<PCB_GENERATOR*>( newBoardItem ) }, {} );
614
615 // The regeneration may have added members
616 newBoardItem->Serialize( newItem );
617 }
618 }
619 }
620 else
621 {
622 BOARD_ITEM* boardItem = *optItem;
623
624 if( boardItem->Type() == PCB_GENERATOR_T )
625 {
626 commit->Modify( boardItem );
627 boardItem->Deserialize( anyItem );
628
629 static_cast<PCB_GROUP*>( boardItem )->FinalizeGroupDeserialization();
630
631 if( GENERATOR_TOOL* genTool = ensureGeneratorTool() )
632 regenerateGenerators( genTool, commit, { static_cast<PCB_GENERATOR*>( boardItem ) }, {} );
633
634 boardItem->Serialize( newItem );
635 }
636
637 // Footprints can't be modified by CopyFrom at the moment because the commit system
638 // doesn't currently know what to do with a footprint that has had its children
639 // replaced with other children; which results in things like the view not having its
640 // cached geometry for footprint children updated when you move a footprint around.
641 // And also, groups are special because they can contain any item type, so we
642 // can't use CopyFrom on them either.
643 else if( boardItem->Type() == PCB_FOOTPRINT_T || boardItem->Type() == PCB_GROUP_T )
644 {
645 // Save group membership before removal, since Remove() severs the relationship
646 PCB_GROUP* parentGroup = dynamic_cast<PCB_GROUP*>( boardItem->GetParentGroup() );
647
648 commit->Remove( boardItem );
649 item->Serialize( newItem );
650
651 BOARD_ITEM* newBoardItem = item.release();
652 commit->Add( newBoardItem );
653
654 // Restore group membership for the newly added item
655 if( parentGroup )
656 parentGroup->AddItem( newBoardItem );
657 }
658 else
659 {
660 EDA_GROUP* parentGroup = boardItem->GetParentGroup();
661 BOARD_ITEM* parent = boardItem->GetParent();
662
663 commit->Modify( boardItem );
664 boardItem->CopyFrom( item.get() );
665
666 if( parentGroup )
667 boardItem->SetParentGroup( parentGroup );
668
669 if( parent )
670 boardItem->SetParent( parent );
671
672 boardItem->Serialize( newItem );
673 }
674 }
675
676 aItemHandler( status, newItem );
677 }
678
679 if( !m_activeClients.count( aClientName ) )
680 {
681 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
682 : _( "Modified items via API" ) );
683 }
684
685 return ItemRequestStatus::IRS_OK;
686}
687
688
690 const google::protobuf::RepeatedField<int>& aTypes )
691{
692 std::vector<KICAD_T> types;
693
694 for( int typeRaw : aTypes )
695 {
696 auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
697 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
698
699 if( type != TYPE_NOT_INIT )
700 types.emplace_back( type );
701 }
702
703 return types;
704}
705
706
708 const HANDLER_CONTEXT<RunAction>& aCtx )
709{
710 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RunAction" ) )
711 return tl::unexpected( *headless );
712
713 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
714 return tl::unexpected( *busy );
715
716 RunActionResponse response;
717
718 if( toolManager()->RunAction( aCtx.Request.action(), true ) )
719 response.set_status( RunActionStatus::RAS_OK );
720 else
721 response.set_status( RunActionStatus::RAS_INVALID );
722
723 return response;
724}
725
726
729{
730 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
731 {
732 ApiResponseStatus e;
733 e.set_status( ApiStatusCode::AS_UNHANDLED );
734 return tl::unexpected( e );
735 }
736
737 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
738 return tl::unexpected( *busy );
739
740 GetItemsResponse response;
741
742 std::vector<BOARD_ITEM*> items;
743
744 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
745 {
746 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
747 items.emplace_back( *item );
748 }
749
750 if( items.empty() )
751 {
752 ApiResponseStatus e;
753 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
754 e.set_error_message( "none of the requested IDs were found or valid" );
755 return tl::unexpected( e );
756 }
757
758 for( const BOARD_ITEM* item : items )
759 {
760 google::protobuf::Any itemBuf;
761 item->Serialize( itemBuf );
762 response.mutable_items()->Add( std::move( itemBuf ) );
763 }
764
765 response.set_status( ItemRequestStatus::IRS_OK );
766 return response;
767}
768
769
772{
773 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetSelection" ) )
774 return tl::unexpected( *headless );
775
776 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
777 {
778 ApiResponseStatus e;
779 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
780 e.set_status( ApiStatusCode::AS_UNHANDLED );
781 return tl::unexpected( e );
782 }
783
784 std::set<KICAD_T> filter;
785
786 for( KICAD_T type : parseRequestedItemTypes( aCtx.Request.types() ) )
787 {
788 filter.insert( type );
789
790 if( type == PCB_DIMENSION_T )
791 {
792 filter.insert( PCB_DIM_ALIGNED_T );
794 filter.insert( PCB_DIM_RADIAL_T );
795 filter.insert( PCB_DIM_LEADER_T );
796 filter.insert( PCB_DIM_CENTER_T );
797 }
798 }
799
800 TOOL_MANAGER* mgr = toolManager();
801 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
802
803 SelectionResponse response;
804
805 for( EDA_ITEM* item : selectionTool->GetSelection() )
806 {
807 if( filter.empty() || filter.contains( item->Type() ) )
808 item->Serialize( *response.add_items() );
809 }
810
811 return response;
812}
813
814
817{
818 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "ClearSelection" ) )
819 return tl::unexpected( *headless );
820
821 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
822 {
823 ApiResponseStatus e;
824 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
825 e.set_status( ApiStatusCode::AS_UNHANDLED );
826 return tl::unexpected( e );
827 }
828
829 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
830 return tl::unexpected( *busy );
831
832 TOOL_MANAGER* mgr = toolManager();
834 m_frame->Refresh();
835
836 return Empty();
837}
838
839
842{
843 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "AddToSelection" ) )
844 return tl::unexpected( *headless );
845
846 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
847 {
848 ApiResponseStatus e;
849 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
850 e.set_status( ApiStatusCode::AS_UNHANDLED );
851 return tl::unexpected( e );
852 }
853
854 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
855 return tl::unexpected( *busy );
856
857 TOOL_MANAGER* mgr = toolManager();
858 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
859
860 std::vector<EDA_ITEM*> toAdd;
861
862 for( const types::KIID& id : aCtx.Request.items() )
863 {
864 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
865 toAdd.emplace_back( *item );
866 }
867
868 selectionTool->AddItemsToSel( &toAdd );
869 m_frame->Refresh();
870
871 SelectionResponse response;
872
873 for( EDA_ITEM* item : selectionTool->GetSelection() )
874 item->Serialize( *response.add_items() );
875
876 return response;
877}
878
879
881{
882 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "FocusOnItems" ) )
883 return tl::unexpected( *headless );
884
885 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
886 return tl::unexpected( documentValidation.error() );
887
888 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
889 return tl::unexpected( *busy );
890
891 if( aCtx.Request.items().empty() )
892 return tl::unexpected( MakeResponseStatus( AS_BAD_REQUEST, "no items were given to focus on" ) );
893
894 std::optional<BOX2I> bbox;
895 std::vector<std::string> missing;
896
897 for( const types::KIID& idMsg : aCtx.Request.items() )
898 {
899 std::optional<BOARD_ITEM*> item = getItemById( KIID( idMsg.value() ) );
900
901 if( !item )
902 {
903 missing.push_back( idMsg.value() );
904 continue;
905 }
906
907 if( bbox )
908 bbox->Merge( ( *item )->GetBoundingBox() );
909 else
910 bbox = ( *item )->GetBoundingBox();
911 }
912
913 if( !missing.empty() )
914 {
915 return tl::unexpected(
916 MakeResponseStatus( AS_BAD_REQUEST, fmt::format( "the items {} are not in the requested document",
917 fmt::join( missing, ", " ) ) ) );
918 }
919
920 if( aCtx.Request.has_margin() )
921 bbox->Inflate( UnpackDistance( aCtx.Request.margin() ) );
922
923 toolManager()->GetTool<COMMON_TOOLS>()->ZoomFitBox( *bbox );
924
925 return Empty();
926}
927
928
931{
932 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RemoveFromSelection" ) )
933 return tl::unexpected( *headless );
934
935 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
936 {
937 ApiResponseStatus e;
938 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
939 e.set_status( ApiStatusCode::AS_UNHANDLED );
940 return tl::unexpected( e );
941 }
942
943 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
944 return tl::unexpected( *busy );
945
946 TOOL_MANAGER* mgr = toolManager();
947 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
948
949 std::vector<EDA_ITEM*> toRemove;
950
951 for( const types::KIID& id : aCtx.Request.items() )
952 {
953 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
954 toRemove.emplace_back( *item );
955 }
956
957 selectionTool->RemoveItemsFromSel( &toRemove );
958 m_frame->Refresh();
959
960 SelectionResponse response;
961
962 for( EDA_ITEM* item : selectionTool->GetSelection() )
963 item->Serialize( *response.add_items() );
964
965 return response;
966}
967
968
971{
972 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
973
974 if( !documentValidation )
975 return tl::unexpected( documentValidation.error() );
976
977 BoardStackupResponse response;
978
979 board::PackBoardStackup( *board(), *response.mutable_stackup() );
980
981 return response;
982}
983
984
987{
988 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
989
990 if( !documentValidation )
991 return tl::unexpected( documentValidation.error() );
992
993 BoardEnabledLayersResponse response;
994
995 BOARD* board = this->board();
996 int copperLayerCount = board->GetCopperLayerCount();
997
998 response.set_copper_layer_count( copperLayerCount );
999
1000 LSET enabled = board->GetEnabledLayers();
1001
1002 // The Rescue layer is an internal detail and should be hidden from the API
1003 enabled.reset( Rescue );
1004
1005 // Just in case this is out of sync; the API should always return the expected copper layers
1006 enabled |= LSET::AllCuMask( copperLayerCount );
1007
1008 board::PackLayerSet( *response.mutable_layers(), enabled );
1009
1010 return response;
1011}
1012
1013
1016{
1017 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1018
1019 if( !documentValidation )
1020 return tl::unexpected( documentValidation.error() );
1021
1023 GraphicsDefaultsResponse response;
1024
1025 // TODO: This should change to be an enum class
1026 constexpr std::array<kiapi::board::BoardLayerClass, LAYER_CLASS_COUNT> classOrder = {
1027 kiapi::board::BLC_SILKSCREEN,
1028 kiapi::board::BLC_COPPER,
1029 kiapi::board::BLC_EDGES,
1030 kiapi::board::BLC_COURTYARD,
1031 kiapi::board::BLC_FABRICATION,
1032 kiapi::board::BLC_OTHER
1033 };
1034
1035 for( int i = 0; i < LAYER_CLASS_COUNT; ++i )
1036 {
1037 kiapi::board::BoardLayerGraphicsDefaults* l = response.mutable_defaults()->add_layers();
1038
1039 l->set_layer( classOrder[i] );
1040 l->mutable_line_thickness()->set_value_nm( bds.m_LineThickness[i] );
1041
1042 kiapi::common::types::TextAttributes* text = l->mutable_text();
1043 text->mutable_size()->set_x_nm( bds.m_TextSize[i].x );
1044 text->mutable_size()->set_y_nm( bds.m_TextSize[i].y );
1045 text->mutable_stroke_width()->set_value_nm( bds.m_TextThickness[i] );
1046 text->set_italic( bds.m_TextItalic[i] );
1047 text->set_keep_upright( bds.m_TextUpright[i] );
1048 }
1049
1050 return response;
1051}
1052
1053
1056{
1057 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1058 {
1059 ApiResponseStatus e;
1060 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1061 e.set_status( ApiStatusCode::AS_UNHANDLED );
1062 return tl::unexpected( e );
1063 }
1064
1065 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1066 return tl::unexpected( *busy );
1067
1068 GetBoundingBoxResponse response;
1069 bool includeText = aCtx.Request.mode() == BoundingBoxMode::BBM_ITEM_AND_CHILD_TEXT;
1070
1071 for( const types::KIID& idMsg : aCtx.Request.items() )
1072 {
1073 KIID id( idMsg.value() );
1074 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1075
1076 if( !optItem )
1077 continue;
1078
1079 BOARD_ITEM* item = *optItem;
1080 BOX2I bbox;
1081
1082 if( item->Type() == PCB_FOOTPRINT_T )
1083 bbox = static_cast<FOOTPRINT*>( item )->GetBoundingBox( includeText );
1084 else
1085 bbox = item->GetBoundingBox();
1086
1087 response.add_items()->set_value( idMsg.value() );
1088 PackBox2( *response.add_boxes(), bbox );
1089 }
1090
1091 return response;
1092}
1093
1094
1097{
1098 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1099 return tl::unexpected( documentValidation.error() );
1100
1101 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1102 return tl::unexpected( *busy );
1103
1104 BoardBoundingBoxResponse response;
1105 BOX2I bbox;
1106
1107 switch( aCtx.Request.mode() )
1108 {
1109 case BoardBoundingBoxMode::BBBM_ALL_ITEMS: bbox = board()->ComputeBoundingBox( false, false ); break;
1110 case BoardBoundingBoxMode::BBBM_PHYSICAL_LAYERS_ONLY: bbox = board()->ComputeBoundingBox( false, true ); break;
1111 case BoardBoundingBoxMode::BBBM_BOARD_EDGES_ONLY: bbox = board()->GetBoardEdgesBoundingBox(); break;
1112 default:
1113 return tl::unexpected( MakeResponseStatus( AS_BAD_REQUEST, "invalid bounding box mode requested" ) );
1114 }
1115
1116 PackBox2( *response.mutable_box(), bbox );
1117
1118 return response;
1119}
1120
1121
1124{
1125 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1126 !documentValidation )
1127 {
1128 return tl::unexpected( documentValidation.error() );
1129 }
1130
1131 PadShapeAsPolygonResponse response;
1133
1134 for( const types::KIID& padRequest : aCtx.Request.pads() )
1135 {
1136 KIID id( padRequest.value() );
1137 std::optional<BOARD_ITEM*> optPad = getItemById( id );
1138
1139 if( !optPad || ( *optPad )->Type() != PCB_PAD_T )
1140 continue;
1141
1142 response.add_pads()->set_value( padRequest.value() );
1143
1144 PAD* pad = static_cast<PAD*>( *optPad );
1145 SHAPE_POLY_SET poly;
1146 pad->TransformShapeToPolygon( poly, pad->Padstack().EffectiveLayerFor( layer ), 0,
1147 pad->GetMaxError(), ERROR_INSIDE );
1148
1149 types::PolygonWithHoles* polyMsg = response.mutable_polygons()->Add();
1150 PackPolyLine( *polyMsg->mutable_outline(), poly.COutline( 0 ) );
1151 }
1152
1153 return response;
1154}
1155
1156
1159{
1160 using board::types::BoardLayer;
1161
1162 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1163 !documentValidation )
1164 {
1165 return tl::unexpected( documentValidation.error() );
1166 }
1167
1168 PadstackPresenceResponse response;
1169
1170 LSET layers;
1171
1172 for( const int layer : aCtx.Request.layers() )
1173 {
1174 PCB_LAYER_ID pcbLayer = FromProtoEnum<PCB_LAYER_ID, BoardLayer>( static_cast<BoardLayer>( layer ) );
1175
1176 if( pcbLayer < 0 || pcbLayer >= PCB_LAYER_ID_COUNT )
1177 continue;
1178
1179 layers.set( pcbLayer );
1180 }
1181
1182 for( const types::KIID& padRequest : aCtx.Request.items() )
1183 {
1184 KIID id( padRequest.value() );
1185 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1186
1187 if( !optItem )
1188 continue;
1189
1190 switch( ( *optItem )->Type() )
1191 {
1192 case PCB_PAD_T:
1193 {
1194 PAD* pad = static_cast<PAD*>( *optItem );
1195
1196 for( PCB_LAYER_ID layer : layers )
1197 {
1198 PadstackPresenceEntry* entry = response.add_entries();
1199 entry->mutable_item()->set_value( pad->m_Uuid.AsStdString() );
1200 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1201 entry->set_presence( pad->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1202 }
1203
1204 break;
1205 }
1206
1207 case PCB_VIA_T:
1208 {
1209 PCB_VIA* via = static_cast<PCB_VIA*>( *optItem );
1210
1211 for( PCB_LAYER_ID layer : layers )
1212 {
1213 PadstackPresenceEntry* entry = response.add_entries();
1214 entry->mutable_item()->set_value( via->m_Uuid.AsStdString() );
1215 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1216 entry->set_presence( via->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1217 }
1218
1219 break;
1220 }
1221
1222 default:
1223 break;
1224 }
1225 }
1226
1227 return response;
1228}
1229
1230
1233{
1234 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1235
1236 if( !documentValidation )
1237 return tl::unexpected( documentValidation.error() );
1238
1239 ExpandTextVariablesResponse reply;
1240 BOARD* board = this->board();
1241
1242 std::function<bool( wxString* )> textResolver =
1243 [&]( wxString* token ) -> bool
1244 {
1245 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1246 return board->ResolveTextVar( token, 0 );
1247 };
1248
1249 for( const std::string& textMsg : aCtx.Request.text() )
1250 {
1251 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver, INTERNAL );
1252
1253 if( aCtx.Request.expand_env_vars() )
1254 text = ExpandEnvVarSubstitutions( text, board->GetProject() );
1255
1256 reply.add_text( text.ToUTF8() );
1257 }
1258
1259 return reply;
1260}
1261
1262
1265{
1266 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "InteractiveMoveItems" ) )
1267 return tl::unexpected( *headless );
1268
1269 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1270
1271 if( !documentValidation )
1272 return tl::unexpected( documentValidation.error() );
1273
1274 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1275 return tl::unexpected( *busy );
1276
1277 TOOL_MANAGER* mgr = toolManager();
1278 std::vector<EDA_ITEM*> toSelect;
1279
1280 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
1281 {
1282 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
1283 toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
1284 }
1285
1286 if( toSelect.empty() )
1287 {
1288 ApiResponseStatus e;
1289 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1290 e.set_error_message( fmt::format( "None of the given items exist on the board",
1291 aCtx.Request.board().board_filename() ) );
1292 return tl::unexpected( e );
1293 }
1294
1295 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1296 selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
1297
1299 mgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &toSelect );
1300
1301 COMMIT* commit = getCurrentCommit( aCtx.ClientName );
1302 mgr->PostAPIAction( PCB_ACTIONS::move, commit );
1303
1304 return Empty();
1305}
1306
1307
1309 const HANDLER_CONTEXT<FlipItems>& aCtx )
1310{
1311 auto containerResult = validateItemHeaderDocument( aCtx.Request.header() );
1312
1313 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
1314 {
1315 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1316 ApiResponseStatus e;
1317 e.set_status( ApiStatusCode::AS_UNHANDLED );
1318 return tl::unexpected( e );
1319 }
1320 else if( !containerResult )
1321 {
1322 return tl::unexpected( containerResult.error() );
1323 }
1324
1325 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1326 return tl::unexpected( *busy );
1327
1329 aCtx.Request.direction() );
1330
1331 FlipItemsResponse response;
1332 response.mutable_header()->CopyFrom( aCtx.Request.header() );
1333
1334 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
1335
1336 bool anyModified = false;
1337
1338 for( const types::KIID& id : aCtx.Request.items() )
1339 {
1340 ItemFlipResult* result = response.add_flipped_items();
1341
1342 std::optional<BOARD_ITEM*> optItem = getItemById( KIID( id.value() ) );
1343
1344 if( !optItem )
1345 {
1346 result->mutable_status()->set_code( ItemStatusCode::ISC_NONEXISTENT );
1347 result->mutable_status()->set_error_message(
1348 fmt::format( "an item with UUID {} does not exist", id.value() ) );
1349 continue;
1350 }
1351
1352 BOARD_ITEM* boardItem = *optItem;
1353
1354 static const std::set<KICAD_T> flippableTypes = {
1356 PCB_PAD_T,
1361 PCB_TEXT_T,
1365 PCB_VIA_T,
1366 PCB_ARC_T,
1367 PCB_ZONE_T,
1379 };
1380
1381 KICAD_T itemType = boardItem->Type();
1382
1383 if( !flippableTypes.contains( itemType ) )
1384 {
1385 result->mutable_status()->set_code( ItemStatusCode::ISC_INVALID_TYPE );
1386 result->mutable_status()->set_error_message(
1387 fmt::format( "items of type {} cannot be flipped",
1388 magic_enum::enum_name( itemType ) ) );
1389 continue;
1390 }
1391
1392 commit->Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
1393 boardItem->Flip( boardItem->GetPosition(), flipDirection );
1394 boardItem->Normalize();
1395
1396 // Maybe this should be in FOOTPRINT::Normalize?
1397 if( boardItem->Type() == PCB_FOOTPRINT_T )
1398 static_cast<FOOTPRINT*>( boardItem )->InvalidateComponentClassCache();
1399
1400 anyModified = true;
1401
1402 google::protobuf::Any itemBuf;
1403 boardItem->Serialize( itemBuf );
1404 *result->mutable_item() = std::move( itemBuf );
1405
1406 result->mutable_status()->set_code( ItemStatusCode::ISC_OK );
1407 }
1408
1409 response.set_status( ItemRequestStatus::IRS_OK );
1410
1411 if( anyModified && !m_activeClients.count( aCtx.ClientName ) )
1412 pushCurrentCommit( aCtx.ClientName, _( "Flipped items via API" ) );
1413
1414 return response;
1415}
1416
1417
1420{
1421 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1422
1423 if( !documentValidation )
1424 return tl::unexpected( documentValidation.error() );
1425
1426 SavedDocumentResponse response;
1427 response.mutable_document()->CopyFrom( aCtx.Request.document() );
1428
1429 CLIPBOARD_IO io;
1430 io.SetWriter(
1431 [&]( const wxString& aData )
1432 {
1433 response.set_contents( aData.ToUTF8() );
1434 } );
1435
1436 io.SaveBoard( wxEmptyString, *board(), nullptr );
1437
1438 return response;
1439}
1440
1441
1444{
1445 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SaveSelectionToString" ) )
1446 return tl::unexpected( *headless );
1447
1448 SavedSelectionResponse response;
1449
1450 TOOL_MANAGER* mgr = toolManager();
1451 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1452 PCB_SELECTION& selection = selectionTool->GetSelection();
1453
1454 CLIPBOARD_IO io;
1455 io.SetWriter(
1456 [&]( const wxString& aData )
1457 {
1458 response.set_contents( aData.ToUTF8() );
1459 } );
1460
1461 io.SetBoard( board() );
1462 io.SaveSelection( selection, thisDocumentType() == kiapi::common::types::DOCTYPE_FOOTPRINT );
1463
1464 return response;
1465}
1466
1467
1470{
1471 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1472
1473 if( !documentValidation )
1474 return tl::unexpected( documentValidation.error() );
1475
1476 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1477 return tl::unexpected( *busy );
1478
1479 CreateItemsResponse response;
1480 return response;
1481}
1482
1483
1486{
1487 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetVisibleLayers" ) )
1488 return tl::unexpected( *headless );
1489
1490 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1491
1492 if( !documentValidation )
1493 return tl::unexpected( documentValidation.error() );
1494
1495 BoardLayers response;
1496
1497 for( PCB_LAYER_ID layer : board()->GetVisibleLayers() )
1498 response.add_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
1499
1500 return response;
1501}
1502
1503
1506{
1507 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SetVisibleLayers" ) )
1508 return tl::unexpected( *headless );
1509
1510 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1511
1512 if( !documentValidation )
1513 return tl::unexpected( documentValidation.error() );
1514
1515 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1516 return tl::unexpected( *busy );
1517
1518 LSET visible;
1519 LSET enabled = board()->GetEnabledLayers();
1520
1521 for( int layerIdx : aCtx.Request.layers() )
1522 {
1523 PCB_LAYER_ID layer =
1524 FromProtoEnum<PCB_LAYER_ID>( static_cast<board::types::BoardLayer>( layerIdx ) );
1525
1526 if( enabled.Contains( layer ) )
1527 visible.set( layer );
1528 }
1529
1530 board()->SetVisibleLayers( visible );
1531
1532 PCB_BASE_EDIT_FRAME* editFrame = static_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
1533 editFrame->GetAppearancePanel()->OnBoardChanged();
1534 editFrame->GetCanvas()->SyncLayersVisibility( board() );
1535 editFrame->Refresh();
1536 return Empty();
1537}
1538
1539
1542{
1543 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetActiveLayer" ) )
1544 return tl::unexpected( *headless );
1545
1546 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1547
1548 if( !documentValidation )
1549 return tl::unexpected( documentValidation.error() );
1550
1551 PCB_BASE_EDIT_FRAME* editFrame = static_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
1552
1553 BoardLayerResponse response;
1554 response.set_layer(
1556
1557 return response;
1558}
1559
1560
1563{
1564 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SetActiveLayer" ) )
1565 return tl::unexpected( *headless );
1566
1567 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1568
1569 if( !documentValidation )
1570 return tl::unexpected( documentValidation.error() );
1571
1572 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1573 return tl::unexpected( *busy );
1574
1575 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID>( aCtx.Request.layer() );
1576
1577 if( !board()->GetEnabledLayers().Contains( layer ) )
1578 {
1579 ApiResponseStatus err;
1580 err.set_status( ApiStatusCode::AS_BAD_REQUEST );
1581 err.set_error_message( fmt::format( "Layer {} is not a valid layer for the given board",
1582 magic_enum::enum_name( layer ) ) );
1583 return tl::unexpected( err );
1584 }
1585
1586 PCB_BASE_EDIT_FRAME* editFrame = static_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
1587 editFrame->SetActiveLayer( layer );
1588 return Empty();
1589}
KICOMMON_API types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:47
std::unique_ptr< BOARD_ITEM > CreateGeneratorForType(const wxString &aGeneratorType, BOARD_ITEM_CONTAINER *aContainer)
std::unique_ptr< EDA_ITEM > CreateItemForType(KICAD_T aType, EDA_ITEM *aContainer)
@ ERROR_INSIDE
@ LAYER_CLASS_COUNT
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:228
API_HANDLER_BOARD(std::shared_ptr< BOARD_CONTEXT > aContext, EDA_BASE_FRAME *aFrame=nullptr)
HANDLER_RESULT< commands::ExpandTextVariablesResponse > handleExpandTextVariables(const HANDLER_CONTEXT< commands::ExpandTextVariables > &aCtx)
HANDLER_RESULT< BoardEnabledLayersResponse > handleGetBoardEnabledLayers(const HANDLER_CONTEXT< GetBoardEnabledLayers > &aCtx)
std::unique_ptr< COMMIT > createCommit() override
Override this to create an appropriate COMMIT subclass for the frame in question.
HANDLER_RESULT< commands::SelectionResponse > handleGetSelection(const HANDLER_CONTEXT< commands::GetSelection > &aCtx)
HANDLER_RESULT< commands::SavedSelectionResponse > handleSaveSelectionToString(const HANDLER_CONTEXT< commands::SaveSelectionToString > &aCtx)
HANDLER_RESULT< Empty > handleSetActiveLayer(const HANDLER_CONTEXT< SetActiveLayer > &aCtx)
HANDLER_RESULT< BoardLayers > handleGetVisibleLayers(const HANDLER_CONTEXT< GetVisibleLayers > &aCtx)
TOOL_MANAGER * toolManager() const
GENERATOR_TOOL * ensureGeneratorTool() const
Creates the generator tool if needed (e.g. in headless mode); returns it if available.
HANDLER_RESULT< commands::SelectionResponse > handleAddToSelection(const HANDLER_CONTEXT< commands::AddToSelection > &aCtx)
HANDLER_RESULT< commands::CreateItemsResponse > handleParseAndCreateItemsFromString(const HANDLER_CONTEXT< commands::ParseAndCreateItemsFromString > &aCtx)
HANDLER_RESULT< Empty > handleClearSelection(const HANDLER_CONTEXT< commands::ClearSelection > &aCtx)
std::vector< KICAD_T > parseRequestedItemTypes(const google::protobuf::RepeatedField< int > &aTypes)
HANDLER_RESULT< Empty > handleInteractiveMoveItems(const HANDLER_CONTEXT< InteractiveMoveItems > &aCtx)
HANDLER_RESULT< BoardBoundingBoxResponse > handleGetBoardBoundingBox(const HANDLER_CONTEXT< GetBoardBoundingBox > &aCtx)
HANDLER_RESULT< types::ItemRequestStatus > handleCreateUpdateItemsInternal(bool aCreate, const std::string &aClientName, const types::ItemHeader &aHeader, const google::protobuf::RepeatedPtrField< google::protobuf::Any > &aItems, std::function< void(commands::ItemStatus, google::protobuf::Any)> aItemHandler) override
HANDLER_RESULT< BoardStackupResponse > handleGetStackup(const HANDLER_CONTEXT< GetBoardStackup > &aCtx)
void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage) override
HANDLER_RESULT< commands::GetItemsResponse > handleGetItemsById(const HANDLER_CONTEXT< commands::GetItemsById > &aCtx)
BOARD * board() const
std::optional< EDA_ITEM * > getItemFromDocument(const DocumentSpecifier &aDocument, const KIID &aId) override
void deleteItemsInternal(std::map< KIID, ItemDeletionStatus > &aItemsToDelete, const std::string &aClientName) override
HANDLER_RESULT< commands::SelectionResponse > handleRemoveFromSelection(const HANDLER_CONTEXT< commands::RemoveFromSelection > &aCtx)
HANDLER_RESULT< GraphicsDefaultsResponse > handleGetGraphicsDefaults(const HANDLER_CONTEXT< GetGraphicsDefaults > &aCtx)
HANDLER_RESULT< BoardLayerResponse > handleGetActiveLayer(const HANDLER_CONTEXT< GetActiveLayer > &aCtx)
HANDLER_RESULT< commands::GetBoundingBoxResponse > handleGetBoundingBox(const HANDLER_CONTEXT< commands::GetBoundingBox > &aCtx)
static HANDLER_RESULT< std::unique_ptr< BOARD_ITEM > > createItemForType(KICAD_T aType, BOARD_ITEM_CONTAINER *aContainer)
HANDLER_RESULT< commands::RunActionResponse > handleRunAction(const HANDLER_CONTEXT< commands::RunAction > &aCtx)
std::optional< ApiResponseStatus > checkForHeadless(const std::string &aCommandName) const
HANDLER_RESULT< commands::SavedDocumentResponse > handleSaveDocumentToString(const HANDLER_CONTEXT< commands::SaveDocumentToString > &aCtx)
HANDLER_RESULT< PadShapeAsPolygonResponse > handleGetPadShapeAsPolygon(const HANDLER_CONTEXT< GetPadShapeAsPolygon > &aCtx)
HANDLER_RESULT< Empty > handleFocusOnItems(const HANDLER_CONTEXT< commands::FocusOnItems > &aCtx)
std::shared_ptr< BOARD_CONTEXT > m_context
HANDLER_RESULT< PadstackPresenceResponse > handleCheckPadstackPresenceOnLayers(const HANDLER_CONTEXT< CheckPadstackPresenceOnLayers > &aCtx)
virtual BOARD_ITEM_CONTAINER * getDefaultContainer()
HANDLER_RESULT< Empty > handleSetVisibleLayers(const HANDLER_CONTEXT< SetVisibleLayers > &aCtx)
void regenerateGenerators(GENERATOR_TOOL *aTool, BOARD_COMMIT *aCommit, const std::vector< PCB_GENERATOR * > &aGenerators, std::function< void(const KIID &, commands::ItemStatus)> aResultHandler) const
std::optional< BOARD_ITEM * > getItemById(const KIID &aId) const
HANDLER_RESULT< FlipItemsResponse > handleFlipItems(const HANDLER_CONTEXT< FlipItems > &aCtx)
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.
API_HANDLER_EDITOR(EDA_BASE_FRAME *aFrame=nullptr)
COMMIT * getCurrentCommit(const std::string &aClientName)
virtual void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage)
std::set< std::string > m_activeClients
virtual std::optional< ApiResponseStatus > checkForBusy()
Checks if the editor can accept commands.
virtual types::DocumentType thisDocumentType() const =0
Override this to specify which document type this editor handles.
virtual void onModified()
EDA_BASE_FRAME * m_frame
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
void OnBoardChanged()
Update the panel contents from the application and board models.
BASE_SET & reset(size_t pos)
Definition base_set.h:153
BASE_SET & set(size_t pos)
Definition base_set.h:126
void RevertToCheckpoint(int aCheckpoint) override
Container for design settings for a BOARD object.
bool m_TextUpright[LAYER_CLASS_COUNT]
int m_TextThickness[LAYER_CLASS_COUNT]
int m_LineThickness[LAYER_CLASS_COUNT]
VECTOR2I m_TextSize[LAYER_CLASS_COUNT]
bool m_TextItalic[LAYER_CLASS_COUNT]
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
virtual void CopyFrom(const BOARD_ITEM *aOther)
void ResetUuid()
Definition board_item.h:281
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:267
virtual void Normalize()
Perform any normalization required after a user rotate and/or flip.
Definition board_item.h:498
virtual void Flip(const VECTOR2I &aCentre, FLIP_DIRECTION aFlipDirection)
Flip this object, i.e.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void SetVisibleLayers(const LSET &aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings changes the bit-mask of vis...
Definition board.cpp:1207
const BOX2I GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition board.h:1290
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1174
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2538
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2142
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
void SaveBoard(const wxString &aFileName, BOARD &aBoard, const std::map< std::string, UTF8 > *aProperties=nullptr) override
Write aBoard to a storage file in a format that this PCB_IO implementation knows about or it can be u...
void SaveSelection(const PCB_SELECTION &selected, bool isFootprintEditor)
void SetWriter(std::function< void(const wxString &)> aWriter)
void SetBoard(BOARD *aBoard)
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:68
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
int Checkpoint()
Definition commit.h:134
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
Handles action that are shared between different applications.
The base frame for deriving all KiCad main window classes.
The base class for create windows for drawing purpose.
A set of EDA_ITEMs (i.e., without duplicates).
Definition eda_group.h:43
virtual void FinalizeGroupDeserialization()
Definition eda_group.h:66
void AddItem(EDA_ITEM *aItem)
Add item to group.
Definition eda_group.cpp:58
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual VECTOR2I GetPosition() const
Definition eda_item.h:348
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:270
virtual EDA_GROUP * GetParentGroup() const
Definition eda_item.h:116
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual void SetParentGroup(EDA_GROUP *aGroup)
Definition eda_item.h:115
virtual wxString GetFriendlyName() const
Definition eda_item.cpp:565
virtual void SetParent(EDA_ITEM *aParent)
Definition eda_item.cpp:153
Handle actions specific to filling copper zones.
Definition kiid.h:46
std::string AsStdString() const
Definition kiid.cpp:270
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
Definition pad.h:61
static TOOL_ACTION genRemove
static TOOL_ACTION move
move or drag an item
Common, abstract interface for edit frames.
APPEARANCE_CONTROLS * GetAppearancePanel()
virtual void SetActiveLayer(PCB_LAYER_ID aLayer)
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
virtual PCB_LAYER_ID GetActiveLayer() const
void SyncLayersVisibility(const BOARD *aBoard)
Update "visibility" property of each layer of a given BOARD.
bool IsMandatory() const
A set of BOARD_ITEMs (i.e., without duplicates).
Definition pcb_group.h:51
The selection tool: currently supports:
PCB_SELECTION & GetSelection()
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
int AddItemsToSel(const TOOL_EVENT &aEvent)
int RemoveItemsFromSel(const TOOL_EVENT &aEvent)
void SetReferencePoint(const VECTOR2I &aP)
virtual void Serialize(google::protobuf::Any &aContainer) const
Serializes this object to the given Any message.
virtual bool Deserialize(const google::protobuf::Any &aContainer)
Deserializes the given protobuf message into this object.
Represent a set of closed polygons.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
@ RUN
Tool is invoked after being inactive.
Definition tool_base.h:75
Master controller class:
T * GetTool()
Return the tool of given type or nullptr if there is no such tool registered.
bool RunAction(const std::string &aActionName, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
bool PostAPIAction(const TOOL_ACTION &aAction, COMMIT *aCommit)
bool RunSynchronousAction(const TOOL_ACTION &aAction, COMMIT *aCommit, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
void ResetTools(TOOL_BASE::RESET_REASON aReason)
Reset all tools (i.e.
TOOL_BASE * FindTool(int aId) const
Search for a tool with given ID.
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject, bool aAllowEscapes)
Replace any environment variable & text variable references with their values.
Definition common.cpp:777
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:96
#define _(s)
@ RECURSE
Definition eda_item.h:51
#define GENERATOR_TOOL_NAME
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ Rescue
Definition layer_ids.h:117
@ PCB_LAYER_ID_COUNT
Definition layer_ids.h:167
FLIP_DIRECTION
Definition mirror.h:23
void PackLayerSet(google::protobuf::RepeatedField< int > &aOutput, const LSET &aLayerSet)
void PackBoardStackup(const BOARD &aBoard, BoardStackup &aOut)
KICOMMON_API int UnpackDistance(const types::Distance &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API std::optional< KICAD_T > TypeNameFromAny(const google::protobuf::Any &aMessage)
Definition api_utils.cpp:50
KICOMMON_API ApiResponseStatus MakeResponseStatus(ApiStatusCode aCode, const std::string &aMessage)
Definition api_utils.cpp:39
KICOMMON_API void PackBox2(types::Box2 &aOutput, const BOX2I &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API std::optional< wxString > GeneratorTypeFromAny(const google::protobuf::Any &aMessage)
KICOMMON_API void PackPolyLine(types::PolyLine &aOutput, const SHAPE_LINE_CHAIN &aSlc, const EDA_IU_SCALE &aScale)
STL namespace.
Class to handle a set of BOARD_ITEMs.
bool IsGeneratedTableCell(const EDA_ITEM *aItem)
True for a cell whose text is generated from the board, so only its formatting is the user's.
Definition pcb_table.h:348
std::vector< EDA_ITEM * > EDA_ITEMS
std::string ClientName
Definition api_handler.h:53
RequestMessageType Request
Definition api_handler.h:54
wxString result
Test unit parsing edge cases and error handling.
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ PCB_CONSTRAINT_T
a geometric constraint between board items
Definition typeinfo.h:237
@ 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_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ TYPE_NOT_INIT
Definition typeinfo.h:73
@ 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_3D_BODY_T
class PCB_3D_BODY, a 3D body (extruded or model) on a footprint
Definition typeinfo.h:241
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_MARKER_T
class PCB_MARKER, a marker used to show something
Definition typeinfo.h:91
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_TARGET_T
class PCB_TARGET, a target (graphic item)
Definition typeinfo.h:99
@ PCB_TABLECELL_T
class PCB_TABLECELL, PCB_TEXTBOX for use in tables
Definition typeinfo.h:87
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ 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_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ 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_POINT_T
class PCB_POINT, a 0-dimensional point
Definition typeinfo.h:105
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97