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 (C) 2023 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 <project.h>
41#include <tool/tool_manager.h>
42#include <tools/pcb_actions.h>
44#include <zone.h>
45
46#include <api/common/types/base_types.pb.h>
48
49using namespace kiapi::common::commands;
50using types::CommandStatus;
51using types::DocumentType;
52using types::ItemRequestStatus;
53
54
56 API_HANDLER_EDITOR( aFrame )
57{
58 registerHandler<RunAction, RunActionResponse>( &API_HANDLER_PCB::handleRunAction );
59 registerHandler<GetOpenDocuments, GetOpenDocumentsResponse>(
61
62
63 registerHandler<GetItems, GetItemsResponse>( &API_HANDLER_PCB::handleGetItems );
64
65 registerHandler<GetBoardStackup, BoardStackupResponse>( &API_HANDLER_PCB::handleGetStackup );
66 registerHandler<GetGraphicsDefaults, GraphicsDefaultsResponse>(
68 registerHandler<GetBoundingBox, GetBoundingBoxResponse>(
70 registerHandler<GetPadShapeAsPolygon, PadShapeAsPolygonResponse>(
72 registerHandler<GetTitleBlockInfo, types::TitleBlockInfo>(
74 registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
76
77 registerHandler<InteractiveMoveItems, Empty>( &API_HANDLER_PCB::handleInteractiveMoveItems );
78 registerHandler<GetNets, NetsResponse>( &API_HANDLER_PCB::handleGetNets );
79 registerHandler<RefillZones, Empty>( &API_HANDLER_PCB::handleRefillZones );
80
81 registerHandler<SaveDocumentToString, SavedDocumentResponse>(
83 registerHandler<SaveSelectionToString, SavedSelectionResponse>(
85 registerHandler<ParseAndCreateItemsFromString, CreateItemsResponse>(
87 registerHandler<GetVisibleLayers, BoardLayers>( &API_HANDLER_PCB::handleGetVisibleLayers );
88 registerHandler<SetVisibleLayers, Empty>( &API_HANDLER_PCB::handleSetVisibleLayers );
89 registerHandler<GetActiveLayer, BoardLayerResponse>( &API_HANDLER_PCB::handleGetActiveLayer );
90 registerHandler<SetActiveLayer, Empty>( &API_HANDLER_PCB::handleSetActiveLayer );
91}
92
93
95{
96 return static_cast<PCB_EDIT_FRAME*>( m_frame );
97}
98
99
101 const HANDLER_CONTEXT& )
102{
103 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
104 return tl::unexpected( *busy );
105
106 RunActionResponse response;
107
108 if( frame()->GetToolManager()->RunAction( aRequest.action(), true ) )
109 response.set_status( RunActionStatus::RAS_OK );
110 else
111 response.set_status( RunActionStatus::RAS_INVALID );
112
113 return response;
114}
115
116
118 GetOpenDocuments& aMsg, const HANDLER_CONTEXT& )
119{
120 if( aMsg.type() != DocumentType::DOCTYPE_PCB )
121 {
122 ApiResponseStatus e;
123 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
124 e.set_status( ApiStatusCode::AS_UNHANDLED );
125 return tl::unexpected( e );
126 }
127
128 GetOpenDocumentsResponse response;
129 common::types::DocumentSpecifier doc;
130
131 wxFileName fn( frame()->GetCurrentFileName() );
132
133 doc.set_type( DocumentType::DOCTYPE_PCB );
134 doc.set_board_filename( fn.GetFullName() );
135
136 doc.mutable_project()->set_name( frame()->Prj().GetProjectName().ToStdString() );
137 doc.mutable_project()->set_path( frame()->Prj().GetProjectDirectory().ToStdString() );
138
139 response.mutable_documents()->Add( std::move( doc ) );
140 return response;
141}
142
143
144void API_HANDLER_PCB::pushCurrentCommit( const HANDLER_CONTEXT& aCtx, const wxString& aMessage )
145{
147 frame()->Refresh();
148}
149
150
151std::unique_ptr<COMMIT> API_HANDLER_PCB::createCommit()
152{
153 return std::make_unique<BOARD_COMMIT>( frame() );
154}
155
156
157std::optional<BOARD_ITEM*> API_HANDLER_PCB::getItemById( const KIID& aId ) const
158{
159 BOARD_ITEM* item = frame()->GetBoard()->GetItem( aId );
160
161 if( item == DELETED_BOARD_ITEM::GetInstance() )
162 return std::nullopt;
163
164 return item;
165}
166
167
168bool API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
169{
170 if( aDocument.type() != DocumentType::DOCTYPE_PCB )
171 return false;
172
173 wxFileName fn( frame()->GetCurrentFileName() );
174 return 0 == aDocument.board_filename().compare( fn.GetFullName() );
175}
176
177
179 BOARD_ITEM_CONTAINER* aContainer )
180{
181 if( !aContainer )
182 {
183 ApiResponseStatus e;
184 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
185 e.set_error_message( "Tried to create an item in a null container" );
186 return tl::unexpected( e );
187 }
188
189 if( aType == PCB_PAD_T && !dynamic_cast<FOOTPRINT*>( aContainer ) )
190 {
191 ApiResponseStatus e;
192 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
193 e.set_error_message( fmt::format( "Tried to create a pad in {}, which is not a footprint",
194 aContainer->GetFriendlyName().ToStdString() ) );
195 return tl::unexpected( e );
196 }
197 else if( aType == PCB_FOOTPRINT_T && !dynamic_cast<BOARD*>( aContainer ) )
198 {
199 ApiResponseStatus e;
200 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
201 e.set_error_message( fmt::format( "Tried to create a footprint in {}, which is not a board",
202 aContainer->GetFriendlyName().ToStdString() ) );
203 return tl::unexpected( e );
204 }
205
206 std::unique_ptr<BOARD_ITEM> created = CreateItemForType( aType, aContainer );
207
208 if( !created )
209 {
210 ApiResponseStatus e;
211 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
212 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
213 magic_enum::enum_name( aType ) ) );
214 return tl::unexpected( e );
215 }
216
217 return created;
218}
219
220
222 const HANDLER_CONTEXT& aCtx,
223 const types::ItemHeader &aHeader,
224 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
225 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
226{
227 ApiResponseStatus e;
228
229 auto containerResult = validateItemHeaderDocument( aHeader );
230
231 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
232 {
233 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
234 e.set_status( ApiStatusCode::AS_UNHANDLED );
235 return tl::unexpected( e );
236 }
237 else if( !containerResult )
238 {
239 e.CopyFrom( containerResult.error() );
240 return tl::unexpected( e );
241 }
242
243 BOARD* board = frame()->GetBoard();
244 BOARD_ITEM_CONTAINER* container = board;
245
246 if( containerResult->has_value() )
247 {
248 const KIID& containerId = **containerResult;
249 std::optional<BOARD_ITEM*> optItem = getItemById( containerId );
250
251 if( optItem )
252 {
253 container = dynamic_cast<BOARD_ITEM_CONTAINER*>( *optItem );
254
255 if( !container )
256 {
257 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
258 e.set_error_message( fmt::format(
259 "The requested container {} is not a valid board item container",
260 containerId.AsStdString() ) );
261 return tl::unexpected( e );
262 }
263 }
264 else
265 {
266 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
267 e.set_error_message( fmt::format(
268 "The requested container {} does not exist in this document",
269 containerId.AsStdString() ) );
270 return tl::unexpected( e );
271 }
272 }
273
274 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx ) );
275
276 for( const google::protobuf::Any& anyItem : aItems )
277 {
278 ItemStatus status;
279 std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
280
281 if( !type )
282 {
283 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
284 status.set_error_message( fmt::format( "Could not decode a valid type from {}",
285 anyItem.type_url() ) );
286 aItemHandler( status, anyItem );
287 continue;
288 }
289
290 if( type == PCB_DIMENSION_T )
291 {
292 board::types::Dimension dimension;
293 anyItem.UnpackTo( &dimension );
294
295 switch( dimension.dimension_style_case() )
296 {
297 case board::types::Dimension::kAligned: type = PCB_DIM_ALIGNED_T; break;
298 case board::types::Dimension::kOrthogonal: type = PCB_DIM_ORTHOGONAL_T; break;
299 case board::types::Dimension::kRadial: type = PCB_DIM_RADIAL_T; break;
300 case board::types::Dimension::kLeader: type = PCB_DIM_LEADER_T; break;
301 case board::types::Dimension::kCenter: type = PCB_DIM_CENTER_T; break;
302 case board::types::Dimension::DIMENSION_STYLE_NOT_SET: break;
303 }
304 }
305
307 createItemForType( *type, container );
308
309 if( !creationResult )
310 {
311 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
312 status.set_error_message( creationResult.error().error_message() );
313 aItemHandler( status, anyItem );
314 continue;
315 }
316
317 std::unique_ptr<BOARD_ITEM> item( std::move( *creationResult ) );
318
319 if( !item->Deserialize( anyItem ) )
320 {
321 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
322 e.set_error_message( fmt::format( "could not unpack {} from request",
323 item->GetClass().ToStdString() ) );
324 return tl::unexpected( e );
325 }
326
327 std::optional<BOARD_ITEM*> optItem = getItemById( item->m_Uuid );
328
329 if( aCreate && optItem )
330 {
331 status.set_code( ItemStatusCode::ISC_EXISTING );
332 status.set_error_message( fmt::format( "an item with UUID {} already exists",
333 item->m_Uuid.AsStdString() ) );
334 aItemHandler( status, anyItem );
335 continue;
336 }
337 else if( !aCreate && !optItem )
338 {
339 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
340 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
341 item->m_Uuid.AsStdString() ) );
342 aItemHandler( status, anyItem );
343 continue;
344 }
345
346 if( aCreate && !board->GetEnabledLayers().Contains( item->GetLayer() ) )
347 {
348 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
349 status.set_error_message( fmt::format( "attempted to add item on disabled layer {}",
350 LayerName( item->GetLayer() ).ToStdString() ) );
351 aItemHandler( status, anyItem );
352 continue;
353 }
354
355 status.set_code( ItemStatusCode::ISC_OK );
356 google::protobuf::Any newItem;
357
358 if( aCreate )
359 {
360 item->Serialize( newItem );
361 commit->Add( item.release() );
362 }
363 else
364 {
365 BOARD_ITEM* boardItem = *optItem;
366 commit->Modify( boardItem );
367 boardItem->SwapItemData( item.get() );
368 boardItem->Serialize( newItem );
369 }
370
371 aItemHandler( status, newItem );
372 }
373
374 if( !m_activeClients.count( aCtx.ClientName ) )
375 {
376 pushCurrentCommit( aCtx, aCreate ? _( "Created items via API" )
377 : _( "Added items via API" ) );
378 }
379
380
381 return ItemRequestStatus::IRS_OK;
382}
383
384
386 const HANDLER_CONTEXT& )
387{
388 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
389 return tl::unexpected( *busy );
390
391 if( !validateItemHeaderDocument( aMsg.header() ) )
392 {
393 ApiResponseStatus e;
394 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
395 e.set_status( ApiStatusCode::AS_UNHANDLED );
396 return tl::unexpected( e );
397 }
398
399 GetItemsResponse response;
400
401 BOARD* board = frame()->GetBoard();
402 std::vector<BOARD_ITEM*> items;
403 std::set<KICAD_T> typesRequested, typesInserted;
404 bool handledAnything = false;
405
406 for( int typeRaw : aMsg.types() )
407 {
408 auto typeMessage = static_cast<common::types::KiCadObjectType>( typeRaw );
409 KICAD_T type = FromProtoEnum<KICAD_T>( typeMessage );
410
411 if( type == TYPE_NOT_INIT )
412 continue;
413
414 typesRequested.emplace( type );
415
416 if( typesInserted.count( type ) )
417 continue;
418
419 switch( type )
420 {
421 case PCB_TRACE_T:
422 case PCB_ARC_T:
423 case PCB_VIA_T:
424 handledAnything = true;
425 std::copy( board->Tracks().begin(), board->Tracks().end(),
426 std::back_inserter( items ) );
427 typesInserted.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
428 break;
429
430 case PCB_PAD_T:
431 {
432 handledAnything = true;
433
434 for( FOOTPRINT* fp : board->Footprints() )
435 {
436 std::copy( fp->Pads().begin(), fp->Pads().end(),
437 std::back_inserter( items ) );
438 }
439
440 typesInserted.insert( PCB_PAD_T );
441 break;
442 }
443
444 case PCB_FOOTPRINT_T:
445 {
446 handledAnything = true;
447
448 std::copy( board->Footprints().begin(), board->Footprints().end(),
449 std::back_inserter( items ) );
450
451 typesInserted.insert( PCB_FOOTPRINT_T );
452 break;
453 }
454
455 case PCB_SHAPE_T:
456 case PCB_TEXT_T:
457 case PCB_TEXTBOX_T:
458 {
459 handledAnything = true;
460 bool inserted = false;
461
462 for( BOARD_ITEM* item : board->Drawings() )
463 {
464 if( item->Type() == type )
465 {
466 items.emplace_back( item );
467 inserted = true;
468 }
469 }
470
471 if( inserted )
472 typesInserted.insert( PCB_SHAPE_T );
473
474 break;
475 }
476
477 case PCB_ZONE_T:
478 {
479 handledAnything = true;
480
481 std::copy( board->Zones().begin(), board->Zones().end(),
482 std::back_inserter( items ) );
483
484 typesInserted.insert( PCB_ZONE_T );
485 break;
486 }
487
488 default:
489 break;
490 }
491 }
492
493 if( !handledAnything )
494 {
495 ApiResponseStatus e;
496 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
497 e.set_error_message( "none of the requested types are valid for a Board object" );
498 return tl::unexpected( e );
499 }
500
501 for( const BOARD_ITEM* item : items )
502 {
503 if( !typesRequested.count( item->Type() ) )
504 continue;
505
506 google::protobuf::Any itemBuf;
507 item->Serialize( itemBuf );
508 response.mutable_items()->Add( std::move( itemBuf ) );
509 }
510
511 response.set_status( ItemRequestStatus::IRS_OK );
512 return response;
513}
514
515
516void API_HANDLER_PCB::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
517 const HANDLER_CONTEXT& aCtx )
518{
519 BOARD* board = frame()->GetBoard();
520 std::vector<BOARD_ITEM*> validatedItems;
521
522 for( std::pair<const KIID, ItemDeletionStatus> pair : aItemsToDelete )
523 {
524 if( BOARD_ITEM* item = board->GetItem( pair.first ) )
525 {
526 validatedItems.push_back( item );
527 aItemsToDelete[pair.first] = ItemDeletionStatus::IDS_OK;
528 }
529
530 // Note: we don't currently support locking items from API modification, but here is where
531 // to add it in the future (and return IDS_IMMUTABLE)
532 }
533
534 COMMIT* commit = getCurrentCommit( aCtx );
535
536 for( BOARD_ITEM* item : validatedItems )
537 commit->Remove( item );
538
539 if( !m_activeClients.count( aCtx.ClientName ) )
540 pushCurrentCommit( aCtx, _( "Deleted items via API" ) );
541}
542
543
544std::optional<EDA_ITEM*> API_HANDLER_PCB::getItemFromDocument( const DocumentSpecifier& aDocument,
545 const KIID& aId )
546{
547 if( !validateDocument( aDocument ) )
548 return std::nullopt;
549
550 return getItemById( aId );
551}
552
553
555 const HANDLER_CONTEXT& aCtx )
556{
557 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
558 return tl::unexpected( *busy );
559
560 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
561
562 if( !documentValidation )
563 return tl::unexpected( documentValidation.error() );
564
565 BoardStackupResponse response;
566 google::protobuf::Any any;
567
569
570 any.UnpackTo( response.mutable_stackup() );
571
572 return response;
573}
574
575
577 GetGraphicsDefaults& aMsg,
578 const HANDLER_CONTEXT& aCtx )
579{
580 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
581 return tl::unexpected( *busy );
582
583 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
584
585 if( !documentValidation )
586 return tl::unexpected( documentValidation.error() );
587
589 GraphicsDefaultsResponse response;
590
591 // TODO: This should change to be an enum class
592 constexpr std::array<kiapi::board::BoardLayerClass, LAYER_CLASS_COUNT> classOrder = {
593 kiapi::board::BLC_SILKSCREEN,
594 kiapi::board::BLC_COPPER,
595 kiapi::board::BLC_EDGES,
596 kiapi::board::BLC_COURTYARD,
597 kiapi::board::BLC_FABRICATION,
598 kiapi::board::BLC_OTHER
599 };
600
601 for( int i = 0; i < LAYER_CLASS_COUNT; ++i )
602 {
603 kiapi::board::BoardLayerGraphicsDefaults* l = response.mutable_defaults()->add_layers();
604
605 l->set_layer( classOrder[i] );
606 l->mutable_line_thickness()->set_value_nm( bds.m_LineThickness[i] );
607
608 kiapi::common::types::TextAttributes* text = l->mutable_text();
609 text->mutable_size()->set_x_nm( bds.m_TextSize[i].x );
610 text->mutable_size()->set_y_nm( bds.m_TextSize[i].y );
611 text->mutable_stroke_width()->set_value_nm( bds.m_TextThickness[i] );
612 text->set_italic( bds.m_TextItalic[i] );
613 text->set_keep_upright( bds.m_TextUpright[i] );
614 }
615
616 return response;
617}
618
619
621 const HANDLER_CONTEXT& aCtx )
622{
623 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
624 return tl::unexpected( *busy );
625
626 if( !validateItemHeaderDocument( aMsg.header() ) )
627 {
628 ApiResponseStatus e;
629 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
630 e.set_status( ApiStatusCode::AS_UNHANDLED );
631 return tl::unexpected( e );
632 }
633
634 GetBoundingBoxResponse response;
635 bool includeText = aMsg.mode() == BoundingBoxMode::BBM_ITEM_AND_CHILD_TEXT;
636
637 for( const types::KIID& idMsg : aMsg.items() )
638 {
639 KIID id( idMsg.value() );
640 std::optional<BOARD_ITEM*> optItem = getItemById( id );
641
642 if( !optItem )
643 continue;
644
645 BOARD_ITEM* item = *optItem;
646 BOX2I bbox;
647
648 if( item->Type() == PCB_FOOTPRINT_T )
649 bbox = static_cast<FOOTPRINT*>( item )->GetBoundingBox( includeText );
650 else
651 bbox = item->GetBoundingBox();
652
653 response.add_items()->set_value( idMsg.value() );
654 PackBox2( *response.add_boxes(), bbox );
655 }
656
657 return response;
658}
659
660
662 GetPadShapeAsPolygon& aMsg,
663 const HANDLER_CONTEXT& aCtx )
664{
665 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
666
667 if( !documentValidation )
668 return tl::unexpected( documentValidation.error() );
669
670 SHAPE_POLY_SET poly;
671 PadShapeAsPolygonResponse response;
672 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( aMsg.layer() );
673
674 for( const types::KIID& padRequest : aMsg.pads() )
675 {
676 KIID id( padRequest.value() );
677 std::optional<BOARD_ITEM*> optPad = getItemById( id );
678
679 if( !optPad || ( *optPad )->Type() != PCB_PAD_T )
680 continue;
681
682 response.add_pads()->set_value( padRequest.value() );
683
684 PAD* pad = static_cast<PAD*>( *optPad );
685 pad->TransformShapeToPolygon( poly, pad->Padstack().EffectiveLayerFor( layer ), 0,
687
688 types::PolygonWithHoles* polyMsg = response.mutable_polygons()->Add();
689 PackPolyLine( *polyMsg->mutable_outline(), poly.COutline( 0 ) );
690 }
691
692 return response;
693}
694
695
697 GetTitleBlockInfo& aMsg,
698 const HANDLER_CONTEXT& aCtx )
699{
700 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.document() );
701
702 if( !documentValidation )
703 return tl::unexpected( documentValidation.error() );
704
705 BOARD* board = frame()->GetBoard();
706 const TITLE_BLOCK& block = board->GetTitleBlock();
707
708 types::TitleBlockInfo response;
709
710 response.set_title( block.GetTitle().ToUTF8() );
711 response.set_date( block.GetDate().ToUTF8() );
712 response.set_revision( block.GetRevision().ToUTF8() );
713 response.set_company( block.GetCompany().ToUTF8() );
714 response.set_comment1( block.GetComment( 0 ).ToUTF8() );
715 response.set_comment2( block.GetComment( 1 ).ToUTF8() );
716 response.set_comment3( block.GetComment( 2 ).ToUTF8() );
717 response.set_comment4( block.GetComment( 3 ).ToUTF8() );
718 response.set_comment5( block.GetComment( 4 ).ToUTF8() );
719 response.set_comment6( block.GetComment( 5 ).ToUTF8() );
720 response.set_comment7( block.GetComment( 6 ).ToUTF8() );
721 response.set_comment8( block.GetComment( 7 ).ToUTF8() );
722 response.set_comment9( block.GetComment( 8 ).ToUTF8() );
723
724 return response;
725}
726
727
729 ExpandTextVariables& aMsg, const HANDLER_CONTEXT& aCtx )
730{
731 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.document() );
732
733 if( !documentValidation )
734 return tl::unexpected( documentValidation.error() );
735
736 ExpandTextVariablesResponse reply;
737 BOARD* board = frame()->GetBoard();
738
739 std::function<bool( wxString* )> textResolver =
740 [&]( wxString* token ) -> bool
741 {
742 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
743 return board->ResolveTextVar( token, 0 );
744 };
745
746 for( const std::string& textMsg : aMsg.text() )
747 {
748 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver );
749 reply.add_text( text.ToUTF8() );
750 }
751
752 return reply;
753}
754
755
757 const HANDLER_CONTEXT& aCtx )
758{
759 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
760 return tl::unexpected( *busy );
761
762 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
763
764 if( !documentValidation )
765 return tl::unexpected( documentValidation.error() );
766
768 std::vector<EDA_ITEM*> toSelect;
769
770 for( const kiapi::common::types::KIID& id : aMsg.items() )
771 {
772 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
773 toSelect.emplace_back( static_cast<EDA_ITEM*>( *item ) );
774 }
775
776 if( toSelect.empty() )
777 {
778 ApiResponseStatus e;
779 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
780 e.set_error_message( fmt::format( "None of the given items exist on the board",
781 aMsg.board().board_filename() ) );
782 return tl::unexpected( e );
783 }
784
785 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
786 selectionTool->GetSelection().SetReferencePoint( toSelect[0]->GetPosition() );
787
789 mgr->RunAction<EDA_ITEMS*>( PCB_ACTIONS::selectItems, &toSelect );
790
791 COMMIT* commit = getCurrentCommit( aCtx );
792 mgr->PostAction( PCB_ACTIONS::move, commit );
793
794 return Empty();
795}
796
797
799 const HANDLER_CONTEXT& aCtx )
800{
801 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
802 return tl::unexpected( *busy );
803
804 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
805
806 if( !documentValidation )
807 return tl::unexpected( documentValidation.error() );
808
809 NetsResponse response;
810 BOARD* board = frame()->GetBoard();
811
812 std::set<wxString> netclassFilter;
813
814 for( const std::string& nc : aMsg.netclass_filter() )
815 netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
816
817 for( NETINFO_ITEM* net : board->GetNetInfo() )
818 {
819 NETCLASS* nc = net->GetNetClass();
820
821 if( !netclassFilter.empty() && nc && !netclassFilter.count( nc->GetName() ) )
822 continue;
823
824 board::types::Net* netProto = response.add_nets();
825 netProto->set_name( net->GetNetname() );
826 netProto->mutable_code()->set_value( net->GetNetCode() );
827 }
828
829 return response;
830}
831
832
834 const HANDLER_CONTEXT& aCtx )
835{
836 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
837 return tl::unexpected( *busy );
838
839 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
840
841 if( !documentValidation )
842 return tl::unexpected( documentValidation.error() );
843
844 if( aMsg.zones().empty() )
845 {
847 frame()->CallAfter( [mgr]()
848 {
850 } );
851 }
852 else
853 {
854 // TODO
855 ApiResponseStatus e;
856 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
857 return tl::unexpected( e );
858 }
859
860 return Empty();
861}
862
863
865 SaveDocumentToString& aMsg, const HANDLER_CONTEXT& aCtx )
866{
867 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.document() );
868
869 if( !documentValidation )
870 return tl::unexpected( documentValidation.error() );
871
872 SavedDocumentResponse response;
873 response.mutable_document()->CopyFrom( aMsg.document() );
874
875 CLIPBOARD_IO io;
876 io.SetWriter(
877 [&]( const wxString& aData )
878 {
879 response.set_contents( aData.ToUTF8() );
880 } );
881
882 io.SaveBoard( wxEmptyString, frame()->GetBoard(), nullptr );
883
884 return response;
885}
886
887
889 SaveSelectionToString& aMsg, const HANDLER_CONTEXT& aCtx )
890{
891 SavedSelectionResponse response;
892
894 PCB_SELECTION_TOOL* selectionTool = mgr->GetTool<PCB_SELECTION_TOOL>();
895 PCB_SELECTION& selection = selectionTool->GetSelection();
896
897 CLIPBOARD_IO io;
898 io.SetWriter(
899 [&]( const wxString& aData )
900 {
901 response.set_contents( aData.ToUTF8() );
902 } );
903
904 io.SetBoard( frame()->GetBoard() );
905 io.SaveSelection( selection, false );
906
907 return response;
908}
909
910
912 ParseAndCreateItemsFromString& aMsg, const HANDLER_CONTEXT& aCtx )
913{
914 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
915 return tl::unexpected( *busy );
916
917 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.document() );
918
919 if( !documentValidation )
920 return tl::unexpected( documentValidation.error() );
921
922 CreateItemsResponse response;
923 return response;
924}
925
926
928 const HANDLER_CONTEXT& aCtx )
929{
930 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
931
932 if( !documentValidation )
933 return tl::unexpected( documentValidation.error() );
934
935 BoardLayers response;
936
937 for( PCB_LAYER_ID layer : frame()->GetBoard()->GetVisibleLayers() )
938 response.add_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
939
940 return response;
941}
942
943
945 const HANDLER_CONTEXT& aCtx )
946{
947 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
948 return tl::unexpected( *busy );
949
950 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
951
952 if( !documentValidation )
953 return tl::unexpected( documentValidation.error() );
954
955 LSET visible;
956 LSET enabled = frame()->GetBoard()->GetEnabledLayers();
957
958 for( int layerIdx : aMsg.layers() )
959 {
960 PCB_LAYER_ID layer =
961 FromProtoEnum<PCB_LAYER_ID>( static_cast<board::types::BoardLayer>( layerIdx ) );
962
963 if( enabled.Contains( layer ) )
964 visible.set( layer );
965 }
966
967 frame()->GetBoard()->SetVisibleLayers( visible );
970 frame()->Refresh();
971 return Empty();
972}
973
974
976 GetActiveLayer& aMsg, const HANDLER_CONTEXT& aCtx )
977{
978 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
979
980 if( !documentValidation )
981 return tl::unexpected( documentValidation.error() );
982
983 BoardLayerResponse response;
984 response.set_layer(
985 ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( frame()->GetActiveLayer() ) );
986
987 return response;
988}
989
990
992 const HANDLER_CONTEXT& aCtx )
993{
994 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
995 return tl::unexpected( *busy );
996
997 HANDLER_RESULT<bool> documentValidation = validateDocument( aMsg.board() );
998
999 if( !documentValidation )
1000 return tl::unexpected( documentValidation.error() );
1001
1002 PCB_LAYER_ID layer = FromProtoEnum<PCB_LAYER_ID>( aMsg.layer() );
1003
1004 if( !frame()->GetBoard()->GetEnabledLayers().Contains( layer ) )
1005 {
1006 ApiResponseStatus err;
1007 err.set_status( ApiStatusCode::AS_BAD_REQUEST );
1008 err.set_error_message( fmt::format( "Layer {} is not a valid layer for the given board",
1009 magic_enum::enum_name( layer ) ) );
1010 return tl::unexpected( err );
1011 }
1012
1013 frame()->SetActiveLayer( layer );
1014 return Empty();
1015}
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
constexpr int ARC_HIGH_DEF
Definition: base_units.h:120
@ LAYER_CLASS_COUNT
Base class for API handlers related to editor frames.
virtual void pushCurrentCommit(const HANDLER_CONTEXT &aCtx, const wxString &aMessage)
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 HANDLER_CONTEXT &aCtx)
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::GetItemsResponse > handleGetItems(commands::GetItems &aMsg, const HANDLER_CONTEXT &aCtx)
bool validateDocumentInternal(const DocumentSpecifier &aDocument) const override
HANDLER_RESULT< BoardLayerResponse > handleGetActiveLayer(GetActiveLayer &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< types::TitleBlockInfo > handleGetTitleBlockInfo(commands::GetTitleBlockInfo &aMsg, const HANDLER_CONTEXT &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
std::unique_ptr< COMMIT > createCommit() override
Override this to create an appropriate COMMIT subclass for the frame in question.
HANDLER_RESULT< Empty > handleInteractiveMoveItems(InteractiveMoveItems &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< BoardStackupResponse > handleGetStackup(GetBoardStackup &aMsg, const HANDLER_CONTEXT &aCtx)
void deleteItemsInternal(std::map< KIID, ItemDeletionStatus > &aItemsToDelete, const HANDLER_CONTEXT &aCtx) override
std::optional< EDA_ITEM * > getItemFromDocument(const DocumentSpecifier &aDocument, const KIID &aId) override
HANDLER_RESULT< commands::CreateItemsResponse > handleParseAndCreateItemsFromString(commands::ParseAndCreateItemsFromString &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< commands::RunActionResponse > handleRunAction(commands::RunAction &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< BoardLayers > handleGetVisibleLayers(GetVisibleLayers &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< commands::SavedSelectionResponse > handleSaveSelectionToString(commands::SaveSelectionToString &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< Empty > handleSetActiveLayer(SetActiveLayer &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< commands::GetBoundingBoxResponse > handleGetBoundingBox(commands::GetBoundingBox &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< types::ItemRequestStatus > handleCreateUpdateItemsInternal(bool aCreate, const HANDLER_CONTEXT &aCtx, const types::ItemHeader &aHeader, const google::protobuf::RepeatedPtrField< google::protobuf::Any > &aItems, std::function< void(commands::ItemStatus, google::protobuf::Any)> aItemHandler) override
HANDLER_RESULT< Empty > handleRefillZones(RefillZones &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< GraphicsDefaultsResponse > handleGetGraphicsDefaults(GetGraphicsDefaults &aMsg, const HANDLER_CONTEXT &aCtx)
void pushCurrentCommit(const HANDLER_CONTEXT &aCtx, const wxString &aMessage) override
HANDLER_RESULT< commands::GetOpenDocumentsResponse > handleGetOpenDocuments(commands::GetOpenDocuments &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< Empty > handleSetVisibleLayers(SetVisibleLayers &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< commands::ExpandTextVariablesResponse > handleExpandTextVariables(commands::ExpandTextVariables &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< PadShapeAsPolygonResponse > handleGetPadShapeAsPolygon(GetPadShapeAsPolygon &aMsg, const HANDLER_CONTEXT &aCtx)
PCB_EDIT_FRAME * frame() const
HANDLER_RESULT< commands::SavedDocumentResponse > handleSaveDocumentToString(commands::SaveDocumentToString &aMsg, const HANDLER_CONTEXT &aCtx)
HANDLER_RESULT< NetsResponse > handleGetNets(GetNets &aMsg, const HANDLER_CONTEXT &aCtx)
BASE_SET & set(size_t pos)
Definition: base_set.h:115
Container for design settings for a BOARD object.
bool m_TextUpright[LAYER_CLASS_COUNT]
int m_TextThickness[LAYER_CLASS_COUNT]
int m_LineThickness[LAYER_CLASS_COUNT]
VECTOR2I m_TextSize[LAYER_CLASS_COUNT]
bool m_TextItalic[LAYER_CLASS_COUNT]
Abstract interface for BOARD_ITEMs capable of storing other items inside.
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition: board_item.h:79
void SwapItemData(BOARD_ITEM *aImage)
Swap data between aItem and aImage.
Definition: board_item.cpp:225
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:290
BOARD_STACKUP GetStackupOrDefault() const
Definition: board.cpp:2290
const NETINFO_LIST & GetNetInfo() const
Definition: board.h:871
LSET GetEnabledLayers() const
A proxy function that calls the corresponding function in m_BoardSettings.
Definition: board.cpp:778
BOARD_ITEM * GetItem(const KIID &aID) const
Definition: board.cpp:1411
const ZONES & Zones() const
Definition: board.h:335
bool ResolveTextVar(wxString *token, int aDepth) const
Definition: board.cpp:433
TITLE_BLOCK & GetTitleBlock()
Definition: board.h:695
const FOOTPRINTS & Footprints() const
Definition: board.h:331
const TRACKS & Tracks() const
Definition: board.h:329
void SetVisibleLayers(LSET aLayerMask)
A proxy function that calls the correspondent function in m_BoardSettings changes the bit-mask of vis...
Definition: board.cpp:810
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition: board.cpp:895
const DRAWINGS & Drawings() const
Definition: board.h:333
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:74
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Notify observers that aItem has been removed.
Definition: commit.h:92
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Create an undo entry for an item that has been already modified.
Definition: commit.h:105
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Notify observers that aItem has been added.
Definition: commit.h:80
static DELETED_BOARD_ITEM * GetInstance()
Definition: board_item.h:475
A base class for most all the KiCad significant classes used in schematics and boards.
Definition: eda_item.h:89
virtual const BOX2I GetBoundingBox() const
Return the orthogonal bounding box of this object for display purposes.
Definition: eda_item.cpp:77
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:101
virtual wxString GetFriendlyName() const
Definition: eda_item.cpp:332
Definition: kiid.h:49
std::string AsStdString() const
Definition: kiid.cpp:244
LSET is a set of PCB_LAYER_IDs.
Definition: lset.h:36
bool Contains(PCB_LAYER_ID aLayer) const
See if the layer set contains a PCB layer.
Definition: lset.h:60
A collection of nets and the parameters used to route or test these nets.
Definition: netclass.h:44
const wxString GetName() const
Gets the consolidated name of this netclass (which may be an aggregate)
Definition: netclass.cpp:151
Handle the data for a net.
Definition: netinfo.h:56
Definition: pad.h:54
static TOOL_ACTION zoneFillAll
Definition: pcb_actions.h:408
static TOOL_ACTION selectionClear
Clear the current selection.
Definition: pcb_actions.h:68
static TOOL_ACTION move
move or drag an item
Definition: pcb_actions.h:120
static TOOL_ACTION selectItems
Select a list of items (specified as the event parameter)
Definition: pcb_actions.h:76
APPEARANCE_CONTROLS * GetAppearancePanel()
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
BOARD * GetBoard() const
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.
The selection tool: currently supports:
PCB_SELECTION & GetSelection()
void SetReferencePoint(const VECTOR2I &aP)
Definition: selection.cpp:180
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 PostAction(const std::string &aActionName, T aParam)
Run the specified action after the current action (coroutine) ends.
Definition: tool_manager.h:235
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:536
PROJECT & Prj()
Definition: kicad.cpp:597
wxString LayerName(int aLayer)
Returns the default display name for a given layer.
Definition: layer_id.cpp:31
PCB_LAYER_ID
A quick note on layer IDs:
Definition: layer_ids.h:60
KICOMMON_API void PackBox2(types::Box2 &aOutput, const BOX2I &aInput)
Definition: api_utils.cpp:104
KICOMMON_API std::optional< KICAD_T > TypeNameFromAny(const google::protobuf::Any &aMessage)
Definition: api_utils.cpp:32
KICOMMON_API void PackPolyLine(types::PolyLine &aOutput, const SHAPE_LINE_CHAIN &aSlc)
Definition: api_utils.cpp:117
Class to handle a set of BOARD_ITEMs.
BOARD * GetBoard()
std::string ClientName
Definition: api_handler.h:50
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_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