2019-09-27 14:52:19 +02:00
#include "libslic3r/libslic3r.h"
#include "Mouse3DController.hpp"
#if ENABLE_3DCONNEXION_DEVICES
2019-10-03 11:38:31 +02:00
#include "Camera.hpp"
2019-09-27 14:52:19 +02:00
#include "GUI_App.hpp"
#include "PresetBundle.hpp"
2019-10-03 10:26:28 +02:00
#include "AppConfig.hpp"
2019-09-27 14:52:19 +02:00
#include <wx/glcanvas.h>
2019-10-02 15:55:26 +02:00
#include <boost/nowide/convert.hpp>
2019-10-04 10:59:27 +02:00
#include <boost/log/trivial.hpp>
2019-10-02 15:55:26 +02:00
#include "I18N.hpp"
2019-09-30 14:58:51 +02:00
// WARN: If updating these lists, please also update resources/udev/90-3dconnexion.rules
2019-09-27 14:52:19 +02: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 11:38:31 +02:00
bool Mouse3DController :: State :: apply ( Camera & camera )
2019-09-27 14:52:19 +02:00
{
if ( ! wxGetApp (). IsActive ())
return false ;
bool ret = false ;
if ( has_translation ())
{
2019-10-03 12:16:59 +02: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 14:52:19 +02:00
ret = true ;
}
if ( has_rotation ())
{
2019-10-03 12:16:59 +02:00
float theta = m_rotation_scale * m_rotation ( 0 );
float phi = m_rotation_scale * m_rotation ( 2 );
2019-09-27 14:52:19 +02: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 12:16:59 +02:00
m_rotation = Vec3f :: Zero ();
2019-09-27 14:52:19 +02:00
ret = true ;
}
if ( has_any_button ())
{
2019-10-03 12:16:59 +02:00
for ( unsigned int i : m_buttons )
2019-09-30 15:58:45 +02:00
{
switch ( i )
{
2019-10-03 11:38:31 +02:00
case 0 : { camera . update_zoom ( 1.0 ); break ; }
case 1 : { camera . update_zoom ( - 1.0 ); break ; }
2019-09-30 15:58:45 +02:00
default : { break ; }
}
}
2019-09-27 14:52:19 +02:00
2019-10-02 15:55:26 +02:00
reset_buttons ();
2019-09-27 14:52:19 +02:00
ret = true ;
}
return ret ;
}
Mouse3DController :: Mouse3DController ()
: m_initialized ( false )
, m_device ( nullptr )
2019-10-03 10:26:28 +02:00
, m_device_str ( "" )
2019-09-27 14:52:19 +02:00
, m_running ( false )
2019-10-02 15:55:26 +02:00
, m_settings_dialog ( false )
2019-09-27 14:52:19 +02:00
{
}
void Mouse3DController :: init ()
{
if ( m_initialized )
return ;
// Initialize the hidapi library
int res = hid_init ();
if ( res != 0 )
2019-10-04 10:59:27 +02:00
{
BOOST_LOG_TRIVIAL ( error ) << "Unable to initialize hidapi library" ;
2019-09-27 14:52:19 +02:00
return ;
2019-10-04 10:59:27 +02:00
}
2019-09-27 14:52:19 +02: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 12:16:59 +02:00
bool Mouse3DController :: apply ( Camera & camera )
{
2019-10-04 10:59:27 +02:00
if ( ! m_initialized )
return false ;
2019-10-03 12:16:59 +02:00
std :: lock_guard < std :: mutex > lock ( m_mutex );
2019-10-04 10:59:27 +02: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 12:16:59 +02:00
}
2019-10-03 11:38:31 +02:00
void Mouse3DController :: render_settings_dialog ( unsigned int canvas_width , unsigned int canvas_height ) const
2019-10-02 15:55:26 +02:00
{
2019-10-03 11:38:31 +02:00
if ( ! m_running || ! m_settings_dialog )
2019-10-02 15:55:26 +02:00
return ;
ImGuiWrapper & imgui = * wxGetApp (). imgui ();
2019-10-03 11:38:31 +02: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 15:55:26 +02: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 10:26:28 +02:00
imgui . text ( m_device_str );
2019-10-02 15:55:26 +02: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 10:59:27 +02:00
bool Mouse3DController :: connect_device ()
2019-09-27 14:52:19 +02:00
{
2019-10-04 10:59:27 +02:00
if ( is_device_connected ())
return false ;
2019-09-27 14:52:19 +02:00
// Enumerates devices
hid_device_info * devices = hid_enumerate ( 0 , 0 );
if ( devices == nullptr )
2019-10-04 10:59:27 +02:00
{
BOOST_LOG_TRIVIAL ( error ) << "Unable to enumerate HID devices" ;
return false ;
}
2019-09-27 14:52:19 +02: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 10:59:27 +02:00
return false ;
2019-09-27 14:52:19 +02:00
// Open the 3Dconnexion device using the VID, PID
m_device = hid_open ( vendor_id , product_id , nullptr );
2019-10-03 10:26:28 +02: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 10:59:27 +02:00
BOOST_LOG_TRIVIAL ( info ) << "Connected device: " << m_device_str ;
// get device parameters from the config, if present
2019-10-03 10:26:28 +02: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 10:59:27 +02:00
return ( m_device != nullptr );
2019-09-27 14:52:19 +02:00
}
void Mouse3DController :: disconnect_device ()
{
2019-10-04 10:59:27 +02:00
if ( ! is_device_connected ())
2019-09-27 14:52:19 +02:00
return ;
2019-10-04 10:59:27 +02:00
// Stop the secondary thread, if running
if ( m_thread . joinable ())
m_thread . join ();
// Store current device parameters into the config
2019-10-03 10:26:28 +02: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 14:52:19 +02:00
// Close the 3Dconnexion device
hid_close ( m_device );
m_device = nullptr ;
2019-10-04 10:59:27 +02:00
BOOST_LOG_TRIVIAL ( info ) << "Disconnected device: " << m_device_str ;
2019-10-03 10:26:28 +02:00
m_device_str = "" ;
2019-09-27 14:52:19 +02:00
}
void Mouse3DController :: start ()
{
2019-10-04 10:59:27 +02:00
if ( ! is_device_connected () || m_running )
2019-09-27 14:52:19 +02:00
return ;
m_thread = std :: thread ( & Mouse3DController :: run , this );
}
void Mouse3DController :: run ()
{
m_running = true ;
while ( m_running )
{
collect_input ();
}
}
void Mouse3DController :: collect_input ()
{
2019-10-04 07:58:01 +02:00
auto convert_input = []( unsigned char first , unsigned char second ) -> double
{
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 14:52:19 +02: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 12:16:59 +02:00
std :: lock_guard < std :: mutex > lock ( m_mutex );
2019-09-27 14:52:19 +02:00
if ( res > 0 )
{
2019-10-07 09:31:23 +02:00
bool updated = false ;
2019-09-27 14:52:19 +02:00
switch ( retrieved_data [ 0 ])
{
case Translation :
{
2019-10-04 07:58:01 +02: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 09:31:23 +02:00
if ( ! translation . isApprox ( Vec3d :: Zero ()))
{
updated = true ;
2019-09-27 14:52:19 +02:00
m_state . set_translation ( translation );
2019-10-07 09:31:23 +02:00
}
2019-09-27 14:52:19 +02:00
break ;
}
case Rotation :
{
2019-10-04 07:58:01 +02: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 14:52:19 +02:00
if ( ! rotation . isApprox ( Vec3f :: Zero ()))
2019-10-07 09:31:23 +02:00
{
updated = true ;
2019-09-27 14:52:19 +02:00
m_state . set_rotation ( rotation );
2019-10-07 09:31:23 +02:00
}
2019-09-27 14:52:19 +02:00
break ;
}
case Button :
{
2019-10-03 10:26:28 +02: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 14:52:19 +02:00
{
if ( retrieved_data [ 1 ] & ( 0x1 << i ))
2019-10-07 09:31:23 +02:00
{
updated = true ;
2019-09-27 14:52:19 +02:00
m_state . set_button ( i );
2019-10-07 09:31:23 +02:00
}
2019-09-27 14:52:19 +02:00
}
2019-10-03 10:26:28 +02: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 14:52:19 +02:00
break ;
}
default :
break ;
}
2019-10-07 09:31:23 +02:00
if ( updated )
// ask for an idle event to update 3D scene
wxWakeUpIdle ();
2019-09-27 14:52:19 +02:00
}
}
} // namespace GUI
} // namespace Slic3r
#endif // ENABLE_3DCONNEXION_DEVICES