7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-02 21:26:54 +00:00
kicad/3d-viewer/3d_canvas/eda_3d_canvas.cpp
JamesJCode c80a71f64a Make netclass name methods clearer, and improve doc strings
There are two netclass name methods, which previously were not
obvious in their uses. These have been renamed to now have:

GetName() : Used for internal or tooling (e.g. netlist export) usage
GetHumanReadableName() : Used for display to users (e.g. in infobars)

Fixing the previous unclear naming will result in fewer bugs
when users start using the multiple netclass functionality, as
the incorrect usage had started creeping in to new code. Also this
will help authors of new code select the correct name method.
2025-01-14 20:44:09 +00:00

1130 lines
33 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015-2016 Mario Luzeiro <mrluzeiro@ua.pt>
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include <gal/opengl/kiglew.h> // Must be included first
#include <gal/opengl/gl_utils.h>
#include <wx/tokenzr.h>
#include "../common_ogl/ogl_utils.h"
#include "eda_3d_canvas.h"
#include <eda_3d_viewer_frame.h>
#include <3d_rendering/raytracing/render_3d_raytrace_gl.h>
#include <3d_rendering/opengl/render_3d_opengl.h>
#include <3d_viewer_id.h>
#include <advanced_config.h>
#include <build_version.h>
#include <board.h>
#include <reporter.h>
#include <gal/opengl/gl_context_mgr.h>
#include <core/profile.h> // To use GetRunningMicroSecs or another profiling utility
#include <bitmaps.h>
#include <macros.h>
#include <pgm_base.h>
#include <settings/settings_manager.h>
#include <tool/tool_dispatcher.h>
#include <widgets/wx_busy_indicator.h>
/**
* Flag to enable 3D canvas debug tracing.
*
* Use "KI_TRACE_EDA_3D_CANVAS" to enable.
*
* @ingroup trace_env_vars
*/
const wxChar* EDA_3D_CANVAS::m_logTrace = wxT( "KI_TRACE_EDA_3D_CANVAS" );
// A custom event, used to call DoRePaint during an idle time
wxDEFINE_EVENT( wxEVT_REFRESH_CUSTOM_COMMAND, wxEvent );
BEGIN_EVENT_TABLE( EDA_3D_CANVAS, HIDPI_GL_3D_CANVAS )
EVT_PAINT( EDA_3D_CANVAS::OnPaint )
// mouse events
EVT_LEFT_DOWN( EDA_3D_CANVAS::OnLeftDown )
EVT_LEFT_UP( EDA_3D_CANVAS::OnLeftUp )
EVT_MIDDLE_UP( EDA_3D_CANVAS::OnMiddleUp )
EVT_MIDDLE_DOWN( EDA_3D_CANVAS::OnMiddleDown)
EVT_MOUSEWHEEL( EDA_3D_CANVAS::OnMouseWheel )
EVT_MOTION( EDA_3D_CANVAS::OnMouseMove )
EVT_MAGNIFY( EDA_3D_CANVAS::OnMagnify )
// touch gesture events
EVT_GESTURE_ZOOM( wxID_ANY, EDA_3D_CANVAS::OnZoomGesture )
EVT_GESTURE_PAN( wxID_ANY, EDA_3D_CANVAS::OnPanGesture )
EVT_GESTURE_ROTATE( wxID_ANY, EDA_3D_CANVAS::OnRotateGesture )
// other events
EVT_ERASE_BACKGROUND( EDA_3D_CANVAS::OnEraseBackground )
EVT_CUSTOM(wxEVT_REFRESH_CUSTOM_COMMAND, ID_CUSTOM_EVENT_1, EDA_3D_CANVAS::OnRefreshRequest )
EVT_CLOSE( EDA_3D_CANVAS::OnCloseWindow )
EVT_SIZE( EDA_3D_CANVAS::OnResize )
END_EVENT_TABLE()
EDA_3D_CANVAS::EDA_3D_CANVAS( wxWindow* aParent, const wxGLAttributes& aGLAttribs,
BOARD_ADAPTER& aBoardAdapter, CAMERA& aCamera,
S3D_CACHE* a3DCachePointer ) :
HIDPI_GL_3D_CANVAS( EDA_DRAW_PANEL_GAL::GetVcSettings(), aCamera, aParent, aGLAttribs,
EDA_3D_CANVAS_ID, wxDefaultPosition,
wxDefaultSize, wxFULL_REPAINT_ON_RESIZE ),
m_eventDispatcher( nullptr ),
m_parentStatusBar( nullptr ),
m_parentInfoBar( nullptr ),
m_glRC( nullptr ),
m_is_opengl_initialized( false ),
m_is_opengl_version_supported( true ),
m_editing_timeout_timer( this, wxID_HIGHEST + 1 ),
m_redraw_trigger_timer( this, wxID_HIGHEST + 2 ),
m_render_pivot( false ),
m_camera_moving_speed( 1.0f ),
m_strtime_camera_movement( 0 ),
m_animation_enabled( true ),
m_moving_speed_multiplier( 3 ),
m_boardAdapter( aBoardAdapter ),
m_3d_render( nullptr ),
m_opengl_supports_raytracing( true ),
m_render_raytracing_was_requested( false ),
m_accelerator3DShapes( nullptr ),
m_currentRollOverItem( nullptr )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::EDA_3D_CANVAS" ) );
m_editing_timeout_timer.SetOwner( this );
Connect( m_editing_timeout_timer.GetId(), wxEVT_TIMER,
wxTimerEventHandler( EDA_3D_CANVAS::OnTimerTimeout_Editing ), nullptr, this );
m_redraw_trigger_timer.SetOwner( this );
Connect( m_redraw_trigger_timer.GetId(), wxEVT_TIMER,
wxTimerEventHandler( EDA_3D_CANVAS::OnTimerTimeout_Redraw ), nullptr, this );
m_is_currently_painting.clear();
m_3d_render_raytracing = new RENDER_3D_RAYTRACE_GL( this, m_boardAdapter, m_camera );
m_3d_render_opengl = new RENDER_3D_OPENGL( this, m_boardAdapter, m_camera );
wxASSERT( m_3d_render_raytracing != nullptr );
wxASSERT( m_3d_render_opengl != nullptr );
auto busy_indicator_factory =
[]()
{
return std::make_unique<WX_BUSY_INDICATOR>();
};
m_3d_render_raytracing->SetBusyIndicatorFactory( busy_indicator_factory );
m_3d_render_opengl->SetBusyIndicatorFactory( busy_indicator_factory );
// We always start with the opengl engine (raytracing is avoided due to very
// long calculation time)
m_3d_render = m_3d_render_opengl;
m_boardAdapter.ReloadColorSettings();
wxASSERT( a3DCachePointer != nullptr );
m_boardAdapter.Set3dCacheManager( a3DCachePointer );
#if defined( __WXMSW__ )
EnableTouchEvents( wxTOUCH_ZOOM_GESTURE | wxTOUCH_ROTATE_GESTURE | wxTOUCH_PAN_GESTURES );
#elif defined( __WXGTK__ )
EnableTouchEvents( wxTOUCH_ZOOM_GESTURE | wxTOUCH_ROTATE_GESTURE );
#endif
const wxEventType events[] =
{
// Binding both EVT_CHAR and EVT_CHAR_HOOK ensures that all key events,
// especially special key like arrow keys, are handled by the GAL event dispatcher,
// and not sent to GUI without filtering, because they have a default action (scroll)
// that must not be called.
wxEVT_LEFT_UP, wxEVT_LEFT_DOWN, wxEVT_LEFT_DCLICK,
wxEVT_RIGHT_UP, wxEVT_RIGHT_DOWN, wxEVT_RIGHT_DCLICK,
wxEVT_MIDDLE_UP, wxEVT_MIDDLE_DOWN, wxEVT_MIDDLE_DCLICK,
wxEVT_MOTION, wxEVT_MOUSEWHEEL, wxEVT_CHAR, wxEVT_CHAR_HOOK,
wxEVT_MAGNIFY,
wxEVT_MENU_OPEN, wxEVT_MENU_CLOSE, wxEVT_MENU_HIGHLIGHT
};
for( wxEventType eventType : events )
Connect( eventType, wxEventHandler( EDA_3D_CANVAS::OnEvent ), nullptr, m_eventDispatcher );
}
EDA_3D_CANVAS::~EDA_3D_CANVAS()
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::~EDA_3D_CANVAS" ) );
delete m_accelerator3DShapes;
m_accelerator3DShapes = nullptr;
releaseOpenGL();
}
void EDA_3D_CANVAS::releaseOpenGL()
{
if( m_glRC )
{
GL_CONTEXT_MANAGER* gl_mgr = Pgm().GetGLContextManager();
gl_mgr->LockCtx( m_glRC, this );
delete m_3d_render_raytracing;
m_3d_render_raytracing = nullptr;
delete m_3d_render_opengl;
m_3d_render_opengl = nullptr;
// This is just a copy of a pointer, can safely be set to NULL.
m_3d_render = nullptr;
gl_mgr->UnlockCtx( m_glRC );
gl_mgr->DestroyCtx( m_glRC );
m_glRC = nullptr;
}
}
void EDA_3D_CANVAS::OnCloseWindow( wxCloseEvent& event )
{
releaseOpenGL();
event.Skip();
}
void EDA_3D_CANVAS::OnResize( wxSizeEvent& event )
{
Request_refresh();
}
bool EDA_3D_CANVAS::initializeOpenGL()
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::initializeOpenGL" ) );
const GLenum err = glewInit();
if( GLEW_OK != err )
{
const wxString msgError = (const char*) glewGetErrorString( err );
wxLogMessage( msgError );
return false;
}
else
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::initializeOpenGL Using GLEW version %s" ),
From_UTF8( (char*) glewGetString( GLEW_VERSION ) ) );
}
SetOpenGLInfo( (const char*) glGetString( GL_VENDOR ), (const char*) glGetString( GL_RENDERER ),
(const char*) glGetString( GL_VERSION ) );
wxString version = From_UTF8( (char *) glGetString( GL_VERSION ) );
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL version string %s." ),
__WXFUNCTION__, version );
// Extract OpenGL version from string. This method is used because prior to OpenGL 2,
// getting the OpenGL major and minor version as integers didn't exist.
wxString tmp;
wxStringTokenizer tokenizer( version );
if( tokenizer.HasMoreTokens() )
{
long major = 0;
long minor = 0;
tmp = tokenizer.GetNextToken();
tokenizer.SetString( tmp, wxString( wxT( "." ) ) );
if( tokenizer.HasMoreTokens() )
tokenizer.GetNextToken().ToLong( &major );
if( tokenizer.HasMoreTokens() )
tokenizer.GetNextToken().ToLong( &minor );
if( major < 2 || ( ( major == 2 ) && ( minor < 1 ) ) )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL ray tracing not supported." ),
__WXFUNCTION__ );
if( GetParent() )
{
wxCommandEvent evt( wxEVT_MENU, ID_DISABLE_RAY_TRACING );
GetParent()->ProcessWindowEvent( evt );
}
m_opengl_supports_raytracing = false;
}
if( ( major == 1 ) && ( minor < 5 ) )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::%s OpenGL not supported." ),
__WXFUNCTION__ );
m_is_opengl_version_supported = false;
}
}
GL_UTILS::SetSwapInterval( -1 );
m_is_opengl_initialized = true;
return true;
}
void EDA_3D_CANVAS::GetScreenshot( wxImage& aDstImage )
{
OglGetScreenshot( aDstImage );
}
void EDA_3D_CANVAS::ReloadRequest( BOARD* aBoard , S3D_CACHE* aCachePointer )
{
if( aCachePointer != nullptr )
m_boardAdapter.Set3dCacheManager( aCachePointer );
if( aBoard != nullptr )
m_boardAdapter.SetBoard( aBoard );
m_boardAdapter.ReloadColorSettings();
if( m_3d_render )
m_3d_render->ReloadRequest();
}
void EDA_3D_CANVAS::RenderRaytracingRequest()
{
m_3d_render = m_3d_render_raytracing;
if( m_3d_render )
m_3d_render->ReloadRequest();
m_render_raytracing_was_requested = true;
Request_refresh();
}
void EDA_3D_CANVAS::DisplayStatus()
{
if( m_parentStatusBar )
{
wxString msg;
msg.Printf( wxT( "dx %3.2f" ), m_camera.GetCameraPos().x );
m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::X_POS ) );
msg.Printf( wxT( "dy %3.2f" ), m_camera.GetCameraPos().y );
m_parentStatusBar->SetStatusText( msg, static_cast<int>( EDA_3D_VIEWER_STATUSBAR::Y_POS ) );
msg.Printf( wxT( "zoom %3.2f" ), 1 / m_camera.GetZoom() );
m_parentStatusBar->SetStatusText( msg,
static_cast<int>( EDA_3D_VIEWER_STATUSBAR::ZOOM_LEVEL ) );
}
}
void EDA_3D_CANVAS::OnPaint( wxPaintEvent& aEvent )
{
// Please have a look at: https://lists.launchpad.net/kicad-developers/msg25149.html
DoRePaint();
}
void EDA_3D_CANVAS::DoRePaint()
{
if( m_is_currently_painting.test_and_set() )
return;
// SwapBuffer requires the window to be shown before calling
if( !IsShownOnScreen() )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::DoRePaint !IsShown" ) );
m_is_currently_painting.clear();
return;
}
// Because the board to draw is handled by the parent viewer frame,
// ensure this parent is still alive. When it is closed before the viewer
// frame, a paint event can be generated after the parent is closed,
// therefore with invalid board.
// This is dependent of the platform.
// Especially on OSX, but also on Windows, it frequently happens
if( !GetParent()->GetParent()->IsShownOnScreen() )
return; // The parent board editor frame is no more alive
wxString err_messages;
INFOBAR_REPORTER warningReporter( m_parentInfoBar );
STATUSBAR_REPORTER activityReporter( m_parentStatusBar, EDA_3D_VIEWER_STATUSBAR::ACTIVITY );
int64_t start_time = GetRunningMicroSecs();
GL_CONTEXT_MANAGER* gl_mgr = Pgm().GetGLContextManager();
// "Makes the OpenGL state that is represented by the OpenGL rendering
// context context current, i.e. it will be used by all subsequent OpenGL calls.
// This function may only be called when the window is shown on screen"
// Explicitly create a new rendering context instance for this canvas.
if( m_glRC == nullptr )
m_glRC = gl_mgr->CreateCtx( this );
// CreateCtx could and does fail per sentry crash events, lets be graceful
if( m_glRC == nullptr )
{
warningReporter.Report( _( "OpenGL context creation error" ), RPT_SEVERITY_ERROR );
warningReporter.Finalize();
m_is_currently_painting.clear();
return;
}
gl_mgr->LockCtx( m_glRC, this );
// Set the OpenGL viewport according to the client size of this canvas.
// This is done here rather than in a wxSizeEvent handler because our
// OpenGL rendering context (and thus viewport setting) is used with
// multiple canvases: If we updated the viewport in the wxSizeEvent
// handler, changing the size of one canvas causes a viewport setting that
// is wrong when next another canvas is repainted.
wxSize clientSize = GetNativePixelSize();
const bool windows_size_changed = m_camera.SetCurWindowSize( clientSize );
// Initialize openGL if need
if( !m_is_opengl_initialized )
{
if( !initializeOpenGL() )
{
gl_mgr->UnlockCtx( m_glRC );
m_is_currently_painting.clear();
return;
}
if( !m_is_opengl_version_supported )
{
warningReporter.Report( _( "Your OpenGL version is not supported. Minimum required "
"is 1.5." ), RPT_SEVERITY_ERROR );
warningReporter.Finalize();
}
}
if( !m_is_opengl_version_supported )
{
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glClear( GL_COLOR_BUFFER_BIT );
SwapBuffers();
gl_mgr->UnlockCtx( m_glRC );
m_is_currently_painting.clear();
return;
}
// Don't attend to ray trace if OpenGL doesn't support it.
if( !m_opengl_supports_raytracing )
{
m_3d_render = m_3d_render_opengl;
m_render_raytracing_was_requested = false;
m_boardAdapter.m_Cfg->m_Render.engine = RENDER_ENGINE::OPENGL;
}
// Check if a raytacing was requested and need to switch to raytracing mode
if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
{
const bool was_camera_changed = m_camera.ParametersChanged();
// It reverts back to OpenGL mode if it was requested a raytracing
// render of the current scene. AND the mouse / camera is moving
if( ( m_mouse_is_moving || m_camera_is_moving || was_camera_changed
|| windows_size_changed )
&& m_render_raytracing_was_requested )
{
m_render_raytracing_was_requested = false;
m_3d_render = m_3d_render_opengl;
}
}
float curtime_delta_s = 0.0f;
if( m_camera_is_moving )
{
const int64_t curtime_delta = GetRunningMicroSecs() - m_strtime_camera_movement;
curtime_delta_s = ( curtime_delta / 1e6 ) * m_camera_moving_speed;
m_camera.Interpolate( curtime_delta_s );
if( curtime_delta_s > 1.0f )
{
m_render_pivot = false;
m_camera_is_moving = false;
m_mouse_was_moved = true;
restart_editingTimeOut_Timer();
DisplayStatus();
}
else
{
Request_refresh();
}
}
// It will return true if the render request a new redraw
bool requested_redraw = false;
if( m_3d_render )
{
try
{
m_3d_render->SetCurWindowSize( clientSize );
bool reloadRaytracingForCalculations = false;
if( m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL
&& m_3d_render_opengl->IsReloadRequestPending() )
{
reloadRaytracingForCalculations = true;
}
requested_redraw = m_3d_render->Redraw( m_mouse_was_moved || m_camera_is_moving,
&activityReporter, &warningReporter );
// Raytracer renderer is responsible for some features also used by the OpenGL
// renderer.
// FIXME: Presumably because raytracing renderer reload is called only after current
// renderer redraw, the old zoom value stays for a single frame. This is ugly, but only
// cosmetic, so I'm not fixing that for now: I don't know how to do this without
// reloading twice (maybe it's not too bad of an idea?) or doing a complicated
// refactor.
if( reloadRaytracingForCalculations )
m_3d_render_raytracing->Reload( nullptr, nullptr, true );
}
catch( std::runtime_error& )
{
m_is_opengl_version_supported = false;
m_opengl_supports_raytracing = false;
m_is_opengl_initialized = false;
gl_mgr->UnlockCtx( m_glRC );
m_is_currently_painting.clear();
return;
}
}
if( m_render_pivot )
{
const float scale = glm::min( m_camera.GetZoom(), 1.0f );
render_pivot( curtime_delta_s, scale );
}
// This will only be enabled by the 3d mouse plugin, so we can leave
// it as a simple if statement
if( m_render3dmousePivot )
{
const float scale = glm::min( m_camera.GetZoom(), 1.0f );
render3dmousePivot( scale );
}
// "Swaps the double-buffer of this window, making the back-buffer the
// front-buffer and vice versa, so that the output of the previous OpenGL
// commands is displayed on the window."
SwapBuffers();
gl_mgr->UnlockCtx( m_glRC );
if( m_mouse_was_moved || m_camera_is_moving )
{
// Calculation time in milliseconds
const double calculation_time = (double)( GetRunningMicroSecs() - start_time ) / 1e3;
activityReporter.Report( wxString::Format( _( "Last render time %.0f ms" ),
calculation_time ) );
}
// This will reset the flag of camera parameters changed
m_camera.ParametersChanged();
warningReporter.Finalize();
if( !err_messages.IsEmpty() )
wxLogMessage( err_messages );
if( ( !m_camera_is_moving ) && requested_redraw )
{
m_mouse_was_moved = false;
Request_refresh( false );
}
m_is_currently_painting.clear();
}
void EDA_3D_CANVAS::SetEventDispatcher( TOOL_DISPATCHER* aEventDispatcher )
{
m_eventDispatcher = aEventDispatcher;
}
void EDA_3D_CANVAS::OnEvent( wxEvent& aEvent )
{
if( !m_eventDispatcher )
aEvent.Skip();
else
m_eventDispatcher->DispatchWxEvent( aEvent );
Refresh();
}
void EDA_3D_CANVAS::OnEraseBackground( wxEraseEvent& event )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::OnEraseBackground" ) );
// Do nothing, to avoid flashing.
}
void EDA_3D_CANVAS::OnMouseWheel( wxMouseEvent& event )
{
wxLogTrace( m_logTrace, wxT( "EDA_3D_CANVAS::OnMouseWheel" ) );
OnMouseWheelCamera( event, m_boardAdapter.m_MousewheelPanning );
if( m_mouse_was_moved )
{
DisplayStatus();
Request_refresh();
restart_editingTimeOut_Timer();
}
}
void EDA_3D_CANVAS::OnMagnify( wxMouseEvent& event )
{
SetFocus();
if( m_camera_is_moving )
return;
//m_is_moving_mouse = true;
restart_editingTimeOut_Timer();
float magnification = ( event.GetMagnification() + 1.0f );
m_camera.Zoom( magnification );
DisplayStatus();
Request_refresh();
}
void EDA_3D_CANVAS::OnZoomGesture( wxZoomGestureEvent& aEvent )
{
SetFocus();
if( aEvent.IsGestureStart() )
{
m_gestureLastZoomFactor = 1.0;
m_camera.SetCurMousePosition( aEvent.GetPosition() );
}
if( m_camera_is_moving )
return;
restart_editingTimeOut_Timer();
m_camera.Pan( aEvent.GetPosition() );
m_camera.SetCurMousePosition( aEvent.GetPosition() );
m_camera.Zoom( aEvent.GetZoomFactor() / m_gestureLastZoomFactor );
m_gestureLastZoomFactor = aEvent.GetZoomFactor();
DisplayStatus();
Request_refresh();
}
void EDA_3D_CANVAS::OnPanGesture( wxPanGestureEvent& aEvent )
{
SetFocus();
if( aEvent.IsGestureStart() )
m_camera.SetCurMousePosition( aEvent.GetPosition() );
if( m_camera_is_moving )
return;
m_camera.Pan( aEvent.GetPosition() );
m_camera.SetCurMousePosition( aEvent.GetPosition() );
DisplayStatus();
Request_refresh();
}
void EDA_3D_CANVAS::OnRotateGesture( wxRotateGestureEvent& aEvent )
{
SetFocus();
if( aEvent.IsGestureStart() )
{
m_gestureLastAngle = 0;
m_camera.SetCurMousePosition( aEvent.GetPosition() );
// We don't want to process the first angle
return;
}
if( m_camera_is_moving )
return;
m_camera.RotateScreen( m_gestureLastAngle - aEvent.GetRotationAngle() );
m_gestureLastAngle = aEvent.GetRotationAngle();
DisplayStatus();
Request_refresh();
}
void EDA_3D_CANVAS::OnMouseMove( wxMouseEvent& event )
{
if( m_3d_render && m_3d_render->IsReloadRequestPending() )
return; // Prevents using invalid m_3d_render_raytracing data
if( m_camera_is_moving )
return;
OnMouseMoveCamera( event );
if( m_mouse_was_moved )
{
DisplayStatus();
Request_refresh();
// *Do not* reactivate the timer here during the mouse move command:
// OnMiddleUp() will do it at the end of mouse drag/move command
}
if( !event.Dragging() && m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
{
STATUSBAR_REPORTER reporter( m_parentStatusBar, EDA_3D_VIEWER_STATUSBAR::HOVERED_ITEM );
RAY mouseRay = getRayAtCurrentMousePosition();
BOARD_ITEM* rollOverItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
auto printNetInfo = []( BOARD_CONNECTED_ITEM* aItem )
{
return wxString::Format( _( "Net %s\tNet class %s" ), aItem->GetNet()->GetNetname(),
aItem->GetNet()->GetNetClass()->GetHumanReadableName() );
};
if( rollOverItem )
{
wxString msg;
if( rollOverItem != m_currentRollOverItem )
{
m_3d_render_opengl->SetCurrentRollOverItem( rollOverItem );
m_currentRollOverItem = rollOverItem;
Request_refresh();
}
switch( rollOverItem->Type() )
{
case PCB_PAD_T:
{
PAD* pad = static_cast<PAD*>( rollOverItem );
if( !pad->GetNumber().IsEmpty() )
msg += wxString::Format( _( "Pad %s\t" ), pad->GetNumber() );
if( pad->IsOnCopperLayer() )
msg += printNetInfo( pad );
break;
}
case PCB_FOOTPRINT_T:
{
FOOTPRINT* footprint = static_cast<FOOTPRINT*>( rollOverItem );
msg += footprint->GetReference();
break;
}
case PCB_TRACE_T:
case PCB_VIA_T:
case PCB_ARC_T:
{
PCB_TRACK* track = static_cast<PCB_TRACK*>( rollOverItem );
msg += printNetInfo( track );
break;
}
case PCB_ZONE_T:
{
ZONE* zone = static_cast<ZONE*>( rollOverItem );
if( !zone->GetZoneName().IsEmpty() )
{
if( zone->GetIsRuleArea() )
msg += wxString::Format( _( "Rule area %s\t" ), zone->GetZoneName() );
else
msg += wxString::Format( _( "Zone %s\t" ), zone->GetZoneName() );
}
if( zone->IsOnCopperLayer() )
msg += printNetInfo( zone );
break;
}
default:
break;
}
reporter.Report( msg );
}
else
{
if( m_currentRollOverItem
&& m_boardAdapter.m_Cfg->m_Render.engine == RENDER_ENGINE::OPENGL )
{
m_3d_render_opengl->SetCurrentRollOverItem( nullptr );
Request_refresh();
reporter.Report( wxEmptyString );
}
m_currentRollOverItem = nullptr;
}
}
}
void EDA_3D_CANVAS::OnLeftDown( wxMouseEvent& event )
{
SetFocus();
stop_editingTimeOut_Timer();
// Ensure m_camera.m_lastPosition (current mouse position) is up to date for
// future drag events (can be not the case when left clicking after
// opening a context menu)
OnMouseMoveCamera( event );
if( !event.Dragging() && ( m_3d_render_raytracing != nullptr ) )
{
RAY mouseRay = getRayAtCurrentMousePosition();
BOARD_ITEM *intersectedBoardItem = m_3d_render_raytracing->IntersectBoardItem( mouseRay );
// !TODO: send a selection item to pcbnew, eg: via kiway?
}
}
void EDA_3D_CANVAS::OnLeftUp( wxMouseEvent& event )
{
if( m_camera_is_moving )
return;
if( m_mouse_is_moving )
{
m_mouse_is_moving = false;
restart_editingTimeOut_Timer();
}
}
void EDA_3D_CANVAS::OnMiddleDown( wxMouseEvent& event )
{
SetFocus();
stop_editingTimeOut_Timer();
}
void EDA_3D_CANVAS::OnMiddleUp( wxMouseEvent& event )
{
if( m_camera_is_moving )
return;
if( m_mouse_is_moving )
{
m_mouse_is_moving = false;
restart_editingTimeOut_Timer();
}
else
{
move_pivot_based_on_cur_mouse_position();
}
}
void EDA_3D_CANVAS::OnTimerTimeout_Editing( wxTimerEvent& aEvent )
{
if( aEvent.GetId() != m_editing_timeout_timer.GetId() )
{
aEvent.Skip();
return;
}
m_mouse_is_moving = false;
m_mouse_was_moved = false;
Request_refresh();
}
void EDA_3D_CANVAS::stop_editingTimeOut_Timer()
{
m_editing_timeout_timer.Stop();
}
void EDA_3D_CANVAS::restart_editingTimeOut_Timer()
{
if( m_3d_render )
m_editing_timeout_timer.Start( m_3d_render->GetWaitForEditingTimeOut(), wxTIMER_ONE_SHOT );
}
void EDA_3D_CANVAS::OnTimerTimeout_Redraw( wxTimerEvent& aEvent )
{
if( aEvent.GetId() != m_redraw_trigger_timer.GetId() )
{
aEvent.Skip();
return;
}
Request_refresh( true );
}
void EDA_3D_CANVAS::OnRefreshRequest( wxEvent& aEvent )
{
Refresh();
}
void EDA_3D_CANVAS::Request_refresh( bool aRedrawImmediately )
{
if( aRedrawImmediately )
{
// Just calling Refresh() does not work always
// Using an event to call DoRepaint ensure the repaint code will be executed,
// and PostEvent will take priority to other events like mouse movements, keys, etc.
// and is executed during the next idle time
wxCommandEvent redrawEvent( wxEVT_REFRESH_CUSTOM_COMMAND, ID_CUSTOM_EVENT_1 );
wxPostEvent( this, redrawEvent );
}
else
{
// Schedule a timed redraw
m_redraw_trigger_timer.Start( 10 , wxTIMER_ONE_SHOT );
}
}
void EDA_3D_CANVAS::request_start_moving_camera( float aMovingSpeed, bool aRenderPivot )
{
wxASSERT( aMovingSpeed > FLT_EPSILON );
// Fast forward the animation if the animation is disabled
if( !m_animation_enabled )
{
m_camera.Interpolate( 1.0f );
DisplayStatus();
Request_refresh();
return;
}
// Map speed multiplier option to actual multiplier value
// [1,2,3,4,5] -> [0.25, 0.5, 1, 2, 4]
aMovingSpeed *= ( 1 << m_moving_speed_multiplier ) / 8.0f;
m_render_pivot = aRenderPivot;
m_camera_moving_speed = aMovingSpeed;
stop_editingTimeOut_Timer();
DisplayStatus();
Request_refresh();
m_camera_is_moving = true;
m_strtime_camera_movement = GetRunningMicroSecs();
}
void EDA_3D_CANVAS::move_pivot_based_on_cur_mouse_position()
{
RAY mouseRay = getRayAtCurrentMousePosition();
float hit_t;
// Test it with the board bounding box
if( m_boardAdapter.GetBBox().Intersect( mouseRay, &hit_t ) )
{
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
m_camera.SetT0_and_T1_current_T();
m_camera.SetLookAtPos_T1( mouseRay.at( hit_t ) );
m_camera.ResetXYpos_T1();
request_start_moving_camera();
}
}
bool EDA_3D_CANVAS::SetView3D( VIEW3D_TYPE aRequestedView )
{
if( m_camera_is_moving )
return false;
const float delta_move = m_delta_move_step_factor * m_camera.GetZoom();
const float arrow_moving_time_speed = 8.0f;
switch( aRequestedView )
{
case VIEW3D_TYPE::VIEW3D_PIVOT_CENTER:
move_pivot_based_on_cur_mouse_position();
return true;
case VIEW3D_TYPE::VIEW3D_PAN_LEFT:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
m_camera.SetT0_and_T1_current_T();
m_camera.Pan_T1( SFVEC3F( -delta_move, 0.0f, 0.0f ) );
request_start_moving_camera( arrow_moving_time_speed, false );
return true;
case VIEW3D_TYPE::VIEW3D_PAN_RIGHT:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
m_camera.SetT0_and_T1_current_T();
m_camera.Pan_T1( SFVEC3F( +delta_move, 0.0f, 0.0f ) );
request_start_moving_camera( arrow_moving_time_speed, false );
return true;
case VIEW3D_TYPE::VIEW3D_PAN_UP:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
m_camera.SetT0_and_T1_current_T();
m_camera.Pan_T1( SFVEC3F( 0.0f, +delta_move, 0.0f ) );
request_start_moving_camera( arrow_moving_time_speed, false );
return true;
case VIEW3D_TYPE::VIEW3D_PAN_DOWN:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::LINEAR );
m_camera.SetT0_and_T1_current_T();
m_camera.Pan_T1( SFVEC3F( 0.0f, -delta_move, 0.0f ) );
request_start_moving_camera( arrow_moving_time_speed, false );
return true;
case VIEW3D_TYPE::VIEW3D_FIT_SCREEN:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
m_camera.SetT0_and_T1_current_T();
m_camera.Reset_T1();
request_start_moving_camera( glm::min( glm::max( m_camera.GetZoom(), 1 / 1.26f ), 1.26f ) );
return true;
case VIEW3D_TYPE::VIEW3D_ZOOM_IN:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
m_camera.SetT0_and_T1_current_T();
if( m_camera.Zoom_T1( 1.26f ) ) // 3 steps per doubling
request_start_moving_camera( 3.0f );
return true;
case VIEW3D_TYPE::VIEW3D_ZOOM_OUT:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
m_camera.SetT0_and_T1_current_T();
if( m_camera.Zoom_T1( 1/1.26f ) ) // 3 steps per halving
request_start_moving_camera( 3.0f );
return true;
case VIEW3D_TYPE::VIEW3D_RIGHT:
case VIEW3D_TYPE::VIEW3D_LEFT:
case VIEW3D_TYPE::VIEW3D_FRONT:
case VIEW3D_TYPE::VIEW3D_BACK:
case VIEW3D_TYPE::VIEW3D_FLIP:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
m_camera.SetT0_and_T1_current_T();
m_camera.ViewCommand_T1( aRequestedView );
request_start_moving_camera();
return true;
case VIEW3D_TYPE::VIEW3D_TOP:
case VIEW3D_TYPE::VIEW3D_BOTTOM:
m_camera.SetInterpolateMode( CAMERA_INTERPOLATION::BEZIER );
m_camera.SetT0_and_T1_current_T();
m_camera.ViewCommand_T1( aRequestedView );
request_start_moving_camera( glm::min( glm::max( m_camera.GetZoom(), 0.5f ), 1.125f ) );
return true;
default:
return false;
}
}
void EDA_3D_CANVAS::RenderEngineChanged()
{
SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
EDA_3D_VIEWER_SETTINGS* cfg = mgr.GetAppSettings<EDA_3D_VIEWER_SETTINGS>( "3d_viewer" );
switch( cfg->m_Render.engine )
{
case RENDER_ENGINE::OPENGL: m_3d_render = m_3d_render_opengl; break;
case RENDER_ENGINE::RAYTRACING: m_3d_render = m_3d_render_raytracing; break;
default: m_3d_render = nullptr; break;
}
if( m_3d_render )
m_3d_render->ReloadRequest();
m_mouse_was_moved = false;
Request_refresh();
}
RAY EDA_3D_CANVAS::getRayAtCurrentMousePosition()
{
SFVEC3F rayOrigin;
SFVEC3F rayDir;
// Generate a ray origin and direction based on current mouser position and camera
m_camera.MakeRayAtCurrentMousePosition( rayOrigin, rayDir );
RAY mouseRay;
mouseRay.Init( rayOrigin, rayDir );
return mouseRay;
}