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 ApplySymbolInstance( symbol, symbolProto, targetPath, schematic() );
1435
1436 if( PackSymbol( &packed, symbol, targetPath ) )
1437 newItem.PackFrom( packed );
1438 }
1439 else if( createdItem->Type() == SCH_SHEET_T )
1440 {
1441 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( createdItem );
1442 kiapi::schematic::types::SheetSymbol packed;
1443
1444 if( sheetProto.page_number().empty() )
1445 sheetProto.set_page_number( hierarchy.GetNextPageNumber().ToUTF8() );
1446
1447 ApplySheetInstance( sheet, sheetProto, targetPath, schematic() );
1448
1449 if( PackSheet( &packed, sheet, targetPath ) )
1450 newItem.PackFrom( packed );
1451 }
1452 else
1453 {
1454 createdItem->Serialize( newItem );
1455 }
1456 }
1457 else
1458 {
1459 // SwapItemData hands the item the temporary's (empty) instance list, so keep the
1460 // placements to restore afterwards.
1461 std::vector<SCH_SYMBOL_INSTANCE> symbolPlacements;
1462 std::vector<SCH_SHEET_INSTANCE> sheetPlacements;
1463
1464 if( existingItem->Type() == SCH_SYMBOL_T )
1465 symbolPlacements = static_cast<SCH_SYMBOL*>( existingItem )->GetInstances();
1466 else if( existingItem->Type() == SCH_SHEET_T )
1467 sheetPlacements = static_cast<SCH_SHEET*>( existingItem )->GetInstances();
1468
1469 commit->Modify( existingItem, targetScreen );
1470 existingItem->SwapItemData( static_cast<SCH_ITEM*>( item.get() ) );
1471
1472 if( existingItem->IsConnectable() )
1473 {
1474 existingItem->SetConnectivityDirty();
1475 connectivityChanged = true;
1476 }
1477
1478 if( existingItem->Type() == SCH_SYMBOL_T )
1479 {
1480 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( existingItem );
1481 kiapi::schematic::types::SchematicSymbolInstance packed;
1482
1483 for( const SCH_SYMBOL_INSTANCE& placement : symbolPlacements )
1484 symbol->AddHierarchicalReference( placement );
1485
1486 ApplySymbolInstance( symbol, symbolProto, existingPath, schematic() );
1487
1488 if( PackSymbol( &packed, symbol, existingPath ) )
1489 newItem.PackFrom( packed );
1490 }
1491 else if( existingItem->Type() == SCH_SHEET_T )
1492 {
1493 SCH_SHEET* sheet = static_cast<SCH_SHEET*>( existingItem );
1494 kiapi::schematic::types::SheetSymbol packed;
1495
1496 for( const SCH_SHEET_INSTANCE& placement : sheetPlacements )
1497 sheet->AddInstance( placement );
1498
1499 ApplySheetInstance( sheet, sheetProto, existingPath, schematic() );
1500
1501 if( PackSheet( &packed, sheet, existingPath ) )
1502 newItem.PackFrom( packed );
1503 }
1504 else
1505 {
1506 existingItem->Serialize( newItem );
1507 }
1508 }
1509
1510 aItemHandler( status, newItem );
1511 }
1512
1513 if( !m_activeClients.contains( aClientName ) )
1514 {
1515 pushCurrentCommit( aClientName, aCreate ? _( "Created items via API" )
1516 : _( "Modified items via API" ) );
1517 }
1518
1519 if( m_frame && connectivityChanged )
1521
1522 return ItemRequestStatus::IRS_OK;
1523}
1524
1525
1526void API_HANDLER_SCH::deleteItemsInternal( std::map<KIID, ItemDeletionStatus>& aItemsToDelete,
1527 const std::string& aClientName )
1528{
1529 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
1530 COMMIT* commit = getCurrentCommit( aClientName );
1531
1532 for( auto& [id, status] : aItemsToDelete )
1533 {
1535 SCH_ITEM* item = hierarchy.ResolveItem( id, &path, true );
1536
1537 if( !item )
1538 continue;
1539
1540 if( !s_allowedTypes.contains( item->Type() ) )
1541 {
1542 status = ItemDeletionStatus::IDS_IMMUTABLE;
1543 continue;
1544 }
1545
1546 commit->Remove( item, path.LastScreen() );
1547 status = ItemDeletionStatus::IDS_OK;
1548 }
1549
1550 if( !m_activeClients.contains( aClientName ) )
1551 pushCurrentCommit( aClientName, _( "Deleted items via API" ) );
1552}
1553
1554
1555std::optional<EDA_ITEM*> API_HANDLER_SCH::getItemFromDocument( const DocumentSpecifier& aDocument, const KIID& aId )
1556{
1557 if( !validateDocument( aDocument ) )
1558 return std::nullopt;
1559
1560 SCH_ITEM* item = schematic()->Hierarchy().ResolveItem( aId, nullptr, true );
1561
1562 if( !item)
1563 return std::nullopt;
1564
1565 return item;
1566}
1567
1568
1569SCH_SCREEN* API_HANDLER_SCH::resolveScreenFromDocument( const DocumentSpecifier& aDocument ) const
1570{
1571 if( aDocument.has_sheet_path() )
1572 {
1573 KIID_PATH path = UnpackSheetPath( aDocument.sheet_path() );
1574
1575 if( std::optional<SCH_SHEET_PATH> sheetPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( path ) )
1576 return sheetPath->LastScreen();
1577
1578 return nullptr;
1579 }
1580
1581 if( std::optional<SCH_SHEET_PATH> current = m_context->GetCurrentSheet() )
1582 return current->LastScreen();
1583
1584 // Headless mode has no current sheet; the first top-level sheet is the implicit target.
1585 return schematic()->RootScreen();
1586}
1587
1588
1589std::optional<TITLE_BLOCK*> API_HANDLER_SCH::getTitleBlock( const DocumentSpecifier& aDocument )
1590{
1591 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1592 return &screen->GetTitleBlock();
1593
1594 return std::nullopt;
1595}
1596
1597
1598std::optional<PAGE_INFO> API_HANDLER_SCH::getPageSettings( const DocumentSpecifier& aDocument )
1599{
1600 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1601 return screen->GetPageSettings();
1602
1603 return std::nullopt;
1604}
1605
1606
1607bool API_HANDLER_SCH::setPageSettings( const DocumentSpecifier& aDocument, const PAGE_INFO& aPageInfo )
1608{
1609 if( SCH_SCREEN* screen = resolveScreenFromDocument( aDocument ) )
1610 {
1611 screen->SetPageSettings( aPageInfo );
1612 screen->SetContentModified();
1613 return true;
1614 }
1615
1616 return false;
1617}
1618
1619
1624
1625
1626void API_HANDLER_SCH::setDrawingSheetFileName( const wxString& aFileName )
1627{
1630
1631 if( m_frame )
1633}
1634
1635
1637{
1638 if( m_frame )
1639 {
1640 frame()->Refresh();
1641 frame()->OnModify();
1642 }
1643 else if( schematic()->GetCurrentScreen() )
1644 {
1646 }
1647}
1648
1649
1651{
1652 if( m_frame )
1653 frame()->Refresh();
1654}
1655
1656
1657static std::optional<ApiResponseStatus>
1658applySchematicPlotSettings( const schematic::jobs::SchematicPlotSettings& aSettings,
1659 const types::DocumentSpecifier& aDocument, JOB_EXPORT_SCH_PLOT& aJob )
1660{
1661 aJob.m_drawingSheet = wxString::FromUTF8( aSettings.drawing_sheet() );
1662 aJob.m_defaultFont = wxString::FromUTF8( aSettings.default_font() );
1663 aJob.m_variant = wxString::FromUTF8( aSettings.variant() );
1664 aJob.m_plotAll = aSettings.plot_all();
1665 aJob.m_plotDrawingSheet = aSettings.plot_drawing_sheet();
1666 aJob.m_show_hop_over = aSettings.show_hop_over();
1667 aJob.m_blackAndWhite = aSettings.black_and_white();
1668 aJob.m_useBackgroundColor = aSettings.use_background_color();
1669 aJob.m_minPenWidth = aSettings.min_pen_width();
1670 aJob.m_theme = wxString::FromUTF8( aSettings.theme() );
1671
1672 aJob.m_plotPages.clear();
1673
1674 for( const std::string& page : aSettings.plot_pages() )
1675 aJob.m_plotPages.push_back( wxString::FromUTF8( page ) );
1676
1677 if( aSettings.page_size() != schematic::jobs::SchematicJobPageSize::SJPS_UNKNOWN )
1678 aJob.m_pageSizeSelect = FromProtoEnum<JOB_PAGE_SIZE>( aSettings.page_size() );
1679
1680 switch( aSettings.sheet_mode() )
1681 {
1682 case schematic::jobs::SJSM_ALL_SHEETS: aJob.m_plotAll = true; break;
1683 case schematic::jobs::SJSM_SINGLE_SHEET: aJob.m_plotAll = false; break;
1684 case schematic::jobs::SJSM_UNKNOWN:
1685 default: aJob.m_plotAll = false; break;
1686 }
1687
1688 if( aDocument.has_sheet_path() )
1689 aJob.m_sheetPath = UnpackSheetPath( aDocument.sheet_path() ).AsString();
1690
1691 return std::nullopt;
1692}
1693
1694
1697{
1698 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1699
1700 if( !documentValidation )
1701 return tl::unexpected( documentValidation.error() );
1702
1703 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1704 return tl::unexpected( *busy );
1705
1706 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_SVG>( aCtx.Request.plot_settings().sheet_mode()
1707 != schematic::jobs::SJSM_SINGLE_SHEET );
1708 plotJob->m_filename = m_context->GetCurrentFileName();
1709
1710 if( !aCtx.Request.job_settings().output_path().empty() )
1711 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1712
1713 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1714 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1715 {
1716 return tl::unexpected( *err );
1717 }
1718
1719 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1720}
1721
1722
1725{
1726 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1727
1728 if( !documentValidation )
1729 return tl::unexpected( documentValidation.error() );
1730
1731 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1732 return tl::unexpected( *busy );
1733
1734 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_DXF>( aCtx.Request.plot_settings().sheet_mode()
1735 != schematic::jobs::SJSM_SINGLE_SHEET );
1736
1737 plotJob->m_filename = m_context->GetCurrentFileName();
1738
1739 if( !aCtx.Request.job_settings().output_path().empty() )
1740 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1741
1742 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1743 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1744 {
1745 return tl::unexpected( *err );
1746 }
1747
1748 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1749}
1750
1751
1754{
1755 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1756
1757 if( !documentValidation )
1758 return tl::unexpected( documentValidation.error() );
1759
1760 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1761 return tl::unexpected( *busy );
1762
1763 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PDF>( false );
1764 plotJob->m_filename = m_context->GetCurrentFileName();
1765
1766 if( !aCtx.Request.job_settings().output_path().empty() )
1767 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1768
1769 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1770 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1771 {
1772 return tl::unexpected( *err );
1773 }
1774
1775 plotJob->m_PDFPropertyPopups = aCtx.Request.property_popups();
1776 plotJob->m_PDFHierarchicalLinks = aCtx.Request.hierarchical_links();
1777 plotJob->m_PDFMetadata = aCtx.Request.include_metadata();
1778
1779 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1780}
1781
1782
1785{
1786 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1787
1788 if( !documentValidation )
1789 return tl::unexpected( documentValidation.error() );
1790
1791 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1792 return tl::unexpected( *busy );
1793
1794 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PS>( aCtx.Request.plot_settings().sheet_mode()
1795 != schematic::jobs::SJSM_SINGLE_SHEET );
1796 plotJob->m_filename = m_context->GetCurrentFileName();
1797
1798 if( !aCtx.Request.job_settings().output_path().empty() )
1799 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1800
1801 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1802 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1803 {
1804 return tl::unexpected( *err );
1805 }
1806
1807 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1808}
1809
1810
1813{
1814 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
1815 return tl::unexpected( validation.error() );
1816
1817 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1818 return tl::unexpected( *busy );
1819
1820 const schematic::jobs::SchematicJobSheetMode sheetMode = aCtx.Request.plot_settings().sheet_mode();
1821
1822 auto plotJob = std::make_unique<JOB_EXPORT_SCH_PLOT_PNG>( sheetMode != schematic::jobs::SJSM_SINGLE_SHEET );
1823 plotJob->m_filename = m_context->GetCurrentFileName();
1824
1825 if( !aCtx.Request.job_settings().output_path().empty() )
1826 plotJob->SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1827
1828 if( std::optional<ApiResponseStatus> err = applySchematicPlotSettings(
1829 aCtx.Request.plot_settings(), aCtx.Request.job_settings().document(), *plotJob ) )
1830 {
1831 return tl::unexpected( *err );
1832 }
1833
1834 if( aCtx.Request.has_dpi() )
1835 {
1836 int dpi = aCtx.Request.dpi();
1837
1838 if( dpi < MIN_PNG_DPI || dpi > MAX_PNG_DPI )
1839 {
1840 ApiResponseStatus status;
1841 status.set_status( ApiStatusCode::AS_BAD_REQUEST );
1842 status.set_error_message( fmt::format( "dpi must be between {} and {}", MIN_PNG_DPI, MAX_PNG_DPI ) );
1843 return tl::unexpected( status );
1844 }
1845
1846 plotJob->m_dpi = dpi;
1847 }
1848
1849 // Unknown -> default AA on
1850 plotJob->m_antialias = aCtx.Request.antialiasing() != types::AntialiasingMode::AAM_NONE;
1851 plotJob->m_useBackgroundColor = aCtx.Request.plot_background_color();
1852
1853 return ExecuteSchematicJob( m_context->GetKiway(), *plotJob );
1854}
1855
1856
1859{
1860 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1861
1862 if( !documentValidation )
1863 return tl::unexpected( documentValidation.error() );
1864
1865 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1866 return tl::unexpected( *busy );
1867
1868 if( aCtx.Request.format() == kiapi::schematic::jobs::SchematicNetlistFormat::SNF_UNKNOWN )
1869 {
1870 ApiResponseStatus e;
1871 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1872 e.set_error_message( "RunSchematicJobExportNetlist requires a valid format" );
1873 return tl::unexpected( e );
1874 }
1875
1876 JOB_EXPORT_SCH_NETLIST netlistJob;
1877 netlistJob.m_filename = m_context->GetCurrentFileName();
1878
1879 if( !aCtx.Request.job_settings().output_path().empty() )
1880 netlistJob.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1881
1882 netlistJob.format = FromProtoEnum<JOB_EXPORT_SCH_NETLIST::FORMAT>( aCtx.Request.format() );
1883
1884 if( !aCtx.Request.variant_name().empty() )
1885 netlistJob.m_variantNames.emplace_back( wxString::FromUTF8( aCtx.Request.variant_name() ) );
1886
1887 return ExecuteSchematicJob( m_context->GetKiway(), netlistJob );
1888}
1889
1890
1893{
1894 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.job_settings().document() );
1895
1896 if( !documentValidation )
1897 return tl::unexpected( documentValidation.error() );
1898
1899 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1900 return tl::unexpected( *busy );
1901
1902 JOB_EXPORT_BOM bomJob;
1903 bomJob.m_filename = m_context->GetCurrentFileName();
1904
1905 if( !aCtx.Request.job_settings().output_path().empty() )
1906 bomJob.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
1907
1908 bomJob.m_bomFmtPresetName = wxString::FromUTF8( aCtx.Request.format().preset_name() );
1909 bomJob.m_fieldDelimiter = wxString::FromUTF8( aCtx.Request.format().field_delimiter() );
1910 bomJob.m_stringDelimiter = wxString::FromUTF8( aCtx.Request.format().string_delimiter() );
1911 bomJob.m_refDelimiter = wxString::FromUTF8( aCtx.Request.format().ref_delimiter() );
1912 bomJob.m_refRangeDelimiter = wxString::FromUTF8( aCtx.Request.format().ref_range_delimiter() );
1913 bomJob.m_keepTabs = aCtx.Request.format().keep_tabs();
1914 bomJob.m_keepLineBreaks = aCtx.Request.format().keep_line_breaks();
1915 bomJob.m_includeByteOrderMark = aCtx.Request.format().include_byte_order_mark();
1916
1917 bomJob.m_bomPresetName = wxString::FromUTF8( aCtx.Request.fields().preset_name() );
1918 bomJob.m_sortField = wxString::FromUTF8( aCtx.Request.fields().sort_field() );
1919 bomJob.m_filterString = wxString::FromUTF8( aCtx.Request.fields().filter() );
1920
1921 if( bomJob.m_bomPresetName.IsEmpty() && aCtx.Request.fields().fields().empty() )
1922 {
1923 ApiResponseStatus e;
1924 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1925 e.set_error_message( "A list of fields fields or a fields preset are required" );
1926 return tl::unexpected( e );
1927 }
1928
1929 switch( aCtx.Request.fields().filter_scope() )
1930 {
1931 case kiapi::schematic::jobs::BOMFilterScope::BFS_VISIBLE:
1933 break;
1934
1935 case kiapi::schematic::jobs::BOMFilterScope::BFS_ALL:
1937 break;
1938
1939 case kiapi::schematic::jobs::BOMFilterScope::BFS_REFERENCE:
1940 default:
1942 break;
1943 }
1944
1945 if( aCtx.Request.fields().sort_direction() == kiapi::schematic::jobs::BOMSortDirection::BSD_ASCENDING )
1946 {
1947 bomJob.m_sortAsc = true;
1948 }
1949 else if( aCtx.Request.fields().sort_direction() == kiapi::schematic::jobs::BOMSortDirection::BSD_DESCENDING )
1950 {
1951 bomJob.m_sortAsc = false;
1952 }
1953
1954 for( const kiapi::schematic::jobs::BOMField& field : aCtx.Request.fields().fields() )
1955 {
1956 bomJob.m_fieldsOrdered.emplace_back( wxString::FromUTF8( field.name() ) );
1957 bomJob.m_fieldsLabels.emplace_back( wxString::FromUTF8( field.label() ) );
1958
1959 if( field.group_by() )
1960 bomJob.m_fieldsGroupBy.emplace_back( wxString::FromUTF8( field.name() ) );
1961 }
1962
1963 bomJob.m_excludeDNP = aCtx.Request.exclude_dnp();
1964 bomJob.m_groupSymbols = aCtx.Request.group_symbols();
1965
1966 if( !aCtx.Request.variant_name().empty() )
1967 bomJob.m_variantNames.emplace_back( wxString::FromUTF8( aCtx.Request.variant_name() ) );
1968
1969 return ExecuteSchematicJob( m_context->GetKiway(), bomJob );
1970}
1971
1972
1973void API_HANDLER_SCH::packSheetInstance( kiapi::schematic::types::SheetInstance* aInstance, SCH_SHEET_PATH& aPath,
1974 SCH_SHEET* aSheet )
1975{
1976 aPath.push_back( aSheet );
1977
1978 PackSheetPath( *aInstance->mutable_path(), aPath );
1979
1980 wxString sheetName = aSheet->GetShownName( INTERNAL );
1981
1982 if( sheetName.IsEmpty() && aSheet->GetScreen() )
1983 {
1984 wxFileName fn( aSheet->GetScreen()->GetFileName() );
1985 sheetName = fn.GetName();
1986 }
1987
1988 aInstance->set_name( sheetName.ToUTF8() );
1989 aInstance->set_filename( aSheet->GetFileName().ToUTF8() );
1990 aInstance->set_page_number( aPath.GetPageNumber().ToUTF8() );
1991
1992 if( aSheet->GetScreen() )
1993 {
1994 std::vector<SCH_ITEM*> childSheets;
1995 aSheet->GetScreen()->GetSheets( &childSheets );
1996
1997 std::ranges::sort( childSheets,
1998 [&]( SCH_ITEM* a, SCH_ITEM* b )
1999 {
2000 SCH_SHEET_PATH pathA = aPath;
2001 pathA.push_back( static_cast<SCH_SHEET*>( a ) );
2002
2003 SCH_SHEET_PATH pathB = aPath;
2004 pathB.push_back( static_cast<SCH_SHEET*>( b ) );
2005
2006 return pathA.ComparePageNum( pathB ) < 0;
2007 } );
2008
2009 for( SCH_ITEM* childItem : childSheets )
2010 {
2011 SCH_SHEET* childSheet = static_cast<SCH_SHEET*>( childItem );
2012 kiapi::schematic::types::SheetInstance* childInstance = aInstance->add_children();
2013 packSheetInstance( childInstance, aPath, childSheet );
2014 }
2015 }
2016
2017 aPath.pop_back();
2018}
2019
2020
2023{
2024 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2025
2026 if( !documentValidation )
2027 return tl::unexpected( documentValidation.error() );
2028
2029 kiapi::schematic::commands::SchematicHierarchyResponse response;
2030 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2031
2032 if( !schematic()->HasHierarchy() )
2034
2036 std::vector<SCH_SHEET*> topLevelSheets = schematic()->GetTopLevelSheets();
2037
2038 std::ranges::sort( topLevelSheets,
2039 [&]( SCH_SHEET* a, SCH_SHEET* b )
2040 {
2041 SCH_SHEET_PATH pathA;
2042 pathA.push_back( a );
2043
2044 SCH_SHEET_PATH pathB;
2045 pathB.push_back( b );
2046
2047 return pathA.ComparePageNum( pathB ) < 0;
2048 } );
2049
2050 for( SCH_SHEET* topSheet : topLevelSheets )
2051 {
2052 kiapi::schematic::types::SheetInstance* instance = response.add_top_level_sheets();
2053 packSheetInstance( instance, path, topSheet );
2054 }
2055
2056 return response;
2057}
2058
2059
2062{
2063 const bool engine = ADVANCED_CFG::GetCfg().m_ConnectivityEngine;
2064
2065 // The engine rebuilds before answering, which an interactive tool must not observe
2066 if( engine && m_frame && !m_frame->ToolStackIsEmpty() )
2067 {
2068 ApiResponseStatus status;
2069 status.set_status( ApiStatusCode::AS_BUSY );
2070 status.set_error_message( "Cannot rebuild connectivity during an interactive operation" );
2071 return tl::unexpected( status );
2072 }
2073
2074 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2075
2076 if( !documentValidation )
2077 return tl::unexpected( documentValidation.error() );
2078
2079 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2080 return tl::unexpected( *busy );
2081
2082 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
2083 const bool filterByType = aCtx.Request.types_size() > 0;
2084
2085 static const std::set<KICAD_T> s_netlistTypes = {
2086 SCH_PIN_T,
2088 SCH_LINE_T,
2098 };
2099
2100 std::set<KICAD_T> typeFilter( types.begin(), types.end() );
2101 std::erase_if( typeFilter, []( KICAD_T aType ) { return !s_netlistTypes.contains( aType ); } );
2102
2103 if( filterByType && typeFilter.empty() )
2104 {
2105 ApiResponseStatus e;
2106 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2107 e.set_error_message( "none of the requested types are valid for a netlist object" );
2108 return tl::unexpected( e );
2109 }
2110
2111 CONNECTION_GRAPH* connectionGraph = schematic()->ConnectionGraph();
2112
2113 if( !connectionGraph )
2114 {
2115 ApiResponseStatus e;
2116 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2117 e.set_error_message( "schematic has no connection graph" );
2118 return tl::unexpected( e );
2119 }
2120
2121 if( engine )
2122 {
2123 try
2124 {
2126 }
2127 catch( const std::exception& error )
2128 {
2129 if( m_frame )
2130 {
2131 m_frame->ShowInfoBarError( _( "Unable to rebuild schematic connectivity." ), true );
2132 frame()->RefreshConnectivity( true );
2133 }
2134
2135 wxLogTrace( traceApi, wxS( "GetSchematicNetlist rebuild failed: %s" ),
2136 wxString::FromUTF8( error.what() ) );
2137
2138 ApiResponseStatus status;
2139 status.set_status( ApiStatusCode::AS_UNKNOWN );
2140 status.set_error_message( error.what() );
2141 return tl::unexpected( status );
2142 }
2143 }
2144
2145 kiapi::schematic::commands::SchematicNetlistResponse response;
2146 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2147
2148 if( engine )
2149 {
2150 for( const auto& group : schematic()->Connectivity().GetNetMap() )
2151 {
2152 if( group.instances.empty() || !group.instances.front().IsNet()
2153 || !group.instances.front().Driver() )
2154 {
2155 continue;
2156 }
2157
2158 kiapi::schematic::types::SchematicNet* net = nullptr;
2159
2160 for( const auto& view : group.instances )
2161 {
2162 const auto physicalItems = view.Items();
2163
2164 if( physicalItems.empty() )
2165 continue;
2166
2167 if( !net )
2168 {
2169 net = response.add_nets();
2170 net->set_name( group.name.ToUTF8() );
2171 }
2172
2173 auto* sheet = net->add_sheets();
2174 PackSheetPath( *sheet->mutable_path(), view.Instance() );
2175
2176 for( SCH_ITEM* item : physicalItems )
2177 {
2178 if( !filterByType || typeFilter.contains( item->Type() ) )
2179 sheet->add_items()->set_value( item->m_Uuid.AsStdString() );
2180 }
2181 }
2182 }
2183
2184 return response;
2185 }
2186
2187 for( const auto& [key, subgraphList] : connectionGraph->GetNetMap() )
2188 {
2189 if( subgraphList.empty() )
2190 continue;
2191
2192 CONNECTION_SUBGRAPH* firstSubgraph = subgraphList[0];
2193
2194 if( firstSubgraph->GetDriverConnection() && firstSubgraph->GetDriverConnection()->IsBus() )
2195 continue;
2196
2198 continue;
2199
2200 kiapi::schematic::types::SchematicNet* net = response.add_nets();
2201 net->set_name( key.Name.ToUTF8() );
2202
2203 for( CONNECTION_SUBGRAPH* subGraph : subgraphList )
2204 {
2205 kiapi::schematic::types::SchematicNetSheetContents* sheetContents = net->add_sheets();
2206 PackSheetPath( *sheetContents->mutable_path(), subGraph->GetSheet() );
2207
2208 for( SCH_ITEM* item : subGraph->GetItems() )
2209 {
2210 if( filterByType && !typeFilter.contains( item->Type() ) )
2211 continue;
2212
2213 sheetContents->add_items()->set_value( item->m_Uuid.AsStdString() );
2214 }
2215 }
2216 }
2217
2218 return response;
2219}
2220
2221
2222// TODO(JE) factor out
2225{
2226 wxLogTrace( traceApi, "Received announce from frame %d at %s",
2227 aCtx.Request.frame_type(), aCtx.Request.socket_path() );
2228
2229 CROSS_PROBE_CLIENT::RegisterPeer( static_cast<FRAME_T>( aCtx.Request.frame_type() ),
2230 aCtx.Request.socket_path() );
2231
2232 CrossProbeAnnounceResponse response;
2233 response.set_status( CPS_OK );
2234 return response;
2235}
2236
2237
2238bool findSymbolsAndPins( const SCH_SHEET_LIST& aSchematicSheetList, const SCH_SHEET_PATH& aSheetPath,
2239 std::unordered_map<wxString, std::vector<SCH_REFERENCE>>& aSyncSymMap,
2240 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>>& aSyncPinMap,
2241 const wxString& aVariantName = wxEmptyString, bool aRecursive = false )
2242{
2243 if( aRecursive )
2244 {
2245 // Iterate over children
2246 for( const SCH_SHEET_PATH& candidate : aSchematicSheetList )
2247 {
2248 if( candidate == aSheetPath || !candidate.IsContainedWithin( aSheetPath ) )
2249 continue;
2250
2251 findSymbolsAndPins( aSchematicSheetList, candidate, aSyncSymMap, aSyncPinMap, aVariantName, aRecursive );
2252 }
2253 }
2254
2255 SCH_REFERENCE_LIST references;
2256
2257 aSheetPath.GetSymbols( references, SYMBOL_FILTER_NON_POWER, true );
2258
2259 for( unsigned ii = 0; ii < references.GetCount(); ii++ )
2260 {
2261 SCH_REFERENCE& schRef = references[ii];
2262
2263 if( schRef.IsSplitNeeded() )
2264 schRef.Split();
2265
2266 SCH_SYMBOL* symbol = schRef.GetSymbol();
2267 wxString refNum = schRef.GetRefNumber();
2268 wxString fullRef = schRef.GetRef() + refNum;
2269
2270 // Skip power symbols
2271 if( fullRef.StartsWith( wxS( "#" ) ) )
2272 continue;
2273
2274 // Unannotated symbols are not supported
2275 if( refNum.compare( wxS( "?" ) ) == 0 )
2276 continue;
2277
2278 // Look for whole footprint
2279 auto symMatchIt = aSyncSymMap.find( fullRef );
2280
2281 if( symMatchIt != aSyncSymMap.end() )
2282 {
2283 symMatchIt->second.emplace_back( schRef );
2284
2285 // Whole footprint was selected, no need to select pins
2286 continue;
2287 }
2288
2289 // Look for pins
2290 auto symPinMatchIt = aSyncPinMap.find( fullRef );
2291
2292 if( symPinMatchIt != aSyncPinMap.end() )
2293 {
2294 std::unordered_map<wxString, SCH_PIN*>& pinMap = symPinMatchIt->second;
2295 std::vector<SCH_PIN*> pinsOnSheet = symbol->GetPins( &aSheetPath );
2296
2297 for( SCH_PIN* pin : pinsOnSheet )
2298 {
2299 int pinUnit = pin->GetLibPin() ? pin->GetLibPin()->GetUnit() : ALL_UNITS;
2300
2301 if( pinUnit > 0 && pinUnit != schRef.GetUnit() )
2302 continue;
2303
2304 // Reverse-map the requested pad back to the owning pin (issue #2282). A pin may
2305 // resolve to several pads via the map; match the first that pcbnew asked for.
2306 for( const wxString& pad :
2307 ExpandStackedPinNotation( pin->GetEffectivePadNumber( aSheetPath, aVariantName ) ) )
2308 {
2309 auto pinIt = pinMap.find( pad );
2310
2311 if( pinIt != pinMap.end() )
2312 {
2313 pinIt->second = pin;
2314 break;
2315 }
2316 }
2317 }
2318 }
2319 }
2320
2321 return false;
2322}
2323
2324
2326 const SCH_SHEET_LIST& aSchematicSheetList, const SCH_SHEET_PATH& aSheetPath,
2327 std::unordered_map<wxString, std::vector<SCH_REFERENCE>>& aSyncSymMap,
2328 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>>& aSyncPinMap,
2329 std::unordered_map<SCH_SHEET_PATH, bool>& aCache )
2330{
2331 auto cacheIt = aCache.find( aSheetPath );
2332
2333 if( cacheIt != aCache.end() )
2334 return cacheIt->second;
2335
2336 // Iterate over children
2337 for( const SCH_SHEET_PATH& candidate : aSchematicSheetList )
2338 {
2339 if( candidate == aSheetPath || !candidate.IsContainedWithin( aSheetPath ) )
2340 continue;
2341
2342 bool childRet = sheetContainsOnlyWantedItems( aSchematicSheetList, candidate, aSyncSymMap,
2343 aSyncPinMap, aCache );
2344
2345 if( !childRet )
2346 {
2347 aCache.emplace( aSheetPath, false );
2348 return false;
2349 }
2350 }
2351
2352 SCH_REFERENCE_LIST references;
2353 aSheetPath.GetSymbols( references, SYMBOL_FILTER_NON_POWER, true );
2354
2355 if( references.GetCount() == 0 ) // Empty sheet, obviously do not contain wanted items
2356 {
2357 aCache.emplace( aSheetPath, false );
2358 return false;
2359 }
2360
2361 for( unsigned ii = 0; ii < references.GetCount(); ii++ )
2362 {
2363 SCH_REFERENCE& schRef = references[ii];
2364
2365 if( schRef.IsSplitNeeded() )
2366 schRef.Split();
2367
2368 wxString refNum = schRef.GetRefNumber();
2369 wxString fullRef = schRef.GetRef() + refNum;
2370
2371 // Skip power symbols
2372 if( fullRef.StartsWith( wxS( "#" ) ) )
2373 continue;
2374
2375 // Unannotated symbols are not supported
2376 if( refNum.compare( wxS( "?" ) ) == 0 )
2377 continue;
2378
2379 if( aSyncSymMap.find( fullRef ) == aSyncSymMap.end() )
2380 {
2381 aCache.emplace( aSheetPath, false );
2382 return false; // Some symbol is not wanted.
2383 }
2384
2385 if( aSyncPinMap.find( fullRef ) != aSyncPinMap.end() )
2386 {
2387 aCache.emplace( aSheetPath, false );
2388 return false; // Looking for specific pins, so can't be mapped
2389 }
2390 }
2391
2392 aCache.emplace( aSheetPath, true );
2393 return true;
2394}
2395
2396
2397std::optional<std::tuple<SCH_SHEET_PATH, SCH_ITEM*, std::vector<SCH_ITEM*>>>
2399 const kiapi::common::commands::SyncSelection& aSync )
2400{
2401 std::unordered_map<wxString, std::vector<SCH_REFERENCE>> syncSymMap;
2402 std::unordered_map<wxString, std::unordered_map<wxString, SCH_PIN*>> syncPinMap;
2403 std::unordered_map<SCH_SHEET_PATH, bool> fullyWantedCache;
2404
2405 std::optional<wxString> focusSymbol;
2406 std::optional<std::pair<wxString, wxString>> focusPin;
2407 std::unordered_map<SCH_SHEET_PATH, std::vector<SCH_ITEM*>> focusItemResults;
2408
2409 const SCH_SHEET_LIST allSheetsList = aSchematic.Hierarchy();
2410
2411 // In orderedSheets, the current sheet comes first.
2412 std::vector<SCH_SHEET_PATH> orderedSheets;
2413 orderedSheets.reserve( allSheetsList.size() );
2414 orderedSheets.push_back( aSchematic.CurrentSheet() );
2415
2416 for( const SCH_SHEET_PATH& sheetPath : allSheetsList )
2417 {
2418 if( sheetPath != aSchematic.CurrentSheet() )
2419 orderedSheets.push_back( sheetPath );
2420 }
2421
2422 const bool focusOnFirst = ( aSync.mode() == kiapi::common::commands::SSM_ITEMS_AND_NETS ) && aSync.has_focus_item();
2423
2424 for( const kiapi::common::commands::SelectionSpec& spec : aSync.items() )
2425 {
2426 switch( spec.spec_case() )
2427 {
2428 case kiapi::common::commands::SelectionSpec::kFootprint:
2429 {
2430 wxString symRef = wxString::FromUTF8( spec.footprint().reference() );
2431 syncSymMap[symRef] = std::vector<SCH_REFERENCE>();
2432 break;
2433 }
2434
2435 case kiapi::common::commands::SelectionSpec::kPad:
2436 {
2437 wxString symRef = wxString::FromUTF8( spec.pad().reference() );
2438 wxString padNum = wxString::FromUTF8( spec.pad().number() );
2439 syncPinMap[symRef][padNum] = nullptr;
2440 break;
2441 }
2442
2443 default:
2444 break;
2445 }
2446 }
2447
2448 if( focusOnFirst )
2449 {
2450 const kiapi::common::commands::SelectionSpec& focusSpec = aSync.focus_item();
2451
2452 if( focusSpec.has_footprint() )
2453 focusSymbol = wxString::FromUTF8( focusSpec.footprint().reference() );
2454 else if( focusSpec.has_pad() )
2455 focusPin = std::make_pair( wxString::FromUTF8( focusSpec.pad().reference() ),
2456 wxString::FromUTF8( focusSpec.pad().number() ) );
2457 }
2458
2459 // Lambda definitions
2460 auto flattenSyncMaps =
2461 [&syncSymMap, &syncPinMap]() -> std::vector<SCH_ITEM*>
2462 {
2463 std::vector<SCH_ITEM*> allVec;
2464
2465 for( const auto& [symRef, symbols] : syncSymMap )
2466 {
2467 for( const SCH_REFERENCE& ref : symbols )
2468 allVec.push_back( ref.GetSymbol() );
2469 }
2470
2471 for( const auto& [symRef, pinMap] : syncPinMap )
2472 {
2473 for( const auto& [padNum, pin] : pinMap )
2474 {
2475 if( pin )
2476 allVec.push_back( pin );
2477 }
2478 }
2479
2480 return allVec;
2481 };
2482
2483 auto clearSyncMaps =
2484 [&syncSymMap, &syncPinMap]()
2485 {
2486 for( auto& [symRef, symbols] : syncSymMap )
2487 symbols.clear();
2488
2489 for( auto& [reference, pins] : syncPinMap )
2490 {
2491 for( auto& [number, pin] : pins )
2492 pin = nullptr;
2493 }
2494 };
2495
2496 auto syncMapsValuesEmpty =
2497 [&syncSymMap, &syncPinMap]() -> bool
2498 {
2499 for( const auto& [symRef, symbols] : syncSymMap )
2500 {
2501 if( symbols.size() > 0 )
2502 return false;
2503 }
2504
2505 for( const auto& [symRef, pins] : syncPinMap )
2506 {
2507 for( const auto& [padNum, pin] : pins )
2508 {
2509 if( pin )
2510 return false;
2511 }
2512 }
2513
2514 return true;
2515 };
2516
2517 auto checkFocusItems =
2518 [&]( const SCH_SHEET_PATH& aSheet )
2519 {
2520 if( focusSymbol )
2521 {
2522 auto findIt = syncSymMap.find( *focusSymbol );
2523
2524 if( findIt != syncSymMap.end() )
2525 {
2526 if( findIt->second.size() > 0 )
2527 focusItemResults[aSheet].push_back( findIt->second.front().GetSymbol() );
2528 }
2529 }
2530 else if( focusPin )
2531 {
2532 auto findIt = syncPinMap.find( focusPin->first );
2533
2534 if( findIt != syncPinMap.end() )
2535 {
2536 if( findIt->second[focusPin->second] )
2537 focusItemResults[aSheet].push_back( findIt->second[focusPin->second] );
2538 }
2539 }
2540 };
2541
2542 auto makeRetForSheet =
2543 [&]( const SCH_SHEET_PATH& aSheet, SCH_ITEM* aFocusItem )
2544 {
2545 clearSyncMaps();
2546
2547 // Fill sync maps
2548 findSymbolsAndPins( allSheetsList, aSheet, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2549 std::vector<SCH_ITEM*> itemsVector = flattenSyncMaps();
2550
2551 // Add fully wanted sheets to vector
2552 for( SCH_ITEM* item : aSheet.LastScreen()->Items().OfType( SCH_SHEET_T ) )
2553 {
2554 KIID_PATH kiidPath = aSheet.Path();
2555 kiidPath.push_back( item->m_Uuid );
2556
2557 std::optional<SCH_SHEET_PATH> subsheetPath =
2558 allSheetsList.GetSheetPathByKIIDPath( kiidPath );
2559
2560 if( !subsheetPath )
2561 continue;
2562
2563 if( sheetContainsOnlyWantedItems( allSheetsList, *subsheetPath, syncSymMap,
2564 syncPinMap, fullyWantedCache ) )
2565 {
2566 itemsVector.push_back( item );
2567 }
2568 }
2569
2570 return std::make_tuple( aSheet, aFocusItem, itemsVector );
2571 };
2572
2573 if( focusOnFirst )
2574 {
2575 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2576 {
2577 clearSyncMaps();
2578
2579 findSymbolsAndPins( allSheetsList, sheetPath, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2580
2581 checkFocusItems( sheetPath );
2582 }
2583
2584 if( focusItemResults.size() > 0 )
2585 {
2586 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2587 {
2588 const std::vector<SCH_ITEM*>& items = focusItemResults[sheetPath];
2589
2590 if( !items.empty() )
2591 return makeRetForSheet( sheetPath, items.front() );
2592 }
2593 }
2594 }
2595 else
2596 {
2597 for( const SCH_SHEET_PATH& sheetPath : orderedSheets )
2598 {
2599 clearSyncMaps();
2600
2601 findSymbolsAndPins( allSheetsList, sheetPath, syncSymMap, syncPinMap, aSchematic.GetCurrentVariant() );
2602
2603 if( !syncMapsValuesEmpty() )
2604 {
2605 // Something found on sheet
2606 return makeRetForSheet( sheetPath, nullptr );
2607 }
2608 }
2609 }
2610
2611 return std::nullopt;
2612}
2613
2614
2616 const HANDLER_CONTEXT<SyncSelection>& aCtx )
2617{
2618 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SyncSelection" ) )
2619 return tl::unexpected( *headless );
2620
2621 SyncSelectionResponse response;
2622
2623 const CROSS_PROBING_SETTINGS& settings = frame()->eeconfig()->m_CrossProbing;
2624
2625 if( !settings.on_selection && aCtx.Request.context() != SyncSelectionContext::SSC_EXPLICIT )
2626 {
2627 response.set_status( CPS_DISABLED );
2628 response.set_message( "implicit selection sync disabled by user" );
2629 return response;
2630 }
2631
2632 // A request carrying no items asks for nothing to be selected, so there is nothing to find.
2633 if( aCtx.Request.items_size() == 0 )
2634 {
2635 frame()->SetSyncingSelection( true ); // recursion guard
2636
2637 frame()->GetToolManager()->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( std::nullopt, nullptr, {} );
2638
2639 frame()->SetSyncingSelection( false );
2640
2641 response.set_status( CPS_OK );
2642 return response;
2643 }
2644
2645 std::optional<std::tuple<SCH_SHEET_PATH, SCH_ITEM*, std::vector<SCH_ITEM*>>> findRet =
2647
2648 if( findRet )
2649 {
2650 auto& [sheetPath, focusItem, items] = *findRet;
2651
2652 frame()->SetSyncingSelection( true ); // recursion guard
2653
2654 frame()->GetToolManager()->GetTool<SCH_SELECTION_TOOL>()->SyncSelection( sheetPath, focusItem, items );
2655
2656 frame()->SetSyncingSelection( false );
2657
2658 if( frame()->eeconfig()->m_CrossProbing.flash_selection )
2659 {
2660 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): flash enabled, items=%zu",
2661 items.size() );
2662
2663 if( items.empty() )
2664 {
2665 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): nothing to flash" );
2666 }
2667 else
2668 {
2669 std::vector<SCH_ITEM*> itemPtrs;
2670 std::copy( items.begin(), items.end(), std::back_inserter( itemPtrs ) );
2671
2672 frame()->StartCrossProbeFlash( itemPtrs );
2673 }
2674 }
2675 else
2676 {
2677 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE): flash disabled" );
2678 }
2679 }
2680
2681 response.set_status( CPS_OK );
2682 return response;
2683}
2684
2685
2687 const HANDLER_CONTEXT<HighlightNets>& aCtx )
2688{
2689 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "HighlightNets" ) )
2690 return tl::unexpected( *headless );
2691
2692 HighlightNetsResponse response;
2693 CROSS_PROBING_SETTINGS& crossProbingSettings = frame()->eeconfig()->m_CrossProbing;
2694
2696 || aCtx.ClientName == KiwayClientName )
2697 {
2698 if( !crossProbingSettings.auto_highlight )
2699 {
2700 response.set_status( CPS_DISABLED );
2701 return response;
2702 }
2703 }
2704
2705 wxString net;
2706
2707 if( aCtx.Request.net_name_size() > 0 )
2708 net = wxString::FromUTF8( aCtx.Request.net_name( 0 ) );
2709
2711
2712 response.set_status( CPS_OK );
2713 return response;
2714}
2715
2716
2717
2720{
2721 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() );
2722
2723 if( !documentValidation )
2724 return tl::unexpected( documentValidation.error() );
2725
2726 SCH_SHEET_PATH path = m_context->GetCurrentSheet().value_or( *schematic()->Hierarchy().begin() );
2727
2728 if( aCtx.Request.document().has_sheet_path() )
2729 {
2730 KIID_PATH kiidPath = UnpackSheetPath( aCtx.Request.document().sheet_path() );
2731
2732 if( std::optional<SCH_SHEET_PATH> resolvedPath = schematic()->Hierarchy().GetSheetPathByKIIDPath( kiidPath ) )
2733 {
2734 path = *resolvedPath;
2735 }
2736 }
2737
2738 ExpandTextVariablesResponse reply;
2739
2740 std::function<bool( wxString* )> textResolver =
2741 [&]( wxString* token ) -> bool
2742 {
2743 return schematic()->ResolveTextVar( &path, token, 0 );
2744 };
2745
2746 PROJECT& project = m_context->Prj();
2747
2748 for( const std::string& textMsg : aCtx.Request.text() )
2749 {
2750 wxString text = ExpandTextVars( wxString::FromUTF8( textMsg ), &textResolver, INTERNAL );
2751
2752 if( aCtx.Request.expand_env_vars() )
2754
2755 reply.add_text( text.ToUTF8() );
2756 }
2757
2758 return reply;
2759}
2760
2761
2763{
2764 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2765 return tl::unexpected( documentValidation.error() );
2766
2767 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2768 return tl::unexpected( *busy );
2769
2770 VariantsResponse response;
2771
2772 response.mutable_document()->CopyFrom( aCtx.Request.document() );
2773
2774 for( const wxString& name : schematic()->GetVariantNames() )
2775 {
2776 types::DesignVariant* var = response.add_variants();
2777 var->set_name( name.ToUTF8() );
2778 var->set_description( schematic()->GetVariantDescription( name ).ToUTF8() );
2779 }
2780
2781 return response;
2782}
2783
2784
2786{
2787 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2788 return tl::unexpected( documentValidation.error() );
2789
2790 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2791 return tl::unexpected( *busy );
2792
2793 SCHEMATIC* schematic = this->schematic();
2794 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2795
2796 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || schematic->HasVariant( name ) )
2797 {
2798 ApiResponseStatus e;
2799 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2800 e.set_error_message( fmt::format( "'{}' is not a usable new variant name", aCtx.Request.name() ) );
2801 return tl::unexpected( e );
2802 }
2803
2805
2806 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
2807 name = *canonical;
2808
2809 schematic->AddVariant( name );
2810
2811 if( aCtx.Request.has_description() )
2812 schematic->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
2813
2814 onModified();
2815
2816 if( m_frame )
2817 {
2818 PICKED_ITEMS_LIST* undoCmd = new PICKED_ITEMS_LIST();
2819
2820 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
2821 undoCmd->SetDescription( _( "Add Variant" ) );
2822 frame()->PushCommandToUndoList( undoCmd );
2823
2824 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2825 }
2826
2827 return Empty();
2828}
2829
2830
2832{
2833 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2834 return tl::unexpected( documentValidation.error() );
2835
2836 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2837 return tl::unexpected( *busy );
2838
2839 SCHEMATIC* schematic = this->schematic();
2840 wxString name = wxString::FromUTF8( aCtx.Request.name() );
2841
2842 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
2843 {
2844 ApiResponseStatus e;
2845 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2846 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
2847 return tl::unexpected( e );
2848 }
2849
2850 if( !schematic->HasVariant( name ) )
2851 {
2852 ApiResponseStatus e;
2853 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2854 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
2855 return tl::unexpected( e );
2856 }
2857
2859 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2860 bool pushedCommit = false;
2861
2862 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
2863 name = *canonical;
2864
2865 schematic->DeleteVariant( name, &commit );
2866
2867 if( !commit.Empty() )
2868 {
2869 commit.Push();
2870 pushedCommit = true;
2871 }
2872
2873 onModified();
2874
2875 if( m_frame )
2876 {
2877 PICKED_ITEMS_LIST* undoCmd = pushedCommit ? frame()->PopCommandFromUndoList()
2878 : new PICKED_ITEMS_LIST();
2879
2880 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
2881 undoCmd->SetDescription( _( "Delete Variant" ) );
2882 frame()->PushCommandToUndoList( undoCmd );
2883
2884 if( frame()->Schematic().GetCurrentVariant().CmpNoCase( name ) == 0 )
2885 frame()->SetCurrentVariant( wxEmptyString );
2886
2887 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2888 frame()->GetCanvas()->Refresh();
2889 }
2890
2891 return Empty();
2892}
2893
2894
2896{
2897 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2898 return tl::unexpected( documentValidation.error() );
2899
2900 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2901 return tl::unexpected( *busy );
2902
2903 SCHEMATIC* schematic = this->schematic();
2904 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
2905 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
2906
2907 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2908 {
2909 ApiResponseStatus e;
2910 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2911 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
2912 return tl::unexpected( e );
2913 }
2914
2915 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2916 {
2917 ApiResponseStatus e;
2918 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2919 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
2920 return tl::unexpected( e );
2921 }
2922
2923 if( !schematic->HasVariant( oldName ) )
2924 {
2925 ApiResponseStatus e;
2926 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2927 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
2928 return tl::unexpected( e );
2929 }
2930
2931 if( schematic->HasVariant( newName ) )
2932 {
2933 ApiResponseStatus e;
2934 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2935 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
2936 return tl::unexpected( e );
2937 }
2938
2939 bool isCurrent = frame() ? frame()->Schematic().GetCurrentVariant().CmpNoCase( oldName ) == 0 : false;
2940
2942 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
2943 bool pushedCommit = false;
2944
2945 if( std::optional<wxString> canonicalOld = FindVariantNoCase( schematic, oldName ) )
2946 oldName = *canonicalOld;
2947
2948 schematic->RenameVariant( oldName, newName, &commit );
2949
2950 if( !commit.Empty() )
2951 {
2952 commit.Push();
2953 pushedCommit = true;
2954 }
2955
2956 onModified();
2957
2958 if( m_frame )
2959 {
2960 PICKED_ITEMS_LIST* undoCmd = pushedCommit ? frame()->PopCommandFromUndoList()
2961 : new PICKED_ITEMS_LIST();
2962
2963 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
2964 undoCmd->SetDescription( _( "Rename Variant" ) );
2965 frame()->PushCommandToUndoList( undoCmd );
2966
2967 // This will zero out the selection if it happened to point to the old name
2968 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
2969
2970 if( isCurrent )
2971 {
2972 // This will reset the variant-selection-ctrl's selection, but only if the
2973 // names have already been updated to include the new name
2974 frame()->SetCurrentVariant( newName );
2975 }
2976 }
2977
2978 return Empty();
2979}
2980
2981
2983{
2984 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
2985 return tl::unexpected( documentValidation.error() );
2986
2987 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2988 return tl::unexpected( *busy );
2989
2990 SCHEMATIC* schematic = this->schematic();
2991 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
2992 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
2993
2994 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2995 {
2996 ApiResponseStatus e;
2997 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2998 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
2999 return tl::unexpected( e );
3000 }
3001
3002 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3003 {
3004 ApiResponseStatus e;
3005 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3006 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
3007 return tl::unexpected( e );
3008 }
3009
3010 if( !schematic->HasVariant( oldName ) )
3011 {
3012 ApiResponseStatus e;
3013 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3014 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
3015 return tl::unexpected( e );
3016 }
3017
3018 if( schematic->HasVariant( newName ) )
3019 {
3020 ApiResponseStatus e;
3021 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3022 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
3023 return tl::unexpected( e );
3024 }
3025
3027 SCH_COMMIT commit( m_frame ? frame()->GetToolManager() : toolManager() );
3028 bool pushedCommit = false;
3029
3030 if( std::optional<wxString> canonicalOld = FindVariantNoCase( schematic, oldName ) )
3031 oldName = *canonicalOld;
3032
3033 schematic->CopyVariant( oldName, newName, &commit );
3034
3035 if( aCtx.Request.has_new_description() )
3036 schematic->SetVariantDescription( newName, wxString::FromUTF8( aCtx.Request.new_description() ) );
3037
3038 if( !commit.Empty() )
3039 {
3040 commit.Push();
3041 pushedCommit = true;
3042 }
3043
3044 onModified();
3045
3046 if( m_frame )
3047 {
3048 PICKED_ITEMS_LIST* undoCmd = pushedCommit ? frame()->PopCommandFromUndoList()
3049 : new PICKED_ITEMS_LIST();
3050
3051 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
3052 undoCmd->SetDescription( _( "Copy Variant" ) );
3053 frame()->PushCommandToUndoList( undoCmd );
3054
3055 frame()->UpdateVariantSelectionCtrl( frame()->Schematic().GetVariantNamesForUI() );
3056 }
3057
3058 return Empty();
3059}
3060
3061
3063{
3064 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3065 return tl::unexpected( documentValidation.error() );
3066
3067 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3068 return tl::unexpected( *busy );
3069
3070 SCHEMATIC* schematic = this->schematic();
3071 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3072
3073 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || !schematic->HasVariant( name ) )
3074 {
3075 ApiResponseStatus e;
3076 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3077 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3078 return tl::unexpected( e );
3079 }
3080
3082
3083 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
3084 name = *canonical;
3085
3086 schematic->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
3087
3088 if( m_frame )
3089 {
3090 PICKED_ITEMS_LIST* undoCmd = new PICKED_ITEMS_LIST();
3091
3092 undoCmd->PushItem( ITEM_PICKER( frame()->GetScreen(), undoItem, UNDO_REDO::VARIANTS ) );
3093 undoCmd->SetDescription( _( "Edit Variant Description" ) );
3094 frame()->PushCommandToUndoList( undoCmd );
3095 }
3096
3097 return Empty();
3098}
3099
3100
3102{
3103 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3104 return tl::unexpected( documentValidation.error() );
3105
3106 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3107 return tl::unexpected( *busy );
3108
3109 SCHEMATIC* schematic = this->schematic();
3110
3111 if( aCtx.Request.has_name() && !aCtx.Request.name().empty() )
3112 {
3113 if( wxString name = wxString::FromUTF8( aCtx.Request.name() ); !schematic->HasVariant( name ) )
3114 {
3115 ApiResponseStatus e;
3116 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3117 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3118 return tl::unexpected( e );
3119 }
3120 }
3121
3122 wxString name = aCtx.Request.has_name() ? wxString::FromUTF8( aCtx.Request.name() ) : wxString();
3123
3124 if( std::optional<wxString> canonical = FindVariantNoCase( schematic, name ) )
3125 name = *canonical;
3126
3127 if( m_frame )
3129 else
3130 schematic->SetCurrentVariant( name );
3131
3132 return Empty();
3133}
3134
3135
3138{
3139 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3140 return tl::unexpected( documentValidation.error() );
3141
3142 CurrentVariantResponse response;
3143
3144 if( wxString current = schematic()->GetCurrentVariant(); !current.IsEmpty() )
3145 response.set_name( current.ToUTF8() );
3146
3147 return response;
3148}
3149
3150
3153{
3154 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
3155 {
3156 ApiResponseStatus e;
3157 e.set_status( ApiStatusCode::AS_UNHANDLED );
3158 return tl::unexpected( e );
3159 }
3160
3161 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3162 return tl::unexpected( *busy );
3163
3164 LIB_ID libId = UnpackLibId( aCtx.Request.lib_id() );
3165
3166 if( !libId.IsValid() )
3167 {
3168 ApiResponseStatus e;
3169 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3170 e.set_error_message( "lib_id must specify both a library nickname and an entry name" );
3171 return tl::unexpected( e );
3172 }
3173
3175 LIB_SYMBOL* libSymbol = adapter->LoadSymbol( libId );
3176
3177 if( !libSymbol )
3178 {
3179 ApiResponseStatus e;
3180 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3181 e.set_error_message( fmt::format( "symbol '{}' not found", libId.Format().wx_str() ) );
3182 return tl::unexpected( e );
3183 }
3184
3185 SCH_SHEET_LIST hierarchy = schematic()->Hierarchy();
3186 SCH_SHEET_PATH targetPath = m_context->GetCurrentSheet().value_or( *hierarchy.begin() );
3187
3188 if( aCtx.Request.header().document().has_sheet_path() )
3189 {
3190 KIID_PATH kp = UnpackSheetPath( aCtx.Request.header().document().sheet_path() );
3191
3192 if( std::optional<SCH_SHEET_PATH> path = hierarchy.GetSheetPathByKIIDPath( kp ) )
3193 {
3194 targetPath = *path;
3195 }
3196 else
3197 {
3198 ApiResponseStatus e;
3199 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3200 e.set_error_message( fmt::format( "the requested sheet path {} is not valid for this schematic",
3201 kp.AsString().ToStdString() ) );
3202 return tl::unexpected( e );
3203 }
3204 }
3205
3206 SCH_SCREEN* targetScreen = targetPath.LastScreen();
3207
3208 int unit = aCtx.Request.has_unit() ? aCtx.Request.unit().unit() : 1;
3209
3210 if( unit < 1 || ( libSymbol->GetUnitCount() > 0 && unit > libSymbol->GetUnitCount() ) )
3211 {
3212 ApiResponseStatus e;
3213 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3214 e.set_error_message( fmt::format( "unit {} is out of range for symbol '{}' ({} units)", unit,
3215 libId.Format().wx_str(), libSymbol->GetUnitCount() ) );
3216 return tl::unexpected( e );
3217 }
3218
3219 VECTOR2I position = UnpackVector2( aCtx.Request.position(), schIUScale );
3220
3221 std::unique_ptr<SCH_SYMBOL> symbol(
3222 std::make_unique<SCH_SYMBOL>( *libSymbol, libId, &targetPath, unit, 0, position ) );
3223
3224 if( aCtx.Request.has_orientation() )
3225 symbol->SetOrientationProp( FromProtoEnum<SYMBOL_ORIENTATION_PROP>( aCtx.Request.orientation() ) );
3226
3227 if( !aCtx.Request.reference().empty() )
3228 {
3229 symbol->SetRef( &targetPath, wxString::FromUTF8( aCtx.Request.reference() ) );
3230 }
3231 else
3232 {
3233 SCH_REFERENCE newReference( symbol.get(), targetPath );
3234 SCH_REFERENCE_LIST existingRefs;
3235 hierarchy.GetSymbols( existingRefs, SYMBOL_FILTER_NON_POWER );
3236
3237 bool annotate = newReference.AlwaysAnnotate();
3238
3239 if( SCH_EDIT_FRAME* frame = this->frame() )
3240 annotate |= frame->eeconfig()->m_AnnotatePanel.automatic;
3241
3242 if( annotate )
3243 {
3244 existingRefs.SortByReferenceOnly();
3245
3246 SCH_REFERENCE_LIST refs;
3247 refs.AddItem( newReference );
3248 refs.SetRefDesTracker( schematic()->Settings().m_refDesTracker );
3249 refs.ReannotateByOptions( static_cast<ANNOTATE_ORDER_T>( schematic()->Settings().m_AnnotateSortOrder ),
3250 static_cast<ANNOTATE_ALGO_T>( schematic()->Settings().m_AnnotateMethod ),
3251 schematic()->Settings().m_AnnotateStartNum, existingRefs, false, &hierarchy );
3252 refs.UpdateAnnotation();
3253 }
3254 }
3255
3256 if( SCH_EDIT_FRAME* frame = this->frame() )
3257 {
3258 if( frame->eeconfig()->m_AutoplaceFields.enable )
3259 symbol->AutoplaceFields( nullptr, AUTOPLACE_AUTO );
3260 }
3261
3262 SCH_COMMIT* commit = static_cast<SCH_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
3263 SCH_SYMBOL* placed = symbol.release();
3264 commit->Add( placed, targetScreen );
3265
3266 if( !m_activeClients.contains( aCtx.ClientName ) )
3267 pushCurrentCommit( aCtx.ClientName, _( "Placed symbol via API" ) );
3268
3269 PlaceFromLibraryResponse response;
3270 response.mutable_header()->CopyFrom( aCtx.Request.header() );
3271
3272 kiapi::schematic::types::SchematicSymbolInstance packed;
3273
3274 if( PackSymbol( &packed, placed, targetPath ) )
3275 response.mutable_item()->PackFrom( packed );
3276
3277 return response;
3278}
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)
void ApplySymbolInstance(SCH_SYMBOL *aSymbol, const kiapi::schematic::types::SchematicSymbolInstance &aInput, const SCH_SHEET_PATH &aPath, SCHEMATIC *aSchematic)
Apply placement-specific data to an aSymbol at aPath: reference, unit, and the per-placement attribut...
tl::expected< bool, ApiResponseStatus > UnpackSheet(SCH_SHEET *aOutput, const kiapi::schematic::types::SheetSymbol &aInput)
Unpack the every placement data from the input.
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 PackSymbol(kiapi::schematic::types::SchematicSymbolInstance *aOutput, const SCH_SYMBOL *aInput, const SCH_SHEET_PATH &aPath)
void ApplySheetInstance(SCH_SHEET *aSheet, const kiapi::schematic::types::SheetSymbol &aInput, const SCH_SHEET_PATH &aParentPath, SCHEMATIC *aSchematic)
Apply the placement data in a sheet message to aSheet: page number and the variants the message carri...
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:157
const std::vector< JOB_OUTPUT > & GetOutputs()
Definition job.h:215
const std::string & GetType() const
Definition job.h:195
wxString AsString() const
Definition kiid.cpp:423
Definition kiid.h:46
A minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the same KiCad...
Definition kiway.h:348
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:740
@ FACE_SCH
eeschema DSO
Definition kiway.h: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:183
virtual const wxString GetProjectName() const
Return the short name of the project.
Definition project.cpp:195
Holds all the data relating to one schematic.
Definition schematic.h:149
SCHEMATIC_SETTINGS & Settings() const
SCH_SCREEN * GetCurrentScreen() const
Definition schematic.h:314
SCH_ITEM * ResolveItem(const KIID &aID, SCH_SHEET_PATH *aPathOut=nullptr, bool aAllowNullptrReturn=false) const
Definition schematic.h:194
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:319
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:304
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:545
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:386
void AddInstance(const SCH_SHEET_INSTANCE &aInstance)
SCH_SCREEN * GetScreen() const
Definition sch_sheet.h:145
wxString GetShownName(RESOLUTION_CONTEXT aContext) const
Definition sch_sheet.h:138
void SetScreen(SCH_SCREEN *aScreen)
Set the SCH_SCREEN associated with this sheet to aScreen.
std::vector< SCH_SHEET_PIN * > & GetPins()
Definition sch_sheet.h:243
Schematic symbol object.
Definition sch_symbol.h:73
void SetLibId(const LIB_ID &aName)
std::vector< std::unique_ptr< SCH_PIN > > & GetRawPins()
Definition sch_symbol.h:678
bool UseLibIdLookup() const
Definition sch_symbol.h:180
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:163
void SetSchSymbolLibraryName(const wxString &aName)
The name of the symbol in the schematic library symbol list.
Definition sch_symbol.h:178
std::unique_ptr< LIB_SYMBOL > & GetLibSymbolRef()
Definition sch_symbol.h:182
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)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:94
#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