From 2068ed31a42871ca564a9acd62fa3b6cd99c1dca Mon Sep 17 00:00:00 2001 From: Kamil Galik Date: Sat, 21 Sep 2024 08:22:19 +0000 Subject: [PATCH] ADDED: 3D Mouse support in Gerber Viewer and Drawing Sheet Editor Fixes https://gitlab.com/kicad/code/kicad/-/issues/13306 --- gerbview/CMakeLists.txt | 7 + gerbview/gerbview_frame.cpp | 50 +- gerbview/gerbview_frame.h | 8 + gerbview/navlib/CMakeLists.txt | 25 + gerbview/navlib/nl_gerbview_plugin.cpp | 52 ++ gerbview/navlib/nl_gerbview_plugin.h | 69 ++ gerbview/navlib/nl_gerbview_plugin_impl.cpp | 598 ++++++++++++++++++ gerbview/navlib/nl_gerbview_plugin_impl.h | 147 +++++ pagelayout_editor/CMakeLists.txt | 7 + pagelayout_editor/navlib/CMakeLists.txt | 25 + .../navlib/nl_pl_editor_plugin.cpp | 52 ++ .../navlib/nl_pl_editor_plugin.h | 69 ++ .../navlib/nl_pl_editor_plugin_impl.cpp | 598 ++++++++++++++++++ .../navlib/nl_pl_editor_plugin_impl.h | 147 +++++ pagelayout_editor/pl_editor_frame.cpp | 44 +- pagelayout_editor/pl_editor_frame.h | 9 + 16 files changed, 1889 insertions(+), 18 deletions(-) create mode 100644 gerbview/navlib/CMakeLists.txt create mode 100644 gerbview/navlib/nl_gerbview_plugin.cpp create mode 100644 gerbview/navlib/nl_gerbview_plugin.h create mode 100644 gerbview/navlib/nl_gerbview_plugin_impl.cpp create mode 100644 gerbview/navlib/nl_gerbview_plugin_impl.h create mode 100644 pagelayout_editor/navlib/CMakeLists.txt create mode 100644 pagelayout_editor/navlib/nl_pl_editor_plugin.cpp create mode 100644 pagelayout_editor/navlib/nl_pl_editor_plugin.h create mode 100644 pagelayout_editor/navlib/nl_pl_editor_plugin_impl.cpp create mode 100644 pagelayout_editor/navlib/nl_pl_editor_plugin_impl.h diff --git a/gerbview/CMakeLists.txt b/gerbview/CMakeLists.txt index 06e735a1d8..348aa6e0cb 100644 --- a/gerbview/CMakeLists.txt +++ b/gerbview/CMakeLists.txt @@ -136,6 +136,13 @@ target_link_libraries( gerbview_kiface_objects core ) +message( STATUS "Including 3Dconnexion SpaceMouse navigation support in gerbview" ) +add_subdirectory( navlib ) + +target_link_libraries( gerbview_kiface_objects PUBLIC gerbview_navlib) + +add_dependencies( gerbview_kiface_objects gerbview_navlib ) + # the main gerbview program, in DSO form. add_library( gerbview_kiface MODULE ) diff --git a/gerbview/gerbview_frame.cpp b/gerbview/gerbview_frame.cpp index e9caff4df1..709aefe694 100644 --- a/gerbview/gerbview_frame.cpp +++ b/gerbview/gerbview_frame.cpp @@ -59,19 +59,21 @@ #include #include +#include +#include -GERBVIEW_FRAME::GERBVIEW_FRAME( KIWAY* aKiway, wxWindow* aParent ) - : EDA_DRAW_FRAME( aKiway, aParent, FRAME_GERBER, wxT( "GerbView" ), wxDefaultPosition, +GERBVIEW_FRAME::GERBVIEW_FRAME( KIWAY* aKiway, wxWindow* aParent ) : + EDA_DRAW_FRAME( aKiway, aParent, FRAME_GERBER, wxT( "GerbView" ), wxDefaultPosition, wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, GERBVIEW_FRAME_NAME, gerbIUScale ), - m_TextInfo( nullptr ), - m_zipFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_ZIP_FILE1, - ID_GERBVIEW_ZIP_FILE_LIST_CLEAR, _( "Clear Recent Zip Files" ) ), - m_drillFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_DRILL_FILE1, - ID_GERBVIEW_DRILL_FILE_LIST_CLEAR, _( "Clear Recent Drill Files" ) ), - m_jobFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_JOB_FILE1, - ID_GERBVIEW_JOB_FILE_LIST_CLEAR, _( "Clear Recent Job Files" ) ), - m_activeLayer( 0 ) + m_TextInfo( nullptr ), + m_zipFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_ZIP_FILE1, + ID_GERBVIEW_ZIP_FILE_LIST_CLEAR, _( "Clear Recent Zip Files" ) ), + m_drillFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_DRILL_FILE1, + ID_GERBVIEW_DRILL_FILE_LIST_CLEAR, _( "Clear Recent Drill Files" ) ), + m_jobFileHistory( DEFAULT_FILE_HISTORY_SIZE, ID_GERBVIEW_JOB_FILE1, + ID_GERBVIEW_JOB_FILE_LIST_CLEAR, _( "Clear Recent Job Files" ) ), + m_activeLayer( 0 ) { m_maximizeByDefault = true; m_gerberLayout = nullptr; @@ -1070,6 +1072,18 @@ void GERBVIEW_FRAME::ActivateGalCanvas() ReCreateOptToolbar(); ReCreateMenuBar(); + + try + { + if( !m_spaceMouse ) + m_spaceMouse = std::make_unique(); + + m_spaceMouse->SetCanvas( galCanvas ); + } + catch( const std::system_error& e ) + { + wxLogTrace( wxT( "KI_TRACE_NAVLIB" ), e.what() ); + } } @@ -1237,3 +1251,19 @@ void GERBVIEW_FRAME::ToggleLayerManager() m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools ); m_auimgr.Update(); } + +void GERBVIEW_FRAME::handleActivateEvent( wxActivateEvent& aEvent ) +{ + EDA_DRAW_FRAME::handleActivateEvent(aEvent); + + if( m_spaceMouse ) + m_spaceMouse->SetFocus( aEvent.GetActive() ); +} + +void GERBVIEW_FRAME::handleIconizeEvent( wxIconizeEvent& aEvent ) +{ + EDA_DRAW_FRAME::handleIconizeEvent(aEvent); + + if( m_spaceMouse ) + m_spaceMouse->SetFocus( false ); +} diff --git a/gerbview/gerbview_frame.h b/gerbview/gerbview_frame.h index 4df6b76252..9b2e4dadef 100644 --- a/gerbview/gerbview_frame.h +++ b/gerbview/gerbview_frame.h @@ -30,6 +30,8 @@ #include #include +#include + #define NO_AVAILABLE_LAYERS UNDEFINED_LAYER class DCODE_SELECTION_BOX; @@ -43,6 +45,7 @@ class LSET; class REPORTER; class SELECTION; class wxStaticText; +class NL_GERBVIEW_PLUGIN; /** @@ -456,6 +459,9 @@ protected: void setupUIConditions() override; void doReCreateMenuBar() override; + void handleActivateEvent( wxActivateEvent& aEvent ) override; + void handleIconizeEvent( wxIconizeEvent& aEvent ) override; + private: void updateComponentListSelectBox(); void updateNetnameListSelectBox(); @@ -525,6 +531,8 @@ private: // relative to the m_SelAperAttributesBox wxStaticText* m_dcodeText; // a message on the auxiliary toolbar, // relative to the m_DCodeSelector + + std::unique_ptr m_spaceMouse; }; #endif /* WX_GERBER_STRUCT_H */ diff --git a/gerbview/navlib/CMakeLists.txt b/gerbview/navlib/CMakeLists.txt new file mode 100644 index 0000000000..ebbeb0fb14 --- /dev/null +++ b/gerbview/navlib/CMakeLists.txt @@ -0,0 +1,25 @@ + +add_library(gerbview_navlib STATIC + "nl_gerbview_plugin.cpp" + "nl_gerbview_plugin_impl.cpp" +) + +# gerbview_navlib depends on make_lexer outputs in common +add_dependencies( gerbview_navlib common ) + +# Find the 3DxWare SDK component 3DxWare::NlClient +# find_package(TDxWare_SDK 4.0 REQUIRED COMPONENTS 3DxWare::Navlib) +target_compile_definitions(gerbview_navlib PRIVATE + $ +) +target_compile_options(gerbview_navlib PRIVATE + $ +) +target_include_directories(gerbview_navlib PRIVATE + $ + $ +) +target_link_libraries(gerbview_navlib + $ + 3DxWare::Navlib +) diff --git a/gerbview/navlib/nl_gerbview_plugin.cpp b/gerbview/navlib/nl_gerbview_plugin.cpp new file mode 100644 index 0000000000..d34b3b891d --- /dev/null +++ b/gerbview/navlib/nl_gerbview_plugin.cpp @@ -0,0 +1,52 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_gerbview_plugin.h" +#include "nl_gerbview_plugin_impl.h" + +#include +#include + + +NL_GERBVIEW_PLUGIN::NL_GERBVIEW_PLUGIN() +{ + if( ADVANCED_CFG::GetCfg().m_Use3DConnexionDriver + && KIPLATFORM::DRIVERS::Valid3DConnexionDriverVersion() ) + { + m_impl = std::make_unique(); + } +} + + +NL_GERBVIEW_PLUGIN::~NL_GERBVIEW_PLUGIN() = default; + + +void NL_GERBVIEW_PLUGIN::SetFocus( bool focus ) +{ + if( m_impl ) + m_impl->SetFocus( focus ); +} + + +void NL_GERBVIEW_PLUGIN::SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ) +{ + if( m_impl ) + m_impl->SetCanvas( aViewport ); +} diff --git a/gerbview/navlib/nl_gerbview_plugin.h b/gerbview/navlib/nl_gerbview_plugin.h new file mode 100644 index 0000000000..1839a4152e --- /dev/null +++ b/gerbview/navlib/nl_gerbview_plugin.h @@ -0,0 +1,69 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_gerbview_plugin.h + * @brief Declaration of the NL_GERBVIEW_PLUGIN class + */ + +#ifndef NL_GERBVIEW_PLUGIN_H_ +#define NL_GERBVIEW_PLUGIN_H_ + +#include + +// Forward declarations. +class EDA_DRAW_PANEL_GAL; +class NL_GERBVIEW_PLUGIN_IMPL; + +/** + * The class that implements the public interface to the SpaceMouse plug-in. + */ +class NL_GERBVIEW_PLUGIN +{ +public: + /** + * Initializes a new instance of the NL_GERBVIEW_PLUGIN. + */ + NL_GERBVIEW_PLUGIN(); + + virtual ~NL_GERBVIEW_PLUGIN(); + + + /** + * Sets the viewport controlled by the SpaceMouse. + * + * @param aViewport is the viewport to be navigated. + */ + void SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ); + + + /** + * Set the connection to the 3Dconnexion driver to the focus state so that + * 3DMouse data is routed to this connexion. + * + * @param aFocus is true to set the connexion active. + */ + void SetFocus( bool aFocus ); + +private: + std::unique_ptr m_impl; +}; + +#endif // NL_GERBVIEW_PLUGIN_H_ diff --git a/gerbview/navlib/nl_gerbview_plugin_impl.cpp b/gerbview/navlib/nl_gerbview_plugin_impl.cpp new file mode 100644 index 0000000000..a7cb50f15e --- /dev/null +++ b/gerbview/navlib/nl_gerbview_plugin_impl.cpp @@ -0,0 +1,598 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_gerbview_plugin_impl.h" + +// KiCAD includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// stdlib +#include +#include +#include +#include +#include +#include + +#include +#include + + +/** + * Flag to enable the NL_GERBVIEW_PLUGIN debug tracing. + * + * Use "KI_TRACE_NL_GERBVIEW_PLUGIN" to enable. + * + * @ingroup trace_env_vars + */ +const wxChar* NL_GERBVIEW_PLUGIN_IMPL::m_logTrace = wxT( "KI_TRACE_NL_GERBVIEW_PLUGIN" ); + + +NL_GERBVIEW_PLUGIN_IMPL::NL_GERBVIEW_PLUGIN_IMPL() : CNavigation3D( false, false ) +{ + PutProfileHint( "KiCAD Gerbview" ); +} + + +NL_GERBVIEW_PLUGIN_IMPL::~NL_GERBVIEW_PLUGIN_IMPL() +{ + std::error_code m_errCode; + EnableNavigation( false, m_errCode ); + if( m_errCode.value() != 0 ) + { + wxLogTrace( wxT( "KI_TRACE_NAVLIB" ), + wxT( "Error occured when calling EnableNavigation. Error code: %d" ), + m_errCode.value() ); + } +} + + +void NL_GERBVIEW_PLUGIN_IMPL::SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ) +{ + m_viewport2D = aViewport; + + if( m_viewport2D == nullptr ) + { + return; + } + + m_view = m_viewport2D->GetView(); + + if( m_view == nullptr ) + { + return; + } + + m_viewportWidth = m_view->GetBoundary().GetWidth(); + + if( !IsEnabled() ) + { + // Use the default settings for the connexion to the 3DMouse navigation + // They are use a single-threaded threading model and row vectors. + EnableNavigation( true ); + + // Use the SpaceMouse internal timing source for the frame rate. + PutFrameTimingSource( TimingSource::SpaceMouse ); + + exportCommandsAndImages(); + } +} + + +void NL_GERBVIEW_PLUGIN_IMPL::SetFocus( bool aFocus ) +{ + wxLogTrace( m_logTrace, wxT( "NL_GERBVIEW_PLUGIN_IMPL::SetFocus %d" ), aFocus ); + NAV_3D::Write( navlib::focus_k, aFocus ); +} + +// temporary store for the command categories +using CATEGORY_STORE = std::map>; + + +/** + * 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. + */ +static void add_category( const std::string& aCategoryPath, CATEGORY_STORE& aCategoryStore ) +{ + using TDx::SpaceMouse::CCategory; + + auto 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 ); + + if( !aCategoryStore.contains( parentPath ) ) + { + add_category( parentPath, aCategoryStore ); + parent_iter = aCategoryStore.find( parentPath ); + } + } + + std::string name = aCategoryPath.substr( pos + 1 ); + auto categoryNode = std::make_unique( aCategoryPath.c_str(), name.c_str() ); + + aCategoryStore.try_emplace( aCategoryStore.end(), aCategoryPath, categoryNode.get() ); + + parent_iter->second->push_back( std::move( categoryNode ) ); +} + + +/** + * add_category wrapper. + * + * Function checks if path exists in the category and adds it if it doesn't. + * + * @param aCategoryPath is the std::string representation of the category. + * @param aCategoryStore is the CATEGORY_STORE instance to add to. + */ +static void try_add_category( const std::string& aCategoryPath, CATEGORY_STORE& aCategoryStore ) +{ + if( !aCategoryStore.contains( aCategoryPath ) ) + { + add_category( aCategoryPath, aCategoryStore ); + } +} + + +void NL_GERBVIEW_PLUGIN_IMPL::exportCommandsAndImages() +{ + wxLogTrace( m_logTrace, wxT( "NL_GERBVIEW_PLUGIN_IMPL::exportCommandsAndImages" ) ); + + std::list actions = ACTION_MANAGER::GetActionList(); + + if( actions.empty() ) + return; + + using TDx::SpaceMouse::CCommand; + using TDx::SpaceMouse::CCommandSet; + + // The root action set node + CCommandSet commandSet( "GERBER_EDITOR", "Gerber Viewer" ); + + // Activate the command set + NAV_3D::PutActiveCommands( commandSet.GetId() ); + + // temporary store for the categories initialized with action set + CATEGORY_STORE categoryStore{ CATEGORY_STORE::value_type( ".", &commandSet ) }; + + std::vector vImages; + + for( const auto action : actions ) + { + std::string label = action->GetMenuLabel().ToStdString(); + + if( label.empty() ) + continue; + + std::string name = action->GetName(); + + // Do no export commands for the 3DViewer app. + + if( name.rfind( "3DViewer.", 0 ) == 0 ) + continue; + + std::string strCategory = action->GetToolName(); + std::string description = action->GetDescription().ToStdString(); + + try_add_category( strCategory, categoryStore ); + CATEGORY_STORE::iterator iter = categoryStore.find( strCategory ); + + // 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() ) + { + const wxStreamBuffer* streamBuffer = imageStream.GetOutputStreamBuffer(); + TDx::CImage tdxImage = TDx::CImage::FromData( "", 0, name.c_str() ); + tdxImage.AssignImage( + std::string( std::bit_cast( streamBuffer->GetBufferStart() ), + streamBuffer->GetBufferSize() ), + 0 ); + + wxLogTrace( m_logTrace, wxT( "Adding image for : %s" ), name ); + vImages.push_back( std::move( tdxImage ) ); + } + } + + wxLogTrace( m_logTrace, wxT( "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_GERBVIEW_PLUGIN_IMPL::GetCameraMatrix( navlib::matrix_t& matrix ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + m_viewPosition = m_view->GetCenter(); + + double x = m_view->IsMirroredX() ? -1 : 1; + double y = m_view->IsMirroredY() ? 1 : -1; + + // x * y * z = 1 for a right-handed coordinate system. + double z = x * y; + + // Note: the connexion has been configured as row vectors, the coordinate system is defined in + // NL_GERBVIEW_PLUGIN_IMPL::GetCoordinateSystem and the front view in NL_GERBVIEW_PLUGIN_IMPL::GetFrontView. + matrix = { { { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, m_viewPosition.x, m_viewPosition.y, 0, + 1 } } }; + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetPointerPosition( navlib::point_t& position ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + VECTOR2D mouse_pointer = m_viewport2D->GetViewControls()->GetMousePosition(); + + position.x = mouse_pointer.x; + position.y = mouse_pointer.y; + position.z = 0; + + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetViewExtents( navlib::box_t& extents ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + double scale = m_viewport2D->GetGAL()->GetWorldScale(); + BOX2D box = m_view->GetViewport(); + + m_viewportWidth = box.GetWidth(); + + extents.min_x = -box.GetWidth() / 2.0; + extents.min_y = -box.GetHeight() / 2.0; + extents.min_z = m_viewport2D->GetGAL()->GetMinDepth() / scale; + extents.max_x = box.GetWidth() / 2.0; + extents.max_y = box.GetHeight() / 2.0; + extents.max_z = m_viewport2D->GetGAL()->GetMaxDepth() / scale; + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetIsViewPerspective( navlib::bool_t& perspective ) const +{ + perspective = false; + + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetCameraMatrix( const navlib::matrix_t& matrix ) +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + + long result = 0; + VECTOR2D viewPos( matrix.m4x4[3][0], matrix.m4x4[3][1] ); + + if( !equals( m_view->GetCenter(), m_viewPosition, + static_cast( FLT_EPSILON ) ) ) + { + m_view->SetCenter( viewPos + m_view->GetCenter() - m_viewPosition ); + result = navlib::make_result_code( navlib::navlib_errc::error ); + } + else + { + m_view->SetCenter( viewPos ); + } + + m_viewPosition = viewPos; + + return result; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetViewExtents( const navlib::box_t& extents ) +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + + long result = 0; + + if( m_viewportWidth != m_view->GetViewport().GetWidth() ) + result = navlib::make_result_code( navlib::navlib_errc::error ); + + double width = m_viewportWidth; + m_viewportWidth = extents.max_x - extents.min_x; + + double scale = width / m_viewportWidth * m_view->GetScale(); + m_view->SetScale( scale, m_view->GetCenter() ); + + if( !equals( m_view->GetScale(), scale, static_cast( FLT_EPSILON ) ) ) + result = navlib::make_result_code( navlib::navlib_errc::error ); + + return result; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetViewFOV( double fov ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetViewFrustum( const navlib::frustum_t& frustum ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetModelExtents( navlib::box_t& extents ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + BOX2I box = static_cast( m_viewport2D->GetParent() )->GetDocumentExtents(); + box.Normalize(); + + double half_depth = 0.1 / m_viewport2D->GetGAL()->GetWorldScale(); + + if( box.GetWidth() == 0 && box.GetHeight() == 0 ) + half_depth = 0; + + extents.min_x = static_cast( box.GetOrigin().x ); + extents.min_y = static_cast( box.GetOrigin().y ); + extents.min_z = -half_depth; + extents.max_x = static_cast( box.GetEnd().x ); + extents.max_y = static_cast( box.GetEnd().y ); + extents.max_z = half_depth; + + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetCoordinateSystem( navlib::matrix_t& matrix ) const +{ + // The coordinate system is defined as x to the right, y down and z into the screen. + matrix = { { { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 } } }; + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetFrontView( navlib::matrix_t& matrix ) const +{ + matrix = { { { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 } } }; + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetIsSelectionEmpty( navlib::bool_t& empty ) const +{ + empty = true; + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetIsViewRotatable( navlib::bool_t& isRotatable ) const +{ + isRotatable = false; + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetActiveCommand( std::string commandId ) +{ + if( commandId.empty() ) + return 0; + + if(m_viewport2D == nullptr) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + wxWindow* parent = m_viewport2D->GetParent(); + + // Only allow command execution if the window is enabled. i.e. there is not a modal dialog + // currently active. + if( parent == nullptr || !parent->IsEnabled() ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + TOOL_MANAGER* tool_manager = dynamic_cast( parent )->GetToolManager(); + + // Only allow for command execution if the tool manager is accessible. + if( tool_manager == nullptr ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + for( std::list actions = ACTION_MANAGER::GetActionList(); + const auto action : actions ) + { + if( action == nullptr ) + { + continue; + } + + if( commandId == action->GetName() ) + { + // Get the selection to use to test if the action is enabled + const SELECTION& sel = tool_manager->GetToolHolder()->GetCurrentSelection(); + + const ACTION_CONDITIONS* aCond = + tool_manager->GetActionManager()->GetCondition( *action ); + + if( aCond == nullptr ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + aCond->enableCondition( sel ); + tool_manager->RunAction( *action ); + break; + } + } + + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetSettingsChanged( long change ) +{ + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetMotionFlag( bool value ) +{ + m_isMoving = value; + + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetTransaction( long value ) +{ + if( value == 0L ) + m_viewport2D->ForceRefresh(); + + return 0; +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetViewFOV( double& fov ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetViewFrustum( navlib::frustum_t& frustum ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetSelectionExtents( navlib::box_t& extents ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetSelectionTransform( navlib::matrix_t& transform ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetSelectionTransform( const navlib::matrix_t& matrix ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetPivotPosition( navlib::point_t& position ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::IsUserPivot( navlib::bool_t& userPivot ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetPivotPosition( const navlib::point_t& position ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetPivotVisible( navlib::bool_t& visible ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetPivotVisible( bool visible ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::GetHitLookAt( navlib::point_t& position ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetHitAperture( double aperture ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetHitDirection( const navlib::vector_t& direction ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetHitLookFrom( const navlib::point_t& eye ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetHitSelectionOnly( bool onlySelection ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_GERBVIEW_PLUGIN_IMPL::SetCameraTarget( const navlib::point_t& position ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} diff --git a/gerbview/navlib/nl_gerbview_plugin_impl.h b/gerbview/navlib/nl_gerbview_plugin_impl.h new file mode 100644 index 0000000000..38bbadd2df --- /dev/null +++ b/gerbview/navlib/nl_gerbview_plugin_impl.h @@ -0,0 +1,147 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_gerbview_plugin_impl.h + * @brief Declaration of the NL_GERBVIEW_PLUGIN_IMPL class + */ + +#ifndef NL_GERBVIEW_PLUGIN_IMPL_H_ +#define NL_GERBVIEW_PLUGIN_IMPL_H_ + +#if _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0603 +#endif +#endif + +// TDxWare SDK. +#include + +// wx +#include + +// KiCAD +#include + +// stdlib +#include + +// Forward declarations. +class EDA_DRAW_PANEL_GAL; +namespace KIGFX +{ +class VIEW; +} + +// Convenience typedef. +typedef TDx::SpaceMouse::Navigation3D::CNavigation3D NAV_3D; + +/** + * The class that implements the accessors and mutators required for + * 3D navigation in an PCB_DRAW_PANEL_GAL using a SpaceMouse. + */ +class NL_GERBVIEW_PLUGIN_IMPL : public NAV_3D +{ +public: + /** + * Initializes a new instance of the NL_GERBVIEW_PLUGIN_IMPL. + */ + NL_GERBVIEW_PLUGIN_IMPL(); + + + virtual ~NL_GERBVIEW_PLUGIN_IMPL(); + + + /** + * Sets the viewport controlled by the SpaceMouse. + * + * @param aViewport is the viewport to be navigated. + */ + void SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ); + + + /** + * 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 ); + +private: + /** + * Export the invocable actions and images to the 3Dconnexion UI. + */ + 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_DRAW_PANEL_GAL* m_viewport2D = nullptr; + KIGFX::VIEW* m_view = nullptr; + bool m_isMoving = false; + mutable double m_viewportWidth = 0.0; + mutable VECTOR2D m_viewPosition; + + /** + * 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_GERBVIEW_PLUGIN". See the wxWidgets documentation on wxLogTrace for + * more information. + */ + static const wxChar* m_logTrace; +}; + +#endif // NL_GERBVIEW_PLUGIN_IMPL diff --git a/pagelayout_editor/CMakeLists.txt b/pagelayout_editor/CMakeLists.txt index 527aed4e76..f287e5368a 100644 --- a/pagelayout_editor/CMakeLists.txt +++ b/pagelayout_editor/CMakeLists.txt @@ -120,6 +120,13 @@ target_link_options( pl_editor_kiface PRIVATE # if building pl_editor, then also build pl_editor_kiface if out of date. add_dependencies( pl_editor pl_editor_kiface ) +message( STATUS "Including 3Dconnexion SpaceMouse navigation support in pagelayout editor" ) +add_subdirectory( navlib ) + +target_link_libraries( pl_editor_kiface pl_editor_navlib) + +add_dependencies( pl_editor_kiface pl_editor_navlib) + # these 2 binaries are a matched set, keep them together: if( APPLE ) set_target_properties( pl_editor PROPERTIES diff --git a/pagelayout_editor/navlib/CMakeLists.txt b/pagelayout_editor/navlib/CMakeLists.txt new file mode 100644 index 0000000000..ad3c388736 --- /dev/null +++ b/pagelayout_editor/navlib/CMakeLists.txt @@ -0,0 +1,25 @@ + +add_library(pl_editor_navlib STATIC + "nl_pl_editor_plugin.cpp" + "nl_pl_editor_plugin_impl.cpp" +) + +# pl_editor_navlib depends on make_lexer outputs in common +add_dependencies( pl_editor_navlib common ) + +# Find the 3DxWare SDK component 3DxWare::NlClient +# find_package(TDxWare_SDK 4.0 REQUIRED COMPONENTS 3DxWare::Navlib) +target_compile_definitions(pl_editor_navlib PRIVATE + $ +) +target_compile_options(pl_editor_navlib PRIVATE + $ +) +target_include_directories(pl_editor_navlib PRIVATE + $ + $ +) +target_link_libraries(pl_editor_navlib + $ + 3DxWare::Navlib +) diff --git a/pagelayout_editor/navlib/nl_pl_editor_plugin.cpp b/pagelayout_editor/navlib/nl_pl_editor_plugin.cpp new file mode 100644 index 0000000000..d029ea6988 --- /dev/null +++ b/pagelayout_editor/navlib/nl_pl_editor_plugin.cpp @@ -0,0 +1,52 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_pl_editor_plugin.h" +#include "nl_pl_editor_plugin_impl.h" + +#include +#include + + +NL_PL_EDITOR_PLUGIN::NL_PL_EDITOR_PLUGIN() +{ + if( ADVANCED_CFG::GetCfg().m_Use3DConnexionDriver + && KIPLATFORM::DRIVERS::Valid3DConnexionDriverVersion() ) + { + m_impl = std::make_unique(); + } +} + + +NL_PL_EDITOR_PLUGIN::~NL_PL_EDITOR_PLUGIN() = default; + + +void NL_PL_EDITOR_PLUGIN::SetFocus( bool focus ) +{ + if( m_impl ) + m_impl->SetFocus( focus ); +} + + +void NL_PL_EDITOR_PLUGIN::SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ) +{ + if( m_impl ) + m_impl->SetCanvas( aViewport ); +} diff --git a/pagelayout_editor/navlib/nl_pl_editor_plugin.h b/pagelayout_editor/navlib/nl_pl_editor_plugin.h new file mode 100644 index 0000000000..e1472d8a6b --- /dev/null +++ b/pagelayout_editor/navlib/nl_pl_editor_plugin.h @@ -0,0 +1,69 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_gerbview_plugin.h + * @brief Declaration of the NL_PL_EDITOR_PLUGIN class + */ + +#ifndef NL_GERBVIEW_PLUGIN_H_ +#define NL_GERBVIEW_PLUGIN_H_ + +#include + +// Forward declarations. +class EDA_DRAW_PANEL_GAL; +class NL_PL_EDITOR_PLUGIN_IMPL; + +/** + * The class that implements the public interface to the SpaceMouse plug-in. + */ +class NL_PL_EDITOR_PLUGIN +{ +public: + /** + * Initializes a new instance of the NL_PL_EDITOR_PLUGIN. + */ + NL_PL_EDITOR_PLUGIN(); + + virtual ~NL_PL_EDITOR_PLUGIN(); + + + /** + * Sets the viewport controlled by the SpaceMouse. + * + * @param aViewport is the viewport to be navigated. + */ + void SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ); + + + /** + * Set the connection to the 3Dconnexion driver to the focus state so that + * 3DMouse data is routed to this connexion. + * + * @param aFocus is true to set the connexion active. + */ + void SetFocus( bool aFocus ); + +private: + std::unique_ptr m_impl; +}; + +#endif // NL_GERBVIEW_PLUGIN_H_ diff --git a/pagelayout_editor/navlib/nl_pl_editor_plugin_impl.cpp b/pagelayout_editor/navlib/nl_pl_editor_plugin_impl.cpp new file mode 100644 index 0000000000..bcbdfc5388 --- /dev/null +++ b/pagelayout_editor/navlib/nl_pl_editor_plugin_impl.cpp @@ -0,0 +1,598 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_pl_editor_plugin_impl.h" + +// KiCAD includes +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// stdlib +#include +#include +#include +#include +#include +#include + +#include +#include + + +/** + * Flag to enable the NL_PL_EDITOR_PLUGIN debug tracing. + * + * Use "KI_TRACE_NL_PL_EDITOR_PLUGIN" to enable. + * + * @ingroup trace_env_vars + */ +const wxChar* NL_PL_EDITOR_PLUGIN_IMPL::m_logTrace = wxT( "KI_TRACE_NL_PL_EDITOR_PLUGIN" ); + + +NL_PL_EDITOR_PLUGIN_IMPL::NL_PL_EDITOR_PLUGIN_IMPL() : CNavigation3D( false, false ) +{ + PutProfileHint( "KiCAD Datasheet Editor" ); +} + + +NL_PL_EDITOR_PLUGIN_IMPL::~NL_PL_EDITOR_PLUGIN_IMPL() +{ + std::error_code m_errCode; + EnableNavigation( false, m_errCode ); + if( m_errCode.value() != 0 ) + { + wxLogTrace( wxT( "KI_TRACE_NAVLIB" ), + wxT( "Error occured when calling EnableNavigation. Error code: %d" ), + m_errCode.value() ); + } +} + + +void NL_PL_EDITOR_PLUGIN_IMPL::SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ) +{ + m_viewport2D = aViewport; + + if( m_viewport2D == nullptr ) + { + return; + } + + m_view = m_viewport2D->GetView(); + + if( m_view == nullptr ) + { + return; + } + + m_viewportWidth = m_view->GetBoundary().GetWidth(); + + if( !IsEnabled() ) + { + // Use the default settings for the connexion to the 3DMouse navigation + // They are use a single-threaded threading model and row vectors. + EnableNavigation( true ); + + // Use the SpaceMouse internal timing source for the frame rate. + PutFrameTimingSource( TimingSource::SpaceMouse ); + + exportCommandsAndImages(); + } +} + + +void NL_PL_EDITOR_PLUGIN_IMPL::SetFocus( bool aFocus ) +{ + wxLogTrace( m_logTrace, wxT( "NL_PL_EDITOR_PLUGIN_IMPL::SetFocus %d" ), aFocus ); + NAV_3D::Write( navlib::focus_k, aFocus ); +} + +// temporary store for the command categories +using CATEGORY_STORE = std::map>; + + +/** + * 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. + */ +static void add_category( const std::string& aCategoryPath, CATEGORY_STORE& aCategoryStore ) +{ + using TDx::SpaceMouse::CCategory; + + auto 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 ); + + if( !aCategoryStore.contains( parentPath ) ) + { + add_category( parentPath, aCategoryStore ); + parent_iter = aCategoryStore.find( parentPath ); + } + } + + std::string name = aCategoryPath.substr( pos + 1 ); + auto categoryNode = std::make_unique( aCategoryPath.c_str(), name.c_str() ); + + aCategoryStore.try_emplace( aCategoryStore.end(), aCategoryPath, categoryNode.get() ); + + parent_iter->second->push_back( std::move( categoryNode ) ); +} + + +/** + * add_category wrapper. + * + * Function checks if path exists in the category and adds it if it doesn't. + * + * @param aCategoryPath is the std::string representation of the category. + * @param aCategoryStore is the CATEGORY_STORE instance to add to. + */ +static void try_add_category( const std::string& aCategoryPath, CATEGORY_STORE& aCategoryStore ) +{ + if( !aCategoryStore.contains( aCategoryPath ) ) + { + add_category( aCategoryPath, aCategoryStore ); + } +} + + +void NL_PL_EDITOR_PLUGIN_IMPL::exportCommandsAndImages() +{ + wxLogTrace( m_logTrace, wxT( "NL_PL_EDITOR_PLUGIN_IMPL::exportCommandsAndImages" ) ); + + std::list actions = ACTION_MANAGER::GetActionList(); + + if( actions.empty() ) + return; + + using TDx::SpaceMouse::CCommand; + using TDx::SpaceMouse::CCommandSet; + + // The root action set node + CCommandSet commandSet( "PL_EDITOR", "Drawing Sheet Editor" ); + + // Activate the command set + NAV_3D::PutActiveCommands( commandSet.GetId() ); + + // temporary store for the categories initialized with action set + CATEGORY_STORE categoryStore{ CATEGORY_STORE::value_type( ".", &commandSet ) }; + + std::vector vImages; + + for( const auto action : actions ) + { + std::string label = action->GetMenuLabel().ToStdString(); + + if( label.empty() ) + continue; + + std::string name = action->GetName(); + + // Do no export commands for the 3DViewer app. + + if( name.rfind( "3DViewer.", 0 ) == 0 ) + continue; + + std::string strCategory = action->GetToolName(); + std::string description = action->GetDescription().ToStdString(); + + try_add_category( strCategory, categoryStore ); + CATEGORY_STORE::iterator iter = categoryStore.find( strCategory ); + + // 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() ) + { + const wxStreamBuffer* streamBuffer = imageStream.GetOutputStreamBuffer(); + TDx::CImage tdxImage = TDx::CImage::FromData( "", 0, name.c_str() ); + tdxImage.AssignImage( + std::string( std::bit_cast( streamBuffer->GetBufferStart() ), + streamBuffer->GetBufferSize() ), + 0 ); + + wxLogTrace( m_logTrace, wxT( "Adding image for : %s" ), name ); + vImages.push_back( std::move( tdxImage ) ); + } + } + + wxLogTrace( m_logTrace, wxT( "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_PL_EDITOR_PLUGIN_IMPL::GetCameraMatrix( navlib::matrix_t& matrix ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + m_viewPosition = m_view->GetCenter(); + + double x = m_view->IsMirroredX() ? -1 : 1; + double y = m_view->IsMirroredY() ? 1 : -1; + + // x * y * z = 1 for a right-handed coordinate system. + double z = x * y; + + // Note: the connexion has been configured as row vectors, the coordinate system is defined in + // NL_PL_EDITOR_PLUGIN_IMPL::GetCoordinateSystem and the front view in NL_PL_EDITOR_PLUGIN_IMPL::GetFrontView. + matrix = { { { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, m_viewPosition.x, m_viewPosition.y, 0, + 1 } } }; + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetPointerPosition( navlib::point_t& position ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + VECTOR2D mouse_pointer = m_viewport2D->GetViewControls()->GetMousePosition(); + + position.x = mouse_pointer.x; + position.y = mouse_pointer.y; + position.z = 0; + + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetViewExtents( navlib::box_t& extents ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + double scale = m_viewport2D->GetGAL()->GetWorldScale(); + BOX2D box = m_view->GetViewport(); + + m_viewportWidth = box.GetWidth(); + + extents.min_x = -box.GetWidth() / 2.0; + extents.min_y = -box.GetHeight() / 2.0; + extents.min_z = m_viewport2D->GetGAL()->GetMinDepth() / scale; + extents.max_x = box.GetWidth() / 2.0; + extents.max_y = box.GetHeight() / 2.0; + extents.max_z = m_viewport2D->GetGAL()->GetMaxDepth() / scale; + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetIsViewPerspective( navlib::bool_t& perspective ) const +{ + perspective = false; + + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetCameraMatrix( const navlib::matrix_t& matrix ) +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + + long result = 0; + VECTOR2D viewPos( matrix.m4x4[3][0], matrix.m4x4[3][1] ); + + if( !equals( m_view->GetCenter(), m_viewPosition, + static_cast( FLT_EPSILON ) ) ) + { + m_view->SetCenter( viewPos + m_view->GetCenter() - m_viewPosition ); + result = navlib::make_result_code( navlib::navlib_errc::error ); + } + else + { + m_view->SetCenter( viewPos ); + } + + m_viewPosition = viewPos; + + return result; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetViewExtents( const navlib::box_t& extents ) +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + + long result = 0; + + if( m_viewportWidth != m_view->GetViewport().GetWidth() ) + result = navlib::make_result_code( navlib::navlib_errc::error ); + + double width = m_viewportWidth; + m_viewportWidth = extents.max_x - extents.min_x; + + double scale = width / m_viewportWidth * m_view->GetScale(); + m_view->SetScale( scale, m_view->GetCenter() ); + + if( !equals( m_view->GetScale(), scale, static_cast( FLT_EPSILON ) ) ) + result = navlib::make_result_code( navlib::navlib_errc::error ); + + return result; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetViewFOV( double fov ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetViewFrustum( const navlib::frustum_t& frustum ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetModelExtents( navlib::box_t& extents ) const +{ + if( m_view == nullptr ) + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); + + BOX2I box = static_cast( m_viewport2D->GetParent() )->GetDocumentExtents(); + box.Normalize(); + + double half_depth = 0.1 / m_viewport2D->GetGAL()->GetWorldScale(); + + if( box.GetWidth() == 0 && box.GetHeight() == 0 ) + half_depth = 0; + + extents.min_x = static_cast( box.GetOrigin().x ); + extents.min_y = static_cast( box.GetOrigin().y ); + extents.min_z = -half_depth; + extents.max_x = static_cast( box.GetEnd().x ); + extents.max_y = static_cast( box.GetEnd().y ); + extents.max_z = half_depth; + + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetCoordinateSystem( navlib::matrix_t& matrix ) const +{ + // The coordinate system is defined as x to the right, y down and z into the screen. + matrix = { { { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 } } }; + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetFrontView( navlib::matrix_t& matrix ) const +{ + matrix = { { { 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1 } } }; + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetIsSelectionEmpty( navlib::bool_t& empty ) const +{ + empty = true; + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetIsViewRotatable( navlib::bool_t& isRotatable ) const +{ + isRotatable = false; + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetActiveCommand( std::string commandId ) +{ + if( commandId.empty() ) + return 0; + + if(m_viewport2D == nullptr) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + wxWindow* parent = m_viewport2D->GetParent(); + + // Only allow command execution if the window is enabled. i.e. there is not a modal dialog + // currently active. + if( parent == nullptr || !parent->IsEnabled() ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + TOOL_MANAGER* tool_manager = dynamic_cast( parent )->GetToolManager(); + + // Only allow for command execution if the tool manager is accessible. + if( tool_manager == nullptr ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + for( std::list actions = ACTION_MANAGER::GetActionList(); + const auto action : actions ) + { + if( action == nullptr ) + { + continue; + } + + if( commandId == action->GetName() ) + { + // Get the selection to use to test if the action is enabled + const SELECTION& sel = tool_manager->GetToolHolder()->GetCurrentSelection(); + + const ACTION_CONDITIONS* aCond = + tool_manager->GetActionManager()->GetCondition( *action ); + + if( aCond == nullptr ) + { + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); + } + + aCond->enableCondition( sel ); + tool_manager->RunAction( *action ); + break; + } + } + + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetSettingsChanged( long change ) +{ + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetMotionFlag( bool value ) +{ + m_isMoving = value; + + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetTransaction( long value ) +{ + if( value == 0L ) + m_viewport2D->ForceRefresh(); + + return 0; +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetViewFOV( double& fov ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetViewFrustum( navlib::frustum_t& frustum ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetSelectionExtents( navlib::box_t& extents ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetSelectionTransform( navlib::matrix_t& transform ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetSelectionTransform( const navlib::matrix_t& matrix ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetPivotPosition( navlib::point_t& position ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::IsUserPivot( navlib::bool_t& userPivot ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetPivotPosition( const navlib::point_t& position ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetPivotVisible( navlib::bool_t& visible ) const +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetPivotVisible( bool visible ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::GetHitLookAt( navlib::point_t& position ) const +{ + return navlib::make_result_code( navlib::navlib_errc::no_data_available ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetHitAperture( double aperture ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetHitDirection( const navlib::vector_t& direction ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetHitLookFrom( const navlib::point_t& eye ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetHitSelectionOnly( bool onlySelection ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} + + +long NL_PL_EDITOR_PLUGIN_IMPL::SetCameraTarget( const navlib::point_t& position ) +{ + return navlib::make_result_code( navlib::navlib_errc::invalid_operation ); +} diff --git a/pagelayout_editor/navlib/nl_pl_editor_plugin_impl.h b/pagelayout_editor/navlib/nl_pl_editor_plugin_impl.h new file mode 100644 index 0000000000..0fae53ae25 --- /dev/null +++ b/pagelayout_editor/navlib/nl_pl_editor_plugin_impl.h @@ -0,0 +1,147 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2024 3Dconnexion + * Copyright (C) 2022 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_gerbview_plugin_impl.h + * @brief Declaration of the NL_PL_EDITOR_PLUGIN_IMPL class + */ + +#ifndef NL_GERBVIEW_PLUGIN_IMPL_H_ +#define NL_GERBVIEW_PLUGIN_IMPL_H_ + +#if _WIN32 +#ifndef _WIN32_WINNT +#define _WIN32_WINNT 0x0603 +#endif +#endif + +// TDxWare SDK. +#include + +// wx +#include + +// KiCAD +#include + +// stdlib +#include + +// Forward declarations. +class EDA_DRAW_PANEL_GAL; +namespace KIGFX +{ +class VIEW; +} + +// Convenience typedef. +typedef TDx::SpaceMouse::Navigation3D::CNavigation3D NAV_3D; + +/** + * The class that implements the accessors and mutators required for + * 3D navigation in an PCB_DRAW_PANEL_GAL using a SpaceMouse. + */ +class NL_PL_EDITOR_PLUGIN_IMPL : public NAV_3D +{ +public: + /** + * Initializes a new instance of the NL_PL_EDITOR_PLUGIN_IMPL. + */ + NL_PL_EDITOR_PLUGIN_IMPL(); + + + virtual ~NL_PL_EDITOR_PLUGIN_IMPL(); + + + /** + * Sets the viewport controlled by the SpaceMouse. + * + * @param aViewport is the viewport to be navigated. + */ + void SetCanvas( EDA_DRAW_PANEL_GAL* aViewport ); + + + /** + * 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 ); + +private: + /** + * Export the invocable actions and images to the 3Dconnexion UI. + */ + 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_DRAW_PANEL_GAL* m_viewport2D = nullptr; + KIGFX::VIEW* m_view = nullptr; + bool m_isMoving = false; + mutable double m_viewportWidth = 0.0; + mutable VECTOR2D m_viewPosition; + + /** + * 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_GERBVIEW_PLUGIN". See the wxWidgets documentation on wxLogTrace for + * more information. + */ + static const wxChar* m_logTrace; +}; + +#endif // NL_PL_EDITOR_PLUGIN_IMPL diff --git a/pagelayout_editor/pl_editor_frame.cpp b/pagelayout_editor/pl_editor_frame.cpp index 34d965378b..6c8edea117 100644 --- a/pagelayout_editor/pl_editor_frame.cpp +++ b/pagelayout_editor/pl_editor_frame.cpp @@ -63,6 +63,9 @@ #include #include #include +#include + +#include BEGIN_EVENT_TABLE( PL_EDITOR_FRAME, EDA_DRAW_FRAME ) @@ -83,14 +86,11 @@ END_EVENT_TABLE() PL_EDITOR_FRAME::PL_EDITOR_FRAME( KIWAY* aKiway, wxWindow* aParent ) : - EDA_DRAW_FRAME( aKiway, aParent, FRAME_PL_EDITOR, wxT( "PlEditorFrame" ), - wxDefaultPosition, wxDefaultSize, - KICAD_DEFAULT_DRAWFRAME_STYLE, PL_EDITOR_FRAME_NAME, drawSheetIUScale ), - m_propertiesPagelayout( nullptr ), - m_propertiesFrameWidth( 200 ), - m_originSelectBox( nullptr ), - m_originSelectChoice( 0 ), - m_pageSelectBox( nullptr ), + EDA_DRAW_FRAME( aKiway, aParent, FRAME_PL_EDITOR, wxT( "PlEditorFrame" ), wxDefaultPosition, + wxDefaultSize, KICAD_DEFAULT_DRAWFRAME_STYLE, PL_EDITOR_FRAME_NAME, + drawSheetIUScale ), + m_propertiesPagelayout( nullptr ), m_propertiesFrameWidth( 200 ), + m_originSelectBox( nullptr ), m_originSelectChoice( 0 ), m_pageSelectBox( nullptr ), m_mruImagePath( wxEmptyString ) { m_maximizeByDefault = true; @@ -230,6 +230,17 @@ PL_EDITOR_FRAME::PL_EDITOR_FRAME( KIWAY* aKiway, wxWindow* aParent ) : // Ensure the controls on the toolbars all are correctly sized UpdateToolbarControlSizes(); } ); + + try + { + if( !m_spaceMouse ) + m_spaceMouse = std::make_unique(); + m_spaceMouse->SetCanvas( GetCanvas() ); + } + catch( const std::system_error& e ) + { + wxLogTrace( wxT( "KI_TRACE_NAVLIB" ), e.what() ); + } } @@ -979,3 +990,20 @@ void PL_EDITOR_FRAME::UpdateMsgPanelInfo() SetMsgPanel( msgItems ); } #endif + +void PL_EDITOR_FRAME::handleActivateEvent( wxActivateEvent& aEvent ) +{ + EDA_DRAW_FRAME::handleActivateEvent(aEvent); + + if( m_spaceMouse ) + m_spaceMouse->SetFocus( aEvent.GetActive() ); +} + + +void PL_EDITOR_FRAME::handleIconizeEvent( wxIconizeEvent& aEvent ) +{ + EDA_DRAW_FRAME::handleIconizeEvent(aEvent); + + if( m_spaceMouse ) + m_spaceMouse->SetFocus( false ); +} diff --git a/pagelayout_editor/pl_editor_frame.h b/pagelayout_editor/pl_editor_frame.h index 6c68ab749c..2a93719b3e 100644 --- a/pagelayout_editor/pl_editor_frame.h +++ b/pagelayout_editor/pl_editor_frame.h @@ -32,10 +32,13 @@ #include "pl_editor_layout.h" #include "pl_draw_panel_gal.h" +#include + class PL_DRAW_PANEL_GAL; class PROPERTIES_FRAME; class DS_DATA_ITEM; class wxChoice; +class NL_PL_EDITOR_PLUGIN; /** @@ -265,6 +268,10 @@ protected: DECLARE_EVENT_TABLE(); +private: + void handleActivateEvent( wxActivateEvent& aEvent ) override; + void handleIconizeEvent( wxIconizeEvent& aEvent ) override; + protected: /// The last filename chosen to be proposed to the user PROPERTIES_FRAME* m_propertiesPagelayout; @@ -281,6 +288,8 @@ private: wxString m_mruImagePath; // Most recently used path for placing a new image // only on page 1, not on page 1 VECTOR2I m_grid_origin; + + std::unique_ptr m_spaceMouse; }; #endif /* _PL_EDITOR_FRAME_H */