KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_sch.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) 2024 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
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <api/api_handler_sch.h>
22#include <api/api_enums.h>
23#include <api/api_sch_utils.h>
24#include <api/api_utils.h>
26#include <api/sch_context.h>
27#include <fmt.h>
28#include <fmt/ranges.h>
29#include <wx/log.h>
30#include <magic_enum.hpp>
31#include <base_screen.h>
32#include <jobs/job_export_bom.h>
35#include <kiway.h>
37#include <sch_field.h>
38#include <sch_group.h>
39#include <common.h>
40#include <connection_graph.h>
41#include <sch_commit.h>
42#include <string_utils.h>
43#include <sch_edit_frame.h>
45#include <ki_error.h>
46#include <richio.h>
48
49#include <sch_label.h>
50#include <sch_screen.h>
51#include <sch_sheet.h>
52#include <sch_sheet_path.h>
53#include <sch_sheet_pin.h>
54#include <sch_symbol.h>
55#include <schematic.h>
56#include <tool/actions.h>
57#include <tool/tool_manager.h>
58#include <tools/sch_actions.h>
60#include <project.h>
62#include <wx/filename.h>
63
64#include <api/common/commands/library_commands.pb.h>
65#include <api/common/types/base_types.pb.h>
67#include <project_sch.h>
68#include <trace_helpers.h>
69
70using namespace kiapi::common::commands;
71using kiapi::common::types::CommandStatus;
72using kiapi::common::types::DocumentType;
73using kiapi::common::types::ItemRequestStatus;
74
75
76std::set<KICAD_T> API_HANDLER_SCH::s_allowedTypes = {
95};
96
97
99{
100 types::RunJobResponse response;
102 int exitCode = aKiway->ProcessJob( KIWAY::FACE_SCH, &aJob, &reporter );
103
104 for( const JOB_OUTPUT& output : aJob.GetOutputs() )
105 response.add_output_path( output.m_outputPath.ToUTF8() );
106
107 if( exitCode == 0 )
108 {
109 response.set_status( types::JobStatus::JS_SUCCESS );
110 return response;
111 }
112
113 response.set_status( types::JobStatus::JS_ERROR );
114 response.set_message( fmt::format( "Schematic export job '{}' failed with exit code {}: {}",
115 aJob.GetType(), exitCode,
116 reporter.GetMessages().ToStdString() ) );
117 return response;
118}
119
120
125
126
127API_HANDLER_SCH::API_HANDLER_SCH( std::shared_ptr<SCH_CONTEXT> aContext,
128 SCH_EDIT_FRAME* aFrame ) :
129 API_HANDLER_EDITOR( aFrame ),
130 m_context( std::move( aContext ) )
131{
132 using namespace kiapi::schematic::jobs;
133 using namespace kiapi::schematic::types;
134 using namespace kiapi::schematic::commands;
135
143
150
156
191}
192
193
194std::unique_ptr<COMMIT> API_HANDLER_SCH::createCommit()
195{
196 if( m_frame )
197 return std::make_unique<SCH_COMMIT>( static_cast<SCH_EDIT_FRAME*>( m_frame ) );
198
199 return std::make_unique<SCH_COMMIT>( toolManager() );
200}
201
202
204{
205 wxCHECK( m_context, nullptr );
206 return m_context->GetSchematic();
207}
208
209
211{
212 return static_cast<SCH_EDIT_FRAME*>( m_frame );
213}
214
215
216std::optional<ApiResponseStatus> API_HANDLER_SCH::checkForHeadless( const std::string& aCommandName ) const
217{
218 if( m_frame )
219 return std::nullopt;
220
221 ApiResponseStatus e;
222 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
223 e.set_error_message( fmt::format( "{} is not available in headless mode", aCommandName ) );
224 return e;
225}
226
227
228bool API_HANDLER_SCH::packSchItem( google::protobuf::Any& aOut, SCH_ITEM* aItem,
229 const SCH_SHEET_PATH& aPath )
230{
231 if( aItem->Type() == SCH_SYMBOL_T )
232 {
233 kiapi::schematic::types::SchematicSymbolInstance symbol;
234
235 if( !PackSymbol( &symbol, static_cast<SCH_SYMBOL*>( aItem ), aPath ) )
236 return false;
237
238 aOut.PackFrom( symbol );
239 }
240 else if( aItem->Type() == SCH_SHEET_T )
241 {
242 kiapi::schematic::types::SheetSymbol sheet;
243
244 if( !PackSheet( &sheet, static_cast<SCH_SHEET*>( aItem ), aPath ) )
245 return false;
246
247 aOut.PackFrom( sheet );
248 }
249 else
250 {
251 aItem->Serialize( aOut );
252 }
253
254 return true;
255}
256
257
258std::optional<SCH_ITEM*> API_HANDLER_SCH::getItemById( const KIID& aId, SCH_SHEET_PATH* aPathOut ) const
259{
260 if( !schematic()->HasHierarchy() )
262
263 SCH_ITEM* item = schematic()->ResolveItem( aId, aPathOut, true );
264
265 if( !item )
266 return std::nullopt;
267
268 return item;
269}
270
271
272tl::expected<bool, ApiResponseStatus>
273API_HANDLER_SCH::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
274{
275 if( aDocument.type() != DocumentType::DOCTYPE_SCHEMATIC )
276 {
277 ApiResponseStatus e;
278 e.set_status( ApiStatusCode::AS_UNHANDLED );
279 return tl::unexpected( e );
280 }
281
282 const PROJECT& prj = m_context->Prj();
283
284 if( aDocument.project().name().compare( prj.GetProjectName().ToUTF8() ) != 0 )
285 {
286 ApiResponseStatus e;
287 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
288 e.set_error_message( fmt::format( "the requested project {} is not open",
289 aDocument.project().name() ) );
290 return tl::unexpected( e );
291 }
292
293 if( aDocument.project().path().compare( prj.GetProjectPath().ToUTF8() ) != 0 )
294 {
295 ApiResponseStatus e;
296 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
297 e.set_error_message( fmt::format( "the requested project {} is not open at path {}",
298 aDocument.project().name(),
299 aDocument.project().path() ) );
300 return tl::unexpected( e );
301 }
302
303 if( aDocument.has_sheet_path() )
304 {
305 KIID_PATH path = UnpackSheetPath( aDocument.sheet_path() );
306
307 if( !schematic()->Hierarchy().HasPath( path ) )
308 {
309 ApiResponseStatus e;
310 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
311 e.set_error_message( fmt::format( "the requested sheet path {} is not valid for this schematic",
312 path.AsString().ToStdString() ) );
313 return tl::unexpected( e );
314 }
315 }
316
317 return true;
318}
319
320
322{
323 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
324 return tl::unexpected( *busy );
325
326 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
327
328 if( !documentValidation )
329 return tl::unexpected( documentValidation.error() );
330
331 if( !context()->SaveSchematic() )
332 {
333 ApiResponseStatus e;
334 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
335 e.set_error_message( "failed to save schematic" );
336 return tl::unexpected( e );
337 }
338
339 return google::protobuf::Empty();
340}
341
342
345{
346 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
347 return tl::unexpected( *busy );
348
349 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
350
351 if( !documentValidation )
352 return tl::unexpected( documentValidation.error() );
353
354 wxFileName schematicPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
355
356 if( !schematicPath.IsOk() || !schematicPath.IsDirWritable() )
357 {
358 ApiResponseStatus e;
359 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
360 e.set_error_message(
361 fmt::format( "save path '{}' could not be opened", schematicPath.GetFullPath().ToStdString() ) );
362 return tl::unexpected( e );
363 }
364
365 if( schematicPath.FileExists() && ( !schematicPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
366 {
367 ApiResponseStatus e;
368 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
369 e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
370 schematicPath.GetFullPath().ToStdString() ) );
371 return tl::unexpected( e );
372 }
373
374 if( schematicPath.GetExt() != FILEEXT::KiCadSchematicFileExtension )
375 {
376 ApiResponseStatus e;
377 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
378 e.set_error_message( fmt::format( "save path '{}' must have a kicad_sch extension",
379 schematicPath.GetFullPath().ToStdString() ) );
380 return tl::unexpected( e );
381 }
382
383 bool includeProject = true;
384
385 if( aCtx.Request.has_options() )
386 includeProject = aCtx.Request.options().include_project();
387
388 if( !context()->SaveSchematicCopy( schematicPath.GetFullPath(), includeProject ) )
389 {
390 ApiResponseStatus e;
391 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
392 e.set_error_message( "failed to save schematic copy" );
393 return tl::unexpected( e );
394 }
395
396 return google::protobuf::Empty();
397}
398
399
402{
403 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
404 {
405 ApiResponseStatus e;
406 e.set_status( ApiStatusCode::AS_UNHANDLED );
407 return tl::unexpected( e );
408 }
409
410 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
411
412 if( !documentValidation )
413 return tl::unexpected( documentValidation.error() );
414
415 if( !m_commits.empty() )
416 {
417 ApiResponseStatus e;
418 e.set_status( ApiStatusCode::AS_BUSY );
419 e.set_error_message( "cannot revert while a commit is open" );
420 return tl::unexpected( e );
421 }
422
423 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
424 return tl::unexpected( *busy );
425
426 if( !context()->RevertToSaved() )
427 {
428 ApiResponseStatus e;
429 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
430 e.set_error_message( "could not revert: there is no saved file on disk to revert to" );
431 return tl::unexpected( e );
432 }
433
434 return google::protobuf::Empty();
435}
436
437
440{
441 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
442
443 if( !documentValidation )
444 return tl::unexpected( documentValidation.error() );
445
446 commands::SavedDocumentResponse response;
447
448 SCH_SHEET* topLevelSheet = schematic()->GetTopLevelSheet( 0 );
449
450 if( !topLevelSheet || !topLevelSheet->GetScreen() )
451 {
452 ApiResponseStatus e;
453 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
454 e.set_error_message( "schematic has no top-level sheet to save" );
455 return tl::unexpected( e );
456 }
457
458 STRING_FORMATTER formatter;
459 SCH_IO_KICAD_SEXPR plugin;
460
461 plugin.FormatSchematicToFormatter( &formatter, topLevelSheet, schematic(), nullptr );
462
463 std::string contents = formatter.GetString();
464 KICAD_FORMAT::Prettify( contents, KICAD_FORMAT::FORMAT_MODE::COMPACT_TEXT_PROPERTIES );
465 response.set_contents( contents );
466
467 return response;
468}
469
470
473{
474 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SaveSelectionToString" ) )
475 return tl::unexpected( *headless );
476
478 SCH_SELECTION& selection = selTool->GetSelection();
479
480 if( selection.Empty() )
481 {
482 ApiResponseStatus e;
483 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
484 e.set_error_message( "the selection is empty" );
485 return tl::unexpected( e );
486 }
487
488 commands::SavedSelectionResponse response;
489
490 SCH_SHEET_PATH selPath = frame()->GetCurrentSheet();
491
492 for( EDA_ITEM* item : selection )
493 response.add_ids()->set_value( item->m_Uuid.AsStdString() );
494
495 STRING_FORMATTER formatter;
496 SCH_IO_KICAD_SEXPR plugin;
497
498 plugin.Format( &selection, &selPath, *schematic(), &formatter, true );
499
500 std::string contents = formatter.GetString();
501 KICAD_FORMAT::Prettify( contents, KICAD_FORMAT::FORMAT_MODE::COMPACT_TEXT_PROPERTIES );
502 response.set_contents( contents );
503
504 return response;
505}
506
507
510{
511 if( aCtx.Request.type() != DocumentType::DOCTYPE_SCHEMATIC )
512 {
513 ApiResponseStatus e;
514
515 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
516 e.set_status( ApiStatusCode::AS_UNHANDLED );
517 return tl::unexpected( e );
518 }
519
520 GetOpenDocumentsResponse response;
521 common::types::DocumentSpecifier doc;
522
523 wxFileName fn( m_context->GetCurrentFileName() );
524
525 doc.set_type( DocumentType::DOCTYPE_SCHEMATIC );
526
527 if( std::optional<SCH_SHEET_PATH> path = m_context->GetCurrentSheet() )
528 PackSheetPath( *doc.mutable_sheet_path(), *path );
529
530 PackProject( *doc.mutable_project(), m_context->Prj() );
531
532 response.mutable_documents()->Add( std::move( doc ) );
533 return response;
534}
535
536
539{
540 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
541 return tl::unexpected( documentValidation.error() );
542
543 GetDocumentModifiedStateResponse response;
544
545 if( aCtx.Request.document().has_sheet_path() )
546 {
547 KIID_PATH path = UnpackSheetPath( aCtx.Request.document().sheet_path() );
548
549 std::optional<SCH_SHEET_PATH> sheetPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( path );
550
551 if( !sheetPath )
552 {
553 ApiResponseStatus e;
554 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
555 e.set_error_message( "the requested sheet path is not valid for this schematic" );
556 return tl::unexpected( e );
557 }
558
559 if( const SCH_SCREEN* screen = sheetPath->LastScreen() )
560 {
561 response.set_state( screen->IsContentModified() ? DocumentModifiedState::DMS_MODIFIED
562 : DocumentModifiedState::DMS_UNMODIFIED );
563 }
564
565 return response;
566 }
567
568 if( !schematic()->HasHierarchy() )
570
571 response.set_state( schematic()->Hierarchy().IsModified() ? DocumentModifiedState::DMS_MODIFIED
572 : DocumentModifiedState::DMS_UNMODIFIED );
573 return response;
574}
575
576
577void API_HANDLER_SCH::filterValidSchTypes( std::set<KICAD_T>& aTypeList )
578{
579 std::erase_if( aTypeList,
580 []( KICAD_T aType )
581 {
582 return !s_allowedTypes.contains( aType );
583 } );
584}
585
586
588{
589 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
590 return tl::unexpected( *busy );
591
592 if( HANDLER_RESULT<std::optional<KIID>> valid = validateItemHeaderDocument( aCtx.Request.header() );
593 !valid.has_value() )
594 {
595 return tl::unexpected( valid.error() );
596 }
597
598 std::vector<KICAD_T> requestedTypes = parseRequestedItemTypes( aCtx.Request.types() );
599
600 if( aCtx.Request.types().empty() )
601 requestedTypes.assign( s_allowedTypes.begin(), s_allowedTypes.end() );
602
603 std::set<KICAD_T> typesRequested;
604
605 for( KICAD_T type : requestedTypes )
606 typesRequested.insert( type );
607
608 filterValidSchTypes( typesRequested );
609
610 if( typesRequested.empty() )
611 {
612 ApiResponseStatus e;
613 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
614 e.set_error_message( "none of the requested types are valid for a Schematic object" );
615 return tl::unexpected( e );
616 }
617
618 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
619 std::optional<SCH_SHEET_PATH> pathFilter;
620
621 if( aCtx.Request.header().document().has_sheet_path() )
622 {
623 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
624 pathFilter = hierarchy.GetSheetPathByKIIDPath( kp );
625 }
626
627 std::map<KICAD_T, std::vector<std::pair<EDA_ITEM*, SCH_SHEET_PATH>>> itemMap;
628
629 auto processScreen =
630 [&]( const SCH_SHEET_PATH& aPath )
631 {
632 const SCH_SCREEN* aScreen = aPath.LastScreen();
633
634 for( SCH_ITEM* aItem : aScreen->Items() )
635 {
636 itemMap[ aItem->Type() ].emplace_back( aItem, aPath );
637
638 // Group members live in the screen's rtree as well as in the group
639 if( aItem->Type() == SCH_GROUP_T )
640 continue;
641
642 aItem->RunOnChildren(
643 [&]( SCH_ITEM* aChild )
644 {
645 itemMap[ aChild->Type() ].emplace_back( aChild, aPath );
646 },
648 }
649 };
650
651 if( pathFilter )
652 {
653 processScreen( *pathFilter );
654 }
655 else
656 {
657 for( const SCH_SHEET_PATH& path : hierarchy )
658 processScreen( path );
659 }
660
661 GetItemsResponse response;
662 google::protobuf::Any any;
663
664 for( KICAD_T type : typesRequested )
665 {
666 for( const auto& [item, itemPath] : itemMap[type] )
667 {
668 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), itemPath ) )
669 response.mutable_items()->Add( std::move( any ) );
670 }
671 }
672
673 response.set_status( ItemRequestStatus::IRS_OK );
674 return response;
675}
676
677
679{
680 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
681 return tl::unexpected( *busy );
682
683 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
684 {
685 ApiResponseStatus e;
686 e.set_status( ApiStatusCode::AS_UNHANDLED );
687 return tl::unexpected( e );
688 }
689
690 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
691 std::optional<SCH_SHEET_PATH> pathFilter;
692
693 if( aCtx.Request.header().document().has_sheet_path() )
694 {
695 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
696 pathFilter = hierarchy.GetSheetPathByKIIDPath( kp );
697 }
698
699 GetItemsResponse response;
700 SCH_ITEM* item = nullptr;
701 google::protobuf::Any any;
702
703 for( const types::KIID& idProto : aCtx.Request.items() )
704 {
705 KIID id( idProto.value() );
706
707 SCH_SHEET_PATH itemPath;
708
709 if( pathFilter )
710 {
711 item = pathFilter->ResolveItem( id );
712 itemPath = *pathFilter;
713 }
714 else
715 {
716 item = hierarchy.ResolveItem( id, &itemPath, true );
717 }
718
719 if( !item || !s_allowedTypes.contains( item->Type() ) )
720 continue;
721
722 if( item->Type() == SCH_SYMBOL_T )
723 {
724 kiapi::schematic::types::SchematicSymbolInstance symbol;
725
726 if( !PackSymbol( &symbol, static_cast<SCH_SYMBOL*>( item ), itemPath ) )
727 continue;
728
729 any.PackFrom( symbol );
730 }
731 else if( item->Type() == SCH_SHEET_T )
732 {
733 kiapi::schematic::types::SheetSymbol sheet;
734
735 if( !PackSheet( &sheet, static_cast<SCH_SHEET*>( item ), itemPath ) )
736 continue;
737
738 any.PackFrom( sheet );
739 }
740 else
741 {
742 item->Serialize( any );
743 }
744
745 response.mutable_items()->Add( std::move( any ) );
746 }
747
748 if( response.items().empty() )
749 {
750 ApiResponseStatus e;
751 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
752 e.set_error_message( "none of the requested IDs were found or valid" );
753 return tl::unexpected( e );
754 }
755
756 response.set_status( ItemRequestStatus::IRS_OK );
757 return response;
758}
759
760
763{
764 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetSelection" ) )
765 return tl::unexpected( *headless );
766
767 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
768 {
769 ApiResponseStatus e;
770 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
771 e.set_status( ApiStatusCode::AS_UNHANDLED );
772 return tl::unexpected( e );
773 }
774
775 std::set<KICAD_T> filter;
776
777 for( KICAD_T type : parseRequestedItemTypes( aCtx.Request.types() ) )
778 filter.insert( type );
779
780 SCH_SELECTION_TOOL* tool = m_context->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
781 SCH_SHEET_PATH path = m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() );
782
783 SelectionResponse response;
784 google::protobuf::Any any;
785
786 for( EDA_ITEM* item : tool->GetSelection() )
787 {
788 if( filter.empty() || filter.contains( item->Type() ) )
789 {
790 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), path ) )
791 response.mutable_items()->Add( std::move( any ) );
792 }
793 }
794
795 return response;
796}
797
798
801{
802 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "ClearSelection" ) )
803 return tl::unexpected( *headless );
804
805 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
806 return tl::unexpected( *busy );
807
808 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
809 {
810 ApiResponseStatus e;
811 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
812 e.set_status( ApiStatusCode::AS_UNHANDLED );
813 return tl::unexpected( e );
814 }
815
816 m_context->GetToolManager()->RunAction( ACTIONS::selectionClear );
817 frame()->Refresh();
818
819 return Empty();
820}
821
822
825{
826 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "AddToSelection" ) )
827 return tl::unexpected( *headless );
828
829 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
830 return tl::unexpected( *busy );
831
832 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
833 {
834 ApiResponseStatus e;
835 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
836 e.set_status( ApiStatusCode::AS_UNHANDLED );
837 return tl::unexpected( e );
838 }
839
840 SCH_SELECTION_TOOL* tool = m_context->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
841 SCH_SHEET_PATH current = m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() );
842
843 EDA_ITEMS toAdd;
844
845 for( const types::KIID& id : aCtx.Request.items() )
846 {
847 SCH_SHEET_PATH itemPath;
848
849 // Selection only operates on the currently-displayed sheet; off-sheet items are skipped
850 if( std::optional<SCH_ITEM*> item = getItemById( KIID( id.value() ), &itemPath );
851 item && itemPath == current )
852 {
853 toAdd.push_back( *item );
854 }
855 }
856
857 tool->AddItemsToSel( &toAdd );
858 frame()->Refresh();
859
860 SelectionResponse response;
861 google::protobuf::Any any;
862
863 for( EDA_ITEM* item : tool->GetSelection() )
864 {
865 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), current ) )
866 response.mutable_items()->Add( std::move( any ) );
867 }
868
869 return response;
870}
871
872
875{
876 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RemoveFromSelection" ) )
877 return tl::unexpected( *headless );
878
879 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
880 return tl::unexpected( *busy );
881
882 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
883 {
884 ApiResponseStatus e;
885 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
886 e.set_status( ApiStatusCode::AS_UNHANDLED );
887 return tl::unexpected( e );
888 }
889
890 SCH_SELECTION_TOOL* tool = m_context->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
891 SCH_SHEET_PATH current = m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() );
892
893 EDA_ITEMS toRemove;
894
895 for( const types::KIID& id : aCtx.Request.items() )
896 {
897 SCH_SHEET_PATH itemPath;
898
899 if( std::optional<SCH_ITEM*> item = getItemById( KIID( id.value() ), &itemPath );
900 item && itemPath == current )
901 {
902 toRemove.push_back( *item );
903 }
904 }
905
906 tool->RemoveItemsFromSel( &toRemove );
907 frame()->Refresh();
908
909 SelectionResponse response;
910 google::protobuf::Any any;
911
912 for( EDA_ITEM* item : tool->GetSelection() )
913 {
914 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), current ) )
915 response.mutable_items()->Add( std::move( any ) );
916 }
917
918 return response;
919}
920
921
923{
924 if( !aContainer )
925 {
926 ApiResponseStatus e;
927 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
928 e.set_error_message( "Tried to create an item in a null container" );
929 return tl::unexpected( e );
930 }
931
932 if( !s_allowedTypes.contains( aType ) )
933 {
934 ApiResponseStatus e;
935 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
936 e.set_error_message( fmt::format( "type {} is not supported by the schematic API handler",
937 magic_enum::enum_name( aType ) ) );
938 return tl::unexpected( e );
939 }
940
941 if( aType == SCH_PIN_T && !dynamic_cast<SCH_SYMBOL*>( aContainer ) )
942 {
943 ApiResponseStatus e;
944 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
945 e.set_error_message( fmt::format( "Tried to create a pin in {}, which is not a symbol",
946 aContainer->GetFriendlyName().ToStdString() ) );
947 return tl::unexpected( e );
948 }
949 else if( aType == SCH_SHEET_T && !dynamic_cast<SCH_SCREEN*>( aContainer ) )
950 {
951 ApiResponseStatus e;
952 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
953 e.set_error_message( fmt::format( "Tried to create a sheet symbol in {}, which is not a "
954 "schematic sheet",
955 aContainer->GetFriendlyName().ToStdString() ) );
956 return tl::unexpected( e );
957 }
958 else if( aType == SCH_SYMBOL_T && !dynamic_cast<SCH_SCREEN*>( aContainer ) )
959 {
960 ApiResponseStatus e;
961 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
962 e.set_error_message( fmt::format( "Tried to create a symbol in {}, which is not a "
963 "schematic sheet",
964 aContainer->GetFriendlyName().ToStdString() ) );
965 return tl::unexpected( e );
966 }
967
968 std::unique_ptr<EDA_ITEM> created = CreateItemForType( aType, aContainer );
969
970 if( created && !created->GetParent() )
971 created->SetParent( aContainer );
972
973 if( !created )
974 {
975 ApiResponseStatus e;
976 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
977 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
978 magic_enum::enum_name( aType ) ) );
979 return tl::unexpected( e );
980 }
981
982 return created;
983}
984
985
987 const std::string& aClientName,
988 const types::ItemHeader &aHeader,
989 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
990 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
991{
992 ApiResponseStatus e;
993
994 auto containerResult = validateItemHeaderDocument( aHeader );
995
996 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
997 {
998 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
999 e.set_status( ApiStatusCode::AS_UNHANDLED );
1000 return tl::unexpected( e );
1001 }
1002 else if( !containerResult )
1003 {
1004 e.CopyFrom( containerResult.error() );
1005 return tl::unexpected( e );
1006 }
1007
1008 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
1009 SCH_SCREEN* targetScreen = schematic()->GetCurrentScreen();
1010 SCH_SHEET_PATH targetPath = m_context->GetCurrentSheet().value_or( *hierarchy.begin() );
1011
1012 if( aHeader.document().has_sheet_path() )
1013 {
1014 KIID_PATH kp = UnpackSheetPath( aHeader.document().sheet_path() );
1015 if( std::optional<SCH_SHEET_PATH> path = hierarchy.GetSheetPathByKIIDPath( kp ) )
1016 {
1017 targetPath = *path;
1018 targetScreen = targetPath.LastScreen();
1019 }
1020 }
1021
1022 SCH_COMMIT* commit = static_cast<SCH_COMMIT*>( getCurrentCommit( aClientName ) );
1023 bool connectivityChanged = false; // an in-place symbol update invalidated the net graph
1024
1025 for( const google::protobuf::Any& anyItem : aItems )
1026 {
1027 ItemStatus status;
1028 std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
1029
1030 if( !type )
1031 {
1032 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
1033 status.set_error_message( fmt::format( "Could not decode a valid type from {}",
1034 anyItem.type_url() ) );
1035 aItemHandler( status, anyItem );
1036 continue;
1037 }
1038
1039 EDA_ITEM* container = targetScreen;
1040
1041 HANDLER_RESULT<std::unique_ptr<EDA_ITEM>> creationResult = createItemForType( *type, container );
1042
1043 if( !creationResult )
1044 {
1045 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
1046 status.set_error_message( creationResult.error().error_message() );
1047 aItemHandler( status, anyItem );
1048 continue;
1049 }
1050
1051 std::unique_ptr<EDA_ITEM> item( std::move( *creationResult ) );
1052
1053 bool unpacked = false;
1054
1055 // Retained past the unpack: the placement data they carry is applied once the item is
1056 // in the schematic.
1057 kiapi::schematic::types::SchematicSymbolInstance symbolProto;
1058 kiapi::schematic::types::SheetSymbol sheetProto;
1059
1060 if( *type == SCH_SYMBOL_T )
1061 {
1062 unpacked = anyItem.UnpackTo( &symbolProto )
1063 && UnpackSymbol( static_cast<SCH_SYMBOL*>( item.get() ), symbolProto );
1064 }
1065 else if( *type == SCH_SHEET_T )
1066 {
1067 unpacked = anyItem.UnpackTo( &sheetProto );
1068
1069 if( unpacked )
1070 {
1071 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item.get() );
1072
1073 if( tl::expected<bool, ApiResponseStatus> result = UnpackSheet( sheet, sheetProto );
1074 result.has_value() )
1075 {
1076 unpacked = *result;
1077 }
1078 else
1079 {
1080 return tl::unexpected( result.error() );
1081 }
1082 }
1083 }
1084 else if( SCH_GROUP* group = dynamic_cast<SCH_GROUP*>( item.get() ) )
1085 {
1086 unpacked = group->DeserializeGroup( anyItem, commit );
1087 }
1088 else
1089 {
1090 unpacked = item->Deserialize( anyItem );
1091 }
1092
1093 if( !unpacked )
1094 {
1095 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1096 e.set_error_message( fmt::format( "could not unpack {} from request",
1097 item->GetClass().ToStdString() ) );
1098 return tl::unexpected( e );
1099 }
1100
1101 if( std::vector<wxString> removed = item->RemoveConflictingCustomProperties(); !removed.empty() )
1102 {
1103 auto as_str =
1104 []( const wxString& aIn )
1105 {
1106 return std::string( aIn.ToUTF8() );
1107 };
1108
1109 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1110 status.set_error_message( fmt::format(
1111 "Invalid custom properties for item {}: property name(s) '{}' already in use",
1112 item->m_Uuid.AsStdString(), fmt::join( std::views::transform( removed, as_str ), ", " ) ) );
1113
1114 aItemHandler( status, anyItem );
1115 continue;
1116 }
1117
1118 SCH_ITEM* existingItem = nullptr;
1119 SCH_SHEET_PATH existingPath;
1120
1121 existingItem = targetPath.ResolveItem( item->m_Uuid );
1122
1123 if( existingItem )
1124 existingPath = targetPath;
1125
1126 if( aCreate && existingItem )
1127 {
1128 status.set_code( ItemStatusCode::ISC_EXISTING );
1129 status.set_error_message( fmt::format( "an item with UUID {} already exists",
1130 item->m_Uuid.AsStdString() ) );
1131 aItemHandler( status, anyItem );
1132 continue;
1133 }
1134 else if( !aCreate && !existingItem )
1135 {
1136 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
1137 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
1138 item->m_Uuid.AsStdString() ) );
1139 aItemHandler( status, anyItem );
1140 continue;
1141 }
1142
1143 if( !aCreate )
1144 {
1145 SCH_SCREEN* itemScreen = existingPath.LastScreen();
1146
1147 if( itemScreen != targetScreen )
1148 {
1149 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1150 status.set_error_message( fmt::format( "item {} exists on a different sheet than targeted",
1151 item->m_Uuid.AsStdString() ) );
1152 aItemHandler( status, anyItem );
1153 continue;
1154 }
1155 }
1156
1157 if( *type == SCH_SHEET_T )
1158 {
1159 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item.get() );
1160
1161 if( aCreate && !sheet->GetScreen() )
1162 sheet->SetScreen( new SCH_SCREEN( schematic() ) );
1163
1164 SCH_SHEET_PATH parentPath;
1165
1166 if( aCreate )
1167 parentPath = targetPath;
1168 else
1169 parentPath = existingPath;
1170
1171 wxString destFilePath = parentPath.LastScreen()->GetFileName();
1172
1173 if( !destFilePath.IsEmpty() )
1174 {
1175 SCH_SHEET_LIST schematicSheets = schematic()->Hierarchy();
1176 SCH_SHEET_LIST loadedSheets( sheet );
1177
1178 if( schematicSheets.TestForRecursion( loadedSheets, destFilePath ) )
1179 {
1180 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1181 status.set_error_message( "sheet update would create recursive hierarchy" );
1182 aItemHandler( status, anyItem );
1183 continue;
1184 }
1185 }
1186 }
1187
1188 status.set_code( ItemStatusCode::ISC_OK );
1189 google::protobuf::Any newItem;
1190
1191 if( aCreate )
1192 {
1193 SCH_ITEM* createdItem = static_cast<SCH_ITEM*>( item.release() );
1194 commit->Add( createdItem, targetScreen );
1195
1196 if( !createdItem )
1197 {
1198 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1199 e.set_error_message( "could not add the requested item to its parent container" );
1200 return tl::unexpected( e );
1201 }
1202
1203 if( createdItem->Type() == SCH_SYMBOL_T )
1204 {
1205 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( createdItem );
1206 kiapi::schematic::types::SchematicSymbolInstance packed;
1207
1208 ApplySymbolInstance( symbol, symbolProto, targetPath, schematic() );
1209
1210 if( PackSymbol( &packed, symbol, targetPath ) )
1211 newItem.PackFrom( packed );
1212 }
1213 else if( createdItem->Type() == SCH_SHEET_T )
1214 {
1215 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( createdItem );
1216 kiapi::schematic::types::SheetSymbol packed;
1217
1218 if( sheetProto.page_number().empty() )
1219 sheetProto.set_page_number( hierarchy.GetNextPageNumber().ToUTF8() );
1220
1221 ApplySheetInstance( sheet, sheetProto, targetPath, schematic() );
1222
1223 if( PackSheet( &packed, sheet, targetPath ) )
1224 newItem.PackFrom( packed );
1225 }
1226 else
1227 {
1228 createdItem->Serialize( newItem );
1229 }
1230 }
1231 else
1232 {
1233 // SwapItemData hands the item the temporary's (empty) instance list, so keep the
1234 // placements to restore afterwards.
1235 std::vector<SCH_SYMBOL_INSTANCE> symbolPlacements;
1236 std::vector<SCH_SHEET_INSTANCE> sheetPlacements;
1237
1238 if( existingItem->Type() == SCH_SYMBOL_T )
1239 symbolPlacements = static_cast<SCH_SYMBOL*>( existingItem )->GetInstances();
1240 else if( existingItem->Type() == SCH_SHEET_T )
1241 sheetPlacements = static_cast<SCH_SHEET*>( existingItem )->GetInstances();
1242
1243 commit->Modify( existingItem, targetScreen );
1244 existingItem->SwapItemData( static_cast<SCH_ITEM*>( item.get() ) );
1245
1246 if( existingItem->IsConnectable() )
1247 {
1248 existingItem->SetConnectivityDirty();
1249 connectivityChanged = true;
1250 }
1251
1252 if( existingItem->Type() == SCH_SYMBOL_T )
1253 {
1254 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( existingItem );
1255 kiapi::schematic::types::SchematicSymbolInstance packed;
1256
1257 for( const SCH_SYMBOL_INSTANCE& placement : symbolPlacements )
1258 symbol->AddHierarchicalReference( placement );
1259
1260 ApplySymbolInstance( symbol, symbolProto, existingPath, schematic() );
1261
1262 if( PackSymbol( &packed, symbol, existingPath ) )
1263 newItem.PackFrom( packed );
1264 }
1265 else if( existingItem->Type() == SCH_SHEET_T )
1266 {
1267 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( existingItem );
1268 kiapi::schematic::types::SheetSymbol packed;
1269
1270 for( const SCH_SHEET_INSTANCE& placement : sheetPlacements )
1271 sheet->AddInstance( placement );
1272
1273 ApplySheetInstance( sheet, sheetProto, existingPath, schematic() );
1274
1275 if( PackSheet( &packed, sheet, existingPath ) )
1276 newItem.PackFrom( packed );
1277 }
1278 else
1279 {
1280 existingItem->Serialize( newItem );
1281 }
1282 }
1283
1284 aItemHandler( status, newItem );
1285 }
1286
1287 if( !m_activeClients.contains( aClientName ) )
1288 {
1289 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
1290 : _( "Modified items via API" ) );
1291 }
1292
1293 if( m_frame && connectivityChanged )
1295
1296 return ItemRequestStatus::IRS_OK;
1297}
1298
1299
1300void API_HANDLER_SCH::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
1301 const std::string& aClientName )
1302{
1303 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
1304 COMMIT* commit = getCurrentCommit( aClientName );
1305
1306 for( auto& [id, status] : aItemsToDelete )
1307 {
1309 SCH_ITEM* item = hierarchy.ResolveItem( id, &path, true );
1310
1311 if( !item )
1312 continue;
1313
1314 if( !s_allowedTypes.contains( item->Type() ) )
1315 {
1316 status = ItemDeletionStatus::IDS_IMMUTABLE;
1317 continue;
1318 }
1319
1320 commit->Remove( item, path.LastScreen() );
1321 status = ItemDeletionStatus::IDS_OK;
1322 }
1323
1324 if( !m_activeClients.contains( aClientName ) )
1325 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
1326}
1327
1328
1329std::optional<EDA_ITEM*> API_HANDLER_SCH::getItemFromDocument( const DocumentSpecifier& aDocument, const KIID& aId )
1330{
1331 if( !validateDocument( aDocument ) )
1332 return std::nullopt;
1333
1334 SCH_ITEM* item = schematic()->Hierarchy().ResolveItem( aId, nullptr, true );
1335
1336 if( !item)
1337 return std::nullopt;
1338
1339 return item;
1340}
1341
1342
1343SCH_SCREEN* API_HANDLER_SCH::resolveScreenFromDocument( const DocumentSpecifier& aDocument ) const
1344{
1345 if( aDocument.has_sheet_path() )
1346 {
1347 KIID_PATH path = UnpackSheetPath( aDocument.sheet_path() );
1348
1349 if( std::optional<SCH_SHEET_PATH> sheetPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( path ) )
1350 return sheetPath->LastScreen();
1351
1352 return nullptr;
1353 }
1354
1355 if( std::optional<SCH_SHEET_PATH> current = m_context->GetCurrentSheet() )
1356 return current->LastScreen();
1357
1358 // Headless mode has no current sheet; the root sheet is the implicit target.
1360 path.push_back( &schematic()->Root() );
1361 return path.LastScreen();
1362}
1363
1364
1365std::optional<TITLE_BLOCK*> API_HANDLER_SCH::getTitleBlock( const DocumentSpecifier& aDocument )
1366{
1367 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1368 return &screen->GetTitleBlock();
1369
1370 return std::nullopt;
1371}
1372
1373
1374std::optional<PAGE_INFO> API_HANDLER_SCH::getPageSettings( const DocumentSpecifier& aDocument )
1375{
1376 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1377 return screen->GetPageSettings();
1378
1379 return std::nullopt;
1380}
1381
1382
1383bool API_HANDLER_SCH::setPageSettings( const DocumentSpecifier& aDocument, const PAGE_INFO& aPageInfo )
1384{
1385 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1386 {
1387 screen->SetPageSettings( aPageInfo );
1388 screen->SetContentModified();
1389 return true;
1390 }
1391
1392 return false;
1393}
1394
1395
1400
1401
1402void API_HANDLER_SCH::setDrawingSheetFileName( const wxString& aFileName )
1403{
1406
1407 if( m_frame )
1409}
1410
1411
1413{
1414 if( m_frame )
1415 {
1416 frame()->Refresh();
1417 frame()->OnModify();
1418 }
1419 else if( schematic()->GetCurrentScreen() )
1420 {
1422 }
1423}
1424
1425
1426static std::optional<ApiResponseStatus>
1427applySchematicPlotSettings( const schematic::jobs::SchematicPlotSettings& aSettings, JOB_EXPORT_SCH_PLOT& aJob )
1428{
1429 aJob.m_drawingSheet = wxString::FromUTF8( aSettings.drawing_sheet() );
1430 aJob.m_defaultFont = wxString::FromUTF8( aSettings.default_font() );
1431 aJob.m_variant = wxString::FromUTF8( aSettings.variant() );
1432 aJob.m_plotAll = aSettings.plot_all();
1433 aJob.m_plotDrawingSheet = aSettings.plot_drawing_sheet();
1434 aJob.m_show_hop_over = aSettings.show_hop_over();
1435 aJob.m_blackAndWhite = aSettings.black_and_white();
1436 aJob.m_useBackgroundColor = aSettings.use_background_color();
1437 aJob.m_minPenWidth = aSettings.min_pen_width();
1438 aJob.m_theme = wxString::FromUTF8( aSettings.theme() );
1439
1440 aJob.m_plotPages.clear();
1441
1442 for( const std::string& page : aSettings.plot_pages() )
1443 aJob.m_plotPages.push_back( wxString::FromUTF8( page ) );
1444
1445 if( aSettings.page_size() != schematic::jobs::SchematicJobPageSize::SJPS_UNKNOWN )
1446 aJob.m_pageSizeSelect = FromProtoEnum<JOB_PAGE_SIZE>( aSettings.page_size() );
1447
1448 switch( aSettings.sheet_mode() )
1449 {
1450 case schematic::jobs::SJSM_ALL_SHEETS: aJob.m_plotAll = true; break;
1451 case schematic::jobs::SJSM_SINGLE_SHEET: aJob.m_plotAll = false; break;
1452 case schematic::jobs::SJSM_UNKNOWN:
1453 default: break;
1454 }
1455
1456 return std::nullopt;
1457}
1458
1459
1462{
1463 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1464 return tl::unexpected( *busy );
1465
1466 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1467
1468 if( !documentValidation )
1469 return tl::unexpected( documentValidation.error() );
1470
1471 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_SVG>( aCtx.Request.plot_settings().sheet_mode()
1472 != schematic::jobs::SJSM_SINGLE_SHEET );
1473 plotJob->m_filename = m_context->GetCurrentFileName();
1474
1475 if( !aCtx.Request.job_settings().output_path().empty() )
1476 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1477
1478 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings( aCtx.Request.plot_settings(), *plotJob ) )
1479 return tl::unexpected( *err );
1480
1481 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1482}
1483
1484
1487{
1488 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1489 return tl::unexpected( *busy );
1490
1491 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1492
1493 if( !documentValidation )
1494 return tl::unexpected( documentValidation.error() );
1495
1496 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_DXF>( aCtx.Request.plot_settings().sheet_mode()
1497 != schematic::jobs::SJSM_SINGLE_SHEET );
1498
1499 plotJob->m_filename = m_context->GetCurrentFileName();
1500
1501 if( !aCtx.Request.job_settings().output_path().empty() )
1502 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1503
1504 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings( aCtx.Request.plot_settings(), *plotJob ) )
1505 return tl::unexpected( *err );
1506
1507 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1508}
1509
1510
1513{
1514 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1515 return tl::unexpected( *busy );
1516
1517 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1518
1519 if( !documentValidation )
1520 return tl::unexpected( documentValidation.error() );
1521
1522 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PDF>( false );
1523 plotJob->m_filename = m_context->GetCurrentFileName();
1524
1525 if( !aCtx.Request.job_settings().output_path().empty() )
1526 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1527
1528 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings( aCtx.Request.plot_settings(), *plotJob ) )
1529 return tl::unexpected( *err );
1530
1531 plotJob->m_PDFPropertyPopups = aCtx.Request.property_popups();
1532 plotJob->m_PDFHierarchicalLinks = aCtx.Request.hierarchical_links();
1533 plotJob->m_PDFMetadata = aCtx.Request.include_metadata();
1534
1535 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1536}
1537
1538
1541{
1542 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1543 return tl::unexpected( *busy );
1544
1545 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1546
1547 if( !documentValidation )
1548 return tl::unexpected( documentValidation.error() );
1549
1550 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PS>( aCtx.Request.plot_settings().sheet_mode()
1551 != schematic::jobs::SJSM_SINGLE_SHEET );
1552 plotJob->m_filename = m_context->GetCurrentFileName();
1553
1554 if( !aCtx.Request.job_settings().output_path().empty() )
1555 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1556
1557 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings( aCtx.Request.plot_settings(), *plotJob ) )
1558 return tl::unexpected( *err );
1559
1560 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1561}
1562
1563
1566{
1567 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1568 return tl::unexpected( *busy );
1569
1570 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
1571 return tl::unexpected( validation.error() );
1572
1573 const schematic::jobs::SchematicJobSheetMode sheetMode = aCtx.Request.plot_settings().sheet_mode();
1574
1575 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PNG>( sheetMode != schematic::jobs::SJSM_SINGLE_SHEET );
1576 plotJob->m_filename = m_context->GetCurrentFileName();
1577
1578 if( !aCtx.Request.job_settings().output_path().empty() )
1579 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1580
1581 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings( aCtx.Request.plot_settings(), *plotJob ) )
1582 return tl::unexpected( *err );
1583
1584 if( aCtx.Request.has_dpi() )
1585 {
1586 int dpi = aCtx.Request.dpi();
1587
1588 if( dpi < MIN_PNG_DPI || dpi > MAX_PNG_DPI )
1589 {
1590 ApiResponseStatus status;
1591 status.set_status( ApiStatusCode::AS_BAD_REQUEST );
1592 status.set_error_message( fmt::format( "dpi must be between {} and {}", MIN_PNG_DPI, MAX_PNG_DPI ) );
1593 return tl::unexpected( status );
1594 }
1595
1596 plotJob->m_dpi = dpi;
1597 }
1598
1599 // Unknown -> default AA on
1600 plotJob->m_antialias = aCtx.Request.antialiasing() != types::AntialiasingMode::AAM_NONE;
1601
1602 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1603}
1604
1605
1608{
1609 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1610 return tl::unexpected( *busy );
1611
1612 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1613
1614 if( !documentValidation )
1615 return tl::unexpected( documentValidation.error() );
1616
1617 if( aCtx.Request.format() == kiapi::schematic::jobs::SchematicNetlistFormat::SNF_UNKNOWN )
1618 {
1619 ApiResponseStatus e;
1620 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1621 e.set_error_message( "RunSchematicJobExportNetlist requires a valid format" );
1622 return tl::unexpected( e );
1623 }
1624
1625 JOB_EXPORT_SCH_NETLIST netlistJob;
1626 netlistJob.m_filename = m_context->GetCurrentFileName();
1627
1628 if( !aCtx.Request.job_settings().output_path().empty() )
1629 netlistJob.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1630
1631 netlistJob.format = FromProtoEnum<JOB_EXPORT_SCH_NETLIST::FORMAT>( aCtx.Request.format() );
1632
1633 if( !aCtx.Request.variant_name().empty() )
1634 netlistJob.m_variantNames.emplace_back( wxString::FromUTF8( aCtx.Request.variant_name() ) );
1635
1636 return ExecuteSchematicJob( m_context->GetKiway(), netlistJob );
1637}
1638
1639
1642{
1643 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1644 return tl::unexpected( *busy );
1645
1646 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1647
1648 if( !documentValidation )
1649 return tl::unexpected( documentValidation.error() );
1650
1651 JOB_EXPORT_BOM bomJob;
1652 bomJob.m_filename = m_context->GetCurrentFileName();
1653
1654 if( !aCtx.Request.job_settings().output_path().empty() )
1655 bomJob.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1656
1657 bomJob.m_bomFmtPresetName = wxString::FromUTF8( aCtx.Request.format().preset_name() );
1658 bomJob.m_fieldDelimiter = wxString::FromUTF8( aCtx.Request.format().field_delimiter() );
1659 bomJob.m_stringDelimiter = wxString::FromUTF8( aCtx.Request.format().string_delimiter() );
1660 bomJob.m_refDelimiter = wxString::FromUTF8( aCtx.Request.format().ref_delimiter() );
1661 bomJob.m_refRangeDelimiter = wxString::FromUTF8( aCtx.Request.format().ref_range_delimiter() );
1662 bomJob.m_keepTabs = aCtx.Request.format().keep_tabs();
1663 bomJob.m_keepLineBreaks = aCtx.Request.format().keep_line_breaks();
1664 bomJob.m_includeByteOrderMark = aCtx.Request.format().include_byte_order_mark();
1665
1666 bomJob.m_bomPresetName = wxString::FromUTF8( aCtx.Request.fields().preset_name() );
1667 bomJob.m_sortField = wxString::FromUTF8( aCtx.Request.fields().sort_field() );
1668 bomJob.m_filterString = wxString::FromUTF8( aCtx.Request.fields().filter() );
1669
1670 switch( aCtx.Request.fields().filter_scope() )
1671 {
1672 case kiapi::schematic::jobs::BOMFilterScope::BFS_VISIBLE:
1674 break;
1675
1676 case kiapi::schematic::jobs::BOMFilterScope::BFS_ALL:
1678 break;
1679
1680 case kiapi::schematic::jobs::BOMFilterScope::BFS_REFERENCE:
1681 default:
1683 break;
1684 }
1685
1686 if( aCtx.Request.fields().sort_direction() == kiapi::schematic::jobs::BOMSortDirection::BSD_ASCENDING )
1687 {
1688 bomJob.m_sortAsc = true;
1689 }
1690 else if( aCtx.Request.fields().sort_direction() == kiapi::schematic::jobs::BOMSortDirection::BSD_DESCENDING )
1691 {
1692 bomJob.m_sortAsc = false;
1693 }
1694
1695 for( const kiapi::schematic::jobs::BOMField& field : aCtx.Request.fields().fields() )
1696 {
1697 bomJob.m_fieldsOrdered.emplace_back( wxString::FromUTF8( field.name() ) );
1698 bomJob.m_fieldsLabels.emplace_back( wxString::FromUTF8( field.label() ) );
1699
1700 if( field.group_by() )
1701 bomJob.m_fieldsGroupBy.emplace_back( wxString::FromUTF8( field.name() ) );
1702 }
1703
1704 bomJob.m_excludeDNP = aCtx.Request.exclude_dnp();
1705 bomJob.m_groupSymbols = aCtx.Request.group_symbols();
1706
1707 if( !aCtx.Request.variant_name().empty() )
1708 bomJob.m_variantNames.emplace_back( wxString::FromUTF8( aCtx.Request.variant_name() ) );
1709
1710 return ExecuteSchematicJob( m_context->GetKiway(), bomJob );
1711}
1712
1713
1714void API_HANDLER_SCH::packSheetInstance( kiapi::schematic::types::SheetInstance* aInstance, SCH_SHEET_PATH& aPath,
1715 SCH_SHEET* aSheet )
1716{
1717 aPath.push_back( aSheet );
1718
1719 PackSheetPath( *aInstance->mutable_path(), aPath );
1720
1721 wxString sheetName = aSheet->GetShownName( INTERNAL );
1722
1723 if( sheetName.IsEmpty() && aSheet->GetScreen() )
1724 {
1725 wxFileName fn( aSheet->GetScreen()->GetFileName() );
1726 sheetName = fn.GetName();
1727 }
1728
1729 aInstance->set_name( sheetName.ToUTF8() );
1730 aInstance->set_filename( aSheet->GetFileName().ToUTF8() );
1731 aInstance->set_page_number( aPath.GetPageNumber().ToUTF8() );
1732
1733 if( aSheet->GetScreen() )
1734 {
1735 std::vector<SCH_ITEM*> childSheets;
1736 aSheet->GetScreen()->GetSheets( &childSheets );
1737
1738 std::ranges::sort( childSheets,
1739 [&]( SCH_ITEM* a, SCH_ITEM* b )
1740 {
1741 SCH_SHEET_PATH pathA = aPath;
1742 pathA.push_back( static_cast<SCH_SHEET*>( a ) );
1743
1744 SCH_SHEET_PATH pathB = aPath;
1745 pathB.push_back( static_cast<SCH_SHEET*>( b ) );
1746
1747 return pathA.ComparePageNum( pathB ) < 0;
1748 } );
1749
1750 for( SCH_ITEM* childItem : childSheets )
1751 {
1752 SCH_SHEET* childSheet = static_cast<SCH_SHEET*>( childItem );
1753 kiapi::schematic::types::SheetInstance* childInstance = aInstance->add_children();
1754 packSheetInstance( childInstance, aPath, childSheet );
1755 }
1756 }
1757
1758 aPath.pop_back();
1759}
1760
1761
1764{
1765 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1766
1767 if( !documentValidation )
1768 return tl::unexpected( documentValidation.error() );
1769
1770 kiapi::schematic::commands::SchematicHierarchyResponse response;
1771 response.mutable_document()->CopyFrom( aCtx.Request.document() );
1772
1773 if( !schematic()->HasHierarchy() )
1775
1777 std::vector<SCH_SHEET*> topLevelSheets = schematic()->GetTopLevelSheets();
1778
1779 std::ranges::sort( topLevelSheets,
1780 [&]( SCH_SHEET* a, SCH_SHEET* b )
1781 {
1782 SCH_SHEET_PATH pathA;
1783 pathA.push_back( a );
1784
1785 SCH_SHEET_PATH pathB;
1786 pathB.push_back( b );
1787
1788 return pathA.ComparePageNum( pathB ) < 0;
1789 } );
1790
1791 for( SCH_SHEET* topSheet : topLevelSheets )
1792 {
1793 kiapi::schematic::types::SheetInstance* instance = response.add_top_level_sheets();
1794 packSheetInstance( instance, path, topSheet );
1795 }
1796
1797 return response;
1798}
1799
1800
1803{
1804 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1805 return tl::unexpected( *busy );
1806
1807 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
1808
1809 if( !documentValidation )
1810 return tl::unexpected( documentValidation.error() );
1811
1812 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1813 const bool filterByType = aCtx.Request.types_size() > 0;
1814
1815 if( filterByType && types.empty() )
1816 {
1817 ApiResponseStatus e;
1818 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1819 e.set_error_message( "none of the requested types are valid for a Schematic object" );
1820 return tl::unexpected( e );
1821 }
1822
1823 std::set<KICAD_T> typeFilter( types.begin(), types.end() );
1824
1825 CONNECTION_GRAPH* connectionGraph = schematic()->ConnectionGraph();
1826
1827 if( !connectionGraph )
1828 {
1829 ApiResponseStatus e;
1830 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1831 e.set_error_message( "schematic has no connection graph" );
1832 return tl::unexpected( e );
1833 }
1834
1835 kiapi::schematic::commands::SchematicNetlistResponse response;
1836 response.mutable_document()->CopyFrom( aCtx.Request.document() );
1837
1838 for( const auto& [key, subgraphList] : connectionGraph->GetNetMap() )
1839 {
1840 if( subgraphList.empty() )
1841 continue;
1842
1843 CONNECTION_SUBGRAPH* firstSubgraph = subgraphList[0];
1844
1845 if( firstSubgraph->GetDriverConnection() && firstSubgraph->GetDriverConnection()->IsBus() )
1846 continue;
1847
1849 continue;
1850
1851 kiapi::schematic::types::SchematicNet* net = response.add_nets();
1852 net->set_name( key.Name.ToUTF8() );
1853
1854 for( CONNECTION_SUBGRAPH* subGraph : subgraphList )
1855 {
1856 kiapi::schematic::types::SchematicNetSheetContents* sheetContents = net->add_sheets();
1857 PackSheetPath( *sheetContents->mutable_path(), subGraph->GetSheet() );
1858
1859 for( SCH_ITEM* item : subGraph->GetItems() )
1860 {
1861 if( filterByType && !typeFilter.contains( item->Type() ) )
1862 continue;
1863
1864 sheetContents->add_items()->set_value( item->m_Uuid.AsStdString() );
1865 }
1866 }
1867 }
1868
1869 return response;
1870}
1871
1872
1873// TODO(JE) factor out
1876{
1877 wxLogTrace( traceApi, "Received announce from frame %d at %s",
1878 aCtx.Request.frame_type(), aCtx.Request.socket_path() );
1879
1880 CROSS_PROBE_CLIENT::RegisterPeer( static_cast<FRAME_T>( aCtx.Request.frame_type() ),
1881 aCtx.Request.socket_path() );
1882
1883 CrossProbeAnnounceResponse response;
1884 response.set_status( CPS_OK );
1885 return response;
1886}
1887
1888
1889bool findSymbolsAndPins( const SCH_SHEET_LIST& aSchematicSheetList, const SCH_SHEET_PATH& aSheetPath,
1890 std::unordered_map<wxString, std::vector<SCH_REFERENCE>>& aSyncSymMap,
1891 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>>& aSyncPinMap,
1892 const wxString& aVariantName = wxEmptyString, bool aRecursive = false )
1893{
1894 if( aRecursive )
1895 {
1896 // Iterate over children
1897 for( const SCH_SHEET_PATH& candidate : aSchematicSheetList )
1898 {
1899 if( candidate == aSheetPath || !candidate.IsContainedWithin( aSheetPath ) )
1900 continue;
1901
1902 findSymbolsAndPins( aSchematicSheetList, candidate, aSyncSymMap, aSyncPinMap, aVariantName, aRecursive );
1903 }
1904 }
1905
1906 SCH_REFERENCE_LIST references;
1907
1908 aSheetPath.GetSymbols( references, SYMBOL_FILTER_NON_POWER, true );
1909
1910 for( unsigned ii = 0; ii < references.GetCount(); ii++ )
1911 {
1912 SCH_REFERENCE& schRef = references[ii];
1913
1914 if( schRef.IsSplitNeeded() )
1915 schRef.Split();
1916
1917 SCH_SYMBOL* symbol = schRef.GetSymbol();
1918 wxString refNum = schRef.GetRefNumber();
1919 wxString fullRef = schRef.GetRef() + refNum;
1920
1921 // Skip power symbols
1922 if( fullRef.StartsWith( wxS( "#" ) ) )
1923 continue;
1924
1925 // Unannotated symbols are not supported
1926 if( refNum.compare( wxS( "?" ) ) == 0 )
1927 continue;
1928
1929 // Look for whole footprint
1930 auto symMatchIt = aSyncSymMap.find( fullRef );
1931
1932 if( symMatchIt != aSyncSymMap.end() )
1933 {
1934 symMatchIt->second.emplace_back( schRef );
1935
1936 // Whole footprint was selected, no need to select pins
1937 continue;
1938 }
1939
1940 // Look for pins
1941 auto symPinMatchIt = aSyncPinMap.find( fullRef );
1942
1943 if( symPinMatchIt != aSyncPinMap.end() )
1944 {
1945 std::unordered_map<wxString, SCH_PIN*>& pinMap = symPinMatchIt->second;
1946 std::vector<SCH_PIN*> pinsOnSheet = symbol->GetPins( &aSheetPath );
1947
1948 for( SCH_PIN* pin : pinsOnSheet )
1949 {
1950 int pinUnit = pin->GetLibPin()->GetUnit();
1951
1952 if( pinUnit > 0 && pinUnit != schRef.GetUnit() )
1953 continue;
1954
1955 // Reverse-map the requested pad back to the owning pin (issue #2282). A pin may
1956 // resolve to several pads via the map; match the first that pcbnew asked for.
1957 for( const wxString& pad :
1958 ExpandStackedPinNotation( pin->GetEffectivePadNumber( aSheetPath, aVariantName ) ) )
1959 {
1960 auto pinIt = pinMap.find( pad );
1961
1962 if( pinIt != pinMap.end() )
1963 {
1964 pinIt->second = pin;
1965 break;
1966 }
1967 }
1968 }
1969 }
1970 }
1971
1972 return false;
1973}
1974
1975
1977 const SCH_SHEET_LIST& aSchematicSheetList, const SCH_SHEET_PATH& aSheetPath,
1978 std::unordered_map<wxString, std::vector<SCH_REFERENCE>>& aSyncSymMap,
1979 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>>& aSyncPinMap,
1980 std::unordered_map<SCH_SHEET_PATH, bool>& aCache )
1981{
1982 auto cacheIt = aCache.find( aSheetPath );
1983
1984 if( cacheIt != aCache.end() )
1985 return cacheIt->second;
1986
1987 // Iterate over children
1988 for( const SCH_SHEET_PATH& candidate : aSchematicSheetList )
1989 {
1990 if( candidate == aSheetPath || !candidate.IsContainedWithin( aSheetPath ) )
1991 continue;
1992
1993 bool childRet = sheetContainsOnlyWantedItems( aSchematicSheetList, candidate, aSyncSymMap,
1994 aSyncPinMap, aCache );
1995
1996 if( !childRet )
1997 {
1998 aCache.emplace( aSheetPath, false );
1999 return false;
2000 }
2001 }
2002
2003 SCH_REFERENCE_LIST references;
2004 aSheetPath.GetSymbols( references, SYMBOL_FILTER_NON_POWER, true );
2005
2006 if( references.GetCount() == 0 ) // Empty sheet, obviously do not contain wanted items
2007 {
2008 aCache.emplace( aSheetPath, false );
2009 return false;
2010 }
2011
2012 for( unsigned ii = 0; ii < references.GetCount(); ii++ )
2013 {
2014 SCH_REFERENCE& schRef = references[ii];
2015
2016 if( schRef.IsSplitNeeded() )
2017 schRef.Split();
2018
2019 wxString refNum = schRef.GetRefNumber();
2020 wxString fullRef = schRef.GetRef() + refNum;
2021
2022 // Skip power symbols
2023 if( fullRef.StartsWith( wxS( "#" ) ) )
2024 continue;
2025
2026 // Unannotated symbols are not supported
2027 if( refNum.compare( wxS( "?" ) ) == 0 )
2028 continue;
2029
2030 if( aSyncSymMap.find( fullRef ) == aSyncSymMap.end() )
2031 {
2032 aCache.emplace( aSheetPath, false );
2033 return false; // Some symbol is not wanted.
2034 }
2035
2036 if( aSyncPinMap.find( fullRef ) != aSyncPinMap.end() )
2037 {
2038 aCache.emplace( aSheetPath, false );
2039 return false; // Looking for specific pins, so can't be mapped
2040 }
2041 }
2042
2043 aCache.emplace( aSheetPath, true );
2044 return true;
2045}
2046
2047
2048std::optional<std::tuple<SCH_SHEET_PATH, SCH_ITEM*, std::vector<SCH_ITEM*>>>
2050 const kiapi::common::commands::SyncSelection& aSync )
2051{
2052 std::unordered_map<wxString, std::vector<SCH_REFERENCE>> syncSymMap;
2053 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>> syncPinMap;
2054 std::unordered_map<SCH_SHEET_PATH, bool> fullyWantedCache;
2055
2056 std::optional<wxString> focusSymbol;
2057 std::optional<std::pair<wxString, wxString>> focusPin;
2058 std::unordered_map<SCH_SHEET_PATH, std::vector<SCH_ITEM*>> focusItemResults;
2059
2060 const SCH_SHEET_LIST allSheetsList = aSchematic.Hierarchy();
2061
2062 // In orderedSheets, the current sheet comes first.
2063 std::vector<SCH_SHEET_PATH> orderedSheets;
2064 orderedSheets.reserve( allSheetsList.size() );
2065 orderedSheets.push_back( aSchematic.CurrentSheet() );
2066
2067 for( const SCH_SHEET_PATH& sheetPath : allSheetsList )
2068 {
2069 if( sheetPath != aSchematic.CurrentSheet() )
2070 orderedSheets.push_back( sheetPath );
2071 }
2072
2073 const bool focusOnFirst = ( aSync.mode() == kiapi::common::commands::SSM_ITEMS_AND_NETS ) && aSync.has_focus_item();
2074
2075 for( const kiapi::common::commands::SelectionSpec& spec : aSync.items() )
2076 {
2077 switch( spec.spec_case() )
2078 {
2079 case kiapi::common::commands::SelectionSpec::kFootprint:
2080 {
2081 wxString symRef = wxString::FromUTF8( spec.footprint().reference() );
2082 syncSymMap[symRef] = std::vector<SCH_REFERENCE>();
2083 break;
2084 }
2085
2086 case kiapi::common::commands::SelectionSpec::kPad:
2087 {
2088 wxString symRef = wxString::FromUTF8( spec.pad().reference() );
2089 wxString padNum = wxString::FromUTF8( spec.pad().number() );
2090 syncPinMap[symRef][padNum] = nullptr;
2091 break;
2092 }
2093
2094 default:
2095 break;
2096 }
2097 }
2098
2099 if( focusOnFirst )
2100 {
2101 const kiapi::common::commands::SelectionSpec& focusSpec = aSync.focus_item();
2102
2103 if( focusSpec.has_footprint() )
2104 focusSymbol = wxString::FromUTF8( focusSpec.footprint().reference() );
2105 else if( focusSpec.has_pad() )
2106 focusPin = std::make_pair( wxString::FromUTF8( focusSpec.pad().reference() ),
2107 wxString::FromUTF8( focusSpec.pad().number() ) );
2108 }
2109
2110 // Lambda definitions
2111 auto flattenSyncMaps =
2112 [&syncSymMap, &syncPinMap]() -> std::vector<SCH_ITEM*>
2113 {
2114 std::vector<SCH_ITEM*> allVec;
2115
2116 for( const auto& [symRef, symbols] : syncSymMap )
2117 {
2118 for( const SCH_REFERENCE& ref : symbols )
2119 allVec.push_back( ref.GetSymbol() );
2120 }
2121
2122 for( const auto& [symRef, pinMap] : syncPinMap )
2123 {
2124 for( const auto& [padNum, pin] : pinMap )
2125 {
2126 if( pin )
2127 allVec.push_back( pin );
2128 }
2129 }
2130
2131 return allVec;
2132 };
2133
2134 auto clearSyncMaps =
2135 [&syncSymMap, &syncPinMap]()
2136 {
2137 for( auto& [symRef, symbols] : syncSymMap )
2138 symbols.clear();
2139
2140 for( auto& [reference, pins] : syncPinMap )
2141 {
2142 for( auto& [number, pin] : pins )
2143 pin = nullptr;
2144 }
2145 };
2146
2147 auto syncMapsValuesEmpty =
2148 [&syncSymMap, &syncPinMap]() -> bool
2149 {
2150 for( const auto& [symRef, symbols] : syncSymMap )
2151 {
2152 if( symbols.size() > 0 )
2153 return false;
2154 }
2155
2156 for( const auto& [symRef, pins] : syncPinMap )
2157 {
2158 for( const auto& [padNum, pin] : pins )
2159 {
2160 if( pin )
2161 return false;
2162 }
2163 }
2164
2165 return true;
2166 };
2167
2168 auto checkFocusItems =
2169 [&]( const SCH_SHEET_PATH& aSheet )
2170 {
2171 if( focusSymbol )
2172 {
2173 auto findIt = syncSymMap.find( *focusSymbol );
2174
2175 if( findIt != syncSymMap.end() )
2176 {
2177 if( findIt->second.size() > 0 )
2178 focusItemResults[aSheet].push_back( findIt->second.front().GetSymbol() );
2179 }
2180 }
2181 else if( focusPin )
2182 {
2183 auto findIt = syncPinMap.find( focusPin->first );
2184
2185 if( findIt != syncPinMap.end() )
2186 {
2187 if( findIt->second[focusPin->second] )
2188 focusItemResults[aSheet].push_back( findIt->second[focusPin->second] );
2189 }
2190 }
2191 };
2192
2193 auto makeRetForSheet =
2194 [&]( const SCH_SHEET_PATH& aSheet, SCH_ITEM* aFocusItem )
2195 {
2196 clearSyncMaps();
2197
2198 // Fill sync maps
2199 findSymbolsAndPins( allSheetsList, aSheet, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2200 std::vector<SCH_ITEM*> itemsVector = flattenSyncMaps();
2201
2202 // Add fully wanted sheets to vector
2203 for( SCH_ITEM* item : aSheet.LastScreen()->Items().OfType( SCH_SHEET_T ) )
2204 {
2205 KIID_PATH kiidPath = aSheet.Path();
2206 kiidPath.push_back( item->m_Uuid );
2207
2208 std::optional<SCH_SHEET_PATH> subsheetPath =
2209 allSheetsList.GetSheetPathByKIIDPath( kiidPath );
2210
2211 if( !subsheetPath )
2212 continue;
2213
2214 if( sheetContainsOnlyWantedItems( allSheetsList, *subsheetPath, syncSymMap,
2215 syncPinMap, fullyWantedCache ) )
2216 {
2217 itemsVector.push_back( item );
2218 }
2219 }
2220
2221 return std::make_tuple( aSheet, aFocusItem, itemsVector );
2222 };
2223
2224 if( focusOnFirst )
2225 {
2226 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2227 {
2228 clearSyncMaps();
2229
2230 findSymbolsAndPins( allSheetsList, sheetPath, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2231
2232 checkFocusItems( sheetPath );
2233 }
2234
2235 if( focusItemResults.size() > 0 )
2236 {
2237 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2238 {
2239 const std::vector<SCH_ITEM*>& items = focusItemResults[sheetPath];
2240
2241 if( !items.empty() )
2242 return makeRetForSheet( sheetPath, items.front() );
2243 }
2244 }
2245 }
2246 else
2247 {
2248 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2249 {
2250 clearSyncMaps();
2251
2252 findSymbolsAndPins( allSheetsList, sheetPath, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2253
2254 if( !syncMapsValuesEmpty() )
2255 {
2256 // Something found on sheet
2257 return makeRetForSheet( sheetPath, nullptr );
2258 }
2259 }
2260 }
2261
2262 return std::nullopt;
2263}
2264
2265
2267 const HANDLER_CONTEXT<SyncSelection>& aCtx )
2268{
2269 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SyncSelection" ) )
2270 return tl::unexpected( *headless );
2271
2272 SyncSelectionResponse response;
2273
2274 const CROSS_PROBING_SETTINGS& settings = frame()->eeconfig()->m_CrossProbing;
2275
2276 if( !settings.on_selection && aCtx.Request.context() != SyncSelectionContext::SSC_EXPLICIT )
2277 {
2278 response.set_status( CPS_DISABLED );
2279 response.set_message( "implicit selection sync disabled by user" );
2280 return response;
2281 }
2282
2283 // A request carrying no items asks for nothing to be selected, so there is nothing to find.
2284 if( aCtx.Request.items_size() == 0 )
2285 {
2286 frame()->SetSyncingSelection( true ); // recursion guard
2287
2288 frame()->GetToolManager()->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( std::nullopt, nullptr, {} );
2289
2290 frame()->SetSyncingSelection( false );
2291
2292 response.set_status( CPS_OK );
2293 return response;
2294 }
2295
2296 std::optional<std::tuple<SCH_SHEET_PATH, SCH_ITEM*, std::vector<SCH_ITEM*>>> findRet =
2298
2299 if( findRet )
2300 {
2301 auto& [sheetPath, focusItem, items] = *findRet;
2302
2303 frame()->SetSyncingSelection( true ); // recursion guard
2304
2305 frame()->GetToolManager()->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( sheetPath, focusItem, items );
2306
2307 frame()->SetSyncingSelection( false );
2308
2309 if( frame()->eeconfig()->m_CrossProbing.flash_selection )
2310 {
2311 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): flash enabled, items=%zu",
2312 items.size() );
2313
2314 if( items.empty() )
2315 {
2316 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): nothing to flash" );
2317 }
2318 else
2319 {
2320 std::vector<SCH_ITEM*> itemPtrs;
2321 std::copy( items.begin(), items.end(), std::back_inserter( itemPtrs ) );
2322
2323 frame()->StartCrossProbeFlash( itemPtrs );
2324 }
2325 }
2326 else
2327 {
2328 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): flash disabled" );
2329 }
2330 }
2331
2332 response.set_status( CPS_OK );
2333 return response;
2334}
2335
2336
2338 const HANDLER_CONTEXT<HighlightNets>& aCtx )
2339{
2340 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "HighlightNets" ) )
2341 return tl::unexpected( *headless );
2342
2343 HighlightNetsResponse response;
2344 CROSS_PROBING_SETTINGS& crossProbingSettings = frame()->eeconfig()->m_CrossProbing;
2345
2347 || aCtx.ClientName == KiwayClientName )
2348 {
2349 if( !crossProbingSettings.auto_highlight )
2350 {
2351 response.set_status( CPS_DISABLED );
2352 return response;
2353 }
2354 }
2355
2356 wxString net;
2357
2358 if( aCtx.Request.net_name_size() > 0 )
2359 net = wxString::FromUTF8( aCtx.Request.net_name( 0 ) );
2360
2362
2363 response.set_status( CPS_OK );
2364 return response;
2365}
2366
2367
2368
2371{
2372 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2373
2374 if( !documentValidation )
2375 return tl::unexpected( documentValidation.error() );
2376
2377 SCH_SHEET_PATH path = m_context->GetCurrentSheet().value_or( *schematic()->Hierarchy().begin() );
2378
2379 if( aCtx.Request.document().has_sheet_path() )
2380 {
2381 KIID_PATH kiidPath = UnpackSheetPath( aCtx.Request.document().sheet_path() );
2382
2383 if( std::optional<SCH_SHEET_PATH> resolvedPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( kiidPath ) )
2384 {
2385 path = *resolvedPath;
2386 }
2387 }
2388
2389 ExpandTextVariablesResponse reply;
2390
2391 std::function<bool( wxString* )> textResolver =
2392 [&]( wxString* token ) -> bool
2393 {
2394 return schematic()->ResolveTextVar( &path, token, 0 );
2395 };
2396
2397 PROJECT& project = m_context->Prj();
2398
2399 for( const std::string& textMsg : aCtx.Request.text() )
2400 {
2401 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver, INTERNAL );
2402
2403 if( aCtx.Request.expand_env_vars() )
2405
2406 reply.add_text( text.ToUTF8() );
2407 }
2408
2409 return reply;
2410}
2411
2412
2414{
2415 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2416 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2417
2418 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2419 return tl::unexpected( *busy );
2420
2421 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2422 return tl::unexpected( documentValidation.error() );
2423
2424 VariantsResponse response;
2425
2426 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2427
2428 for( const wxString& name : schematic()->GetVariantNames() )
2429 {
2430 types::DesignVariant* var = response.add_variants();
2431 var->set_name( name.ToUTF8() );
2432 var->set_description( schematic()->GetVariantDescription( name ).ToUTF8() );
2433 }
2434
2435 return response;
2436}
2437
2438
2440{
2441 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2442 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2443
2444 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2445 return tl::unexpected( *busy );
2446
2447 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2448 return tl::unexpected( documentValidation.error() );
2449
2450 SCHEMATIC* schematic = this->schematic();
2451 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2452
2453 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || schematic->HasVariant( name ) )
2454 {
2455 ApiResponseStatus e;
2456 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2457 e.set_error_message( fmt::format( "'{}' is not a usable new variant name", aCtx.Request.name() ) );
2458 return tl::unexpected( e );
2459 }
2460
2461 schematic->AddVariant( name );
2462
2463 if( aCtx.Request.has_description() )
2464 schematic->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
2465
2466 if( m_frame )
2467 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2468
2469 return Empty();
2470}
2471
2472
2474{
2475 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2476 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2477
2478 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2479 return tl::unexpected( *busy );
2480
2481 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2482 return tl::unexpected( documentValidation.error() );
2483
2484 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2485
2486 SCHEMATIC* schematic = this->schematic();
2487 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2488
2489 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
2490 {
2491 ApiResponseStatus e;
2492 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2493 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
2494 return tl::unexpected( e );
2495 }
2496
2497 if( !schematic->HasVariant( name ) )
2498 {
2499 ApiResponseStatus e;
2500 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2501 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
2502 return tl::unexpected( e );
2503 }
2504
2505 schematic->DeleteVariant( name, &commit );
2506
2507 if( m_frame )
2508 {
2509 if( frame()->Schematic().GetCurrentVariant().CmpNoCase( name ) == 0 )
2510 frame()->SetCurrentVariant( wxEmptyString );
2511
2512 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2513 frame()->GetCanvas()->Refresh();
2514 }
2515
2516 return Empty();
2517}
2518
2519
2521{
2522 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2523 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2524
2525 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2526 return tl::unexpected( *busy );
2527
2528 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2529 return tl::unexpected( documentValidation.error() );
2530
2531 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2532
2533 SCHEMATIC* schematic = this->schematic();
2534 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
2535 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
2536
2537 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2538 {
2539 ApiResponseStatus e;
2540 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2541 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
2542 return tl::unexpected( e );
2543 }
2544
2545 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2546 {
2547 ApiResponseStatus e;
2548 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2549 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
2550 return tl::unexpected( e );
2551 }
2552
2553 if( !schematic->HasVariant( oldName ) )
2554 {
2555 ApiResponseStatus e;
2556 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2557 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
2558 return tl::unexpected( e );
2559 }
2560
2561 if( schematic->HasVariant( newName ) )
2562 {
2563 ApiResponseStatus e;
2564 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2565 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
2566 return tl::unexpected( e );
2567 }
2568
2569 schematic->RenameVariant( oldName, newName, &commit );
2570
2571 if( m_frame )
2572 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2573
2574 return Empty();
2575}
2576
2577
2579{
2580 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2581 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2582
2583 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2584 return tl::unexpected( *busy );
2585
2586 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2587 return tl::unexpected( documentValidation.error() );
2588
2589 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2590
2591 SCHEMATIC* schematic = this->schematic();
2592 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
2593 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
2594
2595 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2596 {
2597 ApiResponseStatus e;
2598 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2599 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
2600 return tl::unexpected( e );
2601 }
2602
2603 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2604 {
2605 ApiResponseStatus e;
2606 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2607 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
2608 return tl::unexpected( e );
2609 }
2610
2611 if( !schematic->HasVariant( oldName ) )
2612 {
2613 ApiResponseStatus e;
2614 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2615 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
2616 return tl::unexpected( e );
2617 }
2618
2619 if( schematic->HasVariant( newName ) )
2620 {
2621 ApiResponseStatus e;
2622 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2623 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
2624 return tl::unexpected( e );
2625 }
2626
2627 schematic->CopyVariant( oldName, newName, &commit );
2628
2629 if( m_frame )
2630 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2631
2632 return Empty();
2633}
2634
2635
2637{
2638 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2639 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2640
2641 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2642 return tl::unexpected( *busy );
2643
2644 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2645 return tl::unexpected( documentValidation.error() );
2646
2647 SCHEMATIC* schematic = this->schematic();
2648 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2649
2650 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || !schematic->HasVariant( name ) )
2651 {
2652 ApiResponseStatus e;
2653 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2654 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
2655 return tl::unexpected( e );
2656 }
2657
2658 schematic->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
2659
2660 return Empty();
2661}
2662
2663
2665{
2666 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2667 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2668
2669 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2670 return tl::unexpected( *busy );
2671
2672 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2673 return tl::unexpected( documentValidation.error() );
2674
2675 SCHEMATIC* schematic = this->schematic();
2676
2677 if( aCtx.Request.has_name() && !aCtx.Request.name().empty() )
2678 {
2679 if( wxString name = wxString::FromUTF8( aCtx.Request.name() ); !schematic->HasVariant( name ) )
2680 {
2681 ApiResponseStatus e;
2682 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2683 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
2684 return tl::unexpected( e );
2685 }
2686 }
2687
2688 wxString name = aCtx.Request.has_name() ? wxString::FromUTF8( aCtx.Request.name() ) : wxString();
2689
2690 if( m_frame )
2692 else
2693 schematic->SetCurrentVariant( name );
2694
2695 return Empty();
2696}
2697
2698
2701{
2702 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
2703 return tl::unexpected( MakeResponseStatus( AS_UNHANDLED ) );
2704
2705 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2706 return tl::unexpected( documentValidation.error() );
2707
2708 CurrentVariantResponse response;
2709
2710 if( wxString current = schematic()->GetCurrentVariant(); !current.IsEmpty() )
2711 response.set_name( current.ToUTF8() );
2712
2713 return response;
2714}
2715
2716
2719{
2720 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2721 return tl::unexpected( *busy );
2722
2723 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
2724 {
2725 ApiResponseStatus e;
2726 e.set_status( ApiStatusCode::AS_UNHANDLED );
2727 return tl::unexpected( e );
2728 }
2729
2730 LIB_ID libId = UnpackLibId( aCtx.Request.lib_id() );
2731
2732 if( !libId.IsValid() )
2733 {
2734 ApiResponseStatus e;
2735 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2736 e.set_error_message( "lib_id must specify both a library nickname and an entry name" );
2737 return tl::unexpected( e );
2738 }
2739
2741 LIB_SYMBOL* libSymbol = adapter->LoadSymbol( libId );
2742
2743 if( !libSymbol )
2744 {
2745 ApiResponseStatus e;
2746 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2747 e.set_error_message( fmt::format( "symbol '{}' not found", libId.Format().wx_str() ) );
2748 return tl::unexpected( e );
2749 }
2750
2751 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
2752 SCH_SHEET_PATH targetPath = m_context->GetCurrentSheet().value_or( *hierarchy.begin() );
2753
2754 if( aCtx.Request.header().document().has_sheet_path() )
2755 {
2756 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
2757
2758 if( std::optional<SCH_SHEET_PATH> path = hierarchy.GetSheetPathByKIIDPath( kp ) )
2759 {
2760 targetPath = *path;
2761 }
2762 else
2763 {
2764 ApiResponseStatus e;
2765 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2766 e.set_error_message( fmt::format( "the requested sheet path {} is not valid for this schematic",
2767 kp.AsString().ToStdString() ) );
2768 return tl::unexpected( e );
2769 }
2770 }
2771
2772 SCH_SCREEN* targetScreen = targetPath.LastScreen();
2773
2774 int unit = aCtx.Request.has_unit() ? aCtx.Request.unit().unit() : 1;
2775
2776 if( unit < 1 || ( libSymbol->GetUnitCount() > 0 && unit > libSymbol->GetUnitCount() ) )
2777 {
2778 ApiResponseStatus e;
2779 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2780 e.set_error_message( fmt::format( "unit {} is out of range for symbol '{}' ({} units)", unit,
2781 libId.Format().wx_str(), libSymbol->GetUnitCount() ) );
2782 return tl::unexpected( e );
2783 }
2784
2785 VECTOR2I position = UnpackVector2( aCtx.Request.position(), schIUScale );
2786
2787 std::unique_ptr<SCH_SYMBOL> symbol(
2788 std::make_unique<SCH_SYMBOL>( *libSymbol, libId, &targetPath, unit, 0, position ) );
2789
2790 if( aCtx.Request.has_orientation() )
2791 symbol->SetOrientationProp( FromProtoEnum<SYMBOL_ORIENTATION_PROP>( aCtx.Request.orientation() ) );
2792
2793 if( !aCtx.Request.reference().empty() )
2794 {
2795 symbol->SetRef( &targetPath, wxString::FromUTF8( aCtx.Request.reference() ) );
2796 }
2797 else
2798 {
2799 SCH_REFERENCE newReference( symbol.get(), targetPath );
2800 SCH_REFERENCE_LIST existingRefs;
2801 hierarchy.GetSymbols( existingRefs, SYMBOL_FILTER_ALL );
2802
2803 bool annotate = newReference.AlwaysAnnotate();
2804
2805 if( SCH_EDIT_FRAME* frame = this->frame() )
2806 annotate |= frame->eeconfig()->m_AnnotatePanel.automatic;
2807
2808 if( annotate )
2809 {
2810 existingRefs.SortByReferenceOnly();
2811
2812 SCH_REFERENCE_LIST refs;
2813 refs.AddItem( newReference );
2814 refs.SetRefDesTracker( schematic()->Settings().m_refDesTracker );
2815 refs.ReannotateByOptions( static_cast<ANNOTATE_ORDER_T>( schematic()->Settings().m_AnnotateSortOrder ),
2816 static_cast<ANNOTATE_ALGO_T>( schematic()->Settings().m_AnnotateMethod ),
2817 schematic()->Settings().m_AnnotateStartNum, existingRefs, false, &hierarchy );
2818 refs.UpdateAnnotation();
2819 }
2820 }
2821
2822 if( SCH_EDIT_FRAME* frame = this->frame() )
2823 {
2824 if( frame->eeconfig()->m_AutoplaceFields.enable )
2825 symbol->AutoplaceFields( nullptr, AUTOPLACE_AUTO );
2826 }
2827
2828 SCH_COMMIT* commit = static_cast<SCH_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
2829 SCH_SYMBOL* placed = symbol.release();
2830 commit->Add( placed, targetScreen );
2831
2832 if( !m_activeClients.contains( aCtx.ClientName ) )
2833 pushCurrentCommit( aCtx.ClientName, _( "Placed symbol via API" ) );
2834
2835 PlaceFromLibraryResponse response;
2836 response.mutable_header()->CopyFrom( aCtx.Request.header() );
2837
2838 kiapi::schematic::types::SchematicSymbolInstance packed;
2839
2840 if( PackSymbol( &packed, placed, targetPath ) )
2841 response.mutable_item()->PackFrom( packed );
2842
2843 return response;
2844}
const char * name
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:45
static std::optional< ApiResponseStatus > applySchematicPlotSettings(const schematic::jobs::SchematicPlotSettings &aSettings, JOB_EXPORT_SCH_PLOT &aJob)
HANDLER_RESULT< types::RunJobResponse > ExecuteSchematicJob(KIWAY *aKiway, JOB &aJob)
bool findSymbolsAndPins(const SCH_SHEET_LIST &aSchematicSheetList, const SCH_SHEET_PATH &aSheetPath, std::unordered_map< wxString, std::vector< SCH_REFERENCE > > &aSyncSymMap, std::unordered_map< wxString, std::unordered_map< wxString, SCH_PIN * > > &aSyncPinMap, const wxString &aVariantName=wxEmptyString, bool aRecursive=false)
std::optional< std::tuple< SCH_SHEET_PATH, SCH_ITEM *, std::vector< SCH_ITEM * > > > findItemsFromSyncSelection(const SCHEMATIC &aSchematic, const kiapi::common::commands::SyncSelection &aSync)
bool sheetContainsOnlyWantedItems(const SCH_SHEET_LIST &aSchematicSheetList, const SCH_SHEET_PATH &aSheetPath, std::unordered_map< wxString, std::vector< SCH_REFERENCE > > &aSyncSymMap, std::unordered_map< wxString, std::unordered_map< wxString, SCH_PIN * > > &aSyncPinMap, std::unordered_map< SCH_SHEET_PATH, bool > &aCache)
std::unique_ptr< EDA_ITEM > CreateItemForType(KICAD_T aType, EDA_ITEM *aContainer)
void ApplySymbolInstance(SCH_SYMBOL *aSymbol, const kiapi::schematic::types::SchematicSymbolInstance &aInput, const SCH_SHEET_PATH &aPath, SCHEMATIC *aSchematic)
Apply placement-specific data to an aSymbol at aPath: reference, unit, and the per-placement attribut...
tl::expected< bool, ApiResponseStatus > UnpackSheet(SCH_SHEET *aOutput, const kiapi::schematic::types::SheetSymbol &aInput)
Unpack the every placement data from the input.
bool PackSheet(kiapi::schematic::types::SheetSymbol *aOutput, const SCH_SHEET *aInput, const SCH_SHEET_PATH &aPath)
bool PackSymbol(kiapi::schematic::types::SchematicSymbolInstance *aOutput, const SCH_SYMBOL *aInput, const SCH_SHEET_PATH &aPath)
void ApplySheetInstance(SCH_SHEET *aSheet, const kiapi::schematic::types::SheetSymbol &aInput, const SCH_SHEET_PATH &aParentPath, SCHEMATIC *aSchematic)
Apply the placement data in a sheet message to aSheet: page number and the variants the message carri...
bool UnpackSymbol(SCH_SYMBOL *aOutput, const kiapi::schematic::types::SchematicSymbolInstance &aInput)
Unpack the geometry, the library definition, fields, and the default-variant attributes that are shar...
BASE_SCREEN class implementation.
constexpr EDA_IU_SCALE schIUScale
Definition base_units.h:123
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
HANDLER_RESULT< bool > validateDocument(const DocumentSpecifier &aDocument)
HANDLER_RESULT< types::PageSettings > handleSetPageSettings(const HANDLER_CONTEXT< commands::SetPageSettings > &aCtx)
HANDLER_RESULT< std::optional< KIID > > validateItemHeaderDocument(const kiapi::common::types::ItemHeader &aHeader)
If the header is valid, returns the item container.
HANDLER_RESULT< types::PageSettings > handleGetPageSettings(const HANDLER_CONTEXT< commands::GetPageSettings > &aCtx)
API_HANDLER_EDITOR(EDA_BASE_FRAME *aFrame=nullptr)
static std::vector< KICAD_T > parseRequestedItemTypes(const google::protobuf::RepeatedField< int > &aTypes)
COMMIT * getCurrentCommit(const std::string &aClientName)
virtual void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage)
std::set< std::string > m_activeClients
std::map< std::string, std::pair< KIID, std::unique_ptr< COMMIT > > > m_commits
virtual std::optional< ApiResponseStatus > checkForBusy()
Checks if the editor can accept commands.
EDA_BASE_FRAME * m_frame
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportDxf(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportDxf > &aCtx)
wxString getDrawingSheetFileName() override
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportPng(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportPng > &aCtx)
HANDLER_RESULT< Empty > handleSetCurrentVariant(const HANDLER_CONTEXT< commands::SetCurrentVariant > &aCtx)
HANDLER_RESULT< kiapi::schematic::commands::SchematicHierarchyResponse > handleGetSchematicHierarchy(const HANDLER_CONTEXT< kiapi::schematic::commands::GetSchematicHierarchy > &aCtx)
HANDLER_RESULT< commands::VariantsResponse > handleGetVariants(const HANDLER_CONTEXT< commands::GetVariants > &aCtx)
std::unique_ptr< COMMIT > createCommit() override
Override this to create an appropriate COMMIT subclass for the frame in question.
SCHEMATIC * schematic() const
HANDLER_RESULT< commands::SelectionResponse > handleGetSelection(const HANDLER_CONTEXT< commands::GetSelection > &aCtx)
HANDLER_RESULT< Empty > handleCopyVariant(const HANDLER_CONTEXT< commands::CopyVariant > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportPdf(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportPdf > &aCtx)
SCH_SCREEN * resolveScreenFromDocument(const DocumentSpecifier &aDocument) const
Returns the sheet path's screen when one is given and it is found, or null.
HANDLER_RESULT< commands::CurrentVariantResponse > handleGetCurrentVariant(const HANDLER_CONTEXT< commands::GetCurrentVariant > &aCtx)
static std::set< KICAD_T > s_allowedTypes
HANDLER_RESULT< commands::GetItemsResponse > handleGetItemsById(const HANDLER_CONTEXT< commands::GetItemsById > &aCtx)
HANDLER_RESULT< google::protobuf::Empty > handleSaveDocument(const HANDLER_CONTEXT< commands::SaveDocument > &aCtx)
std::optional< PAGE_INFO > getPageSettings(const DocumentSpecifier &aDocument) override
HANDLER_RESULT< kiapi::schematic::commands::SchematicNetlistResponse > handleGetSchematicNetlist(const HANDLER_CONTEXT< kiapi::schematic::commands::GetSchematicNetlist > &aCtx)
std::optional< SCH_ITEM * > getItemById(const KIID &aId, SCH_SHEET_PATH *aPathOut=nullptr) const
std::shared_ptr< SCH_CONTEXT > m_context
std::optional< EDA_ITEM * > getItemFromDocument(const DocumentSpecifier &aDocument, const KIID &aId) override
TOOL_MANAGER * toolManager() const
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportSvg(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportSvg > &aCtx)
HANDLER_RESULT< Empty > handleDeleteVariant(const HANDLER_CONTEXT< commands::DeleteVariant > &aCtx)
HANDLER_RESULT< Empty > handleClearSelection(const HANDLER_CONTEXT< commands::ClearSelection > &aCtx)
HANDLER_RESULT< commands::HighlightNetsResponse > handleHighlightNets(const HANDLER_CONTEXT< commands::HighlightNets > &aCtx)
std::optional< ApiResponseStatus > checkForHeadless(const std::string &aCommandName) const
bool setPageSettings(const DocumentSpecifier &aDocument, const PAGE_INFO &aPageInfo) override
HANDLER_RESULT< commands::SavedSelectionResponse > handleSaveSelectionToString(const HANDLER_CONTEXT< commands::SaveSelectionToString > &aCtx)
HANDLER_RESULT< commands::CrossProbeAnnounceResponse > handleCrossProbeAnnounce(const HANDLER_CONTEXT< commands::CrossProbeAnnounce > &aCtx)
HANDLER_RESULT< std::unique_ptr< EDA_ITEM > > createItemForType(KICAD_T aType, EDA_ITEM *aContainer)
HANDLER_RESULT< kiapi::common::commands::PlaceFromLibraryResponse > handlePlaceSymbolFromLibrary(const HANDLER_CONTEXT< kiapi::schematic::commands::PlaceSymbolFromLibrary > &aCtx)
HANDLER_RESULT< commands::GetDocumentModifiedStateResponse > handleGetDocumentModifiedState(const HANDLER_CONTEXT< commands::GetDocumentModifiedState > &aCtx) override
HANDLER_RESULT< Empty > handleRenameVariant(const HANDLER_CONTEXT< commands::RenameVariant > &aCtx)
void filterValidSchTypes(std::set< KICAD_T > &aTypeList)
std::optional< TITLE_BLOCK * > getTitleBlock(const DocumentSpecifier &aDocument) override
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportBOM(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportBOM > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportPs(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportPs > &aCtx)
HANDLER_RESULT< commands::SavedDocumentResponse > handleSaveDocumentToString(const HANDLER_CONTEXT< commands::SaveDocumentToString > &aCtx)
PROJECT & project() const
void onModified() override
API_HANDLER_SCH(SCH_EDIT_FRAME *aFrame)
bool packSchItem(google::protobuf::Any &aOut, SCH_ITEM *aItem, const SCH_SHEET_PATH &aPath)
Serializes a schematic item into aOut, using path-aware packing for symbols and sheets.
HANDLER_RESULT< Empty > handleAddVariant(const HANDLER_CONTEXT< commands::AddVariant > &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< commands::GetOpenDocumentsResponse > handleGetOpenDocuments(const HANDLER_CONTEXT< commands::GetOpenDocuments > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunSchematicJobExportNetlist(const HANDLER_CONTEXT< kiapi::schematic::jobs::RunSchematicJobExportNetlist > &aCtx)
HANDLER_RESULT< google::protobuf::Empty > handleRevertDocument(const HANDLER_CONTEXT< commands::RevertDocument > &aCtx)
HANDLER_RESULT< commands::SelectionResponse > handleAddToSelection(const HANDLER_CONTEXT< commands::AddToSelection > &aCtx)
HANDLER_RESULT< commands::SelectionResponse > handleRemoveFromSelection(const HANDLER_CONTEXT< commands::RemoveFromSelection > &aCtx)
HANDLER_RESULT< commands::ExpandTextVariablesResponse > handleExpandTextVariables(const HANDLER_CONTEXT< commands::ExpandTextVariables > &aCtx)
void deleteItemsInternal(std::map< KIID, ItemDeletionStatus > &aItemsToDelete, const std::string &aClientName) override
void packSheetInstance(kiapi::schematic::types::SheetInstance *aInstance, SCH_SHEET_PATH &aPath, SCH_SHEET *aSheet)
tl::expected< bool, ApiResponseStatus > validateDocumentInternal(const DocumentSpecifier &aDocument) const override
HANDLER_RESULT< commands::SyncSelectionResponse > handleSyncSelection(const HANDLER_CONTEXT< commands::SyncSelection > &aCtx)
SCH_EDIT_FRAME * frame() const
HANDLER_RESULT< google::protobuf::Empty > handleSaveCopyOfDocument(const HANDLER_CONTEXT< commands::SaveCopyOfDocument > &aCtx)
SCH_CONTEXT * context() const
void setDrawingSheetFileName(const wxString &aFileName) override
HANDLER_RESULT< Empty > handleSetVariantDescription(const HANDLER_CONTEXT< commands::SetVariantDescription > &aCtx)
HANDLER_RESULT< commands::GetItemsResponse > handleGetItems(const HANDLER_CONTEXT< commands::GetItems > &aCtx)
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
Definition api_handler.h:93
CROSS_PROBING_SETTINGS m_CrossProbing
static wxString m_DrawingSheetFileName
the name of the drawing sheet file, or empty to use the default drawing sheet
Definition base_screen.h:81
void SetContentModified(bool aModified=true)
Definition base_screen.h:55
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:68
COMMIT & Remove(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Remove a new item from the model.
Definition commit.h:86
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
Calculate the connectivity of a schematic and generate netlists.
const NET_MAP & GetNetMap() const
A subgraph is a set of items that are electrically connected on a single sheet.
static PRIORITY GetDriverPriority(SCH_ITEM *aDriver)
Return the priority (higher is more important) of a candidate driver.
const SCH_CONNECTION * GetDriverConnection() const
static void RegisterPeer(FRAME_T aFrameType, const std::string &aSocketPath)
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
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
virtual wxString GetFriendlyName() const
Definition eda_item.cpp:565
EE_TYPE OfType(KICAD_T aType) const
Definition sch_rtree.h:248
bool IsEmpty() const
std::vector< wxString > m_variantNames
wxString m_stringDelimiter
wxString m_fieldDelimiter
bool m_includeByteOrderMark
wxString m_filename
wxString m_filterString
std::vector< wxString > m_fieldsOrdered
wxString m_refRangeDelimiter
std::vector< wxString > m_fieldsLabels
wxString m_refDelimiter
std::vector< wxString > m_fieldsGroupBy
wxString m_bomFmtPresetName
wxString m_sortField
wxString m_bomPresetName
BOM_FILTER_SCOPE m_filterScope
std::vector< wxString > m_variantNames
JOB_PAGE_SIZE m_pageSizeSelect
std::vector< wxString > m_plotPages
An simple container class that lets us dispatch output jobs to kifaces.
Definition job.h:184
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:157
const std::vector< JOB_OUTPUT > & GetOutputs()
Definition job.h:215
const std::string & GetType() const
Definition job.h:195
wxString AsString() const
Definition kiid.cpp:423
Definition kiid.h:46
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:340
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:740
@ FACE_SCH
eeschema DSO
Definition kiway.h:347
A logical library item identifier and consists of various portions much like a URI.
Definition lib_id.h:45
bool IsValid() const
Check if this LID_ID is valid.
Definition lib_id.h:168
UTF8 Format() const
Definition lib_id.cpp:132
Define a library symbol object.
Definition lib_symbol.h:119
int GetUnitCount() const override
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
static SYMBOL_LIBRARY_ADAPTER * SymbolLibAdapter(PROJECT *aProject)
Accessor for project symbol library manager adapter.
Container for project specific data.
Definition project.h:63
virtual const wxString GetProjectPath() const
Return the full path of the project.
Definition project.cpp:183
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:195
Holds all the data relating to one schematic.
Definition schematic.h:148
SCHEMATIC_SETTINGS & Settings() const
SCH_SCREEN * GetCurrentScreen() const
Definition schematic.h:313
SCH_ITEM * ResolveItem(const KIID &aID, SCH_SHEET_PATH *aPathOut=nullptr, bool aAllowNullptrReturn=false) const
Definition schematic.h:193
SCH_SHEET_LIST Hierarchy() const
Return the full schematic flattened hierarchical sheet list.
SCH_SHEET * GetTopLevelSheet(int aIndex=0) const
wxString GetCurrentVariant() const
Return the current variant being edited.
CONNECTION_GRAPH * ConnectionGraph() const
Definition schematic.h:317
bool ResolveTextVar(const SCH_SHEET_PATH *aSheetPath, wxString *token, int aDepth) const
std::vector< SCH_SHEET * > GetTopLevelSheets() const
Get the list of top-level sheets.
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:303
void RefreshHierarchy()
SCH_DRAW_PANEL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
EESCHEMA_SETTINGS * eeconfig() const
bool IsBus() const
Schematic editor (Eeschema) main window.
void RecalculateConnections(SCH_COMMIT *aCommit, SCH_CLEANUP_FLAGS aCleanupFlags, PROGRESS_REPORTER *aProgressReporter=nullptr, bool aCleanupDone=false)
Generate the connection data for the entire schematic hierarchy.
void OnModify() override
Must be called after a schematic change in order to set the "modify" flag and update other data struc...
void SetCurrentVariant(const wxString &aVariantName)
void UpdateVariantSelectionCtrl(const wxArrayString &aVariantNames)
Update the variant name control on the main toolbar.
SCH_SHEET_PATH & GetCurrentSheet() const
void LoadDrawingSheet()
Load the drawing sheet file.
void SetSyncingSelection(bool aSet)
void StartCrossProbeFlash(const std::vector< SCH_ITEM * > &aItems)
void HandleRemoteNetHighlight(const wxString &aNetName)
A set of SCH_ITEMs (i.e., without duplicates).
Definition sch_group.h:48
A SCH_IO derivation for loading schematic files using the new s-expression file format.
void FormatSchematicToFormatter(OUTPUTFORMATTER *aOut, SCH_SHEET *aSheet, SCHEMATIC *aSchematic, const std::map< std::string, UTF8 > *aProperties=nullptr)
Serialize a schematic sheet to an OUTPUTFORMATTER without file I/O or Prettify.
void Format(SCH_SHEET *aSheet)
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:165
virtual bool IsConnectable() const
Definition sch_item.h:531
void SetConnectivityDirty(bool aDirty=true)
Definition sch_item.h:600
void SwapItemData(SCH_ITEM *aImage)
Swap data between aItem and aImage.
Definition sch_item.cpp:665
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
void SortByReferenceOnly()
Sort the list of references by reference.
void ReannotateByOptions(ANNOTATE_ORDER_T aSortOption, ANNOTATE_ALGO_T aAlgoOption, int aStartNumber, const SCH_REFERENCE_LIST &aAdditionalRefs, bool aStartAtCurrent, SCH_SHEET_LIST *aHierarchy)
Forces reannotation of the provided references.
void SetRefDesTracker(std::shared_ptr< REFDES_TRACKER > aTracker)
void AddItem(const SCH_REFERENCE &aItem)
void UpdateAnnotation()
Update the symbol references for the schematic project (or the current sheet).
A helper to define a symbol's reference designator in a schematic.
bool AlwaysAnnotate() const
Verify the reference should always be automatically annotated.
void Split()
Attempt to split the reference designator into a name (U) and number (1).
bool IsSplitNeeded()
Determine if this reference needs to be split or if it likely already has been.
SCH_SYMBOL * GetSymbol() const
wxString GetRef() const
int GetUnit() const
wxString GetRefNumber() const
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:118
const wxString & GetFileName() const
Definition sch_screen.h:153
void GetSheets(std::vector< SCH_ITEM * > *aItems) const
Similar to Items().OfType( SCH_SHEET_T ), but return the sheets in a deterministic order (L-R,...
SCH_SELECTION & GetSelection()
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
std::optional< SCH_SHEET_PATH > GetSheetPathByKIIDPath(const KIID_PATH &aPath, bool aIncludeLastSheet=true) const
Finds a SCH_SHEET_PATH that matches the provided KIID_PATH.
SCH_ITEM * ResolveItem(const KIID &aID, SCH_SHEET_PATH *aPathOut=nullptr, bool aAllowNullptrReturn=false) const
Fetch a SCH_ITEM by ID.
wxString GetNextPageNumber() const
void GetSymbols(SCH_REFERENCE_LIST &aReferences, SYMBOL_FILTER aSymbolFilter, bool aForceIncludeOrphanSymbols=false) const
Add a SCH_REFERENCE object to aReferences for each symbol in the list of sheets.
bool TestForRecursion(const SCH_SHEET_LIST &aSrcSheetHierarchy, const wxString &aDestFileName)
Test every SCH_SHEET_PATH in this SCH_SHEET_LIST to verify if adding the sheets stored in aSrcSheetHi...
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
int ComparePageNum(const SCH_SHEET_PATH &aSheetPathToTest) const
Compare sheets by their page number.
void GetSymbols(SCH_REFERENCE_LIST &aReferences, SYMBOL_FILTER aSymbolFilter, bool aForceIncludeOrphanSymbols=false) const
Adds SCH_REFERENCE object to aReferences for each symbol in the sheet.
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
SCH_ITEM * ResolveItem(const KIID &aID) const
Fetch a SCH_ITEM by ID.
SCH_SCREEN * LastScreen()
wxString GetPageNumber() const
bool IsContainedWithin(const SCH_SHEET_PATH &aSheetPathToTest) const
Check if this path is contained inside aSheetPathToTest.
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
void pop_back()
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
wxString GetFileName() const
Return the filename corresponding to this sheet.
Definition sch_sheet.h:384
void AddInstance(const SCH_SHEET_INSTANCE &aInstance)
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
wxString GetShownName(RESOLUTION_CONTEXT aContext) const
Definition sch_sheet.h:138
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
Schematic symbol object.
Definition sch_symbol.h:75
std::vector< const SCH_PIN * > GetPins(const SCH_SHEET_PATH *aSheet) const
Retrieve a list of the SCH_PINs for the given sheet path.
void AddHierarchicalReference(const KIID_PATH &aPath, const wxString &aRef, int aUnit)
Add a full hierarchical reference to this symbol.
int AddItemsToSel(const TOOL_EVENT &aEvent)
int RemoveItemsFromSel(const TOOL_EVENT &aEvent)
virtual void Serialize(google::protobuf::Any &aContainer) const
Serializes this object to the given Any message.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
const std::string & GetString()
Definition richio.h:453
An interface to the global shared library manager that is schematic-specific and linked to one projec...
LIB_SYMBOL * LoadSymbol(const wxString &aNickname, const wxString &aName)
Load a LIB_SYMBOL having aName from the library given by aNickname.
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
wxString wx_str() const
Definition utf8.cpp:41
A wrapper for reporting to a wxString object.
Definition reporter.h:242
A type-safe container of any type.
Definition ki_any.h:92
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:92
#define _(s)
@ NO_RECURSE
Definition eda_item.h:52
FRAME_T
The set of EDA_BASE_FRAME derivatives, typically stored in EDA_BASE_FRAME::m_Ident.
Definition frame_type.h:29
static const std::string KiCadSchematicFileExtension
const wxChar *const traceCrossProbeFlash
Flag to enable debug output for cross-probe flash operations.
const wxChar *const traceApi
Flag to enable debug output related to the IPC API and its plugin system.
Definition api_utils.cpp:33
void Prettify(std::string &aSource, FORMAT_MODE aMode)
Pretty-prints s-expression text according to KiCad format rules.
KICOMMON_API void PackProject(types::ProjectSpecifier &aOutput, const PROJECT &aInput)
KICOMMON_API KIID_PATH UnpackSheetPath(const types::SheetPath &aInput)
KICOMMON_API std::optional< KICAD_T > TypeNameFromAny(const google::protobuf::Any &aMessage)
Definition api_utils.cpp:50
KICOMMON_API ApiResponseStatus MakeResponseStatus(ApiStatusCode aCode, const std::string &aMessage)
Definition api_utils.cpp:39
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackSheetPath(types::SheetPath &aOutput, const KIID_PATH &aInput)
KICOMMON_API LIB_ID UnpackLibId(const types::LibraryIdentifier &aId)
const KICOMMON_API std::string KiwayClientName
const KICOMMON_API std::string StandaloneCrossProbeClientName
STL namespace.
constexpr int MIN_PNG_DPI
Definition plotter_png.h:29
constexpr int MAX_PNG_DPI
Definition plotter_png.h:30
std::shared_ptr< SCH_CONTEXT > CreateSchFrameContext(SCH_EDIT_FRAME *aFrame)
Class to handle a set of SCH_ITEMs.
@ AUTOPLACE_AUTO
Definition sch_item.h:70
std::vector< EDA_ITEM * > EDA_ITEMS
ANNOTATE_ORDER_T
Schematic annotation order options.
ANNOTATE_ALGO_T
Schematic annotation type options.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_FILTER_NON_POWER
@ SYMBOL_FILTER_ALL
@ LOCAL_CLEANUP
Definition schematic.h:93
wxString GetDefaultVariantName()
std::vector< wxString > ExpandStackedPinNotation(const wxString &aPinName, bool *aValid)
Expand stacked pin notation like [1,2,3], [1-4], [A1-A4], or [AA1-AA3,AB4,CD12-CD14] into individual ...
Cross-probing behavior.
bool on_selection
Synchronize the selection for multiple items too.
bool auto_highlight
Automatically turn on highlight mode in the target frame.
std::string ClientName
Definition api_handler.h:51
RequestMessageType Request
Definition api_handler.h:52
A simple container for sheet instance information.
A simple container for schematic symbol instance information.
std::string path
IbisParser parser & reporter
KIBIS_PIN * pin
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.
KICAD_T
The set of class identification values stored in EDA_ITEM::m_structType.
Definition typeinfo.h:70
@ SCH_GROUP_T
Definition typeinfo.h:169
@ SCH_TABLE_T
Definition typeinfo.h:161
@ SCH_LINE_T
Definition typeinfo.h:159
@ SCH_NO_CONNECT_T
Definition typeinfo.h:156
@ SCH_SYMBOL_T
Definition typeinfo.h:168
@ SCH_DIRECTIVE_LABEL_T
Definition typeinfo.h:167
@ SCH_LABEL_T
Definition typeinfo.h:163
@ SCH_SHEET_T
Definition typeinfo.h:171
@ SCH_SHAPE_T
Definition typeinfo.h:145
@ SCH_RULE_AREA_T
Definition typeinfo.h:166
@ SCH_HIER_LABEL_T
Definition typeinfo.h:165
@ SCH_BUS_BUS_ENTRY_T
Definition typeinfo.h:158
@ SCH_TEXT_T
Definition typeinfo.h:147
@ SCH_BUS_WIRE_ENTRY_T
Definition typeinfo.h:157
@ SCH_BITMAP_T
Definition typeinfo.h:160
@ SCH_TEXTBOX_T
Definition typeinfo.h:148
@ SCH_GLOBAL_LABEL_T
Definition typeinfo.h:164
@ SCH_JUNCTION_T
Definition typeinfo.h:155
@ SCH_PIN_T
Definition typeinfo.h:149
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683
Definition of file extensions used in Kicad.