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>
25#include <api/api_server.h>
27#include <api/sch_context.h>
28#include <fmt.h>
29#include <fmt/ranges.h>
30#include <wx/log.h>
31#include <magic_enum.hpp>
32#include <base_screen.h>
33#include <base_units.h>
34#include <jobs/job_export_bom.h>
37#include <kiway.h>
39#include <sch_field.h>
40#include <sch_group.h>
41#include <common.h>
42#include <connection_graph.h>
44#include <advanced_config.h>
45#include <sch_commit.h>
46#include <string_utils.h>
47#include <sch_edit_frame.h>
49#include <ki_error.h>
50#include <pgm_base.h>
51#include <richio.h>
53
54#include <sch_label.h>
55#include <sch_screen.h>
56#include <sch_sheet.h>
57#include <sch_sheet_path.h>
58#include <sch_sheet_pin.h>
59#include <sch_symbol.h>
60#include <schematic.h>
62#include <tool/actions.h>
63#include <tool/common_tools.h>
64#include <tool/tool_manager.h>
65#include <tools/sch_actions.h>
67#include <project.h>
69#include <wx/filename.h>
70
71#include <api/common/commands/library_commands.pb.h>
72#include <api/common/types/base_types.pb.h>
74#include <project_sch.h>
75#include <trace_helpers.h>
76
77using namespace kiapi::common::commands;
78using kiapi::common::types::CommandStatus;
79using kiapi::common::types::DocumentType;
80using kiapi::common::types::ItemRequestStatus;
81
82
83std::set<KICAD_T> API_HANDLER_SCH::s_allowedTypes = {
102};
103
104
106{
107 types::RunJobResponse response;
109 int exitCode = aKiway->ProcessJob( KIWAY::FACE_SCH, &aJob, &reporter );
110
111 for( const JOB_OUTPUT& output : aJob.GetOutputs() )
112 response.add_output_path( output.m_outputPath.ToUTF8() );
113
114 if( exitCode == 0 )
115 {
116 response.set_status( types::JobStatus::JS_SUCCESS );
117 return response;
118 }
119
120 response.set_status( types::JobStatus::JS_ERROR );
121 response.set_message( fmt::format( "Schematic export job '{}' failed with exit code {}: {}",
122 aJob.GetType(), exitCode,
123 reporter.GetMessages().ToStdString() ) );
124 return response;
125}
126
127
132
133
134API_HANDLER_SCH::API_HANDLER_SCH( std::shared_ptr<SCH_CONTEXT> aContext,
135 SCH_EDIT_FRAME* aFrame ) :
136 API_HANDLER_EDITOR( aFrame ),
137 m_context( std::move( aContext ) )
138{
139 using namespace kiapi::schematic::jobs;
140 using namespace kiapi::schematic::types;
141 using namespace kiapi::schematic::commands;
142
151
158
165
200}
201
202
203std::unique_ptr<COMMIT> API_HANDLER_SCH::createCommit()
204{
205 if( m_frame )
206 return std::make_unique<SCH_COMMIT>( static_cast<SCH_EDIT_FRAME*>( m_frame ) );
207
208 return std::make_unique<SCH_COMMIT>( toolManager() );
209}
210
211
213{
214 wxCHECK( m_context, nullptr );
215 return m_context->GetSchematic();
216}
217
218
220{
221 return static_cast<SCH_EDIT_FRAME*>( m_frame );
222}
223
224
225std::optional<ApiResponseStatus> API_HANDLER_SCH::checkForHeadless( const std::string& aCommandName ) const
226{
227 if( m_frame )
228 return std::nullopt;
229
230 ApiResponseStatus e;
231 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
232 e.set_error_message( fmt::format( "{} is not available in headless mode", aCommandName ) );
233 return e;
234}
235
236
237bool API_HANDLER_SCH::packSchItem( google::protobuf::Any& aOut, SCH_ITEM* aItem,
238 const SCH_SHEET_PATH& aPath )
239{
240 if( aItem->Type() == SCH_SYMBOL_T )
241 {
242 kiapi::schematic::types::SchematicSymbolInstance symbol;
243
244 if( !PackSymbol( &symbol, static_cast<SCH_SYMBOL*>( aItem ), aPath ) )
245 return false;
246
247 aOut.PackFrom( symbol );
248 }
249 else if( aItem->Type() == SCH_SHEET_T )
250 {
251 kiapi::schematic::types::SheetSymbol sheet;
252
253 if( !PackSheet( &sheet, static_cast<SCH_SHEET*>( aItem ), aPath ) )
254 return false;
255
256 aOut.PackFrom( sheet );
257 }
258 else
259 {
260 aItem->Serialize( aOut );
261 }
262
263 return true;
264}
265
266
267std::optional<SCH_ITEM*> API_HANDLER_SCH::getItemById( const KIID& aId, SCH_SHEET_PATH* aPathOut ) const
268{
269 if( !schematic()->HasHierarchy() )
271
272 SCH_ITEM* item = schematic()->ResolveItem( aId, aPathOut, true );
273
274 if( !item )
275 return std::nullopt;
276
277 return item;
278}
279
280
281tl::expected<bool, ApiResponseStatus>
282API_HANDLER_SCH::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
283{
284 if( aDocument.type() != DocumentType::DOCTYPE_SCHEMATIC )
285 {
286 ApiResponseStatus e;
287 e.set_status( ApiStatusCode::AS_UNHANDLED );
288 return tl::unexpected( e );
289 }
290
291 const PROJECT& prj = m_context->Prj();
292
293 if( aDocument.project().name().compare( prj.GetProjectName().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",
298 aDocument.project().name() ) );
299 return tl::unexpected( e );
300 }
301
302 if( aDocument.project().path().compare( prj.GetProjectPath().ToUTF8() ) != 0 )
303 {
304 ApiResponseStatus e;
305 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
306 e.set_error_message( fmt::format( "the requested project {} is not open at path {}",
307 aDocument.project().name(),
308 aDocument.project().path() ) );
309 return tl::unexpected( e );
310 }
311
312 if( aDocument.has_sheet_path() )
313 {
314 KIID_PATH path = UnpackSheetPath( aDocument.sheet_path() );
315
316 if( !schematic()->Hierarchy().HasPath( path ) )
317 {
318 ApiResponseStatus e;
319 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
320 e.set_error_message( fmt::format( "the requested sheet path {} is not valid for this schematic",
321 path.AsString().ToStdString() ) );
322 return tl::unexpected( e );
323 }
324 }
325
326 return true;
327}
328
329
331{
332 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
333 return tl::unexpected( documentValidation.error() );
334
335 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
336 return tl::unexpected( *busy );
337
338 if( project().IsNullProject() || schematic()->GetFileName().IsEmpty() )
339 {
340 ApiResponseStatus e;
341 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
342 e.set_error_message( "the schematic is untitled; use SaveDocumentAs to save it to a new file" );
343 return tl::unexpected( e );
344 }
345
346 if( !context()->SaveSchematic() )
347 {
348 ApiResponseStatus e;
349 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
350 e.set_error_message( "failed to save schematic" );
351 return tl::unexpected( e );
352 }
353
354 return google::protobuf::Empty();
355}
356
357
360{
361 // In GUI mode a project change may require closing other editor frames, which the API is
362 // not allowed to do; headless mode has no other frames, so it is safe there
363 if( Pgm().IsGUI() )
364 {
365 ApiResponseStatus e;
366 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
367 e.set_error_message( "SaveDocumentAs is not available in GUI mode" );
368 return tl::unexpected( e );
369 }
370
371 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
372 return tl::unexpected( *busy );
373
374 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
375
376 if( !documentValidation )
377 return tl::unexpected( documentValidation.error() );
378
379 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
380
381 if( pathStr.IsEmpty() )
382 {
383 ApiResponseStatus e;
384 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
385 e.set_error_message( "path is required" );
386 return tl::unexpected( e );
387 }
388
389 wxFileName oldSchematicFile( m_context->GetCurrentFileName() );
390
391 if( !context()->SaveSchematicAs( pathStr ) )
392 {
393 ApiResponseStatus e;
394 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
395 e.set_error_message( fmt::format( "failed to save schematic to '{}'", pathStr.ToStdString() ) );
396 return tl::unexpected( e );
397 }
398
399 if( KICAD_API_SERVER* server = apiServer() )
400 {
401 wxFileName newSchematicFile( pathStr );
402 newSchematicFile.SetExt( FILEEXT::KiCadSchematicFileExtension );
403
404 server->NotifyDocumentRenamed( thisDocumentType(), oldSchematicFile.GetFullPath(),
405 newSchematicFile.GetFullPath() );
406 }
407
408 return google::protobuf::Empty();
409}
410
411
414{
415 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
416
417 if( !documentValidation )
418 return tl::unexpected( documentValidation.error() );
419
420 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
421 return tl::unexpected( *busy );
422
423 wxFileName schematicPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
424
425 if( !schematicPath.IsOk() || !schematicPath.IsDirWritable() )
426 {
427 ApiResponseStatus e;
428 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
429 e.set_error_message(
430 fmt::format( "save path '{}' could not be opened", schematicPath.GetFullPath().ToStdString() ) );
431 return tl::unexpected( e );
432 }
433
434 if( schematicPath.FileExists() && ( !schematicPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
435 {
436 ApiResponseStatus e;
437 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
438 e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
439 schematicPath.GetFullPath().ToStdString() ) );
440 return tl::unexpected( e );
441 }
442
443 if( schematicPath.GetExt() != FILEEXT::KiCadSchematicFileExtension )
444 {
445 ApiResponseStatus e;
446 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
447 e.set_error_message( fmt::format( "save path '{}' must have a kicad_sch extension",
448 schematicPath.GetFullPath().ToStdString() ) );
449 return tl::unexpected( e );
450 }
451
452 bool includeProject = true;
453
454 if( aCtx.Request.has_options() )
455 includeProject = aCtx.Request.options().include_project();
456
457 if( !context()->SaveSchematicCopy( schematicPath.GetFullPath(), includeProject ) )
458 {
459 ApiResponseStatus e;
460 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
461 e.set_error_message( "failed to save schematic copy" );
462 return tl::unexpected( e );
463 }
464
465 return google::protobuf::Empty();
466}
467
468
471{
472 if( aCtx.Request.document().type() != DocumentType::DOCTYPE_SCHEMATIC )
473 {
474 ApiResponseStatus e;
475 e.set_status( ApiStatusCode::AS_UNHANDLED );
476 return tl::unexpected( e );
477 }
478
479 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
480
481 if( !documentValidation )
482 return tl::unexpected( documentValidation.error() );
483
484 if( !m_commits.empty() )
485 {
486 ApiResponseStatus e;
487 e.set_status( ApiStatusCode::AS_BUSY );
488 e.set_error_message( "cannot revert while a commit is open" );
489 return tl::unexpected( e );
490 }
491
492 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
493 return tl::unexpected( *busy );
494
495 if( !context()->RevertToSaved() )
496 {
497 ApiResponseStatus e;
498 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
499 e.set_error_message( "could not revert: there is no saved file on disk to revert to" );
500 return tl::unexpected( e );
501 }
502
503 return google::protobuf::Empty();
504}
505
506
509{
510 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
511
512 if( !documentValidation )
513 return tl::unexpected( documentValidation.error() );
514
515 commands::SavedDocumentResponse response;
516
517 SCH_SHEET* topLevelSheet = schematic()->GetTopLevelSheet( 0 );
518
519 if( aCtx.Request.document().has_sheet_path() )
520 {
521 KIID_PATH kiidPath = UnpackSheetPath( aCtx.Request.document().sheet_path() );
522
523 if( std::optional<SCH_SHEET_PATH> resolvedPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( kiidPath ) )
524 topLevelSheet = resolvedPath->Last();
525 }
526
527 if( !topLevelSheet || !topLevelSheet->GetScreen() )
528 {
529 ApiResponseStatus e;
530 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
531 e.set_error_message( "schematic has no top-level sheet to save" );
532 return tl::unexpected( e );
533 }
534
535 STRING_FORMATTER formatter;
536 SCH_IO_KICAD_SEXPR plugin;
537
538 plugin.FormatSchematicToFormatter( &formatter, topLevelSheet, schematic(), nullptr );
539
540 std::string contents = formatter.GetString();
541 KICAD_FORMAT::Prettify( contents, KICAD_FORMAT::FORMAT_MODE::COMPACT_TEXT_PROPERTIES );
542 response.set_contents( contents );
543
544 return response;
545}
546
547
550{
551 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SaveSelectionToString" ) )
552 return tl::unexpected( *headless );
553
555 SCH_SELECTION& selection = selTool->GetSelection();
556
557 if( selection.Empty() )
558 {
559 ApiResponseStatus e;
560 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
561 e.set_error_message( "the selection is empty" );
562 return tl::unexpected( e );
563 }
564
565 commands::SavedSelectionResponse response;
566
567 SCH_SHEET_PATH selPath = frame()->GetCurrentSheet();
568
569 for( EDA_ITEM* item : selection )
570 response.add_ids()->set_value( item->m_Uuid.AsStdString() );
571
572 STRING_FORMATTER formatter;
573 SCH_IO_KICAD_SEXPR plugin;
574
575 plugin.Format( &selection, &selPath, *schematic(), &formatter, true );
576
577 std::string contents = formatter.GetString();
578 KICAD_FORMAT::Prettify( contents, KICAD_FORMAT::FORMAT_MODE::COMPACT_TEXT_PROPERTIES );
579 response.set_contents( contents );
580
581 return response;
582}
583
584
587{
588 if( aCtx.Request.type() != DocumentType::DOCTYPE_SCHEMATIC )
589 {
590 ApiResponseStatus e;
591
592 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
593 e.set_status( ApiStatusCode::AS_UNHANDLED );
594 return tl::unexpected( e );
595 }
596
597 GetOpenDocumentsResponse response;
598 common::types::DocumentSpecifier doc;
599
600 wxFileName fn( m_context->GetCurrentFileName() );
601
602 doc.set_type( DocumentType::DOCTYPE_SCHEMATIC );
603
604 if( std::optional<SCH_SHEET_PATH> path = m_context->GetCurrentSheet() )
605 PackSheetPath( *doc.mutable_sheet_path(), *path );
606
607 PackProject( *doc.mutable_project(), m_context->Prj() );
608
609 response.mutable_documents()->Add( std::move( doc ) );
610 return response;
611}
612
613
616{
617 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
618 return tl::unexpected( documentValidation.error() );
619
620 GetDocumentModifiedStateResponse response;
621
622 if( aCtx.Request.document().has_sheet_path() )
623 {
624 KIID_PATH path = UnpackSheetPath( aCtx.Request.document().sheet_path() );
625
626 std::optional<SCH_SHEET_PATH> sheetPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( path );
627
628 if( !sheetPath )
629 {
630 ApiResponseStatus e;
631 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
632 e.set_error_message( "the requested sheet path is not valid for this schematic" );
633 return tl::unexpected( e );
634 }
635
636 if( const SCH_SCREEN* screen = sheetPath->LastScreen() )
637 {
638 response.set_state( screen->IsContentModified() ? DocumentModifiedState::DMS_MODIFIED
639 : DocumentModifiedState::DMS_UNMODIFIED );
640 }
641
642 return response;
643 }
644
645 if( !schematic()->HasHierarchy() )
647
648 response.set_state( schematic()->Hierarchy().IsModified() ? DocumentModifiedState::DMS_MODIFIED
649 : DocumentModifiedState::DMS_UNMODIFIED );
650 return response;
651}
652
653
654void API_HANDLER_SCH::filterValidSchTypes( std::set<KICAD_T>& aTypeList )
655{
656 std::erase_if( aTypeList,
657 []( KICAD_T aType )
658 {
659 return !s_allowedTypes.contains( aType );
660 } );
661}
662
663
665{
666 if( HANDLER_RESULT<std::optional<KIID>> valid = validateItemHeaderDocument( aCtx.Request.header() );
667 !valid.has_value() )
668 {
669 return tl::unexpected( valid.error() );
670 }
671
672 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
673 return tl::unexpected( *busy );
674
675 std::vector<KICAD_T> requestedTypes = parseRequestedItemTypes( aCtx.Request.types() );
676
677 if( aCtx.Request.types().empty() )
678 requestedTypes.assign( s_allowedTypes.begin(), s_allowedTypes.end() );
679
680 std::set<KICAD_T> typesRequested;
681
682 for( KICAD_T type : requestedTypes )
683 typesRequested.insert( type );
684
685 filterValidSchTypes( typesRequested );
686
687 if( typesRequested.empty() )
688 {
689 ApiResponseStatus e;
690 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
691 e.set_error_message( "none of the requested types are valid for a Schematic object" );
692 return tl::unexpected( e );
693 }
694
695 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
696 std::optional<SCH_SHEET_PATH> pathFilter;
697
698 if( aCtx.Request.header().document().has_sheet_path() )
699 {
700 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
701 pathFilter = hierarchy.GetSheetPathByKIIDPath( kp );
702 }
703
704 std::map<KICAD_T, std::vector<std::pair<EDA_ITEM*, SCH_SHEET_PATH>>> itemMap;
705
706 auto processScreen =
707 [&]( const SCH_SHEET_PATH& aPath )
708 {
709 const SCH_SCREEN* aScreen = aPath.LastScreen();
710
711 if( !aScreen )
712 return;
713
714 for( SCH_ITEM* aItem : aScreen->Items() )
715 {
716 itemMap[ aItem->Type() ].emplace_back( aItem, aPath );
717
718 // Group members live in the screen's rtree as well as in the group
719 if( aItem->Type() == SCH_GROUP_T )
720 continue;
721
722 aItem->RunOnChildren(
723 [&]( SCH_ITEM* aChild )
724 {
725 itemMap[ aChild->Type() ].emplace_back( aChild, aPath );
726 },
728 }
729 };
730
731 if( pathFilter )
732 {
733 processScreen( *pathFilter );
734 }
735 else
736 {
737 for( const SCH_SHEET_PATH& path : hierarchy )
738 processScreen( path );
739 }
740
741 GetItemsResponse response;
742 google::protobuf::Any any;
743
744 for( KICAD_T type : typesRequested )
745 {
746 for( const auto& [item, itemPath] : itemMap[type] )
747 {
748 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), itemPath ) )
749 response.mutable_items()->Add( std::move( any ) );
750 }
751 }
752
753 response.set_status( ItemRequestStatus::IRS_OK );
754 return response;
755}
756
757
759{
760 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
761 {
762 ApiResponseStatus e;
763 e.set_status( ApiStatusCode::AS_UNHANDLED );
764 return tl::unexpected( e );
765 }
766
767 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
768 return tl::unexpected( *busy );
769
770 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
771 std::optional<SCH_SHEET_PATH> pathFilter;
772
773 if( aCtx.Request.header().document().has_sheet_path() )
774 {
775 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
776 pathFilter = hierarchy.GetSheetPathByKIIDPath( kp );
777 }
778
779 GetItemsResponse response;
780 SCH_ITEM* item = nullptr;
781 google::protobuf::Any any;
782
783 for( const types::KIID& idProto : aCtx.Request.items() )
784 {
785 KIID id( idProto.value() );
786
787 SCH_SHEET_PATH itemPath;
788
789 if( pathFilter )
790 {
791 item = pathFilter->ResolveItem( id );
792 itemPath = *pathFilter;
793 }
794 else
795 {
796 item = hierarchy.ResolveItem( id, &itemPath, true );
797 }
798
799 if( !item || !s_allowedTypes.contains( item->Type() ) )
800 continue;
801
802 if( item->Type() == SCH_SYMBOL_T )
803 {
804 kiapi::schematic::types::SchematicSymbolInstance symbol;
805
806 if( !PackSymbol( &symbol, static_cast<SCH_SYMBOL*>( item ), itemPath ) )
807 continue;
808
809 any.PackFrom( symbol );
810 }
811 else if( item->Type() == SCH_SHEET_T )
812 {
813 kiapi::schematic::types::SheetSymbol sheet;
814
815 if( !PackSheet( &sheet, static_cast<SCH_SHEET*>( item ), itemPath ) )
816 continue;
817
818 any.PackFrom( sheet );
819 }
820 else
821 {
822 item->Serialize( any );
823 }
824
825 response.mutable_items()->Add( std::move( any ) );
826 }
827
828 if( response.items().empty() )
829 {
830 ApiResponseStatus e;
831 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
832 e.set_error_message( "none of the requested IDs were found or valid" );
833 return tl::unexpected( e );
834 }
835
836 response.set_status( ItemRequestStatus::IRS_OK );
837 return response;
838}
839
840
843{
844 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetSelection" ) )
845 return tl::unexpected( *headless );
846
847 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
848 {
849 ApiResponseStatus e;
850 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
851 e.set_status( ApiStatusCode::AS_UNHANDLED );
852 return tl::unexpected( e );
853 }
854
855 std::set<KICAD_T> filter;
856
857 for( KICAD_T type : parseRequestedItemTypes( aCtx.Request.types() ) )
858 filter.insert( type );
859
860 SCH_SELECTION_TOOL* tool = m_context->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
861 SCH_SHEET_PATH path = m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() );
862
863 SelectionResponse response;
864 google::protobuf::Any any;
865
866 for( EDA_ITEM* item : tool->GetSelection() )
867 {
868 if( filter.empty() || filter.contains( item->Type() ) )
869 {
870 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), path ) )
871 response.mutable_items()->Add( std::move( any ) );
872 }
873 }
874
875 return response;
876}
877
878
881{
882 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "ClearSelection" ) )
883 return tl::unexpected( *headless );
884
885 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
886 {
887 ApiResponseStatus e;
888 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
889 e.set_status( ApiStatusCode::AS_UNHANDLED );
890 return tl::unexpected( e );
891 }
892
893 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
894 return tl::unexpected( *busy );
895
896 m_context->GetToolManager()->RunAction( ACTIONS::selectionClear );
897 frame()->Refresh();
898
899 return Empty();
900}
901
902
905{
906 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "AddToSelection" ) )
907 return tl::unexpected( *headless );
908
909 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
910 {
911 ApiResponseStatus e;
912 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
913 e.set_status( ApiStatusCode::AS_UNHANDLED );
914 return tl::unexpected( e );
915 }
916
917 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
918 return tl::unexpected( *busy );
919
920 SCH_SELECTION_TOOL* tool = m_context->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
921 SCH_SHEET_PATH current = m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() );
922
923 EDA_ITEMS toAdd;
924
925 for( const types::KIID& id : aCtx.Request.items() )
926 {
927 SCH_SHEET_PATH itemPath;
928
929 // Selection only operates on the currently-displayed sheet; off-sheet items are skipped
930 if( std::optional<SCH_ITEM*> item = getItemById( KIID( id.value() ), &itemPath );
931 item && itemPath == current )
932 {
933 toAdd.push_back( *item );
934 }
935 }
936
937 tool->AddItemsToSel( &toAdd );
938 frame()->Refresh();
939
940 SelectionResponse response;
941 google::protobuf::Any any;
942
943 for( EDA_ITEM* item : tool->GetSelection() )
944 {
945 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), current ) )
946 response.mutable_items()->Add( std::move( any ) );
947 }
948
949 return response;
950}
951
952
955{
956 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "RemoveFromSelection" ) )
957 return tl::unexpected( *headless );
958
959 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
960 {
961 ApiResponseStatus e;
962 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
963 e.set_status( ApiStatusCode::AS_UNHANDLED );
964 return tl::unexpected( e );
965 }
966
967 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
968 return tl::unexpected( *busy );
969
970 SCH_SELECTION_TOOL* tool = m_context->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
971 SCH_SHEET_PATH current = m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() );
972
973 EDA_ITEMS toRemove;
974
975 for( const types::KIID& id : aCtx.Request.items() )
976 {
977 SCH_SHEET_PATH itemPath;
978
979 if( std::optional<SCH_ITEM*> item = getItemById( KIID( id.value() ), &itemPath );
980 item && itemPath == current )
981 {
982 toRemove.push_back( *item );
983 }
984 }
985
986 tool->RemoveItemsFromSel( &toRemove );
987 frame()->Refresh();
988
989 SelectionResponse response;
990 google::protobuf::Any any;
991
992 for( EDA_ITEM* item : tool->GetSelection() )
993 {
994 if( packSchItem( any, static_cast<SCH_ITEM*>( item ), current ) )
995 response.mutable_items()->Add( std::move( any ) );
996 }
997
998 return response;
999}
1000
1001
1003{
1004 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "FocusOnItems" ) )
1005 return tl::unexpected( *headless );
1006
1007 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
1008 return tl::unexpected( documentValidation.error() );
1009
1010 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1011 return tl::unexpected( *busy );
1012
1013 std::vector<KIID> ids;
1014
1015 for( const types::KIID& id : aCtx.Request.items() )
1016 ids.emplace_back( id.value() );
1017
1018 std::optional<KIID_PATH> sheetPath;
1019
1020 if( aCtx.Request.document().has_sheet_path() )
1021 sheetPath = UnpackSheetPath( aCtx.Request.document().sheet_path() );
1022
1023 tl::expected<SCH_FOCUS_TARGET, ApiResponseStatus> target = ResolveFocusItems( *schematic(), ids, sheetPath );
1024
1025 if( !target )
1026 return tl::unexpected( target.error() );
1027
1028 if( m_context->GetCurrentSheet().value_or( SCH_SHEET_PATH() ) != target->Sheet )
1029 m_context->GetToolManager()->RunAction<SCH_SHEET_PATH*>( SCH_ACTIONS::changeSheet, &target->Sheet );
1030
1031 BOX2I bbox = target->BBox;
1032
1033 if( aCtx.Request.has_margin() )
1034 bbox.Inflate( UnpackDistance( aCtx.Request.margin(), schIUScale ) );
1035
1036 m_context->GetToolManager()->GetTool<COMMON_TOOLS>()->ZoomFitBox( bbox );
1037
1038 return Empty();
1039}
1040
1041
1043{
1044 if( !aContainer )
1045 {
1046 ApiResponseStatus e;
1047 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1048 e.set_error_message( "Tried to create an item in a null container" );
1049 return tl::unexpected( e );
1050 }
1051
1052 if( !s_allowedTypes.contains( aType ) )
1053 {
1054 ApiResponseStatus e;
1055 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1056 e.set_error_message( fmt::format( "type {} is not supported by the schematic API handler",
1057 magic_enum::enum_name( aType ) ) );
1058 return tl::unexpected( e );
1059 }
1060
1061 if( aType == SCH_PIN_T && !dynamic_cast<SCH_SYMBOL*>( aContainer ) )
1062 {
1063 ApiResponseStatus e;
1064 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1065 e.set_error_message( fmt::format( "Tried to create a pin in {}, which is not a symbol",
1066 aContainer->GetFriendlyName().ToStdString() ) );
1067 return tl::unexpected( e );
1068 }
1069 else if( aType == SCH_SHEET_T && !dynamic_cast<SCH_SCREEN*>( aContainer ) )
1070 {
1071 ApiResponseStatus e;
1072 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1073 e.set_error_message( fmt::format( "Tried to create a sheet symbol in {}, which is not a "
1074 "schematic sheet",
1075 aContainer->GetFriendlyName().ToStdString() ) );
1076 return tl::unexpected( e );
1077 }
1078 else if( aType == SCH_SYMBOL_T && !dynamic_cast<SCH_SCREEN*>( aContainer ) )
1079 {
1080 ApiResponseStatus e;
1081 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1082 e.set_error_message( fmt::format( "Tried to create a symbol in {}, which is not a "
1083 "schematic sheet",
1084 aContainer->GetFriendlyName().ToStdString() ) );
1085 return tl::unexpected( e );
1086 }
1087
1088 std::unique_ptr<EDA_ITEM> created = CreateItemForType( aType, aContainer );
1089
1090 if( created && !created->GetParent() )
1091 created->SetParent( aContainer );
1092
1093 if( !created )
1094 {
1095 ApiResponseStatus e;
1096 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1097 e.set_error_message( fmt::format( "Tried to create an item of type {}, which is unhandled",
1098 magic_enum::enum_name( aType ) ) );
1099 return tl::unexpected( e );
1100 }
1101
1102 return created;
1103}
1104
1105
1107 const std::string& aClientName,
1108 const types::ItemHeader &aHeader,
1109 const google::protobuf::RepeatedPtrField<google::protobuf::Any>& aItems,
1110 std::function<void( ItemStatus, google::protobuf::Any )> aItemHandler )
1111{
1112 ApiResponseStatus e;
1113
1115
1116 if( !containerResult && containerResult.error().status() == ApiStatusCode::AS_UNHANDLED )
1117 {
1118 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
1119 e.set_status( ApiStatusCode::AS_UNHANDLED );
1120 return tl::unexpected( e );
1121 }
1122 else if( !containerResult )
1123 {
1124 e.CopyFrom( containerResult.error() );
1125 return tl::unexpected( e );
1126 }
1127
1128 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
1129 SCH_SCREEN* targetScreen = schematic()->GetCurrentScreen();
1130 SCH_SHEET_PATH targetPath = m_context->GetCurrentSheet().value_or( *hierarchy.begin() );
1131
1132 if( aHeader.document().has_sheet_path() )
1133 {
1134 KIID_PATH kp = UnpackSheetPath( aHeader.document().sheet_path() );
1135
1136 if( std::optional<SCH_SHEET_PATH> path = hierarchy.GetSheetPathByKIIDPath( kp ) )
1137 {
1138 targetPath = *path;
1139 targetScreen = targetPath.LastScreen();
1140 }
1141 }
1142
1143 SCH_COMMIT* commit = static_cast<SCH_COMMIT*>( getCurrentCommit( aClientName ) );
1144 int commitCheckpoint = commit->Checkpoint();
1145 bool connectivityChanged = false; // an in-place symbol update invalidated the net graph
1146 EDA_ITEM* container = targetScreen;
1147
1148 if( containerResult->has_value() )
1149 {
1150 SCH_ITEM* containerItem = hierarchy.ResolveItem( **containerResult, nullptr, true );
1151
1152 if( !containerItem )
1153 {
1154 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1155 e.set_error_message( "the requested container does not exist in this document" );
1156 return tl::unexpected( e );
1157 }
1158
1159 container = containerItem;
1160 }
1161
1162 for( const google::protobuf::Any& anyItem : aItems )
1163 {
1164 ItemStatus status;
1165 std::optional<KICAD_T> type = TypeNameFromAny( anyItem );
1166
1167 if( !type )
1168 {
1169 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
1170 status.set_error_message( fmt::format( "Could not decode a valid type from {}", anyItem.type_url() ) );
1171 aItemHandler( status, anyItem );
1172 continue;
1173 }
1174
1175 HANDLER_RESULT<std::unique_ptr<EDA_ITEM>> creationResult = createItemForType( *type, container );
1176
1177 if( !creationResult )
1178 {
1179 status.set_code( ItemStatusCode::ISC_INVALID_TYPE );
1180 status.set_error_message( creationResult.error().error_message() );
1181 aItemHandler( status, anyItem );
1182 continue;
1183 }
1184
1185 std::unique_ptr<EDA_ITEM> item( std::move( *creationResult ) );
1186
1187 bool unpacked = false;
1188
1189 // Retained past the unpack: the placement data they carry is applied once the item is
1190 // in the schematic.
1191 kiapi::schematic::types::SchematicSymbolInstance symbolProto;
1192 kiapi::schematic::types::SheetSymbol sheetProto;
1193
1194 if( *type == SCH_SYMBOL_T )
1195 {
1196 unpacked = anyItem.UnpackTo( &symbolProto )
1197 && UnpackSymbol( static_cast<SCH_SYMBOL*>( item.get() ), symbolProto );
1198 }
1199 else if( *type == SCH_SHEET_T )
1200 {
1201 unpacked = anyItem.UnpackTo( &sheetProto );
1202
1203 if( unpacked )
1204 {
1205 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item.get() );
1206
1207 if( tl::expected<bool, ApiResponseStatus> result = UnpackSheet( sheet, sheetProto );
1208 result.has_value() )
1209 {
1210 unpacked = *result;
1211 }
1212 else
1213 {
1214 return tl::unexpected( result.error() );
1215 }
1216 }
1217 }
1218 else if( SCH_GROUP* group = dynamic_cast<SCH_GROUP*>( item.get() ) )
1219 {
1220 unpacked = group->DeserializeGroup( anyItem, commit );
1221 }
1222 else
1223 {
1224 unpacked = item->Deserialize( anyItem );
1225 }
1226
1227 if( !unpacked )
1228 {
1229 commit->RevertToCheckpoint( commitCheckpoint );
1230
1231 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1232 e.set_error_message( fmt::format( "could not unpack {} from request", item->GetClass().ToStdString() ) );
1233 return tl::unexpected( e );
1234 }
1235
1236 if( std::vector<wxString> removed = item->RemoveConflictingCustomProperties(); !removed.empty() )
1237 {
1238 auto as_str =
1239 []( const wxString& aIn )
1240 {
1241 return std::string( aIn.ToUTF8() );
1242 };
1243
1244 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1245 status.set_error_message( fmt::format( "Invalid custom properties for item {}: property name(s) '{}' "
1246 "already in use",
1247 item->m_Uuid.AsStdString(),
1248 fmt::join( std::views::transform( removed, as_str ), ", " ) ) );
1249
1250 aItemHandler( status, anyItem );
1251 continue;
1252 }
1253
1254 SCH_ITEM* existingItem = nullptr;
1255 SCH_SHEET_PATH existingPath;
1256
1257 existingItem = targetPath.ResolveItem( item->m_Uuid );
1258
1259 if( existingItem )
1260 existingPath = targetPath;
1261
1262 if( aCreate && existingItem )
1263 {
1264 status.set_code( ItemStatusCode::ISC_EXISTING );
1265 status.set_error_message( fmt::format( "an item with UUID {} already exists",
1266 item->m_Uuid.AsStdString() ) );
1267 aItemHandler( status, anyItem );
1268 continue;
1269 }
1270 else if( !aCreate && !existingItem )
1271 {
1272 status.set_code( ItemStatusCode::ISC_NONEXISTENT );
1273 status.set_error_message( fmt::format( "an item with UUID {} does not exist",
1274 item->m_Uuid.AsStdString() ) );
1275 aItemHandler( status, anyItem );
1276 continue;
1277 }
1278
1279 if( !aCreate && existingItem->Type() != *type )
1280 {
1281 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1282 status.set_error_message( fmt::format( "item {} is not of the requested type",
1283 item->m_Uuid.AsStdString() ) );
1284 aItemHandler( status, anyItem );
1285 continue;
1286 }
1287
1288 if( !aCreate )
1289 {
1290 SCH_SCREEN* itemScreen = existingPath.LastScreen();
1291
1292 if( itemScreen != targetScreen )
1293 {
1294 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1295 status.set_error_message( fmt::format( "item {} exists on a different sheet than targeted",
1296 item->m_Uuid.AsStdString() ) );
1297 aItemHandler( status, anyItem );
1298 continue;
1299 }
1300 }
1301
1302 if( *type == SCH_SYMBOL_T )
1303 {
1304 if( symbolProto.has_definition() )
1305 {
1306 if( symbolProto.definition().id().entry_name().empty() )
1307 {
1308 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1309 status.set_error_message( "symbol definition is missing an entry name" );
1310 aItemHandler( status, anyItem );
1311 continue;
1312 }
1313 }
1314 else if( aCreate )
1315 {
1316 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1317 status.set_error_message( "a symbol definition is required" );
1318 aItemHandler( status, anyItem );
1319 continue;
1320 }
1321 else
1322 {
1323 // Definition-less update: re-point the temporary at the existing symbol's
1324 // library identity so the swap below keeps the library link
1325 SCH_SYMBOL* existingSymbol = static_cast<SCH_SYMBOL*>( existingItem );
1326 SCH_SYMBOL* tempSymbol = static_cast<SCH_SYMBOL*>( item.get() );
1327
1328 tempSymbol->SetLibId( existingSymbol->GetLibId() );
1329 tempSymbol->SetSchSymbolLibraryName( existingSymbol->UseLibIdLookup()
1330 ? wxString( wxEmptyString )
1331 : existingSymbol->GetSchSymbolLibraryName() );
1332
1333 for( const std::unique_ptr<SCH_PIN>& pin : existingSymbol->GetRawPins() )
1334 {
1335 tempSymbol->GetRawPins().emplace_back( std::make_unique<SCH_PIN>( *pin ) );
1336 tempSymbol->GetRawPins().back()->SetParent( tempSymbol );
1337 }
1338
1339 if( existingSymbol->GetLibSymbolRef() )
1340 tempSymbol->SetLibSymbol( new LIB_SYMBOL( *existingSymbol->GetLibSymbolRef() ) );
1341 }
1342 }
1343
1344 if( *type == SCH_SHEET_T )
1345 {
1346 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( item.get() );
1347
1348 if( aCreate && !sheet->GetScreen() )
1349 {
1350 sheet->SetScreen( new SCH_SCREEN( schematic() ) );
1351
1352 if( wxString rawFilename = sheet->GetFileName(); !rawFilename.IsEmpty() )
1353 {
1354 wxFileName fileName( ExpandTextVars( rawFilename, &schematic()->Project(), INTERNAL ) );
1355
1356 if( !fileName.Normalize( FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS,
1357 targetPath.LastScreen()->GetFileName() ) )
1358 {
1359 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1360 status.set_error_message( fmt::format( "could not resolve sheet filename '{}' for new sheet",
1361 rawFilename.ToStdString() ) );
1362 aItemHandler( status, anyItem );
1363 continue;
1364 }
1365
1366 sheet->GetScreen()->SetFileName( fileName.GetFullPath() );
1367 sheet->GetScreen()->SetContentModified();
1368 }
1369 }
1370
1371 SCH_SHEET_PATH parentPath;
1372
1373 if( aCreate )
1374 parentPath = targetPath;
1375 else
1376 parentPath = existingPath;
1377
1378 wxString destFilePath = parentPath.LastScreen()->GetFileName();
1379
1380 if( !destFilePath.IsEmpty() )
1381 {
1382 SCH_SHEET_LIST schematicSheets = schematic()->Hierarchy();
1383 SCH_SHEET_LIST loadedSheets( sheet );
1384
1385 if( schematicSheets.TestForRecursion( loadedSheets, destFilePath ) )
1386 {
1387 status.set_code( ItemStatusCode::ISC_INVALID_DATA );
1388 status.set_error_message( "sheet update would create recursive hierarchy" );
1389 aItemHandler( status, anyItem );
1390 continue;
1391 }
1392 }
1393 }
1394
1395 status.set_code( ItemStatusCode::ISC_OK );
1396 google::protobuf::Any newItem;
1397
1398 if( aCreate && !item )
1399 {
1400 commit->RevertToCheckpoint( commitCheckpoint );
1401
1402 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1403 e.set_error_message( "could not add the requested item to its parent container" );
1404 return tl::unexpected( e );
1405 }
1406
1407 if( item->Type() == SCH_GROUP_T )
1408 static_cast<SCH_GROUP*>( item.get() )->FinalizeGroupDeserialization();
1409
1410 if( aCreate )
1411 {
1412 if( item->Type() == SCH_SYMBOL_T )
1413 {
1414 for( const std::unique_ptr<SCH_PIN>& pin : static_cast<SCH_SYMBOL*>( item.get() )->GetRawPins() )
1415 const_cast<KIID&>( pin->m_Uuid ) = KIID();
1416 }
1417 else if( item->Type() == SCH_SHEET_T )
1418 {
1419 for( SCH_SHEET_PIN* pin : static_cast<SCH_SHEET*>( item.get() )->GetPins() )
1420 const_cast<KIID&>( pin->m_Uuid ) = KIID();
1421 }
1422 }
1423
1424 if( aCreate )
1425 {
1426 SCH_ITEM* createdItem = static_cast<SCH_ITEM*>( item.release() );
1427 commit->Add( createdItem, targetScreen );
1428
1429 if( createdItem->Type() == SCH_SYMBOL_T )
1430 {
1431 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( createdItem );
1432 kiapi::schematic::types::SchematicSymbolInstance packed;
1433
1434 if( !ApplySymbolInstance( &status, symbol, symbolProto, targetPath, schematic() ) )
1435 {
1436 aItemHandler( status, anyItem );
1437 continue;
1438 }
1439
1440 if( PackSymbol( &packed, symbol, targetPath ) )
1441 newItem.PackFrom( packed );
1442 }
1443 else if( createdItem->Type() == SCH_SHEET_T )
1444 {
1445 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( createdItem );
1446 kiapi::schematic::types::SheetSymbol packed;
1447
1448 if( sheetProto.page_number().empty() )
1449 sheetProto.set_page_number( hierarchy.GetNextPageNumber().ToUTF8() );
1450
1451 if( !ApplySheetInstance( &status, sheet, sheetProto, targetPath, schematic() ) )
1452 {
1453 aItemHandler( status, anyItem );
1454 continue;
1455 }
1456
1457 if( PackSheet( &packed, sheet, targetPath ) )
1458 newItem.PackFrom( packed );
1459 }
1460 else
1461 {
1462 createdItem->Serialize( newItem );
1463 }
1464 }
1465 else
1466 {
1467 // SwapItemData hands the item the temporary's (empty) instance list, so keep the
1468 // placements to restore afterwards.
1469 std::vector<SCH_SYMBOL_INSTANCE> symbolPlacements;
1470 std::vector<SCH_SHEET_INSTANCE> sheetPlacements;
1471
1472 if( existingItem->Type() == SCH_SYMBOL_T )
1473 symbolPlacements = static_cast<SCH_SYMBOL*>( existingItem )->GetInstances();
1474 else if( existingItem->Type() == SCH_SHEET_T )
1475 sheetPlacements = static_cast<SCH_SHEET*>( existingItem )->GetInstances();
1476
1477 commit->Modify( existingItem, targetScreen );
1478 existingItem->SwapItemData( static_cast<SCH_ITEM*>( item.get() ) );
1479
1480 if( existingItem->IsConnectable() )
1481 {
1482 existingItem->SetConnectivityDirty();
1483 connectivityChanged = true;
1484 }
1485
1486 if( existingItem->Type() == SCH_SYMBOL_T )
1487 {
1488 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( existingItem );
1489 kiapi::schematic::types::SchematicSymbolInstance packed;
1490
1491 for( const SCH_SYMBOL_INSTANCE& placement : symbolPlacements )
1492 symbol->AddHierarchicalReference( placement );
1493
1494 if( !ApplySymbolInstance( &status, symbol, symbolProto, existingPath, schematic() ) )
1495 {
1496 aItemHandler( status, anyItem );
1497 continue;
1498 }
1499
1500 if( PackSymbol( &packed, symbol, existingPath ) )
1501 newItem.PackFrom( packed );
1502 }
1503 else if( existingItem->Type() == SCH_SHEET_T )
1504 {
1505 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( existingItem );
1506 kiapi::schematic::types::SheetSymbol packed;
1507
1508 for( const SCH_SHEET_INSTANCE& placement : sheetPlacements )
1509 sheet->AddInstance( placement );
1510
1511 if( !ApplySheetInstance( &status, sheet, sheetProto, existingPath, schematic() ) )
1512 {
1513 aItemHandler( status, anyItem );
1514 continue;
1515 }
1516
1517 if( PackSheet( &packed, sheet, existingPath ) )
1518 newItem.PackFrom( packed );
1519 }
1520 else
1521 {
1522 existingItem->Serialize( newItem );
1523 }
1524 }
1525
1526 aItemHandler( status, newItem );
1527 }
1528
1529 if( !m_activeClients.contains( aClientName ) )
1530 {
1531 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
1532 : _( "Modified items via API" ) );
1533 }
1534
1535 if( m_frame && connectivityChanged )
1537
1538 return ItemRequestStatus::IRS_OK;
1539}
1540
1541
1542void API_HANDLER_SCH::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
1543 const std::string& aClientName )
1544{
1545 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
1546 COMMIT* commit = getCurrentCommit( aClientName );
1547
1548 for( auto& [id, status] : aItemsToDelete )
1549 {
1551 SCH_ITEM* item = hierarchy.ResolveItem( id, &path, true );
1552
1553 if( !item )
1554 continue;
1555
1556 if( !s_allowedTypes.contains( item->Type() ) )
1557 {
1558 status = ItemDeletionStatus::IDS_IMMUTABLE;
1559 continue;
1560 }
1561
1562 commit->Remove( item, path.LastScreen() );
1563 status = ItemDeletionStatus::IDS_OK;
1564 }
1565
1566 if( !m_activeClients.contains( aClientName ) )
1567 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
1568}
1569
1570
1571std::optional<EDA_ITEM*> API_HANDLER_SCH::getItemFromDocument( const DocumentSpecifier& aDocument, const KIID& aId )
1572{
1573 if( !validateDocument( aDocument ) )
1574 return std::nullopt;
1575
1576 SCH_ITEM* item = schematic()->Hierarchy().ResolveItem( aId, nullptr, true );
1577
1578 if( !item)
1579 return std::nullopt;
1580
1581 return item;
1582}
1583
1584
1585SCH_SCREEN* API_HANDLER_SCH::resolveScreenFromDocument( const DocumentSpecifier& aDocument ) const
1586{
1587 if( aDocument.has_sheet_path() )
1588 {
1589 KIID_PATH path = UnpackSheetPath( aDocument.sheet_path() );
1590
1591 if( std::optional<SCH_SHEET_PATH> sheetPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( path ) )
1592 return sheetPath->LastScreen();
1593
1594 return nullptr;
1595 }
1596
1597 if( std::optional<SCH_SHEET_PATH> current = m_context->GetCurrentSheet() )
1598 return current->LastScreen();
1599
1600 // Headless mode has no current sheet; the first top-level sheet is the implicit target.
1601 return schematic()->RootScreen();
1602}
1603
1604
1605std::optional<TITLE_BLOCK*> API_HANDLER_SCH::getTitleBlock( const DocumentSpecifier& aDocument )
1606{
1607 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1608 return &screen->GetTitleBlock();
1609
1610 return std::nullopt;
1611}
1612
1613
1614std::optional<PAGE_INFO> API_HANDLER_SCH::getPageSettings( const DocumentSpecifier& aDocument )
1615{
1616 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1617 return screen->GetPageSettings();
1618
1619 return std::nullopt;
1620}
1621
1622
1623bool API_HANDLER_SCH::setPageSettings( const DocumentSpecifier& aDocument, const PAGE_INFO& aPageInfo )
1624{
1625 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1626 {
1627 screen->SetPageSettings( aPageInfo );
1628 screen->SetContentModified();
1629 return true;
1630 }
1631
1632 return false;
1633}
1634
1635
1640
1641
1642void API_HANDLER_SCH::setDrawingSheetFileName( const wxString& aFileName )
1643{
1646
1647 if( m_frame )
1649}
1650
1651
1653{
1654 if( m_frame )
1655 {
1656 frame()->Refresh();
1657 frame()->OnModify();
1658 }
1659 else if( schematic()->GetCurrentScreen() )
1660 {
1662 }
1663}
1664
1665
1667{
1668 if( m_frame )
1669 frame()->Refresh();
1670}
1671
1672
1673static std::optional<ApiResponseStatus>
1674applySchematicPlotSettings( const schematic::jobs::SchematicPlotSettings& aSettings,
1675 const types::DocumentSpecifier& aDocument, JOB_EXPORT_SCH_PLOT& aJob )
1676{
1677 aJob.m_drawingSheet = wxString::FromUTF8( aSettings.drawing_sheet() );
1678 aJob.m_defaultFont = wxString::FromUTF8( aSettings.default_font() );
1679 aJob.m_variant = wxString::FromUTF8( aSettings.variant() );
1680 aJob.m_plotAll = aSettings.plot_all();
1681 aJob.m_plotDrawingSheet = aSettings.plot_drawing_sheet();
1682 aJob.m_show_hop_over = aSettings.show_hop_over();
1683 aJob.m_blackAndWhite = aSettings.black_and_white();
1684 aJob.m_useBackgroundColor = aSettings.use_background_color();
1685 aJob.m_minPenWidth = aSettings.min_pen_width();
1686 aJob.m_theme = wxString::FromUTF8( aSettings.theme() );
1687
1688 aJob.m_plotPages.clear();
1689
1690 for( const std::string& page : aSettings.plot_pages() )
1691 aJob.m_plotPages.push_back( wxString::FromUTF8( page ) );
1692
1693 if( aSettings.page_size() != schematic::jobs::SchematicJobPageSize::SJPS_UNKNOWN )
1694 aJob.m_pageSizeSelect = FromProtoEnum<JOB_PAGE_SIZE>( aSettings.page_size() );
1695
1696 switch( aSettings.sheet_mode() )
1697 {
1698 case schematic::jobs::SJSM_ALL_SHEETS: aJob.m_plotAll = true; break;
1699 case schematic::jobs::SJSM_SINGLE_SHEET: aJob.m_plotAll = false; break;
1700 case schematic::jobs::SJSM_UNKNOWN:
1701 default: aJob.m_plotAll = false; break;
1702 }
1703
1704 if( aDocument.has_sheet_path() )
1705 aJob.m_sheetPath = UnpackSheetPath( aDocument.sheet_path() ).AsString();
1706
1707 return std::nullopt;
1708}
1709
1710
1713{
1714 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1715
1716 if( !documentValidation )
1717 return tl::unexpected( documentValidation.error() );
1718
1719 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1720 return tl::unexpected( *busy );
1721
1722 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_SVG>( aCtx.Request.plot_settings().sheet_mode()
1723 != schematic::jobs::SJSM_SINGLE_SHEET );
1724 plotJob->m_filename = m_context->GetCurrentFileName();
1725
1726 if( !aCtx.Request.job_settings().output_path().empty() )
1727 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1728
1729 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1730 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1731 {
1732 return tl::unexpected( *err );
1733 }
1734
1735 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1736}
1737
1738
1741{
1742 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1743
1744 if( !documentValidation )
1745 return tl::unexpected( documentValidation.error() );
1746
1747 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1748 return tl::unexpected( *busy );
1749
1750 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_DXF>( aCtx.Request.plot_settings().sheet_mode()
1751 != schematic::jobs::SJSM_SINGLE_SHEET );
1752
1753 plotJob->m_filename = m_context->GetCurrentFileName();
1754
1755 if( !aCtx.Request.job_settings().output_path().empty() )
1756 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1757
1758 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1759 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1760 {
1761 return tl::unexpected( *err );
1762 }
1763
1764 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1765}
1766
1767
1770{
1771 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1772
1773 if( !documentValidation )
1774 return tl::unexpected( documentValidation.error() );
1775
1776 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1777 return tl::unexpected( *busy );
1778
1779 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PDF>( false );
1780 plotJob->m_filename = m_context->GetCurrentFileName();
1781
1782 if( !aCtx.Request.job_settings().output_path().empty() )
1783 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1784
1785 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1786 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1787 {
1788 return tl::unexpected( *err );
1789 }
1790
1791 plotJob->m_PDFPropertyPopups = aCtx.Request.property_popups();
1792 plotJob->m_PDFHierarchicalLinks = aCtx.Request.hierarchical_links();
1793 plotJob->m_PDFMetadata = aCtx.Request.include_metadata();
1794
1795 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1796}
1797
1798
1801{
1802 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1803
1804 if( !documentValidation )
1805 return tl::unexpected( documentValidation.error() );
1806
1807 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1808 return tl::unexpected( *busy );
1809
1810 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PS>( aCtx.Request.plot_settings().sheet_mode()
1811 != schematic::jobs::SJSM_SINGLE_SHEET );
1812 plotJob->m_filename = m_context->GetCurrentFileName();
1813
1814 if( !aCtx.Request.job_settings().output_path().empty() )
1815 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1816
1817 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1818 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1819 {
1820 return tl::unexpected( *err );
1821 }
1822
1823 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1824}
1825
1826
1829{
1830 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
1831 return tl::unexpected( validation.error() );
1832
1833 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1834 return tl::unexpected( *busy );
1835
1836 const schematic::jobs::SchematicJobSheetMode sheetMode = aCtx.Request.plot_settings().sheet_mode();
1837
1838 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PNG>( sheetMode != schematic::jobs::SJSM_SINGLE_SHEET );
1839 plotJob->m_filename = m_context->GetCurrentFileName();
1840
1841 if( !aCtx.Request.job_settings().output_path().empty() )
1842 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1843
1844 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1845 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1846 {
1847 return tl::unexpected( *err );
1848 }
1849
1850 if( aCtx.Request.has_dpi() )
1851 {
1852 int dpi = aCtx.Request.dpi();
1853
1854 if( dpi < MIN_PNG_DPI || dpi > MAX_PNG_DPI )
1855 {
1856 ApiResponseStatus status;
1857 status.set_status( ApiStatusCode::AS_BAD_REQUEST );
1858 status.set_error_message( fmt::format( "dpi must be between {} and {}", MIN_PNG_DPI, MAX_PNG_DPI ) );
1859 return tl::unexpected( status );
1860 }
1861
1862 plotJob->m_dpi = dpi;
1863 }
1864
1865 // Unknown -> default AA on
1866 plotJob->m_antialias = aCtx.Request.antialiasing() != types::AntialiasingMode::AAM_NONE;
1867 plotJob->m_useBackgroundColor = aCtx.Request.plot_background_color();
1868
1869 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1870}
1871
1872
1875{
1876 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1877
1878 if( !documentValidation )
1879 return tl::unexpected( documentValidation.error() );
1880
1881 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1882 return tl::unexpected( *busy );
1883
1884 if( aCtx.Request.format() == kiapi::schematic::jobs::SchematicNetlistFormat::SNF_UNKNOWN )
1885 {
1886 ApiResponseStatus e;
1887 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1888 e.set_error_message( "RunSchematicJobExportNetlist requires a valid format" );
1889 return tl::unexpected( e );
1890 }
1891
1892 JOB_EXPORT_SCH_NETLIST netlistJob;
1893 netlistJob.m_filename = m_context->GetCurrentFileName();
1894
1895 if( !aCtx.Request.job_settings().output_path().empty() )
1896 netlistJob.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1897
1898 netlistJob.format = FromProtoEnum<JOB_EXPORT_SCH_NETLIST::FORMAT>( aCtx.Request.format() );
1899
1900 if( !aCtx.Request.variant_name().empty() )
1901 netlistJob.m_variantNames.emplace_back( wxString::FromUTF8( aCtx.Request.variant_name() ) );
1902
1903 return ExecuteSchematicJob( m_context->GetKiway(), netlistJob );
1904}
1905
1906
1909{
1910 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1911
1912 if( !documentValidation )
1913 return tl::unexpected( documentValidation.error() );
1914
1915 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1916 return tl::unexpected( *busy );
1917
1918 JOB_EXPORT_BOM bomJob;
1919 bomJob.m_filename = m_context->GetCurrentFileName();
1920
1921 if( !aCtx.Request.job_settings().output_path().empty() )
1922 bomJob.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1923
1924 bomJob.m_bomFmtPresetName = wxString::FromUTF8( aCtx.Request.format().preset_name() );
1925 bomJob.m_fieldDelimiter = wxString::FromUTF8( aCtx.Request.format().field_delimiter() );
1926 bomJob.m_stringDelimiter = wxString::FromUTF8( aCtx.Request.format().string_delimiter() );
1927 bomJob.m_refDelimiter = wxString::FromUTF8( aCtx.Request.format().ref_delimiter() );
1928 bomJob.m_refRangeDelimiter = wxString::FromUTF8( aCtx.Request.format().ref_range_delimiter() );
1929 bomJob.m_keepTabs = aCtx.Request.format().keep_tabs();
1930 bomJob.m_keepLineBreaks = aCtx.Request.format().keep_line_breaks();
1931 bomJob.m_includeByteOrderMark = aCtx.Request.format().include_byte_order_mark();
1932
1933 bomJob.m_bomPresetName = wxString::FromUTF8( aCtx.Request.fields().preset_name() );
1934 bomJob.m_sortField = wxString::FromUTF8( aCtx.Request.fields().sort_field() );
1935 bomJob.m_filterString = wxString::FromUTF8( aCtx.Request.fields().filter() );
1936
1937 if( bomJob.m_bomPresetName.IsEmpty() && aCtx.Request.fields().fields().empty() )
1938 {
1939 ApiResponseStatus e;
1940 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1941 e.set_error_message( "A list of fields fields or a fields preset are required" );
1942 return tl::unexpected( e );
1943 }
1944
1945 switch( aCtx.Request.fields().filter_scope() )
1946 {
1947 case kiapi::schematic::jobs::BOMFilterScope::BFS_VISIBLE:
1949 break;
1950
1951 case kiapi::schematic::jobs::BOMFilterScope::BFS_ALL:
1953 break;
1954
1955 case kiapi::schematic::jobs::BOMFilterScope::BFS_REFERENCE:
1956 default:
1958 break;
1959 }
1960
1961 if( aCtx.Request.fields().sort_direction() == kiapi::schematic::jobs::BOMSortDirection::BSD_ASCENDING )
1962 {
1963 bomJob.m_sortAsc = true;
1964 }
1965 else if( aCtx.Request.fields().sort_direction() == kiapi::schematic::jobs::BOMSortDirection::BSD_DESCENDING )
1966 {
1967 bomJob.m_sortAsc = false;
1968 }
1969
1970 for( const kiapi::schematic::jobs::BOMField& field : aCtx.Request.fields().fields() )
1971 {
1972 bomJob.m_fieldsOrdered.emplace_back( wxString::FromUTF8( field.name() ) );
1973 bomJob.m_fieldsLabels.emplace_back( wxString::FromUTF8( field.label() ) );
1974
1975 if( field.group_by() )
1976 bomJob.m_fieldsGroupBy.emplace_back( wxString::FromUTF8( field.name() ) );
1977 }
1978
1979 bomJob.m_excludeDNP = aCtx.Request.exclude_dnp();
1980 bomJob.m_groupSymbols = aCtx.Request.group_symbols();
1981
1982 if( !aCtx.Request.variant_name().empty() )
1983 bomJob.m_variantNames.emplace_back( wxString::FromUTF8( aCtx.Request.variant_name() ) );
1984
1985 return ExecuteSchematicJob( m_context->GetKiway(), bomJob );
1986}
1987
1988
1989void API_HANDLER_SCH::packSheetInstance( kiapi::schematic::types::SheetInstance* aInstance, SCH_SHEET_PATH& aPath,
1990 SCH_SHEET* aSheet )
1991{
1992 aPath.push_back( aSheet );
1993
1994 PackSheetPath( *aInstance->mutable_path(), aPath );
1995
1996 wxString sheetName = aSheet->GetShownName( INTERNAL );
1997
1998 if( sheetName.IsEmpty() && aSheet->GetScreen() )
1999 {
2000 wxFileName fn( aSheet->GetScreen()->GetFileName() );
2001 sheetName = fn.GetName();
2002 }
2003
2004 aInstance->set_name( sheetName.ToUTF8() );
2005 aInstance->set_filename( aSheet->GetFileName().ToUTF8() );
2006 aInstance->set_page_number( aPath.GetPageNumber().ToUTF8() );
2007
2008 if( aSheet->GetScreen() )
2009 {
2010 std::vector<SCH_ITEM*> childSheets;
2011 aSheet->GetScreen()->GetSheets( &childSheets );
2012
2013 std::ranges::sort( childSheets,
2014 [&]( SCH_ITEM* a, SCH_ITEM* b )
2015 {
2016 SCH_SHEET_PATH pathA = aPath;
2017 pathA.push_back( static_cast<SCH_SHEET*>( a ) );
2018
2019 SCH_SHEET_PATH pathB = aPath;
2020 pathB.push_back( static_cast<SCH_SHEET*>( b ) );
2021
2022 return pathA.ComparePageNum( pathB ) < 0;
2023 } );
2024
2025 for( SCH_ITEM* childItem : childSheets )
2026 {
2027 SCH_SHEET* childSheet = static_cast<SCH_SHEET*>( childItem );
2028 kiapi::schematic::types::SheetInstance* childInstance = aInstance->add_children();
2029 packSheetInstance( childInstance, aPath, childSheet );
2030 }
2031 }
2032
2033 aPath.pop_back();
2034}
2035
2036
2039{
2040 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2041
2042 if( !documentValidation )
2043 return tl::unexpected( documentValidation.error() );
2044
2045 kiapi::schematic::commands::SchematicHierarchyResponse response;
2046 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2047
2048 if( !schematic()->HasHierarchy() )
2050
2052 std::vector<SCH_SHEET*> topLevelSheets = schematic()->GetTopLevelSheets();
2053
2054 std::ranges::sort( topLevelSheets,
2055 [&]( SCH_SHEET* a, SCH_SHEET* b )
2056 {
2057 SCH_SHEET_PATH pathA;
2058 pathA.push_back( a );
2059
2060 SCH_SHEET_PATH pathB;
2061 pathB.push_back( b );
2062
2063 return pathA.ComparePageNum( pathB ) < 0;
2064 } );
2065
2066 for( SCH_SHEET* topSheet : topLevelSheets )
2067 {
2068 kiapi::schematic::types::SheetInstance* instance = response.add_top_level_sheets();
2069 packSheetInstance( instance, path, topSheet );
2070 }
2071
2072 return response;
2073}
2074
2075
2078{
2079 const bool engine = ADVANCED_CFG::GetCfg().m_ConnectivityEngine;
2080
2081 // The engine rebuilds before answering, which an interactive tool must not observe
2082 if( engine && m_frame && !m_frame->ToolStackIsEmpty() )
2083 {
2084 ApiResponseStatus status;
2085 status.set_status( ApiStatusCode::AS_BUSY );
2086 status.set_error_message( "Cannot rebuild connectivity during an interactive operation" );
2087 return tl::unexpected( status );
2088 }
2089
2090 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2091
2092 if( !documentValidation )
2093 return tl::unexpected( documentValidation.error() );
2094
2095 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2096 return tl::unexpected( *busy );
2097
2098 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
2099 const bool filterByType = aCtx.Request.types_size() > 0;
2100
2101 static const std::set<KICAD_T> s_netlistTypes = {
2102 SCH_PIN_T,
2104 SCH_LINE_T,
2114 };
2115
2116 std::set<KICAD_T> typeFilter( types.begin(), types.end() );
2117 std::erase_if( typeFilter, []( KICAD_T aType ) { return !s_netlistTypes.contains( aType ); } );
2118
2119 if( filterByType && typeFilter.empty() )
2120 {
2121 ApiResponseStatus e;
2122 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2123 e.set_error_message( "none of the requested types are valid for a netlist object" );
2124 return tl::unexpected( e );
2125 }
2126
2127 CONNECTION_GRAPH* connectionGraph = schematic()->ConnectionGraph();
2128
2129 if( !connectionGraph )
2130 {
2131 ApiResponseStatus e;
2132 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2133 e.set_error_message( "schematic has no connection graph" );
2134 return tl::unexpected( e );
2135 }
2136
2137 if( engine )
2138 {
2139 try
2140 {
2142 }
2143 catch( const std::exception& error )
2144 {
2145 if( m_frame )
2146 {
2147 m_frame->ShowInfoBarError( _( "Unable to rebuild schematic connectivity." ), true );
2148 frame()->RefreshConnectivity( true );
2149 }
2150
2151 wxLogTrace( traceApi, wxS( "GetSchematicNetlist rebuild failed: %s" ),
2152 wxString::FromUTF8( error.what() ) );
2153
2154 ApiResponseStatus status;
2155 status.set_status( ApiStatusCode::AS_UNKNOWN );
2156 status.set_error_message( error.what() );
2157 return tl::unexpected( status );
2158 }
2159 }
2160
2161 kiapi::schematic::commands::SchematicNetlistResponse response;
2162 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2163
2164 if( engine )
2165 {
2166 for( const auto& group : schematic()->Connectivity().GetNetMap() )
2167 {
2168 if( group.instances.empty() || !group.instances.front().IsNet()
2169 || !group.instances.front().Driver() )
2170 {
2171 continue;
2172 }
2173
2174 kiapi::schematic::types::SchematicNet* net = nullptr;
2175
2176 for( const auto& view : group.instances )
2177 {
2178 const auto physicalItems = view.Items();
2179
2180 if( physicalItems.empty() )
2181 continue;
2182
2183 if( !net )
2184 {
2185 net = response.add_nets();
2186 net->set_name( group.name.ToUTF8() );
2187 }
2188
2189 auto* sheet = net->add_sheets();
2190 PackSheetPath( *sheet->mutable_path(), view.Instance() );
2191
2192 for( SCH_ITEM* item : physicalItems )
2193 {
2194 if( !filterByType || typeFilter.contains( item->Type() ) )
2195 sheet->add_items()->set_value( item->m_Uuid.AsStdString() );
2196 }
2197 }
2198 }
2199
2200 return response;
2201 }
2202
2203 for( const auto& [key, subgraphList] : connectionGraph->GetNetMap() )
2204 {
2205 if( subgraphList.empty() )
2206 continue;
2207
2208 CONNECTION_SUBGRAPH* firstSubgraph = subgraphList[0];
2209
2210 if( firstSubgraph->GetDriverConnection() && firstSubgraph->GetDriverConnection()->IsBus() )
2211 continue;
2212
2214 continue;
2215
2216 kiapi::schematic::types::SchematicNet* net = response.add_nets();
2217 net->set_name( key.Name.ToUTF8() );
2218
2219 for( CONNECTION_SUBGRAPH* subGraph : subgraphList )
2220 {
2221 kiapi::schematic::types::SchematicNetSheetContents* sheetContents = net->add_sheets();
2222 PackSheetPath( *sheetContents->mutable_path(), subGraph->GetSheet() );
2223
2224 for( SCH_ITEM* item : subGraph->GetItems() )
2225 {
2226 if( filterByType && !typeFilter.contains( item->Type() ) )
2227 continue;
2228
2229 sheetContents->add_items()->set_value( item->m_Uuid.AsStdString() );
2230 }
2231 }
2232 }
2233
2234 return response;
2235}
2236
2237
2238// TODO(JE) factor out
2241{
2242 wxLogTrace( traceApi, "Received announce from frame %d at %s",
2243 aCtx.Request.frame_type(), aCtx.Request.socket_path() );
2244
2245 CROSS_PROBE_CLIENT::RegisterPeer( static_cast<FRAME_T>( aCtx.Request.frame_type() ),
2246 aCtx.Request.socket_path() );
2247
2248 CrossProbeAnnounceResponse response;
2249 response.set_status( CPS_OK );
2250 return response;
2251}
2252
2253
2254bool findSymbolsAndPins( const SCH_SHEET_LIST& aSchematicSheetList, const SCH_SHEET_PATH& aSheetPath,
2255 std::unordered_map<wxString, std::vector<SCH_REFERENCE>>& aSyncSymMap,
2256 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>>& aSyncPinMap,
2257 const wxString& aVariantName = wxEmptyString, bool aRecursive = false )
2258{
2259 if( aRecursive )
2260 {
2261 // Iterate over children
2262 for( const SCH_SHEET_PATH& candidate : aSchematicSheetList )
2263 {
2264 if( candidate == aSheetPath || !candidate.IsContainedWithin( aSheetPath ) )
2265 continue;
2266
2267 findSymbolsAndPins( aSchematicSheetList, candidate, aSyncSymMap, aSyncPinMap, aVariantName, aRecursive );
2268 }
2269 }
2270
2271 SCH_REFERENCE_LIST references;
2272
2273 aSheetPath.GetSymbols( references, SYMBOL_FILTER_NON_POWER, true );
2274
2275 for( unsigned ii = 0; ii < references.GetCount(); ii++ )
2276 {
2277 SCH_REFERENCE& schRef = references[ii];
2278
2279 if( schRef.IsSplitNeeded() )
2280 schRef.Split();
2281
2282 SCH_SYMBOL* symbol = schRef.GetSymbol();
2283 wxString refNum = schRef.GetRefNumber();
2284 wxString fullRef = schRef.GetRef() + refNum;
2285
2286 // Skip power symbols
2287 if( fullRef.StartsWith( wxS( "#" ) ) )
2288 continue;
2289
2290 // Unannotated symbols are not supported
2291 if( refNum.compare( wxS( "?" ) ) == 0 )
2292 continue;
2293
2294 // Look for whole footprint
2295 auto symMatchIt = aSyncSymMap.find( fullRef );
2296
2297 if( symMatchIt != aSyncSymMap.end() )
2298 {
2299 symMatchIt->second.emplace_back( schRef );
2300
2301 // Whole footprint was selected, no need to select pins
2302 continue;
2303 }
2304
2305 // Look for pins
2306 auto symPinMatchIt = aSyncPinMap.find( fullRef );
2307
2308 if( symPinMatchIt != aSyncPinMap.end() )
2309 {
2310 std::unordered_map<wxString, SCH_PIN*>& pinMap = symPinMatchIt->second;
2311 std::vector<SCH_PIN*> pinsOnSheet = symbol->GetPins( &aSheetPath );
2312
2313 for( SCH_PIN* pin : pinsOnSheet )
2314 {
2315 int pinUnit = pin->GetLibPin() ? pin->GetLibPin()->GetUnit() : ALL_UNITS;
2316
2317 if( pinUnit > 0 && pinUnit != schRef.GetUnit() )
2318 continue;
2319
2320 // Reverse-map the requested pad back to the owning pin (issue #2282). A pin may
2321 // resolve to several pads via the map; match the first that pcbnew asked for.
2322 for( const wxString& pad :
2323 ExpandStackedPinNotation( pin->GetEffectivePadNumber( aSheetPath, aVariantName ) ) )
2324 {
2325 auto pinIt = pinMap.find( pad );
2326
2327 if( pinIt != pinMap.end() )
2328 {
2329 pinIt->second = pin;
2330 break;
2331 }
2332 }
2333 }
2334 }
2335 }
2336
2337 return false;
2338}
2339
2340
2342 const SCH_SHEET_LIST& aSchematicSheetList, const SCH_SHEET_PATH& aSheetPath,
2343 std::unordered_map<wxString, std::vector<SCH_REFERENCE>>& aSyncSymMap,
2344 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>>& aSyncPinMap,
2345 std::unordered_map<SCH_SHEET_PATH, bool>& aCache )
2346{
2347 auto cacheIt = aCache.find( aSheetPath );
2348
2349 if( cacheIt != aCache.end() )
2350 return cacheIt->second;
2351
2352 // Iterate over children
2353 for( const SCH_SHEET_PATH& candidate : aSchematicSheetList )
2354 {
2355 if( candidate == aSheetPath || !candidate.IsContainedWithin( aSheetPath ) )
2356 continue;
2357
2358 bool childRet = sheetContainsOnlyWantedItems( aSchematicSheetList, candidate, aSyncSymMap,
2359 aSyncPinMap, aCache );
2360
2361 if( !childRet )
2362 {
2363 aCache.emplace( aSheetPath, false );
2364 return false;
2365 }
2366 }
2367
2368 SCH_REFERENCE_LIST references;
2369 aSheetPath.GetSymbols( references, SYMBOL_FILTER_NON_POWER, true );
2370
2371 if( references.GetCount() == 0 ) // Empty sheet, obviously do not contain wanted items
2372 {
2373 aCache.emplace( aSheetPath, false );
2374 return false;
2375 }
2376
2377 for( unsigned ii = 0; ii < references.GetCount(); ii++ )
2378 {
2379 SCH_REFERENCE& schRef = references[ii];
2380
2381 if( schRef.IsSplitNeeded() )
2382 schRef.Split();
2383
2384 wxString refNum = schRef.GetRefNumber();
2385 wxString fullRef = schRef.GetRef() + refNum;
2386
2387 // Skip power symbols
2388 if( fullRef.StartsWith( wxS( "#" ) ) )
2389 continue;
2390
2391 // Unannotated symbols are not supported
2392 if( refNum.compare( wxS( "?" ) ) == 0 )
2393 continue;
2394
2395 if( aSyncSymMap.find( fullRef ) == aSyncSymMap.end() )
2396 {
2397 aCache.emplace( aSheetPath, false );
2398 return false; // Some symbol is not wanted.
2399 }
2400
2401 if( aSyncPinMap.find( fullRef ) != aSyncPinMap.end() )
2402 {
2403 aCache.emplace( aSheetPath, false );
2404 return false; // Looking for specific pins, so can't be mapped
2405 }
2406 }
2407
2408 aCache.emplace( aSheetPath, true );
2409 return true;
2410}
2411
2412
2413std::optional<std::tuple<SCH_SHEET_PATH, SCH_ITEM*, std::vector<SCH_ITEM*>>>
2415 const kiapi::common::commands::SyncSelection& aSync )
2416{
2417 std::unordered_map<wxString, std::vector<SCH_REFERENCE>> syncSymMap;
2418 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>> syncPinMap;
2419 std::unordered_map<SCH_SHEET_PATH, bool> fullyWantedCache;
2420
2421 std::optional<wxString> focusSymbol;
2422 std::optional<std::pair<wxString, wxString>> focusPin;
2423 std::unordered_map<SCH_SHEET_PATH, std::vector<SCH_ITEM*>> focusItemResults;
2424
2425 const SCH_SHEET_LIST allSheetsList = aSchematic.Hierarchy();
2426
2427 // In orderedSheets, the current sheet comes first.
2428 std::vector<SCH_SHEET_PATH> orderedSheets;
2429 orderedSheets.reserve( allSheetsList.size() );
2430 orderedSheets.push_back( aSchematic.CurrentSheet() );
2431
2432 for( const SCH_SHEET_PATH& sheetPath : allSheetsList )
2433 {
2434 if( sheetPath != aSchematic.CurrentSheet() )
2435 orderedSheets.push_back( sheetPath );
2436 }
2437
2438 const bool focusOnFirst = ( aSync.mode() == kiapi::common::commands::SSM_ITEMS_AND_NETS ) && aSync.has_focus_item();
2439
2440 for( const kiapi::common::commands::SelectionSpec& spec : aSync.items() )
2441 {
2442 switch( spec.spec_case() )
2443 {
2444 case kiapi::common::commands::SelectionSpec::kFootprint:
2445 {
2446 wxString symRef = wxString::FromUTF8( spec.footprint().reference() );
2447 syncSymMap[symRef] = std::vector<SCH_REFERENCE>();
2448 break;
2449 }
2450
2451 case kiapi::common::commands::SelectionSpec::kPad:
2452 {
2453 wxString symRef = wxString::FromUTF8( spec.pad().reference() );
2454 wxString padNum = wxString::FromUTF8( spec.pad().number() );
2455 syncPinMap[symRef][padNum] = nullptr;
2456 break;
2457 }
2458
2459 default:
2460 break;
2461 }
2462 }
2463
2464 if( focusOnFirst )
2465 {
2466 const kiapi::common::commands::SelectionSpec& focusSpec = aSync.focus_item();
2467
2468 if( focusSpec.has_footprint() )
2469 focusSymbol = wxString::FromUTF8( focusSpec.footprint().reference() );
2470 else if( focusSpec.has_pad() )
2471 focusPin = std::make_pair( wxString::FromUTF8( focusSpec.pad().reference() ),
2472 wxString::FromUTF8( focusSpec.pad().number() ) );
2473 }
2474
2475 // Lambda definitions
2476 auto flattenSyncMaps =
2477 [&syncSymMap, &syncPinMap]() -> std::vector<SCH_ITEM*>
2478 {
2479 std::vector<SCH_ITEM*> allVec;
2480
2481 for( const auto& [symRef, symbols] : syncSymMap )
2482 {
2483 for( const SCH_REFERENCE& ref : symbols )
2484 allVec.push_back( ref.GetSymbol() );
2485 }
2486
2487 for( const auto& [symRef, pinMap] : syncPinMap )
2488 {
2489 for( const auto& [padNum, pin] : pinMap )
2490 {
2491 if( pin )
2492 allVec.push_back( pin );
2493 }
2494 }
2495
2496 return allVec;
2497 };
2498
2499 auto clearSyncMaps =
2500 [&syncSymMap, &syncPinMap]()
2501 {
2502 for( auto& [symRef, symbols] : syncSymMap )
2503 symbols.clear();
2504
2505 for( auto& [reference, pins] : syncPinMap )
2506 {
2507 for( auto& [number, pin] : pins )
2508 pin = nullptr;
2509 }
2510 };
2511
2512 auto syncMapsValuesEmpty =
2513 [&syncSymMap, &syncPinMap]() -> bool
2514 {
2515 for( const auto& [symRef, symbols] : syncSymMap )
2516 {
2517 if( symbols.size() > 0 )
2518 return false;
2519 }
2520
2521 for( const auto& [symRef, pins] : syncPinMap )
2522 {
2523 for( const auto& [padNum, pin] : pins )
2524 {
2525 if( pin )
2526 return false;
2527 }
2528 }
2529
2530 return true;
2531 };
2532
2533 auto checkFocusItems =
2534 [&]( const SCH_SHEET_PATH& aSheet )
2535 {
2536 if( focusSymbol )
2537 {
2538 auto findIt = syncSymMap.find( *focusSymbol );
2539
2540 if( findIt != syncSymMap.end() )
2541 {
2542 if( findIt->second.size() > 0 )
2543 focusItemResults[aSheet].push_back( findIt->second.front().GetSymbol() );
2544 }
2545 }
2546 else if( focusPin )
2547 {
2548 auto findIt = syncPinMap.find( focusPin->first );
2549
2550 if( findIt != syncPinMap.end() )
2551 {
2552 if( findIt->second[focusPin->second] )
2553 focusItemResults[aSheet].push_back( findIt->second[focusPin->second] );
2554 }
2555 }
2556 };
2557
2558 auto makeRetForSheet =
2559 [&]( const SCH_SHEET_PATH& aSheet, SCH_ITEM* aFocusItem )
2560 {
2561 clearSyncMaps();
2562
2563 // Fill sync maps
2564 findSymbolsAndPins( allSheetsList, aSheet, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2565 std::vector<SCH_ITEM*> itemsVector = flattenSyncMaps();
2566
2567 // Add fully wanted sheets to vector
2568 for( SCH_ITEM* item : aSheet.LastScreen()->Items().OfType( SCH_SHEET_T ) )
2569 {
2570 KIID_PATH kiidPath = aSheet.Path();
2571 kiidPath.push_back( item->m_Uuid );
2572
2573 std::optional<SCH_SHEET_PATH> subsheetPath =
2574 allSheetsList.GetSheetPathByKIIDPath( kiidPath );
2575
2576 if( !subsheetPath )
2577 continue;
2578
2579 if( sheetContainsOnlyWantedItems( allSheetsList, *subsheetPath, syncSymMap,
2580 syncPinMap, fullyWantedCache ) )
2581 {
2582 itemsVector.push_back( item );
2583 }
2584 }
2585
2586 return std::make_tuple( aSheet, aFocusItem, itemsVector );
2587 };
2588
2589 if( focusOnFirst )
2590 {
2591 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2592 {
2593 clearSyncMaps();
2594
2595 findSymbolsAndPins( allSheetsList, sheetPath, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2596
2597 checkFocusItems( sheetPath );
2598 }
2599
2600 if( focusItemResults.size() > 0 )
2601 {
2602 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2603 {
2604 const std::vector<SCH_ITEM*>& items = focusItemResults[sheetPath];
2605
2606 if( !items.empty() )
2607 return makeRetForSheet( sheetPath, items.front() );
2608 }
2609 }
2610 }
2611 else
2612 {
2613 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2614 {
2615 clearSyncMaps();
2616
2617 findSymbolsAndPins( allSheetsList, sheetPath, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2618
2619 if( !syncMapsValuesEmpty() )
2620 {
2621 // Something found on sheet
2622 return makeRetForSheet( sheetPath, nullptr );
2623 }
2624 }
2625 }
2626
2627 return std::nullopt;
2628}
2629
2630
2632 const HANDLER_CONTEXT<SyncSelection>& aCtx )
2633{
2634 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SyncSelection" ) )
2635 return tl::unexpected( *headless );
2636
2637 SyncSelectionResponse response;
2638
2639 const CROSS_PROBING_SETTINGS& settings = frame()->eeconfig()->m_CrossProbing;
2640
2641 if( !settings.on_selection && aCtx.Request.context() != SyncSelectionContext::SSC_EXPLICIT )
2642 {
2643 response.set_status( CPS_DISABLED );
2644 response.set_message( "implicit selection sync disabled by user" );
2645 return response;
2646 }
2647
2648 // A request carrying no items asks for nothing to be selected, so there is nothing to find.
2649 if( aCtx.Request.items_size() == 0 )
2650 {
2651 frame()->SetSyncingSelection( true ); // recursion guard
2652
2653 frame()->GetToolManager()->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( std::nullopt, nullptr, {} );
2654
2655 frame()->SetSyncingSelection( false );
2656
2657 response.set_status( CPS_OK );
2658 return response;
2659 }
2660
2661 std::optional<std::tuple<SCH_SHEET_PATH, SCH_ITEM*, std::vector<SCH_ITEM*>>> findRet =
2663
2664 if( findRet )
2665 {
2666 auto& [sheetPath, focusItem, items] = *findRet;
2667
2668 frame()->SetSyncingSelection( true ); // recursion guard
2669
2670 frame()->GetToolManager()->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( sheetPath, focusItem, items );
2671
2672 frame()->SetSyncingSelection( false );
2673
2674 if( frame()->eeconfig()->m_CrossProbing.flash_selection )
2675 {
2676 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): flash enabled, items=%zu",
2677 items.size() );
2678
2679 if( items.empty() )
2680 {
2681 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): nothing to flash" );
2682 }
2683 else
2684 {
2685 std::vector<SCH_ITEM*> itemPtrs;
2686 std::copy( items.begin(), items.end(), std::back_inserter( itemPtrs ) );
2687
2688 frame()->StartCrossProbeFlash( itemPtrs );
2689 }
2690 }
2691 else
2692 {
2693 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): flash disabled" );
2694 }
2695 }
2696
2697 response.set_status( CPS_OK );
2698 return response;
2699}
2700
2701
2703 const HANDLER_CONTEXT<HighlightNets>& aCtx )
2704{
2705 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "HighlightNets" ) )
2706 return tl::unexpected( *headless );
2707
2708 HighlightNetsResponse response;
2709 CROSS_PROBING_SETTINGS& crossProbingSettings = frame()->eeconfig()->m_CrossProbing;
2710
2712 || aCtx.ClientName == KiwayClientName )
2713 {
2714 if( !crossProbingSettings.auto_highlight )
2715 {
2716 response.set_status( CPS_DISABLED );
2717 return response;
2718 }
2719 }
2720
2721 wxString net;
2722
2723 if( aCtx.Request.net_name_size() > 0 )
2724 net = wxString::FromUTF8( aCtx.Request.net_name( 0 ) );
2725
2727
2728 response.set_status( CPS_OK );
2729 return response;
2730}
2731
2732
2733
2736{
2737 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2738
2739 if( !documentValidation )
2740 return tl::unexpected( documentValidation.error() );
2741
2742 SCH_SHEET_PATH path = m_context->GetCurrentSheet().value_or( *schematic()->Hierarchy().begin() );
2743
2744 if( aCtx.Request.document().has_sheet_path() )
2745 {
2746 KIID_PATH kiidPath = UnpackSheetPath( aCtx.Request.document().sheet_path() );
2747
2748 if( std::optional<SCH_SHEET_PATH> resolvedPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( kiidPath ) )
2749 {
2750 path = *resolvedPath;
2751 }
2752 }
2753
2754 ExpandTextVariablesResponse reply;
2755
2756 std::function<bool( wxString* )> textResolver =
2757 [&]( wxString* token ) -> bool
2758 {
2759 return schematic()->ResolveTextVar( &path, token, 0 );
2760 };
2761
2762 PROJECT& project = m_context->Prj();
2763
2764 for( const std::string& textMsg : aCtx.Request.text() )
2765 {
2766 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver, INTERNAL );
2767
2768 if( aCtx.Request.expand_env_vars() )
2770
2771 reply.add_text( text.ToUTF8() );
2772 }
2773
2774 return reply;
2775}
2776
2777
2779{
2780 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2781 return tl::unexpected( documentValidation.error() );
2782
2783 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2784 return tl::unexpected( *busy );
2785
2786 VariantsResponse response;
2787
2788 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2789
2790 for( const wxString& name : schematic()->GetVariantNames() )
2791 {
2792 types::DesignVariant* var = response.add_variants();
2793 var->set_name( name.ToUTF8() );
2794 var->set_description( schematic()->GetVariantDescription( name ).ToUTF8() );
2795 }
2796
2797 return response;
2798}
2799
2800
2802{
2803 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2804 return tl::unexpected( documentValidation.error() );
2805
2806 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2807 return tl::unexpected( *busy );
2808
2809 SCHEMATIC* schematic = this->schematic();
2810 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2811
2812 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || schematic->HasVariant( name ) )
2813 {
2814 ApiResponseStatus e;
2815 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2816 e.set_error_message( fmt::format( "'{}' is not a usable new variant name", aCtx.Request.name() ) );
2817 return tl::unexpected( e );
2818 }
2819
2821
2822 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
2823 name = *canonical;
2824
2825 schematic->AddVariant( name );
2826
2827 if( aCtx.Request.has_description() )
2828 schematic->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
2829
2830 onModified();
2831
2832 if( m_frame )
2833 {
2834 PICKED_ITEMS_LIST* undoCmd = new PICKED_ITEMS_LIST();
2835
2836 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
2837 undoCmd->SetDescription( _( "Add Variant" ) );
2838 frame()->PushCommandToUndoList( undoCmd );
2839
2840 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2841 }
2842
2843 return Empty();
2844}
2845
2846
2848{
2849 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2850 return tl::unexpected( documentValidation.error() );
2851
2852 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2853 return tl::unexpected( *busy );
2854
2855 SCHEMATIC* schematic = this->schematic();
2856 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2857
2858 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
2859 {
2860 ApiResponseStatus e;
2861 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2862 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
2863 return tl::unexpected( e );
2864 }
2865
2866 if( !schematic->HasVariant( name ) )
2867 {
2868 ApiResponseStatus e;
2869 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2870 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
2871 return tl::unexpected( e );
2872 }
2873
2875 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2876 bool pushedCommit = false;
2877
2878 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
2879 name = *canonical;
2880
2881 schematic->DeleteVariant( name, &commit );
2882
2883 if( !commit.Empty() )
2884 {
2885 commit.Push();
2886 pushedCommit = true;
2887 }
2888
2889 onModified();
2890
2891 if( m_frame )
2892 {
2893 PICKED_ITEMS_LIST* undoCmd = pushedCommit ? frame()->PopCommandFromUndoList()
2894 : new PICKED_ITEMS_LIST();
2895
2896 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
2897 undoCmd->SetDescription( _( "Delete Variant" ) );
2898 frame()->PushCommandToUndoList( undoCmd );
2899
2900 if( frame()->Schematic().GetCurrentVariant().CmpNoCase( name ) == 0 )
2901 frame()->SetCurrentVariant( wxEmptyString );
2902
2903 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2904 frame()->GetCanvas()->Refresh();
2905 }
2906
2907 return Empty();
2908}
2909
2910
2912{
2913 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2914 return tl::unexpected( documentValidation.error() );
2915
2916 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2917 return tl::unexpected( *busy );
2918
2919 SCHEMATIC* schematic = this->schematic();
2920 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
2921 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
2922
2923 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2924 {
2925 ApiResponseStatus e;
2926 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2927 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
2928 return tl::unexpected( e );
2929 }
2930
2931 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2932 {
2933 ApiResponseStatus e;
2934 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2935 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
2936 return tl::unexpected( e );
2937 }
2938
2939 if( !schematic->HasVariant( oldName ) )
2940 {
2941 ApiResponseStatus e;
2942 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2943 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
2944 return tl::unexpected( e );
2945 }
2946
2947 if( schematic->HasVariant( newName ) )
2948 {
2949 ApiResponseStatus e;
2950 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2951 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
2952 return tl::unexpected( e );
2953 }
2954
2955 bool isCurrent = frame() ? frame()->Schematic().GetCurrentVariant().CmpNoCase( oldName ) == 0 : false;
2956
2958 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2959 bool pushedCommit = false;
2960
2961 if( std::optional<wxString> canonicalOld = FindVariantNoCase( schematic, oldName ) )
2962 oldName = *canonicalOld;
2963
2964 schematic->RenameVariant( oldName, newName, &commit );
2965
2966 if( !commit.Empty() )
2967 {
2968 commit.Push();
2969 pushedCommit = true;
2970 }
2971
2972 onModified();
2973
2974 if( m_frame )
2975 {
2976 PICKED_ITEMS_LIST* undoCmd = pushedCommit ? frame()->PopCommandFromUndoList()
2977 : new PICKED_ITEMS_LIST();
2978
2979 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
2980 undoCmd->SetDescription( _( "Rename Variant" ) );
2981 frame()->PushCommandToUndoList( undoCmd );
2982
2983 // This will zero out the selection if it happened to point to the old name
2984 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2985
2986 if( isCurrent )
2987 {
2988 // This will reset the variant-selection-ctrl's selection, but only if the
2989 // names have already been updated to include the new name
2990 frame()->SetCurrentVariant( newName );
2991 }
2992 }
2993
2994 return Empty();
2995}
2996
2997
2999{
3000 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3001 return tl::unexpected( documentValidation.error() );
3002
3003 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3004 return tl::unexpected( *busy );
3005
3006 SCHEMATIC* schematic = this->schematic();
3007 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
3008 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
3009
3010 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3011 {
3012 ApiResponseStatus e;
3013 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3014 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
3015 return tl::unexpected( e );
3016 }
3017
3018 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3019 {
3020 ApiResponseStatus e;
3021 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3022 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
3023 return tl::unexpected( e );
3024 }
3025
3026 if( !schematic->HasVariant( oldName ) )
3027 {
3028 ApiResponseStatus e;
3029 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3030 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
3031 return tl::unexpected( e );
3032 }
3033
3034 if( schematic->HasVariant( newName ) )
3035 {
3036 ApiResponseStatus e;
3037 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3038 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
3039 return tl::unexpected( e );
3040 }
3041
3043 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
3044 bool pushedCommit = false;
3045
3046 if( std::optional<wxString> canonicalOld = FindVariantNoCase( schematic, oldName ) )
3047 oldName = *canonicalOld;
3048
3049 schematic->CopyVariant( oldName, newName, &commit );
3050
3051 if( aCtx.Request.has_new_description() )
3052 schematic->SetVariantDescription( newName, wxString::FromUTF8( aCtx.Request.new_description() ) );
3053
3054 if( !commit.Empty() )
3055 {
3056 commit.Push();
3057 pushedCommit = true;
3058 }
3059
3060 onModified();
3061
3062 if( m_frame )
3063 {
3064 PICKED_ITEMS_LIST* undoCmd = pushedCommit ? frame()->PopCommandFromUndoList()
3065 : new PICKED_ITEMS_LIST();
3066
3067 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
3068 undoCmd->SetDescription( _( "Copy Variant" ) );
3069 frame()->PushCommandToUndoList( undoCmd );
3070
3071 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
3072 }
3073
3074 return Empty();
3075}
3076
3077
3079{
3080 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3081 return tl::unexpected( documentValidation.error() );
3082
3083 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3084 return tl::unexpected( *busy );
3085
3086 SCHEMATIC* schematic = this->schematic();
3087 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3088
3089 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || !schematic->HasVariant( name ) )
3090 {
3091 ApiResponseStatus e;
3092 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3093 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3094 return tl::unexpected( e );
3095 }
3096
3098
3099 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
3100 name = *canonical;
3101
3102 schematic->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
3103
3104 if( m_frame )
3105 {
3106 PICKED_ITEMS_LIST* undoCmd = new PICKED_ITEMS_LIST();
3107
3108 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
3109 undoCmd->SetDescription( _( "Edit Variant Description" ) );
3110 frame()->PushCommandToUndoList( undoCmd );
3111 }
3112
3113 return Empty();
3114}
3115
3116
3118{
3119 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3120 return tl::unexpected( documentValidation.error() );
3121
3122 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3123 return tl::unexpected( *busy );
3124
3125 SCHEMATIC* schematic = this->schematic();
3126
3127 if( aCtx.Request.has_name() && !aCtx.Request.name().empty() )
3128 {
3129 if( wxString name = wxString::FromUTF8( aCtx.Request.name() ); !schematic->HasVariant( name ) )
3130 {
3131 ApiResponseStatus e;
3132 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3133 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3134 return tl::unexpected( e );
3135 }
3136 }
3137
3138 wxString name = aCtx.Request.has_name() ? wxString::FromUTF8( aCtx.Request.name() ) : wxString();
3139
3140 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
3141 name = *canonical;
3142
3143 if( m_frame )
3145 else
3146 schematic->SetCurrentVariant( name );
3147
3148 return Empty();
3149}
3150
3151
3154{
3155 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3156 return tl::unexpected( documentValidation.error() );
3157
3158 CurrentVariantResponse response;
3159
3160 if( wxString current = schematic()->GetCurrentVariant(); !current.IsEmpty() )
3161 response.set_name( current.ToUTF8() );
3162
3163 return response;
3164}
3165
3166
3169{
3170 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
3171 {
3172 ApiResponseStatus e;
3173 e.set_status( ApiStatusCode::AS_UNHANDLED );
3174 return tl::unexpected( e );
3175 }
3176
3177 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3178 return tl::unexpected( *busy );
3179
3180 LIB_ID libId = UnpackLibId( aCtx.Request.lib_id() );
3181
3182 if( !libId.IsValid() )
3183 {
3184 ApiResponseStatus e;
3185 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3186 e.set_error_message( "lib_id must specify both a library nickname and an entry name" );
3187 return tl::unexpected( e );
3188 }
3189
3191 LIB_SYMBOL* libSymbol = adapter->LoadSymbol( libId );
3192
3193 if( !libSymbol )
3194 {
3195 ApiResponseStatus e;
3196 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3197 e.set_error_message( fmt::format( "symbol '{}' not found", libId.Format().wx_str() ) );
3198 return tl::unexpected( e );
3199 }
3200
3201 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
3202 SCH_SHEET_PATH targetPath = m_context->GetCurrentSheet().value_or( *hierarchy.begin() );
3203
3204 if( aCtx.Request.header().document().has_sheet_path() )
3205 {
3206 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
3207
3208 if( std::optional<SCH_SHEET_PATH> path = hierarchy.GetSheetPathByKIIDPath( kp ) )
3209 {
3210 targetPath = *path;
3211 }
3212 else
3213 {
3214 ApiResponseStatus e;
3215 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3216 e.set_error_message( fmt::format( "the requested sheet path {} is not valid for this schematic",
3217 kp.AsString().ToStdString() ) );
3218 return tl::unexpected( e );
3219 }
3220 }
3221
3222 SCH_SCREEN* targetScreen = targetPath.LastScreen();
3223
3224 int unit = aCtx.Request.has_unit() ? aCtx.Request.unit().unit() : 1;
3225
3226 if( unit < 1 || ( libSymbol->GetUnitCount() > 0 && unit > libSymbol->GetUnitCount() ) )
3227 {
3228 ApiResponseStatus e;
3229 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3230 e.set_error_message( fmt::format( "unit {} is out of range for symbol '{}' ({} units)", unit,
3231 libId.Format().wx_str(), libSymbol->GetUnitCount() ) );
3232 return tl::unexpected( e );
3233 }
3234
3235 VECTOR2I position = UnpackVector2( aCtx.Request.position(), schIUScale );
3236
3237 std::unique_ptr<SCH_SYMBOL> symbol(
3238 std::make_unique<SCH_SYMBOL>( *libSymbol, libId, &targetPath, unit, 0, position ) );
3239
3240 if( aCtx.Request.has_orientation() )
3241 symbol->SetOrientationProp( FromProtoEnum<SYMBOL_ORIENTATION_PROP>( aCtx.Request.orientation() ) );
3242
3243 if( !aCtx.Request.reference().empty() )
3244 {
3245 symbol->SetRef( &targetPath, wxString::FromUTF8( aCtx.Request.reference() ) );
3246 }
3247 else
3248 {
3249 SCH_REFERENCE newReference( symbol.get(), targetPath );
3250 SCH_REFERENCE_LIST existingRefs;
3251 hierarchy.GetSymbols( existingRefs, SYMBOL_FILTER_NON_POWER );
3252
3253 bool annotate = newReference.AlwaysAnnotate();
3254
3255 if( SCH_EDIT_FRAME* frame = this->frame() )
3256 annotate |= frame->eeconfig()->m_AnnotatePanel.automatic;
3257
3258 if( annotate )
3259 {
3260 existingRefs.SortByReferenceOnly();
3261
3262 SCH_REFERENCE_LIST refs;
3263 refs.AddItem( newReference );
3264 refs.SetRefDesTracker( schematic()->Settings().m_refDesTracker );
3265 refs.ReannotateByOptions( static_cast<ANNOTATE_ORDER_T>( schematic()->Settings().m_AnnotateSortOrder ),
3266 static_cast<ANNOTATE_ALGO_T>( schematic()->Settings().m_AnnotateMethod ),
3267 schematic()->Settings().m_AnnotateStartNum, existingRefs, false, &hierarchy );
3268 refs.UpdateAnnotation();
3269 }
3270 }
3271
3272 if( SCH_EDIT_FRAME* frame = this->frame() )
3273 {
3274 if( frame->eeconfig()->m_AutoplaceFields.enable )
3275 symbol->AutoplaceFields( nullptr, AUTOPLACE_AUTO );
3276 }
3277
3278 SCH_COMMIT* commit = static_cast<SCH_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
3279 SCH_SYMBOL* placed = symbol.release();
3280 commit->Add( placed, targetScreen );
3281
3282 if( !m_activeClients.contains( aCtx.ClientName ) )
3283 pushCurrentCommit( aCtx.ClientName, _( "Placed symbol via API" ) );
3284
3285 PlaceFromLibraryResponse response;
3286 response.mutable_header()->CopyFrom( aCtx.Request.header() );
3287
3288 kiapi::schematic::types::SchematicSymbolInstance packed;
3289
3290 if( PackSymbol( &packed, placed, targetPath ) )
3291 response.mutable_item()->PackFrom( packed );
3292
3293 return response;
3294}
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:47
static std::optional< ApiResponseStatus > applySchematicPlotSettings(const schematic::jobs::SchematicPlotSettings &aSettings, const types::DocumentSpecifier &aDocument, 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)
tl::expected< bool, ApiResponseStatus > UnpackSheet(SCH_SHEET *aOutput, const kiapi::schematic::types::SheetSymbol &aInput)
Unpack the every placement data from the input.
bool ApplySymbolInstance(kiapi::common::commands::ItemStatus *aStatus, 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...
std::optional< wxString > FindVariantNoCase(const SCHEMATIC *aSchematic, const wxString &aName)
Variant names are stored and compared exactly, but SCHEMATIC::HasVariant matches case-insensitively.
bool PackSheet(kiapi::schematic::types::SheetSymbol *aOutput, const SCH_SHEET *aInput, const SCH_SHEET_PATH &aPath)
bool ApplySheetInstance(commands::ItemStatus *aStatus, SCH_SHEET *aSheet, const kiapi::schematic::types::SheetSymbol &aInput, const SCH_SHEET_PATH &aParentPath, SCHEMATIC *aSchematic)
bool PackSymbol(kiapi::schematic::types::SchematicSymbolInstance *aOutput, const SCH_SYMBOL *aInput, const SCH_SHEET_PATH &aPath)
tl::expected< SCH_FOCUS_TARGET, ApiResponseStatus > ResolveFocusItems(SCHEMATIC &aSchematic, const std::vector< KIID > &aIds, const std::optional< KIID_PATH > &aSheetPath)
Resolve the items of a FocusOnItems request to the sheet that shows them and their combined bounding ...
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:130
BOX2< VECTOR2I > BOX2I
Definition box2.h:914
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static const ADVANCED_CFG & GetCfg()
Get the singleton instance's config, which is shared by all consumers.
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)
HANDLER_RESULT< Empty > handleFocusOnItems(const HANDLER_CONTEXT< commands::FocusOnItems > &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)
kiapi::common::types::DocumentType thisDocumentType() const override
Override this to specify which document type this editor handles.
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)
void onNetSettingsChanged() override
Called on all registered handlers after project net settings (netclasses or netclass assignments) hav...
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)
HANDLER_RESULT< google::protobuf::Empty > handleSaveDocumentAs(const HANDLER_CONTEXT< commands::SaveDocumentAs > &aCtx)
KICAD_API_SERVER * apiServer() const
Definition api_handler.h:90
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
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
int Checkpoint()
Definition commit.h:134
bool Empty() const
Definition commit.h:145
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr, RECURSE_MODE aRecurse=RECURSE_MODE::NO_RECURSE)
Modify a given item in the model.
Definition commit.h:102
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
Handles action that are shared between different applications.
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 PushCommandToUndoList(PICKED_ITEMS_LIST *aItem)
Add a command to undo in the undo list.
virtual PICKED_ITEMS_LIST * PopCommandFromUndoList()
Return the last command to undo and remove it from list, nothing is deleted.
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
virtual void FinalizeGroupDeserialization()
Definition eda_group.h:66
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:156
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:348
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:753
@ FACE_SCH
eeschema DSO
Definition kiway.h:355
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:114
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
A holder to handle information on schematic or board items.
void PushItem(const ITEM_PICKER &aItem)
Push aItem to the top of the list.
void SetDescription(const wxString &aDescription)
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:276
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:288
Holds all the data relating to one schematic.
Definition schematic.h:149
SCHEMATIC_SETTINGS & Settings() const
SCH_SCREEN * GetCurrentScreen() const
Definition schematic.h:322
SCH_ITEM * ResolveItem(const KIID &aID, SCH_SHEET_PATH *aPathOut=nullptr, bool aAllowNullptrReturn=false) const
Definition schematic.h:202
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:327
SCH_SCREEN * RootScreen() const
Helper to retrieve the screen of the root sheet.
bool ResolveTextVar(const SCH_SHEET_PATH *aSheetPath, wxString *token, int aDepth) const
void RebuildConnectivity(std::function< void(SCH_ITEM *)> *aChangedItemHandler=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr, KIGFX::SCH_VIEW *aSchView=nullptr)
std::vector< SCH_SHEET * > GetTopLevelSheets() const
Get the list of top-level sheets.
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:312
void RefreshHierarchy()
static TOOL_ACTION changeSheet
SCH_DRAW_PANEL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
EESCHEMA_SETTINGS * eeconfig() const
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
void RevertToCheckpoint(int aCheckpoint) override
void Push(const wxString &aMessage=wxEmptyString, int aCommitFlags=0) override
Execute the changes.
bool IsBus() const
Schematic editor (Eeschema) main window.
void OnModify() override
Must be called after a schematic change in order to set the "modify" flag and update other data struc...
void RefreshConnectivity(bool aForce=false, const SCH_CONNECTIVITY::CHANGE_SET *aChanges=nullptr)
void SetCurrentVariant(const wxString &aVariantName)
void UpdateVariantSelectionCtrl(const wxArrayString &aVariantNames)
Update the variant name control on the main toolbar.
bool RecalculateConnections(SCH_COMMIT *aCommit, SCH_CLEANUP_FLAGS aCleanupFlags, PROGRESS_REPORTER *aProgressReporter=nullptr, bool aCleanupDone=false)
Generate the connection data for the entire schematic hierarchy.
SCH_SHEET_PATH & GetCurrentSheet() const
SCHEMATIC & Schematic() 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:170
virtual bool IsConnectable() const
Definition sch_item.h:550
void SetConnectivityDirty(bool aDirty=true)
Set the dirty flag.
Definition sch_item.cpp:314
void SwapItemData(SCH_ITEM *aImage)
Swap data between aItem and aImage.
Definition sch_item.cpp:840
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).
SCH_SYMBOL * GetSymbol() const
wxString GetRef() const
bool IsSplitNeeded() const
Determine if this reference needs to be split or if it likely already has been.
int GetUnit() const
wxString GetRefNumber() const
EE_RTREE & Items()
Get the full RTree, usually for iterating.
Definition sch_screen.h:122
const wxString & GetFileName() const
Definition sch_screen.h:157
void SetFileName(const wxString &aFileName)
Set the file name for this screen to aFileName.
TITLE_BLOCK & GetTitleBlock()
Definition sch_screen.h:168
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.
Define a sheet pin (label) used in sheets to create hierarchical schematics.
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:392
void AddInstance(const SCH_SHEET_INSTANCE &aInstance)
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:151
wxString GetShownName(RESOLUTION_CONTEXT aContext) const
Definition sch_sheet.h:144
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:249
Schematic symbol object.
Definition sch_symbol.h:75
void SetLibId(const LIB_ID &aName)
std::vector< std::unique_ptr< SCH_PIN > > & GetRawPins()
Definition sch_symbol.h:686
bool UseLibIdLookup() const
Definition sch_symbol.h:182
wxString GetSchSymbolLibraryName() const
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.
const LIB_ID & GetLibId() const override
Definition sch_symbol.h:165
void SetSchSymbolLibraryName(const wxString &aName)
The name of the symbol in the schematic library symbol list.
Definition sch_symbol.h:180
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:184
void SetLibSymbol(LIB_SYMBOL *aLibSymbol)
Set this schematic symbol library symbol reference to aLibSymbol.
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.
T * GetTool()
Return the tool of given type or nullptr if there is no such tool registered.
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, bool aAllowEscapes)
Replace any environment variable & text variable references with their values.
Definition common.cpp:777
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:96
#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
bool m_ConnectivityEngine
Use the schematic connectivity engine instead of CONNECTION_GRAPH.
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 int UnpackDistance(const types::Distance &aInput, const EDA_IU_SCALE &aScale)
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 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.
static int plotJob(JOB_EXPORT_PCB_PLOT *aJob, PCB_PLOTTER &aPlotter, const wxString &aOutPath, bool aSingleOutput, bool aUseOverrides)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
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:75
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
@ LOCAL_CLEANUP
Definition schematic.h:94
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:53
RequestMessageType Request
Definition api_handler.h:54
A simple container for sheet instance information.
A simple container for schematic symbol instance information.
#define ALL_UNITS
Definition symbol.h:150
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_SHEET_PIN_T
Definition typeinfo.h:170
@ 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:708
Definition of file extensions used in Kicad.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().
Definition wx_filename.h:35