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 ///