Merge remote-tracking branch 'remotes/origin/vb_3dconnexion_refactoring'

This commit is contained in:
bubnikv 2020-03-05 09:56:06 +01:00
commit a2b7c6945c
14 changed files with 616 additions and 617 deletions

View File

@ -40,7 +40,7 @@ public:
~ExtrusionEntityCollection() { clear(); } ~ExtrusionEntityCollection() { clear(); }
explicit operator ExtrusionPaths() const; explicit operator ExtrusionPaths() const;
bool is_collection() const { return true; } bool is_collection() const override { return true; }
ExtrusionRole role() const override { ExtrusionRole role() const override {
ExtrusionRole out = erNone; ExtrusionRole out = erNone;
for (const ExtrusionEntity *ee : entities) { for (const ExtrusionEntity *ee : entities) {
@ -49,7 +49,7 @@ public:
} }
return out; 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(); } bool empty() const { return this->entities.empty(); }
void clear(); void clear();
void swap (ExtrusionEntityCollection &c); 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); 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 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); } { return this->no_sort ? *this : chained_path_from(this->entities, start_near, role); }
void reverse(); void reverse() override;
const Point& first_point() const { return this->entities.front()->first_point(); } const Point& first_point() const override { return this->entities.front()->first_point(); }
const Point& last_point() const { return this->entities.back()->last_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. // 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. // 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; 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). /// 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). /// \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; 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; } 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. // 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"); throw std::runtime_error("Calling as_polyline() on a ExtrusionEntityCollection");
return Polyline(); return Polyline();
}; };

View File

@ -30,14 +30,6 @@ public:
// When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place. // When allocating extruder overrides of an object's ExtrusionEntity, overrides for maximum 3 copies are allocated in place.
typedef boost::container::small_vector<int32_t, 3> ExtruderPerCopy; typedef boost::container::small_vector<int32_t, 3> 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: // 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); const ExtruderPerCopy* get_extruder_overrides(const ExtrusionEntity* entity, int correct_extruder_id, size_t num_of_copies);

View File

@ -39,7 +39,7 @@ public:
// last point == first point for polygons // last point == first point for polygons
const Point& last_point() const override { return this->points.front(); } 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; Polyline split_at_vertex(const Point &point) const;
// Split a closed polygon into an open polyline, with the split point duplicated at both ends. // Split a closed polygon into an open polyline, with the split point duplicated at both ends.
Polyline split_at_index(int index) const; Polyline split_at_index(int index) const;

View File

@ -65,7 +65,7 @@ public:
const Point& last_point() const override { return this->points.back(); } const Point& last_point() const override { return this->points.back(); }
const Point& leftmost_point() const; const Point& leftmost_point() const;
virtual Lines lines() const; Lines lines() const override;
void clip_end(double distance); void clip_end(double distance);
void clip_start(double distance); void clip_start(double distance);
void extend_end(double distance); void extend_end(double distance);

View File

@ -349,7 +349,7 @@ public:
Print() = default; Print() = default;
virtual ~Print() { this->clear(); } 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. // Methods, which change the state of Print / PrintObject / PrintRegion.
// The following methods are synchronized with process() and export_gcode(), // The following methods are synchronized with process() and export_gcode(),

View File

@ -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); it->second["zoom_speed"] = std::to_string(zoom_speed);
} }
bool AppConfig::get_mouse_device_translation_speed(const std::string& name, double& speed) std::vector<std::string> AppConfig::get_mouse_device_names() const
{ {
std::string key = std::string("mouse_device:") + name; static constexpr char *prefix = "mouse_device:";
auto it = m_storage.find(key); static constexpr size_t prefix_len = 13; // strlen(prefix); reports error C2131: expression did not evaluate to a constant on VS2019
if (it == m_storage.end()) std::vector<std::string> out;
return false; for (const std::pair<std::string, std::map<std::string, std::string>>& key_value_pair : m_storage)
if (boost::starts_with(key_value_pair.first, "mouse_device:") && key_value_pair.first.size() > prefix_len)
auto it_val = it->second.find("translation_speed"); out.emplace_back(key_value_pair.first.substr(prefix_len));
if (it_val == it->second.end()) return out;
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;
} }
void AppConfig::update_config_dir(const std::string &dir) void AppConfig::update_config_dir(const std::string &dir)

View File

@ -134,16 +134,36 @@ public:
void set_recent_projects(const std::vector<std::string>& recent_projects); void set_recent_projects(const std::vector<std::string>& 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); 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); std::vector<std::string> get_mouse_device_names() const;
bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone); bool get_mouse_device_translation_speed(const std::string& name, double& speed) const
bool get_mouse_device_rotation_speed(const std::string& name, float& speed); { return get_3dmouse_device_numeric_value(name, "translation_speed", speed); }
bool get_mouse_device_rotation_deadzone(const std::string& name, float& deadzone); bool get_mouse_device_translation_deadzone(const std::string& name, double& deadzone) const
bool get_mouse_device_zoom_speed(const std::string& name, double& speed); { 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_FILAMENTS;
static const std::string SECTION_MATERIALS; static const std::string SECTION_MATERIALS;
private: private:
template<typename T>
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 // Map of section, name -> value
std::map<std::string, std::map<std::string, std::string>> m_storage; std::map<std::string, std::map<std::string, std::string>> m_storage;
// Map of enabled vendors / models / variants // Map of enabled vendors / models / variants

View File

@ -284,7 +284,7 @@ public:
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {} TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
~TextCtrl() {} ~TextCtrl() {}
void BUILD(); void BUILD() override;
bool value_was_changed(); bool value_was_changed();
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER // Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
void propagate_value(); void propagate_value();
@ -303,9 +303,9 @@ public:
void msw_rescale(bool rescale_sidetext = false) override; void msw_rescale(bool rescale_sidetext = false) override;
virtual void enable(); void enable() override;
virtual void disable(); void disable() override;
virtual wxWindow* getWindow() { return window; } wxWindow* getWindow() override { return window; }
}; };
class CheckBox : public Field { class CheckBox : public Field {

View File

@ -3082,10 +3082,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt) void GLCanvas3D::on_mouse_wheel(wxMouseEvent& evt)
{ {
// try to filter out events coming from mouse 3d #ifdef WIN32
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller(); // Try to filter out spurious mouse wheel events comming from 3D mouse.
if (controller.process_mouse_wheel()) if (wxGetApp().plater()->get_mouse3d_controller().process_mouse_wheel())
return; return;
#endif
if (!m_initialized) if (!m_initialized)
return; return;
@ -3465,7 +3466,7 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
if (m_hover_volume_idxs.empty() && m_mouse.is_start_position_3D_defined()) 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.); 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). // Virtual track ball (similar to the 3DConnexion mouse).
m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.)); m_camera.rotate_local_around_target(Vec3d(rot.y(), rot.x(), 0.));
else else

View File

@ -109,7 +109,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
return; 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. // Weird things happen as the Paint messages are floating around the windows being destructed.
// Avoid the Paint messages by hiding the main window. // 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. // In addition, there were some crashes due to the Paint events sent to already destructed windows.
this->Show(false); 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, // 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. // but in rare cases it may not have been called yet.
wxGetApp().app_config->save(); wxGetApp().app_config->save();

View File

@ -53,205 +53,163 @@ static const std::vector<int> _3DCONNEXION_DEVICES =
namespace Slic3r { namespace Slic3r {
namespace GUI { 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 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
, m_translation_queue_max_size(0) template<typename T>
, m_rotation_queue_max_size(0) void update_maximum(std::atomic<T>& maximum_value, T const& value) noexcept
, m_buttons_queue_max_size(0)
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
{ {
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) tbb::mutex::scoped_lock lock(m_input_queue_mutex);
{ while (m_input_queue.size() >= input_queue_max_size)
m_translation.queue.pop(); m_input_queue.pop_front();
} m_input_queue.emplace_back(QueueItem::translation(translation));
m_translation.queue.push(translation);
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #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 #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) tbb::mutex::scoped_lock lock(m_input_queue_mutex);
{ while (m_input_queue.size() >= input_queue_max_size)
m_rotation.queue.pop(); m_input_queue.pop_front();
} m_input_queue.emplace_back(QueueItem::rotation(rotation.cast<double>()));
m_rotation.queue.push(rotation); #ifdef WIN32
if (rotation.x() != 0.0f)
++ m_mouse_wheel_counter;
#endif // WIN32
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
m_rotation_queue_max_size = std::max(m_rotation_queue_max_size, m_rotation.queue.size()); update_maximum(input_queue_max_size_achieved, m_input_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());
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #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() 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; return false;
else if (!m_rotation.queue.empty()) 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; -- m_mouse_wheel_counter;
return true; return true;
} }
m_mouse_wheel_counter = 0;
m_mouse_wheel_counter.store(0);
return true; return true;
} }
#endif // WIN32
void Mouse3DController::State::set_queues_max_size(size_t size) bool Mouse3DController::State::apply(const Mouse3DController::Params &params, Camera& camera)
{ {
if (size > 0) if (! wxGetApp().IsActive())
{
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())
return false; return false;
bool ret = false; std::deque<QueueItem> input_queue;
if (has_translation())
{ {
const Vec3d& translation = m_translation.queue.front(); // Atomically move m_input_queue to input_queue.
double zoom_factor = camera.min_zoom() / camera.get_zoom(); tbb::mutex::scoped_lock lock(m_input_queue_mutex);
camera.set_target(camera.get_target() + zoom_factor * m_translation_params.scale * (translation(0) * camera.get_dir_right() + translation(2) * camera.get_dir_up())); input_queue = std::move(m_input_queue);
if (translation(1) != 0.0) m_input_queue.clear();
camera.update_zoom(m_zoom_params.scale * translation(1) / std::abs(translation(1)));
m_translation.queue.pop();
ret = true;
} }
if (has_rotation()) for (const QueueItem &input_queue_item : input_queue) {
{ if (input_queue_item.is_translation()) {
Vec3d rot = (m_rotation_params.scale * m_rotation.queue.front()).cast<double>() * (PI / 180.); const Vec3d& translation = input_queue_item.vector;
camera.rotate_local_around_target(Vec3d(rot.x(), - rot.z(), rot.y())); double zoom_factor = camera.min_zoom() / camera.get_zoom();
m_rotation.queue.pop(); camera.set_target(camera.get_target() + zoom_factor * params.translation.scale * (translation.x() * camera.get_dir_right() + translation.z() * camera.get_dir_up()));
ret = true; 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()) return ! input_queue.empty();
{
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;
} }
Mouse3DController::Mouse3DController() // Load the device parameter database from appconfig. To be called on application startup.
: m_initialized(false) void Mouse3DController::load_config(const AppConfig &appconfig)
, 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__
{ {
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) // We do not synchronize m_params_by_device with the background thread explicitely
return; // 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) {
// Initialize the hidapi library const std::string &device_name = key_value_pair.first;
int res = hid_init(); const Params &params = key_value_pair.second;
if (res != 0) // Store current device parameters into the config
{ appconfig.set_mouse_device(device_name, params.translation.scale / Params::DefaultTranslationScale, params.translation.deadzone,
BOOST_LOG_TRIVIAL(error) << "Unable to initialize hidapi library"; params.rotation.scale / Params::DefaultRotationScale, params.rotation.deadzone, params.zoom.scale / Params::DefaultZoomScale);
return; }
}
m_initialized = true;
}
void Mouse3DController::shutdown()
{
if (!m_initialized)
return;
stop();
disconnect_device();
// Finalize the hidapi library
hid_exit();
m_initialized = false;
} }
bool Mouse3DController::apply(Camera& camera) bool Mouse3DController::apply(Camera& camera)
{ {
if (!m_initialized)
return false;
// check if the user unplugged the device // check if the user unplugged the device
if (!is_running() && is_device_connected()) if (! m_connected) {
{
disconnect_device();
// hides the settings dialog if the user un-plug the device // hides the settings dialog if the user un-plug the device
m_show_settings_dialog = false; m_show_settings_dialog = false;
m_settings_dialog_closed_by_user = false; m_settings_dialog_closed_by_user = false;
} }
return m_state.apply(m_params, camera);
// check if the user plugged the device
if (connect_device())
start();
return is_device_connected() ? m_state.apply(camera) : false;
} }
void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
{ {
if (!is_running() || !m_show_settings_dialog) if (! m_show_settings_dialog || ! m_connected)
return; return;
// when the user clicks on [X] or [Close] button we need to trigger // 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; 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(); Size cnv_size = canvas.get_canvas_size();
ImGuiWrapper& imgui = *wxGetApp().imgui(); ImGuiWrapper& imgui = *wxGetApp().imgui();
@ -296,30 +261,40 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
imgui.text(_(L("Speed:"))); imgui.text(_(L("Speed:")));
ImGui::PopStyleColor(); ImGui::PopStyleColor();
float translation_scale = (float)m_state.get_translation_scale() / State::DefaultTranslationScale; float translation_scale = (float)params_copy.translation.scale / Params::DefaultTranslationScale;
if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) if (imgui.slider_float(_(L("Translation")) + "##1", &translation_scale, 0.1f, 10.0f, "%.1f")) {
m_state.set_translation_scale(State::DefaultTranslationScale * (double)translation_scale); params_copy.translation.scale = Params::DefaultTranslationScale * (double)translation_scale;
params_changed = true;
}
float rotation_scale = m_state.get_rotation_scale() / State::DefaultRotationScale; float rotation_scale = params_copy.rotation.scale / Params::DefaultRotationScale;
if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) if (imgui.slider_float(_(L("Rotation")) + "##1", &rotation_scale, 0.1f, 10.0f, "%.1f")) {
m_state.set_rotation_scale(State::DefaultRotationScale * rotation_scale); params_copy.rotation.scale = Params::DefaultRotationScale * rotation_scale;
params_changed = true;
}
float zoom_scale = m_state.get_zoom_scale() / State::DefaultZoomScale; float zoom_scale = params_copy.zoom.scale / Params::DefaultZoomScale;
if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) if (imgui.slider_float(_(L("Zoom")), &zoom_scale, 0.1f, 10.0f, "%.1f")) {
m_state.set_zoom_scale(State::DefaultZoomScale * zoom_scale); params_copy.zoom.scale = Params::DefaultZoomScale * zoom_scale;
params_changed = true;
}
ImGui::Separator(); ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Text, color); ImGui::PushStyleColor(ImGuiCol_Text, color);
imgui.text(_(L("Deadzone:"))); imgui.text(_(L("Deadzone:")));
ImGui::PopStyleColor(); ImGui::PopStyleColor();
float translation_deadzone = (float)m_state.get_translation_deadzone(); float translation_deadzone = (float)params_copy.translation.deadzone;
if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)State::MaxTranslationDeadzone, "%.2f")) if (imgui.slider_float(_(L("Translation")) + "/" + _(L("Zoom")), &translation_deadzone, 0.0f, (float)Params::MaxTranslationDeadzone, "%.2f")) {
m_state.set_translation_deadzone((double)translation_deadzone); params_copy.translation.deadzone = (double)translation_deadzone;
params_changed = true;
}
float rotation_deadzone = m_state.get_rotation_deadzone(); float rotation_deadzone = params_copy.rotation.deadzone;
if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, State::MaxRotationDeadzone, "%.2f")) if (imgui.slider_float(_(L("Rotation")) + "##2", &rotation_deadzone, 0.0f, Params::MaxRotationDeadzone, "%.2f")) {
m_state.set_rotation_deadzone(rotation_deadzone); params_copy.rotation.deadzone = rotation_deadzone;
params_changed = true;
}
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
ImGui::Separator(); ImGui::Separator();
@ -328,8 +303,8 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
imgui.text("DEBUG:"); imgui.text("DEBUG:");
imgui.text("Vectors:"); imgui.text("Vectors:");
ImGui::PopStyleColor(); ImGui::PopStyleColor();
Vec3f translation = m_state.get_translation().cast<float>(); Vec3f translation = m_state.get_first_vector_of_type(State::QueueItem::TranslationType).cast<float>();
Vec3f rotation = m_state.get_rotation(); Vec3f rotation = m_state.get_first_vector_of_type(State::QueueItem::RotationType).cast<float>();
ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly); ImGui::InputFloat3("Translation##3", translation.data(), "%.3f", ImGuiInputTextFlags_ReadOnly);
ImGui::InputFloat3("Rotation##3", rotation.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.text("Queue size:");
ImGui::PopStyleColor(); ImGui::PopStyleColor();
int translation_size[2] = { (int)m_state.get_translation_queue_size(), (int)m_state.get_translation_queue_max_size() }; int input_queue_size_current[2] = { int(m_state.input_queue_size_current()), int(m_state.input_queue_max_size_achieved) };
int rotation_size[2] = { (int)m_state.get_rotation_queue_size(), (int)m_state.get_rotation_queue_max_size() }; ImGui::InputInt2("Current##4", input_queue_size_current, ImGuiInputTextFlags_ReadOnly);
int buttons_size[2] = { (int)m_state.get_buttons_queue_size(), (int)m_state.get_buttons_queue_max_size() };
ImGui::InputInt2("Translation##4", translation_size, ImGuiInputTextFlags_ReadOnly); int input_queue_size_param = int(params_copy.input_queue_max_size);
ImGui::InputInt2("Rotation##4", rotation_size, ImGuiInputTextFlags_ReadOnly); if (ImGui::InputInt("Max size", &input_queue_size_param, 1, 1, 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))
{ {
if (queue_size > 0) if (input_queue_size_param > 0) {
m_state.set_queues_max_size(queue_size); params_copy.input_queue_max_size = input_queue_size_param;
params_changed = true;
}
} }
ImGui::Separator(); ImGui::Separator();
@ -377,23 +349,165 @@ void Mouse3DController::render_settings_dialog(GLCanvas3D& canvas) const
} }
imgui.end(); 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<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)
{
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<std::mutex> 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() bool Mouse3DController::connect_device()
{ {
#ifdef __APPLE__ if (m_stop)
return false; return false;
#endif//__APPLE__
static const long long DETECTION_TIME_MS = 2000; // two seconds
if (is_device_connected()) {
return false; // Wait for 2 seconds, but cancellable by m_stop.
std::unique_lock<std::mutex> 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 (m_stop)
if (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_last_time).count() < DETECTION_TIME_MS) return false;
return false;
m_last_time = std::chrono::high_resolution_clock::now();
// Enumerates devices // Enumerates devices
hid_device_info* devices = hid_enumerate(0, 0); hid_device_info* devices = hid_enumerate(0, 0);
@ -623,23 +737,11 @@ bool Mouse3DController::connect_device()
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std::cout << "Opened device." << std::endl; std::cout << "Opened device." << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
// get device parameters from the config, if present // Copy the parameters for m_device_str into the current parameters.
double translation_speed = 4.0; if (auto it_params = m_params_by_device.find(m_device_str); it_params != m_params_by_device.end()) {
float rotation_speed = 4.0; tbb::mutex::scoped_lock lock(m_params_ui_mutex);
double translation_deadzone = State::DefaultTranslationDeadzone; m_params = m_params_ui = it_params->second;
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));
} }
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
else else
@ -657,138 +759,85 @@ bool Mouse3DController::connect_device()
void Mouse3DController::disconnect_device() void Mouse3DController::disconnect_device()
{ {
if (!is_device_connected()) if (m_device) {
return; hid_close(m_device);
m_device = nullptr;
// Stop the secondary thread, if running BOOST_LOG_TRIVIAL(info) << "Disconnected device: " << m_device_str;
if (m_thread.joinable()) // Copy the current parameters for m_device_str into the parameter database.
m_thread.join(); {
tbb::mutex::scoped_lock lock(m_params_ui_mutex);
// Store current device parameters into the config m_params_by_device[m_device_str] = m_params_ui;
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); m_device_str.clear();
m_connected = false;
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 = "";
} }
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() void Mouse3DController::collect_input()
{ {
DataPacketRaw packet = { 0 }; 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); int res = hid_read_timeout(m_device, packet.data(), packet.size(), 100);
if (res < 0) if (res < 0) {
{ // An error occourred (device detached from pc ?). Close the 3Dconnexion device.
// An error occourred (device detached from pc ?) this->disconnect_device();
stop(); } else
return; this->handle_input(packet, res, m_params, m_state);
}
handle_input(packet, res);
} }
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 &params, State &state_in_out)
{ {
if (!wxGetApp().IsActive()) if (! wxGetApp().IsActive())
return; return false;
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;
int res = packet_lenght; int res = packet_lenght;
bool updated = false; bool updated = false;
if (res == 7) if (res == 7)
updated = handle_packet(packet); updated = handle_packet(packet, params, state_in_out);
else if (res == 13) 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)) else if ((res == 3) && (packet[0] == 3))
// On Mac button packets can be 3 bytes long // 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 #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
else if (res > 0) else if (res > 0)
std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl; std::cout << "Got unknown data packet of length: " << res << ", code:" << (int)packet[0] << std::endl;
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
if (updated) #if 1
{ if (updated) {
wxGetApp().plater()->set_current_canvas_as_dirty(); wxGetApp().plater()->set_current_canvas_as_dirty();
// ask for an idle event to update 3D scene // ask for an idle event to update 3D scene
wxWakeUpIdle(); 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 &params, State &state_in_out)
{ {
switch (packet[0]) switch (packet[0])
{ {
case 1: // Translation case 1: // Translation
{ {
if (handle_packet_translation(packet)) if (handle_packet_translation(packet, params, state_in_out))
return true; return true;
break; break;
} }
case 2: // Rotation case 2: // Rotation
{ {
if (handle_packet_rotation(packet, 1)) if (handle_packet_rotation(packet, 1, params, state_in_out))
return true; return true;
break; break;
} }
case 3: // Button 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; return true;
break; break;
@ -796,14 +845,14 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet)
case 23: // Battery charge case 23: // Battery charge
{ {
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #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 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break; break;
} }
default: default:
{ {
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #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 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break; break;
} }
@ -812,14 +861,15 @@ bool Mouse3DController::handle_packet(const DataPacketRaw& packet)
return false; 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 &params, State &state_in_out)
{ {
switch (packet[0]) switch (packet[0])
{ {
case 1: // Translation + Rotation case 1: // Translation + Rotation
{ {
bool updated = handle_packet_translation(packet); bool updated = handle_packet_translation(packet, params, state_in_out);
updated |= handle_packet_rotation(packet, 7); updated |= handle_packet_rotation(packet, 7, params, state_in_out);
if (updated) if (updated)
return true; return true;
@ -828,7 +878,7 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
} }
case 3: // Button case 3: // Button
{ {
if (handle_packet_button(packet, 12)) if (params.buttons_enabled && handle_packet_button(packet, 12, params, state_in_out))
return true; return true;
break; break;
@ -836,14 +886,14 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
case 23: // Battery charge case 23: // Battery charge
{ {
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #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 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break; break;
} }
default: default:
{ {
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #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 #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
break; break;
} }
@ -852,46 +902,52 @@ bool Mouse3DController::handle_wireless_packet(const DataPacketRaw& packet)
return false; 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; double ret = (double)value / 350.0;
return (std::abs(ret) > deadzone) ? ret : 0.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 &params, 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), Vec3d translation(-convert_input(packet[1], packet[2], deadzone),
convert_input(packet[3], packet[4], deadzone), convert_input(packet[3], packet[4], deadzone),
convert_input(packet[5], packet[6], deadzone)); convert_input(packet[5], packet[6], deadzone));
if (!translation.isApprox(Vec3d::Zero())) if (!translation.isApprox(Vec3d::Zero()))
{ {
m_state.append_translation(translation); state_in_out.append_translation(translation, params.input_queue_max_size);
return true; return true;
} }
return false; 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 &params, 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), 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 + 2], packet[first_byte + 3], deadzone),
(float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone)); (float)convert_input(packet[first_byte + 4], packet[first_byte + 5], deadzone));
if (!rotation.isApprox(Vec3f::Zero())) if (!rotation.isApprox(Vec3f::Zero()))
{ {
m_state.append_rotation(rotation); state_in_out.append_rotation(rotation, params.input_queue_max_size);
return true; return true;
} }
return false; 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 &params, State &state_in_out)
{ {
unsigned int data = 0; unsigned int data = 0;
for (unsigned int i = 1; i < packet_size; ++i) 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)) 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; return true;
} }
} }
@ -912,5 +968,7 @@ bool Mouse3DController::handle_packet_button(const DataPacketRaw& packet, unsign
return false; return false;
} }
#endif //__APPLE__
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r

View File

@ -10,67 +10,80 @@
#include <queue> #include <queue>
#include <thread> #include <thread>
#include <mutex>
#include <vector> #include <vector>
#include <chrono> #include <chrono>
#include <tbb/mutex.h>
namespace Slic3r { namespace Slic3r {
class AppConfig;
namespace GUI { namespace GUI {
#if __APPLE__
class Mouse3DHandlerMac;
#endif//__APPLE__
struct Camera; struct Camera;
class GLCanvas3D; class GLCanvas3D;
class Mouse3DController class Mouse3DController
{ {
class State // 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
public: // to copy the parameters.
static const double DefaultTranslationScale; struct Params
static const double MaxTranslationDeadzone; {
static const double DefaultTranslationDeadzone; static constexpr double DefaultTranslationScale = 2.5;
static const float DefaultRotationScale; static constexpr double MaxTranslationDeadzone = 0.2;
static const float MaxRotationDeadzone; static constexpr double DefaultTranslationDeadzone = 0.5 * MaxTranslationDeadzone;
static const float DefaultRotationDeadzone; static constexpr float DefaultRotationScale = 1.0f;
static const double DefaultZoomScale; static constexpr float MaxRotationDeadzone = 0.2f;
static constexpr float DefaultRotationDeadzone = 0.5f * MaxRotationDeadzone;
static constexpr double DefaultZoomScale = 0.1;
private:
template <typename Number> template <typename Number>
struct CustomParameters struct CustomParameters
{ {
Number scale; Number scale;
Number deadzone; Number deadzone;
CustomParameters(Number scale, Number deadzone) : scale(scale), deadzone(deadzone) {}
}; };
template <class T> CustomParameters<double> translation { DefaultTranslationScale, DefaultTranslationDeadzone };
struct InputQueue CustomParameters<float> rotation { DefaultRotationScale, DefaultRotationDeadzone };
{ CustomParameters<double> zoom { DefaultZoomScale, 0.0 };
size_t max_size; // Do not process button presses from 3DConnexion device, let the user map the 3DConnexion keys in 3DConnexion driver.
std::queue<T> queue; 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 // Queue of the 3DConnexion input events (translations, rotations, button presses).
// The effects of changing this value can be tested by setting ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT to 1 class State
// and playing with the imgui dialog which shows by pressing CTRL+M {
InputQueue() : max_size(5) {} 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; private:
InputQueue<Vec3f> m_rotation; // m_input_queue is accessed by the background thread and by the UI thread. Access to m_input_queue
std::queue<unsigned int> m_buttons; // is guarded with m_input_queue_mutex.
std::deque<QueueItem> m_input_queue;
bool m_buttons_enabled; mutable tbb::mutex m_input_queue_mutex;
CustomParameters<double> m_translation_params;
CustomParameters<float> m_rotation_params;
CustomParameters<double> m_zoom_params;
#ifdef WIN32
// When the 3Dconnexion driver is running the system gets, by default, mouse wheel events when rotations around the X axis are detected. // 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 // 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 // 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 // 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_mouse_wheel() through the call to the process_mouse_wheel() method
// GLCanvas3D::on_idle() through the call to the apply() method // GLCanvas3D::on_idle() through the call to the apply() method
std::atomic<unsigned int> m_mouse_wheel_counter; unsigned int m_mouse_wheel_counter { 0 };
#endif /* WIN32 */
#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
public: public:
State(); // 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_translation(const Vec3d& translation); void append_rotation(const Vec3f& rotation, size_t input_queue_max_size);
void append_rotation(const Vec3f& rotation); void append_button(unsigned int id, size_t input_queue_max_size);
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(); }
#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(); bool process_mouse_wheel();
#endif /* WIN32 */
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; }
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
Vec3d get_translation() const { return has_translation() ? m_translation.queue.front() : Vec3d::Zero(); } Vec3d get_first_vector_of_type(unsigned int type) const {
Vec3f get_rotation() const { return has_rotation() ? m_rotation.queue.front() : Vec3f::Zero(); } tbb::mutex::scoped_lock lock(m_input_queue_mutex);
unsigned int get_button() const { return has_button() ? m_buttons.front() : 0; } 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;
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(); } size_t input_queue_size_current() const {
unsigned int get_buttons_queue_size() const { return (unsigned int)m_buttons.size(); } tbb::mutex::scoped_lock lock(m_input_queue_mutex);
return m_input_queue.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; } std::atomic<size_t> input_queue_max_size_achieved;
size_t get_buttons_queue_max_size() const { return m_buttons_queue_max_size; }
#endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT #endif // ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
size_t get_queues_max_size() const { return m_translation.max_size; } // Apply the 3DConnexion events stored in the input queue, reset the input queue.
void set_queues_max_size(size_t size); // Returns true if any change to the camera took place.
bool apply(const Params &params, Camera& camera);
// return true if any change to the camera took place
bool apply(Camera& camera);
}; };
bool m_initialized; // Background thread works with this copy.
mutable State m_state; Params m_params;
std::thread m_thread; // UI thread will read / write this copy.
hid_device* m_device; Params m_params_ui;
std::string m_device_str; bool m_params_ui_changed { false };
bool m_running; mutable tbb::mutex m_params_ui_mutex;
bool m_mac_mouse_connected;
mutable bool m_show_settings_dialog; // This is a database of parametes of all 3DConnexion devices ever connected.
// set to true when ther user closes the dialog by clicking on [X] or [Close] buttons // This database is loaded from AppConfig on application start and it is stored to AppConfig on application exit.
mutable bool m_settings_dialog_closed_by_user; // We need to do that as the AppConfig is not thread safe and we need read the parameters on device connect / disconnect,
std::chrono::time_point<std::chrono::high_resolution_clock> m_last_time; // 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: 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(); void init();
// Stop the background thread (Windows and Linux).
// Disconnect from a 3DConnextion driver (OSX).
// Call save_config() after shutdown().
void shutdown(); void shutdown();
bool is_device_connected() const { return m_device != nullptr || m_mac_mouse_connected; } bool connected() const { return m_connected; }
bool is_running() const { return m_running || m_mac_mouse_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
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(); } 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 apply(Camera& camera);
bool is_settings_dialog_shown() const { return m_show_settings_dialog; } 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; void render_settings_dialog(GLCanvas3D& canvas) const;
typedef std::array<double, 6> DataPacketAxis; #if ! __APPLE__
void handle_input_axis(const DataPacketAxis& packet);
private: private:
bool connect_device(); bool connect_device();
void disconnect_device(); void disconnect_device();
void start();
void stop() { m_running = false; }
typedef std::array<unsigned char, 13> DataPacketRaw;
// secondary thread methods // secondary thread methods
void run(); void run();
void collect_input(); 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__ typedef std::array<unsigned char, 13> DataPacketRaw;
Mouse3DHandlerMac* m_handler_mac;
#endif//__APPLE__ // 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 &params, State &state_in_out);
// The following is called by handle_input() from the worker thread.
static bool handle_packet(const DataPacketRaw& packet, const Params &params, State &state_in_out);
static bool handle_wireless_packet(const DataPacketRaw& packet, const Params &params, State &state_in_out);
static bool handle_packet_translation(const DataPacketRaw& packet, const Params &params, State &state_in_out);
static bool handle_packet_rotation(const DataPacketRaw& packet, unsigned int first_byte, const Params &params, State &state_in_out);
static bool handle_packet_button(const DataPacketRaw& packet, unsigned int packet_size, const Params &params, State &state_in_out);
#endif /* __APPLE__ */
}; };
#if __APPLE__
class Mouse3DHandlerMac{
public:
Mouse3DHandlerMac(Mouse3DController* controller);
~Mouse3DHandlerMac();
bool available();
};
#endif//__APPLE__
} // namespace GUI } // namespace GUI
} // namespace Slic3r } // namespace Slic3r
#endif // slic3r_Mouse3DController_hpp_ #endif // slic3r_Mouse3DController_hpp_

View File

@ -1,4 +1,3 @@
#include "Mouse3DController.hpp" #include "Mouse3DController.hpp"
#include <stdint.h> #include <stdint.h>
@ -10,7 +9,7 @@
#include <cstdio> #include <cstdio>
static Slic3r::GUI::Mouse3DController* mouse_3d_controller = NULL; static Slic3r::GUI::Mouse3DController* mouse_3d_controller = nullptr;
static uint16_t clientID = 0; static uint16_t clientID = 0;
@ -65,7 +64,7 @@ typedef int16_t (*ConnexionClientControl_ptr)(uint16_t clientID,
int32_t param, int32_t param,
int32_t *result); int32_t *result);
#define DECLARE_FUNC(name) name##_ptr name = NULL #define DECLARE_FUNC(name) name##_ptr name = nullptr
DECLARE_FUNC(SetConnexionHandlers); DECLARE_FUNC(SetConnexionHandlers);
DECLARE_FUNC(InstallConnexionHandlers); DECLARE_FUNC(InstallConnexionHandlers);
@ -79,15 +78,12 @@ static void *load_func(void *module, const char *func_name)
{ {
void *func = dlsym(module, func_name); void *func = dlsym(module, func_name);
//#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
if (func) { if (func) {
BOOST_LOG_TRIVIAL(info) << func_name <<" loaded"; BOOST_LOG_TRIVIAL(info) << func_name <<" loaded";
} }
else { else {
//printf("<!> %s\n", dlerror());
BOOST_LOG_TRIVIAL(error) <<"loading 3dx drivers dlsym error: "<< dlerror(); BOOST_LOG_TRIVIAL(error) <<"loading 3dx drivers dlsym error: "<< dlerror();
} }
//#endif
return func; return func;
} }
@ -98,9 +94,8 @@ static void *module; // handle to the whole driver
static bool load_driver_functions() static bool load_driver_functions()
{ {
if (driver_loaded) { if (driver_loaded)
return true; return true;
}
module = dlopen("/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient", module = dlopen("/Library/Frameworks/3DconnexionClient.framework/3DconnexionClient",
RTLD_LAZY | RTLD_LOCAL); RTLD_LAZY | RTLD_LOCAL);
@ -109,15 +104,14 @@ static bool load_driver_functions()
BOOST_LOG_TRIVIAL(info) << "loading 3dx drivers"; BOOST_LOG_TRIVIAL(info) << "loading 3dx drivers";
LOAD_FUNC(SetConnexionHandlers); LOAD_FUNC(SetConnexionHandlers);
if (SetConnexionHandlers != NULL) { if (SetConnexionHandlers != nullptr) {
driver_loaded = true; driver_loaded = true;
has_new_driver = true; has_new_driver = true;
} }
else { else {
BOOST_LOG_TRIVIAL(info) << "installing 3dx drivers"; BOOST_LOG_TRIVIAL(info) << "installing 3dx drivers";
LOAD_FUNC(InstallConnexionHandlers); LOAD_FUNC(InstallConnexionHandlers);
driver_loaded = (InstallConnexionHandlers != nullptr);
driver_loaded = (InstallConnexionHandlers != NULL);
} }
if (driver_loaded) { if (driver_loaded) {
@ -128,17 +122,11 @@ static bool load_driver_functions()
LOAD_FUNC(ConnexionClientControl); LOAD_FUNC(ConnexionClientControl);
} }
} }
else { else {
BOOST_LOG_TRIVIAL(error) << "3dx drivers module loading error: "<< dlerror() ; 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"); BOOST_LOG_TRIVIAL(info) << "3dx drivers loaded: " << (driver_loaded ? (has_new_driver ? "YES, new" : "YES, old") : "NO");
printf("new: %s\n", has_new_driver ? "YES" : "NO");
#endif
BOOST_LOG_TRIVIAL(info) << "3dx drivers loaded: "<< driver_loaded ? "YES" : "NO" ;
return driver_loaded; return driver_loaded;
} }
@ -149,29 +137,22 @@ static void unload_driver()
static void DeviceAdded(uint32_t unused) static void DeviceAdded(uint32_t unused)
{ {
#if ENABLE_3DCONNEXION_DEVICES_DEBUG_OUTPUT
std::cout<<"3D device added"<<std::endl;
#endif
BOOST_LOG_TRIVIAL(info)<<"3dx device added"; BOOST_LOG_TRIVIAL(info)<<"3dx device added";
// determine exactly which device is plugged in // determine exactly which device is plugged in
int32_t result; int32_t result;
ConnexionClientControl(clientID, kConnexionCtlGetDeviceID, 0, &result); ConnexionClientControl(clientID, kConnexionCtlGetDeviceID, 0, &result);
int16_t vendorID = result >> 16; int vendorID = result >> 16;
int16_t productID = result & 0xffff; int productID = result & 0xffff;
//TODO: verify device //TODO: verify device
char buf[64];
sprintf(buf, "VID%04X,PID%04X", vendorID, productID);
mouse_3d_controller->set_mac_mouse_connected(true); mouse_3d_controller->connected(buf);
} }
static void DeviceRemoved(uint32_t unused) 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"; 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) 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) { if (s->client == clientID) {
switch (s->command) { switch (s->command) {
case kConnexionCmdHandleAxis: { 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.
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;
//Actually we are getting values way over 1024. Max is probably 2048 now. for (int i = 0; i < 6; ++ i)
std::array<double, 6> 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. 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(packet);
mouse_3d_controller->handle_input_axis(packet);
break; break;
} }
case kConnexionCmdHandleButtons: case kConnexionCmdHandleButtons:
break; break;
case kConnexionCmdAppSpecific: case kConnexionCmdAppSpecific:
break; break;
default: default:
@ -203,41 +179,35 @@ static void DeviceEvent(uint32_t unused, uint32_t msg_type, void *msg_arg)
} }
} }
} }
} }
namespace Slic3r { namespace Slic3r {
namespace GUI { 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()) { if (load_driver_functions()) {
mouse_3d_controller = controller; mouse_3d_controller = this;
uint16_t error; uint16_t error = has_new_driver ?
if (has_new_driver) { SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, false) :
error = SetConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved, false); InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved);
}
else {
error = InstallConnexionHandlers(DeviceEvent, DeviceAdded, DeviceRemoved);
}
if (error) { if (! error) {
return; // 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) { if (driver_loaded) {
UnregisterConnexionClient(clientID); UnregisterConnexionClient(clientID);
@ -247,9 +217,4 @@ Mouse3DHandlerMac::~Mouse3DHandlerMac()
mouse_3d_controller = nullptr; mouse_3d_controller = nullptr;
} }
bool Mouse3DHandlerMac::available()
{
return driver_loaded;
}
}}//namespace Slic3r::GUI }}//namespace Slic3r::GUI

View File

@ -2145,6 +2145,10 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// updates camera type from .ini file // updates camera type from .ini file
camera.set_type(get_config("use_perspective_camera")); 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(); mouse3d_controller.init();
// Initialize the Undo / Redo stack with a first snapshot. // Initialize the Undo / Redo stack with a first snapshot.
@ -2156,8 +2160,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
Plater::priv::~priv() Plater::priv::~priv()
{ {
mouse3d_controller.shutdown();
if (config != nullptr) if (config != nullptr)
delete config; delete config;
} }