7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-03-30 04:56:54 +00:00

ADDED: A new IPC API based on protobuf and nng

Details, documentation, and language bindings are works in
progress and will be evolving over the course of KiCad 9
development.
This commit is contained in:
Jon Evans 2023-01-29 13:06:05 -05:00
parent 77eaa75db1
commit f613cd1cb4
75 changed files with 6286 additions and 41 deletions

View File

@ -247,6 +247,10 @@ option( KICAD_GAL_PROFILE
"Enable profiling info for GAL"
OFF )
option( KICAD_IPC_API
"Enable experimental IPC API"
OFF )
# Global setting: exports are explicit
set( CMAKE_CXX_VISIBILITY_PRESET "hidden" )
set( CMAKE_VISIBILITY_INLINES_HIDDEN ON )
@ -278,6 +282,10 @@ if( KICAD_WAYLAND )
add_compile_definitions( KICAD_WAYLAND )
endif()
if( KICAD_IPC_API )
add_definitions( -DKICAD_IPC_API )
endif()
# Ensure DEBUG is defined for all platforms in Debug builds
add_compile_definitions( $<$<CONFIG:Debug>:DEBUG> )
@ -842,6 +850,26 @@ if( OCC_VERSION_STRING VERSION_LESS 7.5.0 )
endif()
include_directories( SYSTEM ${OCC_INCLUDE_DIR} )
option( KICAD_USE_CMAKE_FINDPROTOBUF "Use FindProtobuf provided by CMake" OFF )
mark_as_advanced( KICAD_USE_CMAKE_FINDPROTOBUF )
if( KICAD_USE_CMAKE_FINDPROTOBUF )
include( FindProtobuf )
find_package( Protobuf REQUIRED )
else()
find_package( Protobuf REQUIRED CONFIG )
set( Protobuf_LIBRARY "protobuf::libprotobuf" )
endif()
if( NOT Protobuf_PROTOC_EXECUTABLE )
set( Protobuf_PROTOC_EXECUTABLE "protobuf::protoc" )
endif()
if( NOT Protobuf_INCLUDE_DIR )
get_target_property( Protobuf_INCLUDE_DIR protobuf::libprotobuf INTERFACE_INCLUDE_DIRECTORIES )
endif()
# Assist with header file searching optimization:
# INC_BEFORE and INC_AFTER are two lists which go at the front and back of the
# header file search lists, respectively.
@ -1051,6 +1079,10 @@ if( KICAD_USE_SENTRY )
add_compile_definitions( KICAD_USE_SENTRY )
endif()
if( KICAD_IPC_API )
find_package( nng REQUIRED )
endif()
#================================================
# Add the doxygen target
#================================================
@ -1121,6 +1153,8 @@ if( KICAD_BUILD_I18N )
add_subdirectory( translation )
endif()
add_subdirectory( api )
if( APPLE )
set( KICAD_OSX_CODESIGN ON
CACHE BOOL "Sign KiCad.app on macOS" FORCE )

118
api/CMakeLists.txt Normal file
View File

@ -0,0 +1,118 @@
# This program source code file is part of KiCad, a free EDA CAD application.
#
# Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
# Search paths for protoc when generating code
set( Protobuf_IMPORT_DIRS ${Protobuf_INCLUDE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/proto )
set( KIAPI_PROTO_SRCS
common/envelope.proto
common/types/base_types.proto
common/commands/base_commands.proto
common/commands/editor_commands.proto
board/board_types.proto
)
# Generated C++ code must be in the build dir; it is dependent on the version of protoc installed
set( KIAPI_CPP_BASEPATH ${CMAKE_CURRENT_BINARY_DIR}/cpp/api )
foreach( PROTO_SRC ${KIAPI_PROTO_SRCS} )
string( REGEX REPLACE "\.proto$" ".pb.cc" CPP_SRC ${PROTO_SRC} )
string( REGEX REPLACE "\.proto$" ".pb.h" CPP_HEADER ${PROTO_SRC} )
set( KIAPI_CPP_SRCS ${KIAPI_CPP_SRCS} ${KIAPI_CPP_BASEPATH}/${CPP_SRC} )
set( KIAPI_CPP_HEADERS ${KIAPI_CPP_HEADERS} ${KIAPI_CPP_BASEPATH}/${CPP_HEADER} )
set( KIAPI_PROTO_SRC_FULLPATHS ${KIAPI_PROTO_SRC_FULLPATHS} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${PROTO_SRC} )
endforeach ()
add_custom_command( COMMAND ${CMAKE_COMMAND} -E make_directory ${KIAPI_CPP_BASEPATH}
COMMAND ${Protobuf_PROTOC_EXECUTABLE}
--cpp_out=dllexport_decl=KIAPI_IMPORTEXPORT:${KIAPI_CPP_BASEPATH}
--proto_path=${CMAKE_CURRENT_SOURCE_DIR}/proto
${KIAPI_PROTO_SRCS}
COMMENT "Generating API protobuf source files from proto definitions..."
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${KIAPI_PROTO_SRC_FULLPATHS}
OUTPUT ${KIAPI_CPP_SRCS} ${KIAPI_CPP_HEADERS}
)
# kiapi must be a shared DLL because the protobuf messages can only be initialized once
add_library( kiapi SHARED
${CMAKE_CURRENT_SOURCE_DIR}/../include/import_export.h
${KIAPI_CPP_SRCS}
${KIAPI_CPP_HEADERS}
)
target_compile_definitions( kiapi PRIVATE KIAPI_IMPORTEXPORT=APIEXPORT )
target_compile_definitions( kiapi INTERFACE KIAPI_IMPORTEXPORT=APIIMPORT )
# https://groups.google.com/g/protobuf/c/PDR1bqRazts
if(MSVC)
target_compile_options( kiapi PRIVATE /FI${CMAKE_CURRENT_SOURCE_DIR}/../include/import_export.h )
else()
add_definitions( -include ${CMAKE_CURRENT_SOURCE_DIR}/../include/import_export.h )
endif()
if( APPLE )
# puts library into the main kicad.app bundle in build tree
set_target_properties( kiapi PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${OSX_BUNDLE_BUILD_LIB_DIR}"
INSTALL_NAME_DIR "${OSX_BUNDLE_BUILD_LIB_DIR}"
)
endif()
install( TARGETS
kiapi
RUNTIME DESTINATION ${KICAD_LIB}
LIBRARY DESTINATION ${KICAD_LIB}
COMPONENT binary
)
if( KICAD_WIN32_INSTALL_PDBS )
# Get the PDBs to copy over for MSVC
install(FILES $<TARGET_PDB_FILE:kiapi> DESTINATION ${KICAD_BIN})
endif()
# Because CMake doesn't guess this from the .cc extension generated by protoc
set_target_properties( kiapi PROPERTIES LINKER_LANGUAGE CXX )
target_include_directories( kiapi SYSTEM PUBLIC ${Protobuf_INCLUDE_DIRS} )
target_link_libraries( kiapi protobuf::libprotobuf )
target_include_directories( kiapi INTERFACE
${CMAKE_CURRENT_BINARY_DIR}/cpp # Leaving off the /api/ to make #include statments less ambiguous
)
# Because when building internally, the generated files do not include the "api" base path
target_include_directories( kiapi PUBLIC ${KIAPI_CPP_BASEPATH} )
option( KICAD_BUILD_ENUM_EXPORTER
"Build the enum exporter used as part of generating the IPC APIs"
OFF )
if( KICAD_BUILD_ENUM_EXPORTER )
add_subdirectory( enums )
add_custom_target( enum_definitions
COMMAND $<TARGET_FILE:enum_exporter> ${CMAKE_CURRENT_BINARY_DIR}/enums.json
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generating API definitions from KiCad enums..."
DEPENDS enum_exporter
)
endif()

43
api/enums/CMakeLists.txt Normal file
View File

@ -0,0 +1,43 @@
# This program source code file is part of KiCad, a free EDA CAD application.
#
# Copyright (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or (at your
# option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
include_directories( BEFORE ${INC_BEFORE} )
include_directories(
${INC_AFTER}
)
add_executable( enum_exporter WIN32
enum_exporter.cpp
)
target_link_libraries( enum_exporter
common
)
target_include_directories( enum_exporter PRIVATE
$<TARGET_PROPERTY:magic_enum,INTERFACE_INCLUDE_DIRECTORIES>
)
if( MSVC )
# The cli needs subsystem:console or else we can't link wmain/main
set_target_properties(enum_exporter PROPERTIES COMPILE_DEFINITIONS_DEBUG "_CONSOLE")
set_target_properties(enum_exporter PROPERTIES COMPILE_DEFINITIONS_RELWITHDEBINFO "_CONSOLE")
set_target_properties(enum_exporter PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
set_target_properties(enum_exporter PROPERTIES LINK_FLAGS_RELWITHDEBINFO "/SUBSYSTEM:CONSOLE")
set_target_properties(enum_exporter PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:CONSOLE")
set_target_properties(enum_exporter PROPERTIES LINK_FLAGS_MINSIZEREL "/SUBSYSTEM:CONSOLE")
endif()

View File

@ -0,0 +1,91 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <filesystem>
#include <iostream>
#include <string>
#include <argparse/argparse.hpp>
#include <fmt.h>
#include <nlohmann/json.hpp>
#define MAGIC_ENUM_RANGE_MAX 1024
#include <magic_enum.hpp>
#include <layer_ids.h>
#include <eda_shape.h>
#include <core/typeinfo.h>
template<typename T>
nlohmann::json FormatEnum()
{
nlohmann::json js;
js["type"] = magic_enum::enum_type_name<T>();
js["values"] = nlohmann::json::array();
for( const std::pair<T, std::string_view>& entry : magic_enum::enum_entries<T>() )
{
js["values"].emplace_back( nlohmann::json( {
{ "key", entry.second },
{ "value", static_cast<int>( entry.first ) }
} ) );
}
return js;
}
int main( int argc, char* argv[] )
{
argparse::ArgumentParser args( "enum_exporter" );
args.add_argument( "output_dir" ).default_value( std::string() );
try
{
args.parse_args( argc, argv );
}
catch( const std::runtime_error& err )
{
std::cerr << err.what() << std::endl;
std::cerr << args;
std::exit( 1 );
}
std::filesystem::path path( args.get<std::string>( "output_dir" ) );
std::ofstream outfile;
if( !path.empty() )
{
path = std::filesystem::absolute( path );
outfile.open( path );
}
std::ostream& out = outfile.is_open() ? outfile : std::cout;
nlohmann::json js = nlohmann::json::array();
js += FormatEnum<PCB_LAYER_ID>();
js += FormatEnum<SHAPE_T>();
js += FormatEnum<KICAD_T>();
out << js.dump( 4 ) << std::endl;
}

View File

@ -0,0 +1,110 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
syntax = "proto3";
package kiapi.board.types;
import "common/types/base_types.proto";
/// Represents a track segment on a board
message Track
{
kiapi.common.types.KIID id = 1;
kiapi.common.types.Point2D start = 2;
kiapi.common.types.Point2D end = 3;
kiapi.common.types.Distance width = 4;
kiapi.common.types.LockedState locked = 5;
kiapi.common.types.BoardLayer layer = 6;
kiapi.common.types.Net net = 7;
}
/// Represents an arc track (not a PCB_SHAPE in arc shape)
/// Arc tracks in KiCad store start, midpoint, and end.
/// All other values (center point, angles, etc) are inferred.
message Arc
{
kiapi.common.types.KIID id = 1;
kiapi.common.types.Point2D start = 2;
kiapi.common.types.Point2D mid = 3; /// Arc midpoint
kiapi.common.types.Point2D end = 4;
kiapi.common.types.Distance width = 5;
kiapi.common.types.LockedState locked = 6;
kiapi.common.types.BoardLayer layer = 7;
kiapi.common.types.Net net = 8;
}
enum PadStackType
{
PST_UNKNOWN = 0;
PST_THROUGH = 1; /// Through all layers; same shape on all layers
PST_BLIND_BURIED = 2; /// From a start layer to end layer (inclusive); same shape on all included layers
}
enum UnconnectedLayerRemoval
{
ULR_UNKNOWN = 0;
/// Keep annular rings on all layers
ULR_KEEP = 1;
/// Remove annular rings on unconnected layers, including start and end layers.
ULR_REMOVE = 2;
/// Remove annular rings on unconnected layers, but preserve start and end layers even if unconnected.
ULR_REMOVE_EXCEPT_START_AND_END = 3;
}
/// A pad stack definition for a multilayer pad or via.
message PadStack
{
/// What type of pad stack this represents.
PadStackType type = 1;
/// Lowest (closest to F_Cu) layer this stack exists on. Ignored if type == PST_THROUGH.
kiapi.common.types.BoardLayer start_layer = 2;
/// Highest (closest to B_Cu) layer this stack exists on. Ignored if type == PST_THROUGH.
kiapi.common.types.BoardLayer end_layer = 3;
/// How to treat annular rings on unconnected layers.
UnconnectedLayerRemoval unconnected_layer_removal = 4;
}
/// Represents a via
message Via
{
/// The unique identifier of the via
kiapi.common.types.KIID id = 1;
/// The location of the via's center point
kiapi.common.types.Point2D position = 2;
/// The diameter of the via's circular copper pad
kiapi.common.types.Distance pad_diameter = 4;
/// The diameter of the via's drilled hole
kiapi.common.types.Distance drill_diameter = 5;
/// The pad stack definition for this via. The via's VIATYPE (blind/buried/normal) is inferred from this.
PadStack pad_stack = 6;
kiapi.common.types.LockedState locked = 7;
kiapi.common.types.Net net = 8;
}

View File

@ -0,0 +1,33 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
syntax = "proto3";
package kiapi.common.commands;
import "common/types/base_types.proto";
message GetVersion
{
}
message GetVersionResponse
{
kiapi.common.types.KiCadVersion version = 1;
}

View File

@ -0,0 +1,251 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Commands and responses related to manipulating editor windows
*/
syntax = "proto3";
package kiapi.common.commands;
import "google/protobuf/any.proto";
import "common/types/base_types.proto";
/// Refreshes the given frame, if that frame is open
message RefreshEditor
{
kiapi.common.types.FrameType frame = 1;
}
/// Retrieves a list of open documents of the given type
message GetOpenDocuments
{
/// Which type of documents to query
kiapi.common.types.DocumentType type = 1;
}
message GetOpenDocumentsResponse
{
repeated kiapi.common.types.DocumentSpecifier documents = 1;
}
/*
* Runs a TOOL_ACTION using the TOOL_MANAGER of a given frame.
* WARNING: The TOOL_ACTIONs are specifically *not* an API.
* Command names may change as code is refactored, and commands may disappear.
* This API method is provided for low-level prototyping purposes only.
*/
message RunAction
{
string action = 1; // Action name, like "eeschema.InteractiveSelection.ClearSelection"
}
enum RunActionStatus
{
RAS_UNKNOWN = 0;
RAS_OK = 1; // The action was submitted successfully.
RAS_INVALID = 2; // The action was unknown for the targeted frame.
RAS_FRAME_NOT_OPEN = 3; // The targeted frame was not open when the call was submitted.
}
/*
* NOTE: At the moment, RAS_FRAME_NOT_OPEN won't be returned as the handler is inside the frame.
*/
message RunActionResponse
{
RunActionStatus status = 1;
}
/*
* Begins a staged set of changes. Any modifications made to a document through the API after this
* call will be saved to a pending commit, and will not appear in KiCad until a matching call to
* END_COMMIT.
*/
message BeginCommit
{
}
message BeginCommitResponse
{
}
enum CommitResult
{
CR_UNKNOWN = 0;
CR_OK = 1; // Commit was pushed successfully
CR_NO_COMMIT = 2; // There was no commit started
}
message EndCommit
{
// Optional message describing this changeset
string message = 1;
}
message EndCommitResponse
{
CommitResult result = 1;
}
/// Creates new items on a given document
message CreateItems
{
/// Specifies which document to create on, which fields are included, etc.
kiapi.common.types.ItemHeader header = 1;
/// List of items to create
repeated google.protobuf.Any items = 2;
/// Items may be created on a top-level document (sheet, board, etc) or inside a container
/// (symbol, footprint). If this field is not empty, it holds the ID of a symbol or footprint
/// that the items should be added to. This ID must be an existing symbol (for schematic
/// documents) or footprint (for board documents). If the given container does not exist or is
/// not the correct item type, the CreateItems call will fail.
kiapi.common.types.KIID container = 3;
}
enum ItemCreationStatus
{
ICS_UNKNOWN = 0;
ICS_OK = 1; /// The item was created
ICS_INVALID_TYPE = 2; /// The item's type is not valid for the given document
ICS_EXISTING = 3; /// The item had a specified KIID and that KIID was already in use
}
message ItemCreationResult
{
ItemCreationStatus status = 1;
/// The created version of the item, including an updated KIID as applicable
google.protobuf.Any item = 2;
}
message CreateItemsResponse
{
/// Specifies which document was modified, which fields are included in created_items, etc.
kiapi.common.types.ItemHeader header = 1;
/// Status of the overall request; may return IRS_OK even if no items were created
kiapi.common.types.ItemRequestStatus status = 2;
/// Status of each item to be created
repeated ItemCreationResult created_items = 3;
}
message GetItems
{
/// Specifies which document to query, which fields to return, etc.
kiapi.common.types.ItemHeader header = 1;
/// List of one or more types of items to retreive
repeated kiapi.common.types.ItemType types = 2;
}
message GetItemsResponse
{
/// Specifies which document was modified, which fields are included in items, etc.
kiapi.common.types.ItemHeader header = 1;
/// Status of the overall request; may return IRS_OK even if no items were retrieved
kiapi.common.types.ItemRequestStatus status = 2;
repeated google.protobuf.Any items = 3;
}
/// Updates items in a given document
message UpdateItems
{
/// Specifies which document to modify, which fields are included, etc.
kiapi.common.types.ItemHeader header = 1;
/// List of items to modify
repeated google.protobuf.Any items = 2;
}
enum ItemUpdateStatus
{
IUS_UNKNOWN = 0;
IUS_OK = 1; /// The item was updated
IUS_INVALID_TYPE = 2; /// The item's type is not valid for the given document
IUS_NONEXISTENT = 3; /// The item did not exist in the given document
IUS_IMMUTABLE = 4; /// The item is not allowed to be modified by the API
}
message ItemUpdateResult
{
ItemUpdateStatus status = 1;
/// The update version of the item
google.protobuf.Any item = 2;
}
message UpdateItemsResponse
{
/// Specifies which document was modified, which fields are included in updated_items, etc.
kiapi.common.types.ItemHeader header = 1;
/// Status of the overall request; may return IRS_OK even if no items were modified
kiapi.common.types.ItemRequestStatus status = 2;
/// Status of each item to be created
repeated ItemUpdateResult updated_items = 3;
}
/// Deletes items in a given document
message DeleteItems
{
/// Specifies which document to modify
kiapi.common.types.ItemHeader header = 1;
/// List of item KIIDs to delete
repeated kiapi.common.types.KIID item_ids = 2;
}
enum ItemDeletionStatus
{
IDS_UNKNOWN = 0;
IDS_OK = 1;
IDS_NONEXISTENT = 2; /// The item did not exist in the given document
IDS_IMMUTABLE = 3; /// The item is not allowed to be modified by the API
}
message ItemDeletionResult
{
kiapi.common.types.KIID id = 1;
ItemDeletionStatus status = 2;
}
message DeleteItemsResponse
{
/// Specifies which document was modified, etc.
kiapi.common.types.ItemHeader header = 1;
/// Status of the overall request; may return IRS_OK even if no items were deleted
kiapi.common.types.ItemRequestStatus status = 2;
/// Status of each item requested to be deleted
repeated ItemDeletionResult deleted_items = 3;
}

View File

@ -0,0 +1,90 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
syntax = "proto3";
package kiapi.common;
import "google/protobuf/any.proto";
enum ApiStatusCode
{
AS_UNKNOWN = 0;
AS_OK = 1; // Request succeeded
AS_TIMEOUT = 2; // Request timed out
AS_BAD_REQUEST = 3; // The request had invalid parameters or otherwise was illegal
AS_NOT_READY = 4; // KiCad was not (yet) in a state where it could handle API requests
AS_UNHANDLED = 5; // The request was not handled by KiCad
AS_TOKEN_MISMATCH = 6; // The kicad_token in the request didn't match this KiCad's token
}
/*
* For future expansion: any header fields that should be sent with a request
*/
message ApiRequestHeader
{
// An opaque string identifying a running instance of KiCad. If this is set to a non-empty
// string in an API request, KiCad will reject the request if the value doesn't match its own
// token. This can be used to let API clients make sure they are still talking to the same
// instance of KiCad if they are long-running.
string kicad_token = 1;
// A string identifying an API client. Should be set by the client to a value that is unique
// to a specific instance of a client, for example the package name of the client plus its
// process ID or a random string, e.g. "com.github.me.my_awesome_plugin-73951". The main purpose
// of this name is to identify the client in debug logs.
string client_name = 2;
}
/*
* The top-level envelope container for an API request (message from a client to the KiCad API server)
*/
message ApiRequest
{
ApiRequestHeader header = 1;
google.protobuf.Any message = 2;
}
/*
* For future expansion: any header fields that should be sent with a response
*/
message ApiResponseHeader
{
/// An opaque string identifying a running instance of KiCad.
string kicad_token = 1;
}
message ApiResponse
{
ApiResponseHeader header = 1;
ApiResponseStatus status = 2;
google.protobuf.Any message = 3;
}
message ApiResponseStatus
{
/// A code describing the category of error (or AS_OK if no error)
ApiStatusCode status = 1;
/// A human-readable description of the error, if any
string error_message = 2;
}

View File

@ -0,0 +1,217 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* base_types.proto
* Includes types used in many parts of the API
*/
syntax = "proto3";
package kiapi.common.types;
import "google/protobuf/field_mask.proto";
enum CommandStatus
{
CS_UNKNOWN = 0;
CS_OK = 1; // Command succeeded
CS_FAILED = 2; // Command failed
}
message CommandStatusResponse
{
CommandStatus status = 1;
}
/**
* Describes a particular version of KiCad
*/
message KiCadVersion
{
uint32 major = 1;
uint32 minor = 2;
uint32 patch = 3;
// Full identifier string, potentially containing git hashes, packager-added info, etc.
string full_version = 4;
}
/**
* Some commands are specific to a KiCad window (frame). This list contains all addressable frames.
*/
enum FrameType
{
FT_UNKNOWN = 0;
FT_PROJECT_MANAGER = 1;
FT_SCHEMATIC_EDITOR = 2;
FT_PCB_EDITOR = 3;
FT_SPICE_SIMULATOR = 4;
FT_SYMBOL_EDITOR = 5;
FT_FOOTPRINT_EDITOR = 6;
FT_DRAWING_SHEET_EDITOR = 7;
}
/**
* Describes a KIID, or UUID of an object in a KiCad editor model.
*/
message KIID
{
// The KIID's value in standard UUID format, stored as a string for easy portability
string value = 1;
}
/**
* Identifier for the type of document being targeted by a request
*/
enum DocumentType
{
DOCTYPE_UNKNOWN = 0;
DOCTYPE_SCHEMATIC = 1;
DOCTYPE_SYMBOL = 2;
DOCTYPE_PCB = 3;
DOCTYPE_FOOTPRINT = 4;
DOCTYPE_DRAWING_SHEET = 5;
}
/**
* Describes a KiCad LIB_ID; a unique identifier for a loaded symbol or footprint
*/
message LibraryIdentifier
{
/// The library portion of the LIB_ID
string library_nickname = 1;
/// The symbol or footprint name
string entry_name = 2;
}
/**
* Describes a unique sheet in a schematic
*/
message SheetPath
{
/// The canonical path to the sheet. The first KIID will be the root sheet, etc.
repeated KIID path = 1;
/// The path converted to a human readable form such as "/", "/child", or "/child/grandchild"
string path_human_readable = 2;
}
/**
* Describes a document that will be the target of a request
*/
message DocumentSpecifier
{
DocumentType type = 1;
oneof identifier
{
/// If type == DT_SYMBOL or DT_FOOTPRINT, identifies a certain library entry
LibraryIdentifier lib_id = 2;
/// If type == DT_SCHEMATIC, identifies a sheet with a given path
SheetPath sheet_path = 3;
/// If type == DT_PCB, identifies a PCB with a given filename, e.g. "board.kicad_pcb"
string board_filename = 4;
}
}
/**
* Describes the type of a KiCad item (wrapper for KICAD_T)
*/
message ItemType
{
/// Must be a valid value in the KICAD_T C++ enum (see typeinfo.h)
int32 type = 1;
}
/**
* This header is included in requests and responses about item(s) in a document
*/
message ItemHeader
{
/// Which document is this request targeting?
DocumentSpecifier document = 1;
/// Which fields on the item(s) are included with this request or response
google.protobuf.FieldMask field_mask = 2;
}
/**
* Status of a request that included an ItemHeader
*/
enum ItemRequestStatus
{
IRS_UNKNOWN = 0;
IRS_OK = 1;
IRS_DOCUMENT_NOT_FOUND = 2; /// The given document is not open in KiCad
IRS_FIELD_MASK_INVALID = 3; /// The given field_mask contains invalid specifiers
}
/// Describes a point in 2D space. All coordinates are in nanometers.
message Point2D
{
int64 x_nm = 1;
int64 y_nm = 2;
}
/// Describes a point in 3D space. All coordinates are in nanometers.
message Point3D
{
int64 x_nm = 1;
int64 y_nm = 2;
int64 z_nm = 3;
}
/// Describes a quantity of distance (size, length, etc). All coordinates are in nanometers.
message Distance
{
int64 value_nm = 1;
}
/// Describes whether or not an item is locked for editing or movement
enum LockedState
{
LS_UNKNOWN = 0;
LS_UNLOCKED = 1;
LS_LOCKED = 2;
}
message BoardLayer
{
int32 layer_id = 1; /// From PCB_LAYER_T
}
/// Describes a copper item's net
message Net
{
/// A unique code representing this net
int32 code = 1;
/// Human-readable net name
string name = 2;
}
/// Describes a net class (a grouping of nets)
message NetClass
{
string name = 1;
}

26
cmake/Findnng.cmake Normal file
View File

@ -0,0 +1,26 @@
find_package(PkgConfig)
if(PKG_CONFIG_FOUND)
pkg_check_modules(_NNG nng)
endif (PKG_CONFIG_FOUND)
FIND_PATH(NNG_INCLUDE_DIR
NAMES
nng/nng.h
PATH_SUFFIXES
include
)
FIND_LIBRARY(NNG_LIBRARY
NAMES
nng
PATH_SUFFIXES
"lib"
"local/lib"
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(nng
REQUIRED_VARS NNG_INCLUDE_DIR NNG_LIBRARY)
MARK_AS_ADVANCED(NNG_INCLUDE_DIR NNG_LIBRARY)

View File

@ -186,6 +186,7 @@ set_target_properties(kicommon PROPERTIES CXX_VISIBILITY_PRESET hidden)
target_link_libraries( kicommon
core
kiapi
kimath
kiplatform
nlohmann_json
@ -254,6 +255,7 @@ target_include_directories( kicommon
.
${CMAKE_BINARY_DIR}
$<TARGET_PROPERTY:pegtl,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:kiapi,INTERFACE_INCLUDE_DIRECTORIES>
)
add_dependencies( kicommon pegtl version_header )
@ -335,6 +337,8 @@ set( COMMON_DLG_SRCS
dialogs/panel_mouse_settings_base.cpp
dialogs/panel_packages_and_updates.cpp
dialogs/panel_packages_and_updates_base.cpp
dialogs/panel_python_settings.cpp
dialogs/panel_python_settings_base.cpp
dialogs/panel_setup_netclasses.cpp
dialogs/panel_setup_netclasses_base.cpp
dialogs/panel_setup_severities.cpp
@ -608,6 +612,15 @@ set( COMMON_SRCS
)
if( KICAD_IPC_API )
set( COMMON_SRCS
${COMMON_SRCS}
api/api_server.cpp
api/api_handler.cpp
api/api_handler_common.cpp
)
endif()
add_library( common STATIC
${COMMON_SRCS}
)
@ -647,6 +660,12 @@ if( KICAD_USE_SENTRY )
sentry )
endif()
if( KICAD_IPC_API )
target_link_libraries( common
kinng
)
endif()
target_include_directories( common
PUBLIC
.
@ -656,19 +675,17 @@ target_include_directories( common
# text markup support
add_dependencies( common pegtl )
target_include_directories( common PUBLIC
$<TARGET_PROPERTY:pegtl,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:magic_enum,INTERFACE_INCLUDE_DIRECTORIES>
$<TARGET_PROPERTY:expected,INTERFACE_INCLUDE_DIRECTORIES>
)
target_include_directories( common SYSTEM PUBLIC
$<TARGET_PROPERTY:nanodbc,INTERFACE_INCLUDE_DIRECTORIES>
)
target_include_directories( common PUBLIC
$<TARGET_PROPERTY:magic_enum,INTERFACE_INCLUDE_DIRECTORIES>
)
set( PCB_COMMON_SRCS
fp_lib_table.cpp
hash_eda.cpp

View File

@ -110,6 +110,7 @@ static const wxChar OcePluginAngularDeflection[] = wxT( "OcePluginAngularDeflect
static const wxChar TriangulateSimplificationLevel[] = wxT( "TriangulateSimplificationLevel" );
static const wxChar TriangulateMinimumArea[] = wxT( "TriangulateMinimumArea" );
static const wxChar EnableCacheFriendlyFracture[] = wxT( "EnableCacheFriendlyFracture" );
static const wxChar EnableAPILogging[] = wxT( "EnableAPILogging" );
} // namespace KEYS
@ -244,6 +245,7 @@ ADVANCED_CFG::ADVANCED_CFG()
m_3DRT_BevelExtentFactor = 1.0 / 16.0;
m_UseClipper2 = true;
m_EnableAPILogging = false;
m_Use3DConnexionDriver = true;
@ -444,6 +446,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGenerators,
&m_EnableGenerators, m_EnableGenerators ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableAPILogging,
&m_EnableAPILogging, m_EnableAPILogging ) );
configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGit,
&m_EnableGit, m_EnableGit ) );

View File

@ -0,0 +1,74 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <api/api_handler.h>
using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiResponseStatus;
API_RESULT API_HANDLER::Handle( ApiRequest& aMsg )
{
ApiResponseStatus status;
if( !aMsg.has_message() )
{
status.set_status( ApiStatusCode::AS_BAD_REQUEST );
status.set_error_message( "request has no inner message" );
return tl::unexpected( status );
}
std::string typeName;
if( !google::protobuf::Any::ParseAnyTypeUrl( aMsg.message().type_url(), &typeName ) )
{
status.set_status( ApiStatusCode::AS_BAD_REQUEST );
status.set_error_message( "could not parse inner message type" );
return tl::unexpected( status );
}
auto it = m_handlers.find( typeName );
if( it != m_handlers.end() )
{
REQUEST_HANDLER& handler = it->second;
return handler( aMsg );
}
status.set_status( ApiStatusCode::AS_UNHANDLED );
// This response is used internally; no need for an error message
return tl::unexpected( status );
}
std::optional<KICAD_T> API_HANDLER::TypeNameFromAny( const google::protobuf::Any& aMessage )
{
static const std::map<std::string, KICAD_T> s_types = {
{ "type.googleapis.com/kiapi.board.types.Track", PCB_TRACE_T },
{ "type.googleapis.com/kiapi.board.types.Arc", PCB_ARC_T },
{ "type.googleapis.com/kiapi.board.types.Via", PCB_VIA_T },
};
auto it = s_types.find( aMessage.type_url() );
if( it != s_types.end() )
return it->second;
return std::nullopt;
}

View File

@ -0,0 +1,52 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <tuple>
#include <api/api_handler_common.h>
#include <build_version.h>
#include <pgm_base.h>
#include <wx/string.h>
using namespace kiapi::common::commands;
using namespace kiapi::common::types;
using google::protobuf::Empty;
API_HANDLER_COMMON::API_HANDLER_COMMON() :
API_HANDLER()
{
registerHandler<GetVersion, GetVersionResponse>( &API_HANDLER_COMMON::handleGetVersion );
}
HANDLER_RESULT<GetVersionResponse> API_HANDLER_COMMON::handleGetVersion( GetVersion& aMsg )
{
GetVersionResponse reply;
reply.mutable_version()->set_full_version( GetBuildVersion().ToStdString() );
std::tuple<int, int, int> version = GetMajorMinorPatchTuple();
reply.mutable_version()->set_major( std::get<0>( version ) );
reply.mutable_version()->set_minor( std::get<1>( version ) );
reply.mutable_version()->set_patch( std::get<2>( version ) );
return reply;
}

193
common/api/api_server.cpp Normal file
View File

@ -0,0 +1,193 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <wx/app.h>
#include <wx/datetime.h>
#include <wx/event.h>
#include <advanced_config.h>
#include <api/api_server.h>
#include <api/api_handler_common.h>
#include <kiid.h>
#include <kinng.h>
#include <paths.h>
#include <pgm_base.h>
#include <string_utils.h>
#include <api/common/envelope.pb.h>
using kiapi::common::ApiRequest, kiapi::common::ApiResponse, kiapi::common::ApiStatusCode;
wxString KICAD_API_SERVER::s_logFileName = "api.log";
wxDEFINE_EVENT( API_REQUEST_EVENT, wxCommandEvent );
KICAD_API_SERVER::KICAD_API_SERVER() :
wxEvtHandler(),
m_token( KIID().AsStdString() ),
m_readyToReply( false )
{
m_server = std::make_unique<KINNG_REQUEST_SERVER>();
m_server->SetCallback( [&]( std::string* aRequest ) { onApiRequest( aRequest ); } );
m_commonHandler = std::make_unique<API_HANDLER_COMMON>();
RegisterHandler( m_commonHandler.get() );
m_logFilePath.AssignDir( PATHS::GetLogsPath() );
m_logFilePath.SetName( s_logFileName );
if( ADVANCED_CFG::GetCfg().m_EnableAPILogging )
PATHS::EnsurePathExists( PATHS::GetLogsPath() );
log( "--- KiCad API server started ---\n" );
Bind( API_REQUEST_EVENT, &KICAD_API_SERVER::handleApiEvent, this );
}
KICAD_API_SERVER::~KICAD_API_SERVER()
{
}
void KICAD_API_SERVER::RegisterHandler( API_HANDLER* aHandler )
{
wxCHECK( aHandler, /* void */ );
m_handlers.insert( aHandler );
}
void KICAD_API_SERVER::DeregisterHandler( API_HANDLER* aHandler )
{
m_handlers.erase( aHandler );
}
void KICAD_API_SERVER::onApiRequest( std::string* aRequest )
{
if( !m_readyToReply )
{
ApiResponse notHandled;
notHandled.mutable_status()->set_status( ApiStatusCode::AS_NOT_READY );
notHandled.mutable_status()->set_error_message( "KiCad is not ready to reply" );
m_server->Reply( notHandled.SerializeAsString() );
log( "Got incoming request but was not yet ready to reply." );
return;
}
wxCommandEvent* evt = new wxCommandEvent( API_REQUEST_EVENT );
// We don't actually need write access to this string, but client data is non-const
evt->SetClientData( static_cast<void*>( aRequest ) );
// Takes ownership and frees the wxCommandEvent
QueueEvent( evt );
}
void KICAD_API_SERVER::handleApiEvent( wxCommandEvent& aEvent )
{
std::string& requestString = *static_cast<std::string*>( aEvent.GetClientData() );
ApiRequest request;
if( !request.ParseFromString( requestString ) )
{
ApiResponse error;
error.mutable_header()->set_kicad_token( m_token );
error.mutable_status()->set_status( ApiStatusCode::AS_BAD_REQUEST );
error.mutable_status()->set_error_message( "request could not be parsed" );
m_server->Reply( error.SerializeAsString() );
log( "Response (ERROR): " + error.Utf8DebugString() );
}
log( "Request: " + request.Utf8DebugString() );
if( !request.header().kicad_token().empty() &&
request.header().kicad_token().compare( m_token ) != 0 )
{
ApiResponse error;
error.mutable_header()->set_kicad_token( m_token );
error.mutable_status()->set_status( ApiStatusCode::AS_TOKEN_MISMATCH );
error.mutable_status()->set_error_message(
"the provided kicad_token did not match this KiCad instance's token" );
m_server->Reply( error.SerializeAsString() );
log( "Response (ERROR): " + error.Utf8DebugString() );
}
API_RESULT result;
for( API_HANDLER* handler : m_handlers )
{
result = handler->Handle( request );
if( result.has_value() )
break;
else if( result.error().status() != ApiStatusCode::AS_UNHANDLED )
break;
}
// Note: at the point we call Reply(), we no longer own requestString.
if( result.has_value() )
{
result->mutable_header()->set_kicad_token( m_token );
m_server->Reply( result->SerializeAsString() );
log( "Response: " + result->Utf8DebugString() );
}
else
{
ApiResponse error;
error.mutable_status()->CopyFrom( result.error() );
error.mutable_header()->set_kicad_token( m_token );
if( result.error().status() == ApiStatusCode::AS_UNHANDLED )
{
std::string type = "<unparseable Any>";
google::protobuf::Any::ParseAnyTypeUrl( request.message().type_url(), &type );
std::string msg = fmt::format( "no handler available for request of type {}", type );
error.mutable_status()->set_error_message( msg );
}
m_server->Reply( error.SerializeAsString() );
log( "Response (ERROR): " + error.Utf8DebugString() );
}
}
void KICAD_API_SERVER::log( const std::string& aOutput )
{
if( !ADVANCED_CFG::GetCfg().m_EnableAPILogging )
return;
FILE* fp = wxFopen( m_logFilePath.GetFullPath(), wxT( "a" ) );
if( !fp )
return;
wxString out;
wxDateTime now = wxDateTime::Now();
fprintf( fp, "%s", TO_UTF8( out.Format( wxS( "%s: %s" ),
now.FormatISOCombined(), aOutput ) ) );
fclose( fp );
}

View File

@ -0,0 +1,109 @@
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
* Copyright (C) 2023 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <dialogs/panel_python_settings.h>
#include <widgets/ui_common.h>
#include <pgm_base.h>
#include <python_manager.h>
#include <settings/common_settings.h>
#include <settings/settings_manager.h>
PANEL_PYTHON_SETTINGS::PANEL_PYTHON_SETTINGS( wxWindow* aParent ) :
PANEL_PYTHON_SETTINGS_BASE( aParent )
{
wxFont helpFont = KIUI::GetInfoFont( this ).Italic();
m_stPythonStatus->SetFont( helpFont );
}
void PANEL_PYTHON_SETTINGS::ResetPanel()
{
}
bool PANEL_PYTHON_SETTINGS::TransferDataToWindow()
{
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
COMMON_SETTINGS* settings = mgr.GetCommonSettings();
m_pickerPythonInterpreter->SetFileName( settings->m_Python.interpreter_path );
validateInterpreter();
return true;
}
bool PANEL_PYTHON_SETTINGS::TransferDataFromWindow()
{
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
COMMON_SETTINGS* settings = mgr.GetCommonSettings();
if( m_interpreterValid )
settings->m_Python.interpreter_path = m_pickerPythonInterpreter->GetTextCtrlValue();
return true;
}
void PANEL_PYTHON_SETTINGS::OnPythonInterpreterChanged( wxFileDirPickerEvent& event )
{
validateInterpreter();
}
void PANEL_PYTHON_SETTINGS::OnBtnDetectAutomaticallyClicked( wxCommandEvent& aEvent )
{
}
void PANEL_PYTHON_SETTINGS::validateInterpreter()
{
m_interpreterValid = false;
wxFileName pythonExe( m_pickerPythonInterpreter->GetTextCtrlValue() );
if( !pythonExe.FileExists() )
{
m_stPythonStatus->SetLabel( _( "No valid Python interpreter chosen; external Python "
"plugins will not be available" ) );
return;
}
PYTHON_MANAGER manager( pythonExe.GetFullPath() );
manager.Execute( wxS( "--version" ),
[&]( int aRetCode, const wxString& aStdOut )
{
wxString msg;
if( aRetCode == 0 && aStdOut.Contains( wxS( "Python 3" ) ) )
{
msg = wxString::Format( _( "Found %s" ), aStdOut );
m_interpreterValid = true;
}
else
{
msg = _( "Not a valid Python 3 interpreter" );
}
m_stPythonStatus->SetLabel( msg );
} );
}

View File

@ -0,0 +1,70 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#include "panel_python_settings_base.h"
///////////////////////////////////////////////////////////////////////////
PANEL_PYTHON_SETTINGS_BASE::PANEL_PYTHON_SETTINGS_BASE( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : RESETTABLE_PANEL( parent, id, pos, size, style, name )
{
wxBoxSizer* bPanelSizer;
bPanelSizer = new wxBoxSizer( wxHORIZONTAL );
wxBoxSizer* bSizer8;
bSizer8 = new wxBoxSizer( wxVERTICAL );
wxStaticBoxSizer* sbSizer1;
sbSizer1 = new wxStaticBoxSizer( new wxStaticBox( this, wxID_ANY, _("Python Interpreter") ), wxVERTICAL );
wxBoxSizer* bSizer4;
bSizer4 = new wxBoxSizer( wxHORIZONTAL );
m_staticText2 = new wxStaticText( sbSizer1->GetStaticBox(), wxID_ANY, _("Path to Python interpreter:"), wxDefaultPosition, wxDefaultSize, 0 );
m_staticText2->Wrap( -1 );
bSizer4->Add( m_staticText2, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_pickerPythonInterpreter = new wxFilePickerCtrl( sbSizer1->GetStaticBox(), wxID_ANY, wxEmptyString, _("Select the path to a Python interpreter"), _("*.*"), wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE|wxFLP_USE_TEXTCTRL );
bSizer4->Add( m_pickerPythonInterpreter, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
m_btnDetectAutomatically = new wxButton( sbSizer1->GetStaticBox(), wxID_ANY, _("Detect Automatically"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer4->Add( m_btnDetectAutomatically, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
sbSizer1->Add( bSizer4, 0, wxEXPAND, 5 );
m_stPythonStatus = new wxStaticText( sbSizer1->GetStaticBox(), wxID_ANY, _("No Python interpreter chosen; external Python plugins will not be available"), wxDefaultPosition, wxDefaultSize, 0 );
m_stPythonStatus->Wrap( -1 );
m_stPythonStatus->SetToolTip( _("Python interpreter status") );
sbSizer1->Add( m_stPythonStatus, 0, wxALL, 5 );
bSizer8->Add( sbSizer1, 0, wxALL|wxEXPAND, 5 );
bSizer8->Add( 0, 0, 1, wxEXPAND, 5 );
bPanelSizer->Add( bSizer8, 1, wxEXPAND, 5 );
this->SetSizer( bPanelSizer );
this->Layout();
bPanelSizer->Fit( this );
// Connect Events
m_pickerPythonInterpreter->Connect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( PANEL_PYTHON_SETTINGS_BASE::OnPythonInterpreterChanged ), NULL, this );
m_btnDetectAutomatically->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PYTHON_SETTINGS_BASE::OnBtnDetectAutomaticallyClicked ), NULL, this );
}
PANEL_PYTHON_SETTINGS_BASE::~PANEL_PYTHON_SETTINGS_BASE()
{
// Disconnect Events
m_pickerPythonInterpreter->Disconnect( wxEVT_COMMAND_FILEPICKER_CHANGED, wxFileDirPickerEventHandler( PANEL_PYTHON_SETTINGS_BASE::OnPythonInterpreterChanged ), NULL, this );
m_btnDetectAutomatically->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_PYTHON_SETTINGS_BASE::OnBtnDetectAutomaticallyClicked ), NULL, this );
}

View File

@ -0,0 +1,371 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<wxFormBuilder_Project>
<FileVersion major="1" minor="16" />
<object class="Project" expanded="1">
<property name="class_decoration"></property>
<property name="code_generation">C++</property>
<property name="disconnect_events">1</property>
<property name="disconnect_mode">source_name</property>
<property name="disconnect_php_events">0</property>
<property name="disconnect_python_events">0</property>
<property name="embedded_files_path">res</property>
<property name="encoding">UTF-8</property>
<property name="event_generation">connect</property>
<property name="file">panel_python_settings_base</property>
<property name="first_id">1000</property>
<property name="help_provider">none</property>
<property name="image_path_wrapper_function_name"></property>
<property name="indent_with_spaces"></property>
<property name="internationalize">1</property>
<property name="name">PanelPythonSettings</property>
<property name="namespace"></property>
<property name="path">.</property>
<property name="precompiled_header"></property>
<property name="relative_path">1</property>
<property name="skip_lua_events">1</property>
<property name="skip_php_events">1</property>
<property name="skip_python_events">1</property>
<property name="ui_table">UI</property>
<property name="use_array_enum">0</property>
<property name="use_enum">1</property>
<property name="use_microsoft_bom">0</property>
<object class="Panel" expanded="1">
<property name="aui_managed">0</property>
<property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
<property name="bg"></property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="enabled">1</property>
<property name="event_handler">impl_virtual</property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property>
<property name="name">PANEL_PYTHON_SETTINGS_BASE</property>
<property name="pos"></property>
<property name="size">-1,-1</property>
<property name="subclass">RESETTABLE_PANEL; widgets/resettable_panel.h; Not forward_declare</property>
<property name="tooltip"></property>
<property name="two_step_creation">0</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style">wxTAB_TRAVERSAL</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bPanelSizer</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer8</property>
<property name="orient">wxVERTICAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL|wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxStaticBoxSizer" expanded="1">
<property name="id">wxID_ANY</property>
<property name="label">Python Interpreter</property>
<property name="minimum_size"></property>
<property name="name">sbSizer1</property>
<property name="orient">wxVERTICAL</property>
<property name="parent">1</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">0</property>
<object class="wxBoxSizer" expanded="1">
<property name="minimum_size"></property>
<property name="name">bSizer4</property>
<property name="orient">wxHORIZONTAL</property>
<property name="permission">none</property>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Path to Python interpreter:</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_staticText2</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">1</property>
<object class="wxFilePickerCtrl" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="message">Select the path to a Python interpreter</property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_pickerPythonInterpreter</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style">wxFLP_DEFAULT_STYLE|wxFLP_USE_TEXTCTRL</property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="value"></property>
<property name="wildcard">*.*</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnFileChanged">OnPythonInterpreterChanged</event>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
<property name="proportion">0</property>
<object class="wxButton" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="auth_needed">0</property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="bitmap"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="current"></property>
<property name="default">0</property>
<property name="default_pane">0</property>
<property name="disabled"></property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="focus"></property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">Detect Automatically</property>
<property name="margins"></property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_btnDetectAutomatically</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">protected</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="position"></property>
<property name="pressed"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip"></property>
<property name="validator_data_type"></property>
<property name="validator_style">wxFILTER_NONE</property>
<property name="validator_type">wxDefaultValidator</property>
<property name="validator_variable"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnButtonClick">OnBtnDetectAutomaticallyClicked</event>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxALL</property>
<property name="proportion">0</property>
<object class="wxStaticText" expanded="1">
<property name="BottomDockable">1</property>
<property name="LeftDockable">1</property>
<property name="RightDockable">1</property>
<property name="TopDockable">1</property>
<property name="aui_layer"></property>
<property name="aui_name"></property>
<property name="aui_position"></property>
<property name="aui_row"></property>
<property name="best_size"></property>
<property name="bg"></property>
<property name="caption"></property>
<property name="caption_visible">1</property>
<property name="center_pane">0</property>
<property name="close_button">1</property>
<property name="context_help"></property>
<property name="context_menu">1</property>
<property name="default_pane">0</property>
<property name="dock">Dock</property>
<property name="dock_fixed">0</property>
<property name="docking">Left</property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="floatable">1</property>
<property name="font"></property>
<property name="gripper">0</property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">No Python interpreter chosen; external Python plugins will not be available</property>
<property name="markup">0</property>
<property name="max_size"></property>
<property name="maximize_button">0</property>
<property name="maximum_size"></property>
<property name="min_size"></property>
<property name="minimize_button">0</property>
<property name="minimum_size"></property>
<property name="moveable">1</property>
<property name="name">m_stPythonStatus</property>
<property name="pane_border">1</property>
<property name="pane_position"></property>
<property name="pane_size"></property>
<property name="permission">public</property>
<property name="pin_button">1</property>
<property name="pos"></property>
<property name="resize">Resizable</property>
<property name="show">1</property>
<property name="size"></property>
<property name="style"></property>
<property name="subclass">; ; forward_declare</property>
<property name="toolbar_pane">0</property>
<property name="tooltip">Python interpreter status</property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<property name="wrap">-1</property>
</object>
</object>
</object>
</object>
<object class="sizeritem" expanded="1">
<property name="border">5</property>
<property name="flag">wxEXPAND</property>
<property name="proportion">1</property>
<object class="spacer" expanded="1">
<property name="height">0</property>
<property name="permission">protected</property>
<property name="width">0</property>
</object>
</object>
</object>
</object>
</object>
</object>
</object>
</wxFormBuilder_Project>

View File

@ -0,0 +1,56 @@
///////////////////////////////////////////////////////////////////////////
// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b3)
// http://www.wxformbuilder.org/
//
// PLEASE DO *NOT* EDIT THIS FILE!
///////////////////////////////////////////////////////////////////////////
#pragma once
#include <wx/artprov.h>
#include <wx/xrc/xmlres.h>
#include <wx/intl.h>
#include "widgets/resettable_panel.h"
#include <wx/string.h>
#include <wx/stattext.h>
#include <wx/gdicmn.h>
#include <wx/font.h>
#include <wx/colour.h>
#include <wx/settings.h>
#include <wx/filepicker.h>
#include <wx/button.h>
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/icon.h>
#include <wx/sizer.h>
#include <wx/statbox.h>
#include <wx/panel.h>
///////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
/// Class PANEL_PYTHON_SETTINGS_BASE
///////////////////////////////////////////////////////////////////////////////
class PANEL_PYTHON_SETTINGS_BASE : public RESETTABLE_PANEL
{
private:
protected:
wxStaticText* m_staticText2;
wxFilePickerCtrl* m_pickerPythonInterpreter;
wxButton* m_btnDetectAutomatically;
// Virtual event handlers, override them in your derived class
virtual void OnPythonInterpreterChanged( wxFileDirPickerEvent& event ) { event.Skip(); }
virtual void OnBtnDetectAutomaticallyClicked( wxCommandEvent& event ) { event.Skip(); }
public:
wxStaticText* m_stPythonStatus;
PANEL_PYTHON_SETTINGS_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
~PANEL_PYTHON_SETTINGS_BASE();
};

View File

@ -32,6 +32,7 @@
#include <dialogs/panel_common_settings.h>
#include <dialogs/panel_mouse_settings.h>
#include <dialogs/panel_data_collection.h>
#include <dialogs/panel_python_settings.h>
#include <eda_dde.h>
#include <file_history.h>
#include <id.h>
@ -1235,6 +1236,10 @@ void EDA_BASE_FRAME::ShowPreferences( wxString aStartPage, wxString aStartParent
{
}
#ifdef KICAD_IPC_API
book->AddPage( new PANEL_PYTHON_SETTINGS( book ), _( "Python Scripting" ) );
#endif
// Update all of the action hotkeys. The process of loading the actions through
// the KiFACE will only get us the default hotkeys
ReadHotKeyConfigIntoActions( wxEmptyString, hotkeysPanel->ActionsList() );

View File

@ -260,6 +260,12 @@ wxString KIID::AsString() const
}
std::string KIID::AsStdString() const
{
return boost::uuids::to_string( m_uuid );
}
wxString KIID::AsLegacyTimestampString() const
{
return wxString::Format( "%8.8lX", (unsigned long) AsLegacyTimestamp() );

View File

@ -399,6 +399,17 @@ wxString PATHS::GetInstanceCheckerPath()
}
wxString PATHS::GetLogsPath()
{
wxFileName tmp;
getUserDocumentPath( tmp );
tmp.AppendDir( wxT( "logs" ) );
return tmp.GetPath();
}
bool PATHS::EnsurePathExists( const wxString& aPath )
{
wxFileName path( aPath );

View File

@ -74,6 +74,10 @@
#include <build_version.h>
#endif
#ifdef KICAD_IPC_API
#include <api/api_server.h>
#endif
/**
* Current list of languages supported by KiCad.
*

View File

@ -53,7 +53,8 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
m_System(),
m_DoNotShowAgain(),
m_NetclassPanel(),
m_PackageManager()
m_PackageManager(),
m_Python()
{
/*
* Automatic dark mode detection works fine on Mac.
@ -397,7 +398,8 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
m_params.emplace_back( new PARAM<bool>( "git.useDefaultAuthor",
&m_Git.useDefaultAuthor, true ) );
m_params.emplace_back( new PARAM<wxString>( "python.interpreter_path",
&m_Python.interpreter_path, wxS( "" ) ) );
registerMigration( 0, 1, std::bind( &COMMON_SETTINGS::migrateSchema0to1, this ) );
registerMigration( 1, 2, std::bind( &COMMON_SETTINGS::migrateSchema1to2, this ) );

View File

@ -58,6 +58,10 @@
#include <sentry.h>
#endif
#ifdef KICAD_IPC_API
#include <api/api_server.h>
#endif
// Only a single KIWAY is supported in this single_top top level component,
// which is dedicated to loading only a single DSO.
KIWAY Kiway( KFCTL_STANDALONE );
@ -78,6 +82,10 @@ static struct PGM_SINGLE_TOP : public PGM_BASE
Kiway.OnKiwayEnd();
#ifdef KICAD_IPC_API
m_api_server.reset();
#endif
if( m_settings_manager && m_settings_manager->IsOK() )
{
SaveCommonSettings();
@ -350,6 +358,11 @@ bool PGM_SINGLE_TOP::OnPgmInit()
GetSettingsManager().RegisterSettings( new KICAD_SETTINGS );
#ifdef KICAD_IPC_API
// Create the API server thread once the app event loop exists
m_api_server = std::make_unique<KICAD_API_SERVER>();
#endif
// Use KIWAY to create a top window, which registers its existence also.
// "TOP_FRAME" is a macro that is passed on compiler command line from CMake,
// and is one of the types in FRAME_T.
@ -424,5 +437,9 @@ bool PGM_SINGLE_TOP::OnPgmInit()
frame->OpenProjectFiles( fileArgs );
}
#ifdef KICAD_IPC_API
m_api_server->SetReadyToReply();
#endif
return true;
}

Some files were not shown because too many files have changed in this diff Show More