Merge branch 'lm_seq_ht90'

This commit is contained in:
Lukas Matena 2025-02-27 12:11:44 +01:00
commit 0d9a72416e
12 changed files with 219 additions and 40 deletions

View File

@ -281,6 +281,62 @@
]
}
]
},
{
"printer_notes_regex": ".*PRINTER_MODEL_HT90.*",
"gantry_model_filename": "prusa3d_ht90_actuator.stl",
"slices": [
{
"height": "0",
"type": "convex",
"polygons": [
"-5,-5; 5,-5; 5,5; -5,5"
]
},
{
"height": "1",
"type": "convex",
"polygons": [
"-33,-2; 33,-2; 33,48; -33,48",
"-46,-37; 46,-37; 46,2; -46,2"
]
},
{
"height": "20",
"type": "convex",
"polygons": [
"-55,-55; 55,-55; 55,55; -55,55"
]
},
{
"height": "28",
"type": "convex",
"polygons": [
"-76,-68; 76,-68; 76,65; -76,65"
]
},
{
"height": "40",
"type": "convex",
"polygons": [
"-120,-118; 120,-118; 120,94; -120,94"
]
},
{
"height": "60",
"type": "box",
"polygons": [
"-170,-164; 170,-164; 170,130; -170,130"
]
},
{
"height": "80",
"type": "box",
"polygons": [
"-400,-400; 400,-400; 400,400; -400,400"
]
}
]
}
]
}

View File

@ -28,7 +28,6 @@ using namespace Slic3r;
namespace Sequential
{
class ObjectTooLargeException : public std::runtime_error { public: explicit ObjectTooLargeException(const std::string& msg) : std::runtime_error(msg) {}};
class InternalErrorException : public std::runtime_error { public: explicit InternalErrorException(const std::string& msg) : std::runtime_error(msg) {} };

View File

@ -86,6 +86,7 @@ bool PrinterGeometry::convert_Geometry2PlateBounds(Slic3r::BoundingBox &plate_bo
plate_bounding_polygon.points.insert(plate_bounding_polygon.points.begin() + i, Point(plate.points[i].x() / SEQ_SLICER_SCALE_FACTOR,
plate.points[i].y() / SEQ_SLICER_SCALE_FACTOR));
}
plate_bounding_polygon.make_counter_clockwise();
return false;
}
else

View File

@ -742,7 +742,7 @@ void prepare_ExtruderPolygons(const SolverConfiguration &solver
}
else
{
throw InternalErrorException("MISMATCH BETWEEN OBJECT AND PRINTER SLICE HEIGHTS.");
throw std::runtime_error("MISMATCH BETWEEN OBJECT AND PRINTER SLICE HEIGHTS.");
}
}
}

View File

@ -148,6 +148,19 @@ void assume_BedBoundingPolygon(z3::context &Context,
{
BoundingBox box = get_extents(polygon);
#ifdef DEBUG
{
printf("Polygon box: [%d,%d] [%d,%d]\n", box.min.x(), box.min.y(), box.max.x(), box.max.y());
printf("Bed bounding polygon: %ld\n", bed_bounding_polygon.points.size());
for (unsigned int i = 0; i < bed_bounding_polygon.points.size(); ++i)
{
printf("[%d,%d] ", bed_bounding_polygon.points[i].x(), bed_bounding_polygon.points[i].y());
}
printf("\n");
}
#endif
assume_PointInsidePolygon(Context,
dec_var_X + box.min.x(),
dec_var_Y + box.min.y(),
@ -470,7 +483,8 @@ void introduce_ConsequentialTemporalLepoxAgainstFixed(z3::solver
#endif
for (unsigned int j = 1; j < undecided.size(); ++j)
{
Solver.add(dec_vars_T[0] + temporal_spread < dec_vars_T[undecided[j]]);
//Solver.add(dec_vars_T[0] + temporal_spread < dec_vars_T[undecided[j]]);
Solver.add(dec_vars_T[undecided[j]] < 0 || dec_vars_T[0] + temporal_spread < dec_vars_T[undecided[j]]);
}
}
else if (is_fixed(0, fixed))
@ -481,8 +495,9 @@ void introduce_ConsequentialTemporalLepoxAgainstFixed(z3::solver
}
#endif
for (unsigned int j = 0; j < undecided.size(); ++j)
{
Solver.add(Context.real_val(dec_values_T[0].numerator, dec_values_T[0].denominator) + temporal_spread < dec_vars_T[undecided[j]]);
{
//Solver.add(Context.real_val(dec_values_T[0].numerator, dec_values_T[0].denominator) + temporal_spread < dec_vars_T[undecided[j]]);
Solver.add(dec_vars_T[undecided[j]] < 0 || Context.real_val(dec_values_T[0].numerator, dec_values_T[0].denominator) + temporal_spread < dec_vars_T[undecided[j]]);
}
}
else
@ -506,7 +521,8 @@ void introduce_ConsequentialTemporalLepoxAgainstFixed(z3::solver
printf("Undecided --> Undecided: %d --> %d standard\n", undecided[i], next_i);
}
#endif
Solver.add(dec_vars_T[undecided[i]] + temporal_spread < dec_vars_T[next_i] && dec_vars_T[undecided[i]] + temporal_spread + temporal_spread / 2 > dec_vars_T[next_i]);
//Solver.add(dec_vars_T[undecided[i]] + temporal_spread < dec_vars_T[next_i] && dec_vars_T[undecided[i]] + temporal_spread + temporal_spread / 2 > dec_vars_T[next_i]);
Solver.add((dec_vars_T[undecided[i]] < 0 || dec_vars_T[next_i] < 0) || dec_vars_T[undecided[i]] + temporal_spread < dec_vars_T[next_i] && dec_vars_T[undecided[i]] + temporal_spread + temporal_spread / 2 > dec_vars_T[next_i]);
}
/* Undecided --> missing */
else
@ -520,12 +536,14 @@ void introduce_ConsequentialTemporalLepoxAgainstFixed(z3::solver
{
if (i != j)
{
Solver.add(dec_vars_T[undecided[j]] + temporal_spread < dec_vars_T[undecided[i]]);
//Solver.add(dec_vars_T[undecided[j]] + temporal_spread < dec_vars_T[undecided[i]]);
Solver.add(dec_vars_T[undecided[j]] < 0 || dec_vars_T[undecided[j]] + temporal_spread < dec_vars_T[undecided[i]]);
}
}
for (unsigned int j = 0; j < fixed.size(); ++j)
{
Solver.add(Context.real_val(dec_values_T[fixed[j]].numerator, dec_values_T[fixed[j]].denominator) + temporal_spread < dec_vars_T[undecided[i]]);
//Solver.add(Context.real_val(dec_values_T[fixed[j]].numerator, dec_values_T[fixed[j]].denominator) + temporal_spread < dec_vars_T[undecided[i]]);
Solver.add(dec_vars_T[undecided[i]] < 0 || Context.real_val(dec_values_T[fixed[j]].numerator, dec_values_T[fixed[j]].denominator) + temporal_spread < dec_vars_T[undecided[i]]);
}
}
}
@ -544,8 +562,12 @@ void introduce_ConsequentialTemporalLepoxAgainstFixed(z3::solver
printf("Fixed --> Undecided: %d --> %d standard\n", fixed[i], next_i);
}
#endif
/*
Solver.add( Context.real_val(dec_values_T[fixed[i]].numerator, dec_values_T[fixed[i]].denominator) + temporal_spread < dec_vars_T[next_i]
&& Context.real_val(dec_values_T[fixed[i]].numerator, dec_values_T[fixed[i]].denominator) + temporal_spread + temporal_spread / 2 > dec_vars_T[next_i]);
*/
Solver.add(dec_vars_T[next_i] < 0 || ( Context.real_val(dec_values_T[fixed[i]].numerator, dec_values_T[fixed[i]].denominator) + temporal_spread < dec_vars_T[next_i]
&& Context.real_val(dec_values_T[fixed[i]].numerator, dec_values_T[fixed[i]].denominator) + temporal_spread + temporal_spread / 2 > dec_vars_T[next_i]));
}
/* Fixed --> Fixed */
else if (is_fixed(next_i, fixed))
@ -557,8 +579,12 @@ void introduce_ConsequentialTemporalLepoxAgainstFixed(z3::solver
#endif
for (unsigned int j = 0; j < undecided.size(); ++j)
{
/*
Solver.add( Context.real_val(dec_values_T[fixed[i]].numerator, dec_values_T[fixed[i]].denominator) > dec_vars_T[undecided[j]] + temporal_spread
|| Context.real_val(dec_values_T[next_i].numerator, dec_values_T[next_i].denominator) + temporal_spread < dec_vars_T[undecided[j]]);
*/
Solver.add(dec_vars_T[undecided[j]] < 0 || ( Context.real_val(dec_values_T[fixed[i]].numerator, dec_values_T[fixed[i]].denominator) > dec_vars_T[undecided[j]] + temporal_spread
|| Context.real_val(dec_values_T[next_i].numerator, dec_values_T[next_i].denominator) + temporal_spread < dec_vars_T[undecided[j]]));
}
}
}
@ -1431,7 +1457,7 @@ void assume_PointInsidePolygon(z3::context &Context,
z3::expr inside_half_plane( (normal.x() * dec_var_X)
+ (normal.y() * dec_var_Y)
- (normal.x() * line.a.x())
- (normal.y() * line.a.y()) < 0);
- (normal.y() * line.a.y()) < 0);
if (point == polygon.points.begin())
{
@ -1442,7 +1468,6 @@ void assume_PointInsidePolygon(z3::context &Context,
in_conjunction = in_conjunction && inside_half_plane;
}
}
constraints.push_back(in_conjunction);
}
}
@ -9488,8 +9513,6 @@ bool optimize_ConsequentialWeakPolygonNonoverlappingBinaryCentered(z3::solver
bool size_solvable = false;
z3::expr_vector bounding_box_assumptions(Context);
coord_t box_min_x = (_outer_half_box.min.x() + _inner_half_box.min.x()) / 2;
coord_t box_max_x = (_outer_half_box.max.x() + _inner_half_box.max.x()) / 2;
@ -9770,6 +9793,8 @@ bool optimize_ConsequentialWeakPolygonNonoverlappingBinaryCentered(z3::solver
std::vector<Rational> local_dec_values_Y = dec_values_Y;
std::vector<Rational> local_dec_values_T = dec_values_T;
assert(inner_half_polygon.points.size() == solver_configuration.plate_bounding_polygon.points.size());
Polygon _inner_half_polygon = inner_half_polygon;
Polygon _outer_half_polygon = solver_configuration.plate_bounding_polygon;
@ -9839,7 +9864,6 @@ bool optimize_ConsequentialWeakPolygonNonoverlappingBinaryCentered(z3::solver
bool size_solvable = false;
z3::expr_vector bounding_box_assumptions(Context);
Polygon bounding_polygon;
for (unsigned int i = 0; i < _outer_half_polygon.points.size(); ++i)
@ -9883,7 +9907,7 @@ bool optimize_ConsequentialWeakPolygonNonoverlappingBinaryCentered(z3::solver
undecided,
polygons,
unreachable_polygons))
{
{
switch (Solver.check(complete_assumptions))
{
case z3::sat:
@ -11102,7 +11126,7 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
BoundingBox inner_half_box({box_center_x, box_center_y}, {box_center_x, box_center_y});
for (unsigned int curr_polygon = 0; curr_polygon < polygons.size(); /* nothing */)
{
{
bool optimized = false;
z3::set_param("timeout", solver_configuration.optimization_timeout.c_str());
@ -11311,7 +11335,7 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
if (!optimized)
{
if (curr_polygon <= 0)
{
{
return false;
}
else
@ -11328,7 +11352,6 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
return true;
}
}
assert(remaining_polygons.empty());
}
assert(remaining_polygons.empty());
@ -11398,7 +11421,7 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
}
for (unsigned int curr_polygon = 0; curr_polygon < solvable_objects.size(); /* nothing */)
{
{
bool optimized = false;
z3::set_param("timeout", solver_configuration.optimization_timeout.c_str());
@ -11477,12 +11500,11 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
polygons,
lepox_to_next,
trans_bed_lepox);
std::vector<int> missing;
std::vector<int> remaining_local;
while(object_group_size > 0)
{
{
z3::expr_vector presence_assumptions(z_context);
assume_ConsequentialObjectPresence(z_context, local_dec_vars_T, undecided, missing, presence_assumptions);
@ -11493,6 +11515,11 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
{
printf(" %d\n", undecided[j]);
}
printf("Missing\n");
for (unsigned int j = 0; j < missing.size(); ++j)
{
printf(" %d\n", missing[j]);
}
printf("Decided\n");
for (unsigned int j = 0; j < decided_polygons.size(); ++j)
{
@ -11579,7 +11606,7 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
}
if (optimized)
{
{
for (unsigned int i = 0; i < undecided.size(); ++i)
{
dec_values_X[undecided[i]] = local_values_X[undecided[i]];
@ -11650,7 +11677,6 @@ bool optimize_SubglobalConsequentialPolygonNonoverlappingBinaryCentered(const So
return true;
}
}
assert(remaining_polygons.empty());
}
assert(remaining_polygons.empty());

View File

@ -71,7 +71,7 @@ static Sequential::PrinterGeometry get_printer_geometry(const ConfigBase& config
{
if (! printer_notes.empty()) {
try {
boost::nowide::ifstream in(resources_dir() + "/data/printer_gantries/geometries.txt");
boost::nowide::ifstream in(resources_dir() + "/data/printer_gantries/geometries.json");
boost::property_tree::ptree pt;
boost::property_tree::read_json(in, pt);
for (const auto& printer : pt.get_child("printers")) {

View File

@ -217,12 +217,13 @@ int GCodeViewer::SequentialView::ActualSpeedImguiWidget::plot(const char* label,
}
#endif // ENABLE_ACTUAL_SPEED_DEBUG
void GCodeViewer::SequentialView::Marker::init(std::optional<std::unique_ptr<GLModel>>& model_opt)
void GCodeViewer::SequentialView::Marker::init(std::optional<std::unique_ptr<GLModel>>& model_opt, bool is_ht90)
{
if (! model_opt.has_value())
return;
m_model.reset();
m_is_ht90 = is_ht90;
m_generic_marker = (model_opt->get() == nullptr);
if (m_generic_marker)
@ -233,6 +234,85 @@ void GCodeViewer::SequentialView::Marker::init(std::optional<std::unique_ptr<GLM
m_model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f });
}
// This does not do any scaling!
static Transform3d align_cylinder(const Vec3d& p1, const Vec3d& p2, double r) {
Vec3d direction = p2 - p1;
Vec3d axis = direction.normalized();
Vec3d z0(0, 0, 1);
Matrix3d rotation;
if (axis.isApprox(z0))
rotation = Eigen::Matrix3d::Identity(); // Already aligned, use identity rotation
else if (axis.isApprox(-z0)) {
// 180-degree rotation around x or y (choose any perpendicular axis)
rotation = Eigen::AngleAxisd(M_PI, Vec3d(1, 0, 0)).toRotationMatrix();
} else {
Vec3d rotationAxis = z0.cross(axis);
double angle = acos(z0.dot(axis));
rotation = Eigen::AngleAxisd(angle, rotationAxis.normalized()).toRotationMatrix();
}
Transform3d transform = Transform3d::Identity();
transform.linear() = rotation;// * Geometry::scale_transform(Vec3d(2*r,2*r,length)).matrix().block<3,3>(0,0);
transform.translation() = p1;
return transform;
}
static void render_ht90_rods(const Vec3d& pos, GLShaderProgram* shader, const Transform3d& view_matrix, const Vec3d& bed_offset, GLModel& model)
{
Vec3d start(30.06, 27.70, 35); // Position of the back-right ball bearing, on the extruder head.
Vec3d end(32.33, 212.92, 338.8); // The other ball bearing, on the printer.
double r=4.725; // Diameter of the rod.
double rods_spacing = 60.;
double height = (end-start).norm();
if (!model.is_initialized()) {
auto t0 = its_make_sphere(r, 0.5);
auto t1 = its_make_sphere(r, 0.5);
auto t2 = its_make_cylinder(r, height);
its_translate(t1, Vec3f(0., 0., height));
its_merge(t2, t0);
its_merge(t2, t1);
model.init_from(t2);
model.set_color({ 1.0f, 1.0f, 1.0f, 0.5f });
}
// trans transforms extruder to world coord system of the first bed
Transform3d trans = Geometry::translation_transform(pos);
Vec3d p1 = trans * start;
Vec3d p2 = end; // already in world coords
for (int j=0; j<6; ++j) {
if (j == 3) {
p1.x() = p1.x() - rods_spacing;
p2.x() = p2.x() - rods_spacing;
}
p1 = trans * Geometry::rotation_transform(Vec3d(0,0,2*M_PI/3)) * trans.inverse() * p1;
p2 = Geometry::rotation_transform(Vec3d(0,0,2*M_PI/3)) * p2;
double dx = p2.x() - p1.x();
double dy = p2.y() - p1.y();
double dz = std::sqrt(height * height - dx * dx - dy * dy);
p2.z() = p1.z() + dz;
Transform3d wm = align_cylinder(p1, p2, r);
shader->set_uniform("view_model_matrix", view_matrix * wm);
shader->set_uniform("volume_world_matrix", Geometry::translation_transform(bed_offset) * wm);
model.render();
}
}
void GCodeViewer::SequentialView::Marker::render()
{
if (!m_visible)
@ -263,7 +343,7 @@ void GCodeViewer::SequentialView::Marker::render()
BoundingBoxf box = s_multiple_beds.get_build_volume_box();
box.translate(to_2d(bed_inst_offset));
// add a bit on both sides
box = box.inflated(40.0f);
box = box.inflated(m_is_ht90 ? 60.f : 40.f);
clip_planes = {{ { 1.0f, 0.0f, 0.0f, -box.min.cast<float>().x() } , { -1.0f, 0.0f, 0.0f, box.max.cast<float>().x() }}};
}
@ -287,8 +367,12 @@ void GCodeViewer::SequentialView::Marker::render()
shader->set_uniform("clipping_planes[0]", clip_planes[0]);
shader->set_uniform("clipping_planes[1]", clip_planes[1]);
shader->set_uniform("volume_world_matrix", volume_world_matrix);
m_model.render();
if (m_is_ht90 && ! m_generic_marker)
render_ht90_rods(m_world_position.cast<double>(), shader, view_matrix, bed_inst_offset, m_model_ht90_rod);
shader->stop_using();
if (curr_cull_face)
@ -1204,8 +1288,8 @@ void GCodeViewer::render()
m_sequential_view.marker.set_z_offset(m_z_offset);
// Following just makes sure that the shown marker is correct.
auto marker_model_opt = wxGetApp().plater()->get_current_canvas3D()->get_current_marker_model();
m_sequential_view.marker.init(marker_model_opt);
auto [marker_model_opt, is_ht90] = wxGetApp().plater()->get_current_canvas3D()->get_current_marker_model();
m_sequential_view.marker.init(marker_model_opt, is_ht90);
if (marker_model_opt.has_value())
m_max_bounding_box.reset();

View File

@ -115,6 +115,7 @@ public:
class Marker
{
GLModel m_model;
GLModel m_model_ht90_rod;
Vec3f m_world_position;
// For seams, the position of the marker is on the last endpoint of the toolpath containing it.
// This offset is used to show the correct value of tool position in the "ToolPosition" window.
@ -128,14 +129,20 @@ public:
bool m_fixed_screen_size{ false };
float m_scale_factor{ 1.0f };
bool m_generic_marker{ true };
bool m_is_ht90{false};
#if ENABLE_ACTUAL_SPEED_DEBUG
ActualSpeedImguiWidget m_actual_speed_imgui_widget;
#endif // ENABLE_ACTUAL_SPEED_DEBUG
public:
void init(std::optional<std::unique_ptr<GLModel>>& model_opt);
void init(std::optional<std::unique_ptr<GLModel>>& model_opt, bool is_ht90);
const BoundingBoxf3& get_bounding_box() const { return m_model.get_bounding_box(); }
BoundingBoxf3 get_bounding_box() const {
auto bb = m_model.get_bounding_box();
if (m_is_ht90)
bb.max.z() += scale_(400);
return bb;
}
void set_world_position(const Vec3f& position) { m_world_position = position; }
void set_world_offset(const Vec3f& offset) { m_world_offset = offset; }

View File

@ -162,9 +162,10 @@ void GLCanvas3D::select_bed(int i, bool triggered_by_user)
// - nullopt = same as before
// - nullptr = none available, use generic
// - GLModel = the model to use
std::optional<std::unique_ptr<GLModel>> GLCanvas3D::get_current_marker_model() const
// The other field is whether the printer is HT90.
std::pair<std::optional<std::unique_ptr<GLModel>>, bool> GLCanvas3D::get_current_marker_model() const
{
std::optional<std::unique_ptr<GLModel>> out;
auto out = std::make_pair<std::optional<std::unique_ptr<GLModel>>, bool>(std::nullopt, false);
static std::string last_printer_notes;
static double old_r = 0.;
@ -182,11 +183,13 @@ std::optional<std::unique_ptr<GLModel>> GLCanvas3D::get_current_marker_model() c
old_h = h;
old_seq = seq;
out = std::make_optional(nullptr);
out.second = (printer_notes.find("PRINTER_MODEL_HT90") != std::string::npos);
out.first = std::make_optional(nullptr);
if (! seq)
return out;
try {
boost::nowide::ifstream in(resources_dir() + "/data/printer_gantries/geometries.txt");
boost::nowide::ifstream in(resources_dir() + "/data/printer_gantries/geometries.json");
boost::property_tree::ptree pt;
boost::property_tree::read_json(in, pt);
for (const auto& printer : pt.get_child("printers")) {
@ -197,7 +200,7 @@ std::optional<std::unique_ptr<GLModel>> GLCanvas3D::get_current_marker_model() c
if (boost::filesystem::exists(filename)) {
std::unique_ptr<GLModel> m = std::make_unique<GLModel>();
if (m->init_from_file(filename))
out = std::make_optional(std::move(m));
out.first = std::make_optional(std::move(m));
}
break;
}
@ -205,7 +208,7 @@ std::optional<std::unique_ptr<GLModel>> GLCanvas3D::get_current_marker_model() c
} catch (...) {
// Whatever happened, ignore it. We will return nullptr.
}
if (*out == nullptr && seq) {
if (*(out.first) == nullptr && seq) {
// Generic sequential extruder model.
double gantry_height = 10;
auto mesh = its_make_cylinder(r, h + gantry_height - 0.001);
@ -215,7 +218,7 @@ std::optional<std::unique_ptr<GLModel>> GLCanvas3D::get_current_marker_model() c
its_merge(mesh, mesh2);
std::unique_ptr<GLModel> m = std::make_unique<GLModel>();
m->init_from(mesh);
out = std::make_optional(std::move(m));
out.first = std::make_optional(std::move(m));
}
}
return out;

View File

@ -729,7 +729,7 @@ public:
const libvgcode::Interval& get_gcode_view_visible_range() const { return m_gcode_viewer.get_gcode_view_visible_range(); }
const libvgcode::PathVertex& get_gcode_vertex_at(size_t id) const { return m_gcode_viewer.get_gcode_vertex_at(id); }
std::optional<std::unique_ptr<GLModel>> get_current_marker_model() const;
std::pair<std::optional<std::unique_ptr<GLModel>>, bool> get_current_marker_model() const;
void toggle_sla_auxiliaries_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1);
void toggle_model_objects_visibility(bool visible, const ModelObject* mo = nullptr, int instance_idx = -1, const ModelVolume* mv = nullptr);

View File

@ -33,8 +33,11 @@ void SeqArrangeJob::process(Ctl& ctl)
}
);
} catch (const SeqArrangeJobException&) {
// The task was canceled. Just make sure that the progress notification disappears.
ctl.update_status(100, "");
ctl.update_status(100, ""); // Hide progress notification.
}
catch (const std::exception&) {
ctl.update_status(100, ""); // Hide progress notification.
throw;
}
}
@ -60,7 +63,7 @@ void SeqArrangeJob::finalize(bool canceled, std::exception_ptr& eptr)
dlg.ShowModal();
error = true;
eptr = nullptr; // The exception is handled.
} catch (const Sequential::InternalErrorException& ex) {
} catch (const std::runtime_error& ex) {
ErrorDialog dlg(wxGetApp().plater(), GUI::format_wxstr(_L("Internal error: %1%"), ex.what()), false);
dlg.ShowModal();
error = true;