2019-09-27 12:52:19 +00:00
# include "libslic3r/libslic3r.h"
# include "Mouse3DController.hpp"
# if ENABLE_3DCONNEXION_DEVICES
2019-10-03 09:38:31 +00:00
# include "Camera.hpp"
2019-09-27 12:52:19 +00:00
# include "GUI_App.hpp"
# include "PresetBundle.hpp"
2019-10-03 08:26:28 +00:00
# include "AppConfig.hpp"
2019-09-27 12:52:19 +00:00
# include <wx/glcanvas.h>
2019-10-02 13:55:26 +00:00
# include <boost/nowide/convert.hpp>
2019-10-04 08:59:27 +00:00
# include <boost/log/trivial.hpp>
2019-10-02 13:55:26 +00:00
# include "I18N.hpp"
2019-09-30 12:58:51 +00:00
// WARN: If updating these lists, please also update resources/udev/90-3dconnexion.rules
2019-09-27 12:52:19 +00:00
static const std : : vector < int > _3DCONNEXION_VENDORS =
{
0x046d , // LOGITECH = 1133 // Logitech (3Dconnexion is made by Logitech)
0x256F // 3DCONNECTION = 9583 // 3Dconnexion
} ;
static const std : : vector < int > _3DCONNEXION_DEVICES =
{
0xC623 , // TRAVELER = 50723
0xC626 , // NAVIGATOR = 50726
0xc628 , // NAVIGATOR_FOR_NOTEBOOKS = 50728
0xc627 , // SPACEEXPLORER = 50727
0xC603 , // SPACEMOUSE = 50691
0xC62B , // SPACEMOUSEPRO = 50731
0xc621 , // SPACEBALL5000 = 50721
0xc625 , // SPACEPILOT = 50725
0xc629 // SPACEPILOTPRO = 50729
} ;
namespace Slic3r {
namespace GUI {
const double Mouse3DController : : State : : DefaultTranslationScale = 2.5 ;
const float Mouse3DController : : State : : DefaultRotationScale = 1.0 ;
Mouse3DController : : State : : State ( )
: m_translation ( Vec3d : : Zero ( ) )
, m_rotation ( Vec3f : : Zero ( ) )
, m_translation_scale ( DefaultTranslationScale )
, m_rotation_scale ( DefaultRotationScale )
{
}
2019-10-03 09:38:31 +00:00
bool Mouse3DController : : State : : apply ( Camera & camera )
2019-09-27 12:52:19 +00:00
{
if ( ! wxGetApp ( ) . IsActive ( ) )
return false ;
bool ret = false ;
if ( has_translation ( ) )
{
2019-10-03 10:16:59 +00:00
camera . set_target ( camera . get_target ( ) + m_translation_scale * ( m_translation ( 0 ) * camera . get_dir_right ( ) + m_translation ( 1 ) * camera . get_dir_forward ( ) + m_translation ( 2 ) * camera . get_dir_up ( ) ) ) ;
m_translation = Vec3d : : Zero ( ) ;
2019-09-27 12:52:19 +00:00
ret = true ;
}
if ( has_rotation ( ) )
{
2019-10-03 10:16:59 +00:00
float theta = m_rotation_scale * m_rotation ( 0 ) ;
float phi = m_rotation_scale * m_rotation ( 2 ) ;
2019-09-27 12:52:19 +00:00
float sign = camera . inverted_phi ? - 1.0f : 1.0f ;
camera . phi + = sign * phi ;
camera . set_theta ( camera . get_theta ( ) + theta , wxGetApp ( ) . preset_bundle - > printers . get_edited_preset ( ) . printer_technology ( ) ! = ptSLA ) ;
2019-10-03 10:16:59 +00:00
m_rotation = Vec3f : : Zero ( ) ;
2019-09-27 12:52:19 +00:00
ret = true ;
}
if ( has_any_button ( ) )
{
2019-10-03 10:16:59 +00:00
for ( unsigned int i : m_buttons )
2019-09-30 13:58:45 +00:00
{
switch ( i )
{
2019-10-03 09:38:31 +00:00
case 0 : { camera . update_zoom ( 1.0 ) ; break ; }
case 1 : { camera . update_zoom ( - 1.0 ) ; break ; }
2019-09-30 13:58:45 +00:00
default : { break ; }
}
}
2019-09-27 12:52:19 +00:00
2019-10-02 13:55:26 +00:00
reset_buttons ( ) ;
2019-09-27 12:52:19 +00:00
ret = true ;
}
return ret ;
}
Mouse3DController : : Mouse3DController ( )
: m_initialized ( false )
, m_device ( nullptr )
2019-10-03 08:26:28 +00:00
, m_device_str ( " " )
2019-09-27 12:52:19 +00:00
, m_running ( false )
2019-10-02 13:55:26 +00:00
, m_settings_dialog ( false )
2019-09-27 12:52:19 +00:00
{
}
void Mouse3DController : : init ( )
{
if ( m_initialized )
return ;
// Initialize the hidapi library
int res = hid_init ( ) ;
if ( res ! = 0 )
2019-10-04 08:59:27 +00:00
{
BOOST_LOG_TRIVIAL ( error ) < < " Unable to initialize hidapi library " ;
2019-09-27 12:52:19 +00:00
return ;
2019-10-04 08:59:27 +00:00
}
2019-09-27 12:52:19 +00:00
m_initialized = true ;
}
void Mouse3DController : : shutdown ( )
{
if ( ! m_initialized )
return ;
stop ( ) ;
disconnect_device ( ) ;
// Finalize the hidapi library
hid_exit ( ) ;
m_initialized = false ;
}
2019-10-03 10:16:59 +00:00
bool Mouse3DController : : apply ( Camera & camera )
{
2019-10-04 08:59:27 +00:00
if ( ! m_initialized )
return false ;
2019-10-03 10:16:59 +00:00
std : : lock_guard < std : : mutex > lock ( m_mutex ) ;
2019-10-04 08:59:27 +00:00
// check if the user unplugged the device
if ( ! m_running & & is_device_connected ( ) )
{
disconnect_device ( ) ;
// hides the settings dialog if the user re-plug the device
m_settings_dialog = false ;
}
// check if the user plugged the device
if ( connect_device ( ) )
start ( ) ;
return is_device_connected ( ) ? m_state . apply ( camera ) : false ;
2019-10-03 10:16:59 +00:00
}
2019-10-03 09:38:31 +00:00
void Mouse3DController : : render_settings_dialog ( unsigned int canvas_width , unsigned int canvas_height ) const
2019-10-02 13:55:26 +00:00
{
2019-10-03 09:38:31 +00:00
if ( ! m_running | | ! m_settings_dialog )
2019-10-02 13:55:26 +00:00
return ;
ImGuiWrapper & imgui = * wxGetApp ( ) . imgui ( ) ;
2019-10-03 09:38:31 +00:00
imgui . set_next_window_pos ( 0.5f * ( float ) canvas_width , 0.5f * ( float ) canvas_height , ImGuiCond_Always , 0.5f , 0.5f ) ;
2019-10-02 13:55:26 +00:00
imgui . set_next_window_bg_alpha ( 0.5f ) ;
ImGui : : PushStyleVar ( ImGuiStyleVar_WindowRounding , 0.0f ) ;
imgui . begin ( _ ( L ( " 3Dconnexion settings " ) ) , ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse ) ;
const ImVec4 & color = ImGui : : GetStyleColorVec4 ( ImGuiCol_Separator ) ;
ImGui : : PushStyleColor ( ImGuiCol_Text , color ) ;
imgui . text ( _ ( L ( " Device: " ) ) ) ;
ImGui : : PopStyleColor ( ) ;
ImGui : : SameLine ( ) ;
2019-10-03 08:26:28 +00:00
imgui . text ( m_device_str ) ;
2019-10-02 13:55:26 +00:00
ImGui : : Separator ( ) ;
ImGui : : PushStyleColor ( ImGuiCol_Text , color ) ;
imgui . text ( _ ( L ( " Speed: " ) ) ) ;
ImGui : : PopStyleColor ( ) ;
float translation = ( float ) m_state . get_translation_scale ( ) / State : : DefaultTranslationScale ;
if ( ImGui : : SliderFloat ( _ ( L ( " Translation " ) ) , & translation , 0.5f , 2.0f , " %.1f " ) )
m_state . set_translation_scale ( State : : DefaultTranslationScale * ( double ) translation ) ;
float rotation = ( float ) m_state . get_rotation_scale ( ) / State : : DefaultRotationScale ;
if ( ImGui : : SliderFloat ( _ ( L ( " Rotation " ) ) , & rotation , 0.5f , 2.0f , " %.1f " ) )
m_state . set_rotation_scale ( State : : DefaultRotationScale * rotation ) ;
imgui . end ( ) ;
ImGui : : PopStyleVar ( ) ;
}
2019-10-04 08:59:27 +00:00
bool Mouse3DController : : connect_device ( )
2019-09-27 12:52:19 +00:00
{
2019-10-04 08:59:27 +00:00
if ( is_device_connected ( ) )
return false ;
2019-09-27 12:52:19 +00:00
// Enumerates devices
hid_device_info * devices = hid_enumerate ( 0 , 0 ) ;
if ( devices = = nullptr )
2019-10-04 08:59:27 +00:00
{
BOOST_LOG_TRIVIAL ( error ) < < " Unable to enumerate HID devices " ;
return false ;
}
2019-09-27 12:52:19 +00:00
// Searches for 1st connected 3Dconnexion device
unsigned short vendor_id = 0 ;
unsigned short product_id = 0 ;
hid_device_info * current = devices ;
while ( current ! = nullptr )
{
for ( size_t i = 0 ; i < _3DCONNEXION_VENDORS . size ( ) ; + + i )
{
if ( _3DCONNEXION_VENDORS [ i ] = = current - > vendor_id )
{
vendor_id = current - > vendor_id ;
break ;
}
}
if ( vendor_id ! = 0 )
{
for ( size_t i = 0 ; i < _3DCONNEXION_DEVICES . size ( ) ; + + i )
{
if ( _3DCONNEXION_DEVICES [ i ] = = current - > product_id )
{
product_id = current - > product_id ;
break ;
}
}
if ( product_id = = 0 )
vendor_id = 0 ;
}
if ( vendor_id ! = 0 )
break ;
current = current - > next ;
}
// Free enumerated devices
hid_free_enumeration ( devices ) ;
if ( vendor_id = = 0 )
2019-10-04 08:59:27 +00:00
return false ;
2019-09-27 12:52:19 +00:00
// Open the 3Dconnexion device using the VID, PID
m_device = hid_open ( vendor_id , product_id , nullptr ) ;
2019-10-03 08:26:28 +00:00
if ( m_device ! = nullptr )
{
std : : vector < wchar_t > product ( 1024 , 0 ) ;
hid_get_product_string ( m_device , product . data ( ) , 1024 ) ;
m_device_str = boost : : nowide : : narrow ( product . data ( ) ) ;
2019-10-04 08:59:27 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Connected device: " < < m_device_str ;
// get device parameters from the config, if present
2019-10-03 08:26:28 +00:00
double translation = 1.0 ;
float rotation = 1.0 ;
wxGetApp ( ) . app_config - > get_mouse_device_translation_speed ( m_device_str , translation ) ;
wxGetApp ( ) . app_config - > get_mouse_device_rotation_speed ( m_device_str , rotation ) ;
// clamp to valid values
m_state . set_translation_scale ( State : : DefaultTranslationScale * std : : max ( 0.5 , std : : min ( 2.0 , translation ) ) ) ;
m_state . set_rotation_scale ( State : : DefaultRotationScale * std : : max ( 0.5f , std : : min ( 2.0f , rotation ) ) ) ;
}
2019-10-04 08:59:27 +00:00
return ( m_device ! = nullptr ) ;
2019-09-27 12:52:19 +00:00
}
void Mouse3DController : : disconnect_device ( )
{
2019-10-04 08:59:27 +00:00
if ( ! is_device_connected ( ) )
2019-09-27 12:52:19 +00:00
return ;
2019-10-04 08:59:27 +00:00
// Stop the secondary thread, if running
if ( m_thread . joinable ( ) )
m_thread . join ( ) ;
// Store current device parameters into the config
2019-10-03 08:26:28 +00:00
wxGetApp ( ) . app_config - > set_mouse_device ( m_device_str , m_state . get_translation_scale ( ) / State : : DefaultTranslationScale , m_state . get_rotation_scale ( ) / State : : DefaultRotationScale ) ;
wxGetApp ( ) . app_config - > save ( ) ;
2019-09-27 12:52:19 +00:00
// Close the 3Dconnexion device
hid_close ( m_device ) ;
m_device = nullptr ;
2019-10-04 08:59:27 +00:00
BOOST_LOG_TRIVIAL ( info ) < < " Disconnected device: " < < m_device_str ;
2019-10-03 08:26:28 +00:00
m_device_str = " " ;
2019-09-27 12:52:19 +00:00
}
void Mouse3DController : : start ( )
{
2019-10-04 08:59:27 +00:00
if ( ! is_device_connected ( ) | | m_running )
2019-09-27 12:52:19 +00:00
return ;
m_thread = std : : thread ( & Mouse3DController : : run , this ) ;
}
void Mouse3DController : : run ( )
{
m_running = true ;
while ( m_running )
{
collect_input ( ) ;
}
}
2019-10-04 05:58:01 +00:00
void Mouse3DController : : collect_input ( )
2019-09-27 12:52:19 +00:00
{
2019-10-04 05:58:01 +00:00
auto convert_input = [ ] ( unsigned char first , unsigned char second ) - > double
2019-09-27 12:52:19 +00:00
{
2019-10-04 05:58:01 +00:00
int ret = 0 ;
switch ( second )
{
case 0 : { ret = ( int ) first ; break ; }
case 1 : { ret = ( int ) first + 255 ; break ; }
case 254 : { ret = - 511 + ( int ) first ; break ; }
case 255 : { ret = - 255 + ( int ) first ; break ; }
default : { break ; }
}
return ( double ) ret / 349.0 ;
} ;
2019-09-27 12:52:19 +00:00
// Read data from device
enum EDataType
{
Translation = 1 ,
Rotation ,
Button
} ;
unsigned char retrieved_data [ 8 ] = { 0 } ;
int res = hid_read_timeout ( m_device , retrieved_data , sizeof ( retrieved_data ) , 100 ) ;
if ( res < 0 )
{
// An error occourred (device detached from pc ?)
stop ( ) ;
return ;
}
2019-10-03 10:16:59 +00:00
std : : lock_guard < std : : mutex > lock ( m_mutex ) ;
2019-09-27 12:52:19 +00:00
if ( res > 0 )
{
2019-10-07 07:31:23 +00:00
bool updated = false ;
2019-09-27 12:52:19 +00:00
switch ( retrieved_data [ 0 ] )
{
case Translation :
{
2019-10-04 05:58:01 +00:00
Vec3d translation ( - convert_input ( retrieved_data [ 1 ] , retrieved_data [ 2 ] ) ,
convert_input ( retrieved_data [ 3 ] , retrieved_data [ 4 ] ) ,
convert_input ( retrieved_data [ 5 ] , retrieved_data [ 6 ] ) ) ;
2019-10-07 07:31:23 +00:00
if ( ! translation . isApprox ( Vec3d : : Zero ( ) ) )
{
updated = true ;
2019-09-27 12:52:19 +00:00
m_state . set_translation ( translation ) ;
2019-10-07 07:31:23 +00:00
}
2019-09-27 12:52:19 +00:00
break ;
}
case Rotation :
{
2019-10-04 05:58:01 +00:00
Vec3f rotation ( - ( float ) convert_input ( retrieved_data [ 1 ] , retrieved_data [ 2 ] ) ,
( float ) convert_input ( retrieved_data [ 3 ] , retrieved_data [ 4 ] ) ,
- ( float ) convert_input ( retrieved_data [ 5 ] , retrieved_data [ 6 ] ) ) ;
2019-09-27 12:52:19 +00:00
if ( ! rotation . isApprox ( Vec3f : : Zero ( ) ) )
2019-10-07 07:31:23 +00:00
{
updated = true ;
2019-09-27 12:52:19 +00:00
m_state . set_rotation ( rotation ) ;
2019-10-07 07:31:23 +00:00
}
2019-09-27 12:52:19 +00:00
break ;
}
case Button :
{
2019-10-03 08:26:28 +00:00
// Because of lack of documentation, it is not clear how we should interpret the retrieved data for the button case.
// Experiments made with SpaceNavigator:
// retrieved_data[1] == 0 if no button pressed
// retrieved_data[1] == 1 if left button pressed
// retrieved_data[1] == 2 if right button pressed
// retrieved_data[1] == 3 if left and right button pressed
// seems to show that each button is associated to a bit of retrieved_data[1], which means that at max 8 buttons can be supported.
for ( unsigned int i = 0 ; i < 8 ; + + i )
2019-09-27 12:52:19 +00:00
{
if ( retrieved_data [ 1 ] & ( 0x1 < < i ) )
2019-10-07 07:31:23 +00:00
{
updated = true ;
2019-09-27 12:52:19 +00:00
m_state . set_button ( i ) ;
2019-10-07 07:31:23 +00:00
}
2019-09-27 12:52:19 +00:00
}
2019-10-03 08:26:28 +00:00
// // On the other hand, other libraries, as in https://github.com/koenieee/CrossplatformSpacemouseDriver/blob/master/SpaceMouseDriver/driver/SpaceMouseController.cpp
// // interpret retrieved_data[1] as the button id
// if (retrieved_data[1] > 0)
// m_state.set_button((unsigned int)retrieved_data[1]);
2019-09-27 12:52:19 +00:00
break ;
}
default :
break ;
}
2019-10-07 07:31:23 +00:00
if ( updated )
// ask for an idle event to update 3D scene
wxWakeUpIdle ( ) ;
2019-09-27 12:52:19 +00:00
}
}
} // namespace GUI
} // namespace Slic3r
# endif // ENABLE_3DCONNEXION_DEVICES