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.

1126 lines
33 KiB

5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
  5. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU 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, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include <gal/opengl/kiglew.h> // Must be included first
  25. #include <gal/opengl/gl_utils.h>
  26. #include <wx/tokenzr.h>
  27. #include "../common_ogl/ogl_utils.h"
  28. #include "eda_3d_canvas.h"
  29. #include <eda_3d_viewer_frame.h>
  30. #include <3d_rendering/raytracing/render_3d_raytrace_gl.h>
  31. #include <3d_rendering/opengl/render_3d_opengl.h>
  32. #include <3d_viewer_id.h>
  33. #include <advanced_config.h>
  34. #include <build_version.h>
  35. #include <board.h>
  36. #include <reporter.h>
  37. #include <gal/opengl/gl_context_mgr.h>
  38. #include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
  39. #include <bitmaps.h>
  40. #include <macros.h>
  41. #include <pgm_base.h>
  42. #include <settings/settings_manager.h>
  43. #include <tool/tool_dispatcher.h>
  44. #include <widgets/wx_busy_indicator.h>
  45. /**
  46. * Flag to enable 3D canvas debug tracing.
  47. *
  48. * Use "KI_TRACE_EDA_3D_CANVAS" to enable.
  49. *
  50. * @ingroup trace_env_vars
  51. */
  52. const wxChar* EDA_3D_CANVAS::m_logTrace = wxT( "KI_TRACE_EDA_3D_CANVAS" );
  53. // A custom event, used to call DoRePaint during an idle time
  54. wxDEFINE_EVENT( wxEVT_REFRESH_CUSTOM_COMMAND, wxEvent );
  55. BEGIN_EVENT_TABLE( EDA_3D_CANVAS, HIDPI_GL_3D_CANVAS )
  56. EVT_PAINT( EDA_3D_CANVAS::OnPaint )
  57. // mouse events
  58. EVT_LEFT_DOWN( EDA_3D_CANVAS::OnLeftDown )
  59. EVT_LEFT_UP( EDA_3D_CANVAS::OnLeftUp )
  60. EVT_MIDDLE_UP( EDA_3D_CANVAS::OnMiddleUp )
  61. EVT_MIDDLE_DOWN( EDA_3D_CANVAS::OnMiddleDown)
  62. EVT_MOUSEWHEEL( EDA_3D_CANVAS::OnMouseWheel )
  63. EVT_MOTION( EDA_3D_CANVAS::OnMouseMove )
  64. EVT_MAGNIFY( EDA_3D_CANVAS::OnMagnify )
  65. // touch gesture events
  66. EVT_GESTURE_ZOOM( wxID_ANY, EDA_3D_CANVAS::OnZoomGesture )
  67. EVT_GESTURE_PAN( wxID_ANY, EDA_3D_CANVAS::OnPanGesture )
  68. EVT_GESTURE_ROTATE( wxID_ANY, EDA_3D_CANVAS::OnRotateGesture )
  69. // other events
  70. EVT_ERASE_BACKGROUND( EDA_3D_CANVAS::OnEraseBackground )
  71. EVT_CUSTOM(wxEVT_REFRESH_CUSTOM_COMMAND, ID_CUSTOM_EVENT_1, EDA_3D_CANVAS::OnRefreshRequest )
  72. EVT_CLOSE( EDA_3D_CANVAS::OnCloseWindow )
  73. EVT_SIZE( EDA_3D_CANVAS::OnResize )
  74. END_EVENT_TABLE()
  75. EDA_3D_CANVAS::EDA_3D_CANVAS( wxWindow* aParent, const wxGLAttributes& aGLAttribs,
  76. BOARD_ADAPTER& aBoardAdapter, CAMERA& aCamera,
  77. S3D_CACHE* a3DCachePointer ) :
  78. HIDPI_GL_3D_CANVAS( EDA_DRAW_PANEL_GAL::GetVcSettings(), aCamera, aParent, aGLAttribs,
  79. EDA_3D_CANVAS_ID, wxDefaultPosition,
  80. wxDefaultSize, wxFULL_REPAINT_ON_RESIZE ),
  81. m_eventDispatcher( nullptr ),
  82. m_parentStatusBar( nullptr ),
  83. m_parentInfoBar( nullptr ),
  84. m_glRC( nullptr ),
  85. m_is_opengl_initialized( false ),
  86. m_is_opengl_version_supported( true ),
  87. m_editing_timeout_timer( this, wxID_HIGHEST + 1 ),
  88. m_redraw_trigger_timer( this, wxID_HIGHEST + 2 ),
  89. m_render_pivot( false ),
  90. m_camera_moving_speed( 1.0f ),
  91. m_strtime_camera_movement( 0 ),
  92. m_animation_enabled( true ),
  93. m_moving_speed_multiplier( 3 ),
  94. m_boardAdapter( aBoardAdapter ),
  95. m_3d_render( nullptr ),
  96. m_opengl_supports_raytracing( true ),
  97. m_render_raytracing_was_requested( false ),
  98. m_accelerator3DShapes( nullptr ),
  99. m_currentRollOverItem( nullptr )
  100. {
  101. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::EDA_3D_CANVAS" ) );
  102. m_editing_timeout_timer.SetOwner( this );
  103. Connect( m_editing_timeout_timer.GetId(), wxEVT_TIMER,
  104. wxTimerEventHandler( EDA_3D_CANVAS::OnTimerTimeout_Editing ), nullptr, this );
  105. m_redraw_trigger_timer.SetOwner( this );
  106. Connect( m_redraw_trigger_timer.GetId(), wxEVT_TIMER,
  107. wxTimerEventHandler( EDA_3D_CANVAS::OnTimerTimeout_Redraw ), nullptr, this );
  108. m_is_currently_painting.clear();
  109. m_3d_render_raytracing = new RENDER_3D_RAYTRACE_GL( this, m_boardAdapter, m_camera );
  110. m_3d_render_opengl = new RENDER_3D_OPENGL( this, m_boardAdapter, m_camera );
  111. wxASSERT( m_3d_render_raytracing != nullptr );
  112. wxASSERT( m_3d_render_opengl != nullptr );
  113. auto busy_indicator_factory =
  114. []()
  115. {
  116. return std::make_unique<WX_BUSY_INDICATOR>();
  117. };
  118. m_3d_render_raytracing->SetBusyIndicatorFactory( busy_indicator_factory );
  119. m_3d_render_opengl->SetBusyIndicatorFactory( busy_indicator_factory );
  120. // We always start with the opengl engine (raytracing is avoided due to very
  121. // long calculation time)
  122. m_3d_render = m_3d_render_opengl;
  123. m_boardAdapter.ReloadColorSettings();
  124. wxASSERT( a3DCachePointer != nullptr );
  125. m_boardAdapter.Set3dCacheManager( a3DCachePointer );
  126. #ifdef __WXMSW__
  127. EnableTouchEvents( wxTOUCH_ZOOM_GESTURE | wxTOUCH_PAN_GESTURES | wxTOUCH_ROTATE_GESTURE );
  128. #endif
  129. const wxEventType events[] =
  130. {
  131. // Binding both EVT_CHAR and EVT_CHAR_HOOK ensures that all key events,
  132. // especially special key like arrow keys, are handled by the GAL event dispatcher,
  133. // and not sent to GUI without filtering, because they have a default action (scroll)
  134. // that must not be called.
  135. wxEVT_LEFT_UP, wxEVT_LEFT_DOWN, wxEVT_LEFT_DCLICK,
  136. wxEVT_RIGHT_UP, wxEVT_RIGHT_DOWN, wxEVT_RIGHT_DCLICK,
  137. wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DOWN, wxEVT_MIDDLE_DCLICK,
  138. wxEVT_MOTION, wxEVT_MOUSEWHEEL, wxEVT_CHAR, wxEVT_CHAR_HOOK,
  139. wxEVT_MAGNIFY,
  140. wxEVT_MENU_OPEN, wxEVT_MENU_CLOSE, wxEVT_MENU_HIGHLIGHT
  141. };
  142. for( wxEventType eventType : events )
  143. Connect( eventType, wxEventHandler( EDA_3D_CANVAS::OnEvent ), nullptr, m_eventDispatcher );
  144. }
  145. EDA_3D_CANVAS::~EDA_3D_CANVAS()
  146. {
  147. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::~EDA_3D_CANVAS" ) );
  148. delete m_accelerator3DShapes;
  149. m_accelerator3DShapes = nullptr;
  150. releaseOpenGL();
  151. }
  152. void EDA_3D_CANVAS::releaseOpenGL()
  153. {
  154. if( m_glRC )
  155. {
  156. GL_CONTEXT_MANAGER::Get().LockCtx( m_glRC, this );
  157. delete m_3d_render_raytracing;
  158. m_3d_render_raytracing = nullptr;
  159. delete m_3d_render_opengl;
  160. m_3d_render_opengl = nullptr;
  161. // This is just a copy of a pointer, can safely be set to NULL.
  162. m_3d_render = nullptr;
  163. GL_CONTEXT_MANAGER::Get().UnlockCtx( m_glRC );
  164. GL_CONTEXT_MANAGER::Get().DestroyCtx( m_glRC );
  165. m_glRC = nullptr;
  166. }
  167. }
  168. void EDA_3D_CANVAS::OnCloseWindow( wxCloseEvent& event )
  169. {
  170. releaseOpenGL();
  171. event.Skip();
  172. }
  173. void EDA_3D_CANVAS::OnResize( wxSizeEvent& event )
  174. {
  175. Request_refresh();
  176. }
  177. bool EDA_3D_CANVAS::initializeOpenGL()
  178. {
  179. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::initializeOpenGL" ) );
  180. const GLenum err = glewInit();
  181. if( GLEW_OK != err )
  182. {
  183. const wxString msgError = (const char*) glewGetErrorString( err );
  184. wxLogMessage( msgError );
  185. return false;
  186. }
  187. else
  188. {
  189. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::initializeOpenGL Using GLEW version %s" ),
  190. From_UTF8( (char*) glewGetString( GLEW_VERSION ) ) );
  191. }
  192. SetOpenGLInfo( (const char*) glGetString( GL_VENDOR ), (const char*) glGetString( GL_RENDERER ),
  193. (const char*) glGetString( GL_VERSION ) );
  194. wxString version = From_UTF8( (char *) glGetString( GL_VERSION ) );
  195. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL version string %s." ),
  196. __WXFUNCTION__, version );
  197. // Extract OpenGL version from string. This method is used because prior to OpenGL 2,
  198. // getting the OpenGL major and minor version as integers didn't exist.
  199. wxString tmp;
  200. wxStringTokenizer tokenizer( version );
  201. if( tokenizer.HasMoreTokens() )
  202. {
  203. long major = 0;
  204. long minor = 0;
  205. tmp = tokenizer.GetNextToken();
  206. tokenizer.SetString( tmp, wxString( wxT( "." ) ) );
  207. if( tokenizer.HasMoreTokens() )
  208. tokenizer.GetNextToken().ToLong( &major );
  209. if( tokenizer.HasMoreTokens() )
  210. tokenizer.GetNextToken().ToLong( &minor );
  211. if( major < 2 || ( ( major == 2 ) && ( minor < 1 ) ) )
  212. {
  213. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL ray tracing not supported." ),
  214. __WXFUNCTION__ );
  215. if( GetParent() )
  216. {
  217. wxCommandEvent evt( wxEVT_MENU, ID_DISABLE_RAY_TRACING );
  218. GetParent()->ProcessWindowEvent( evt );
  219. }
  220. m_opengl_supports_raytracing = false;
  221. }
  222. if( ( major == 1 ) && ( minor < 5 ) )
  223. {
  224. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL not supported." ),
  225. __WXFUNCTION__ );
  226. m_is_opengl_version_supported = false;
  227. }
  228. }
  229. GL_UTILS::SetSwapInterval( -1 );
  230. m_is_opengl_initialized = true;
  231. return true;
  232. }
  233. void EDA_3D_CANVAS::GetScreenshot( wxImage& aDstImage )
  234. {
  235. OglGetScreenshot( aDstImage );
  236. }
  237. void EDA_3D_CANVAS::ReloadRequest( BOARD* aBoard , S3D_CACHE* aCachePointer )
  238. {
  239. if( aCachePointer != nullptr )
  240. m_boardAdapter.Set3dCacheManager( aCachePointer );
  241. if( aBoard != nullptr )
  242. m_boardAdapter.SetBoard( aBoard );
  243. m_boardAdapter.ReloadColorSettings();
  244. if( m_3d_render )
  245. m_3d_render->ReloadRequest();
  246. }
  247. void EDA_3D_CANVAS::RenderRaytracingRequest()
  248. {
  249. m_3d_render = m_3d_render_raytracing;
  250. if( m_3d_render )
  251. m_3d_render->ReloadRequest();
  252. m_render_raytracing_was_requested = true;
  253. Request_refresh();
  254. }
  255. void EDA_3D_CANVAS::DisplayStatus()
  256. {
  257. if( m_parentStatusBar )
  258. {
  259. wxString msg;
  260. msg.Printf( wxT( "dx %3.2f" ), m_camera.GetCameraPos().x );
  261. m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::X_POS ) );
  262. msg.Printf( wxT( "dy %3.2f" ), m_camera.GetCameraPos().y );
  263. m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::Y_POS ) );
  264. msg.Printf( wxT( "zoom %3.2f" ), 1 / m_camera.GetZoom() );
  265. m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::ZOOM_LEVEL ) );
  266. }
  267. }
  268. void EDA_3D_CANVAS::OnPaint( wxPaintEvent& aEvent )
  269. {
  270. // Please have a look at: https://lists.launchpad.net/kicad-developers/msg25149.html
  271. DoRePaint();
  272. }
  273. void EDA_3D_CANVAS::DoRePaint()
  274. {
  275. if( m_is_currently_painting.test_and_set() )
  276. return;
  277. // SwapBuffer requires the window to be shown before calling
  278. if( !IsShownOnScreen() )
  279. {
  280. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::DoRePaint !IsShown" ) );
  281. m_is_currently_painting.clear();
  282. return;
  283. }
  284. // Because the board to draw is handled by the parent viewer frame,
  285. // ensure this parent is still alive. When it is closed before the viewer
  286. // frame, a paint event can be generated after the parent is closed,
  287. // therefore with invalid board.
  288. // This is dependent of the platform.
  289. // Especially on OSX, but also on Windows, it frequently happens
  290. if( !GetParent()->GetParent()->IsShownOnScreen() )
  291. return; // The parent board editor frame is no more alive
  292. wxString err_messages;
  293. INFOBAR_REPORTER warningReporter( m_parentInfoBar );
  294. STATUSBAR_REPORTER activityReporter( m_parentStatusBar, EDA_3D_VIEWER_STATUSBAR::ACTIVITY );
  295. int64_t start_time = GetRunningMicroSecs();
  296. // "Makes the OpenGL state that is represented by the OpenGL rendering
  297. // context context current, i.e. it will be used by all subsequent OpenGL calls.
  298. // This function may only be called when the window is shown on screen"
  299. // Explicitly create a new rendering context instance for this canvas.
  300. if( m_glRC == nullptr )
  301. m_glRC = GL_CONTEXT_MANAGER::Get().CreateCtx( this );
  302. // CreateCtx could and does fail per sentry crash events, lets be graceful
  303. if( m_glRC == nullptr )
  304. {
  305. warningReporter.Report( _( "OpenGL context creation error" ), RPT_SEVERITY_ERROR );
  306. warningReporter.Finalize();
  307. m_is_currently_painting.clear();
  308. return;
  309. }
  310. GL_CONTEXT_MANAGER::Get().LockCtx( m_glRC, this );
  311. // Set the OpenGL viewport according to the client size of this canvas.
  312. // This is done here rather than in a wxSizeEvent handler because our
  313. // OpenGL rendering context (and thus viewport setting) is used with
  314. // multiple canvases: If we updated the viewport in the wxSizeEvent
  315. // handler, changing the size of one canvas causes a viewport setting that
  316. // is wrong when next another canvas is repainted.
  317. wxSize clientSize = GetNativePixelSize();
  318. const bool windows_size_changed = m_camera.SetCurWindowSize( clientSize );
  319. // Initialize openGL if need
  320. if( !m_is_opengl_initialized )
  321. {
  322. if( !initializeOpenGL() )
  323. {
  324. GL_CONTEXT_MANAGER::Get().UnlockCtx( m_glRC );
  325. m_is_currently_painting.clear();
  326. return;
  327. }
  328. if( !m_is_opengl_version_supported )
  329. {
  330. warningReporter.Report( _( "Your OpenGL version is not supported. Minimum required "
  331. "is 1.5." ), RPT_SEVERITY_ERROR );
  332. warningReporter.Finalize();
  333. }
  334. }
  335. if( !m_is_opengl_version_supported )
  336. {
  337. glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
  338. glClear( GL_COLOR_BUFFER_BIT );
  339. SwapBuffers();
  340. GL_CONTEXT_MANAGER::Get().UnlockCtx( m_glRC );
  341. m_is_currently_painting.clear();
  342. return;
  343. }
  344. // Don't attend to ray trace if OpenGL doesn't support it.
  345. if( !m_opengl_supports_raytracing )
  346. {
  347. m_3d_render = m_3d_render_opengl;
  348. m_render_raytracing_was_requested = false;
  349. m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
  350. }
  351. // Check if a raytacing was requested and need to switch to raytracing mode
  352. if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
  353. {
  354. const bool was_camera_changed = m_camera.ParametersChanged();
  355. // It reverts back to OpenGL mode if it was requested a raytracing
  356. // render of the current scene. AND the mouse / camera is moving
  357. if( ( m_mouse_is_moving || m_camera_is_moving || was_camera_changed
  358. || windows_size_changed )
  359. && m_render_raytracing_was_requested )
  360. {
  361. m_render_raytracing_was_requested = false;
  362. m_3d_render = m_3d_render_opengl;
  363. }
  364. }
  365. float curtime_delta_s = 0.0f;
  366. if( m_camera_is_moving )
  367. {
  368. const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
  369. curtime_delta_s = (curtime_delta / 1e6) * m_camera_moving_speed;
  370. m_camera.Interpolate( curtime_delta_s );
  371. if( curtime_delta_s > 1.0f )
  372. {
  373. m_render_pivot = false;
  374. m_camera_is_moving = false;
  375. m_mouse_was_moved = true;
  376. restart_editingTimeOut_Timer();
  377. DisplayStatus();
  378. }
  379. else
  380. {
  381. Request_refresh();
  382. }
  383. }
  384. // It will return true if the render request a new redraw
  385. bool requested_redraw = false;
  386. if( m_3d_render )
  387. {
  388. try
  389. {
  390. m_3d_render->SetCurWindowSize( clientSize );
  391. bool reloadRaytracingForCalculations = false;
  392. if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL
  393. && m_3d_render_opengl->IsReloadRequestPending() )
  394. {
  395. reloadRaytracingForCalculations = true;
  396. }
  397. requested_redraw = m_3d_render->Redraw( m_mouse_was_moved || m_camera_is_moving,
  398. &activityReporter, &warningReporter );
  399. // Raytracer renderer is responsible for some features also used by the OpenGL
  400. // renderer.
  401. // FIXME: Presumably because raytracing renderer reload is called only after current
  402. // renderer redraw, the old zoom value stays for a single frame. This is ugly, but only
  403. // cosmetic, so I'm not fixing that for now: I don't know how to do this without
  404. // reloading twice (maybe it's not too bad of an idea?) or doing a complicated
  405. // refactor.
  406. if( reloadRaytracingForCalculations )
  407. m_3d_render_raytracing->Reload( nullptr, nullptr, true );
  408. }
  409. catch( std::runtime_error& )
  410. {
  411. m_is_opengl_version_supported = false;
  412. m_opengl_supports_raytracing = false;
  413. m_is_opengl_initialized = false;
  414. GL_CONTEXT_MANAGER::Get().UnlockCtx( m_glRC );
  415. m_is_currently_painting.clear();
  416. return;
  417. }
  418. }
  419. if( m_render_pivot )
  420. {
  421. const float scale = glm::min( m_camera.GetZoom(), 1.0f );
  422. render_pivot( curtime_delta_s, scale );
  423. }
  424. // This will only be enabled by the 3d mouse plugin, so we can leave
  425. // it as a simple if statement
  426. if( m_render3dmousePivot )
  427. {
  428. const float scale = glm::min( m_camera.GetZoom(), 1.0f );
  429. render3dmousePivot( scale );
  430. }
  431. // "Swaps the double-buffer of this window, making the back-buffer the
  432. // front-buffer and vice versa, so that the output of the previous OpenGL
  433. // commands is displayed on the window."
  434. SwapBuffers();
  435. GL_CONTEXT_MANAGER::Get().UnlockCtx( m_glRC );
  436. if( m_mouse_was_moved || m_camera_is_moving )
  437. {
  438. // Calculation time in milliseconds
  439. const double calculation_time = (double)( GetRunningMicroSecs() - start_time ) / 1e3;
  440. activityReporter.Report( wxString::Format( _( "Last render time %.0f ms" ),
  441. calculation_time ) );
  442. }
  443. // This will reset the flag of camera parameters changed
  444. m_camera.ParametersChanged();
  445. warningReporter.Finalize();
  446. if( !err_messages.IsEmpty() )
  447. wxLogMessage( err_messages );
  448. if( ( !m_camera_is_moving ) && requested_redraw )
  449. {
  450. m_mouse_was_moved = false;
  451. Request_refresh( false );
  452. }
  453. m_is_currently_painting.clear();
  454. }
  455. void EDA_3D_CANVAS::SetEventDispatcher( TOOL_DISPATCHER* aEventDispatcher )
  456. {
  457. m_eventDispatcher = aEventDispatcher;
  458. }
  459. void EDA_3D_CANVAS::OnEvent( wxEvent& aEvent )
  460. {
  461. if( !m_eventDispatcher )
  462. aEvent.Skip();
  463. else
  464. m_eventDispatcher->DispatchWxEvent( aEvent );
  465. Refresh();
  466. }
  467. void EDA_3D_CANVAS::OnEraseBackground( wxEraseEvent& event )
  468. {
  469. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::OnEraseBackground" ) );
  470. // Do nothing, to avoid flashing.
  471. }
  472. void EDA_3D_CANVAS::OnMouseWheel( wxMouseEvent& event )
  473. {
  474. wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::OnMouseWheel" ) );
  475. OnMouseWheelCamera( event, m_boardAdapter.m_MousewheelPanning );
  476. if( m_mouse_was_moved )
  477. {
  478. DisplayStatus();
  479. Request_refresh();
  480. restart_editingTimeOut_Timer();
  481. }
  482. }
  483. void EDA_3D_CANVAS::OnMagnify( wxMouseEvent& event )
  484. {
  485. SetFocus();
  486. if( m_camera_is_moving )
  487. return;
  488. //m_is_moving_mouse = true;
  489. restart_editingTimeOut_Timer();
  490. float magnification = ( event.GetMagnification() + 1.0f );
  491. m_camera.Zoom( magnification );
  492. DisplayStatus();
  493. Request_refresh();
  494. }
  495. void EDA_3D_CANVAS::OnZoomGesture( wxZoomGestureEvent& aEvent )
  496. {
  497. SetFocus();
  498. if( aEvent.IsGestureStart() )
  499. {
  500. m_gestureLastZoomFactor = 1.0;
  501. m_camera.SetCurMousePosition( aEvent.GetPosition() );
  502. }
  503. if( m_camera_is_moving )
  504. return;
  505. restart_editingTimeOut_Timer();
  506. m_camera.Pan( aEvent.GetPosition() );
  507. m_camera.SetCurMousePosition( aEvent.GetPosition() );
  508. m_camera.Zoom( aEvent.GetZoomFactor() / m_gestureLastZoomFactor );
  509. m_gestureLastZoomFactor = aEvent.GetZoomFactor();
  510. DisplayStatus();
  511. Request_refresh();
  512. }
  513. void EDA_3D_CANVAS::OnPanGesture( wxPanGestureEvent& aEvent )
  514. {
  515. SetFocus();
  516. if( aEvent.IsGestureStart() )
  517. m_camera.SetCurMousePosition( aEvent.GetPosition() );
  518. if( m_camera_is_moving )
  519. return;
  520. m_camera.Pan( aEvent.GetPosition() );
  521. m_camera.SetCurMousePosition( aEvent.GetPosition() );
  522. DisplayStatus();
  523. Request_refresh();
  524. }
  525. void EDA_3D_CANVAS::OnRotateGesture( wxRotateGestureEvent& aEvent )
  526. {
  527. SetFocus();
  528. if( aEvent.IsGestureStart() )
  529. {
  530. m_gestureLastAngle = 0;
  531. m_camera.SetCurMousePosition( aEvent.GetPosition() );
  532. // We don't want to process the first angle
  533. return;
  534. }
  535. if( m_camera_is_moving )
  536. return;
  537. m_camera.RotateScreen( m_gestureLastAngle - aEvent.GetRotationAngle() );
  538. m_gestureLastAngle = aEvent.GetRotationAngle();
  539. DisplayStatus();
  540. Request_refresh();
  541. }
  542. void EDA_3D_CANVAS::OnMouseMove( wxMouseEvent& event )
  543. {
  544. if( m_3d_render && m_3d_render->IsReloadRequestPending() )
  545. return; // Prevents using invalid m_3d_render_raytracing data
  546. if( m_camera_is_moving )
  547. return;
  548. OnMouseMoveCamera( event );
  549. if( m_mouse_was_moved )
  550. {
  551. DisplayStatus();
  552. Request_refresh();
  553. // *Do not* reactivate the timer here during the mouse move command:
  554. // OnMiddleUp() will do it at the end of mouse drag/move command
  555. }
  556. if( !event.Dragging() && m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
  557. {
  558. STATUSBAR_REPORTER reporter( m_parentStatusBar, EDA_3D_VIEWER_STATUSBAR::HOVERED_ITEM );
  559. RAY mouseRay = getRayAtCurrentMousePosition();
  560. BOARD_ITEM* rollOverItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
  561. auto printNetInfo =
  562. []( BOARD_CONNECTED_ITEM* aItem )
  563. {
  564. return wxString::Format( _( "Net %s\tNet class %s" ),
  565. aItem->GetNet()->GetNetname(),
  566. aItem->GetNet()->GetNetClass()->GetName() );
  567. };
  568. if( rollOverItem )
  569. {
  570. wxString msg;
  571. if( rollOverItem != m_currentRollOverItem )
  572. {
  573. m_3d_render_opengl->SetCurrentRollOverItem( rollOverItem );
  574. m_currentRollOverItem = rollOverItem;
  575. Request_refresh();
  576. }
  577. switch( rollOverItem->Type() )
  578. {
  579. case PCB_PAD_T:
  580. {
  581. PAD* pad = static_cast<PAD*>( rollOverItem );
  582. if( !pad->GetNumber().IsEmpty() )
  583. msg += wxString::Format( _( "Pad %s\t" ), pad->GetNumber() );
  584. if( pad->IsOnCopperLayer() )
  585. msg += printNetInfo( pad );
  586. break;
  587. }
  588. case PCB_FOOTPRINT_T:
  589. {
  590. FOOTPRINT* footprint = static_cast<FOOTPRINT*>( rollOverItem );
  591. msg += footprint->GetReference();
  592. break;
  593. }
  594. case PCB_TRACE_T:
  595. case PCB_VIA_T:
  596. case PCB_ARC_T:
  597. {
  598. PCB_TRACK* track = static_cast<PCB_TRACK*>( rollOverItem );
  599. msg += printNetInfo( track );
  600. break;
  601. }
  602. case PCB_ZONE_T:
  603. {
  604. ZONE* zone = static_cast<ZONE*>( rollOverItem );
  605. if( !zone->GetZoneName().IsEmpty() )
  606. {
  607. if( zone->GetIsRuleArea() )
  608. msg += wxString::Format( _( "Rule area %s\t" ), zone->GetZoneName() );
  609. else
  610. msg += wxString::Format( _( "Zone %s\t" ), zone->GetZoneName() );
  611. }
  612. if( zone->IsOnCopperLayer() )
  613. msg += printNetInfo( zone );
  614. break;
  615. }
  616. default:
  617. break;
  618. }
  619. reporter.Report( msg );
  620. }
  621. else
  622. {
  623. if( m_currentRollOverItem
  624. && m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
  625. {
  626. m_3d_render_opengl->SetCurrentRollOverItem( nullptr );
  627. Request_refresh();
  628. reporter.Report( wxEmptyString );
  629. }
  630. m_currentRollOverItem = nullptr;
  631. }
  632. }
  633. }
  634. void EDA_3D_CANVAS::OnLeftDown( wxMouseEvent& event )
  635. {
  636. SetFocus();
  637. stop_editingTimeOut_Timer();
  638. // Ensure m_camera.m_lastPosition (current mouse position) is up to date for
  639. // future drag events (can be not the case when left clicking after
  640. // opening a context menu)
  641. OnMouseMoveCamera( event );
  642. if( !event.Dragging() && ( m_3d_render_raytracing != nullptr ) )
  643. {
  644. RAY mouseRay = getRayAtCurrentMousePosition();
  645. BOARD_ITEM *intersectedBoardItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
  646. // !TODO: send a selection item to pcbnew, eg: via kiway?
  647. }
  648. }
  649. void EDA_3D_CANVAS::OnLeftUp( wxMouseEvent& event )
  650. {
  651. if( m_camera_is_moving )
  652. return;
  653. if( m_mouse_is_moving )
  654. {
  655. m_mouse_is_moving = false;
  656. restart_editingTimeOut_Timer();
  657. }
  658. }
  659. void EDA_3D_CANVAS::OnMiddleDown( wxMouseEvent& event )
  660. {
  661. SetFocus();
  662. stop_editingTimeOut_Timer();
  663. }
  664. void EDA_3D_CANVAS::OnMiddleUp( wxMouseEvent& event )
  665. {
  666. if( m_camera_is_moving )
  667. return;
  668. if( m_mouse_is_moving )
  669. {
  670. m_mouse_is_moving = false;
  671. restart_editingTimeOut_Timer();
  672. }
  673. else
  674. {
  675. move_pivot_based_on_cur_mouse_position();
  676. }
  677. }
  678. void EDA_3D_CANVAS::OnTimerTimeout_Editing( wxTimerEvent& aEvent )
  679. {
  680. if( aEvent.GetId() != m_editing_timeout_timer.GetId() )
  681. {
  682. aEvent.Skip();
  683. return;
  684. }
  685. m_mouse_is_moving = false;
  686. m_mouse_was_moved = false;
  687. Request_refresh();
  688. }
  689. void EDA_3D_CANVAS::stop_editingTimeOut_Timer()
  690. {
  691. m_editing_timeout_timer.Stop();
  692. }
  693. void EDA_3D_CANVAS::restart_editingTimeOut_Timer()
  694. {
  695. if( m_3d_render )
  696. m_editing_timeout_timer.Start( m_3d_render->GetWaitForEditingTimeOut(), wxTIMER_ONE_SHOT );
  697. }
  698. void EDA_3D_CANVAS::OnTimerTimeout_Redraw( wxTimerEvent& aEvent )
  699. {
  700. if( aEvent.GetId() != m_redraw_trigger_timer.GetId() )
  701. {
  702. aEvent.Skip();
  703. return;
  704. }
  705. Request_refresh( true );
  706. }
  707. void EDA_3D_CANVAS::OnRefreshRequest( wxEvent& aEvent )
  708. {
  709. Refresh();
  710. }
  711. void EDA_3D_CANVAS::Request_refresh( bool aRedrawImmediately )
  712. {
  713. if( aRedrawImmediately )
  714. {
  715. // Just calling Refresh() does not work always
  716. // Using an event to call DoRepaint ensure the repaint code will be executed,
  717. // and PostEvent will take priority to other events like mouse movements, keys, etc.
  718. // and is executed during the next idle time
  719. wxCommandEvent redrawEvent( wxEVT_REFRESH_CUSTOM_COMMAND, ID_CUSTOM_EVENT_1 );
  720. wxPostEvent( this, redrawEvent );
  721. }
  722. else
  723. {
  724. // Schedule a timed redraw
  725. m_redraw_trigger_timer.Start( 10 , wxTIMER_ONE_SHOT );
  726. }
  727. }
  728. void EDA_3D_CANVAS::request_start_moving_camera( float aMovingSpeed, bool aRenderPivot )
  729. {
  730. wxASSERT( aMovingSpeed > FLT_EPSILON );
  731. // Fast forward the animation if the animation is disabled
  732. if( !m_animation_enabled )
  733. {
  734. m_camera.Interpolate( 1.0f );
  735. DisplayStatus();
  736. Request_refresh();
  737. return;
  738. }
  739. // Map speed multiplier option to actual multiplier value
  740. // [1,2,3,4,5] -> [0.25, 0.5, 1, 2, 4]
  741. aMovingSpeed *= ( 1 << m_moving_speed_multiplier ) / 8.0f;
  742. m_render_pivot = aRenderPivot;
  743. m_camera_moving_speed = aMovingSpeed;
  744. stop_editingTimeOut_Timer();
  745. DisplayStatus();
  746. Request_refresh();
  747. m_camera_is_moving = true;
  748. m_strtime_camera_movement = GetRunningMicroSecs();
  749. }
  750. void EDA_3D_CANVAS::move_pivot_based_on_cur_mouse_position()
  751. {
  752. RAY mouseRay = getRayAtCurrentMousePosition();
  753. float hit_t;
  754. // Test it with the board bounding box
  755. if( m_boardAdapter.GetBBox().Intersect( mouseRay, &hit_t ) )
  756. {
  757. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
  758. m_camera.SetT0_and_T1_current_T();
  759. m_camera.SetLookAtPos_T1( mouseRay.at( hit_t ) );
  760. m_camera.ResetXYpos_T1();
  761. request_start_moving_camera();
  762. }
  763. }
  764. bool EDA_3D_CANVAS::SetView3D( VIEW3D_TYPE aRequestedView )
  765. {
  766. if( m_camera_is_moving )
  767. return false;
  768. const float delta_move = m_delta_move_step_factor * m_camera.GetZoom();
  769. const float arrow_moving_time_speed = 8.0f;
  770. switch( aRequestedView )
  771. {
  772. case VIEW3D_TYPE::VIEW3D_PIVOT_CENTER:
  773. move_pivot_based_on_cur_mouse_position();
  774. return true;
  775. case VIEW3D_TYPE::VIEW3D_PAN_LEFT:
  776. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
  777. m_camera.SetT0_and_T1_current_T();
  778. m_camera.Pan_T1( SFVEC3F( -delta_move, 0.0f, 0.0f ) );
  779. request_start_moving_camera( arrow_moving_time_speed, false );
  780. return true;
  781. case VIEW3D_TYPE::VIEW3D_PAN_RIGHT:
  782. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
  783. m_camera.SetT0_and_T1_current_T();
  784. m_camera.Pan_T1( SFVEC3F( +delta_move, 0.0f, 0.0f ) );
  785. request_start_moving_camera( arrow_moving_time_speed, false );
  786. return true;
  787. case VIEW3D_TYPE::VIEW3D_PAN_UP:
  788. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
  789. m_camera.SetT0_and_T1_current_T();
  790. m_camera.Pan_T1( SFVEC3F( 0.0f, +delta_move, 0.0f ) );
  791. request_start_moving_camera( arrow_moving_time_speed, false );
  792. return true;
  793. case VIEW3D_TYPE::VIEW3D_PAN_DOWN:
  794. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
  795. m_camera.SetT0_and_T1_current_T();
  796. m_camera.Pan_T1( SFVEC3F( 0.0f, -delta_move, 0.0f ) );
  797. request_start_moving_camera( arrow_moving_time_speed, false );
  798. return true;
  799. case VIEW3D_TYPE::VIEW3D_FIT_SCREEN:
  800. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
  801. m_camera.SetT0_and_T1_current_T();
  802. m_camera.Reset_T1();
  803. request_start_moving_camera( glm::min( glm::max( m_camera.GetZoom(), 1 / 1.26f ), 1.26f ) );
  804. return true;
  805. case VIEW3D_TYPE::VIEW3D_ZOOM_IN:
  806. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
  807. m_camera.SetT0_and_T1_current_T();
  808. if( m_camera.Zoom_T1( 1.26f ) ) // 3 steps per doubling
  809. request_start_moving_camera( 3.0f );
  810. return true;
  811. case VIEW3D_TYPE::VIEW3D_ZOOM_OUT:
  812. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
  813. m_camera.SetT0_and_T1_current_T();
  814. if( m_camera.Zoom_T1( 1/1.26f ) ) // 3 steps per halving
  815. request_start_moving_camera( 3.0f );
  816. return true;
  817. case VIEW3D_TYPE::VIEW3D_RIGHT:
  818. case VIEW3D_TYPE::VIEW3D_LEFT:
  819. case VIEW3D_TYPE::VIEW3D_FRONT:
  820. case VIEW3D_TYPE::VIEW3D_BACK:
  821. case VIEW3D_TYPE::VIEW3D_FLIP:
  822. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
  823. m_camera.SetT0_and_T1_current_T();
  824. m_camera.ViewCommand_T1( aRequestedView );
  825. request_start_moving_camera();
  826. return true;
  827. case VIEW3D_TYPE::VIEW3D_TOP:
  828. case VIEW3D_TYPE::VIEW3D_BOTTOM:
  829. m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
  830. m_camera.SetT0_and_T1_current_T();
  831. m_camera.ViewCommand_T1( aRequestedView );
  832. request_start_moving_camera( glm::min( glm::max( m_camera.GetZoom(), 0.5f ), 1.125f ) );
  833. return true;
  834. default:
  835. return false;
  836. }
  837. }
  838. void EDA_3D_CANVAS::RenderEngineChanged()
  839. {
  840. SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
  841. EDA_3D_VIEWER_SETTINGS* cfg = mgr.GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" );
  842. switch( cfg->m_Render.engine )
  843. {
  844. case RENDER_ENGINE::OPENGL: m_3d_render = m_3d_render_opengl; break;
  845. case RENDER_ENGINE::RAYTRACING: m_3d_render = m_3d_render_raytracing; break;
  846. default: m_3d_render = nullptr; break;
  847. }
  848. if( m_3d_render )
  849. m_3d_render->ReloadRequest();
  850. m_mouse_was_moved = false;
  851. Request_refresh();
  852. }
  853. RAY EDA_3D_CANVAS::getRayAtCurrentMousePosition()
  854. {
  855. SFVEC3F rayOrigin;
  856. SFVEC3F rayDir;
  857. // Generate a ray origin and direction based on current mouser position and camera
  858. m_camera.MakeRayAtCurrentMousePosition( rayOrigin, rayDir );
  859. RAY mouseRay;
  860. mouseRay.Init( rayOrigin, rayDir );
  861. return mouseRay;
  862. }