mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
Recommendation is to avoid using the year nomenclature as this information is already encoded in the git repo. Avoids needing to repeatly update. Also updates AUTHORS.txt from current repo with contributor names
382 lines
13 KiB
C++
382 lines
13 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
|
|
* Copyright The 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 <eda_shape.h>
|
|
#include <eda_text.h>
|
|
#include <geometry/shape_compound.h>
|
|
#include <google/protobuf/empty.pb.h>
|
|
#include <paths.h>
|
|
#include <pgm_base.h>
|
|
#include <api/api_plugin.h>
|
|
#include <api/api_utils.h>
|
|
#include <project/net_settings.h>
|
|
#include <project/project_file.h>
|
|
#include <settings/settings_manager.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<commands::GetVersion, GetVersionResponse>( &API_HANDLER_COMMON::handleGetVersion );
|
|
registerHandler<GetNetClasses, NetClassesResponse>( &API_HANDLER_COMMON::handleGetNetClasses );
|
|
registerHandler<SetNetClasses, Empty>( &API_HANDLER_COMMON::handleSetNetClasses );
|
|
registerHandler<Ping, Empty>( &API_HANDLER_COMMON::handlePing );
|
|
registerHandler<GetTextExtents, types::Box2>( &API_HANDLER_COMMON::handleGetTextExtents );
|
|
registerHandler<GetTextAsShapes, GetTextAsShapesResponse>(
|
|
&API_HANDLER_COMMON::handleGetTextAsShapes );
|
|
registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
|
|
&API_HANDLER_COMMON::handleExpandTextVariables );
|
|
registerHandler<GetPluginSettingsPath, StringResponse>(
|
|
&API_HANDLER_COMMON::handleGetPluginSettingsPath );
|
|
registerHandler<GetTextVariables, project::TextVariables>(
|
|
&API_HANDLER_COMMON::handleGetTextVariables );
|
|
registerHandler<SetTextVariables, Empty>(
|
|
&API_HANDLER_COMMON::handleSetTextVariables );
|
|
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<GetVersionResponse> API_HANDLER_COMMON::handleGetVersion(
|
|
const HANDLER_CONTEXT<GetVersion>& )
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<NetClassesResponse> API_HANDLER_COMMON::handleGetNetClasses(
|
|
const HANDLER_CONTEXT<GetNetClasses>& aCtx )
|
|
{
|
|
NetClassesResponse reply;
|
|
|
|
std::shared_ptr<NET_SETTINGS>& netSettings =
|
|
Pgm().GetSettingsManager().Prj().GetProjectFile().m_NetSettings;
|
|
|
|
google::protobuf::Any any;
|
|
|
|
netSettings->GetDefaultNetclass()->Serialize( any );
|
|
any.UnpackTo( reply.add_net_classes() );
|
|
|
|
for( const auto& netClass : netSettings->GetNetclasses() | std::views::values )
|
|
{
|
|
netClass->Serialize( any );
|
|
any.UnpackTo( reply.add_net_classes() );
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_COMMON::handleSetNetClasses(
|
|
const HANDLER_CONTEXT<SetNetClasses>& aCtx )
|
|
{
|
|
std::shared_ptr<NET_SETTINGS>& netSettings =
|
|
Pgm().GetSettingsManager().Prj().GetProjectFile().m_NetSettings;
|
|
|
|
if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE )
|
|
netSettings->ClearNetclasses();
|
|
|
|
auto netClasses = netSettings->GetNetclasses();
|
|
google::protobuf::Any any;
|
|
|
|
for( const auto& ncProto : aCtx.Request.net_classes() )
|
|
{
|
|
any.PackFrom( ncProto );
|
|
wxString name = wxString::FromUTF8( ncProto.name() );
|
|
|
|
if( name == wxT( "Default" ) )
|
|
{
|
|
netSettings->GetDefaultNetclass()->Deserialize( any );
|
|
}
|
|
else
|
|
{
|
|
if( !netClasses.contains( name ) )
|
|
netClasses.insert( { name, std::make_shared<NETCLASS>( name, false ) } );
|
|
|
|
netClasses[name]->Deserialize( any );
|
|
}
|
|
}
|
|
|
|
netSettings->SetNetclasses( netClasses );
|
|
|
|
return Empty();
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_COMMON::handlePing( const HANDLER_CONTEXT<Ping>& aCtx )
|
|
{
|
|
return Empty();
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<types::Box2> API_HANDLER_COMMON::handleGetTextExtents(
|
|
const HANDLER_CONTEXT<GetTextExtents>& aCtx )
|
|
{
|
|
EDA_TEXT text( pcbIUScale );
|
|
google::protobuf::Any any;
|
|
any.PackFrom( aCtx.Request.text() );
|
|
|
|
if( !text.Deserialize( any ) )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
e.set_error_message( "Could not decode text in GetTextExtents message" );
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
types::Box2 response;
|
|
|
|
BOX2I bbox = text.GetTextBox();
|
|
EDA_ANGLE angle = text.GetTextAngle();
|
|
|
|
if( !angle.IsZero() )
|
|
bbox = bbox.GetBoundingBoxRotated( text.GetTextPos(), text.GetTextAngle() );
|
|
|
|
response.mutable_position()->set_x_nm( bbox.GetPosition().x );
|
|
response.mutable_position()->set_y_nm( bbox.GetPosition().y );
|
|
response.mutable_size()->set_x_nm( bbox.GetSize().x );
|
|
response.mutable_size()->set_y_nm( bbox.GetSize().y );
|
|
|
|
return response;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<GetTextAsShapesResponse> API_HANDLER_COMMON::handleGetTextAsShapes(
|
|
const HANDLER_CONTEXT<GetTextAsShapes>& aCtx )
|
|
{
|
|
GetTextAsShapesResponse reply;
|
|
|
|
for( const TextOrTextBox& textMsg : aCtx.Request.text() )
|
|
{
|
|
Text dummyText;
|
|
const Text* textPtr = &textMsg.text();
|
|
|
|
if( textMsg.has_textbox() )
|
|
{
|
|
dummyText.set_text( textMsg.textbox().text() );
|
|
dummyText.mutable_attributes()->CopyFrom( textMsg.textbox().attributes() );
|
|
textPtr = &dummyText;
|
|
}
|
|
|
|
EDA_TEXT text( pcbIUScale );
|
|
google::protobuf::Any any;
|
|
any.PackFrom( *textPtr );
|
|
|
|
if( !text.Deserialize( any ) )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
e.set_error_message( "Could not decode text in GetTextAsShapes message" );
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
std::shared_ptr<SHAPE_COMPOUND> shapes = text.GetEffectiveTextShape( false );
|
|
|
|
TextWithShapes* entry = reply.add_text_with_shapes();
|
|
entry->mutable_text()->CopyFrom( textMsg );
|
|
|
|
for( SHAPE* subshape : shapes->Shapes() )
|
|
{
|
|
EDA_SHAPE proxy( *subshape );
|
|
proxy.Serialize( any );
|
|
GraphicShape* shapeMsg = entry->mutable_shapes()->add_shapes();
|
|
any.UnpackTo( shapeMsg );
|
|
}
|
|
|
|
if( textMsg.has_textbox() )
|
|
{
|
|
GraphicShape* border = entry->mutable_shapes()->add_shapes();
|
|
int width = textMsg.textbox().attributes().stroke_width().value_nm();
|
|
border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
|
|
VECTOR2I tl = UnpackVector2( textMsg.textbox().top_left() );
|
|
VECTOR2I br = UnpackVector2( textMsg.textbox().bottom_right() );
|
|
|
|
// top
|
|
PackVector2( *border->mutable_segment()->mutable_start(), tl );
|
|
PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( br.x, tl.y ) );
|
|
|
|
// right
|
|
border = entry->mutable_shapes()->add_shapes();
|
|
border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
|
|
PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( br.x, tl.y ) );
|
|
PackVector2( *border->mutable_segment()->mutable_end(), br );
|
|
|
|
// bottom
|
|
border = entry->mutable_shapes()->add_shapes();
|
|
border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
|
|
PackVector2( *border->mutable_segment()->mutable_start(), br );
|
|
PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( tl.x, br.y ) );
|
|
|
|
// left
|
|
border = entry->mutable_shapes()->add_shapes();
|
|
border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
|
|
PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( tl.x, br.y ) );
|
|
PackVector2( *border->mutable_segment()->mutable_end(), tl );
|
|
}
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<ExpandTextVariablesResponse> API_HANDLER_COMMON::handleExpandTextVariables(
|
|
const HANDLER_CONTEXT<ExpandTextVariables>& aCtx )
|
|
{
|
|
if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_UNHANDLED );
|
|
// No error message, this is a flag that the server should try a different handler
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
ExpandTextVariablesResponse reply;
|
|
PROJECT& project = Pgm().GetSettingsManager().Prj();
|
|
|
|
for( const std::string& textMsg : aCtx.Request.text() )
|
|
{
|
|
wxString result = ExpandTextVars( wxString::FromUTF8( textMsg ), &project );
|
|
reply.add_text( result.ToUTF8() );
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<StringResponse> API_HANDLER_COMMON::handleGetPluginSettingsPath(
|
|
const HANDLER_CONTEXT<GetPluginSettingsPath>& aCtx )
|
|
{
|
|
wxString identifier = wxString::FromUTF8( aCtx.Request.identifier() );
|
|
|
|
if( identifier.IsEmpty() )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
e.set_error_message( "plugin identifier is missing" );
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
if( API_PLUGIN::IsValidIdentifier( identifier ) )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_BAD_REQUEST );
|
|
e.set_error_message( "plugin identifier is invalid" );
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
wxFileName path( PATHS::GetUserSettingsPath(), wxEmptyString );
|
|
path.AppendDir( "plugins" );
|
|
|
|
// Create the base plugins path if needed, but leave the specific plugin to create its own path
|
|
PATHS::EnsurePathExists( path.GetPath() );
|
|
|
|
path.AppendDir( identifier );
|
|
|
|
StringResponse reply;
|
|
reply.set_response( path.GetPath() );
|
|
return reply;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<project::TextVariables> API_HANDLER_COMMON::handleGetTextVariables(
|
|
const HANDLER_CONTEXT<GetTextVariables>& aCtx )
|
|
{
|
|
if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_UNHANDLED );
|
|
// No error message, this is a flag that the server should try a different handler
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
const PROJECT& project = Pgm().GetSettingsManager().Prj();
|
|
|
|
if( project.IsNullProject() )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_NOT_READY );
|
|
e.set_error_message( "no valid project is loaded, cannot get text variables" );
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
const std::map<wxString, wxString>& vars = project.GetTextVars();
|
|
|
|
project::TextVariables reply;
|
|
auto map = reply.mutable_variables();
|
|
|
|
for( const auto& [key, value] : vars )
|
|
( *map )[ std::string( key.ToUTF8() ) ] = value.ToUTF8();
|
|
|
|
return reply;
|
|
}
|
|
|
|
|
|
HANDLER_RESULT<Empty> API_HANDLER_COMMON::handleSetTextVariables(
|
|
const HANDLER_CONTEXT<SetTextVariables>& aCtx )
|
|
{
|
|
if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_UNHANDLED );
|
|
// No error message, this is a flag that the server should try a different handler
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
PROJECT& project = Pgm().GetSettingsManager().Prj();
|
|
|
|
if( project.IsNullProject() )
|
|
{
|
|
ApiResponseStatus e;
|
|
e.set_status( ApiStatusCode::AS_NOT_READY );
|
|
e.set_error_message( "no valid project is loaded, cannot set text variables" );
|
|
return tl::unexpected( e );
|
|
}
|
|
|
|
const project::TextVariables& newVars = aCtx.Request.variables();
|
|
std::map<wxString, wxString>& vars = project.GetTextVars();
|
|
|
|
if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE )
|
|
vars.clear();
|
|
|
|
for( const auto& [key, value] : newVars.variables() )
|
|
vars[wxString::FromUTF8( key )] = wxString::FromUTF8( value );
|
|
|
|
Pgm().GetSettingsManager().SaveProject();
|
|
|
|
return Empty();
|
|
}
|