mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-10-31 04:31:15 -06:00 
			
		
		
		
	Allow wipe tower rotation by the rotation gizmo
This commit is contained in:
		
							parent
							
								
									045879f68a
								
							
						
					
					
						commit
						5f226c5d7f
					
				
					 7 changed files with 135 additions and 89 deletions
				
			
		|  | @ -709,12 +709,15 @@ int GLVolumeCollection::load_wipe_tower_preview( | |||
|     brim_mesh.translate(-brim_width, -brim_width, 0.f); | ||||
|     mesh.merge(brim_mesh); | ||||
| 
 | ||||
|     mesh.rotate(rotation_angle, &origin_of_rotation); // rotates the box according to the config rotation setting
 | ||||
|     //mesh.rotate(rotation_angle, &origin_of_rotation); // rotates the box according to the config rotation setting
 | ||||
|      | ||||
| 
 | ||||
|     this->volumes.emplace_back(new GLVolume(color)); | ||||
|     GLVolume &v = *this->volumes.back(); | ||||
|     v.indexed_vertex_array.load_mesh(mesh, use_VBOs); | ||||
|     v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); | ||||
|     v.set_volume_rotation(Vec3d(0., 0., (M_PI/180.) * rotation_angle)); | ||||
| 
 | ||||
|     // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
 | ||||
|     v.bounding_box = v.indexed_vertex_array.bounding_box(); | ||||
|     v.indexed_vertex_array.finalize_geometry(use_VBOs); | ||||
|  |  | |||
|  | @ -1201,6 +1201,7 @@ wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); | |||
| wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); | ||||
| wxDEFINE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); | ||||
|  | @ -3090,6 +3091,10 @@ void GLCanvas3D::do_rotate() | |||
|     for (const GLVolume* v : m_volumes.volumes) | ||||
|     { | ||||
|         int object_idx = v->object_idx(); | ||||
|         if (object_idx == 1000) { // the wipe tower
 | ||||
|             Vec3d offset = v->get_volume_offset(); | ||||
|             post_event(Vec3dEvent(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3d(offset(0), offset(1), v->get_volume_rotation()(2)))); | ||||
|         } | ||||
|         if ((object_idx < 0) || ((int)m_model->objects.size() <= object_idx)) | ||||
|             continue; | ||||
| 
 | ||||
|  | @ -4289,6 +4294,7 @@ void GLCanvas3D::_render_selection_sidebar_hints() const | |||
|         m_shader.stop_using(); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| void GLCanvas3D::_update_volumes_hover_state() const | ||||
| { | ||||
|     for (GLVolume* v : m_volumes.volumes) | ||||
|  |  | |||
|  | @ -116,6 +116,7 @@ wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_MOVED, SimpleEvent); | |||
| wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_MOVED, Vec3dEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_ROTATED, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_INSTANCE_SCALED, SimpleEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_WIPETOWER_ROTATED, Vec3dEvent); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, Event<bool>); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_UPDATE_GEOMETRY, Vec3dsEvent<2>); | ||||
| wxDECLARE_EVENT(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, SimpleEvent); | ||||
|  |  | |||
|  | @ -102,7 +102,7 @@ protected: | |||
|             m_gizmos[i].set_hover_id((m_hover_id == i) ? 0 : -1); | ||||
|         } | ||||
|     } | ||||
|     virtual bool on_is_activable(const Selection& selection) const { return !selection.is_wipe_tower(); } | ||||
|     virtual bool on_is_activable(const Selection& selection) const { return true; } | ||||
|     virtual void on_enable_grabber(unsigned int id) | ||||
|     { | ||||
|         if ((0 <= id) && (id < 3)) | ||||
|  |  | |||
|  | @ -4,6 +4,7 @@ | |||
| #include "slic3r/GUI/3DScene.hpp" | ||||
| #include "slic3r/GUI/GUI_App.hpp" | ||||
| #include "slic3r/GUI/GUI_ObjectManipulation.hpp" | ||||
| #include "slic3r/GUI/PresetBundle.hpp" | ||||
| 
 | ||||
| #include <GL/glew.h> | ||||
| #include <wx/glcanvas.h> | ||||
|  | @ -264,8 +265,12 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) | |||
| 
 | ||||
|     const Selection& selection = canvas.get_selection(); | ||||
| 
 | ||||
|     bool enable_move_z = !selection.is_wipe_tower(); | ||||
|     enable_grabber(Move, 2, enable_move_z); | ||||
|     bool is_wipe_tower = selection.is_wipe_tower(); | ||||
|     enable_grabber(Move, 2, !is_wipe_tower); | ||||
|     enable_grabber(Move, 2, !is_wipe_tower); | ||||
|     enable_grabber(Rotate, 0, !is_wipe_tower); | ||||
|     enable_grabber(Rotate, 1, !is_wipe_tower); | ||||
|      | ||||
|     bool enable_scale_xyz = selection.is_single_full_instance() || selection.is_single_volume() || selection.is_single_modifier(); | ||||
|     for (int i = 0; i < 6; ++i) | ||||
|     { | ||||
|  | @ -290,6 +295,14 @@ void GLGizmosManager::update_data(GLCanvas3D& canvas) | |||
|         set_flattening_data(nullptr); | ||||
|         set_sla_support_data(nullptr, selection); | ||||
|     } | ||||
|     else if (is_wipe_tower) | ||||
|     { | ||||
|         DynamicPrintConfig& config = wxGetApp().preset_bundle->prints.get_edited_preset().config; | ||||
|         set_scale(Vec3d::Ones()); | ||||
|         set_rotation(Vec3d(0., 0., (M_PI/180.) * dynamic_cast<const ConfigOptionFloat*>(config.option("wipe_tower_rotation_angle"))->value)); | ||||
|         set_flattening_data(nullptr); | ||||
|         set_sla_support_data(nullptr, selection); | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         set_scale(Vec3d::Ones()); | ||||
|  |  | |||
|  | @ -1307,6 +1307,7 @@ struct Plater::priv | |||
|     void on_object_select(SimpleEvent&); | ||||
|     void on_right_click(Vec2dEvent&); | ||||
|     void on_wipetower_moved(Vec3dEvent&); | ||||
|     void on_wipetower_rotated(Vec3dEvent&); | ||||
|     void on_update_geometry(Vec3dsEvent<2>&); | ||||
|     void on_3dcanvas_mouse_dragging_finished(SimpleEvent&); | ||||
| 
 | ||||
|  | @ -1437,6 +1438,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|         { if (evt.data == 1) this->q->increase_instances(); else if (this->can_decrease_instances()) this->q->decrease_instances(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_MOVED, [this](SimpleEvent&) { update(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_MOVED, &priv::on_wipetower_moved, this); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_WIPETOWER_ROTATED, &priv::on_wipetower_rotated, this); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_ROTATED, [this](SimpleEvent&) { update(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_INSTANCE_SCALED, [this](SimpleEvent&) { update(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_ENABLE_ACTION_BUTTONS, [this](Event<bool> &evt) { this->sidebar->enable_buttons(evt.data); }); | ||||
|  | @ -1444,6 +1446,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) | |||
|     view3D_canvas->Bind(EVT_GLCANVAS_MOUSE_DRAGGING_FINISHED, &priv::on_3dcanvas_mouse_dragging_finished, this); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); | ||||
|     view3D_canvas->Bind(EVT_GLCANVAS_RESETGIZMOS, [this](SimpleEvent&) { reset_all_gizmos(); }); | ||||
| 
 | ||||
|     // 3DScene/Toolbar:
 | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_ADD, &priv::on_action_add, this); | ||||
|     view3D_canvas->Bind(EVT_GLTOOLBAR_DELETE, [q](SimpleEvent&) { q->remove_selected(); }); | ||||
|  | @ -2851,6 +2854,15 @@ void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) | |||
|     wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::on_wipetower_rotated(Vec3dEvent& evt) | ||||
| { | ||||
|     DynamicPrintConfig cfg; | ||||
|     cfg.opt<ConfigOptionFloat>("wipe_tower_x", true)->value = evt.data(0); | ||||
|     cfg.opt<ConfigOptionFloat>("wipe_tower_y", true)->value = evt.data(1); | ||||
|     cfg.opt<ConfigOptionFloat>("wipe_tower_rotation_angle", true)->value = Geometry::rad2deg(evt.data(2)); | ||||
|     wxGetApp().get_tab(Preset::TYPE_PRINT)->load_config(cfg); | ||||
| } | ||||
| 
 | ||||
| void Plater::priv::on_update_geometry(Vec3dsEvent<2>&) | ||||
| { | ||||
|     // TODO
 | ||||
|  |  | |||
|  | @ -492,100 +492,111 @@ void Selection::rotate(const Vec3d& rotation, TransformationType transformation_ | |||
|     // Only relative rotation values are allowed in the world coordinate system.
 | ||||
|     assert(!transformation_type.world() || transformation_type.relative()); | ||||
| 
 | ||||
|     int rot_axis_max = 0; | ||||
|     if (rotation.isApprox(Vec3d::Zero())) | ||||
|     { | ||||
|         for (unsigned int i : m_list) | ||||
|     if (!is_wipe_tower()) { | ||||
|         int rot_axis_max = 0; | ||||
|         if (rotation.isApprox(Vec3d::Zero())) | ||||
|         { | ||||
|             GLVolume &volume = *(*m_volumes)[i]; | ||||
|             if (m_mode == Instance) | ||||
|             { | ||||
|                 volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); | ||||
|                 volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); | ||||
|             } | ||||
|             else if (m_mode == Volume) | ||||
|             { | ||||
|                 volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); | ||||
|                 volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     else | ||||
|     { | ||||
|         //FIXME this does not work for absolute rotations (transformation_type.absolute() is true)
 | ||||
|         rotation.cwiseAbs().maxCoeff(&rot_axis_max); | ||||
| 
 | ||||
|         // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it.
 | ||||
|         std::vector<int> object_instance_first(m_model->objects.size(), -1); | ||||
|         auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { | ||||
|             int first_volume_idx = object_instance_first[volume.object_idx()]; | ||||
|             if (rot_axis_max != 2 && first_volume_idx != -1) { | ||||
|                 // Generic rotation, but no rotation around the Z axis.
 | ||||
|                 // Always do a local rotation (do not consider the selection to be a rigid body).
 | ||||
|                 assert(is_approx(rotation.z(), 0.0)); | ||||
|                 const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; | ||||
|                 const Vec3d    &rotation = first_volume.get_instance_rotation(); | ||||
|                 double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); | ||||
|                 volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); | ||||
|             } | ||||
|             else { | ||||
|                 // extracts rotations from the composed transformation
 | ||||
|                 Vec3d new_rotation = transformation_type.world() ? | ||||
|                     Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : | ||||
|                     transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); | ||||
|                 if (rot_axis_max == 2 && transformation_type.joint()) { | ||||
|                     // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
 | ||||
|                     Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); | ||||
|                     volume.set_instance_offset(m_cache.dragging_center + offset); | ||||
|                 } | ||||
|                 volume.set_instance_rotation(new_rotation); | ||||
|                 object_instance_first[volume.object_idx()] = i; | ||||
|             } | ||||
|         }; | ||||
| 
 | ||||
|         for (unsigned int i : m_list) | ||||
|         { | ||||
|             GLVolume &volume = *(*m_volumes)[i]; | ||||
|             if (is_single_full_instance()) | ||||
|                 rotate_instance(volume, i); | ||||
|             else if (is_single_volume() || is_single_modifier()) | ||||
|             { | ||||
|                 if (transformation_type.independent()) | ||||
|                     volume.set_volume_rotation(volume.get_volume_rotation() + rotation); | ||||
|                 else | ||||
|                 { | ||||
|                     Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); | ||||
|                     Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); | ||||
|                     volume.set_volume_rotation(new_rotation); | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             for (unsigned int i : m_list) | ||||
|             { | ||||
|                 GLVolume &volume = *(*m_volumes)[i]; | ||||
|                 if (m_mode == Instance) | ||||
|                     rotate_instance(volume, i); | ||||
|                 { | ||||
|                     volume.set_instance_rotation(m_cache.volumes_data[i].get_instance_rotation()); | ||||
|                     volume.set_instance_offset(m_cache.volumes_data[i].get_instance_position()); | ||||
|                 } | ||||
|                 else if (m_mode == Volume) | ||||
|                 { | ||||
|                     // extracts rotations from the composed transformation
 | ||||
|                     Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); | ||||
|                     Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); | ||||
|                     if (transformation_type.joint()) | ||||
|                     { | ||||
|                         Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; | ||||
|                         Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); | ||||
|                         volume.set_volume_offset(local_pivot + offset); | ||||
|                     } | ||||
|                     volume.set_volume_rotation(new_rotation); | ||||
|                     volume.set_volume_rotation(m_cache.volumes_data[i].get_volume_rotation()); | ||||
|                     volume.set_volume_offset(m_cache.volumes_data[i].get_volume_position()); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|         else { // this is not the wipe tower
 | ||||
|             //FIXME this does not work for absolute rotations (transformation_type.absolute() is true)
 | ||||
|             rotation.cwiseAbs().maxCoeff(&rot_axis_max); | ||||
| 
 | ||||
| #if !DISABLE_INSTANCES_SYNCH | ||||
|     if (m_mode == Instance) | ||||
|         synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); | ||||
|     else if (m_mode == Volume) | ||||
|         synchronize_unselected_volumes(); | ||||
| #endif // !DISABLE_INSTANCES_SYNCH
 | ||||
|             // For generic rotation, we want to rotate the first volume in selection, and then to synchronize the other volumes with it.
 | ||||
|             std::vector<int> object_instance_first(m_model->objects.size(), -1); | ||||
|             auto rotate_instance = [this, &rotation, &object_instance_first, rot_axis_max, transformation_type](GLVolume &volume, int i) { | ||||
|                 int first_volume_idx = object_instance_first[volume.object_idx()]; | ||||
|                 if (rot_axis_max != 2 && first_volume_idx != -1) { | ||||
|                     // Generic rotation, but no rotation around the Z axis.
 | ||||
|                     // Always do a local rotation (do not consider the selection to be a rigid body).
 | ||||
|                     assert(is_approx(rotation.z(), 0.0)); | ||||
|                     const GLVolume &first_volume = *(*m_volumes)[first_volume_idx]; | ||||
|                     const Vec3d    &rotation = first_volume.get_instance_rotation(); | ||||
|                     double z_diff = Geometry::rotation_diff_z(m_cache.volumes_data[first_volume_idx].get_instance_rotation(), m_cache.volumes_data[i].get_instance_rotation()); | ||||
|                     volume.set_instance_rotation(Vec3d(rotation(0), rotation(1), rotation(2) + z_diff)); | ||||
|                 } | ||||
|                 else { | ||||
|                     // extracts rotations from the composed transformation
 | ||||
|                     Vec3d new_rotation = transformation_type.world() ? | ||||
|                         Geometry::extract_euler_angles(Geometry::assemble_transform(Vec3d::Zero(), rotation) * m_cache.volumes_data[i].get_instance_rotation_matrix()) : | ||||
|                         transformation_type.absolute() ? rotation : rotation + m_cache.volumes_data[i].get_instance_rotation(); | ||||
|                     if (rot_axis_max == 2 && transformation_type.joint()) { | ||||
|                         // Only allow rotation of multiple instances as a single rigid body when rotating around the Z axis.
 | ||||
|                         Vec3d offset = Geometry::assemble_transform(Vec3d::Zero(), Vec3d(0.0, 0.0, new_rotation(2) - m_cache.volumes_data[i].get_instance_rotation()(2))) * (m_cache.volumes_data[i].get_instance_position() - m_cache.dragging_center); | ||||
|                         volume.set_instance_offset(m_cache.dragging_center + offset); | ||||
|                     } | ||||
|                     volume.set_instance_rotation(new_rotation); | ||||
|                     object_instance_first[volume.object_idx()] = i; | ||||
|                 } | ||||
|             }; | ||||
| 
 | ||||
|             for (unsigned int i : m_list) | ||||
|             { | ||||
|                 GLVolume &volume = *(*m_volumes)[i]; | ||||
|                 if (is_single_full_instance()) | ||||
|                     rotate_instance(volume, i); | ||||
|                 else if (is_single_volume() || is_single_modifier()) | ||||
|                 { | ||||
|                     if (transformation_type.independent()) | ||||
|                         volume.set_volume_rotation(volume.get_volume_rotation() + rotation); | ||||
|                     else | ||||
|                     { | ||||
|                         Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); | ||||
|                         Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); | ||||
|                         volume.set_volume_rotation(new_rotation); | ||||
|                     } | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     if (m_mode == Instance) | ||||
|                         rotate_instance(volume, i); | ||||
|                     else if (m_mode == Volume) | ||||
|                     { | ||||
|                         // extracts rotations from the composed transformation
 | ||||
|                         Transform3d m = Geometry::assemble_transform(Vec3d::Zero(), rotation); | ||||
|                         Vec3d new_rotation = Geometry::extract_euler_angles(m * m_cache.volumes_data[i].get_volume_rotation_matrix()); | ||||
|                         if (transformation_type.joint()) | ||||
|                         { | ||||
|                             Vec3d local_pivot = m_cache.volumes_data[i].get_instance_full_matrix().inverse() * m_cache.dragging_center; | ||||
|                             Vec3d offset = m * (m_cache.volumes_data[i].get_volume_position() - local_pivot); | ||||
|                             volume.set_volume_offset(local_pivot + offset); | ||||
|                         } | ||||
|                         volume.set_volume_rotation(new_rotation); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|     #if !DISABLE_INSTANCES_SYNCH | ||||
|         if (m_mode == Instance) | ||||
|             synchronize_unselected_instances((rot_axis_max == 2) ? SYNC_ROTATION_NONE : SYNC_ROTATION_GENERAL); | ||||
|         else if (m_mode == Volume) | ||||
|             synchronize_unselected_volumes(); | ||||
|     #endif // !DISABLE_INSTANCES_SYNCH
 | ||||
|     } | ||||
|     else { // it's the wipe tower that's selected and being rotated
 | ||||
|         GLVolume& volume = *((*m_volumes)[*m_list.begin()]); // the wipe tower is always alone in the selection
 | ||||
| 
 | ||||
|         // make sure the wipe tower rotates around its center, not origin
 | ||||
|         // we can assume that only Z rotation changes
 | ||||
|         Vec3d center_local = volume.transformed_bounding_box().center() - volume.get_volume_offset(); | ||||
|         Vec3d center_local_new = Eigen::AngleAxisd(rotation(2)-volume.get_volume_rotation()(2), Vec3d(0, 0, 1)) * center_local; | ||||
|         volume.set_volume_rotation(rotation); | ||||
|         volume.set_volume_offset(volume.get_volume_offset() + center_local - center_local_new); | ||||
|     } | ||||
| 
 | ||||
|     m_bounding_box_dirty = true; | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lukas Matena
						Lukas Matena