Merge remote-tracking branch 'origin/master' into ys_bugfixing

This commit is contained in:
YuSanka 2019-06-19 16:32:40 +02:00
commit 09f4831f4e
86 changed files with 4705 additions and 4380 deletions

View file

@ -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());

View file

@ -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; }

View file

@ -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());

View file

@ -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

View file

@ -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") ?

View file

@ -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();
}

View file

@ -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);

View file

@ -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();
});

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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;

View file

@ -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);

View file

@ -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",

View file

@ -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;

View file

@ -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();

View file

@ -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();

View file

@ -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;

View file

@ -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")), "" };