mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-11 16:57:53 -06:00
Reworked the 3DConnexion interfacing code to run the device
enumeration / connect / disconnect and read out at the background thread only.
This commit is contained in:
parent
a87ba5d6a6
commit
0b96855c2e
8 changed files with 603 additions and 593 deletions
|
@ -2,7 +2,7 @@
|
|||
#define slic3r_Mouse3DController_hpp_
|
||||
|
||||
// Enabled debug output to console and extended imgui dialog
|
||||
#define ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT 0
|
||||
#define ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT 1
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
|
@ -10,67 +10,80 @@
|
|||
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
|
||||
|
||||
#include <tbb/mutex.h>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class AppConfig;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
#if __APPLE__
|
||||
class Mouse3DHandlerMac;
|
||||
#endif//__APPLE__
|
||||
|
||||
struct Camera;
|
||||
class GLCanvas3D;
|
||||
|
||||
class Mouse3DController
|
||||
{
|
||||
class State
|
||||
{
|
||||
public:
|
||||
static const double DefaultTranslationScale;
|
||||
static const double MaxTranslationDeadzone;
|
||||
static const double DefaultTranslationDeadzone;
|
||||
static const float DefaultRotationScale;
|
||||
static const float MaxRotationDeadzone;
|
||||
static const float DefaultRotationDeadzone;
|
||||
static const double DefaultZoomScale;
|
||||
// Parameters, which are configured by the ImGUI dialog when pressing Ctrl+M.
|
||||
// The UI thread modifies a copy of the parameters and indicates to the background thread that there was a change
|
||||
// to copy the parameters.
|
||||
struct Params
|
||||
{
|
||||
static constexpr double DefaultTranslationScale = 2.5;
|
||||
static constexpr double MaxTranslationDeadzone = 0.2;
|
||||
static constexpr double DefaultTranslationDeadzone = 0.5 * MaxTranslationDeadzone;
|
||||
static constexpr float DefaultRotationScale = 1.0f;
|
||||
static constexpr float MaxRotationDeadzone = 0.2f;
|
||||
static constexpr float DefaultRotationDeadzone = 0.5f * MaxRotationDeadzone;
|
||||
static constexpr double DefaultZoomScale = 0.1;
|
||||
|
||||
private:
|
||||
template <typename Number>
|
||||
struct CustomParameters
|
||||
{
|
||||
Number scale;
|
||||
Number deadzone;
|
||||
|
||||
CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct InputQueue
|
||||
{
|
||||
size_t max_size;
|
||||
std::queue<T> queue;
|
||||
CustomParameters<double> translation { DefaultTranslationScale, DefaultTranslationDeadzone };
|
||||
CustomParameters<float> rotation { DefaultRotationScale, DefaultRotationDeadzone };
|
||||
CustomParameters<double> zoom { DefaultZoomScale, 0.0 };
|
||||
// Do not process button presses from 3DConnexion device, let the user map the 3DConnexion keys in 3DConnexion driver.
|
||||
bool buttons_enabled { false };
|
||||
// The default value of 15 for max_size seems to work fine on all platforms
|
||||
// The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
|
||||
// and playing with the imgui dialog which shows by pressing CTRL+M
|
||||
size_t input_queue_max_size { 15 };
|
||||
};
|
||||
|
||||
// The default value of 5 for max_size seems to work fine on all platforms
|
||||
// The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1
|
||||
// and playing with the imgui dialog which shows by pressing CTRL+M
|
||||
InputQueue() : max_size(5) {}
|
||||
// Queue of the 3DConnexion input events (translations, rotations, button presses).
|
||||
class State
|
||||
{
|
||||
public:
|
||||
struct QueueItem {
|
||||
static QueueItem translation(const Vec3d &translation) { QueueItem out; out.vector = translation; out.type_or_buttons = TranslationType; return out; }
|
||||
static QueueItem rotation(const Vec3d &rotation) { QueueItem out; out.vector = rotation; out.type_or_buttons = RotationType; return out; }
|
||||
static QueueItem buttons(unsigned int buttons) { QueueItem out; out.type_or_buttons = buttons; return out; }
|
||||
|
||||
bool is_translation() const { return this->type_or_buttons == TranslationType; }
|
||||
bool is_rotation() const { return this->type_or_buttons == RotationType; }
|
||||
bool is_buttons() const { return ! this->is_translation() && ! this->is_rotation(); }
|
||||
|
||||
Vec3d vector;
|
||||
unsigned int type_or_buttons;
|
||||
|
||||
static constexpr unsigned int TranslationType = std::numeric_limits<unsigned int>::max();
|
||||
static constexpr unsigned int RotationType = TranslationType - 1;
|
||||
};
|
||||
|
||||
InputQueue<Vec3d> m_translation;
|
||||
InputQueue<Vec3f> m_rotation;
|
||||
std::queue<unsigned int> m_buttons;
|
||||
|
||||
bool m_buttons_enabled;
|
||||
|
||||
CustomParameters<double> m_translation_params;
|
||||
CustomParameters<float> m_rotation_params;
|
||||
CustomParameters<double> m_zoom_params;
|
||||
private:
|
||||
// m_input_queue is accessed by the background thread and by the UI thread. Access to m_input_queue
|
||||
// is guarded with m_input_queue_mutex.
|
||||
std::deque<QueueItem> m_input_queue;
|
||||
mutable tbb::mutex m_input_queue_mutex;
|
||||
|
||||
#ifdef WIN32
|
||||
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected.
|
||||
// We want to filter these out because we are getting the data directly from the device, bypassing the driver, and those mouse wheel events interfere
|
||||
// by triggering unwanted zoom in/out of the scene
|
||||
|
@ -78,131 +91,136 @@ class Mouse3DController
|
|||
// Mouse3DController::collect_input() through the call to the append_rotation() method
|
||||
// GLCanvas3D::on_mouse_wheel() through the call to the process_mouse_wheel() method
|
||||
// GLCanvas3D::on_idle() through the call to the apply() method
|
||||
std::atomic<unsigned int> m_mouse_wheel_counter;
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
size_t m_translation_queue_max_size;
|
||||
size_t m_rotation_queue_max_size;
|
||||
size_t m_buttons_queue_max_size;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
unsigned int m_mouse_wheel_counter { 0 };
|
||||
#endif /* WIN32 */
|
||||
|
||||
public:
|
||||
State();
|
||||
|
||||
void append_translation(const Vec3d& translation);
|
||||
void append_rotation(const Vec3f& rotation);
|
||||
void append_button(unsigned int id);
|
||||
|
||||
bool has_translation() const { return !m_translation.queue.empty(); }
|
||||
bool has_rotation() const { return !m_rotation.queue.empty(); }
|
||||
bool has_button() const { return !m_buttons.empty(); }
|
||||
// Called by the background thread or by by Mouse3DHandlerMac.mm when a new event is received from 3DConnexion device.
|
||||
void append_translation(const Vec3d& translation, size_t input_queue_max_size);
|
||||
void append_rotation(const Vec3f& rotation, size_t input_queue_max_size);
|
||||
void append_button(unsigned int id, size_t input_queue_max_size);
|
||||
|
||||
#ifdef WIN32
|
||||
// Called by GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
|
||||
// to filter out spurious mouse scroll events produced by the 3DConnexion driver on Windows.
|
||||
bool process_mouse_wheel();
|
||||
|
||||
double get_translation_scale() const { return m_translation_params.scale; }
|
||||
void set_translation_scale(double scale) { m_translation_params.scale = scale; }
|
||||
|
||||
float get_rotation_scale() const { return m_rotation_params.scale; }
|
||||
void set_rotation_scale(float scale) { m_rotation_params.scale = scale; }
|
||||
|
||||
double get_zoom_scale() const { return m_zoom_params.scale; }
|
||||
void set_zoom_scale(double scale) { m_zoom_params.scale = scale; }
|
||||
|
||||
double get_translation_deadzone() const { return m_translation_params.deadzone; }
|
||||
void set_translation_deadzone(double deadzone) { m_translation_params.deadzone = deadzone; }
|
||||
|
||||
float get_rotation_deadzone() const { return m_rotation_params.deadzone; }
|
||||
void set_rotation_deadzone(float deadzone) { m_rotation_params.deadzone = deadzone; }
|
||||
#endif /* WIN32 */
|
||||
|
||||
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
Vec3d get_translation() const { return has_translation() ? m_translation.queue.front() : Vec3d::Zero(); }
|
||||
Vec3f get_rotation() const { return has_rotation() ? m_rotation.queue.front() : Vec3f::Zero(); }
|
||||
unsigned int get_button() const { return has_button() ? m_buttons.front() : 0; }
|
||||
|
||||
unsigned int get_translation_queue_size() const { return (unsigned int)m_translation.queue.size(); }
|
||||
unsigned int get_rotation_queue_size() const { return (unsigned int)m_rotation.queue.size(); }
|
||||
unsigned int get_buttons_queue_size() const { return (unsigned int)m_buttons.size(); }
|
||||
|
||||
size_t get_translation_queue_max_size() const { return m_translation_queue_max_size; }
|
||||
size_t get_rotation_queue_max_size() const { return m_rotation_queue_max_size; }
|
||||
size_t get_buttons_queue_max_size() const { return m_buttons_queue_max_size; }
|
||||
Vec3d get_first_vector_of_type(unsigned int type) const {
|
||||
tbb::mutex::scoped_lock lock(m_input_queue_mutex);
|
||||
auto it = std::find_if(m_input_queue.begin(), m_input_queue.end(), [type](const QueueItem& item) { return item.type_or_buttons == type; });
|
||||
return (it == m_input_queue.end()) ? Vec3d::Zero() : it->vector;
|
||||
}
|
||||
size_t input_queue_size_current() const {
|
||||
tbb::mutex::scoped_lock lock(m_input_queue_mutex);
|
||||
return m_input_queue.size();
|
||||
}
|
||||
std::atomic<size_t> input_queue_max_size_achieved;
|
||||
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
|
||||
|
||||
size_t get_queues_max_size() const { return m_translation.max_size; }
|
||||
void set_queues_max_size(size_t size);
|
||||
|
||||
// return true if any change to the camera took place
|
||||
bool apply(Camera& camera);
|
||||
// Apply the 3DConnexion events stored in the input queue, reset the input queue.
|
||||
// Returns true if any change to the camera took place.
|
||||
bool apply(const Params ¶ms, Camera& camera);
|
||||
};
|
||||
|
||||
bool m_initialized;
|
||||
mutable State m_state;
|
||||
std::thread m_thread;
|
||||
hid_device* m_device;
|
||||
std::string m_device_str;
|
||||
bool m_running;
|
||||
bool m_mac_mouse_connected;
|
||||
mutable bool m_show_settings_dialog;
|
||||
// set to true when ther user closes the dialog by clicking on [X] or [Close] buttons
|
||||
mutable bool m_settings_dialog_closed_by_user;
|
||||
std::chrono::time_point<std::chrono::high_resolution_clock> m_last_time;
|
||||
// Background thread works with this copy.
|
||||
Params m_params;
|
||||
// UI thread will read / write this copy.
|
||||
Params m_params_ui;
|
||||
bool m_params_ui_changed { false };
|
||||
mutable tbb::mutex m_params_ui_mutex;
|
||||
|
||||
// This is a database of parametes of all 3DConnexion devices ever connected.
|
||||
// This database is loaded from AppConfig on application start and it is stored to AppConfig on application exit.
|
||||
// We need to do that as the AppConfig is not thread safe and we need read the parameters on device connect / disconnect,
|
||||
// which is now done by a background thread.
|
||||
std::map<std::string, Params> m_params_by_device;
|
||||
|
||||
mutable State m_state;
|
||||
std::atomic<bool> m_connected;
|
||||
std::string m_device_str;
|
||||
|
||||
#if ! __APPLE__
|
||||
// Worker thread for enumerating devices, connecting, reading data from the device and closing the device.
|
||||
std::thread m_thread;
|
||||
hid_device* m_device { nullptr };
|
||||
// Using m_stop_condition_mutex to synchronize m_stop.
|
||||
bool m_stop { false };
|
||||
// Mutex and condition variable for sleeping during the detection of 3DConnexion devices by polling while allowing
|
||||
// cancellation before the end of the polling interval.
|
||||
std::mutex m_stop_condition_mutex;
|
||||
std::condition_variable m_stop_condition;
|
||||
#endif
|
||||
|
||||
// Is the ImGUI dialog shown? Accessed from UI thread only.
|
||||
mutable bool m_show_settings_dialog { false };
|
||||
// Set to true when ther user closes the dialog by clicking on [X] or [Close] buttons. Accessed from UI thread only.
|
||||
mutable bool m_settings_dialog_closed_by_user { false };
|
||||
|
||||
public:
|
||||
Mouse3DController();
|
||||
|
||||
// Load the device parameter database from appconfig. To be called on application startup.
|
||||
void load_config(const AppConfig &appconfig);
|
||||
// Store the device parameter database back to appconfig. To be called on application closeup.
|
||||
void save_config(AppConfig &appconfig) const;
|
||||
// Start the background thread to detect and connect to a HID device (Windows and Linux).
|
||||
// Connect to a 3DConnextion driver (OSX).
|
||||
// Call load_config() before init().
|
||||
void init();
|
||||
// Stop the background thread (Windows and Linux).
|
||||
// Disconnect from a 3DConnextion driver (OSX).
|
||||
// Call save_config() after shutdown().
|
||||
void shutdown();
|
||||
|
||||
bool is_device_connected() const { return m_device != nullptr || m_mac_mouse_connected; }
|
||||
bool is_running() const { return m_running || m_mac_mouse_connected; }
|
||||
bool connected() const { return m_connected; }
|
||||
|
||||
void set_mac_mouse_connected(bool b){m_mac_mouse_connected = b;};
|
||||
|
||||
#if __APPLE__
|
||||
// Interfacing with the Objective C code (MouseHandlerMac.mm)
|
||||
void connected(std::string device_name);
|
||||
void disconnected();
|
||||
typedef std::array<double, 6> DataPacketAxis;
|
||||
// Unpack a 3DConnexion packet provided by the 3DConnexion driver into m_state. Called by Mouse3DHandlerMac.mm
|
||||
static bool handle_input(const DataPacketAxis& packet, const Params ¶ms, State &state_in_out);
|
||||
#endif // __APPLE__
|
||||
|
||||
#ifdef WIN32
|
||||
// On Windows, the 3DConnexion driver sends out mouse wheel rotation events to an active application
|
||||
// if the application does not register at the driver. This is a workaround to ignore these superfluous
|
||||
// mouse wheel events.
|
||||
bool process_mouse_wheel() { return m_state.process_mouse_wheel(); }
|
||||
#endif // WIN32
|
||||
|
||||
// Apply the received 3DConnexion mouse events to the camera. Called from the UI rendering thread.
|
||||
bool apply(Camera& camera);
|
||||
|
||||
bool is_settings_dialog_shown() const { return m_show_settings_dialog; }
|
||||
void show_settings_dialog(bool show) { m_show_settings_dialog = show && is_running(); }
|
||||
void show_settings_dialog(bool show) { m_show_settings_dialog = show && this->connected(); }
|
||||
void render_settings_dialog(GLCanvas3D& canvas) const;
|
||||
|
||||
typedef std::array<double, 6> DataPacketAxis;
|
||||
void handle_input_axis(const DataPacketAxis& packet);
|
||||
#if ! __APPLE__
|
||||
private:
|
||||
bool connect_device();
|
||||
void disconnect_device();
|
||||
void start();
|
||||
void stop() { m_running = false; }
|
||||
|
||||
typedef std::array<unsigned char, 13> DataPacketRaw;
|
||||
// secondary thread methods
|
||||
void run();
|
||||
void collect_input();
|
||||
void handle_input(const DataPacketRaw& packet, const int packet_lenght);
|
||||
bool handle_packet(const DataPacketRaw& packet);
|
||||
bool handle_wireless_packet(const DataPacketRaw& packet);
|
||||
bool handle_packet_translation(const DataPacketRaw& packet);
|
||||
bool handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte);
|
||||
bool handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size);
|
||||
|
||||
#if __APPLE__
|
||||
Mouse3DHandlerMac* m_handler_mac;
|
||||
#endif//__APPLE__
|
||||
typedef std::array<unsigned char, 13> DataPacketRaw;
|
||||
|
||||
// Unpack raw 3DConnexion HID packet of a wired 3D mouse into m_state. Called by the worker thread.
|
||||
static bool handle_input(const DataPacketRaw& packet, const int packet_lenght, const Params ¶ms, State &state_in_out);
|
||||
// The following is called by handle_input() from the worker thread.
|
||||
static bool handle_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out);
|
||||
static bool handle_wireless_packet(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out);
|
||||
static bool handle_packet_translation(const DataPacketRaw& packet, const Params ¶ms, State &state_in_out);
|
||||
static bool handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params ¶ms, State &state_in_out);
|
||||
static bool handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params ¶ms, State &state_in_out);
|
||||
#endif /* __APPLE__ */
|
||||
};
|
||||
|
||||
#if __APPLE__
|
||||
class Mouse3DHandlerMac{
|
||||
public:
|
||||
Mouse3DHandlerMac(Mouse3DController* controller);
|
||||
~Mouse3DHandlerMac();
|
||||
|
||||
bool available();
|
||||
};
|
||||
#endif//__APPLE__
|
||||
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
||||
|
||||
#endif // slic3r_Mouse3DController_hpp_
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue