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
44#include <layer_ids.h>
45#include <project.h>
46#include <tool/common_tools.h>
47#include <tool/tool_manager.h>
48#include <tools/pcb_actions.h>
51
52#include <api/common/types/base_types.pb.h>
53
54using namespace kiapi::common::commands;
55using types::CommandStatus;
56
57using types::DocumentType;
58using types::ItemRequestStatus;
59
60
61API_HANDLER_BOARD::API_HANDLER_BOARD( std::shared_ptr<BOARD_CONTEXT> aContext,
62 EDA_BASE_FRAME* aFrame ) :
63 API_HANDLER_EDITOR( aFrame ),
64 m_context( std::move( aContext ) )
65{
66 wxCHECK( m_context, /* void */ );
67
69
71
78
93
96
107}
108
109
110std::optional<ApiResponseStatus> API_HANDLER_BOARD::checkForHeadless(
111 const std::string& aCommandName ) const
112{
113 if( m_frame )
114 return std::nullopt;
115
116 ApiResponseStatus e;
117 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
118 e.set_error_message( fmt::format( "{} is not available in headless mode", aCommandName ) );
119 return e;
120}
121
122
127
128
129void API_HANDLER_BOARD::pushCurrentCommit( const std::string& aClientName,
130 const wxString& aMessage )
131{
132 API_HANDLER_EDITOR::pushCurrentCommit( aClientName, aMessage );
133 onModified();
134}
135
136
137std::unique_ptr<COMMIT> API_HANDLER_BOARD::createCommit()
138{
139 if( m_frame )
140 return std::make_unique<BOARD_COMMIT>( static_cast<EDA_DRAW_FRAME*>( m_frame ) );
141
142 bool isFootprintEditor = thisDocumentType() == kiapi::common::types::DOCTYPE_FOOTPRINT;
143
144 return std::make_unique<BOARD_COMMIT>( toolManager(), !isFootprintEditor, isFootprintEditor );
145}
146
147
148std::optional<BOARD_ITEM*> API_HANDLER_BOARD::getItemById( const KIID& aId ) const
149{
150 BOARD_ITEM* item = board()->ResolveItem( aId, true );
151
152 if( !item )
153 return std::nullopt;
154
155 return item;
156}
157
158
160 BOARD_ITEM_CONTAINER* aContainer )
161{
162 if( !aContainer )
163 {
164 ApiResponseStatus e;
165 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
166 e.set_error_message( "Tried to create an item in a null container" );
167 return tl::unexpected( e );
168 }
169
170 if( aType == PCB_PAD_T && !dynamic_cast<FOOTPRINT*>( aContainer ) )
171 {
172 ApiResponseStatus e;
173 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
174 e.set_error_message( fmt::format( "Tried to create a pad in {}, which is not a footprint",
175 aContainer->GetFriendlyName().ToStdString() ) );
176 return tl::unexpected( e );
177 }
178 else if( aType == PCB_FOOTPRINT_T && !dynamic_cast<BOARD*>( aContainer ) )
179 {
180 ApiResponseStatus e;
181 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
182 e.set_error_message( fmt::format( "Tried to create a footprint in {}, which is not a board",
183 aContainer->GetFriendlyName().ToStdString() ) );
184 return tl::unexpected( e );
185 }
186
187 if( dynamic_cast<FOOTPRINT*>( aContainer ) )
188 {
189 static const std::set<KICAD_T> s_footprintItemTypes = {
194 };
195
196 if( !s_footprintItemTypes.contains( aType ) )
197 {
198 ApiResponseStatus e;
199 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
200 e.set_error_message( fmt::format( "items of type {} cannot be created in a footprint",
201 magic_enum::enum_name( aType ) ) );
202 return tl::unexpected( e );
203 }
204 }
205
206 std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
207
208 if( !created )
209 {
210 ApiResponseStatus e;
211 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
212 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
213 magic_enum::enum_name( aType ) ) );
214 return tl::unexpected( e );
215 }
216
217 return created;
218}
219
220
221void API_HANDLER_BOARD::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
222 const std::string& aClientName )
223{
224 BOARD* board = this->board();
225 std::vector<BOARD_ITEM*> validatedItems;
226
227 for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
228 {
229 if( BOARD_ITEM* item = board->ResolveItem( pair.first, true ) )
230 {
231 // A footprint without its mandatory fields is not a state the editor can load or
232 // render; the const field accessors return nullptr and callers dereference them
233 if( item->Type() == PCB_FIELD_T && static_cast<PCB_FIELD*>( item )->IsMandatory() )
234 {
235 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_IMMUTABLE;
236 continue;
237 }
238
239 validatedItems.push_back( item );
240 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_OK;
241 }
242
243 // Note: we don't currently support locking items from API modification, but here is where
244 // to add it in the future (and return IDS_IMMUTABLE)
245 }
246
247 COMMIT* commit = getCurrentCommit( aClientName );
248
249 for( BOARD_ITEM* item : validatedItems )
250 {
251 if( item->Type() == PCB_TABLECELL_T )
252 {
253 // Cells are owned by their table; the commit removal path doesn't handle them.
254 // Match the GUI delete: clear the cell contents (generated-table cells have no user text).
255 if( IsGeneratedTableCell( item ) )
256 continue;
257
258 commit->Modify( item );
259 static_cast<PCB_TABLECELL*>( item )->SetText( wxEmptyString );
260 }
261 else if( item->Type() == PCB_GENERATOR_T )
262 {
263 TOOL_MANAGER* mgr = toolManager();
264
265 if( ensureGeneratorTool() )
266 {
268 static_cast<PCB_GENERATOR*>( item ) );
269 }
270 else
271 {
272 commit->Remove( item );
273 }
274 }
275 else
276 {
277 commit->Remove( item );
278 }
279 }
280
281 if( !m_activeClients.count( aClientName ) )
282 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
283}
284
285
287{
288 TOOL_MANAGER* mgr = toolManager();
289
290 if( !mgr->FindTool( GENERATOR_TOOL_NAME ) )
291 {
292 mgr->RegisterTool( new GENERATOR_TOOL );
294 }
295
296 return mgr->GetTool<GENERATOR_TOOL>();
297}
298
299
301 const std::vector<PCB_GENERATOR*>& aGenerators,
302 std::function<void( const KIID&, ItemStatus )> aResultHandler ) const
303{
304 BOARD* board = this->board();
305
306 for( PCB_GENERATOR* generator : aGenerators )
307 {
308 ItemStatus status;
309
310 try
311 {
312 generator->EditStart( aTool, board, aCommit );
313 bool ok = generator->Update( aTool, board, aCommit );
314 generator->EditFinish( aTool, board, aCommit );
315
316 status.set_code( ok ? ItemStatusCode::ISC_OK : ItemStatusCode::ISC_INVALID_DATA );
317
318 if( !ok )
319 status.set_error_message( "the generator reported an update failure" );
320 }
321 catch( const std::exception& exc )
322 {
323 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
324 status.set_error_message( fmt::format( "regeneration exception: {}", exc.what() ) );
325 }
326
327 if( aResultHandler )
328 aResultHandler( generator->m_Uuid, status );
329 }
330}
331
332
334 const DocumentSpecifier& aDocument, const KIID& aId )
335{
336 if( !validateDocument( aDocument ) )
337 return std::nullopt;
338
339 return getItemById( aId );
340}
341
342
344 const std::string& aClientName,
345 const types::ItemHeader &aHeader,
346 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
347 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
348{
349 ApiResponseStatus e;
350
351 auto containerResult = validateItemHeaderDocument( aHeader );
352
353 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
354 {
355 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
356 e.set_status( ApiStatusCode::AS_UNHANDLED );
357 return tl::unexpected( e );
358 }
359 else if( !containerResult )
360 {
361 e.CopyFrom( containerResult.error() );
362 return tl::unexpected( e );
363 }
364
365 BOARD* board = this->board();
367
368 if( containerResult->has_value() )
369 {
370 const KIID& containerId = **containerResult;
371 std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
372
373 if( optItem )
374 {
375 container = dynamic_cast<BOARD_ITEM_CONTAINER*>( *optItem );
376
377 if( !container )
378 {
379 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
380 e.set_error_message( fmt::format( "The requested container {} is not a valid board item container",
381 containerId.AsStdString() ) );
382 return tl::unexpected( e );
383 }
384 }
385 else
386 {
387 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
388 e.set_error_message( fmt::format( "The requested container {} does not exist in this document",
389 containerId.AsStdString() ) );
390 return tl::unexpected( e );
391 }
392 }
393
394 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aClientName ) );
395 int commitCheckpoint = commit->Checkpoint();
396
397 for( const google::protobuf::Any& anyItem : aItems )
398 {
399 ItemStatus status;
400 std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
401
402 if( !type )
403 {
404 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
405 status.set_error_message( fmt::format( "Could not decode a valid type from {}", anyItem.type_url() ) );
406 aItemHandler( status, anyItem );
407 continue;
408 }
409
410 if( type == PCB_DIMENSION_T )
411 {
412 board::types::Dimension dimension;
413 anyItem.UnpackTo( &dimension );
414
415 switch( dimension.dimension_style_case() )
416 {
417 case board::types::Dimension::kAligned: type = PCB_DIM_ALIGNED_T; break;
418 case board::types::Dimension::kOrthogonal: type = PCB_DIM_ORTHOGONAL_T; break;
419 case board::types::Dimension::kRadial: type = PCB_DIM_RADIAL_T; break;
420 case board::types::Dimension::kLeader: type = PCB_DIM_LEADER_T; break;
421 case board::types::Dimension::kCenter: type = PCB_DIM_CENTER_T; break;
422 case board::types::Dimension::DIMENSION_STYLE_NOT_SET: break;
423 }
424 }
425
426 if( *type == PCB_SHAPE_T )
427 {
428 board::types::BoardGraphicShape shape;
429 anyItem.UnpackTo( &shape );
430
431 if( shape.has_pad_custom_shape_options() )
432 {
433 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
434 status.set_error_message( "pad_custom_shape_options are only valid on shapes inside a pad's "
435 "padstack custom shapes" );
436 aItemHandler( status, anyItem );
437 continue;
438 }
439 }
440
442 [&]() -> HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>
443 {
444 if( *type == PCB_GENERATOR_T )
445 {
446 std::optional<wxString> generatorType = GeneratorTypeFromAny( anyItem );
447
448 if( !generatorType )
449 {
450 ItemStatus genStatus;
451 genStatus.set_code( ItemStatusCode::ISC_INVALID_TYPE );
452 genStatus.set_error_message( fmt::format( "could not decode a generator from {}",
453 anyItem.type_url() ) );
454 aItemHandler( genStatus, anyItem );
455 return HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>( std::unique_ptr<BOARD_ITEM>() );
456 }
457
458 std::unique_ptr<BOARD_ITEM> genItem = CreateGeneratorForType( *generatorType, container );
459
460 if( !genItem )
461 {
462 ItemStatus genStatus;
463 genStatus.set_code( ItemStatusCode::ISC_INVALID_TYPE );
464 genStatus.set_error_message( fmt::format( "generator type {} is not registered",
465 generatorType->ToStdString() ) );
466 aItemHandler( genStatus, anyItem );
467 return HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>( std::unique_ptr<BOARD_ITEM>() );
468 }
469
470 return HANDLER_RESULT<std::unique_ptr<BOARD_ITEM>>( std::move( genItem ) );
471 }
472
473 return createItemForType( *type, container );
474 }();
475
476 if( !creationResult || !creationResult.value() )
477 {
478 if( !creationResult )
479 {
480 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
481 status.set_error_message( creationResult.error().error_message() );
482 aItemHandler( status, anyItem );
483 }
484
485 continue;
486 }
487
488 std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
489
490 bool unpacked = false;
491
492 if( item->Type() == PCB_GENERATOR_T )
493 unpacked = item->Deserialize( anyItem );
494 else if( PCB_GROUP* group = dynamic_cast<PCB_GROUP*>( item.get() ) )
495 unpacked = group->DeserializeGroup( anyItem, commit );
496 else
497 unpacked = item->Deserialize( anyItem );
498
499 if( !unpacked )
500 {
501 commit->RevertToCheckpoint( commitCheckpoint );
502
503 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
504 e.set_error_message( fmt::format( "could not unpack {} from request",
505 item->GetClass().ToStdString() ) );
506 return tl::unexpected( e );
507 }
508
509 if( std::vector<wxString> removed = item->RemoveConflictingCustomProperties(); !removed.empty() )
510 {
511 auto as_str =
512 []( const wxString& aIn )
513 {
514 return std::string( aIn.ToUTF8() );
515 };
516
517 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
518 status.set_error_message( fmt::format( "Invalid custom properties for item {}: property name(s) '{}' "
519 "already in use",
520 item->m_Uuid.AsStdString(),
521 fmt::join( std::views::transform( removed, as_str ), ", " ) ) );
522
523 aItemHandler( status, anyItem );
524 continue;
525 }
526
527 std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
528
529 if( aCreate && optItem )
530 {
531 status.set_code( ItemStatusCode::ISC_EXISTING );
532 status.set_error_message( fmt::format( "an item with UUID {} already exists",
533 item->m_Uuid.AsStdString() ) );
534 aItemHandler( status, anyItem );
535 continue;
536 }
537 else if( !aCreate && !optItem )
538 {
539 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
540 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
541 item->m_Uuid.AsStdString() ) );
542 aItemHandler( status, anyItem );
543 continue;
544 }
545
546 if( !aCreate && ( *optItem )->Type() != item->Type() )
547 {
548 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
549 status.set_error_message( fmt::format( "item {} is of type {}, not {}",
550 item->m_Uuid.AsStdString(),
551 magic_enum::enum_name( ( *optItem )->Type() ),
552 magic_enum::enum_name( item->Type() ) ) );
553 aItemHandler( status, anyItem );
554 continue;
555 }
556
557 if( aCreate
558 && !item->FitsEnabledLayers( board->GetEnabledLayers(), board->GetCopperLayerCount() ) )
559 {
560 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
561 status.set_error_message( "attempted to add item with no overlapping layers with the board" );
562 aItemHandler( status, anyItem );
563 continue;
564 }
565
566 status.set_code( ItemStatusCode::ISC_OK );
567 google::protobuf::Any newItem;
568
569 if( item->Type() == PCB_GROUP_T )
570 static_cast<PCB_GROUP*>( item.get() )->FinalizeGroupDeserialization();
571
572 if( aCreate )
573 {
574 if( item->Type() == PCB_TABLECELL_T )
575 {
576 PCB_TABLE* table = dynamic_cast<PCB_TABLE*>( container );
577
578 if( !table )
579 {
580 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
581 status.set_error_message( "a table cell must target a table container" );
582 aItemHandler( status, anyItem );
583 continue;
584 }
585
586 PCB_TABLECELL* cell = static_cast<PCB_TABLECELL*>( item.release() );
587 commit->Modify( table );
588 table->AddCell( cell );
589 cell->Serialize( newItem );
590 }
591 else
592 {
593 if( item->Type() == PCB_FOOTPRINT_T || item->Type() == PCB_TABLE_T )
594 {
595 // Ensure children have unique identifiers; in case the API client created
596 // this new item by cloning an existing one and only changing the parent UUID.
597 item->RunOnChildren(
598 []( BOARD_ITEM* aChild )
599 {
600 aChild->ResetUuid();
601 },
602 RECURSE );
603 }
604
605 BOARD_ITEM* newBoardItem = item.get();
606 item->Serialize( newItem );
607 commit->Add( item.release() );
608
609 if( newBoardItem->Type() == PCB_GENERATOR_T )
610 {
611 if( GENERATOR_TOOL* genTool = ensureGeneratorTool() )
612 regenerateGenerators( genTool, commit, { static_cast<PCB_GENERATOR*>( newBoardItem ) }, {} );
613
614 // The regeneration may have added members
615 newBoardItem->Serialize( newItem );
616 }
617 }
618 }
619 else
620 {
621 BOARD_ITEM* boardItem = *optItem;
622
623 if( boardItem->Type() == PCB_GENERATOR_T )
624 {
625 commit->Modify( boardItem );
626 boardItem->Deserialize( anyItem );
627
628 static_cast<PCB_GROUP*>( boardItem )->FinalizeGroupDeserialization();
629
630 if( GENERATOR_TOOL* genTool = ensureGeneratorTool() )
631 regenerateGenerators( genTool, commit, { static_cast<PCB_GENERATOR*>( boardItem ) }, {} );
632
633 boardItem->Serialize( newItem );
634 }
635
636 // Footprints can't be modified by CopyFrom at the moment because the commit system
637 // doesn't currently know what to do with a footprint that has had its children
638 // replaced with other children; which results in things like the view not having its
639 // cached geometry for footprint children updated when you move a footprint around.
640 // And also, groups are special because they can contain any item type, so we
641 // can't use CopyFrom on them either.
642 else if( boardItem->Type() == PCB_FOOTPRINT_T || boardItem->Type() == PCB_GROUP_T )
643 {
644 // Save group membership before removal, since Remove() severs the relationship
645 PCB_GROUP* parentGroup = dynamic_cast<PCB_GROUP*>( boardItem->GetParentGroup() );
646
647 commit->Remove( boardItem );
648 item->Serialize( newItem );
649
650 BOARD_ITEM* newBoardItem = item.release();
651 commit->Add( newBoardItem );
652
653 // Restore group membership for the newly added item
654 if( parentGroup )
655 parentGroup->AddItem( newBoardItem );
656 }
657 else
658 {
659 EDA_GROUP* parentGroup = boardItem->GetParentGroup();
660 BOARD_ITEM* parent = boardItem->GetParent();
661
662 commit->Modify( boardItem );
663 boardItem->CopyFrom( item.get() );
664
665 if( parentGroup )
666 boardItem->SetParentGroup( parentGroup );
667
668 if( parent )
669 boardItem->SetParent( parent );
670
671 boardItem->Serialize( newItem );
672 }
673 }
674
675 aItemHandler( status, newItem );
676 }
677
678 if( !m_activeClients.count( aClientName ) )
679 {
680 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
681 : _( "Modified items via API" ) );
682 }
683
684 return ItemRequestStatus::IRS_OK;
685}
686
687
689 const google::protobuf::RepeatedField<int>& aTypes )
690{
691 std::vector<KICAD_T> types;
692
693 for( int typeRaw : aTypes )
694 {
695 auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
696 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
697
698 if( type != TYPE_NOT_INIT )
699 types.emplace_back( type );
700 }
701
702 return types;
703}
704
705
707 const HANDLER_CONTEXT<RunAction>& aCtx )
708{
709 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RunAction" ) )
710 return tl::unexpected( *headless );
711
712 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
713 return tl::unexpected( *busy );
714
715 RunActionResponse response;
716
717 if( toolManager()->RunAction( aCtx.Request.action(), true ) )
718 response.set_status( RunActionStatus::RAS_OK );
719 else
720 response.set_status( RunActionStatus::RAS_INVALID );
721
722 return response;
723}
724
725
728{
729 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
730 {
731 ApiResponseStatus e;
732 e.set_status( ApiStatusCode::AS_UNHANDLED );
733 return tl::unexpected( e );
734 }
735
736 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
737 return tl::unexpected( *busy );
738
739 GetItemsResponse response;
740
741 std::vector<BOARD_ITEM*> items;
742
743 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
744 {
745 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
746 items.emplace_back( *item );
747 }
748
749 if( items.empty() )
750 {
751 ApiResponseStatus e;
752 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
753 e.set_error_message( "none of the requested IDs were found or valid" );
754 return tl::unexpected( e );
755 }
756
757 for( const BOARD_ITEM* item : items )
758 {
759 google::protobuf::Any itemBuf;
760 item->Serialize( itemBuf );
761 response.mutable_items()->Add( std::move( itemBuf ) );
762 }
763
764 response.set_status( ItemRequestStatus::IRS_OK );
765 return response;
766}
767
768
771{
772 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetSelection" ) )
773 return tl::unexpected( *headless );
774
775 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
776 {
777 ApiResponseStatus e;
778 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
779 e.set_status( ApiStatusCode::AS_UNHANDLED );
780 return tl::unexpected( e );
781 }
782
783 std::set<KICAD_T> filter;
784
785 for( KICAD_T type : parseRequestedItemTypes( aCtx.Request.types() ) )
786 {
787 filter.insert( type );
788
789 if( type == PCB_DIMENSION_T )
790 {
791 filter.insert( PCB_DIM_ALIGNED_T );
793 filter.insert( PCB_DIM_RADIAL_T );
794 filter.insert( PCB_DIM_LEADER_T );
795 filter.insert( PCB_DIM_CENTER_T );
796 }
797 }
798
799 TOOL_MANAGER* mgr = toolManager();
800 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
801
802 SelectionResponse response;
803
804 for( EDA_ITEM* item : selectionTool->GetSelection() )
805 {
806 if( filter.empty() || filter.contains( item->Type() ) )
807 item->Serialize( *response.add_items() );
808 }
809
810 return response;
811}
812
813
816{
817 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "ClearSelection" ) )
818 return tl::unexpected( *headless );
819
820 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
821 {
822 ApiResponseStatus e;
823 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
824 e.set_status( ApiStatusCode::AS_UNHANDLED );
825 return tl::unexpected( e );
826 }
827
828 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
829 return tl::unexpected( *busy );
830
831 TOOL_MANAGER* mgr = toolManager();
833 m_frame->Refresh();
834
835 return Empty();
836}
837
838
841{
842 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "AddToSelection" ) )
843 return tl::unexpected( *headless );
844
845 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
846 {
847 ApiResponseStatus e;
848 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
849 e.set_status( ApiStatusCode::AS_UNHANDLED );
850 return tl::unexpected( e );
851 }
852
853 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
854 return tl::unexpected( *busy );
855
856 TOOL_MANAGER* mgr = toolManager();
857 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
858
859 std::vector<EDA_ITEM*> toAdd;
860
861 for( const types::KIID& id : aCtx.Request.items() )
862 {
863 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
864 toAdd.emplace_back( *item );
865 }
866
867 selectionTool->AddItemsToSel( &toAdd );
868 m_frame->Refresh();
869
870 SelectionResponse response;
871
872 for( EDA_ITEM* item : selectionTool->GetSelection() )
873 item->Serialize( *response.add_items() );
874
875 return response;
876}
877
878
880{
881 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "FocusOnItems" ) )
882 return tl::unexpected( *headless );
883
884 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
885 return tl::unexpected( documentValidation.error() );
886
887 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
888 return tl::unexpected( *busy );
889
890 if( aCtx.Request.items().empty() )
891 return tl::unexpected( MakeResponseStatus( AS_BAD_REQUEST, "no items were given to focus on" ) );
892
893 std::optional<BOX2I> bbox;
894 std::vector<std::string> missing;
895
896 for( const types::KIID& idMsg : aCtx.Request.items() )
897 {
898 std::optional<BOARD_ITEM*> item = getItemById( KIID( idMsg.value() ) );
899
900 if( !item )
901 {
902 missing.push_back( idMsg.value() );
903 continue;
904 }
905
906 if( bbox )
907 bbox->Merge( ( *item )->GetBoundingBox() );
908 else
909 bbox = ( *item )->GetBoundingBox();
910 }
911
912 if( !missing.empty() )
913 {
914 return tl::unexpected(
915 MakeResponseStatus( AS_BAD_REQUEST, fmt::format( "the items {} are not in the requested document",
916 fmt::join( missing, ", " ) ) ) );
917 }
918
919 if( aCtx.Request.has_margin() )
920 bbox->Inflate( UnpackDistance( aCtx.Request.margin() ) );
921
922 toolManager()->GetTool<COMMON_TOOLS>()->ZoomFitBox( *bbox );
923
924 return Empty();
925}
926
927
930{
931 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RemoveFromSelection" ) )
932 return tl::unexpected( *headless );
933
934 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
935 {
936 ApiResponseStatus e;
937 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
938 e.set_status( ApiStatusCode::AS_UNHANDLED );
939 return tl::unexpected( e );
940 }
941
942 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
943 return tl::unexpected( *busy );
944
945 TOOL_MANAGER* mgr = toolManager();
946 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
947
948 std::vector<EDA_ITEM*> toRemove;
949
950 for( const types::KIID& id : aCtx.Request.items() )
951 {
952 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
953 toRemove.emplace_back( *item );
954 }
955
956 selectionTool->RemoveItemsFromSel( &toRemove );
957 m_frame->Refresh();
958
959 SelectionResponse response;
960
961 for( EDA_ITEM* item : selectionTool->GetSelection() )
962 item->Serialize( *response.add_items() );
963
964 return response;
965}
966
967
970{
971 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
972
973 if( !documentValidation )
974 return tl::unexpected( documentValidation.error() );
975
976 BoardStackupResponse response;
977
978 board::PackBoardStackup( *board(), *response.mutable_stackup() );
979
980 return response;
981}
982
983
986{
987 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
988
989 if( !documentValidation )
990 return tl::unexpected( documentValidation.error() );
991
992 BoardEnabledLayersResponse response;
993
994 BOARD* board = this->board();
995 int copperLayerCount = board->GetCopperLayerCount();
996
997 response.set_copper_layer_count( copperLayerCount );
998
999 LSET enabled = board->GetEnabledLayers();
1000
1001 // The Rescue layer is an internal detail and should be hidden from the API
1002 enabled.reset( Rescue );
1003
1004 // Just in case this is out of sync; the API should always return the expected copper layers
1005 enabled |= LSET::AllCuMask( copperLayerCount );
1006
1007 board::PackLayerSet( *response.mutable_layers(), enabled );
1008
1009 return response;
1010}
1011
1012
1015{
1016 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1017
1018 if( !documentValidation )
1019 return tl::unexpected( documentValidation.error() );
1020
1022 GraphicsDefaultsResponse response;
1023
1024 // TODO: This should change to be an enum class
1025 constexpr std::array<kiapi::board::BoardLayerClass, LAYER_CLASS_COUNT> classOrder = {
1026 kiapi::board::BLC_SILKSCREEN,
1027 kiapi::board::BLC_COPPER,
1028 kiapi::board::BLC_EDGES,
1029 kiapi::board::BLC_COURTYARD,
1030 kiapi::board::BLC_FABRICATION,
1031 kiapi::board::BLC_OTHER
1032 };
1033
1034 for( int i = 0; i < LAYER_CLASS_COUNT; ++i )
1035 {
1036 kiapi::board::BoardLayerGraphicsDefaults* l = response.mutable_defaults()->add_layers();
1037
1038 l->set_layer( classOrder[i] );
1039 l->mutable_line_thickness()->set_value_nm( bds.m_LineThickness[i] );
1040
1041 kiapi::common::types::TextAttributes* text = l->mutable_text();
1042 text->mutable_size()->set_x_nm( bds.m_TextSize[i].x );
1043 text->mutable_size()->set_y_nm( bds.m_TextSize[i].y );
1044 text->mutable_stroke_width()->set_value_nm( bds.m_TextThickness[i] );
1045 text->set_italic( bds.m_TextItalic[i] );
1046 text->set_keep_upright( bds.m_TextUpright[i] );
1047 }
1048
1049 return response;
1050}
1051
1052
1055{
1056 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1057 {
1058 ApiResponseStatus e;
1059 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1060 e.set_status( ApiStatusCode::AS_UNHANDLED );
1061 return tl::unexpected( e );
1062 }
1063
1064 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1065 return tl::unexpected( *busy );
1066
1067 GetBoundingBoxResponse response;
1068 bool includeText = aCtx.Request.mode() == BoundingBoxMode::BBM_ITEM_AND_CHILD_TEXT;
1069
1070 for( const types::KIID& idMsg : aCtx.Request.items() )
1071 {
1072 KIID id( idMsg.value() );
1073 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1074
1075 if( !optItem )
1076 continue;
1077
1078 BOARD_ITEM* item = *optItem;
1079 BOX2I bbox;
1080
1081 if( item->Type() == PCB_FOOTPRINT_T )
1082 bbox = static_cast<FOOTPRINT*>( item )->GetBoundingBox( includeText );
1083 else
1084 bbox = item->GetBoundingBox();
1085
1086 response.add_items()->set_value( idMsg.value() );
1087 PackBox2( *response.add_boxes(), bbox );
1088 }
1089
1090 return response;
1091}
1092
1093
1096{
1097 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1098 return tl::unexpected( documentValidation.error() );
1099
1100 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1101 return tl::unexpected( *busy );
1102
1103 BoardBoundingBoxResponse response;
1104 BOX2I bbox;
1105
1106 switch( aCtx.Request.mode() )
1107 {
1108 case BoardBoundingBoxMode::BBBM_ALL_ITEMS: bbox = board()->ComputeBoundingBox( false, false ); break;
1109 case BoardBoundingBoxMode::BBBM_PHYSICAL_LAYERS_ONLY: bbox = board()->ComputeBoundingBox( false, true ); break;
1110 case BoardBoundingBoxMode::BBBM_BOARD_EDGES_ONLY: bbox = board()->GetBoardEdgesBoundingBox(); break;
1111 default:
1112 return tl::unexpected( MakeResponseStatus( AS_BAD_REQUEST, "invalid bounding box mode requested" ) );
1113 }
1114
1115 PackBox2( *response.mutable_box(), bbox );
1116
1117 return response;
1118}
1119
1120
1123{
1124 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1125 !documentValidation )
1126 {
1127 return tl::unexpected( documentValidation.error() );
1128 }
1129
1130 PadShapeAsPolygonResponse response;
1132
1133 for( const types::KIID& padRequest : aCtx.Request.pads() )
1134 {
1135 KIID id( padRequest.value() );
1136 std::optional<BOARD_ITEM*> optPad = getItemById( id );
1137
1138 if( !optPad || ( *optPad )->Type() != PCB_PAD_T )
1139 continue;
1140
1141 response.add_pads()->set_value( padRequest.value() );
1142
1143 PAD* pad = static_cast<PAD*>( *optPad );
1144 SHAPE_POLY_SET poly;
1145 pad->TransformShapeToPolygon( poly, pad->Padstack().EffectiveLayerFor( layer ), 0,
1146 pad->GetMaxError(), ERROR_INSIDE );
1147
1148 types::PolygonWithHoles* polyMsg = response.mutable_polygons()->Add();
1149 PackPolyLine( *polyMsg->mutable_outline(), poly.COutline( 0 ) );
1150 }
1151
1152 return response;
1153}
1154
1155
1158{
1159 using board::types::BoardLayer;
1160
1161 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1162 !documentValidation )
1163 {
1164 return tl::unexpected( documentValidation.error() );
1165 }
1166
1167 PadstackPresenceResponse response;
1168
1169 LSET layers;
1170
1171 for( const int layer : aCtx.Request.layers() )
1172 {
1173 PCB_LAYER_ID pcbLayer = FromProtoEnum<PCB_LAYER_ID, BoardLayer>( static_cast<BoardLayer>( layer ) );
1174
1175 if( pcbLayer < 0 || pcbLayer >= PCB_LAYER_ID_COUNT )
1176 continue;
1177
1178 layers.set( pcbLayer );
1179 }
1180
1181 for( const types::KIID& padRequest : aCtx.Request.items() )
1182 {
1183 KIID id( padRequest.value() );
1184 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1185
1186 if( !optItem )
1187 continue;
1188
1189 switch( ( *optItem )->Type() )
1190 {
1191 case PCB_PAD_T:
1192 {
1193 PAD* pad = static_cast<PAD*>( *optItem );
1194
1195 for( PCB_LAYER_ID layer : layers )
1196 {
1197 PadstackPresenceEntry* entry = response.add_entries();
1198 entry->mutable_item()->set_value( pad->m_Uuid.AsStdString() );
1199 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1200 entry->set_presence( pad->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1201 }
1202
1203 break;
1204 }
1205
1206 case PCB_VIA_T:
1207 {
1208 PCB_VIA* via = static_cast<PCB_VIA*>( *optItem );
1209
1210 for( PCB_LAYER_ID layer : layers )
1211 {
1212 PadstackPresenceEntry* entry = response.add_entries();
1213 entry->mutable_item()->set_value( via->m_Uuid.AsStdString() );
1214 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1215 entry->set_presence( via->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1216 }
1217
1218 break;
1219 }
1220
1221 default:
1222 break;
1223 }
1224 }
1225
1226 return response;
1227}
1228
1229
1232{
1233 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1234
1235 if( !documentValidation )
1236 return tl::unexpected( documentValidation.error() );
1237
1238 ExpandTextVariablesResponse reply;
1239 BOARD* board = this->board();
1240
1241 std::function<bool( wxString* )> textResolver =
1242 [&]( wxString* token ) -> bool
1243 {
1244 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1245 return board->ResolveTextVar( token, 0 );
1246 };
1247
1248 for( const std::string& textMsg : aCtx.Request.text() )
1249 {
1250 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver, INTERNAL );
1251
1252 if( aCtx.Request.expand_env_vars() )
1253 text = ExpandEnvVarSubstitutions( text, board->GetProject() );
1254
1255 reply.add_text( text.ToUTF8() );
1256 }
1257
1258 return reply;
1259}
1260
1261
1264{
1265 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "InteractiveMoveItems" ) )
1266 return tl::unexpected( *headless );
1267
1268 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1269
1270 if( !documentValidation )
1271 return tl::unexpected( documentValidation.error() );
1272
1273 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1274 return tl::unexpected( *busy );
1275
1276 TOOL_MANAGER* mgr = toolManager();
1277 std::vector<EDA_ITEM*> toSelect;
1278
1279 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
1280 {
1281 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
1282 toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
1283 }
1284
1285 if( toSelect.empty() )
1286 {
1287 ApiResponseStatus e;
1288 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1289 e.set_error_message( fmt::format( "None of the given items exist on the board",
1290 aCtx.Request.board().board_filename() ) );
1291 return tl::unexpected( e );
1292 }
1293
1294 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1295 selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
1296
1298 mgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &toSelect );
1299
1300 COMMIT* commit = getCurrentCommit( aCtx.ClientName );
1301 mgr->PostAPIAction( PCB_ACTIONS::move, commit );
1302
1303 return Empty();
1304}
1305
1306
1308 const HANDLER_CONTEXT<FlipItems>& aCtx )
1309{
1310 auto containerResult = validateItemHeaderDocument( aCtx.Request.header() );
1311
1312 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
1313 {
1314 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1315 ApiResponseStatus e;
1316 e.set_status( ApiStatusCode::AS_UNHANDLED );
1317 return tl::unexpected( e );
1318 }
1319 else if( !containerResult )
1320 {
1321 return tl::unexpected( containerResult.error() );
1322 }
1323
1324 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1325 return tl::unexpected( *busy );
1326
1328 aCtx.Request.direction() );
1329
1330 FlipItemsResponse response;
1331 response.mutable_header()->CopyFrom( aCtx.Request.header() );
1332
1333 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
1334
1335 bool anyModified = false;
1336
1337 for( const types::KIID& id : aCtx.Request.items() )
1338 {
1339 ItemFlipResult* result = response.add_flipped_items();
1340
1341 std::optional<BOARD_ITEM*> optItem = getItemById( KIID( id.value() ) );
1342
1343 if( !optItem )
1344 {
1345 result->mutable_status()->set_code( ItemStatusCode::ISC_NONEXISTENT );
1346 result->mutable_status()->set_error_message(
1347 fmt::format( "an item with UUID {} does not exist", id.value() ) );
1348 continue;
1349 }
1350
1351 BOARD_ITEM* boardItem = *optItem;
1352
1353 static const std::set<KICAD_T> flippableTypes = {
1355 PCB_PAD_T,
1360 PCB_TEXT_T,
1364 PCB_VIA_T,
1365 PCB_ARC_T,
1366 PCB_ZONE_T,
1378 };
1379
1380 KICAD_T itemType = boardItem->Type();
1381
1382 if( !flippableTypes.contains( itemType ) )
1383 {
1384 result->mutable_status()->set_code( ItemStatusCode::ISC_INVALID_TYPE );
1385 result->mutable_status()->set_error_message(
1386 fmt::format( "items of type {} cannot be flipped",
1387 magic_enum::enum_name( itemType ) ) );
1388 continue;
1389 }
1390
1391 commit->Modify( boardItem, nullptr, RECURSE_MODE::RECURSE );
1392 boardItem->Flip( boardItem->GetPosition(), flipDirection );
1393 boardItem->Normalize();
1394
1395 // Maybe this should be in FOOTPRINT::Normalize?
1396 if( boardItem->Type() == PCB_FOOTPRINT_T )
1397 static_cast<FOOTPRINT*>( boardItem )->InvalidateComponentClassCache();
1398
1399 anyModified = true;
1400
1401 google::protobuf::Any itemBuf;
1402 boardItem->Serialize( itemBuf );
1403 *result->mutable_item() = std::move( itemBuf );
1404
1405 result->mutable_status()->set_code( ItemStatusCode::ISC_OK );
1406 }
1407
1408 response.set_status( ItemRequestStatus::IRS_OK );
1409
1410 if( anyModified && !m_activeClients.count( aCtx.ClientName ) )
1411 pushCurrentCommit( aCtx.ClientName, _( "Flipped items via API" ) );
1412
1413 return response;
1414}
1415
1416
1419{
1420 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1421
1422 if( !documentValidation )
1423 return tl::unexpected( documentValidation.error() );
1424
1425 SavedDocumentResponse response;
1426 response.mutable_document()->CopyFrom( aCtx.Request.document() );
1427
1428 CLIPBOARD_IO io;
1429 io.SetWriter(
1430 [&]( const wxString& aData )
1431 {
1432 response.set_contents( aData.ToUTF8() );
1433 } );
1434
1435 io.SaveBoard( wxEmptyString, *board(), nullptr );
1436
1437 return response;
1438}
1439
1440
1443{
1444 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SaveSelectionToString" ) )
1445 return tl::unexpected( *headless );
1446
1447 SavedSelectionResponse response;
1448
1449 TOOL_MANAGER* mgr = toolManager();
1450 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1451 PCB_SELECTION& selection = selectionTool->GetSelection();
1452
1453 CLIPBOARD_IO io;
1454 io.SetWriter(
1455 [&]( const wxString& aData )
1456 {
1457 response.set_contents( aData.ToUTF8() );
1458 } );
1459
1460 io.SetBoard( board() );
1461 io.SaveSelection( selection, thisDocumentType() == kiapi::common::types::DOCTYPE_FOOTPRINT );
1462
1463 return response;
1464}
1465
1466
1469{
1470 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1471
1472 if( !documentValidation )
1473 return tl::unexpected( documentValidation.error() );
1474
1475 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1476 return tl::unexpected( *busy );
1477
1478 CreateItemsResponse response;
1479 return response;
1480}
1481
1482
1485{
1486 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetVisibleLayers" ) )
1487 return tl::unexpected( *headless );
1488
1489 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1490
1491 if( !documentValidation )
1492 return tl::unexpected( documentValidation.error() );
1493
1494 BoardLayers response;
1495
1496 for( PCB_LAYER_ID layer : board()->GetVisibleLayers() )
1497 response.add_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
1498
1499 return response;
1500}
1501
1502
1505{
1506 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SetVisibleLayers" ) )
1507 return tl::unexpected( *headless );
1508
1509 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1510
1511 if( !documentValidation )
1512 return tl::unexpected( documentValidation.error() );
1513
1514 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1515 return tl::unexpected( *busy );
1516
1517 LSET visible;
1518 LSET enabled = board()->GetEnabledLayers();
1519
1520 for( int layerIdx : aCtx.Request.layers() )
1521 {
1522 PCB_LAYER_ID layer =
1523 FromProtoEnum<PCB_LAYER_ID>( static_cast<board::types::BoardLayer>( layerIdx ) );
1524
1525 if( enabled.Contains( layer ) )
1526 visible.set( layer );
1527 }
1528
1529 board()->SetVisibleLayers( visible );
1530
1531 PCB_BASE_EDIT_FRAME* editFrame = static_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
1532 editFrame->GetAppearancePanel()->OnBoardChanged();
1533 editFrame->GetCanvas()->SyncLayersVisibility( board() );
1534 editFrame->Refresh();
1535 return Empty();
1536}
1537
1538
1541{
1542 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetActiveLayer" ) )
1543 return tl::unexpected( *headless );
1544
1545 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1546
1547 if( !documentValidation )
1548 return tl::unexpected( documentValidation.error() );
1549
1550 PCB_BASE_EDIT_FRAME* editFrame = static_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
1551
1552 BoardLayerResponse response;
1553 response.set_layer(
1555
1556 return response;
1557}
1558
1559
1562{
1563 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SetActiveLayer" ) )
1564 return tl::unexpected( *headless );
1565
1566 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1567
1568 if( !documentValidation )
1569 return tl::unexpected( documentValidation.error() );
1570
1571 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1572 return tl::unexpected( *busy );
1573
1574 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID>( aCtx.Request.layer() );
1575
1576 if( !board()->GetEnabledLayers().Contains( layer ) )
1577 {
1578 ApiResponseStatus err;
1579 err.set_status( ApiStatusCode::AS_BAD_REQUEST );
1580 err.set_error_message( fmt::format( "Layer {} is not a valid layer for the given board",
1581 magic_enum::enum_name( layer ) ) );
1582 return tl::unexpected( err );
1583 }
1584
1585 PCB_BASE_EDIT_FRAME* editFrame = static_cast<PCB_BASE_EDIT_FRAME*>( m_frame );
1586 editFrame->SetActiveLayer( layer );
1587 return Empty();
1588}
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:84
virtual void CopyFrom(const BOARD_ITEM *aOther)
void ResetUuid()
Definition board_item.h:280
BOARD_ITEM_CONTAINER * GetParent() const
Definition board_item.h:266
virtual void Normalize()
Perform any normalization required after a user rotate and/or flip.
Definition board_item.h:489
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:410
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:1218
const BOX2I GetBoardEdgesBoundingBox() const
Return the board bounding box calculated using exclusively the board edges (graphics on Edge....
Definition board.h:1288
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:1185
BOX2I ComputeBoundingBox(bool aBoardEdgesOnly=false, bool aPhysicalLayersOnly=false) const
Calculate the bounding box containing all board items (or board edge segments).
Definition board.cpp:2742
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:2143
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)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:94
#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_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