You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
13 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2023 Jon Evans <jon@craftyjon.com>
  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 along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. #include <tuple>
  21. #include <api/api_handler_common.h>
  22. #include <build_version.h>
  23. #include <eda_shape.h>
  24. #include <eda_text.h>
  25. #include <gestfich.h>
  26. #include <geometry/shape_compound.h>
  27. #include <google/protobuf/empty.pb.h>
  28. #include <paths.h>
  29. #include <pgm_base.h>
  30. #include <api/api_plugin.h>
  31. #include <api/api_utils.h>
  32. #include <project/net_settings.h>
  33. #include <project/project_file.h>
  34. #include <settings/settings_manager.h>
  35. #include <wx/string.h>
  36. using namespace kiapi::common::commands;
  37. using namespace kiapi::common::types;
  38. using google::protobuf::Empty;
  39. API_HANDLER_COMMON::API_HANDLER_COMMON() :
  40. API_HANDLER()
  41. {
  42. registerHandler<commands::GetVersion, GetVersionResponse>( &API_HANDLER_COMMON::handleGetVersion );
  43. registerHandler<GetKiCadBinaryPath, PathResponse>(
  44. &API_HANDLER_COMMON::handleGetKiCadBinaryPath );
  45. registerHandler<GetNetClasses, NetClassesResponse>( &API_HANDLER_COMMON::handleGetNetClasses );
  46. registerHandler<SetNetClasses, Empty>( &API_HANDLER_COMMON::handleSetNetClasses );
  47. registerHandler<Ping, Empty>( &API_HANDLER_COMMON::handlePing );
  48. registerHandler<GetTextExtents, types::Box2>( &API_HANDLER_COMMON::handleGetTextExtents );
  49. registerHandler<GetTextAsShapes, GetTextAsShapesResponse>(
  50. &API_HANDLER_COMMON::handleGetTextAsShapes );
  51. registerHandler<ExpandTextVariables, ExpandTextVariablesResponse>(
  52. &API_HANDLER_COMMON::handleExpandTextVariables );
  53. registerHandler<GetPluginSettingsPath, StringResponse>(
  54. &API_HANDLER_COMMON::handleGetPluginSettingsPath );
  55. registerHandler<GetTextVariables, project::TextVariables>(
  56. &API_HANDLER_COMMON::handleGetTextVariables );
  57. registerHandler<SetTextVariables, Empty>(
  58. &API_HANDLER_COMMON::handleSetTextVariables );
  59. }
  60. HANDLER_RESULT<GetVersionResponse> API_HANDLER_COMMON::handleGetVersion(
  61. const HANDLER_CONTEXT<commands::GetVersion>& )
  62. {
  63. GetVersionResponse reply;
  64. reply.mutable_version()->set_full_version( GetBuildVersion().ToStdString() );
  65. std::tuple<int, int, int> version = GetMajorMinorPatchTuple();
  66. reply.mutable_version()->set_major( std::get<0>( version ) );
  67. reply.mutable_version()->set_minor( std::get<1>( version ) );
  68. reply.mutable_version()->set_patch( std::get<2>( version ) );
  69. return reply;
  70. }
  71. HANDLER_RESULT<PathResponse> API_HANDLER_COMMON::handleGetKiCadBinaryPath(
  72. const HANDLER_CONTEXT<GetKiCadBinaryPath>& aCtx )
  73. {
  74. wxFileName fn( wxEmptyString, wxString::FromUTF8( aCtx.Request.binary_name() ) );
  75. #ifdef _WIN32
  76. fn.SetExt( wxT( "exe" ) );
  77. #endif
  78. wxString path = FindKicadFile( fn.GetFullName() );
  79. PathResponse reply;
  80. reply.set_path( path.ToUTF8() );
  81. return reply;
  82. }
  83. HANDLER_RESULT<NetClassesResponse> API_HANDLER_COMMON::handleGetNetClasses(
  84. const HANDLER_CONTEXT<GetNetClasses>& aCtx )
  85. {
  86. NetClassesResponse reply;
  87. std::shared_ptr<NET_SETTINGS>& netSettings =
  88. Pgm().GetSettingsManager().Prj().GetProjectFile().m_NetSettings;
  89. google::protobuf::Any any;
  90. netSettings->GetDefaultNetclass()->Serialize( any );
  91. any.UnpackTo( reply.add_net_classes() );
  92. for( const auto& netClass : netSettings->GetNetclasses() | std::views::values )
  93. {
  94. netClass->Serialize( any );
  95. any.UnpackTo( reply.add_net_classes() );
  96. }
  97. return reply;
  98. }
  99. HANDLER_RESULT<Empty> API_HANDLER_COMMON::handleSetNetClasses(
  100. const HANDLER_CONTEXT<SetNetClasses>& aCtx )
  101. {
  102. std::shared_ptr<NET_SETTINGS>& netSettings =
  103. Pgm().GetSettingsManager().Prj().GetProjectFile().m_NetSettings;
  104. if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE )
  105. netSettings->ClearNetclasses();
  106. auto netClasses = netSettings->GetNetclasses();
  107. google::protobuf::Any any;
  108. for( const auto& ncProto : aCtx.Request.net_classes() )
  109. {
  110. any.PackFrom( ncProto );
  111. wxString name = wxString::FromUTF8( ncProto.name() );
  112. if( name == wxT( "Default" ) )
  113. {
  114. netSettings->GetDefaultNetclass()->Deserialize( any );
  115. }
  116. else
  117. {
  118. if( !netClasses.contains( name ) )
  119. netClasses.insert( { name, std::make_shared<NETCLASS>( name, false ) } );
  120. netClasses[name]->Deserialize( any );
  121. }
  122. }
  123. netSettings->SetNetclasses( netClasses );
  124. return Empty();
  125. }
  126. HANDLER_RESULT<Empty> API_HANDLER_COMMON::handlePing( const HANDLER_CONTEXT<Ping>& aCtx )
  127. {
  128. return Empty();
  129. }
  130. HANDLER_RESULT<types::Box2> API_HANDLER_COMMON::handleGetTextExtents(
  131. const HANDLER_CONTEXT<GetTextExtents>& aCtx )
  132. {
  133. EDA_TEXT text( pcbIUScale );
  134. google::protobuf::Any any;
  135. any.PackFrom( aCtx.Request.text() );
  136. if( !text.Deserialize( any ) )
  137. {
  138. ApiResponseStatus e;
  139. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  140. e.set_error_message( "Could not decode text in GetTextExtents message" );
  141. return tl::unexpected( e );
  142. }
  143. types::Box2 response;
  144. BOX2I bbox = text.GetTextBox();
  145. EDA_ANGLE angle = text.GetTextAngle();
  146. if( !angle.IsZero() )
  147. bbox = bbox.GetBoundingBoxRotated( text.GetTextPos(), text.GetTextAngle() );
  148. response.mutable_position()->set_x_nm( bbox.GetPosition().x );
  149. response.mutable_position()->set_y_nm( bbox.GetPosition().y );
  150. response.mutable_size()->set_x_nm( bbox.GetSize().x );
  151. response.mutable_size()->set_y_nm( bbox.GetSize().y );
  152. return response;
  153. }
  154. HANDLER_RESULT<GetTextAsShapesResponse> API_HANDLER_COMMON::handleGetTextAsShapes(
  155. const HANDLER_CONTEXT<GetTextAsShapes>& aCtx )
  156. {
  157. GetTextAsShapesResponse reply;
  158. for( const TextOrTextBox& textMsg : aCtx.Request.text() )
  159. {
  160. Text dummyText;
  161. const Text* textPtr = &textMsg.text();
  162. if( textMsg.has_textbox() )
  163. {
  164. dummyText.set_text( textMsg.textbox().text() );
  165. dummyText.mutable_attributes()->CopyFrom( textMsg.textbox().attributes() );
  166. textPtr = &dummyText;
  167. }
  168. EDA_TEXT text( pcbIUScale );
  169. google::protobuf::Any any;
  170. any.PackFrom( *textPtr );
  171. if( !text.Deserialize( any ) )
  172. {
  173. ApiResponseStatus e;
  174. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  175. e.set_error_message( "Could not decode text in GetTextAsShapes message" );
  176. return tl::unexpected( e );
  177. }
  178. std::shared_ptr<SHAPE_COMPOUND> shapes = text.GetEffectiveTextShape( false );
  179. TextWithShapes* entry = reply.add_text_with_shapes();
  180. entry->mutable_text()->CopyFrom( textMsg );
  181. for( SHAPE* subshape : shapes->Shapes() )
  182. {
  183. EDA_SHAPE proxy( *subshape );
  184. proxy.Serialize( any );
  185. GraphicShape* shapeMsg = entry->mutable_shapes()->add_shapes();
  186. any.UnpackTo( shapeMsg );
  187. }
  188. if( textMsg.has_textbox() )
  189. {
  190. GraphicShape* border = entry->mutable_shapes()->add_shapes();
  191. int width = textMsg.textbox().attributes().stroke_width().value_nm();
  192. border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
  193. VECTOR2I tl = UnpackVector2( textMsg.textbox().top_left() );
  194. VECTOR2I br = UnpackVector2( textMsg.textbox().bottom_right() );
  195. // top
  196. PackVector2( *border->mutable_segment()->mutable_start(), tl );
  197. PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( br.x, tl.y ) );
  198. // right
  199. border = entry->mutable_shapes()->add_shapes();
  200. border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
  201. PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( br.x, tl.y ) );
  202. PackVector2( *border->mutable_segment()->mutable_end(), br );
  203. // bottom
  204. border = entry->mutable_shapes()->add_shapes();
  205. border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
  206. PackVector2( *border->mutable_segment()->mutable_start(), br );
  207. PackVector2( *border->mutable_segment()->mutable_end(), VECTOR2I( tl.x, br.y ) );
  208. // left
  209. border = entry->mutable_shapes()->add_shapes();
  210. border->mutable_attributes()->mutable_stroke()->mutable_width()->set_value_nm( width );
  211. PackVector2( *border->mutable_segment()->mutable_start(), VECTOR2I( tl.x, br.y ) );
  212. PackVector2( *border->mutable_segment()->mutable_end(), tl );
  213. }
  214. }
  215. return reply;
  216. }
  217. HANDLER_RESULT<ExpandTextVariablesResponse> API_HANDLER_COMMON::handleExpandTextVariables(
  218. const HANDLER_CONTEXT<ExpandTextVariables>& aCtx )
  219. {
  220. if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
  221. {
  222. ApiResponseStatus e;
  223. e.set_status( ApiStatusCode::AS_UNHANDLED );
  224. // No error message, this is a flag that the server should try a different handler
  225. return tl::unexpected( e );
  226. }
  227. ExpandTextVariablesResponse reply;
  228. PROJECT& project = Pgm().GetSettingsManager().Prj();
  229. for( const std::string& textMsg : aCtx.Request.text() )
  230. {
  231. wxString result = ExpandTextVars( wxString::FromUTF8( textMsg ), &project );
  232. reply.add_text( result.ToUTF8() );
  233. }
  234. return reply;
  235. }
  236. HANDLER_RESULT<StringResponse> API_HANDLER_COMMON::handleGetPluginSettingsPath(
  237. const HANDLER_CONTEXT<GetPluginSettingsPath>& aCtx )
  238. {
  239. wxString identifier = wxString::FromUTF8( aCtx.Request.identifier() );
  240. if( identifier.IsEmpty() )
  241. {
  242. ApiResponseStatus e;
  243. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  244. e.set_error_message( "plugin identifier is missing" );
  245. return tl::unexpected( e );
  246. }
  247. if( API_PLUGIN::IsValidIdentifier( identifier ) )
  248. {
  249. ApiResponseStatus e;
  250. e.set_status( ApiStatusCode::AS_BAD_REQUEST );
  251. e.set_error_message( "plugin identifier is invalid" );
  252. return tl::unexpected( e );
  253. }
  254. wxFileName path( PATHS::GetUserSettingsPath(), wxEmptyString );
  255. path.AppendDir( "plugins" );
  256. // Create the base plugins path if needed, but leave the specific plugin to create its own path
  257. PATHS::EnsurePathExists( path.GetPath() );
  258. path.AppendDir( identifier );
  259. StringResponse reply;
  260. reply.set_response( path.GetPath() );
  261. return reply;
  262. }
  263. HANDLER_RESULT<project::TextVariables> API_HANDLER_COMMON::handleGetTextVariables(
  264. const HANDLER_CONTEXT<GetTextVariables>& aCtx )
  265. {
  266. if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
  267. {
  268. ApiResponseStatus e;
  269. e.set_status( ApiStatusCode::AS_UNHANDLED );
  270. // No error message, this is a flag that the server should try a different handler
  271. return tl::unexpected( e );
  272. }
  273. const PROJECT& project = Pgm().GetSettingsManager().Prj();
  274. if( project.IsNullProject() )
  275. {
  276. ApiResponseStatus e;
  277. e.set_status( ApiStatusCode::AS_NOT_READY );
  278. e.set_error_message( "no valid project is loaded, cannot get text variables" );
  279. return tl::unexpected( e );
  280. }
  281. const std::map<wxString, wxString>& vars = project.GetTextVars();
  282. project::TextVariables reply;
  283. auto map = reply.mutable_variables();
  284. for( const auto& [key, value] : vars )
  285. ( *map )[ std::string( key.ToUTF8() ) ] = value.ToUTF8();
  286. return reply;
  287. }
  288. HANDLER_RESULT<Empty> API_HANDLER_COMMON::handleSetTextVariables(
  289. const HANDLER_CONTEXT<SetTextVariables>& aCtx )
  290. {
  291. if( !aCtx.Request.has_document() || aCtx.Request.document().type() != DOCTYPE_PROJECT )
  292. {
  293. ApiResponseStatus e;
  294. e.set_status( ApiStatusCode::AS_UNHANDLED );
  295. // No error message, this is a flag that the server should try a different handler
  296. return tl::unexpected( e );
  297. }
  298. PROJECT& project = Pgm().GetSettingsManager().Prj();
  299. if( project.IsNullProject() )
  300. {
  301. ApiResponseStatus e;
  302. e.set_status( ApiStatusCode::AS_NOT_READY );
  303. e.set_error_message( "no valid project is loaded, cannot set text variables" );
  304. return tl::unexpected( e );
  305. }
  306. const project::TextVariables& newVars = aCtx.Request.variables();
  307. std::map<wxString, wxString>& vars = project.GetTextVars();
  308. if( aCtx.Request.merge_mode() == MapMergeMode::MMM_REPLACE )
  309. vars.clear();
  310. for( const auto& [key, value] : newVars.variables() )
  311. vars[wxString::FromUTF8( key )] = wxString::FromUTF8( value );
  312. Pgm().GetSettingsManager().SaveProject();
  313. return Empty();
  314. }