From aec7802fcfc86dcc79e749d574103773866d38d9 Mon Sep 17 00:00:00 2001 From: markus-bonk Date: Thu, 24 Jun 2021 10:47:28 +0200 Subject: [PATCH] EDA_3D_VIEWER_FRAME: Implement SpaceMouse navigation and command export for 3D-viewer. Full support for using a 3Dconnexion device in the 3D viewer has been added. Full 3D navigation is available for both the orthographic and perspective projections. Commands are exported and can be assigned to 3D mouse buttons. Any limitations to the functionality are limitations of the installed 3Dconnexion driver for the device and OS. ADDED: A build option KICAD_USE_3DCONNEXION (default = ON) has been added to the main CMakeLists.txt. The option controls whether the SpaceMouse support is compiled into the solution. --- 3d-viewer/3d_canvas/eda_3d_canvas.h | 22 + 3d-viewer/3d_navlib/CMakeLists.txt | 28 + 3d-viewer/3d_navlib/nl_3d_viewer_plugin.cpp | 52 ++ 3d-viewer/3d_navlib/nl_3d_viewer_plugin.h | 59 ++ .../3d_navlib/nl_3d_viewer_plugin_impl.cpp | 656 ++++++++++++++++++ .../3d_navlib/nl_3d_viewer_plugin_impl.h | 133 ++++ 3d-viewer/3d_rendering/camera.cpp | 68 +- 3d-viewer/3d_rendering/camera.h | 25 +- 3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp | 32 +- 3d-viewer/3d_viewer/eda_3d_viewer_frame.h | 4 + 3d-viewer/CMakeLists.txt | 6 + CMakeLists.txt | 8 + .../3dxware_sdk/inc/SpaceMouse/CImage.hpp | 2 +- .../3dxware_sdk/inc/navlib/navlib_types.h | 1 + 14 files changed, 1081 insertions(+), 15 deletions(-) create mode 100644 3d-viewer/3d_navlib/CMakeLists.txt create mode 100644 3d-viewer/3d_navlib/nl_3d_viewer_plugin.cpp create mode 100644 3d-viewer/3d_navlib/nl_3d_viewer_plugin.h create mode 100644 3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.cpp create mode 100644 3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.h diff --git a/3d-viewer/3d_canvas/eda_3d_canvas.h b/3d-viewer/3d_canvas/eda_3d_canvas.h index 2d68d080cd..4d74dec442 100644 --- a/3d-viewer/3d_canvas/eda_3d_canvas.h +++ b/3d-viewer/3d_canvas/eda_3d_canvas.h @@ -161,6 +161,28 @@ public: */ void OnEvent( wxEvent& aEvent ); + /** + * Get the canvas camera. + */ + CAMERA* GetCamera() { return &m_camera; } + + /** + * Get information used to display 3D board. + */ + const BOARD_ADAPTER& GetBoardAdapter() const { return m_boardAdapter; } + + /** + * Get a value indicating whether to render the pivot. + */ + bool GetRenderPivot() { return m_render_pivot; } + + /** + * Set aValue indicating whether to render the pivot. + * + * @param aValue true will cause the pivot to be rendered on the next redraw. + */ + void SetRenderPivot( bool aValue ) { m_render_pivot = aValue; } + private: /** * Called by a wxPaintEvent event diff --git a/3d-viewer/3d_navlib/CMakeLists.txt b/3d-viewer/3d_navlib/CMakeLists.txt new file mode 100644 index 0000000000..ddabe2837e --- /dev/null +++ b/3d-viewer/3d_navlib/CMakeLists.txt @@ -0,0 +1,28 @@ +if( KICAD_USE_3DCONNEXION ) + add_library(3d-viewer_navlib STATIC + "nl_3d_viewer_plugin.cpp" + "nl_3d_viewer_plugin_impl.cpp" + ) + + # Find the 3DxWare SDK component 3DxWare::NlClient + # find_package(TDxWare_SDK 4.0 REQUIRED COMPONENTS 3DxWare::Navlib) + target_compile_definitions(3d-viewer_navlib PRIVATE + $ + ) + target_compile_options(3d-viewer_navlib PRIVATE + $ + ) + target_include_directories(3d-viewer_navlib PRIVATE + $ + $ + ) + target_link_libraries(3d-viewer_navlib + $ + 3DxWare::Navlib + ) +else() + add_library(3d-viewer_navlib STATIC + "nl_3d_viewer_plugin.cpp" + ) +endif(KICAD_USE_3DCONNEXION) + diff --git a/3d-viewer/3d_navlib/nl_3d_viewer_plugin.cpp b/3d-viewer/3d_navlib/nl_3d_viewer_plugin.cpp new file mode 100644 index 0000000000..2f849a2c50 --- /dev/null +++ b/3d-viewer/3d_navlib/nl_3d_viewer_plugin.cpp @@ -0,0 +1,52 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021 3Dconnexion + * Copyright (C) 2021 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 3 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, see . + */ + +#include "nl_3d_viewer_plugin.h" + +#if defined( KICAD_USE_3DCONNEXION ) +#include "nl_3d_viewer_plugin_impl.h" + +NL_3D_VIEWER_PLUGIN::NL_3D_VIEWER_PLUGIN( EDA_3D_CANVAS* aViewport ) : + m_impl( new NL_3D_VIEWER_PLUGIN_IMPL( aViewport ) ) +{ +} + +NL_3D_VIEWER_PLUGIN::~NL_3D_VIEWER_PLUGIN() +{ + delete m_impl; +} + +void NL_3D_VIEWER_PLUGIN::SetFocus( bool focus ) +{ + m_impl->SetFocus( focus ); +} +#else +NL_3DVIEWER_PLUGIN::NL_3DVIEWER_PLUGIN( EDA_3D_CANVAS* aViewport ) +{ +} + +void NL_3DVIEWER_PLUGIN::SetFocus( bool focus ) +{ +} + +NL_3DVIEWER_PLUGIN::~NL_3DVIEWER_PLUGIN() +{ +} +#endif diff --git a/3d-viewer/3d_navlib/nl_3d_viewer_plugin.h b/3d-viewer/3d_navlib/nl_3d_viewer_plugin.h new file mode 100644 index 0000000000..7cfbea042d --- /dev/null +++ b/3d-viewer/3d_navlib/nl_3d_viewer_plugin.h @@ -0,0 +1,59 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021 3Dconnexion + * Copyright (C) 2021 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 3 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, see . + */ + +/** + * @file nl_3d_viewer_plugin.h + * @brief Declaration of the NL_3D_VIEWER_PLUGIN class + */ + +#ifndef NL_3D_VIEWER_PLUGIN_H_ +#define NL_3D_VIEWER_PLUGIN_H_ + +// Forward declarations. +class EDA_3D_CANVAS; +class NL_3D_VIEWER_PLUGIN_IMPL; +/** + * The class that implements the public interface to the SpaceMouse plug-in. + */ +class NL_3D_VIEWER_PLUGIN +{ +public: + /** + * Initializes a new instance of the NL_3D_VIEWER_PLUGIN. + * + * @param aViewport is the viewport to be navigated. + */ + NL_3D_VIEWER_PLUGIN( EDA_3D_CANVAS* aViewport ); + + virtual ~NL_3D_VIEWER_PLUGIN(); + + /** + * Set the connection to the 3Dconnexion driver to the focus state so that + * 3DMouse data is routed here. + * + * @param aFocus is true to set the connection active. + */ + void SetFocus( bool aFocus = true ); + +private: + NL_3D_VIEWER_PLUGIN_IMPL* m_impl; +}; + +#endif // NL_3D_VIEWER_PLUGIN_H_ diff --git a/3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.cpp b/3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.cpp new file mode 100644 index 0000000000..62c2a4b27e --- /dev/null +++ b/3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.cpp @@ -0,0 +1,656 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021 3Dconnexion + * Copyright (C) 2021 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 3 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, see . + */ + +#include "nl_3d_viewer_plugin_impl.h" + +// 3d-viewer +#include <3d-viewer/3d_rendering/track_ball.h> +#include <3d-viewer/3d_canvas/eda_3d_canvas.h> + +// KiCAD includes +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// stdlib +#include +#include +#include + +#include +/** + * Flag to enable the NL_3D_VIEWER_PLUGIN debug tracing. + * + * Use "KI_TRACE_NL_3D_VIEWER_PLUGIN" to enable. + * + * @ingroup trace_env_vars + */ +const wxChar* NL_3D_VIEWER_PLUGIN_IMPL::m_logTrace = wxT( "KI_TRACE_NL_3D_VIEWER_PLUGIN" ); + +/** + * Template to compare two floating point values for equality within a required epsilon. + * + * @param aFirst value to compare. + * @param aSecond value to compare. + * @param aEpsilon allowed error. + * @return true if the values considered equal within the specified epsilon, otherwise false. + */ +template +bool equals( T aFirst, T aSecond, T aEpsilon = static_cast( FLT_EPSILON ) ) +{ + T diff = fabs( aFirst - aSecond ); + if( diff < aEpsilon ) + { + return true; + } + aFirst = fabs( aFirst ); + aSecond = fabs( aSecond ); + T largest = aFirst > aSecond ? aFirst : aSecond; + if( diff <= largest * aEpsilon ) + { + return true; + } + return false; +} + +/** + * Template to compare two glm::mat values for equality within a required epsilon. + * + * @param aFirst value to compare. + * @param aSecond value to compare. + * @param aEpsilon allowed error. + * @return true if the values considered equal within the specified epsilon, otherwise false. + */ +template +bool equals( glm::mat const& aFirst, glm::mat const& aSecond, + T aEpsilon = static_cast( FLT_EPSILON * 10 ) ) +{ + T const* first = glm::value_ptr( aFirst ); + T const* second = glm::value_ptr( aSecond ); + + for( glm::length_t j = 0; j < L * C; ++j ) + { + if( !equals( first[j], second[j], aEpsilon ) ) + { + return false; + } + } + + return true; +} + +NL_3D_VIEWER_PLUGIN_IMPL::NL_3D_VIEWER_PLUGIN_IMPL( EDA_3D_CANVAS* aCanvas ) : + NAV_3D( false, false ), m_canvas( aCanvas ), m_capIsMoving( false ) +{ + m_camera = dynamic_cast( m_canvas->GetCamera() ); + + PutProfileHint( "KiCAD 3D" ); + + EnableNavigation( true ); + PutFrameTimingSource( TimingSource::SpaceMouse ); + + exportCommandsAndImages(); +} + +NL_3D_VIEWER_PLUGIN_IMPL::~NL_3D_VIEWER_PLUGIN_IMPL() +{ + EnableNavigation( false ); +} + +void NL_3D_VIEWER_PLUGIN_IMPL::SetFocus( bool aFocus ) +{ + wxLogTrace( m_logTrace, wxT( "NL_3D_VIEWER_PLUGIN_IMPL::SetFocus %d" ), aFocus ); + NAV_3D::Write( navlib::focus_k, aFocus ); +} + +// temporary store for the categories +typedef std::map CATEGORY_STORE; + +/** + * Add a category to the store. + * + * The function adds category paths of the format "A.B" where B is a sub-category of A. + * + * @param aCategoryPath is the std::string representation of the category. + * @param aCategoryStore is the CATEGORY_STORE instance to add to. + * @return a CATEGORY_STORE::iterator where the category was added. + */ +CATEGORY_STORE::iterator add_category( std::string aCategoryPath, CATEGORY_STORE& aCategoryStore ) +{ + using TDx::SpaceMouse::CCategory; + + CATEGORY_STORE::iterator parent_iter = aCategoryStore.begin(); + std::string::size_type pos = aCategoryPath.find_last_of( '.' ); + + if( pos != std::string::npos ) + { + std::string parentPath = aCategoryPath.substr( 0, pos ); + parent_iter = aCategoryStore.find( parentPath ); + if( parent_iter == aCategoryStore.end() ) + { + parent_iter = add_category( parentPath, aCategoryStore ); + } + } + + std::string name = aCategoryPath.substr( pos + 1 ); + std::unique_ptr categoryNode = + std::make_unique( aCategoryPath.c_str(), name.c_str() ); + + CATEGORY_STORE::iterator iter = aCategoryStore.insert( + aCategoryStore.end(), CATEGORY_STORE::value_type( aCategoryPath, categoryNode.get() ) ); + + parent_iter->second->push_back( std::move( categoryNode ) ); + return iter; +} + +/** + * Export the invocable actions and images to the 3Dconnexion UI. + */ +void NL_3D_VIEWER_PLUGIN_IMPL::exportCommandsAndImages() +{ + wxLogTrace( m_logTrace, wxT( "NL_3D_VIEWER_PLUGIN_IMPL::exportCommandsAndImages" ) ); + + std::list actions = ACTION_MANAGER::GetActionList(); + + if( actions.size() == 0 ) + { + return; + } + + using TDx::SpaceMouse::CCommand; + using TDx::SpaceMouse::CCommandSet; + + // The root action set node + CCommandSet commandSet( "EDA_3D_CANVAS", "3D Viewer" ); + + // Activate the command set + NAV_3D::PutActiveCommands( commandSet.GetId() ); + + // temporary store for the categories + CATEGORY_STORE categoryStore; + + std::vector vImages; + + // add the action set to the category_store + CATEGORY_STORE::iterator iter = categoryStore.insert( + categoryStore.end(), CATEGORY_STORE::value_type( ".", &commandSet ) ); + + std::list::const_iterator it; + + for( it = actions.begin(); it != actions.end(); ++it ) + { + const TOOL_ACTION* action = *it; + std::string label = action->GetLabel().ToStdString(); + if( label.empty() ) + { + continue; + } + + std::string name = action->GetName(); + + // Do no export commands for the pcbnew app. + if( name.rfind( "pcbnew.", 0 ) == 0 ) + { + continue; + } + + std::string strCategory = action->GetToolName(); + CATEGORY_STORE::iterator iter = categoryStore.find( strCategory ); + if( iter == categoryStore.end() ) + { + iter = add_category( std::move( strCategory ), categoryStore ); + } + + std::string description = action->GetDescription().ToStdString(); + + // Arbitrary 8-bit data stream + wxMemoryOutputStream imageStream; + if( action->GetIcon() != BITMAPS::INVALID_BITMAP ) + { + wxImage image = KiBitmap( action->GetIcon() ).ConvertToImage(); + image.SaveFile( imageStream, wxBitmapType::wxBITMAP_TYPE_PNG ); + image.Destroy(); + + if( imageStream.GetSize() ) + { + wxStreamBuffer* streamBuffer = imageStream.GetOutputStreamBuffer(); + TDx::CImage image = TDx::CImage::FromData( "", 0, name.c_str() ); + image.AssignImage( std::string( reinterpret_cast( + streamBuffer->GetBufferStart() ), + streamBuffer->GetBufferSize() ), + 0 ); + + wxLogTrace( m_logTrace, "Adding image for : %s", name ); + vImages.push_back( std::move( image ) ); + } + } + + wxLogTrace( m_logTrace, "Inserting command: %s, description: %s, in category: %s", name, + description, iter->first ); + + iter->second->push_back( + CCommand( std::move( name ), std::move( label ), std::move( description ) ) ); + } + + NAV_3D::AddCommandSet( commandSet ); + NAV_3D::AddImages( vImages ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetCameraMatrix( navlib::matrix_t& matrix ) const +{ + // cache the camera matrix so that we can tell if the view has been moved and + // calculate a delta transform if required. + m_cameraMatrix = m_camera->GetViewMatrix(); + + std::copy_n( glm::value_ptr( glm::inverse( m_cameraMatrix ) ), 16, matrix.m ); + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetPointerPosition( navlib::point_t& position ) const +{ + SFVEC3F origin, direction; + m_camera->MakeRayAtCurrentMousePosition( origin, direction ); + + position = { origin.x, origin.y, origin.z }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetViewExtents( navlib::box_t& extents ) const +{ + if( m_camera->GetProjection() == PROJECTION_TYPE::PERSPECTIVE ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + const CAMERA_FRUSTUM& f = m_camera->GetFrustum(); + + double half_width = f.fw / 2.; + double half_height = f.fh / 2.; + extents = { -half_width, -half_height, f.nearD, half_width, half_height, f.farD }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetViewFOV( double& aFov ) const +{ + const CAMERA_FRUSTUM& f = m_camera->GetFrustum(); + + aFov = glm::radians( f.angle ); + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetViewFrustum( navlib::frustum_t& aFrustum ) const +{ + if( m_camera->GetProjection() != PROJECTION_TYPE::PERSPECTIVE ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + const CAMERA_FRUSTUM& f = m_camera->GetFrustum(); + double half_width = f.nw / 2.; + double half_height = f.nh / 2.; + aFrustum = { -half_width, half_width, -half_height, half_height, f.nearD, f.farD }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetIsViewPerspective( navlib::bool_t& perspective ) const +{ + perspective = m_camera->GetProjection() == PROJECTION_TYPE::PERSPECTIVE ? 1 : 0; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetCameraMatrix( const navlib::matrix_t& aCameraMatrix ) +{ + long result = 0; + + glm::mat4 cam, viewMatrix; + std::copy_n( aCameraMatrix.m, 16, glm::value_ptr( cam ) ); + viewMatrix = glm::inverse( cam ); + + + glm::mat4 camera = m_camera->GetViewMatrix(); + + // The navlib does not move the camera in its z-axis in an orthographic projection + // as this does not change the viewed object size. However ... + if( m_camera->GetProjection() == PROJECTION_TYPE::ORTHO ) + { + // ... the CAMERA class couples zoom and distance to the object: we need to + // ensure that The CAMERA's z position relative to the lookat_pos is not changed + // in an orthographic projection. + glm::vec4 lookat( m_camera->GetLookAtPos(), 1.0f ); + glm::vec4 lookat_new = viewMatrix * lookat; + glm::vec4 lookat_old = camera * lookat; + + viewMatrix[3].z += lookat_old.z - lookat_new.z; + } + + if( !equals( camera, m_cameraMatrix ) ) + { + // Some other input device has moved the camera. Apply only the intended delta + // transform ... + m_camera->SetViewMatrix( viewMatrix * glm::inverse( m_cameraMatrix ) * camera ); + m_camera->Update(); + + // .., cache the intended camera matrix so that we can calculate the delta + // transform when needed ... + m_cameraMatrix = viewMatrix; + + // ... and let the 3DMouse controller know, that something is amiss. + return navlib::make_result_code( navlib::navlib_errc::error ); + } + + m_camera->SetViewMatrix( viewMatrix ); + m_camera->Update(); + + // cache the view matrix so that we know when it has changed. + m_cameraMatrix = m_camera->GetViewMatrix(); + + // The camera has a constraint on the z position so we need to check that ... + if( !equals( m_cameraMatrix[3].z, viewMatrix[3].z ) ) + { + // ... and let the 3DMouse controller know, when something is amiss. + return navlib::make_result_code( navlib::navlib_errc::error ); + } + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetViewExtents( const navlib::box_t& extents ) +{ + const CAMERA_FRUSTUM& f = m_camera->GetFrustum(); + + float factor = 2 * f.nw / ( extents.max_x - extents.min_x ); + float zoom = m_camera->GetZoom() / factor; + + m_camera->Zoom( factor ); + + // The camera auto positions the camera to match the zoom values. We need to + // update our cached camera matrix to match the new z value + m_cameraMatrix[3].z = m_camera->GetViewMatrix()[3].z; + + // The camera has a constraint on the zoom factor so we need to check that ... + if( zoom != m_camera->GetZoom() ) + { + // ... and let the 3DMouse controller know, when something is amiss. + return navlib::make_result_code( navlib::navlib_errc::error ); + } + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetViewFOV( double fov ) +{ + return navlib::make_result_code( navlib::navlib_errc::function_not_supported ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetViewFrustum( const navlib::frustum_t& frustum ) +{ + return navlib::make_result_code( navlib::navlib_errc::permission_denied ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetModelExtents( navlib::box_t& extents ) const +{ + SFVEC3F min = m_canvas->GetBoardAdapter().GetBBox().Min(); + SFVEC3F max = m_canvas->GetBoardAdapter().GetBBox().Max(); + + extents = { min.x, min.y, min.z, max.x, max.y, max.z }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetSelectionExtents( navlib::box_t& extents ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetSelectionTransform( navlib::matrix_t& transform ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetIsSelectionEmpty( navlib::bool_t& empty ) const +{ + empty = true; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetSelectionTransform( const navlib::matrix_t& matrix ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetPivotPosition( navlib::point_t& position ) const +{ + SFVEC3F lap = m_camera->GetLookAtPos(); + + position = { lap.x, lap.y, lap.z }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::IsUserPivot( navlib::bool_t& userPivot ) const +{ + userPivot = false; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetPivotPosition( const navlib::point_t& position ) +{ + SFVEC3F pivotPos = SFVEC3F( position.x, position.y, position.z ); + + // Set the pivot icon position . + m_camera->SetLookAtPos_T1( pivotPos ); + +#if 0 + // Set the trackball pivot to the same position as the 3DMouse pivot. + glm::mat4 m = m_camera->GetViewMatrix(); + m_camera->SetLookAtPos( pivotPos ); + m_camera->SetViewMatrix( std::move( m ) ); + m_camera->Update(); +#endif + + m_canvas->Request_refresh(); + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetPivotVisible( navlib::bool_t& visible ) const +{ + visible = m_canvas->GetRenderPivot(); + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetPivotVisible( bool visible ) +{ + m_canvas->SetRenderPivot( visible ); + + m_canvas->Request_refresh(); + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetHitLookAt( navlib::point_t& position ) const +{ + RAY mouseRay; + mouseRay.Init( m_rayOrigin, m_rayDirection ); + + float hit; + glm::vec3 vec; + + // Test it with the board bounding box + if( m_canvas->GetBoardAdapter().GetBBox().Intersect( mouseRay, &hit ) ) + { + vec = mouseRay.at( hit ); + position = { vec.x, vec.y, vec.z }; + return 0; + } + + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetHitAperture( double aperture ) +{ + return navlib::make_result_code( navlib::navlib_errc::function_not_supported ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetHitDirection( const navlib::vector_t& direction ) +{ + m_rayDirection = { direction.x, direction.y, direction.z }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetHitLookFrom( const navlib::point_t& eye ) +{ + m_rayOrigin = { eye.x, eye.y, eye.z }; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetHitSelectionOnly( bool onlySelection ) +{ + return navlib::make_result_code( navlib::navlib_errc::function_not_supported ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetActiveCommand( std::string commandId ) +{ + if( commandId.empty() ) + { + return 0; + } + + std::list actions = ACTION_MANAGER::GetActionList(); + TOOL_ACTION* context = nullptr; + + for( std::list::const_iterator it = actions.begin(); it != actions.end(); it++ ) + { + TOOL_ACTION* action = *it; + std::string nm = action->GetName(); + + if( commandId == nm ) + { + context = action; + } + } + + if( context != nullptr ) + { + wxWindow* parent = m_canvas->GetParent(); + + // Only allow command execution if the window is enabled. i.e. there is not a modal dialog + // currently active. + if( parent->IsEnabled() ) + { + TOOL_MANAGER* tool_manager = static_cast( parent )->GetToolManager(); + + if( tool_manager == nullptr ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + // Get the selection to use to test if the action is enabled + SELECTION& sel = tool_manager->GetToolHolder()->GetCurrentSelection(); + + bool runAction = true; + + if( const ACTION_CONDITIONS* aCond = + tool_manager->GetActionManager()->GetCondition( *context ) ) + { + runAction = aCond->enableCondition( sel ); + } + + if( runAction ) + { + tool_manager->RunAction( *context, true ); + m_canvas->Request_refresh(); + } + } + else + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + } + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetSettingsChanged( long change ) +{ + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetMotionFlag( bool value ) +{ + m_capIsMoving = value; + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetTransaction( long value ) +{ + if( value != 0L ) + { + } + else + { + m_canvas->Request_refresh( true ); + wxLogTrace( m_logTrace, wxT( "End of transaction" ) ); + } + + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::SetCameraTarget( const navlib::point_t& position ) +{ + return navlib::make_result_code( navlib::navlib_errc::function_not_supported ); +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetFrontView( navlib::matrix_t& matrix ) const +{ + matrix = { 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1 }; + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetCoordinateSystem( navlib::matrix_t& matrix ) const +{ + // Use the right-handed coordinate system X-right, Z-up, Y-in (row vectors) + matrix = { 1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1 }; + return 0; +} + +long NL_3D_VIEWER_PLUGIN_IMPL::GetIsViewRotatable( navlib::bool_t& isRotatable ) const +{ + isRotatable = true; + return 0; +} diff --git a/3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.h b/3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.h new file mode 100644 index 0000000000..708e0c48f2 --- /dev/null +++ b/3d-viewer/3d_navlib/nl_3d_viewer_plugin_impl.h @@ -0,0 +1,133 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2021 3Dconnexion + * Copyright (C) 2021 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 3 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, see . + */ + +/** + * @file nl_3d_viewer_plugin_impl.h + * @brief Declaration of the NL_3D_VIEWER_PLUGIN_IMPL class + */ + +#ifndef NL_3D_VIEWER_PLUGIN_IMPL_H_ +#define NL_3D_VIEWER_PLUGIN_IMPL_H_ + +// TDxWare SDK. +#include + +// KiCAD +#include + +// wx +#include + +// glm +#include + +// Forward declarations. +class EDA_3D_CANVAS; +class TRACK_BALL; + +// Convenience typedef. +typedef TDx::SpaceMouse::Navigation3D::CNavigation3D NAV_3D; + +/** + * The class that implements the accessors and mutators required for + * 3D navigation in an EDA_3D_CANVAS using a SpaceMouse. + */ +class NL_3D_VIEWER_PLUGIN_IMPL : public NAV_3D +{ +public: + /** + * Initializes a new instance of the NL_3DVIEWER_PLUGIN. + * + * @param aCanvas is the viewport to be navigated. + */ + NL_3D_VIEWER_PLUGIN_IMPL( EDA_3D_CANVAS* aCanvas ); + + virtual ~NL_3D_VIEWER_PLUGIN_IMPL(); + + /** + * Set the connection to the 3Dconnexion driver to the focus state so that + * 3DMouse data is routed here. + * + * @param aFocus is true to set the connection active. + */ + void SetFocus( bool aFocus = true ); + +private: + void exportCommandsAndImages(); + + long GetCameraMatrix( navlib::matrix_t& aMatrix ) const override; + long GetPointerPosition( navlib::point_t& aPosition ) const override; + long GetViewExtents( navlib::box_t& aExtents ) const override; + long GetViewFOV( double& aFov ) const override; + long GetViewFrustum( navlib::frustum_t& aFrustum ) const override; + long GetIsViewPerspective( navlib::bool_t& aPerspective ) const override; + long SetCameraMatrix( const navlib::matrix_t& aMatrix ) override; + long SetViewExtents( const navlib::box_t& aExtents ) override; + long SetViewFOV( double aFov ) override; + long SetViewFrustum( const navlib::frustum_t& aFrustum ) override; + long GetModelExtents( navlib::box_t& aExtents ) const override; + long GetSelectionExtents( navlib::box_t& aExtents ) const override; + long GetSelectionTransform( navlib::matrix_t& aTransform ) const override; + long GetIsSelectionEmpty( navlib::bool_t& aEmpty ) const override; + long SetSelectionTransform( const navlib::matrix_t& aMatrix ) override; + long GetPivotPosition( navlib::point_t& aPosition ) const override; + long IsUserPivot( navlib::bool_t& aUserPivot ) const override; + long SetPivotPosition( const navlib::point_t& aPosition ) override; + long GetPivotVisible( navlib::bool_t& aVisible ) const override; + long SetPivotVisible( bool aVisible ) override; + long GetHitLookAt( navlib::point_t& aPosition ) const override; + long SetHitAperture( double aAperture ) override; + long SetHitDirection( const navlib::vector_t& aDirection ) override; + long SetHitLookFrom( const navlib::point_t& aPosition ) override; + long SetHitSelectionOnly( bool aSelectionOnly ) override; + long SetActiveCommand( std::string aCommandId ) override; + + long SetSettingsChanged( long aChangeNumber ) override; + long SetMotionFlag( bool aValue ) override; + long SetTransaction( long aValue ) override; + long SetCameraTarget( const navlib::point_t& aPosition ) override; + + long GetFrontView( navlib::matrix_t& aMatrix ) const override; + long GetCoordinateSystem( navlib::matrix_t& aMatrix ) const override; + long GetIsViewRotatable( navlib::bool_t& isRotatable ) const override; + +private: + EDA_3D_CANVAS* m_canvas; + TRACK_BALL* m_camera; + bool m_capIsMoving; + double m_newWidth; + SFVEC3F m_rayOrigin; + SFVEC3F m_rayDirection; + + // The cached CAMERA affine matrix. This is used to determine if the camera has been + // moved. The camera matrix is the world to camera affine. + mutable glm::mat4 m_cameraMatrix; + + + /** + * Trace mask used to enable or disable the trace output of this class. + * The debug output can be turned on by setting the WXTRACE environment variable to + * "KI_TRACE_NL_3DVIEWER_PLUGIN". See the wxWidgets documentation on wxLogTrace for + * more information. + */ + static const wxChar* m_logTrace; +}; + +#endif // NL_3D_VIEWER_PLUGIN_IMPL_H_ diff --git a/3d-viewer/3d_rendering/camera.cpp b/3d-viewer/3d_rendering/camera.cpp index 20584016ff..eb9bdf87a5 100644 --- a/3d-viewer/3d_rendering/camera.cpp +++ b/3d-viewer/3d_rendering/camera.cpp @@ -28,7 +28,7 @@ #include "camera.h" #include - +#include // A helper function to normalize aAngle between -2PI and +2PI inline void normalise2PI( float& aAngle ) @@ -47,8 +47,8 @@ inline void normalise2PI( float& aAngle ) const wxChar *CAMERA::m_logTrace = wxT( "KI_TRACE_CAMERA" ); -#define DEFAULT_MIN_ZOOM 0.10f -#define DEFAULT_MAX_ZOOM 1.20f +#define DEFAULT_MIN_ZOOM 0.020f +#define DEFAULT_MAX_ZOOM 2.0f CAMERA::CAMERA( float aInitialDistance ) @@ -178,11 +178,17 @@ void CAMERA::updateRotationMatrix() } -const glm::mat4 CAMERA::GetRotationMatrix() const +glm::mat4 CAMERA::GetRotationMatrix() const { return m_rotationMatrix * m_rotationMatrixAux; } +void CAMERA::SetRotationMatrix( const glm::mat4& aRotation ) +{ + m_parametersChanged = true; + std::copy_n( glm::value_ptr( aRotation * glm::inverse( m_rotationMatrixAux ) ), 12, + glm::value_ptr( m_rotationMatrix ) ); +} void CAMERA::rebuildProjection() { @@ -444,6 +450,38 @@ const glm::mat4& CAMERA::GetViewMatrix() const } +void CAMERA::SetViewMatrix( glm::mat4 aViewMatrix ) +{ + SetRotationMatrix( aViewMatrix ); + + // The look at position in the view frame. + glm::vec4 lookat = aViewMatrix * glm::vec4( m_lookat_pos, 1.0f ); + + wxLogTrace( m_logTrace, + wxT( "CAMERA::SetViewMatrix aViewMatrix[3].z =%f, old_zoom=%f, new_zoom=%f, " + "m[3].z=%f" ), + aViewMatrix[3].z, m_zoom, lookat.z / m_camera_pos_init.z, lookat.z ); + + m_zoom = lookat.z / m_camera_pos_init.z; + + if( m_zoom > m_maxZoom ) + { + m_zoom = m_maxZoom; + aViewMatrix[3].z += -lookat.z + m_maxZoom * m_camera_pos_init.z; + } + else if( m_zoom < m_minZoom ) + { + m_zoom = m_minZoom; + aViewMatrix[3].z += -lookat.z + m_minZoom * m_camera_pos_init.z; + } + + m_viewMatrix = std::move( aViewMatrix ); + m_camera_pos = m_viewMatrix + * glm::inverse( m_rotationMatrix * m_rotationMatrixAux + * glm::translate( glm::mat4( 1.0f ), -m_lookat_pos ) )[3]; +} + + const glm::mat4& CAMERA::GetViewMatrix_Inv() const { return m_viewMatrixInverse; @@ -495,22 +533,38 @@ void CAMERA::ZoomReset() bool CAMERA::Zoom( float aFactor ) { - if( ( m_zoom == m_minZoom && aFactor > 1 ) || ( m_zoom == m_maxZoom && aFactor < 1 ) + if( ( m_zoom <= m_minZoom && aFactor > 1 ) || ( m_zoom >= m_maxZoom && aFactor < 1 ) || aFactor == 1 ) { return false; } + float zoom = m_zoom; m_zoom /= aFactor; - zoomChanged(); + if( m_zoom <= m_minZoom && aFactor > 1 ) + { + aFactor = zoom / m_minZoom; + m_zoom = m_minZoom; + } + else if( m_zoom >= m_maxZoom && aFactor < 1 ) + { + aFactor = zoom / m_maxZoom; + m_zoom = m_maxZoom; + } + + m_camera_pos.z /= aFactor; + + updateViewMatrix(); + rebuildProjection(); + return true; } bool CAMERA::Zoom_T1( float aFactor ) { - if( ( m_zoom == m_minZoom && aFactor > 1 ) || ( m_zoom == m_maxZoom && aFactor < 1 ) + if( ( m_zoom <= m_minZoom && aFactor > 1 ) || ( m_zoom >= m_maxZoom && aFactor < 1 ) || aFactor == 1 ) { return false; diff --git a/3d-viewer/3d_rendering/camera.h b/3d-viewer/3d_rendering/camera.h index 4a8df65116..51b4b45bcc 100644 --- a/3d-viewer/3d_rendering/camera.h +++ b/3d-viewer/3d_rendering/camera.h @@ -93,7 +93,7 @@ public: * * @return the rotation matrix of the camera */ - const glm::mat4 GetRotationMatrix() const; + glm::mat4 GetRotationMatrix() const; const glm::mat4& GetViewMatrix() const; const glm::mat4& GetViewMatrix_Inv() const; @@ -108,6 +108,25 @@ public: const SFVEC2F& GetFocalLen() const { return m_focalLen; } float GetNear() const { return m_frustum.nearD; } float GetFar() const { return m_frustum.farD; } + const CAMERA_FRUSTUM& GetFrustum() const { return m_frustum; } + const SFVEC3F& GetLookAtPos() const { return m_lookat_pos; } + + /** + * Set the rotation matrix to be applied in a transformation camera, without + * making any new calculations on camera. + * + * @param aRotation is the total rotation matrix of the camera. + */ + void SetRotationMatrix( const glm::mat4& aRotation ); + + /** + * Set the affine matrix to be applied to a transformation camera. + * + * @param aViewMatrix is the affine matrix of the camera. The affine matrix + * maps coordinates in the world frame to those in the + * camera frame. + */ + void SetViewMatrix( glm::mat4 aViewMatrix ); void SetBoardLookAtPos( const SFVEC3F& aBoardPos ); @@ -143,6 +162,8 @@ public: void ResetXYpos(); void ResetXYpos_T1(); + const wxPoint& GetCurMousePosition() { return m_lastPosition; } + /** * Update the current mouse position without make any new calculations on camera. */ @@ -243,6 +264,8 @@ public: */ void MakeRayAtCurrentMousePosition( SFVEC3F& aOutOrigin, SFVEC3F& aOutDirection ) const; + void Update() { updateFrustum(); } + protected: void zoomChanged(); void rebuildProjection(); diff --git a/3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp b/3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp index 0d389e57ca..d3dfef1483 100644 --- a/3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp +++ b/3d-viewer/3d_viewer/eda_3d_viewer_frame.cpp @@ -50,6 +50,10 @@ #include #include +#if defined( KICAD_USE_3DCONNEXION ) +#include <3d_navlib/nl_3d_viewer_plugin.h> +#endif + /** * Flag to enable 3D viewer main frame window debug tracing. * @@ -76,14 +80,12 @@ BEGIN_EVENT_TABLE( EDA_3D_VIEWER_FRAME, EDA_BASE_FRAME ) END_EVENT_TABLE() -EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME( KIWAY *aKiway, PCB_BASE_FRAME *aParent, - const wxString &aTitle, long style ) : +EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME( KIWAY* aKiway, PCB_BASE_FRAME* aParent, + const wxString& aTitle, long style ) : KIWAY_PLAYER( aKiway, aParent, FRAME_PCB_DISPLAY3D, aTitle, wxDefaultPosition, wxDefaultSize, style, QUALIFIED_VIEWER3D_FRAMENAME( aParent ) ), - m_mainToolBar( nullptr ), - m_canvas( nullptr ), - m_currentCamera( m_trackBallCamera ), - m_trackBallCamera( 2 * RANGE_SCALE_3D ) + m_mainToolBar( nullptr ), m_canvas( nullptr ), m_currentCamera( m_trackBallCamera ), + m_trackBallCamera( 2 * RANGE_SCALE_3D ), m_spaceMouse( nullptr ) { wxLogTrace( m_logTrace, "EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME %s", aTitle ); @@ -154,6 +156,10 @@ EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME( KIWAY *aKiway, PCB_BASE_FRAME *aParent m_canvas->SetInfoBar( m_infoBar ); m_canvas->SetStatusBar( status_bar ); +#if defined( KICAD_USE_3DCONNEXION ) + m_spaceMouse = new NL_3D_VIEWER_PLUGIN( m_canvas ); +#endif + // Fixes bug in Windows (XP and possibly others) where the canvas requires the focus // in order to receive mouse events. Otherwise, the user has to click somewhere on // the canvas before it will respond to mouse wheel events. @@ -163,6 +169,13 @@ EDA_3D_VIEWER_FRAME::EDA_3D_VIEWER_FRAME( KIWAY *aKiway, PCB_BASE_FRAME *aParent EDA_3D_VIEWER_FRAME::~EDA_3D_VIEWER_FRAME() { +#if defined( KICAD_USE_3DCONNEXION ) + if( m_spaceMouse != nullptr ) + { + delete m_spaceMouse; + } +#endif + m_canvas->SetEventDispatcher( nullptr ); m_auimgr.UnInit(); @@ -386,6 +399,13 @@ void EDA_3D_VIEWER_FRAME::OnActivate( wxActivateEvent &aEvent ) m_canvas->SetFocus(); } +#if defined( KICAD_USE_3DCONNEXION ) + if( m_spaceMouse != nullptr ) + { + m_spaceMouse->SetFocus( aEvent.GetActive() ); + } +#endif + aEvent.Skip(); // required under wxMAC } diff --git a/3d-viewer/3d_viewer/eda_3d_viewer_frame.h b/3d-viewer/3d_viewer/eda_3d_viewer_frame.h index ff2ca577da..c78c652967 100644 --- a/3d-viewer/3d_viewer/eda_3d_viewer_frame.h +++ b/3d-viewer/3d_viewer/eda_3d_viewer_frame.h @@ -47,6 +47,8 @@ #define KICAD_DEFAULT_3D_DRAWFRAME_STYLE (wxDEFAULT_FRAME_STYLE | wxWANTS_CHARS) +// Forward declarations +class NL_3D_VIEWER_PLUGIN; enum EDA_3D_VIEWER_STATUSBAR { @@ -168,6 +170,8 @@ private: bool m_disable_ray_tracing; + NL_3D_VIEWER_PLUGIN* m_spaceMouse; + /** * Trace mask used to enable or disable the trace output of this class. * The debug output can be turned on by setting the WXTRACE environment variable to diff --git a/3d-viewer/CMakeLists.txt b/3d-viewer/CMakeLists.txt index 664065f00a..d48dabadb3 100644 --- a/3d-viewer/CMakeLists.txt +++ b/3d-viewer/CMakeLists.txt @@ -120,3 +120,9 @@ target_link_libraries( 3d-viewer kicad_3dsg ) add_subdirectory( 3d_cache ) + +if( KICAD_USE_3DCONNEXION ) + message( STATUS "Including 3Dconnexion SpaceMouse navigation support in 3d-viewer" ) + add_subdirectory( 3d_navlib ) + target_link_libraries( 3d-viewer 3d-viewer_navlib) +endif() diff --git a/CMakeLists.txt b/CMakeLists.txt index 7480331d93..8298d5d2d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -152,6 +152,14 @@ option( KICAD_STEP_EXPORT_LIB "Build and use kicad2step as a library, meant for debugging" OFF ) +option( KICAD_USE_3DCONNEXION + "Build KiCad with support for 3Dconnexion devices" + ON ) + +if( KICAD_USE_3DCONNEXION ) + add_compile_definitions( KICAD_USE_3DCONNEXION ) +endif() + # Global setting: exports are explicit set( CMAKE_CXX_VISIBILITY_PRESET "hidden" ) set( CMAKE_VISIBILITY_INLINES_HIDDEN ON ) diff --git a/thirdparty/3dxware_sdk/inc/SpaceMouse/CImage.hpp b/thirdparty/3dxware_sdk/inc/SpaceMouse/CImage.hpp index fb6d1f362c..f2d83b3e1d 100644 --- a/thirdparty/3dxware_sdk/inc/SpaceMouse/CImage.hpp +++ b/thirdparty/3dxware_sdk/inc/SpaceMouse/CImage.hpp @@ -39,7 +39,7 @@ #endif #endif -#ifdef __APPLE__ +#ifndef _WIN32 #define IS_INTRESOURCE(x) false #endif diff --git a/thirdparty/3dxware_sdk/inc/navlib/navlib_types.h b/thirdparty/3dxware_sdk/inc/navlib/navlib_types.h index a79490aa87..8f17acc093 100644 --- a/thirdparty/3dxware_sdk/inc/navlib/navlib_types.h +++ b/thirdparty/3dxware_sdk/inc/navlib/navlib_types.h @@ -45,6 +45,7 @@ typedef unsigned int size_t; #include #if __cplusplus +#include #include #include ///