2019-09-27 14:52:19 +02:00
#include "libslic3r/libslic3r.h"
#include "Mouse3DController.hpp"
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-12-16 13:35:45 +01:00
#include "GLCanvas3D.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-10-09 14:01:13 +02:00
#include <bitset>
2020-01-21 14:34:22 +01:00
//unofficial linux lib
//#include <spnav.h>
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
};
2019-10-08 13:38:08 +02:00
// See: https://github.com/FreeSpacenav/spacenavd/blob/a9eccf34e7cac969ee399f625aef827f4f4aaec6/src/dev.c#L202
2019-09-27 14:52:19 +02:00
static const std :: vector < int > _3DCONNEXION_DEVICES =
{
2019-10-08 13:38:08 +02:00
0xc603 , /* 50691 spacemouse plus XT */
0xc605 , /* 50693 cadman */
0xc606 , /* 50694 spacemouse classic */
0xc621 , /* 50721 spaceball 5000 */
0xc623 , /* 50723 space traveller */
0xc625 , /* 50725 space pilot */
0xc626 , /* 50726 space navigator *TESTED* */
0xc627 , /* 50727 space explorer */
0xc628 , /* 50728 space navigator for notebooks*/
0xc629 , /* 50729 space pilot pro*/
0xc62b , /* 50731 space mouse pro*/
2019-10-09 14:01:13 +02:00
0xc62e , /* 50734 spacemouse wireless (USB cable) *TESTED* */
0xc62f , /* 50735 spacemouse wireless receiver */
2019-10-08 13:38:08 +02:00
0xc631 , /* 50737 spacemouse pro wireless *TESTED* */
0xc632 , /* 50738 spacemouse pro wireless receiver */
0xc633 , /* 50739 spacemouse enterprise */
2019-10-09 14:01:13 +02:00
0xc635 , /* 50741 spacemouse compact *TESTED* */
2019-10-08 13:38:08 +02:00
0xc636 , /* 50742 spacemouse module */
0xc640 , /* 50752 nulooq */
2019-10-11 15:29:57 +02:00
0xc652 , /* 50770 3Dconnexion universal receiver *TESTED* */
2019-09-27 14:52:19 +02:00
};
namespace Slic3r {
namespace GUI {
2019-10-09 15:23:30 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
template < typename T >
void update_maximum ( std :: atomic < T >& maximum_value , T const & value ) noexcept
2019-09-27 14:52:19 +02:00
{
2020-03-04 11:36:36 +01:00
T prev_value = maximum_value ;
while ( prev_value < value && ! maximum_value . compare_exchange_weak ( prev_value , value )) ;
2019-09-27 14:52:19 +02:00
}
2020-03-04 11:36:36 +01:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-09-27 14:52:19 +02:00
2020-03-04 11:36:36 +01:00
void Mouse3DController :: State :: append_translation ( const Vec3d & translation , size_t input_queue_max_size )
2019-10-10 11:55:17 +02:00
{
2020-03-04 11:36:36 +01:00
tbb :: mutex :: scoped_lock lock ( m_input_queue_mutex );
while ( m_input_queue . size () >= input_queue_max_size )
m_input_queue . pop_front ();
m_input_queue . emplace_back ( QueueItem :: translation ( translation ));
2019-10-10 11:55:17 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
update_maximum ( input_queue_max_size_achieved , m_input_queue . size ());
2019-10-10 11:55:17 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
}
2020-03-04 11:36:36 +01:00
void Mouse3DController :: State :: append_rotation ( const Vec3f & rotation , size_t input_queue_max_size )
2019-10-10 11:55:17 +02:00
{
2020-03-04 11:36:36 +01:00
tbb :: mutex :: scoped_lock lock ( m_input_queue_mutex );
while ( m_input_queue . size () >= input_queue_max_size )
m_input_queue . pop_front ();
m_input_queue . emplace_back ( QueueItem :: rotation ( rotation . cast < double > ()));
#ifdef WIN32
if ( rotation . x () != 0.0f )
++ m_mouse_wheel_counter ;
#endif // WIN32
2019-10-10 11:55:17 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
update_maximum ( input_queue_max_size_achieved , m_input_queue . size ());
2019-10-10 11:55:17 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
}
2020-03-04 11:36:36 +01:00
void Mouse3DController :: State :: append_button ( unsigned int id , size_t /* input_queue_max_size */ )
{
tbb :: mutex :: scoped_lock lock ( m_input_queue_mutex );
m_input_queue . emplace_back ( QueueItem :: buttons ( id ));
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
update_maximum ( input_queue_max_size_achieved , m_input_queue . size ());
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
}
#ifdef WIN32
2020-03-13 14:19:02 +01:00
// Called by Win32 HID enumeration callback.
void Mouse3DController :: device_attached ( const std :: string & device )
{
int vid = 0 ;
int pid = 0 ;
if ( sscanf ( device . c_str (), " \\\\ ? \\ HID#VID_%x&PID_%x&" , & vid , & pid ) == 2 ) {
// BOOST_LOG_TRIVIAL(trace) << boost::format("Mouse3DController::device_attached(VID_%04xxPID_%04x)") % vid % pid;
// BOOST_LOG_TRIVIAL(trace) << "Mouse3DController::device_attached: " << device;
if ( std :: find ( _3DCONNEXION_VENDORS . begin (), _3DCONNEXION_VENDORS . end (), vid ) != _3DCONNEXION_VENDORS . end ()) {
// Signal the worker thread to wake up and enumerate HID devices, if not connected at the moment.
// The message may come multiple times per each USB device. For example, some USB wireless dongles register as multiple HID sockets
// for multiple devices to connect to.
// Never mind, enumeration will be performed until connected.
m_wakeup = true ;
m_stop_condition . notify_all ();
}
}
}
2020-03-04 11:36:36 +01:00
// Filter out mouse scroll events produced by the 3DConnexion driver.
2019-10-08 08:44:50 +02:00
bool Mouse3DController :: State :: process_mouse_wheel ()
{
2020-03-04 11:36:36 +01:00
tbb :: mutex :: scoped_lock lock ( m_input_queue_mutex );
if ( m_mouse_wheel_counter == 0 )
// No 3DConnexion rotation has been captured since the last mouse scroll event.
2019-10-08 13:38:08 +02:00
return false ;
2020-03-04 11:36:36 +01:00
if ( std :: find_if ( m_input_queue . begin (), m_input_queue . end (), []( const QueueItem & item ){ return item . is_rotation (); }) != m_input_queue . end ()) {
// There is a rotation stored in the queue. Suppress one mouse scroll event.
-- m_mouse_wheel_counter ;
2019-10-08 08:44:50 +02:00
return true ;
}
2020-03-04 11:36:36 +01:00
m_mouse_wheel_counter = 0 ;
2019-10-11 09:16:20 +02:00
return true ;
2019-10-08 08:44:50 +02:00
}
2020-03-04 11:36:36 +01:00
#endif // WIN32
2019-10-08 08:44:50 +02:00
2020-03-04 11:36:36 +01:00
bool Mouse3DController :: State :: apply ( const Mouse3DController :: Params & params , Camera & camera )
2019-10-10 11:55:17 +02:00
{
2020-03-04 11:36:36 +01:00
if ( ! wxGetApp (). IsActive ())
2019-09-27 14:52:19 +02:00
return false ;
2020-03-04 11:36:36 +01:00
std :: deque < QueueItem > input_queue ;
2019-09-27 14:52:19 +02:00
{
2020-03-04 11:36:36 +01:00
// Atomically move m_input_queue to input_queue.
tbb :: mutex :: scoped_lock lock ( m_input_queue_mutex );
input_queue = std :: move ( m_input_queue );
m_input_queue . clear ();
2019-09-27 14:52:19 +02:00
}
2020-03-04 11:36:36 +01:00
for ( const QueueItem & input_queue_item : input_queue ) {
if ( input_queue_item . is_translation ()) {
2020-03-20 17:19:20 +01:00
Vec3d translation = params . swap_yz ? Vec3d ( input_queue_item . vector . x (), - input_queue_item . vector . z (), input_queue_item . vector . y ()) : input_queue_item . vector ;
2020-03-20 13:09:42 +01:00
double zoom_factor = camera . min_zoom () / camera . get_zoom ();
2020-03-04 11:36:36 +01:00
camera . set_target ( camera . get_target () + zoom_factor * params . translation . scale * ( translation . x () * camera . get_dir_right () + translation . z () * camera . get_dir_up ()));
2020-03-10 13:43:49 +01:00
if ( translation . y () != 0.0 )
camera . update_zoom ( params . zoom . scale * translation . y ());
} else if ( input_queue_item . is_rotation ()) {
2020-03-20 16:13:08 +01:00
Vec3d rot = params . rotation . scale * input_queue_item . vector * ( PI / 180. );
if ( params . swap_yz )
2020-03-20 17:19:20 +01:00
rot = Vec3d ( rot . x (), - rot . z (), rot . y ());
2020-03-20 16:13:08 +01:00
camera . rotate_local_around_target ( Vec3d ( rot . x (), - rot . z (), rot . y ()));
2020-03-04 11:36:36 +01:00
break ;
} else {
assert ( input_queue_item . is_buttons ());
switch ( input_queue_item . type_or_buttons ) {
case 0 : camera . update_zoom ( 1.0 ); break ;
case 1 : camera . update_zoom ( - 1.0 ); break ;
default : break ;
}
}
2019-09-27 14:52:19 +02:00
}
2020-03-04 11:36:36 +01:00
return ! input_queue . empty ();
2019-09-27 14:52:19 +02:00
}
2020-03-04 11:36:36 +01:00
// Load the device parameter database from appconfig. To be called on application startup.
void Mouse3DController :: load_config ( const AppConfig & appconfig )
2019-09-27 14:52:19 +02:00
{
2020-03-04 11:36:36 +01:00
// We do not synchronize m_params_by_device with the background thread explicitely
// as there should be a full memory barrier executed once the background thread is started.
m_params_by_device . clear ();
for ( const std :: string & device_name : appconfig . get_mouse_device_names ()) {
double translation_speed = 4.0 ;
float rotation_speed = 4.0 ;
double translation_deadzone = Params :: DefaultTranslationDeadzone ;
float rotation_deadzone = Params :: DefaultRotationDeadzone ;
double zoom_speed = 2.0 ;
2020-03-20 16:13:08 +01:00
bool swap_yz = false ;
2020-03-20 13:09:42 +01:00
appconfig . get_mouse_device_translation_speed ( device_name , translation_speed );
2020-03-04 11:36:36 +01:00
appconfig . get_mouse_device_translation_deadzone ( device_name , translation_deadzone );
appconfig . get_mouse_device_rotation_speed ( device_name , rotation_speed );
appconfig . get_mouse_device_rotation_deadzone ( device_name , rotation_deadzone );
appconfig . get_mouse_device_zoom_speed ( device_name , zoom_speed );
2020-03-20 16:13:08 +01:00
appconfig . get_mouse_device_swap_yz ( device_name , swap_yz );
2020-03-20 13:09:42 +01:00
// clamp to valid values
2020-03-04 11:36:36 +01:00
Params params ;
params . translation . scale = Params :: DefaultTranslationScale * std :: clamp ( translation_speed , 0.1 , 10.0 );
params . translation . deadzone = std :: clamp ( translation_deadzone , 0.0 , Params :: MaxTranslationDeadzone );
params . rotation . scale = Params :: DefaultRotationScale * std :: clamp ( rotation_speed , 0.1f , 10.0f );
params . rotation . deadzone = std :: clamp ( rotation_deadzone , 0.0f , Params :: MaxRotationDeadzone );
params . zoom . scale = Params :: DefaultZoomScale * std :: clamp ( zoom_speed , 0.1 , 10.0 );
2020-03-20 16:13:08 +01:00
params . swap_yz = swap_yz ;
2020-03-20 13:09:42 +01:00
m_params_by_device [ device_name ] = std :: move ( params );
2020-03-04 11:36:36 +01:00
}
2019-09-27 14:52:19 +02:00
}
2020-03-04 11:36:36 +01:00
// Store the device parameter database back to appconfig. To be called on application closeup.
void Mouse3DController :: save_config ( AppConfig & appconfig ) const
2019-09-27 14:52:19 +02:00
{
2020-03-04 11:36:36 +01:00
// We do not synchronize m_params_by_device with the background thread explicitely
// as there should be a full memory barrier executed once the background thread is stopped.
for ( const std :: pair < std :: string , Params > & key_value_pair : m_params_by_device ) {
const std :: string & device_name = key_value_pair . first ;
const Params & params = key_value_pair . second ;
// Store current device parameters into the config
2020-03-20 13:09:42 +01:00
appconfig . set_mouse_device ( device_name , params . translation . scale / Params :: DefaultTranslationScale , params . translation . deadzone ,
2020-03-20 16:13:08 +01:00
params . rotation . scale / Params :: DefaultRotationScale , params . rotation . deadzone , params . zoom . scale / Params :: DefaultZoomScale , params . swap_yz );
2020-03-20 13:09:42 +01:00
}
2019-09-27 14:52:19 +02:00
}
2019-10-03 12:16:59 +02:00
bool Mouse3DController :: apply ( Camera & camera )
{
2019-10-04 10:59:27 +02:00
// check if the user unplugged the device
2020-03-04 11:36:36 +01:00
if ( ! m_connected ) {
2019-12-16 13:35:45 +01:00
// hides the settings dialog if the user un-plug the device
m_show_settings_dialog = false ;
m_settings_dialog_closed_by_user = false ;
2019-10-04 10:59:27 +02:00
}
2020-03-04 11:36:36 +01:00
return m_state . apply ( m_params , camera );
2019-10-03 12:16:59 +02:00
}
2019-12-16 13:35:45 +01:00
void Mouse3DController :: render_settings_dialog ( GLCanvas3D & canvas ) const
2019-10-02 15:55:26 +02:00
{
2020-03-04 11:36:36 +01:00
if ( ! m_show_settings_dialog || ! m_connected )
2019-10-02 15:55:26 +02:00
return ;
2019-12-16 13:35:45 +01:00
// when the user clicks on [X] or [Close] button we need to trigger
// an extra frame to let the dialog disappear
if ( m_settings_dialog_closed_by_user )
2019-10-10 09:04:44 +02:00
{
2019-12-16 13:35:45 +01:00
m_show_settings_dialog = false ;
m_settings_dialog_closed_by_user = false ;
canvas . request_extra_frame ();
return ;
2019-10-10 09:04:44 +02:00
}
2019-11-11 13:04:02 +01:00
2020-03-04 11:36:36 +01:00
Params params_copy ;
bool params_changed = false ;
{
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
params_copy = m_params_ui ;
}
2019-12-16 13:35:45 +01:00
Size cnv_size = canvas . get_canvas_size ();
ImGuiWrapper & imgui = * wxGetApp (). imgui ();
imgui . set_next_window_pos ( 0.5f * ( float ) cnv_size . get_width (), 0.5f * ( float ) cnv_size . get_height (), ImGuiCond_Always , 0.5f , 0.5f );
static ImVec2 last_win_size ( 0.0f , 0.0f );
bool shown = true ;
if ( imgui . begin ( _ ( L ( "3Dconnexion settings" )), & shown , ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse ))
{
if ( shown )
{
ImVec2 win_size = ImGui :: GetWindowSize ();
if (( last_win_size . x != win_size . x ) || ( last_win_size . y != win_size . y ))
{
// when the user clicks on [X] button, the next time the dialog is shown
// has a dummy size, so we trigger an extra frame to let it have the correct size
last_win_size = win_size ;
canvas . request_extra_frame ();
}
const ImVec4 & color = ImGui :: GetStyleColorVec4 ( ImGuiCol_Separator );
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( _ ( L ( "Device:" )));
ImGui :: PopStyleColor ();
ImGui :: SameLine ();
imgui . text ( m_device_str );
ImGui :: Separator ();
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( _ ( L ( "Speed:" )));
ImGui :: PopStyleColor ();
2020-03-04 11:36:36 +01:00
float translation_scale = ( float ) params_copy . translation . scale / Params :: DefaultTranslationScale ;
if ( imgui . slider_float ( _ ( L ( "Translation" )) + "##1" , & translation_scale , 0.1f , 10.0f , "%.1f" )) {
params_copy . translation . scale = Params :: DefaultTranslationScale * ( double ) translation_scale ;
params_changed = true ;
}
2019-12-16 13:35:45 +01:00
2020-03-04 11:36:36 +01:00
float rotation_scale = params_copy . rotation . scale / Params :: DefaultRotationScale ;
if ( imgui . slider_float ( _ ( L ( "Rotation" )) + "##1" , & rotation_scale , 0.1f , 10.0f , "%.1f" )) {
params_copy . rotation . scale = Params :: DefaultRotationScale * rotation_scale ;
params_changed = true ;
}
2019-12-16 13:35:45 +01:00
2020-03-04 11:36:36 +01:00
float zoom_scale = params_copy . zoom . scale / Params :: DefaultZoomScale ;
if ( imgui . slider_float ( _ ( L ( "Zoom" )), & zoom_scale , 0.1f , 10.0f , "%.1f" )) {
params_copy . zoom . scale = Params :: DefaultZoomScale * zoom_scale ;
params_changed = true ;
}
2020-01-03 14:42:52 +01:00
2019-12-16 13:35:45 +01:00
ImGui :: Separator ();
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( _ ( L ( "Deadzone:" )));
ImGui :: PopStyleColor ();
2020-03-04 11:36:36 +01:00
float translation_deadzone = ( float ) params_copy . translation . deadzone ;
if ( imgui . slider_float ( _ ( L ( "Translation" )) + "/" + _ ( L ( "Zoom" )), & translation_deadzone , 0.0f , ( float ) Params :: MaxTranslationDeadzone , "%.2f" )) {
params_copy . translation . deadzone = ( double ) translation_deadzone ;
params_changed = true ;
}
2019-12-16 13:35:45 +01:00
2020-03-04 11:36:36 +01:00
float rotation_deadzone = params_copy . rotation . deadzone ;
if ( imgui . slider_float ( _ ( L ( "Rotation" )) + "##2" , & rotation_deadzone , 0.0f , Params :: MaxRotationDeadzone , "%.2f" )) {
params_copy . rotation . deadzone = rotation_deadzone ;
params_changed = true ;
}
2019-12-16 13:35:45 +01:00
2020-03-20 13:09:42 +01:00
ImGui :: Separator ();
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( _ ( L ( "Options:" )));
ImGui :: PopStyleColor ();
2020-03-20 16:13:08 +01:00
bool swap_yz = params_copy . swap_yz ;
if ( imgui . checkbox ( "Swap Y/Z axes" , swap_yz )) {
params_copy . swap_yz = swap_yz ;
2020-03-20 13:09:42 +01:00
params_changed = true ;
}
2019-12-16 13:35:45 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
ImGui :: Separator ();
ImGui :: Separator ();
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( "DEBUG:" );
imgui . text ( "Vectors:" );
ImGui :: PopStyleColor ();
2020-03-04 11:36:36 +01:00
Vec3f translation = m_state . get_first_vector_of_type ( State :: QueueItem :: TranslationType ). cast < float > ();
Vec3f rotation = m_state . get_first_vector_of_type ( State :: QueueItem :: RotationType ). cast < float > ();
2019-12-16 13:35:45 +01:00
ImGui :: InputFloat3 ( "Translation##3" , translation . data (), "%.3f" , ImGuiInputTextFlags_ReadOnly );
ImGui :: InputFloat3 ( "Rotation##3" , rotation . data (), "%.3f" , ImGuiInputTextFlags_ReadOnly );
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( "Queue size:" );
ImGui :: PopStyleColor ();
2020-03-04 11:36:36 +01:00
int input_queue_size_current [ 2 ] = { int ( m_state . input_queue_size_current ()), int ( m_state . input_queue_max_size_achieved ) };
ImGui :: InputInt2 ( "Current##4" , input_queue_size_current , ImGuiInputTextFlags_ReadOnly );
2019-12-16 13:35:45 +01:00
2020-03-04 11:36:36 +01:00
int input_queue_size_param = int ( params_copy . input_queue_max_size );
if ( ImGui :: InputInt ( "Max size" , & input_queue_size_param , 1 , 1 , ImGuiInputTextFlags_ReadOnly ))
2019-12-16 13:35:45 +01:00
{
2020-03-04 11:36:36 +01:00
if ( input_queue_size_param > 0 ) {
params_copy . input_queue_max_size = input_queue_size_param ;
params_changed = true ;
}
2019-12-16 13:35:45 +01:00
}
ImGui :: Separator ();
ImGui :: PushStyleColor ( ImGuiCol_Text , color );
imgui . text ( "Camera:" );
ImGui :: PopStyleColor ();
Vec3f target = wxGetApp (). plater () -> get_camera (). get_target (). cast < float > ();
ImGui :: InputFloat3 ( "Target" , target . data (), "%.3f" , ImGuiInputTextFlags_ReadOnly );
2019-10-09 14:18:43 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-12-16 13:35:45 +01:00
ImGui :: Separator ();
if ( imgui . button ( _ ( L ( "Close" ))))
{
// the user clicked on the [Close] button
m_settings_dialog_closed_by_user = true ;
canvas . set_as_dirty ();
}
}
else
{
// the user clicked on the [X] button
m_settings_dialog_closed_by_user = true ;
canvas . set_as_dirty ();
}
}
2019-10-09 14:18:43 +02:00
2019-10-02 15:55:26 +02:00
imgui . end ();
2020-03-04 11:36:36 +01:00
if ( params_changed ) {
// Synchronize front end parameters to back end.
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
auto pthis = const_cast < Mouse3DController *> ( this );
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
if ( params_copy . input_queue_max_size != params_copy . input_queue_max_size )
// Reset the statistics counter.
m_state . input_queue_max_size_achieved = 0 ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
pthis -> m_params_ui = params_copy ;
pthis -> m_params_ui_changed = true ;
}
}
#if __APPLE__
void Mouse3DController :: connected ( std :: string device_name )
{
2020-03-09 10:36:55 +01:00
assert ( ! m_connected );
assert ( m_device_str . empty ());
2020-03-04 11:36:36 +01:00
m_device_str = device_name ;
// Copy the parameters for m_device_str into the current parameters.
if ( auto it_params = m_params_by_device . find ( m_device_str ); it_params != m_params_by_device . end ()) {
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
m_params = m_params_ui = it_params -> second ;
}
m_connected = true ;
}
void Mouse3DController :: disconnected ()
{
// Copy the current parameters for m_device_str into the parameter database.
2020-03-09 10:36:55 +01:00
assert ( m_connected == ! m_device_str . empty ());
if ( m_connected ) {
2020-03-04 11:36:36 +01:00
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
m_params_by_device [ m_device_str ] = m_params_ui ;
2020-03-09 10:36:55 +01:00
m_device_str . clear ();
m_connected = false ;
2020-03-13 14:57:45 +01:00
wxGetApp (). plater () -> CallAfter ([]() {
Plater * plater = wxGetApp (). plater ();
if ( plater != nullptr ) {
plater -> get_camera (). recover_from_free_camera ();
plater -> set_current_canvas_as_dirty ();
}
});
2020-03-04 11:36:36 +01:00
}
}
2020-03-04 12:18:59 +01:00
bool Mouse3DController :: handle_input ( const DataPacketAxis & packet )
2020-03-04 11:36:36 +01:00
{
if ( ! wxGetApp (). IsActive ())
2020-03-04 12:18:59 +01:00
return false ;
2020-03-04 11:36:36 +01:00
{
// Synchronize parameters between the UI thread and the background thread.
//FIXME is this necessary on OSX? Are these notifications triggered from the main thread or from a worker thread?
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
if ( m_params_ui_changed ) {
m_params = m_params_ui ;
m_params_ui_changed = false ;
}
}
bool updated = false ;
2020-03-04 12:18:59 +01:00
// translation
double deadzone = m_params . translation . deadzone ;
2020-03-04 11:36:36 +01:00
Vec3d translation ( std :: abs ( packet [ 0 ]) > deadzone ? - packet [ 0 ] : 0.0 ,
std :: abs ( packet [ 1 ]) > deadzone ? packet [ 1 ] : 0.0 ,
std :: abs ( packet [ 2 ]) > deadzone ? packet [ 2 ] : 0.0 );
2020-03-04 12:18:59 +01:00
if ( ! translation . isApprox ( Vec3d :: Zero ())) {
m_state . append_translation ( translation , m_params . input_queue_max_size );
2020-03-04 11:36:36 +01:00
updated = true ;
}
2020-03-04 12:18:59 +01:00
// rotation
deadzone = m_params . rotation . deadzone ;
2020-03-04 11:36:36 +01:00
Vec3f rotation ( std :: abs ( packet [ 3 ]) > deadzone ? ( float ) packet [ 3 ] : 0.0 ,
std :: abs ( packet [ 4 ]) > deadzone ? ( float ) packet [ 4 ] : 0.0 ,
std :: abs ( packet [ 5 ]) > deadzone ? ( float ) packet [ 5 ] : 0.0 );
2020-03-04 12:18:59 +01:00
if ( ! rotation . isApprox ( Vec3f :: Zero ())) {
m_state . append_rotation ( rotation , m_params . input_queue_max_size );
2020-03-04 11:36:36 +01:00
updated = true ;
}
if ( updated ) {
wxGetApp (). plater () -> set_current_canvas_as_dirty ();
// ask for an idle event to update 3D scene
wxWakeUpIdle ();
}
return updated ;
}
#else //__APPLE__
// Initialize the application.
void Mouse3DController :: init ()
{
assert ( ! m_thread . joinable ());
if ( ! m_thread . joinable ()) {
m_stop = false ;
m_thread = std :: thread ( & Mouse3DController :: run , this );
}
}
// Closing the application.
void Mouse3DController :: shutdown ()
{
if ( m_thread . joinable ()) {
// Stop the worker thread, if running.
{
// Notify the worker thread to cancel wait on detection polling.
2020-03-06 15:10:58 +01:00
std :: lock_guard < std :: mutex > lock ( m_stop_condition_mutex );
2020-03-04 11:36:36 +01:00
m_stop = true ;
}
2020-03-06 15:10:58 +01:00
m_stop_condition . notify_all ();
2020-03-04 11:36:36 +01:00
// Wait for the worker thread to stop.
m_thread . join ();
2020-03-06 15:10:58 +01:00
m_stop = false ;
2020-03-04 11:36:36 +01:00
}
}
// Main routine of the worker thread.
void Mouse3DController :: run ()
{
// Initialize the hidapi library
int res = hid_init ();
if ( res != 0 ) {
// Give up.
2020-03-12 09:38:22 +01:00
#if defined(__unix__) || defined(__unix) || defined(unix)
if ( res == - 1 )
// Hopefully this error code comes from our bundled patched hidapi. In that case, -1 is returned by hid_wrapper_udev_init() and it mean
BOOST_LOG_TRIVIAL ( error ) << "Unable to initialize hidapi library: failed to load libudev.so.1 or libudev.so.0" ;
else if ( res == - 2 )
// Hopefully this error code comes from our bundled patched hidapi. In that case, -2 is returned by hid_wrapper_udev_init() and it mean
BOOST_LOG_TRIVIAL ( error ) << "Unable to initialize hidapi library: failed to resolve some function from libudev.so.1 or libudev.so.0" ;
else
#endif // unixes
BOOST_LOG_TRIVIAL ( error ) << "Unable to initialize hidapi library" ;
2020-03-04 11:36:36 +01:00
return ;
}
2020-03-13 14:19:02 +01:00
#ifdef _WIN32
// Enumerate once just after thread start.
m_wakeup = true ;
#endif // _WIN32
2020-03-04 11:36:36 +01:00
for (;;) {
{
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
if ( m_stop )
break ;
if ( m_params_ui_changed ) {
m_params = m_params_ui ;
m_params_ui_changed = false ;
}
}
if ( m_device == nullptr )
// Polls the HID devices, blocks for maximum 2 seconds.
m_connected = this -> connect_device ();
else
// Waits for 3DConnexion mouse input for maximum 100ms, then repeats.
this -> collect_input ();
}
this -> disconnect_device ();
// Finalize the hidapi library
hid_exit ();
2019-10-02 15:55:26 +02:00
}
2019-10-04 10:59:27 +02:00
bool Mouse3DController :: connect_device ()
2019-09-27 14:52:19 +02:00
{
2020-03-04 11:36:36 +01:00
if ( m_stop )
return false ;
2019-11-25 10:34:42 +01:00
2020-03-04 11:36:36 +01:00
{
// Wait for 2 seconds, but cancellable by m_stop.
std :: unique_lock < std :: mutex > lock ( m_stop_condition_mutex );
2020-03-13 14:19:02 +01:00
#ifdef _WIN32
// Wait indifinetely for the stop signal.
m_stop_condition . wait ( lock , [ this ]{ return m_stop || m_wakeup ; });
m_wakeup = false ;
#else
m_stop_condition . wait_for ( lock , std :: chrono :: seconds ( 2 ), [ this ]{ return m_stop ; });
#endif
2020-03-04 11:36:36 +01:00
}
2019-09-27 14:52:19 +02:00
2020-03-04 11:36:36 +01:00
if ( m_stop )
return false ;
2019-11-25 10:34:42 +01:00
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
{
2020-03-13 14:19:02 +01:00
BOOST_LOG_TRIVIAL ( trace ) << "Mouse3DController::connect_device() - no HID device enumerated." ;
2019-10-04 10:59:27 +02:00
return false ;
}
2019-09-27 14:52:19 +02:00
2020-03-13 14:19:02 +01:00
#ifdef _WIN32
BOOST_LOG_TRIVIAL ( trace ) << "Mouse3DController::connect_device() - enumerating HID devices." ;
#endif // _WIN32
2019-09-27 14:52:19 +02:00
// Searches for 1st connected 3Dconnexion device
2019-10-21 14:21:51 +02:00
struct DeviceData
{
2019-10-21 15:20:36 +02:00
std :: string path ;
unsigned short usage_page ;
unsigned short usage ;
DeviceData ()
2019-11-08 11:51:56 +01:00
: path ( "" ), usage_page ( 0 ), usage ( 0 )
2019-10-21 15:20:36 +02:00
{}
2019-11-08 11:51:56 +01:00
DeviceData ( const std :: string & path , unsigned short usage_page , unsigned short usage )
: path ( path ), usage_page ( usage_page ), usage ( usage )
2019-10-21 15:20:36 +02:00
{}
2019-11-11 13:04:02 +01:00
bool has_valid_usage () const { return ( usage_page == 1 ) && ( usage == 8 ); }
2019-10-21 14:21:51 +02:00
};
2019-09-27 14:52:19 +02:00
2019-11-11 13:04:02 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
hid_device_info * cur = devices ;
std :: cout << std :: endl << "======================================================================================================================================" << std :: endl ;
std :: cout << "Detected devices:" << std :: endl ;
while ( cur != nullptr )
{
std :: cout << " \" " ;
std :: wcout << (( cur -> manufacturer_string != nullptr ) ? cur -> manufacturer_string : L "Unknown" );
std :: cout << "/" ;
std :: wcout << (( cur -> product_string != nullptr ) ? cur -> product_string : L "Unknown" );
std :: cout << " \" code: " << cur -> vendor_id << "/" << cur -> product_id << " (" << std :: hex << cur -> vendor_id << "/" << cur -> product_id << std :: dec << ")" ;
std :: cout << " serial number: '" ;
std :: wcout << (( cur -> serial_number != nullptr ) ? cur -> serial_number : L "Unknown" );
std :: cout << "' usage page: " << cur -> usage_page << " usage: " << cur -> usage << " interface number: " << cur -> interface_number << std :: endl ;
cur = cur -> next ;
}
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-09 14:18:43 +02:00
2019-10-21 14:21:51 +02:00
// When using 3Dconnexion universal receiver, multiple devices are detected sharing the same vendor_id and product_id.
// To choose from them the right one we use:
// On Windows and Mac: usage_page == 1 and usage == 8
2019-11-08 11:51:56 +01:00
// On Linux: as usage_page and usage are not defined (see hidapi.h) we try all detected devices until one is succesfully open
2019-10-21 14:21:51 +02:00
// When only a single device is detected, as for wired connections, vendor_id and product_id are enough
// First we count all the valid devices from the enumerated list,
2019-09-27 14:52:19 +02:00
hid_device_info * current = devices ;
2019-10-21 14:21:51 +02:00
typedef std :: pair < unsigned short , unsigned short > DeviceIds ;
typedef std :: vector < DeviceData > DeviceDataList ;
typedef std :: map < DeviceIds , DeviceDataList > DetectedDevices ;
DetectedDevices detected_devices ;
2019-11-08 11:51:56 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-11-11 13:04:02 +01:00
std :: cout << std :: endl << "Detected 3D connexion devices:" << std :: endl ;
2019-11-08 11:51:56 +01:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-09-27 14:52:19 +02:00
while ( current != nullptr )
{
2019-10-21 14:21:51 +02:00
unsigned short vendor_id = 0 ;
unsigned short product_id = 0 ;
2019-09-27 14:52:19 +02:00
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 )
{
2019-10-21 14:21:51 +02:00
if ( _3DCONNEXION_DEVICES [ i ] == current -> product_id )
2019-09-27 14:52:19 +02:00
{
product_id = current -> product_id ;
2019-10-21 14:21:51 +02:00
DeviceIds detected_device ( vendor_id , product_id );
DetectedDevices :: iterator it = detected_devices . find ( detected_device );
if ( it == detected_devices . end ())
it = detected_devices . insert ( DetectedDevices :: value_type ( detected_device , DeviceDataList ())). first ;
2019-11-08 11:51:56 +01:00
it -> second . emplace_back ( current -> path , current -> usage_page , current -> usage );
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: wcout << " \" " << (( current -> manufacturer_string != nullptr ) ? current -> manufacturer_string : L "Unknown" );
std :: cout << "/" ;
std :: wcout << (( current -> product_string != nullptr ) ? current -> product_string : L "Unknown" );
std :: cout << " \" code: " << current -> vendor_id << "/" << current -> product_id << " (" << std :: hex << current -> vendor_id << "/" << current -> product_id << std :: dec << ")" ;
std :: cout << " serial number: '" ;
std :: wcout << (( current -> serial_number != nullptr ) ? current -> serial_number : L "Unknown" );
std :: cout << "' usage page: " << current -> usage_page << " usage: " << current -> usage << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-09-27 14:52:19 +02:00
}
}
}
current = current -> next ;
}
// Free enumerated devices
hid_free_enumeration ( devices );
2019-10-21 14:21:51 +02:00
if ( detected_devices . empty ())
return false ;
std :: string path = "" ;
unsigned short vendor_id = 0 ;
unsigned short product_id = 0 ;
// Then we'll decide the choosing logic to apply in dependence of the device count and operating system
for ( const DetectedDevices :: value_type & device : detected_devices )
{
if ( device . second . size () == 1 )
{
2020-01-21 14:34:22 +01:00
#if defined(__linux__)
2019-11-11 13:41:50 +01:00
hid_device * test_device = hid_open ( device . first . first , device . first . second , nullptr );
2019-11-11 13:04:02 +01:00
if ( test_device != nullptr )
{
hid_close ( test_device );
#else
if ( device . second . front (). has_valid_usage ())
{
2020-01-21 14:34:22 +01:00
#endif // __linux__
2019-11-11 13:04:02 +01:00
vendor_id = device . first . first ;
product_id = device . first . second ;
break ;
}
2019-10-21 14:21:51 +02:00
}
else
{
2019-11-08 11:51:56 +01:00
bool found = false ;
2019-11-08 14:43:15 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: cout << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-21 14:21:51 +02:00
for ( const DeviceData & data : device . second )
{
2019-11-08 14:43:15 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: cout << "Test device: " << std :: hex << device . first . first << std :: dec << "/" << std :: hex << device . first . second << std :: dec << " \" " << data . path << " \" " ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-01-21 14:34:22 +01:00
2019-11-08 14:43:15 +01:00
#ifdef __linux__
2019-11-08 11:51:56 +01:00
hid_device * test_device = hid_open_path ( data . path . c_str ());
if ( test_device != nullptr )
{
path = data . path ;
vendor_id = device . first . first ;
product_id = device . first . second ;
found = true ;
2019-11-08 13:31:34 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-11-08 14:43:15 +01:00
std :: cout << "-> PASSED" << std :: endl ;
2019-11-08 13:31:34 +01:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-11-08 14:43:15 +01:00
hid_close ( test_device );
2019-11-08 11:51:56 +01:00
break ;
}
2020-01-21 14:34:22 +01:00
#else // !__linux__
2019-11-11 13:04:02 +01:00
if ( data . has_valid_usage ())
2019-10-21 14:21:51 +02:00
{
path = data . path ;
vendor_id = device . first . first ;
product_id = device . first . second ;
2019-11-08 11:51:56 +01:00
found = true ;
2019-11-08 14:43:15 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: cout << "-> PASSED" << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-21 14:21:51 +02:00
break ;
}
2019-11-08 15:54:44 +01:00
#endif // __linux__
2019-11-08 14:43:15 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
else
std :: cout << "-> NOT PASSED" << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-21 14:21:51 +02:00
}
2019-11-11 13:04:02 +01:00
2019-11-08 11:51:56 +01:00
if ( found )
break ;
2019-10-21 14:21:51 +02:00
}
}
if ( path . empty ())
2019-11-08 13:31:34 +01:00
{
if (( vendor_id != 0 ) && ( product_id != 0 ))
2019-11-08 14:43:15 +01:00
{
2019-11-08 13:31:34 +01:00
// Open the 3Dconnexion device using vendor_id and product_id
2019-11-08 14:43:15 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: cout << std :: endl << "Opening device: " << std :: hex << vendor_id << std :: dec << "/" << std :: hex << product_id << std :: dec << " using hid_open()" << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-11-08 13:31:34 +01:00
m_device = hid_open ( vendor_id , product_id , nullptr );
2019-11-08 14:43:15 +01:00
}
2019-11-08 13:31:34 +01:00
else
return false ;
}
else
2019-11-08 14:43:15 +01:00
{
2019-11-08 13:31:34 +01:00
// Open the 3Dconnexion device using the device path
2019-11-08 14:43:15 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: cout << std :: endl << "Opening device: " << std :: hex << vendor_id << std :: dec << "/" << std :: hex << product_id << std :: dec << " \" " << path << " \" using hid_open_path()" << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-11-08 13:31:34 +01:00
m_device = hid_open_path ( path . c_str ());
2019-11-08 14:43:15 +01:00
}
2019-09-27 14:52:19 +02:00
2019-10-03 10:26:28 +02:00
if ( m_device != nullptr )
{
2020-01-08 12:30:42 +01:00
wchar_t buffer [ 1024 ];
hid_get_manufacturer_string ( m_device , buffer , 1024 );
m_device_str = boost :: nowide :: narrow ( buffer );
// #3479 seems to show that sometimes an extra whitespace is added, so we remove it
boost :: algorithm :: trim ( m_device_str );
2019-10-10 09:04:44 +02:00
2020-01-08 12:30:42 +01:00
hid_get_product_string ( m_device , buffer , 1024 );
m_device_str += "/" + boost :: nowide :: narrow ( buffer );
// #3479 seems to show that sometimes an extra whitespace is added, so we remove it
boost :: algorithm :: trim ( m_device_str );
2019-10-03 10:26:28 +02:00
2020-01-03 10:01:27 +01:00
BOOST_LOG_TRIVIAL ( info ) << "Connected 3DConnexion device:" ;
BOOST_LOG_TRIVIAL ( info ) << "Manufacturer/product: " << m_device_str ;
BOOST_LOG_TRIVIAL ( info ) << "Manufacturer id.....: " << vendor_id << " (" << std :: hex << vendor_id << std :: dec << ")" ;
BOOST_LOG_TRIVIAL ( info ) << "Product id..........: " << product_id << " (" << std :: hex << product_id << std :: dec << ")" ;
if ( ! path . empty ())
BOOST_LOG_TRIVIAL ( info ) << "Path................: '" << path << "'" ;
2020-01-21 14:34:22 +01:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std :: cout << "Opened device." << std :: endl ;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
// Copy the parameters for m_device_str into the current parameters.
if ( auto it_params = m_params_by_device . find ( m_device_str ); it_params != m_params_by_device . end ()) {
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
m_params = m_params_ui = it_params -> second ;
}
2019-10-03 10:26:28 +02:00
}
2019-10-21 11:06:18 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
else
{
std :: cout << std :: endl << "Unable to connect to device:" << std :: endl ;
std :: cout << "Manufacturer/product: " << m_device_str << std :: endl ;
std :: cout << "Manufacturer id.....: " << vendor_id << " (" << std :: hex << vendor_id << std :: dec << ")" << std :: endl ;
std :: cout << "Product id..........: " << product_id << " (" << std :: hex << product_id << std :: dec << ")" << std :: endl ;
std :: cout << "Path................: '" << path << "'" << std :: endl ;
}
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-04 10:59:27 +02:00
return ( m_device != nullptr );
2019-09-27 14:52:19 +02:00
}
void Mouse3DController :: disconnect_device ()
{
2020-03-04 11:36:36 +01:00
if ( m_device ) {
hid_close ( m_device );
m_device = nullptr ;
BOOST_LOG_TRIVIAL ( info ) << "Disconnected device: " << m_device_str ;
// Copy the current parameters for m_device_str into the parameter database.
{
tbb :: mutex :: scoped_lock lock ( m_params_ui_mutex );
m_params_by_device [ m_device_str ] = m_params_ui ;
}
m_device_str . clear ();
m_connected = false ;
2020-03-13 14:19:02 +01:00
#ifdef _WIN32
// Enumerate once immediately after disconnect.
m_wakeup = true ;
2020-03-13 14:57:45 +01:00
#endif // _WIN32
wxGetApp (). plater () -> CallAfter ([]() {
Plater * plater = wxGetApp (). plater ();
if ( plater != nullptr ) {
plater -> get_camera (). recover_from_free_camera ();
plater -> set_current_canvas_as_dirty ();
}
});
2020-03-10 13:02:39 +01:00
}
2019-09-27 14:52:19 +02:00
}
void Mouse3DController :: collect_input ()
{
2020-01-21 14:34:22 +01:00
DataPacketRaw packet = { 0 };
2020-03-04 11:36:36 +01:00
// Read packet, block maximum 100 ms. That means when closing the application, closing the application will be delayed by 100 ms.
2019-10-08 13:38:08 +02:00
int res = hid_read_timeout ( m_device , packet . data (), packet . size (), 100 );
2020-03-04 11:36:36 +01:00
if ( res < 0 ) {
// An error occourred (device detached from pc ?). Close the 3Dconnexion device.
this -> disconnect_device ();
} else
this -> handle_input ( packet , res , m_params , m_state );
2020-01-21 14:34:22 +01:00
}
2020-03-04 11:36:36 +01:00
// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread.
bool Mouse3DController :: handle_input ( const DataPacketRaw & packet , const int packet_lenght , const Params & params , State & state_in_out )
2020-01-21 14:34:22 +01:00
{
2020-03-04 11:36:36 +01:00
if ( ! wxGetApp (). IsActive ())
return false ;
2019-10-08 13:38:08 +02:00
2020-01-21 14:34:22 +01:00
int res = packet_lenght ;
2019-10-08 13:38:08 +02:00
bool updated = false ;
if ( res == 7 )
2020-03-04 11:36:36 +01:00
updated = handle_packet ( packet , params , state_in_out );
2019-10-08 13:38:08 +02:00
else if ( res == 13 )
2020-03-04 11:36:36 +01:00
updated = handle_wireless_packet ( packet , params , state_in_out );
2019-11-08 08:47:57 +01:00
else if (( res == 3 ) && ( packet [ 0 ] == 3 ))
// On Mac button packets can be 3 bytes long
2020-03-04 11:36:36 +01:00
updated = handle_packet ( packet , params , state_in_out );
2019-10-11 09:16:20 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-08 13:38:08 +02:00
else if ( res > 0 )
std :: cout << "Got unknown data packet of length: " << res << ", code:" << ( int ) packet [ 0 ] << std :: endl ;
2019-10-11 09:16:20 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-08 13:38:08 +02:00
2020-03-04 11:36:36 +01:00
#if 1
if ( updated ) {
2020-01-03 11:41:29 +01:00
wxGetApp (). plater () -> set_current_canvas_as_dirty ();
2019-10-08 13:38:08 +02:00
// ask for an idle event to update 3D scene
wxWakeUpIdle ();
2020-01-03 11:41:29 +01:00
}
2020-03-04 11:36:36 +01:00
#endif
return updated ;
2019-10-08 13:38:08 +02:00
}
2020-03-04 11:36:36 +01:00
// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by handle_input() from the worker thread.
bool Mouse3DController :: handle_packet ( const DataPacketRaw & packet , const Params & params , State & state_in_out )
2019-10-08 13:38:08 +02:00
{
switch ( packet [ 0 ])
2019-09-27 14:52:19 +02:00
{
2019-10-08 13:38:08 +02:00
case 1 : // Translation
2019-09-27 14:52:19 +02:00
{
2020-03-04 11:36:36 +01:00
if ( handle_packet_translation ( packet , params , state_in_out ))
2019-10-08 13:38:08 +02:00
return true ;
2019-09-27 14:52:19 +02:00
break ;
}
2019-10-08 13:38:08 +02:00
case 2 : // Rotation
{
2020-03-04 11:36:36 +01:00
if ( handle_packet_rotation ( packet , 1 , params , state_in_out ))
2019-10-08 13:38:08 +02:00
return true ;
2019-10-07 09:31:23 +02:00
2019-10-08 13:38:08 +02:00
break ;
}
case 3 : // Button
{
2020-03-04 11:36:36 +01:00
if ( params . buttons_enabled && handle_packet_button ( packet , packet . size () - 1 , params , state_in_out ))
2019-10-08 13:38:08 +02:00
return true ;
2019-10-11 15:29:57 +02:00
break ;
}
case 23 : // Battery charge
{
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
std :: cout << "3DConnexion - battery level: " << ( int ) packet [ 1 ] << " percent" << std :: endl ;
2019-10-11 15:29:57 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-08 13:38:08 +02:00
break ;
}
default :
{
2019-10-11 09:16:20 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
std :: cout << "3DConnexion - Got unknown data packet of code: " << ( int ) packet [ 0 ] << std :: endl ;
2019-10-11 09:16:20 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-08 13:38:08 +02:00
break ;
}
2019-09-27 14:52:19 +02:00
}
2019-10-08 13:38:08 +02:00
return false ;
}
2020-03-04 11:36:36 +01:00
// Unpack raw 3DConnexion HID packet of a wireless 3D mouse into m_state. Called by handle_input() from the worker thread.
bool Mouse3DController :: handle_wireless_packet ( const DataPacketRaw & packet , const Params & params , State & state_in_out )
2019-10-08 13:38:08 +02:00
{
switch ( packet [ 0 ])
{
case 1 : // Translation + Rotation
{
2020-03-04 11:36:36 +01:00
bool updated = handle_packet_translation ( packet , params , state_in_out );
updated |= handle_packet_rotation ( packet , 7 , params , state_in_out );
2019-10-08 13:38:08 +02:00
if ( updated )
return true ;
break ;
}
case 3 : // Button
{
2020-03-04 11:36:36 +01:00
if ( params . buttons_enabled && handle_packet_button ( packet , 12 , params , state_in_out ))
2019-10-08 13:38:08 +02:00
return true ;
2019-10-11 15:29:57 +02:00
break ;
}
case 23 : // Battery charge
{
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
std :: cout << "3DConnexion - battery level: " << ( int ) packet [ 1 ] << " percent" << std :: endl ;
2019-10-11 15:29:57 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-08 13:38:08 +02:00
break ;
}
default :
{
2019-10-11 09:16:20 +02:00
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2020-03-04 11:36:36 +01:00
std :: cout << "3DConnexion - Got unknown data packet of code: " << ( int ) packet [ 0 ] << std :: endl ;
2019-10-11 09:16:20 +02:00
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
2019-10-08 13:38:08 +02:00
break ;
}
}
return false ;
}
2020-03-04 11:36:36 +01:00
// Convert a signed 16bit word from a 3DConnexion mouse HID packet into a double coordinate, apply a dead zone.
static double convert_input ( int coord_byte_low , int coord_byte_high , double deadzone )
2019-10-08 13:38:08 +02:00
{
2020-03-04 11:36:36 +01:00
int value = coord_byte_low | ( coord_byte_high << 8 );
if ( value >= 32768 )
value = value - 65536 ;
2019-10-09 14:39:28 +02:00
double ret = ( double ) value / 350.0 ;
return ( std :: abs ( ret ) > deadzone ) ? ret : 0.0 ;
2019-10-09 14:01:13 +02:00
}
2019-10-08 13:38:08 +02:00
2020-03-04 11:36:36 +01:00
// Unpack raw 3DConnexion HID packet, decode state of translation axes into state_in_out. Called by handle_input() from the worker thread.
bool Mouse3DController :: handle_packet_translation ( const DataPacketRaw & packet , const Params & params , State & state_in_out )
2019-10-08 13:38:08 +02:00
{
2020-03-04 11:36:36 +01:00
double deadzone = params . translation . deadzone ;
2019-10-09 14:39:28 +02:00
Vec3d translation ( - convert_input ( packet [ 1 ], packet [ 2 ], deadzone ),
convert_input ( packet [ 3 ], packet [ 4 ], deadzone ),
convert_input ( packet [ 5 ], packet [ 6 ], deadzone ));
if ( ! translation . isApprox ( Vec3d :: Zero ()))
2019-10-08 13:38:08 +02:00
{
2020-03-04 11:36:36 +01:00
state_in_out . append_translation ( translation , params . input_queue_max_size );
2019-10-08 13:38:08 +02:00
return true ;
}
return false ;
}
2020-03-04 11:36:36 +01:00
// Unpack raw 3DConnexion HID packet, decode state of rotation axes into state_in_out. Called by the handle_input() from worker thread.
bool Mouse3DController :: handle_packet_rotation ( const DataPacketRaw & packet , unsigned int first_byte , const Params & params , State & state_in_out )
2019-10-08 13:38:08 +02:00
{
2020-03-04 11:36:36 +01:00
double deadzone = ( double ) params . rotation . deadzone ;
2020-01-15 12:49:34 +01:00
Vec3f rotation (( float ) convert_input ( packet [ first_byte + 0 ], packet [ first_byte + 1 ], deadzone ),
( float ) convert_input ( packet [ first_byte + 2 ], packet [ first_byte + 3 ], deadzone ),
( float ) convert_input ( packet [ first_byte + 4 ], packet [ first_byte + 5 ], deadzone ));
2019-10-08 14:32:05 +02:00
2019-10-09 14:39:28 +02:00
if ( ! rotation . isApprox ( Vec3f :: Zero ()))
2019-10-08 13:38:08 +02:00
{
2020-03-04 11:36:36 +01:00
state_in_out . append_rotation ( rotation , params . input_queue_max_size );
2019-10-08 13:38:08 +02:00
return true ;
}
return false ;
}
2020-03-04 11:36:36 +01:00
// Unpack raw 3DConnexion HID packet, decode button state into state_in_out. Called by handle_input() from the worker thread.
bool Mouse3DController :: handle_packet_button ( const DataPacketRaw & packet , unsigned int packet_size , const Params & params , State & state_in_out )
2019-10-08 13:38:08 +02:00
{
2019-11-08 09:18:59 +01:00
unsigned int data = 0 ;
for ( unsigned int i = 1 ; i < packet_size ; ++ i )
{
data |= packet [ i ] << 8 * ( i - 1 );
}
2019-10-09 14:01:13 +02:00
const std :: bitset < 32 > data_bits { data };
for ( size_t i = 0 ; i < data_bits . size (); ++ i )
2019-10-08 13:38:08 +02:00
{
2019-10-09 14:01:13 +02:00
if ( data_bits . test ( i ))
2019-10-08 13:38:08 +02:00
{
2020-03-04 11:36:36 +01:00
state_in_out . append_button (( unsigned int ) i , params . input_queue_max_size );
2019-10-09 14:01:13 +02:00
return true ;
2019-10-08 13:38:08 +02:00
}
}
return false ;
2019-09-27 14:52:19 +02:00
}
2020-03-04 11:36:36 +01:00
#endif //__APPLE__
2019-09-27 14:52:19 +02:00
} // namespace GUI
} // namespace Slic3r