diff --git a/src/libslic3r/ExtrusionEntityCollection.hpp b/src/libslic3r/ExtrusionEntityCollection.hpp index 8aacb4d46..dfece6949 100644 --- a/src/libslic3r/ExtrusionEntityCollection.hpp +++ b/src/libslic3r/ExtrusionEntityCollection.hpp @@ -40,7 +40,7 @@ public: ~ExtrusionEntityCollection() { clear(); } explicit operator ExtrusionPaths() const; - bool is_collection() const { return true; } + bool is_collection() const override { return true; } ExtrusionRole role() const override { ExtrusionRole out = erNone; for (const ExtrusionEntity *ee : entities) { @@ -49,7 +49,7 @@ public: } return out; } - bool can_reverse() const { return !this->no_sort; } + bool can_reverse() const override { return !this->no_sort; } bool empty() const { return this->entities.empty(); } void clear(); void swap (ExtrusionEntityCollection &c); @@ -83,9 +83,9 @@ public: static ExtrusionEntityCollection chained_path_from(const ExtrusionEntitiesPtr &extrusion_entities, const Point &start_near, ExtrusionRole role = erMixed); ExtrusionEntityCollection chained_path_from(const Point &start_near, ExtrusionRole role = erMixed) const { return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); } - void reverse(); - const Point& first_point() const { return this->entities.front()->first_point(); } - const Point& last_point() const { return this->entities.back()->last_point(); } + void reverse() override; + const Point& first_point() const override { return this->entities.front()->first_point(); } + const Point& last_point() const override { return this->entities.back()->last_point(); } // Produce a list of 2D polygons covered by the extruded paths, offsetted by the extrusion width. // Increase the offset by scaled_epsilon to achieve an overlap, so a union will produce no gaps. void polygons_covered_by_width(Polygons &out, const float scaled_epsilon) const override; @@ -102,11 +102,11 @@ public: /// You should be iterating over flatten().entities if you are interested in the underlying ExtrusionEntities (and don't care about hierarchy). /// \param preserve_ordering Flag to method that will flatten if and only if the underlying collection is sortable when True (default: False). ExtrusionEntityCollection flatten(bool preserve_ordering = false) const; - double min_mm3_per_mm() const; + double min_mm3_per_mm() const override; double total_volume() const override { double volume=0.; for (const auto& ent : entities) volume+=ent->total_volume(); return volume; } // Following methods shall never be called on an ExtrusionEntityCollection. - Polyline as_polyline() const { + Polyline as_polyline() const override { throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection"); return Polyline(); }; diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index de0460f1b..2252270d4 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -30,14 +30,6 @@ public: // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. typedef boost::container::small_vector ExtruderPerCopy; - class ExtruderOverrides - { - public: - ExtruderOverrides(const ExtruderPerCopy *overrides, const int correct_extruder_id) : m_overrides(overrides) {} - private: - const ExtruderPerCopy *m_overrides; - }; - // This is called from GCode::process_layer - see implementation for further comments: const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies); diff --git a/src/libslic3r/Polygon.hpp b/src/libslic3r/Polygon.hpp index 8230b49f8..0f8457ebd 100644 --- a/src/libslic3r/Polygon.hpp +++ b/src/libslic3r/Polygon.hpp @@ -39,7 +39,7 @@ public: // last point == first point for polygons const Point& last_point() const override { return this->points.front(); } - virtual Lines lines() const; + Lines lines() const override; Polyline split_at_vertex(const Point &point) const; // Split a closed polygon into an open polyline, with the split point duplicated at both ends. Polyline split_at_index(int index) const; diff --git a/src/libslic3r/Polyline.hpp b/src/libslic3r/Polyline.hpp index 7e3a1e506..2a53a13f9 100644 --- a/src/libslic3r/Polyline.hpp +++ b/src/libslic3r/Polyline.hpp @@ -65,7 +65,7 @@ public: const Point& last_point() const override { return this->points.back(); } const Point& leftmost_point() const; - virtual Lines lines() const; + Lines lines() const override; void clip_end(double distance); void clip_start(double distance); void extend_end(double distance); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index a23922426..1ff6e097b 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -349,7 +349,7 @@ public: Print() = default; virtual ~Print() { this->clear(); } - PrinterTechnology technology() const noexcept { return ptFFF; } + PrinterTechnology technology() const noexcept override { return ptFFF; } // Methods, which change the state of Print / PrintObject / PrintRegion. // The following methods are synchronized with process() and export_gcode(), diff --git a/src/slic3r/GUI/AppConfig.cpp b/src/slic3r/GUI/AppConfig.cpp index 35799e046..212d7bcf2 100644 --- a/src/slic3r/GUI/AppConfig.cpp +++ b/src/slic3r/GUI/AppConfig.cpp @@ -295,79 +295,15 @@ void AppConfig::set_mouse_device(const std::string& name, double translation_spe it->second["zoom_speed"] = std::to_string(zoom_speed); } -bool AppConfig::get_mouse_device_translation_speed(const std::string& name, double& speed) +std::vector AppConfig::get_mouse_device_names() const { - std::string key = std::string("mouse_device:") + name; - auto it = m_storage.find(key); - if (it == m_storage.end()) - return false; - - auto it_val = it->second.find("translation_speed"); - if (it_val == it->second.end()) - return false; - - speed = ::atof(it_val->second.c_str()); - return true; -} - -bool AppConfig::get_mouse_device_translation_deadzone(const std::string& name, double& deadzone) -{ - std::string key = std::string("mouse_device:") + name; - auto it = m_storage.find(key); - if (it == m_storage.end()) - return false; - - auto it_val = it->second.find("translation_deadzone"); - if (it_val == it->second.end()) - return false; - - deadzone = ::atof(it_val->second.c_str()); - return true; -} - -bool AppConfig::get_mouse_device_rotation_speed(const std::string& name, float& speed) -{ - std::string key = std::string("mouse_device:") + name; - auto it = m_storage.find(key); - if (it == m_storage.end()) - return false; - - auto it_val = it->second.find("rotation_speed"); - if (it_val == it->second.end()) - return false; - - speed = (float)::atof(it_val->second.c_str()); - return true; -} - -bool AppConfig::get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone) -{ - std::string key = std::string("mouse_device:") + name; - auto it = m_storage.find(key); - if (it == m_storage.end()) - return false; - - auto it_val = it->second.find("rotation_deadzone"); - if (it_val == it->second.end()) - return false; - - deadzone = (float)::atof(it_val->second.c_str()); - return true; -} - -bool AppConfig::get_mouse_device_zoom_speed(const std::string& name, double& speed) -{ - std::string key = std::string("mouse_device:") + name; - auto it = m_storage.find(key); - if (it == m_storage.end()) - return false; - - auto it_val = it->second.find("zoom_speed"); - if (it_val == it->second.end()) - return false; - - speed = (float)::atof(it_val->second.c_str()); - return true; + static constexpr char *prefix = "mouse_device:"; + static constexpr size_t prefix_len = 13; // strlen(prefix); reports error C2131: expression did not evaluate to a constant on VS2019 + std::vector out; + for (const std::pair>& key_value_pair : m_storage) + if (boost::starts_with(key_value_pair.first, "mouse_device:") && key_value_pair.first.size() > prefix_len) + out.emplace_back(key_value_pair.first.substr(prefix_len)); + return out; } void AppConfig::update_config_dir(const std::string &dir) diff --git a/src/slic3r/GUI/AppConfig.hpp b/src/slic3r/GUI/AppConfig.hpp index c49260173..58e04433b 100644 --- a/src/slic3r/GUI/AppConfig.hpp +++ b/src/slic3r/GUI/AppConfig.hpp @@ -134,16 +134,36 @@ public: void set_recent_projects(const std::vector& recent_projects); void set_mouse_device(const std::string& name, double translation_speed, double translation_deadzone, float rotation_speed, float rotation_deadzone, double zoom_speed); - bool get_mouse_device_translation_speed(const std::string& name, double& speed); - bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone); - bool get_mouse_device_rotation_speed(const std::string& name, float& speed); - bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone); - bool get_mouse_device_zoom_speed(const std::string& name, double& speed); + std::vector get_mouse_device_names() const; + bool get_mouse_device_translation_speed(const std::string& name, double& speed) const + { return get_3dmouse_device_numeric_value(name, "translation_speed", speed); } + bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone) const + { return get_3dmouse_device_numeric_value(name, "translation_deadzone", deadzone); } + bool get_mouse_device_rotation_speed(const std::string& name, float& speed) const + { return get_3dmouse_device_numeric_value(name, "rotation_speed", speed); } + bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone) const + { return get_3dmouse_device_numeric_value(name, "rotation_deadzone", deadzone); } + bool get_mouse_device_zoom_speed(const std::string& name, double& speed) const + { return get_3dmouse_device_numeric_value(name, "zoom_speed", speed); } static const std::string SECTION_FILAMENTS; static const std::string SECTION_MATERIALS; private: + template + bool get_3dmouse_device_numeric_value(const std::string &device_name, const char *parameter_name, T &out) const + { + std::string key = std::string("mouse_device:") + device_name; + auto it = m_storage.find(key); + if (it == m_storage.end()) + return false; + auto it_val = it->second.find(parameter_name); + if (it_val == it->second.end()) + return false; + out = T(::atof(it_val->second.c_str())); + return true; + } + // Map of section, name -> value std::map> m_storage; // Map of enabled vendors / models / variants diff --git a/src/slic3r/GUI/Field.hpp b/src/slic3r/GUI/Field.hpp index ca0c77d5c..1f8b642b1 100644 --- a/src/slic3r/GUI/Field.hpp +++ b/src/slic3r/GUI/Field.hpp @@ -284,7 +284,7 @@ public: TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} ~TextCtrl() {} - void BUILD(); + void BUILD() override; bool value_was_changed(); // Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER void propagate_value(); @@ -303,9 +303,9 @@ public: void msw_rescale(bool rescale_sidetext = false) override; - virtual void enable(); - virtual void disable(); - virtual wxWindow* getWindow() { return window; } + void enable() override; + void disable() override; + wxWindow* getWindow() override { return window; } }; class CheckBox : public Field { diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a6eee73e8..540f82b4c 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -3082,10 +3082,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt) void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) { - // try to filter out events coming from mouse 3d - Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); - if (controller.process_mouse_wheel()) +#ifdef WIN32 + // Try to filter out spurious mouse wheel events comming from 3D mouse. + if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel()) return; +#endif if (!m_initialized) return; @@ -3465,7 +3466,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt) if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined()) { const Vec3d rot = (Vec3d(pos.x(), pos.y(), 0.) - m_mouse.drag.start_position_3D) * (PI * TRACKBALLSIZE / 180.); - if (wxGetApp().plater()->get_mouse3d_controller().is_running() || (wxGetApp().app_config->get("use_free_camera") == "1")) + if (wxGetApp().plater()->get_mouse3d_controller().connected() || (wxGetApp().app_config->get("use_free_camera") == "1")) // Virtual track ball (similar to the 3DConnexion mouse). m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.)); else diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index a4ae3e008..2bbdaaffc 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -109,7 +109,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S return; } - if(m_plater) m_plater->stop_jobs(); + if (m_plater) + m_plater->stop_jobs(); // Weird things happen as the Paint messages are floating around the windows being destructed. // Avoid the Paint messages by hiding the main window. @@ -117,6 +118,12 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S // In addition, there were some crashes due to the Paint events sent to already destructed windows. this->Show(false); + // Stop the background thread (Windows and Linux). + // Disconnect from a 3DConnextion driver (OSX). + m_plater->get_mouse3d_controller().shutdown(); + // Store the device parameter database back to appconfig. + m_plater->get_mouse3d_controller().save_config(*wxGetApp().app_config); + // Save the slic3r.ini.Usually the ini file is saved from "on idle" callback, // but in rare cases it may not have been called yet. wxGetApp().app_config->save(); diff --git a/src/slic3r/GUI/Mouse3DController.cpp b/src/slic3r/GUI/Mouse3DController.cpp index c92557417..6781ba9ff 100644 --- a/src/slic3r/GUI/Mouse3DController.cpp +++ b/src/slic3r/GUI/Mouse3DController.cpp @@ -53,205 +53,163 @@ static const std::vector _3DCONNEXION_DEVICES = namespace Slic3r { namespace GUI { - -const double Mouse3DController::State::DefaultTranslationScale = 2.5; -const double Mouse3DController::State::MaxTranslationDeadzone = 0.2; -const double Mouse3DController::State::DefaultTranslationDeadzone = 0.5 * Mouse3DController::State::MaxTranslationDeadzone; -const float Mouse3DController::State::DefaultRotationScale = 1.0f; -const float Mouse3DController::State::MaxRotationDeadzone = 0.2f; -const float Mouse3DController::State::DefaultRotationDeadzone = 0.5f * Mouse3DController::State::MaxRotationDeadzone; -const double Mouse3DController::State::DefaultZoomScale = 0.1; -Mouse3DController::State::State() - : m_buttons_enabled(false) - , m_translation_params(DefaultTranslationScale, DefaultTranslationDeadzone) - , m_rotation_params(DefaultRotationScale, DefaultRotationDeadzone) - , m_zoom_params(DefaultZoomScale, 0.0) - , m_mouse_wheel_counter(0) #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - , m_translation_queue_max_size(0) - , m_rotation_queue_max_size(0) - , m_buttons_queue_max_size(0) -#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT +template +void update_maximum(std::atomic& maximum_value, T const& value) noexcept { + T prev_value = maximum_value; + while (prev_value < value && ! maximum_value.compare_exchange_weak(prev_value, value)) ; } +#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT -void Mouse3DController::State::append_translation(const Vec3d& translation) +void Mouse3DController::State::append_translation(const Vec3d& translation, size_t input_queue_max_size) { - while (m_translation.queue.size() >= m_translation.max_size) - { - m_translation.queue.pop(); - } - m_translation.queue.push(translation); + 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)); #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_translation_queue_max_size = std::max(m_translation_queue_max_size, m_translation.queue.size()); + update_maximum(input_queue_max_size_achieved, m_input_queue.size()); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT } -void Mouse3DController::State::append_rotation(const Vec3f& rotation) +void Mouse3DController::State::append_rotation(const Vec3f& rotation, size_t input_queue_max_size) { - while (m_rotation.queue.size() >= m_rotation.max_size) - { - m_rotation.queue.pop(); - } - m_rotation.queue.push(rotation); + 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())); +#ifdef WIN32 + if (rotation.x() != 0.0f) + ++ m_mouse_wheel_counter; +#endif // WIN32 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size()); -#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - if (rotation(0) != 0.0f) - ++m_mouse_wheel_counter; -} - -void Mouse3DController::State::append_button(unsigned int id) -{ - if (!m_buttons_enabled) - return; - - m_buttons.push(id); -#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_buttons_queue_max_size = std::max(m_buttons_queue_max_size, m_buttons.size()); + update_maximum(input_queue_max_size_achieved, m_input_queue.size()); #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT } +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 +// Filter out mouse scroll events produced by the 3DConnexion driver. bool Mouse3DController::State::process_mouse_wheel() { - if (m_mouse_wheel_counter.load() == 0) + 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. return false; - else if (!m_rotation.queue.empty()) - { - --m_mouse_wheel_counter; + 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; return true; } - - m_mouse_wheel_counter.store(0); + m_mouse_wheel_counter = 0; return true; } +#endif // WIN32 -void Mouse3DController::State::set_queues_max_size(size_t size) +bool Mouse3DController::State::apply(const Mouse3DController::Params ¶ms, Camera& camera) { - if (size > 0) - { - m_translation.max_size = size; - m_rotation.max_size = size; - -#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - m_translation_queue_max_size = 0; - m_rotation_queue_max_size = 0; - m_buttons_queue_max_size = 0; -#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - } -} - -bool Mouse3DController::State::apply(Camera& camera) -{ - if (!wxGetApp().IsActive()) + if (! wxGetApp().IsActive()) return false; - bool ret = false; - - if (has_translation()) + std::deque input_queue; { - const Vec3d& translation = m_translation.queue.front(); - double zoom_factor = camera.min_zoom() / camera.get_zoom(); - camera.set_target(camera.get_target() + zoom_factor * m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(2) * camera.get_dir_up())); - if (translation(1) != 0.0) - camera.update_zoom(m_zoom_params.scale * translation(1) / std::abs(translation(1))); - m_translation.queue.pop(); - ret = true; + // 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(); } - if (has_rotation()) - { - Vec3d rot = (m_rotation_params.scale * m_rotation.queue.front()).cast() * (PI / 180.); - camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y())); - m_rotation.queue.pop(); - ret = true; + for (const QueueItem &input_queue_item : input_queue) { + if (input_queue_item.is_translation()) { + const Vec3d& translation = input_queue_item.vector; + double zoom_factor = camera.min_zoom() / camera.get_zoom(); + camera.set_target(camera.get_target() + zoom_factor * params.translation.scale * (translation.x() * camera.get_dir_right() + translation.z() * camera.get_dir_up())); + if (translation.y() != 0.0) + camera.update_zoom(params.zoom.scale * translation.y() / std::abs(translation.y())); + } else if (input_queue_item.is_rotation()) { + Vec3d rot = params.rotation.scale * input_queue_item.vector * (PI / 180.); + camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y())); + 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; + } + } } - if (m_buttons_enabled && has_button()) - { - unsigned int button = m_buttons.front(); - switch (button) - { - case 0: { camera.update_zoom(1.0); break; } - case 1: { camera.update_zoom(-1.0); break; } - default: { break; } - } - m_buttons.pop(); - ret = true; - } - - return ret; + return ! input_queue.empty(); } -Mouse3DController::Mouse3DController() - : m_initialized(false) - , m_device(nullptr) - , m_device_str("") - , m_running(false) - , m_show_settings_dialog(false) - , m_mac_mouse_connected(false) - , m_settings_dialog_closed_by_user(false) -#if __APPLE__ - ,m_handler_mac(new Mouse3DHandlerMac(this)) -#endif //__APPLE__ +// Load the device parameter database from appconfig. To be called on application startup. +void Mouse3DController::load_config(const AppConfig &appconfig) { - m_last_time = std::chrono::high_resolution_clock::now(); + // 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; + appconfig.get_mouse_device_translation_speed(device_name, translation_speed); + 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); + // clamp to valid values + 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); + m_params_by_device[device_name] = std::move(params); + } } -void Mouse3DController::init() +// Store the device parameter database back to appconfig. To be called on application closeup. +void Mouse3DController::save_config(AppConfig &appconfig) const { - if (m_initialized) - return; - - // Initialize the hidapi library - int res = hid_init(); - if (res != 0) - { - BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library"; - return; - } - - m_initialized = true; -} - -void Mouse3DController::shutdown() -{ - if (!m_initialized) - return; - - stop(); - disconnect_device(); - - // Finalize the hidapi library - hid_exit(); - m_initialized = false; + // 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 &key_value_pair : m_params_by_device) { + const std::string &device_name = key_value_pair.first; + const Params ¶ms = key_value_pair.second; + // Store current device parameters into the config + appconfig.set_mouse_device(device_name, params.translation.scale / Params::DefaultTranslationScale, params.translation.deadzone, + params.rotation.scale / Params::DefaultRotationScale, params.rotation.deadzone, params.zoom.scale / Params::DefaultZoomScale); + } } bool Mouse3DController::apply(Camera& camera) { - if (!m_initialized) - return false; - // check if the user unplugged the device - if (!is_running() && is_device_connected()) - { - disconnect_device(); + if (! m_connected) { // hides the settings dialog if the user un-plug the device m_show_settings_dialog = false; m_settings_dialog_closed_by_user = false; } - - // check if the user plugged the device - if (connect_device()) - start(); - - return is_device_connected() ? m_state.apply(camera) : false; + return m_state.apply(m_params, camera); } void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const { - if (!is_running() || !m_show_settings_dialog) + if (! m_show_settings_dialog || ! m_connected) return; // when the user clicks on [X] or [Close] button we need to trigger @@ -264,6 +222,13 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const return; } + Params params_copy; + bool params_changed = false; + { + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + params_copy = m_params_ui; + } + Size cnv_size = canvas.get_canvas_size(); ImGuiWrapper& imgui = *wxGetApp().imgui(); @@ -296,30 +261,40 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const imgui.text(_(L("Speed:"))); ImGui::PopStyleColor(); - float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale; - if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) - m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale); + 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; + } - float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale; - if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) - m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale); + 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; + } - float zoom_scale = m_state.get_zoom_scale() / State::DefaultZoomScale; - if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) - m_state.set_zoom_scale(State::DefaultZoomScale * zoom_scale); + 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; + } ImGui::Separator(); ImGui::PushStyleColor(ImGuiCol_Text, color); imgui.text(_(L("Deadzone:"))); ImGui::PopStyleColor(); - float translation_deadzone = (float)m_state.get_translation_deadzone(); - if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f")) - m_state.set_translation_deadzone((double)translation_deadzone); + 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; + } - float rotation_deadzone = m_state.get_rotation_deadzone(); - if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f")) - m_state.set_rotation_deadzone(rotation_deadzone); + 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; + } #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT ImGui::Separator(); @@ -328,8 +303,8 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const imgui.text("DEBUG:"); imgui.text("Vectors:"); ImGui::PopStyleColor(); - Vec3f translation = m_state.get_translation().cast(); - Vec3f rotation = m_state.get_rotation(); + Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast(); + Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast(); ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Rotation##3", rotation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); @@ -337,19 +312,16 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const imgui.text("Queue size:"); ImGui::PopStyleColor(); - int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() }; - int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() }; - int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() }; + 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); - ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly); - ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly); - ImGui::InputInt2("Buttons", buttons_size, ImGuiInputTextFlags_ReadOnly); - - int queue_size = (int)m_state.get_queues_max_size(); - if (ImGui::InputInt("Max size", &queue_size, 1, 1, ImGuiInputTextFlags_ReadOnly)) + 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)) { - if (queue_size > 0) - m_state.set_queues_max_size(queue_size); + if (input_queue_size_param > 0) { + params_copy.input_queue_max_size = input_queue_size_param; + params_changed = true; + } } ImGui::Separator(); @@ -377,23 +349,165 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const } imgui.end(); + + if (params_changed) { + // Synchronize front end parameters to back end. + tbb::mutex::scoped_lock lock(m_params_ui_mutex); + auto pthis = const_cast(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) +{ + 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. + assert(! m_device_str.empty()); + if (! m_device_str.empty()) { + 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; +} + +bool Mouse3DController::handle_input(const DataPacketAxis& packet) +{ + if (! wxGetApp().IsActive()) + return false; + + { + // 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; + // translation + double deadzone = m_params.translation.deadzone; + 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); + if (! translation.isApprox(Vec3d::Zero())) { + m_state.append_translation(translation, m_params.input_queue_max_size); + updated = true; + } + // rotation + deadzone = m_params.rotation.deadzone; + 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); + if (! rotation.isApprox(Vec3f::Zero())) { + m_state.append_rotation(rotation, m_params.input_queue_max_size); + 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. + std::unique_lock lock(m_stop_condition_mutex); + m_stop = true; + m_stop_condition.notify_all(); + } + // Wait for the worker thread to stop. + m_thread.join(); + } +} + +// Main routine of the worker thread. +void Mouse3DController::run() +{ + // Initialize the hidapi library + int res = hid_init(); + if (res != 0) { + // Give up. + BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library"; + return; + } + + 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(); } bool Mouse3DController::connect_device() { -#ifdef __APPLE__ - return false; -#endif//__APPLE__ - static const long long DETECTION_TIME_MS = 2000; // two seconds + if (m_stop) + return false; - if (is_device_connected()) - return false; + { + // Wait for 2 seconds, but cancellable by m_stop. + std::unique_lock lock(m_stop_condition_mutex); + m_stop_condition.wait_for(lock, std::chrono::seconds(2), [this]{ return this->m_stop; }); + } - // check time since last detection took place - if (std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS) - return false; - - m_last_time = std::chrono::high_resolution_clock::now(); + if (m_stop) + return false; // Enumerates devices hid_device_info* devices = hid_enumerate(0, 0); @@ -623,23 +737,11 @@ bool Mouse3DController::connect_device() #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT std::cout << "Opened device." << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - // get device parameters from the config, if present - double translation_speed = 4.0; - float rotation_speed = 4.0; - double translation_deadzone = State::DefaultTranslationDeadzone; - float rotation_deadzone = State::DefaultRotationDeadzone; - double zoom_speed = 2.0; - wxGetApp().app_config->get_mouse_device_translation_speed(m_device_str, translation_speed); - wxGetApp().app_config->get_mouse_device_translation_deadzone(m_device_str, translation_deadzone); - wxGetApp().app_config->get_mouse_device_rotation_speed(m_device_str, rotation_speed); - wxGetApp().app_config->get_mouse_device_rotation_deadzone(m_device_str, rotation_deadzone); - wxGetApp().app_config->get_mouse_device_zoom_speed(m_device_str, zoom_speed); - // clamp to valid values - m_state.set_translation_scale(State::DefaultTranslationScale * std::clamp(translation_speed, 0.1, 10.0)); - m_state.set_translation_deadzone(std::clamp(translation_deadzone, 0.0, State::MaxTranslationDeadzone)); - m_state.set_rotation_scale(State::DefaultRotationScale * std::clamp(rotation_speed, 0.1f, 10.0f)); - m_state.set_rotation_deadzone(std::clamp(rotation_deadzone, 0.0f, State::MaxRotationDeadzone)); - m_state.set_zoom_scale(State::DefaultZoomScale * std::clamp(zoom_speed, 0.1, 10.0)); + // 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; + } } #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT else @@ -657,138 +759,85 @@ bool Mouse3DController::connect_device() void Mouse3DController::disconnect_device() { - if (!is_device_connected()) - return; - - // Stop the secondary thread, if running - if (m_thread.joinable()) - m_thread.join(); - - // Store current device parameters into the config - wxGetApp().app_config->set_mouse_device(m_device_str, m_state.get_translation_scale() / State::DefaultTranslationScale, m_state.get_translation_deadzone(), - m_state.get_rotation_scale() / State::DefaultRotationScale, m_state.get_rotation_deadzone(), m_state.get_zoom_scale() / State::DefaultZoomScale); - - wxGetApp().app_config->save(); - - // Close the 3Dconnexion device - hid_close(m_device); - m_device = nullptr; - - BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str; - - m_device_str = ""; + 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; + } } -void Mouse3DController::start() -{ - if (!is_device_connected() || m_running) - return; - - m_thread = std::thread(&Mouse3DController::run, this); -} - -void Mouse3DController::run() -{ - m_running = true; - while (m_running) - { - collect_input(); - } -} void Mouse3DController::collect_input() { DataPacketRaw packet = { 0 }; + // Read packet, block maximum 100 ms. That means when closing the application, closing the application will be delayed by 100 ms. int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100); - if (res < 0) - { - // An error occourred (device detached from pc ?) - stop(); - return; - } - handle_input(packet, res); + 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); } - -void Mouse3DController::handle_input_axis(const DataPacketAxis& packet) + +// 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 ¶ms, State &state_in_out) { - if (!wxGetApp().IsActive()) - return; - bool appended = false; - //translation - double deadzone = m_state.get_translation_deadzone(); - 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); - if (!translation.isApprox(Vec3d::Zero())) - { - m_state.append_translation(translation); - appended = true; - } - //rotation - deadzone = m_state.get_rotation_deadzone(); - 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); - if (!rotation.isApprox(Vec3f::Zero())) - { - m_state.append_rotation(rotation); - appended = true; - } - if (appended) - { - wxGetApp().plater()->set_current_canvas_as_dirty(); - // ask for an idle event to update 3D scene - wxWakeUpIdle(); - } -} -void Mouse3DController::handle_input(const DataPacketRaw& packet, const int packet_lenght) -{ - if (!wxGetApp().IsActive()) - return; + if (! wxGetApp().IsActive()) + return false; int res = packet_lenght; bool updated = false; if (res == 7) - updated = handle_packet(packet); + updated = handle_packet(packet, params, state_in_out); else if (res == 13) - updated = handle_wireless_packet(packet); + updated = handle_wireless_packet(packet, params, state_in_out); else if ((res == 3) && (packet[0] == 3)) // On Mac button packets can be 3 bytes long - updated = handle_packet(packet); + updated = handle_packet(packet, params, state_in_out); #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT else if (res > 0) std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - if (updated) - { +#if 1 + if (updated) { wxGetApp().plater()->set_current_canvas_as_dirty(); // ask for an idle event to update 3D scene wxWakeUpIdle(); } +#endif + return updated; } -bool Mouse3DController::handle_packet(const DataPacketRaw& packet) +// 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 ¶ms, State &state_in_out) { switch (packet[0]) { case 1: // Translation { - if (handle_packet_translation(packet)) + if (handle_packet_translation(packet, params, state_in_out)) return true; break; } case 2: // Rotation { - if (handle_packet_rotation(packet, 1)) + if (handle_packet_rotation(packet, 1, params, state_in_out)) return true; break; } case 3: // Button { - if (handle_packet_button(packet, packet.size() - 1)) + if (params.buttons_enabled && handle_packet_button(packet, packet.size() - 1, params, state_in_out)) return true; break; @@ -796,14 +845,14 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet) case 23: // Battery charge { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl; + std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } default: { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl; + std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } @@ -812,14 +861,15 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet) return false; } -bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) +// 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 ¶ms, State &state_in_out) { switch (packet[0]) { case 1: // Translation + Rotation { - bool updated = handle_packet_translation(packet); - updated |= handle_packet_rotation(packet, 7); + bool updated = handle_packet_translation(packet, params, state_in_out); + updated |= handle_packet_rotation(packet, 7, params, state_in_out); if (updated) return true; @@ -828,7 +878,7 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) } case 3: // Button { - if (handle_packet_button(packet, 12)) + if (params.buttons_enabled && handle_packet_button(packet, 12, params, state_in_out)) return true; break; @@ -836,14 +886,14 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) case 23: // Battery charge { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << m_device_str << " - battery level: " << (int)packet[1] << " percent" << std::endl; + std::cout << "3DConnexion - battery level: " << (int)packet[1] << " percent" << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } default: { #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout << "Got unknown data packet of code: " << (int)packet[0] << std::endl; + std::cout << "3DConnexion - Got unknown data packet of code: " << (int)packet[0] << std::endl; #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT break; } @@ -852,46 +902,52 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet) return false; } -double convert_input(unsigned char first, unsigned char second, double deadzone) +// 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) { - short value = first | second << 8; + int value = coord_byte_low | (coord_byte_high << 8); + if (value >= 32768) + value = value - 65536; double ret = (double)value / 350.0; return (std::abs(ret) > deadzone) ? ret : 0.0; } -bool Mouse3DController::handle_packet_translation(const DataPacketRaw& packet) +// 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 ¶ms, State &state_in_out) { - double deadzone = m_state.get_translation_deadzone(); + double deadzone = params.translation.deadzone; 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())) { - m_state.append_translation(translation); + state_in_out.append_translation(translation, params.input_queue_max_size); return true; } return false; } -bool Mouse3DController::handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte) +// 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 ¶ms, State &state_in_out) { - double deadzone = (double)m_state.get_rotation_deadzone(); + double deadzone = (double)params.rotation.deadzone; 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)); if (!rotation.isApprox(Vec3f::Zero())) { - m_state.append_rotation(rotation); + state_in_out.append_rotation(rotation, params.input_queue_max_size); return true; } return false; } -bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size) +// 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 ¶ms, State &state_in_out) { unsigned int data = 0; for (unsigned int i = 1; i < packet_size; ++i) @@ -904,7 +960,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign { if (data_bits.test(i)) { - m_state.append_button((unsigned int)i); + state_in_out.append_button((unsigned int)i, params.input_queue_max_size); return true; } } @@ -912,5 +968,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign return false; } +#endif //__APPLE__ + } // namespace GUI } // namespace Slic3r diff --git a/src/slic3r/GUI/Mouse3DController.hpp b/src/slic3r/GUI/Mouse3DController.hpp index f98745124..99c5511a5 100644 --- a/src/slic3r/GUI/Mouse3DController.hpp +++ b/src/slic3r/GUI/Mouse3DController.hpp @@ -10,67 +10,80 @@ #include #include -#include #include #include - +#include 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 struct CustomParameters { Number scale; Number deadzone; - - CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {} }; - template - struct InputQueue - { - size_t max_size; - std::queue queue; + CustomParameters translation { DefaultTranslationScale, DefaultTranslationDeadzone }; + CustomParameters rotation { DefaultRotationScale, DefaultRotationDeadzone }; + CustomParameters 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::max(); + static constexpr unsigned int RotationType = TranslationType - 1; }; - InputQueue m_translation; - InputQueue m_rotation; - std::queue m_buttons; - - bool m_buttons_enabled; - - CustomParameters m_translation_params; - CustomParameters m_rotation_params; - CustomParameters 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 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 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 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 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 m_params_by_device; + + mutable State m_state; + std::atomic 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 DataPacketAxis; + // Unpack a 3DConnexion packet provided by the 3DConnexion driver into m_state. Called by Mouse3DHandlerMac.mm + bool handle_input(const DataPacketAxis& packet); +#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 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 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 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_ - diff --git a/src/slic3r/GUI/Mouse3DHandlerMac.mm b/src/slic3r/GUI/Mouse3DHandlerMac.mm index 60006b8c1..d40948cd1 100644 --- a/src/slic3r/GUI/Mouse3DHandlerMac.mm +++ b/src/slic3r/GUI/Mouse3DHandlerMac.mm @@ -1,4 +1,3 @@ - #include "Mouse3DController.hpp" #include @@ -10,7 +9,7 @@ #include -static Slic3r::GUI::Mouse3DController* mouse_3d_controller = NULL; +static Slic3r::GUI::Mouse3DController* mouse_3d_controller = nullptr; static uint16_t clientID = 0; @@ -65,7 +64,7 @@ typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID, int32_t param, int32_t *result); -#define DECLARE_FUNC(name) name##_ptr name = NULL +#define DECLARE_FUNC(name) name##_ptr name = nullptr DECLARE_FUNC(SetConnexionHandlers); DECLARE_FUNC(InstallConnexionHandlers); @@ -79,15 +78,12 @@ static void *load_func(void *module, const char *func_name) { void *func = dlsym(module, func_name); -//#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT if (func) { BOOST_LOG_TRIVIAL(info) << func_name <<" loaded"; } else { - //printf(" %s\n", dlerror()); BOOST_LOG_TRIVIAL(error) <<"loading 3dx drivers dlsym error: "<< dlerror(); } -//#endif return func; } @@ -98,9 +94,8 @@ static void *module; // handle to the whole driver static bool load_driver_functions() { - if (driver_loaded) { + if (driver_loaded) return true; - } module = dlopen("/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient", RTLD_LAZY | RTLD_LOCAL); @@ -109,15 +104,14 @@ static bool load_driver_functions() BOOST_LOG_TRIVIAL(info) << "loading 3dx drivers"; LOAD_FUNC(SetConnexionHandlers); - if (SetConnexionHandlers != NULL) { + if (SetConnexionHandlers != nullptr) { driver_loaded = true; has_new_driver = true; } else { - BOOST_LOG_TRIVIAL(info) << "installing 3dx drivers"; + BOOST_LOG_TRIVIAL(info) << "installing 3dx drivers"; LOAD_FUNC(InstallConnexionHandlers); - - driver_loaded = (InstallConnexionHandlers != NULL); + driver_loaded = (InstallConnexionHandlers != nullptr); } if (driver_loaded) { @@ -128,17 +122,11 @@ static bool load_driver_functions() LOAD_FUNC(ConnexionClientControl); } } - else { + else { BOOST_LOG_TRIVIAL(error) << "3dx drivers module loading error: "<< dlerror() ; -#if DENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - printf(" %s\n", dlerror()); -#endif } -#if DENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - printf("loaded: %s\n", driver_loaded ? "YES" : "NO"); - printf("new: %s\n", has_new_driver ? "YES" : "NO"); -#endif - BOOST_LOG_TRIVIAL(info) << "3dx drivers loaded: "<< driver_loaded ? "YES" : "NO" ; + + BOOST_LOG_TRIVIAL(info) << "3dx drivers loaded: " << (driver_loaded ? (has_new_driver ? "YES, new" : "YES, old") : "NO"); return driver_loaded; } @@ -149,29 +137,22 @@ static void unload_driver() static void DeviceAdded(uint32_t unused) { -#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - std::cout<<"3D device added"<> 16; - int16_t productID = result & 0xffff; - + int vendorID = result >> 16; + int productID = result & 0xffff; //TODO: verify device - - - mouse_3d_controller->set_mac_mouse_connected(true); + char buf[64]; + sprintf(buf, "VID%04X,PID%04X", vendorID, productID); + mouse_3d_controller->connected(buf); } static void DeviceRemoved(uint32_t unused) { -#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT - printf("3d device removed\n"); -#endif BOOST_LOG_TRIVIAL(info) << "3dx device removed\n"; - mouse_3d_controller->set_mac_mouse_connected(true); + mouse_3d_controller->disconnected(); } static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg) @@ -181,21 +162,16 @@ static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg) if (s->client == clientID) { switch (s->command) { case kConnexionCmdHandleAxis: { - /* - The axis field is an array of 6 signed 16-bit integers corresponding to the 6 device axes. Data is ordered as Tx, Tz, Ty, Rx, Rz, Ry. The values reported are scaled by the driver according to the speed slider settings on the 3Dconnexion preference panel. At maximum speed, the range is - 1024 to 1024. Typical range that you should optimize your application for should be -500 to 500. - */ - //Actually we are getting values way over 1024. Max is probably 2048 now. - std::array packet; - for (int i = 0; i < 6; i++) { + // The axis field is an array of 6 signed 16-bit integers corresponding to the 6 device axes. Data is ordered as Tx, Tz, Ty, Rx, Rz, Ry. The values reported are scaled by the driver according to the speed slider settings on the 3Dconnexion preference panel. At maximum speed, the range is - 1024 to 1024. Typical range that you should optimize your application for should be -500 to 500. + // Actually we are getting values way over 1024. Max is probably 2048 now. + Slic3r::GUI::Mouse3DController::DataPacketAxis packet; + for (int i = 0; i < 6; ++ i) packet[i] = (double)s->axis[i]/350.0;//wanted to divide by 500 but 350 is used at raw input so i used same value. - } - mouse_3d_controller->handle_input_axis(packet); - - + mouse_3d_controller->handle_input(packet); break; } case kConnexionCmdHandleButtons: - break; + break; case kConnexionCmdAppSpecific: break; default: @@ -203,41 +179,35 @@ static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg) } } } - } namespace Slic3r { namespace GUI { -Mouse3DHandlerMac::Mouse3DHandlerMac(Mouse3DController* controller) + +// Initialize the application. +void Mouse3DController::init() { - BOOST_LOG_TRIVIAL(info) << "3dx mac handler starts"; + BOOST_LOG_TRIVIAL(info) << "3dx mac handler starts"; if (load_driver_functions()) { - mouse_3d_controller = controller; + mouse_3d_controller = this; - uint16_t error; - if (has_new_driver) { - error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, false); - } - else { - error = InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved); - } + uint16_t error = has_new_driver ? + SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, false) : + InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved); - if (error) { - return; + if (! error) { + // Registration is done either by 4letter constant (CFBundleSignature - obsolete + //and we dont have that) or Executable name in pascal string(first byte is string lenght). + //If no packets are recieved the name might be different - check cmake. If debugging try commenting + // set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") + clientID = RegisterConnexionClient( + 0, "\013PrusaSlicer", kConnexionClientModeTakeOver, kConnexionMaskAxis); + BOOST_LOG_TRIVIAL(info) << "3dx mac handler registered"; } - - // Registration is done either by 4letter constant (CFBundleSignature - obsolete - //and we dont have that) or Executable name in pascal string(first byte is string lenght). - //If no packets are recieved the name might be different - check cmake. If debugging try commenting - // set_target_properties(PrusaSlicer PROPERTIES OUTPUT_NAME "prusa-slicer") - - clientID = RegisterConnexionClient( - 0, "\013PrusaSlicer", kConnexionClientModeTakeOver, kConnexionMaskAxis); - BOOST_LOG_TRIVIAL(info) << "3dx mac handler registered"; } } -Mouse3DHandlerMac::~Mouse3DHandlerMac() +void Mouse3DController::shutdown() { if (driver_loaded) { UnregisterConnexionClient(clientID); @@ -247,9 +217,4 @@ Mouse3DHandlerMac::~Mouse3DHandlerMac() mouse_3d_controller = nullptr; } -bool Mouse3DHandlerMac::available() -{ - return driver_loaded; -} - }}//namespace Slic3r::GUI diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index d434ac2d0..2177d622c 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -2145,6 +2145,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) // updates camera type from .ini file camera.set_type(get_config("use_perspective_camera")); + // Load the 3DConnexion device database. + mouse3d_controller.load_config(*wxGetApp().app_config); + // Start the background thread to detect and connect to a HID device (Windows and Linux). + // Connect to a 3DConnextion driver (OSX). mouse3d_controller.init(); // Initialize the Undo / Redo stack with a first snapshot. @@ -2156,8 +2160,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) Plater::priv::~priv() { - mouse3d_controller.shutdown(); - if (config != nullptr) delete config; }