feat(gui): assignable keyboard shortcuts (#15706)

This commit is contained in:
Kris Austin
2026-09-21 17:36:04 -03:00
committed by GitHub
parent bfe458507b
commit 824b216f18
52 changed files with 2685 additions and 1299 deletions
+287 -537
View File
@@ -29,6 +29,7 @@
#include "MainFrame.hpp"
#include "WipeTowerDialog.hpp"
#include "GUI_App.hpp"
#include "Shortcuts.hpp"
#include "GUI_ObjectList.hpp"
#include "GUI_Colors.hpp"
#include "Mouse3DController.hpp"
@@ -77,6 +78,7 @@
#include <float.h>
#include <algorithm>
#include <cmath>
#include <set>
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
@@ -1055,7 +1057,6 @@ wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_BED_SHAPE, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_TAB, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_RESETGIZMOS, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_MOVE_SLIDERS, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_EDIT_COLOR_CHANGE, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_JUMP_TO, wxKeyEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_UNDO, SimpleEvent);
wxDEFINE_EVENT(EVT_GLCANVAS_REDO, SimpleEvent);
@@ -1174,7 +1175,6 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D &bed)
, m_dynamic_background_enabled(false)
, m_multisample_allowed(false)
, m_moving(false)
, m_tab_down(false)
, m_camera_movement(false)
, m_cursor_type(Standard)
, m_color_by("volume")
@@ -3229,6 +3229,9 @@ void GLCanvas3D::bind_event_handlers()
m_canvas->Bind(wxEVT_PAINT, &GLCanvas3D::on_paint, this);
m_canvas->Bind(wxEVT_SET_FOCUS, &GLCanvas3D::on_set_focus, this);
m_canvas->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent& evt) {
// The key-up that would commit a keyboard edit goes to whatever took the focus.
if (m_selection_edit.kind != SelectionEdit::None)
finish_selection_edit();
ImGui::SetWindowFocus(nullptr);
render();
evt.Skip();
@@ -3358,8 +3361,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
// see include/wx/defs.h enum wxKeyCode
int keyCode = evt.GetKeyCode();
int ctrlMask = wxMOD_CONTROL;
int shiftMask = wxMOD_SHIFT;
auto imgui = wxGetApp().imgui();
if (imgui->update_key_data(evt)) {
@@ -3367,6 +3368,11 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
return;
}
#ifdef SLIC3R_CAD
const int ctrlMask = wxMOD_CONTROL;
const int shiftMask = wxMOD_SHIFT;
#endif
// Design tab: Delete/Backspace removes the selected sketch entities while a
// sketch tool is active and the canvas has focus (dialog text fields are separate
// wx controls, so this never eats their editing keys).
@@ -3425,12 +3431,6 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
}
#endif
bool is_in_painting_mode = false;
GLGizmoPainterBase *current_gizmo_painter = dynamic_cast<GLGizmoPainterBase *>(get_gizmos_manager().get_current());
if (current_gizmo_painter != nullptr) {
is_in_painting_mode = true;
}
//BBS: add orient deactivate logic
if (keyCode == WXK_ESCAPE
&& (_deactivate_arrange_menu() || _deactivate_orient_menu()))
@@ -3439,361 +3439,271 @@ void GLCanvas3D::on_char(wxKeyEvent& evt)
if (m_gizmos.on_char(evt))
return;
if ((evt.GetModifiers() & ctrlMask) != 0) {
// CTRL is pressed
switch (keyCode) {
#ifdef __APPLE__
case 'a':
case 'A':
#else /* __APPLE__ */
case WXK_CONTROL_A:
#endif /* __APPLE__ */
if (!is_in_painting_mode && !m_layers_editing.is_enabled()) {
if (evt.ShiftDown())
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
else
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL));
}
if (const KeyChord chord = KeyChord::from_event(evt); chord.is_punctuation() && handle_shortcut(chord))
return;
if (evt.HasModifiers()) {
evt.Skip();
return;
}
auto obj_list = wxGetApp().obj_list();
switch (keyCode)
{
case WXK_ESCAPE: { deselect_all(); break; }
// BBS: use keypad to change extruder
case '1': {
if (!m_timer_set_color.IsRunning()) {
m_timer_set_color.StartOnce(500);
break;
}
}
case '0': //Color logic for material 10
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
if (m_timer_set_color.IsRunning()) {
if (keyCode < '7') keyCode += 10;
m_timer_set_color.Stop();
}
if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(keyCode - '0');
break;
#ifdef __APPLE__
case 'c':
case 'C':
#else /* __APPLE__ */
case WXK_CONTROL_C:
#endif /* __APPLE__ */
if (!is_in_painting_mode)
post_event(SimpleEvent(EVT_GLTOOLBAR_COPY));
break;
#ifdef __APPLE__
case 'm':
case 'M':
#else /* __APPLE__ */
case WXK_CONTROL_M:
#endif /* __APPLE__ */
{
#ifdef _WIN32
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "true") {
#endif //_WIN32
#ifdef __APPLE__
// On OSX use Cmd+Shift+M to "Show/Hide 3Dconnexion devices settings dialog"
if ((evt.GetModifiers() & shiftMask) != 0) {
#endif // __APPLE__
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
m_dirty = true;
#ifdef __APPLE__
}
else
// and Cmd+M to minimize application
wxGetApp().mainframe->Iconize();
#endif // __APPLE__
#ifdef _WIN32
}
#endif //_WIN32
break;
}
#ifdef __APPLE__
case 'v':
case 'V':
#else /* __APPLE__ */
case WXK_CONTROL_V:
#endif /* __APPLE__ */
if (!is_in_painting_mode)
post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE));
break;
#ifdef __APPLE__
case 'x':
case 'X':
#else /* __APPLE__ */
case WXK_CONTROL_X:
#endif /* __APPLE__ */
if (!is_in_painting_mode)
post_event(SimpleEvent(EVT_GLTOOLBAR_CUT));
break;
#ifdef __APPLE__
case 'f':
case 'F':
#else /* __APPLE__ */
case WXK_CONTROL_F:
#endif /* __APPLE__ */
break;
#ifdef __APPLE__
case 'y':
case 'Y':
#else /* __APPLE__ */
case WXK_CONTROL_Y:
#endif /* __APPLE__ */
if (m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView) {
post_event(SimpleEvent(EVT_GLCANVAS_REDO));
}
break;
#ifdef __APPLE__
case 'z':
case 'Z':
#else /* __APPLE__ */
case WXK_CONTROL_Z:
#endif /* __APPLE__ */
// only support redu/undo in CanvasView3D
if (m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView) {
post_event(SimpleEvent(EVT_GLCANVAS_UNDO));
}
break;
// BBS
#ifdef __APPLE__
case 'E':
case 'e':
#else /* __APPLE__ */
case WXK_CONTROL_E:
#endif /* __APPLE__ */
{ m_labels.show(!m_labels.is_shown()); m_dirty = true; break; }
case '0': {
select_view("plate");
zoom_to_bed();
break; }
case '1': { select_view("top"); break; }
case '2': { select_view("bottom"); break; }
case '3': { select_view("front"); break; }
case '4': { select_view("rear"); break; }
case '5': { select_view("left"); break; }
case '6': { select_view("right"); break; }
case '7': { select_plate(); break; }
//case WXK_BACK:
//case WXK_DELETE:
#ifdef __APPLE__
case 'd':
case 'D':
#else /* __APPLE__ */
case WXK_CONTROL_D:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
break;
#ifdef __APPLE__
case 'k':
case 'K':
#else /* __APPLE__ */
case WXK_CONTROL_K:
#endif /* __APPLE__ */
post_event(SimpleEvent(EVT_GLTOOLBAR_CLONE));
break;
default: evt.Skip();
}
} else {
auto obj_list = wxGetApp().obj_list();
switch (keyCode)
{
//case WXK_BACK:
case WXK_DELETE: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
// BBS
#ifdef __APPLE__
case WXK_BACK: { post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE)); break; }
#endif
case WXK_ESCAPE: { deselect_all(); break; }
case WXK_F5: {
if (wxGetApp().mainframe->is_printer_view())
wxGetApp().mainframe->load_printer_url();
//if ((wxGetApp().is_editor() && !wxGetApp().plater()->model().objects.empty()) ||
// (wxGetApp().is_gcode_viewer() && !wxGetApp().plater()->get_last_loaded_gcode().empty()))
// post_event(SimpleEvent(EVT_GLCANVAS_RELOAD_FROM_DISK));
break;
}
// BBS: use keypad to change extruder
case '1': {
if (!m_timer_set_color.IsRunning()) {
m_timer_set_color.StartOnce(500);
break;
}
}
case '0': //Color logic for material 10
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9': {
if (m_timer_set_color.IsRunning()) {
if (keyCode < '7') keyCode += 10;
m_timer_set_color.Stop();
}
if (m_gizmos.get_current_type() != GLGizmosManager::MmSegmentation)
obj_list->set_extruder_for_selected_items(keyCode - '0');
break;
}
case '+': {
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
else
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1));
break;
}
case '-': {
if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
post_event(wxKeyEvent(EVT_GLCANVAS_EDIT_COLOR_CHANGE, evt));
else
post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1));
break;
}
case '?': { post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break; }
case 'A':
case 'a':
{
if ((evt.GetModifiers() & shiftMask) != 0)
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_PARTPLATE));
else
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
break;
}
//case 'B':
//case 'b': { zoom_to_bed(); break; }
case 'C':
case 'c': { wxGetApp().toggle_show_gcode_window(); m_dirty = true; request_extra_frame(); break; }
//case 'G':
//case 'g': {
// if ((evt.GetModifiers() & shiftMask) != 0) {
// if (dynamic_cast<Preview*>(m_canvas->GetParent()) != nullptr)
// post_event(wxKeyEvent(EVT_GLCANVAS_JUMP_TO, evt));
// }
// break;
//}
case 'I':
case 'i': { _update_camera_zoom(1.0); break; }
//case 'K':
//case 'k': { wxGetApp().plater()->get_camera().select_next_type(); m_dirty = true; break; }
//case 'L':
//case 'l': {
//if (!m_main_toolbar.is_enabled()) {
// m_gcode_viewer.enable_legend(!m_gcode_viewer.is_legend_enabled());
// m_dirty = true;
// wxGetApp().plater()->update_preview_bottom_toolbar();
//}
//break;
//}
case 'O':
case 'o': { _update_camera_zoom(-1.0); break; }
case 'q':
case 'Q':
{
if ((evt.GetModifiers() & shiftMask) != 0)
post_event(SimpleEvent(EVT_GLCANVAS_ORIENT_PARTPLATE));
else
post_event(SimpleEvent(EVT_GLCANVAS_ORIENT));
break;
}
//case 'Z':
//case 'z': {
// if (!m_selection.is_empty())
// zoom_to_selection();
// else {
// if (!m_volumes.empty())
// zoom_to_volumes();
// else
// _zoom_to_box(m_gcode_viewer.get_paths_bounding_box());
// }
// break;
//}
case 'v':
case 'V': { post_event(SimpleEvent(EVT_GLCANVAS_PRINTABLE)); break; }
default: { evt.Skip(); break; }
}
}
default: { evt.Skip(); break; }
}
}
class TranslationProcessor
bool GLCanvas3D::handle_shortcut(const KeyChord& chord)
{
using UpAction = std::function<void(void)>;
using DownAction = std::function<void(const Vec3d&, bool, bool)>;
const ShortcutContext context = m_canvas_type == CanvasPreview ? ShortcutContext::Preview : ShortcutContext::Plater;
const std::optional<ShortcutRegistry::Match> match = wxGetApp().shortcuts().match(context, chord);
if (!match.has_value())
return false;
const Shortcut shortcut = match->shortcut;
const int held = match->step_modifiers;
if (m_key_down.repeat && !shortcut_info(shortcut).repeatable)
return true;
UpAction m_up_action{ nullptr };
DownAction m_down_action{ nullptr };
const bool painting = dynamic_cast<GLGizmoPainterBase*>(m_gizmos.get_current()) != nullptr;
const bool can_edit = m_canvas_type == CanvasView3D || m_canvas_type == CanvasAssembleView;
auto edit_selection = [this](SelectionEdit::Kind kind, const Vec3d& direction) {
if (!m_gizmos.is_enabled() || m_selection.is_empty() || m_canvas_type == CanvasAssembleView)
return false;
m_selection_edit.kind = kind;
m_selection_edit.key = m_key_down.code;
m_selection_edit.direction = direction;
return true;
};
auto move_selection = [&](const Vec3d& direction) {
if (edit_selection(SelectionEdit::Move, direction))
apply_selection_move((held & wxMOD_SHIFT) != 0, (held & wxMOD_CONTROL) != 0);
};
auto rotate_selection = [&](double angle_z_rad) {
if (edit_selection(SelectionEdit::Rotate, Vec3d::UnitZ()))
apply_selection_rotate(angle_z_rad);
};
auto step_slider = [this, held](auto&& step) {
IMSlider* layers = get_gcode_viewer().get_layers_slider();
IMSlider* moves = get_gcode_viewer().get_moves_slider();
step(layers, moves, held != 0 ? 5 : 1);
if (layers->is_dirty() && layers->is_one_layer())
layers->SetLowerValue(layers->GetHigherValue());
m_dirty = true;
};
bool m_running{ false };
Vec3d m_direction{ Vec3d::UnitX() };
public:
TranslationProcessor(UpAction up_action, DownAction down_action)
: m_up_action(up_action), m_down_action(down_action)
{
}
void process(wxKeyEvent& evt)
{
const int keyCode = evt.GetKeyCode();
wxEventType type = evt.GetEventType();
if (type == wxEVT_KEY_UP) {
switch (keyCode)
{
case WXK_NUMPAD_LEFT: case WXK_LEFT:
case WXK_NUMPAD_RIGHT: case WXK_RIGHT:
case WXK_NUMPAD_UP: case WXK_UP:
case WXK_NUMPAD_DOWN: case WXK_DOWN:
{
m_running = false;
m_up_action();
break;
}
default: { break; }
}
}
else if (type == wxEVT_KEY_DOWN) {
bool apply = false;
switch (keyCode)
{
case WXK_SHIFT:
{
if (m_running)
apply = true;
break;
}
case WXK_NUMPAD_LEFT:
case WXK_LEFT:
{
m_direction = -Vec3d::UnitX();
apply = true;
break;
}
case WXK_NUMPAD_RIGHT:
case WXK_RIGHT:
{
m_direction = Vec3d::UnitX();
apply = true;
break;
}
case WXK_NUMPAD_UP:
case WXK_UP:
{
m_direction = Vec3d::UnitY();
apply = true;
break;
}
case WXK_NUMPAD_DOWN:
case WXK_DOWN:
{
m_direction = -Vec3d::UnitY();
apply = true;
break;
}
default: { break; }
}
if (apply) {
m_running = true;
m_down_action(m_direction, evt.ShiftDown(), evt.CmdDown());
}
switch (shortcut) {
case Shortcut::SelectAll:
if (!painting && !m_layers_editing.is_enabled())
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_CURR_PLATE_ALL));
break;
case Shortcut::SelectAllPlates:
if (!painting && !m_layers_editing.is_enabled())
post_event(SimpleEvent(EVT_GLCANVAS_SELECT_ALL));
break;
case Shortcut::Copy: if (!painting) post_event(SimpleEvent(EVT_GLTOOLBAR_COPY)); break;
case Shortcut::Paste: if (!painting) post_event(SimpleEvent(EVT_GLTOOLBAR_PASTE)); break;
case Shortcut::Cut: if (!painting) post_event(SimpleEvent(EVT_GLTOOLBAR_CUT)); break;
case Shortcut::Undo: if (can_edit) post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); break;
case Shortcut::Redo: if (can_edit) post_event(SimpleEvent(EVT_GLCANVAS_REDO)); break;
case Shortcut::DeleteSelected:
if (!m_gizmos.on_delete_key())
post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE));
break;
case Shortcut::DeleteAll: post_event(SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); break;
case Shortcut::CloneSelected: post_event(SimpleEvent(EVT_GLTOOLBAR_CLONE)); break;
case Shortcut::AddInstance: post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, +1)); break;
case Shortcut::RemoveInstance: post_event(Event<int>(EVT_GLCANVAS_INCREASE_INSTANCES, -1)); break;
case Shortcut::TogglePrintable: post_event(SimpleEvent(EVT_GLCANVAS_PRINTABLE)); break;
case Shortcut::Arrange:
if (!m_gizmos.is_running())
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE));
break;
case Shortcut::ArrangePlate:
if (!m_gizmos.is_running())
post_event(SimpleEvent(EVT_GLCANVAS_ARRANGE_PARTPLATE));
break;
case Shortcut::Orient: post_event(SimpleEvent(EVT_GLCANVAS_ORIENT)); break;
case Shortcut::OrientPlate: post_event(SimpleEvent(EVT_GLCANVAS_ORIENT_PARTPLATE)); break;
case Shortcut::RotateSelectionLeft: rotate_selection(0.25 * M_PI); break;
case Shortcut::RotateSelectionRight: rotate_selection(-0.25 * M_PI); break;
case Shortcut::MoveSelectionLeft: move_selection(-Vec3d::UnitX()); break;
case Shortcut::MoveSelectionRight: move_selection(Vec3d::UnitX()); break;
case Shortcut::MoveSelectionUp: move_selection(Vec3d::UnitY()); break;
case Shortcut::MoveSelectionDown: move_selection(-Vec3d::UnitY()); break;
case Shortcut::ZoomIn: _update_camera_zoom(1.0); break;
case Shortcut::ZoomOut: _update_camera_zoom(-1.0); break;
case Shortcut::SwitchView: post_event(SimpleEvent(EVT_GLCANVAS_TAB)); break;
case Shortcut::CollapseSidebar:
if (!wxGetApp().is_gcode_viewer())
post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
break;
case Shortcut::ShowWireframe:
wxGetApp().plater()->toggle_show_wireframe();
m_dirty = true;
break;
case Shortcut::Mouse3DSettings: {
#ifdef _WIN32
if (wxGetApp().app_config->get("use_legacy_3DConnexion") == "true") {
#endif //_WIN32
Mouse3DController& controller = wxGetApp().plater()->get_mouse3d_controller();
controller.show_settings_dialog(!controller.is_settings_dialog_shown());
m_dirty = true;
#ifdef _WIN32
}
#endif //_WIN32
break;
}
};
case Shortcut::ReloadDevicePage:
if (wxGetApp().mainframe->is_printer_view())
wxGetApp().mainframe->load_printer_url();
break;
case Shortcut::KeyboardShortcuts: post_event(SimpleEvent(EVT_GLCANVAS_QUESTION_MARK)); break;
case Shortcut::ToggleGcodeWindow:
wxGetApp().toggle_show_gcode_window();
m_dirty = true;
request_extra_frame();
break;
case Shortcut::ToggleOneLayerMode:
get_gcode_viewer().get_layers_slider()->switch_one_layer_mode();
m_dirty = true;
break;
case Shortcut::GoToLayer:
if (!m_gizmos.is_enabled()) {
get_gcode_viewer().get_layers_slider()->show_go_to_layer(true);
m_dirty = true;
}
break;
case Shortcut::LayerSliderUp:
case Shortcut::LayerSliderDown:
step_slider([up = shortcut == Shortcut::LayerSliderUp](IMSlider* layers, IMSlider* moves, int increment) {
const int delta = up ? increment : -increment;
if (layers->GetSelection() == ssHigher) {
layers->SetHigherValue(layers->GetHigherValue() + delta);
moves->SetHigherValue(moves->GetMaxValue());
}
else if (layers->GetSelection() == ssLower)
layers->SetLowerValue(layers->GetLowerValue() + delta);
});
break;
case Shortcut::MovesSliderLeft:
step_slider([](IMSlider* layers, IMSlider* moves, int increment) {
if (moves->GetHigherValue() == moves->GetMinValue() && layers->GetHigherValue() > layers->GetMinValue()) {
layers->SetHigherValue(layers->GetHigherValue() - 1);
moves->SetHigherValue(moves->GetMaxValue());
}
else
moves->SetHigherValue(moves->GetHigherValue() - increment);
});
break;
case Shortcut::MovesSliderRight:
step_slider([](IMSlider* layers, IMSlider* moves, int increment) {
if (moves->GetHigherValue() == moves->GetMaxValue() && layers->GetHigherValue() < layers->GetMaxValue()) {
layers->SetHigherValue(layers->GetHigherValue() + 1);
moves->SetHigherValue(moves->GetMinValue());
}
else
moves->SetHigherValue(moves->GetHigherValue() + increment);
});
break;
case Shortcut::MovesSliderStart:
case Shortcut::MovesSliderEnd:
step_slider([start = shortcut == Shortcut::MovesSliderStart](IMSlider*, IMSlider* moves, int) {
moves->SetHigherValue(start ? moves->GetMinValue() : moves->GetMaxValue());
moves->set_as_dirty();
});
break;
default:
if (!m_gizmos.open_gizmo_by_shortcut(shortcut))
return false;
m_dirty = true;
break;
}
return true;
}
void GLCanvas3D::apply_selection_move(bool slow, bool camera_space)
{
m_selection.setup_cache();
const double multiplier = slow ? 1.0 : 10.0;
Vec3d displacement;
if (camera_space) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
displacement = multiplier * (inv_view_3x3 * m_selection_edit.direction);
displacement.z() = 0.0;
}
else
displacement = multiplier * m_selection_edit.direction;
TransformationType trafo_type;
trafo_type.set_relative();
m_selection.translate(displacement, trafo_type);
m_dirty = true;
}
void GLCanvas3D::apply_selection_rotate(double angle_z_rad)
{
m_selection.setup_cache();
m_selection.rotate(angle_z_rad * m_selection_edit.direction, TransformationType(TransformationType::World_Relative_Joint));
m_dirty = true;
}
void GLCanvas3D::finish_selection_edit()
{
const SelectionEdit::Kind kind = m_selection_edit.kind;
m_selection_edit.kind = SelectionEdit::None;
if (kind == SelectionEdit::Move)
do_move(L("Tool move"));
else
do_rotate(L("Tool Rotate"));
m_gizmos.update_data();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
refresh_camera_scene_box();
m_dirty = true;
}
// Keys held since their last key-up, shared by every canvas because a shortcut can move the
// focus, and with it the key-up, to another one.
static std::set<int> s_keys_down;
static bool key_repeats(int key)
{
for (auto it = s_keys_down.begin(); it != s_keys_down.end();) // drops keys released while no canvas had the focus
it = wxGetKeyState(wxKeyCode(*it)) ? std::next(it) : s_keys_down.erase(it);
return !s_keys_down.insert(key).second;
}
static void key_released(int key) { s_keys_down.erase(key); }
void GLCanvas3D::on_key(wxKeyEvent& evt)
{
@@ -3811,45 +3721,11 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
#endif
static GLCanvas3D const * thiz = nullptr;
static TranslationProcessor translationProcessor(nullptr, nullptr);
if (thiz != this) {
thiz = this;
translationProcessor = TranslationProcessor(
[this]() {
do_move(L("Tool move"));
m_gizmos.update_data();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
refresh_camera_scene_box();
m_dirty = true;
},
[this](const Vec3d& direction, bool slow, bool camera_space) {
m_selection.setup_cache();
double multiplier = slow ? 1.0 : 10.0;
Vec3d displacement;
if (camera_space) {
Eigen::Matrix<double, 3, 3, Eigen::DontAlign> inv_view_3x3 = wxGetApp().plater()->get_camera().get_view_matrix().inverse().matrix().block(0, 0, 3, 3);
displacement = multiplier * (inv_view_3x3 * direction);
displacement.z() = 0.0;
}
else
displacement = multiplier * direction;
TransformationType trafo_type;
trafo_type.set_relative();
m_selection.translate(displacement, trafo_type);
m_dirty = true;
}
);}
const int keyCode = evt.GetKeyCode();
if (evt.GetEventType() == wxEVT_KEY_DOWN)
m_key_down = { keyCode, key_repeats(keyCode) };
else if (evt.GetEventType() == wxEVT_KEY_UP)
key_released(keyCode);
auto imgui = wxGetApp().imgui();
if (imgui->update_key_data(evt))
@@ -3863,23 +3739,8 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
wxGetApp().plater()->toggle_render_statistic_dialog();
m_dirty = true;
#endif
} else if ((evt.ShiftDown() && evt.ControlDown() && keyCode == WXK_RETURN) ||
(evt.ShiftDown() && evt.AltDown() && keyCode == WXK_RETURN)) {
wxGetApp().plater()->toggle_show_wireframe();
m_dirty = true;
}
else if (m_tab_down && keyCode == WXK_TAB && !evt.HasAnyModifiers()) {
// Enable switching between 3D and Preview with Tab
// m_canvas->HandleAsNavigationKey(evt); // XXX: Doesn't work in some cases / on Linux
post_event(SimpleEvent(EVT_GLCANVAS_TAB));
}
else if (keyCode == WXK_TAB && evt.ShiftDown() && !evt.ControlDown() && ! wxGetApp().is_gcode_viewer()) {
// Collapse side-panel with Shift+Tab
post_event(SimpleEvent(EVT_GLCANVAS_COLLAPSE_SIDEBAR));
}
else if (keyCode == WXK_SHIFT) {
translationProcessor.process(evt);
if (m_picking_enabled && m_rectangle_selection.is_dragging()) {
_update_selection_from_hover();
m_rectangle_selection.stop_dragging();
@@ -3905,70 +3766,15 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
else if (keyCode == WXK_CONTROL)
m_dirty = true;
else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) {
translationProcessor.process(evt);
switch (keyCode)
{
case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP:
case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN:
{
do_rotate(L("Tool Rotate"));
m_gizmos.update_data();
// BBS
//wxGetApp().obj_manipul()->set_dirty();
// Let the plater know that the dragging finished, so a delayed refresh
// of the scene with the background processing data should be performed.
post_event(SimpleEvent(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED));
// updates camera target constraints
refresh_camera_scene_box();
m_dirty = true;
break;
}
default: { break; }
}
}
// BBS: add select view logic
if (evt.ControlDown()) {
switch (keyCode) {
case '0':
case WXK_NUMPAD0: //0 on numpad
{ select_view("plate");
zoom_to_bed();
break;
}
case '1':
case WXK_NUMPAD1: //1 on numpad
{ select_view("top"); break; }
case '2':
case WXK_NUMPAD2: //2 on numpad
{ select_view("bottom"); break; }
case '3':
case WXK_NUMPAD3: //3 on numpad
{ select_view("front"); break; }
case '4':
case WXK_NUMPAD4: //4 on numpad
{ select_view("rear"); break; }
case '5':
case WXK_NUMPAD5: //5 on numpad
{ select_view("left"); break; }
case '6':
case WXK_NUMPAD6: //6 on numpad
{ select_view("right"); break; }
case '7':
case WXK_NUMPAD7: //7 on numpad
{ select_plate(); break; }
default: break;
}
}
else if (m_selection_edit.kind != SelectionEdit::None && keyCode == m_selection_edit.key)
finish_selection_edit();
}
else if (evt.GetEventType() == wxEVT_KEY_DOWN) {
m_tab_down = keyCode == WXK_TAB && !evt.HasAnyModifiers();
if (handle_shortcut(KeyChord::from_event(evt)))
return;
if (keyCode == WXK_SHIFT) {
translationProcessor.process(evt);
if (m_selection_edit.kind == SelectionEdit::Move)
apply_selection_move(true, evt.CmdDown());
if (m_picking_enabled /*&& (m_gizmos.get_current_type() != GLGizmosManager::SlaSupports)*/)
{
@@ -3985,68 +3791,6 @@ void GLCanvas3D::on_key(wxKeyEvent& evt)
}
else if (keyCode == WXK_CONTROL)
m_dirty = true;
else if (m_gizmos.is_enabled() && !m_selection.is_empty() && m_canvas_type != CanvasAssembleView) {
auto _do_rotate = [this](double angle_z_rad) {
m_selection.setup_cache();
m_selection.rotate(Vec3d(0.0, 0.0, angle_z_rad), TransformationType(TransformationType::World_Relative_Joint));
m_dirty = true;
// wxGetApp().obj_manipul()->set_dirty();
};
translationProcessor.process(evt);
switch (keyCode)
{
case WXK_NUMPAD_PAGEUP: case WXK_PAGEUP: { _do_rotate(0.25 * M_PI); break; }
case WXK_NUMPAD_PAGEDOWN: case WXK_PAGEDOWN: { _do_rotate(-0.25 * M_PI); break; }
default: { break; }
}
} else if (!m_gizmos.is_enabled()) {
// DoubleSlider navigation in Preview
if (m_canvas_type == CanvasPreview) {
IMSlider *m_layers_slider = get_gcode_viewer().get_layers_slider();
IMSlider *m_moves_slider = get_gcode_viewer().get_moves_slider();
int increment = (evt.CmdDown() || evt.ShiftDown()) ? 5 : 1;
if ((evt.CmdDown() || evt.ShiftDown()) && evt.GetKeyCode() == 'G') {
m_layers_slider->show_go_to_layer(true);
}
else if (keyCode == WXK_UP || keyCode == WXK_DOWN) {
int new_pos;
if (m_layers_slider->GetSelection() == ssHigher) {
new_pos = keyCode == WXK_UP ? m_layers_slider->GetHigherValue() + increment : m_layers_slider->GetHigherValue() - increment;
m_layers_slider->SetHigherValue(new_pos);
m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue());
}
else if (m_layers_slider->GetSelection() == ssLower) {
new_pos = keyCode == WXK_UP ? m_layers_slider->GetLowerValue() + increment : m_layers_slider->GetLowerValue() - increment;
m_layers_slider->SetLowerValue(new_pos);
}
} else if (keyCode == WXK_LEFT) {
if (m_moves_slider->GetHigherValue() == m_moves_slider->GetMinValue() && (m_layers_slider->GetHigherValue() > m_layers_slider->GetMinValue())) {
m_layers_slider->SetHigherValue(m_layers_slider->GetHigherValue() - 1);
m_moves_slider->SetHigherValue(m_moves_slider->GetMaxValue());
} else {
m_moves_slider->SetHigherValue(m_moves_slider->GetHigherValue() - increment);
}
} else if (keyCode == WXK_RIGHT) {
if (m_moves_slider->GetHigherValue() == m_moves_slider->GetMaxValue() && (m_layers_slider->GetHigherValue() < m_layers_slider->GetMaxValue())) {
m_layers_slider->SetHigherValue(m_layers_slider->GetHigherValue() + 1);
m_moves_slider->SetHigherValue(m_moves_slider->GetMinValue());
} else {
m_moves_slider->SetHigherValue(m_moves_slider->GetHigherValue() + increment);
}
} else if (keyCode == WXK_HOME || keyCode == WXK_END) {
const int new_pos = keyCode == WXK_HOME ? m_moves_slider->GetMinValue() : m_moves_slider->GetMaxValue();
m_moves_slider->SetHigherValue(new_pos);
m_moves_slider->set_as_dirty();
}
if (m_layers_slider->is_dirty() && m_layers_slider->is_one_layer())
m_layers_slider->SetLowerValue(m_layers_slider->GetHigherValue());
m_dirty = true;
}
}
}
}
else return;
@@ -7115,7 +6859,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "add";
item.icon_filename = m_is_dark ? "toolbar_open_dark.svg" : "toolbar_open.svg";
item.tooltip = _utf8(L("Add")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
item.sprite_id = 0;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
item.enabling_callback = []()->bool {return wxGetApp().plater()->can_add_model(); };
@@ -7133,7 +6876,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "orient";
item.icon_filename = m_is_dark ? "toolbar_orient_dark.svg" : "toolbar_orient.svg";
item.tooltip = _utf8(L("Auto orient all/selected objects")) + " [Q]\n" + _utf8(L("Auto orient all objects on current plate")) + " [" + _utf8(L("Shift+")) + "Q]";
item.sprite_id++;
item.left.render_callback = nullptr;
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
@@ -7155,7 +6897,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "arrange";
item.icon_filename = m_is_dark ? "toolbar_arrange_dark.svg" : "toolbar_arrange.svg";
item.tooltip = _utf8(L("Arrange all objects")) + " [A]\n" + _utf8(L("Arrange objects on selected plates")) + " [" + _utf8(L("Shift+")) + "A]";
item.sprite_id++;
item.left.action_callback = []() {};
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
@@ -7179,7 +6920,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "more";
item.icon_filename = m_is_dark ? "instance_add_dark.svg" : "instance_add.svg";
item.tooltip = _utf8(L("Add instance")) + " [+]";
item.sprite_id++;
item.left.render_callback = nullptr;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_MORE)); };
@@ -7191,7 +6931,6 @@ bool GLCanvas3D::_init_main_toolbar()
item.name = "fewer";
item.icon_filename = m_is_dark ? "instance_remove_dark.svg" : "instance_remove.svg";
item.tooltip = _utf8(L("Remove instance")) + " [-]";
item.sprite_id++;
item.left.render_callback = nullptr;
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
@@ -7241,9 +6980,20 @@ bool GLCanvas3D::_init_main_toolbar()
if (!m_main_toolbar.add_item(item))
return false;
update_shortcut_tooltips();
return true;
}
void GLCanvas3D::update_shortcut_tooltips()
{
const ShortcutRegistry& shortcuts = wxGetApp().shortcuts();
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("add"), shortcuts.with_key(_u8L("Add"), Shortcut::ImportModel));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("orient"), shortcuts.with_key(_u8L("Auto orient all/selected objects"), Shortcut::Orient) + "\n" + shortcuts.with_key(_u8L("Auto orient all objects on current plate"), Shortcut::OrientPlate));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("arrange"), shortcuts.with_key(_u8L("Arrange all objects"), Shortcut::Arrange) + "\n" + shortcuts.with_key(_u8L("Arrange objects on selected plates"), Shortcut::ArrangePlate));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("more"), shortcuts.with_key(_u8L("Add instance"), Shortcut::AddInstance));
m_main_toolbar.set_tooltip(m_main_toolbar.get_item_id("fewer"), shortcuts.with_key(_u8L("Remove instance"), Shortcut::RemoveInstance));
}
//BBS: GUI refactor: GLToolbar
bool GLCanvas3D::_init_select_plate_toolbar()
{