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_3d_body.h>
58#include <pcb_point.h>
59#include <kiway.h>
60#include <drc/drc_item.h>
74#include <project_pcb.h>
80#include <pcb_plot_params.h>
82#include <jobs/job_pcb_render.h>
83#include <layer_ids.h>
86#include <project.h>
88#include <tool/actions.h>
89#include <string_utils.h>
90#include <tool/tool_manager.h>
91#include <tools/pcb_actions.h>
95#include <zone.h>
96#include <zone_filler.h>
97
98#include <api/common/types/base_types.pb.h>
99
101#include <reporter.h>
102#include <api/board/board_rules.pb.h>
104#include <google/protobuf/util/json_util.h>
106#include <drc/drc_rule_parser.h>
110#include <trace_helpers.h>
111#include <wx/ffile.h>
112
113using namespace kiapi::common::commands;
114using namespace kiapi::board::commands;
115using types::CommandStatus;
116using types::DocumentType;
117using types::ItemRequestStatus;
118
119
124
125
126API_HANDLER_PCB::API_HANDLER_PCB( std::shared_ptr<PCB_CONTEXT> aContext, PCB_EDIT_FRAME* aFrame ) :
127 API_HANDLER_BOARD( std::move( aContext ), aFrame )
128{
135
137
150
161
170
180
215
218
222
225}
226
227
229{
230 return static_cast<PCB_EDIT_FRAME*>( m_frame );
231}
232
233
236{
237 if( aCtx.Request.type() != DocumentType::DOCTYPE_PCB )
238 {
239 ApiResponseStatus e;
240 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
241 e.set_status( ApiStatusCode::AS_UNHANDLED );
242 return tl::unexpected( e );
243 }
244
245 GetOpenDocumentsResponse response;
246 common::types::DocumentSpecifier doc;
247
248 wxFileName fn( pcbContext()->GetCurrentFileName() );
249
250 doc.set_type( DocumentType::DOCTYPE_PCB );
251 doc.set_board_filename( fn.GetFullName() );
252
253 doc.mutable_project()->set_name( project().GetProjectName().ToStdString() );
254 doc.mutable_project()->set_path( project().GetProjectDirectory().ToStdString() );
255
256 response.mutable_documents()->Add( std::move( doc ) );
257 return response;
258}
259
260
263{
264 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
265 return tl::unexpected( documentValidation.error() );
266
267 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
268 return tl::unexpected( *busy );
269
270 if( board()->GetFileName().IsEmpty() )
271 {
272 ApiResponseStatus e;
273 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
274 e.set_error_message( "the board is untitled; use SaveDocumentAs to save it to a new file" );
275 return tl::unexpected( e );
276 }
277
278 if( !pcbContext()->SaveBoard() )
279 {
280 ApiResponseStatus e;
281 e.set_status( ApiStatusCode::AS_INTERNAL_ERROR );
282 e.set_error_message( "board could not be saved" );
283 return tl::unexpected( e );
284 }
285
286 return Empty();
287}
288
289
291{
292 if( Pgm().IsGUI() )
293 {
294 ApiResponseStatus e;
295 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
296 e.set_error_message( "SaveDocumentAs is not available in GUI mode" );
297 return tl::unexpected( e );
298 }
299
300 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
301 return tl::unexpected( documentValidation.error() );
302
303 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
304 return tl::unexpected( *busy );
305
306 wxString pathStr = wxString::FromUTF8( aCtx.Request.path() );
307
308 if( pathStr.IsEmpty() )
309 {
310 ApiResponseStatus e;
311 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
312 e.set_error_message( "path is required" );
313 return tl::unexpected( e );
314 }
315
316 wxFileName oldBoardFile( pcbContext()->GetCurrentFileName() );
317
318 if( !pcbContext()->SaveBoardAs( pathStr ) )
319 {
320 ApiResponseStatus e;
321 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
322 e.set_error_message( fmt::format( "failed to save board to '{}'", pathStr.ToStdString() ) );
323 return tl::unexpected( e );
324 }
325
326 if( KICAD_API_SERVER* server = apiServer() )
327 {
328 wxFileName newBoardFile( pathStr );
329 newBoardFile.SetExt( FILEEXT::KiCadPcbFileExtension );
330
331 server->NotifyDocumentRenamed( thisDocumentType(), oldBoardFile.GetFullPath(), newBoardFile.GetFullPath() );
332 }
333
334 return Empty();
335}
336
337
340{
341 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
342 return tl::unexpected( documentValidation.error() );
343
344 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
345 return tl::unexpected( *busy );
346
347 wxFileName boardPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.path() ) ) );
348
349 if( !boardPath.IsOk() || !boardPath.IsDirWritable() )
350 {
351 ApiResponseStatus e;
352 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
353 e.set_error_message( fmt::format( "save path '{}' could not be opened",
354 boardPath.GetFullPath().ToStdString() ) );
355 return tl::unexpected( e );
356 }
357
358 if( boardPath.FileExists()
359 && ( !boardPath.IsFileWritable() || !aCtx.Request.options().overwrite() ) )
360 {
361 ApiResponseStatus e;
362 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
363 e.set_error_message( fmt::format( "save path '{}' exists and cannot be overwritten",
364 boardPath.GetFullPath().ToStdString() ) );
365 return tl::unexpected( e );
366 }
367
368 if( boardPath.GetExt() != FILEEXT::KiCadPcbFileExtension )
369 {
370 ApiResponseStatus e;
371 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
372 e.set_error_message( fmt::format( "save path '{}' must have a kicad_pcb extension",
373 boardPath.GetFullPath().ToStdString() ) );
374 return tl::unexpected( e );
375 }
376
377 BOARD* board = this->board();
378
379 if( board->GetFileName().Matches( boardPath.GetFullPath() ) )
380 {
381 if( !pcbContext()->SaveBoard() )
382 {
383 ApiResponseStatus e;
384 e.set_status( ApiStatusCode::AS_INTERNAL_ERROR );
385 e.set_error_message( "board could not be saved" );
386 return tl::unexpected( e );
387 }
388
389 return Empty();
390 }
391
392 bool includeProject = true;
393
394 if( aCtx.Request.has_options() )
395 includeProject = aCtx.Request.options().include_project();
396
397 if( !pcbContext()->SavePcbCopy( boardPath.GetFullPath(), includeProject, /* aHeadless = */ true ) )
398 {
399 ApiResponseStatus e;
400 e.set_status( ApiStatusCode::AS_INTERNAL_ERROR );
401 e.set_error_message( fmt::format( "board could not be saved to '{}'",
402 boardPath.GetFullPath().ToStdString() ) );
403 return tl::unexpected( e );
404 }
405
406 return Empty();
407}
408
409
412{
413 // Validate first so a request meant for another editor's document is not captured here
414 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
415 return tl::unexpected( documentValidation.error() );
416
417 // Reloading frees every item, so refuse while any client transaction is open; the staged
418 // commit would otherwise dangle
419 if( !m_commits.empty() )
420 {
421 ApiResponseStatus e;
422 e.set_status( ApiStatusCode::AS_BUSY );
423 e.set_error_message( "cannot revert while a commit is open" );
424 return tl::unexpected( e );
425 }
426
427 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
428 return tl::unexpected( *busy );
429
430 if( !pcbContext()->RevertToSaved() )
431 {
432 ApiResponseStatus e;
433 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
434 e.set_error_message( "could not revert: there is no saved file on disk to revert to" );
435 return tl::unexpected( e );
436 }
437
438 return Empty();
439}
440
441
442tl::expected<bool, ApiResponseStatus> API_HANDLER_PCB::validateDocumentInternal( const DocumentSpecifier& aDocument ) const
443{
444 if( aDocument.type() != DocumentType::DOCTYPE_PCB )
445 {
446 ApiResponseStatus e;
447 e.set_status( ApiStatusCode::AS_UNHANDLED );
448 return tl::unexpected( e );
449 }
450
451 wxFileName fn( pcbContext()->GetCurrentFileName() );
452
453 if( aDocument.board_filename().compare( fn.GetFullName() ) != 0 )
454 {
455 ApiResponseStatus e;
456 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
457 e.set_error_message( fmt::format( "the requested document {} is not open",
458 aDocument.board_filename() ) );
459 return tl::unexpected( e );
460 }
461
462 return true;
463}
464
465
466// Board types that are directly retrievable by GetItems
488
489
491{
492 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
493 {
494 ApiResponseStatus e;
495 // No message needed for AS_UNHANDLED; this is an internal flag for the API server
496 e.set_status( ApiStatusCode::AS_UNHANDLED );
497 return tl::unexpected( e );
498 }
499
500 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
501 return tl::unexpected( *busy );
502
503 GetItemsResponse response;
504
505 BOARD* board = this->board();
506 std::vector<BOARD_ITEM*> items;
507 std::set<KICAD_T> typesRequested, typesHandled;
508
509 std::vector<KICAD_T> requestedTypes = parseRequestedItemTypes( aCtx.Request.types() );
510
511 if( aCtx.Request.types().empty() )
512 requestedTypes.assign( s_allowedBoardTypes.begin(), s_allowedBoardTypes.end() );
513
514 for( KICAD_T type : requestedTypes )
515 {
516 typesRequested.emplace( type );
517
518 if( typesHandled.count( type ) )
519 continue;
520
521 switch( type )
522 {
523 case PCB_TRACE_T:
524 case PCB_ARC_T:
525 case PCB_VIA_T:
526 std::ranges::copy( board->Tracks(), std::back_inserter( items ) );
527 typesHandled.insert( { PCB_TRACE_T, PCB_ARC_T, PCB_VIA_T } );
528 break;
529
530 case PCB_PAD_T:
531 for( FOOTPRINT* fp : board->Footprints() )
532 std::ranges::copy( fp->Pads(), std::back_inserter( items ) );
533
534 typesHandled.insert( PCB_PAD_T );
535 break;
536
537 case PCB_FOOTPRINT_T:
538 std::ranges::copy( board->Footprints(), std::back_inserter( items ) );
539 typesHandled.insert( PCB_FOOTPRINT_T );
540 break;
541
542 case PCB_SHAPE_T:
543 case PCB_TABLE_T:
544 case PCB_TEXT_T:
545 case PCB_TEXTBOX_T:
546 case PCB_BARCODE_T:
548 case PCB_GRID_ITEM_T:
549 case PCB_DIMENSION_T:
550 for( BOARD_ITEM* item : board->Drawings() )
551 {
552 if( item->Type() == type || BaseType( item->Type() ) == type )
553 items.emplace_back( item );
554 }
555
556 typesHandled.insert( type );
557 break;
558
559 case PCB_ZONE_T:
560 std::ranges::copy( board->Zones(), std::back_inserter( items ) );
561 typesHandled.insert( PCB_ZONE_T );
562 break;
563
564 case PCB_GROUP_T:
565 std::ranges::copy( board->Groups(), std::back_inserter( items ) );
566 typesHandled.insert( PCB_GROUP_T );
567 break;
568
569 case PCB_POINT_T:
570 std::ranges::copy( board->Points(), std::back_inserter( items ) );
571 typesHandled.insert( PCB_POINT_T );
572 break;
573
574 case PCB_3D_BODY_T:
575 // Not supported on a board context yet
576 break;
577
578 case PCB_GENERATOR_T:
579 std::ranges::copy( board->Generators(), std::back_inserter( items ) );
580 typesHandled.insert( PCB_GENERATOR_T );
581 break;
582
583 case PCB_CONSTRAINT_T:
584 std::ranges::copy( board->Constraints(), std::back_inserter( items ) );
585 typesHandled.insert( PCB_CONSTRAINT_T );
586 break;
587
588 default:
589 break;
590 }
591 }
592
593 if( typesHandled.empty() )
594 {
595 ApiResponseStatus e;
596 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
597 e.set_error_message( "none of the requested types are valid for a Board object" );
598 return tl::unexpected( e );
599 }
600
601 for( const BOARD_ITEM* item : items )
602 {
603 if( !typesRequested.count( item->Type() ) && !typesRequested.count( BaseType( item->Type() ) ) )
604 continue;
605
606 google::protobuf::Any itemBuf;
607 item->Serialize( itemBuf );
608 response.mutable_items()->Add( std::move( itemBuf ) );
609 }
610
611 response.set_status( ItemRequestStatus::IRS_OK );
612 return response;
613}
614
615
618{
619 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
620 return tl::unexpected( documentValidation.error() );
621
622 if( aCtx.Request.copper_layer_count() < 2 || aCtx.Request.copper_layer_count() % 2 != 0 )
623 {
624 ApiResponseStatus e;
625 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
626 e.set_error_message( "copper_layer_count must be an even number of at least 2" );
627 return tl::unexpected( e );
628 }
629
630 if( aCtx.Request.copper_layer_count() > MAX_CU_LAYERS )
631 {
632 ApiResponseStatus e;
633 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
634 e.set_error_message( fmt::format( "copper_layer_count must be below %d", MAX_CU_LAYERS ) );
635 return tl::unexpected( e );
636 }
637
638 int copperLayerCount = static_cast<int>( aCtx.Request.copper_layer_count() );
639 LSET enabled = board::UnpackLayerSet( aCtx.Request.layers() );
640
641 // Sanitize the input
642 enabled |= LSET( { Edge_Cuts, Margin, F_CrtYd, B_CrtYd } );
643 enabled &= ~LSET::AllCuMask();
644 enabled |= LSET::AllCuMask( copperLayerCount );
645
646 BOARD* board = this->board();
647
648 LSET previousEnabled = board->GetEnabledLayers();
649 LSET changedLayers = enabled ^ previousEnabled;
650
651 board->SetEnabledLayers( enabled );
652 board->SetVisibleLayers( board->GetVisibleLayers() | changedLayers );
653
654 LSEQ removedLayers;
655
656 for( PCB_LAYER_ID layer_id : previousEnabled )
657 {
658 if( !enabled[layer_id] && board->HasItemsOnLayer( layer_id ) )
659 removedLayers.push_back( layer_id );
660 }
661
662 bool modified = false;
663
664 if( !removedLayers.empty() )
665 {
667
668 for( PCB_LAYER_ID layer_id : removedLayers )
669 modified |= board->RemoveAllItemsOnLayer( layer_id );
670 }
671
672 if( enabled != previousEnabled || modified )
673 onModified();
674
675 BoardEnabledLayersResponse response;
676
677 response.set_copper_layer_count( copperLayerCount );
678 board::PackLayerSet( *response.mutable_layers(), enabled );
679
680 return response;
681}
682
683
686{
687 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
688 return tl::unexpected( documentValidation.error() );
689
690 common::types::EmbeddedFiles response;
691 board::PackEmbeddedFiles( response, *board()->GetEmbeddedFiles() );
692 return response;
693}
694
695
696HANDLER_RESULT<Empty> unpackEmbeddedFiles( EMBEDDED_FILES& aOutput, const common::types::EmbeddedFiles& aProto )
697{
698 if( !board::UnpackEmbeddedFiles( aOutput, aProto ) )
699 {
700 ApiResponseStatus e;
701 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
702 e.set_error_message( "embedded file validation failed" );
703 return tl::unexpected( e );
704 }
705
706 return Empty();
707}
708
709
711{
712 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
713 return tl::unexpected( documentValidation.error() );
714
715 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
716 return tl::unexpected( *busy );
717
718 EMBEDDED_FILES files;
720
721 if( !result.has_value() )
722 return result;
723
724 EMBEDDED_FILES* boardFiles = board()->GetEmbeddedFiles();
725
726 for( const std::shared_ptr<EMBEDDED_FILES::EMBEDDED_FILE>& file : files.EmbeddedFileMap() | std::views::values )
727 {
728 auto copy = std::make_shared<EMBEDDED_FILES::EMBEDDED_FILE>( *file );
729 boardFiles->AddFile( copy );
730 }
731
732 onModified();
733 return result;
734}
735
736
738{
739 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
740 return tl::unexpected( documentValidation.error() );
741
742 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
743 return tl::unexpected( *busy );
744
745 HANDLER_RESULT<Empty> result = unpackEmbeddedFiles( *board()->GetEmbeddedFiles(),
746 aCtx.Request.files() );
747
748 if( !result.has_value() )
749 return result;
750
751 onModified();
752 return result;
753}
754
755
758{
759 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
760
761 if( !documentValidation )
762 return tl::unexpected( documentValidation.error() );
763
765 BoardDesignRulesResponse response;
766 kiapi::board::BoardDesignRules* rules = response.mutable_rules();
767
768 kiapi::board::MinimumConstraints* constraints = rules->mutable_constraints();
769
770 constraints->mutable_min_clearance()->set_value_nm( bds.m_MinClearance );
771 constraints->mutable_min_groove_width()->set_value_nm( bds.m_MinGrooveWidth );
772 constraints->mutable_min_connection_width()->set_value_nm( bds.m_MinConn );
773 constraints->mutable_min_track_width()->set_value_nm( bds.m_TrackMinWidth );
774 constraints->mutable_min_via_annular_width()->set_value_nm( bds.m_ViasMinAnnularWidth );
775 constraints->mutable_min_via_size()->set_value_nm( bds.m_ViasMinSize );
776 constraints->mutable_min_through_drill()->set_value_nm( bds.m_MinThroughDrill );
777 constraints->mutable_min_microvia_size()->set_value_nm( bds.m_MicroViasMinSize );
778 constraints->mutable_min_microvia_drill()->set_value_nm( bds.m_MicroViasMinDrill );
779 constraints->mutable_copper_edge_clearance()->set_value_nm( bds.m_CopperEdgeClearance );
780 constraints->mutable_hole_clearance()->set_value_nm( bds.m_HoleClearance );
781 constraints->mutable_hole_to_hole_min()->set_value_nm( bds.m_HoleToHoleMin );
782 constraints->mutable_silk_clearance()->set_value_nm( bds.m_SilkClearance );
783 constraints->set_min_resolved_spokes( bds.m_MinResolvedSpokes );
784 constraints->mutable_min_silk_text_height()->set_value_nm( bds.m_MinSilkTextHeight );
785 constraints->mutable_min_silk_text_thickness()->set_value_nm( bds.m_MinSilkTextThickness );
786
787 kiapi::board::PredefinedSizes* sizes = rules->mutable_predefined_sizes();
788
789 for( size_t ii = 1; ii < bds.m_TrackWidthList.size(); ++ii )
790 sizes->add_tracks()->mutable_width()->set_value_nm( bds.m_TrackWidthList[ii] );
791
792 for( size_t ii = 1; ii < bds.m_ViasDimensionsList.size(); ++ii )
793 {
794 kiapi::board::PresetViaDimension* via = sizes->add_vias();
795 via->mutable_diameter()->set_value_nm( bds.m_ViasDimensionsList[ii].m_Diameter );
796 via->mutable_drill()->set_value_nm( bds.m_ViasDimensionsList[ii].m_Drill );
797 }
798
799 for( size_t ii = 1; ii < bds.m_DiffPairDimensionsList.size(); ++ii )
800 {
801 kiapi::board::PresetDiffPairDimension* pair = sizes->add_diff_pairs();
802 pair->mutable_width()->set_value_nm( bds.m_DiffPairDimensionsList[ii].m_Width );
803 pair->mutable_gap()->set_value_nm( bds.m_DiffPairDimensionsList[ii].m_Gap );
804 pair->mutable_via_gap()->set_value_nm( bds.m_DiffPairDimensionsList[ii].m_ViaGap );
805 }
806
807 kiapi::board::SolderMaskPasteDefaults* maskPaste = rules->mutable_solder_mask_paste();
808
809 maskPaste->mutable_mask_expansion()->set_value_nm( bds.m_SolderMaskExpansion );
810 maskPaste->mutable_mask_min_width()->set_value_nm( bds.m_SolderMaskMinWidth );
811 maskPaste->mutable_mask_to_copper_clearance()->set_value_nm( bds.m_SolderMaskToCopperClearance );
812 maskPaste->mutable_paste_margin()->set_value_nm( bds.m_SolderPasteMargin );
813 maskPaste->set_paste_margin_ratio( bds.m_SolderPasteMarginRatio );
814 maskPaste->set_allow_soldermask_bridges_in_footprints( bds.m_AllowSoldermaskBridgesInFPs );
815
816 kiapi::board::TeardropDefaults* teardrops = rules->mutable_teardrops();
817
818 teardrops->set_target_vias( bds.m_TeardropParamsList.m_TargetVias );
819 teardrops->set_target_pth_pads( bds.m_TeardropParamsList.m_TargetPTHPads );
820 teardrops->set_target_smd_pads( bds.m_TeardropParamsList.m_TargetSMDPads );
821 teardrops->set_target_track_to_track( bds.m_TeardropParamsList.m_TargetTrack2Track );
822 teardrops->set_use_round_shapes_only( bds.m_TeardropParamsList.m_UseRoundShapesOnly );
823
825
826 for( int target = TARGET_ROUND; target <= TARGET_TRACK; ++target )
827 {
828 const TEARDROP_PARAMETERS* params = tdList.GetParameters( static_cast<TARGET_TD>( target ) );
829 kiapi::board::TeardropTargetEntry* entry = teardrops->add_target_params();
830
832 static_cast<TARGET_TD>( target ) ) );
833 entry->mutable_params()->set_enabled( params->m_Enabled );
834 entry->mutable_params()->mutable_max_length()->set_value_nm( params->m_TdMaxLen );
835 entry->mutable_params()->mutable_max_width()->set_value_nm( params->m_TdMaxWidth );
836 entry->mutable_params()->set_best_length_ratio( params->m_BestLengthRatio );
837 entry->mutable_params()->set_best_width_ratio( params->m_BestWidthRatio );
838 entry->mutable_params()->set_width_to_size_filter_ratio( params->m_WidthtoSizeFilterRatio );
839 entry->mutable_params()->set_curved_edges( params->m_CurvedEdges );
840 entry->mutable_params()->set_allow_two_tracks( params->m_AllowUseTwoTracks );
841 entry->mutable_params()->set_on_pads_in_zones( params->m_TdOnPadsInZones );
842 }
843
844 kiapi::board::ViaProtectionDefaults* viaProtection = rules->mutable_via_protection();
845
846 viaProtection->set_tent_front( bds.m_TentViasFront );
847 viaProtection->set_tent_back( bds.m_TentViasBack );
848 viaProtection->set_cover_front( bds.m_CoverViasFront );
849 viaProtection->set_cover_back( bds.m_CoverViasBack );
850 viaProtection->set_plug_front( bds.m_PlugViasFront );
851 viaProtection->set_plug_back( bds.m_PlugViasBack );
852 viaProtection->set_cap( bds.m_CapVias );
853 viaProtection->set_fill( bds.m_FillVias );
854
855 for( const auto& [errorCode, severity] : bds.m_DRCSeverities )
856 {
857 board::DrcSeveritySetting* setting = rules->add_severities();
858 setting->set_rule_type(
860 setting->set_severity( ToProtoEnum<SEVERITY, types::RuleSeverity>( severity ) );
861 }
862
863 for( const DRC_EXCLUSION& exclusion : bds.m_DrcExclusions )
864 rules->add_exclusions()->CopyFrom( exclusion.ToProto() );
865
866 response.set_custom_rules_status( CRS_NONE );
867
868 wxString rulesPath = board()->GetDesignRulesPath();
869
870 if( !rulesPath.IsEmpty() && wxFileName::IsFileReadable( rulesPath ) )
871 {
872 wxFFile file( rulesPath, "r" );
873 wxString content;
874 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
875
876 if( !file.IsOpened() )
877 {
878 response.set_custom_rules_status( CRS_INVALID );
879 return response;
880 }
881
882 file.ReadAll( &content );
883 file.Close();
884
885 try
886 {
887 DRC_RULES_PARSER parser( content, "File" );
888 parser.Parse( parsedRules, nullptr );
889 response.set_custom_rules_status( CRS_VALID );
890 }
891 catch( const IO_ERROR& )
892 {
893 response.set_custom_rules_status( CRS_INVALID );
894 }
895 }
896
897 return response;
898}
899
900
903{
904 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
905
906 if( !documentValidation )
907 return tl::unexpected( documentValidation.error() );
908
909 BOARD_DESIGN_SETTINGS newSettings( board()->GetDesignSettings() );
910 const kiapi::board::BoardDesignRules& rules = aCtx.Request.rules();
911
912 if( rules.has_constraints() )
913 {
914 const kiapi::board::MinimumConstraints& constraints = rules.constraints();
915
916 newSettings.m_MinClearance = constraints.min_clearance().value_nm();
917 newSettings.m_MinGrooveWidth = constraints.min_groove_width().value_nm();
918 newSettings.m_MinConn = constraints.min_connection_width().value_nm();
919 newSettings.m_TrackMinWidth = constraints.min_track_width().value_nm();
920 newSettings.m_ViasMinAnnularWidth = constraints.min_via_annular_width().value_nm();
921 newSettings.m_ViasMinSize = constraints.min_via_size().value_nm();
922 newSettings.m_MinThroughDrill = constraints.min_through_drill().value_nm();
923 newSettings.m_MicroViasMinSize = constraints.min_microvia_size().value_nm();
924 newSettings.m_MicroViasMinDrill = constraints.min_microvia_drill().value_nm();
925 newSettings.m_CopperEdgeClearance = constraints.copper_edge_clearance().value_nm();
926 newSettings.m_HoleClearance = constraints.hole_clearance().value_nm();
927 newSettings.m_HoleToHoleMin = constraints.hole_to_hole_min().value_nm();
928 newSettings.m_SilkClearance = constraints.silk_clearance().value_nm();
929 newSettings.m_MinResolvedSpokes = constraints.min_resolved_spokes();
930 newSettings.m_MinSilkTextHeight = constraints.min_silk_text_height().value_nm();
931 newSettings.m_MinSilkTextThickness = constraints.min_silk_text_thickness().value_nm();
932 }
933
934 if( rules.has_predefined_sizes() )
935 {
936 newSettings.m_TrackWidthList.clear();
937 newSettings.m_TrackWidthList.emplace_back( 0 );
938
939 for( const kiapi::board::PresetTrackWidth& track : rules.predefined_sizes().tracks() )
940 newSettings.m_TrackWidthList.emplace_back( track.width().value_nm() );
941
942 newSettings.m_ViasDimensionsList.clear();
943 newSettings.m_ViasDimensionsList.emplace_back( 0, 0 );
944
945 for( const kiapi::board::PresetViaDimension& via : rules.predefined_sizes().vias() )
946 {
947 newSettings.m_ViasDimensionsList.emplace_back( static_cast<int>( via.diameter().value_nm() ),
948 static_cast<int>( via.drill().value_nm() ) );
949 }
950
951 newSettings.m_DiffPairDimensionsList.clear();
952 newSettings.m_DiffPairDimensionsList.emplace_back( 0, 0, 0 );
953
954 for( const kiapi::board::PresetDiffPairDimension& pair : rules.predefined_sizes().diff_pairs() )
955 {
956 newSettings.m_DiffPairDimensionsList.emplace_back(
957 static_cast<int>( pair.width().value_nm() ),
958 static_cast<int>( pair.gap().value_nm() ),
959 static_cast<int>( pair.via_gap().value_nm() ) );
960 }
961 }
962
963 if( rules.has_solder_mask_paste() )
964 {
965 const kiapi::board::SolderMaskPasteDefaults& maskPaste = rules.solder_mask_paste();
966
967 newSettings.m_SolderMaskExpansion = maskPaste.mask_expansion().value_nm();
968 newSettings.m_SolderMaskMinWidth = maskPaste.mask_min_width().value_nm();
969 newSettings.m_SolderMaskToCopperClearance = maskPaste.mask_to_copper_clearance().value_nm();
970 newSettings.m_SolderPasteMargin = maskPaste.paste_margin().value_nm();
971 newSettings.m_SolderPasteMarginRatio = maskPaste.paste_margin_ratio();
973 maskPaste.allow_soldermask_bridges_in_footprints();
974 }
975
976 if( rules.has_teardrops() )
977 {
978 const kiapi::board::TeardropDefaults& teardrops = rules.teardrops();
979
980 newSettings.m_TeardropParamsList.m_TargetVias = teardrops.target_vias();
981 newSettings.m_TeardropParamsList.m_TargetPTHPads = teardrops.target_pth_pads();
982 newSettings.m_TeardropParamsList.m_TargetSMDPads = teardrops.target_smd_pads();
983 newSettings.m_TeardropParamsList.m_TargetTrack2Track = teardrops.target_track_to_track();
984 newSettings.m_TeardropParamsList.m_UseRoundShapesOnly = teardrops.use_round_shapes_only();
985
986 for( const kiapi::board::TeardropTargetEntry& entry : teardrops.target_params() )
987 {
988 if( entry.target() == kiapi::board::TeardropTarget::TDT_UNKNOWN )
989 continue;
990
992 entry.target() );
993
994 if( target == TARGET_UNKNOWN )
995 {
996 ApiResponseStatus e;
997 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
998 e.set_error_message( "unknown teardrop target" );
999 return tl::unexpected( e );
1000 }
1001
1002 TEARDROP_PARAMETERS* params = newSettings.m_TeardropParamsList.GetParameters( target );
1003
1004 params->m_Enabled = entry.params().enabled();
1005 params->m_TdMaxLen = entry.params().max_length().value_nm();
1006 params->m_TdMaxWidth = entry.params().max_width().value_nm();
1007 params->m_BestLengthRatio = entry.params().best_length_ratio();
1008 params->m_BestWidthRatio = entry.params().best_width_ratio();
1009 params->m_WidthtoSizeFilterRatio = entry.params().width_to_size_filter_ratio();
1010 params->m_CurvedEdges = entry.params().curved_edges();
1011 params->m_AllowUseTwoTracks = entry.params().allow_two_tracks();
1012 params->m_TdOnPadsInZones = entry.params().on_pads_in_zones();
1013 }
1014 }
1015
1016 if( rules.has_via_protection() )
1017 {
1018 const kiapi::board::ViaProtectionDefaults& viaProtection = rules.via_protection();
1019
1020 newSettings.m_TentViasFront = viaProtection.tent_front();
1021 newSettings.m_TentViasBack = viaProtection.tent_back();
1022 newSettings.m_CoverViasFront = viaProtection.cover_front();
1023 newSettings.m_CoverViasBack = viaProtection.cover_back();
1024 newSettings.m_PlugViasFront = viaProtection.plug_front();
1025 newSettings.m_PlugViasBack = viaProtection.plug_back();
1026 newSettings.m_CapVias = viaProtection.cap();
1027 newSettings.m_FillVias = viaProtection.fill();
1028 }
1029
1030 if( rules.severities_size() > 0 )
1031 {
1032 newSettings.m_DRCSeverities.clear();
1033
1034 for( const kiapi::board::DrcSeveritySetting& severitySetting : rules.severities() )
1035 {
1036 PCB_DRC_CODE ruleType =
1037 FromProtoEnum<PCB_DRC_CODE, kiapi::board::DrcErrorType>( severitySetting.rule_type() );
1038
1039 const std::unordered_set<SEVERITY> permitted( { RPT_SEVERITY_ERROR, RPT_SEVERITY_WARNING, RPT_SEVERITY_IGNORE } );
1040 SEVERITY setting = FromProtoEnum<SEVERITY, kiapi::common::types::RuleSeverity>( severitySetting.severity() );
1041
1042 if( !permitted.contains( setting ) )
1043 {
1044 ApiResponseStatus e;
1045 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1046 e.set_error_message( fmt::format( "DRC severity must be error, warning, or ignore" ) );
1047 return tl::unexpected( e );
1048 }
1049
1050 newSettings.m_DRCSeverities[ruleType] = setting;
1051 }
1052 }
1053
1054 if( rules.exclusions_size() > 0 )
1055 {
1056 newSettings.m_DrcExclusions.clear();
1057
1058 for( const kiapi::board::DrcExclusion& exclusion : rules.exclusions() )
1059 newSettings.m_DrcExclusions.insert( DRC_EXCLUSION::FromProto( exclusion ) );
1060 }
1061
1062 std::vector<BOARD_DESIGN_SETTINGS::VALIDATION_ERROR> errors = newSettings.ValidateDesignRules();
1063
1064 if( !errors.empty() )
1065 {
1066 const BOARD_DESIGN_SETTINGS::VALIDATION_ERROR& error = errors.front();
1067
1068 ApiResponseStatus e;
1069 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1070 e.set_error_message( fmt::format( "Invalid board design rules: {}: {}",
1071 error.setting_name.ToStdString(),
1072 error.error_message.ToStdString() ) );
1073 return tl::unexpected( e );
1074 }
1075
1076 board()->SetDesignSettings( newSettings );
1077
1078 onModified();
1079
1080 HANDLER_CONTEXT<GetBoardDesignRules> getCtx = { aCtx.ClientName, GetBoardDesignRules() };
1081 *getCtx.Request.mutable_board() = aCtx.Request.board();
1082
1083 return handleGetBoardDesignRules( getCtx );
1084}
1085
1086
1089{
1090 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1091
1092 if( !documentValidation )
1093 return tl::unexpected( documentValidation.error() );
1094
1095 CustomRulesResponse response;
1096 response.set_status( CRS_NONE );
1097
1098 wxString rulesPath = board()->GetDesignRulesPath();
1099
1100 if( rulesPath.IsEmpty() || !wxFileName::IsFileReadable( rulesPath ) )
1101 return response;
1102
1103 wxFFile file( rulesPath, "r" );
1104
1105 if( !file.IsOpened() )
1106 {
1107 response.set_status( CRS_INVALID );
1108 response.set_error_text( "Failed to open custom rules file" );
1109 return response;
1110 }
1111
1112 wxString content;
1113 file.ReadAll( &content );
1114 file.Close();
1115
1116 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
1117
1118 try
1119 {
1120 DRC_RULES_PARSER parser( content, "File" );
1121 parser.Parse( parsedRules, nullptr );
1122 }
1123 catch( const IO_ERROR& ioe )
1124 {
1125 response.set_status( CRS_INVALID );
1126 response.set_error_text( ioe.What().ToStdString() );
1127 return response;
1128 }
1129
1130 for( const std::shared_ptr<DRC_RULE>& rule : parsedRules )
1131 {
1132 // TODO(JE) since we now need this for both here and the rules editor, maybe it's time
1133 // to just make comment parsing part of the parser?
1134 wxString text = DRC_RULE_LOADER::ExtractRuleText( content, rule->m_Name );
1135 wxString comment = DRC_RULE_LOADER::ExtractRuleComment( text );
1136
1137 kiapi::board::CustomRule* customRule = response.add_rules();
1138
1139 if( rule->m_Condition )
1140 customRule->set_condition( rule->m_Condition->GetExpression().ToUTF8() );
1141
1142 for( const DRC_CONSTRAINT& constraint : rule->m_Constraints )
1143 {
1144 board::CustomRuleConstraint* constraintProto = customRule->add_constraints();
1145 constraint.ToProto( *constraintProto );
1146 }
1147
1148 customRule->set_severity( ToProtoEnum<SEVERITY, types::RuleSeverity>( rule->m_Severity ) );
1149 customRule->set_name( rule->m_Name.ToUTF8() );
1150
1151 if( rule->m_LayerSource.CmpNoCase( wxS( "outer" ) ) == 0 )
1152 {
1153 customRule->set_layer_mode( kiapi::board::CRLM_OUTER );
1154 }
1155 else if( rule->m_LayerSource.CmpNoCase( wxS( "inner" ) ) == 0 )
1156 {
1157 customRule->set_layer_mode( kiapi::board::CRLM_INNER );
1158 }
1159 else if( !rule->m_LayerSource.IsEmpty() )
1160 {
1161 int layer = LSET::NameToLayer( rule->m_LayerSource );
1162
1163 if( layer != UNDEFINED_LAYER && layer != UNSELECTED_LAYER && layer < PCB_LAYER_ID_COUNT )
1164 {
1165 customRule->set_single_layer(
1167 }
1168 }
1169
1170 if( !comment.IsEmpty() )
1171 customRule->set_comments( comment );
1172 }
1173
1174 response.set_status( CRS_VALID );
1175 return response;
1176}
1177
1178
1181{
1182 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1183
1184 if( !documentValidation )
1185 return tl::unexpected( documentValidation.error() );
1186
1187 wxString rulesPath = board()->GetDesignRulesPath();
1188
1189 if( aCtx.Request.rules_size() == 0 )
1190 {
1191 if( wxFileName::FileExists( rulesPath ) )
1192 {
1193 if( !wxRemoveFile( rulesPath ) )
1194 {
1195 CustomRulesResponse response;
1196 response.set_status( CRS_INVALID );
1197 response.set_error_text( "Failed to remove custom rules file" );
1198 return response;
1199 }
1200 }
1201
1202 CustomRulesResponse response;
1203 response.set_status( CRS_NONE );
1204 return response;
1205 }
1206
1207 wxString rulesText;
1208 rulesText << "(version 2)\n";
1209
1210 for( const board::CustomRule& rule : aCtx.Request.rules() )
1211 {
1212 wxString serializationError;
1213 wxString serializedRule = DRC_RULE::FormatRuleFromProto( rule, &serializationError );
1214
1215 if( serializedRule.IsEmpty() )
1216 {
1217 CustomRulesResponse response;
1218 response.set_status( CRS_INVALID );
1219
1220 if( serializationError.IsEmpty() )
1221 response.set_error_text( "Failed to serialize custom rule" );
1222 else
1223 response.set_error_text( serializationError.ToUTF8() );
1224
1225 return response;
1226 }
1227
1228 rulesText << "\n" << serializedRule;
1229 }
1230
1231 // Validate generated file text before writing so callers get parser errors in response.
1232 try
1233 {
1234 std::vector<std::shared_ptr<DRC_RULE>> parsedRules;
1235 DRC_RULES_PARSER parser( rulesText, "SetCustomDesignRules" );
1236 parser.Parse( parsedRules, nullptr );
1237 }
1238 catch( const IO_ERROR& ioe )
1239 {
1240 CustomRulesResponse response;
1241 response.set_status( CRS_INVALID );
1242 response.set_error_text( ioe.What().ToStdString() );
1243 return response;
1244 }
1245
1246 wxFFile file( rulesPath, "w" );
1247
1248 if( !file.IsOpened() )
1249 {
1250 CustomRulesResponse response;
1251 response.set_status( CRS_INVALID );
1252 response.set_error_text( "Failed to open custom rules file for writing" );
1253 return response;
1254 }
1255
1256 if( !file.Write( rulesText ) )
1257 {
1258 file.Close();
1259
1260 CustomRulesResponse response;
1261 response.set_status( CRS_INVALID );
1262 response.set_error_text( "Failed to write custom rules file" );
1263 return response;
1264 }
1265
1266 file.Close();
1267
1268 HANDLER_CONTEXT<GetCustomDesignRules> getCtx = { aCtx.ClientName, GetCustomDesignRules() };
1269 *getCtx.Request.mutable_board() = aCtx.Request.board();
1270 return handleGetCustomDesignRules( getCtx );
1271}
1272
1273
1276{
1277 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1278 !documentValidation )
1279 {
1280 return tl::unexpected( documentValidation.error() );
1281 }
1282
1283 VECTOR2I origin;
1284 const BOARD_DESIGN_SETTINGS& settings = board()->GetDesignSettings();
1285
1286 switch( aCtx.Request.type() )
1287 {
1288 case BOT_GRID:
1289 origin = settings.GetGridOrigin();
1290 break;
1291
1292 case BOT_DRILL:
1293 origin = settings.GetAuxOrigin();
1294 break;
1295
1296 default:
1297 case BOT_UNKNOWN:
1298 {
1299 ApiResponseStatus e;
1300 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1301 e.set_error_message( "Unexpected origin type" );
1302 return tl::unexpected( e );
1303 }
1304 }
1305
1306 types::Vector2 reply;
1307 PackVector2( reply, origin );
1308 return reply;
1309}
1310
1313{
1314 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1315 return tl::unexpected( documentValidation.error() );
1316
1317 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1318 return tl::unexpected( *busy );
1319
1320 VECTOR2I origin = UnpackVector2( aCtx.Request.origin() );
1321
1322 switch( aCtx.Request.type() )
1323 {
1324 case BOT_GRID:
1325 {
1326 PCB_EDIT_FRAME* f = frame();
1327
1328 if( f )
1329 {
1330 frame()->CallAfter(
1331 [f, origin]()
1332 {
1333 // gridSetOrigin takes ownership and frees this
1334 VECTOR2D* dorigin = new VECTOR2D( origin );
1335 TOOL_MANAGER* mgr = f->GetToolManager();
1336 mgr->RunAction( PCB_ACTIONS::gridSetOrigin, dorigin );
1337 f->Refresh();
1338 } );
1339 }
1340 else
1341 {
1342 board()->GetDesignSettings().SetGridOrigin( origin );
1344 }
1345
1346 break;
1347 }
1348
1349 case BOT_DRILL:
1350 {
1351 PCB_EDIT_FRAME* f = frame();
1352
1353 if( f )
1354 {
1355 frame()->CallAfter(
1356 [f, origin]()
1357 {
1358 TOOL_MANAGER* mgr = f->GetToolManager();
1359 mgr->RunAction( PCB_ACTIONS::drillSetOrigin, origin );
1360 f->Refresh();
1361 } );
1362 }
1363 else
1364 {
1365 board()->GetDesignSettings().SetAuxOrigin( origin );
1367 }
1368
1369 break;
1370 }
1371
1372 default:
1373 case BOT_UNKNOWN:
1374 {
1375 ApiResponseStatus e;
1376 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1377 e.set_error_message( "Unexpected origin type" );
1378 return tl::unexpected( e );
1379 }
1380 }
1381
1382 return Empty();
1383}
1384
1385
1388{
1389 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1390 !documentValidation )
1391 {
1392 return tl::unexpected( documentValidation.error() );
1393 }
1394
1395 BoardLayerNameResponse response;
1396
1398
1399 response.set_name( board()->GetLayerName( id ) );
1400
1401 return response;
1402}
1403
1404
1407{
1408 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1409 !documentValidation )
1410 {
1411 return tl::unexpected( documentValidation.error() );
1412 }
1413
1414 BoardLayerResponse response;
1415
1416 PCB_LAYER_ID id = board()->GetLayerID( wxString::FromUTF8( aCtx.Request.name() ) );
1417 response.set_layer( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( id ) );
1418
1419 return response;
1420}
1421
1422
1423std::optional<TITLE_BLOCK*> API_HANDLER_PCB::getTitleBlock( const DocumentSpecifier& aDocument )
1424{
1425 return &context()->GetBoard()->GetTitleBlock();
1426}
1427
1428
1429std::optional<PAGE_INFO> API_HANDLER_PCB::getPageSettings( const DocumentSpecifier& aDocument )
1430{
1431 return context()->GetBoard()->GetPageSettings();
1432}
1433
1434
1435bool API_HANDLER_PCB::setPageSettings( const DocumentSpecifier& aDocument, const PAGE_INFO& aPageInfo )
1436{
1437 context()->GetBoard()->SetPageSettings( aPageInfo );
1438 return true;
1439}
1440
1441
1446
1447
1448void API_HANDLER_PCB::setDrawingSheetFileName( const wxString& aFileName )
1449{
1451
1452 if( frame() )
1453 {
1456 }
1457}
1458
1459
1461{
1463
1464 if( frame() )
1465 {
1466 frame()->Refresh();
1467 frame()->OnModify();
1469 }
1470}
1471
1472
1474{
1475 if( BOARD* brd = board() )
1476 brd->SynchronizeNetsAndNetClasses( false );
1477
1478 // Refresh UI that depends on netclasses, such as the properties panel
1479 if( TOOL_MANAGER* mgr = toolManager() )
1480 mgr->ProcessEvent( EVENTS::SelectedItemsModified );
1481
1482 if( frame() )
1483 {
1484 frame()->Refresh();
1486 }
1487}
1488
1489
1492{
1493 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
1494 return tl::unexpected( documentValidation.error() );
1495
1496 GetDocumentModifiedStateResponse response;
1497 response.set_state( pcbContext()->IsContentModified() ? DocumentModifiedState::DMS_MODIFIED
1498 : DocumentModifiedState::DMS_UNMODIFIED );
1499 return response;
1500}
1501
1502
1504{
1505 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1506 return tl::unexpected( documentValidation.error() );
1507
1508 NetsResponse response;
1509 BOARD* board = this->board();
1510
1511 std::set<wxString> netclassFilter;
1512
1513 for( const std::string& nc : aCtx.Request.netclass_filter() )
1514 netclassFilter.insert( wxString( nc.c_str(), wxConvUTF8 ) );
1515
1516 for( NETINFO_ITEM* net : board->GetNetInfo() )
1517 {
1518 NETCLASS* nc = net->GetNetClass();
1519
1520 if( !netclassFilter.empty() && nc )
1521 {
1522 bool inClass = false;
1523
1524 for( const wxString& filter : netclassFilter )
1525 {
1526 if( nc->ContainsNetclassWithName( filter ) )
1527 {
1528 inClass = true;
1529 break;
1530 }
1531 }
1532
1533 if( !inClass )
1534 continue;
1535 }
1536
1537 board::types::Net* netProto = response.add_nets();
1538 netProto->set_name( net->GetNetname() );
1539 netProto->mutable_code()->set_value( net->GetNetCode() );
1540 }
1541
1542 return response;
1543}
1544
1545
1548{
1549 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1550 {
1551 ApiResponseStatus e;
1552 e.set_status( ApiStatusCode::AS_UNHANDLED );
1553 return tl::unexpected( e );
1554 }
1555
1556 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1557 return tl::unexpected( *busy );
1558
1559 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1560 const bool filterByType = aCtx.Request.types_size() > 0;
1561
1562 if( filterByType && types.empty() )
1563 {
1564 ApiResponseStatus e;
1565 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1566 e.set_error_message( "none of the requested types are valid for a Board object" );
1567 return tl::unexpected( e );
1568 }
1569
1570 std::set<KICAD_T> typeFilter( types.begin(), types.end() );
1571 std::vector<BOARD_CONNECTED_ITEM*> sourceItems;
1572
1573 for( const types::KIID& id : aCtx.Request.items() )
1574 {
1575 if( std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) ) )
1576 {
1577 if( BOARD_CONNECTED_ITEM* connected = dynamic_cast<BOARD_CONNECTED_ITEM*>( *item ) )
1578 sourceItems.emplace_back( connected );
1579 }
1580 }
1581
1582 if( sourceItems.empty() )
1583 {
1584 ApiResponseStatus e;
1585 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1586 e.set_error_message( "none of the requested IDs were found or valid connected items" );
1587 return tl::unexpected( e );
1588 }
1589
1590 GetItemsResponse response;
1591 std::shared_ptr<CONNECTIVITY_DATA> conn = board()->GetConnectivity();
1592 std::set<KIID> insertedItems;
1593
1594 for( BOARD_CONNECTED_ITEM* source : sourceItems )
1595 {
1596 for( BOARD_CONNECTED_ITEM* connected : conn->GetConnectedItems( source ) )
1597 {
1598 if( filterByType && !typeFilter.contains( connected->Type() ) )
1599 continue;
1600
1601 if( !insertedItems.insert( connected->m_Uuid ).second )
1602 continue;
1603
1604 connected->Serialize( *response.add_items() );
1605 }
1606 }
1607
1608 response.set_status( ItemRequestStatus::IRS_OK );
1609 return response;
1610}
1611
1612
1614 const HANDLER_CONTEXT<GetItemsByNet>& aCtx )
1615{
1616 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1617 {
1618 ApiResponseStatus e;
1619 e.set_status( ApiStatusCode::AS_UNHANDLED );
1620 return tl::unexpected( e );
1621 }
1622
1623 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1624 return tl::unexpected( *busy );
1625
1626 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1627 const bool filterByType = aCtx.Request.types_size() > 0;
1628
1629 if( filterByType && types.empty() )
1630 {
1631 ApiResponseStatus e;
1632 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1633 e.set_error_message( "none of the requested types are valid for a Board object" );
1634 return tl::unexpected( e );
1635 }
1636
1637 if( !filterByType )
1639
1640 GetItemsResponse response;
1641 BOARD* board = this->board();
1642 std::shared_ptr<CONNECTIVITY_DATA> conn = board->GetConnectivity();
1643 std::set<KIID> insertedItems;
1644
1645 const NETINFO_LIST& nets = board->GetNetInfo();
1646
1647 for( const board::types::Net& net : aCtx.Request.nets() )
1648 {
1649 NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
1650
1651 if( !netInfo )
1652 continue;
1653
1654 for( BOARD_CONNECTED_ITEM* item : conn->GetNetItems( netInfo->GetNetCode(), types ) )
1655 {
1656 if( !insertedItems.insert( item->m_Uuid ).second )
1657 continue;
1658
1659 item->Serialize( *response.add_items() );
1660 }
1661 }
1662
1663 response.set_status( ItemRequestStatus::IRS_OK );
1664 return response;
1665}
1666
1667
1670{
1671 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
1672 {
1673 ApiResponseStatus e;
1674 e.set_status( ApiStatusCode::AS_UNHANDLED );
1675 return tl::unexpected( e );
1676 }
1677
1678 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1679 return tl::unexpected( *busy );
1680
1681 std::vector<KICAD_T> types = parseRequestedItemTypes( aCtx.Request.types() );
1682 const bool filterByType = aCtx.Request.types_size() > 0;
1683
1684 if( filterByType && types.empty() )
1685 {
1686 ApiResponseStatus e;
1687 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1688 e.set_error_message( "none of the requested types are valid for a Board object" );
1689 return tl::unexpected( e );
1690 }
1691
1692 if( !filterByType )
1694
1695 std::set<wxString> requestedClasses;
1696
1697 for( const std::string& netClass : aCtx.Request.net_classes() )
1698 requestedClasses.insert( wxString( netClass.c_str(), wxConvUTF8 ) );
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 for( NETINFO_ITEM* net : board->GetNetInfo() )
1706 {
1707 if( !net )
1708 continue;
1709
1710 NETCLASS* nc = net->GetNetClass();
1711
1712 if( !requestedClasses.empty() )
1713 {
1714 if( !nc )
1715 continue;
1716
1717 bool inClass = false;
1718
1719 for( const wxString& filter : requestedClasses )
1720 {
1721 if( nc->ContainsNetclassWithName( filter ) )
1722 {
1723 inClass = true;
1724 break;
1725 }
1726 }
1727
1728 if( !inClass )
1729 continue;
1730 }
1731
1732 for( BOARD_CONNECTED_ITEM* item : conn->GetNetItems( net->GetNetCode(), types ) )
1733 {
1734 if( !insertedItems.insert( item->m_Uuid ).second )
1735 continue;
1736
1737 item->Serialize( *response.add_items() );
1738 }
1739 }
1740
1741 response.set_status( ItemRequestStatus::IRS_OK );
1742 return response;
1743}
1744
1745
1748{
1749 NetClassForNetsResponse response;
1750
1751 BOARD* board = this->board();
1752 const NETINFO_LIST& nets = board->GetNetInfo();
1753
1754 for( const board::types::Net& net : aCtx.Request.net() )
1755 {
1756 NETINFO_ITEM* netInfo = nets.GetNetItem( wxString::FromUTF8( net.name() ) );
1757
1758 if( !netInfo )
1759 continue;
1760
1761 auto [pair, rc] = response.mutable_classes()->insert( { net.name(), {} } );
1762 netInfo->GetNetClass()->Serialize( pair->second );
1763 }
1764
1765 return response;
1766}
1767
1768
1770{
1771 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1772 return tl::unexpected( documentValidation.error() );
1773
1774 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1775 return tl::unexpected( *busy );
1776
1777 TOOL_MANAGER* mgr = toolManager();
1778
1779 // A frame's tool manager always carries the zone filler tool; headless sessions start with a
1780 // bare tool manager and register it on first use, like the CLI jobs do.
1781 if( !mgr->FindTool( ZONE_FILLER_TOOL_NAME ) )
1782 mgr->RegisterTool( new ZONE_FILLER_TOOL );
1783
1784 if( aCtx.Request.zones().empty() )
1785 {
1786 if( frame() )
1787 {
1788 frame()->CallAfter( [mgr]()
1789 {
1791 } );
1792 }
1793 else
1794 {
1795 // Headless sessions have no event loop to defer to; fill synchronously through the
1796 // same tool the CLI jobs use.
1797 mgr->GetTool<ZONE_FILLER_TOOL>()->FillAllZones( nullptr, nullptr, true );
1798 }
1799 }
1800 else
1801 {
1802 std::vector<ZONE*> toFill;
1803
1804 for( const types::KIID& id : aCtx.Request.zones() )
1805 {
1806 std::optional<BOARD_ITEM*> item = getItemById( KIID( id.value() ) );
1807
1808 if( !item || ( *item )->Type() != PCB_ZONE_T )
1809 {
1810 ApiResponseStatus e;
1811 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1812 e.set_error_message( fmt::format( "zone with ID {} not found on the board", id.value() ) );
1813 return tl::unexpected( e );
1814 }
1815
1816 ZONE* zone = static_cast<ZONE*>( *item );
1817
1818 // The filler silently skips rule areas, which would turn this into a false success
1819 if( zone->GetIsRuleArea() )
1820 {
1821 ApiResponseStatus e;
1822 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1823 e.set_error_message( fmt::format( "zone with ID {} is a rule area and cannot be filled",
1824 id.value() ) );
1825 return tl::unexpected( e );
1826 }
1827
1828 // A repeated id would enqueue concurrent fill tasks for the same zone
1829 if( !alg::contains( toFill, zone ) )
1830 toFill.push_back( zone );
1831 }
1832
1833 std::unique_ptr<COMMIT> commit = createCommit();
1834 ZONE_FILLER filler( board(), commit.get() );
1835
1836 if( !filler.Fill( toFill ) )
1837 {
1838 commit->Revert();
1839
1840 ApiResponseStatus e;
1841 e.set_status( ApiStatusCode::AS_UNKNOWN );
1842 e.set_error_message( "zone fill failed" );
1843 return tl::unexpected( e );
1844 }
1845
1846 commit->Push( _( "Fill Zone(s)" ), SKIP_CONNECTIVITY | ZONE_FILL_OP );
1847
1848 // Push skipped connectivity, so run the same post-fill refresh as the interactive fill
1849 mgr->GetTool<ZONE_FILLER_TOOL>()->PostFillRefresh( frame() == nullptr );
1850 }
1851
1852 onModified();
1853 return Empty();
1854}
1855
1856
1858{
1859 HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() );
1860
1861 if( !documentValidation )
1862 return tl::unexpected( documentValidation.error() );
1863
1864 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1865 return tl::unexpected( *busy );
1866
1867 wxFileName netlistPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.netlist_path() ) ) );
1868
1869 if( !netlistPath.IsOk() || !netlistPath.FileExists() )
1870 {
1871 ApiResponseStatus e;
1872 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1873 e.set_error_message(
1874 fmt::format( "netlist file '{}' could not be opened", netlistPath.GetFullPath().ToStdString() ) );
1875 return tl::unexpected( e );
1876 }
1877
1878 PCB_CONTEXT* ctx = pcbContext();
1880
1881 const bool lookupByTimestamp = aCtx.Request.match_mode() != NetlistMatchMode::NMM_REFERENCE;
1882
1884 netlist.SetFindByTimeStamp( lookupByTimestamp );
1885 netlist.SetReplaceFootprints( aCtx.Request.update_footprints() );
1886
1887 if( !ctx->ReadNetlistFromFile( netlistPath.GetFullPath(), netlist, reporter ) )
1888 {
1889 ApiResponseStatus e;
1890 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1891 e.set_error_message( fmt::format( "unable to handle netlist file '{}': {}",
1892 netlistPath.GetFullPath().ToStdString(),
1893 reporter.GetMessages().ToStdString() ) );
1894 return tl::unexpected( e );
1895 }
1896
1897 std::unique_ptr<BOARD_NETLIST_UPDATER> updater = ctx->MakeNetlistUpdater();
1898
1899 updater->SetReporter( &reporter );
1900 updater->SetIsDryRun( aCtx.Request.dry_run() );
1901 updater->SetWillDrag( false );
1902 updater->SetLookupByTimestamp( lookupByTimestamp );
1903 updater->SetDeleteUnusedFootprints( aCtx.Request.delete_extra_footprints() );
1904 updater->SetReplaceFootprints( aCtx.Request.update_footprints() );
1905 updater->SetTransferGroups( aCtx.Request.transfer_groups() );
1906 updater->SetOverrideLocks( aCtx.Request.override_locks() );
1907 updater->SetUpdateFields( true );
1908
1909 const bool success = updater->UpdateNetlist( netlist );
1910
1911 if( !aCtx.Request.dry_run() && success )
1912 ctx->OnNetlistChanged( *updater );
1913
1914 ImportNetlistResponse response;
1915 response.set_report( reporter.GetMessages().ToUTF8() );
1916 response.set_error_count( updater->GetErrorCount() );
1917 response.set_warning_count( updater->GetWarningCount() );
1918 response.set_new_footprint_count( updater->GetNewFootprintCount() );
1919 return response;
1920}
1921
1922
1925{
1926 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
1927 return tl::unexpected( documentValidation.error() );
1928
1929 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
1930 return tl::unexpected( *busy );
1931
1932 wxFileName sesPath( project().AbsolutePath( wxString::FromUTF8( aCtx.Request.session_path() ) ) );
1933
1934 if( !sesPath.IsOk() || !sesPath.FileExists() )
1935 {
1936 ApiResponseStatus e;
1937 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1938 e.set_error_message(
1939 fmt::format( "session file '{}' could not be opened", sesPath.GetFullPath().ToStdString() ) );
1940 return tl::unexpected( e );
1941 }
1942
1943 int skippedCount = 0;
1944 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
1946
1947 try
1948 {
1949 DSN::ImportSpecctraSession( board(), sesPath.GetFullPath(), *commit, &reporter, &skippedCount );
1950 }
1951 catch( const IO_ERROR& ioe )
1952 {
1953 commit->Revert();
1954
1955 ApiResponseStatus e;
1956 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
1957 e.set_error_message( fmt::format( "failed to import session '{}': {}", sesPath.GetFullPath().ToStdString(),
1958 ioe.What().ToStdString() ) );
1959 return tl::unexpected( e );
1960 }
1961
1962 if( !m_activeClients.count( aCtx.ClientName ) )
1963 pushCurrentCommit( aCtx.ClientName, _( "Imported Specctra session via API" ) );
1964
1965 ImportSpecctraSessionResponse response;
1966 response.set_report( reporter.GetMessages().ToUTF8() );
1967 response.set_skipped_count( skippedCount );
1968 return response;
1969}
1970
1971
1974{
1975 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "GetBoardEditorAppearanceSettings" ) )
1976 return tl::unexpected( *headless );
1977
1978 BoardEditorAppearanceSettings reply;
1979
1980 // TODO: might be nice to put all these things in one place and have it derive SERIALIZABLE
1981
1982 const PCB_DISPLAY_OPTIONS& displayOptions = frame()->GetDisplayOptions();
1983
1984 reply.set_inactive_layer_display( ToProtoEnum<HIGH_CONTRAST_MODE, InactiveLayerDisplayMode>(
1985 displayOptions.m_ContrastModeDisplay ) );
1986 reply.set_net_color_display(
1988
1989 reply.set_board_flip( frame()->GetCanvas()->GetView()->IsMirroredX()
1990 ? BoardFlipMode::BFM_FLIPPED_X
1991 : BoardFlipMode::BFM_NORMAL );
1992
1993 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
1994
1995 reply.set_ratsnest_display( ToProtoEnum<RATSNEST_MODE, RatsnestDisplayMode>(
1996 editorSettings->m_Display.m_RatsnestMode ) );
1997
1998 return reply;
1999}
2000
2001
2004{
2005 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SetBoardEditorAppearanceSettings" ) )
2006 return tl::unexpected( *headless );
2007
2008 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2009 return tl::unexpected( *busy );
2010
2012 PCBNEW_SETTINGS* editorSettings = frame()->GetPcbNewSettings();
2013 const BoardEditorAppearanceSettings& newSettings = aCtx.Request.settings();
2014
2015 options.m_ContrastModeDisplay =
2016 FromProtoEnum<HIGH_CONTRAST_MODE>( newSettings.inactive_layer_display() );
2017 options.m_NetColorMode =
2018 FromProtoEnum<NET_COLOR_MODE>( newSettings.net_color_display() );
2019 options.m_FlipBoardView = newSettings.board_flip() == BoardFlipMode::BFM_FLIPPED_X;
2020
2021 editorSettings->m_Display.m_RatsnestMode =
2022 FromProtoEnum<RATSNEST_MODE>( newSettings.ratsnest_display() );
2023
2024 frame()->SetDisplayOptions( options );
2026 frame()->GetCanvas()->Refresh();
2027
2028 return Empty();
2029}
2030
2031
2034{
2035 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
2036 return tl::unexpected( documentValidation.error() );
2037
2038 const PCB_PLOT_PARAMS& plotOpts = board()->GetPlotOptions();
2039
2040 BoardPlotSettingsResponse response;
2041 BoardPlotSettings* settings = response.mutable_plot_settings();
2042
2043 board::PackLayerSet( *settings->mutable_layers(), plotOpts.GetLayerSelection() );
2044
2045 for( PCB_LAYER_ID layer : plotOpts.GetPlotOnAllLayersSequence() )
2046 settings->add_common_layers( ToProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( layer ) );
2047
2048 settings->set_mirror( plotOpts.GetMirror() );
2049 settings->set_black_and_white( plotOpts.GetBlackAndWhite() );
2050 settings->set_negative( plotOpts.GetNegative() );
2051 settings->set_scale( plotOpts.GetScale() );
2052
2053 settings->set_sketch_pads_on_fab_layers( plotOpts.GetSketchPadsOnFabLayers() );
2054 settings->set_hide_dnp_footprints_on_fab_layers( plotOpts.GetHideDNPFPsOnFabLayers() );
2055 settings->set_sketch_dnp_footprints_on_fab_layers( plotOpts.GetSketchDNPFPsOnFabLayers() );
2056 settings->set_crossout_dnp_footprints_on_fab_layers( plotOpts.GetCrossoutDNPFPsOnFabLayers() );
2057
2058 settings->set_plot_footprint_values( plotOpts.GetPlotValue() );
2059 settings->set_plot_reference_designators( plotOpts.GetPlotReference() );
2060 settings->set_plot_drawing_sheet( plotOpts.GetPlotFrameRef() );
2061 settings->set_subtract_solder_mask_from_silk( plotOpts.GetSubtractMaskFromSilk() );
2062 settings->set_plot_pad_numbers( plotOpts.GetPlotPadNumbers() );
2063
2064 settings->set_drill_marks( ToProtoEnum<DRILL_MARKS, PlotDrillMarks>( plotOpts.GetDrillMarksType() ) );
2065 settings->set_use_drill_origin( plotOpts.GetUseAuxOrigin() );
2066
2067 return response;
2068}
2069
2070
2072{
2073 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
2074 return tl::unexpected( documentValidation.error() );
2075
2076 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2077 return tl::unexpected( *busy );
2078
2079 const BoardPlotSettings& settings = aCtx.Request.plot_settings();
2080 PCB_PLOT_PARAMS plotOpts = board()->GetPlotOptions();
2081
2082 plotOpts.SetLayerSelection( board::UnpackLayerSet( settings.layers() ) );
2083
2084 LSEQ commonLayers;
2085
2086 for( int layer : settings.common_layers() )
2087 {
2088 PCB_LAYER_ID layerId =
2089 FromProtoEnum<PCB_LAYER_ID, board::types::BoardLayer>( static_cast<board::types::BoardLayer>( layer ) );
2090
2091 if( !IsPcbLayer( layerId ) )
2092 {
2093 ApiResponseStatus e;
2094 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2095 e.set_error_message( fmt::format( "SetBoardPlotSettings contains an invalid layer {}",
2096 magic_enum::enum_name( layerId ) ) );
2097 return tl::unexpected( e );
2098 }
2099
2100 commonLayers.push_back( layerId );
2101 }
2102
2103 plotOpts.SetPlotOnAllLayersSequence( commonLayers );
2104
2105 plotOpts.SetMirror( settings.mirror() );
2106 plotOpts.SetBlackAndWhite( settings.black_and_white() );
2107 plotOpts.SetNegative( settings.negative() );
2108 plotOpts.SetScale( settings.scale() );
2109
2110 plotOpts.SetSketchPadsOnFabLayers( settings.sketch_pads_on_fab_layers() );
2111 plotOpts.SetHideDNPFPsOnFabLayers( settings.hide_dnp_footprints_on_fab_layers() );
2112 plotOpts.SetSketchDNPFPsOnFabLayers( settings.sketch_dnp_footprints_on_fab_layers() );
2113 plotOpts.SetCrossoutDNPFPsOnFabLayers( settings.crossout_dnp_footprints_on_fab_layers() );
2114
2115 plotOpts.SetPlotValue( settings.plot_footprint_values() );
2116 plotOpts.SetPlotReference( settings.plot_reference_designators() );
2117 plotOpts.SetPlotFrameRef( settings.plot_drawing_sheet() );
2118 plotOpts.SetSubtractMaskFromSilk( settings.subtract_solder_mask_from_silk() );
2119 plotOpts.SetPlotPadNumbers( settings.plot_pad_numbers() );
2120
2121 plotOpts.SetDrillMarksType( FromProtoEnum<DRILL_MARKS>( settings.drill_marks() ) );
2122 plotOpts.SetUseAuxOrigin( settings.use_drill_origin() );
2123
2124 board()->SetPlotOptions( plotOpts );
2125
2126 onModified();
2127
2128 return Empty();
2129}
2130
2131
2134{
2135 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.board() ); !documentValidation )
2136 return tl::unexpected( documentValidation.error() );
2137
2138 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2139 return tl::unexpected( *busy );
2140
2141 SEVERITY severity = FromProtoEnum<SEVERITY>( aCtx.Request.severity() );
2142 int layer = severity == RPT_SEVERITY_WARNING ? LAYER_DRC_WARNING : LAYER_DRC_ERROR;
2144
2145 std::shared_ptr<DRC_ITEM> drcItem = DRC_ITEM::Create( code );
2146
2147 drcItem->SetErrorMessage( wxString::FromUTF8( aCtx.Request.message() ) );
2148
2149 std::vector<KIID> ids;
2150
2151 for( const auto& id : aCtx.Request.items() )
2152 ids.emplace_back( KIID( id.value() ) );
2153
2154 if( !ids.empty() )
2155 drcItem->SetItems( ids );
2156
2157 const auto& pos = aCtx.Request.position();
2158 VECTOR2I position( static_cast<int>( pos.x_nm() ), static_cast<int>( pos.y_nm() ) );
2159
2160 PCB_MARKER* marker = new PCB_MARKER( drcItem, position, layer );
2161
2162 COMMIT* commit = getCurrentCommit( aCtx.ClientName );
2163 commit->Add( marker );
2164
2165 if( !m_activeClients.count( aCtx.ClientName ) )
2166 pushCurrentCommit( aCtx.ClientName, _( "API injected DRC marker" ) );
2167
2168 InjectDrcErrorResponse response;
2169 response.mutable_marker()->set_value( marker->GetUUID().AsStdString() );
2170
2171 return response;
2172}
2173
2174
2175std::optional<ApiResponseStatus> ValidateUnitsInchMm( types::Units aUnits,
2176 const std::string& aCommandName )
2177{
2178 if( aUnits == types::Units::U_INCH || aUnits == types::Units::U_MM
2179 || aUnits == types::Units::U_UNKNOWN )
2180 {
2181 return std::nullopt;
2182 }
2183
2184 ApiResponseStatus e;
2185 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2186 e.set_error_message( fmt::format( "{} supports only inch and mm units", aCommandName ) );
2187 return e;
2188}
2189
2190
2191std::optional<ApiResponseStatus>
2192ValidatePaginationModeForSingleOrPerFile( kiapi::board::jobs::BoardJobPaginationMode aMode,
2193 const std::string& aCommandName )
2194{
2195 if( aMode == kiapi::board::jobs::BoardJobPaginationMode::BJPM_UNKNOWN
2196 || aMode == kiapi::board::jobs::BoardJobPaginationMode::BJPM_ALL_LAYERS_ONE_PAGE
2197 || aMode == kiapi::board::jobs::BoardJobPaginationMode::BJPM_EACH_LAYER_OWN_FILE )
2198 {
2199 return std::nullopt;
2200 }
2201
2202 ApiResponseStatus e;
2203 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2204 e.set_error_message( fmt::format( "{} does not support EACH_LAYER_OWN_PAGE pagination mode",
2205 aCommandName ) );
2206 return e;
2207}
2208
2209
2210std::optional<ApiResponseStatus> ApplyBoardPlotSettings( const BoardPlotSettings& aSettings,
2211 JOB_EXPORT_PCB_PLOT& aJob )
2212{
2213 for( int layer : aSettings.layers() )
2214 {
2216 static_cast<board::types::BoardLayer>( layer ) );
2217
2218 if( !IsPcbLayer( static_cast<int>( layerId ) ) )
2219 {
2220 ApiResponseStatus e;
2221 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2222 e.set_error_message( "Board plot settings contain an invalid layer" );
2223 return e;
2224 }
2225
2226 aJob.m_plotLayerSequence.push_back( layerId );
2227 }
2228
2229 for( int layer : aSettings.common_layers() )
2230 {
2232 static_cast<board::types::BoardLayer>( layer ) );
2233
2234 if( !IsPcbLayer( static_cast<int>( layerId ) ) )
2235 {
2236 ApiResponseStatus e;
2237 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2238 e.set_error_message( "Board plot settings contain an invalid common layer" );
2239 return e;
2240 }
2241
2242 aJob.m_plotOnAllLayersSequence.push_back( layerId );
2243 }
2244
2245 aJob.m_colorTheme = wxString::FromUTF8( aSettings.color_theme() );
2246 aJob.m_drawingSheet = wxString::FromUTF8( aSettings.drawing_sheet() );
2247 aJob.m_variant = wxString::FromUTF8( aSettings.variant() );
2248
2249 aJob.m_mirror = aSettings.mirror();
2250 aJob.m_blackAndWhite = aSettings.black_and_white();
2251 aJob.m_negative = aSettings.negative();
2252 aJob.m_scale = aSettings.scale();
2253
2254 aJob.m_sketchPadsOnFabLayers = aSettings.sketch_pads_on_fab_layers();
2255 aJob.m_hideDNPFPsOnFabLayers = aSettings.hide_dnp_footprints_on_fab_layers();
2256 aJob.m_sketchDNPFPsOnFabLayers = aSettings.sketch_dnp_footprints_on_fab_layers();
2257 aJob.m_crossoutDNPFPsOnFabLayers = aSettings.crossout_dnp_footprints_on_fab_layers();
2258
2259 aJob.m_plotFootprintValues = aSettings.plot_footprint_values();
2260 aJob.m_plotRefDes = aSettings.plot_reference_designators();
2261 aJob.m_plotDrawingSheet = aSettings.plot_drawing_sheet();
2262 aJob.m_subtractSolderMaskFromSilk = aSettings.subtract_solder_mask_from_silk();
2263 aJob.m_plotPadNumbers = aSettings.plot_pad_numbers();
2264
2265 aJob.m_drillShapeOption = FromProtoEnum<DRILL_MARKS>( aSettings.drill_marks() );
2266
2267 aJob.m_useDrillOrigin = aSettings.use_drill_origin();
2268 aJob.m_checkZonesBeforePlot = aSettings.check_zones_before_plot();
2269
2270 return std::nullopt;
2271}
2272
2273
2275{
2276 types::RunJobResponse response;
2278
2279 if( !aContext || !aContext->GetKiway() )
2280 {
2281 response.set_status( types::JobStatus::JS_ERROR );
2282 response.set_message( "Internal error" );
2283 wxCHECK_MSG( false, response, "context missing valid kiway in ExecuteBoardJob?" );
2284 return response;
2285 }
2286
2287 int exitCode = aContext->GetKiway()->ProcessJob( KIWAY::FACE_PCB, &aJob, &reporter );
2288
2289 for( const JOB_OUTPUT& output : aJob.GetOutputs() )
2290 response.add_output_path( output.m_outputPath.ToUTF8() );
2291
2292 if( exitCode == 0 )
2293 {
2294 response.set_status( types::JobStatus::JS_SUCCESS );
2295 return response;
2296 }
2297
2298 response.set_status( types::JobStatus::JS_ERROR );
2299 response.set_message( fmt::format( "Board export job '{}' failed with exit code {}: {}",
2300 aJob.GetType(), exitCode,
2301 reporter.GetMessages().ToStdString() ) );
2302 return response;
2303}
2304
2305
2308{
2309 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2310 return tl::unexpected( validation.error() );
2311
2312 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2313 return tl::unexpected( *busy );
2314
2317 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2318
2320
2321 job.m_variant = wxString::FromUTF8( aCtx.Request.variant() );
2322 job.m_3dparams.m_NetFilter = wxString::FromUTF8( aCtx.Request.net_filter() );
2323 job.m_3dparams.m_ComponentFilter = wxString::FromUTF8( aCtx.Request.component_filter() );
2324
2325 job.m_hasUserOrigin = aCtx.Request.has_user_origin();
2326 job.m_3dparams.m_Origin = VECTOR2D( aCtx.Request.origin().x_nm(), aCtx.Request.origin().y_nm() );
2327
2328 job.m_3dparams.m_Overwrite = aCtx.Request.overwrite();
2329 job.m_3dparams.m_UseGridOrigin = aCtx.Request.use_grid_origin();
2330 job.m_3dparams.m_UseDrillOrigin = aCtx.Request.use_drill_origin();
2331 job.m_3dparams.m_UseDefinedOrigin = aCtx.Request.use_defined_origin() || aCtx.Request.has_user_origin();
2332 job.m_3dparams.m_UsePcbCenterOrigin = aCtx.Request.use_pcb_center_origin();
2333
2334 job.m_3dparams.m_IncludeUnspecified = aCtx.Request.include_unspecified();
2335 job.m_3dparams.m_IncludeDNP = aCtx.Request.include_dnp();
2336 job.m_3dparams.m_SubstModels = aCtx.Request.substitute_models();
2337
2338 job.m_3dparams.m_BoardOutlinesChainingEpsilon = aCtx.Request.board_outlines_chaining_epsilon();
2339 job.m_3dparams.m_BoardOnly = aCtx.Request.board_only();
2340 job.m_3dparams.m_CutViasInBody = aCtx.Request.cut_vias_in_body();
2341 job.m_3dparams.m_ExportBoardBody = aCtx.Request.export_board_body();
2342 job.m_3dparams.m_ExportComponents = aCtx.Request.export_components();
2343 job.m_3dparams.m_ExportTracksVias = aCtx.Request.export_tracks_and_vias();
2344 job.m_3dparams.m_ExportPads = aCtx.Request.export_pads();
2345 job.m_3dparams.m_ExportZones = aCtx.Request.export_zones();
2346 job.m_3dparams.m_ExportInnerCopper = aCtx.Request.export_inner_copper();
2347 job.m_3dparams.m_ExportSilkscreen = aCtx.Request.export_silkscreen();
2348 job.m_3dparams.m_ExportSoldermask = aCtx.Request.export_soldermask();
2349 job.m_3dparams.m_FuseShapes = aCtx.Request.fuse_shapes();
2350 job.m_3dparams.m_FillAllVias = aCtx.Request.fill_all_vias();
2351 job.m_3dparams.m_OptimizeStep = aCtx.Request.optimize_step();
2352 job.m_3dparams.m_ExtraPadThickness = aCtx.Request.extra_pad_thickness();
2353
2355
2356 job.m_vrmlModelDir = wxString::FromUTF8( aCtx.Request.vrml_model_dir() );
2357 job.m_vrmlRelativePaths = aCtx.Request.vrml_relative_paths();
2358
2359 return ExecuteBoardJob( pcbContext(), job );
2360}
2361
2362
2365{
2366 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2367 return tl::unexpected( validation.error() );
2368
2369 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2370 return tl::unexpected( *busy );
2371
2372 JOB_PCB_RENDER job;
2374 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2375
2378 job.m_bgStyle = FromProtoEnum<JOB_PCB_RENDER::BG_STYLE>( aCtx.Request.background_style() );
2379
2380 job.m_width = aCtx.Request.width();
2381 job.m_height = aCtx.Request.height();
2382 job.m_appearancePreset = aCtx.Request.appearance_preset();
2383 job.m_useBoardStackupColors = aCtx.Request.use_board_stackup_colors();
2384
2386
2387 job.m_zoom = aCtx.Request.zoom();
2388 job.m_perspective = aCtx.Request.perspective();
2389
2390 job.m_rotation = UnpackVector3D( aCtx.Request.rotation() );
2391 job.m_pan = UnpackVector3D( aCtx.Request.pan() );
2392 job.m_pivot = UnpackVector3D( aCtx.Request.pivot() );
2393
2394 job.m_proceduralTextures = aCtx.Request.procedural_textures();
2395 job.m_floor = aCtx.Request.floor();
2396 job.m_antiAlias = aCtx.Request.anti_alias();
2397 job.m_postProcess = aCtx.Request.post_process();
2398
2399 job.m_lightTopIntensity = UnpackVector3D( aCtx.Request.light_top_intensity() );
2400 job.m_lightBottomIntensity = UnpackVector3D( aCtx.Request.light_bottom_intensity() );
2401 job.m_lightCameraIntensity = UnpackVector3D( aCtx.Request.light_camera_intensity() );
2402 job.m_lightSideIntensity = UnpackVector3D( aCtx.Request.light_side_intensity() );
2403 job.m_lightSideElevation = aCtx.Request.light_side_elevation();
2404
2405 return ExecuteBoardJob( pcbContext(), job );
2406}
2407
2408
2411{
2412 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2413 return tl::unexpected( validation.error() );
2414
2415 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2416 return tl::unexpected( *busy );
2417
2420 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2421
2422 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2423 return tl::unexpected( *err );
2424
2425 job.m_fitPageToBoard = aCtx.Request.fit_page_to_board();
2426 job.m_precision = aCtx.Request.precision();
2427
2428 if( std::optional<ApiResponseStatus> paginationError =
2430 "RunBoardJobExportSvg" ) )
2431 {
2432 return tl::unexpected( *paginationError );
2433 }
2434
2436
2437 return ExecuteBoardJob( pcbContext(), job );
2438}
2439
2440
2443{
2444 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2445 return tl::unexpected( validation.error() );
2446
2447 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2448 return tl::unexpected( *busy );
2449
2452 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2453
2454 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2455 return tl::unexpected( *err );
2456
2457 job.m_plotGraphicItemsUsingContours = aCtx.Request.plot_graphic_items_using_contours();
2458 job.m_polygonMode = aCtx.Request.polygon_mode();
2459
2460 if( std::optional<ApiResponseStatus> unitError =
2461 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportDxf" ) )
2462 {
2463 return tl::unexpected( *unitError );
2464 }
2465
2467
2468 if( std::optional<ApiResponseStatus> paginationError =
2470 "RunBoardJobExportDxf" ) )
2471 {
2472 return tl::unexpected( *paginationError );
2473 }
2474
2476
2477 return ExecuteBoardJob( pcbContext(), job );
2478}
2479
2480
2483{
2484 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2485 return tl::unexpected( validation.error() );
2486
2487 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2488 return tl::unexpected( *busy );
2489
2492 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2493
2494 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2495 return tl::unexpected( *err );
2496
2497 job.m_pdfFrontFPPropertyPopups = aCtx.Request.front_footprint_property_popups();
2498 job.m_pdfBackFPPropertyPopups = aCtx.Request.back_footprint_property_popups();
2499 job.m_pdfMetadata = aCtx.Request.include_metadata();
2500 job.m_pdfSingle = aCtx.Request.single_document();
2501 job.m_pdfBackgroundColor = wxString::FromUTF8( aCtx.Request.background_color() );
2502
2504
2505 return ExecuteBoardJob( pcbContext(), job );
2506}
2507
2508
2511{
2512 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2513 return tl::unexpected( validation.error() );
2514
2515 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2516 return tl::unexpected( *busy );
2517
2520 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2521
2522 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2523 return tl::unexpected( *err );
2524
2525 if( std::optional<ApiResponseStatus> paginationError =
2527 "RunBoardJobExportPs" ) )
2528 {
2529 return tl::unexpected( *paginationError );
2530 }
2531
2533
2534 job.m_trackWidthCorrection = aCtx.Request.track_width_correction();
2535 job.m_XScaleAdjust = aCtx.Request.x_scale_adjust();
2536 job.m_YScaleAdjust = aCtx.Request.y_scale_adjust();
2537 job.m_forceA4 = aCtx.Request.force_a4();
2538 job.m_useGlobalSettings = aCtx.Request.use_global_settings();
2539
2540 return ExecuteBoardJob( pcbContext(), job );
2541}
2542
2543
2546{
2547 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2548 return tl::unexpected( validation.error() );
2549
2550 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2551 return tl::unexpected( *busy );
2552
2555 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2556
2557 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2558 return tl::unexpected( *err );
2559
2560 if( std::optional<ApiResponseStatus> paginationError =
2561 ValidatePaginationModeForSingleOrPerFile( aCtx.Request.page_mode(), "RunBoardJobExportPng" ) )
2562 {
2563 return tl::unexpected( *paginationError );
2564 }
2565
2567
2568 if( aCtx.Request.has_dpi() )
2569 {
2570 int dpi = aCtx.Request.dpi();
2571
2572 if( dpi < MIN_PNG_DPI || dpi > MAX_PNG_DPI )
2573 {
2574 ApiResponseStatus status;
2575 status.set_status( ApiStatusCode::AS_BAD_REQUEST );
2576 status.set_error_message( fmt::format( "dpi must be between {} and {}", MIN_PNG_DPI, MAX_PNG_DPI ) );
2577 return tl::unexpected( status );
2578 }
2579
2580 job.m_dpi = dpi;
2581 }
2582
2583 // Unknown -> default AA on
2584 job.m_antialias = aCtx.Request.antialiasing() != types::AntialiasingMode::AAM_NONE;
2585 job.m_useBackgroundColor = aCtx.Request.plot_background_color();
2586
2587 return ExecuteBoardJob( pcbContext(), job );
2588}
2589
2590
2593{
2594 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2595 return tl::unexpected( validation.error() );
2596
2597 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2598 return tl::unexpected( *busy );
2599
2602 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2603
2604 job.m_useBoardPlotParams = aCtx.Request.use_board_plot_params();
2605
2606 if( !job.m_useBoardPlotParams )
2607 {
2608 if( std::optional<ApiResponseStatus> err = ApplyBoardPlotSettings( aCtx.Request.plot_settings(), job ) )
2609 return tl::unexpected( *err );
2610 }
2611
2612 job.m_createJobsFile = aCtx.Request.create_gerber_job_file();
2613 job.m_includeNetlistAttributes = aCtx.Request.include_netlist_attributes();
2614 job.m_useX2Format = aCtx.Request.use_x2_format();
2615 job.m_disableApertureMacros = aCtx.Request.disable_aperture_macros();
2616 job.m_useProtelFileExtension = aCtx.Request.use_protel_file_extensions();
2617
2618 switch( aCtx.Request.precision() )
2619 {
2620 default:
2621 case GerberPrecision::GP_5: job.m_precision = 5; break;
2622 case GerberPrecision::GP_6: job.m_precision = 6; break;
2623 }
2624
2625 return ExecuteBoardJob( pcbContext(), job );
2626}
2627
2628
2631{
2632 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2633 return tl::unexpected( validation.error() );
2634
2635 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2636 return tl::unexpected( *busy );
2637
2640 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2641
2643
2644 if( std::optional<ApiResponseStatus> unitError =
2645 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportDrill" ) )
2646 {
2647 return tl::unexpected( *unitError );
2648 }
2649
2653
2654 if( aCtx.Request.has_excellon() )
2655 {
2656 const ExcellonFormatOptions& excellonOptions = aCtx.Request.excellon();
2657
2658 if( excellonOptions.has_mirror_y() )
2659 job.m_excellonMirrorY = excellonOptions.mirror_y();
2660
2661 if( excellonOptions.has_minimal_header() )
2662 job.m_excellonMinimalHeader = excellonOptions.minimal_header();
2663
2664 if( excellonOptions.has_combine_pth_npth() )
2665 job.m_excellonCombinePTHNPTH = excellonOptions.combine_pth_npth();
2666
2667 if( excellonOptions.has_route_oval_holes() )
2668 job.m_excellonOvalDrillRoute = excellonOptions.route_oval_holes();
2669 }
2670
2671 if( aCtx.Request.map_format() != DrillMapFormat::DMF_UNKNOWN )
2672 {
2673 job.m_generateMap = true;
2675 }
2676
2677 job.m_gerberPrecision = aCtx.Request.gerber_precision() == DrillGerberPrecision::DGP_4_5 ? 5 : 6;
2678
2679 if( aCtx.Request.has_gerber_generate_tenting() )
2680 job.m_generateTenting = aCtx.Request.gerber_generate_tenting();
2681
2682 if( aCtx.Request.report_format() != DrillReportFormat::DRF_UNKNOWN )
2683 {
2684 job.m_generateReport = true;
2685
2686 if( aCtx.Request.has_report_filename() )
2687 job.m_reportPath = wxString::FromUTF8( aCtx.Request.report_filename() );
2688 }
2689
2690 return ExecuteBoardJob( pcbContext(), job );
2691}
2692
2693
2696{
2697 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2698 return tl::unexpected( validation.error() );
2699
2700 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2701 return tl::unexpected( *busy );
2702
2705 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2706
2707 if( aCtx.Request.has_use_drill_place_file_origin() )
2708 job.m_useDrillPlaceFileOrigin = aCtx.Request.use_drill_place_file_origin();
2709
2710 job.m_smdOnly = aCtx.Request.smd_only();
2711 job.m_excludeFootprintsWithTh = aCtx.Request.exclude_footprints_with_th();
2712 job.m_excludeDNP = aCtx.Request.exclude_dnp();
2713 job.m_excludeBOM = aCtx.Request.exclude_from_bom();
2714 job.m_negateBottomX = aCtx.Request.negate_bottom_x();
2715 job.m_singleFile = aCtx.Request.single_file();
2716 job.m_nakedFilename = aCtx.Request.naked_filename();
2717 if( aCtx.Request.has_include_board_edge_for_gerber() )
2718 job.m_gerberBoardEdge = aCtx.Request.include_board_edge_for_gerber();
2719
2720 job.m_variant = wxString::FromUTF8( aCtx.Request.variant() );
2721
2723
2724 if( std::optional<ApiResponseStatus> unitError =
2725 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportPosition" ) )
2726 {
2727 return tl::unexpected( *unitError );
2728 }
2729
2732
2733 return ExecuteBoardJob( pcbContext(), job );
2734}
2735
2736
2739{
2740 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2741 return tl::unexpected( validation.error() );
2742
2743 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2744 return tl::unexpected( *busy );
2745
2748 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2749
2750 job.m_flipBottomPads = aCtx.Request.flip_bottom_pads();
2751 job.m_useIndividualShapes = aCtx.Request.use_individual_shapes();
2752 job.m_storeOriginCoords = aCtx.Request.store_origin_coords();
2753 job.m_useDrillOrigin = aCtx.Request.use_drill_origin();
2754 job.m_useUniquePins = aCtx.Request.use_unique_pins();
2755
2756 return ExecuteBoardJob( pcbContext(), job );
2757}
2758
2759
2762{
2763 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2764 return tl::unexpected( validation.error() );
2765
2766 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2767 return tl::unexpected( *busy );
2768
2771 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2772
2773 return ExecuteBoardJob( pcbContext(), job );
2774}
2775
2776
2779{
2780 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2781 return tl::unexpected( validation.error() );
2782
2783 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2784 return tl::unexpected( *busy );
2785
2788 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2789
2790 job.m_drawingSheet = wxString::FromUTF8( aCtx.Request.drawing_sheet() );
2791 if( !aCtx.Request.variant().empty() )
2792 {
2793 job.m_variantNames.push_back( wxString::FromUTF8( aCtx.Request.variant() ) );
2794 }
2795
2796 if( aCtx.Request.has_precision() )
2797 job.m_precision = aCtx.Request.precision();
2798
2799 job.m_compress = aCtx.Request.compress();
2800 job.m_colInternalId = wxString::FromUTF8( aCtx.Request.internal_id_column() );
2801 job.m_colMfgPn = wxString::FromUTF8( aCtx.Request.manufacturer_part_number_column() );
2802 job.m_colMfg = wxString::FromUTF8( aCtx.Request.manufacturer_column() );
2803 job.m_colDistPn = wxString::FromUTF8( aCtx.Request.distributor_part_number_column() );
2804 job.m_colDist = wxString::FromUTF8( aCtx.Request.distributor_column() );
2805 job.m_bomRev = wxString::FromUTF8( aCtx.Request.bom_revision() );
2806
2807 if( std::optional<ApiResponseStatus> unitError =
2808 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportIpc2581" ) )
2809 {
2810 return tl::unexpected( *unitError );
2811 }
2812
2814 job.m_checkZonesBeforeExport = aCtx.Request.check_zones();
2817 ApplyFabExportContent( aCtx.Request.content(), job );
2818
2819 return ExecuteBoardJob( pcbContext(), job );
2820}
2821
2822
2825{
2826 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2827 return tl::unexpected( validation.error() );
2828
2829 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2830 return tl::unexpected( *busy );
2831
2834 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2835
2836 return ExecuteBoardJob( pcbContext(), job );
2837}
2838
2839
2840void ApplyFabExportContent( const FabExportContent& aContent, JOB_EXPORT_PCB_FAB& aJob )
2841{
2842 aJob.m_dataSet = FromProtoEnum<JOB_EXPORT_PCB_FAB::DATA_SET>( aContent.data_set() );
2843 aJob.m_sections = aContent.sections().empty()
2844 ? std::nullopt
2845 : std::optional<wxString>( wxString::FromUTF8( aContent.sections() ) );
2846 aJob.m_netNames = FromProtoEnum<JOB_EXPORT_PCB_FAB::NET_NAMES>( aContent.net_names() );
2847}
2848
2849
2850void ApplyOdbJobRequest( const RunBoardJobExportODB& aRequest, JOB_EXPORT_PCB_ODB& aJob )
2851{
2852 aJob.m_drawingSheet = wxString::FromUTF8( aRequest.drawing_sheet() );
2853
2854 if( aRequest.variant_names_size() > 0 )
2855 {
2856 for( const std::string& name : aRequest.variant_names() )
2857 aJob.m_variantNames.push_back( wxString::FromUTF8( name ) );
2858 }
2859 else if( !aRequest.variant().empty() )
2860 {
2861 aJob.m_variantNames.push_back( wxString::FromUTF8( aRequest.variant() ) );
2862 }
2863
2865 aRequest.variant_packaging() );
2866
2867 if( aRequest.has_precision() )
2868 aJob.m_precision = aRequest.precision();
2869
2870 aJob.m_units = FromProtoEnum<JOB_EXPORT_PCB_FAB::UNITS>( aRequest.units() );
2871 aJob.m_checkZonesBeforeExport = aRequest.check_zones();
2873 aJob.m_colMfgPn = wxString::FromUTF8( aRequest.manufacturer_part_number_column() );
2874 aJob.m_origin = FromProtoEnum<JOB_EXPORT_PCB_ODB::ORIGIN>( aRequest.origin() );
2875 aJob.m_productName = wxString::FromUTF8( aRequest.product_name() );
2876 ApplyFabExportContent( aRequest.content(), aJob );
2877}
2878
2879
2882{
2883 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2884 return tl::unexpected( validation.error() );
2885
2886 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2887 return tl::unexpected( *busy );
2888
2891 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2892
2893 if( std::optional<ApiResponseStatus> unitError =
2894 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportODB" ) )
2895 {
2896 return tl::unexpected( *unitError );
2897 }
2898
2899 ApplyOdbJobRequest( aCtx.Request, job );
2900
2901 return ExecuteBoardJob( pcbContext(), job );
2902}
2903
2904
2907{
2908 if( HANDLER_RESULT<bool> validation = validateDocument( aCtx.Request.job_settings().document() ); !validation )
2909 return tl::unexpected( validation.error() );
2910
2911 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2912 return tl::unexpected( *busy );
2913
2916 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2917
2919
2920 if( std::optional<ApiResponseStatus> unitError =
2921 ValidateUnitsInchMm( aCtx.Request.units(), "RunBoardJobExportStats" ) )
2922 {
2923 return tl::unexpected( *unitError );
2924 }
2925
2927
2928 job.m_excludeFootprintsWithoutPads = aCtx.Request.exclude_footprints_without_pads();
2929 job.m_subtractHolesFromBoardArea = aCtx.Request.subtract_holes_from_board_area();
2930 job.m_subtractHolesFromCopperAreas = aCtx.Request.subtract_holes_from_copper_areas();
2931
2932 return ExecuteBoardJob( pcbContext(), job );
2933}
2934
2935
2938{
2939 if( HANDLER_RESULT<bool> valid = validateDocument( aCtx.Request.job_settings().document() ); !valid )
2940 return tl::unexpected( valid.error() );
2941
2942 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
2943 return tl::unexpected( *busy );
2944
2945 switch( aCtx.Request.units() )
2946 {
2947 case types::Units::U_UNKNOWN:
2948 case types::Units::U_MM:
2949 case types::Units::U_MILS:
2950 break;
2951
2952 default:
2953 {
2954 ApiResponseStatus e;
2955 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
2956 e.set_error_message( "RunBoardJobExportIdf supports only mm and mils units" );
2957 return tl::unexpected( e );
2958 }
2959 }
2960
2963 job.SetConfiguredOutputPath( wxString::FromUTF8( aCtx.Request.job_settings().output_path() ) );
2964
2967
2968 if( aCtx.Request.origin_mode() == IdfOriginMode::IOM_USER )
2969 {
2970 // m_userOrigin is expressed in the units selected by m_units
2971 double scale = job.m_units == IDF_SETTINGS::UNITS::MM ? pcbIUScale.MM_PER_IU : pcbIUScale.MILS_PER_IU;
2972 job.m_userOrigin = UnpackVector2( aCtx.Request.user_origin() ) * scale;
2973 }
2974
2975 job.m_includeUnspecified = aCtx.Request.include_unspecified();
2976 job.m_includeDNP = aCtx.Request.include_dnp();
2977 job.m_calculateHeightFromModels = !aCtx.Request.only_use_explicit_heights();
2978
2979 if( aCtx.Request.has_part_number_field() )
2980 job.m_partNumberField = wxString::FromUTF8( aCtx.Request.part_number_field() );
2981 else
2982 job.m_partNumberField = wxS( "Value" );
2983
2984 return ExecuteBoardJob( pcbContext(), job );
2985}
2986
2987
2990{
2991 wxLogTrace( traceApi, "Received announce from frame %d at %s",
2992 aCtx.Request.frame_type(), aCtx.Request.socket_path() );
2993
2994 CROSS_PROBE_CLIENT::RegisterPeer( static_cast<FRAME_T>( aCtx.Request.frame_type() ),
2995 aCtx.Request.socket_path() );
2996
2997 CrossProbeAnnounceResponse response;
2998 response.set_status( CPS_OK );
2999 return response;
3000}
3001
3002
3004{
3005 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "SyncSelection" ) )
3006 return tl::unexpected( *headless );
3007
3008 SyncSelectionResponse response;
3009
3011
3012 if( !settings.on_selection && aCtx.Request.context() != SyncSelectionContext::SSC_EXPLICIT )
3013 {
3014 response.set_status( CPS_DISABLED );
3015 response.set_message( "implicit selection sync disabled by user" );
3016 return response;
3017 }
3018
3019 std::vector<BOARD_ITEM*> items =
3021
3022 frame()->m_ProbingSchToPcb = true; // recursion guard
3023
3024 if( aCtx.Request.mode() == SyncSelectionMode::SSM_ITEMS_AND_NETS )
3026 else
3028
3029 // Update 3D viewer highlighting
3030 frame()->Update3DView( false, frame()->GetPcbNewSettings()->m_Display.m_Live3DRefresh );
3031
3032 frame()->m_ProbingSchToPcb = false;
3033
3034 if( settings.flash_selection )
3035 {
3036 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE) PCB: flash enabled, items=%zu", items.size() );
3037 if( items.empty() )
3038 {
3039 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE) PCB: nothing to flash" );
3040 }
3041 else
3042 {
3043 std::vector<BOARD_ITEM*> boardItems;
3044 std::copy( items.begin(), items.end(), std::back_inserter( boardItems ) );
3045 frame()->StartCrossProbeFlash( boardItems );
3046 }
3047 }
3048 else
3049 {
3050 wxLogTrace( traceCrossProbeFlash, "MAIL_SELECTION(_FORCE) PCB: flash disabled" );
3051 }
3052
3053 response.set_status( CPS_OK );
3054 return response;
3055}
3056
3057
3059 const HANDLER_CONTEXT<HighlightNets>& aCtx )
3060{
3061 if( std::optional<ApiResponseStatus> headless = checkForHeadless( "HighlightNets" ) )
3062 return tl::unexpected( *headless );
3063
3064 HighlightNetsResponse response;
3065 CROSS_PROBING_SETTINGS& crossProbingSettings = frame()->GetPcbNewSettings()->m_CrossProbing;
3066
3068 || aCtx.ClientName == KiwayClientName )
3069 {
3070 if( !crossProbingSettings.auto_highlight )
3071 {
3072 response.set_status( CPS_DISABLED );
3073 response.set_message( "net highlight cross-probing disabled by user" );
3074 return response;
3075 }
3076 }
3077
3078 std::vector<wxString> nets;
3079
3080 for( const std::string& name : aCtx.Request.net_name() )
3081 nets.emplace_back( wxString::FromUTF8( name ) );
3082
3084
3085 response.set_status( CPS_OK );
3086 return response;
3087}
3088
3089
3091{
3092 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3093 return tl::unexpected( documentValidation.error() );
3094
3095 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3096 return tl::unexpected( *busy );
3097
3099 VariantsResponse response;
3100
3101 response.mutable_document()->CopyFrom( aCtx.Request.document() );
3102
3103 for( const wxString& name : board->GetVariantNames() )
3104 {
3105 types::DesignVariant* var = response.add_variants();
3106 var->set_name( name.ToUTF8() );
3107 var->set_description( board->GetVariantDescription( name ).ToUTF8() );
3108 }
3109
3110 return response;
3111}
3112
3113
3115{
3116 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3117 return tl::unexpected( documentValidation.error() );
3118
3119 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3120 return tl::unexpected( *busy );
3121
3123
3124 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3125
3126 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
3127 {
3128 ApiResponseStatus e;
3129 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3130 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
3131 return tl::unexpected( e );
3132 }
3133
3134 if( board->HasVariant( name ) )
3135 {
3136 ApiResponseStatus e;
3137 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3138 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.name() ) );
3139 return tl::unexpected( e );
3140 }
3141
3142 board->AddVariant( name );
3143
3144 if( aCtx.Request.has_description() )
3145 board->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
3146
3147 onModified();
3148
3149 if( frame() )
3151
3152 return Empty();
3153}
3154
3155
3157{
3158 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3159 return tl::unexpected( documentValidation.error() );
3160
3161 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3162 return tl::unexpected( *busy );
3163
3165
3166 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3167
3168 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 )
3169 {
3170 ApiResponseStatus e;
3171 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3172 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.name() ) );
3173 return tl::unexpected( e );
3174 }
3175
3176 if( !board->HasVariant( name ) )
3177 {
3178 ApiResponseStatus e;
3179 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3180 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3181 return tl::unexpected( e );
3182 }
3183
3184 board->DeleteVariant( name );
3185
3186 onModified();
3187
3188 if( frame() )
3190
3191 return Empty();
3192}
3193
3194
3196{
3197 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3198 return tl::unexpected( documentValidation.error() );
3199
3200 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3201 return tl::unexpected( *busy );
3202
3204
3205 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
3206 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
3207
3208 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3209 {
3210 ApiResponseStatus e;
3211 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3212 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
3213 return tl::unexpected( e );
3214 }
3215
3216 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3217 {
3218 ApiResponseStatus e;
3219 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3220 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
3221 return tl::unexpected( e );
3222 }
3223
3224 if( !board->HasVariant( oldName ) )
3225 {
3226 ApiResponseStatus e;
3227 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3228 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
3229 return tl::unexpected( e );
3230 }
3231
3232 if( board->HasVariant( newName ) )
3233 {
3234 ApiResponseStatus e;
3235 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3236 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
3237 return tl::unexpected( e );
3238 }
3239
3240 board->RenameVariant( oldName, newName );
3241
3242 onModified();
3243
3244 if( frame() )
3246
3247 return Empty();
3248}
3249
3250
3252{
3253 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3254 return tl::unexpected( documentValidation.error() );
3255
3256 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3257 return tl::unexpected( *busy );
3258
3260
3261 wxString oldName = wxString::FromUTF8( aCtx.Request.old_name() );
3262 wxString newName = wxString::FromUTF8( aCtx.Request.new_name() );
3263
3264 if( oldName.IsEmpty() || oldName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3265 {
3266 ApiResponseStatus e;
3267 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3268 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.old_name() ) );
3269 return tl::unexpected( e );
3270 }
3271
3272 if( newName.IsEmpty() || newName.CmpNoCase( GetDefaultVariantName() ) == 0 )
3273 {
3274 ApiResponseStatus e;
3275 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3276 e.set_error_message( fmt::format( "'{}' is not a valid variant name", aCtx.Request.new_name() ) );
3277 return tl::unexpected( e );
3278 }
3279
3280 if( !board->HasVariant( oldName ) )
3281 {
3282 ApiResponseStatus e;
3283 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3284 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.old_name() ) );
3285 return tl::unexpected( e );
3286 }
3287
3288 if( board->HasVariant( newName ) )
3289 {
3290 ApiResponseStatus e;
3291 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3292 e.set_error_message( fmt::format( "a variant named '{}' already exists", aCtx.Request.new_name() ) );
3293 return tl::unexpected( e );
3294 }
3295
3296 board->CopyVariant( oldName, newName,
3297 aCtx.Request.has_new_description() ? wxString::FromUTF8( aCtx.Request.new_description() )
3298 : wxString() );
3299
3300 onModified();
3301
3302 if( frame() )
3304
3305 return Empty();
3306}
3307
3308
3310{
3311 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3312 return tl::unexpected( documentValidation.error() );
3313
3314 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3315 return tl::unexpected( *busy );
3316
3318
3319 wxString name = wxString::FromUTF8( aCtx.Request.name() );
3320
3321 if( name.IsEmpty() || name.CmpNoCase( GetDefaultVariantName() ) == 0 || !board->HasVariant( name ) )
3322 {
3323 ApiResponseStatus e;
3324 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3325 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3326 return tl::unexpected( e );
3327 }
3328
3329 board->SetVariantDescription( name, wxString::FromUTF8( aCtx.Request.description() ) );
3330
3331 onModified();
3332
3333 return Empty();
3334}
3335
3336
3338{
3339 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3340 return tl::unexpected( documentValidation.error() );
3341
3342 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3343 return tl::unexpected( *busy );
3344
3346
3347 if( aCtx.Request.has_name() && !aCtx.Request.name().empty() )
3348 {
3349 if( wxString name = wxString::FromUTF8( aCtx.Request.name() ); !board->HasVariant( name ) )
3350 {
3351 ApiResponseStatus e;
3352 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3353 e.set_error_message( fmt::format( "no variant named '{}' exists", aCtx.Request.name() ) );
3354 return tl::unexpected( e );
3355 }
3356 }
3357
3358 wxString varName = aCtx.Request.has_name() ? wxString::FromUTF8( aCtx.Request.name() ) : wxString();
3359
3360 if( frame() )
3361 frame()->SetCurrentVariant( varName );
3362 else
3363 board->SetCurrentVariant( varName );
3364
3365 return Empty();
3366}
3367
3368
3371{
3372 if( HANDLER_RESULT<bool> documentValidation = validateDocument( aCtx.Request.document() ); !documentValidation )
3373 return tl::unexpected( documentValidation.error() );
3374
3375 CurrentVariantResponse response;
3376
3377 if( wxString current = pcbContext()->GetBoard()->GetCurrentVariant(); !current.IsEmpty() )
3378 response.set_name( current.ToUTF8() );
3379
3380 return response;
3381}
3382
3383
3386{
3387 if( !validateItemHeaderDocument( aCtx.Request.header() ) )
3388 {
3389 ApiResponseStatus e;
3390 e.set_status( ApiStatusCode::AS_UNHANDLED );
3391 return tl::unexpected( e );
3392 }
3393
3394 if( std::optional<ApiResponseStatus> busy = checkForBusy() )
3395 return tl::unexpected( *busy );
3396
3397 LIB_ID libId = UnpackLibId( aCtx.Request.lib_id() );
3398
3399 if( !libId.IsValid() )
3400 {
3401 ApiResponseStatus e;
3402 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3403 e.set_error_message( "lib_id must specify both a library nickname and an entry name" );
3404 return tl::unexpected( e );
3405 }
3406
3408
3409 if( layer == UNDEFINED_LAYER )
3410 layer = F_Cu;
3411
3412 // TODO update if we support inner layer footprints in the future
3413 if( !IsExternalCopperLayer( layer ) )
3414 {
3415 ApiResponseStatus e;
3416 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3417 e.set_error_message( "layer must be F_Cu or B_Cu" );
3418 return tl::unexpected( e );
3419 }
3420
3421 std::unique_ptr<FOOTPRINT> footprint( LoadFootprintFromProject( board(), libId ) );
3422
3423 if( !footprint )
3424 {
3425 ApiResponseStatus e;
3426 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
3427 e.set_error_message( fmt::format( "footprint '{}' not found", libId.Format().wx_str() ) );
3428 return tl::unexpected( e );
3429 }
3430
3431 footprint->SetUuid( KIID() );
3432 footprint->RunOnChildren(
3433 []( BOARD_ITEM* aChild )
3434 {
3435 aChild->ResetUuid();
3436 },
3438
3439 footprint->SetParent( board() );
3440
3441 footprint->SetPosition( UnpackVector2( aCtx.Request.position() ) );
3442
3443 if( aCtx.Request.has_orientation() )
3444 footprint->SetOrientationDegrees( aCtx.Request.orientation().value_degrees() );
3445
3446 footprint->SetLayerAndFlip( layer );
3447
3448 BOARD_COMMIT* commit = static_cast<BOARD_COMMIT*>( getCurrentCommit( aCtx.ClientName ) );
3449 FOOTPRINT* placed = footprint.release();
3450 commit->Add( placed );
3451
3452 if( !m_activeClients.contains( aCtx.ClientName ) )
3453 pushCurrentCommit( aCtx.ClientName, _( "Placed footprint via API" ) );
3454
3455 PlaceFromLibraryResponse response;
3456 response.mutable_header()->CopyFrom( aCtx.Request.header() );
3457 placed->Serialize( *response.mutable_item() );
3458
3459 return response;
3460}
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 > handleRunBoardJobExportSpecctraDsn(const HANDLER_CONTEXT< RunBoardJobExportSpecctraDsn > &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< ImportSpecctraSessionResponse > handleImportSpecctraSession(const HANDLER_CONTEXT< ImportSpecctraSession > &aCtx)
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
void Revert() override
Revert the commit by restoring the modified items state.
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:85
virtual void CopyFrom(const BOARD_ITEM *aOther)
void ResetUuid()
Definition board_item.h:281
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:411
void SetPlotOptions(const PCB_PLOT_PARAMS &aOptions)
Definition board.h:1024
EMBEDDED_FILES * GetEmbeddedFiles() override
Definition board.cpp:3662
const PAGE_INFO & GetPageSettings() const
Definition board.h:1020
void SetDesignSettings(const BOARD_DESIGN_SETTINGS &aSettings)
Definition board.cpp:1302
TITLE_BLOCK & GetTitleBlock()
Definition board.h:1026
PCB_LAYER_ID GetLayerID(const wxString &aLayerName) const
Return the ID of a layer.
Definition board.cpp:912
void SetPageSettings(const PAGE_INFO &aPageSettings)
Definition board.h:1021
const wxString & GetFileName() const
Definition board.h:454
const PCB_PLOT_PARAMS & GetPlotOptions() const
Definition board.h:1023
wxString GetDesignRulesPath() const
Return the absolute path to the design rules file for this board.
Definition board.cpp:437
BOARD_DESIGN_SETTINGS & GetDesignSettings()
Definition board.cpp:1290
std::shared_ptr< CONNECTIVITY_DATA > GetConnectivity() const
Return a list of missing connections between components/tracks.
Definition board.h:758
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:445
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:492
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:353
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:156
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:1172
Definition kiid.h:46
std::string AsStdString() const
Definition kiid.cpp:270
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
int ProcessJob(KIWAY::FACE_T aFace, JOB *aJob, REPORTER *aReporter=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr)
Definition kiway.cpp:753
@ 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:44
bool ContainsNetclassWithName(const wxString &netclass) const
Determines if the given netclass name is a constituent of this (maybe aggregate) netclass.
Definition netclass.cpp:336
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)
wxString m_BoardDrawingSheetFile
PcbNew params.
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
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:46
@ DRCE_GENERIC_ERROR
Definition drc_item.h:105
@ DRCE_GENERIC_WARNING
Definition drc_item.h:104
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:730
#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:752
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
@ F_Cu
Definition layer_ids.h:60
bool ImportSpecctraSession(BOARD *aBoard, const wxString &fullFileName, COMMIT &aCommit, REPORTER *aReporter=nullptr, int *aSkippedCount=nullptr)
Helper method to import SES file to a board.
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.
constexpr KICAD_T BaseType(const KICAD_T aType)
Return the underlying type of the given type.
Definition typeinfo.h:260
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_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_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_3D_BODY_T
class PCB_3D_BODY, a 3D body (extruded or model) on a footprint
Definition typeinfo.h:241
@ 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_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
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
VECTOR2< double > VECTOR2D
Definition vector2d.h:707
#define ZONE_FILLER_TOOL_NAME