KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_pcb.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 Jon Evans <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include <magic_enum.hpp>
22
23#include <api/api_handler_pcb.h>
24#include <api/api_pcb_utils.h>
25#include <api/api_enums.h>
26#include <api/api_utils.h>
27#include <board_commit.h>
29#include <footprint.h>
30#include <kicad_clipboard.h>
31#include <netinfo.h>
32#include <pad.h>
33#include <pcb_edit_frame.h>
34#include <pcb_group.h>
35#include <pcb_reference_image.h>
36#include <pcb_shape.h>
37#include <pcb_text.h>
38#include <pcb_textbox.h>
39#include <pcb_track.h>
40#include <pcbnew_id.h>
41#include <project.h>
42#include <tool/tool_manager.h>
43#include <tools/pcb_actions.h>
45#include <zone.h>
46
47#include <api/common/types/base_types.pb.h>
49
50using namespace kiapi::common::commands;
51using types::CommandStatus;
52using types::DocumentType;
53using types::ItemRequestStatus;
54
55
57 API_HANDLER_EDITOR( aFrame )
58{
65
68
74
94
100
115}
116
117
119{
120 return static_cast<PCB_EDIT_FRAME*>( m_frame );
121}
122
123
125 const HANDLER_CONTEXT<RunAction>& aCtx )
126{
127 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
128 return tl::unexpected( *busy );
129
130 RunActionResponse response;
131
132 if( frame()->GetToolManager()->RunAction( aCtx.Request.action(), true ) )
133 response.set_status( RunActionStatus::RAS_OK );
134 else
135 response.set_status( RunActionStatus::RAS_INVALID );
136
137 return response;
138}
139
140
143{
144 if( aCtx.Request.type() != DocumentType::DOCTYPE_PCB )
145 {
146 ApiResponseStatus e;
147 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
148 e.set_status( ApiStatusCode::AS_UNHANDLED );
149 return tl::unexpected( e );
150 }
151
152 GetOpenDocumentsResponse response;
153 common::types::DocumentSpecifier doc;
154
155 wxFileName fn( frame()->GetCurrentFileName() );
156
157 doc.set_type( DocumentType::DOCTYPE_PCB );
158 doc.set_board_filename( fn.GetFullName() );
159
160 doc.mutable_project()->set_name( frame()->Prj().GetProjectName().ToStdString() );
161 doc.mutable_project()->set_path( frame()->Prj().GetProjectDirectory().ToStdString() );
162
163 response.mutable_documents()->Add( std::move( doc ) );
164 return response;
165}
166
167
170{
171 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
172 return tl::unexpected( *busy );
173
174 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
175
176 if( !documentValidation )
177 return tl::unexpected( documentValidation.error() );
178
179 frame()->SaveBoard();
180 return Empty();
181}
182
183
186{
187 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
188 return tl::unexpected( *busy );
189
190 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
191
192 if( !documentValidation )
193 return tl::unexpected( documentValidation.error() );
194
195 wxFileName boardPath( frame()->Prj().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
196
197 if( !boardPath.IsOk() || !boardPath.IsDirWritable() )
198 {
199 ApiResponseStatus e;
200 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
201 e.set_error_message( fmt::format( "save path '{}' could not be opened",
202 boardPath.GetFullPath().ToStdString() ) );
203 return tl::unexpected( e );
204 }
205
206 if( boardPath.FileExists()
207 && ( !boardPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
208 {
209 ApiResponseStatus e;
210 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
211 e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
212 boardPath.GetFullPath().ToStdString() ) );
213 return tl::unexpected( e );
214 }
215
216 if( boardPath.GetExt() != FILEEXT::KiCadPcbFileExtension )
217 {
218 ApiResponseStatus e;
219 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
220 e.set_error_message( fmt::format( "save path '{}' must have a kicad_pcb extension",
221 boardPath.GetFullPath().ToStdString() ) );
222 return tl::unexpected( e );
223 }
224
225 BOARD* board = frame()->GetBoard();
226
227 if( board->GetFileName().Matches( boardPath.GetFullPath() ) )
228 {
229 frame()->SaveBoard();
230 return Empty();
231 }
232
233 bool includeProject = true;
234
235 if( aCtx.Request.has_options() )
236 includeProject = aCtx.Request.options().include_project();
237
238 frame()->SavePcbCopy( boardPath.GetFullPath(), includeProject, /* aHeadless = */ true );
239
240 return Empty();
241}
242
243
246{
247 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
248 return tl::unexpected( *busy );
249
250 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
251
252 if( !documentValidation )
253 return tl::unexpected( documentValidation.error() );
254
255 wxFileName fn = frame()->Prj().AbsolutePath( frame()->GetBoard()->GetFileName() );
256
257 frame()->GetScreen()->SetContentModified( false );
258 frame()->ReleaseFile();
259 frame()->OpenProjectFiles( std::vector<wxString>( 1, fn.GetFullPath() ), KICTL_REVERT );
260
261 return Empty();
262}
263
264
265void API_HANDLER_PCB::pushCurrentCommit( const std::string& aClientName, const wxString& aMessage )
266{
267 API_HANDLER_EDITOR::pushCurrentCommit( aClientName, aMessage );
268 frame()->Refresh();
269}
270
271
272std::unique_ptr<COMMIT> API_HANDLER_PCB::createCommit()
273{
274 return std::make_unique<BOARD_COMMIT>( frame() );
275}
276
277
278std::optional<BOARD_ITEM*> API_HANDLER_PCB::getItemById( const KIID& aId ) const
279{
280 BOARD_ITEM* item = frame()->GetBoard()->ResolveItem( aId, true );
281
282 if( !item )
283 return std::nullopt;
284
285 return item;
286}
287
288
289bool API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
290{
291 if( aDocument.type() != DocumentType::DOCTYPE_PCB )
292 return false;
293
294 wxFileName fn( frame()->GetCurrentFileName() );
295 return 0 == aDocument.board_filename().compare( fn.GetFullName() );
296}
297
298
300 BOARD_ITEM_CONTAINER* aContainer )
301{
302 if( !aContainer )
303 {
304 ApiResponseStatus e;
305 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
306 e.set_error_message( "Tried to create an item in a null container" );
307 return tl::unexpected( e );
308 }
309
310 if( aType == PCB_PAD_T && !dynamic_cast<FOOTPRINT*>( aContainer ) )
311 {
312 ApiResponseStatus e;
313 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
314 e.set_error_message( fmt::format( "Tried to create a pad in {}, which is not a footprint",
315 aContainer->GetFriendlyName().ToStdString() ) );
316 return tl::unexpected( e );
317 }
318 else if( aType == PCB_FOOTPRINT_T && !dynamic_cast<BOARD*>( aContainer ) )
319 {
320 ApiResponseStatus e;
321 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
322 e.set_error_message( fmt::format( "Tried to create a footprint in {}, which is not a board",
323 aContainer->GetFriendlyName().ToStdString() ) );
324 return tl::unexpected( e );
325 }
326
327 std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
328
329 if( !created )
330 {
331 ApiResponseStatus e;
332 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
333 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
334 magic_enum::enum_name( aType ) ) );
335 return tl::unexpected( e );
336 }
337
338 return created;
339}
340
341
343 const std::string& aClientName,
344 const types::ItemHeader &aHeader,
345 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
346 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
347{
348 ApiResponseStatus e;
349
350 auto containerResult = validateItemHeaderDocument( aHeader );
351
352 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
353 {
354 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
355 e.set_status( ApiStatusCode::AS_UNHANDLED );
356 return tl::unexpected( e );
357 }
358 else if( !containerResult )
359 {
360 e.CopyFrom( containerResult.error() );
361 return tl::unexpected( e );
362 }
363
364 BOARD* board = frame()->GetBoard();
365 BOARD_ITEM_CONTAINER* container = board;
366
367 if( containerResult->has_value() )
368 {
369 const KIID& containerId = **containerResult;
370 std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
371
372 if( optItem )
373 {
374 container = dynamic_cast<BOARD_ITEM_CONTAINER*>( *optItem );
375
376 if( !container )
377 {
378 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
379 e.set_error_message( fmt::format(
380 "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(
389 "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
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 {}",
406 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
428 createItemForType( *type, container );
429
430 if( !creationResult )
431 {
432 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
433 status.set_error_message( creationResult.error().error_message() );
434 aItemHandler( status, anyItem );
435 continue;
436 }
437
438 std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
439
440 if( !item->Deserialize( anyItem ) )
441 {
442 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
443 e.set_error_message( fmt::format( "could not unpack {} from request",
444 item->GetClass().ToStdString() ) );
445 return tl::unexpected( e );
446 }
447
448 std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
449
450 if( aCreate && optItem )
451 {
452 status.set_code( ItemStatusCode::ISC_EXISTING );
453 status.set_error_message( fmt::format( "an item with UUID {} already exists",
454 item->m_Uuid.AsStdString() ) );
455 aItemHandler( status, anyItem );
456 continue;
457 }
458 else if( !aCreate && !optItem )
459 {
460 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
461 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
462 item->m_Uuid.AsStdString() ) );
463 aItemHandler( status, anyItem );
464 continue;
465 }
466
467 if( aCreate && !( board->GetEnabledLayers() & item->GetLayerSet() ).any() )
468 {
469 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
470 status.set_error_message(
471 "attempted to add item with no overlapping layers with the board" );
472 aItemHandler( status, anyItem );
473 continue;
474 }
475
476 status.set_code( ItemStatusCode::ISC_OK );
477 google::protobuf::Any newItem;
478
479 if( aCreate )
480 {
481 item->Serialize( newItem );
482 commit->Add( item.release() );
483 }
484 else
485 {
486 BOARD_ITEM* boardItem = *optItem;
487
488 // Footprints can't be modified by CopyFrom at the moment because the commit system
489 // doesn't currently know what to do with a footprint that has had its children
490 // replaced with other children; which results in things like the view not having its
491 // cached geometry for footprint children updated when you move a footprint around.
492 // And also, groups are special because they can contain any item type, so we
493 // can't use CopyFrom on them either.
494 if( boardItem->Type() == PCB_FOOTPRINT_T || boardItem->Type() == PCB_GROUP_T )
495 {
496 commit->Remove( boardItem );
497 item->Serialize( newItem );
498 commit->Add( item.release() );
499 }
500 else
501 {
502 commit->Modify( boardItem );
503 boardItem->CopyFrom( item.get() );
504 boardItem->Serialize( newItem );
505 }
506 }
507
508 aItemHandler( status, newItem );
509 }
510
511 if( !m_activeClients.count( aClientName ) )
512 {
513 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
514 : _( "Modified items via API" ) );
515 }
516
517
518 return ItemRequestStatus::IRS_OK;
519}
520
521
523{
524 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
525 return tl::unexpected( *busy );
526
527 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
528 {
529 ApiResponseStatus e;
530 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
531 e.set_status( ApiStatusCode::AS_UNHANDLED );
532 return tl::unexpected( e );
533 }
534
535 GetItemsResponse response;
536
537 BOARD* board = frame()->GetBoard();
538 std::vector<BOARD_ITEM*> items;
539 std::set<KICAD_T> typesRequested, typesInserted;
540 bool handledAnything = false;
541
542 for( int typeRaw : aCtx.Request.types() )
543 {
544 auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
545 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
546
547 if( type == TYPE_NOT_INIT )
548 continue;
549
550 typesRequested.emplace( type );
551
552 if( typesInserted.count( type ) )
553 continue;
554
555 switch( type )
556 {
557 case PCB_TRACE_T:
558 case PCB_ARC_T:
559 case PCB_VIA_T:
560 handledAnything = true;
561 std::copy( board->Tracks().begin(), board->Tracks().end(),
562 std::back_inserter( items ) );
563 typesInserted.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
564 break;
565
566 case PCB_PAD_T:
567 {
568 handledAnything = true;
569
570 for( FOOTPRINT* fp : board->Footprints() )
571 {
572 std::copy( fp->Pads().begin(), fp->Pads().end(),
573 std::back_inserter( items ) );
574 }
575
576 typesInserted.insert( PCB_PAD_T );
577 break;
578 }
579
580 case PCB_FOOTPRINT_T:
581 {
582 handledAnything = true;
583
584 std::copy( board->Footprints().begin(), board->Footprints().end(),
585 std::back_inserter( items ) );
586
587 typesInserted.insert( PCB_FOOTPRINT_T );
588 break;
589 }
590
591 case PCB_SHAPE_T:
592 case PCB_TEXT_T:
593 case PCB_TEXTBOX_T:
594 case PCB_BARCODE_T:
595 {
596 handledAnything = true;
597 bool inserted = false;
598
599 for( BOARD_ITEM* item : board->Drawings() )
600 {
601 if( item->Type() == type )
602 {
603 items.emplace_back( item );
604 inserted = true;
605 }
606 }
607
608 if( inserted )
609 typesInserted.insert( type );
610
611 break;
612 }
613
614 case PCB_ZONE_T:
615 {
616 handledAnything = true;
617
618 std::copy( board->Zones().begin(), board->Zones().end(),
619 std::back_inserter( items ) );
620
621 typesInserted.insert( PCB_ZONE_T );
622 break;
623 }
624
625 case PCB_GROUP_T:
626 {
627 handledAnything = true;
628
629 std::copy( board->Groups().begin(), board->Groups().end(),
630 std::back_inserter( items ) );
631
632 typesInserted.insert( PCB_GROUP_T );
633 break;
634 }
635 default:
636 break;
637 }
638 }
639
640 if( !handledAnything )
641 {
642 ApiResponseStatus e;
643 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
644 e.set_error_message( "none of the requested types are valid for a Board object" );
645 return tl::unexpected( e );
646 }
647
648 for( const BOARD_ITEM* item : items )
649 {
650 if( !typesRequested.count( item->Type() ) )
651 continue;
652
653 google::protobuf::Any itemBuf;
654 item->Serialize( itemBuf );
655 response.mutable_items()->Add( std::move( itemBuf ) );
656 }
657
658 response.set_status( ItemRequestStatus::IRS_OK );
659 return response;
660}
661
662
665{
666 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
667 return tl::unexpected( *busy );
668
669 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
670 {
671 ApiResponseStatus e;
672 e.set_status( ApiStatusCode::AS_UNHANDLED );
673 return tl::unexpected( e );
674 }
675
676 GetItemsResponse response;
677
678 std::vector<BOARD_ITEM*> items;
679
680 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
681 {
682 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
683 items.emplace_back( *item );
684 }
685
686 if( items.empty() )
687 {
688 ApiResponseStatus e;
689 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
690 e.set_error_message( "none of the requested IDs were found or valid" );
691 return tl::unexpected( e );
692 }
693
694 for( const BOARD_ITEM* item : items )
695 {
696 google::protobuf::Any itemBuf;
697 item->Serialize( itemBuf );
698 response.mutable_items()->Add( std::move( itemBuf ) );
699 }
700
701 response.set_status( ItemRequestStatus::IRS_OK );
702 return response;
703}
704
705void API_HANDLER_PCB::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
706 const std::string& aClientName )
707{
708 BOARD* board = frame()->GetBoard();
709 std::vector<BOARD_ITEM*> validatedItems;
710
711 for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
712 {
713 if( BOARD_ITEM* item = board->ResolveItem( pair.first, true ) )
714 {
715 validatedItems.push_back( item );
716 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_OK;
717 }
718
719 // Note: we don't currently support locking items from API modification, but here is where
720 // to add it in the future (and return IDS_IMMUTABLE)
721 }
722
723 COMMIT* commit = getCurrentCommit( aClientName );
724
725 for( BOARD_ITEM* item : validatedItems )
726 commit->Remove( item );
727
728 if( !m_activeClients.count( aClientName ) )
729 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
730}
731
732
733std::optional<EDA_ITEM*> API_HANDLER_PCB::getItemFromDocument( const DocumentSpecifier& aDocument,
734 const KIID& aId )
735{
736 if( !validateDocument( aDocument ) )
737 return std::nullopt;
738
739 return getItemById( aId );
740}
741
742
745{
746 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
747 {
748 ApiResponseStatus e;
749 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
750 e.set_status( ApiStatusCode::AS_UNHANDLED );
751 return tl::unexpected( e );
752 }
753
754 std::set<KICAD_T> filter;
755
756 for( int typeRaw : aCtx.Request.types() )
757 {
758 auto typeMessage = static_cast<types::KiCadObjectType>( typeRaw );
759 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
760
761 if( type == TYPE_NOT_INIT )
762 continue;
763
764 filter.insert( type );
765 }
766
768 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
769
770 SelectionResponse response;
771
772 for( EDA_ITEM* item : selectionTool->GetSelection() )
773 {
774 if( filter.empty() || filter.contains( item->Type() ) )
775 item->Serialize( *response.add_items() );
776 }
777
778 return response;
779}
780
781
784{
785 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
786 return tl::unexpected( *busy );
787
788 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
789 {
790 ApiResponseStatus e;
791 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
792 e.set_status( ApiStatusCode::AS_UNHANDLED );
793 return tl::unexpected( e );
794 }
795
798 frame()->Refresh();
799
800 return Empty();
801}
802
803
806{
807 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
808 return tl::unexpected( *busy );
809
810 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
811 {
812 ApiResponseStatus e;
813 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
814 e.set_status( ApiStatusCode::AS_UNHANDLED );
815 return tl::unexpected( e );
816 }
817
819 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
820
821 std::vector<EDA_ITEM*> toAdd;
822
823 for( const types::KIID& id : aCtx.Request.items() )
824 {
825 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
826 toAdd.emplace_back( *item );
827 }
828
829 selectionTool->AddItemsToSel( &toAdd );
830 frame()->Refresh();
831
832 SelectionResponse response;
833
834 for( EDA_ITEM* item : selectionTool->GetSelection() )
835 item->Serialize( *response.add_items() );
836
837 return response;
838}
839
840
843{
844 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
845 return tl::unexpected( *busy );
846
847 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
848 {
849 ApiResponseStatus e;
850 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
851 e.set_status( ApiStatusCode::AS_UNHANDLED );
852 return tl::unexpected( e );
853 }
854
856 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
857
858 std::vector<EDA_ITEM*> toRemove;
859
860 for( const types::KIID& id : aCtx.Request.items() )
861 {
862 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
863 toRemove.emplace_back( *item );
864 }
865
866 selectionTool->RemoveItemsFromSel( &toRemove );
867 frame()->Refresh();
868
869 SelectionResponse response;
870
871 for( EDA_ITEM* item : selectionTool->GetSelection() )
872 item->Serialize( *response.add_items() );
873
874 return response;
875}
876
877
880{
881 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
882
883 if( !documentValidation )
884 return tl::unexpected( documentValidation.error() );
885
886 BoardStackupResponse response;
887 google::protobuf::Any any;
888
890
891 any.UnpackTo( response.mutable_stackup() );
892
893 // User-settable layer names are not stored in BOARD_STACKUP at the moment
894 for( board::BoardStackupLayer& layer : *response.mutable_stackup()->mutable_layers() )
895 {
896 if( layer.type() == board::BoardStackupLayerType::BSLT_DIELECTRIC )
897 continue;
898
899 PCB_LAYER_ID id = FromProtoEnum<PCB_LAYER_ID>( layer.layer() );
900 wxCHECK2( id != UNDEFINED_LAYER, continue );
901
902 layer.set_user_name( frame()->GetBoard()->GetLayerName( id ) );
903 }
904
905 return response;
906}
907
908
911{
912 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
913
914 if( !documentValidation )
915 return tl::unexpected( documentValidation.error() );
916
917 BoardEnabledLayersResponse response;
918
919 BOARD* board = frame()->GetBoard();
920 int copperLayerCount = board->GetCopperLayerCount();
921
922 response.set_copper_layer_count( copperLayerCount );
923
924 LSET enabled = board->GetEnabledLayers();
925
926 // The Rescue layer is an internal detail and should be hidden from the API
927 enabled.reset( Rescue );
928
929 // Just in case this is out of sync; the API should always return the expected copper layers
930 enabled |= LSET::AllCuMask( copperLayerCount );
931
932 board::PackLayerSet( *response.mutable_layers(), enabled );
933
934 return response;
935}
936
937
940{
941 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
942
943 if( !documentValidation )
944 return tl::unexpected( documentValidation.error() );
945
946 if( aCtx.Request.copper_layer_count() % 2 != 0 )
947 {
948 ApiResponseStatus e;
949 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
950 e.set_error_message( "copper_layer_count must be an even number" );
951 return tl::unexpected( e );
952 }
953
954 if( aCtx.Request.copper_layer_count() > MAX_CU_LAYERS )
955 {
956 ApiResponseStatus e;
957 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
958 e.set_error_message( fmt::format( "copper_layer_count must be below %d", MAX_CU_LAYERS ) );
959 return tl::unexpected( e );
960 }
961
962 int copperLayerCount = static_cast<int>( aCtx.Request.copper_layer_count() );
963 LSET enabled = board::UnpackLayerSet( aCtx.Request.layers() );
964
965 // Sanitize the input
966 enabled |= LSET( { Edge_Cuts, Margin, F_CrtYd, B_CrtYd } );
967 enabled &= ~LSET::AllCuMask();
968 enabled |= LSET::AllCuMask( copperLayerCount );
969
970 BOARD* board = frame()->GetBoard();
971
972 LSET previousEnabled = board->GetEnabledLayers();
973 LSET changedLayers = enabled ^ previousEnabled;
974
975 board->SetEnabledLayers( enabled );
976 board->SetVisibleLayers( board->GetVisibleLayers() | changedLayers );
977
978 LSEQ removedLayers;
979
980 for( PCB_LAYER_ID layer_id : previousEnabled )
981 {
982 if( !enabled[layer_id] && board->HasItemsOnLayer( layer_id ) )
983 removedLayers.push_back( layer_id );
984 }
985
986 bool modified = false;
987
988 if( !removedLayers.empty() )
989 {
990 m_frame->GetToolManager()->RunAction( PCB_ACTIONS::selectionClear );
991
992 for( PCB_LAYER_ID layer_id : removedLayers )
993 modified |= board->RemoveAllItemsOnLayer( layer_id );
994 }
995
996 if( enabled != previousEnabled )
998
999 if( modified )
1000 frame()->OnModify();
1001
1002 BoardEnabledLayersResponse response;
1003
1004 response.set_copper_layer_count( copperLayerCount );
1005 board::PackLayerSet( *response.mutable_layers(), enabled );
1006
1007 return response;
1008}
1009
1010
1013{
1014 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1015
1016 if( !documentValidation )
1017 return tl::unexpected( documentValidation.error() );
1018
1020 GraphicsDefaultsResponse response;
1021
1022 // TODO: This should change to be an enum class
1023 constexpr std::array<kiapi::board::BoardLayerClass, LAYER_CLASS_COUNT> classOrder = {
1024 kiapi::board::BLC_SILKSCREEN,
1025 kiapi::board::BLC_COPPER,
1026 kiapi::board::BLC_EDGES,
1027 kiapi::board::BLC_COURTYARD,
1028 kiapi::board::BLC_FABRICATION,
1029 kiapi::board::BLC_OTHER
1030 };
1031
1032 for( int i = 0; i < LAYER_CLASS_COUNT; ++i )
1033 {
1034 kiapi::board::BoardLayerGraphicsDefaults* l = response.mutable_defaults()->add_layers();
1035
1036 l->set_layer( classOrder[i] );
1037 l->mutable_line_thickness()->set_value_nm( bds.m_LineThickness[i] );
1038
1039 kiapi::common::types::TextAttributes* text = l->mutable_text();
1040 text->mutable_size()->set_x_nm( bds.m_TextSize[i].x );
1041 text->mutable_size()->set_y_nm( bds.m_TextSize[i].y );
1042 text->mutable_stroke_width()->set_value_nm( bds.m_TextThickness[i] );
1043 text->set_italic( bds.m_TextItalic[i] );
1044 text->set_keep_upright( bds.m_TextUpright[i] );
1045 }
1046
1047 return response;
1048}
1049
1050
1053{
1054 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1055 !documentValidation )
1056 {
1057 return tl::unexpected( documentValidation.error() );
1058 }
1059
1060 VECTOR2I origin;
1061 const BOARD_DESIGN_SETTINGS& settings = frame()->GetBoard()->GetDesignSettings();
1062
1063 switch( aCtx.Request.type() )
1064 {
1065 case BOT_GRID:
1066 origin = settings.GetGridOrigin();
1067 break;
1068
1069 case BOT_DRILL:
1070 origin = settings.GetAuxOrigin();
1071 break;
1072
1073 default:
1074 case BOT_UNKNOWN:
1075 {
1076 ApiResponseStatus e;
1077 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1078 e.set_error_message( "Unexpected origin type" );
1079 return tl::unexpected( e );
1080 }
1081 }
1082
1083 types::Vector2 reply;
1084 PackVector2( reply, origin );
1085 return reply;
1086}
1087
1090{
1091 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1092 return tl::unexpected( *busy );
1093
1094 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1095 !documentValidation )
1096 {
1097 return tl::unexpected( documentValidation.error() );
1098 }
1099
1100 VECTOR2I origin = UnpackVector2( aCtx.Request.origin() );
1101
1102 switch( aCtx.Request.type() )
1103 {
1104 case BOT_GRID:
1105 {
1106 PCB_EDIT_FRAME* f = frame();
1107
1108 frame()->CallAfter( [f, origin]()
1109 {
1110 // gridSetOrigin takes ownership and frees this
1111 VECTOR2D* dorigin = new VECTOR2D( origin );
1112 TOOL_MANAGER* mgr = f->GetToolManager();
1113 mgr->RunAction( PCB_ACTIONS::gridSetOrigin, dorigin );
1114 f->Refresh();
1115 } );
1116 break;
1117 }
1118
1119 case BOT_DRILL:
1120 {
1121 PCB_EDIT_FRAME* f = frame();
1122
1123 frame()->CallAfter( [f, origin]()
1124 {
1125 TOOL_MANAGER* mgr = f->GetToolManager();
1126 mgr->RunAction( PCB_ACTIONS::drillSetOrigin, origin );
1127 f->Refresh();
1128 } );
1129 break;
1130 }
1131
1132 default:
1133 case BOT_UNKNOWN:
1134 {
1135 ApiResponseStatus e;
1136 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1137 e.set_error_message( "Unexpected origin type" );
1138 return tl::unexpected( e );
1139 }
1140 }
1141
1142 return Empty();
1143}
1144
1145
1148{
1149 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1150 return tl::unexpected( *busy );
1151
1152 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1153 {
1154 ApiResponseStatus e;
1155 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1156 e.set_status( ApiStatusCode::AS_UNHANDLED );
1157 return tl::unexpected( e );
1158 }
1159
1160 GetBoundingBoxResponse response;
1161 bool includeText = aCtx.Request.mode() == BoundingBoxMode::BBM_ITEM_AND_CHILD_TEXT;
1162
1163 for( const types::KIID& idMsg : aCtx.Request.items() )
1164 {
1165 KIID id( idMsg.value() );
1166 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1167
1168 if( !optItem )
1169 continue;
1170
1171 BOARD_ITEM* item = *optItem;
1172 BOX2I bbox;
1173
1174 if( item->Type() == PCB_FOOTPRINT_T )
1175 bbox = static_cast<FOOTPRINT*>( item )->GetBoundingBox( includeText );
1176 else
1177 bbox = item->GetBoundingBox();
1178
1179 response.add_items()->set_value( idMsg.value() );
1180 PackBox2( *response.add_boxes(), bbox );
1181 }
1182
1183 return response;
1184}
1185
1186
1189{
1190 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1191 !documentValidation )
1192 {
1193 return tl::unexpected( documentValidation.error() );
1194 }
1195
1196 PadShapeAsPolygonResponse response;
1198
1199 for( const types::KIID& padRequest : aCtx.Request.pads() )
1200 {
1201 KIID id( padRequest.value() );
1202 std::optional<BOARD_ITEM*> optPad = getItemById( id );
1203
1204 if( !optPad || ( *optPad )->Type() != PCB_PAD_T )
1205 continue;
1206
1207 response.add_pads()->set_value( padRequest.value() );
1208
1209 PAD* pad = static_cast<PAD*>( *optPad );
1210 SHAPE_POLY_SET poly;
1211 pad->TransformShapeToPolygon( poly, pad->Padstack().EffectiveLayerFor( layer ), 0,
1212 pad->GetMaxError(), ERROR_INSIDE );
1213
1214 types::PolygonWithHoles* polyMsg = response.mutable_polygons()->Add();
1215 PackPolyLine( *polyMsg->mutable_outline(), poly.COutline( 0 ) );
1216 }
1217
1218 return response;
1219}
1220
1221
1224{
1225 using board::types::BoardLayer;
1226
1227 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1228 !documentValidation )
1229 {
1230 return tl::unexpected( documentValidation.error() );
1231 }
1232
1233 PadstackPresenceResponse response;
1234
1235 LSET layers;
1236
1237 for( const int layer : aCtx.Request.layers() )
1238 layers.set( FromProtoEnum<PCB_LAYER_ID, BoardLayer>( static_cast<BoardLayer>( layer ) ) );
1239
1240 for( const types::KIID& padRequest : aCtx.Request.items() )
1241 {
1242 KIID id( padRequest.value() );
1243 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1244
1245 if( !optItem )
1246 continue;
1247
1248 switch( ( *optItem )->Type() )
1249 {
1250 case PCB_PAD_T:
1251 {
1252 PAD* pad = static_cast<PAD*>( *optItem );
1253
1254 for( PCB_LAYER_ID layer : layers )
1255 {
1256 PadstackPresenceEntry* entry = response.add_entries();
1257 entry->mutable_item()->set_value( pad->m_Uuid.AsStdString() );
1258 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1259 entry->set_presence( pad->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1260 }
1261
1262 break;
1263 }
1264
1265 case PCB_VIA_T:
1266 {
1267 PCB_VIA* via = static_cast<PCB_VIA*>( *optItem );
1268
1269 for( PCB_LAYER_ID layer : layers )
1270 {
1271 PadstackPresenceEntry* entry = response.add_entries();
1272 entry->mutable_item()->set_value( via->m_Uuid.AsStdString() );
1273 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1274 entry->set_presence( via->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1275 }
1276
1277 break;
1278 }
1279
1280 default:
1281 break;
1282 }
1283 }
1284
1285 return response;
1286}
1287
1288
1291{
1292 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1293
1294 if( !documentValidation )
1295 return tl::unexpected( documentValidation.error() );
1296
1297 BOARD* board = frame()->GetBoard();
1298 const TITLE_BLOCK& block = board->GetTitleBlock();
1299
1300 types::TitleBlockInfo response;
1301
1302 response.set_title( block.GetTitle().ToUTF8() );
1303 response.set_date( block.GetDate().ToUTF8() );
1304 response.set_revision( block.GetRevision().ToUTF8() );
1305 response.set_company( block.GetCompany().ToUTF8() );
1306 response.set_comment1( block.GetComment( 0 ).ToUTF8() );
1307 response.set_comment2( block.GetComment( 1 ).ToUTF8() );
1308 response.set_comment3( block.GetComment( 2 ).ToUTF8() );
1309 response.set_comment4( block.GetComment( 3 ).ToUTF8() );
1310 response.set_comment5( block.GetComment( 4 ).ToUTF8() );
1311 response.set_comment6( block.GetComment( 5 ).ToUTF8() );
1312 response.set_comment7( block.GetComment( 6 ).ToUTF8() );
1313 response.set_comment8( block.GetComment( 7 ).ToUTF8() );
1314 response.set_comment9( block.GetComment( 8 ).ToUTF8() );
1315
1316 return response;
1317}
1318
1319
1322{
1323 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1324
1325 if( !documentValidation )
1326 return tl::unexpected( documentValidation.error() );
1327
1328 ExpandTextVariablesResponse reply;
1329 BOARD* board = frame()->GetBoard();
1330
1331 std::function<bool( wxString* )> textResolver =
1332 [&]( wxString* token ) -> bool
1333 {
1334 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1335 return board->ResolveTextVar( token, 0 );
1336 };
1337
1338 for( const std::string& textMsg : aCtx.Request.text() )
1339 {
1340 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver );
1341 reply.add_text( text.ToUTF8() );
1342 }
1343
1344 return reply;
1345}
1346
1347
1350{
1351 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1352 return tl::unexpected( *busy );
1353
1354 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1355
1356 if( !documentValidation )
1357 return tl::unexpected( documentValidation.error() );
1358
1359 TOOL_MANAGER* mgr = frame()->GetToolManager();
1360 std::vector<EDA_ITEM*> toSelect;
1361
1362 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
1363 {
1364 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
1365 toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
1366 }
1367
1368 if( toSelect.empty() )
1369 {
1370 ApiResponseStatus e;
1371 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1372 e.set_error_message( fmt::format( "None of the given items exist on the board",
1373 aCtx.Request.board().board_filename() ) );
1374 return tl::unexpected( e );
1375 }
1376
1377 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1378 selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
1379
1381 mgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &toSelect );
1382
1383 COMMIT* commit = getCurrentCommit( aCtx.ClientName );
1384 mgr->PostAPIAction( PCB_ACTIONS::move, commit );
1385
1386 return Empty();
1387}
1388
1389
1391{
1392 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1393
1394 if( !documentValidation )
1395 return tl::unexpected( documentValidation.error() );
1396
1397 NetsResponse response;
1398 BOARD* board = frame()->GetBoard();
1399
1400 std::set<wxString> netclassFilter;
1401
1402 for( const std::string& nc : aCtx.Request.netclass_filter() )
1403 netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
1404
1405 for( NETINFO_ITEM* net : board->GetNetInfo() )
1406 {
1407 NETCLASS* nc = net->GetNetClass();
1408
1409 if( !netclassFilter.empty() && nc && !netclassFilter.count( nc->GetName() ) )
1410 continue;
1411
1412 board::types::Net* netProto = response.add_nets();
1413 netProto->set_name( net->GetNetname() );
1414 netProto->mutable_code()->set_value( net->GetNetCode() );
1415 }
1416
1417 return response;
1418}
1419
1420
1423{
1424 NetClassForNetsResponse response;
1425
1426 BOARD* board = frame()->GetBoard();
1427 const NETINFO_LIST& nets = board->GetNetInfo();
1428 google::protobuf::Any any;
1429
1430 for( const board::types::Net& net : aCtx.Request.net() )
1431 {
1432 NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
1433
1434 if( !netInfo )
1435 continue;
1436
1437 netInfo->GetNetClass()->Serialize( any );
1438 auto [pair, rc] = response.mutable_classes()->insert( { net.name(), {} } );
1439 any.UnpackTo( &pair->second );
1440 }
1441
1442 return response;
1443}
1444
1445
1447{
1448 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1449 return tl::unexpected( *busy );
1450
1451 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1452
1453 if( !documentValidation )
1454 return tl::unexpected( documentValidation.error() );
1455
1456 if( aCtx.Request.zones().empty() )
1457 {
1458 TOOL_MANAGER* mgr = frame()->GetToolManager();
1459 frame()->CallAfter( [mgr]()
1460 {
1462 } );
1463 }
1464 else
1465 {
1466 // TODO
1467 ApiResponseStatus e;
1468 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
1469 return tl::unexpected( e );
1470 }
1471
1472 return Empty();
1473}
1474
1475
1478{
1479 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1480
1481 if( !documentValidation )
1482 return tl::unexpected( documentValidation.error() );
1483
1484 SavedDocumentResponse response;
1485 response.mutable_document()->CopyFrom( aCtx.Request.document() );
1486
1487 CLIPBOARD_IO io;
1488 io.SetWriter(
1489 [&]( const wxString& aData )
1490 {
1491 response.set_contents( aData.ToUTF8() );
1492 } );
1493
1494 io.SaveBoard( wxEmptyString, frame()->GetBoard(), nullptr );
1495
1496 return response;
1497}
1498
1499
1502{
1503 SavedSelectionResponse response;
1504
1505 TOOL_MANAGER* mgr = frame()->GetToolManager();
1506 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1507 PCB_SELECTION& selection = selectionTool->GetSelection();
1508
1509 CLIPBOARD_IO io;
1510 io.SetWriter(
1511 [&]( const wxString& aData )
1512 {
1513 response.set_contents( aData.ToUTF8() );
1514 } );
1515
1516 io.SetBoard( frame()->GetBoard() );
1517 io.SaveSelection( selection, false );
1518
1519 return response;
1520}
1521
1522
1525{
1526 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1527 return tl::unexpected( *busy );
1528
1529 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1530
1531 if( !documentValidation )
1532 return tl::unexpected( documentValidation.error() );
1533
1534 CreateItemsResponse response;
1535 return response;
1536}
1537
1538
1541{
1542 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1543
1544 if( !documentValidation )
1545 return tl::unexpected( documentValidation.error() );
1546
1547 BoardLayers response;
1548
1549 for( PCB_LAYER_ID layer : frame()->GetBoard()->GetVisibleLayers() )
1550 response.add_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
1551
1552 return response;
1553}
1554
1555
1558{
1559 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1560 return tl::unexpected( *busy );
1561
1562 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1563
1564 if( !documentValidation )
1565 return tl::unexpected( documentValidation.error() );
1566
1567 LSET visible;
1568 LSET enabled = frame()->GetBoard()->GetEnabledLayers();
1569
1570 for( int layerIdx : aCtx.Request.layers() )
1571 {
1572 PCB_LAYER_ID layer =
1573 FromProtoEnum<PCB_LAYER_ID>( static_cast<board::types::BoardLayer>( layerIdx ) );
1574
1575 if( enabled.Contains( layer ) )
1576 visible.set( layer );
1577 }
1578
1579 frame()->GetBoard()->SetVisibleLayers( visible );
1582 frame()->Refresh();
1583 return Empty();
1584}
1585
1586
1589{
1590 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1591
1592 if( !documentValidation )
1593 return tl::unexpected( documentValidation.error() );
1594
1595 BoardLayerResponse response;
1596 response.set_layer(
1598
1599 return response;
1600}
1601
1602
1605{
1606 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1607 return tl::unexpected( *busy );
1608
1609 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1610
1611 if( !documentValidation )
1612 return tl::unexpected( documentValidation.error() );
1613
1614 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID>( aCtx.Request.layer() );
1615
1616 if( !frame()->GetBoard()->GetEnabledLayers().Contains( layer ) )
1617 {
1618 ApiResponseStatus err;
1619 err.set_status( ApiStatusCode::AS_BAD_REQUEST );
1620 err.set_error_message( fmt::format( "Layer {} is not a valid layer for the given board",
1621 magic_enum::enum_name( layer ) ) );
1622 return tl::unexpected( err );
1623 }
1624
1625 frame()->SetActiveLayer( layer );
1626 return Empty();
1627}
1628
1629
1632{
1633 BoardEditorAppearanceSettings reply;
1634
1635 // TODO: might be nice to put all these things in one place and have it derive SERIALIZABLE
1636
1637 const PCB_DISPLAY_OPTIONS& displayOptions = frame()->GetDisplayOptions();
1638
1639 reply.set_inactive_layer_display( ToProtoEnum<HIGH_CONTRAST_MODE, InactiveLayerDisplayMode>(
1640 displayOptions.m_ContrastModeDisplay ) );
1641 reply.set_net_color_display(
1643
1644 reply.set_board_flip( frame()->GetCanvas()->GetView()->IsMirroredX()
1645 ? BoardFlipMode::BFM_FLIPPED_X
1646 : BoardFlipMode::BFM_NORMAL );
1647
1648 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
1649
1650 reply.set_ratsnest_display( ToProtoEnum<RATSNEST_MODE, RatsnestDisplayMode>(
1651 editorSettings->m_Display.m_RatsnestMode ) );
1652
1653 return reply;
1654}
1655
1656
1659{
1660 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1661 return tl::unexpected( *busy );
1662
1664 KIGFX::PCB_VIEW* view = frame()->GetCanvas()->GetView();
1665 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
1666 const BoardEditorAppearanceSettings& newSettings = aCtx.Request.settings();
1667
1668 options.m_ContrastModeDisplay =
1669 FromProtoEnum<HIGH_CONTRAST_MODE>( newSettings.inactive_layer_display() );
1670 options.m_NetColorMode =
1671 FromProtoEnum<NET_COLOR_MODE>( newSettings.net_color_display() );
1672
1673 bool flip = newSettings.board_flip() == BoardFlipMode::BFM_FLIPPED_X;
1674
1675 if( flip != view->IsMirroredX() )
1676 {
1677 view->SetMirror( !view->IsMirroredX(), view->IsMirroredY() );
1678 view->RecacheAllItems();
1679 }
1680
1681 editorSettings->m_Display.m_RatsnestMode =
1682 FromProtoEnum<RATSNEST_MODE>( newSettings.ratsnest_display() );
1683
1684 frame()->SetDisplayOptions( options );
1686 frame()->GetCanvas()->Refresh();
1687
1688 return Empty();
1689}
types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
Definition api_enums.cpp:96
KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:35
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:45
std::unique_ptr< EDA_ITEM > CreateItemForType(KICAD_T aType, EDA_ITEM *aContainer)
@ ERROR_INSIDE
@ LAYER_CLASS_COUNT
BOX2< VECTOR2I > BOX2I
Definition box2.h:922
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:223
static TOOL_ACTION gridSetOrigin
Definition actions.h:194
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition actions.h:231
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.
EDA_BASE_FRAME * m_frame
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 > handleInteractiveMoveItems(const HANDLER_CONTEXT< InteractiveMoveItems > &aCtx)
bool validateDocumentInternal(const DocumentSpecifier &aDocument) const override
HANDLER_RESULT< Empty > handleSetActiveLayer(const HANDLER_CONTEXT< SetActiveLayer > &aCtx)
API_HANDLER_PCB(PCB_EDIT_FRAME *aFrame)
static HANDLER_RESULT< std::unique_ptr< BOARD_ITEM > > createItemForType(KICAD_T aType, BOARD_ITEM_CONTAINER *aContainer)
std::optional< BOARD_ITEM * > getItemById(const KIID &aId) const
HANDLER_RESULT< types::Vector2 > handleGetBoardOrigin(const HANDLER_CONTEXT< GetBoardOrigin > &aCtx)
std::unique_ptr< COMMIT > createCommit() override
Override this to create an appropriate COMMIT subclass for the frame in question.
HANDLER_RESULT< BoardStackupResponse > handleGetStackup(const HANDLER_CONTEXT< GetBoardStackup > &aCtx)
HANDLER_RESULT< types::TitleBlockInfo > handleGetTitleBlockInfo(const HANDLER_CONTEXT< commands::GetTitleBlockInfo > &aCtx)
HANDLER_RESULT< commands::SelectionResponse > handleGetSelection(const HANDLER_CONTEXT< commands::GetSelection > &aCtx)
HANDLER_RESULT< NetClassForNetsResponse > handleGetNetClassForNets(const HANDLER_CONTEXT< GetNetClassForNets > &aCtx)
std::optional< EDA_ITEM * > getItemFromDocument(const DocumentSpecifier &aDocument, const KIID &aId) override
HANDLER_RESULT< commands::ExpandTextVariablesResponse > handleExpandTextVariables(const HANDLER_CONTEXT< commands::ExpandTextVariables > &aCtx)
HANDLER_RESULT< Empty > handleSetVisibleLayers(const HANDLER_CONTEXT< SetVisibleLayers > &aCtx)
HANDLER_RESULT< Empty > handleSaveCopyOfDocument(const HANDLER_CONTEXT< commands::SaveCopyOfDocument > &aCtx)
HANDLER_RESULT< GraphicsDefaultsResponse > handleGetGraphicsDefaults(const HANDLER_CONTEXT< GetGraphicsDefaults > &aCtx)
HANDLER_RESULT< commands::GetItemsResponse > handleGetItemsById(const HANDLER_CONTEXT< commands::GetItemsById > &aCtx)
HANDLER_RESULT< Empty > handleSetBoardOrigin(const HANDLER_CONTEXT< SetBoardOrigin > &aCtx)
HANDLER_RESULT< Empty > handleClearSelection(const HANDLER_CONTEXT< commands::ClearSelection > &aCtx)
HANDLER_RESULT< commands::RunActionResponse > handleRunAction(const HANDLER_CONTEXT< commands::RunAction > &aCtx)
HANDLER_RESULT< BoardLayers > handleGetVisibleLayers(const HANDLER_CONTEXT< GetVisibleLayers > &aCtx)
HANDLER_RESULT< PadstackPresenceResponse > handleCheckPadstackPresenceOnLayers(const HANDLER_CONTEXT< CheckPadstackPresenceOnLayers > &aCtx)
HANDLER_RESULT< commands::SelectionResponse > handleRemoveFromSelection(const HANDLER_CONTEXT< commands::RemoveFromSelection > &aCtx)
HANDLER_RESULT< commands::GetOpenDocumentsResponse > handleGetOpenDocuments(const HANDLER_CONTEXT< commands::GetOpenDocuments > &aCtx)
HANDLER_RESULT< BoardEditorAppearanceSettings > handleGetBoardEditorAppearanceSettings(const HANDLER_CONTEXT< GetBoardEditorAppearanceSettings > &aCtx)
HANDLER_RESULT< NetsResponse > handleGetNets(const HANDLER_CONTEXT< GetNets > &aCtx)
HANDLER_RESULT< commands::SavedDocumentResponse > handleSaveDocumentToString(const HANDLER_CONTEXT< commands::SaveDocumentToString > &aCtx)
HANDLER_RESULT< commands::GetBoundingBoxResponse > handleGetBoundingBox(const HANDLER_CONTEXT< commands::GetBoundingBox > &aCtx)
HANDLER_RESULT< Empty > handleSaveDocument(const HANDLER_CONTEXT< commands::SaveDocument > &aCtx)
void deleteItemsInternal(std::map< KIID, ItemDeletionStatus > &aItemsToDelete, const std::string &aClientName) override
HANDLER_RESULT< BoardEnabledLayersResponse > handleGetBoardEnabledLayers(const HANDLER_CONTEXT< GetBoardEnabledLayers > &aCtx)
HANDLER_RESULT< commands::GetItemsResponse > handleGetItems(const HANDLER_CONTEXT< commands::GetItems > &aCtx)
HANDLER_RESULT< PadShapeAsPolygonResponse > handleGetPadShapeAsPolygon(const HANDLER_CONTEXT< GetPadShapeAsPolygon > &aCtx)
PCB_EDIT_FRAME * frame() const
HANDLER_RESULT< BoardEnabledLayersResponse > handleSetBoardEnabledLayers(const HANDLER_CONTEXT< SetBoardEnabledLayers > &aCtx)
HANDLER_RESULT< Empty > handleSetBoardEditorAppearanceSettings(const HANDLER_CONTEXT< SetBoardEditorAppearanceSettings > &aCtx)
HANDLER_RESULT< Empty > handleRefillZones(const HANDLER_CONTEXT< RefillZones > &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< BoardLayerResponse > handleGetActiveLayer(const HANDLER_CONTEXT< GetActiveLayer > &aCtx)
HANDLER_RESULT< Empty > handleRevertDocument(const HANDLER_CONTEXT< commands::RevertDocument > &aCtx)
HANDLER_RESULT< commands::SavedSelectionResponse > handleSaveSelectionToString(const HANDLER_CONTEXT< commands::SaveSelectionToString > &aCtx)
void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage) override
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
Definition api_handler.h:93
void SetContentModified(bool aModified=true)
Definition base_screen.h:59
BASE_SET & reset(size_t pos)
Definition base_set.h:143
BASE_SET & set(size_t pos)
Definition base_set.h:116
Container for design settings for a BOARD object.
bool m_TextUpright[LAYER_CLASS_COUNT]
const VECTOR2I & GetGridOrigin() const
const VECTOR2I & GetAuxOrigin() const
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:79
virtual void CopyFrom(const BOARD_ITEM *aOther)
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:322
BOARD_STACKUP GetStackupOrDefault() const
Definition board.cpp:2642
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:956
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1041
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition board.cpp:924
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition board.cpp:1742
void SaveSelection(const PCB_SELECTION &selected, bool isFootprintEditor)
void SetWriter(std::function< void(const wxString &)> aWriter)
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 SetBoard(BOARD *aBoard)
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:72
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:90
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:106
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:78
void ReleaseFile()
Release the current file marked in use.
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:98
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition eda_item.cpp:110
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual wxString GetFriendlyName() const
Definition eda_item.cpp:401
void SetMirror(bool aMirrorX, bool aMirrorY)
Control the mirroring of the VIEW.
Definition view.cpp:557
void UpdateAllLayersColor()
Apply the new coloring scheme to all layers.
Definition view.cpp:775
bool IsMirroredX() const
Return true if view is flipped across the X axis.
Definition view.h:250
void RecacheAllItems()
Rebuild GAL display lists.
Definition view.cpp:1451
bool IsMirroredY() const
Return true if view is flipped across the Y axis.
Definition view.h:258
Definition kiid.h:49
std::string AsStdString() const
Definition kiid.cpp:252
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static LSET AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:591
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition lset.h:63
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:45
const wxString GetName() const
Gets the name of this (maybe aggregate) netclass in a format for internal usage or for export to exte...
Definition netclass.cpp:322
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
Definition netclass.cpp:136
Handle the data for a net.
Definition netinfo.h:54
NETCLASS * GetNetClass()
Definition netinfo.h:99
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:330
NETINFO_ITEM * GetNetItem(int aNetCode) const
Definition pad.h:54
DISPLAY_OPTIONS m_Display
static TOOL_ACTION zoneFillAll
static TOOL_ACTION move
move or drag an item
static TOOL_ACTION drillSetOrigin
APPEARANCE_CONTROLS * GetAppearancePanel()
const PCB_DISPLAY_OPTIONS & GetDisplayOptions() const
Display options control the way tracks, vias, outlines and other things are shown (for instance solid...
PCBNEW_SETTINGS * GetPcbNewSettings() const
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
PCB_SCREEN * GetScreen() const override
Return a pointer to a BASE_SCREEN or one of its derivatives.
BOARD * GetBoard() const
void SetDisplayOptions(const PCB_DISPLAY_OPTIONS &aOptions, bool aRefresh=true)
Update the current display options.
HIGH_CONTRAST_MODE m_ContrastModeDisplay
How inactive layers are displayed.
NET_COLOR_MODE m_NetColorMode
How to use color overrides on specific nets and netclasses.
virtual KIGFX::PCB_VIEW * GetView() const override
Return a pointer to the #VIEW instance used in the panel.
void SyncLayersVisibility(const BOARD *aBoard)
Update "visibility" property of each layer of a given BOARD.
The main frame for Pcbnew.
void SetActiveLayer(PCB_LAYER_ID aLayer) override
Change the currently active layer to aLayer and also update the APPEARANCE_CONTROLS.
void OnModify() override
Must be called after a board change to set the modified flag.
bool SaveBoard(bool aSaveAs=false, bool aSaveCopy=false)
bool OpenProjectFiles(const std::vector< wxString > &aFileSet, int aCtl=0) override
Load a KiCad board (.kicad_pcb) from aFileName.
void UpdateUserInterface()
Update the layer manager and other widgets from the board setup (layer and items visibility,...
bool SavePcbCopy(const wxString &aFileName, bool aCreateProject=false, bool aHeadless=false)
Write the board data structures to aFileName.
The selection tool: currently supports:
PCB_SELECTION & GetSelection()
virtual const wxString AbsolutePath(const wxString &aFileName) const
Fix up aFileName if it is relative to the project's directory to be an absolute path and filename.
Definition project.cpp:386
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.
Represent a set of closed polygons.
const SHAPE_LINE_CHAIN & COutline(int aIndex) const
Hold the information shown in the lower right corner of a plot, printout, or editing view.
Definition title_block.h:41
const wxString & GetCompany() const
Definition title_block.h:96
const wxString & GetRevision() const
Definition title_block.h:86
const wxString & GetDate() const
Definition title_block.h:76
const wxString & GetComment(int aIdx) const
const wxString & GetTitle() const
Definition title_block.h:63
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Master controller class:
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)
A type-safe container of any type.
Definition ki_any.h:93
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition common.cpp:59
#define _(s)
std::vector< EDA_ITEM * > EDA_ITEMS
Define list of drawing items for screens.
Definition eda_item.h:566
static const std::string KiCadPcbFileExtension
PROJECT & Prj()
Definition kicad.cpp:612
#define KICTL_REVERT
reverting to a previously-saved (KiCad) file.
#define MAX_CU_LAYERS
Definition layer_ids.h:176
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:60
@ F_CrtYd
Definition layer_ids.h:116
@ Edge_Cuts
Definition layer_ids.h:112
@ Margin
Definition layer_ids.h:113
@ B_CrtYd
Definition layer_ids.h:115
@ UNDEFINED_LAYER
Definition layer_ids.h:61
@ Rescue
Definition layer_ids.h:121
void PackLayerSet(google::protobuf::RepeatedField< int > &aOutput, const LSET &aLayerSet)
LSET UnpackLayerSet(const google::protobuf::RepeatedField< int > &aProtoLayerSet)
KICOMMON_API void PackBox2(types::Box2 &aOutput, const BOX2I &aInput)
KICOMMON_API std::optional< KICAD_T > TypeNameFromAny(const google::protobuf::Any &aMessage)
Definition api_utils.cpp:33
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput)
Definition api_utils.cpp:86
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput)
Definition api_utils.cpp:79
KICOMMON_API void PackPolyLine(types::PolyLine &aOutput, const SHAPE_LINE_CHAIN &aSlc)
Class to handle a set of BOARD_ITEMs.
BOARD * GetBoard()
std::string ClientName
Definition api_handler.h:51
RequestMessageType Request
Definition api_handler.h:52
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:78
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:88
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:106
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:103
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:97
@ TYPE_NOT_INIT
Definition typeinfo.h:81
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:104
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:111
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:93
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:108
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:92
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:101
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:86
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:102
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:87
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:98
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:100
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:96
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:105
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
VECTOR2< double > VECTOR2D
Definition vector2d.h:694