Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_sinking_objects_collision

This commit is contained in:
enricoturri1966 2021-10-04 13:07:53 +02:00
commit 5739178306
18 changed files with 570 additions and 59 deletions

View file

@ -2861,6 +2861,8 @@ void GCodeProcessor::process_M109(const GCodeReader::GCodeLine& line)
else
m_extruder_temps[m_extruder_id] = new_temp;
}
else if (line.has_value('S', new_temp))
m_extruder_temps[m_extruder_id] = new_temp;
}
void GCodeProcessor::process_M132(const GCodeReader::GCodeLine& line)

View file

@ -20,6 +20,12 @@
#include <boost/algorithm/string/split.hpp>
#include <boost/log/trivial.hpp>
#if defined(_MSC_VER) && defined(__clang__)
#define BOOST_NO_CXX17_HDR_STRING_VIEW
#endif
#include <boost/multiprecision/integer.hpp>
#ifdef SLIC3R_DEBUG
#include "SVG.hpp"
#endif
@ -1552,4 +1558,210 @@ double rotation_diff_z(const Vec3d &rot_xyz_from, const Vec3d &rot_xyz_to)
return (axis.z() < 0) ? -angle : angle;
}
} }
namespace rotcalip {
using int256_t = boost::multiprecision::int256_t;
using int128_t = boost::multiprecision::int128_t;
inline int128_t magnsq(const Point &p)
{
return int128_t(p.x()) * p.x() + int64_t(p.y()) * p.y();
}
inline int128_t dot(const Point &a, const Point &b)
{
return int128_t(a.x()) * b.x() + int64_t(a.y()) * b.y();
}
template<class Scalar = int64_t>
inline Scalar dotperp(const Point &a, const Point &b)
{
return Scalar(a.x()) * b.y() - Scalar(a.y()) * b.x();
}
using boost::multiprecision::abs;
// Compares the angle enclosed by vectors dir and dirA (alpha) with the angle
// enclosed by -dir and dirB (beta). Returns -1 if alpha is less than beta, 0
// if they are equal and 1 if alpha is greater than beta. Note that dir is
// reversed for beta, because it represents the opposite side of a caliper.
int cmp_angles(const Point &dir, const Point &dirA, const Point &dirB) {
int128_t dotA = dot(dir, dirA);
int128_t dotB = dot(-dir, dirB);
int256_t dcosa = int256_t(magnsq(dirB)) * int256_t(abs(dotA)) * dotA;
int256_t dcosb = int256_t(magnsq(dirA)) * int256_t(abs(dotB)) * dotB;
int256_t diff = dcosa - dcosb;
return diff > 0? -1 : (diff < 0 ? 1 : 0);
}
// A helper class to navigate on a polygon. Given a vertex index, one can
// get the edge belonging to that vertex, the coordinates of the vertex, the
// next and previous edges. Stuff that is needed in the rotating calipers algo.
class Idx
{
size_t m_idx;
const Polygon *m_poly;
public:
explicit Idx(const Polygon &p): m_idx{0}, m_poly{&p} {}
explicit Idx(size_t idx, const Polygon &p): m_idx{idx}, m_poly{&p} {}
size_t idx() const { return m_idx; }
void set_idx(size_t i) { m_idx = i; }
size_t next() const { return (m_idx + 1) % m_poly->size(); }
size_t inc() { return m_idx = (m_idx + 1) % m_poly->size(); }
Point prev_dir() const {
return pt() - (*m_poly)[(m_idx + m_poly->size() - 1) % m_poly->size()];
}
const Point &pt() const { return (*m_poly)[m_idx]; }
const Point dir() const { return (*m_poly)[next()] - pt(); }
const Point next_dir() const
{
return (*m_poly)[(m_idx + 2) % m_poly->size()] - (*m_poly)[next()];
}
const Polygon &poly() const { return *m_poly; }
};
enum class AntipodalVisitMode { Full, EdgesOnly };
// Visit all antipodal pairs starting from the initial ia, ib pair which
// has to be a valid antipodal pair (not checked). fn is called for every
// antipodal pair encountered including the initial one.
// The callback Fn has a signiture of bool(size_t i, size_t j, const Point &dir)
// where i,j are the vertex indices of the antipodal pair and dir is the
// direction of the calipers touching the i vertex.
template<AntipodalVisitMode mode = AntipodalVisitMode::Full, class Fn>
void visit_antipodals (Idx& ia, Idx &ib, Fn &&fn)
{
// Set current caliper direction to be the lower edge angle from X axis
int cmp = cmp_angles(ia.prev_dir(), ia.dir(), ib.dir());
Idx *current = cmp <= 0 ? &ia : &ib, *other = cmp <= 0 ? &ib : &ia;
bool visitor_continue = true;
size_t a_start = ia.idx(), b_start = ib.idx();
bool a_finished = false, b_finished = false;
while (visitor_continue && !(a_finished && b_finished)) {
Point current_dir_a = current == &ia ? current->dir() : -current->dir();
visitor_continue = fn(ia.idx(), ib.idx(), current_dir_a);
// Parallel edges encountered. An additional pair of antipodals
// can be yielded.
if constexpr (mode == AntipodalVisitMode::Full)
if (cmp == 0 && visitor_continue) {
visitor_continue = fn(current == &ia ? ia.idx() : ia.next(),
current == &ib ? ib.idx() : ib.next(),
current_dir_a);
}
cmp = cmp_angles(current->dir(), current->next_dir(), other->dir());
current->inc();
if (cmp > 0) {
std::swap(current, other);
}
if (ia.idx() == a_start) a_finished = true;
if (ib.idx() == b_start) b_finished = true;
}
}
} // namespace rotcalip
bool intersects(const Polygon &A, const Polygon &B)
{
using namespace rotcalip;
// Establish starting antipodals as extremes in XY plane. Use the
// easily obtainable bounding boxes to check if A and B is disjoint
// and return false if the are.
struct BB
{
size_t xmin = 0, xmax = 0, ymin = 0, ymax = 0;
const Polygon &P;
static bool cmpy(const Point &l, const Point &u)
{
return l.y() < u.y() || (l.y() == u.y() && l.x() < u.x());
}
BB(const Polygon &poly): P{poly}
{
for (size_t i = 0; i < P.size(); ++i) {
if (P[i] < P[xmin]) xmin = i;
if (P[xmax] < P[i]) xmax = i;
if (cmpy(P[i], P[ymin])) ymin = i;
if (cmpy(P[ymax], P[i])) ymax = i;
}
}
};
BB bA{A}, bB{B};
BoundingBox bbA{{A[bA.xmin].x(), A[bA.ymin].y()}, {A[bA.xmax].x(), A[bA.ymax].y()}};
BoundingBox bbB{{B[bB.xmin].x(), B[bB.ymin].y()}, {B[bB.xmax].x(), B[bB.ymax].y()}};
if (!bbA.overlap(bbB))
return false;
// Establish starting antipodals as extreme vertex pairs in X or Y direction
// which reside on different polygons. If no such pair is found, the two
// polygons are certainly not disjoint.
Idx imin{bA.xmin, A}, imax{bB.xmax, B};
if (B[bB.xmin] < imin.pt()) imin = Idx{bB.xmin, B};
if (imax.pt() < A[bA.xmax]) imax = Idx{bA.xmax, A};
if (&imin.poly() == &imax.poly()) {
imin = Idx{bA.ymin, A};
imax = Idx{bB.ymax, B};
if (B[bB.ymin] < imin.pt()) imin = Idx{bB.ymin, B};
if (imax.pt() < A[bA.ymax]) imax = Idx{bA.ymax, A};
}
if (&imin.poly() == &imax.poly())
return true;
bool found_divisor = false;
visit_antipodals<AntipodalVisitMode::EdgesOnly>(
imin, imax,
[&imin, &imax, &found_divisor](size_t ia, size_t ib, const Point &dir) {
// std::cout << "A" << ia << " B" << ib << " dir " <<
// dir.x() << " " << dir.y() << std::endl;
const Polygon &A = imin.poly(), &B = imax.poly();
Point ref_a = A[(ia + 2) % A.size()], ref_b = B[(ib + 2) % B.size()];
bool is_left_a = dotperp( dir, ref_a - A[ia]) > 0;
bool is_left_b = dotperp(-dir, ref_b - B[ib]) > 0;
// If both reference points are on the left (or right) of the
// support line and the opposite support line is to the righ (or
// left), the divisor line is found. We only test the reference
// point, as by definition, if that is on one side, all the other
// points must be on the same side of a support line.
auto d = dotperp(dir, B[ib] - A[ia]);
if (d == 0 && ((is_left_a && is_left_b) || (!is_left_a && !is_left_b))) {
// The caliper lines are collinear, not just parallel
// Check if the lines are overlapping and if they do ignore the divisor
Point a = A[ia], b = A[(ia + 1) % A.size()];
if (b < a) std::swap(a, b);
Point c = B[ib], d = B[(ib + 1) % B.size()];
if (d < c) std::swap(c, d);
found_divisor = b < c;
} else if (d > 0) { // B is to the left of (A, A+1)
found_divisor = !is_left_a && !is_left_b;
} else { // B is to the right of (A, A+1)
found_divisor = is_left_a && is_left_b;
}
return !found_divisor;
});
// Intersects if the divisor was not found
return !found_divisor;
}
}} // namespace Slic3r::Geometry

View file

@ -561,6 +561,8 @@ inline bool is_rotation_ninety_degrees(const Vec3d &rotation)
return is_rotation_ninety_degrees(rotation.x()) && is_rotation_ninety_degrees(rotation.y()) && is_rotation_ninety_degrees(rotation.z());
}
bool intersects(const Polygon &convex_poly1, const Polygon &convex_poly2);
} } // namespace Slicer::Geometry
#endif

View file

@ -30,6 +30,8 @@
#include "libslic3r/Platform.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/libslic3r.h"
#include "libslic3r/Model.hpp"
#include "GUI.hpp"
#include "GUI_App.hpp"
#include "GUI_Utils.hpp"
@ -40,7 +42,6 @@
#include "slic3r/Utils/PresetUpdater.hpp"
#include "format.hpp"
#include "MsgDialog.hpp"
#include "libslic3r/libslic3r.h"
#include "UnsavedChangesDialog.hpp"
#if defined(__linux__) && defined(__WXGTK3__)
@ -2477,6 +2478,46 @@ static std::string get_first_added_preset(const std::map<std::string, std::strin
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater, bool& apply_keeped_changes)
{
wxString header, caption = _L("Configuration is editing from ConfigWizard");
const auto enabled_vendors = appconfig_new.vendors();
bool suppress_sla_printer = model_has_multi_part_objects(wxGetApp().model());
PrinterTechnology preferred_pt = ptAny;
auto get_preferred_printer_technology = [enabled_vendors, suppress_sla_printer](const std::string& bundle_name, const Bundle& bundle) {
const auto config = enabled_vendors.find(bundle_name);
PrinterTechnology pt = ptAny;
if (config != enabled_vendors.end()) {
for (const auto& model : bundle.vendor_profile->models) {
if (const auto model_it = config->second.find(model.id);
model_it != config->second.end() && model_it->second.size() > 0) {
if (pt == ptAny)
pt = model.technology;
// if preferred printer model has SLA printer technology it's important to check the model for multypart state
if (pt == ptSLA && suppress_sla_printer)
continue;
else
return pt;
}
}
}
return pt;
};
// Prusa printers are considered first, then 3rd party.
if (preferred_pt = get_preferred_printer_technology("PrusaResearch", bundles.prusa_bundle());
preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)) {
for (const auto& bundle : bundles) {
if (bundle.second.is_prusa_bundle) { continue; }
if (PrinterTechnology pt = get_preferred_printer_technology(bundle.first, bundle.second); pt == ptAny)
continue;
else if (preferred_pt == ptAny)
preferred_pt = pt;
if(!(preferred_pt == ptAny || (preferred_pt == ptSLA && suppress_sla_printer)))
break;
}
}
if (preferred_pt == ptSLA && !wxGetApp().may_switch_to_SLA_preset(caption))
return false;
bool check_unsaved_preset_changes = page_welcome->reset_user_profile();
if (check_unsaved_preset_changes)
header = _L("All user presets will be deleted.");
@ -2484,8 +2525,6 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
if (!check_unsaved_preset_changes)
act_btns |= UnsavedChangesDialog::ActionButtons::SAVE;
const auto enabled_vendors = appconfig_new.vendors();
// Install bundles from resources if needed:
std::vector<std::string> install_bundles;
for (const auto &pair : bundles) {
@ -2564,13 +2603,14 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
std::string preferred_model;
std::string preferred_variant;
const auto enabled_vendors_old = app_config->vendors();
auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
auto get_preferred_printer_model = [enabled_vendors, enabled_vendors_old, preferred_pt](const std::string& bundle_name, const Bundle& bundle, std::string& variant) {
const auto config = enabled_vendors.find(bundle_name);
if (config == enabled_vendors.end())
return std::string();
for (const auto& model : bundle.vendor_profile->models) {
if (const auto model_it = config->second.find(model.id);
model_it != config->second.end() && model_it->second.size() > 0) {
model_it != config->second.end() && model_it->second.size() > 0 &&
preferred_pt == model.technology) {
variant = *model_it->second.begin();
const auto config_old = enabled_vendors_old.find(bundle_name);
if (config_old == enabled_vendors_old.end())

View file

@ -2432,6 +2432,20 @@ void GUI_App::open_web_page_localized(const std::string &http_address)
open_browser_with_warning_dialog(http_address + "&lng=" + this->current_language_code_safe());
}
// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s).
// Because of we can't to print the multi-part objects with SLA technology.
bool GUI_App::may_switch_to_SLA_preset(const wxString& caption)
{
if (model_has_multi_part_objects(model())) {
show_info(nullptr,
_L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" +
_L("Please check your object list before preset changing."),
caption);
return false;
}
return true;
}
bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page)
{
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
@ -2447,13 +2461,9 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
if (res) {
load_current_presets();
if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA
&& Slic3r::model_has_multi_part_objects(wxGetApp().model())) {
GUI::show_info(nullptr,
_L("It's impossible to print multi-part object(s) with SLA technology.") + "\n\n" +
_L("Please check and fix your object list."),
_L("Attention!"));
}
// #ysFIXME - delete after testing: This part of code looks redundant. All checks are inside ConfigWizard::priv::apply_config()
if (preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA)
may_switch_to_SLA_preset(_L("Configuration is editing from ConfigWizard"));
}
return res;

View file

@ -309,6 +309,7 @@ public:
PrintHostJobQueue& printhost_job_queue() { return *m_printhost_job_queue.get(); }
void open_web_page_localized(const std::string &http_address);
bool may_switch_to_SLA_preset(const wxString& caption);
bool run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage start_page = ConfigWizard::SP_WELCOME);
void show_desktop_integration_dialog();

View file

@ -377,10 +377,7 @@ void ObjectList::get_selection_indexes(std::vector<int>& obj_idxs, std::vector<i
int ObjectList::get_mesh_errors_count(const int obj_idx, const int vol_idx /*= -1*/) const
{
if (obj_idx < 0)
return 0;
return (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx);
return obj_idx >= 0 ? (*m_objects)[obj_idx]->get_mesh_errors_count(vol_idx) : 0;
}
static std::string get_warning_icon_name(const TriangleMeshStats& stats)
@ -393,7 +390,7 @@ std::pair<wxString, std::string> ObjectList::get_mesh_errors(const int obj_idx,
const int errors = get_mesh_errors_count(obj_idx, vol_idx);
if (errors == 0)
return { "", "" }; // hide tooltip
return { {}, {} }; // hide tooltip
// Create tooltip string, if there are errors
wxString tooltip = format_wxstr(_L_PLURAL("Auto-repaired %1$d error", "Auto-repaired %1$d errors", errors), errors) + ":\n";
@ -4043,17 +4040,21 @@ void ObjectList::fix_through_netfabb()
// clear selections from the non-broken models if any exists
// and than fill names of models to repairing
if (vol_idxs.empty()) {
#if !FIX_THROUGH_NETFABB_ALWAYS
for (int i = int(obj_idxs.size())-1; i >= 0; --i)
if (object(obj_idxs[i])->get_mesh_errors_count() == 0)
obj_idxs.erase(obj_idxs.begin()+i);
#endif // FIX_THROUGH_NETFABB_ALWAYS
for (int obj_idx : obj_idxs)
model_names.push_back(object(obj_idx)->name);
}
else {
ModelObject* obj = object(obj_idxs.front());
#if !FIX_THROUGH_NETFABB_ALWAYS
for (int i = int(vol_idxs.size()) - 1; i >= 0; --i)
if (obj->get_mesh_errors_count(vol_idxs[i]) == 0)
vol_idxs.erase(vol_idxs.begin() + i);
#endif // FIX_THROUGH_NETFABB_ALWAYS
for (int vol_idx : vol_idxs)
model_names.push_back(obj->volumes[vol_idx]->name);
}
@ -4106,8 +4107,10 @@ void ObjectList::fix_through_netfabb()
if (vol_idxs.empty()) {
int vol_idx{ -1 };
for (int obj_idx : obj_idxs) {
#if !FIX_THROUGH_NETFABB_ALWAYS
if (object(obj_idx)->get_mesh_errors_count(vol_idx) == 0)
continue;
#endif // FIX_THROUGH_NETFABB_ALWAYS
if (!fix_and_update_progress(obj_idx, vol_idx, model_idx, progress_dlg, succes_models, failed_models))
break;
model_idx++;

View file

@ -36,6 +36,9 @@ typedef double coordf_t;
typedef std::pair<coordf_t, coordf_t> t_layer_height_range;
typedef std::map<t_layer_height_range, ModelConfig> t_layer_config_ranges;
// Manifold mesh may contain self-intersections, so we want to always allow fixing the mesh.
#define FIX_THROUGH_NETFABB_ALWAYS 1
namespace GUI {
wxDECLARE_EVENT(EVT_OBJ_LIST_OBJECT_SELECT, SimpleEvent);

View file

@ -182,7 +182,7 @@ void GLGizmoCut::on_render_input_window(float x, float y, float bottom_limit)
double cut_z = m_cut_z;
if (imperial_units)
cut_z *= ObjectManipulation::mm_to_in;
ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f");
ImGui::InputDouble("", &cut_z, 0.0f, 0.0f, "%.2f", ImGuiInputTextFlags_CharsDecimal);
ImGui::SameLine();
m_imgui->text(imperial_units ? _L("in") : _L("mm"));

View file

@ -60,8 +60,9 @@ void GLGizmoSimplify::on_render_for_picking() {}
void GLGizmoSimplify::on_render_input_window(float x, float y, float bottom_limit)
{
create_gui_cfg();
int obj_index;
ModelVolume *act_volume = get_selected_volume(&obj_index);
const Selection &selection = m_parent.get_selection();
int obj_index = selection.get_object_idx();
ModelVolume *act_volume = get_volume(selection, wxGetApp().plater()->model());
if (act_volume == nullptr) {
switch (m_state) {
case State::settings: close(); break;
@ -427,17 +428,34 @@ bool GLGizmoSimplify::exist_volume(ModelVolume *volume) {
return false;
}
ModelVolume *GLGizmoSimplify::get_selected_volume(int *object_idx_ptr) const
ModelVolume * GLGizmoSimplify::get_volume(const Selection &selection, Model &model)
{
const Selection &selection = m_parent.get_selection();
int object_idx = selection.get_object_idx();
if (object_idx_ptr != nullptr) *object_idx_ptr = object_idx;
if (object_idx < 0) return nullptr;
ModelObjectPtrs &objs = wxGetApp().plater()->model().objects;
if (static_cast<int>(objs.size()) <= object_idx) return nullptr;
ModelObject *obj = objs[object_idx];
if (obj->volumes.empty()) return nullptr;
return obj->volumes.front();
const Selection::IndicesList& idxs = selection.get_volume_idxs();
if (idxs.empty()) return nullptr;
// only one selected volume
if (idxs.size() != 1) return nullptr;
const GLVolume *selected_volume = selection.get_volume(*idxs.begin());
if (selected_volume == nullptr) return nullptr;
const GLVolume::CompositeID &cid = selected_volume->composite_id;
const ModelObjectPtrs& objs = model.objects;
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
return nullptr;
const ModelObject* obj = objs[cid.object_id];
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
return nullptr;
return obj->volumes[cid.volume_id];
}
const ModelVolume *GLGizmoSimplify::get_volume(const GLVolume::CompositeID &cid, const Model &model)
{
const ModelObjectPtrs &objs = model.objects;
if (cid.object_id < 0 || objs.size() <= static_cast<size_t>(cid.object_id))
return nullptr;
const ModelObject *obj = objs[cid.object_id];
if (cid.volume_id < 0 || obj->volumes.size() <= static_cast<size_t>(cid.volume_id))
return nullptr;
return obj->volumes[cid.volume_id];
}
void GLGizmoSimplify::init_wireframe()
@ -478,13 +496,17 @@ void GLGizmoSimplify::render_wireframe() const
// is initialized?
if (m_wireframe_VBO_id == 0 || m_wireframe_IBO_id == 0) return;
if (!m_show_wireframe) return;
ModelVolume *act_volume = get_selected_volume();
if (act_volume == nullptr) return;
const Transform3d trafo_matrix =
act_volume->get_object()->instances[m_parent.get_selection().get_instance_idx()]
->get_transformation().get_matrix() *
act_volume->get_matrix();
const auto& selection = m_parent.get_selection();
const auto& volume_idxs = selection.get_volume_idxs();
if (volume_idxs.empty() || volume_idxs.size() != 1) return;
const GLVolume *selected_volume = selection.get_volume(*volume_idxs.begin());
// check that selected model is wireframe initialized
if (m_volume != get_volume(selected_volume->composite_id, *m_parent.get_model()))
return;
const Transform3d trafo_matrix = selected_volume->world_matrix();
glsafe(::glPushMatrix());
glsafe(::glMultMatrixd(trafo_matrix.data()));

View file

@ -43,7 +43,9 @@ private:
void set_its(indexed_triangle_set &its);
void create_gui_cfg();
void request_rerender();
ModelVolume *get_selected_volume(int *object_idx = nullptr) const;
// move to global functions
static ModelVolume *get_volume(const Selection &selection, Model &model);
static const ModelVolume *get_volume(const GLVolume::CompositeID &cid, const Model &model);
// return false when volume was deleted
static bool exist_volume(ModelVolume *volume);

View file

@ -359,7 +359,7 @@ bool ImGuiWrapper::image_button()
bool ImGuiWrapper::input_double(const std::string &label, const double &value, const std::string &format)
{
return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str());
return ImGui::InputDouble(label.c_str(), const_cast<double*>(&value), 0.0f, 0.0f, format.c_str(), ImGuiInputTextFlags_CharsDecimal);
}
bool ImGuiWrapper::input_double(const wxString &label, const double &value, const std::string &format)

View file

@ -834,7 +834,7 @@ bool MainFrame::can_save() const
#if ENABLE_SAVE_COMMANDS_ALWAYS_ENABLED
return (m_plater != nullptr) &&
!m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) &&
!m_plater->get_project_filename().empty() && m_plater->is_project_dirty();
m_plater->is_project_dirty();
#else
return (m_plater != nullptr) && !m_plater->model().objects.empty() &&
!m_plater->canvas3D()->get_gizmos_manager().is_in_editing_mode(false) &&

View file

@ -3087,10 +3087,13 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
//actualizate warnings
if (invalidated != Print::APPLY_STATUS_UNCHANGED) {
if (background_process.empty())
process_validation_warning(std::string());
actualize_slicing_warnings(*this->background_process.current_print());
actualize_object_warnings(*this->background_process.current_print());
show_warning_dialog = false;
process_completed_with_error = false;
}
if (invalidated != Print::APPLY_STATUS_UNCHANGED && was_running && ! this->background_process.running() &&
@ -4540,6 +4543,11 @@ bool Plater::priv::can_fix_through_netfabb() const
std::vector<int> obj_idxs, vol_idxs;
sidebar->obj_list()->get_selection_indexes(obj_idxs, vol_idxs);
#if FIX_THROUGH_NETFABB_ALWAYS
// Fixing always.
return ! obj_idxs.empty() || ! vol_idxs.empty();
#else // FIX_THROUGH_NETFABB_ALWAYS
// Fixing only if the model is not manifold.
if (vol_idxs.empty()) {
for (auto obj_idx : obj_idxs)
if (model.objects[obj_idx]->get_mesh_errors_count() > 0)
@ -4551,11 +4559,10 @@ bool Plater::priv::can_fix_through_netfabb() const
for (auto vol_idx : vol_idxs)
if (model.objects[obj_idx]->get_mesh_errors_count(vol_idx) > 0)
return true;
return false;
#endif // FIX_THROUGH_NETFABB_ALWAYS
}
bool Plater::priv::can_simplify() const
{
// is object for simplification selected

View file

@ -3236,7 +3236,7 @@ void Tab::select_preset(std::string preset_name, bool delete_current /*=false*/,
const PresetWithVendorProfile new_printer_preset_with_vendor_profile = m_presets->get_preset_with_vendor_profile(new_printer_preset);
PrinterTechnology old_printer_technology = m_presets->get_edited_preset().printer_technology();
PrinterTechnology new_printer_technology = new_printer_preset.printer_technology();
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !may_switch_to_SLA_preset())
if (new_printer_technology == ptSLA && old_printer_technology == ptFFF && !wxGetApp().may_switch_to_SLA_preset(_L("New printer preset is selecting")))
canceled = true;
else {
struct PresetUpdate {
@ -3409,21 +3409,6 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr
return true;
}
// If we are switching from the FFF-preset to the SLA, we should to control the printed objects if they have a part(s).
// Because of we can't to print the multi-part objects with SLA technology.
bool Tab::may_switch_to_SLA_preset()
{
if (model_has_multi_part_objects(wxGetApp().model()))
{
show_info( parent(),
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
_(L("Please check your object list before preset changing.")),
_(L("Attention!")) );
return false;
}
return true;
}
void Tab::clear_pages()
{
// invalidated highlighter, if any exists

View file

@ -282,7 +282,6 @@ public:
// Select a new preset, possibly delete the current one.
void select_preset(std::string preset_name = "", bool delete_current = false, const std::string& last_selected_ph_printer_name = "");
bool may_discard_current_dirty_preset(PresetCollection* presets = nullptr, const std::string& new_printer_name = "");
bool may_switch_to_SLA_preset();
virtual void clear_pages();
virtual void update_description_lines();

View file

@ -23,6 +23,7 @@ add_executable(${_TEST_NAME}_tests
test_png_io.cpp
test_timeutils.cpp
test_indexed_triangle_set.cpp
../libnest2d/printer_parts.cpp
)
if (TARGET OpenVDB::openvdb)

View file

@ -9,6 +9,14 @@
#include "libslic3r/ClipperUtils.hpp"
#include "libslic3r/ShortestPath.hpp"
//#include <random>
//#include "libnest2d/tools/benchmark.h"
#include "libslic3r/SVG.hpp"
#include "../libnest2d/printer_parts.hpp"
#include <unordered_set>
using namespace Slic3r;
TEST_CASE("Polygon::contains works properly", "[Geometry]"){
@ -452,3 +460,217 @@ SCENARIO("Ported from xs/t/14_geometry.t", "[Geometry]"){
REQUIRE(! Slic3r::Geometry::directions_parallel(M_PI /2, PI, M_PI /180));
}
}
TEST_CASE("Convex polygon intersection on two disjoint squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(20 / SCALING_FACTOR, 0);
bool is_inters = Geometry::intersects(A, B);
REQUIRE(is_inters != true);
}
TEST_CASE("Convex polygon intersection on two intersecting squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(5 / SCALING_FACTOR, 5 / SCALING_FACTOR);
bool is_inters = Geometry::intersects(A, B);
REQUIRE(is_inters == true);
}
TEST_CASE("Convex polygon intersection on two squares touching one edge", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 0);
bool is_inters = Geometry::intersects(A, B);
REQUIRE(is_inters == true);
}
TEST_CASE("Convex polygon intersection on two squares touching one vertex", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
B.translate(10 / SCALING_FACTOR, 10);
bool is_inters = Geometry::intersects(A, B);
REQUIRE(is_inters == true);
}
TEST_CASE("Convex polygon intersection on two overlapping squares", "[Geometry][Rotcalip]") {
Polygon A{{0, 0}, {10, 0}, {10, 10}, {0, 10}};
A.scale(1. / SCALING_FACTOR);
Polygon B = A;
bool is_inters = Geometry::intersects(A, B);
REQUIRE(is_inters == true);
}
// Only for benchmarking
//static Polygon gen_convex_poly(std::mt19937_64 &rg, size_t point_cnt)
//{
// std::uniform_int_distribution<coord_t> dist(0, 100);
// Polygon out;
// out.points.reserve(point_cnt);
// coord_t tr = dist(rg) * 2 / SCALING_FACTOR;
// for (size_t i = 0; i < point_cnt; ++i)
// out.points.emplace_back(tr + dist(rg) / SCALING_FACTOR,
// tr + dist(rg) / SCALING_FACTOR);
// return Geometry::convex_hull(out.points);
//}
//TEST_CASE("Convex polygon intersection test on random polygons", "[Geometry]") {
// constexpr size_t TEST_CNT = 1000;
// constexpr size_t POINT_CNT = 1000;
// std::mt19937_64 rg{std::random_device{}()};
// Benchmark bench;
// auto tests = reserve_vector<std::pair<Polygon, Polygon>>(TEST_CNT);
// auto results = reserve_vector<bool>(TEST_CNT);
// auto expects = reserve_vector<bool>(TEST_CNT);
// for (size_t i = 0; i < TEST_CNT; ++i) {
// tests.emplace_back(gen_convex_poly(rg, POINT_CNT), gen_convex_poly(rg, POINT_CNT));
// }
// bench.start();
// for (const auto &test : tests)
// results.emplace_back(Geometry::intersects(test.first, test.second));
// bench.stop();
// std::cout << "Test time: " << bench.getElapsedSec() << std::endl;
// bench.start();
// for (const auto &test : tests)
// expects.emplace_back(!intersection(test.first, test.second).empty());
// bench.stop();
// std::cout << "Clipper time: " << bench.getElapsedSec() << std::endl;
// REQUIRE(results.size() == expects.size());
// for (size_t i = 0; i < results.size(); ++i) {
// // std::cout << expects[i] << " ";
// if (results[i] != expects[i]) {
// SVG svg{std::string("fail") + std::to_string(i) + ".svg"};
// svg.draw(tests[i].first, "blue");
// svg.draw(tests[i].second, "green");
// svg.Close();
// // std::cout << std::endl;
// }
// REQUIRE(results[i] == expects[i]);
// }
// std::cout << std::endl;
//}
struct Pair
{
size_t first, second;
bool operator==(const Pair &b) const { return first == b.first && second == b.second; }
};
template<> struct std::hash<Pair> {
size_t operator()(const Pair &c) const
{
return c.first * PRINTER_PART_POLYGONS.size() + c.second;
}
};
TEST_CASE("Convex polygon intersection test prusa polygons", "[Geometry][Rotcalip]") {
// Overlap of the same polygon should always be an intersection
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
Polygon P = PRINTER_PART_POLYGONS[i];
P = Geometry::convex_hull(P.points);
bool res = Geometry::intersects(P, P);
if (!res) {
SVG svg{std::string("fail_self") + std::to_string(i) + ".svg"};
svg.draw(P, "green");
svg.Close();
}
REQUIRE(res == true);
}
std::unordered_set<Pair> combos;
for (size_t i = 0; i < PRINTER_PART_POLYGONS.size(); ++i) {
for (size_t j = 0; j < PRINTER_PART_POLYGONS.size(); ++j) {
if (i != j) {
size_t a = std::min(i, j), b = std::max(i, j);
combos.insert(Pair{a, b});
}
}
}
// All disjoint
for (const auto &combo : combos) {
Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
A = Geometry::convex_hull(A.points);
B = Geometry::convex_hull(B.points);
auto bba = A.bounding_box();
auto bbb = B.bounding_box();
A.translate(-bba.center());
B.translate(-bbb.center());
B.translate(bba.size() + bbb.size());
bool res = Geometry::intersects(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
}
REQUIRE(res == ref);
}
// All intersecting
for (const auto &combo : combos) {
Polygon A = PRINTER_PART_POLYGONS[combo.first], B = PRINTER_PART_POLYGONS[combo.second];
A = Geometry::convex_hull(A.points);
B = Geometry::convex_hull(B.points);
auto bba = A.bounding_box();
auto bbb = B.bounding_box();
A.translate(-bba.center());
B.translate(-bbb.center());
bool res = Geometry::intersects(A, B);
bool ref = !intersection(A, B).empty();
if (res != ref) {
SVG svg{std::string("fail") + std::to_string(combo.first) + "_" + std::to_string(combo.second) + ".svg"};
svg.draw(A, "blue");
svg.draw(B, "green");
svg.Close();
}
REQUIRE(res == ref);
}
}