KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_pcb.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 Jon Evans <[email protected]>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <magic_enum.hpp>
22#include <memory>
23#include <pgm_base.h>
24#include <properties/property.h>
25#include <ranges>
26
27#include <common.h>
28#include <fmt.h>
29#include <api/api_handler_pcb.h>
30#include <api/api_pcb_utils.h>
31#include <api/api_enums.h>
32#include <api/api_server.h>
33#include <api/api_utils.h>
34#include <api/common/commands/library_commands.pb.h>
36#include <wx/log.h>
37#include <base_screen.h>
38#include <board_commit.h>
41#include <core/kicad_algo.h>
42#include <footprint.h>
44#include <kicad_clipboard.h>
45#include <netinfo.h>
46#include <pad.h>
47#include <pcb_draw_panel_gal.h>
48#include <pcb_edit_frame.h>
49#include <pcb_group.h>
50#include <pcb_reference_image.h>
51#include <pcb_shape.h>
52#include <pcb_text.h>
53#include <pcb_textbox.h>
54#include <pcb_track.h>
55#include <pcbnew_id.h>
56#include <pcb_marker.h>
57#include <pcb_point.h>
58#include <kiway.h>
59#include <drc/drc_item.h>
72#include <project_pcb.h>
78#include <pcb_plot_params.h>
80#include <jobs/job_pcb_render.h>
81#include <layer_ids.h>
84#include <project.h>
85#include <tool/actions.h>
86#include <string_utils.h>
87#include <tool/tool_manager.h>
88#include <tools/pcb_actions.h>
92#include <zone.h>
93#include <zone_filler.h>
94
95#include <api/common/types/base_types.pb.h>
96#include <api/board/board_rules.pb.h>
98#include <google/protobuf/util/json_util.h>
100#include <drc/drc_rule_parser.h>
104#include <trace_helpers.h>
105#include <wx/ffile.h>
106
107using namespace kiapi::common::commands;
108using namespace kiapi::board::commands;
109using types::CommandStatus;
110using types::DocumentType;
111using types::ItemRequestStatus;
112
113
118
119
120API_HANDLER_PCB::API_HANDLER_PCB( std::shared_ptr<PCB_CONTEXT> aContext, PCB_EDIT_FRAME* aFrame ) :
121 API_HANDLER_BOARD( std::move( aContext ), aFrame )
122{
129
131
144
153
162
172
205
208
212
215}
216
217
219{
220 return static_cast<PCB_EDIT_FRAME*>( m_frame );
221}
222
223
226{
227 if( aCtx.Request.type() != DocumentType::DOCTYPE_PCB )
228 {
229 ApiResponseStatus e;
230 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
231 e.set_status( ApiStatusCode::AS_UNHANDLED );
232 return tl::unexpected( e );
233 }
234
235 GetOpenDocumentsResponse response;
236 common::types::DocumentSpecifier doc;
237
238 wxFileName fn( pcbContext()->GetCurrentFileName() );
239
240 doc.set_type( DocumentType::DOCTYPE_PCB );
241 doc.set_board_filename( fn.GetFullName() );
242
243 doc.mutable_project()->set_name( project().GetProjectName().ToStdString() );
244 doc.mutable_project()->set_path( project().GetProjectDirectory().ToStdString() );
245
246 response.mutable_documents()->Add( std::move( doc ) );
247 return response;
248}
249
250
253{
254 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
255 return tl::unexpected( documentValidation.error() );
256
257 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
258 return tl::unexpected( *busy );
259
260 if( board()->GetFileName().IsEmpty() )
261 {
262 ApiResponseStatus e;
263 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
264 e.set_error_message( "the board is untitled; use SaveDocumentAs to save it to a new file" );
265 return tl::unexpected( e );
266 }
267
268 if( !pcbContext()->SaveBoard() )
269 {
270 ApiResponseStatus e;
271 e.set_status( ApiStatusCode::AS_INTERNAL_ERROR );
272 e.set_error_message( "board could not be saved" );
273 return tl::unexpected( e );
274 }
275
276 return Empty();
277}
278
279
281{
282 if( Pgm().IsGUI() )
283 {
284 ApiResponseStatus e;
285 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
286 e.set_error_message( "SaveDocumentAs is not available in GUI mode" );
287 return tl::unexpected( e );
288 }
289
290 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
291 return tl::unexpected( documentValidation.error() );
292
293 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
294 return tl::unexpected( *busy );
295
296 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
297
298 if( pathStr.IsEmpty() )
299 {
300 ApiResponseStatus e;
301 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
302 e.set_error_message( "path is required" );
303 return tl::unexpected( e );
304 }
305
306 wxFileName oldBoardFile( pcbContext()->GetCurrentFileName() );
307
308 if( !pcbContext()->SaveBoardAs( pathStr ) )
309 {
310 ApiResponseStatus e;
311 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
312 e.set_error_message( fmt::format( "failed to save board to '{}'", pathStr.ToStdString() ) );
313 return tl::unexpected( e );
314 }
315
316 if( KICAD_API_SERVER* server = apiServer() )
317 {
318 wxFileName newBoardFile( pathStr );
319 newBoardFile.SetExt( FILEEXT::KiCadPcbFileExtension );
320
321 server->NotifyDocumentRenamed( thisDocumentType(), oldBoardFile.GetFullPath(), newBoardFile.GetFullPath() );
322 }
323
324 return Empty();
325}
326
327
330{
331 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
332 return tl::unexpected( documentValidation.error() );
333
334 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
335 return tl::unexpected( *busy );
336
337 wxFileName boardPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
338
339 if( !boardPath.IsOk() || !boardPath.IsDirWritable() )
340 {
341 ApiResponseStatus e;
342 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
343 e.set_error_message( fmt::format( "save path '{}' could not be opened",
344 boardPath.GetFullPath().ToStdString() ) );
345 return tl::unexpected( e );
346 }
347
348 if( boardPath.FileExists()
349 && ( !boardPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
350 {
351 ApiResponseStatus e;
352 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
353 e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
354 boardPath.GetFullPath().ToStdString() ) );
355 return tl::unexpected( e );
356 }
357
358 if( boardPath.GetExt() != FILEEXT::KiCadPcbFileExtension )
359 {
360 ApiResponseStatus e;
361 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
362 e.set_error_message( fmt::format( "save path '{}' must have a kicad_pcb extension",
363 boardPath.GetFullPath().ToStdString() ) );
364 return tl::unexpected( e );
365 }
366
367 BOARD* board = this->board();
368
369 if( board->GetFileName().Matches( boardPath.GetFullPath() ) )
370 {
371 if( !pcbContext()->SaveBoard() )
372 {
373 ApiResponseStatus e;
374 e.set_status( ApiStatusCode::AS_INTERNAL_ERROR );
375 e.set_error_message( "board could not be saved" );
376 return tl::unexpected( e );
377 }
378
379 return Empty();
380 }
381
382 bool includeProject = true;
383
384 if( aCtx.Request.has_options() )
385 includeProject = aCtx.Request.options().include_project();
386
387 if( !pcbContext()->SavePcbCopy( boardPath.GetFullPath(), includeProject, /* aHeadless = */ true ) )
388 {
389 ApiResponseStatus e;
390 e.set_status( ApiStatusCode::AS_INTERNAL_ERROR );
391 e.set_error_message( fmt::format( "board could not be saved to '{}'",
392 boardPath.GetFullPath().ToStdString() ) );
393 return tl::unexpected( e );
394 }
395
396 return Empty();
397}
398
399
402{
403 // Validate first so a request meant for another editor's document is not captured here
404 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
405 return tl::unexpected( documentValidation.error() );
406
407 // Reloading frees every item, so refuse while any client transaction is open; the staged
408 // commit would otherwise dangle
409 if( !m_commits.empty() )
410 {
411 ApiResponseStatus e;
412 e.set_status( ApiStatusCode::AS_BUSY );
413 e.set_error_message( "cannot revert while a commit is open" );
414 return tl::unexpected( e );
415 }
416
417 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
418 return tl::unexpected( *busy );
419
420 if( !pcbContext()->RevertToSaved() )
421 {
422 ApiResponseStatus e;
423 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
424 e.set_error_message( "could not revert: there is no saved file on disk to revert to" );
425 return tl::unexpected( e );
426 }
427
428 return Empty();
429}
430
431
432tl::expected<bool, ApiResponseStatus> API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
433{
434 if( aDocument.type() != DocumentType::DOCTYPE_PCB )
435 {
436 ApiResponseStatus e;
437 e.set_status( ApiStatusCode::AS_UNHANDLED );
438 return tl::unexpected( e );
439 }
440
441 wxFileName fn( pcbContext()->GetCurrentFileName() );
442
443 if( aDocument.board_filename().compare( fn.GetFullName() ) != 0 )
444 {
445 ApiResponseStatus e;
446 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
447 e.set_error_message( fmt::format( "the requested document {} is not open",
448 aDocument.board_filename() ) );
449 return tl::unexpected( e );
450 }
451
452 return true;
453}
454
455
456// Board types that are directly retrievable by GetItems
477
478
480{
481 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
482 {
483 ApiResponseStatus e;
484 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
485 e.set_status( ApiStatusCode::AS_UNHANDLED );
486 return tl::unexpected( e );
487 }
488
489 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
490 return tl::unexpected( *busy );
491
492 GetItemsResponse response;
493
494 BOARD* board = this->board();
495 std::vector<BOARD_ITEM*> items;
496 std::set<KICAD_T> typesRequested, typesInserted;
497 bool handledAnything = false;
498
499 std::vector<KICAD_T> requestedTypes = parseRequestedItemTypes( aCtx.Request.types() );
500
501 if( aCtx.Request.types().empty() )
502 requestedTypes.assign( s_allowedBoardTypes.begin(), s_allowedBoardTypes.end() );
503
504 for( KICAD_T type : requestedTypes )
505 {
506 typesRequested.emplace( type );
507
508 if( typesInserted.count( type ) )
509 continue;
510
511 switch( type )
512 {
513 case PCB_TRACE_T:
514 case PCB_ARC_T:
515 case PCB_VIA_T:
516 handledAnything = true;
517 std::copy( board->Tracks().begin(), board->Tracks().end(),
518 std::back_inserter( items ) );
519 typesInserted.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
520 break;
521
522 case PCB_PAD_T:
523 {
524 handledAnything = true;
525
526 for( FOOTPRINT* fp : board->Footprints() )
527 {
528 std::copy( fp->Pads().begin(), fp->Pads().end(),
529 std::back_inserter( items ) );
530 }
531
532 typesInserted.insert( PCB_PAD_T );
533 break;
534 }
535
536 case PCB_FOOTPRINT_T:
537 {
538 handledAnything = true;
539
540 std::copy( board->Footprints().begin(), board->Footprints().end(),
541 std::back_inserter( items ) );
542
543 typesInserted.insert( PCB_FOOTPRINT_T );
544 break;
545 }
546
547 case PCB_SHAPE_T:
548 case PCB_TABLE_T:
549 case PCB_TEXT_T:
550 case PCB_TEXTBOX_T:
551 case PCB_BARCODE_T:
553 case PCB_GRID_ITEM_T:
554 {
555 handledAnything = true;
556 bool inserted = false;
557
558 for( BOARD_ITEM* item : board->Drawings() )
559 {
560 if( item->Type() == type )
561 {
562 items.emplace_back( item );
563 inserted = true;
564 }
565 }
566
567 if( inserted )
568 typesInserted.insert( type );
569
570 break;
571 }
572
573 case PCB_DIMENSION_T:
574 {
575 handledAnything = true;
576 bool inserted = false;
577
578 for( BOARD_ITEM* item : board->Drawings() )
579 {
580 switch (item->Type()) {
582 case PCB_DIM_CENTER_T:
583 case PCB_DIM_RADIAL_T:
585 case PCB_DIM_LEADER_T:
586 items.emplace_back( item );
587 inserted = true;
588 break;
589 default:
590 break;
591 }
592 }
593 // we have to add the dimension subtypes to the requested to get them out
595
596 if( inserted )
598
599 break;
600 }
601
602 case PCB_ZONE_T:
603 {
604 handledAnything = true;
605
606 std::copy( board->Zones().begin(), board->Zones().end(),
607 std::back_inserter( items ) );
608
609 typesInserted.insert( PCB_ZONE_T );
610 break;
611 }
612
613 case PCB_GROUP_T:
614 {
615 handledAnything = true;
616
617 std::copy( board->Groups().begin(), board->Groups().end(),
618 std::back_inserter( items ) );
619
620 typesInserted.insert( PCB_GROUP_T );
621 break;
622 }
623
624 case PCB_POINT_T:
625 {
626 handledAnything = true;
627 std::copy( board->Points().begin(), board->Points().end(), std::back_inserter( items ) );
628 typesInserted.insert( PCB_POINT_T );
629 break;
630 }
631
632 case PCB_GENERATOR_T:
633 {
634 handledAnything = true;
635 std::ranges::copy( board->Generators(), std::back_inserter( items ) );
636 typesInserted.insert( PCB_GENERATOR_T );
637 break;
638 }
639
640 case PCB_CONSTRAINT_T:
641 {
642 handledAnything = true;
643
644 std::copy( board->Constraints().begin(), board->Constraints().end(),
645 std::back_inserter( items ) );
646
647 typesInserted.insert( PCB_CONSTRAINT_T );
648 break;
649 }
650
651 default:
652 break;
653 }
654 }
655
656 if( !handledAnything )
657 {
658 ApiResponseStatus e;
659 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
660 e.set_error_message( "none of the requested types are valid for a Board object" );
661 return tl::unexpected( e );
662 }
663
664 for( const BOARD_ITEM* item : items )
665 {
666 if( !typesRequested.count( item->Type() ) )
667 continue;
668
669 google::protobuf::Any itemBuf;
670 item->Serialize( itemBuf );
671 response.mutable_items()->Add( std::move( itemBuf ) );
672 }
673
674 response.set_status( ItemRequestStatus::IRS_OK );
675 return response;
676}
677
678
681{
682 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
683 return tl::unexpected( documentValidation.error() );
684
685 if( aCtx.Request.copper_layer_count() < 2 || aCtx.Request.copper_layer_count() % 2 != 0 )
686 {
687 ApiResponseStatus e;
688 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
689 e.set_error_message( "copper_layer_count must be an even number of at least 2" );
690 return tl::unexpected( e );
691 }
692
693 if( aCtx.Request.copper_layer_count() > MAX_CU_LAYERS )
694 {
695 ApiResponseStatus e;
696 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
697 e.set_error_message( fmt::format( "copper_layer_count must be below %d", MAX_CU_LAYERS ) );
698 return tl::unexpected( e );
699 }
700
701 int copperLayerCount = static_cast<int>( aCtx.Request.copper_layer_count() );
702 LSET enabled = board::UnpackLayerSet( aCtx.Request.layers() );
703
704 // Sanitize the input
705 enabled |= LSET( { Edge_Cuts, Margin, F_CrtYd, B_CrtYd } );
706 enabled &= ~LSET::AllCuMask();
707 enabled |= LSET::AllCuMask( copperLayerCount );
708
709 BOARD* board = this->board();
710
711 LSET previousEnabled = board->GetEnabledLayers();
712 LSET changedLayers = enabled ^ previousEnabled;
713
714 board->SetEnabledLayers( enabled );
715 board->SetVisibleLayers( board->GetVisibleLayers() | changedLayers );
716
717 LSEQ removedLayers;
718
719 for( PCB_LAYER_ID layer_id : previousEnabled )
720 {
721 if( !enabled[layer_id] && board->HasItemsOnLayer( layer_id ) )
722 removedLayers.push_back( layer_id );
723 }
724
725 bool modified = false;
726
727 if( !removedLayers.empty() )
728 {
730
731 for( PCB_LAYER_ID layer_id : removedLayers )
732 modified |= board->RemoveAllItemsOnLayer( layer_id );
733 }
734
735 if( enabled != previousEnabled || modified )
736 onModified();
737
738 BoardEnabledLayersResponse response;
739
740 response.set_copper_layer_count( copperLayerCount );
741 board::PackLayerSet( *response.mutable_layers(), enabled );
742
743 return response;
744}
745
746
749{
750 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
751 return tl::unexpected( documentValidation.error() );
752
753 common::types::EmbeddedFiles response;
754 board::PackEmbeddedFiles( response, *board()->GetEmbeddedFiles() );
755 return response;
756}
757
758
759HANDLER_RESULT<Empty> unpackEmbeddedFiles( EMBEDDED_FILES& aOutput, const common::types::EmbeddedFiles& aProto )
760{
761 if( !board::UnpackEmbeddedFiles( aOutput, aProto ) )
762 {
763 ApiResponseStatus e;
764 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
765 e.set_error_message( "embedded file validation failed" );
766 return tl::unexpected( e );
767 }
768
769 return Empty();
770}
771
772
774{
775 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
776 return tl::unexpected( documentValidation.error() );
777
778 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
779 return tl::unexpected( *busy );
780
781 EMBEDDED_FILES files;
783
784 if( !result.has_value() )
785 return result;
786
787 EMBEDDED_FILES* boardFiles = board()->GetEmbeddedFiles();
788
789 for( const std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>& file : files.EmbeddedFileMap() | std::views::values )
790 {
791 auto copy = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>( *file );
792 boardFiles->AddFile( copy );
793 }
794
795 onModified();
796 return result;
797}
798
799
801{
802 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
803 return tl::unexpected( documentValidation.error() );
804
805 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
806 return tl::unexpected( *busy );
807
808 HANDLER_RESULT<Empty> result = unpackEmbeddedFiles( *board()->GetEmbeddedFiles(),
809 aCtx.Request.files() );
810
811 if( !result.has_value() )
812 return result;
813
814 onModified();
815 return result;
816}
817
818
821{
822 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
823
824 if( !documentValidation )
825 return tl::unexpected( documentValidation.error() );
826
828 BoardDesignRulesResponse response;
829 kiapi::board::BoardDesignRules* rules = response.mutable_rules();
830
831 kiapi::board::MinimumConstraints* constraints = rules->mutable_constraints();
832
833 constraints->mutable_min_clearance()->set_value_nm( bds.m_MinClearance );
834 constraints->mutable_min_groove_width()->set_value_nm( bds.m_MinGrooveWidth );
835 constraints->mutable_min_connection_width()->set_value_nm( bds.m_MinConn );
836 constraints->mutable_min_track_width()->set_value_nm( bds.m_TrackMinWidth );
837 constraints->mutable_min_via_annular_width()->set_value_nm( bds.m_ViasMinAnnularWidth );
838 constraints->mutable_min_via_size()->set_value_nm( bds.m_ViasMinSize );
839 constraints->mutable_min_through_drill()->set_value_nm( bds.m_MinThroughDrill );
840 constraints->mutable_min_microvia_size()->set_value_nm( bds.m_MicroViasMinSize );
841 constraints->mutable_min_microvia_drill()->set_value_nm( bds.m_MicroViasMinDrill );
842 constraints->mutable_copper_edge_clearance()->set_value_nm( bds.m_CopperEdgeClearance );
843 constraints->mutable_hole_clearance()->set_value_nm( bds.m_HoleClearance );
844 constraints->mutable_hole_to_hole_min()->set_value_nm( bds.m_HoleToHoleMin );
845 constraints->mutable_silk_clearance()->set_value_nm( bds.m_SilkClearance );
846 constraints->set_min_resolved_spokes( bds.m_MinResolvedSpokes );
847 constraints->mutable_min_silk_text_height()->set_value_nm( bds.m_MinSilkTextHeight );
848 constraints->mutable_min_silk_text_thickness()->set_value_nm( bds.m_MinSilkTextThickness );
849
850 kiapi::board::PredefinedSizes* sizes = rules->mutable_predefined_sizes();
851
852 for( size_t ii = 1; ii < bds.m_TrackWidthList.size(); ++ii )
853 sizes->add_tracks()->mutable_width()->set_value_nm( bds.m_TrackWidthList[ii] );
854
855 for( size_t ii = 1; ii < bds.m_ViasDimensionsList.size(); ++ii )
856 {
857 kiapi::board::PresetViaDimension* via = sizes->add_vias();
858 via->mutable_diameter()->set_value_nm( bds.m_ViasDimensionsList[ii].m_Diameter );
859 via->mutable_drill()->set_value_nm( bds.m_ViasDimensionsList[ii].m_Drill );
860 }
861
862 for( size_t ii = 1; ii < bds.m_DiffPairDimensionsList.size(); ++ii )
863 {
864 kiapi::board::PresetDiffPairDimension* pair = sizes->add_diff_pairs();
865 pair->mutable_width()->set_value_nm( bds.m_DiffPairDimensionsList[ii].m_Width );
866 pair->mutable_gap()->set_value_nm( bds.m_DiffPairDimensionsList[ii].m_Gap );
867 pair->mutable_via_gap()->set_value_nm( bds.m_DiffPairDimensionsList[ii].m_ViaGap );
868 }
869
870 kiapi::board::SolderMaskPasteDefaults* maskPaste = rules->mutable_solder_mask_paste();
871
872 maskPaste->mutable_mask_expansion()->set_value_nm( bds.m_SolderMaskExpansion );
873 maskPaste->mutable_mask_min_width()->set_value_nm( bds.m_SolderMaskMinWidth );
874 maskPaste->mutable_mask_to_copper_clearance()->set_value_nm( bds.m_SolderMaskToCopperClearance );
875 maskPaste->mutable_paste_margin()->set_value_nm( bds.m_SolderPasteMargin );
876 maskPaste->set_paste_margin_ratio( bds.m_SolderPasteMarginRatio );
877 maskPaste->set_allow_soldermask_bridges_in_footprints( bds.m_AllowSoldermaskBridgesInFPs );
878
879 kiapi::board::TeardropDefaults* teardrops = rules->mutable_teardrops();
880
881 teardrops->set_target_vias( bds.m_TeardropParamsList.m_TargetVias );
882 teardrops->set_target_pth_pads( bds.m_TeardropParamsList.m_TargetPTHPads );
883 teardrops->set_target_smd_pads( bds.m_TeardropParamsList.m_TargetSMDPads );
884 teardrops->set_target_track_to_track( bds.m_TeardropParamsList.m_TargetTrack2Track );
885 teardrops->set_use_round_shapes_only( bds.m_TeardropParamsList.m_UseRoundShapesOnly );
886
888
889 for( int target = TARGET_ROUND; target <= TARGET_TRACK; ++target )
890 {
891 const TEARDROP_PARAMETERS* params = tdList.GetParameters( static_cast<TARGET_TD>( target ) );
892 kiapi::board::TeardropTargetEntry* entry = teardrops->add_target_params();
893
895 static_cast<TARGET_TD>( target ) ) );
896 entry->mutable_params()->set_enabled( params->m_Enabled );
897 entry->mutable_params()->mutable_max_length()->set_value_nm( params->m_TdMaxLen );
898 entry->mutable_params()->mutable_max_width()->set_value_nm( params->m_TdMaxWidth );
899 entry->mutable_params()->set_best_length_ratio( params->m_BestLengthRatio );
900 entry->mutable_params()->set_best_width_ratio( params->m_BestWidthRatio );
901 entry->mutable_params()->set_width_to_size_filter_ratio( params->m_WidthtoSizeFilterRatio );
902 entry->mutable_params()->set_curved_edges( params->m_CurvedEdges );
903 entry->mutable_params()->set_allow_two_tracks( params->m_AllowUseTwoTracks );
904 entry->mutable_params()->set_on_pads_in_zones( params->m_TdOnPadsInZones );
905 }
906
907 kiapi::board::ViaProtectionDefaults* viaProtection = rules->mutable_via_protection();
908
909 viaProtection->set_tent_front( bds.m_TentViasFront );
910 viaProtection->set_tent_back( bds.m_TentViasBack );
911 viaProtection->set_cover_front( bds.m_CoverViasFront );
912 viaProtection->set_cover_back( bds.m_CoverViasBack );
913 viaProtection->set_plug_front( bds.m_PlugViasFront );
914 viaProtection->set_plug_back( bds.m_PlugViasBack );
915 viaProtection->set_cap( bds.m_CapVias );
916 viaProtection->set_fill( bds.m_FillVias );
917
918 for( const auto& [errorCode, severity] : bds.m_DRCSeverities )
919 {
920 board::DrcSeveritySetting* setting = rules->add_severities();
921 setting->set_rule_type(
923 setting->set_severity( ToProtoEnum<SEVERITY, types::RuleSeverity>( severity ) );
924 }
925
926 for( const DRC_EXCLUSION& exclusion : bds.m_DrcExclusions )
927 rules->add_exclusions()->CopyFrom( exclusion.ToProto() );
928
929 response.set_custom_rules_status( CRS_NONE );
930
931 wxString rulesPath = board()->GetDesignRulesPath();
932
933 if( !rulesPath.IsEmpty() && wxFileName::IsFileReadable( rulesPath ) )
934 {
935 wxFFile file( rulesPath, "r" );
936 wxString content;
937 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
938
939 if( !file.IsOpened() )
940 {
941 response.set_custom_rules_status( CRS_INVALID );
942 return response;
943 }
944
945 file.ReadAll( &content );
946 file.Close();
947
948 try
949 {
950 DRC_RULES_PARSER parser( content, "File" );
951 parser.Parse( parsedRules, nullptr );
952 response.set_custom_rules_status( CRS_VALID );
953 }
954 catch( const IO_ERROR& )
955 {
956 response.set_custom_rules_status( CRS_INVALID );
957 }
958 }
959
960 return response;
961}
962
963
966{
967 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
968
969 if( !documentValidation )
970 return tl::unexpected( documentValidation.error() );
971
972 BOARD_DESIGN_SETTINGS newSettings( board()->GetDesignSettings() );
973 const kiapi::board::BoardDesignRules& rules = aCtx.Request.rules();
974
975 if( rules.has_constraints() )
976 {
977 const kiapi::board::MinimumConstraints& constraints = rules.constraints();
978
979 newSettings.m_MinClearance = constraints.min_clearance().value_nm();
980 newSettings.m_MinGrooveWidth = constraints.min_groove_width().value_nm();
981 newSettings.m_MinConn = constraints.min_connection_width().value_nm();
982 newSettings.m_TrackMinWidth = constraints.min_track_width().value_nm();
983 newSettings.m_ViasMinAnnularWidth = constraints.min_via_annular_width().value_nm();
984 newSettings.m_ViasMinSize = constraints.min_via_size().value_nm();
985 newSettings.m_MinThroughDrill = constraints.min_through_drill().value_nm();
986 newSettings.m_MicroViasMinSize = constraints.min_microvia_size().value_nm();
987 newSettings.m_MicroViasMinDrill = constraints.min_microvia_drill().value_nm();
988 newSettings.m_CopperEdgeClearance = constraints.copper_edge_clearance().value_nm();
989 newSettings.m_HoleClearance = constraints.hole_clearance().value_nm();
990 newSettings.m_HoleToHoleMin = constraints.hole_to_hole_min().value_nm();
991 newSettings.m_SilkClearance = constraints.silk_clearance().value_nm();
992 newSettings.m_MinResolvedSpokes = constraints.min_resolved_spokes();
993 newSettings.m_MinSilkTextHeight = constraints.min_silk_text_height().value_nm();
994 newSettings.m_MinSilkTextThickness = constraints.min_silk_text_thickness().value_nm();
995 }
996
997 if( rules.has_predefined_sizes() )
998 {
999 newSettings.m_TrackWidthList.clear();
1000 newSettings.m_TrackWidthList.emplace_back( 0 );
1001
1002 for( const kiapi::board::PresetTrackWidth& track : rules.predefined_sizes().tracks() )
1003 newSettings.m_TrackWidthList.emplace_back( track.width().value_nm() );
1004
1005 newSettings.m_ViasDimensionsList.clear();
1006 newSettings.m_ViasDimensionsList.emplace_back( 0, 0 );
1007
1008 for( const kiapi::board::PresetViaDimension& via : rules.predefined_sizes().vias() )
1009 {
1010 newSettings.m_ViasDimensionsList.emplace_back( static_cast<int>( via.diameter().value_nm() ),
1011 static_cast<int>( via.drill().value_nm() ) );
1012 }
1013
1014 newSettings.m_DiffPairDimensionsList.clear();
1015 newSettings.m_DiffPairDimensionsList.emplace_back( 0, 0, 0 );
1016
1017 for( const kiapi::board::PresetDiffPairDimension& pair : rules.predefined_sizes().diff_pairs() )
1018 {
1019 newSettings.m_DiffPairDimensionsList.emplace_back(
1020 static_cast<int>( pair.width().value_nm() ),
1021 static_cast<int>( pair.gap().value_nm() ),
1022 static_cast<int>( pair.via_gap().value_nm() ) );
1023 }
1024 }
1025
1026 if( rules.has_solder_mask_paste() )
1027 {
1028 const kiapi::board::SolderMaskPasteDefaults& maskPaste = rules.solder_mask_paste();
1029
1030 newSettings.m_SolderMaskExpansion = maskPaste.mask_expansion().value_nm();
1031 newSettings.m_SolderMaskMinWidth = maskPaste.mask_min_width().value_nm();
1032 newSettings.m_SolderMaskToCopperClearance = maskPaste.mask_to_copper_clearance().value_nm();
1033 newSettings.m_SolderPasteMargin = maskPaste.paste_margin().value_nm();
1034 newSettings.m_SolderPasteMarginRatio = maskPaste.paste_margin_ratio();
1035 newSettings.m_AllowSoldermaskBridgesInFPs =
1036 maskPaste.allow_soldermask_bridges_in_footprints();
1037 }
1038
1039 if( rules.has_teardrops() )
1040 {
1041 const kiapi::board::TeardropDefaults& teardrops = rules.teardrops();
1042
1043 newSettings.m_TeardropParamsList.m_TargetVias = teardrops.target_vias();
1044 newSettings.m_TeardropParamsList.m_TargetPTHPads = teardrops.target_pth_pads();
1045 newSettings.m_TeardropParamsList.m_TargetSMDPads = teardrops.target_smd_pads();
1046 newSettings.m_TeardropParamsList.m_TargetTrack2Track = teardrops.target_track_to_track();
1047 newSettings.m_TeardropParamsList.m_UseRoundShapesOnly = teardrops.use_round_shapes_only();
1048
1049 for( const kiapi::board::TeardropTargetEntry& entry : teardrops.target_params() )
1050 {
1051 if( entry.target() == kiapi::board::TeardropTarget::TDT_UNKNOWN )
1052 continue;
1053
1055 entry.target() );
1056
1057 if( target == TARGET_UNKNOWN )
1058 {
1059 ApiResponseStatus e;
1060 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1061 e.set_error_message( "unknown teardrop target" );
1062 return tl::unexpected( e );
1063 }
1064
1065 TEARDROP_PARAMETERS* params = newSettings.m_TeardropParamsList.GetParameters( target );
1066
1067 params->m_Enabled = entry.params().enabled();
1068 params->m_TdMaxLen = entry.params().max_length().value_nm();
1069 params->m_TdMaxWidth = entry.params().max_width().value_nm();
1070 params->m_BestLengthRatio = entry.params().best_length_ratio();
1071 params->m_BestWidthRatio = entry.params().best_width_ratio();
1072 params->m_WidthtoSizeFilterRatio = entry.params().width_to_size_filter_ratio();
1073 params->m_CurvedEdges = entry.params().curved_edges();
1074 params->m_AllowUseTwoTracks = entry.params().allow_two_tracks();
1075 params->m_TdOnPadsInZones = entry.params().on_pads_in_zones();
1076 }
1077 }
1078
1079 if( rules.has_via_protection() )
1080 {
1081 const kiapi::board::ViaProtectionDefaults& viaProtection = rules.via_protection();
1082
1083 newSettings.m_TentViasFront = viaProtection.tent_front();
1084 newSettings.m_TentViasBack = viaProtection.tent_back();
1085 newSettings.m_CoverViasFront = viaProtection.cover_front();
1086 newSettings.m_CoverViasBack = viaProtection.cover_back();
1087 newSettings.m_PlugViasFront = viaProtection.plug_front();
1088 newSettings.m_PlugViasBack = viaProtection.plug_back();
1089 newSettings.m_CapVias = viaProtection.cap();
1090 newSettings.m_FillVias = viaProtection.fill();
1091 }
1092
1093 if( rules.severities_size() > 0 )
1094 {
1095 newSettings.m_DRCSeverities.clear();
1096
1097 for( const kiapi::board::DrcSeveritySetting& severitySetting : rules.severities() )
1098 {
1099 PCB_DRC_CODE ruleType =
1100 FromProtoEnum<PCB_DRC_CODE, kiapi::board::DrcErrorType>( severitySetting.rule_type() );
1101
1102 const std::unordered_set<SEVERITY> permitted( { RPT_SEVERITY_ERROR, RPT_SEVERITY_WARNING, RPT_SEVERITY_IGNORE } );
1103 SEVERITY setting = FromProtoEnum<SEVERITY, kiapi::common::types::RuleSeverity>( severitySetting.severity() );
1104
1105 if( !permitted.contains( setting ) )
1106 {
1107 ApiResponseStatus e;
1108 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1109 e.set_error_message( fmt::format( "DRC severity must be error, warning, or ignore" ) );
1110 return tl::unexpected( e );
1111 }
1112
1113 newSettings.m_DRCSeverities[ruleType] = setting;
1114 }
1115 }
1116
1117 if( rules.exclusions_size() > 0 )
1118 {
1119 newSettings.m_DrcExclusions.clear();
1120
1121 for( const kiapi::board::DrcExclusion& exclusion : rules.exclusions() )
1122 newSettings.m_DrcExclusions.insert( DRC_EXCLUSION::FromProto( exclusion ) );
1123 }
1124
1125 std::vector<BOARD_DESIGN_SETTINGS::VALIDATION_ERROR> errors = newSettings.ValidateDesignRules();
1126
1127 if( !errors.empty() )
1128 {
1129 const BOARD_DESIGN_SETTINGS::VALIDATION_ERROR& error = errors.front();
1130
1131 ApiResponseStatus e;
1132 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1133 e.set_error_message( fmt::format( "Invalid board design rules: {}: {}",
1134 error.setting_name.ToStdString(),
1135 error.error_message.ToStdString() ) );
1136 return tl::unexpected( e );
1137 }
1138
1139 board()->SetDesignSettings( newSettings );
1140
1141 onModified();
1142
1143 HANDLER_CONTEXT<GetBoardDesignRules> getCtx = { aCtx.ClientName, GetBoardDesignRules() };
1144 *getCtx.Request.mutable_board() = aCtx.Request.board();
1145
1146 return handleGetBoardDesignRules( getCtx );
1147}
1148
1149
1152{
1153 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1154
1155 if( !documentValidation )
1156 return tl::unexpected( documentValidation.error() );
1157
1158 CustomRulesResponse response;
1159 response.set_status( CRS_NONE );
1160
1161 wxString rulesPath = board()->GetDesignRulesPath();
1162
1163 if( rulesPath.IsEmpty() || !wxFileName::IsFileReadable( rulesPath ) )
1164 return response;
1165
1166 wxFFile file( rulesPath, "r" );
1167
1168 if( !file.IsOpened() )
1169 {
1170 response.set_status( CRS_INVALID );
1171 response.set_error_text( "Failed to open custom rules file" );
1172 return response;
1173 }
1174
1175 wxString content;
1176 file.ReadAll( &content );
1177 file.Close();
1178
1179 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
1180
1181 try
1182 {
1183 DRC_RULES_PARSER parser( content, "File" );
1184 parser.Parse( parsedRules, nullptr );
1185 }
1186 catch( const IO_ERROR& ioe )
1187 {
1188 response.set_status( CRS_INVALID );
1189 response.set_error_text( ioe.What().ToStdString() );
1190 return response;
1191 }
1192
1193 for( const std::shared_ptr<DRC_RULE>& rule : parsedRules )
1194 {
1195 // TODO(JE) since we now need this for both here and the rules editor, maybe it's time
1196 // to just make comment parsing part of the parser?
1197 wxString text = DRC_RULE_LOADER::ExtractRuleText( content, rule->m_Name );
1198 wxString comment = DRC_RULE_LOADER::ExtractRuleComment( text );
1199
1200 kiapi::board::CustomRule* customRule = response.add_rules();
1201
1202 if( rule->m_Condition )
1203 customRule->set_condition( rule->m_Condition->GetExpression().ToUTF8() );
1204
1205 for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
1206 {
1207 board::CustomRuleConstraint* constraintProto = customRule->add_constraints();
1208 constraint.ToProto( *constraintProto );
1209 }
1210
1211 customRule->set_severity( ToProtoEnum<SEVERITY, types::RuleSeverity>( rule->m_Severity ) );
1212 customRule->set_name( rule->m_Name.ToUTF8() );
1213
1214 if( rule->m_LayerSource.CmpNoCase( wxS( "outer" ) ) == 0 )
1215 {
1216 customRule->set_layer_mode( kiapi::board::CRLM_OUTER );
1217 }
1218 else if( rule->m_LayerSource.CmpNoCase( wxS( "inner" ) ) == 0 )
1219 {
1220 customRule->set_layer_mode( kiapi::board::CRLM_INNER );
1221 }
1222 else if( !rule->m_LayerSource.IsEmpty() )
1223 {
1224 int layer = LSET::NameToLayer( rule->m_LayerSource );
1225
1226 if( layer != UNDEFINED_LAYER && layer != UNSELECTED_LAYER && layer < PCB_LAYER_ID_COUNT )
1227 {
1228 customRule->set_single_layer(
1230 }
1231 }
1232
1233 if( !comment.IsEmpty() )
1234 customRule->set_comments( comment );
1235 }
1236
1237 response.set_status( CRS_VALID );
1238 return response;
1239}
1240
1241
1244{
1245 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1246
1247 if( !documentValidation )
1248 return tl::unexpected( documentValidation.error() );
1249
1250 wxString rulesPath = board()->GetDesignRulesPath();
1251
1252 if( aCtx.Request.rules_size() == 0 )
1253 {
1254 if( wxFileName::FileExists( rulesPath ) )
1255 {
1256 if( !wxRemoveFile( rulesPath ) )
1257 {
1258 CustomRulesResponse response;
1259 response.set_status( CRS_INVALID );
1260 response.set_error_text( "Failed to remove custom rules file" );
1261 return response;
1262 }
1263 }
1264
1265 CustomRulesResponse response;
1266 response.set_status( CRS_NONE );
1267 return response;
1268 }
1269
1270 wxString rulesText;
1271 rulesText << "(version 2)\n";
1272
1273 for( const board::CustomRule& rule : aCtx.Request.rules() )
1274 {
1275 wxString serializationError;
1276 wxString serializedRule = DRC_RULE::FormatRuleFromProto( rule, &serializationError );
1277
1278 if( serializedRule.IsEmpty() )
1279 {
1280 CustomRulesResponse response;
1281 response.set_status( CRS_INVALID );
1282
1283 if( serializationError.IsEmpty() )
1284 response.set_error_text( "Failed to serialize custom rule" );
1285 else
1286 response.set_error_text( serializationError.ToUTF8() );
1287
1288 return response;
1289 }
1290
1291 rulesText << "\n" << serializedRule;
1292 }
1293
1294 // Validate generated file text before writing so callers get parser errors in response.
1295 try
1296 {
1297 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
1298 DRC_RULES_PARSER parser( rulesText, "SetCustomDesignRules" );
1299 parser.Parse( parsedRules, nullptr );
1300 }
1301 catch( const IO_ERROR& ioe )
1302 {
1303 CustomRulesResponse response;
1304 response.set_status( CRS_INVALID );
1305 response.set_error_text( ioe.What().ToStdString() );
1306 return response;
1307 }
1308
1309 wxFFile file( rulesPath, "w" );
1310
1311 if( !file.IsOpened() )
1312 {
1313 CustomRulesResponse response;
1314 response.set_status( CRS_INVALID );
1315 response.set_error_text( "Failed to open custom rules file for writing" );
1316 return response;
1317 }
1318
1319 if( !file.Write( rulesText ) )
1320 {
1321 file.Close();
1322
1323 CustomRulesResponse response;
1324 response.set_status( CRS_INVALID );
1325 response.set_error_text( "Failed to write custom rules file" );
1326 return response;
1327 }
1328
1329 file.Close();
1330
1331 HANDLER_CONTEXT<GetCustomDesignRules> getCtx = { aCtx.ClientName, GetCustomDesignRules() };
1332 *getCtx.Request.mutable_board() = aCtx.Request.board();
1333 return handleGetCustomDesignRules( getCtx );
1334}
1335
1336
1339{
1340 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1341 !documentValidation )
1342 {
1343 return tl::unexpected( documentValidation.error() );
1344 }
1345
1346 VECTOR2I origin;
1347 const BOARD_DESIGN_SETTINGS& settings = board()->GetDesignSettings();
1348
1349 switch( aCtx.Request.type() )
1350 {
1351 case BOT_GRID:
1352 origin = settings.GetGridOrigin();
1353 break;
1354
1355 case BOT_DRILL:
1356 origin = settings.GetAuxOrigin();
1357 break;
1358
1359 default:
1360 case BOT_UNKNOWN:
1361 {
1362 ApiResponseStatus e;
1363 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1364 e.set_error_message( "Unexpected origin type" );
1365 return tl::unexpected( e );
1366 }
1367 }
1368
1369 types::Vector2 reply;
1370 PackVector2( reply, origin );
1371 return reply;
1372}
1373
1376{
1377 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1378 return tl::unexpected( documentValidation.error() );
1379
1380 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1381 return tl::unexpected( *busy );
1382
1383 VECTOR2I origin = UnpackVector2( aCtx.Request.origin() );
1384
1385 switch( aCtx.Request.type() )
1386 {
1387 case BOT_GRID:
1388 {
1389 PCB_EDIT_FRAME* f = frame();
1390
1391 if( f )
1392 {
1393 frame()->CallAfter(
1394 [f, origin]()
1395 {
1396 // gridSetOrigin takes ownership and frees this
1397 VECTOR2D* dorigin = new VECTOR2D( origin );
1398 TOOL_MANAGER* mgr = f->GetToolManager();
1399 mgr->RunAction( PCB_ACTIONS::gridSetOrigin, dorigin );
1400 f->Refresh();
1401 } );
1402 }
1403 else
1404 {
1405 board()->GetDesignSettings().SetGridOrigin( origin );
1407 }
1408
1409 break;
1410 }
1411
1412 case BOT_DRILL:
1413 {
1414 PCB_EDIT_FRAME* f = frame();
1415
1416 if( f )
1417 {
1418 frame()->CallAfter(
1419 [f, origin]()
1420 {
1421 TOOL_MANAGER* mgr = f->GetToolManager();
1422 mgr->RunAction( PCB_ACTIONS::drillSetOrigin, origin );
1423 f->Refresh();
1424 } );
1425 }
1426 else
1427 {
1428 board()->GetDesignSettings().SetAuxOrigin( origin );
1430 }
1431
1432 break;
1433 }
1434
1435 default:
1436 case BOT_UNKNOWN:
1437 {
1438 ApiResponseStatus e;
1439 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1440 e.set_error_message( "Unexpected origin type" );
1441 return tl::unexpected( e );
1442 }
1443 }
1444
1445 return Empty();
1446}
1447
1448
1451{
1452 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1453 !documentValidation )
1454 {
1455 return tl::unexpected( documentValidation.error() );
1456 }
1457
1458 BoardLayerNameResponse response;
1459
1461
1462 response.set_name( board()->GetLayerName( id ) );
1463
1464 return response;
1465}
1466
1467
1470{
1471 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1472 !documentValidation )
1473 {
1474 return tl::unexpected( documentValidation.error() );
1475 }
1476
1477 BoardLayerResponse response;
1478
1479 PCB_LAYER_ID id = board()->GetLayerID( wxString::FromUTF8( aCtx.Request.name() ) );
1480 response.set_layer( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( id ) );
1481
1482 return response;
1483}
1484
1485
1486std::optional<TITLE_BLOCK*> API_HANDLER_PCB::getTitleBlock( const DocumentSpecifier& aDocument )
1487{
1488 return &context()->GetBoard()->GetTitleBlock();
1489}
1490
1491
1492std::optional<PAGE_INFO> API_HANDLER_PCB::getPageSettings( const DocumentSpecifier& aDocument )
1493{
1494 return context()->GetBoard()->GetPageSettings();
1495}
1496
1497
1498bool API_HANDLER_PCB::setPageSettings( const DocumentSpecifier& aDocument, const PAGE_INFO& aPageInfo )
1499{
1500 context()->GetBoard()->SetPageSettings( aPageInfo );
1501 return true;
1502}
1503
1504
1509
1510
1511void API_HANDLER_PCB::setDrawingSheetFileName( const wxString& aFileName )
1512{
1514
1515 if( frame() )
1517}
1518
1519
1521{
1523
1524 if( frame() )
1525 {
1526 frame()->Refresh();
1527 frame()->OnModify();
1529 }
1530}
1531
1532
1534{
1535 if( BOARD* brd = board() )
1536 brd->SynchronizeNetsAndNetClasses( false );
1537
1538 // Refresh UI that depends on netclasses, such as the properties panel
1539 if( TOOL_MANAGER* mgr = toolManager() )
1540 mgr->ProcessEvent( EVENTS::SelectedItemsModified );
1541
1542 if( frame() )
1543 {
1544 frame()->Refresh();
1546 }
1547}
1548
1549
1552{
1553 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
1554 return tl::unexpected( documentValidation.error() );
1555
1556 GetDocumentModifiedStateResponse response;
1557 response.set_state( pcbContext()->IsContentModified() ? DocumentModifiedState::DMS_MODIFIED
1558 : DocumentModifiedState::DMS_UNMODIFIED );
1559 return response;
1560}
1561
1562
1564{
1565 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1566 return tl::unexpected( documentValidation.error() );
1567
1568 NetsResponse response;
1569 BOARD* board = this->board();
1570
1571 std::set<wxString> netclassFilter;
1572
1573 for( const std::string& nc : aCtx.Request.netclass_filter() )
1574 netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
1575
1576 for( NETINFO_ITEM* net : board->GetNetInfo() )
1577 {
1578 NETCLASS* nc = net->GetNetClass();
1579
1580 if( !netclassFilter.empty() && nc )
1581 {
1582 bool inClass = false;
1583
1584 for( const wxString& filter : netclassFilter )
1585 {
1586 if( nc->ContainsNetclassWithName( filter ) )
1587 {
1588 inClass = true;
1589 break;
1590 }
1591 }
1592
1593 if( !inClass )
1594 continue;
1595 }
1596
1597 board::types::Net* netProto = response.add_nets();
1598 netProto->set_name( net->GetNetname() );
1599 netProto->mutable_code()->set_value( net->GetNetCode() );
1600 }
1601
1602 return response;
1603}
1604
1605
1608{
1609 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1610 {
1611 ApiResponseStatus e;
1612 e.set_status( ApiStatusCode::AS_UNHANDLED );
1613 return tl::unexpected( e );
1614 }
1615
1616 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1617 return tl::unexpected( *busy );
1618
1619 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1620 const bool filterByType = aCtx.Request.types_size() > 0;
1621
1622 if( filterByType && types.empty() )
1623 {
1624 ApiResponseStatus e;
1625 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1626 e.set_error_message( "none of the requested types are valid for a Board object" );
1627 return tl::unexpected( e );
1628 }
1629
1630 std::set<KICAD_T> typeFilter( types.begin(), types.end() );
1631 std::vector<BOARD_CONNECTED_ITEM*> sourceItems;
1632
1633 for( const types::KIID& id : aCtx.Request.items() )
1634 {
1635 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
1636 {
1637 if( BOARD_CONNECTED_ITEM* connected = dynamic_cast<BOARD_CONNECTED_ITEM*>( *item ) )
1638 sourceItems.emplace_back( connected );
1639 }
1640 }
1641
1642 if( sourceItems.empty() )
1643 {
1644 ApiResponseStatus e;
1645 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1646 e.set_error_message( "none of the requested IDs were found or valid connected items" );
1647 return tl::unexpected( e );
1648 }
1649
1650 GetItemsResponse response;
1651 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
1652 std::set<KIID> insertedItems;
1653
1654 for( BOARD_CONNECTED_ITEM* source : sourceItems )
1655 {
1656 for( BOARD_CONNECTED_ITEM* connected : conn->GetConnectedItems( source ) )
1657 {
1658 if( filterByType && !typeFilter.contains( connected->Type() ) )
1659 continue;
1660
1661 if( !insertedItems.insert( connected->m_Uuid ).second )
1662 continue;
1663
1664 connected->Serialize( *response.add_items() );
1665 }
1666 }
1667
1668 response.set_status( ItemRequestStatus::IRS_OK );
1669 return response;
1670}
1671
1672
1674 const HANDLER_CONTEXT<GetItemsByNet>& aCtx )
1675{
1676 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1677 {
1678 ApiResponseStatus e;
1679 e.set_status( ApiStatusCode::AS_UNHANDLED );
1680 return tl::unexpected( e );
1681 }
1682
1683 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1684 return tl::unexpected( *busy );
1685
1686 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1687 const bool filterByType = aCtx.Request.types_size() > 0;
1688
1689 if( filterByType && types.empty() )
1690 {
1691 ApiResponseStatus e;
1692 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1693 e.set_error_message( "none of the requested types are valid for a Board object" );
1694 return tl::unexpected( e );
1695 }
1696
1697 if( !filterByType )
1699
1700 GetItemsResponse response;
1701 BOARD* board = this->board();
1702 std::shared_ptr<CONNECTIVITY_DATA> conn = board->GetConnectivity();
1703 std::set<KIID> insertedItems;
1704
1705 const NETINFO_LIST& nets = board->GetNetInfo();
1706
1707 for( const board::types::Net& net : aCtx.Request.nets() )
1708 {
1709 NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
1710
1711 if( !netInfo )
1712 continue;
1713
1714 for( BOARD_CONNECTED_ITEM* item : conn->GetNetItems( netInfo->GetNetCode(), types ) )
1715 {
1716 if( !insertedItems.insert( item->m_Uuid ).second )
1717 continue;
1718
1719 item->Serialize( *response.add_items() );
1720 }
1721 }
1722
1723 response.set_status( ItemRequestStatus::IRS_OK );
1724 return response;
1725}
1726
1727
1730{
1731 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1732 {
1733 ApiResponseStatus e;
1734 e.set_status( ApiStatusCode::AS_UNHANDLED );
1735 return tl::unexpected( e );
1736 }
1737
1738 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1739 return tl::unexpected( *busy );
1740
1741 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1742 const bool filterByType = aCtx.Request.types_size() > 0;
1743
1744 if( filterByType && types.empty() )
1745 {
1746 ApiResponseStatus e;
1747 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1748 e.set_error_message( "none of the requested types are valid for a Board object" );
1749 return tl::unexpected( e );
1750 }
1751
1752 if( !filterByType )
1754
1755 std::set<wxString> requestedClasses;
1756
1757 for( const std::string& netClass : aCtx.Request.net_classes() )
1758 requestedClasses.insert( wxString( netClass.c_str(), wxConvUTF8 ) );
1759
1760 GetItemsResponse response;
1761 BOARD* board = this->board();
1762 std::shared_ptr<CONNECTIVITY_DATA> conn = board->GetConnectivity();
1763 std::set<KIID> insertedItems;
1764
1765 for( NETINFO_ITEM* net : board->GetNetInfo() )
1766 {
1767 if( !net )
1768 continue;
1769
1770 NETCLASS* nc = net->GetNetClass();
1771
1772 if( !requestedClasses.empty() )
1773 {
1774 if( !nc )
1775 continue;
1776
1777 bool inClass = false;
1778
1779 for( const wxString& filter : requestedClasses )
1780 {
1781 if( nc->ContainsNetclassWithName( filter ) )
1782 {
1783 inClass = true;
1784 break;
1785 }
1786 }
1787
1788 if( !inClass )
1789 continue;
1790 }
1791
1792 for( BOARD_CONNECTED_ITEM* item : conn->GetNetItems( net->GetNetCode(), types ) )
1793 {
1794 if( !insertedItems.insert( item->m_Uuid ).second )
1795 continue;
1796
1797 item->Serialize( *response.add_items() );
1798 }
1799 }
1800
1801 response.set_status( ItemRequestStatus::IRS_OK );
1802 return response;
1803}
1804
1805
1808{
1809 NetClassForNetsResponse response;
1810
1811 BOARD* board = this->board();
1812 const NETINFO_LIST& nets = board->GetNetInfo();
1813
1814 for( const board::types::Net& net : aCtx.Request.net() )
1815 {
1816 NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
1817
1818 if( !netInfo )
1819 continue;
1820
1821 auto [pair, rc] = response.mutable_classes()->insert( { net.name(), {} } );
1822 netInfo->GetNetClass()->Serialize( pair->second );
1823 }
1824
1825 return response;
1826}
1827
1828
1830{
1831 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1832 return tl::unexpected( documentValidation.error() );
1833
1834 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1835 return tl::unexpected( *busy );
1836
1837 TOOL_MANAGER* mgr = toolManager();
1838
1839 // A frame's tool manager always carries the zone filler tool; headless sessions start with a
1840 // bare tool manager and register it on first use, like the CLI jobs do.
1841 if( !mgr->FindTool( ZONE_FILLER_TOOL_NAME ) )
1842 mgr->RegisterTool( new ZONE_FILLER_TOOL );
1843
1844 if( aCtx.Request.zones().empty() )
1845 {
1846 if( frame() )
1847 {
1848 frame()->CallAfter( [mgr]()
1849 {
1851 } );
1852 }
1853 else
1854 {
1855 // Headless sessions have no event loop to defer to; fill synchronously through the
1856 // same tool the CLI jobs use.
1857 mgr->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, nullptr, true );
1858 }
1859 }
1860 else
1861 {
1862 std::vector<ZONE*> toFill;
1863
1864 for( const types::KIID& id : aCtx.Request.zones() )
1865 {
1866 std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) );
1867
1868 if( !item || ( *item )->Type() != PCB_ZONE_T )
1869 {
1870 ApiResponseStatus e;
1871 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1872 e.set_error_message( fmt::format( "zone with ID {} not found on the board", id.value() ) );
1873 return tl::unexpected( e );
1874 }
1875
1876 ZONE* zone = static_cast<ZONE*>( *item );
1877
1878 // The filler silently skips rule areas, which would turn this into a false success
1879 if( zone->GetIsRuleArea() )
1880 {
1881 ApiResponseStatus e;
1882 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1883 e.set_error_message( fmt::format( "zone with ID {} is a rule area and cannot be filled",
1884 id.value() ) );
1885 return tl::unexpected( e );
1886 }
1887
1888 // A repeated id would enqueue concurrent fill tasks for the same zone
1889 if( !alg::contains( toFill, zone ) )
1890 toFill.push_back( zone );
1891 }
1892
1893 std::unique_ptr<COMMIT> commit = createCommit();
1894 ZONE_FILLER filler( board(), commit.get() );
1895
1896 if( !filler.Fill( toFill ) )
1897 {
1898 commit->Revert();
1899
1900 ApiResponseStatus e;
1901 e.set_status( ApiStatusCode::AS_UNKNOWN );
1902 e.set_error_message( "zone fill failed" );
1903 return tl::unexpected( e );
1904 }
1905
1906 commit->Push( _( "Fill Zone(s)" ), SKIP_CONNECTIVITY | ZONE_FILL_OP );
1907
1908 // Push skipped connectivity, so run the same post-fill refresh as the interactive fill
1909 mgr->GetTool<ZONE_FILLER_TOOL>()->PostFillRefresh( frame() == nullptr );
1910 }
1911
1912 return Empty();
1913}
1914
1915
1917{
1918 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1919
1920 if( !documentValidation )
1921 return tl::unexpected( documentValidation.error() );
1922
1923 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1924 return tl::unexpected( *busy );
1925
1926 wxFileName netlistPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.netlist_path() ) ) );
1927
1928 if( !netlistPath.IsOk() || !netlistPath.FileExists() )
1929 {
1930 ApiResponseStatus e;
1931 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1932 e.set_error_message(
1933 fmt::format( "netlist file '{}' could not be opened", netlistPath.GetFullPath().ToStdString() ) );
1934 return tl::unexpected( e );
1935 }
1936
1937 PCB_CONTEXT* ctx = pcbContext();
1939
1940 const bool lookupByTimestamp = aCtx.Request.match_mode() != NetlistMatchMode::NMM_REFERENCE;
1941
1943 netlist.SetFindByTimeStamp( lookupByTimestamp );
1944 netlist.SetReplaceFootprints( aCtx.Request.update_footprints() );
1945
1946 if( !ctx->ReadNetlistFromFile( netlistPath.GetFullPath(), netlist, reporter ) )
1947 {
1948 ApiResponseStatus e;
1949 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1950 e.set_error_message( fmt::format( "unable to handle netlist file '{}': {}",
1951 netlistPath.GetFullPath().ToStdString(),
1952 reporter.GetMessages().ToStdString() ) );
1953 return tl::unexpected( e );
1954 }
1955
1956 std::unique_ptr<BOARD_NETLIST_UPDATER> updater = ctx->MakeNetlistUpdater();
1957
1958 updater->SetReporter( &reporter );
1959 updater->SetIsDryRun( aCtx.Request.dry_run() );
1960 updater->SetLookupByTimestamp( lookupByTimestamp );
1961 updater->SetDeleteUnusedFootprints( aCtx.Request.delete_extra_footprints() );
1962 updater->SetReplaceFootprints( aCtx.Request.update_footprints() );
1963 updater->SetTransferGroups( aCtx.Request.transfer_groups() );
1964 updater->SetOverrideLocks( aCtx.Request.override_locks() );
1965 updater->SetUpdateFields( true );
1966
1967 const bool success = updater->UpdateNetlist( netlist );
1968
1969 if( !aCtx.Request.dry_run() && success )
1970 ctx->OnNetlistChanged( *updater );
1971
1972 ImportNetlistResponse response;
1973 response.set_report( reporter.GetMessages().ToUTF8() );
1974 response.set_error_count( updater->GetErrorCount() );
1975 response.set_warning_count( updater->GetWarningCount() );
1976 response.set_new_footprint_count( updater->GetNewFootprintCount() );
1977 return response;
1978}
1979
1980
1983{
1984 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetBoardEditorAppearanceSettings" ) )
1985 return tl::unexpected( *headless );
1986
1987 BoardEditorAppearanceSettings reply;
1988
1989 // TODO: might be nice to put all these things in one place and have it derive SERIALIZABLE
1990
1991 const PCB_DISPLAY_OPTIONS& displayOptions = frame()->GetDisplayOptions();
1992
1993 reply.set_inactive_layer_display( ToProtoEnum<HIGH_CONTRAST_MODE, InactiveLayerDisplayMode>(
1994 displayOptions.m_ContrastModeDisplay ) );
1995 reply.set_net_color_display(
1997
1998 reply.set_board_flip( frame()->GetCanvas()->GetView()->IsMirroredX()
1999 ? BoardFlipMode::BFM_FLIPPED_X
2000 : BoardFlipMode::BFM_NORMAL );
2001
2002 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
2003
2004 reply.set_ratsnest_display( ToProtoEnum<RATSNEST_MODE, RatsnestDisplayMode>(
2005 editorSettings->m_Display.m_RatsnestMode ) );
2006
2007 return reply;
2008}
2009
2010
2013{
2014 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SetBoardEditorAppearanceSettings" ) )
2015 return tl::unexpected( *headless );
2016
2017 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2018 return tl::unexpected( *busy );
2019
2021 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
2022 const BoardEditorAppearanceSettings& newSettings = aCtx.Request.settings();
2023
2024 options.m_ContrastModeDisplay =
2025 FromProtoEnum<HIGH_CONTRAST_MODE>( newSettings.inactive_layer_display() );
2026 options.m_NetColorMode =
2027 FromProtoEnum<NET_COLOR_MODE>( newSettings.net_color_display() );
2028 options.m_FlipBoardView = newSettings.board_flip() == BoardFlipMode::BFM_FLIPPED_X;
2029
2030 editorSettings->m_Display.m_RatsnestMode =
2031 FromProtoEnum<RATSNEST_MODE>( newSettings.ratsnest_display() );
2032
2033 frame()->SetDisplayOptions( options );
2035 frame()->GetCanvas()->Refresh();
2036
2037 return Empty();
2038}
2039
2040
2043{
2044 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
2045 return tl::unexpected( documentValidation.error() );
2046
2047 const PCB_PLOT_PARAMS& plotOpts = board()->GetPlotOptions();
2048
2049 BoardPlotSettingsResponse response;
2050 BoardPlotSettings* settings = response.mutable_plot_settings();
2051
2052 board::PackLayerSet( *settings->mutable_layers(), plotOpts.GetLayerSelection() );
2053
2054 for( PCB_LAYER_ID layer : plotOpts.GetPlotOnAllLayersSequence() )
2055 settings->add_common_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
2056
2057 settings->set_mirror( plotOpts.GetMirror() );
2058 settings->set_black_and_white( plotOpts.GetBlackAndWhite() );
2059 settings->set_negative( plotOpts.GetNegative() );
2060 settings->set_scale( plotOpts.GetScale() );
2061
2062 settings->set_sketch_pads_on_fab_layers( plotOpts.GetSketchPadsOnFabLayers() );
2063 settings->set_hide_dnp_footprints_on_fab_layers( plotOpts.GetHideDNPFPsOnFabLayers() );
2064 settings->set_sketch_dnp_footprints_on_fab_layers( plotOpts.GetSketchDNPFPsOnFabLayers() );
2065 settings->set_crossout_dnp_footprints_on_fab_layers( plotOpts.GetCrossoutDNPFPsOnFabLayers() );
2066
2067 settings->set_plot_footprint_values( plotOpts.GetPlotValue() );
2068 settings->set_plot_reference_designators( plotOpts.GetPlotReference() );
2069 settings->set_plot_drawing_sheet( plotOpts.GetPlotFrameRef() );
2070 settings->set_subtract_solder_mask_from_silk( plotOpts.GetSubtractMaskFromSilk() );
2071 settings->set_plot_pad_numbers( plotOpts.GetPlotPadNumbers() );
2072
2073 settings->set_drill_marks( ToProtoEnum<DRILL_MARKS, PlotDrillMarks>( plotOpts.GetDrillMarksType() ) );
2074 settings->set_use_drill_origin( plotOpts.GetUseAuxOrigin() );
2075
2076 return response;
2077}
2078
2079
2081{
2082 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
2083 return tl::unexpected( documentValidation.error() );
2084
2085 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2086 return tl::unexpected( *busy );
2087
2088 const BoardPlotSettings& settings = aCtx.Request.plot_settings();
2089 PCB_PLOT_PARAMS plotOpts = board()->GetPlotOptions();
2090
2091 plotOpts.SetLayerSelection( board::UnpackLayerSet( settings.layers() ) );
2092
2093 LSEQ commonLayers;
2094
2095 for( int layer : settings.common_layers() )
2096 {
2097 PCB_LAYER_ID layerId =
2098 FromProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( static_cast<board::types::BoardLayer>( layer ) );
2099
2100 if( !IsPcbLayer( layerId ) )
2101 {
2102 ApiResponseStatus e;
2103 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2104 e.set_error_message( fmt::format( "SetBoardPlotSettings contains an invalid layer {}",
2105 magic_enum::enum_name( layerId ) ) );
2106 return tl::unexpected( e );
2107 }
2108
2109 commonLayers.push_back( layerId );
2110 }
2111
2112 plotOpts.SetPlotOnAllLayersSequence( commonLayers );
2113
2114 plotOpts.SetMirror( settings.mirror() );
2115 plotOpts.SetBlackAndWhite( settings.black_and_white() );
2116 plotOpts.SetNegative( settings.negative() );
2117 plotOpts.SetScale( settings.scale() );
2118
2119 plotOpts.SetSketchPadsOnFabLayers( settings.sketch_pads_on_fab_layers() );
2120 plotOpts.SetHideDNPFPsOnFabLayers( settings.hide_dnp_footprints_on_fab_layers() );
2121 plotOpts.SetSketchDNPFPsOnFabLayers( settings.sketch_dnp_footprints_on_fab_layers() );
2122 plotOpts.SetCrossoutDNPFPsOnFabLayers( settings.crossout_dnp_footprints_on_fab_layers() );
2123
2124 plotOpts.SetPlotValue( settings.plot_footprint_values() );
2125 plotOpts.SetPlotReference( settings.plot_reference_designators() );
2126 plotOpts.SetPlotFrameRef( settings.plot_drawing_sheet() );
2127 plotOpts.SetSubtractMaskFromSilk( settings.subtract_solder_mask_from_silk() );
2128 plotOpts.SetPlotPadNumbers( settings.plot_pad_numbers() );
2129
2130 plotOpts.SetDrillMarksType( FromProtoEnum<DRILL_MARKS>( settings.drill_marks() ) );
2131 plotOpts.SetUseAuxOrigin( settings.use_drill_origin() );
2132
2133 board()->SetPlotOptions( plotOpts );
2134
2135 if( frame() )
2136 frame()->OnModify();
2137
2138 return Empty();
2139}
2140
2141
2144{
2145 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
2146 return tl::unexpected( documentValidation.error() );
2147
2148 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2149 return tl::unexpected( *busy );
2150
2151 SEVERITY severity = FromProtoEnum<SEVERITY>( aCtx.Request.severity() );
2152 int layer = severity == RPT_SEVERITY_WARNING ? LAYER_DRC_WARNING : LAYER_DRC_ERROR;
2154
2155 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( code );
2156
2157 drcItem->SetErrorMessage( wxString::FromUTF8( aCtx.Request.message() ) );
2158
2159 RC_ITEM::KIIDS ids;
2160
2161 for( const auto& id : aCtx.Request.items() )
2162 ids.emplace_back( KIID( id.value() ) );
2163
2164 if( !ids.empty() )
2165 drcItem->SetItems( ids );
2166
2167 const auto& pos = aCtx.Request.position();
2168 VECTOR2I position( static_cast<int>( pos.x_nm() ), static_cast<int>( pos.y_nm() ) );
2169
2170 PCB_MARKER* marker = new PCB_MARKER( drcItem, position, layer );
2171
2172 COMMIT* commit = getCurrentCommit( aCtx.ClientName );
2173 commit->Add( marker );
2174
2175 if( !m_activeClients.count( aCtx.ClientName ) )
2176 pushCurrentCommit( aCtx.ClientName, _( "API injected DRC marker" ) );
2177
2178 InjectDrcErrorResponse response;
2179 response.mutable_marker()->set_value( marker->GetUUID().AsStdString() );
2180
2181 return response;
2182}
2183
2184
2185std::optional<ApiResponseStatus> ValidateUnitsInchMm( types::Units aUnits,
2186 const std::string& aCommandName )
2187{
2188 if( aUnits == types::Units::U_INCH || aUnits == types::Units::U_MM
2189 || aUnits == types::Units::U_UNKNOWN )
2190 {
2191 return std::nullopt;
2192 }
2193
2194 ApiResponseStatus e;
2195 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2196 e.set_error_message( fmt::format( "{} supports only inch and mm units", aCommandName ) );
2197 return e;
2198}
2199
2200
2201std::optional<ApiResponseStatus>
2202ValidatePaginationModeForSingleOrPerFile( kiapi::board::jobs::BoardJobPaginationMode aMode,
2203 const std::string& aCommandName )
2204{
2205 if( aMode == kiapi::board::jobs::BoardJobPaginationMode::BJPM_UNKNOWN
2206 || aMode == kiapi::board::jobs::BoardJobPaginationMode::BJPM_ALL_LAYERS_ONE_PAGE
2207 || aMode == kiapi::board::jobs::BoardJobPaginationMode::BJPM_EACH_LAYER_OWN_FILE )
2208 {
2209 return std::nullopt;
2210 }
2211
2212 ApiResponseStatus e;
2213 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2214 e.set_error_message( fmt::format( "{} does not support EACH_LAYER_OWN_PAGE pagination mode",
2215 aCommandName ) );
2216 return e;
2217}
2218
2219
2220std::optional<ApiResponseStatus> ApplyBoardPlotSettings( const BoardPlotSettings& aSettings,
2221 JOB_EXPORT_PCB_PLOT& aJob )
2222{
2223 for( int layer : aSettings.layers() )
2224 {
2226 static_cast<board::types::BoardLayer>( layer ) );
2227
2228 if( !IsPcbLayer( static_cast<int>( layerId ) ) )
2229 {
2230 ApiResponseStatus e;
2231 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2232 e.set_error_message( "Board plot settings contain an invalid layer" );
2233 return e;
2234 }
2235
2236 aJob.m_plotLayerSequence.push_back( layerId );
2237 }
2238
2239 for( int layer : aSettings.common_layers() )
2240 {
2242 static_cast<board::types::BoardLayer>( layer ) );
2243
2244 if( !IsPcbLayer( static_cast<int>( layerId ) ) )
2245 {
2246 ApiResponseStatus e;
2247 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2248 e.set_error_message( "Board plot settings contain an invalid common layer" );
2249 return e;
2250 }
2251
2252 aJob.m_plotOnAllLayersSequence.push_back( layerId );
2253 }
2254
2255 aJob.m_colorTheme = wxString::FromUTF8( aSettings.color_theme() );
2256 aJob.m_drawingSheet = wxString::FromUTF8( aSettings.drawing_sheet() );
2257 aJob.m_variant = wxString::FromUTF8( aSettings.variant() );
2258
2259 aJob.m_mirror = aSettings.mirror();
2260 aJob.m_blackAndWhite = aSettings.black_and_white();
2261 aJob.m_negative = aSettings.negative();
2262 aJob.m_scale = aSettings.scale();
2263
2264 aJob.m_sketchPadsOnFabLayers = aSettings.sketch_pads_on_fab_layers();
2265 aJob.m_hideDNPFPsOnFabLayers = aSettings.hide_dnp_footprints_on_fab_layers();
2266 aJob.m_sketchDNPFPsOnFabLayers = aSettings.sketch_dnp_footprints_on_fab_layers();
2267 aJob.m_crossoutDNPFPsOnFabLayers = aSettings.crossout_dnp_footprints_on_fab_layers();
2268
2269 aJob.m_plotFootprintValues = aSettings.plot_footprint_values();
2270 aJob.m_plotRefDes = aSettings.plot_reference_designators();
2271 aJob.m_plotDrawingSheet = aSettings.plot_drawing_sheet();
2272 aJob.m_subtractSolderMaskFromSilk = aSettings.subtract_solder_mask_from_silk();
2273 aJob.m_plotPadNumbers = aSettings.plot_pad_numbers();
2274
2275 aJob.m_drillShapeOption = FromProtoEnum<DRILL_MARKS>( aSettings.drill_marks() );
2276
2277 aJob.m_useDrillOrigin = aSettings.use_drill_origin();
2278 aJob.m_checkZonesBeforePlot = aSettings.check_zones_before_plot();
2279
2280 return std::nullopt;
2281}
2282
2283
2285{
2286 types::RunJobResponse response;
2288
2289 if( !aContext || !aContext->GetKiway() )
2290 {
2291 response.set_status( types::JobStatus::JS_ERROR );
2292 response.set_message( "Internal error" );
2293 wxCHECK_MSG( false, response, "context missing valid kiway in ExecuteBoardJob?" );
2294 return response;
2295 }
2296
2297 int exitCode = aContext->GetKiway()->ProcessJob( KIWAY::FACE_PCB, &aJob, &reporter );
2298
2299 for( const JOB_OUTPUT& output : aJob.GetOutputs() )
2300 response.add_output_path( output.m_outputPath.ToUTF8() );
2301
2302 if( exitCode == 0 )
2303 {
2304 response.set_status( types::JobStatus::JS_SUCCESS );
2305 return response;
2306 }
2307
2308 response.set_status( types::JobStatus::JS_ERROR );
2309 response.set_message( fmt::format( "Board export job '{}' failed with exit code {}: {}",
2310 aJob.GetType(), exitCode,
2311 reporter.GetMessages().ToStdString() ) );
2312 return response;
2313}
2314
2315
2318{
2319 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2320 return tl::unexpected( validation.error() );
2321
2322 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2323 return tl::unexpected( *busy );
2324
2327 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2328
2330
2331 job.m_variant = wxString::FromUTF8( aCtx.Request.variant() );
2332 job.m_3dparams.m_NetFilter = wxString::FromUTF8( aCtx.Request.net_filter() );
2333 job.m_3dparams.m_ComponentFilter = wxString::FromUTF8( aCtx.Request.component_filter() );
2334
2335 job.m_hasUserOrigin = aCtx.Request.has_user_origin();
2336 job.m_3dparams.m_Origin = VECTOR2D( aCtx.Request.origin().x_nm(), aCtx.Request.origin().y_nm() );
2337
2338 job.m_3dparams.m_Overwrite = aCtx.Request.overwrite();
2339 job.m_3dparams.m_UseGridOrigin = aCtx.Request.use_grid_origin();
2340 job.m_3dparams.m_UseDrillOrigin = aCtx.Request.use_drill_origin();
2341 job.m_3dparams.m_UseDefinedOrigin = aCtx.Request.use_defined_origin() || aCtx.Request.has_user_origin();
2342 job.m_3dparams.m_UsePcbCenterOrigin = aCtx.Request.use_pcb_center_origin();
2343
2344 job.m_3dparams.m_IncludeUnspecified = aCtx.Request.include_unspecified();
2345 job.m_3dparams.m_IncludeDNP = aCtx.Request.include_dnp();
2346 job.m_3dparams.m_SubstModels = aCtx.Request.substitute_models();
2347
2348 job.m_3dparams.m_BoardOutlinesChainingEpsilon = aCtx.Request.board_outlines_chaining_epsilon();
2349 job.m_3dparams.m_BoardOnly = aCtx.Request.board_only();
2350 job.m_3dparams.m_CutViasInBody = aCtx.Request.cut_vias_in_body();
2351 job.m_3dparams.m_ExportBoardBody = aCtx.Request.export_board_body();
2352 job.m_3dparams.m_ExportComponents = aCtx.Request.export_components();
2353 job.m_3dparams.m_ExportTracksVias = aCtx.Request.export_tracks_and_vias();
2354 job.m_3dparams.m_ExportPads = aCtx.Request.export_pads();
2355 job.m_3dparams.m_ExportZones = aCtx.Request.export_zones();
2356 job.m_3dparams.m_ExportInnerCopper = aCtx.Request.export_inner_copper();
2357 job.m_3dparams.m_ExportSilkscreen = aCtx.Request.export_silkscreen();
2358 job.m_3dparams.m_ExportSoldermask = aCtx.Request.export_soldermask();
2359 job.m_3dparams.m_FuseShapes = aCtx.Request.fuse_shapes();
2360 job.m_3dparams.m_FillAllVias = aCtx.Request.fill_all_vias();
2361 job.m_3dparams.m_OptimizeStep = aCtx.Request.optimize_step();
2362 job.m_3dparams.m_ExtraPadThickness = aCtx.Request.extra_pad_thickness();
2363
2365
2366 job.m_vrmlModelDir = wxString::FromUTF8( aCtx.Request.vrml_model_dir() );
2367 job.m_vrmlRelativePaths = aCtx.Request.vrml_relative_paths();
2368
2369 return ExecuteBoardJob( pcbContext(), job );
2370}
2371
2372
2375{
2376 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2377 return tl::unexpected( validation.error() );
2378
2379 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2380 return tl::unexpected( *busy );
2381
2382 JOB_PCB_RENDER job;
2384 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2385
2388 job.m_bgStyle = FromProtoEnum<JOB_PCB_RENDER::BG_STYLE>( aCtx.Request.background_style() );
2389
2390 job.m_width = aCtx.Request.width();
2391 job.m_height = aCtx.Request.height();
2392 job.m_appearancePreset = aCtx.Request.appearance_preset();
2393 job.m_useBoardStackupColors = aCtx.Request.use_board_stackup_colors();
2394
2396
2397 job.m_zoom = aCtx.Request.zoom();
2398 job.m_perspective = aCtx.Request.perspective();
2399
2400 job.m_rotation = UnpackVector3D( aCtx.Request.rotation() );
2401 job.m_pan = UnpackVector3D( aCtx.Request.pan() );
2402 job.m_pivot = UnpackVector3D( aCtx.Request.pivot() );
2403
2404 job.m_proceduralTextures = aCtx.Request.procedural_textures();
2405 job.m_floor = aCtx.Request.floor();
2406 job.m_antiAlias = aCtx.Request.anti_alias();
2407 job.m_postProcess = aCtx.Request.post_process();
2408
2409 job.m_lightTopIntensity = UnpackVector3D( aCtx.Request.light_top_intensity() );
2410 job.m_lightBottomIntensity = UnpackVector3D( aCtx.Request.light_bottom_intensity() );
2411 job.m_lightCameraIntensity = UnpackVector3D( aCtx.Request.light_camera_intensity() );
2412 job.m_lightSideIntensity = UnpackVector3D( aCtx.Request.light_side_intensity() );
2413 job.m_lightSideElevation = aCtx.Request.light_side_elevation();
2414
2415 return ExecuteBoardJob( pcbContext(), job );
2416}
2417
2418
2421{
2422 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2423 return tl::unexpected( validation.error() );
2424
2425 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2426 return tl::unexpected( *busy );
2427
2430 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2431
2432 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2433 return tl::unexpected( *err );
2434
2435 job.m_fitPageToBoard = aCtx.Request.fit_page_to_board();
2436 job.m_precision = aCtx.Request.precision();
2437
2438 if( std::optional<ApiResponseStatus> paginationError =
2440 "RunBoardJobExportSvg" ) )
2441 {
2442 return tl::unexpected( *paginationError );
2443 }
2444
2446
2447 return ExecuteBoardJob( pcbContext(), job );
2448}
2449
2450
2453{
2454 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2455 return tl::unexpected( validation.error() );
2456
2457 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2458 return tl::unexpected( *busy );
2459
2462 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2463
2464 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2465 return tl::unexpected( *err );
2466
2467 job.m_plotGraphicItemsUsingContours = aCtx.Request.plot_graphic_items_using_contours();
2468 job.m_polygonMode = aCtx.Request.polygon_mode();
2469
2470 if( std::optional<ApiResponseStatus> unitError =
2471 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportDxf" ) )
2472 {
2473 return tl::unexpected( *unitError );
2474 }
2475
2477
2478 if( std::optional<ApiResponseStatus> paginationError =
2480 "RunBoardJobExportDxf" ) )
2481 {
2482 return tl::unexpected( *paginationError );
2483 }
2484
2486
2487 return ExecuteBoardJob( pcbContext(), job );
2488}
2489
2490
2493{
2494 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2495 return tl::unexpected( validation.error() );
2496
2497 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2498 return tl::unexpected( *busy );
2499
2502 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2503
2504 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2505 return tl::unexpected( *err );
2506
2507 job.m_pdfFrontFPPropertyPopups = aCtx.Request.front_footprint_property_popups();
2508 job.m_pdfBackFPPropertyPopups = aCtx.Request.back_footprint_property_popups();
2509 job.m_pdfMetadata = aCtx.Request.include_metadata();
2510 job.m_pdfSingle = aCtx.Request.single_document();
2511 job.m_pdfBackgroundColor = wxString::FromUTF8( aCtx.Request.background_color() );
2512
2514
2515 return ExecuteBoardJob( pcbContext(), job );
2516}
2517
2518
2521{
2522 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2523 return tl::unexpected( validation.error() );
2524
2525 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2526 return tl::unexpected( *busy );
2527
2530 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2531
2532 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2533 return tl::unexpected( *err );
2534
2535 if( std::optional<ApiResponseStatus> paginationError =
2537 "RunBoardJobExportPs" ) )
2538 {
2539 return tl::unexpected( *paginationError );
2540 }
2541
2543
2544 job.m_trackWidthCorrection = aCtx.Request.track_width_correction();
2545 job.m_XScaleAdjust = aCtx.Request.x_scale_adjust();
2546 job.m_YScaleAdjust = aCtx.Request.y_scale_adjust();
2547 job.m_forceA4 = aCtx.Request.force_a4();
2548 job.m_useGlobalSettings = aCtx.Request.use_global_settings();
2549
2550 return ExecuteBoardJob( pcbContext(), job );
2551}
2552
2553
2556{
2557 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2558 return tl::unexpected( validation.error() );
2559
2560 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2561 return tl::unexpected( *busy );
2562
2565 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2566
2567 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2568 return tl::unexpected( *err );
2569
2570 if( std::optional<ApiResponseStatus> paginationError =
2571 ValidatePaginationModeForSingleOrPerFile( aCtx.Request.page_mode(), "RunBoardJobExportPng" ) )
2572 {
2573 return tl::unexpected( *paginationError );
2574 }
2575
2577
2578 if( aCtx.Request.has_dpi() )
2579 {
2580 int dpi = aCtx.Request.dpi();
2581
2582 if( dpi < MIN_PNG_DPI || dpi > MAX_PNG_DPI )
2583 {
2584 ApiResponseStatus status;
2585 status.set_status( ApiStatusCode::AS_BAD_REQUEST );
2586 status.set_error_message( fmt::format( "dpi must be between {} and {}", MIN_PNG_DPI, MAX_PNG_DPI ) );
2587 return tl::unexpected( status );
2588 }
2589
2590 job.m_dpi = dpi;
2591 }
2592
2593 // Unknown -> default AA on
2594 job.m_antialias = aCtx.Request.antialiasing() != types::AntialiasingMode::AAM_NONE;
2595 job.m_useBackgroundColor = aCtx.Request.plot_background_color();
2596
2597 return ExecuteBoardJob( pcbContext(), job );
2598}
2599
2600
2603{
2604 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2605 return tl::unexpected( validation.error() );
2606
2607 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2608 return tl::unexpected( *busy );
2609
2612 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2613
2614 job.m_useBoardPlotParams = aCtx.Request.use_board_plot_params();
2615
2616 if( !job.m_useBoardPlotParams )
2617 {
2618 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2619 return tl::unexpected( *err );
2620 }
2621
2622 job.m_createJobsFile = aCtx.Request.create_gerber_job_file();
2623 job.m_includeNetlistAttributes = aCtx.Request.include_netlist_attributes();
2624 job.m_useX2Format = aCtx.Request.use_x2_format();
2625 job.m_disableApertureMacros = aCtx.Request.disable_aperture_macros();
2626 job.m_useProtelFileExtension = aCtx.Request.use_protel_file_extensions();
2627
2628 switch( aCtx.Request.precision() )
2629 {
2630 default:
2631 case GerberPrecision::GP_5: job.m_precision = 5; break;
2632 case GerberPrecision::GP_6: job.m_precision = 6; break;
2633 }
2634
2635 return ExecuteBoardJob( pcbContext(), job );
2636}
2637
2638
2641{
2642 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2643 return tl::unexpected( validation.error() );
2644
2645 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2646 return tl::unexpected( *busy );
2647
2650 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2651
2653
2654 if( std::optional<ApiResponseStatus> unitError =
2655 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportDrill" ) )
2656 {
2657 return tl::unexpected( *unitError );
2658 }
2659
2663
2664 if( aCtx.Request.has_excellon() )
2665 {
2666 const ExcellonFormatOptions& excellonOptions = aCtx.Request.excellon();
2667
2668 if( excellonOptions.has_mirror_y() )
2669 job.m_excellonMirrorY = excellonOptions.mirror_y();
2670
2671 if( excellonOptions.has_minimal_header() )
2672 job.m_excellonMinimalHeader = excellonOptions.minimal_header();
2673
2674 if( excellonOptions.has_combine_pth_npth() )
2675 job.m_excellonCombinePTHNPTH = excellonOptions.combine_pth_npth();
2676
2677 if( excellonOptions.has_route_oval_holes() )
2678 job.m_excellonOvalDrillRoute = excellonOptions.route_oval_holes();
2679 }
2680
2681 if( aCtx.Request.map_format() != DrillMapFormat::DMF_UNKNOWN )
2682 {
2683 job.m_generateMap = true;
2685 }
2686
2687 job.m_gerberPrecision = aCtx.Request.gerber_precision() == DrillGerberPrecision::DGP_4_5 ? 5 : 6;
2688
2689 if( aCtx.Request.has_gerber_generate_tenting() )
2690 job.m_generateTenting = aCtx.Request.gerber_generate_tenting();
2691
2692 if( aCtx.Request.report_format() != DrillReportFormat::DRF_UNKNOWN )
2693 {
2694 job.m_generateReport = true;
2695
2696 if( aCtx.Request.has_report_filename() )
2697 job.m_reportPath = wxString::FromUTF8( aCtx.Request.report_filename() );
2698 }
2699
2700 return ExecuteBoardJob( pcbContext(), job );
2701}
2702
2703
2706{
2707 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2708 return tl::unexpected( validation.error() );
2709
2710 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2711 return tl::unexpected( *busy );
2712
2715 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2716
2717 if( aCtx.Request.has_use_drill_place_file_origin() )
2718 job.m_useDrillPlaceFileOrigin = aCtx.Request.use_drill_place_file_origin();
2719
2720 job.m_smdOnly = aCtx.Request.smd_only();
2721 job.m_excludeFootprintsWithTh = aCtx.Request.exclude_footprints_with_th();
2722 job.m_excludeDNP = aCtx.Request.exclude_dnp();
2723 job.m_excludeBOM = aCtx.Request.exclude_from_bom();
2724 job.m_negateBottomX = aCtx.Request.negate_bottom_x();
2725 job.m_singleFile = aCtx.Request.single_file();
2726 job.m_nakedFilename = aCtx.Request.naked_filename();
2727 if( aCtx.Request.has_include_board_edge_for_gerber() )
2728 job.m_gerberBoardEdge = aCtx.Request.include_board_edge_for_gerber();
2729
2730 job.m_variant = wxString::FromUTF8( aCtx.Request.variant() );
2731
2733
2734 if( std::optional<ApiResponseStatus> unitError =
2735 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportPosition" ) )
2736 {
2737 return tl::unexpected( *unitError );
2738 }
2739
2742
2743 return ExecuteBoardJob( pcbContext(), job );
2744}
2745
2746
2749{
2750 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2751 return tl::unexpected( validation.error() );
2752
2753 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2754 return tl::unexpected( *busy );
2755
2758 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2759
2760 job.m_flipBottomPads = aCtx.Request.flip_bottom_pads();
2761 job.m_useIndividualShapes = aCtx.Request.use_individual_shapes();
2762 job.m_storeOriginCoords = aCtx.Request.store_origin_coords();
2763 job.m_useDrillOrigin = aCtx.Request.use_drill_origin();
2764 job.m_useUniquePins = aCtx.Request.use_unique_pins();
2765
2766 return ExecuteBoardJob( pcbContext(), job );
2767}
2768
2769
2772{
2773 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2774 return tl::unexpected( validation.error() );
2775
2776 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2777 return tl::unexpected( *busy );
2778
2781 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2782
2783 job.m_drawingSheet = wxString::FromUTF8( aCtx.Request.drawing_sheet() );
2784 if( !aCtx.Request.variant().empty() )
2785 {
2786 job.m_variantNames.push_back( wxString::FromUTF8( aCtx.Request.variant() ) );
2787 }
2788
2789 if( aCtx.Request.has_precision() )
2790 job.m_precision = aCtx.Request.precision();
2791
2792 job.m_compress = aCtx.Request.compress();
2793 job.m_colInternalId = wxString::FromUTF8( aCtx.Request.internal_id_column() );
2794 job.m_colMfgPn = wxString::FromUTF8( aCtx.Request.manufacturer_part_number_column() );
2795 job.m_colMfg = wxString::FromUTF8( aCtx.Request.manufacturer_column() );
2796 job.m_colDistPn = wxString::FromUTF8( aCtx.Request.distributor_part_number_column() );
2797 job.m_colDist = wxString::FromUTF8( aCtx.Request.distributor_column() );
2798 job.m_bomRev = wxString::FromUTF8( aCtx.Request.bom_revision() );
2799
2800 if( std::optional<ApiResponseStatus> unitError =
2801 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportIpc2581" ) )
2802 {
2803 return tl::unexpected( *unitError );
2804 }
2805
2807 job.m_checkZonesBeforeExport = aCtx.Request.check_zones();
2810 ApplyFabExportContent( aCtx.Request.content(), job );
2811
2812 return ExecuteBoardJob( pcbContext(), job );
2813}
2814
2815
2818{
2819 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2820 return tl::unexpected( validation.error() );
2821
2822 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2823 return tl::unexpected( *busy );
2824
2827 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2828
2829 return ExecuteBoardJob( pcbContext(), job );
2830}
2831
2832
2833void ApplyFabExportContent( const FabExportContent& aContent, JOB_EXPORT_PCB_FAB& aJob )
2834{
2835 aJob.m_dataSet = FromProtoEnum<JOB_EXPORT_PCB_FAB::DATA_SET>( aContent.data_set() );
2836 aJob.m_sections = aContent.sections().empty()
2837 ? std::nullopt
2838 : std::optional<wxString>( wxString::FromUTF8( aContent.sections() ) );
2839 aJob.m_netNames = FromProtoEnum<JOB_EXPORT_PCB_FAB::NET_NAMES>( aContent.net_names() );
2840}
2841
2842
2843void ApplyOdbJobRequest( const RunBoardJobExportODB& aRequest, JOB_EXPORT_PCB_ODB& aJob )
2844{
2845 aJob.m_drawingSheet = wxString::FromUTF8( aRequest.drawing_sheet() );
2846
2847 if( aRequest.variant_names_size() > 0 )
2848 {
2849 for( const std::string& name : aRequest.variant_names() )
2850 aJob.m_variantNames.push_back( wxString::FromUTF8( name ) );
2851 }
2852 else if( !aRequest.variant().empty() )
2853 {
2854 aJob.m_variantNames.push_back( wxString::FromUTF8( aRequest.variant() ) );
2855 }
2856
2858 aRequest.variant_packaging() );
2859
2860 if( aRequest.has_precision() )
2861 aJob.m_precision = aRequest.precision();
2862
2863 aJob.m_units = FromProtoEnum<JOB_EXPORT_PCB_FAB::UNITS>( aRequest.units() );
2864 aJob.m_checkZonesBeforeExport = aRequest.check_zones();
2866 aJob.m_colMfgPn = wxString::FromUTF8( aRequest.manufacturer_part_number_column() );
2867 aJob.m_origin = FromProtoEnum<JOB_EXPORT_PCB_ODB::ORIGIN>( aRequest.origin() );
2868 aJob.m_productName = wxString::FromUTF8( aRequest.product_name() );
2869 ApplyFabExportContent( aRequest.content(), aJob );
2870}
2871
2872
2875{
2876 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2877 return tl::unexpected( validation.error() );
2878
2879 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2880 return tl::unexpected( *busy );
2881
2884 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2885
2886 if( std::optional<ApiResponseStatus> unitError =
2887 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportODB" ) )
2888 {
2889 return tl::unexpected( *unitError );
2890 }
2891
2892 ApplyOdbJobRequest( aCtx.Request, job );
2893
2894 return ExecuteBoardJob( pcbContext(), job );
2895}
2896
2897
2900{
2901 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2902 return tl::unexpected( validation.error() );
2903
2904 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2905 return tl::unexpected( *busy );
2906
2909 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2910
2912
2913 if( std::optional<ApiResponseStatus> unitError =
2914 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportStats" ) )
2915 {
2916 return tl::unexpected( *unitError );
2917 }
2918
2920
2921 job.m_excludeFootprintsWithoutPads = aCtx.Request.exclude_footprints_without_pads();
2922 job.m_subtractHolesFromBoardArea = aCtx.Request.subtract_holes_from_board_area();
2923 job.m_subtractHolesFromCopperAreas = aCtx.Request.subtract_holes_from_copper_areas();
2924
2925 return ExecuteBoardJob( pcbContext(), job );
2926}
2927
2928
2931{
2932 if( HANDLER_RESULT<bool> valid = validateDocument( aCtx.Request.job_settings().document() ); !valid )
2933 return tl::unexpected( valid.error() );
2934
2935 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2936 return tl::unexpected( *busy );
2937
2938 switch( aCtx.Request.units() )
2939 {
2940 case types::Units::U_UNKNOWN:
2941 case types::Units::U_MM:
2942 case types::Units::U_MILS:
2943 break;
2944
2945 default:
2946 {
2947 ApiResponseStatus e;
2948 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2949 e.set_error_message( "RunBoardJobExportIdf supports only mm and mils units" );
2950 return tl::unexpected( e );
2951 }
2952 }
2953
2956 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2957
2960
2961 if( aCtx.Request.origin_mode() == IdfOriginMode::IOM_USER )
2962 {
2963 // m_userOrigin is expressed in the units selected by m_units
2964 double scale = job.m_units == IDF_SETTINGS::UNITS::MM ? pcbIUScale.MM_PER_IU : pcbIUScale.MILS_PER_IU;
2965 job.m_userOrigin = UnpackVector2( aCtx.Request.user_origin() ) * scale;
2966 }
2967
2968 job.m_includeUnspecified = aCtx.Request.include_unspecified();
2969 job.m_includeDNP = aCtx.Request.include_dnp();
2970 job.m_calculateHeightFromModels = !aCtx.Request.only_use_explicit_heights();
2971
2972 if( aCtx.Request.has_part_number_field() )
2973 job.m_partNumberField = wxString::FromUTF8( aCtx.Request.part_number_field() );
2974 else
2975 job.m_partNumberField = wxS( "Value" );
2976
2977 return ExecuteBoardJob( pcbContext(), job );
2978}
2979
2980
2983{
2984 wxLogTrace( traceApi, "Received announce from frame %d at %s",
2985 aCtx.Request.frame_type(), aCtx.Request.socket_path() );
2986
2987 CROSS_PROBE_CLIENT::RegisterPeer( static_cast<FRAME_T>( aCtx.Request.frame_type() ),
2988 aCtx.Request.socket_path() );
2989
2990 CrossProbeAnnounceResponse response;
2991 response.set_status( CPS_OK );
2992 return response;
2993}
2994
2995
2997{
2998 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SyncSelection" ) )
2999 return tl::unexpected( *headless );
3000
3001 SyncSelectionResponse response;
3002
3004
3005 if( !settings.on_selection && aCtx.Request.context() != SyncSelectionContext::SSC_EXPLICIT )
3006 {
3007 response.set_status( CPS_DISABLED );
3008 response.set_message( "implicit selection sync disabled by user" );
3009 return response;
3010 }
3011
3012 std::vector<BOARD_ITEM*> items =
3014
3015 frame()->m_ProbingSchToPcb = true; // recursion guard
3016
3017 if( aCtx.Request.mode() == SyncSelectionMode::SSM_ITEMS_AND_NETS )
3019 else
3021
3022 // Update 3D viewer highlighting
3023 frame()->Update3DView( false, frame()->GetPcbNewSettings()->m_Display.m_Live3DRefresh );
3024
3025 frame()->m_ProbingSchToPcb = false;
3026
3027 if( settings.flash_selection )
3028 {
3029 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE) PCB: flash enabled, items=%zu", items.size() );
3030 if( items.empty() )
3031 {
3032 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE) PCB: nothing to flash" );
3033 }
3034 else
3035 {
3036 std::vector<BOARD_ITEM*> boardItems;
3037 std::copy( items.begin(), items.end(), std::back_inserter( boardItems ) );
3038 frame()->StartCrossProbeFlash( boardItems );
3039 }
3040 }
3041 else
3042 {
3043 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE) PCB: flash disabled" );
3044 }
3045
3046 response.set_status( CPS_OK );
3047 return response;
3048}
3049
3050
3052 const HANDLER_CONTEXT<HighlightNets>& aCtx )
3053{
3054 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "HighlightNets" ) )
3055 return tl::unexpected( *headless );
3056
3057 HighlightNetsResponse response;
3058 CROSS_PROBING_SETTINGS& crossProbingSettings = frame()->GetPcbNewSettings()->m_CrossProbing;
3059
3061 || aCtx.ClientName == KiwayClientName )
3062 {
3063 if( !crossProbingSettings.auto_highlight )
3064 {
3065 response.set_status( CPS_DISABLED );
3066 response.set_message( "net highlight cross-probing disabled by user" );
3067 return response;
3068 }
3069 }
3070
3071 std::vector<wxString> nets;
3072
3073 for( const std::string& name : aCtx.Request.net_name() )
3074 nets.emplace_back( wxString::FromUTF8( name ) );
3075
3077
3078 response.set_status( CPS_OK );
3079 return response;
3080}
3081
3082
3084{
3085 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3086 return tl::unexpected( documentValidation.error() );
3087
3088 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3089 return tl::unexpected( *busy );
3090
3092 VariantsResponse response;
3093
3094 response.mutable_document()->CopyFrom( aCtx.Request.document() );
3095
3096 for( const wxString& name : board->GetVariantNames() )
3097 {
3098 types::DesignVariant* var = response.add_variants();
3099 var->set_name( name.ToUTF8() );
3100 var->set_description( board->GetVariantDescription( name ).ToUTF8() );
3101 }
3102
3103 return response;
3104}
3105
3106
3108{
3109 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3110 return tl::unexpected( documentValidation.error() );
3111
3112 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3113 return tl::unexpected( *busy );
3114
3116
3117 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3118
3119 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
3120 {
3121 ApiResponseStatus e;
3122 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3123 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
3124 return tl::unexpected( e );
3125 }
3126
3127 if( board->HasVariant( name ) )
3128 {
3129 ApiResponseStatus e;
3130 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3131 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.name() ) );
3132 return tl::unexpected( e );
3133 }
3134
3135 board->AddVariant( name );
3136
3137 if( aCtx.Request.has_description() )
3138 board->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
3139
3140 onModified();
3141
3142 if( frame() )
3144
3145 return Empty();
3146}
3147
3148
3150{
3151 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3152 return tl::unexpected( documentValidation.error() );
3153
3154 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3155 return tl::unexpected( *busy );
3156
3158
3159 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3160
3161 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
3162 {
3163 ApiResponseStatus e;
3164 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3165 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
3166 return tl::unexpected( e );
3167 }
3168
3169 if( !board->HasVariant( name ) )
3170 {
3171 ApiResponseStatus e;
3172 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3173 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3174 return tl::unexpected( e );
3175 }
3176
3177 board->DeleteVariant( name );
3178
3179 onModified();
3180
3181 if( frame() )
3183
3184 return Empty();
3185}
3186
3187
3189{
3190 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3191 return tl::unexpected( documentValidation.error() );
3192
3193 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3194 return tl::unexpected( *busy );
3195
3197
3198 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
3199 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
3200
3201 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3202 {
3203 ApiResponseStatus e;
3204 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3205 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
3206 return tl::unexpected( e );
3207 }
3208
3209 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3210 {
3211 ApiResponseStatus e;
3212 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3213 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
3214 return tl::unexpected( e );
3215 }
3216
3217 if( !board->HasVariant( oldName ) )
3218 {
3219 ApiResponseStatus e;
3220 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3221 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
3222 return tl::unexpected( e );
3223 }
3224
3225 if( board->HasVariant( newName ) )
3226 {
3227 ApiResponseStatus e;
3228 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3229 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
3230 return tl::unexpected( e );
3231 }
3232
3233 board->RenameVariant( oldName, newName );
3234
3235 onModified();
3236
3237 if( frame() )
3239
3240 return Empty();
3241}
3242
3243
3245{
3246 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3247 return tl::unexpected( documentValidation.error() );
3248
3249 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3250 return tl::unexpected( *busy );
3251
3253
3254 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
3255 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
3256
3257 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3258 {
3259 ApiResponseStatus e;
3260 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3261 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
3262 return tl::unexpected( e );
3263 }
3264
3265 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3266 {
3267 ApiResponseStatus e;
3268 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3269 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
3270 return tl::unexpected( e );
3271 }
3272
3273 if( !board->HasVariant( oldName ) )
3274 {
3275 ApiResponseStatus e;
3276 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3277 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
3278 return tl::unexpected( e );
3279 }
3280
3281 if( board->HasVariant( newName ) )
3282 {
3283 ApiResponseStatus e;
3284 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3285 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
3286 return tl::unexpected( e );
3287 }
3288
3289 board->CopyVariant( oldName, newName,
3290 aCtx.Request.has_new_description() ? wxString::FromUTF8( aCtx.Request.new_description() )
3291 : wxString() );
3292
3293 onModified();
3294
3295 if( frame() )
3297
3298 return Empty();
3299}
3300
3301
3303{
3304 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3305 return tl::unexpected( documentValidation.error() );
3306
3307 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3308 return tl::unexpected( *busy );
3309
3311
3312 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3313
3314 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || !board->HasVariant( name ) )
3315 {
3316 ApiResponseStatus e;
3317 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3318 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3319 return tl::unexpected( e );
3320 }
3321
3322 board->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
3323
3324 onModified();
3325
3326 return Empty();
3327}
3328
3329
3331{
3332 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3333 return tl::unexpected( documentValidation.error() );
3334
3335 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3336 return tl::unexpected( *busy );
3337
3339
3340 if( aCtx.Request.has_name() && !aCtx.Request.name().empty() )
3341 {
3342 if( wxString name = wxString::FromUTF8( aCtx.Request.name() ); !board->HasVariant( name ) )
3343 {
3344 ApiResponseStatus e;
3345 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3346 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3347 return tl::unexpected( e );
3348 }
3349 }
3350
3351 wxString varName = aCtx.Request.has_name() ? wxString::FromUTF8( aCtx.Request.name() ) : wxString();
3352
3353 if( frame() )
3354 frame()->SetCurrentVariant( varName );
3355 else
3356 board->SetCurrentVariant( varName );
3357
3358 return Empty();
3359}
3360
3361
3364{
3365 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3366 return tl::unexpected( documentValidation.error() );
3367
3368 CurrentVariantResponse response;
3369
3370 if( wxString current = pcbContext()->GetBoard()->GetCurrentVariant(); !current.IsEmpty() )
3371 response.set_name( current.ToUTF8() );
3372
3373 return response;
3374}
3375
3376
3379{
3380 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
3381 {
3382 ApiResponseStatus e;
3383 e.set_status( ApiStatusCode::AS_UNHANDLED );
3384 return tl::unexpected( e );
3385 }
3386
3387 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3388 return tl::unexpected( *busy );
3389
3390 LIB_ID libId = UnpackLibId( aCtx.Request.lib_id() );
3391
3392 if( !libId.IsValid() )
3393 {
3394 ApiResponseStatus e;
3395 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3396 e.set_error_message( "lib_id must specify both a library nickname and an entry name" );
3397 return tl::unexpected( e );
3398 }
3399
3401
3402 // TODO update if we support inner layer footprints in the future
3403 if( !IsExternalCopperLayer( layer ) )
3404 {
3405 ApiResponseStatus e;
3406 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3407 e.set_error_message( "layer must be F_Cu or B_Cu" );
3408 return tl::unexpected( e );
3409 }
3410
3411 std::unique_ptr<FOOTPRINT> footprint( LoadFootprintFromProject( board(), libId ) );
3412
3413 if( !footprint )
3414 {
3415 ApiResponseStatus e;
3416 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3417 e.set_error_message( fmt::format( "footprint '{}' not found", libId.Format().wx_str() ) );
3418 return tl::unexpected( e );
3419 }
3420
3421 footprint->SetUuid( KIID() );
3422 footprint->RunOnChildren(
3423 []( BOARD_ITEM* aChild )
3424 {
3425 aChild->ResetUuid();
3426 },
3428
3429 footprint->SetParent( board() );
3430
3431 footprint->SetPosition( UnpackVector2( aCtx.Request.position() ) );
3432
3433 if( aCtx.Request.has_orientation() )
3434 footprint->SetOrientationDegrees( aCtx.Request.orientation().value_degrees() );
3435
3436 footprint->SetLayerAndFlip( layer );
3437
3438 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
3439 FOOTPRINT* placed = footprint.release();
3440 commit->Add( placed );
3441
3442 if( !m_activeClients.contains( aCtx.ClientName ) )
3443 pushCurrentCommit( aCtx.ClientName, _( "Placed footprint via API" ) );
3444
3445 PlaceFromLibraryResponse response;
3446 response.mutable_header()->CopyFrom( aCtx.Request.header() );
3447 placed->Serialize( *response.mutable_item() );
3448
3449 return response;
3450}
const char * name
KICOMMON_API types::KiCadObjectType ToProtoEnum(KICAD_T aValue)
KICOMMON_API KICAD_T FromProtoEnum(types::KiCadObjectType aValue)
Definition api_enums.cpp:55
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:47
std::optional< ApiResponseStatus > ValidatePaginationModeForSingleOrPerFile(kiapi::board::jobs::BoardJobPaginationMode aMode, const std::string &aCommandName)
HANDLER_RESULT< Empty > unpackEmbeddedFiles(EMBEDDED_FILES &aOutput, const common::types::EmbeddedFiles &aProto)
void ApplyOdbJobRequest(const RunBoardJobExportODB &aRequest, JOB_EXPORT_PCB_ODB &aJob)
std::optional< ApiResponseStatus > ApplyBoardPlotSettings(const BoardPlotSettings &aSettings, JOB_EXPORT_PCB_PLOT &aJob)
std::optional< ApiResponseStatus > ValidateUnitsInchMm(types::Units aUnits, const std::string &aCommandName)
HANDLER_RESULT< types::RunJobResponse > ExecuteBoardJob(PCB_CONTEXT *aContext, JOB &aJob)
void ApplyFabExportContent(const FabExportContent &aContent, JOB_EXPORT_PCB_FAB &aJob)
static const std::vector< KICAD_T > s_allowedBoardTypes
void ApplyOdbJobRequest(const kiapi::board::jobs::RunBoardJobExportODB &aRequest, JOB_EXPORT_PCB_ODB &aJob)
void ApplyFabExportContent(const kiapi::board::jobs::FabExportContent &aContent, JOB_EXPORT_PCB_FAB &aJob)
BASE_SCREEN class implementation.
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:128
#define ZONE_FILL_OP
static TOOL_ACTION selectionClear
Clear the current selection.
Definition actions.h:220
static TOOL_ACTION gridSetOrigin
Definition actions.h:191
API_HANDLER_BOARD(std::shared_ptr< BOARD_CONTEXT > aContext, EDA_BASE_FRAME *aFrame=nullptr)
std::unique_ptr< COMMIT > createCommit() override
Override this to create an appropriate COMMIT subclass for the frame in question.
TOOL_MANAGER * toolManager() const
std::vector< KICAD_T > parseRequestedItemTypes(const google::protobuf::RepeatedField< int > &aTypes)
PROJECT & project() const
void pushCurrentCommit(const std::string &aClientName, const wxString &aMessage) override
BOARD * board() const
BOARD_CONTEXT * context() const
std::optional< ApiResponseStatus > checkForHeadless(const std::string &aCommandName) const
std::optional< BOARD_ITEM * > getItemById(const KIID &aId) const
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)
COMMIT * getCurrentCommit(const std::string &aClientName)
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< ImportNetlistResponse > handleImportNetlist(const HANDLER_CONTEXT< ImportNetlist > &aCtx)
HANDLER_RESULT< BoardPlotSettingsResponse > handleGetBoardPlotSettings(const HANDLER_CONTEXT< GetBoardPlotSettings > &aCtx)
HANDLER_RESULT< Empty > handleDeleteVariant(const HANDLER_CONTEXT< commands::DeleteVariant > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportPdf(const HANDLER_CONTEXT< RunBoardJobExportPdf > &aCtx)
HANDLER_RESULT< google::protobuf::Empty > handleSetEmbeddedFiles(const HANDLER_CONTEXT< SetEmbeddedFiles > &aCtx)
HANDLER_RESULT< commands::CurrentVariantResponse > handleGetCurrentVariant(const HANDLER_CONTEXT< commands::GetCurrentVariant > &aCtx)
HANDLER_RESULT< BoardDesignRulesResponse > handleSetBoardDesignRules(const HANDLER_CONTEXT< SetBoardDesignRules > &aCtx)
API_HANDLER_PCB(PCB_EDIT_FRAME *aFrame)
HANDLER_RESULT< Empty > handleAddVariant(const HANDLER_CONTEXT< commands::AddVariant > &aCtx)
HANDLER_RESULT< commands::GetItemsResponse > handleGetConnectedItems(const HANDLER_CONTEXT< GetConnectedItems > &aCtx)
HANDLER_RESULT< commands::VariantsResponse > handleGetVariants(const HANDLER_CONTEXT< commands::GetVariants > &aCtx)
HANDLER_RESULT< types::Vector2 > handleGetBoardOrigin(const HANDLER_CONTEXT< GetBoardOrigin > &aCtx)
HANDLER_RESULT< commands::GetItemsResponse > handleGetItemsByNetClass(const HANDLER_CONTEXT< GetItemsByNetClass > &aCtx)
HANDLER_RESULT< Empty > handleSaveDocumentAs(const HANDLER_CONTEXT< commands::SaveDocumentAs > &aCtx)
HANDLER_RESULT< NetClassForNetsResponse > handleGetNetClassForNets(const HANDLER_CONTEXT< GetNetClassForNets > &aCtx)
HANDLER_RESULT< commands::CrossProbeAnnounceResponse > handleCrossProbeAnnounce(const HANDLER_CONTEXT< commands::CrossProbeAnnounce > &aCtx)
HANDLER_RESULT< Empty > handleCopyVariant(const HANDLER_CONTEXT< commands::CopyVariant > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportIpcD356(const HANDLER_CONTEXT< RunBoardJobExportIpcD356 > &aCtx)
PCB_CONTEXT * pcbContext() const
HANDLER_RESULT< BoardDesignRulesResponse > handleGetBoardDesignRules(const HANDLER_CONTEXT< GetBoardDesignRules > &aCtx)
HANDLER_RESULT< commands::GetDocumentModifiedStateResponse > handleGetDocumentModifiedState(const HANDLER_CONTEXT< commands::GetDocumentModifiedState > &aCtx) override
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportGerbers(const HANDLER_CONTEXT< RunBoardJobExportGerbers > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportODB(const HANDLER_CONTEXT< RunBoardJobExportODB > &aCtx)
HANDLER_RESULT< Empty > handleSaveCopyOfDocument(const HANDLER_CONTEXT< commands::SaveCopyOfDocument > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExport3D(const HANDLER_CONTEXT< RunBoardJobExport3D > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportPng(const HANDLER_CONTEXT< RunBoardJobExportPng > &aCtx)
HANDLER_RESULT< Empty > handleSetCurrentVariant(const HANDLER_CONTEXT< commands::SetCurrentVariant > &aCtx)
void setDrawingSheetFileName(const wxString &aFileName) override
HANDLER_RESULT< commands::GetItemsResponse > handleGetItemsByNet(const HANDLER_CONTEXT< GetItemsByNet > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportIpc2581(const HANDLER_CONTEXT< RunBoardJobExportIpc2581 > &aCtx)
std::optional< TITLE_BLOCK * > getTitleBlock(const DocumentSpecifier &aDocument) override
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportPosition(const HANDLER_CONTEXT< RunBoardJobExportPosition > &aCtx)
HANDLER_RESULT< Empty > handleSetBoardOrigin(const HANDLER_CONTEXT< SetBoardOrigin > &aCtx)
HANDLER_RESULT< Empty > handleSetBoardPlotSettings(const HANDLER_CONTEXT< SetBoardPlotSettings > &aCtx)
HANDLER_RESULT< BoardLayerNameResponse > handleGetBoardLayerName(const HANDLER_CONTEXT< GetBoardLayerName > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportStats(const HANDLER_CONTEXT< RunBoardJobExportStats > &aCtx)
void onModified() override
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportPs(const HANDLER_CONTEXT< RunBoardJobExportPs > &aCtx)
HANDLER_RESULT< CustomRulesResponse > handleGetCustomDesignRules(const HANDLER_CONTEXT< GetCustomDesignRules > &aCtx)
bool setPageSettings(const DocumentSpecifier &aDocument, const PAGE_INFO &aPageInfo) override
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportSvg(const HANDLER_CONTEXT< RunBoardJobExportSvg > &aCtx)
kiapi::common::types::DocumentType thisDocumentType() const override
Override this to specify which document type this editor handles.
HANDLER_RESULT< commands::GetOpenDocumentsResponse > handleGetOpenDocuments(const HANDLER_CONTEXT< commands::GetOpenDocuments > &aCtx)
HANDLER_RESULT< commands::SyncSelectionResponse > handleSyncSelection(const HANDLER_CONTEXT< commands::SyncSelection > &aCtx)
HANDLER_RESULT< BoardEditorAppearanceSettings > handleGetBoardEditorAppearanceSettings(const HANDLER_CONTEXT< GetBoardEditorAppearanceSettings > &aCtx)
HANDLER_RESULT< NetsResponse > handleGetNets(const HANDLER_CONTEXT< GetNets > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportDxf(const HANDLER_CONTEXT< RunBoardJobExportDxf > &aCtx)
HANDLER_RESULT< Empty > handleSetVariantDescription(const HANDLER_CONTEXT< commands::SetVariantDescription > &aCtx)
HANDLER_RESULT< Empty > handleSaveDocument(const HANDLER_CONTEXT< commands::SaveDocument > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportDrill(const HANDLER_CONTEXT< RunBoardJobExportDrill > &aCtx)
HANDLER_RESULT< commands::GetItemsResponse > handleGetItems(const HANDLER_CONTEXT< commands::GetItems > &aCtx)
HANDLER_RESULT< kiapi::common::commands::PlaceFromLibraryResponse > handlePlaceFootprintFromLibrary(const HANDLER_CONTEXT< kiapi::board::commands::PlaceFootprintFromLibrary > &aCtx)
HANDLER_RESULT< InjectDrcErrorResponse > handleInjectDrcError(const HANDLER_CONTEXT< InjectDrcError > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportRender(const HANDLER_CONTEXT< RunBoardJobExportRender > &aCtx)
PCB_EDIT_FRAME * frame() const
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportGencad(const HANDLER_CONTEXT< RunBoardJobExportGencad > &aCtx)
HANDLER_RESULT< BoardEnabledLayersResponse > handleSetBoardEnabledLayers(const HANDLER_CONTEXT< SetBoardEnabledLayers > &aCtx)
HANDLER_RESULT< Empty > handleRenameVariant(const HANDLER_CONTEXT< commands::RenameVariant > &aCtx)
std::optional< PAGE_INFO > getPageSettings(const DocumentSpecifier &aDocument) override
HANDLER_RESULT< Empty > handleSetBoardEditorAppearanceSettings(const HANDLER_CONTEXT< SetBoardEditorAppearanceSettings > &aCtx)
tl::expected< bool, ApiResponseStatus > validateDocumentInternal(const DocumentSpecifier &aDocument) const override
HANDLER_RESULT< Empty > handleRefillZones(const HANDLER_CONTEXT< RefillZones > &aCtx)
HANDLER_RESULT< commands::HighlightNetsResponse > handleHighlightNets(const HANDLER_CONTEXT< commands::HighlightNets > &aCtx)
HANDLER_RESULT< types::RunJobResponse > handleRunBoardJobExportIdf(const HANDLER_CONTEXT< RunBoardJobExportIdf > &aCtx)
HANDLER_RESULT< google::protobuf::Empty > handleAddEmbeddedFiles(const HANDLER_CONTEXT< AddEmbeddedFiles > &aCtx)
HANDLER_RESULT< common::types::EmbeddedFiles > handleGetEmbeddedFiles(const HANDLER_CONTEXT< GetEmbeddedFiles > &aCtx)
HANDLER_RESULT< BoardLayerResponse > handleGetBoardLayerByName(const HANDLER_CONTEXT< GetBoardLayerByName > &aCtx)
HANDLER_RESULT< Empty > handleRevertDocument(const HANDLER_CONTEXT< commands::RevertDocument > &aCtx)
HANDLER_RESULT< CustomRulesResponse > handleSetCustomDesignRules(const HANDLER_CONTEXT< SetCustomDesignRules > &aCtx)
void onNetSettingsChanged() override
Called on all registered handlers after project net settings (netclasses or netclass assignments) hav...
wxString getDrawingSheetFileName() override
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
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
virtual KIWAY * GetKiway() const =0
virtual BOARD * GetBoard() const =0
Container for design settings for a BOARD object.
std::map< int, SEVERITY > m_DRCSeverities
void SetGridOrigin(const VECTOR2I &aOrigin)
std::vector< DIFF_PAIR_DIMENSION > m_DiffPairDimensionsList
const VECTOR2I & GetGridOrigin() const
TEARDROP_PARAMETERS_LIST m_TeardropParamsList
The parameters of teardrops for the different teardrop targets (via/pad, track end).
void SetAuxOrigin(const VECTOR2I &aOrigin)
const VECTOR2I & GetAuxOrigin() const
std::vector< int > m_TrackWidthList
std::vector< VALIDATION_ERROR > ValidateDesignRules(std::optional< EDA_UNITS > aUnits=std::nullopt) const
Validate design settings values and return per-field errors.
std::vector< VIA_DIMENSION > m_ViasDimensionsList
std::set< DRC_EXCLUSION, DRC_EXCLUSION_COMPARE > m_DrcExclusions
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
virtual void CopyFrom(const BOARD_ITEM *aOther)
void ResetUuid()
Definition board_item.h:280
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
void SetPlotOptions(const PCB_PLOT_PARAMS &aOptions)
Definition board.h:1022
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition board.cpp:3846
const PAGE_INFO & GetPageSettings() const
Definition board.h:1018
void SetDesignSettings(const BOARD_DESIGN_SETTINGS &aSettings)
Definition board.cpp:1307
TITLE_BLOCK & GetTitleBlock()
Definition board.h:1024
PCB_LAYER_ID GetLayerID(const wxString &aLayerName) const
Return the ID of a layer.
Definition board.cpp:923
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition board.h:1019
const wxString & GetFileName() const
Definition board.h:453
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1021
wxString GetDesignRulesPath() const
Return the absolute path to the design rules file for this board.
Definition board.cpp:438
BOARD_DESIGN_SETTINGS & GetDesignSettings() const
Definition board.cpp:1301
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:759
Represent a set of changes (additions, deletions or modifications) of a data model (e....
Definition commit.h:68
COMMIT & Add(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Add a new item to the model.
Definition commit.h:74
static void RegisterPeer(FRAME_T aFrameType, const std::string &aSocketPath)
void ToProto(kiapi::board::CustomRuleConstraint &aProto) const
Definition drc_rule.cpp:415
Container for an DRC exclusion, which is a PCB_MARKER plus an optional comment.
static DRC_EXCLUSION FromProto(const kiapi::board::DrcExclusion &aMessage)
const kiapi::board::DrcExclusion & ToProto() const
static std::shared_ptr< DRC_ITEM > Create(int aErrorCode)
Constructs a DRC_ITEM for the given error code.
Definition drc_item.cpp:444
void Parse(std::vector< std::shared_ptr< DRC_RULE > > &aRules, REPORTER *aReporter)
static wxString ExtractRuleComment(const wxString &aOriginalText)
Extract comment lines from a rule.
static wxString ExtractRuleText(const wxString &aContent, const wxString &aRuleName)
Extract the complete original text of a rule from file content.
static wxString FormatRuleFromProto(const kiapi::board::CustomRule &aRule, wxString *aErrorText=nullptr)
Definition drc_rule.cpp:121
virtual void Refresh(bool aEraseBackground=true, const wxRect *aRect=nullptr) override
EMBEDDED_FILE * AddFile(const wxFileName &aName, bool aOverwrite)
Load a file from disk and adds it to the collection.
const std::map< wxString, std::shared_ptr< EMBEDDED_FILE > > & EmbeddedFileMap() const
Provide an iterable view of the file collection.
static const TOOL_EVENT SelectedItemsModified
Selected item had a property changed (except movement)
Definition actions.h:352
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
virtual const wxString What() const
A composite of Problem() and Where()
JOB_EXPORT_PCB_3D::FORMAT m_format
EXPORTER_STEP_PARAMS m_3dparams
Despite the name; also used for other formats.
std::optional< wxString > m_sections
Section key that replaces the optional sections of the data set Unset lets the data set select them a...
std::vector< wxString > m_variantNames
IDF_SETTINGS::COORD_ORIGIN m_originMode
VECTOR2D m_userOrigin
In whatever units the user specified in m_units.
IDF_SETTINGS::UNITS m_units
VARIANT_PACKAGING m_variantPackaging
ODB_COMPRESSION m_compressionMode
wxString m_pdfBackgroundColor
The background color specified in a hex string.
GEN_MODE m_pdfGenMode
uused by the cli, will be removed when the other behavior is deprecated
bool m_useDrillOrigin
Used by SVG/DXF/PDF/Gerbers.
LSEQ m_plotOnAllLayersSequence
Layers to include on all individual layer prints.
DRILL_MARKS m_drillShapeOption
Used by SVG & PDF.
bool m_mirror
Common Options.
wxString m_variant
Variant name for variant-aware filtering.
VECTOR3D m_lightBottomIntensity
VECTOR3D m_lightTopIntensity
VECTOR3D m_lightCameraIntensity
VECTOR3D m_rotation
wxString m_filename
bool m_useBoardStackupColors
VECTOR3D m_lightSideIntensity
std::string m_appearancePreset
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
void UpdateAllLayersColor()
Apply the new coloring scheme to all layers.
Definition view.cpp:884
Definition kiid.h:46
std::string AsStdString() const
Definition kiid.cpp:270
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:740
@ FACE_PCB
pcbnew DSO
Definition kiway.h:356
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
LSEQ is a sequence (and therefore also a set) of PCB_LAYER_IDs.
Definition lseq.h:47
LSET is a set of PCB_LAYER_IDs.
Definition lset.h:37
static const LSET & AllCuMask()
return AllCuMask( MAX_CU_LAYERS );
Definition lset.cpp:604
static int NameToLayer(wxString &aName)
Return the layer number from a layer name.
Definition lset.cpp:113
A collection of nets and the parameters used to route or test these nets.
Definition netclass.h:43
bool ContainsNetclassWithName(const wxString &netclass) const
Determines if the given netclass name is a constituent of this (maybe aggregate) netclass.
Definition netclass.cpp:324
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
Definition netclass.cpp:232
Handle the data for a net.
Definition netinfo.h:50
NETCLASS * GetNetClass()
Definition netinfo.h:101
int GetNetCode() const
Definition netinfo.h:104
Container for NETINFO_ITEM elements, which are the nets.
Definition netinfo.h:232
NETINFO_ITEM * GetNetItem(int aNetCode) const
Store information read from a netlist along with the flags used to update the NETLIST in the BOARD.
Describe the page size and margins of a paper page on which to eventually print or plot.
Definition page_info.h:75
DISPLAY_OPTIONS m_Display
static TOOL_ACTION zoneFillAll
static TOOL_ACTION syncSelection
Sets selection to specified items, zooms to fit, if enabled.
Definition pcb_actions.h:63
static TOOL_ACTION drillSetOrigin
static TOOL_ACTION syncSelectionWithNets
Sets selection to specified items with connected nets, zooms to fit, if enabled.
Definition pcb_actions.h:66
const PCB_DISPLAY_OPTIONS & GetDisplayOptions() const
Display options control the way tracks, vias, outlines and other things are shown (for instance solid...
PCBNEW_SETTINGS * GetPcbNewSettings() const
PCB_DRAW_PANEL_GAL * GetCanvas() const override
Return a pointer to GAL-based canvas of given EDA draw frame.
void SetDisplayOptions(const PCB_DISPLAY_OPTIONS &aOptions, bool aRefresh=true)
Update the current display options.
virtual void Update3DView(bool aMarkDirty, bool aRefresh, const wxString *aTitle=nullptr)
Update the 3D view, if the viewer is opened by this frame.
PCB-editor-specific context; extends BOARD_CONTEXT with save/filename operations.
Definition pcb_context.h:38
virtual wxString GetCurrentFileName() const =0
virtual void OnNetlistChanged(BOARD_NETLIST_UPDATER &aUpdater)=0
Post-import board sync (nets, classes, DRC, ratsnest, new footprint placement).
virtual bool ReadNetlistFromFile(const wxString &aFilename, NETLIST &aNetlist, REPORTER &aReporter)=0
Read a netlist file and preload component footprints.
virtual void SetContentModified(bool aModified=true)=0
virtual std::unique_ptr< BOARD_NETLIST_UPDATER > MakeNetlistUpdater()=0
Create a netlist updater bound to this context's board.
bool m_FlipBoardView
true if the board is flipped to show the mirrored view
HIGH_CONTRAST_MODE m_ContrastModeDisplay
How inactive layers are displayed.
NET_COLOR_MODE m_NetColorMode
How to use color overrides on specific nets and netclasses.
virtual KIGFX::PCB_VIEW * GetView() const override
Return a pointer to the KIGFX::VIEW instance used in the panel.
The main frame for Pcbnew.
void LoadDrawingSheet()
Load the drawing sheet file.
void OnModify() override
Must be called after a board change to set the modified flag.
void StartCrossProbeFlash(const std::vector< BOARD_ITEM * > &aItems)
void UpdateVariantSelectionCtrl()
Update the variant selection dropdown with the current board's variant names.
void SetCurrentVariant(const wxString &aVariantName)
Set the current variant on the board and update the drawing sheet's cached variant name and descripti...
void UpdateUserInterface()
Update the layer manager and other widgets from the board setup (layer and items visibility,...
void HandleRemoteNetHighlight(const std::vector< wxString > &aNetNames)
const KIID GetUUID() const override
Definition pcb_marker.h:50
Parameters and options when plotting/printing a board.
bool GetNegative() const
void SetDrillMarksType(DRILL_MARKS aVal)
bool GetUseAuxOrigin() const
LSEQ GetPlotOnAllLayersSequence() const
bool GetHideDNPFPsOnFabLayers() const
void SetLayerSelection(const LSET &aSelection)
void SetPlotReference(bool aFlag)
void SetSketchPadsOnFabLayers(bool aFlag)
bool GetMirror() const
bool GetCrossoutDNPFPsOnFabLayers() const
bool GetSketchDNPFPsOnFabLayers() const
void SetPlotOnAllLayersSequence(LSEQ aSeq)
void SetPlotFrameRef(bool aFlag)
void SetSketchDNPFPsOnFabLayers(bool aFlag)
double GetScale() const
void SetPlotPadNumbers(bool aFlag)
LSET GetLayerSelection() const
bool GetPlotReference() const
void SetScale(double aVal)
void SetMirror(bool aFlag)
void SetBlackAndWhite(bool blackAndWhite)
void SetSubtractMaskFromSilk(bool aSubtract)
bool GetSketchPadsOnFabLayers() const
void SetHideDNPFPsOnFabLayers(bool aFlag)
bool GetSubtractMaskFromSilk() const
void SetPlotValue(bool aFlag)
bool GetPlotPadNumbers() const
DRILL_MARKS GetDrillMarksType() const
bool GetPlotValue() const
void SetNegative(bool aFlag)
bool GetBlackAndWhite() const
void SetUseAuxOrigin(bool aAux)
bool GetPlotFrameRef() const
void SetCrossoutDNPFPsOnFabLayers(bool aFlag)
std::vector< KIID > KIIDS
Definition rc_item.h:83
TEARDROP_PARAMETERS_LIST is a helper class to handle the list of TEARDROP_PARAMETERS needed to build ...
bool m_UseRoundShapesOnly
True to create teardrops for round shapes only.
bool m_TargetVias
True to create teardrops for vias.
bool m_TargetPTHPads
True to create teardrops for pads with holes.
bool m_TargetTrack2Track
True to create teardrops at the end of a track connected to the end of another track having a differe...
TEARDROP_PARAMETERS * GetParameters(TARGET_TD aTdType)
bool m_TargetSMDPads
True to create teardrops for pads SMD, edge connectors,.
TEARDROP_PARAMETARS is a helper class to handle parameters needed to build teardrops for a board thes...
double m_BestWidthRatio
The height of a teardrop as ratio between height and size of pad/via.
int m_TdMaxLen
max allowed length for teardrops in IU. <= 0 to disable
bool m_AllowUseTwoTracks
True to create teardrops using 2 track segments if the first in too small.
int m_TdMaxWidth
max allowed height for teardrops in IU. <= 0 to disable
double m_BestLengthRatio
The length of a teardrop as ratio between length and size of pad/via.
double m_WidthtoSizeFilterRatio
The ratio (H/D) between the via/pad size and the track width max value to create a teardrop 1....
bool m_TdOnPadsInZones
A filter to exclude pads inside zone fills.
bool m_Enabled
Flag to enable teardrops.
bool m_CurvedEdges
True if the teardrop should be curved.
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Master controller class:
T * GetTool()
Return the tool of given type or nullptr if there is no such tool registered.
bool RunAction(const std::string &aActionName, T aParam)
Run the specified action immediately, pausing the current action to run the new one.
TOOL_BASE * FindTool(int aId) const
Search for a tool with given ID.
void RegisterTool(TOOL_BASE *aTool)
Add a tool to the manager set and sets it up.
wxString wx_str() const
Definition utf8.cpp:41
A wrapper for reporting to a wxString object.
Definition reporter.h:242
Handle actions specific to filling copper zones.
bool Fill(const std::vector< ZONE * > &aZones, bool aCheck=false, wxWindow *aParent=nullptr)
Fills the given list of zones.
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:832
PCB_DRC_CODE
Definition drc_item.h:35
@ DRCE_GENERIC_ERROR
Definition drc_item.h:94
@ DRCE_GENERIC_WARNING
Definition drc_item.h:93
static std::string ToStdString(const wxString &aStr)
#define _(s)
@ RECURSE
Definition eda_item.h:51
FRAME_T
The set of EDA_BASE_FRAME derivatives, typically stored in EDA_BASE_FRAME::m_Ident.
Definition frame_type.h:29
static const std::string KiCadPcbFileExtension
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
std::string source
bool IsPcbLayer(int aLayer)
Test whether a layer is a valid layer for Pcbnew.
Definition layer_ids.h:692
#define MAX_CU_LAYERS
Definition layer_ids.h:172
@ LAYER_DRC_WARNING
Layer for DRC markers with RPT_SEVERITY_WARNING.
Definition layer_ids.h:297
@ LAYER_DRC_ERROR
Layer for DRC markers with RPT_SEVERITY_ERROR.
Definition layer_ids.h:273
bool IsExternalCopperLayer(int aLayerId)
Test whether a layer is an external (F_Cu or B_Cu) copper layer.
Definition layer_ids.h:714
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ F_CrtYd
Definition layer_ids.h:112
@ Edge_Cuts
Definition layer_ids.h:108
@ UNSELECTED_LAYER
Definition layer_ids.h:58
@ Margin
Definition layer_ids.h:109
@ B_CrtYd
Definition layer_ids.h:111
@ UNDEFINED_LAYER
Definition layer_ids.h:57
@ PCB_LAYER_ID_COUNT
Definition layer_ids.h:167
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
void PackLayerSet(google::protobuf::RepeatedField< int > &aOutput, const LSET &aLayerSet)
void PackEmbeddedFiles(common::types::EmbeddedFiles &aOutput, const EMBEDDED_FILES &aFiles)
bool UnpackEmbeddedFiles(EMBEDDED_FILES &aOutput, const common::types::EmbeddedFiles &aProto)
LSET UnpackLayerSet(const google::protobuf::RepeatedField< int > &aProtoLayerSet)
std::vector< BOARD_ITEM * > FindItemsFromSyncSelection(const BOARD *aBoard, const google::protobuf::RepeatedPtrField< kiapi::common::commands::SelectionSpec > &aItems)
Resolve a cross-probe selection request against a board.
KICOMMON_API VECTOR3D UnpackVector3D(const types::Vector3D &aInput)
KICOMMON_API VECTOR2I UnpackVector2(const types::Vector2 &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API void PackVector2(types::Vector2 &aOutput, const VECTOR2I &aInput, const EDA_IU_SCALE &aScale)
KICOMMON_API LIB_ID UnpackLibId(const types::LibraryIdentifier &aId)
const KICOMMON_API std::string KiwayClientName
const KICOMMON_API std::string StandaloneCrossProbeClientName
STL namespace.
std::shared_ptr< PCB_CONTEXT > CreatePcbFrameContext(PCB_EDIT_FRAME *aFrame)
Class to handle a set of BOARD_ITEMs.
FOOTPRINT * LoadFootprintFromProject(BOARD *aBoard, const LIB_ID &aFootprintId, bool aKeepUuid)
Load a footprint from the project library table and apply board default settings.
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
SEVERITY
@ RPT_SEVERITY_WARNING
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_IGNORE
#define SKIP_CONNECTIVITY
Definition sch_commit.h:41
const int scale
wxString GetDefaultVariantName()
Cross-probing behavior.
bool flash_selection
Flash newly cross-probed selection (visual attention aid).
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
@ TARGET_UNKNOWN
@ TARGET_ROUND
@ TARGET_TRACK
std::string netlist
IbisParser parser & reporter
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
@ PCB_CONSTRAINT_T
a geometric constraint between board items
Definition typeinfo.h:237
@ PCB_SHAPE_T
class PCB_SHAPE, a segment not on copper layers
Definition typeinfo.h:80
@ PCB_DIM_ORTHOGONAL_T
class PCB_DIM_ORTHOGONAL, a linear dimension constrained to x/y
Definition typeinfo.h:98
@ PCB_DIM_LEADER_T
class PCB_DIM_LEADER, a leader dimension (graphic item)
Definition typeinfo.h:95
@ PCB_GENERATOR_T
class PCB_GENERATOR, generator on a layer
Definition typeinfo.h:83
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_DIM_CENTER_T
class PCB_DIM_CENTER, a center point marking (graphic item)
Definition typeinfo.h:96
@ PCB_GROUP_T
class PCB_GROUP, a set of BOARD_ITEMs
Definition typeinfo.h:103
@ PCB_TEXTBOX_T
class PCB_TEXTBOX, wrapped text on a layer
Definition typeinfo.h:85
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_TEXT_T
class PCB_TEXT, text on a layer
Definition typeinfo.h:84
@ PCB_REFERENCE_IMAGE_T
class PCB_REFERENCE_IMAGE, bitmap on a layer
Definition typeinfo.h:81
@ PCB_BARCODE_T
class PCB_BARCODE, a barcode (graphic item)
Definition typeinfo.h:93
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_GRID_ITEM_T
a subgrid placed on a board
Definition typeinfo.h:238
@ PCB_DIM_ALIGNED_T
class PCB_DIM_ALIGNED, a linear dimension (graphic item)
Definition typeinfo.h:94
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_DIMENSION_T
class PCB_DIMENSION_BASE: abstract dimension meta-type
Definition typeinfo.h:92
@ PCB_TABLE_T
class PCB_TABLE, table of PCB_TABLECELLs
Definition typeinfo.h:86
@ PCB_POINT_T
class PCB_POINT, a 0-dimensional point
Definition typeinfo.h:105
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
@ PCB_DIM_RADIAL_T
class PCB_DIM_RADIAL, a radius or diameter dimension
Definition typeinfo.h:97
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
#define ZONE_FILLER_TOOL_NAME