mirror of
				https://github.com/SoftFever/OrcaSlicer.git
				synced 2025-11-02 20:51:23 -07:00 
			
		
		
		
	Merge branch 'et_gcode_viewer' of https://github.com/prusa3d/PrusaSlicer into et_gcode_viewer
This commit is contained in:
		
						commit
						e2bf72a494
					
				
					 19 changed files with 527 additions and 176 deletions
				
			
		
							
								
								
									
										54
									
								
								resources/icons/thumb_left.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								resources/icons/thumb_left.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
 | 
			
		||||
   sodipodi:docname="thumb_left.svg"
 | 
			
		||||
   xml:space="preserve"
 | 
			
		||||
   enable-background="new 0 0 16 16"
 | 
			
		||||
   viewBox="0 0 16 16"
 | 
			
		||||
   y="0px"
 | 
			
		||||
   x="0px"
 | 
			
		||||
   id="Layer_1"
 | 
			
		||||
   version="1.0"><metadata
 | 
			
		||||
   id="metadata32"><rdf:RDF><cc:Work
 | 
			
		||||
       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
 | 
			
		||||
         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
 | 
			
		||||
   id="defs30" /><sodipodi:namedview
 | 
			
		||||
   inkscape:current-layer="Layer_1"
 | 
			
		||||
   inkscape:window-maximized="0"
 | 
			
		||||
   inkscape:window-y="0"
 | 
			
		||||
   inkscape:window-x="1268"
 | 
			
		||||
   inkscape:cy="8"
 | 
			
		||||
   inkscape:cx="8"
 | 
			
		||||
   inkscape:zoom="63"
 | 
			
		||||
   showgrid="false"
 | 
			
		||||
   id="namedview28"
 | 
			
		||||
   inkscape:window-height="1368"
 | 
			
		||||
   inkscape:window-width="1283"
 | 
			
		||||
   inkscape:pageshadow="2"
 | 
			
		||||
   inkscape:pageopacity="0"
 | 
			
		||||
   guidetolerance="10"
 | 
			
		||||
   gridtolerance="10"
 | 
			
		||||
   objecttolerance="10"
 | 
			
		||||
   borderopacity="1"
 | 
			
		||||
   bordercolor="#666666"
 | 
			
		||||
   pagecolor="#ffffff" />
 | 
			
		||||
<g
 | 
			
		||||
   transform="rotate(-90,8.0158731,7.984127)"
 | 
			
		||||
   id="hex_x5F_plus">
 | 
			
		||||
	<g
 | 
			
		||||
   id="g24">
 | 
			
		||||
		<polygon
 | 
			
		||||
   id="polygon22"
 | 
			
		||||
   style="stroke:#ffffff;stroke-width:1"
 | 
			
		||||
   points="15,7 15,5 8,0 1,5 1,7 1,8 15,8 "
 | 
			
		||||
   fill="#ed6b21" />
 | 
			
		||||
	</g>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.6 KiB  | 
							
								
								
									
										54
									
								
								resources/icons/thumb_right.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								resources/icons/thumb_right.svg
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
			
		||||
<svg
 | 
			
		||||
   xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
			
		||||
   xmlns:cc="http://creativecommons.org/ns#"
 | 
			
		||||
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
			
		||||
   xmlns:svg="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
			
		||||
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
 | 
			
		||||
   inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
 | 
			
		||||
   sodipodi:docname="thumb_right.svg"
 | 
			
		||||
   xml:space="preserve"
 | 
			
		||||
   enable-background="new 0 0 16 16"
 | 
			
		||||
   viewBox="0 0 16 16"
 | 
			
		||||
   y="0px"
 | 
			
		||||
   x="0px"
 | 
			
		||||
   id="Layer_1"
 | 
			
		||||
   version="1.0"><metadata
 | 
			
		||||
   id="metadata32"><rdf:RDF><cc:Work
 | 
			
		||||
       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
 | 
			
		||||
         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
 | 
			
		||||
   id="defs30" /><sodipodi:namedview
 | 
			
		||||
   inkscape:current-layer="Layer_1"
 | 
			
		||||
   inkscape:window-maximized="0"
 | 
			
		||||
   inkscape:window-y="0"
 | 
			
		||||
   inkscape:window-x="1268"
 | 
			
		||||
   inkscape:cy="8"
 | 
			
		||||
   inkscape:cx="8"
 | 
			
		||||
   inkscape:zoom="63"
 | 
			
		||||
   showgrid="false"
 | 
			
		||||
   id="namedview28"
 | 
			
		||||
   inkscape:window-height="1368"
 | 
			
		||||
   inkscape:window-width="1283"
 | 
			
		||||
   inkscape:pageshadow="2"
 | 
			
		||||
   inkscape:pageopacity="0"
 | 
			
		||||
   guidetolerance="10"
 | 
			
		||||
   gridtolerance="10"
 | 
			
		||||
   objecttolerance="10"
 | 
			
		||||
   borderopacity="1"
 | 
			
		||||
   bordercolor="#666666"
 | 
			
		||||
   pagecolor="#ffffff" />
 | 
			
		||||
<g
 | 
			
		||||
   transform="matrix(0,-1,-1,0,16.012532,16)"
 | 
			
		||||
   id="hex_x5F_plus">
 | 
			
		||||
	<g
 | 
			
		||||
   id="g24">
 | 
			
		||||
		<polygon
 | 
			
		||||
   id="polygon22"
 | 
			
		||||
   style="stroke:#ffffff;stroke-width:1"
 | 
			
		||||
   points="15,8 15,7 15,5 8,0 1,5 1,7 1,8 "
 | 
			
		||||
   fill="#ed6b21" />
 | 
			
		||||
	</g>
 | 
			
		||||
</g>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.6 KiB  | 
| 
						 | 
				
			
			@ -450,6 +450,31 @@ void Model::convert_multipart_object(unsigned int max_extruders)
 | 
			
		|||
    this->objects.push_back(object);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool Model::looks_like_imperial_units() const
 | 
			
		||||
{
 | 
			
		||||
    if (this->objects.size() == 0)
 | 
			
		||||
        return false;
 | 
			
		||||
 | 
			
		||||
    stl_vertex size = this->objects[0]->get_object_stl_stats().size;
 | 
			
		||||
 | 
			
		||||
    for (ModelObject* o : this->objects) {
 | 
			
		||||
        auto sz = o->get_object_stl_stats().size;
 | 
			
		||||
 | 
			
		||||
        if (size[0] < sz[0]) size[0] = sz[0];
 | 
			
		||||
        if (size[1] < sz[1]) size[1] = sz[1];
 | 
			
		||||
        if (size[2] < sz[2]) size[2] = sz[2];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (size[0] < 3 && size[1] < 3 && size[2] < 3);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Model::convert_from_imperial_units()
 | 
			
		||||
{
 | 
			
		||||
    double in_to_mm = 25.4;
 | 
			
		||||
    for (ModelObject* o : this->objects)
 | 
			
		||||
        o->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Model::adjust_min_z()
 | 
			
		||||
{
 | 
			
		||||
    if (objects.empty())
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -851,6 +851,8 @@ public:
 | 
			
		|||
 | 
			
		||||
    bool 		  looks_like_multipart_object() const;
 | 
			
		||||
    void 		  convert_multipart_object(unsigned int max_extruders);
 | 
			
		||||
    bool          looks_like_imperial_units() const;
 | 
			
		||||
    void          convert_from_imperial_units();
 | 
			
		||||
 | 
			
		||||
    // Ensures that the min z of the model is not negative
 | 
			
		||||
    void 		  adjust_min_z();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,8 +64,13 @@ Control::Control( wxWindow *parent,
 | 
			
		|||
    if (!is_osx)
 | 
			
		||||
        SetDoubleBuffered(true);// SetDoubleBuffered exists on Win and Linux/GTK, but is missing on OSX
 | 
			
		||||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_right") : ScalableBitmap(this, "thumb_up"));
 | 
			
		||||
    m_bmp_thumb_lower  = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "thumb_left")  : ScalableBitmap(this, "thumb_down"));
 | 
			
		||||
#else
 | 
			
		||||
    m_bmp_thumb_higher = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "right_half_circle.png") : ScalableBitmap(this, "thumb_up"));
 | 
			
		||||
    m_bmp_thumb_lower  = (style == wxSL_HORIZONTAL ? ScalableBitmap(this, "left_half_circle.png" ) : ScalableBitmap(this, "thumb_down"));
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_thumb_size = m_bmp_thumb_lower.GetBmpSize();
 | 
			
		||||
 | 
			
		||||
    m_bmp_add_tick_on  = ScalableBitmap(this, "colorchange_add");
 | 
			
		||||
| 
						 | 
				
			
			@ -576,6 +581,10 @@ void Control::draw_thumb_text(wxDC& dc, const wxPoint& pos, const SelectedSlider
 | 
			
		|||
 | 
			
		||||
void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider& selection)
 | 
			
		||||
{
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    wxCoord x_draw = pos.x - int(0.5 * m_thumb_size.x);
 | 
			
		||||
    wxCoord y_draw = pos.y - int(0.5 * m_thumb_size.y);
 | 
			
		||||
#else
 | 
			
		||||
    wxCoord x_draw, y_draw;
 | 
			
		||||
    if (selection == ssLower) {
 | 
			
		||||
        if (is_horizontal()) {
 | 
			
		||||
| 
						 | 
				
			
			@ -597,6 +606,7 @@ void Control::draw_thumb_item(wxDC& dc, const wxPoint& pos, const SelectedSlider
 | 
			
		|||
            y_draw = pos.y - int(0.5*m_thumb_size.y);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
    dc.DrawBitmap(selection == ssLower ? m_bmp_thumb_lower.bmp() : m_bmp_thumb_higher.bmp(), x_draw, y_draw);
 | 
			
		||||
 | 
			
		||||
    // Update thumb rect
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -613,7 +613,7 @@ void GUI_App::set_auto_toolbar_icon_scale(float scale) const
 | 
			
		|||
    const float icon_sc = m_em_unit * 0.1f;
 | 
			
		||||
#endif // __APPLE__
 | 
			
		||||
 | 
			
		||||
    int int_val = std::min(int(scale / icon_sc * 100), 100);
 | 
			
		||||
    long int_val = std::min(int(std::lround(scale / icon_sc * 100)), 100);
 | 
			
		||||
    std::string val = std::to_string(int_val);
 | 
			
		||||
 | 
			
		||||
    app_config->set("auto_toolbar_size", val);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,6 +17,8 @@ namespace Slic3r
 | 
			
		|||
namespace GUI
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
const double ObjectManipulation::in_to_mm = 25.4;
 | 
			
		||||
const double ObjectManipulation::mm_to_in = 0.0393700787;
 | 
			
		||||
 | 
			
		||||
// Helper function to be used by drop to bed button. Returns lowest point of this
 | 
			
		||||
// volume in world coordinate system.
 | 
			
		||||
| 
						 | 
				
			
			@ -121,6 +123,8 @@ static void set_font_and_background_style(wxWindow* win, const wxFont& font)
 | 
			
		|||
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		||||
    OG_Settings(parent, true)
 | 
			
		||||
{
 | 
			
		||||
    m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
 | 
			
		||||
 | 
			
		||||
    m_manifold_warning_bmp = ScalableBitmap(parent, "exclamation");
 | 
			
		||||
 | 
			
		||||
    // Load bitmaps to be used for the mirroring buttons:
 | 
			
		||||
| 
						 | 
				
			
			@ -314,15 +318,15 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
    };
 | 
			
		||||
    
 | 
			
		||||
    // add Units 
 | 
			
		||||
    auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit)
 | 
			
		||||
    auto add_unit_text = [this, parent, editors_grid_sizer, height](std::string unit, wxStaticText** unit_text)
 | 
			
		||||
    {
 | 
			
		||||
        wxStaticText* unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
 | 
			
		||||
        set_font_and_background_style(unit_text, wxGetApp().normal_font()); 
 | 
			
		||||
        *unit_text = new wxStaticText(parent, wxID_ANY, _(unit));
 | 
			
		||||
        set_font_and_background_style(*unit_text, wxGetApp().normal_font()); 
 | 
			
		||||
 | 
			
		||||
        // Unit text should be the same height as labels      
 | 
			
		||||
        wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
        sizer->SetMinSize(wxSize(-1, height));
 | 
			
		||||
        sizer->Add(unit_text, 0, wxALIGN_CENTER_VERTICAL);
 | 
			
		||||
        sizer->Add(*unit_text, 0, wxALIGN_CENTER_VERTICAL);
 | 
			
		||||
 | 
			
		||||
        editors_grid_sizer->Add(sizer);
 | 
			
		||||
        m_rescalable_sizers.push_back(sizer);
 | 
			
		||||
| 
						 | 
				
			
			@ -330,7 +334,7 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
 | 
			
		||||
    for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
 | 
			
		||||
        add_edit_boxes("position", axis_idx);
 | 
			
		||||
    add_unit_text(L("mm"));
 | 
			
		||||
    add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_position_unit);
 | 
			
		||||
 | 
			
		||||
    // Add drop to bed button
 | 
			
		||||
    m_drop_to_bed_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "drop_to_bed"));
 | 
			
		||||
| 
						 | 
				
			
			@ -356,7 +360,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
 | 
			
		||||
    for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
 | 
			
		||||
        add_edit_boxes("rotation", axis_idx);
 | 
			
		||||
    add_unit_text("°");
 | 
			
		||||
    wxStaticText* rotation_unit{ nullptr };
 | 
			
		||||
    add_unit_text("°", &rotation_unit);
 | 
			
		||||
 | 
			
		||||
    // Add reset rotation button
 | 
			
		||||
    m_reset_rotation_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
 | 
			
		||||
| 
						 | 
				
			
			@ -390,7 +395,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
 | 
			
		||||
    for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
 | 
			
		||||
        add_edit_boxes("scale", axis_idx);
 | 
			
		||||
    add_unit_text("%");
 | 
			
		||||
    wxStaticText* scale_unit{ nullptr };
 | 
			
		||||
    add_unit_text("%", &scale_unit);
 | 
			
		||||
 | 
			
		||||
    // Add reset scale button
 | 
			
		||||
    m_reset_scale_button = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
 | 
			
		||||
| 
						 | 
				
			
			@ -405,11 +411,20 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
 | 
			
		|||
 | 
			
		||||
    for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++)
 | 
			
		||||
        add_edit_boxes("size", axis_idx);
 | 
			
		||||
    add_unit_text("mm");
 | 
			
		||||
    add_unit_text(m_imperial_units ? L("in") : L("mm"), &m_size_unit);
 | 
			
		||||
    editors_grid_sizer->AddStretchSpacer(1);
 | 
			
		||||
 | 
			
		||||
    m_main_grid_sizer->Add(editors_grid_sizer, 1, wxEXPAND);
 | 
			
		||||
 | 
			
		||||
    m_check_inch = new wxCheckBox(parent, wxID_ANY, "Inches");
 | 
			
		||||
    m_check_inch->SetValue(m_imperial_units);
 | 
			
		||||
    m_check_inch->Bind(wxEVT_CHECKBOX, [this](wxCommandEvent&) {
 | 
			
		||||
        wxGetApp().app_config->set("use_inches", m_check_inch->GetValue() ? "1" : "0");
 | 
			
		||||
        wxGetApp().sidebar().update_ui_from_settings();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    m_main_grid_sizer->Add(m_check_inch, 1, wxEXPAND);
 | 
			
		||||
 | 
			
		||||
    m_og->sizer->Clear(true);
 | 
			
		||||
    m_og->sizer->Add(m_main_grid_sizer, 1, wxEXPAND | wxALL, border);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -452,6 +467,32 @@ void ObjectManipulation::UpdateAndShow(const bool show)
 | 
			
		|||
    OG_Settings::UpdateAndShow(show);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectManipulation::update_ui_from_settings()
 | 
			
		||||
{
 | 
			
		||||
    if (m_imperial_units != (wxGetApp().app_config->get("use_inches") == "1")) {
 | 
			
		||||
        m_imperial_units = wxGetApp().app_config->get("use_inches") == "1";
 | 
			
		||||
 | 
			
		||||
        auto update_unit_text = [](const wxString& new_unit_text, wxStaticText* widget) {
 | 
			
		||||
            widget->SetLabel(new_unit_text);
 | 
			
		||||
            if (wxOSX) set_font_and_background_style(widget, wxGetApp().normal_font());
 | 
			
		||||
        };
 | 
			
		||||
        update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_position_unit);
 | 
			
		||||
        update_unit_text(m_imperial_units ? _L("in") : _L("mm"), m_size_unit);
 | 
			
		||||
 | 
			
		||||
        for (int i = 0; i < 3; ++i) {
 | 
			
		||||
            auto update = [this, i](/*ManipulationEditorKey*/int key_id, const Vec3d& new_value) {
 | 
			
		||||
                wxString new_text = double_to_string(m_imperial_units ? new_value(i) * mm_to_in : new_value(i), 2);
 | 
			
		||||
                const int id = key_id * 3 + i;
 | 
			
		||||
                if (id >= 0) m_editors[id]->set_value(new_text);
 | 
			
		||||
            };
 | 
			
		||||
            update(0/*mePosition*/, m_new_position);
 | 
			
		||||
            update(3/*meSize*/,     m_new_size);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m_check_inch->SetValue(m_imperial_units);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ObjectManipulation::update_settings_value(const Selection& selection)
 | 
			
		||||
{
 | 
			
		||||
	m_new_move_label_string   = L("Position");
 | 
			
		||||
| 
						 | 
				
			
			@ -562,6 +603,8 @@ void ObjectManipulation::update_if_dirty()
 | 
			
		|||
			if (std::abs(cached_rounded(i) - new_rounded) > EPSILON) {
 | 
			
		||||
				cached_rounded(i) = new_rounded;
 | 
			
		||||
                const int id = key_id*3+i;
 | 
			
		||||
                if (m_imperial_units && (key_id == mePosition || key_id == meSize))
 | 
			
		||||
                    new_text = double_to_string(new_value(i)*mm_to_in, 2);
 | 
			
		||||
                if (id >= 0) m_editors[id]->set_value(new_text);
 | 
			
		||||
            }
 | 
			
		||||
			cached(i) = new_value(i);
 | 
			
		||||
| 
						 | 
				
			
			@ -851,6 +894,9 @@ void ObjectManipulation::on_change(const std::string& opt_key, int axis, double
 | 
			
		|||
    if (!m_cache.is_valid())
 | 
			
		||||
        return;
 | 
			
		||||
 | 
			
		||||
    if (m_imperial_units && (opt_key == "position" || opt_key == "size"))
 | 
			
		||||
        new_value *= in_to_mm;
 | 
			
		||||
 | 
			
		||||
    if (opt_key == "position")
 | 
			
		||||
        change_position_value(axis, new_value);
 | 
			
		||||
    else if (opt_key == "rotation")
 | 
			
		||||
| 
						 | 
				
			
			@ -929,6 +975,9 @@ void ObjectManipulation::msw_rescale()
 | 
			
		|||
    for (ManipulationEditor* editor : m_editors)
 | 
			
		||||
        editor->msw_rescale();
 | 
			
		||||
 | 
			
		||||
    // rescale "inches" checkbox
 | 
			
		||||
    m_check_inch->SetMinSize(wxSize(-1, int(1.5f * m_check_inch->GetFont().GetPixelSize().y + 0.5f)));
 | 
			
		||||
 | 
			
		||||
    get_og()->msw_rescale();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,6 +10,7 @@ class wxBitmapComboBox;
 | 
			
		|||
class wxStaticText;
 | 
			
		||||
class LockButton;
 | 
			
		||||
class wxStaticBitmap;
 | 
			
		||||
class wxCheckBox;
 | 
			
		||||
 | 
			
		||||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
| 
						 | 
				
			
			@ -41,6 +42,11 @@ private:
 | 
			
		|||
 | 
			
		||||
class ObjectManipulation : public OG_Settings
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
    static const double in_to_mm;
 | 
			
		||||
    static const double mm_to_in;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    struct Cache
 | 
			
		||||
    {
 | 
			
		||||
        Vec3d position;
 | 
			
		||||
| 
						 | 
				
			
			@ -76,6 +82,10 @@ class ObjectManipulation : public OG_Settings
 | 
			
		|||
    wxStaticText*   m_scale_Label = nullptr;
 | 
			
		||||
    wxStaticText*   m_rotate_Label = nullptr;
 | 
			
		||||
 | 
			
		||||
    bool            m_imperial_units { false };
 | 
			
		||||
    wxStaticText*   m_position_unit  { nullptr };
 | 
			
		||||
    wxStaticText*   m_size_unit      { nullptr };
 | 
			
		||||
 | 
			
		||||
    wxStaticText*   m_item_name = nullptr;
 | 
			
		||||
    wxStaticText*   m_empty_str = nullptr;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -84,6 +94,8 @@ class ObjectManipulation : public OG_Settings
 | 
			
		|||
    ScalableButton* m_reset_rotation_button = nullptr;
 | 
			
		||||
    ScalableButton* m_drop_to_bed_button = nullptr;
 | 
			
		||||
 | 
			
		||||
    wxCheckBox*     m_check_inch {nullptr};
 | 
			
		||||
 | 
			
		||||
    // Mirroring buttons and their current state
 | 
			
		||||
    enum MirrorButtonState {
 | 
			
		||||
        mbHidden,
 | 
			
		||||
| 
						 | 
				
			
			@ -138,6 +150,7 @@ public:
 | 
			
		|||
    void        Show(const bool show) override;
 | 
			
		||||
    bool        IsShown() override;
 | 
			
		||||
    void        UpdateAndShow(const bool show) override;
 | 
			
		||||
    void update_ui_from_settings();
 | 
			
		||||
 | 
			
		||||
    void        set_dirty() { m_dirty = true; }
 | 
			
		||||
	// Called from the App to update the UI if dirty.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -184,8 +184,8 @@ Preview::Preview(
 | 
			
		|||
    : m_canvas_widget(nullptr)
 | 
			
		||||
    , m_canvas(nullptr)
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    , m_bottom_toolbar_sizer(nullptr)
 | 
			
		||||
    , m_layers_slider_sizer(nullptr)
 | 
			
		||||
    , m_bottom_toolbar_panel(nullptr)
 | 
			
		||||
#else
 | 
			
		||||
    , m_double_slider_sizer(nullptr)
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +194,7 @@ Preview::Preview(
 | 
			
		|||
    , m_label_show(nullptr)
 | 
			
		||||
    , m_combochecklist_features(nullptr)
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    , m_combochecklist_features_pos(0)
 | 
			
		||||
    , m_combochecklist_options(nullptr)
 | 
			
		||||
#else
 | 
			
		||||
    , m_checkbox_travel(nullptr)
 | 
			
		||||
| 
						 | 
				
			
			@ -251,14 +252,20 @@ bool Preview::init(wxWindow* parent, Model* model)
 | 
			
		|||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_layers_slider_sizer = create_layers_slider_sizer();
 | 
			
		||||
 | 
			
		||||
    m_bottom_toolbar_panel = new wxPanel(this);
 | 
			
		||||
 | 
			
		||||
    m_label_view_type = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("View"));
 | 
			
		||||
 | 
			
		||||
    m_choice_view_type = new wxChoice(m_bottom_toolbar_panel, wxID_ANY);
 | 
			
		||||
#else
 | 
			
		||||
    m_double_slider_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
    create_double_slider();
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
 | 
			
		||||
    m_label_view_type = new wxStaticText(this, wxID_ANY, _L("View"));
 | 
			
		||||
 | 
			
		||||
    m_choice_view_type = new wxChoice(this, wxID_ANY);
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_choice_view_type->Append(_L("Feature type"));
 | 
			
		||||
    m_choice_view_type->Append(_L("Height"));
 | 
			
		||||
    m_choice_view_type->Append(_L("Width"));
 | 
			
		||||
| 
						 | 
				
			
			@ -269,10 +276,18 @@ bool Preview::init(wxWindow* parent, Model* model)
 | 
			
		|||
    m_choice_view_type->Append(_L("Color Print"));
 | 
			
		||||
    m_choice_view_type->SetSelection(0);
 | 
			
		||||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_label_show = new wxStaticText(m_bottom_toolbar_panel, wxID_ANY, _L("Show"));
 | 
			
		||||
#else
 | 
			
		||||
    m_label_show = new wxStaticText(this, wxID_ANY, _L("Show"));
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
 | 
			
		||||
    m_combochecklist_features = new wxComboCtrl();
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_combochecklist_features->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
 | 
			
		||||
#else
 | 
			
		||||
    m_combochecklist_features->Create(this, wxID_ANY, _L("Feature types"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
    std::string feature_items = GUI::into_u8(
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
        _L("Unknown") + "|1|" +
 | 
			
		||||
| 
						 | 
				
			
			@ -296,7 +311,7 @@ bool Preview::init(wxWindow* parent, Model* model)
 | 
			
		|||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_combochecklist_options = new wxComboCtrl();
 | 
			
		||||
    m_combochecklist_options->Create(this, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
 | 
			
		||||
    m_combochecklist_options->Create(m_bottom_toolbar_panel, wxID_ANY, _L("Options"), wxDefaultPosition, wxDefaultSize, wxCB_READONLY);
 | 
			
		||||
    std::string options_items = GUI::into_u8(
 | 
			
		||||
        get_option_type_string(OptionType::Travel) + "|0|" +
 | 
			
		||||
        get_option_type_string(OptionType::Retractions) + "|0|" +
 | 
			
		||||
| 
						 | 
				
			
			@ -328,20 +343,23 @@ bool Preview::init(wxWindow* parent, Model* model)
 | 
			
		|||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_moves_slider = new DoubleSlider::Control(this, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 3 * GetTextExtent("m").y), wxSL_HORIZONTAL);
 | 
			
		||||
    m_moves_slider = new DoubleSlider::Control(m_bottom_toolbar_panel, wxID_ANY, 0, 0, 0, 100, wxDefaultPosition, wxSize(-1, 4 * GetTextExtent("m").y), wxSL_HORIZONTAL);
 | 
			
		||||
    m_moves_slider->SetDrawMode(DoubleSlider::dmSequentialGCodeView);
 | 
			
		||||
    m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
 | 
			
		||||
 | 
			
		||||
    m_bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
    m_bottom_toolbar_sizer->AddSpacer(5);
 | 
			
		||||
    m_bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
 | 
			
		||||
    m_bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0);
 | 
			
		||||
    m_bottom_toolbar_sizer->AddSpacer(5);
 | 
			
		||||
    m_bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
 | 
			
		||||
    m_bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0);
 | 
			
		||||
    m_bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
 | 
			
		||||
    m_bottom_toolbar_sizer->AddSpacer(5);
 | 
			
		||||
    m_bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 5);
 | 
			
		||||
    wxBoxSizer* bottom_toolbar_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
    bottom_toolbar_sizer->AddSpacer(5);
 | 
			
		||||
    bottom_toolbar_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5);
 | 
			
		||||
    bottom_toolbar_sizer->Add(m_choice_view_type, 0, wxALIGN_CENTER_VERTICAL, 0);
 | 
			
		||||
    bottom_toolbar_sizer->AddSpacer(5);
 | 
			
		||||
    bottom_toolbar_sizer->Add(m_label_show, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxRIGHT, 5);
 | 
			
		||||
    bottom_toolbar_sizer->Add(m_combochecklist_options, 0, wxALIGN_CENTER_VERTICAL, 0);
 | 
			
		||||
    // change the following number if editing the layout of the bottom toolbar sizer. It is used into update_bottom_toolbar()
 | 
			
		||||
    m_combochecklist_features_pos = 6;
 | 
			
		||||
    bottom_toolbar_sizer->Add(m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
 | 
			
		||||
    bottom_toolbar_sizer->Hide(m_combochecklist_features);
 | 
			
		||||
    bottom_toolbar_sizer->AddSpacer(5);
 | 
			
		||||
    bottom_toolbar_sizer->Add(m_moves_slider, 1, wxALL | wxEXPAND, 0);
 | 
			
		||||
    m_bottom_toolbar_panel->SetSizer(bottom_toolbar_sizer);
 | 
			
		||||
#else
 | 
			
		||||
    wxBoxSizer* bottom_sizer = new wxBoxSizer(wxHORIZONTAL);
 | 
			
		||||
    bottom_sizer->Add(m_label_view_type, 0, wxALIGN_CENTER_VERTICAL, 5);
 | 
			
		||||
| 
						 | 
				
			
			@ -364,8 +382,8 @@ bool Preview::init(wxWindow* parent, Model* model)
 | 
			
		|||
    wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
 | 
			
		||||
    main_sizer->Add(top_sizer, 1, wxALL | wxEXPAND, 0);
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    main_sizer->Add(m_bottom_toolbar_sizer, 0, wxALL | wxEXPAND, 0);
 | 
			
		||||
    main_sizer->Hide(m_bottom_toolbar_sizer);
 | 
			
		||||
    main_sizer->Add(m_bottom_toolbar_panel, 0, wxALL | wxEXPAND, 0);
 | 
			
		||||
    main_sizer->Hide(m_bottom_toolbar_panel);
 | 
			
		||||
#else
 | 
			
		||||
    main_sizer->Add(bottom_sizer, 0, wxALL | wxEXPAND, 0);
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
| 
						 | 
				
			
			@ -565,6 +583,7 @@ void Preview::bind_event_handlers()
 | 
			
		|||
    m_combochecklist_features->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_combochecklist_options->Bind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this);
 | 
			
		||||
    m_moves_slider->Bind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
 | 
			
		||||
#else
 | 
			
		||||
    m_checkbox_travel->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
 | 
			
		||||
    m_checkbox_retractions->Bind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
 | 
			
		||||
| 
						 | 
				
			
			@ -581,6 +600,7 @@ void Preview::unbind_event_handlers()
 | 
			
		|||
    m_combochecklist_features->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_features, this);
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    m_combochecklist_options->Unbind(wxEVT_CHECKLISTBOX, &Preview::on_combochecklist_options, this);
 | 
			
		||||
    m_moves_slider->Unbind(wxEVT_SCROLL_CHANGED, &Preview::on_moves_slider_scroll_changed, this);
 | 
			
		||||
#else
 | 
			
		||||
    m_checkbox_travel->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_travel, this);
 | 
			
		||||
    m_checkbox_retractions->Unbind(wxEVT_CHECKBOX, &Preview::on_checkbox_retractions, this);
 | 
			
		||||
| 
						 | 
				
			
			@ -773,11 +793,34 @@ void Preview::update_bottom_toolbar()
 | 
			
		|||
    combochecklist_set_flags(m_combochecklist_features, m_canvas->get_toolpath_role_visibility_flags());
 | 
			
		||||
    combochecklist_set_flags(m_combochecklist_options, m_canvas->get_gcode_options_visibility_flags());
 | 
			
		||||
 | 
			
		||||
    m_bottom_toolbar_sizer->Show(m_combochecklist_features,
 | 
			
		||||
        !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType);
 | 
			
		||||
    m_bottom_toolbar_sizer->Layout();
 | 
			
		||||
    // updates visibility of features combobox
 | 
			
		||||
    if (m_bottom_toolbar_panel->IsShown())
 | 
			
		||||
    {
 | 
			
		||||
        wxSizer* sizer = m_bottom_toolbar_panel->GetSizer();
 | 
			
		||||
        bool show = !m_canvas->is_gcode_legend_enabled() || m_canvas->get_gcode_view_type() != GCodeViewer::EViewType::FeatureType;
 | 
			
		||||
 | 
			
		||||
        if (show)
 | 
			
		||||
        {
 | 
			
		||||
            if (sizer->GetItem(m_combochecklist_features) == nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                sizer->Insert(m_combochecklist_features_pos, m_combochecklist_features, 0, wxALIGN_CENTER_VERTICAL | wxLEFT, 5);
 | 
			
		||||
                sizer->Show(m_combochecklist_features);
 | 
			
		||||
                sizer->Layout();
 | 
			
		||||
                Refresh();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            if (sizer->GetItem(m_combochecklist_features) != nullptr)
 | 
			
		||||
            {
 | 
			
		||||
                sizer->Hide(m_combochecklist_features);
 | 
			
		||||
                sizer->Detach(m_combochecklist_features);
 | 
			
		||||
                sizer->Layout();
 | 
			
		||||
                Refresh();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
| 
						 | 
				
			
			@ -1243,8 +1286,9 @@ void Preview::load_print_as_fff(bool keep_z_range)
 | 
			
		|||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
            m_canvas->load_gcode_preview(*m_gcode_result);
 | 
			
		||||
            m_canvas->refresh_gcode_preview(*m_gcode_result, colors);
 | 
			
		||||
            GetSizer()->Show(m_bottom_toolbar_sizer);
 | 
			
		||||
            GetSizer()->Show(m_bottom_toolbar_panel); 
 | 
			
		||||
            GetSizer()->Layout();
 | 
			
		||||
            Refresh();
 | 
			
		||||
            zs = m_canvas->get_gcode_layers_zs();
 | 
			
		||||
#else
 | 
			
		||||
            m_canvas->load_gcode_preview(*m_gcode_preview_data, colors);
 | 
			
		||||
| 
						 | 
				
			
			@ -1254,8 +1298,9 @@ void Preview::load_print_as_fff(bool keep_z_range)
 | 
			
		|||
            // Load the initial preview based on slices, not the final G-code.
 | 
			
		||||
            m_canvas->load_preview(colors, color_print_values);
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
            GetSizer()->Hide(m_bottom_toolbar_sizer);
 | 
			
		||||
            GetSizer()->Hide(m_bottom_toolbar_panel);
 | 
			
		||||
            GetSizer()->Layout();
 | 
			
		||||
            Refresh();
 | 
			
		||||
            zs = m_canvas->get_volumes_print_zs(true);
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1316,8 +1361,9 @@ void Preview::load_print_as_sla()
 | 
			
		|||
    {
 | 
			
		||||
        m_canvas->load_sla_preview();
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
        GetSizer()->Hide(m_bottom_toolbar_sizer);
 | 
			
		||||
        GetSizer()->Hide(m_bottom_toolbar_panel); 
 | 
			
		||||
        GetSizer()->Layout();
 | 
			
		||||
        Refresh();
 | 
			
		||||
#else
 | 
			
		||||
        show_hide_ui_elements("none");
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -84,7 +84,7 @@ class Preview : public wxPanel
 | 
			
		|||
    GLCanvas3D* m_canvas;
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    wxBoxSizer* m_layers_slider_sizer;
 | 
			
		||||
    wxBoxSizer* m_bottom_toolbar_sizer;
 | 
			
		||||
    wxPanel* m_bottom_toolbar_panel;
 | 
			
		||||
#else
 | 
			
		||||
    wxBoxSizer* m_double_slider_sizer;
 | 
			
		||||
#endif // ENABLE_GCODE_VIEWER
 | 
			
		||||
| 
						 | 
				
			
			@ -93,6 +93,7 @@ class Preview : public wxPanel
 | 
			
		|||
    wxStaticText* m_label_show;
 | 
			
		||||
    wxComboCtrl* m_combochecklist_features;
 | 
			
		||||
#if ENABLE_GCODE_VIEWER
 | 
			
		||||
    size_t m_combochecklist_features_pos;
 | 
			
		||||
    wxComboCtrl* m_combochecklist_options;
 | 
			
		||||
#else
 | 
			
		||||
    wxCheckBox* m_checkbox_travel;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -15,6 +15,8 @@
 | 
			
		|||
namespace Slic3r {
 | 
			
		||||
namespace GUI {
 | 
			
		||||
 | 
			
		||||
static constexpr size_t MaxVertexBuffers = 50;
 | 
			
		||||
 | 
			
		||||
GLGizmoFdmSupports::GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id)
 | 
			
		||||
    : GLGizmoBase(parent, icon_filename, sprite_id)
 | 
			
		||||
    , m_quadric(nullptr)
 | 
			
		||||
| 
						 | 
				
			
			@ -123,10 +125,9 @@ void GLGizmoFdmSupports::render_triangles(const Selection& selection) const
 | 
			
		|||
 | 
			
		||||
        // Now render both enforcers and blockers.
 | 
			
		||||
        for (int i=0; i<2; ++i) {
 | 
			
		||||
            if (m_ivas[mesh_id][i].has_VBOs()) {
 | 
			
		||||
            glsafe(::glColor4f(i ? 1.f : 0.2f, 0.2f, i ? 0.2f : 1.0f, 0.5f));
 | 
			
		||||
                m_ivas[mesh_id][i].render();
 | 
			
		||||
            }
 | 
			
		||||
            for (const GLIndexedVertexArray& iva : m_ivas[mesh_id][i])
 | 
			
		||||
                iva.render();
 | 
			
		||||
        }
 | 
			
		||||
        glsafe(::glPopMatrix());
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -205,8 +206,14 @@ void GLGizmoFdmSupports::update_from_model_object()
 | 
			
		|||
            ++num_of_volumes;
 | 
			
		||||
    m_selected_facets.resize(num_of_volumes);
 | 
			
		||||
    m_neighbors.resize(num_of_volumes);
 | 
			
		||||
 | 
			
		||||
    m_ivas.clear();
 | 
			
		||||
    m_ivas.resize(num_of_volumes);
 | 
			
		||||
    for (size_t i=0; i<num_of_volumes; ++i) {
 | 
			
		||||
        m_ivas[i][0].reserve(MaxVertexBuffers);
 | 
			
		||||
        m_ivas[i][1].reserve(MaxVertexBuffers);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    int volume_id = -1;
 | 
			
		||||
    for (const ModelVolume* mv : mo->volumes) {
 | 
			
		||||
| 
						 | 
				
			
			@ -226,7 +233,8 @@ void GLGizmoFdmSupports::update_from_model_object()
 | 
			
		|||
            for (int i : list)
 | 
			
		||||
                m_selected_facets[volume_id][i] = type;
 | 
			
		||||
        }
 | 
			
		||||
        update_vertex_buffers(mv, volume_id, true, true);
 | 
			
		||||
        update_vertex_buffers(mesh, volume_id, FacetSupportType::ENFORCER);
 | 
			
		||||
        update_vertex_buffers(mesh, volume_id, FacetSupportType::BLOCKER);
 | 
			
		||||
 | 
			
		||||
        m_neighbors[volume_id].resize(3 * mesh->its.indices.size());
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -325,7 +333,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
        Vec3f closest_hit = Vec3f::Zero();
 | 
			
		||||
        double closest_hit_squared_distance = std::numeric_limits<double>::max();
 | 
			
		||||
        size_t closest_facet = 0;
 | 
			
		||||
        size_t closest_hit_mesh_id = size_t(-1);
 | 
			
		||||
        int closest_hit_mesh_id = -1;
 | 
			
		||||
 | 
			
		||||
        // Transformations of individual meshes
 | 
			
		||||
        std::vector<Transform3d> trafo_matrices;
 | 
			
		||||
| 
						 | 
				
			
			@ -368,17 +376,22 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
        }
 | 
			
		||||
        // We now know where the ray hit, let's save it and cast another ray
 | 
			
		||||
        if (closest_hit_mesh_id != size_t(-1)) // only if there is at least one hit
 | 
			
		||||
            hit_positions_and_facet_ids[closest_hit_mesh_id].emplace_back(closest_hit, closest_facet);
 | 
			
		||||
 | 
			
		||||
            some_mesh_was_hit = true;
 | 
			
		||||
 | 
			
		||||
        if (some_mesh_was_hit) {
 | 
			
		||||
            // Now propagate the hits
 | 
			
		||||
            mesh_id = -1;
 | 
			
		||||
            const TriangleMesh* mesh = nullptr;
 | 
			
		||||
            for (const ModelVolume* mv : mo->volumes) {
 | 
			
		||||
 | 
			
		||||
                if (! mv->is_model_part())
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                ++mesh_id;
 | 
			
		||||
                if (mesh_id == closest_hit_mesh_id) {
 | 
			
		||||
                    mesh = &mv->mesh();
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool update_both = false;
 | 
			
		||||
 | 
			
		||||
            const Transform3d& trafo_matrix = trafo_matrices[mesh_id];
 | 
			
		||||
| 
						 | 
				
			
			@ -389,24 +402,22 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
            const float avg_scaling = (sf(0) + sf(1) + sf(2))/3.;
 | 
			
		||||
            const float limit = pow(m_cursor_radius/avg_scaling , 2.f);
 | 
			
		||||
 | 
			
		||||
            // For all hits on this mesh...
 | 
			
		||||
            for (const std::pair<Vec3f, size_t>& hit_and_facet : hit_positions_and_facet_ids[mesh_id]) {
 | 
			
		||||
                some_mesh_was_hit = true;
 | 
			
		||||
                const TriangleMesh* mesh = &mv->mesh();
 | 
			
		||||
                std::vector<NeighborData>& neighbors = m_neighbors[mesh_id];
 | 
			
		||||
            const std::pair<Vec3f, size_t>& hit_and_facet = { closest_hit, closest_facet };
 | 
			
		||||
 | 
			
		||||
            const std::vector<NeighborData>& neighbors = m_neighbors[mesh_id];
 | 
			
		||||
 | 
			
		||||
            // Calculate direction from camera to the hit (in mesh coords):
 | 
			
		||||
            Vec3f dir = ((trafo_matrix.inverse() * camera.get_position()).cast<float>() - hit_and_facet.first).normalized();
 | 
			
		||||
 | 
			
		||||
            // A lambda to calculate distance from the centerline:
 | 
			
		||||
                auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f point) -> float {
 | 
			
		||||
            auto squared_distance_from_line = [&hit_and_facet, &dir](const Vec3f& point) -> float {
 | 
			
		||||
                Vec3f diff = hit_and_facet.first - point;
 | 
			
		||||
                return (diff - diff.dot(dir) * dir).squaredNorm();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            // A lambda to determine whether this facet is potentionally visible (still can be obscured)
 | 
			
		||||
                auto faces_camera = [&dir](const ModelVolume* mv, const size_t& facet) -> bool {
 | 
			
		||||
                    return (mv->mesh().stl.facet_start[facet].normal.dot(dir) > 0.);
 | 
			
		||||
            auto faces_camera = [&dir, &mesh](const size_t& facet) -> bool {
 | 
			
		||||
                return (mesh->stl.facet_start[facet].normal.dot(dir) > 0.);
 | 
			
		||||
            };
 | 
			
		||||
            // Now start with the facet the pointer points to and check all adjacent facets. neighbors vector stores
 | 
			
		||||
            // pairs of vertex_idx - facet_idx and is sorted with respect to the former. Neighboring facet index can be
 | 
			
		||||
| 
						 | 
				
			
			@ -427,7 +438,7 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
                        if (dist < limit) {
 | 
			
		||||
                            it = std::lower_bound(neighbors.begin(), neighbors.end(), vertex);
 | 
			
		||||
                            while (it != neighbors.end() && it->first == vertex.first) {
 | 
			
		||||
                                    if (it->second != facet && faces_camera(mv, it->second))
 | 
			
		||||
                                if (it->second != facet && faces_camera(it->second))
 | 
			
		||||
                                    facets_to_select.push_back(it->second);
 | 
			
		||||
                                ++it;
 | 
			
		||||
                            }
 | 
			
		||||
| 
						 | 
				
			
			@ -438,41 +449,50 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
                ++facet_idx;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
                // Now just select all facets that passed.
 | 
			
		||||
            std::vector<size_t> new_facets;
 | 
			
		||||
            new_facets.reserve(facets_to_select.size());
 | 
			
		||||
 | 
			
		||||
            // Now just select all facets that passed and remember which
 | 
			
		||||
            // ones have really changed state.
 | 
			
		||||
            for (size_t next_facet : facets_to_select) {
 | 
			
		||||
                FacetSupportType& facet = m_selected_facets[mesh_id][next_facet];
 | 
			
		||||
 | 
			
		||||
                    if (facet != new_state && facet != FacetSupportType::NONE) {
 | 
			
		||||
                if (facet != new_state) {
 | 
			
		||||
                    if (facet != FacetSupportType::NONE) {
 | 
			
		||||
                        // this triangle is currently in the other VBA.
 | 
			
		||||
                        // Both VBAs need to be refreshed.
 | 
			
		||||
                        update_both = true;
 | 
			
		||||
                    }
 | 
			
		||||
                    facet = new_state;
 | 
			
		||||
                    new_facets.push_back(next_facet);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            update_vertex_buffers(mv, mesh_id,
 | 
			
		||||
                                  new_state == FacetSupportType::ENFORCER || update_both,
 | 
			
		||||
                                  new_state == FacetSupportType::BLOCKER || update_both
 | 
			
		||||
                                  );
 | 
			
		||||
            if (! new_facets.empty()) {
 | 
			
		||||
                if (new_state != FacetSupportType::NONE) {
 | 
			
		||||
                    // append triangles into the respective VBA
 | 
			
		||||
                    update_vertex_buffers(mesh, mesh_id, new_state, &new_facets);
 | 
			
		||||
                    if (update_both) {
 | 
			
		||||
                        auto other = new_state == FacetSupportType::ENFORCER
 | 
			
		||||
                                ? FacetSupportType::BLOCKER
 | 
			
		||||
                                : FacetSupportType::ENFORCER;
 | 
			
		||||
                        update_vertex_buffers(mesh, mesh_id, other); // regenerate the other VBA
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                else {
 | 
			
		||||
                    update_vertex_buffers(mesh, mesh_id, FacetSupportType::ENFORCER);
 | 
			
		||||
                    update_vertex_buffers(mesh, mesh_id, FacetSupportType::BLOCKER);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        if (some_mesh_was_hit)
 | 
			
		||||
        {
 | 
			
		||||
 | 
			
		||||
            if (m_button_down == Button::None)
 | 
			
		||||
                m_button_down = ((action == SLAGizmoEventType::LeftDown) ? Button::Left : Button::Right);
 | 
			
		||||
            // Force rendering. In case the user is dragging, the queue can be
 | 
			
		||||
            // flooded by wxEVT_MOVING event and rendering would be skipped.
 | 
			
		||||
            m_parent.render();
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        if (action == SLAGizmoEventType::Dragging && m_button_down != Button::None) {
 | 
			
		||||
            // Same as above. We don't want the cursor to freeze when we
 | 
			
		||||
            // leave the mesh while painting.
 | 
			
		||||
            m_parent.render();
 | 
			
		||||
        if (action == SLAGizmoEventType::Dragging && m_button_down != Button::None)
 | 
			
		||||
            return true;
 | 
			
		||||
    }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if ((action == SLAGizmoEventType::LeftUp || action == SLAGizmoEventType::RightUp)
 | 
			
		||||
      && m_button_down != Button::None) {
 | 
			
		||||
| 
						 | 
				
			
			@ -493,34 +513,54 @@ bool GLGizmoFdmSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void GLGizmoFdmSupports::update_vertex_buffers(const ModelVolume* mv,
 | 
			
		||||
void GLGizmoFdmSupports::update_vertex_buffers(const TriangleMesh* mesh,
 | 
			
		||||
                                               int mesh_id,
 | 
			
		||||
                                               bool update_enforcers,
 | 
			
		||||
                                               bool update_blockers)
 | 
			
		||||
                                               FacetSupportType type,
 | 
			
		||||
                                               const std::vector<size_t>* new_facets)
 | 
			
		||||
{
 | 
			
		||||
    const TriangleMesh* mesh = &mv->mesh();
 | 
			
		||||
    std::vector<GLIndexedVertexArray>& ivas = m_ivas[mesh_id][type == FacetSupportType::ENFORCER ? 0 : 1];
 | 
			
		||||
 | 
			
		||||
    for (FacetSupportType type : {FacetSupportType::ENFORCER, FacetSupportType::BLOCKER}) {
 | 
			
		||||
        if ((type == FacetSupportType::ENFORCER && ! update_enforcers)
 | 
			
		||||
         || (type == FacetSupportType::BLOCKER && ! update_blockers))
 | 
			
		||||
            continue;
 | 
			
		||||
 | 
			
		||||
        GLIndexedVertexArray& iva = m_ivas[mesh_id][type==FacetSupportType::ENFORCER ? 0 : 1];
 | 
			
		||||
        iva.release_geometry();
 | 
			
		||||
        size_t triangle_cnt=0;
 | 
			
		||||
        for (size_t facet_idx=0; facet_idx<m_selected_facets[mesh_id].size(); ++facet_idx) {
 | 
			
		||||
            FacetSupportType status = m_selected_facets[mesh_id][facet_idx];
 | 
			
		||||
            if (status != type)
 | 
			
		||||
                continue;
 | 
			
		||||
    // lambda to push facet into vertex buffer
 | 
			
		||||
    auto push_facet = [this, &mesh, &mesh_id](size_t idx, GLIndexedVertexArray& iva) {
 | 
			
		||||
        for (int i=0; i<3; ++i)
 | 
			
		||||
                iva.push_geometry(mesh->its.vertices[mesh->its.indices[facet_idx](i)].cast<double>(),
 | 
			
		||||
                                  MeshRaycaster::get_triangle_normal(mesh->its, facet_idx).cast<double>());
 | 
			
		||||
            iva.push_triangle(3*triangle_cnt, 3*triangle_cnt+1, 3*triangle_cnt+2);
 | 
			
		||||
            ++triangle_cnt;
 | 
			
		||||
            iva.push_geometry(
 | 
			
		||||
                mesh->its.vertices[mesh->its.indices[idx](i)].cast<double>(),
 | 
			
		||||
                m_c->raycaster()->raycasters()[mesh_id]->get_triangle_normal(idx).cast<double>()
 | 
			
		||||
            );
 | 
			
		||||
        size_t num = iva.triangle_indices_size;
 | 
			
		||||
        iva.push_triangle(num, num+1, num+2);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    if (ivas.size() == MaxVertexBuffers || ! new_facets) {
 | 
			
		||||
        // If there are too many or they should be regenerated, make one large
 | 
			
		||||
        // GLVertexBufferArray.
 | 
			
		||||
        ivas.clear(); // destructors release geometry
 | 
			
		||||
        ivas.push_back(GLIndexedVertexArray());
 | 
			
		||||
 | 
			
		||||
        bool pushed = false;
 | 
			
		||||
        for (size_t facet_idx=0; facet_idx<m_selected_facets[mesh_id].size(); ++facet_idx) {
 | 
			
		||||
            if (m_selected_facets[mesh_id][facet_idx] == type) {
 | 
			
		||||
                push_facet(facet_idx, ivas.back());
 | 
			
		||||
                pushed = true;
 | 
			
		||||
            }
 | 
			
		||||
        if (! m_selected_facets[mesh_id].empty())
 | 
			
		||||
            iva.finalize_geometry(true);
 | 
			
		||||
        }
 | 
			
		||||
        if (pushed)
 | 
			
		||||
            ivas.back().finalize_geometry(true);
 | 
			
		||||
        else
 | 
			
		||||
            ivas.pop_back();
 | 
			
		||||
    } else {
 | 
			
		||||
        // we are only appending - let's make new vertex array and let the old ones live
 | 
			
		||||
        ivas.push_back(GLIndexedVertexArray());
 | 
			
		||||
        for (size_t facet_idx : *new_facets)
 | 
			
		||||
            push_facet(facet_idx, ivas.back());
 | 
			
		||||
 | 
			
		||||
        if (! new_facets->empty())
 | 
			
		||||
            ivas.back().finalize_geometry(true);
 | 
			
		||||
        else
 | 
			
		||||
            ivas.pop_back();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -553,7 +593,8 @@ void GLGizmoFdmSupports::select_facets_by_angle(float threshold_deg, bool overwr
 | 
			
		|||
                        ? FacetSupportType::BLOCKER
 | 
			
		||||
                        : FacetSupportType::ENFORCER;
 | 
			
		||||
        }
 | 
			
		||||
        update_vertex_buffers(mv, mesh_id, true, true);
 | 
			
		||||
        update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::ENFORCER);
 | 
			
		||||
        update_vertex_buffers(&mv->mesh(), mesh_id, FacetSupportType::BLOCKER);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Plater::TakeSnapshot(wxGetApp().plater(), block ? _L("Block supports by angle")
 | 
			
		||||
| 
						 | 
				
			
			@ -623,7 +664,8 @@ void GLGizmoFdmSupports::on_render_input_window(float x, float y, float bottom_l
 | 
			
		|||
                if (mv->is_model_part()) {
 | 
			
		||||
                    m_selected_facets[idx].assign(m_selected_facets[idx].size(), FacetSupportType::NONE);
 | 
			
		||||
                    mv->m_supported_facets.clear();
 | 
			
		||||
                    update_vertex_buffers(mv, idx, true, true);
 | 
			
		||||
                    update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::ENFORCER);
 | 
			
		||||
                    update_vertex_buffers(&mv->mesh(), idx, FacetSupportType::BLOCKER);
 | 
			
		||||
                    m_parent.set_as_dirty();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -33,14 +33,16 @@ private:
 | 
			
		|||
    // individual facets (one of the enum values above).
 | 
			
		||||
    std::vector<std::vector<FacetSupportType>> m_selected_facets;
 | 
			
		||||
 | 
			
		||||
    // Store two vertex buffer arrays (for enforcers/blockers)
 | 
			
		||||
    // for each model-part volume.
 | 
			
		||||
    std::vector<std::array<GLIndexedVertexArray, 2>> m_ivas;
 | 
			
		||||
    // Vertex buffer arrays for each model-part volume. There is a vector of
 | 
			
		||||
    // arrays so that adding triangles can be done without regenerating all
 | 
			
		||||
    // other triangles. Enforcers and blockers are of course separate.
 | 
			
		||||
    std::vector<std::array<std::vector<GLIndexedVertexArray>, 2>> m_ivas;
 | 
			
		||||
 | 
			
		||||
    void update_vertex_buffers(const ModelVolume* mv,
 | 
			
		||||
    void update_vertex_buffers(const TriangleMesh* mesh,
 | 
			
		||||
                               int mesh_id,
 | 
			
		||||
                               bool update_enforcers,
 | 
			
		||||
                               bool update_blockers);
 | 
			
		||||
                               FacetSupportType type, // enforcers / blockers
 | 
			
		||||
                               const std::vector<size_t>* new_facets = nullptr); // nullptr -> regenerate all
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
public:
 | 
			
		||||
    GLGizmoFdmSupports(GLCanvas3D& parent, const std::string& icon_filename, unsigned int sprite_id);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -623,6 +623,10 @@ void MainFrame::init_menubar()
 | 
			
		|||
            [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(); }, "import_plater", nullptr,
 | 
			
		||||
            [this](){return m_plater != nullptr; }, this);
 | 
			
		||||
        
 | 
			
		||||
        append_menu_item(import_menu, wxID_ANY, _L("Import STL (imperial units)"), _L("Load an model saved with imperial units"),
 | 
			
		||||
            [this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
 | 
			
		||||
            [this](){return m_plater != nullptr; }, this);
 | 
			
		||||
        
 | 
			
		||||
        append_menu_item(import_menu, wxID_ANY, _(L("Import SL1 archive")) + dots, _(L("Load an SL1 output archive")),
 | 
			
		||||
            [this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
 | 
			
		||||
            [this](){return m_plater != nullptr; }, this);    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,11 +95,9 @@ void MeshClipper::recalculate_triangles()
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Vec3f MeshRaycaster::get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx)
 | 
			
		||||
Vec3f MeshRaycaster::get_triangle_normal(size_t facet_idx) const
 | 
			
		||||
{
 | 
			
		||||
    Vec3f a(its.vertices[its.indices[facet_idx](1)] - its.vertices[its.indices[facet_idx](0)]);
 | 
			
		||||
    Vec3f b(its.vertices[its.indices[facet_idx](2)] - its.vertices[its.indices[facet_idx](0)]);
 | 
			
		||||
    return Vec3f(a.cross(b)).normalized();
 | 
			
		||||
    return m_normals[facet_idx];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MeshRaycaster::line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
 | 
			
		||||
| 
						 | 
				
			
			@ -218,12 +216,9 @@ Vec3f MeshRaycaster::get_closest_point(const Vec3f& point, Vec3f* normal) const
 | 
			
		|||
    int idx = 0;
 | 
			
		||||
    Vec3d closest_point;
 | 
			
		||||
    m_emesh.squared_distance(point.cast<double>(), idx, closest_point);
 | 
			
		||||
    if (normal) {
 | 
			
		||||
        auto indices = m_emesh.F().row(idx);
 | 
			
		||||
        Vec3d a(m_emesh.V().row(indices(1)) - m_emesh.V().row(indices(0)));
 | 
			
		||||
        Vec3d b(m_emesh.V().row(indices(2)) - m_emesh.V().row(indices(0)));
 | 
			
		||||
        *normal = Vec3f(a.cross(b).cast<float>());
 | 
			
		||||
    }
 | 
			
		||||
    if (normal)
 | 
			
		||||
        *normal = m_normals[idx];
 | 
			
		||||
 | 
			
		||||
    return closest_point.cast<float>();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -108,7 +108,11 @@ public:
 | 
			
		|||
    // The pointer can be invalidated after constructor returns.
 | 
			
		||||
    MeshRaycaster(const TriangleMesh& mesh)
 | 
			
		||||
        : m_emesh(mesh)
 | 
			
		||||
    {}
 | 
			
		||||
    {
 | 
			
		||||
        m_normals.reserve(mesh.stl.facet_start.size());
 | 
			
		||||
        for (const stl_facet& facet : mesh.stl.facet_start)
 | 
			
		||||
            m_normals.push_back(facet.normal);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void line_from_mouse_pos(const Vec2d& mouse_pos, const Transform3d& trafo, const Camera& camera,
 | 
			
		||||
                             Vec3d& point, Vec3d& direction) const;
 | 
			
		||||
| 
						 | 
				
			
			@ -140,10 +144,11 @@ public:
 | 
			
		|||
 | 
			
		||||
    Vec3f get_closest_point(const Vec3f& point, Vec3f* normal = nullptr) const;
 | 
			
		||||
 | 
			
		||||
    static Vec3f get_triangle_normal(const indexed_triangle_set& its, size_t facet_idx);
 | 
			
		||||
    Vec3f get_triangle_normal(size_t facet_idx) const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
    sla::EigenMesh3D m_emesh;
 | 
			
		||||
    std::vector<stl_normal> m_normals;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1170,12 +1170,15 @@ void Sidebar::show_info_sizer()
 | 
			
		|||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
 | 
			
		||||
    double koef = imperial_units ? ObjectManipulation::mm_to_in : 1.0f;
 | 
			
		||||
 | 
			
		||||
    auto size = model_object->bounding_box().size();
 | 
			
		||||
    p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0), size(1), size(2)));
 | 
			
		||||
    p->object_info->info_size->SetLabel(wxString::Format("%.2f x %.2f x %.2f",size(0)*koef, size(1)*koef, size(2)*koef));
 | 
			
		||||
    p->object_info->info_materials->SetLabel(wxString::Format("%d", static_cast<int>(model_object->materials_count())));
 | 
			
		||||
 | 
			
		||||
    const auto& stats = model_object->get_object_stl_stats();//model_object->volumes.front()->mesh.stl.stats;
 | 
			
		||||
    p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume));
 | 
			
		||||
    p->object_info->info_volume->SetLabel(wxString::Format("%.2f", stats.volume*pow(koef,3)));
 | 
			
		||||
    p->object_info->info_facets->SetLabel(wxString::Format(_L("%d (%d shells)"), static_cast<int>(model_object->facets_count()), stats.number_of_parts));
 | 
			
		||||
 | 
			
		||||
    int errors = stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
 | 
			
		||||
| 
						 | 
				
			
			@ -1253,18 +1256,24 @@ void Sidebar::update_sliced_info_sizer()
 | 
			
		|||
            const PrintStatistics& ps = p->plater->fff_print().print_statistics();
 | 
			
		||||
            const bool is_wipe_tower = ps.total_wipe_tower_filament > 0;
 | 
			
		||||
 | 
			
		||||
            wxString new_label = _L("Used Filament (m)");
 | 
			
		||||
            bool imperial_units = wxGetApp().app_config->get("use_inches") == "1";
 | 
			
		||||
            double koef = imperial_units ? ObjectManipulation::in_to_mm : 1000.0;
 | 
			
		||||
 | 
			
		||||
            wxString new_label = imperial_units ? _L("Used Filament (in)") : _L("Used Filament (m)");
 | 
			
		||||
            if (is_wipe_tower)
 | 
			
		||||
                new_label += format_wxstr(":\n    - %1%\n    - %2%", _L("objects"), _L("wipe tower"));
 | 
			
		||||
 | 
			
		||||
            wxString info_text = is_wipe_tower ?
 | 
			
		||||
                                wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / 1000,
 | 
			
		||||
                                                (ps.total_used_filament - ps.total_wipe_tower_filament) / 1000,
 | 
			
		||||
                                                ps.total_wipe_tower_filament / 1000) :
 | 
			
		||||
                                wxString::Format("%.2f", ps.total_used_filament / 1000);
 | 
			
		||||
                                wxString::Format("%.2f \n%.2f \n%.2f", ps.total_used_filament / /*1000*/koef,
 | 
			
		||||
                                                (ps.total_used_filament - ps.total_wipe_tower_filament) / /*1000*/koef,
 | 
			
		||||
                                                ps.total_wipe_tower_filament / /*1000*/koef) :
 | 
			
		||||
                                wxString::Format("%.2f", ps.total_used_filament / /*1000*/koef);
 | 
			
		||||
            p->sliced_info->SetTextAndShow(siFilament_m,    info_text,      new_label);
 | 
			
		||||
 | 
			
		||||
            p->sliced_info->SetTextAndShow(siFilament_mm3,  wxString::Format("%.2f", ps.total_extruded_volume));
 | 
			
		||||
            koef = imperial_units ? pow(ObjectManipulation::mm_to_in, 3) : 1.0f;
 | 
			
		||||
            new_label = imperial_units ? _L("Used Filament (in³)") : _L("Used Filament (mm³)");
 | 
			
		||||
            info_text = wxString::Format("%.2f", imperial_units ? ps.total_extruded_volume * koef : ps.total_extruded_volume);
 | 
			
		||||
            p->sliced_info->SetTextAndShow(siFilament_mm3,  info_text,      new_label);
 | 
			
		||||
            p->sliced_info->SetTextAndShow(siFilament_g,    ps.total_weight == 0.0 ? "N/A" : wxString::Format("%.2f", ps.total_weight));
 | 
			
		||||
 | 
			
		||||
            new_label = _L("Cost");
 | 
			
		||||
| 
						 | 
				
			
			@ -1414,6 +1423,13 @@ void Sidebar::collapse(bool collapse)
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void Sidebar::update_ui_from_settings()
 | 
			
		||||
{
 | 
			
		||||
    p->object_manipulation->update_ui_from_settings();
 | 
			
		||||
    show_info_sizer();
 | 
			
		||||
    update_sliced_info_sizer();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<PresetComboBox*>& Sidebar::combos_filament()
 | 
			
		||||
{
 | 
			
		||||
    return p->combos_filament;
 | 
			
		||||
| 
						 | 
				
			
			@ -1653,7 +1669,7 @@ struct Plater::priv
 | 
			
		|||
    BoundingBoxf bed_shape_bb() const;
 | 
			
		||||
    BoundingBox scaled_bed_shape_bb() const;
 | 
			
		||||
 | 
			
		||||
    std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config);
 | 
			
		||||
    std::vector<size_t> load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool used_inches = false);
 | 
			
		||||
    std::vector<size_t> load_model_objects(const ModelObjectPtrs &model_objects);
 | 
			
		||||
    wxString get_export_file(GUI::FileType file_type);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2134,6 +2150,8 @@ void Plater::priv::update_ui_from_settings()
 | 
			
		|||
 | 
			
		||||
    view3D->get_canvas3d()->update_ui_from_settings();
 | 
			
		||||
    preview->get_canvas3d()->update_ui_from_settings();
 | 
			
		||||
 | 
			
		||||
    sidebar->update_ui_from_settings();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Called after the print technology was changed.
 | 
			
		||||
| 
						 | 
				
			
			@ -2166,7 +2184,7 @@ BoundingBox Plater::priv::scaled_bed_shape_bb() const
 | 
			
		|||
    return bed_shape.bounding_box();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config)
 | 
			
		||||
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/)
 | 
			
		||||
{
 | 
			
		||||
    if (input_files.empty()) { return std::vector<size_t>(); }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2263,6 +2281,23 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
 | 
			
		|||
        {
 | 
			
		||||
            // The model should now be initialized
 | 
			
		||||
 | 
			
		||||
            auto convert_from_imperial_units = [](Model& model) {
 | 
			
		||||
                model.convert_from_imperial_units();
 | 
			
		||||
                wxGetApp().app_config->set("use_inches", "1");
 | 
			
		||||
                wxGetApp().sidebar().update_ui_from_settings();
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            if (imperial_units)
 | 
			
		||||
                convert_from_imperial_units(model);
 | 
			
		||||
            else if (model.looks_like_imperial_units()) {
 | 
			
		||||
                wxMessageDialog msg_dlg(q, _L(
 | 
			
		||||
                    "This model looks like saved in inches.\n"
 | 
			
		||||
                    "Should I consider this model as a saved in inches and convert it?") + "\n",
 | 
			
		||||
                    _L("Saved in inches object detected"), wxICON_WARNING | wxYES | wxNO);
 | 
			
		||||
                if (msg_dlg.ShowModal() == wxID_YES)
 | 
			
		||||
                    convert_from_imperial_units(model);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (! is_project_file) {
 | 
			
		||||
                if (model.looks_like_multipart_object()) {
 | 
			
		||||
                    wxMessageDialog msg_dlg(q, _L(
 | 
			
		||||
| 
						 | 
				
			
			@ -4317,7 +4352,7 @@ void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& lab
 | 
			
		|||
// Plater / Public
 | 
			
		||||
 | 
			
		||||
Plater::Plater(wxWindow *parent, MainFrame *main_frame)
 | 
			
		||||
    : wxPanel(parent)
 | 
			
		||||
    : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(76 * wxGetApp().em_unit(), 49 * wxGetApp().em_unit()))
 | 
			
		||||
    , p(new priv(this, main_frame))
 | 
			
		||||
{
 | 
			
		||||
    // Initialization performed in the private c-tor
 | 
			
		||||
| 
						 | 
				
			
			@ -4369,7 +4404,7 @@ void Plater::load_project(const wxString& filename)
 | 
			
		|||
        p->set_project_filename(filename);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::add_model()
 | 
			
		||||
void Plater::add_model(bool imperial_units/* = false*/)
 | 
			
		||||
{
 | 
			
		||||
    wxArrayString input_files;
 | 
			
		||||
    wxGetApp().import_model(this, input_files);
 | 
			
		||||
| 
						 | 
				
			
			@ -4397,7 +4432,7 @@ void Plater::add_model()
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    Plater::TakeSnapshot snapshot(this, snapshot_label);
 | 
			
		||||
    load_files(paths, true, false);
 | 
			
		||||
    load_files(paths, true, false, imperial_units);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::import_sl1_archive()
 | 
			
		||||
| 
						 | 
				
			
			@ -4418,16 +4453,16 @@ void Plater::extract_config_from_project()
 | 
			
		|||
    load_files(input_paths, false, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config) { return p->load_files(input_files, load_model, load_config); }
 | 
			
		||||
std::vector<size_t> Plater::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/) { return p->load_files(input_files, load_model, load_config, imperial_units); }
 | 
			
		||||
 | 
			
		||||
// To be called when providing a list of files to the GUI slic3r on command line.
 | 
			
		||||
std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, bool load_model, bool load_config)
 | 
			
		||||
std::vector<size_t> Plater::load_files(const std::vector<std::string>& input_files, bool load_model, bool load_config, bool imperial_units /*= false*/)
 | 
			
		||||
{
 | 
			
		||||
    std::vector<fs::path> paths;
 | 
			
		||||
    paths.reserve(input_files.size());
 | 
			
		||||
    for (const std::string& path : input_files)
 | 
			
		||||
        paths.emplace_back(path);
 | 
			
		||||
    return p->load_files(paths, load_model, load_config);
 | 
			
		||||
    return p->load_files(paths, load_model, load_config, imperial_units);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Plater::update() { p->update(); }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -133,6 +133,7 @@ public:
 | 
			
		|||
    bool                    is_collapsed();
 | 
			
		||||
    void                    collapse(bool collapse);
 | 
			
		||||
    void                    update_searcher();
 | 
			
		||||
    void                    update_ui_from_settings();
 | 
			
		||||
 | 
			
		||||
    std::vector<PresetComboBox*>&   combos_filament();
 | 
			
		||||
    Search::OptionsSearcher&        get_searcher();
 | 
			
		||||
| 
						 | 
				
			
			@ -165,13 +166,13 @@ public:
 | 
			
		|||
    void new_project();
 | 
			
		||||
    void load_project();
 | 
			
		||||
    void load_project(const wxString& filename);
 | 
			
		||||
    void add_model();
 | 
			
		||||
    void add_model(bool imperial_units = false);
 | 
			
		||||
    void import_sl1_archive();
 | 
			
		||||
    void extract_config_from_project();
 | 
			
		||||
 | 
			
		||||
    std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true);
 | 
			
		||||
    std::vector<size_t> load_files(const std::vector<boost::filesystem::path>& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
 | 
			
		||||
    // To be called when providing a list of files to the GUI slic3r on command line.
 | 
			
		||||
    std::vector<size_t> load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true);
 | 
			
		||||
    std::vector<size_t> load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true, bool imperial_units = false);
 | 
			
		||||
 | 
			
		||||
    void update();
 | 
			
		||||
    void stop_jobs();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -120,7 +120,16 @@ void PreferencesDialog::build()
 | 
			
		|||
	option = Option (def, "use_retina_opengl");
 | 
			
		||||
	m_optgroup_general->append_single_option_line(option);
 | 
			
		||||
#endif
 | 
			
		||||
/*  // ysFIXME THis part is temporary commented
 | 
			
		||||
    // The using of inches is implemented just for object's size and position
 | 
			
		||||
    
 | 
			
		||||
	def.label = L("Use inches instead of millimeters");
 | 
			
		||||
	def.type = coBool;
 | 
			
		||||
	def.tooltip = L("Use inches instead of millimeters for the object's size");
 | 
			
		||||
	def.set_default_value(new ConfigOptionBool{ app_config->get("use_inches") == "1" });
 | 
			
		||||
	option = Option(def, "use_inches");
 | 
			
		||||
	m_optgroup_general->append_single_option_line(option);
 | 
			
		||||
*/
 | 
			
		||||
	m_optgroup_camera = std::make_shared<ConfigOptionsGroup>(this, _(L("Camera")));
 | 
			
		||||
	m_optgroup_camera->label_width = 40;
 | 
			
		||||
	m_optgroup_camera->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -460,7 +460,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
 | 
			
		|||
 | 
			
		||||
    check_sizer->Add(new wxStaticText(this, wxID_ANY, _L("Use for search") + ":"), 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
 | 
			
		||||
    check_sizer->Add(check_category, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
 | 
			
		||||
    if (GUI::wxGetApp().is_localized())
 | 
			
		||||
    if (check_english)
 | 
			
		||||
        check_sizer->Add(check_english,  0, wxALIGN_CENTER_VERTICAL | wxRIGHT, border);
 | 
			
		||||
    check_sizer->AddStretchSpacer(border);
 | 
			
		||||
    check_sizer->Add(cancel_btn,     0, wxALIGN_CENTER_VERTICAL);
 | 
			
		||||
| 
						 | 
				
			
			@ -484,7 +484,7 @@ SearchDialog::SearchDialog(OptionsSearcher* searcher)
 | 
			
		|||
#endif //__WXMSW__
 | 
			
		||||
 | 
			
		||||
    check_category->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
 | 
			
		||||
    if (GUI::wxGetApp().is_localized())
 | 
			
		||||
    if (check_english)
 | 
			
		||||
        check_english ->Bind(wxEVT_CHECKBOX, &SearchDialog::OnCheck, this);
 | 
			
		||||
 | 
			
		||||
    Bind(wxEVT_MOTION, &SearchDialog::OnMotion, this);
 | 
			
		||||
| 
						 | 
				
			
			@ -505,6 +505,7 @@ void SearchDialog::Popup(wxPoint position /*= wxDefaultPosition*/)
 | 
			
		|||
 | 
			
		||||
    const OptionViewParameters& params = searcher->view_params;
 | 
			
		||||
    check_category->SetValue(params.category);
 | 
			
		||||
    if (check_english)
 | 
			
		||||
        check_english->SetValue(params.english);
 | 
			
		||||
 | 
			
		||||
    this->SetPosition(position);
 | 
			
		||||
| 
						 | 
				
			
			@ -594,6 +595,9 @@ void SearchDialog::OnSelect(wxDataViewEvent& event)
 | 
			
		|||
 | 
			
		||||
void SearchDialog::update_list()
 | 
			
		||||
{
 | 
			
		||||
    // Under OSX model->Clear invoke wxEVT_DATAVIEW_SELECTION_CHANGED, so
 | 
			
		||||
    // set prevent_list_events to true already here 
 | 
			
		||||
    prevent_list_events = true;
 | 
			
		||||
    search_list_model->Clear();
 | 
			
		||||
 | 
			
		||||
    const std::vector<FoundOption>& filters = searcher->found_options();
 | 
			
		||||
| 
						 | 
				
			
			@ -601,7 +605,6 @@ void SearchDialog::update_list()
 | 
			
		|||
        search_list_model->Prepend(item.label);
 | 
			
		||||
 | 
			
		||||
    // select first item 
 | 
			
		||||
    prevent_list_events = true;
 | 
			
		||||
    search_list->Select(search_list_model->GetItem(0));
 | 
			
		||||
    prevent_list_events = false;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -609,6 +612,7 @@ void SearchDialog::update_list()
 | 
			
		|||
void SearchDialog::OnCheck(wxCommandEvent& event)
 | 
			
		||||
{
 | 
			
		||||
    OptionViewParameters& params = searcher->view_params;
 | 
			
		||||
    if (check_english)
 | 
			
		||||
        params.english  = check_english->GetValue();
 | 
			
		||||
    params.category = check_category->GetValue();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue