NEW:add groove cut function

Jira:STUDIO-4227
Approximately 70% of the code comes from Prusa,thanks for PrusaSlcer and YuSanka

commit 492e356a21734b3503caae115fbb280da5fbaa22
Author: YuSanka <yusanka@gmail.com>
Date:   Thu Aug 3 16:09:28 2023 +0200
    CutGizmo: Fixed and improved Undo/Redo.
...

Change-Id: I63abb69180aec2ab0ce9bc8f30477d9e9a25a5fb
This commit is contained in:
zhou.xu 2023-09-21 10:39:13 +08:00 committed by Lane.Wei
parent 88e27d84c2
commit f9f44d0785
13 changed files with 1451 additions and 689 deletions

View File

@ -1811,7 +1811,7 @@ void ModelObject::apply_cut_attributes(ModelObjectCutAttributes attributes)
void ModelObject::clone_for_cut(ModelObject **obj)
{
(*obj) = ModelObject::new_clone(*this);
(*obj)->set_model(nullptr);
(*obj)->set_model(this->get_model());
(*obj)->sla_support_points.clear();
(*obj)->sla_drain_holes.clear();
(*obj)->sla_points_status = sla::PointsStatus::NoPoints;

View File

@ -234,6 +234,11 @@ private:
friend class ModelObject;
};
enum class CutMode : int {
cutPlanar,
cutTongueAndGroove
};
enum class CutConnectorType : int {
Plug,
Dowel,

View File

@ -70,6 +70,7 @@ using Transform2d = Eigen::Transform<double, 2, Eigen::Affine, Eigen::DontAli
using Transform3f = Eigen::Transform<float, 3, Eigen::Affine, Eigen::DontAlign>;
using Transform3d = Eigen::Transform<double, 3, Eigen::Affine, Eigen::DontAlign>;
using ColorRGBA = std::array<float, 4>;
// I don't know why Eigen::Transform::Identity() return a const object...
template<int N, class T> Transform<N, T> identity() { return Transform<N, T>::Identity(); }
inline const auto &identity3f = identity<3, float>;

File diff suppressed because it is too large Load Diff

View File

@ -67,16 +67,25 @@ private:
class GLGizmoAdvancedCut : public GLGizmoRotate3D
{
struct Rotate_data {
double angle;
Axis ax;
Rotate_data(double an, Axis a)
: angle(an), ax(a)
{
}
};
private:
double m_snap_step{1.0};
// archived values
Vec3d m_ar_plane_center{Vec3d::Zero()};
// plane_center and so on
Vec3d m_plane_center{Vec3d::Zero()};//old name:m_cut_plane_center
Vec3d m_plane_center_drag_start{Vec3d::Zero()};
Vec3d m_plane_drag_start{Vec3d::Zero()};
Vec3d m_bb_center{Vec3d::Zero()};//box center
Vec3d m_center_offset{Vec3d::Zero()};
Vec3d m_plane_normal{Vec3d::UnitZ()}; //old namce:Vec3d m_cut_normal//m_cut_plane_normal
Vec3d m_plane_x_direction{Vec3d::UnitY()};
Vec3d m_clp_normal{Vec3d::Ones()};
// data to check position of the cut palne center on gizmo activation
Vec3d m_min_pos{Vec3d::Zero()};
Vec3d m_max_pos{Vec3d::Zero()};
static const double Offset;
static const double Margin;
static const std::array<float, 4> GrabberColor;
@ -89,18 +98,16 @@ private:
double m_start_height;
Vec3d m_rotation;
//Vec3d m_current_base_rotation;
std::vector<Rotate_data> m_rotate_cmds;
Vec3d m_buffered_rotation;
double m_buffered_movement;
double m_buffered_height;
Vec3d m_drag_pos;
Vec3d m_drag_pos_start;
bool m_keep_upper;
bool m_keep_lower;
bool m_cut_to_parts;
bool m_cut_to_parts{false};
bool m_place_on_cut_upper{true};
bool m_place_on_cut_lower{false};
bool m_rotate_upper{false};
@ -110,24 +117,51 @@ private:
double m_segment_smoothing_alpha;
int m_segment_number;
std::array<Vec3d, 4> m_cut_plane_points;
mutable Grabber m_move_grabber;
mutable Grabber m_move_z_grabber;
mutable Grabber m_move_x_grabber;
unsigned int m_last_active_id;
bool m_connectors_editing{false};
std::vector<size_t> m_invalid_connectors_idxs;
bool m_show_shortcuts{false};
std::vector<std::pair<wxString, wxString>> m_shortcuts;
std::vector<std::pair<wxString, wxString>> m_connector_shortcuts;
std::vector<std::pair<wxString, wxString>> m_cut_plane_shortcuts;
std::vector<std::pair<wxString, wxString>> m_cut_groove_shortcuts;
double m_label_width{150.0};
double m_control_width{ 200.0 };
double m_editing_window_width;
CutMode m_cut_mode{CutMode::cutPlanar};
CutConnectorType m_connector_type;
size_t m_connector_style;
size_t m_connector_shape_id;
// Dovetail para
Groove m_groove;
bool m_groove_editing{false};
float m_contour_width{0.4f};
float m_cut_plane_radius_koef{1.5f};
float m_shortcut_label_width{-1.f};
bool m_is_slider_editing_done{false};
bool m_hide_cut_plane{false};
double m_radius{0.0};
double m_grabber_radius{0.0};
double m_grabber_connection_len{0.0};
Vec3d m_cut_plane_start_move_pos{Vec3d::Zero()};
bool m_cut_plane_as_circle{false};
std::vector<Vec3d> m_groove_vertices;
bool m_was_cut_plane_dragged{false};
bool m_was_contour_selected{false};
bool m_is_dragging{false};
PartSelection * m_part_selection{nullptr};
// dragging angel in hovered axes
double m_rotate_angle{0.0};
bool m_imperial_units{false};
BoundingBoxf3 m_bounding_box;
BoundingBoxf3 m_transformed_bounding_box;
float m_connector_depth_ratio{3.f};
float m_connector_depth_ratio_tolerance{0.1f};
@ -138,20 +172,18 @@ private:
float m_snap_bulge_proportion{0.15f};
TriangleMesh m_connector_mesh;
bool m_has_invalid_connector{false};
// remember the connectors which is selected
mutable std::vector<bool> m_selected;
int m_selected_count{0};
Vec3d m_cut_plane_center{Vec3d::Zero()};
Vec3d m_cut_plane_normal{Vec3d::UnitZ()};
GLModel m_plane; // old name:PickingModel
Vec3d m_cut_line_begin{Vec3d::Zero()};
Vec3d m_cut_line_end{Vec3d::Zero()};
Transform3d m_rotate_matrix{Transform3d::Identity()};
Transform3d m_start_dragging_m{Transform3d::Identity()};
std::map<CutConnectorAttributes, GLModel> m_shapes;
struct InvalidConnectorsStatistics
@ -177,16 +209,15 @@ public:
bool on_key(wxKeyEvent &evt);
double get_movement() const { return m_movement; }
void set_movement(double movement) const;
void finish_rotation();
std::string get_tooltip() const override;
BoundingBoxf3 bounding_box() const;
//BoundingBoxf3 transformed_bounding_box(const Vec3d &plane_center, bool revert_move = false) const;
BoundingBoxf3 transformed_bounding_box(const Vec3d &plane_center, const Transform3d &rotation_m = Transform3d::Identity()) const;
bool is_looking_forward() const;
bool unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &pos, Vec3d &pos_world);
bool unproject_on_cut_plane(const Vec2d &mouse_pos, Vec3d &pos, Vec3d &pos_world, bool respect_contours = true);
virtual bool apply_clipping_plane() { return m_connectors_editing; }
static void render_glmodel(GLModel &model, const std::array<float, 4> &color, Transform3d view_model_matrix, bool for_picking = false);
@ -194,12 +225,15 @@ protected:
virtual bool on_init();
virtual void on_load(cereal::BinaryInputArchive &ar) override;
virtual void on_save(cereal::BinaryOutputArchive &ar) const override;
virtual void data_changed(bool is_serializing) override;
virtual std::string on_get_name() const;
virtual void on_set_state();
virtual bool on_is_activable() const;
virtual CommonGizmosDataID on_get_requirements() const override;
virtual void on_start_dragging() override;
virtual void on_stop_dragging() override;
virtual void update_plate_center(Axis axis_type, double projection, bool is_abs_move); // old name:dragging_grabber_move
virtual void update_plate_normal_boundingbox_clipper(Vec3d rotation); // old name:dragging_grabber_rotation
virtual void on_update(const UpdateData& data);
virtual void on_render();
virtual void on_render_for_picking();
@ -232,19 +266,16 @@ protected:
private:
void perform_cut(const Selection& selection);
bool can_perform_cut() const;
void apply_connectors_in_model(ModelObject *mo, bool &create_dowels_as_separate_object);
void apply_connectors_in_model(ModelObject *mo, int &dowels_count);
bool is_selection_changed(bool alt_down, bool shift_down);
void select_connector(int idx, bool select);
double calc_projection(const Vec3d &drag_pos, const Linef3 &mouse_ray, const Vec3d &project_dir) const;
Vec3d calc_plane_normal(const std::array<Vec3d, 4>& plane_points) const;
Vec3d calc_plane_center(const std::array<Vec3d, 4>& plane_points) const;
Vec3d get_plane_normal() const;
Vec3d get_plane_center() const;
void update_plane_points();
std::array<Vec3d, 4> get_plane_points() const;
std::array<Vec3d, 4> get_plane_points_world_coord() const;
void reset_cut_plane();
void reset_all();
@ -270,9 +301,27 @@ private:
bool delete_selected_connectors();
bool is_outside_of_cut_contour(size_t idx, const CutConnectors &connectors, const Vec3d cur_pos);
bool is_conflict_for_connector(size_t idx, const CutConnectors &connectors, const Vec3d cur_pos);
void check_conflict_for_all_connectors();
//deal groove
void switch_to_mode(CutMode new_mode);
void flip_cut_plane();
void update_plane_model();
void init_picking_models();
bool has_valid_groove() const;
bool has_valid_contour() const;
void reset_cut_by_contours();
void process_contours();
void toggle_model_objects_visibility(bool show_in_3d = false);
void delete_part_selection();
void deal_connector_pos_by_type(Vec3d &pos, float &height, CutConnectorType, CutConnectorStyle, bool looking_forward, bool is_edit, const Vec3d &clp_normal);
void update_bb();
void check_and_update_connectors_state();
void set_center(const Vec3d &center, bool update_tbb = false);
bool set_center_pos(const Vec3d &center_pos, bool update_tbb = false);
void invalidate_cut_plane();
void rotate_vec3d_around_plane_center(Vec3d &vec, const Transform3d &rotate_matrix, const Vec3d &center);
Transform3d get_cut_matrix(const Selection &selection);
// render input window
bool render_cut_mode_combo(double label_width);
void render_cut_plane_input_window(float x, float y, float bottom_limit);
void init_connectors_input_window_data();
void render_connectors_input_window(float x, float y, float bottom_limit);

View File

@ -121,7 +121,7 @@ protected:
ImGuiWrapper* m_imgui;
bool m_first_input_window_render;
mutable std::string m_tooltip;
CommonGizmosDataPool* m_c;
CommonGizmosDataPool* m_c{nullptr};
GLModel m_cone;
GLModel m_cylinder;
GLModel m_sphere;
@ -190,7 +190,10 @@ public:
virtual void on_change_color_mode(bool is_dark) { m_is_dark_mode = is_dark; }
virtual std::string get_tooltip() const { return ""; }
/// <summary>
/// Is called when data (Selection) is changed
/// </summary>
virtual void data_changed(bool is_serializing){};
int get_count() { return ++count; }
std::string get_gizmo_name() { return on_get_name(); }

View File

@ -228,7 +228,7 @@ void InstancesHider::render_cut() const
}
glsafe(::glPushAttrib(GL_DEPTH_TEST));
glsafe(::glDisable(GL_DEPTH_TEST));
clipper->render_cut();
clipper->render_cut(mv->is_model_part() ? ColorRGBA{0.8f, 0.3f, 0.0f, 1.0f} : color_from_model_volume(*mv));
glsafe(::glPopAttrib());
glsafe(::glPopMatrix());
@ -365,33 +365,36 @@ std::vector<const MeshRaycaster*> Raycaster::raycasters() const
void ObjectClipper::on_update()
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
if (! mo)
return;
const ModelObject *mo = get_pool()->selection_info()->model_object();
if (!mo) return;
// which mesh should be cut?
std::vector<const TriangleMesh*> meshes;
bool has_hollowed = get_pool()->hollowed_mesh() && get_pool()->hollowed_mesh()->get_hollowed_mesh();
if (has_hollowed)
meshes.push_back(get_pool()->hollowed_mesh()->get_hollowed_mesh());
std::vector<const TriangleMesh *> meshes;
std::vector<Geometry::Transformation> trafos;
bool force_clipper_regeneration = false;
if (meshes.empty())
for (const ModelVolume* mv : mo->volumes)
meshes.push_back(&mv->mesh());
std::unique_ptr<MeshClipper> mc;
Geometry::Transformation mc_tr;
if (meshes != m_old_meshes) {
m_clippers.clear();
for (const TriangleMesh* mesh : meshes) {
m_clippers.emplace_back(new MeshClipper);
m_clippers.back()->set_mesh(*mesh);
if (!mc && meshes.empty()) {
for (const ModelVolume *mv : mo->volumes) {
meshes.emplace_back(&mv->mesh());
trafos.emplace_back(mv->get_transformation());
}
m_old_meshes = meshes;
}
if (has_hollowed)
m_clippers.front()->set_negative_mesh(*get_pool()->hollowed_mesh()->get_hollowed_interior());
if (mc || force_clipper_regeneration || meshes != m_old_meshes) {
m_clippers.clear();
for (size_t i = 0; i < meshes.size(); ++i) {
m_clippers.emplace_back(new MeshClipper, trafos[i]);
auto tri_mesh = new TriangleMesh(meshes[i]->its);
m_clippers.back().first->set_mesh(*tri_mesh);
}
m_old_meshes = std::move(meshes);
m_active_inst_bb_radius =
mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
if (mc) { m_clippers.emplace_back(std::move(mc), mc_tr); }
m_active_inst_bb_radius = mo->instance_bounding_box(get_pool()->selection_info()->get_active_instance()).radius();
}
}
@ -405,82 +408,35 @@ void ObjectClipper::on_release()
}
void ObjectClipper::render_cut() const
void CommonGizmosDataObjects::ObjectClipper::render_cut(const std::vector<size_t> *ignore_idxs) const
{
if (m_clp_ratio == 0.)
return;
const SelectionInfo* sel_info = get_pool()->selection_info();
const ModelObject* mo = sel_info->model_object();
Geometry::Transformation inst_trafo;
bool is_assem_cnv = get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView;
inst_trafo = is_assem_cnv ?
mo->instances[sel_info->get_active_instance()]->get_assemble_transformation() :
mo->instances[sel_info->get_active_instance()]->get_transformation();
auto offset_to_assembly = mo->instances[0]->get_offset_to_assembly();
if (m_clp_ratio == 0.) return;
const SelectionInfo * sel_info = get_pool()->selection_info();
const Geometry::Transformation inst_trafo = sel_info->model_object()->instances[sel_info->get_active_instance()]->get_transformation();
auto debug = sel_info->get_sla_shift();
std::vector<size_t> ignore_idxs_local = ignore_idxs ? *ignore_idxs : std::vector<size_t>();
size_t clipper_id = 0;
for (const ModelVolume* mv : mo->volumes) {
Geometry::Transformation vol_trafo = mv->get_transformation();
Geometry::Transformation trafo = inst_trafo * vol_trafo;
if (is_assem_cnv) {
trafo.set_offset(trafo.get_offset() + offset_to_assembly * (GLVolume::explosion_ratio - 1.0) + vol_trafo.get_offset() * (GLVolume::explosion_ratio - 1.0));
}
else {
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
}
auto& clipper = m_clippers[clipper_id];
clipper->set_plane(*m_clp);
clipper->set_transformation(trafo);
if (is_assem_cnv)
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), std::numeric_limits<double>::max()));
else
clipper->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
glsafe(::glPushMatrix());
// BBS
glsafe(::glColor3f(0.25f, 0.25f, 0.25f));
clipper->render_cut();
glsafe(::glPopMatrix());
for (auto &clipper : m_clippers) {
Geometry::Transformation trafo = inst_trafo * clipper.second;
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
clipper.first->set_plane(*m_clp);
clipper.first->set_transformation(trafo);
clipper.first->set_limiting_plane(ClippingPlane(Vec3d::UnitZ(), -SINKING_Z_THRESHOLD));
clipper.first->render_cut({1.0f, 0.37f, 0.0f, 1.0f}, &ignore_idxs_local);
clipper.first->render_contour({1.f, 1.f, 1.f, 1.f}, &ignore_idxs_local);
++clipper_id;
// Now update the ignore idxs. Find the first element belonging to the next clipper,
// and remove everything before it and decrement everything by current number of contours.
const int num_of_contours = clipper.first->get_number_of_contours();
ignore_idxs_local.erase(ignore_idxs_local.begin(),std::find_if(ignore_idxs_local.begin(), ignore_idxs_local.end(), [num_of_contours](size_t idx) {
return idx >= size_t(num_of_contours);
}));
for (size_t &idx : ignore_idxs_local) idx -= num_of_contours;
}
}
void ObjectClipper::set_position(double pos, bool keep_normal)
{
const ModelObject* mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
double z_shift = get_pool()->selection_info()->get_sla_shift();
//Vec3d camera_dir = wxGetApp().plater()->get_camera().get_dir_forward();
//if (abs(camera_dir(0)) > EPSILON || abs(camera_dir(1)) > EPSILON)
// camera_dir(2) = 0;
Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : /*-camera_dir;*/ -wxGetApp().plater()->get_camera().get_dir_forward();
Vec3d center;
if (get_pool()->get_canvas()->get_canvas_type() == GLCanvas3D::CanvasAssembleView) {
const SelectionInfo* sel_info = get_pool()->selection_info();
auto trafo = mo->instances[sel_info->get_active_instance()]->get_assemble_transformation();
auto offset_to_assembly = mo->instances[0]->get_offset_to_assembly();
center = trafo.get_offset() + offset_to_assembly * (GLVolume::explosion_ratio - 1.0);
}
else {
center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
}
float dist = normal.dot(center);
if (pos < 0.)
pos = m_clp_ratio;
m_clp_ratio = pos;
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2 * m_active_inst_bb_radius)));
get_pool()->get_canvas()->set_as_dirty();
}
void ObjectClipper::set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos)
{
m_clp.reset(new ClippingPlane(cpl_normal, cpl_offset));
@ -488,29 +444,66 @@ void ObjectClipper::set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset
get_pool()->get_canvas()->set_as_dirty();
}
void CommonGizmosDataObjects::ObjectClipper::set_behaviour(bool hide_clipped, bool fill_cut, double contour_width)
{
m_hide_clipped = hide_clipped;
for (auto &clipper : m_clippers)
clipper.first->set_behaviour(fill_cut, contour_width);
}
void ObjectClipper::set_position(double pos, bool keep_normal)
{
const ModelObject *mo = get_pool()->selection_info()->model_object();
int active_inst = get_pool()->selection_info()->get_active_instance();
double z_shift = get_pool()->selection_info()->get_sla_shift();
Vec3d normal = (keep_normal && m_clp) ? m_clp->get_normal() : -wxGetApp().plater()->get_camera().get_dir_forward();
const Vec3d &center = mo->instances[active_inst]->get_offset() + Vec3d(0., 0., z_shift);
float dist = normal.dot(center);
if (pos < 0.) pos = m_clp_ratio;
m_clp_ratio = pos;
m_clp.reset(new ClippingPlane(normal, (dist - (-m_active_inst_bb_radius) - m_clp_ratio * 2 * m_active_inst_bb_radius)));
get_pool()->get_canvas()->set_as_dirty();
}
int CommonGizmosDataObjects::ObjectClipper::get_number_of_contours() const
{
int sum = 0;
for (const auto &[clipper, trafo] : m_clippers)
sum += clipper->get_number_of_contours();
return sum;
}
std::vector<Vec3d> CommonGizmosDataObjects::ObjectClipper::point_per_contour() const
{
std::vector<Vec3d> pts;
for (const auto &clipper : m_clippers) {
const std::vector<Vec3d> pts_clipper = clipper->point_per_contour();
const std::vector<Vec3d> pts_clipper = clipper.first->point_per_contour();
pts.insert(pts.end(), pts_clipper.begin(), pts_clipper.end());
;
}
return pts;
}
bool ObjectClipper::is_projection_inside_cut(const Vec3d &point) const
int ObjectClipper::is_projection_inside_cut(const Vec3d &point) const
{
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [point](const auto &cl) {
return cl->is_projection_inside_cut(point);
});
if (m_clp_ratio == 0.)
return -1;
int idx_offset = 0;
for (const auto &[clipper, trafo] : m_clippers) {
if (int idx = clipper->is_projection_inside_cut(point); idx != -1)
return idx_offset + idx;
idx_offset += clipper->get_number_of_contours();
}
return -1;
}
bool ObjectClipper::has_valid_contour() const
{
return m_clp_ratio != 0. && std::any_of(m_clippers.begin(), m_clippers.end(), [](const auto &cl) {
return cl->has_valid_contour();
return cl.first->has_valid_contour();
});
}
@ -598,8 +591,7 @@ void SupportsClipper::render_cut() const
m_clipper->set_transformation(supports_trafo);
glsafe(::glPushMatrix());
glsafe(::glColor3f(1.0f, 0.f, 0.37f));
m_clipper->render_cut();
m_clipper->render_cut({1.0f, 0.f, 0.37f, 1.0f});
glsafe(::glPopMatrix());
}
@ -748,8 +740,7 @@ void ModelObjectsClipper::render_cut() const
clipper->set_transformation(trafo);
glsafe(::glPushMatrix());
// BBS
glsafe(::glColor3f(0.25f, 0.25f, 0.25f));
clipper->render_cut();
clipper->render_cut({0.25f, 0.25f, 0.25f, 1.0f});
glsafe(::glPopMatrix());
++clipper_id;

View File

@ -162,7 +162,7 @@ public:
ModelObject* model_object() const { return m_model_object; }
int get_active_instance() const;
float get_sla_shift() const { return m_z_shift; }
void set_use_shift(bool use) { m_use_shift = use; }
protected:
void on_update() override;
void on_release() override;
@ -171,6 +171,7 @@ private:
ModelObject* m_model_object = nullptr;
// int m_active_inst = -1;
float m_z_shift = 0.f;
bool m_use_shift = false;
};
@ -264,12 +265,14 @@ public:
void set_position(double pos, bool keep_normal);
double get_position() const { return m_clp_ratio; }
ClippingPlane* get_clipping_plane() const { return m_clp.get(); }
void render_cut() const;
void set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos);
void render_cut(const std::vector<size_t> *ignore_idxs = nullptr) const;
void set_range_and_pos(const Vec3d &cpl_normal, double cpl_offset, double pos);
void set_behaviour(bool hide_clipped, bool fill_cut, double contour_width);
int get_number_of_contours() const;
std::vector<Vec3d> point_per_contour() const;
bool is_projection_inside_cut(const Vec3d &point_in) const;
int is_projection_inside_cut(const Vec3d &point_in) const;
bool has_valid_contour() const;
protected:
@ -278,10 +281,11 @@ protected:
private:
std::vector<const TriangleMesh*> m_old_meshes;
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
std::vector<std::pair<std::unique_ptr<MeshClipper>, Geometry::Transformation>> m_clippers;
std::unique_ptr<ClippingPlane> m_clp;
double m_clp_ratio = 0.;
double m_active_inst_bb_radius = 0.;
bool m_hide_clipped = true;
};

View File

@ -432,6 +432,8 @@ void GLGizmosManager::update_data()
? get_current()->get_requirements()
: CommonGizmosDataID(0));
}
if (m_current != Undefined)
m_gizmos[m_current]->data_changed(m_serializing);
if (selection.is_single_full_instance())
{

View File

@ -6,7 +6,10 @@
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/Model.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/Camera.hpp"
#include "slic3r/GUI/CameraUtils.hpp"
#include <GL/glew.h>
@ -14,34 +17,35 @@
#include "CameraUtils.hpp"
namespace Slic3r {
namespace GUI {
namespace Slic3r { namespace GUI {
void MeshClipper::set_behaviour(bool fill_cut, double contour_width)
{
if (fill_cut != m_fill_cut || !is_approx(contour_width, m_contour_width)) m_result.reset();
m_fill_cut = fill_cut;
m_contour_width = contour_width;
}
void MeshClipper::set_plane(const ClippingPlane& plane)
void MeshClipper::set_plane(const ClippingPlane &plane)
{
if (m_plane != plane) {
m_plane = plane;
m_triangles_valid = false;
m_result.reset();
}
}
void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
{
if (m_limiting_plane != plane) {
m_limiting_plane = plane;
m_triangles_valid = false;
m_result.reset();
}
}
void MeshClipper::set_mesh(const TriangleMesh& mesh)
{
if (m_mesh != &mesh) {
m_mesh = &mesh;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_result.reset();
}
}
@ -49,8 +53,7 @@ void MeshClipper::set_negative_mesh(const TriangleMesh& mesh)
{
if (m_negative_mesh != &mesh) {
m_negative_mesh = &mesh;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_result.reset();
}
}
@ -60,34 +63,75 @@ void MeshClipper::set_transformation(const Geometry::Transformation& trafo)
{
if (! m_trafo.get_matrix().isApprox(trafo.get_matrix())) {
m_trafo = trafo;
m_triangles_valid = false;
m_triangles2d.resize(0);
m_result.reset();
}
}
void MeshClipper::render_cut()
void MeshClipper::render_cut(const ColorRGBA &color, const std::vector<size_t> *ignore_idxs)
{
if (! m_triangles_valid)
recalculate_triangles();
if (!m_result) recalculate_triangles();
GLShaderProgram *curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr) curr_shader->stop_using();
if (m_vertex_array.has_VBOs())
m_vertex_array.render();
GLShaderProgram *shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
const Camera &camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
for (size_t i = 0; i < m_result->cut_islands.size(); ++i) {
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) continue;
CutIsland &isl = m_result->cut_islands[i];
ColorRGBA gray{0.5f, 0.5f, 0.5f, 1.f};
isl.model.set_color(-1, isl.disabled ? gray : color);
isl.model.render();
}
shader->stop_using();
}
if (curr_shader != nullptr) curr_shader->start_using();
}
bool MeshClipper::is_projection_inside_cut(const Vec3d &point_in) const
void MeshClipper::render_contour(const ColorRGBA &color, const std::vector<size_t> *ignore_idxs)
{
if (!m_result) recalculate_triangles();
GLShaderProgram *curr_shader = wxGetApp().get_current_shader();
if (curr_shader != nullptr) curr_shader->stop_using();
GLShaderProgram *shader = wxGetApp().get_shader("flat");
if (shader != nullptr) {
shader->start_using();
const Camera &camera = wxGetApp().plater()->get_camera();
shader->set_uniform("view_model_matrix", camera.get_view_matrix());
shader->set_uniform("projection_matrix", camera.get_projection_matrix());
for (size_t i = 0; i < m_result->cut_islands.size(); ++i) {
if (ignore_idxs && std::binary_search(ignore_idxs->begin(), ignore_idxs->end(), i)) continue;
CutIsland &isl = m_result->cut_islands[i];
ColorRGBA red{1.0f, 0.f, 0.f, 1.f};
isl.model_expanded.set_color(-1, isl.disabled ? red : color);
isl.model_expanded.render();
}
shader->stop_using();
}
if (curr_shader != nullptr)
curr_shader->start_using();
}
int MeshClipper::is_projection_inside_cut(const Vec3d &point_in) const
{
if (!m_result || m_result->cut_islands.empty())
return false;
return -1;
Vec3d point = m_result->trafo.inverse() * point_in;
Point pt_2d = Point::new_scale(Vec2d(point.x(), point.y()));
for (const CutIsland &isl : m_result->cut_islands) {
for (int i = 0; i < int(m_result->cut_islands.size()); ++i) {
const CutIsland &isl = m_result->cut_islands[i];
if (isl.expoly_bb.contains(pt_2d) && isl.expoly.contains(pt_2d))
return true;
return i; // TODO: handle intersecting contours
}
return false;
return -1;
}
bool MeshClipper::has_valid_contour() const
@ -138,22 +182,25 @@ std::vector<Vec3d> MeshClipper::point_per_contour() const {
void MeshClipper::recalculate_triangles()
{
const Transform3f& instance_matrix_no_translation_no_scaling = m_trafo.get_matrix(true,false,true).cast<float>();
// Calculate clipping plane normal in mesh coordinates.
const Vec3f up_noscale = instance_matrix_no_translation_no_scaling.inverse() * m_plane.get_normal().cast<float>();
const Vec3d up = up_noscale.cast<double>().cwiseProduct(m_trafo.get_scaling_factor());
// Calculate distance from mesh origin to the clipping plane (in mesh coordinates).
const float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
m_result = ClipResult();
auto plane_mesh = Eigen::Hyperplane<double, 3>(m_plane.get_normal(), -m_plane.distance(Vec3d::Zero())).transform(m_trafo.get_matrix().inverse());
const Vec3d up = plane_mesh.normal();
const float height_mesh = -plane_mesh.offset();
// Now do the cutting
MeshSlicingParams slicing_params;
slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
ExPolygons expolys;
// if (m_csgmesh.empty()) {
if (m_mesh) {
expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
}
if (m_negative_mesh && !m_negative_mesh->empty()) {
const ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
expolys = diff_ex(expolys, neg_expolys);
expolys = diff_ex(expolys, neg_expolys);
}
// Triangulate and rotate the cut into world coords:
@ -163,41 +210,37 @@ void MeshClipper::recalculate_triangles()
tr.rotate(q);
tr = m_trafo.get_matrix() * tr;
m_result = ClipResult();
m_result->trafo = tr;
if (m_limiting_plane != ClippingPlane::ClipsNothing())
{
if (m_limiting_plane != ClippingPlane::ClipsNothing()) {
// Now remove whatever ended up below the limiting plane (e.g. sinking objects).
// First transform the limiting plane from world to mesh coords.
// Note that inverse of tr transforms the plane from world to horizontal.
const Vec3d normal_old = m_limiting_plane.get_normal().normalized();
const Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
const Vec3d normal_new = (tr.matrix().block<3, 3>(0, 0).transpose() * normal_old).normalized();
// normal_new should now be the plane normal in mesh coords. To find the offset,
// transform a point and set offset so it belongs to the transformed plane.
Vec3d pt = Vec3d::Zero();
Vec3d pt = Vec3d::Zero();
const double plane_offset = m_limiting_plane.get_data()[3];
if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
pt.z() = - plane_offset / normal_old.z();
pt.z() = -plane_offset / normal_old.z();
else if (std::abs(normal_old.y()) > 0.5)
pt.y() = - plane_offset / normal_old.y();
pt.y() = -plane_offset / normal_old.y();
else
pt.x() = - plane_offset / normal_old.x();
pt = tr.inverse() * pt;
pt.x() = -plane_offset / normal_old.x();
pt = tr.inverse() * pt;
const double offset = -(normal_new.dot(pt));
if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
// The cuts are parallel, show all or nothing.
if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh)
expolys.clear();
if (normal_old.dot(m_plane.get_normal().normalized()) < 0.0 && offset < height_mesh) expolys.clear();
} else {
// The cut is a horizontal plane defined by z=height_mesh.
// ax+by+e=0 is the line of intersection with the limiting plane.
// Normalized so a^2 + b^2 = 1.
const double len = std::hypot(normal_new.x(), normal_new.y());
if (len == 0.)
return;
if (len == 0.) return;
const double a = normal_new.x() / len;
const double b = normal_new.y() / len;
const double e = (normal_new.z() * height_mesh + offset) / len;
@ -211,36 +254,140 @@ void MeshClipper::recalculate_triangles()
// it so it lies on our line. This will be the figure to subtract
// from the cut. The coordinates must not overflow after the transform,
// make the rectangle a bit smaller.
const coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
const coord_t size = (std::numeric_limits<coord_t>::max() / 2 - scale_(std::max(std::abs(e * a), std::abs(e * b)))) / 4;
Polygons ep{Polygon({Point(-size, 0), Point(size, 0), Point(size, 2 * size), Point(-size, 2 * size)})};
ep.front().rotate(angle);
ep.front().translate(scale_(-e * a), scale_(-e * b));
expolys = diff_ex(expolys, ep);
}
}
for (const ExPolygon &exp : expolys) {
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
Transform3d tr2 = tr;
tr2.pretranslate(0.002 * m_plane.get_normal().normalized());
std::vector<Vec2f> triangles2d;
for (const ExPolygon &exp : expolys) {
triangles2d.clear();
m_result->cut_islands.push_back(CutIsland());
CutIsland &isl = m_result->cut_islands.back();
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(exp);
if (m_fill_cut) {
triangles2d = triangulate_expolygon_2f(exp, m_trafo.get_matrix().matrix().determinant() < 0.);
GLModel::InitializationData init_data; // GLModel::Geometry init_data;
init_data.entities.push_back(GLModel::InitializationData::Entity());
init_data.entities.back().type = GLModel::PrimitiveType::Triangles;
init_data.entities.back().positions.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().normals.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().indices.reserve(triangles2d.size());
/*init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3};
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());*/
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
/*init_data.add_vertex((Vec3f) (tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>(), (Vec3f) up.cast<float>());
init_data.add_vertex((Vec3f) (tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>(), (Vec3f) up.cast<float>());
init_data.add_vertex((Vec3f) (tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>(), (Vec3f) up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.add_triangle((unsigned int) idx, (unsigned int) idx + 1, (unsigned int) idx + 2);*/
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.entities.back().indices.push_back((unsigned int) idx);
init_data.entities.back().indices.push_back((unsigned int) idx + 1);
init_data.entities.back().indices.push_back((unsigned int) idx + 2);
}
if (init_data.entities.back().indices.size() != 0) isl.model.init_from(std::move(init_data));
}
if (m_contour_width != 0. && !exp.contour.empty()) {
triangles2d.clear();
// The contours must not scale with the object. Check the scale factor
// in the respective directions, create a scaled copy of the ExPolygon
// offset it and then unscale the result again.
Transform3d t = tr;
t.translation() = Vec3d::Zero();
double scale_x = (t * Vec3d::UnitX()).norm();
double scale_y = (t * Vec3d::UnitY()).norm();
// To prevent overflow after scaling, downscale the input if needed:
double extra_scale = 1.;
int32_t limit = int32_t(
std::min(std::numeric_limits<coord_t>::max() / (2. * std::max(1., scale_x)), std::numeric_limits<coord_t>::max() / (2. * std::max(1., scale_y))));
int32_t max_coord = 0;
for (const Point &pt : exp.contour) max_coord = std::max(max_coord, std::max(std::abs(pt.x()), std::abs(pt.y())));
if (max_coord + m_contour_width >= limit) extra_scale = 0.9 * double(limit) / max_coord;
ExPolygon exp_copy = exp;
if (extra_scale != 1.) exp_copy.scale(extra_scale);
exp_copy.scale(scale_x, scale_y);
ExPolygons expolys_exp = offset_ex(exp_copy, scale_(m_contour_width));
expolys_exp = diff_ex(expolys_exp, ExPolygons({exp_copy}));
for (ExPolygon &e : expolys_exp) {
e.scale(1. / scale_x, 1. / scale_y);
if (extra_scale != 1.) e.scale(1. / extra_scale);
}
triangles2d = triangulate_expolygons_2f(expolys_exp, m_trafo.get_matrix().matrix().determinant() < 0.);
GLModel::InitializationData init_data; // GLModel::Geometry init_data;
init_data.entities.push_back(GLModel::InitializationData::Entity());
init_data.entities.back().type = GLModel::PrimitiveType::Triangles;
init_data.entities.back().positions.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().normals.reserve(triangles2d.size() * (init_data.entities.back().type == GLModel::PrimitiveType::Triangles ? 3 : 2));
init_data.entities.back().indices.reserve(triangles2d.size());
/*GLModel::Geometry init_data = GLModel::Geometry();
init_data.format = {GLModel::Geometry::EPrimitiveType::Triangles, GLModel::Geometry::EVertexLayout::P3N3};
init_data.reserve_vertices(triangles2d.size());
init_data.reserve_indices(triangles2d.size());*/
// vertices + indices
for (auto it = triangles2d.cbegin(); it != triangles2d.cend(); it = it + 3) {
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 0)).x(), (*(it + 0)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 1)).x(), (*(it + 1)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
init_data.entities.back().positions.push_back((Vec3f) (tr * Vec3d((*(it + 2)).x(), (*(it + 2)).y(), height_mesh)).cast<float>());
init_data.entities.back().normals.push_back((Vec3f) up.cast<float>());
const size_t idx = it - triangles2d.cbegin();
init_data.entities.back().indices.push_back((unsigned int) idx);
init_data.entities.back().indices.push_back((unsigned int) idx + 1);
init_data.entities.back().indices.push_back((unsigned int) idx + 2);
}
if (init_data.entities.back().indices.size() != 0) isl.model_expanded.init_from(std::move(init_data));
}
isl.expoly = std::move(exp);
isl.expoly_bb = get_extents(isl.expoly);
Point centroid_scaled = isl.expoly.contour.centroid();
Vec3d centroid_world = m_result->trafo * Vec3d(unscale(centroid_scaled).x(), unscale(centroid_scaled).y(), 0.);
isl.hash = isl.expoly.contour.size() + size_t(std::abs(100. * centroid_world.x())) + size_t(std::abs(100. * centroid_world.y())) +
size_t(std::abs(100. * centroid_world.z()));
}
m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
tr.pretranslate(0.001 * m_plane.get_normal().normalized()); // to avoid z-fighting
m_vertex_array.release_geometry();
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
m_vertex_array.push_geometry(tr * Vec3d((*(it+0))(0), (*(it+0))(1), height_mesh), up);
m_vertex_array.push_geometry(tr * Vec3d((*(it+1))(0), (*(it+1))(1), height_mesh), up);
m_vertex_array.push_geometry(tr * Vec3d((*(it+2))(0), (*(it+2))(1), height_mesh), up);
const size_t idx = it - m_triangles2d.cbegin();
m_vertex_array.push_triangle(idx, idx+1, idx+2);
}
m_vertex_array.finalize_geometry(true);
m_triangles_valid = true;
// Now sort the islands so they are in defined order. This is a hack needed by cut gizmo, which sometimes
// flips the normal of the cut, in which case the contours stay the same but their order may change.
std::sort(m_result->cut_islands.begin(), m_result->cut_islands.end(), [](const CutIsland &a, const CutIsland &b) { return a.hash < b.hash; });
}

View File

@ -54,7 +54,13 @@ public:
}
void set_offset(double offset) { m_data[3] = offset; }
double get_offset() const { return m_data[3]; }
Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); }
Vec3d get_normal() const { return Vec3d(m_data[0], m_data[1], m_data[2]); }
void invert_normal(){
m_data[0] *= -1.0;
m_data[1] *= -1.0;
m_data[2] *= -1.0;
}
ClippingPlane inverted_normal() const { return ClippingPlane(-get_normal(), get_offset()); }
bool is_active() const { return m_data[3] != DBL_MAX; }
static ClippingPlane ClipsNothing() { return ClippingPlane(Vec3d(0., 0., 1.), DBL_MAX); }
const double* get_data() const { return m_data; }
@ -71,6 +77,9 @@ public:
// MeshClipper class cuts a mesh and is able to return a triangulated cut.
class MeshClipper {
public:
// Set whether the cut should be triangulated and whether a cut
// contour should be calculated and shown.
void set_behaviour(bool fill_cut, double contour_width);
// Inform MeshClipper about which plane we want to use to cut the mesh
// This is supposed to be in world coordinates.
void set_plane(const ClippingPlane& plane);
@ -92,35 +101,44 @@ public:
// Render the triangulated cut. Transformation matrices should
// be set in world coords.
void render_cut();
bool is_projection_inside_cut(const Vec3d &point) const;
bool has_valid_contour() const;
void render_cut(const ColorRGBA &color, const std::vector<size_t> *ignore_idxs = nullptr);
void render_contour(const ColorRGBA &color, const std::vector<size_t> *ignore_idxs = nullptr);
int is_projection_inside_cut(const Vec3d &point) const;
bool has_valid_contour() const;
int get_number_of_contours() const { return m_result ? m_result->cut_islands.size() : 0; }
std::vector<Vec3d> point_per_contour() const;
private:
void recalculate_triangles();
Geometry::Transformation m_trafo;
const TriangleMesh* m_mesh = nullptr;
const TriangleMesh* m_negative_mesh = nullptr;
Geometry::Transformation m_trafo;
const TriangleMesh * m_mesh = nullptr;
const TriangleMesh * m_negative_mesh = nullptr;
ClippingPlane m_plane;
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
std::vector<Vec2f> m_triangles2d;
/*std::vector<Vec2f> m_triangles2d;
GLIndexedVertexArray m_vertex_array;
bool m_triangles_valid = false;
bool m_triangles_valid = false;*/
struct CutIsland
{
ExPolygon expoly;
BoundingBox expoly_bb;
GLModel model;
GLModel model_expanded;
bool disabled = false;
size_t hash;
};
struct ClipResult
{
std::vector<CutIsland> cut_islands;
Transform3d trafo; // this rotates the cut into world coords
};
std::optional<ClipResult> m_result; // the cut plane
std::optional<ClipResult> m_result; // the cut plane
bool m_fill_cut = true;
double m_contour_width = 0.;
};

View File

@ -10135,6 +10135,26 @@ void Plater::segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha
}
}
void Plater::apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs &cut_objects)
{
model().delete_object(obj_idx);
sidebar().obj_list()->delete_object_from_list(obj_idx);
// suppress to call selection update for Object List to avoid call of early Gizmos on/off update
p->load_model_objects(cut_objects, false, false);
// now process all updates of the 3d scene
update();
// Update InfoItems in ObjectList after update() to use of a correct value of the GLCanvas3D::is_sinking(),
// which is updated after a view3D->reload_scene(false, flags & (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH) call
for (size_t idx = 0; idx < p->model.objects.size(); idx++) wxGetApp().obj_list()->update_info_items(idx);
Selection &selection = p->get_selection();
size_t last_id = p->model.objects.size() - 1;
for (size_t i = 0; i < cut_objects.size(); ++i)
selection.add_object((unsigned int) (last_id - i), i == 0);
}
// BBS
void Plater::merge(size_t obj_idx, std::vector<int>& vol_indeces)
{

View File

@ -328,7 +328,8 @@ public:
// BBS: segment model with CGAL
void segment(size_t obj_idx, size_t instance_idx, double smoothing_alpha=0.5, int segment_number=5);
void merge(size_t obj_idx, std::vector<int>& vol_indeces);
void apply_cut_object_to_model(size_t obj_idx, const ModelObjectPtrs &cut_objects);
void merge(size_t obj_idx, std::vector<int> &vol_indeces);
void send_to_printer(bool isall = false);
void export_gcode(bool prefer_removable);