mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-08-03 20:13:59 -06:00
Merge remote-tracking branch 'origin/master' into ys_bugfixing
This commit is contained in:
commit
09f4831f4e
86 changed files with 4705 additions and 4380 deletions
|
@ -241,8 +241,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
|
|||
: m_transformed_bounding_box_dirty(true)
|
||||
, m_sla_shift_z(0.0)
|
||||
, m_transformed_convex_hull_bounding_box_dirty(true)
|
||||
, m_convex_hull(nullptr)
|
||||
, m_convex_hull_owned(false)
|
||||
// geometry_id == 0 -> invalid
|
||||
, geometry_id(std::pair<size_t, size_t>(0, 0))
|
||||
, extruder_id(0)
|
||||
|
@ -268,12 +266,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
|
|||
set_render_color(r, g, b, a);
|
||||
}
|
||||
|
||||
GLVolume::~GLVolume()
|
||||
{
|
||||
if (m_convex_hull_owned)
|
||||
delete m_convex_hull;
|
||||
}
|
||||
|
||||
void GLVolume::set_render_color(float r, float g, float b, float a)
|
||||
{
|
||||
render_color[0] = r;
|
||||
|
@ -335,12 +327,6 @@ void GLVolume::set_color_from_model_volume(const ModelVolume *model_volume)
|
|||
color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
|
||||
}
|
||||
|
||||
void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned)
|
||||
{
|
||||
m_convex_hull = convex_hull;
|
||||
m_convex_hull_owned = owned;
|
||||
}
|
||||
|
||||
Transform3d GLVolume::world_matrix() const
|
||||
{
|
||||
Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix();
|
||||
|
@ -377,7 +363,7 @@ const BoundingBoxf3& GLVolume::transformed_convex_hull_bounding_box() const
|
|||
|
||||
BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &trafo) const
|
||||
{
|
||||
return (m_convex_hull != nullptr && m_convex_hull->stl.stats.number_of_facets > 0) ?
|
||||
return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ?
|
||||
m_convex_hull->transformed_bounding_box(trafo) :
|
||||
bounding_box.transformed(trafo);
|
||||
}
|
||||
|
@ -587,7 +573,7 @@ int GLVolumeCollection::load_object_volume(
|
|||
const ModelVolume *model_volume = model_object->volumes[volume_idx];
|
||||
const int extruder_id = model_volume->extruder_id();
|
||||
const ModelInstance *instance = model_object->instances[instance_idx];
|
||||
const TriangleMesh& mesh = model_volume->mesh;
|
||||
const TriangleMesh& mesh = model_volume->mesh();
|
||||
float color[4];
|
||||
memcpy(color, GLVolume::MODEL_COLOR[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
|
||||
/* if (model_volume->is_support_blocker()) {
|
||||
|
@ -613,7 +599,7 @@ int GLVolumeCollection::load_object_volume(
|
|||
if (model_volume->is_model_part())
|
||||
{
|
||||
// GLVolume will reference a convex hull from model_volume!
|
||||
v.set_convex_hull(&model_volume->get_convex_hull(), false);
|
||||
v.set_convex_hull(model_volume->get_convex_hull_shared_ptr());
|
||||
if (extruder_id != -1)
|
||||
v.extruder_id = extruder_id;
|
||||
}
|
||||
|
@ -656,7 +642,10 @@ void GLVolumeCollection::load_object_auxiliary(
|
|||
v.composite_id = GLVolume::CompositeID(obj_idx, - int(milestone), (int)instance_idx.first);
|
||||
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
|
||||
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
|
||||
v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true);
|
||||
if (&instance_idx == &instances.back())
|
||||
v.set_convex_hull(std::move(convex_hull));
|
||||
else
|
||||
v.set_convex_hull(convex_hull);
|
||||
v.is_modifier = false;
|
||||
v.shader_outside_printer_detection_enabled = (milestone == slaposSupportTree);
|
||||
v.set_instance_transformation(model_instance.get_transformation());
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "slic3r/GUI/GLCanvas3DManager.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#ifndef NDEBUG
|
||||
#define HAS_GLSAFE
|
||||
|
@ -243,7 +244,6 @@ public:
|
|||
|
||||
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
|
||||
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
|
||||
~GLVolume();
|
||||
|
||||
private:
|
||||
Geometry::Transformation m_instance_transformation;
|
||||
|
@ -255,10 +255,8 @@ private:
|
|||
mutable BoundingBoxf3 m_transformed_bounding_box;
|
||||
// Whether or not is needed to recalculate the transformed bounding box.
|
||||
mutable bool m_transformed_bounding_box_dirty;
|
||||
// Pointer to convex hull of the original mesh, if any.
|
||||
// This object may or may not own the convex hull instance based on m_convex_hull_owned
|
||||
const TriangleMesh* m_convex_hull;
|
||||
bool m_convex_hull_owned;
|
||||
// Convex hull of the volume, if any.
|
||||
std::shared_ptr<const TriangleMesh> m_convex_hull;
|
||||
// Bounding box of this volume, in unscaled coordinates.
|
||||
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
|
||||
// Whether or not is needed to recalculate the transformed convex hull bounding box.
|
||||
|
@ -395,7 +393,9 @@ public:
|
|||
double get_sla_shift_z() const { return m_sla_shift_z; }
|
||||
void set_sla_shift_z(double z) { m_sla_shift_z = z; }
|
||||
|
||||
void set_convex_hull(const TriangleMesh *convex_hull, bool owned);
|
||||
void set_convex_hull(std::shared_ptr<const TriangleMesh> convex_hull) { m_convex_hull = std::move(convex_hull); }
|
||||
void set_convex_hull(const TriangleMesh &convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(convex_hull); }
|
||||
void set_convex_hull(TriangleMesh &&convex_hull) { m_convex_hull = std::make_shared<const TriangleMesh>(std::move(convex_hull)); }
|
||||
|
||||
int object_idx() const { return this->composite_id.object_id; }
|
||||
int volume_idx() const { return this->composite_id.volume_id; }
|
||||
|
|
|
@ -89,7 +89,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
if (copy_file(m_temp_output_path, export_path) != 0)
|
||||
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
|
||||
throw std::runtime_error(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?")));
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
run_post_process_scripts(export_path, m_fff_print->config());
|
||||
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
|
||||
|
|
|
@ -5524,7 +5524,7 @@ void GLCanvas3D::_load_sla_shells()
|
|||
v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0));
|
||||
v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation));
|
||||
v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.);
|
||||
v.set_convex_hull(new TriangleMesh(std::move(mesh.convex_hull_3d())), true);
|
||||
v.set_convex_hull(mesh.convex_hull_3d());
|
||||
};
|
||||
|
||||
// adds objects' volumes
|
||||
|
|
|
@ -261,7 +261,7 @@ wxString ObjectList::get_mesh_errors_list(const int obj_idx, const int vol_idx /
|
|||
|
||||
const stl_stats& stats = vol_idx == -1 ?
|
||||
(*m_objects)[obj_idx]->get_object_stl_stats() :
|
||||
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh.stl.stats;
|
||||
(*m_objects)[obj_idx]->volumes[vol_idx]->mesh().stl.stats;
|
||||
|
||||
std::map<std::string, int> error_msg = {
|
||||
{ L("degenerate facets"), stats.degenerate_facets },
|
||||
|
@ -1597,7 +1597,7 @@ void ObjectList::load_generic_subobject(const std::string& type_name, const Mode
|
|||
// First (any) GLVolume of the selected instance. They all share the same instance matrix.
|
||||
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
// Transform the new modifier to be aligned with the print bed.
|
||||
const BoundingBoxf3 mesh_bb = new_volume->mesh.bounding_box();
|
||||
const BoundingBoxf3 mesh_bb = new_volume->mesh().bounding_box();
|
||||
new_volume->set_transformation(volume_to_bed_transformation(v->get_instance_transformation(), mesh_bb));
|
||||
// Set the modifier position.
|
||||
auto offset = (type_name == "Slab") ?
|
||||
|
|
|
@ -92,6 +92,7 @@ void msw_rescale_word_local_combo(wxBitmapComboBox* combo)
|
|||
combo->SetValue(selection);
|
||||
}
|
||||
|
||||
|
||||
ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
||||
OG_Settings(parent, true)
|
||||
#ifndef __APPLE__
|
||||
|
@ -162,16 +163,71 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
|
||||
const int field_width = 5;
|
||||
|
||||
// Mirror button size:
|
||||
const int mirror_btn_width = 3;
|
||||
|
||||
// Legend for object modification
|
||||
line = Line{ "", "" };
|
||||
def.label = "";
|
||||
def.type = coString;
|
||||
def.width = field_width/*50*/;
|
||||
def.width = field_width - mirror_btn_width;//field_width/*50*/;
|
||||
|
||||
// Load bitmaps to be used for the mirroring buttons:
|
||||
m_mirror_bitmap_on = ScalableBitmap(parent, "mirroring_on.png");
|
||||
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png");
|
||||
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
|
||||
|
||||
for (const std::string axis : { "x", "y", "z" }) {
|
||||
const std::string label = boost::algorithm::to_upper_copy(axis);
|
||||
def.set_default_value(new ConfigOptionString{ " " + label });
|
||||
Option option = Option(def, axis + "_axis_legend");
|
||||
|
||||
unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
|
||||
|
||||
// We will add a button to toggle mirroring to each axis:
|
||||
auto mirror_button = [=](wxWindow* parent) {
|
||||
wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
|
||||
btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label));
|
||||
|
||||
m_mirror_buttons[axis_idx].first = btn;
|
||||
m_mirror_buttons[axis_idx].second = mbShown;
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn);
|
||||
|
||||
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
|
||||
Axis axis = (Axis)(axis_idx + X);
|
||||
if (m_mirror_buttons[axis_idx].second == mbHidden)
|
||||
return;
|
||||
|
||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||
Selection& selection = canvas->get_selection();
|
||||
|
||||
if (selection.is_single_volume() || selection.is_single_modifier()) {
|
||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
|
||||
volume->set_volume_mirror(axis, -volume->get_volume_mirror(axis));
|
||||
}
|
||||
else if (selection.is_single_full_instance()) {
|
||||
for (unsigned int idx : selection.get_volume_idxs()){
|
||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
|
||||
volume->set_instance_mirror(axis, -volume->get_instance_mirror(axis));
|
||||
}
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
// Update mirroring at the GLVolumes.
|
||||
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
||||
selection.synchronize_unselected_volumes();
|
||||
// Copy mirroring values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
|
||||
canvas->do_mirror();
|
||||
canvas->set_as_dirty();
|
||||
UpdateAndShow(true);
|
||||
});
|
||||
return sizer;
|
||||
};
|
||||
|
||||
option.side_widget = mirror_button;
|
||||
line.append_option(option);
|
||||
}
|
||||
line.near_label_widget = [this](wxWindow* parent) {
|
||||
|
@ -190,8 +246,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
def.set_default_value(new ConfigOptionFloat(0.0));
|
||||
def.width = field_width/*50*/;
|
||||
|
||||
// Add "uniform scaling" button in front of "Scale" option
|
||||
if (option_name == "Scale") {
|
||||
// Add "uniform scaling" button in front of "Scale" option
|
||||
line.near_label_widget = [this](wxWindow* parent) {
|
||||
auto btn = new LockButton(parent, wxID_ANY);
|
||||
btn->Bind(wxEVT_BUTTON, [btn, this](wxCommandEvent &event){
|
||||
|
@ -201,8 +257,59 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
m_lock_bnt = btn;
|
||||
return btn;
|
||||
};
|
||||
// Add reset scale button
|
||||
auto reset_scale_button = [=](wxWindow* parent) {
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
|
||||
btn->SetToolTip(_(L("Reset scale")));
|
||||
m_reset_scale_button = btn;
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn, wxBU_EXACTFIT);
|
||||
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
|
||||
change_scale_value(0, 100.);
|
||||
change_scale_value(1, 100.);
|
||||
change_scale_value(2, 100.);
|
||||
});
|
||||
return sizer;
|
||||
};
|
||||
line.append_widget(reset_scale_button);
|
||||
}
|
||||
else if (option_name == "Rotation") {
|
||||
// Add reset rotation button
|
||||
auto reset_rotation_button = [=](wxWindow* parent) {
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, ScalableBitmap(parent, "undo"));
|
||||
btn->SetToolTip(_(L("Reset rotation")));
|
||||
m_reset_rotation_button = btn;
|
||||
auto sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
sizer->Add(btn, wxBU_EXACTFIT);
|
||||
btn->Bind(wxEVT_BUTTON, [=](wxCommandEvent &e) {
|
||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||
Selection& selection = canvas->get_selection();
|
||||
|
||||
if (selection.is_single_volume() || selection.is_single_modifier()) {
|
||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(*selection.get_volume_idxs().begin()));
|
||||
volume->set_volume_rotation(Vec3d::Zero());
|
||||
}
|
||||
else if (selection.is_single_full_instance()) {
|
||||
for (unsigned int idx : selection.get_volume_idxs()){
|
||||
GLVolume* volume = const_cast<GLVolume*>(selection.get_volume(idx));
|
||||
volume->set_instance_rotation(Vec3d::Zero());
|
||||
}
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
||||
// Update rotation at the GLVolumes.
|
||||
selection.synchronize_unselected_instances(Selection::SYNC_ROTATION_GENERAL);
|
||||
selection.synchronize_unselected_volumes();
|
||||
// Copy rotation values from GLVolumes into Model (ModelInstance / ModelVolume), trigger background processing.
|
||||
canvas->do_rotate();
|
||||
|
||||
UpdateAndShow(true);
|
||||
});
|
||||
return sizer;
|
||||
};
|
||||
line.append_widget(reset_rotation_button);
|
||||
}
|
||||
// Add empty bmp (Its size have to be equal to PrusaLockButton) in front of "Size" option to label alignment
|
||||
else if (option_name == "Size") {
|
||||
line.near_label_widget = [this](wxWindow* parent) {
|
||||
|
@ -224,8 +331,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
return line;
|
||||
};
|
||||
|
||||
|
||||
// Settings table
|
||||
m_og->sidetext_width = 3;
|
||||
m_og->append_line(add_og_to_object_settings(L("Position"), L("mm")), &m_move_Label);
|
||||
m_og->append_line(add_og_to_object_settings(L("Rotation"), "°"), &m_rotate_Label);
|
||||
m_og->append_line(add_og_to_object_settings(L("Scale"), "%"), &m_scale_Label);
|
||||
|
@ -239,6 +346,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
ctrl->msw_rescale();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ObjectManipulation::Show(const bool show)
|
||||
{
|
||||
|
@ -408,9 +517,95 @@ void ObjectManipulation::update_if_dirty()
|
|||
else
|
||||
m_og->disable();
|
||||
|
||||
update_reset_buttons_visibility();
|
||||
update_mirror_buttons_visibility();
|
||||
|
||||
m_dirty = false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ObjectManipulation::update_reset_buttons_visibility()
|
||||
{
|
||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||
if (!canvas)
|
||||
return;
|
||||
const Selection& selection = canvas->get_selection();
|
||||
|
||||
bool show_rotation = false;
|
||||
bool show_scale = false;
|
||||
|
||||
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
Vec3d rotation;
|
||||
Vec3d scale;
|
||||
|
||||
if (selection.is_single_full_instance()) {
|
||||
rotation = volume->get_instance_rotation();
|
||||
scale = volume->get_instance_scaling_factor();
|
||||
}
|
||||
else {
|
||||
rotation = volume->get_volume_rotation();
|
||||
scale = volume->get_volume_scaling_factor();
|
||||
}
|
||||
show_rotation = !rotation.isApprox(Vec3d::Zero());
|
||||
show_scale = !scale.isApprox(Vec3d::Ones());
|
||||
}
|
||||
|
||||
wxGetApp().CallAfter([this, show_rotation, show_scale]{
|
||||
m_reset_rotation_button->Show(show_rotation);
|
||||
m_reset_scale_button->Show(show_scale);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
void ObjectManipulation::update_mirror_buttons_visibility()
|
||||
{
|
||||
GLCanvas3D* canvas = wxGetApp().plater()->canvas3D();
|
||||
Selection& selection = canvas->get_selection();
|
||||
std::array<MirrorButtonState, 3> new_states = {mbHidden, mbHidden, mbHidden};
|
||||
|
||||
if (!m_world_coordinates) {
|
||||
if (selection.is_single_full_instance() || selection.is_single_modifier() || selection.is_single_volume()) {
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
Vec3d mirror;
|
||||
|
||||
if (selection.is_single_full_instance())
|
||||
mirror = volume->get_instance_mirror();
|
||||
else
|
||||
mirror = volume->get_volume_mirror();
|
||||
|
||||
for (unsigned char i=0; i<3; ++i)
|
||||
new_states[i] = (mirror[i] < 0. ? mbActive : mbShown);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// the mirroring buttons should be hidden in world coordinates,
|
||||
// unless we make it actually mirror in world coords.
|
||||
}
|
||||
|
||||
// Hiding the buttons through Hide() always messed up the sizers. As a workaround, the button
|
||||
// is assigned a transparent bitmap. We must of course remember the actual state.
|
||||
wxGetApp().CallAfter([this, new_states]{
|
||||
for (int i=0; i<3; ++i) {
|
||||
if (new_states[i] != m_mirror_buttons[i].second) {
|
||||
const wxBitmap* bmp;
|
||||
switch (new_states[i]) {
|
||||
case mbHidden : bmp = &m_mirror_bitmap_hidden.bmp(); m_mirror_buttons[i].first->Enable(false); break;
|
||||
case mbShown : bmp = &m_mirror_bitmap_off.bmp(); m_mirror_buttons[i].first->Enable(true); break;
|
||||
case mbActive : bmp = &m_mirror_bitmap_on.bmp(); m_mirror_buttons[i].first->Enable(true); break;
|
||||
}
|
||||
m_mirror_buttons[i].first->SetBitmap(*bmp);
|
||||
m_mirror_buttons[i].second = new_states[i];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#ifndef __APPLE__
|
||||
void ObjectManipulation::emulate_kill_focus()
|
||||
{
|
||||
|
@ -493,7 +688,7 @@ void ObjectManipulation::change_rotation_value(int axis, double value)
|
|||
|
||||
m_cache.rotation = rotation;
|
||||
m_cache.rotation_rounded(axis) = DBL_MAX;
|
||||
this->UpdateAndShow(true);
|
||||
this->UpdateAndShow(true);
|
||||
}
|
||||
|
||||
void ObjectManipulation::change_scale_value(int axis, double value)
|
||||
|
@ -511,6 +706,7 @@ void ObjectManipulation::change_scale_value(int axis, double value)
|
|||
this->UpdateAndShow(true);
|
||||
}
|
||||
|
||||
|
||||
void ObjectManipulation::change_size_value(int axis, double value)
|
||||
{
|
||||
if (std::abs(m_cache.size_rounded(axis) - value) < EPSILON)
|
||||
|
@ -666,6 +862,12 @@ void ObjectManipulation::msw_rescale()
|
|||
m_manifold_warning_bmp.msw_rescale();
|
||||
m_fix_throught_netfab_bitmap->SetBitmap(m_manifold_warning_bmp.bmp());
|
||||
|
||||
m_mirror_bitmap_on.msw_rescale();
|
||||
m_mirror_bitmap_off.msw_rescale();
|
||||
m_mirror_bitmap_hidden.msw_rescale();
|
||||
m_reset_scale_button->msw_rescale();
|
||||
m_reset_rotation_button->msw_rescale();
|
||||
|
||||
get_og()->msw_rescale();
|
||||
}
|
||||
|
||||
|
|
|
@ -53,6 +53,23 @@ class ObjectManipulation : public OG_Settings
|
|||
wxStaticText* m_scale_Label = nullptr;
|
||||
wxStaticText* m_rotate_Label = nullptr;
|
||||
|
||||
// Non-owning pointers to the reset buttons, so we can hide and show them.
|
||||
ScalableButton* m_reset_scale_button = nullptr;
|
||||
ScalableButton* m_reset_rotation_button = nullptr;
|
||||
|
||||
// Mirroring buttons and their current state
|
||||
enum MirrorButtonState {
|
||||
mbHidden,
|
||||
mbShown,
|
||||
mbActive
|
||||
};
|
||||
std::array<std::pair<ScalableButton*, MirrorButtonState>, 3> m_mirror_buttons;
|
||||
|
||||
// Bitmaps for the mirroring buttons.
|
||||
ScalableBitmap m_mirror_bitmap_on;
|
||||
ScalableBitmap m_mirror_bitmap_off;
|
||||
ScalableBitmap m_mirror_bitmap_hidden;
|
||||
|
||||
// Needs to be updated from OnIdle?
|
||||
bool m_dirty = false;
|
||||
// Cached labels for the delayed update, not localized!
|
||||
|
@ -111,10 +128,10 @@ private:
|
|||
void reset_settings_value();
|
||||
void update_settings_value(const Selection& selection);
|
||||
|
||||
// update size values after scale unit changing or "gizmos"
|
||||
void update_size_value(const Vec3d& size);
|
||||
// update rotation value after "gizmos"
|
||||
void update_rotation_value(const Vec3d& rotation);
|
||||
// Show or hide scale/rotation reset buttons if needed
|
||||
void update_reset_buttons_visibility();
|
||||
//Show or hide mirror buttons
|
||||
void update_mirror_buttons_visibility();
|
||||
|
||||
// change values
|
||||
void change_position_value(int axis, double value);
|
||||
|
|
|
@ -27,6 +27,7 @@ GLGizmoSlaSupports::GLGizmoSlaSupports(GLCanvas3D& parent, unsigned int sprite_i
|
|||
: GLGizmoBase(parent, sprite_id)
|
||||
#endif // ENABLE_SVG_ICONS
|
||||
, m_quadric(nullptr)
|
||||
, m_its(nullptr)
|
||||
{
|
||||
m_quadric = ::gluNewQuadric();
|
||||
if (m_quadric != nullptr)
|
||||
|
@ -379,36 +380,23 @@ bool GLGizmoSlaSupports::is_point_clipped(const Vec3d& point) const
|
|||
bool GLGizmoSlaSupports::is_mesh_update_necessary() const
|
||||
{
|
||||
return ((m_state == On) && (m_model_object != nullptr) && !m_model_object->instances.empty())
|
||||
&& ((m_model_object->id() != m_current_mesh_model_id) || m_V.size()==0);
|
||||
&& ((m_model_object->id() != m_current_mesh_model_id) || m_its == nullptr);
|
||||
}
|
||||
|
||||
void GLGizmoSlaSupports::update_mesh()
|
||||
{
|
||||
wxBusyCursor wait;
|
||||
Eigen::MatrixXf& V = m_V;
|
||||
Eigen::MatrixXi& F = m_F;
|
||||
// We rely on SLA model object having a single volume,
|
||||
// this way we can use that mesh directly.
|
||||
// This mesh does not account for the possible Z up SLA offset.
|
||||
m_mesh = &m_model_object->volumes.front()->mesh;
|
||||
const_cast<TriangleMesh*>(m_mesh)->require_shared_vertices(); // TriangleMeshSlicer needs this
|
||||
const stl_file& stl = m_mesh->stl;
|
||||
V.resize(3 * stl.stats.number_of_facets, 3);
|
||||
F.resize(stl.stats.number_of_facets, 3);
|
||||
for (unsigned int i=0; i<stl.stats.number_of_facets; ++i) {
|
||||
const stl_facet* facet = stl.facet_start+i;
|
||||
V(3*i+0, 0) = facet->vertex[0](0); V(3*i+0, 1) = facet->vertex[0](1); V(3*i+0, 2) = facet->vertex[0](2);
|
||||
V(3*i+1, 0) = facet->vertex[1](0); V(3*i+1, 1) = facet->vertex[1](1); V(3*i+1, 2) = facet->vertex[1](2);
|
||||
V(3*i+2, 0) = facet->vertex[2](0); V(3*i+2, 1) = facet->vertex[2](1); V(3*i+2, 2) = facet->vertex[2](2);
|
||||
F(i, 0) = 3*i+0;
|
||||
F(i, 1) = 3*i+1;
|
||||
F(i, 2) = 3*i+2;
|
||||
}
|
||||
m_mesh = &m_model_object->volumes.front()->mesh();
|
||||
m_its = &m_mesh->its;
|
||||
m_current_mesh_model_id = m_model_object->id();
|
||||
m_editing_mode = false;
|
||||
|
||||
m_AABB = igl::AABB<Eigen::MatrixXf,3>();
|
||||
m_AABB.init(m_V, m_F);
|
||||
m_AABB.deinit();
|
||||
m_AABB.init(
|
||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3));
|
||||
}
|
||||
|
||||
// Unprojects the mouse position on the mesh and return the hit point and normal of the facet.
|
||||
|
@ -416,7 +404,7 @@ void GLGizmoSlaSupports::update_mesh()
|
|||
std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse_pos)
|
||||
{
|
||||
// if the gizmo doesn't have the V, F structures for igl, calculate them first:
|
||||
if (m_V.size() == 0)
|
||||
if (m_its == nullptr)
|
||||
update_mesh();
|
||||
|
||||
const Camera& camera = m_parent.get_camera();
|
||||
|
@ -442,7 +430,10 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse
|
|||
point1 = inv * point1;
|
||||
point2 = inv * point2;
|
||||
|
||||
if (!m_AABB.intersect_ray(m_V, m_F, point1.cast<float>(), (point2-point1).cast<float>(), hits))
|
||||
if (!m_AABB.intersect_ray(
|
||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
|
||||
point1.cast<float>(), (point2-point1).cast<float>(), hits))
|
||||
throw std::invalid_argument("unproject_on_mesh(): No intersection found.");
|
||||
|
||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& a, const igl::Hit& b) { return a.t < b.t; });
|
||||
|
@ -457,9 +448,9 @@ std::pair<Vec3f, Vec3f> GLGizmoSlaSupports::unproject_on_mesh(const Vec2d& mouse
|
|||
igl::Hit& hit = hits[i];
|
||||
int fid = hit.id; // facet id
|
||||
bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
|
||||
a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
|
||||
b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
|
||||
result = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
|
||||
a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]);
|
||||
b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]);
|
||||
result = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)];
|
||||
if (m_clipping_plane_distance == 0.f || !is_point_clipped(result.cast<double>()))
|
||||
break;
|
||||
}
|
||||
|
@ -564,15 +555,18 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
// Cast a ray in the direction of the camera and look for intersection with the mesh:
|
||||
std::vector<igl::Hit> hits;
|
||||
// Offset the start of the ray to the front of the ball + EPSILON to account for numerical inaccuracies.
|
||||
if (m_AABB.intersect_ray(m_V, m_F, support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) {
|
||||
if (m_AABB.intersect_ray(
|
||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
|
||||
support_point.pos + direction_to_camera_mesh * (support_point.head_front_radius + EPSILON), direction_to_camera_mesh, hits)) {
|
||||
std::sort(hits.begin(), hits.end(), [](const igl::Hit& h1, const igl::Hit& h2) { return h1.t < h2.t; });
|
||||
|
||||
if (m_clipping_plane_distance != 0.f) {
|
||||
// If the closest hit facet normal points in the same direction as the ray,
|
||||
// we are looking through the mesh and should therefore discard the point:
|
||||
int fid = hits.front().id; // facet id
|
||||
Vec3f a = (m_V.row(m_F(fid, 1)) - m_V.row(m_F(fid, 0)));
|
||||
Vec3f b = (m_V.row(m_F(fid, 2)) - m_V.row(m_F(fid, 0)));
|
||||
Vec3f a = (m_its->vertices[m_its->indices[fid](1)] - m_its->vertices[m_its->indices[fid](0)]);
|
||||
Vec3f b = (m_its->vertices[m_its->indices[fid](2)] - m_its->vertices[m_its->indices[fid](0)]);
|
||||
if ((a.cross(b)).dot(direction_to_camera_mesh) > 0.f)
|
||||
is_obscured = true;
|
||||
|
||||
|
@ -582,7 +576,7 @@ bool GLGizmoSlaSupports::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
int fid = hit.id; // facet id
|
||||
|
||||
Vec3f bc = Vec3f(1-hit.u-hit.v, hit.u, hit.v); // barycentric coordinates of the hit
|
||||
Vec3f hit_pos = bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2));
|
||||
Vec3f hit_pos = bc(0) * m_its->vertices[m_its->indices[fid](0)] + bc(1) * m_its->vertices[m_its->indices[fid](1)] + bc(2)*m_its->vertices[m_its->indices[fid](2)];
|
||||
if (is_point_clipped(hit_pos.cast<double>())) {
|
||||
hits.erase(hits.begin()+j);
|
||||
--j;
|
||||
|
@ -759,9 +753,12 @@ void GLGizmoSlaSupports::update_cache_entry_normal(unsigned int i) const
|
|||
int idx = 0;
|
||||
Eigen::Matrix<float, 1, 3> pp = m_editing_mode_cache[i].support_point.pos;
|
||||
Eigen::Matrix<float, 1, 3> cc;
|
||||
m_AABB.squared_distance(m_V, m_F, pp, idx, cc);
|
||||
Vec3f a = (m_V.row(m_F(idx, 1)) - m_V.row(m_F(idx, 0)));
|
||||
Vec3f b = (m_V.row(m_F(idx, 2)) - m_V.row(m_F(idx, 0)));
|
||||
m_AABB.squared_distance(
|
||||
MapMatrixXfUnaligned(m_its->vertices.front().data(), m_its->vertices.size(), 3),
|
||||
MapMatrixXiUnaligned(m_its->indices.front().data(), m_its->indices.size(), 3),
|
||||
pp, idx, cc);
|
||||
Vec3f a = (m_its->vertices[m_its->indices[idx](1)] - m_its->vertices[m_its->indices[idx](0)]);
|
||||
Vec3f b = (m_its->vertices[m_its->indices[idx](2)] - m_its->vertices[m_its->indices[idx](0)]);
|
||||
m_editing_mode_cache[i].normal = a.cross(b);
|
||||
}
|
||||
|
||||
|
@ -1067,8 +1064,7 @@ void GLGizmoSlaSupports::on_set_state()
|
|||
m_clipping_plane_distance = 0.f;
|
||||
// Release triangle mesh slicer and the AABB spatial search structure.
|
||||
m_AABB.deinit();
|
||||
m_V = Eigen::MatrixXf();
|
||||
m_F = Eigen::MatrixXi();
|
||||
m_its = nullptr;
|
||||
m_tms.reset();
|
||||
m_supports_tms.reset();
|
||||
});
|
||||
|
|
|
@ -35,10 +35,11 @@ private:
|
|||
const float RenderPointScale = 1.f;
|
||||
|
||||
GLUquadricObj* m_quadric;
|
||||
Eigen::MatrixXf m_V; // vertices
|
||||
Eigen::MatrixXi m_F; // facets indices
|
||||
igl::AABB<Eigen::MatrixXf,3> m_AABB;
|
||||
typedef Eigen::Map<const Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXfUnaligned;
|
||||
typedef Eigen::Map<const Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor | Eigen::DontAlign>> MapMatrixXiUnaligned;
|
||||
igl::AABB<MapMatrixXfUnaligned, 3> m_AABB;
|
||||
const TriangleMesh* m_mesh;
|
||||
const indexed_triangle_set* m_its;
|
||||
mutable const TriangleMesh* m_supports_mesh;
|
||||
mutable std::vector<Vec2f> m_triangles;
|
||||
mutable std::vector<Vec2f> m_supports_triangles;
|
||||
|
@ -131,6 +132,11 @@ private:
|
|||
|
||||
protected:
|
||||
void on_set_state() override;
|
||||
virtual void on_set_hover_id()
|
||||
{
|
||||
if ((int)m_editing_mode_cache.size() <= m_hover_id)
|
||||
m_hover_id = -1;
|
||||
}
|
||||
void on_start_dragging(const Selection& selection) override;
|
||||
virtual void on_render_input_window(float x, float y, float bottom_limit, const Selection& selection) override;
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
#endif // _WIN32
|
||||
|
||||
// initialize status bar
|
||||
m_statusbar = new ProgressStatusBar(this);
|
||||
m_statusbar.reset(new ProgressStatusBar(this));
|
||||
m_statusbar->embed(this);
|
||||
m_statusbar->set_status_text(_(L("Version")) + " " +
|
||||
SLIC3R_VERSION +
|
||||
|
@ -103,6 +103,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
event.Veto();
|
||||
return;
|
||||
}
|
||||
|
||||
if(m_plater) m_plater->stop_jobs();
|
||||
|
||||
// Weird things happen as the Paint messages are floating around the windows being destructed.
|
||||
// Avoid the Paint messages by hiding the main window.
|
||||
|
@ -138,6 +140,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
update_ui_from_settings(); // FIXME (?)
|
||||
}
|
||||
|
||||
MainFrame::~MainFrame() = default;
|
||||
|
||||
void MainFrame::update_title()
|
||||
{
|
||||
wxString title = wxEmptyString;
|
||||
|
|
|
@ -89,7 +89,7 @@ protected:
|
|||
|
||||
public:
|
||||
MainFrame();
|
||||
~MainFrame() {}
|
||||
~MainFrame();
|
||||
|
||||
Plater* plater() { return m_plater; }
|
||||
|
||||
|
@ -126,7 +126,7 @@ public:
|
|||
Plater* m_plater { nullptr };
|
||||
wxNotebook* m_tabpanel { nullptr };
|
||||
wxProgressDialog* m_progress_dialog { nullptr };
|
||||
ProgressStatusBar* m_statusbar { nullptr };
|
||||
std::unique_ptr<ProgressStatusBar> m_statusbar;
|
||||
};
|
||||
|
||||
} // GUI
|
||||
|
|
|
@ -276,7 +276,7 @@ void OptionsGroup::append_line(const Line& line, wxStaticText** full_Label/* = n
|
|||
// add sidetext if any
|
||||
if (option.sidetext != "") {
|
||||
auto sidetext = new wxStaticText( this->ctrl_parent(), wxID_ANY, _(option.sidetext), wxDefaultPosition,
|
||||
/*wxSize(sidetext_width*wxGetApp().em_unit(), -1)*/wxDefaultSize, wxALIGN_LEFT);
|
||||
wxSize(sidetext_width != -1 ? sidetext_width*wxGetApp().em_unit() : -1, -1) /*wxDefaultSize*/, wxALIGN_LEFT);
|
||||
sidetext->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
sidetext->SetFont(wxGetApp().normal_font());
|
||||
sizer_tmp->Add(sidetext, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, 4);
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
#include <vector>
|
||||
#include <string>
|
||||
#include <regex>
|
||||
#include <future>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/stattext.h>
|
||||
|
@ -37,7 +39,12 @@
|
|||
#include "libslic3r/SLA/SLARotfinder.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "libnest2d/optimizers/nlopt/genetic.hpp"
|
||||
//#include "libslic3r/ClipperUtils.hpp"
|
||||
|
||||
// #include "libnest2d/optimizers/nlopt/genetic.hpp"
|
||||
// #include "libnest2d/backends/clipper/geometries.hpp"
|
||||
// #include "libnest2d/utils/rotcalipers.hpp"
|
||||
#include "libslic3r/MinAreaBoundingBox.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
|
@ -1253,8 +1260,243 @@ struct Plater::priv
|
|||
Preview *preview;
|
||||
|
||||
BackgroundSlicingProcess background_process;
|
||||
bool arranging;
|
||||
bool rotoptimizing;
|
||||
|
||||
// A class to handle UI jobs like arranging and optimizing rotation.
|
||||
// These are not instant jobs, the user has to be informed about their
|
||||
// state in the status progress indicator. On the other hand they are
|
||||
// separated from the background slicing process. Ideally, these jobs should
|
||||
// run when the background process is not running.
|
||||
//
|
||||
// TODO: A mechanism would be useful for blocking the plater interactions:
|
||||
// objects would be frozen for the user. In case of arrange, an animation
|
||||
// could be shown, or with the optimize orientations, partial results
|
||||
// could be displayed.
|
||||
class Job: public wxEvtHandler {
|
||||
int m_range = 100;
|
||||
std::future<void> m_ftr;
|
||||
priv *m_plater = nullptr;
|
||||
std::atomic<bool> m_running {false}, m_canceled {false};
|
||||
bool m_finalized = false;
|
||||
|
||||
void run() {
|
||||
m_running.store(true); process(); m_running.store(false);
|
||||
|
||||
// ensure to call the last status to finalize the job
|
||||
update_status(status_range(), "");
|
||||
}
|
||||
|
||||
protected:
|
||||
|
||||
// status range for a particular job
|
||||
virtual int status_range() const { return 100; }
|
||||
|
||||
// status update, to be used from the work thread (process() method)
|
||||
void update_status(int st, const wxString& msg = "") {
|
||||
auto evt = new wxThreadEvent(); evt->SetInt(st); evt->SetString(msg);
|
||||
wxQueueEvent(this, evt);
|
||||
}
|
||||
|
||||
priv& plater() { return *m_plater; }
|
||||
bool was_canceled() const { return m_canceled.load(); }
|
||||
|
||||
// Launched just before start(), a job can use it to prepare internals
|
||||
virtual void prepare() {}
|
||||
|
||||
// Launched when the job is finished. It refreshes the 3dscene by def.
|
||||
virtual void finalize() {
|
||||
// Do a full refresh of scene tree, including regenerating
|
||||
// all the GLVolumes. FIXME The update function shall just
|
||||
// reload the modified matrices.
|
||||
if(! was_canceled())
|
||||
plater().update(true);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
Job(priv *_plater): m_plater(_plater)
|
||||
{
|
||||
Bind(wxEVT_THREAD, [this](const wxThreadEvent& evt){
|
||||
auto msg = evt.GetString();
|
||||
if(! msg.empty()) plater().statusbar()->set_status_text(msg);
|
||||
|
||||
if(m_finalized) return;
|
||||
|
||||
plater().statusbar()->set_progress(evt.GetInt());
|
||||
if(evt.GetInt() == status_range()) {
|
||||
|
||||
// set back the original range and cancel callback
|
||||
plater().statusbar()->set_range(m_range);
|
||||
plater().statusbar()->set_cancel_callback();
|
||||
wxEndBusyCursor();
|
||||
|
||||
finalize();
|
||||
|
||||
// dont do finalization again for the same process
|
||||
m_finalized = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: use this when we all migrated to VS2019
|
||||
// Job(const Job&) = delete;
|
||||
// Job(Job&&) = default;
|
||||
// Job& operator=(const Job&) = delete;
|
||||
// Job& operator=(Job&&) = default;
|
||||
Job(const Job&) = delete;
|
||||
Job& operator=(const Job&) = delete;
|
||||
Job(Job &&o) :
|
||||
m_range(o.m_range),
|
||||
m_ftr(std::move(o.m_ftr)),
|
||||
m_plater(o.m_plater),
|
||||
m_finalized(o.m_finalized)
|
||||
{
|
||||
m_running.store(o.m_running.load());
|
||||
m_canceled.store(o.m_canceled.load());
|
||||
}
|
||||
|
||||
virtual void process() = 0;
|
||||
|
||||
void start() { // Start the job. No effect if the job is already running
|
||||
if(! m_running.load()) {
|
||||
|
||||
prepare();
|
||||
|
||||
// Save the current status indicatior range and push the new one
|
||||
m_range = plater().statusbar()->get_range();
|
||||
plater().statusbar()->set_range(status_range());
|
||||
|
||||
// init cancellation flag and set the cancel callback
|
||||
m_canceled.store(false);
|
||||
plater().statusbar()->set_cancel_callback( [this](){
|
||||
m_canceled.store(true);
|
||||
});
|
||||
|
||||
m_finalized = false;
|
||||
|
||||
// Changing cursor to busy
|
||||
wxBeginBusyCursor();
|
||||
|
||||
try { // Execute the job
|
||||
m_ftr = std::async(std::launch::async, &Job::run, this);
|
||||
} catch(std::exception& ) {
|
||||
update_status(status_range(),
|
||||
_(L("ERROR: not enough resources to execute a new job.")));
|
||||
}
|
||||
|
||||
// The state changes will be undone when the process hits the
|
||||
// last status value, in the status update handler (see ctor)
|
||||
}
|
||||
}
|
||||
|
||||
// To wait for the running job and join the threads. False is returned
|
||||
// if the timeout has been reached and the job is still running. Call
|
||||
// cancel() before this fn if you want to explicitly end the job.
|
||||
bool join(int timeout_ms = 0) const {
|
||||
if(!m_ftr.valid()) return true;
|
||||
|
||||
if(timeout_ms <= 0)
|
||||
m_ftr.wait();
|
||||
else if(m_ftr.wait_for(std::chrono::milliseconds(timeout_ms)) ==
|
||||
std::future_status::timeout)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_running() const { return m_running.load(); }
|
||||
void cancel() { m_canceled.store(true); }
|
||||
};
|
||||
|
||||
enum class Jobs : size_t {
|
||||
Arrange,
|
||||
Rotoptimize
|
||||
};
|
||||
|
||||
// Jobs defined inside the group class will be managed so that only one can
|
||||
// run at a time. Also, the background process will be stopped if a job is
|
||||
// started.
|
||||
class ExclusiveJobGroup {
|
||||
|
||||
static const int ABORT_WAIT_MAX_MS = 10000;
|
||||
|
||||
priv * m_plater;
|
||||
|
||||
class ArrangeJob : public Job
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
protected:
|
||||
void prepare() override
|
||||
{
|
||||
count = 0;
|
||||
for (auto obj : plater().model.objects)
|
||||
count += int(obj->instances.size());
|
||||
}
|
||||
|
||||
public:
|
||||
//using Job::Job;
|
||||
ArrangeJob(priv * pltr): Job(pltr) {}
|
||||
int status_range() const override { return count; }
|
||||
void set_count(int c) { count = c; }
|
||||
void process() override;
|
||||
} arrange_job/*{m_plater}*/;
|
||||
|
||||
class RotoptimizeJob : public Job
|
||||
{
|
||||
public:
|
||||
//using Job::Job;
|
||||
RotoptimizeJob(priv * pltr): Job(pltr) {}
|
||||
void process() override;
|
||||
} rotoptimize_job/*{m_plater}*/;
|
||||
|
||||
// To create a new job, just define a new subclass of Job, implement
|
||||
// the process and the optional prepare() and finalize() methods
|
||||
// Register the instance of the class in the m_jobs container
|
||||
// if it cannot run concurrently with other jobs in this group
|
||||
|
||||
std::vector<std::reference_wrapper<Job>> m_jobs/*{arrange_job,
|
||||
rotoptimize_job}*/;
|
||||
|
||||
public:
|
||||
ExclusiveJobGroup(priv *_plater)
|
||||
: m_plater(_plater)
|
||||
, arrange_job(m_plater)
|
||||
, rotoptimize_job(m_plater)
|
||||
, m_jobs({arrange_job, rotoptimize_job})
|
||||
{}
|
||||
|
||||
void start(Jobs jid) {
|
||||
m_plater->background_process.stop();
|
||||
stop_all();
|
||||
m_jobs[size_t(jid)].get().start();
|
||||
}
|
||||
|
||||
void cancel_all() { for (Job& j : m_jobs) j.cancel(); }
|
||||
|
||||
void join_all(int wait_ms = 0)
|
||||
{
|
||||
std::vector<bool> aborted(m_jobs.size(), false);
|
||||
|
||||
for (size_t jid = 0; jid < m_jobs.size(); ++jid)
|
||||
aborted[jid] = m_jobs[jid].get().join(wait_ms);
|
||||
|
||||
if (!all_of(aborted))
|
||||
BOOST_LOG_TRIVIAL(error) << "Could not abort a job!";
|
||||
}
|
||||
|
||||
void stop_all() { cancel_all(); join_all(ABORT_WAIT_MAX_MS); }
|
||||
|
||||
const Job& get(Jobs jobid) const { return m_jobs[size_t(jobid)]; }
|
||||
|
||||
bool is_any_running() const
|
||||
{
|
||||
return std::any_of(m_jobs.begin(),
|
||||
m_jobs.end(),
|
||||
[](const Job &j) { return j.is_running(); });
|
||||
}
|
||||
|
||||
} m_ui_jobs{this};
|
||||
|
||||
bool delayed_scene_refresh;
|
||||
std::string delayed_error_message;
|
||||
|
||||
|
@ -1429,8 +1671,6 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
{
|
||||
this->q->SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
|
||||
arranging = false;
|
||||
rotoptimizing = false;
|
||||
background_process.set_fff_print(&fff_print);
|
||||
background_process.set_sla_print(&sla_print);
|
||||
background_process.set_gcode_preview_data(&gcode_preview_data);
|
||||
|
@ -1536,7 +1776,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
|
||||
void Plater::priv::update(bool force_full_scene_refresh)
|
||||
{
|
||||
wxWindowUpdateLocker freeze_guard(q);
|
||||
// the following line, when enabled, causes flickering on NVIDIA graphics cards
|
||||
// wxWindowUpdateLocker freeze_guard(q);
|
||||
if (get_config("autocenter") == "1") {
|
||||
// auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||
// const auto bed_shape = Slic3r::Polygon::new_scale(bed_shape_opt->values);
|
||||
|
@ -1606,7 +1847,7 @@ void Plater::priv::update_ui_from_settings()
|
|||
|
||||
ProgressStatusBar* Plater::priv::statusbar()
|
||||
{
|
||||
return main_frame->m_statusbar;
|
||||
return main_frame->m_statusbar.get();
|
||||
}
|
||||
|
||||
std::string Plater::priv::get_config(const std::string &key) const
|
||||
|
@ -2143,59 +2384,45 @@ void Plater::priv::mirror(Axis axis)
|
|||
|
||||
void Plater::priv::arrange()
|
||||
{
|
||||
if (arranging) { return; }
|
||||
arranging = true;
|
||||
Slic3r::ScopeGuard arranging_guard([this]() { arranging = false; });
|
||||
m_ui_jobs.start(Jobs::Arrange);
|
||||
}
|
||||
|
||||
wxBusyCursor wait;
|
||||
// This method will find an optimal orientation for the currently selected item
|
||||
// Very similar in nature to the arrange method above...
|
||||
void Plater::priv::sla_optimize_rotation() {
|
||||
m_ui_jobs.start(Jobs::Rotoptimize);
|
||||
}
|
||||
|
||||
this->background_process.stop();
|
||||
void Plater::priv::ExclusiveJobGroup::ArrangeJob::process() {
|
||||
// TODO: we should decide whether to allow arrange when the search is
|
||||
// running we should probably disable explicit slicing and background
|
||||
// processing
|
||||
|
||||
unsigned count = 0;
|
||||
for(auto obj : model.objects) count += obj->instances.size();
|
||||
static const auto arrangestr = _(L("Arranging"));
|
||||
|
||||
auto prev_range = statusbar()->get_range();
|
||||
statusbar()->set_range(count);
|
||||
|
||||
auto statusfn = [this, count] (unsigned st, const std::string& msg) {
|
||||
/* // In case we would run the arrange asynchronously
|
||||
wxCommandEvent event(EVT_PROGRESS_BAR);
|
||||
event.SetInt(st);
|
||||
event.SetString(msg);
|
||||
wxQueueEvent(this->q, event.Clone()); */
|
||||
statusbar()->set_progress(count - st);
|
||||
statusbar()->set_status_text(_(msg));
|
||||
|
||||
// ok, this is dangerous, but we are protected by the flag
|
||||
// 'arranging' and the arrange button is also disabled.
|
||||
// This call is needed for the cancel button to work.
|
||||
wxYieldIfNeeded();
|
||||
};
|
||||
|
||||
statusbar()->set_cancel_callback([this, statusfn](){
|
||||
arranging = false;
|
||||
statusfn(0, L("Arranging canceled"));
|
||||
});
|
||||
|
||||
static const std::string arrangestr = L("Arranging");
|
||||
auto &config = plater().config;
|
||||
auto &view3D = plater().view3D;
|
||||
auto &model = plater().model;
|
||||
|
||||
// FIXME: I don't know how to obtain the minimum distance, it depends
|
||||
// on printer technology. I guess the following should work but it crashes.
|
||||
double dist = 6; //PrintConfig::min_object_distance(config);
|
||||
if(printer_technology == ptFFF) {
|
||||
double dist = 6; // PrintConfig::min_object_distance(config);
|
||||
if (plater().printer_technology == ptFFF) {
|
||||
dist = PrintConfig::min_object_distance(config);
|
||||
}
|
||||
|
||||
auto min_obj_distance = coord_t(dist/SCALING_FACTOR);
|
||||
auto min_obj_distance = coord_t(dist / SCALING_FACTOR);
|
||||
|
||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>(
|
||||
"bed_shape");
|
||||
|
||||
assert(bed_shape_opt);
|
||||
auto& bedpoints = bed_shape_opt->values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||
auto & bedpoints = bed_shape_opt->values;
|
||||
Polyline bed;
|
||||
bed.points.reserve(bedpoints.size());
|
||||
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
statusfn(0, arrangestr);
|
||||
update_status(0, arrangestr);
|
||||
|
||||
arr::WipeTowerInfo wti = view3D->get_canvas3d()->get_wipe_tower_info();
|
||||
|
||||
|
@ -2211,129 +2438,87 @@ void Plater::priv::arrange()
|
|||
bed,
|
||||
hint,
|
||||
false, // create many piles not just one pile
|
||||
[statusfn](unsigned st) { statusfn(st, arrangestr); },
|
||||
[this] () { return !arranging; });
|
||||
} catch(std::exception& /*e*/) {
|
||||
GUI::show_error(this->q, L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid."));
|
||||
[this](unsigned st) {
|
||||
if (st > 0)
|
||||
update_status(count - int(st), arrangestr);
|
||||
},
|
||||
[this]() { return was_canceled(); });
|
||||
} catch (std::exception & /*e*/) {
|
||||
GUI::show_error(plater().q,
|
||||
L("Could not arrange model objects! "
|
||||
"Some geometries may be invalid."));
|
||||
}
|
||||
|
||||
update_status(count,
|
||||
was_canceled() ? _(L("Arranging canceled."))
|
||||
: _(L("Arranging done.")));
|
||||
|
||||
// it remains to move the wipe tower:
|
||||
view3D->get_canvas3d()->arrange_wipe_tower(wti);
|
||||
|
||||
statusfn(0, L("Arranging done."));
|
||||
statusbar()->set_range(prev_range);
|
||||
statusbar()->set_cancel_callback(); // remove cancel button
|
||||
|
||||
// Do a full refresh of scene tree, including regenerating all the GLVolumes.
|
||||
//FIXME The update function shall just reload the modified matrices.
|
||||
update(true);
|
||||
}
|
||||
|
||||
// This method will find an optimal orientation for the currently selected item
|
||||
// Very similar in nature to the arrange method above...
|
||||
void Plater::priv::sla_optimize_rotation() {
|
||||
|
||||
// TODO: we should decide whether to allow arrange when the search is
|
||||
// running we should probably disable explicit slicing and background
|
||||
// processing
|
||||
|
||||
if (rotoptimizing) { return; }
|
||||
rotoptimizing = true;
|
||||
Slic3r::ScopeGuard rotoptimizing_guard([this]() { rotoptimizing = false; });
|
||||
|
||||
int obj_idx = get_selected_object_idx();
|
||||
void Plater::priv::ExclusiveJobGroup::RotoptimizeJob::process()
|
||||
{
|
||||
int obj_idx = plater().get_selected_object_idx();
|
||||
if (obj_idx < 0) { return; }
|
||||
|
||||
ModelObject * o = model.objects[size_t(obj_idx)];
|
||||
|
||||
background_process.stop();
|
||||
|
||||
auto prev_range = statusbar()->get_range();
|
||||
statusbar()->set_range(100);
|
||||
|
||||
auto stfn = [this] (unsigned st, const std::string& msg) {
|
||||
statusbar()->set_progress(int(st));
|
||||
statusbar()->set_status_text(msg);
|
||||
|
||||
// could be problematic, but we need the cancel button.
|
||||
wxYieldIfNeeded();
|
||||
};
|
||||
|
||||
statusbar()->set_cancel_callback([this, stfn](){
|
||||
rotoptimizing = false;
|
||||
stfn(0, L("Orientation search canceled"));
|
||||
});
|
||||
ModelObject *o = plater().model.objects[size_t(obj_idx)];
|
||||
|
||||
auto r = sla::find_best_rotation(
|
||||
*o, .005f,
|
||||
[stfn](unsigned s) { stfn(s, L("Searching for optimal orientation")); },
|
||||
[this](){ return !rotoptimizing; }
|
||||
);
|
||||
*o,
|
||||
.005f,
|
||||
[this](unsigned s) {
|
||||
if (s < 100)
|
||||
update_status(int(s),
|
||||
_(L("Searching for optimal orientation")));
|
||||
},
|
||||
[this]() { return was_canceled(); });
|
||||
|
||||
const auto *bed_shape_opt = config->opt<ConfigOptionPoints>("bed_shape");
|
||||
const auto *bed_shape_opt =
|
||||
plater().config->opt<ConfigOptionPoints>("bed_shape");
|
||||
|
||||
assert(bed_shape_opt);
|
||||
|
||||
auto& bedpoints = bed_shape_opt->values;
|
||||
Polyline bed; bed.points.reserve(bedpoints.size());
|
||||
for(auto& v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||
auto & bedpoints = bed_shape_opt->values;
|
||||
Polyline bed;
|
||||
bed.points.reserve(bedpoints.size());
|
||||
for (auto &v : bedpoints) bed.append(Point::new_scale(v(0), v(1)));
|
||||
|
||||
double mindist = 6.0; // FIXME
|
||||
double offs = mindist / 2.0 - EPSILON;
|
||||
|
||||
if(rotoptimizing) // wasn't canceled
|
||||
for(ModelInstance * oi : o->instances) {
|
||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||
|
||||
auto trchull = o->convex_hull_2d(oi->get_transformation().get_matrix());
|
||||
|
||||
namespace opt = libnest2d::opt;
|
||||
opt::StopCriteria stopcr;
|
||||
stopcr.relative_score_difference = 0.01;
|
||||
stopcr.max_iterations = 10000;
|
||||
stopcr.stop_score = 0.0;
|
||||
opt::GeneticOptimizer solver(stopcr);
|
||||
Polygon pbed(bed);
|
||||
|
||||
auto bin = pbed.bounding_box();
|
||||
double binw = bin.size()(X) * SCALING_FACTOR - offs;
|
||||
double binh = bin.size()(Y) * SCALING_FACTOR - offs;
|
||||
|
||||
auto result = solver.optimize_min([&trchull, binw, binh](double rot){
|
||||
auto chull = trchull;
|
||||
chull.rotate(rot);
|
||||
|
||||
auto bb = chull.bounding_box();
|
||||
double bbw = bb.size()(X) * SCALING_FACTOR;
|
||||
double bbh = bb.size()(Y) * SCALING_FACTOR;
|
||||
|
||||
auto wdiff = bbw - binw;
|
||||
auto hdiff = bbh - binh;
|
||||
double diff = 0;
|
||||
if(wdiff < 0 && hdiff < 0) diff = wdiff + hdiff;
|
||||
if(wdiff > 0) diff += wdiff;
|
||||
if(hdiff > 0) diff += hdiff;
|
||||
|
||||
return diff;
|
||||
}, opt::initvals(0.0), opt::bound(-PI/2, PI/2));
|
||||
|
||||
double r = std::get<0>(result.optimum);
|
||||
|
||||
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
||||
oi->set_rotation(rt);
|
||||
|
||||
if (!was_canceled()) {
|
||||
for(ModelInstance * oi : o->instances) {
|
||||
oi->set_rotation({r[X], r[Y], r[Z]});
|
||||
|
||||
auto trmatrix = oi->get_transformation().get_matrix();
|
||||
Polygon trchull = o->convex_hull_2d(trmatrix);
|
||||
|
||||
MinAreaBoundigBox rotbb(trchull, MinAreaBoundigBox::pcConvex);
|
||||
double r = rotbb.angle_to_X();
|
||||
|
||||
// The box should be landscape
|
||||
if(rotbb.width() < rotbb.height()) r += PI / 2;
|
||||
|
||||
Vec3d rt = oi->get_rotation(); rt(Z) += r;
|
||||
|
||||
oi->set_rotation(rt);
|
||||
}
|
||||
|
||||
arr::WipeTowerInfo wti; // useless in SLA context
|
||||
arr::find_new_position(plater().model,
|
||||
o->instances,
|
||||
coord_t(mindist / SCALING_FACTOR),
|
||||
bed,
|
||||
wti);
|
||||
|
||||
// Correct the z offset of the object which was corrupted be
|
||||
// the rotation
|
||||
o->ensure_on_bed();
|
||||
}
|
||||
|
||||
arr::WipeTowerInfo wti; // useless in SLA context
|
||||
arr::find_new_position(model, o->instances, coord_t(mindist/SCALING_FACTOR), bed, wti);
|
||||
|
||||
// Correct the z offset of the object which was corrupted be the rotation
|
||||
o->ensure_on_bed();
|
||||
|
||||
stfn(0, L("Orientation found."));
|
||||
statusbar()->set_range(prev_range);
|
||||
statusbar()->set_cancel_callback();
|
||||
|
||||
update(true);
|
||||
update_status(100,
|
||||
was_canceled() ? _(L("Orientation search canceled."))
|
||||
: _(L("Orientation found.")));
|
||||
}
|
||||
|
||||
void Plater::priv::split_object()
|
||||
|
@ -2514,7 +2699,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation)
|
|||
// Restart background processing thread based on a bitmask of UpdateBackgroundProcessReturnState.
|
||||
bool Plater::priv::restart_background_process(unsigned int state)
|
||||
{
|
||||
if (arranging || rotoptimizing) {
|
||||
if (m_ui_jobs.is_any_running()) {
|
||||
// Avoid a race condition
|
||||
return false;
|
||||
}
|
||||
|
@ -2745,7 +2930,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
|
|||
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
|
||||
{
|
||||
if (evt.status.percent >= -1) {
|
||||
if (arranging || rotoptimizing) {
|
||||
if (m_ui_jobs.is_any_running()) {
|
||||
// Avoid a race condition
|
||||
return;
|
||||
}
|
||||
|
@ -3226,7 +3411,7 @@ bool Plater::priv::can_fix_through_netfabb() const
|
|||
|
||||
bool Plater::priv::can_increase_instances() const
|
||||
{
|
||||
if (arranging || rotoptimizing) {
|
||||
if (m_ui_jobs.is_any_running()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3236,7 +3421,7 @@ bool Plater::priv::can_increase_instances() const
|
|||
|
||||
bool Plater::priv::can_decrease_instances() const
|
||||
{
|
||||
if (arranging || rotoptimizing) {
|
||||
if (m_ui_jobs.is_any_running()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3256,7 +3441,7 @@ bool Plater::priv::can_split_to_volumes() const
|
|||
|
||||
bool Plater::priv::can_arrange() const
|
||||
{
|
||||
return !model.objects.empty() && !arranging;
|
||||
return !model.objects.empty() && !m_ui_jobs.is_any_running();
|
||||
}
|
||||
|
||||
bool Plater::priv::can_layers_editing() const
|
||||
|
@ -3323,6 +3508,7 @@ SLAPrint& Plater::sla_print() { return p->sla_print; }
|
|||
|
||||
void Plater::new_project()
|
||||
{
|
||||
p->select_view_3D("3D");
|
||||
wxPostEvent(p->view3D->get_wxglcanvas(), SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL));
|
||||
}
|
||||
|
||||
|
@ -3383,6 +3569,8 @@ void Plater::load_files(const std::vector<std::string>& input_files, bool load_m
|
|||
|
||||
void Plater::update() { p->update(); }
|
||||
|
||||
void Plater::stop_jobs() { p->m_ui_jobs.stop_all(); }
|
||||
|
||||
void Plater::update_ui_from_settings() { p->update_ui_from_settings(); }
|
||||
|
||||
void Plater::select_view(const std::string& direction) { p->select_view(direction); }
|
||||
|
@ -3583,7 +3771,7 @@ void Plater::export_stl(bool extended, bool selection_only)
|
|||
else
|
||||
{
|
||||
const GLVolume* volume = selection.get_volume(*selection.get_volume_idxs().begin());
|
||||
mesh = model_object->volumes[volume->volume_idx()]->mesh;
|
||||
mesh = model_object->volumes[volume->volume_idx()]->mesh();
|
||||
mesh.transform(volume->get_volume_transformation().get_matrix());
|
||||
mesh.translate(-model_object->origin_translation.cast<float>());
|
||||
}
|
||||
|
@ -3689,7 +3877,7 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
|||
if (!path.Lower().EndsWith(".3mf"))
|
||||
return;
|
||||
|
||||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
DynamicPrintConfig cfg = wxGetApp().preset_bundle->full_config_secure();
|
||||
const std::string path_u8 = into_u8(path);
|
||||
wxBusyCursor wait;
|
||||
if (Slic3r::store_3mf(path_u8.c_str(), &p->model, export_config ? &cfg : nullptr)) {
|
||||
|
@ -3705,6 +3893,9 @@ void Plater::export_3mf(const boost::filesystem::path& output_path)
|
|||
|
||||
void Plater::reslice()
|
||||
{
|
||||
// Stop arrange and (or) optimize rotation tasks.
|
||||
this->stop_jobs();
|
||||
|
||||
//FIXME Don't reslice if export of G-code or sending to OctoPrint is running.
|
||||
// bitmask of UpdateBackgroundProcessReturnState
|
||||
unsigned int state = this->p->update_background_process(true);
|
||||
|
@ -3740,7 +3931,7 @@ void Plater::reslice_SLA_supports(const ModelObject &object)
|
|||
if (state & priv::UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE)
|
||||
this->p->view3D->reload_scene(false);
|
||||
|
||||
if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
|
||||
if (this->p->background_process.empty() || (state & priv::UPDATE_BACKGROUND_PROCESS_INVALID))
|
||||
// Nothing to do on empty input or invalid configuration.
|
||||
return;
|
||||
|
||||
|
|
|
@ -144,6 +144,7 @@ public:
|
|||
void load_files(const std::vector<std::string>& input_files, bool load_model = true, bool load_config = true);
|
||||
|
||||
void update();
|
||||
void stop_jobs();
|
||||
void select_view(const std::string& direction);
|
||||
void select_view_3D(const std::string& name);
|
||||
|
||||
|
|
|
@ -509,6 +509,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
|
|||
"printer_technology",
|
||||
"bed_shape", "max_print_height",
|
||||
"display_width", "display_height", "display_pixels_x", "display_pixels_y",
|
||||
"display_mirror_x", "display_mirror_y",
|
||||
"display_orientation",
|
||||
"fast_tilt_time", "slow_tilt_time", "area_fill",
|
||||
"relative_correction",
|
||||
|
|
|
@ -781,7 +781,7 @@ void PresetBundle::load_config_file_config(const std::string &name_or_path, bool
|
|||
if (i == 0)
|
||||
suffix[0] = 0;
|
||||
else
|
||||
sprintf(suffix, "%d", i);
|
||||
sprintf(suffix, "%d", (int)i);
|
||||
std::string new_name = name + suffix;
|
||||
loaded = &this->filaments.load_preset(this->filaments.path_from_name(new_name),
|
||||
new_name, std::move(cfg), i == 0);
|
||||
|
@ -837,7 +837,7 @@ void PresetBundle::load_config_file_config_bundle(const std::string &path, const
|
|||
return preset_name_dst;
|
||||
// Try to generate another name.
|
||||
char buf[64];
|
||||
sprintf(buf, " (%d)", i);
|
||||
sprintf(buf, " (%d)", (int)i);
|
||||
preset_name_dst = preset_name_src + buf + bundle_name;
|
||||
}
|
||||
}
|
||||
|
@ -1379,7 +1379,7 @@ void PresetBundle::export_configbundle(const std::string &path, bool export_syst
|
|||
for (size_t i = 0; i < this->filament_presets.size(); ++ i) {
|
||||
char suffix[64];
|
||||
if (i > 0)
|
||||
sprintf(suffix, "_%d", i);
|
||||
sprintf(suffix, "_%d", (int)i);
|
||||
else
|
||||
suffix[0] = 0;
|
||||
c << "filament" << suffix << " = " << this->filament_presets[i] << std::endl;
|
||||
|
|
|
@ -168,6 +168,11 @@ void ProgressStatusBar::set_status_text(const char *txt)
|
|||
this->set_status_text(wxString::FromUTF8(txt));
|
||||
}
|
||||
|
||||
wxString ProgressStatusBar::get_status_text() const
|
||||
{
|
||||
return self->GetStatusText();
|
||||
}
|
||||
|
||||
void ProgressStatusBar::show_cancel_button()
|
||||
{
|
||||
if(m_cancelbutton) m_cancelbutton->Show();
|
||||
|
|
|
@ -52,6 +52,7 @@ public:
|
|||
void set_status_text(const wxString& txt);
|
||||
void set_status_text(const std::string& txt);
|
||||
void set_status_text(const char *txt);
|
||||
wxString get_status_text() const;
|
||||
|
||||
// Temporary methods to satisfy Perl side
|
||||
void show_cancel_button();
|
||||
|
|
|
@ -333,6 +333,8 @@ private:
|
|||
void render_sidebar_rotation_hint(Axis axis) const;
|
||||
void render_sidebar_scale_hint(Axis axis) const;
|
||||
void render_sidebar_size_hint(Axis axis, double length) const;
|
||||
|
||||
public:
|
||||
enum SyncRotationType {
|
||||
// Do not synchronize rotation. Either not rotating at all, or rotating by world Z axis.
|
||||
SYNC_ROTATION_NONE = 0,
|
||||
|
@ -343,6 +345,8 @@ private:
|
|||
};
|
||||
void synchronize_unselected_instances(SyncRotationType sync_rotation_type);
|
||||
void synchronize_unselected_volumes();
|
||||
|
||||
private:
|
||||
void ensure_on_bed();
|
||||
bool is_from_fully_selected_instance(unsigned int volume_idx) const;
|
||||
|
||||
|
|
|
@ -2087,6 +2087,10 @@ void TabPrinter::build_sla()
|
|||
line.append_option(optgroup->get_option("display_pixels_y"));
|
||||
optgroup->append_line(line);
|
||||
optgroup->append_single_option_line("display_orientation");
|
||||
|
||||
// FIXME: This should be on one line in the UI
|
||||
optgroup->append_single_option_line("display_mirror_x");
|
||||
optgroup->append_single_option_line("display_mirror_y");
|
||||
|
||||
optgroup = page->new_optgroup(_(L("Tilt")));
|
||||
line = { _(L("Tilt time")), "" };
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue