/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2023 Jon Evans * 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 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace kiapi::common::commands; using namespace kiapi::common::types; using google::protobuf::Empty; API_HANDLER_COMMON::API_HANDLER_COMMON() : API_HANDLER() { registerHandler( &API_HANDLER_COMMON::handleGetVersion ); registerHandler( &API_HANDLER_COMMON::handleGetNetClasses ); registerHandler( &API_HANDLER_COMMON::handleSetNetClasses ); registerHandler( &API_HANDLER_COMMON::handlePing ); registerHandler( &API_HANDLER_COMMON::handleGetTextExtents ); registerHandler( &API_HANDLER_COMMON::handleGetTextAsShapes ); registerHandler( &API_HANDLER_COMMON::handleExpandTextVariables ); registerHandler( &API_HANDLER_COMMON::handleGetPluginSettingsPath ); registerHandler( &API_HANDLER_COMMON::handleGetTextVariables ); registerHandler( &API_HANDLER_COMMON::handleSetTextVariables ); } HANDLER_RESULT API_HANDLER_COMMON::handleGetVersion( const HANDLER_CONTEXT& ) { GetVersionResponse reply; reply.mutable_version()->set_full_version( GetBuildVersion().ToStdString() ); std::tuple 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 API_HANDLER_COMMON::handleGetNetClasses( const HANDLER_CONTEXT& aCtx ) { NetClassesResponse reply; std::shared_ptr& 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 API_HANDLER_COMMON::handleSetNetClasses( const HANDLER_CONTEXT& aCtx ) { std::shared_ptr& 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( name, false ) } ); netClasses[name]->Deserialize( any ); } } netSettings->SetNetclasses( netClasses ); return Empty(); } HANDLER_RESULT API_HANDLER_COMMON::handlePing( const HANDLER_CONTEXT& aCtx ) { return Empty(); } HANDLER_RESULT API_HANDLER_COMMON::handleGetTextExtents( const HANDLER_CONTEXT& 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 API_HANDLER_COMMON::handleGetTextAsShapes( const HANDLER_CONTEXT& 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 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 API_HANDLER_COMMON::handleExpandTextVariables( const HANDLER_CONTEXT& 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 API_HANDLER_COMMON::handleGetPluginSettingsPath( const HANDLER_CONTEXT& 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 API_HANDLER_COMMON::handleGetTextVariables( const HANDLER_CONTEXT& 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& 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 API_HANDLER_COMMON::handleSetTextVariables( const HANDLER_CONTEXT& 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& 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(); }