KiCad PCB EDA Suite
Loading...
Searching...
No Matches
eda_3d_canvas.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) 2015-2016 Mario Luzeiro <[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
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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include <kicad_gl/kiglu.h> // Must be included first
22#include <kicad_gl/gl_utils.h>
24
25#include <wx/tokenzr.h>
26
27#include <fmt/format.h>
28
29#include <api/api_utils.h>
30#include <api/common/commands/cross_probe_commands.pb.h>
31
33#include "eda_3d_canvas.h"
34
35#include <optional>
36
37#include <3d_math.h>
39#include <glm/gtc/type_ptr.hpp>
41#include <eda_3d_viewer_frame.h>
44#include <3d_viewer_id.h>
45#include <advanced_config.h>
46#include <math/util.h>
47#include <build_version.h>
49#include <board.h>
50#include <footprint.h>
51#include <pad.h>
52#include <pcb_field.h>
53#include <pcb_track.h>
54#include <reporter.h>
55#include <widgets/wx_infobar.h>
56#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
57#include <bitmaps.h>
58#include <kiway_holder.h>
59#include <kiway.h>
60#include <macros.h>
61#include <pgm_base.h>
64#include <string_utils.h>
65#include <mail_type.h>
66#include <kiway_mail.h>
68#include <zone.h>
69#include <chrono>
70#include <cmath>
71#include <limits>
72#include <ratio>
73
74
82const wxChar* EDA_3D_CANVAS::m_logTrace = wxT( "KI_TRACE_EDA_3D_CANVAS" );
83
84
85// A custom event, used to call DoRePaint during an idle time
86wxDEFINE_EVENT( wxEVT_REFRESH_CUSTOM_COMMAND, wxCommandEvent );
87
88
89BEGIN_EVENT_TABLE( EDA_3D_CANVAS, HIDPI_GL_3D_CANVAS )
90 EVT_PAINT( EDA_3D_CANVAS::OnPaint )
91
92 // mouse events
93 EVT_LEFT_DOWN( EDA_3D_CANVAS::OnLeftDown )
94 EVT_LEFT_UP( EDA_3D_CANVAS::OnLeftUp )
95 EVT_MIDDLE_UP( EDA_3D_CANVAS::OnMiddleUp )
96 EVT_MIDDLE_DOWN( EDA_3D_CANVAS::OnMiddleDown)
97 EVT_RIGHT_DOWN( EDA_3D_CANVAS::OnRightDown )
98 EVT_RIGHT_UP( EDA_3D_CANVAS::OnRightUp )
99 EVT_MOUSEWHEEL( EDA_3D_CANVAS::OnMouseWheel )
100 EVT_MOTION( EDA_3D_CANVAS::OnMouseMove )
101 EVT_LEAVE_WINDOW( EDA_3D_CANVAS::OnMouseLeave )
102 EVT_MAGNIFY( EDA_3D_CANVAS::OnMagnify )
103
104 // touch gesture events
105 EVT_GESTURE_ZOOM( wxID_ANY, EDA_3D_CANVAS::OnZoomGesture )
106 EVT_GESTURE_PAN( wxID_ANY, EDA_3D_CANVAS::OnPanGesture )
107 EVT_GESTURE_ROTATE( wxID_ANY, EDA_3D_CANVAS::OnRotateGesture )
108
109 // other events
110 EVT_ERASE_BACKGROUND( EDA_3D_CANVAS::OnEraseBackground )
111 EVT_CUSTOM(wxEVT_REFRESH_CUSTOM_COMMAND, ID_CUSTOM_EVENT_1, EDA_3D_CANVAS::OnRefreshRequest )
112
114 EVT_SIZE( EDA_3D_CANVAS::OnResize )
115END_EVENT_TABLE()
116
117
118EDA_3D_CANVAS::EDA_3D_CANVAS( wxWindow* aParent, const wxGLAttributes& aGLAttribs,
119 BOARD_ADAPTER& aBoardAdapter, CAMERA& aCamera,
120 S3D_CACHE* a3DCachePointer ) :
121 HIDPI_GL_3D_CANVAS( EDA_DRAW_PANEL_GAL::GetVcSettings(), aCamera, aParent, aGLAttribs,
122 EDA_3D_CANVAS_ID, wxDefaultPosition,
123 wxDefaultSize, wxFULL_REPAINT_ON_RESIZE ),
124 m_editing_timeout_timer( this, wxID_HIGHEST + 1 ),
125 m_redraw_trigger_timer( this, wxID_HIGHEST + 2 ),
126 m_boardAdapter( aBoardAdapter )
127{
128 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::EDA_3D_CANVAS" ) );
129
130 m_editing_timeout_timer.SetOwner( this );
131 Connect( m_editing_timeout_timer.GetId(), wxEVT_TIMER,
132 wxTimerEventHandler( EDA_3D_CANVAS::OnTimerTimeout_Editing ), nullptr, this );
133
134 m_redraw_trigger_timer.SetOwner( this );
135 Connect( m_redraw_trigger_timer.GetId(), wxEVT_TIMER,
136 wxTimerEventHandler( EDA_3D_CANVAS::OnTimerTimeout_Redraw ), nullptr, this );
137
139
140 m_3d_render_raytracing = std::make_unique<RENDER_3D_RAYTRACE_GL>( this, m_boardAdapter, m_camera );
141 m_3d_render_opengl = std::make_unique<RENDER_3D_OPENGL>( this, m_boardAdapter, m_camera );
142
143 auto busy_indicator_factory =
144 []()
145 {
146 return std::make_unique<WX_BUSY_INDICATOR>();
147 };
148
149 m_3d_render_raytracing->SetBusyIndicatorFactory( busy_indicator_factory );
150 m_3d_render_opengl->SetBusyIndicatorFactory( busy_indicator_factory );
151
152 // We always start with the opengl engine (raytracing is avoided due to very
153 // long calculation time)
155
156 m_boardAdapter.ReloadColorSettings();
157
158 wxASSERT( a3DCachePointer != nullptr );
159 m_boardAdapter.Set3dCacheManager( a3DCachePointer );
160
161#if defined( __WXMSW__ )
162 EnableTouchEvents( wxTOUCH_ZOOM_GESTURE | wxTOUCH_ROTATE_GESTURE | wxTOUCH_PAN_GESTURES );
163#elif defined( __WXGTK__ )
164 EnableTouchEvents( wxTOUCH_ZOOM_GESTURE | wxTOUCH_ROTATE_GESTURE );
165#endif
166
167 const wxEventType events[] =
168 {
169 // Binding both EVT_CHAR and EVT_CHAR_HOOK ensures that all key events,
170 // especially special key like arrow keys, are handled by the GAL event dispatcher,
171 // and not sent to GUI without filtering, because they have a default action (scroll)
172 // that must not be called.
173 wxEVT_LEFT_UP, wxEVT_LEFT_DOWN, wxEVT_LEFT_DCLICK,
174 wxEVT_RIGHT_UP, wxEVT_RIGHT_DOWN, wxEVT_RIGHT_DCLICK,
175 wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DOWN, wxEVT_MIDDLE_DCLICK,
176 wxEVT_MOTION, wxEVT_MOUSEWHEEL, wxEVT_CHAR, wxEVT_CHAR_HOOK,
177 wxEVT_MAGNIFY,
178 wxEVT_MENU_OPEN, wxEVT_MENU_CLOSE, wxEVT_MENU_HIGHLIGHT
179 };
180
181 for( wxEventType eventType : events )
182 Connect( eventType, wxEventHandler( EDA_3D_CANVAS::OnEvent ), nullptr, m_eventDispatcher );
183}
184
185
187{
188 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::~EDA_3D_CANVAS" ) );
189
190 // Detach UI reporters before joining/destroying renderers that may still report.
192 m_activityReporterSync->SetNullReporter();
193
195 m_warningReporterSync->SetNullReporter();
196
198 m_accelerator3DShapes = nullptr;
199
201}
202
203
205{
206 // Join the bg worker before taking the GL lock or destroying renderers. The worker
207 // may still be in ReloadRaytracingForHitTesting() and must not outlive either renderer.
209 m_3d_render_opengl->StopBgWorker();
210
211 if( m_glRC )
212 {
214 wxASSERT( gl_mgr );
215
216 if( gl_mgr )
217 {
218 // wxGTK destroys the native window before this runs, and our context cannot be
219 // made current without it. The renderers then have to come down without GL
220 std::optional<ORPHANED_GL_OBJECTS> orphaned;
221
222 if( !gl_mgr->LockCtx( m_glRC, this ) )
223 orphaned.emplace();
224
225 m_3d_render = nullptr;
226
227 // OpenGL dtor joins the bg worker; reset it before raytracing.
228 m_3d_render_opengl.reset();
230
231 gl_mgr->UnlockCtx( m_glRC );
232 gl_mgr->DestroyCtx( m_glRC );
233 }
234
235 m_glRC = nullptr;
236 }
237}
238
239
240void EDA_3D_CANVAS::OnCloseWindow( wxCloseEvent& event )
241{
243
244 event.Skip();
245}
246
247
248void EDA_3D_CANVAS::OnResize( wxSizeEvent& event )
249{
251}
252
253
255{
256 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::initializeOpenGL" ) );
257
259
260 const int glVersion = gladLoaderLoadGL();
261
262 if( glVersion == 0 )
263 {
264 wxLogMessage( wxT( "Failed to load OpenGL via loader" ) );
265
266 return false;
267 }
268 else
269 {
270 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::initializeOpenGL Using OpenGL version %s" ),
271 From_UTF8( (char*) glGetString( GL_VERSION ) ) );
272 }
273
274 SetOpenGLInfo( (const char*) glGetString( GL_VENDOR ), (const char*) glGetString( GL_RENDERER ),
275 (const char*) glGetString( GL_VERSION ) );
276
277 wxString version = From_UTF8( (char *) glGetString( GL_VERSION ) );
278
279 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL version string %s." ),
280 __WXFUNCTION__, version );
281
282 // Extract OpenGL version from string. This method is used because prior to OpenGL 2,
283 // getting the OpenGL major and minor version as integers didn't exist.
284 wxString tmp;
285
286 wxStringTokenizer tokenizer( version, " \t\r\n" );
287
288 if( tokenizer.HasMoreTokens() )
289 {
290 long major = 0;
291 long minor = 0;
292
293 tmp = tokenizer.GetNextToken();
294
295 tokenizer.SetString( tmp, wxString( wxT( "." ) ) );
296
297 if( tokenizer.HasMoreTokens() )
298 tokenizer.GetNextToken().ToLong( &major );
299
300 if( tokenizer.HasMoreTokens() )
301 tokenizer.GetNextToken().ToLong( &minor );
302
303 if( major < 2 || ( ( major == 2 ) && ( minor < 1 ) ) )
304 {
305 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL ray tracing not supported." ),
306 __WXFUNCTION__ );
307
308 if( GetParent() )
309 {
310 wxCommandEvent evt( wxEVT_MENU, ID_DISABLE_RAY_TRACING );
311 GetParent()->ProcessWindowEvent( evt );
312 }
313
315 }
316
317 if( ( major == 1 ) && ( minor < 5 ) )
318 {
319 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL not supported." ),
320 __WXFUNCTION__ );
321
323 }
324 }
325
326#if wxCHECK_VERSION( 3, 3, 3 )
327 wxGLCanvas::SetSwapInterval( -1 );
328#else
329 GL_UTILS::SetSwapInterval( this, -1 );
330#endif
331
333
334 return true;
335}
336
337
338void EDA_3D_CANVAS::GetScreenshot( wxImage& aDstImage )
339{
340 OglGetScreenshot( aDstImage );
341}
342
343
345{
346 if( m_3d_render )
347 m_3d_render->JoinBgWorker();
348}
349
350
351void EDA_3D_CANVAS::ReloadRequest( BOARD* aBoard , S3D_CACHE* aCachePointer )
352{
354 m_3d_render_opengl->StopBgWorker();
355
356 if( aCachePointer != nullptr )
357 m_boardAdapter.Set3dCacheManager( aCachePointer );
358
359 if( aBoard != nullptr )
360 m_boardAdapter.SetBoard( aBoard );
361
362 m_boardAdapter.ReloadColorSettings();
363
364 if( m_3d_render )
365 m_3d_render->ReloadRequest();
366}
367
368
370{
372 m_3d_render_raytracing->Reload( true, aStop );
373}
374
375
381
382
384{
386
387 if( m_3d_render )
388 m_3d_render->ReloadRequest();
389
391
393}
394
395
397{
399 {
400 wxString msg;
401
402 msg.Printf( wxT( "dx %3.2f" ), m_camera.GetCameraPos().x );
403 m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::X_POS ) );
404
405 msg.Printf( wxT( "dy %3.2f" ), m_camera.GetCameraPos().y );
406 m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::Y_POS ) );
407
408 msg.Printf( wxT( "zoom %3.2f" ), 1 / m_camera.GetZoom() );
409 m_parentStatusBar->SetStatusText( msg,
410 static_cast<int>( EDA_3D_VIEWER_STATUSBAR::ZOOM_LEVEL ) );
411 }
412}
413
414
415void EDA_3D_CANVAS::OnPaint( wxPaintEvent& aEvent )
416{
417 // Please have a look at: https://lists.launchpad.net/kicad-developers/msg25149.html
418 DoRePaint();
419}
420
421
423{
424 if( m_is_currently_painting.test_and_set() )
425 return;
426
427 // SwapBuffer requires the window to be shown before calling
428 if( !IsShownOnScreen() )
429 {
430 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::DoRePaint !IsShown" ) );
432 return;
433 }
434
435 // Because the board to draw is handled by the parent viewer frame,
436 // ensure this parent is still alive. When it is closed before the viewer
437 // frame, a paint event can be generated after the parent is closed,
438 // therefore with invalid board.
439 // This is dependent of the platform.
440 // Especially on OSX, but also on Windows, it frequently happens
441 wxWindow* viewer = wxGetTopLevelParent( this );
442 wxWindow* owner = viewer ? viewer->GetParent() : nullptr;
443
444 if( owner && !owner->IsShownOnScreen() )
445 {
447 return; // The parent board editor frame is no more alive
448 }
449
451 {
453 std::make_unique<STATUSBAR_REPORTER>( m_parentStatusBar, EDA_3D_VIEWER_STATUSBAR::ACTIVITY );
454 m_infoBarReporter = std::make_unique<INFOBAR_REPORTER>( m_parentInfoBar );
455
456 m_activityReporterSync = std::make_shared<SYNC_REPORTER>( *m_statusBarReporter );
457 m_warningReporterSync = std::make_shared<SYNC_REPORTER>( *m_infoBarReporter );
458
461
464 }
465
466 wxString err_messages;
467 auto start_time = std::chrono::steady_clock::now();
469
470 if( !gl_mgr )
471 {
473 return;
474 }
475
476 // "Makes the OpenGL state that is represented by the OpenGL rendering
477 // context context current, i.e. it will be used by all subsequent OpenGL calls.
478 // This function may only be called when the window is shown on screen"
479
480 // Explicitly create a new rendering context instance for this canvas.
481 if( m_glRC == nullptr )
482 m_glRC = gl_mgr->CreateCtx( this );
483
484 // CreateCtx could and does fail per sentry crash events, lets be graceful
485 if( m_glRC == nullptr )
486 {
487 m_warningReporterSync->Report( _( "OpenGL context creation error" ), RPT_SEVERITY_ERROR );
488 m_warningReporterSync->Finalize();
490 return;
491 }
492
493 gl_mgr->LockCtx( m_glRC, this );
494
495 // Set the OpenGL viewport according to the client size of this canvas.
496 // This is done here rather than in a wxSizeEvent handler because our
497 // OpenGL rendering context (and thus viewport setting) is used with
498 // multiple canvases: If we updated the viewport in the wxSizeEvent
499 // handler, changing the size of one canvas causes a viewport setting that
500 // is wrong when next another canvas is repainted.
501 wxSize clientSize = GetNativePixelSize();
502
503 const bool windows_size_changed = m_camera.SetCurWindowSize( clientSize );
504
505 // Initialize openGL if need
507 {
508 if( !initializeOpenGL() )
509 {
510 gl_mgr->UnlockCtx( m_glRC );
512
513 return;
514 }
515
517 {
518 m_warningReporterSync->Report( _( "Your OpenGL version is not supported. Minimum required "
519 "is 1.5." ), RPT_SEVERITY_ERROR );
520
521 m_warningReporterSync->Finalize();
522 }
523 }
524
526 {
527 glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
528 glClear( GL_COLOR_BUFFER_BIT );
529
530 SwapBuffers();
531
532 gl_mgr->UnlockCtx( m_glRC );
534
535 return;
536 }
537
538 // Don't attempt to ray trace if OpenGL doesn't support it.
540 {
543 m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
544 }
545
546 // Check if a raytracing was requested and need to switch to raytracing mode
547 if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
548 {
549 const bool was_camera_changed = m_camera.ParametersChanged();
550
551 // It reverts back to OpenGL mode if it was requested a raytracing
552 // render of the current scene. AND the mouse / camera is moving
553 if( ( m_mouse_is_moving || m_camera_is_moving || was_camera_changed
554 || windows_size_changed )
556 {
559 }
560 }
561
562 float curtime_delta_s = 0.0f;
563
565 {
566 const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
567 // Convert microseconds to seconds as float and apply speed multiplier
568 curtime_delta_s = static_cast<float>( static_cast<double>( curtime_delta ) / 1e6 )
570 m_camera.Interpolate( curtime_delta_s );
571
572 if( curtime_delta_s > 1.0f )
573 {
574 m_render_pivot = false;
575 m_camera_is_moving = false;
576 m_mouse_was_moved = true;
577
580 }
581 else
582 {
584 }
585 }
586
587 // It will return true if the render request a new redraw
588 bool requested_redraw = false;
589
590 if( m_3d_render )
591 {
592 try
593 {
594 m_3d_render->SetCurWindowSize( clientSize );
595
596 requested_redraw = m_3d_render->Redraw( m_mouse_was_moved || m_camera_is_moving );
597 }
598 catch( std::runtime_error& )
599 {
603 gl_mgr->UnlockCtx( m_glRC );
605 return;
606 }
607 }
608
610
611 if( m_render_pivot )
612 {
613 const float scale = glm::min( m_camera.GetZoom(), 1.0f );
614 render_pivot( curtime_delta_s, scale );
615 }
616
617 // This will only be enabled by the 3d mouse plugin, so we can leave
618 // it as a simple if statement
620 {
621 const float scale = glm::min( m_camera.GetZoom(), 1.0f );
623 }
624
625 // "Swaps the double-buffer of this window, making the back-buffer the
626 // front-buffer and vice versa, so that the output of the previous OpenGL
627 // commands is displayed on the window."
628 SwapBuffers();
629
630 gl_mgr->UnlockCtx( m_glRC );
631
632 // Calculation time in milliseconds
633 const double calculation_time =
634 std::chrono::duration<double, std::milli>( std::chrono::steady_clock::now() - start_time ).count();
635
637 {
638 m_parentStatusBar->SetStatusText( wxString::Format( _( "Last render time %.0f ms" ), calculation_time ),
640 }
641
642 // This will reset the flag of camera parameters changed
643 m_camera.ParametersChanged();
644
646 m_warningReporterSync->Finalize();
647
648 if( !err_messages.IsEmpty() )
649 wxLogMessage( err_messages );
650
651 if( ( !m_camera_is_moving ) && requested_redraw )
652 {
653 m_mouse_was_moved = false;
654 Request_refresh( false );
655 }
656
657 static constexpr std::array<VIEW3D_TYPE, static_cast<int>( SPHERES_GIZMO::GizmoSphereSelection::Count )>
660
661 SPHERES_GIZMO::GizmoSphereSelection selectedGizmoSphere = m_3d_render_opengl->getSelectedGizmoSphere();
662 int index = static_cast<int>( selectedGizmoSphere );
663 if( index >= 0 && index < static_cast<int>( viewTable.size() ) )
664 {
665 SetView3D( viewTable[index] );
666 }
667
668 m_3d_render_opengl->resetSelectedGizmoSphere();
669
671}
672
673
674void EDA_3D_CANVAS::RenderToFrameBuffer( unsigned char* buffer, int width, int height )
675{
676 if( m_is_currently_painting.test_and_set() )
677 return;
678
679 // Validate input parameters
680 if( !buffer || width <= 0 || height <= 0 )
681 {
683 return;
684 }
685
686 // Because the board to draw is handled by the parent viewer frame,
687 // ensure this parent is still alive
688 if( !GetParent() || !GetParent()->GetParent() || !GetParent()->GetParent()->IsShownOnScreen() )
689 {
691 return;
692 }
693
694 wxString err_messages;
695 int64_t start_time = GetRunningMicroSecs();
697
698 if( !gl_mgr )
699 {
701 return;
702 }
703
704 // Create OpenGL context if needed
705 if( m_glRC == nullptr )
706 m_glRC = gl_mgr->CreateCtx( this );
707
708 if( m_glRC == nullptr )
709 {
710 wxLogError( _( "OpenGL context creation error" ) );
712 return;
713 }
714
715 gl_mgr->LockCtx( m_glRC, this );
716
717 // Set up framebuffer objects for off-screen rendering
718 GLuint framebuffer = 0;
719 GLuint colorTexture = 0;
720 GLuint depthStencilBuffer = 0;
721 GLint oldFramebuffer = 0;
722 GLint oldViewport[4];
723
724 // Save current state
725 glGetIntegerv( GL_FRAMEBUFFER_BINDING, &oldFramebuffer );
726 glGetIntegerv( GL_VIEWPORT, oldViewport );
727
728 // Create and bind framebuffer
729 glGenFramebuffers( 1, &framebuffer );
730 glBindFramebuffer( GL_FRAMEBUFFER, framebuffer );
731
732 // Create color texture attachment
733 glGenTextures( 1, &colorTexture );
734 glBindTexture( GL_TEXTURE_2D, colorTexture );
735 glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
736 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
737 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
738 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
739 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
740 glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0 );
741
742 // Create combined depth+stencil renderbuffer attachment. The stencil buffer is required
743 // because the OpenGL renderer uses stencil operations to cut holes in copper layers and
744 // the board body (see OPENGL_RENDER_LIST::DrawCulled).
745 glGenRenderbuffers( 1, &depthStencilBuffer );
746 glBindRenderbuffer( GL_RENDERBUFFER, depthStencilBuffer );
747 glRenderbufferStorage( GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height );
748 glFramebufferRenderbuffer( GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
749 depthStencilBuffer );
750
751 auto resetState = std::unique_ptr<void, std::function<void(void*)>>(
752 reinterpret_cast<void*>(1),
753 [&](void*) {
754 glBindFramebuffer( GL_FRAMEBUFFER, oldFramebuffer );
755 glViewport( oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3] );
756 glDeleteFramebuffers( 1, &framebuffer );
757 glDeleteTextures( 1, &colorTexture );
758 glDeleteRenderbuffers( 1, &depthStencilBuffer );
759 gl_mgr->UnlockCtx( m_glRC );
761 }
762 );
763
764 // Check framebuffer completeness
765 GLenum framebufferStatus = glCheckFramebufferStatus( GL_FRAMEBUFFER );
766
767 if( framebufferStatus != GL_FRAMEBUFFER_COMPLETE )
768 {
769 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer Framebuffer incomplete: 0x%04X" ),
770 framebufferStatus );
771
772 return;
773 }
774
775 // Set viewport for off-screen rendering
776 glViewport( 0, 0, width, height );
777
778 // Set window size for camera and rendering
779 wxSize clientSize( width, height );
780 const bool windows_size_changed = m_camera.SetCurWindowSize( clientSize );
781
782 // Initialize OpenGL if needed
784 {
785 if( !initializeOpenGL() )
786 {
787 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer OpenGL initialization failed." ) );
788 return;
789 }
790
792 {
793 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer OpenGL version not supported." ) );
794 }
795 }
796
798 {
799 glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
800 glClear( GL_COLOR_BUFFER_BIT );
801
802 // Read black screen to buffer
803 glReadPixels( 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
804 return;
805 }
806
807 // Handle raytracing/OpenGL renderer selection
809 {
812 m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
813 }
814
815 if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
816 {
817 const bool was_camera_changed = m_camera.ParametersChanged();
818
819 if( ( m_mouse_is_moving || m_camera_is_moving || was_camera_changed || windows_size_changed )
821 {
824 }
825 }
826
827 // Handle camera animation (simplified for off-screen rendering)
828 float curtime_delta_s = 0.0f;
830 {
831 const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
832 curtime_delta_s = static_cast<float>( static_cast<double>( curtime_delta ) / 1e6 )
834 m_camera.Interpolate( curtime_delta_s );
835
836 if( curtime_delta_s > 1.0f )
837 {
838 m_render_pivot = false;
839 m_camera_is_moving = false;
840 m_mouse_was_moved = true;
841 }
842 }
843
844 // Perform the actual rendering. The first redraw may start background loading;
845 // wait for it to finish and redraw once more before reading pixels.
846 if( m_3d_render )
847 {
848 try
849 {
850 m_3d_render->SetCurWindowSize( clientSize );
851 m_3d_render->Redraw( false );
852
853 if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
854 {
855 m_3d_render->JoinBgWorker();
856 m_3d_render->Redraw( false );
857 }
858 }
859 catch( std::runtime_error& )
860 {
864 return;
865 }
866 }
867
868 glFinish();
869
870 // Read pixels from framebuffer to the provided buffer
871 glReadPixels( 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
872
873 // Check for OpenGL errors
874 GLenum error = glGetError();
875 if( error != GL_NO_ERROR )
876 {
877 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::RenderToFrameBuffer OpenGL error: 0x%04X" ), error );
878 err_messages += wxString::Format( _( "OpenGL error during off-screen rendering: 0x%04X\n" ), error );
879 }
880
881 // Reset camera parameters changed flag
882 m_camera.ParametersChanged();
883
884 if( !err_messages.IsEmpty() )
885 wxLogMessage( err_messages );
886}
887
888
890{
891 m_eventDispatcher = aEventDispatcher;
892}
893
894
895void EDA_3D_CANVAS::OnEvent( wxEvent& aEvent )
896{
897 if( !m_eventDispatcher )
898 aEvent.Skip();
899 else
900 m_eventDispatcher->DispatchWxEvent( aEvent );
901
902 Refresh();
903}
904
905
906void EDA_3D_CANVAS::OnEraseBackground( wxEraseEvent& event )
907{
908 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::OnEraseBackground" ) );
909 // Do nothing, to avoid flashing.
910}
911
912
913void EDA_3D_CANVAS::OnMouseWheel( wxMouseEvent& event )
914{
915 wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::OnMouseWheel" ) );
916
917 OnMouseWheelCamera( event, m_boardAdapter.m_MousewheelPanning );
918
920 {
924 }
925}
926
927
928void EDA_3D_CANVAS::OnMagnify( wxMouseEvent& event )
929{
930 SetFocus();
931
933 return;
934
935 //m_is_moving_mouse = true;
937
938 float magnification = ( event.GetMagnification() + 1.0f );
939
940 m_camera.Zoom( magnification );
941
944}
945
946
947void EDA_3D_CANVAS::OnZoomGesture( wxZoomGestureEvent& aEvent )
948{
949 SetFocus();
950
951 if( aEvent.IsGestureStart() )
952 {
954 m_camera.SetCurMousePosition( aEvent.GetPosition() );
955 }
956
958 return;
959
961
962 m_camera.Pan( aEvent.GetPosition() );
963 m_camera.SetCurMousePosition( aEvent.GetPosition() );
964
965 m_camera.Zoom( static_cast<float>( aEvent.GetZoomFactor() / m_gestureLastZoomFactor ) );
966
967 m_gestureLastZoomFactor = aEvent.GetZoomFactor();
968
971}
972
973
974void EDA_3D_CANVAS::OnPanGesture( wxPanGestureEvent& aEvent )
975{
976 SetFocus();
977
978 if( aEvent.IsGestureStart() )
979 m_camera.SetCurMousePosition( aEvent.GetPosition() );
980
982 return;
983
984 m_camera.Pan( aEvent.GetPosition() );
985 m_camera.SetCurMousePosition( aEvent.GetPosition() );
986
989}
990
991
992void EDA_3D_CANVAS::OnRotateGesture( wxRotateGestureEvent& aEvent )
993{
994 SetFocus();
995
996 if( aEvent.IsGestureStart() )
997 {
999 m_camera.SetCurMousePosition( aEvent.GetPosition() );
1000
1001 // We don't want to process the first angle
1002 return;
1003 }
1004
1005 if( m_camera_is_moving )
1006 return;
1007
1008 m_camera.RotateScreen( static_cast<float>( m_gestureLastAngle - aEvent.GetRotationAngle() ) );
1009 m_gestureLastAngle = aEvent.GetRotationAngle();
1010
1011 DisplayStatus();
1013}
1014
1015
1016void EDA_3D_CANVAS::OnMouseMove( wxMouseEvent& event )
1017{
1018 if( m_3d_render && m_3d_render->IsReloadRequestPending() )
1019 return; // Prevents using invalid m_3d_render_raytracing data
1020
1021 if( m_camera_is_moving )
1022 return;
1023
1024 OnMouseMoveCamera( event );
1025
1026 if( m_mouse_was_moved )
1027 {
1028 DisplayStatus();
1030 // *Do not* reactivate the timer here during the mouse move command:
1031 // OnMiddleUp() will do it at the end of mouse drag/move command
1032 }
1033
1034 // A pick handler owns the cursor: report the hover to it instead of rolling over board items.
1035 if( m_hoverHandler )
1036 {
1037 if( !event.Dragging() )
1039
1040 return;
1041 }
1042
1043 if( !event.Dragging() && m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
1044 {
1046 RAY mouseRay = getRayAtCurrentMousePosition();
1047 BOARD_ITEM* rollOverItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
1048
1049 auto printNetInfo =
1050 []( BOARD_CONNECTED_ITEM* aItem )
1051 {
1052 return wxString::Format( _( "Net %s\tNet class %s" ), aItem->GetNet()->GetNetname(),
1053 aItem->GetNet()->GetNetClass()->GetHumanReadableName() );
1054 };
1055
1056 if( rollOverItem )
1057 {
1058 wxString msg;
1059
1060 if( rollOverItem != m_currentRollOverItem )
1061 {
1062 m_3d_render_opengl->SetCurrentRollOverItem( rollOverItem );
1063 m_currentRollOverItem = rollOverItem;
1064
1066 }
1067
1068 switch( rollOverItem->Type() )
1069 {
1070 case PCB_PAD_T:
1071 {
1072 PAD* pad = static_cast<PAD*>( rollOverItem );
1073
1074 if( !pad->GetNumber().IsEmpty() )
1075 msg += wxString::Format( _( "Pad %s\t" ), pad->GetNumber() );
1076
1077 if( pad->IsOnCopperLayer() )
1078 msg += printNetInfo( pad );
1079
1080 break;
1081 }
1082
1083 case PCB_FOOTPRINT_T:
1084 {
1085 FOOTPRINT* footprint = static_cast<FOOTPRINT*>( rollOverItem );
1086 msg += footprint->GetReference() + wxT( " " ) + footprint->GetValue();
1087 break;
1088 }
1089
1090 case PCB_TRACE_T:
1091 case PCB_VIA_T:
1092 case PCB_ARC_T:
1093 {
1094 PCB_TRACK* track = static_cast<PCB_TRACK*>( rollOverItem );
1095 msg += printNetInfo( track );
1096 break;
1097 }
1098
1099 case PCB_ZONE_T:
1100 {
1101 ZONE* zone = static_cast<ZONE*>( rollOverItem );
1102
1103 if( !zone->GetZoneName().IsEmpty() )
1104 {
1105 if( zone->GetIsRuleArea() )
1106 msg += wxString::Format( _( "Rule area %s\t" ), zone->GetZoneName() );
1107 else
1108 msg += wxString::Format( _( "Zone %s\t" ), zone->GetZoneName() );
1109 }
1110
1111 if( zone->IsOnCopperLayer() )
1112 msg += printNetInfo( zone );
1113
1114 break;
1115 }
1116
1117 default:
1118 break;
1119 }
1120
1121 reporter.Report( msg );
1122 }
1123 else
1124 {
1126 && m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
1127 {
1128 m_3d_render_opengl->SetCurrentRollOverItem( nullptr );
1130
1131 reporter.Report( wxEmptyString );
1132 }
1133
1134 m_currentRollOverItem = nullptr;
1135 }
1136 }
1137}
1138
1139
1140void EDA_3D_CANVAS::OnMouseLeave( wxMouseEvent& event )
1141{
1142 if( m_hoverHandler )
1143 m_hoverHandler( std::nullopt );
1144
1145 event.Skip();
1146}
1147
1148
1149void EDA_3D_CANVAS::OnLeftDown( wxMouseEvent& event )
1150{
1151 SetFocus();
1153
1154 // Ensure m_camera.m_lastPosition (current mouse position) is up to date for
1155 // future drag events (can be not the case when left clicking after
1156 // opening a context menu)
1157 OnMouseMoveCamera( event );
1158
1159 // Selection/deselection is handled on button release in OnLeftUp, so a click-drag
1160 // used to rotate the view does not change the current selection
1161}
1162
1163
1164void EDA_3D_CANVAS::OnLeftUp( wxMouseEvent& event )
1165{
1166 if( m_camera_is_moving )
1167 return;
1168
1169 bool wasRotating = m_mouse_is_moving;
1170
1171 if( m_mouse_is_moving )
1172 {
1173 m_mouse_is_moving = false;
1175 }
1176
1177 bool gizmoClicked = false;
1178
1179 if( m_boardAdapter.m_Cfg->m_Render.show_navigator
1180 && m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
1181 {
1182 wxSize logicalSize = GetClientSize();
1183 int logicalW = logicalSize.GetWidth();
1184 int logicalH = logicalSize.GetHeight();
1185
1186 int gizmo_x = 0, gizmo_y = 0, gizmo_width = 0, gizmo_height = 0;
1187 std::tie( gizmo_x, gizmo_y, gizmo_width, gizmo_height ) = m_3d_render_opengl->getGizmoViewport();
1188
1189 float scaleX = static_cast<float>( static_cast<double>( gizmo_width ) / static_cast<double>( logicalW ) );
1190 float scaleY = static_cast<float>( static_cast<double>( gizmo_height ) / static_cast<double>( logicalH ) );
1191
1192 int scaledMouseX = static_cast<int>( static_cast<float>( event.GetX() ) * scaleX );
1193 int scaledMouseY = static_cast<int>( static_cast<float>( logicalH - event.GetY() ) * scaleY );
1194
1195 m_3d_render_opengl->handleGizmoMouseInput( scaledMouseX, scaledMouseY );
1196 m_3d_render_opengl->updateGizmoSelection( m_camera.GetRotationMatrix() );
1197
1198 gizmoClicked = m_3d_render_opengl->getSelectedGizmoSphere() != SPHERES_GIZMO::GizmoSphereSelection::None;
1199 }
1200
1201 if( !wasRotating && !gizmoClicked && m_pickHandler )
1202 {
1203 // A completed pick can clear or replace the canvas handler.
1204 auto handler = m_pickHandler;
1205
1206 if( handler( getRayAtCurrentMousePosition() ) )
1207 {
1208 Refresh();
1209 return;
1210 }
1211 }
1212
1213 // A plain click that missed the orientation gizmo: cross-probe the clicked footprint,
1214 // or clear the selection when clicking empty space. A click-drag rotation or a click
1215 // on the gizmo leaves the current selection untouched.
1216 if( !wasRotating && !gizmoClicked && m_3d_render_raytracing != nullptr )
1217 {
1218 RAY mouseRay = getRayAtCurrentMousePosition();
1219 BOARD_ITEM* intersectedBoardItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
1220 FOOTPRINT* footprint = nullptr;
1221
1222 if( intersectedBoardItem )
1223 {
1224 switch( intersectedBoardItem->Type() )
1225 {
1226 case PCB_FOOTPRINT_T: footprint = static_cast<FOOTPRINT*>( intersectedBoardItem ); break;
1227
1228 case PCB_PAD_T: footprint = static_cast<PAD*>( intersectedBoardItem )->GetParentFootprint(); break;
1229
1230 case PCB_FIELD_T: footprint = static_cast<PCB_FIELD*>( intersectedBoardItem )->GetParentFootprint(); break;
1231
1232 default: break;
1233 }
1234 }
1235
1236 // We send a message (by ExpressMail) to the board and schematic editor, but only
1237 // if the manager of this canvas is a EDA_3D_VIEWER_FRAME, because only this
1238 // kind of frame has ExpressMail stuff
1239 if( EDA_3D_VIEWER_FRAME* frame = dynamic_cast<EDA_3D_VIEWER_FRAME*>( wxGetTopLevelParent( this ) ) )
1240 {
1241 kiapi::common::commands::SyncSelection sync;
1242
1243 if( footprint )
1244 sync.add_items()->mutable_footprint()->set_reference( footprint->GetReference().ToUTF8() );
1245
1246 sync.set_mode( kiapi::common::commands::SyncSelectionMode::SSM_ITEMS_ONLY );
1247 sync.set_context( kiapi::common::commands::SyncSelectionContext::SSC_IMPLICIT );
1248
1249 std::string payload;
1250 kiapi::common::PackKiwayApiMessage( sync, payload );
1251
1252 frame->Kiway().ExpressMail( FRAME_PCB_EDITOR, MAIL_SELECTION, payload, frame );
1253 frame->Kiway().ExpressMail( FRAME_SCH, MAIL_SELECTION, payload, frame );
1254 }
1255 }
1256
1257 Refresh();
1258}
1259
1260
1261void EDA_3D_CANVAS::OnRightDown( wxMouseEvent& event )
1262{
1263 SetFocus();
1265
1266 // Ensure m_camera.m_lastPosition is up to date for future drag events.
1267 OnMouseMoveCamera( event );
1268}
1269
1270
1271void EDA_3D_CANVAS::OnRightUp( wxMouseEvent& event )
1272{
1273 if( m_camera_is_moving )
1274 return;
1275
1276 if( m_mouse_is_moving )
1277 {
1278 m_mouse_is_moving = false;
1280 }
1281}
1282
1283
1284void EDA_3D_CANVAS::OnMiddleDown( wxMouseEvent& event )
1285{
1286 SetFocus();
1288}
1289
1290
1291void EDA_3D_CANVAS::OnMiddleUp( wxMouseEvent& event )
1292{
1293 if( m_camera_is_moving )
1294 return;
1295
1296 if( m_mouse_is_moving )
1297 {
1298 m_mouse_is_moving = false;
1300 }
1301 else
1302 {
1304 }
1305}
1306
1307
1308void EDA_3D_CANVAS::OnTimerTimeout_Editing( wxTimerEvent& aEvent )
1309{
1310 if( aEvent.GetId() != m_editing_timeout_timer.GetId() )
1311 {
1312 aEvent.Skip();
1313 return;
1314 }
1315
1316 m_mouse_is_moving = false;
1317 m_mouse_was_moved = false;
1318
1320}
1321
1322
1327
1328
1330{
1331 if( m_3d_render )
1332 m_editing_timeout_timer.Start( m_3d_render->GetWaitForEditingTimeOut(), wxTIMER_ONE_SHOT );
1333}
1334
1335
1336void EDA_3D_CANVAS::OnTimerTimeout_Redraw( wxTimerEvent& aEvent )
1337{
1338 if( aEvent.GetId() != m_redraw_trigger_timer.GetId() )
1339 {
1340 aEvent.Skip();
1341 return;
1342 }
1343
1344 Request_refresh( true );
1345}
1346
1347
1348void EDA_3D_CANVAS::OnRefreshRequest( wxEvent& aEvent )
1349{
1350 Refresh();
1351}
1352
1353
1354void EDA_3D_CANVAS::Request_refresh( bool aRedrawImmediately )
1355{
1356 if( aRedrawImmediately )
1357 {
1358 // Just calling Refresh() does not work always
1359 // Using an event to call DoRepaint ensure the repaint code will be executed,
1360 // and PostEvent will take priority to other events like mouse movements, keys, etc.
1361 // and is executed during the next idle time
1362 wxCommandEvent redrawEvent( wxEVT_REFRESH_CUSTOM_COMMAND, ID_CUSTOM_EVENT_1 );
1363 wxPostEvent( this, redrawEvent );
1364 }
1365 else
1366 {
1367 // Schedule a timed redraw
1368 m_redraw_trigger_timer.Start( 10 , wxTIMER_ONE_SHOT );
1369 }
1370}
1371
1372
1373void EDA_3D_CANVAS::request_start_moving_camera( float aMovingSpeed, bool aRenderPivot )
1374{
1375 wxASSERT( aMovingSpeed > FLT_EPSILON );
1376
1377 // Fast forward the animation if the animation is disabled
1378 if( !m_animation_enabled )
1379 {
1380 m_camera.Interpolate( 1.0f );
1381 DisplayStatus();
1383 return;
1384 }
1385
1386 // Map speed multiplier option to actual multiplier value
1387 // [1,2,3,4,5] -> [0.25, 0.5, 1, 2, 4]
1388 aMovingSpeed *= static_cast<float>( ( 1 << m_moving_speed_multiplier ) ) / 8.0f;
1389
1390 m_render_pivot = aRenderPivot;
1391 m_camera_moving_speed = aMovingSpeed;
1392
1394
1395 DisplayStatus();
1397
1398 m_camera_is_moving = true;
1399
1401}
1402
1403
1405{
1406 RAY mouseRay = getRayAtCurrentMousePosition();
1407
1408 float hit_t = 0.0f;
1409
1410 // Test it with the board bounding box
1411 if( m_boardAdapter.GetBBox().Intersect( mouseRay, &hit_t ) )
1412 {
1413 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
1414 m_camera.SetT0_and_T1_current_T();
1415 m_camera.SetLookAtPos_T1( mouseRay.at( hit_t ) );
1416 m_camera.ResetXYpos_T1();
1417
1419 }
1420}
1421
1422
1424{
1425 if( m_camera_is_moving )
1426 return false;
1427
1428 const float delta_move = m_delta_move_step_factor * m_camera.GetZoom();
1429 const float arrow_moving_time_speed = 8.0f;
1430
1431 switch( aRequestedView )
1432 {
1435 return true;
1436
1438 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
1439 m_camera.SetT0_and_T1_current_T();
1440 m_camera.Pan_T1( SFVEC3F( -delta_move, 0.0f, 0.0f ) );
1441 request_start_moving_camera( arrow_moving_time_speed, false );
1442 return true;
1443
1445 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
1446 m_camera.SetT0_and_T1_current_T();
1447 m_camera.Pan_T1( SFVEC3F( +delta_move, 0.0f, 0.0f ) );
1448 request_start_moving_camera( arrow_moving_time_speed, false );
1449 return true;
1450
1452 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
1453 m_camera.SetT0_and_T1_current_T();
1454 m_camera.Pan_T1( SFVEC3F( 0.0f, +delta_move, 0.0f ) );
1455 request_start_moving_camera( arrow_moving_time_speed, false );
1456 return true;
1457
1459 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
1460 m_camera.SetT0_and_T1_current_T();
1461 m_camera.Pan_T1( SFVEC3F( 0.0f, -delta_move, 0.0f ) );
1462 request_start_moving_camera( arrow_moving_time_speed, false );
1463 return true;
1464
1466 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
1467 m_camera.SetT0_and_T1_current_T();
1468 m_camera.Reset_T1();
1469 request_start_moving_camera( glm::min( glm::max( m_camera.GetZoom(), 1 / 1.26f ), 1.26f ) );
1470 return true;
1471
1473 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
1474 m_camera.SetT0_and_T1_current_T();
1475
1476 if( m_camera.Zoom_T1( 1.26f ) ) // 3 steps per doubling
1478
1479 return true;
1480
1482 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
1483 m_camera.SetT0_and_T1_current_T();
1484
1485 if( m_camera.Zoom_T1( 1/1.26f ) ) // 3 steps per halving
1487
1488 return true;
1489
1495 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
1496 m_camera.SetT0_and_T1_current_T();
1497 m_camera.ViewCommand_T1( aRequestedView );
1499 return true;
1500
1503 m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
1504 m_camera.SetT0_and_T1_current_T();
1505 m_camera.ViewCommand_T1( aRequestedView );
1506 request_start_moving_camera( glm::min( glm::max( m_camera.GetZoom(), 0.5f ), 1.125f ) );
1507 return true;
1508
1509 default:
1510 return false;
1511 }
1512}
1513
1514
1516{
1518 {
1519 switch( cfg->m_Render.engine )
1520 {
1523 default: m_3d_render = nullptr; break;
1524 }
1525 }
1526
1527 if( m_3d_render )
1528 m_3d_render->ReloadRequest();
1529
1530 m_mouse_was_moved = false;
1531
1533}
1534
1535
1537{
1538 if( m_overlays.empty() )
1539 return;
1540
1541 glMatrixMode( GL_PROJECTION );
1542 glLoadMatrixf( glm::value_ptr( m_camera.GetProjectionMatrix() ) );
1543
1544 glMatrixMode( GL_MODELVIEW );
1545 glLoadMatrixf( glm::value_ptr( m_camera.GetViewMatrix() ) );
1546
1547 glDisable( GL_LIGHTING );
1548 glDisable( GL_CULL_FACE );
1549 glEnable( GL_COLOR_MATERIAL );
1550 glEnable( GL_BLEND );
1551 glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
1552 glDepthMask( GL_FALSE );
1553 glEnable( GL_POLYGON_OFFSET_FILL );
1554 glPolygonOffset( -2.0f, -2.0f );
1555
1556 glDisableClientState( GL_TEXTURE_COORD_ARRAY );
1557 glDisableClientState( GL_COLOR_ARRAY );
1558 glDisableClientState( GL_NORMAL_ARRAY );
1559 glEnableClientState( GL_VERTEX_ARRAY );
1560
1561 // The raytracer paints a textured quad and leaves no usable depth buffer.
1562 const bool depthUsable = m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL;
1563
1564 for( const OVERLAY& overlay : m_overlays )
1565 {
1566 if( overlay.vertices.empty() )
1567 continue;
1568
1569 if( depthUsable && !overlay.alwaysVisible )
1570 glEnable( GL_DEPTH_TEST );
1571 else
1572 glDisable( GL_DEPTH_TEST );
1573
1574 glPushMatrix();
1575 glMultMatrixf( glm::value_ptr( overlay.transform ) );
1576 glColor4f( overlay.color.r, overlay.color.g, overlay.color.b, overlay.color.a );
1577 glVertexPointer( 3, GL_FLOAT, 0, overlay.vertices.data() );
1578 glDrawArrays( GL_TRIANGLES, 0, (GLsizei) overlay.vertices.size() );
1579 glPopMatrix();
1580 }
1581
1582 glDisableClientState( GL_VERTEX_ARRAY );
1583 glDisable( GL_POLYGON_OFFSET_FILL );
1584 glDepthMask( GL_TRUE );
1585 glDisable( GL_BLEND );
1586 glEnable( GL_DEPTH_TEST );
1587}
1588
1589
1590std::optional<EDA_3D_CANVAS::MODEL_HIT> EDA_3D_CANVAS::PickModel(
1591 const RAY& aRay, const S3DMODEL& aGeometry, const FP_3DMODEL& aModel,
1592 const FOOTPRINT& aFootprint ) const
1593{
1594 glm::mat4 world = m_boardAdapter.GetFootprintMatrix( aFootprint ) * CalcModelMatrix(
1595 SFVEC3F( aModel.m_Offset.x, aModel.m_Offset.y, aModel.m_Offset.z ),
1596 SFVEC3F( aModel.m_Rotation.x, aModel.m_Rotation.y, aModel.m_Rotation.z ),
1597 SFVEC3F( aModel.m_Scale.x, aModel.m_Scale.y, aModel.m_Scale.z ) );
1598 const double determinant = glm::determinant( glm::dmat4( world ) );
1599
1600 if( !aGeometry.m_Meshes || !std::isfinite( determinant ) || determinant == 0.0 )
1601 return std::nullopt;
1602
1603 const glm::dmat4 inverse = glm::inverse( glm::dmat4( world ) );
1604 const glm::dvec3 origin( inverse * glm::dvec4( aRay.m_Origin, 1.0 ) );
1605 const glm::dvec3 direction( inverse * glm::dvec4( aRay.m_Dir, 0.0 ) );
1606 double nearest = std::numeric_limits<double>::infinity();
1607 std::optional<MODEL_HIT> hit;
1608
1609 for( unsigned int i = 0; i < aGeometry.m_MeshesSize; ++i )
1610 {
1611 const SMESH& mesh = aGeometry.m_Meshes[i];
1612
1613 if( !mesh.m_Positions || !mesh.m_FaceIdx )
1614 continue;
1615
1616 for( unsigned int j = 0; j + 2 < mesh.m_FaceIdxSize; j += 3 )
1617 {
1618 if( !IsTriangleInRange( mesh.m_FaceIdx, j, mesh.m_VertexSize ) )
1619 continue;
1620
1621 const glm::dvec3 a( mesh.m_Positions[mesh.m_FaceIdx[j]] );
1622 const glm::dvec3 edge1 = glm::dvec3( mesh.m_Positions[mesh.m_FaceIdx[j + 1]] ) - a;
1623 const glm::dvec3 edge2 = glm::dvec3( mesh.m_Positions[mesh.m_FaceIdx[j + 2]] ) - a;
1624 const glm::dvec3 p = glm::cross( direction, edge2 );
1625 const double det = glm::dot( edge1, p );
1626 const double tolerance = 1e-12 * glm::length( edge1 ) * glm::length( p );
1627
1628 if( !std::isfinite( det ) || std::abs( det ) <= tolerance )
1629 continue;
1630
1631 const glm::dvec3 delta = origin - a;
1632 const double u = glm::dot( delta, p ) / det;
1633 const glm::dvec3 q = glm::cross( delta, edge1 );
1634 const double v = glm::dot( direction, q ) / det;
1635 const double distance = glm::dot( edge2, q ) / det;
1636
1637 if( u >= 0.0 && v >= 0.0 && u + v <= 1.0 && distance >= 0.0 && distance < nearest )
1638 {
1639 nearest = distance;
1640 hit = MODEL_HIT{ i, j };
1641 }
1642 }
1643 }
1644
1645 return hit;
1646}
1647
1648
1649PAD* EDA_3D_CANVAS::PickFootprintPad( const RAY& aRay, const FOOTPRINT& aFootprint ) const
1650{
1651 const double units = m_boardAdapter.BiuTo3dUnits();
1652
1653 if( units <= 0.0 || std::abs( aRay.m_Dir.z ) < 1e-9 )
1654 return nullptr;
1655
1656 const double z = m_boardAdapter.GetFootprintZPos( aFootprint.IsFlipped() );
1657 const double distance = ( z - aRay.m_Origin.z ) / aRay.m_Dir.z;
1658 const double x = ( aRay.m_Origin.x + distance * aRay.m_Dir.x ) / units;
1659 const double y = -( aRay.m_Origin.y + distance * aRay.m_Dir.y ) / units;
1660
1661 if( distance < 0.0 || !std::isfinite( x ) || !std::isfinite( y )
1662 || std::abs( x ) > std::numeric_limits<int>::max()
1663 || std::abs( y ) > std::numeric_limits<int>::max() )
1664 {
1665 return nullptr;
1666 }
1667
1668 const VECTOR2I point( KiROUND( x ), KiROUND( y ) );
1669 const PCB_LAYER_ID copper = aFootprint.IsFlipped() ? B_Cu : F_Cu;
1670
1671 for( PAD* pad : aFootprint.Pads() )
1672 {
1673 if( pad->HitTest( point, 0, copper ) )
1674 return pad;
1675 }
1676
1677 return nullptr;
1678}
1679
1680
1682{
1683 SFVEC3F rayOrigin;
1684 SFVEC3F rayDir;
1685
1686 // Generate a ray origin and direction based on current mouser position and camera
1687 m_camera.MakeRayAtCurrentMousePosition( rayOrigin, rayDir );
1688
1689 RAY mouseRay;
1690 mouseRay.Init( rayOrigin, rayDir );
1691
1692 return mouseRay;
1693}
VIEW3D_TYPE
Definition 3d_enums.h:74
@ VIEW3D_ZOOM_OUT
Definition 3d_enums.h:90
@ VIEW3D_PAN_LEFT
Definition 3d_enums.h:87
@ VIEW3D_FIT_SCREEN
Definition 3d_enums.h:94
@ VIEW3D_ZOOM_IN
Definition 3d_enums.h:89
@ VIEW3D_PIVOT_CENTER
Definition 3d_enums.h:91
@ VIEW3D_BOTTOM
Definition 3d_enums.h:77
@ VIEW3D_PAN_UP
Definition 3d_enums.h:85
@ VIEW3D_PAN_DOWN
Definition 3d_enums.h:86
@ VIEW3D_PAN_RIGHT
Definition 3d_enums.h:88
glm::mat4 CalcModelMatrix(const SFVEC3F &aOffset, const SFVEC3F &aRotation, const SFVEC3F &aScale)
Build the model-to-footprint transform used by rendering and picking.
Definition 3d_math.cpp:34
Defines math related functions.
@ ID_CUSTOM_EVENT_1
@ ID_DISABLE_RAY_TRACING
int index
constexpr BOX2I KiROUND(const BOX2D &aBoxD)
Definition box2.h:982
void SetOpenGLInfo(const char *aVendor, const char *aRenderer, const char *aVersion)
A setter for OpenGL info when it's initialized.
void SetOpenGLBackendInfo(wxString aBackend)
A setter for OpenGL backend info after the canvas is created.
define an internal structure to be used by the 3D renders
bool IsTriangleInRange(const unsigned int *aFaceIdx, unsigned int aTriangleIdx, unsigned int aVertexCount)
Test whether the three face indices of a triangle all reference valid vertices.
Definition c3dmodel.h:100
Helper class to handle information needed to display 3D board.
A base class derived from BOARD_ITEM for items that can be connected and have a net,...
A base class for any item which can be embedded within the BOARD container class, and therefore insta...
Definition board_item.h:84
Information pertinent to a Pcbnew printed circuit board.
Definition board.h:410
A class used to derive camera objects from.
Definition camera.h:99
Implement a canvas based on a wxGLCanvas.
TOOL_DISPATCHER * m_eventDispatcher
void OnEvent(wxEvent &aEvent)
Used to forward events to the canvas from popups, etc.
void OnMagnify(wxMouseEvent &event)
void restart_editingTimeOut_Timer()
Reset the editing timer.
void RenderToFrameBuffer(unsigned char *aBuffer, int aWidth, int aHeight)
BOARD_ITEM * m_currentRollOverItem
RENDER_3D_BASE * m_3d_render
Non-owning pointer to the active renderer (one of the two below).
std::function< void(const std::optional< RAY > &)> m_hoverHandler
bool m_is_opengl_initialized
void OnResize(wxSizeEvent &event)
bool m_render3dmousePivot
void OnMouseWheel(wxMouseEvent &event)
WX_INFOBAR * m_parentInfoBar
wxTimer m_editing_timeout_timer
int64_t m_strtime_camera_movement
void render_overlays()
Draw the translucent overlays over the rendered scene.
void OnLeftDown(wxMouseEvent &event)
void OnPanGesture(wxPanGestureEvent &event)
void OnRightUp(wxMouseEvent &event)
wxGLContext * m_glRC
ACCELERATOR_3D * m_accelerator3DShapes
std::function< bool(const RAY &)> m_pickHandler
void OnTimerTimeout_Redraw(wxTimerEvent &event)
wxStatusBar * m_parentStatusBar
void DoRePaint()
The actual function to repaint the canvas.
std::shared_ptr< SYNC_REPORTER > m_warningReporterSync
void OnRightDown(wxMouseEvent &event)
void InvalidateRaytracingHitTesting()
Invalidate the hover hit-test BVH before board layers are rebuilt.
int m_moving_speed_multiplier
void JoinBgWorker()
Block until any in-progress OpenGL background loading has finished.
bool m_is_opengl_version_supported
wxTimer m_redraw_trigger_timer
BOARD_ADAPTER & m_boardAdapter
void DisplayStatus()
Update the status bar with the position information.
void render3dmousePivot(float aScale)
Render the 3dmouse pivot cursor.
void OnPaint(wxPaintEvent &aEvent)
std::vector< OVERLAY > m_overlays
void RenderRaytracingRequest()
Request to render the current view in Raytracing mode.
void SetEventDispatcher(TOOL_DISPATCHER *aEventDispatcher)
Set a dispatcher that processes events and forwards them to tools.
std::optional< MODEL_HIT > PickModel(const RAY &aRay, const S3DMODEL &aGeometry, const FP_3DMODEL &aModel, const FOOTPRINT &aFootprint) const
Pick current mesh geometry; independent of the renderer's cached hit-test scene.
bool m_render_raytracing_was_requested
float m_camera_moving_speed
void ReloadRaytracingForHitTesting(std::stop_token aStop)
Rebuild the auxiliary raytracing BVH used for hover hit-testing in OpenGL mode.
PAD * PickFootprintPad(const RAY &aRay, const FOOTPRINT &aFootprint) const
Project onto the footprint's seating plane and hit-test copper pads.
void OnLeftUp(wxMouseEvent &event)
void ReloadRequest(BOARD *aBoard=nullptr, S3D_CACHE *aCachePointer=nullptr)
RAY getRayAtCurrentMousePosition()
double m_gestureLastZoomFactor
Used to track gesture events.
void OnCloseWindow(wxCloseEvent &event)
bool SetView3D(VIEW3D_TYPE aRequestedView)
Select a specific 3D view or operation.
void OnMiddleDown(wxMouseEvent &event)
void GetScreenshot(wxImage &aDstImage)
Request a screenshot and output it to the aDstImage.
std::unique_ptr< INFOBAR_REPORTER > m_infoBarReporter
void OnZoomGesture(wxZoomGestureEvent &event)
std::unique_ptr< STATUSBAR_REPORTER > m_statusBarReporter
std::unique_ptr< RENDER_3D_OPENGL > m_3d_render_opengl
void OnMiddleUp(wxMouseEvent &event)
void render_pivot(float t, float aScale)
Render the pivot cursor.
void request_start_moving_camera(float aMovingSpeed=2.0f, bool aRenderPivot=true)
Start a camera movement.
void RenderEngineChanged()
Notify that the render engine was changed.
std::shared_ptr< SYNC_REPORTER > m_activityReporterSync
void OnMouseLeave(wxMouseEvent &event)
std::atomic_flag m_is_currently_painting
void OnRefreshRequest(wxEvent &aEvent)
void OnEraseBackground(wxEraseEvent &event)
void releaseOpenGL()
Free created targets and openGL context.
std::unique_ptr< RENDER_3D_RAYTRACE_GL > m_3d_render_raytracing
void Request_refresh(bool aRedrawImmediately=true)
Schedule a refresh update of the canvas.
bool m_opengl_supports_raytracing
~EDA_3D_CANVAS() override
void OnMouseMove(wxMouseEvent &event)
void OnRotateGesture(wxRotateGestureEvent &event)
EDA_3D_CANVAS(wxWindow *aParent, const wxGLAttributes &aGLAttribs, BOARD_ADAPTER &aSettings, CAMERA &aCamera, S3D_CACHE *a3DCachePointer)
Create a new 3D Canvas with an attribute list.
void move_pivot_based_on_cur_mouse_position()
This function hits a ray to the board and start a movement.
double m_gestureLastAngle
void OnTimerTimeout_Editing(wxTimerEvent &event)
void stop_editingTimeOut_Timer()
Stop the editing time so it will not timeout.
Create and handle a window for the 3d viewer connected to a Kiway and a pcbboard.
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:110
std::deque< PAD * > & Pads()
Definition footprint.h:405
bool IsFlipped() const
Definition footprint.h:662
const wxString & GetValue() const
Definition footprint.h:933
const wxString & GetReference() const
Definition footprint.h:909
VECTOR3D m_Offset
3D model offset (mm)
Definition footprint.h:184
VECTOR3D m_Rotation
3D model rotation (degrees)
Definition footprint.h:183
VECTOR3D m_Scale
3D model scaling factor (dimensionless)
Definition footprint.h:182
void UnlockCtx(wxGLContext *aContext)
Allow other canvases to bind an OpenGL context.
void DestroyCtx(wxGLContext *aContext)
Destroy a managed OpenGL context.
bool LockCtx(wxGLContext *aContext, wxGLCanvas *aCanvas)
Set a context as current and prevents other canvases from switching it.
wxGLContext * CreateCtx(wxGLCanvas *aCanvas, const wxGLContext *aOther=nullptr)
Create a managed OpenGL context.
static int SetSwapInterval(wxGLCanvas *aCanvas, int aVal)
Attempt to set the OpenGL swap interval.
Definition gl_utils.cpp:78
static wxString DetectGLBackend(wxGLCanvas *aCanvas)
Definition gl_utils.cpp:50
Provides basic 3D controls ( zoom, rotate, translate, ... )
static const float m_delta_move_step_factor
HIDPI_GL_3D_CANVAS(const KIGFX::VC_SETTINGS &aVcSettings, CAMERA &aCamera, wxWindow *parent, const wxGLAttributes &aGLAttribs, wxWindowID id=wxID_ANY, const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxDefaultSize, long style=0, const wxString &name=wxGLCanvasName, const wxPalette &palette=wxNullPalette)
void OnMouseWheelCamera(wxMouseEvent &event, bool aPan)
void OnMouseMoveCamera(wxMouseEvent &event)
virtual wxSize GetNativePixelSize() const
Definition pad.h:61
GL_CONTEXT_MANAGER * GetGLContextManager()
Definition pgm_base.h:113
Cache for storing the 3D shapes.
Definition 3d_cache.h:53
GizmoSphereSelection
Enum to indicate which sphere (direction) is selected.
@ Count
Number of selectable spheres.
A wrapper for reporting to a specific text location in a statusbar.
Definition reporter.h:410
Handle a list of polygons defining a copper zone.
Definition zone.h:70
bool GetIsRuleArea() const
Accessors to parameters used in Rule Area zones:
Definition zone.h:832
const wxString & GetZoneName() const
Definition zone.h:160
bool IsOnCopperLayer() const override
Definition zone.cpp:615
#define _(s)
wxDEFINE_EVENT(wxEVT_REFRESH_CUSTOM_COMMAND, wxCommandEvent)
#define EDA_3D_CANVAS_ID
Declaration of the eda_3d_viewer class.
@ ZOOM_LEVEL
@ HOVERED_ITEM
@ RENDER_TIME
@ FRAME_PCB_EDITOR
Definition frame_type.h:38
@ FRAME_SCH
Definition frame_type.h:30
static const wxChar * m_logTrace
Trace mask used to enable or disable the trace output of this class.
static wxString From_UTF8(const char *cstring)
Convert a UTF8 encoded C string to a wxString for all wxWidgets build modes.
Definition idf_helpers.h:44
PCB_LAYER_ID
A quick note on layer IDs:
Definition layer_ids.h:56
@ B_Cu
Definition layer_ids.h:61
@ F_Cu
Definition layer_ids.h:60
This file contains miscellaneous commonly used macros and functions.
@ MAIL_SELECTION
Definition mail_type.h:36
KICOMMON_API bool PackKiwayApiMessage(const google::protobuf::Message &aMessage, std::string &aBytes)
EDA_ANGLE abs(const EDA_ANGLE &aAngle)
Definition eda_angle.h:437
void OglGetScreenshot(wxImage &aDstImage)
Get the pixel data of current OpenGL image.
Definition ogl_utils.cpp:32
Define generic OpenGL functions that are common to any OpenGL target.
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
std::shared_ptr< PNS_LOG_VIEWER_OVERLAY > overlay
int64_t GetRunningMicroSecs()
An alternate way to calculate an elapsed time (in microsecondes) to class PROF_COUNTER.
static float distance(const SFVEC2UI &a, const SFVEC2UI &b)
@ RPT_SEVERITY_ERROR
T * GetAppSettings(const char *aFilename)
const int scale
A translucent triangle soup drawn over the scene.
Definition ray.h:59
SFVEC3F m_Dir
Definition ray.h:63
void Init(const SFVEC3F &o, const SFVEC3F &d)
Definition ray.cpp:31
SFVEC3F m_Origin
Definition ray.h:60
SFVEC3F at(float t) const
Definition ray.h:80
Store the a model based on meshes and materials.
Definition c3dmodel.h:111
unsigned int m_MeshesSize
Number of meshes in the array.
Definition c3dmodel.h:112
SMESH * m_Meshes
The meshes list of this model.
Definition c3dmodel.h:113
Per-vertex normal/color/texcoors structure.
Definition c3dmodel.h:77
unsigned int * m_FaceIdx
Triangle Face Indexes.
Definition c3dmodel.h:84
unsigned int m_VertexSize
Number of vertex in the arrays.
Definition c3dmodel.h:78
unsigned int m_FaceIdxSize
Number of elements of the m_FaceIdx array.
Definition c3dmodel.h:83
SFVEC3F * m_Positions
Vertex position array.
Definition c3dmodel.h:79
IbisParser parser & reporter
int delta
@ PCB_VIA_T
class PCB_VIA, a via (like a track segment on a copper layer)
Definition typeinfo.h:89
@ PCB_ZONE_T
class ZONE, a copper pour area
Definition typeinfo.h:100
@ PCB_FIELD_T
class PCB_FIELD, text associated with a footprint property
Definition typeinfo.h:82
@ PCB_FOOTPRINT_T
class FOOTPRINT, a footprint
Definition typeinfo.h:78
@ PCB_PAD_T
class PAD, a pad in a footprint
Definition typeinfo.h:79
@ PCB_ARC_T
class PCB_ARC, an arc track segment on a copper layer
Definition typeinfo.h:90
@ PCB_TRACE_T
class PCB_TRACK, a track segment (segment on a copper layer)
Definition typeinfo.h:88
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708
unsigned int GLenum
Definition vrml_layer.h:43
glm::vec3 SFVEC3F
Definition xv3d_types.h:40