KiCad PCB EDA Suite
Loading...
Searching...
No Matches
api_handler_common.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 <ranges>
22#include <tuple>
23
25#include <build_version.h>
26#include <eda_shape.h>
27#include <eda_text.h>
28#include <gestfich.h>
30#include <google/protobuf/empty.pb.h>
31#include <paths.h>
32#include <pgm_base.h>
33#include <api/api_plugin.h>
34#include <api/api_utils.h>
38#include <wx/string.h>
39
40using namespace kiapi::common::commands;
41using namespace kiapi::common::types;
42using google::protobuf::Empty;
43
44
47{
74}
75
76
79{
80 GetVersionResponse reply;
81
82 reply.mutable_version()->set_full_version( GetBuildVersion().ToStdString() );
83
84 std::tuple<int, int, int> version = GetMajorMinorPatchTuple();
85 reply.mutable_version()->set_major( std::get<0>( version ) );
86 reply.mutable_version()->set_minor( std::get<1>( version ) );
87 reply.mutable_version()->set_patch( std::get<2>( version ) );
88
89 return reply;
90}
91
92
95{
96 wxFileName fn( wxEmptyString, wxString::FromUTF8( aCtx.Request.binary_name() ) );
97#ifdef _WIN32
98 fn.SetExt( wxT( "exe" ) );
99#endif
100
101 wxString path = FindKicadFile( fn.GetFullName() );
102 PathResponse reply;
103 reply.set_path( path.ToUTF8() );
104 return reply;
105}
106
107
110{
111 NetClassesResponse reply;
112
113 std::shared_ptr<NET_SETTINGS>& netSettings =
115
116 google::protobuf::Any any;
117
118 netSettings->GetDefaultNetclass()->Serialize( any );
119 any.UnpackTo( reply.add_net_classes() );
120
121 for( const auto& netClass : netSettings->GetNetclasses() | std::views::values )
122 {
123 netClass->Serialize( any );
124 any.UnpackTo( reply.add_net_classes() );
125 }
126
127 return reply;
128}
129
130
133{
134 std::shared_ptr<NET_SETTINGS>& netSettings =
136
137 if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE )
138 netSettings->ClearNetclasses();
139
140 auto netClasses = netSettings->GetNetclasses();
141 google::protobuf::Any any;
142
143 for( const auto& ncProto : aCtx.Request.net_classes() )
144 {
145 any.PackFrom( ncProto );
146 wxString name = wxString::FromUTF8( ncProto.name() );
147
148 if( name == wxT( "Default" ) )
149 {
150 netSettings->GetDefaultNetclass()->Deserialize( any );
151 }
152 else
153 {
154 if( !netClasses.contains( name ) )
155 netClasses.insert( { name, std::make_shared<NETCLASS>( name, false ) } );
156
157 netClasses[name]->Deserialize( any );
158 }
159 }
160
161 netSettings->SetNetclasses( netClasses );
162
163 return Empty();
164}
165
166
171
172
175{
177 google::protobuf::Any any;
178 any.PackFrom( aCtx.Request.text() );
179
180 if( !text.Deserialize( any ) )
181 {
182 ApiResponseStatus e;
183 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
184 e.set_error_message( "Could not decode text in GetTextExtents message" );
185 return tl::unexpected( e );
186 }
187
188 types::Box2 response;
189
190 BOX2I bbox = text.GetTextBox( nullptr );
191 EDA_ANGLE angle = text.GetTextAngle();
192
193 if( !angle.IsZero() )
194 bbox = bbox.GetBoundingBoxRotated( text.GetTextPos(), text.GetTextAngle() );
195
196 response.mutable_position()->set_x_nm( bbox.GetPosition().x );
197 response.mutable_position()->set_y_nm( bbox.GetPosition().y );
198 response.mutable_size()->set_x_nm( bbox.GetSize().x );
199 response.mutable_size()->set_y_nm( bbox.GetSize().y );
200
201 return response;
202}
203
204
207{
208 GetTextAsShapesResponse reply;
209
210 for( const TextOrTextBox& textMsg : aCtx.Request.text() )
211 {
212 Text dummyText;
213 const Text* textPtr = &textMsg.text();
214
215 if( textMsg.has_textbox() )
216 {
217 dummyText.set_text( textMsg.textbox().text() );
218 dummyText.mutable_attributes()->CopyFrom( textMsg.textbox().attributes() );
219 textPtr = &dummyText;
220 }
221
223 google::protobuf::Any any;
224 any.PackFrom( *textPtr );
225
226 if( !text.Deserialize( any ) )
227 {
228 ApiResponseStatus e;
229 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
230 e.set_error_message( "Could not decode text in GetTextAsShapes message" );
231 return tl::unexpected( e );
232 }
233
234 std::shared_ptr<SHAPE_COMPOUND> shapes = text.GetEffectiveTextShape( false );
235
236 TextWithShapes* entry = reply.add_text_with_shapes();
237 entry->mutable_text()->CopyFrom( textMsg );
238
239 for( SHAPE* subshape : shapes->Shapes() )
240 {
241 EDA_SHAPE proxy( *subshape );
242 proxy.Serialize( any );
243 GraphicShape* shapeMsg = entry->mutable_shapes()->add_shapes();
244 any.UnpackTo( shapeMsg );
245 }
246
247 if( textMsg.has_textbox() )
248 {
249 GraphicShape* border = entry->mutable_shapes()->add_shapes();
250 int width = textMsg.textbox().attributes().stroke_width().value_nm();
251 border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
252 VECTOR2I tl = UnpackVector2( textMsg.textbox().top_left() );
253 VECTOR2I br = UnpackVector2( textMsg.textbox().bottom_right() );
254
255 // top
256 PackVector2( *border->mutable_segment()->mutable_start(), tl );
257 PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( br.x, tl.y ) );
258
259 // right
260 border = entry->mutable_shapes()->add_shapes();
261 border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
262 PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( br.x, tl.y ) );
263 PackVector2( *border->mutable_segment()->mutable_end(), br );
264
265 // bottom
266 border = entry->mutable_shapes()->add_shapes();
267 border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
268 PackVector2( *border->mutable_segment()->mutable_start(), br );
269 PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( tl.x, br.y ) );
270
271 // left
272 border = entry->mutable_shapes()->add_shapes();
273 border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
274 PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( tl.x, br.y ) );
275 PackVector2( *border->mutable_segment()->mutable_end(), tl );
276 }
277 }
278
279 return reply;
280}
281
282
285{
286 if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
287 {
288 ApiResponseStatus e;
289 e.set_status( ApiStatusCode::AS_UNHANDLED );
290 // No error message, this is a flag that the server should try a different handler
291 return tl::unexpected( e );
292 }
293
294 ExpandTextVariablesResponse reply;
296
297 for( const std::string& textMsg : aCtx.Request.text() )
298 {
299 wxString result = ExpandTextVars( wxString::FromUTF8( textMsg ), &project, INTERNAL );
300
301 if( aCtx.Request.expand_env_vars() )
303
304 reply.add_text( result.ToUTF8() );
305 }
306
307 return reply;
308}
309
310
313{
314 wxString identifier = wxString::FromUTF8( aCtx.Request.identifier() );
315
316 if( identifier.IsEmpty() )
317 {
318 ApiResponseStatus e;
319 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
320 e.set_error_message( "plugin identifier is missing" );
321 return tl::unexpected( e );
322 }
323
324 if( !API_PLUGIN::IsValidIdentifier( identifier ) )
325 {
326 ApiResponseStatus e;
327 e.set_status( ApiStatusCode::AS_BAD_REQUEST );
328 e.set_error_message( "plugin identifier is invalid" );
329 return tl::unexpected( e );
330 }
331
332 wxFileName path( PATHS::GetUserSettingsPath(), wxEmptyString );
333 path.AppendDir( "plugins" );
334
335 // Create the base plugins path if needed, but leave the specific plugin to create its own path
336 PATHS::EnsurePathExists( path.GetPath() );
337
338 path.AppendDir( identifier );
339
340 StringResponse reply;
341 reply.set_response( path.GetPath() );
342 return reply;
343}
344
345
348{
349 if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
350 {
351 ApiResponseStatus e;
352 e.set_status( ApiStatusCode::AS_UNHANDLED );
353 // No error message, this is a flag that the server should try a different handler
354 return tl::unexpected( e );
355 }
356
358
359 if( project.IsNullProject() )
360 {
361 ApiResponseStatus e;
362 e.set_status( ApiStatusCode::AS_NOT_READY );
363 e.set_error_message( "no valid project is loaded, cannot get text variables" );
364 return tl::unexpected( e );
365 }
366
367 const std::map<wxString, wxString>& vars = project.GetTextVars();
368
369 project::TextVariables reply;
370 auto map = reply.mutable_variables();
371
372 for( const auto& [key, value] : vars )
373 ( *map )[ std::string( key.ToUTF8() ) ] = value.ToUTF8();
374
375 return reply;
376}
377
378
381{
382 if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
383 {
384 ApiResponseStatus e;
385 e.set_status( ApiStatusCode::AS_UNHANDLED );
386 // No error message, this is a flag that the server should try a different handler
387 return tl::unexpected( e );
388 }
389
391
392 if( project.IsNullProject() )
393 {
394 ApiResponseStatus e;
395 e.set_status( ApiStatusCode::AS_NOT_READY );
396 e.set_error_message( "no valid project is loaded, cannot set text variables" );
397 return tl::unexpected( e );
398 }
399
400 const project::TextVariables& newVars = aCtx.Request.variables();
401 std::map<wxString, wxString>& vars = project.GetTextVars();
402
403 if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE )
404 vars.clear();
405
406 for( const auto& [key, value] : newVars.variables() )
407 vars[wxString::FromUTF8( key )] = wxString::FromUTF8( value );
408
410
411 return Empty();
412}
413
414
417{
419 {
420 ApiResponseStatus e;
421 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
422 e.set_error_message( "OpenDocument is not available in this KiCad mode" );
423 return tl::unexpected( e );
424 }
425
426 return m_openDocumentHandler( aCtx.Request );
427}
428
429
432{
434 {
435 ApiResponseStatus e;
436 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
437 e.set_error_message( "CloseDocument is not available in this KiCad mode" );
438 return tl::unexpected( e );
439 }
440
441 return m_closeDocumentHandler( aCtx.Request );
442}
443
444
447{
449 {
450 ApiResponseStatus e;
451 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
452 e.set_error_message( "CreateDocument is not available in this KiCad mode" );
453 return tl::unexpected( e );
454 }
455
456 return m_createDocumentHandler( aCtx.Request );
457}
458
459
461{
463 {
464 ApiResponseStatus e;
465 e.set_status( ApiStatusCode::AS_UNIMPLEMENTED );
466 e.set_error_message( "CloseAllDocuments is not available in this KiCad mode" );
467 return tl::unexpected( e );
468 }
469
470 return m_closeAllDocumentsHandler( aCtx.Request );
471}
472
473
475{
476 GetPathsResponse reply;
477
478 auto addPath = [&]( types::PathType aType, const wxString& aPath )
479 {
480 PathEntry* entry = reply.add_paths();
481 entry->set_type( aType );
482 entry->set_path( aPath.ToUTF8() );
483 };
484
485 addPath( types::PATH_USER_PLUGINS, PATHS::GetUserPluginsPath() );
486 addPath( types::PATH_USER_TEMPLATES, PATHS::GetUserTemplatesPath() );
487 addPath( types::PATH_USER_SETTINGS, PATHS::GetUserSettingsPath() );
488 addPath( types::PATH_STOCK_SYMBOLS, PATHS::GetStockSymbolsPath() );
489 addPath( types::PATH_STOCK_FOOTPRINTS, PATHS::GetStockFootprintsPath() );
490 addPath( types::PATH_STOCK_DESIGN_BLOCKS, PATHS::GetStockDesignBlocksPath() );
491 addPath( types::PATH_STOCK_3DMODELS, PATHS::GetStock3dmodelsPath() );
492 addPath( types::PATH_STOCK_TEMPLATES, PATHS::GetStockTemplatesPath() );
493
494 return reply;
495}
const char * name
tl::expected< T, ApiResponseStatus > HANDLER_RESULT
Definition api_handler.h:45
constexpr EDA_IU_SCALE pcbIUScale
Definition base_units.h:121
BOX2< VECTOR2I > BOX2I
Definition box2.h:927
const std::tuple< int, int, int > & GetMajorMinorPatchTuple()
Get the build version numbers as a tuple.
wxString GetBuildVersion()
Get the full KiCad version string.
HANDLER_RESULT< Empty > handlePing(const HANDLER_CONTEXT< commands::Ping > &aCtx)
HANDLER_RESULT< types::Box2 > handleGetTextExtents(const HANDLER_CONTEXT< commands::GetTextExtents > &aCtx)
CLOSE_ALL_DOCUMENTS_HANDLER m_closeAllDocumentsHandler
HANDLER_RESULT< commands::GetTextAsShapesResponse > handleGetTextAsShapes(const HANDLER_CONTEXT< commands::GetTextAsShapes > &aCtx)
HANDLER_RESULT< commands::GetVersionResponse > handleGetVersion(const HANDLER_CONTEXT< commands::GetVersion > &aCtx)
CREATE_DOCUMENT_HANDLER m_createDocumentHandler
HANDLER_RESULT< Empty > handleCloseAllDocuments(const HANDLER_CONTEXT< commands::CloseAllDocuments > &aCtx)
HANDLER_RESULT< commands::OpenDocumentResponse > handleCreateDocument(const HANDLER_CONTEXT< commands::CreateDocument > &aCtx)
HANDLER_RESULT< commands::PathResponse > handleGetKiCadBinaryPath(const HANDLER_CONTEXT< commands::GetKiCadBinaryPath > &aCtx)
HANDLER_RESULT< commands::NetClassesResponse > handleGetNetClasses(const HANDLER_CONTEXT< commands::GetNetClasses > &aCtx)
CLOSE_DOCUMENT_HANDLER m_closeDocumentHandler
OPEN_DOCUMENT_HANDLER m_openDocumentHandler
HANDLER_RESULT< Empty > handleCloseDocument(const HANDLER_CONTEXT< commands::CloseDocument > &aCtx)
HANDLER_RESULT< Empty > handleSetNetClasses(const HANDLER_CONTEXT< commands::SetNetClasses > &aCtx)
HANDLER_RESULT< commands::StringResponse > handleGetPluginSettingsPath(const HANDLER_CONTEXT< commands::GetPluginSettingsPath > &aCtx)
HANDLER_RESULT< commands::OpenDocumentResponse > handleOpenDocument(const HANDLER_CONTEXT< commands::OpenDocument > &aCtx)
HANDLER_RESULT< commands::ExpandTextVariablesResponse > handleExpandTextVariables(const HANDLER_CONTEXT< commands::ExpandTextVariables > &aCtx)
HANDLER_RESULT< Empty > handleSetTextVariables(const HANDLER_CONTEXT< commands::SetTextVariables > &aCtx)
HANDLER_RESULT< commands::GetPathsResponse > handleGetPaths(const HANDLER_CONTEXT< commands::GetPaths > &aCtx)
HANDLER_RESULT< project::TextVariables > handleGetTextVariables(const HANDLER_CONTEXT< commands::GetTextVariables > &aCtx)
void registerHandler(HANDLER_RESULT< ResponseType >(HandlerType::*aHandler)(const HANDLER_CONTEXT< RequestType > &))
Registers an API command handler for the given message types.
Definition api_handler.h:93
static bool IsValidIdentifier(const wxString &aIdentifier)
constexpr const Vec & GetPosition() const
Definition box2.h:208
const BOX2< Vec > GetBoundingBoxRotated(const VECTOR2I &aRotCenter, const EDA_ANGLE &aAngle) const
Useful to calculate bounding box of rotated items, when rotation is not cardinal.
Definition box2.h:715
constexpr const SizeVec & GetSize() const
Definition box2.h:203
bool IsZero() const
Definition eda_angle.h:136
void Serialize(google::protobuf::Any &aContainer) const override
Serializes this object to the given Any message.
A mix-in class (via multiple inheritance) that handles texts such as labels, parts,...
Definition eda_text.h:94
static wxString GetUserPluginsPath()
Gets the user path for plugins.
Definition paths.cpp:49
static wxString GetStockSymbolsPath()
Gets the stock (install) symbols path.
Definition paths.cpp:300
static wxString GetUserTemplatesPath()
Gets the user path for custom templates.
Definition paths.cpp:71
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
Definition paths.cpp:508
static wxString GetStock3dmodelsPath()
Gets the stock (install) 3dmodels path.
Definition paths.cpp:333
static wxString GetStockTemplatesPath()
Gets the stock (install) templates path.
Definition paths.cpp:355
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
Definition paths.cpp:624
static wxString GetStockDesignBlocksPath()
Gets the stock (install) footprints path.
Definition paths.cpp:322
static wxString GetStockFootprintsPath()
Gets the stock (install) footprints path.
Definition paths.cpp:311
virtual SETTINGS_MANAGER & GetSettingsManager() const
Definition pgm_base.h:123
std::shared_ptr< NET_SETTINGS > m_NetSettings
Net settings for this project (owned here)
Container for project specific data.
Definition project.h:63
virtual PROJECT_FILE & GetProjectFile() const
Definition project.h:201
bool SaveProject(const wxString &aFullPath=wxEmptyString, PROJECT *aProject=nullptr)
Save a loaded project.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
An abstract shape on 2D plane.
Definition shape.h:124
A type-safe container of any type.
Definition ki_any.h:92
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
@ INTERNAL
Definition common.h:92
static std::string ToStdString(const wxString &aStr)
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
Definition gestfich.cpp:62
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)
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
RequestMessageType Request
Definition api_handler.h:52
std::string path
wxString result
Test unit parsing edge cases and error handling.
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:683