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{
59 registerHandler<RunAction, RunActionResponse>( &API_HANDLER_PCB::handleRunAction );
60 registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
62 registerHandler<SaveDocument, Empty>( &API_HANDLER_PCB::handleSaveDocument );
63 registerHandler<SaveCopyOfDocument, Empty>( &API_HANDLER_PCB::handleSaveCopyOfDocument );
64 registerHandler<RevertDocument, Empty>( &API_HANDLER_PCB::handleRevertDocument );
65
66 registerHandler<GetItems, GetItemsResponse>( &API_HANDLER_PCB::handleGetItems );
67 registerHandler<GetItemsById, GetItemsResponse>( &API_HANDLER_PCB::handleGetItemsById );
68
69 registerHandler<GetSelection, SelectionResponse>( &API_HANDLER_PCB::handleGetSelection );
70 registerHandler<ClearSelection, Empty>( &API_HANDLER_PCB::handleClearSelection );
71 registerHandler<AddToSelection, SelectionResponse>( &API_HANDLER_PCB::handleAddToSelection );
72 registerHandler<RemoveFromSelection, SelectionResponse>(
74
75 registerHandler<GetBoardStackup, BoardStackupResponse>( &API_HANDLER_PCB::handleGetStackup );
76 registerHandler<GetGraphicsDefaults, GraphicsDefaultsResponse>(
78 registerHandler<GetBoundingBox, GetBoundingBoxResponse>(
80 registerHandler<GetPadShapeAsPolygon, PadShapeAsPolygonResponse>(
82 registerHandler<CheckPadstackPresenceOnLayers, PadstackPresenceResponse>(
84 registerHandler<GetTitleBlockInfo, types::TitleBlockInfo>(
86 registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
88 registerHandler<GetBoardOrigin, types::Vector2>( &API_HANDLER_PCB::handleGetBoardOrigin );
89 registerHandler<SetBoardOrigin, Empty>( &API_HANDLER_PCB::handleSetBoardOrigin );
90
91 registerHandler<InteractiveMoveItems, Empty>( &API_HANDLER_PCB::handleInteractiveMoveItems );
92 registerHandler<GetNets, NetsResponse>( &API_HANDLER_PCB::handleGetNets );
93 registerHandler<GetNetClassForNets, NetClassForNetsResponse>(
95 registerHandler<RefillZones, Empty>( &API_HANDLER_PCB::handleRefillZones );
96
97 registerHandler<SaveDocumentToString, SavedDocumentResponse>(
99 registerHandler<SaveSelectionToString, SavedSelectionResponse>(
101 registerHandler<ParseAndCreateItemsFromString, CreateItemsResponse>(
103 registerHandler<GetVisibleLayers, BoardLayers>( &API_HANDLER_PCB::handleGetVisibleLayers );
104 registerHandler<SetVisibleLayers, Empty>( &API_HANDLER_PCB::handleSetVisibleLayers );
105 registerHandler<GetActiveLayer, BoardLayerResponse>( &API_HANDLER_PCB::handleGetActiveLayer );
106 registerHandler<SetActiveLayer, Empty>( &API_HANDLER_PCB::handleSetActiveLayer );
107 registerHandler<GetBoardEditorAppearanceSettings, BoardEditorAppearanceSettings>(
109 registerHandler<SetBoardEditorAppearanceSettings, Empty>(
111}
112
113
115{
116 return static_cast<PCB_EDIT_FRAME*>( m_frame );
117}
118
119
121 const HANDLER_CONTEXT<RunAction>& aCtx )
122{
123 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
124 return tl::unexpected( *busy );
125
126 RunActionResponse response;
127
128 if( frame()->GetToolManager()->RunAction( aCtx.Request.action(), true ) )
129 response.set_status( RunActionStatus::RAS_OK );
130 else
131 response.set_status( RunActionStatus::RAS_INVALID );
132
133 return response;
134}
135
136
139{
140 if( aCtx.Request.type() != DocumentType::DOCTYPE_PCB )
141 {
142 ApiResponseStatus e;
143 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
144 e.set_status( ApiStatusCode::AS_UNHANDLED );
145 return tl::unexpected( e );
146 }
147
148 GetOpenDocumentsResponse response;
149 common::types::DocumentSpecifier doc;
150
151 wxFileName fn( frame()->GetCurrentFileName() );
152
153 doc.set_type( DocumentType::DOCTYPE_PCB );
154 doc.set_board_filename( fn.GetFullName() );
155
156 doc.mutable_project()->set_name( frame()->Prj().GetProjectName().ToStdString() );
157 doc.mutable_project()->set_path( frame()->Prj().GetProjectDirectory().ToStdString() );
158
159 response.mutable_documents()->Add( std::move( doc ) );
160 return response;
161}
162
163
166{
167 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
168 return tl::unexpected( *busy );
169
170 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
171
172 if( !documentValidation )
173 return tl::unexpected( documentValidation.error() );
174
175 frame()->SaveBoard();
176 return Empty();
177}
178
179
182{
183 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
184 return tl::unexpected( *busy );
185
186 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
187
188 if( !documentValidation )
189 return tl::unexpected( documentValidation.error() );
190
191 wxFileName boardPath( frame()->Prj().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
192
193 if( !boardPath.IsOk() || !boardPath.IsDirWritable() )
194 {
195 ApiResponseStatus e;
196 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
197 e.set_error_message( fmt::format( "save path '{}' could not be opened",
198 boardPath.GetFullPath().ToStdString() ) );
199 return tl::unexpected( e );
200 }
201
202 if( boardPath.FileExists()
203 && ( !boardPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
204 {
205 ApiResponseStatus e;
206 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
207 e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
208 boardPath.GetFullPath().ToStdString() ) );
209 return tl::unexpected( e );
210 }
211
212 if( boardPath.GetExt() != FILEEXT::KiCadPcbFileExtension )
213 {
214 ApiResponseStatus e;
215 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
216 e.set_error_message( fmt::format( "save path '{}' must have a kicad_pcb extension",
217 boardPath.GetFullPath().ToStdString() ) );
218 return tl::unexpected( e );
219 }
220
221 BOARD* board = frame()->GetBoard();
222
223 if( board->GetFileName().Matches( boardPath.GetFullPath() ) )
224 {
225 frame()->SaveBoard();
226 return Empty();
227 }
228
229 bool includeProject = true;
230
231 if( aCtx.Request.has_options() )
232 includeProject = aCtx.Request.options().include_project();
233
234 frame()->SavePcbCopy( boardPath.GetFullPath(), includeProject, /* aHeadless = */ true );
235
236 return Empty();
237}
238
239
242{
243 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
244 return tl::unexpected( *busy );
245
246 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
247
248 if( !documentValidation )
249 return tl::unexpected( documentValidation.error() );
250
251 wxFileName fn = frame()->Prj().AbsolutePath( frame()->GetBoard()->GetFileName() );
252
253 frame()->GetScreen()->SetContentModified( false );
254 frame()->ReleaseFile();
255 frame()->OpenProjectFiles( std::vector<wxString>( 1, fn.GetFullPath() ), KICTL_REVERT );
256
257 return Empty();
258}
259
260
261void API_HANDLER_PCB::pushCurrentCommit( const std::string& aClientName, const wxString& aMessage )
262{
263 API_HANDLER_EDITOR::pushCurrentCommit( aClientName, aMessage );
264 frame()->Refresh();
265}
266
267
268std::unique_ptr<COMMIT> API_HANDLER_PCB::createCommit()
269{
270 return std::make_unique<BOARD_COMMIT>( frame() );
271}
272
273
274std::optional<BOARD_ITEM*> API_HANDLER_PCB::getItemById( const KIID& aId ) const
275{
276 BOARD_ITEM* item = frame()->GetBoard()->ResolveItem( aId, true );
277
278 if( !item )
279 return std::nullopt;
280
281 return item;
282}
283
284
285bool API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
286{
287 if( aDocument.type() != DocumentType::DOCTYPE_PCB )
288 return false;
289
290 wxFileName fn( frame()->GetCurrentFileName() );
291 return 0 == aDocument.board_filename().compare( fn.GetFullName() );
292}
293
294
296 BOARD_ITEM_CONTAINER* aContainer )
297{
298 if( !aContainer )
299 {
300 ApiResponseStatus e;
301 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
302 e.set_error_message( "Tried to create an item in a null container" );
303 return tl::unexpected( e );
304 }
305
306 if( aType == PCB_PAD_T && !dynamic_cast<FOOTPRINT*>( aContainer ) )
307 {
308 ApiResponseStatus e;
309 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
310 e.set_error_message( fmt::format( "Tried to create a pad in {}, which is not a footprint",
311 aContainer->GetFriendlyName().ToStdString() ) );
312 return tl::unexpected( e );
313 }
314 else if( aType == PCB_FOOTPRINT_T && !dynamic_cast<BOARD*>( aContainer ) )
315 {
316 ApiResponseStatus e;
317 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
318 e.set_error_message( fmt::format( "Tried to create a footprint in {}, which is not a board",
319 aContainer->GetFriendlyName().ToStdString() ) );
320 return tl::unexpected( e );
321 }
322
323 std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
324
325 if( !created )
326 {
327 ApiResponseStatus e;
328 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
329 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
330 magic_enum::enum_name( aType ) ) );
331 return tl::unexpected( e );
332 }
333
334 return created;
335}
336
337
339 const std::string& aClientName,
340 const types::ItemHeader &aHeader,
341 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
342 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
343{
344 ApiResponseStatus e;
345
346 auto containerResult = validateItemHeaderDocument( aHeader );
347
348 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
349 {
350 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
351 e.set_status( ApiStatusCode::AS_UNHANDLED );
352 return tl::unexpected( e );
353 }
354 else if( !containerResult )
355 {
356 e.CopyFrom( containerResult.error() );
357 return tl::unexpected( e );
358 }
359
360 BOARD* board = frame()->GetBoard();
361 BOARD_ITEM_CONTAINER* container = board;
362
363 if( containerResult->has_value() )
364 {
365 const KIID& containerId = **containerResult;
366 std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
367
368 if( optItem )
369 {
370 container = dynamic_cast<BOARD_ITEM_CONTAINER*>( *optItem );
371
372 if( !container )
373 {
374 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
375 e.set_error_message( fmt::format(
376 "The requested container {} is not a valid board item container",
377 containerId.AsStdString() ) );
378 return tl::unexpected( e );
379 }
380 }
381 else
382 {
383 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
384 e.set_error_message( fmt::format(
385 "The requested container {} does not exist in this document",
386 containerId.AsStdString() ) );
387 return tl::unexpected( e );
388 }
389 }
390
391 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aClientName ) );
392
393 for( const google::protobuf::Any& anyItem : aItems )
394 {
395 ItemStatus status;
396 std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
397
398 if( !type )
399 {
400 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
401 status.set_error_message( fmt::format( "Could not decode a valid type from {}",
402 anyItem.type_url() ) );
403 aItemHandler( status, anyItem );
404 continue;
405 }
406
407 if( type == PCB_DIMENSION_T )
408 {
409 board::types::Dimension dimension;
410 anyItem.UnpackTo( &dimension );
411
412 switch( dimension.dimension_style_case() )
413 {
414 case board::types::Dimension::kAligned: type = PCB_DIM_ALIGNED_T; break;
415 case board::types::Dimension::kOrthogonal: type = PCB_DIM_ORTHOGONAL_T; break;
416 case board::types::Dimension::kRadial: type = PCB_DIM_RADIAL_T; break;
417 case board::types::Dimension::kLeader: type = PCB_DIM_LEADER_T; break;
418 case board::types::Dimension::kCenter: type = PCB_DIM_CENTER_T; break;
419 case board::types::Dimension::DIMENSION_STYLE_NOT_SET: break;
420 }
421 }
422
424 createItemForType( *type, container );
425
426 if( !creationResult )
427 {
428 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
429 status.set_error_message( creationResult.error().error_message() );
430 aItemHandler( status, anyItem );
431 continue;
432 }
433
434 std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
435
436 if( !item->Deserialize( anyItem ) )
437 {
438 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
439 e.set_error_message( fmt::format( "could not unpack {} from request",
440 item->GetClass().ToStdString() ) );
441 return tl::unexpected( e );
442 }
443
444 std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
445
446 if( aCreate && optItem )
447 {
448 status.set_code( ItemStatusCode::ISC_EXISTING );
449 status.set_error_message( fmt::format( "an item with UUID {} already exists",
450 item->m_Uuid.AsStdString() ) );
451 aItemHandler( status, anyItem );
452 continue;
453 }
454 else if( !aCreate && !optItem )
455 {
456 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
457 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
458 item->m_Uuid.AsStdString() ) );
459 aItemHandler( status, anyItem );
460 continue;
461 }
462
463 if( aCreate && !( board->GetEnabledLayers() & item->GetLayerSet() ).any() )
464 {
465 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
466 status.set_error_message(
467 "attempted to add item with no overlapping layers with the board" );
468 aItemHandler( status, anyItem );
469 continue;
470 }
471
472 status.set_code( ItemStatusCode::ISC_OK );
473 google::protobuf::Any newItem;
474
475 if( aCreate )
476 {
477 item->Serialize( newItem );
478 commit->Add( item.release() );
479 }
480 else
481 {
482 BOARD_ITEM* boardItem = *optItem;
483
484 // Footprints can't be modified by CopyFrom at the moment because the commit system
485 // doesn't currently know what to do with a footprint that has had its children
486 // replaced with other children; which results in things like the view not having its
487 // cached geometry for footprint children updated when you move a footprint around.
488 // And also, groups are special because they can contain any item type, so we
489 // can't use CopyFrom on them either.
490 if( boardItem->Type() == PCB_FOOTPRINT_T || boardItem->Type() == PCB_GROUP_T )
491 {
492 commit->Remove( boardItem );
493 item->Serialize( newItem );
494 commit->Add( item.release() );
495 }
496 else
497 {
498 commit->Modify( boardItem );
499 boardItem->CopyFrom( item.get() );
500 boardItem->Serialize( newItem );
501 }
502 }
503
504 aItemHandler( status, newItem );
505 }
506
507 if( !m_activeClients.count( aClientName ) )
508 {
509 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
510 : _( "Modified items via API" ) );
511 }
512
513
514 return ItemRequestStatus::IRS_OK;
515}
516
517
519 const HANDLER_CONTEXT<GetItems>& aCtx )
520{
521 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
522 return tl::unexpected( *busy );
523
524 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
525 {
526 ApiResponseStatus e;
527 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
528 e.set_status( ApiStatusCode::AS_UNHANDLED );
529 return tl::unexpected( e );
530 }
531
532 GetItemsResponse response;
533
534 BOARD* board = frame()->GetBoard();
535 std::vector<BOARD_ITEM*> items;
536 std::set<KICAD_T> typesRequested, typesInserted;
537 bool handledAnything = false;
538
539 for( int typeRaw : aCtx.Request.types() )
540 {
541 auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
542 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
543
544 if( type == TYPE_NOT_INIT )
545 continue;
546
547 typesRequested.emplace( type );
548
549 if( typesInserted.count( type ) )
550 continue;
551
552 switch( type )
553 {
554 case PCB_TRACE_T:
555 case PCB_ARC_T:
556 case PCB_VIA_T:
557 handledAnything = true;
558 std::copy( board->Tracks().begin(), board->Tracks().end(),
559 std::back_inserter( items ) );
560 typesInserted.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
561 break;
562
563 case PCB_PAD_T:
564 {
565 handledAnything = true;
566
567 for( FOOTPRINT* fp : board->Footprints() )
568 {
569 std::copy( fp->Pads().begin(), fp->Pads().end(),
570 std::back_inserter( items ) );
571 }
572
573 typesInserted.insert( PCB_PAD_T );
574 break;
575 }
576
577 case PCB_FOOTPRINT_T:
578 {
579 handledAnything = true;
580
581 std::copy( board->Footprints().begin(), board->Footprints().end(),
582 std::back_inserter( items ) );
583
584 typesInserted.insert( PCB_FOOTPRINT_T );
585 break;
586 }
587
588 case PCB_SHAPE_T:
589 case PCB_TEXT_T:
590 case PCB_TEXTBOX_T:
591 {
592 handledAnything = true;
593 bool inserted = false;
594
595 for( BOARD_ITEM* item : board->Drawings() )
596 {
597 if( item->Type() == type )
598 {
599 items.emplace_back( item );
600 inserted = true;
601 }
602 }
603
604 if( inserted )
605 typesInserted.insert( PCB_SHAPE_T );
606
607 break;
608 }
609
610 case PCB_ZONE_T:
611 {
612 handledAnything = true;
613
614 std::copy( board->Zones().begin(), board->Zones().end(),
615 std::back_inserter( items ) );
616
617 typesInserted.insert( PCB_ZONE_T );
618 break;
619 }
620
621 case PCB_GROUP_T:
622 {
623 handledAnything = true;
624
625 std::copy( board->Groups().begin(), board->Groups().end(),
626 std::back_inserter( items ) );
627
628 typesInserted.insert( PCB_GROUP_T );
629 break;
630 }
631 default:
632 break;
633 }
634 }
635
636 if( !handledAnything )
637 {
638 ApiResponseStatus e;
639 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
640 e.set_error_message( "none of the requested types are valid for a Board object" );
641 return tl::unexpected( e );
642 }
643
644 for( const BOARD_ITEM* item : items )
645 {
646 if( !typesRequested.count( item->Type() ) )
647 continue;
648
649 google::protobuf::Any itemBuf;
650 item->Serialize( itemBuf );
651 response.mutable_items()->Add( std::move( itemBuf ) );
652 }
653
654 response.set_status( ItemRequestStatus::IRS_OK );
655 return response;
656}
657
658
661{
662 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
663 return tl::unexpected( *busy );
664
665 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
666 {
667 ApiResponseStatus e;
668 e.set_status( ApiStatusCode::AS_UNHANDLED );
669 return tl::unexpected( e );
670 }
671
672 GetItemsResponse response;
673
674 std::vector<BOARD_ITEM*> items;
675
676 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
677 {
678 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
679 items.emplace_back( *item );
680 }
681
682 if( items.empty() )
683 {
684 ApiResponseStatus e;
685 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
686 e.set_error_message( "none of the requested IDs were found or valid" );
687 return tl::unexpected( e );
688 }
689
690 for( const BOARD_ITEM* item : items )
691 {
692 google::protobuf::Any itemBuf;
693 item->Serialize( itemBuf );
694 response.mutable_items()->Add( std::move( itemBuf ) );
695 }
696
697 response.set_status( ItemRequestStatus::IRS_OK );
698 return response;
699}
700
701void API_HANDLER_PCB::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
702 const std::string& aClientName )
703{
704 BOARD* board = frame()->GetBoard();
705 std::vector<BOARD_ITEM*> validatedItems;
706
707 for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
708 {
709 if( BOARD_ITEM* item = board->ResolveItem( pair.first, true ) )
710 {
711 validatedItems.push_back( item );
712 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_OK;
713 }
714
715 // Note: we don't currently support locking items from API modification, but here is where
716 // to add it in the future (and return IDS_IMMUTABLE)
717 }
718
719 COMMIT* commit = getCurrentCommit( aClientName );
720
721 for( BOARD_ITEM* item : validatedItems )
722 commit->Remove( item );
723
724 if( !m_activeClients.count( aClientName ) )
725 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
726}
727
728
729std::optional<EDA_ITEM*> API_HANDLER_PCB::getItemFromDocument( const DocumentSpecifier& aDocument,
730 const KIID& aId )
731{
732 if( !validateDocument( aDocument ) )
733 return std::nullopt;
734
735 return getItemById( aId );
736}
737
738
741{
742 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
743 {
744 ApiResponseStatus e;
745 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
746 e.set_status( ApiStatusCode::AS_UNHANDLED );
747 return tl::unexpected( e );
748 }
749
750 std::set<KICAD_T> filter;
751
752 for( int typeRaw : aCtx.Request.types() )
753 {
754 auto typeMessage = static_cast<types::KiCadObjectType>( typeRaw );
755 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
756
757 if( type == TYPE_NOT_INIT )
758 continue;
759
760 filter.insert( type );
761 }
762
764 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
765
766 SelectionResponse response;
767
768 for( EDA_ITEM* item : selectionTool->GetSelection() )
769 {
770 if( filter.empty() || filter.contains( item->Type() ) )
771 item->Serialize( *response.add_items() );
772 }
773
774 return response;
775}
776
777
780{
781 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
782 return tl::unexpected( *busy );
783
784 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
785 {
786 ApiResponseStatus e;
787 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
788 e.set_status( ApiStatusCode::AS_UNHANDLED );
789 return tl::unexpected( e );
790 }
791
794 frame()->Refresh();
795
796 return Empty();
797}
798
799
802{
803 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
804 return tl::unexpected( *busy );
805
806 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
807 {
808 ApiResponseStatus e;
809 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
810 e.set_status( ApiStatusCode::AS_UNHANDLED );
811 return tl::unexpected( e );
812 }
813
815 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
816
817 std::vector<EDA_ITEM*> toAdd;
818
819 for( const types::KIID& id : aCtx.Request.items() )
820 {
821 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
822 toAdd.emplace_back( *item );
823 }
824
825 selectionTool->AddItemsToSel( &toAdd );
826 frame()->Refresh();
827
828 SelectionResponse response;
829
830 for( EDA_ITEM* item : selectionTool->GetSelection() )
831 item->Serialize( *response.add_items() );
832
833 return response;
834}
835
836
839{
840 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
841 return tl::unexpected( *busy );
842
843 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
844 {
845 ApiResponseStatus e;
846 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
847 e.set_status( ApiStatusCode::AS_UNHANDLED );
848 return tl::unexpected( e );
849 }
850
852 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
853
854 std::vector<EDA_ITEM*> toRemove;
855
856 for( const types::KIID& id : aCtx.Request.items() )
857 {
858 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
859 toRemove.emplace_back( *item );
860 }
861
862 selectionTool->RemoveItemsFromSel( &toRemove );
863 frame()->Refresh();
864
865 SelectionResponse response;
866
867 for( EDA_ITEM* item : selectionTool->GetSelection() )
868 item->Serialize( *response.add_items() );
869
870 return response;
871}
872
873
876{
877 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
878
879 if( !documentValidation )
880 return tl::unexpected( documentValidation.error() );
881
882 BoardStackupResponse response;
883 google::protobuf::Any any;
884
886
887 any.UnpackTo( response.mutable_stackup() );
888
889 // User-settable layer names are not stored in BOARD_STACKUP at the moment
890 for( board::BoardStackupLayer& layer : *response.mutable_stackup()->mutable_layers() )
891 {
892 if( layer.type() == board::BoardStackupLayerType::BSLT_DIELECTRIC )
893 continue;
894
895 PCB_LAYER_ID id = FromProtoEnum<PCB_LAYER_ID>( layer.layer() );
896 wxCHECK2( id != UNDEFINED_LAYER, continue );
897
898 layer.set_user_name( frame()->GetBoard()->GetLayerName( id ) );
899 }
900
901 return response;
902}
903
904
907{
908 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
909
910 if( !documentValidation )
911 return tl::unexpected( documentValidation.error() );
912
914 GraphicsDefaultsResponse response;
915
916 // TODO: This should change to be an enum class
917 constexpr std::array<kiapi::board::BoardLayerClass, LAYER_CLASS_COUNT> classOrder = {
918 kiapi::board::BLC_SILKSCREEN,
919 kiapi::board::BLC_COPPER,
920 kiapi::board::BLC_EDGES,
921 kiapi::board::BLC_COURTYARD,
922 kiapi::board::BLC_FABRICATION,
923 kiapi::board::BLC_OTHER
924 };
925
926 for( int i = 0; i < LAYER_CLASS_COUNT; ++i )
927 {
928 kiapi::board::BoardLayerGraphicsDefaults* l = response.mutable_defaults()->add_layers();
929
930 l->set_layer( classOrder[i] );
931 l->mutable_line_thickness()->set_value_nm( bds.m_LineThickness[i] );
932
933 kiapi::common::types::TextAttributes* text = l->mutable_text();
934 text->mutable_size()->set_x_nm( bds.m_TextSize[i].x );
935 text->mutable_size()->set_y_nm( bds.m_TextSize[i].y );
936 text->mutable_stroke_width()->set_value_nm( bds.m_TextThickness[i] );
937 text->set_italic( bds.m_TextItalic[i] );
938 text->set_keep_upright( bds.m_TextUpright[i] );
939 }
940
941 return response;
942}
943
944
947{
948 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
949 !documentValidation )
950 {
951 return tl::unexpected( documentValidation.error() );
952 }
953
954 VECTOR2I origin;
955 const BOARD_DESIGN_SETTINGS& settings = frame()->GetBoard()->GetDesignSettings();
956
957 switch( aCtx.Request.type() )
958 {
959 case BOT_GRID:
960 origin = settings.GetGridOrigin();
961 break;
962
963 case BOT_DRILL:
964 origin = settings.GetAuxOrigin();
965 break;
966
967 default:
968 case BOT_UNKNOWN:
969 {
970 ApiResponseStatus e;
971 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
972 e.set_error_message( "Unexpected origin type" );
973 return tl::unexpected( e );
974 }
975 }
976
977 types::Vector2 reply;
978 PackVector2( reply, origin );
979 return reply;
980}
981
984{
985 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
986 return tl::unexpected( *busy );
987
988 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
989 !documentValidation )
990 {
991 return tl::unexpected( documentValidation.error() );
992 }
993
994 VECTOR2I origin = UnpackVector2( aCtx.Request.origin() );
995
996 switch( aCtx.Request.type() )
997 {
998 case BOT_GRID:
999 {
1000 PCB_EDIT_FRAME* f = frame();
1001
1002 frame()->CallAfter( [f, origin]()
1003 {
1004 // gridSetOrigin takes ownership and frees this
1005 VECTOR2D* dorigin = new VECTOR2D( origin );
1006 TOOL_MANAGER* mgr = f->GetToolManager();
1007 mgr->RunAction( PCB_ACTIONS::gridSetOrigin, dorigin );
1008 f->Refresh();
1009 } );
1010 break;
1011 }
1012
1013 case BOT_DRILL:
1014 {
1015 PCB_EDIT_FRAME* f = frame();
1016
1017 frame()->CallAfter( [f, origin]()
1018 {
1019 TOOL_MANAGER* mgr = f->GetToolManager();
1020 mgr->RunAction( PCB_ACTIONS::drillSetOrigin, origin );
1021 f->Refresh();
1022 } );
1023 break;
1024 }
1025
1026 default:
1027 case BOT_UNKNOWN:
1028 {
1029 ApiResponseStatus e;
1030 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1031 e.set_error_message( "Unexpected origin type" );
1032 return tl::unexpected( e );
1033 }
1034 }
1035
1036 return Empty();
1037}
1038
1039
1042{
1043 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1044 return tl::unexpected( *busy );
1045
1046 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1047 {
1048 ApiResponseStatus e;
1049 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1050 e.set_status( ApiStatusCode::AS_UNHANDLED );
1051 return tl::unexpected( e );
1052 }
1053
1054 GetBoundingBoxResponse response;
1055 bool includeText = aCtx.Request.mode() == BoundingBoxMode::BBM_ITEM_AND_CHILD_TEXT;
1056
1057 for( const types::KIID& idMsg : aCtx.Request.items() )
1058 {
1059 KIID id( idMsg.value() );
1060 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1061
1062 if( !optItem )
1063 continue;
1064
1065 BOARD_ITEM* item = *optItem;
1066 BOX2I bbox;
1067
1068 if( item->Type() == PCB_FOOTPRINT_T )
1069 bbox = static_cast<FOOTPRINT*>( item )->GetBoundingBox( includeText );
1070 else
1071 bbox = item->GetBoundingBox();
1072
1073 response.add_items()->set_value( idMsg.value() );
1074 PackBox2( *response.add_boxes(), bbox );
1075 }
1076
1077 return response;
1078}
1079
1080
1083{
1084 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1085 !documentValidation )
1086 {
1087 return tl::unexpected( documentValidation.error() );
1088 }
1089
1090 PadShapeAsPolygonResponse response;
1091 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( aCtx.Request.layer() );
1092
1093 for( const types::KIID& padRequest : aCtx.Request.pads() )
1094 {
1095 KIID id( padRequest.value() );
1096 std::optional<BOARD_ITEM*> optPad = getItemById( id );
1097
1098 if( !optPad || ( *optPad )->Type() != PCB_PAD_T )
1099 continue;
1100
1101 response.add_pads()->set_value( padRequest.value() );
1102
1103 PAD* pad = static_cast<PAD*>( *optPad );
1104 SHAPE_POLY_SET poly;
1105 pad->TransformShapeToPolygon( poly, pad->Padstack().EffectiveLayerFor( layer ), 0,
1106 pad->GetMaxError(), ERROR_INSIDE );
1107
1108 types::PolygonWithHoles* polyMsg = response.mutable_polygons()->Add();
1109 PackPolyLine( *polyMsg->mutable_outline(), poly.COutline( 0 ) );
1110 }
1111
1112 return response;
1113}
1114
1115
1118{
1119 using board::types::BoardLayer;
1120
1121 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1122 !documentValidation )
1123 {
1124 return tl::unexpected( documentValidation.error() );
1125 }
1126
1127 PadstackPresenceResponse response;
1128
1129 LSET layers;
1130
1131 for( const int layer : aCtx.Request.layers() )
1132 layers.set( FromProtoEnum<PCB_LAYER_ID, BoardLayer>( static_cast<BoardLayer>( layer ) ) );
1133
1134 for( const types::KIID& padRequest : aCtx.Request.items() )
1135 {
1136 KIID id( padRequest.value() );
1137 std::optional<BOARD_ITEM*> optItem = getItemById( id );
1138
1139 if( !optItem )
1140 continue;
1141
1142 switch( ( *optItem )->Type() )
1143 {
1144 case PCB_PAD_T:
1145 {
1146 PAD* pad = static_cast<PAD*>( *optItem );
1147
1148 for( PCB_LAYER_ID layer : layers )
1149 {
1150 PadstackPresenceEntry* entry = response.add_entries();
1151 entry->mutable_item()->set_value( pad->m_Uuid.AsStdString() );
1152 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1153 entry->set_presence( pad->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1154 }
1155
1156 break;
1157 }
1158
1159 case PCB_VIA_T:
1160 {
1161 PCB_VIA* via = static_cast<PCB_VIA*>( *optItem );
1162
1163 for( PCB_LAYER_ID layer : layers )
1164 {
1165 PadstackPresenceEntry* entry = response.add_entries();
1166 entry->mutable_item()->set_value( via->m_Uuid.AsStdString() );
1167 entry->set_layer( ToProtoEnum<PCB_LAYER_ID, BoardLayer>( layer ) );
1168 entry->set_presence( via->FlashLayer( layer ) ? PSP_PRESENT : PSP_NOT_PRESENT );
1169 }
1170
1171 break;
1172 }
1173
1174 default:
1175 break;
1176 }
1177 }
1178
1179 return response;
1180}
1181
1182
1185{
1186 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1187
1188 if( !documentValidation )
1189 return tl::unexpected( documentValidation.error() );
1190
1191 BOARD* board = frame()->GetBoard();
1192 const TITLE_BLOCK& block = board->GetTitleBlock();
1193
1194 types::TitleBlockInfo response;
1195
1196 response.set_title( block.GetTitle().ToUTF8() );
1197 response.set_date( block.GetDate().ToUTF8() );
1198 response.set_revision( block.GetRevision().ToUTF8() );
1199 response.set_company( block.GetCompany().ToUTF8() );
1200 response.set_comment1( block.GetComment( 0 ).ToUTF8() );
1201 response.set_comment2( block.GetComment( 1 ).ToUTF8() );
1202 response.set_comment3( block.GetComment( 2 ).ToUTF8() );
1203 response.set_comment4( block.GetComment( 3 ).ToUTF8() );
1204 response.set_comment5( block.GetComment( 4 ).ToUTF8() );
1205 response.set_comment6( block.GetComment( 5 ).ToUTF8() );
1206 response.set_comment7( block.GetComment( 6 ).ToUTF8() );
1207 response.set_comment8( block.GetComment( 7 ).ToUTF8() );
1208 response.set_comment9( block.GetComment( 8 ).ToUTF8() );
1209
1210 return response;
1211}
1212
1213
1216{
1217 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1218
1219 if( !documentValidation )
1220 return tl::unexpected( documentValidation.error() );
1221
1222 ExpandTextVariablesResponse reply;
1223 BOARD* board = frame()->GetBoard();
1224
1225 std::function<bool( wxString* )> textResolver =
1226 [&]( wxString* token ) -> bool
1227 {
1228 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1229 return board->ResolveTextVar( token, 0 );
1230 };
1231
1232 for( const std::string& textMsg : aCtx.Request.text() )
1233 {
1234 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver );
1235 reply.add_text( text.ToUTF8() );
1236 }
1237
1238 return reply;
1239}
1240
1241
1244{
1245 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1246 return tl::unexpected( *busy );
1247
1248 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1249
1250 if( !documentValidation )
1251 return tl::unexpected( documentValidation.error() );
1252
1253 TOOL_MANAGER* mgr = frame()->GetToolManager();
1254 std::vector<EDA_ITEM*> toSelect;
1255
1256 for( const kiapi::common::types::KIID& id : aCtx.Request.items() )
1257 {
1258 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
1259 toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
1260 }
1261
1262 if( toSelect.empty() )
1263 {
1264 ApiResponseStatus e;
1265 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1266 e.set_error_message( fmt::format( "None of the given items exist on the board",
1267 aCtx.Request.board().board_filename() ) );
1268 return tl::unexpected( e );
1269 }
1270
1271 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1272 selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
1273
1275 mgr->RunAction<EDA_ITEMS*>( ACTIONS::selectItems, &toSelect );
1276
1277 COMMIT* commit = getCurrentCommit( aCtx.ClientName );
1278 mgr->PostAPIAction( PCB_ACTIONS::move, commit );
1279
1280 return Empty();
1281}
1282
1283
1285{
1286 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1287
1288 if( !documentValidation )
1289 return tl::unexpected( documentValidation.error() );
1290
1291 NetsResponse response;
1292 BOARD* board = frame()->GetBoard();
1293
1294 std::set<wxString> netclassFilter;
1295
1296 for( const std::string& nc : aCtx.Request.netclass_filter() )
1297 netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
1298
1299 for( NETINFO_ITEM* net : board->GetNetInfo() )
1300 {
1301 NETCLASS* nc = net->GetNetClass();
1302
1303 if( !netclassFilter.empty() && nc && !netclassFilter.count( nc->GetName() ) )
1304 continue;
1305
1306 board::types::Net* netProto = response.add_nets();
1307 netProto->set_name( net->GetNetname() );
1308 netProto->mutable_code()->set_value( net->GetNetCode() );
1309 }
1310
1311 return response;
1312}
1313
1314
1317{
1318 NetClassForNetsResponse response;
1319
1320 BOARD* board = frame()->GetBoard();
1321 const NETINFO_LIST& nets = board->GetNetInfo();
1322 google::protobuf::Any any;
1323
1324 for( const board::types::Net& net : aCtx.Request.net() )
1325 {
1326 NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
1327
1328 if( !netInfo )
1329 continue;
1330
1331 netInfo->GetNetClass()->Serialize( any );
1332 auto [pair, rc] = response.mutable_classes()->insert( { net.name(), {} } );
1333 any.UnpackTo( &pair->second );
1334 }
1335
1336 return response;
1337}
1338
1339
1341{
1342 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1343 return tl::unexpected( *busy );
1344
1345 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1346
1347 if( !documentValidation )
1348 return tl::unexpected( documentValidation.error() );
1349
1350 if( aCtx.Request.zones().empty() )
1351 {
1352 TOOL_MANAGER* mgr = frame()->GetToolManager();
1353 frame()->CallAfter( [mgr]()
1354 {
1356 } );
1357 }
1358 else
1359 {
1360 // TODO
1361 ApiResponseStatus e;
1362 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
1363 return tl::unexpected( e );
1364 }
1365
1366 return Empty();
1367}
1368
1369
1372{
1373 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1374
1375 if( !documentValidation )
1376 return tl::unexpected( documentValidation.error() );
1377
1378 SavedDocumentResponse response;
1379 response.mutable_document()->CopyFrom( aCtx.Request.document() );
1380
1381 CLIPBOARD_IO io;
1382 io.SetWriter(
1383 [&]( const wxString& aData )
1384 {
1385 response.set_contents( aData.ToUTF8() );
1386 } );
1387
1388 io.SaveBoard( wxEmptyString, frame()->GetBoard(), nullptr );
1389
1390 return response;
1391}
1392
1393
1396{
1397 SavedSelectionResponse response;
1398
1399 TOOL_MANAGER* mgr = frame()->GetToolManager();
1400 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
1401 PCB_SELECTION& selection = selectionTool->GetSelection();
1402
1403 CLIPBOARD_IO io;
1404 io.SetWriter(
1405 [&]( const wxString& aData )
1406 {
1407 response.set_contents( aData.ToUTF8() );
1408 } );
1409
1410 io.SetBoard( frame()->GetBoard() );
1411 io.SaveSelection( selection, false );
1412
1413 return response;
1414}
1415
1416
1419{
1420 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1421 return tl::unexpected( *busy );
1422
1423 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1424
1425 if( !documentValidation )
1426 return tl::unexpected( documentValidation.error() );
1427
1428 CreateItemsResponse response;
1429 return response;
1430}
1431
1432
1435{
1436 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1437
1438 if( !documentValidation )
1439 return tl::unexpected( documentValidation.error() );
1440
1441 BoardLayers response;
1442
1443 for( PCB_LAYER_ID layer : frame()->GetBoard()->GetVisibleLayers() )
1444 response.add_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
1445
1446 return response;
1447}
1448
1449
1452{
1453 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1454 return tl::unexpected( *busy );
1455
1456 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1457
1458 if( !documentValidation )
1459 return tl::unexpected( documentValidation.error() );
1460
1461 LSET visible;
1462 LSET enabled = frame()->GetBoard()->GetEnabledLayers();
1463
1464 for( int layerIdx : aCtx.Request.layers() )
1465 {
1466 PCB_LAYER_ID layer =
1467 FromProtoEnum<PCB_LAYER_ID>( static_cast<board::types::BoardLayer>( layerIdx ) );
1468
1469 if( enabled.Contains( layer ) )
1470 visible.set( layer );
1471 }
1472
1473 frame()->GetBoard()->SetVisibleLayers( visible );
1476 frame()->Refresh();
1477 return Empty();
1478}
1479
1480
1483{
1484 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1485
1486 if( !documentValidation )
1487 return tl::unexpected( documentValidation.error() );
1488
1489 BoardLayerResponse response;
1490 response.set_layer(
1491 ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( frame()->GetActiveLayer() ) );
1492
1493 return response;
1494}
1495
1496
1499{
1500 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1501 return tl::unexpected( *busy );
1502
1503 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1504
1505 if( !documentValidation )
1506 return tl::unexpected( documentValidation.error() );
1507
1508 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID>( aCtx.Request.layer() );
1509
1510 if( !frame()->GetBoard()->GetEnabledLayers().Contains( layer ) )
1511 {
1512 ApiResponseStatus err;
1513 err.set_status( ApiStatusCode::AS_BAD_REQUEST );
1514 err.set_error_message( fmt::format( "Layer {} is not a valid layer for the given board",
1515 magic_enum::enum_name( layer ) ) );
1516 return tl::unexpected( err );
1517 }
1518
1519 frame()->SetActiveLayer( layer );
1520 return Empty();
1521}
1522
1523
1526{
1527 BoardEditorAppearanceSettings reply;
1528
1529 // TODO: might be nice to put all these things in one place and have it derive SERIALIZABLE
1530
1531 const PCB_DISPLAY_OPTIONS& displayOptions = frame()->GetDisplayOptions();
1532
1533 reply.set_inactive_layer_display( ToProtoEnum<HIGH_CONTRAST_MODE, InactiveLayerDisplayMode>(
1534 displayOptions.m_ContrastModeDisplay ) );
1535 reply.set_net_color_display(
1536 ToProtoEnum<NET_COLOR_MODE, NetColorDisplayMode>( displayOptions.m_NetColorMode ) );
1537
1538 reply.set_board_flip( frame()->GetCanvas()->GetView()->IsMirroredX()
1539 ? BoardFlipMode::BFM_FLIPPED_X
1540 : BoardFlipMode::BFM_NORMAL );
1541
1542 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
1543
1544 reply.set_ratsnest_display( ToProtoEnum<RATSNEST_MODE, RatsnestDisplayMode>(
1545 editorSettings->m_Display.m_RatsnestMode ) );
1546
1547 return reply;
1548}
1549
1550
1553{
1554 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1555 return tl::unexpected( *busy );
1556
1558 KIGFX::PCB_VIEW* view = frame()->GetCanvas()->GetView();
1559 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
1560 const BoardEditorAppearanceSettings& newSettings = aCtx.Request.settings();
1561
1562 options.m_ContrastModeDisplay =
1563 FromProtoEnum<HIGH_CONTRAST_MODE>( newSettings.inactive_layer_display() );
1564 options.m_NetColorMode =
1565 FromProtoEnum<NET_COLOR_MODE>( newSettings.net_color_display() );
1566
1567 bool flip = newSettings.board_flip() == BoardFlipMode::BFM_FLIPPED_X;
1568
1569 if( flip != view->IsMirroredX() )
1570 {
1571 view->SetMirror( !view->IsMirroredX(), view->IsMirroredY() );
1572 view->RecacheAllItems();
1573 }
1574
1575 editorSettings->m_Display.m_RatsnestMode =
1576 FromProtoEnum<RATSNEST_MODE>( newSettings.ratsnest_display() );
1577
1578 frame()->SetDisplayOptions( options );
1580 frame()->GetCanvas()->Refresh();
1581
1582 return Empty();
1583}
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
Definition: approximation.h:34
@ LAYER_CLASS_COUNT
static TOOL_ACTION selectionClear
Clear the current selection.
Definition: actions.h:221
static TOOL_ACTION gridSetOrigin
Definition: actions.h:192
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition: actions.h:229
Base class for API handlers related to editor frames.
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.
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< 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< 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 SetContentModified(bool aModified=true)
Definition: base_screen.h:59
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)
Definition: board_item.cpp:72
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:317
BOARD_STACKUP GetStackupOrDefault() const
Definition: board.cpp:2459
const NETINFO_LIST & GetNetInfo() const
Definition: board.h:934
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:939
const ZONES & Zones() const
Definition: board.h:362
const GROUPS & Groups() const
The groups must maintain the following invariants.
Definition: board.h:389
bool ResolveTextVar(wxString *token, int aDepth) const
Definition: board.cpp:500
TITLE_BLOCK & GetTitleBlock()
Definition: board.h:748
const FOOTPRINTS & Footprints() const
Definition: board.h:358
const TRACKS & Tracks() const
Definition: board.h:356
const wxString & GetFileName() const
Definition: board.h:354
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:1024
const LSET & GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:907
BOARD_ITEM * ResolveItem(const KIID &aID, bool aAllowNullptrReturn=false) const
Definition: board.cpp:1583
const DRAWINGS & Drawings() const
Definition: board.h:360
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:73
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition: commit.h:91
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:107
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition: commit.h:79
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:402
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.
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:37
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:56
NETCLASS * GetNetClass()
Definition: netinfo.h:101
Container for NETINFO_ITEM elements, which are the nets.
Definition: netinfo.h:346
NETINFO_ITEM * GetNetItem(int aNetCode) const
Definition: pad.h:54
DISPLAY_OPTIONS m_Display
static TOOL_ACTION zoneFillAll
Definition: pcb_actions.h:392
static TOOL_ACTION move
move or drag an item
Definition: pcb_actions.h:106
static TOOL_ACTION drillSetOrigin
Definition: pcb_actions.h:552
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.
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.
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:373
int AddItemsToSel(const TOOL_EVENT &aEvent)
int RemoveItemsFromSel(const TOOL_EVENT &aEvent)
void SetReferencePoint(const VECTOR2I &aP)
Definition: selection.cpp:178
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
Definition: title_block.h:107
const wxString & GetTitle() const
Definition: title_block.h:63
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Definition: tools_holder.h:55
Master controller class:
Definition: tool_manager.h:62
bool RunAction(const std::string &aActionName, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
Definition: tool_manager.h:150
bool PostAPIAction(const TOOL_ACTION &aAction, COMMIT *aCommit)
Definition: tool_manager.h:282
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:608
#define KICTL_REVERT
reverting to a previously-saved (KiCad) file.
Definition: kiway_player.h:78
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
@ UNDEFINED_LAYER
Definition: layer_ids.h:61
KICOMMON_API void PackBox2(types::Box2 &aOutput, const BOX2I &aInput)
Definition: api_utils.cpp:105
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:85
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput)
Definition: api_utils.cpp:78
KICOMMON_API void PackPolyLine(types::PolyLine &aOutput, const SHAPE_LINE_CHAIN &aSlc)
Definition: api_utils.cpp:118
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:105
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition: typeinfo.h:102
@ 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:103
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition: typeinfo.h:110
@ 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:107
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition: typeinfo.h:92
@ 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:101
@ 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:104
VECTOR2< double > VECTOR2D
Definition: vector2d.h:694