Fixed conflicts after merge with master
This commit is contained in:
commit
6084a92d9b
191 changed files with 13248 additions and 4361 deletions
|
@ -54,6 +54,10 @@
|
|||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/miniz_extension.hpp"
|
||||
|
||||
// For stl export
|
||||
#include "libslic3r/CSGMesh/ModelToCSGMesh.hpp"
|
||||
#include "libslic3r/CSGMesh/PerformCSGMeshBooleans.hpp"
|
||||
|
||||
#include "GUI.hpp"
|
||||
#include "GUI_App.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
@ -828,6 +832,7 @@ Sidebar::Sidebar(Plater *parent)
|
|||
wxRIGHT, margin_5);
|
||||
#else
|
||||
wxBOTTOM, 1);
|
||||
(void)margin_5; // supress unused capture warning
|
||||
#endif // __WXGTK3__
|
||||
} else {
|
||||
sizer_filaments->Add(combo_and_btn_sizer, 0, wxEXPAND |
|
||||
|
@ -1374,7 +1379,7 @@ void Sidebar::update_sliced_info_sizer()
|
|||
wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time));
|
||||
p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":");
|
||||
|
||||
p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed());
|
||||
p->plater->get_notification_manager()->set_slicing_complete_print_time(_u8L("Estimated printing time") + ": " + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed());
|
||||
|
||||
// Hide non-SLA sliced info parameters
|
||||
p->sliced_info->SetTextAndShow(siFilament_m, "N/A");
|
||||
|
@ -1464,7 +1469,7 @@ void Sidebar::update_sliced_info_sizer()
|
|||
new_label += format_wxstr("\n - %1%", _L("normal mode"));
|
||||
info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time));
|
||||
|
||||
p->plater->get_notification_manager()->set_slicing_complete_print_time(_utf8("Estimated printing time: ") + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed());
|
||||
p->plater->get_notification_manager()->set_slicing_complete_print_time(_u8L("Estimated printing time") + ": " + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed());
|
||||
|
||||
}
|
||||
if (ps.estimated_silent_print_time != "N/A") {
|
||||
|
@ -2012,7 +2017,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
|
||||
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
|
||||
"brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width",
|
||||
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing",
|
||||
"extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology",
|
||||
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
|
||||
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
|
||||
|
@ -2245,7 +2250,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
|
|||
this->q->load_files(input_files);
|
||||
});
|
||||
|
||||
this->q->Bind(EVT_START_DOWNLOAD_OTHER_INSTANCE, [this](StartDownloadOtherInstanceEvent& evt) {
|
||||
this->q->Bind(EVT_START_DOWNLOAD_OTHER_INSTANCE, [](StartDownloadOtherInstanceEvent& evt) {
|
||||
BOOST_LOG_TRIVIAL(trace) << "Received url from other instance event.";
|
||||
wxGetApp().mainframe->Raise();
|
||||
for (size_t i = 0; i < evt.data.size(); ++i) {
|
||||
|
@ -2337,8 +2342,8 @@ void Plater::priv::collapse_sidebar(bool collapse)
|
|||
|
||||
// Now update the tooltip in the toolbar.
|
||||
std::string new_tooltip = collapse
|
||||
? _utf8(L("Expand sidebar"))
|
||||
: _utf8(L("Collapse sidebar"));
|
||||
? _u8L("Expand sidebar")
|
||||
: _u8L("Collapse sidebar");
|
||||
new_tooltip += " [Shift+Tab]";
|
||||
int id = collapse_toolbar.get_item_id("collapse_sidebar");
|
||||
collapse_toolbar.set_tooltip(id, new_tooltip);
|
||||
|
@ -3027,8 +3032,8 @@ bool Plater::priv::delete_object_from_model(size_t obj_idx)
|
|||
ModelObject* obj = model.objects[obj_idx];
|
||||
if (obj->is_cut()) {
|
||||
InfoDialog dialog(q, _L("Delete object which is a part of cut object"),
|
||||
_L("You try to delete an object which is a part of a cut object.\n"
|
||||
"This action will break a cut correspondence.\n"
|
||||
_L("You try to delete an object which is a part of a cut object.") + "\n" +
|
||||
_L("This action will break a cut information.\n"
|
||||
"After that PrusaSlicer can't guarantee model consistency"),
|
||||
false, wxYES | wxCANCEL | wxCANCEL_DEFAULT | wxICON_WARNING);
|
||||
dialog.SetButtonLabel(wxID_YES, _L("Delete object"));
|
||||
|
@ -3154,6 +3159,8 @@ void Plater::priv::split_object()
|
|||
// causing original positions not to be kept
|
||||
std::vector<size_t> idxs = load_model_objects(new_objects);
|
||||
|
||||
// clear previosli selection
|
||||
get_selection().clear();
|
||||
// select newly added objects
|
||||
for (size_t idx : idxs)
|
||||
{
|
||||
|
@ -4232,7 +4239,7 @@ void Plater::priv::on_process_completed(SlicingProcessCompletedEvent &evt)
|
|||
}
|
||||
if (evt.cancelled()) {
|
||||
// this->statusbar()->set_status_text(_L("Cancelled"));
|
||||
this->notification_manager->set_slicing_progress_canceled(_utf8("Slicing Cancelled."));
|
||||
this->notification_manager->set_slicing_progress_canceled(_u8L("Slicing Cancelled."));
|
||||
}
|
||||
|
||||
this->sidebar->show_sliced_info_sizer(evt.success());
|
||||
|
@ -4535,7 +4542,7 @@ bool Plater::priv::init_view_toolbar()
|
|||
|
||||
item.name = "3D";
|
||||
item.icon_filename = "editor.svg";
|
||||
item.tooltip = _utf8(L("3D editor view")) + " [" + GUI::shortkey_ctrl_prefix() + "5]";
|
||||
item.tooltip = _u8L("3D editor view") + " [" + GUI::shortkey_ctrl_prefix() + "5]";
|
||||
item.sprite_id = 0;
|
||||
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_3D)); };
|
||||
if (!view_toolbar.add_item(item))
|
||||
|
@ -4543,7 +4550,7 @@ bool Plater::priv::init_view_toolbar()
|
|||
|
||||
item.name = "Preview";
|
||||
item.icon_filename = "preview.svg";
|
||||
item.tooltip = _utf8(L("Preview")) + " [" + GUI::shortkey_ctrl_prefix() + "6]";
|
||||
item.tooltip = _u8L("Preview") + " [" + GUI::shortkey_ctrl_prefix() + "6]";
|
||||
item.sprite_id = 1;
|
||||
item.left.action_callback = [this]() { if (this->q != nullptr) wxPostEvent(this->q, SimpleEvent(EVT_GLVIEWTOOLBAR_PREVIEW)); };
|
||||
if (!view_toolbar.add_item(item))
|
||||
|
@ -4752,7 +4759,7 @@ bool Plater::priv::can_increase_instances() const
|
|||
if (q->canvas3D()->get_gizmos_manager().get_current_type() == GLGizmosManager::Emboss) return false;
|
||||
|
||||
const auto obj_idxs = get_selection().get_object_idxs();
|
||||
return !obj_idxs.empty() && !sidebar->obj_list()->has_selected_cut_object();
|
||||
return !obj_idxs.empty() && !get_selection().is_wipe_tower() && !sidebar->obj_list()->has_selected_cut_object();
|
||||
}
|
||||
|
||||
bool Plater::priv::can_decrease_instances(int obj_idx /*= -1*/) const
|
||||
|
@ -5410,7 +5417,7 @@ protected:
|
|||
|
||||
LoadProjectsDialog::LoadProjectsDialog(const std::vector<fs::path>& paths)
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY,
|
||||
from_u8((boost::format(_utf8(L("%s - Multiple projects file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition,
|
||||
format_wxstr(_L("%1% - Multiple projects file"), SLIC3R_APP_NAME), wxDefaultPosition,
|
||||
wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
@ -5530,99 +5537,101 @@ bool Plater::preview_zip_archive(const boost::filesystem::path& archive_path)
|
|||
mz_zip_zero_struct(&archive);
|
||||
|
||||
if (!open_zip_reader(&archive, archive_path.string())) {
|
||||
std::string err_msg = GUI::format(_utf8("Loading of a zip archive on path %1% has failed."), archive_path.string());
|
||||
std::string err_msg = GUI::format(_u8L("Loading of a zip archive on path %1% has failed."), archive_path.string());
|
||||
throw Slic3r::FileIOError(err_msg);
|
||||
}
|
||||
|
||||
mz_uint num_entries = mz_zip_reader_get_num_files(&archive);
|
||||
|
||||
mz_zip_archive_file_stat stat;
|
||||
|
||||
std::vector<fs::path> selected_paths;
|
||||
|
||||
// selected_paths contains paths and its uncompressed size. The size is used to distinguish between files with same path.
|
||||
std::vector<std::pair<fs::path, size_t>> selected_paths;
|
||||
FileArchiveDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe), &archive, selected_paths);
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
{
|
||||
{
|
||||
std::string archive_path_string = archive_path.string();
|
||||
archive_path_string = archive_path_string.substr(0, archive_path_string.size() - 4);
|
||||
|
||||
fs::path archive_dir(wxStandardPaths::Get().GetTempDir().utf8_str().data());
|
||||
|
||||
for (auto& path_w_size : selected_paths) {
|
||||
const fs::path& path = path_w_size.first;
|
||||
size_t size = path_w_size.second;
|
||||
// find path in zip archive
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
if (size != stat.m_uncomp_size) // size must fit
|
||||
continue;
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = boost::nowide::narrow(wname);
|
||||
fs::path archive_path(name);
|
||||
|
||||
for (mz_uint i = 0; i < num_entries; ++i) {
|
||||
if (mz_zip_reader_file_stat(&archive, i, &stat)) {
|
||||
wxString wname = boost::nowide::widen(stat.m_filename);
|
||||
std::string name = GUI::format(wname);
|
||||
fs::path archive_path(name);
|
||||
std::string extra(1024, 0);
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(&archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
archive_path = fs::path(extra.substr(0, extra_size));
|
||||
name = archive_path.string();
|
||||
}
|
||||
|
||||
std::string extra(1024, 0);
|
||||
size_t extra_size = mz_zip_reader_get_filename_from_extra(&archive, i, extra.data(), extra.size());
|
||||
if (extra_size > 0) {
|
||||
archive_path = fs::path(extra.substr(0, extra_size));
|
||||
name = archive_path.string();
|
||||
}
|
||||
if (archive_path.empty())
|
||||
continue;
|
||||
if (path != archive_path)
|
||||
continue;
|
||||
// decompressing
|
||||
try
|
||||
{
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
// rename if file exists
|
||||
std::string filename = path.filename().string();
|
||||
std::string extension = boost::filesystem::extension(path);
|
||||
std::string just_filename = filename.substr(0, filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
|
||||
if (archive_path.empty())
|
||||
continue;
|
||||
for (const auto& path : selected_paths) {
|
||||
if (path == archive_path) {
|
||||
try
|
||||
size_t version = 0;
|
||||
while (fs::exists(archive_dir / (final_filename + extension)))
|
||||
{
|
||||
std::replace(name.begin(), name.end(), '\\', '/');
|
||||
// rename if file exists
|
||||
std::string filename = path.filename().string();
|
||||
std::string extension = boost::filesystem::extension(path);
|
||||
std::string just_filename = filename.substr(0, filename.size() - extension.size());
|
||||
std::string final_filename = just_filename;
|
||||
|
||||
size_t version = 0;
|
||||
while (fs::exists(archive_dir / (final_filename + extension)))
|
||||
{
|
||||
++version;
|
||||
final_filename = just_filename + "(" + std::to_string(version) + ")";
|
||||
}
|
||||
filename = final_filename + extension;
|
||||
fs::path final_path = archive_dir / filename;
|
||||
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
mz_bool res = mz_zip_reader_extract_file_to_mem(&archive, stat.m_filename, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2% "), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive)));
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
continue;
|
||||
}
|
||||
fs::fstream file(final_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
file.write(buffer.c_str(), buffer.size());
|
||||
file.close();
|
||||
if (!fs::exists(final_path)) {
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to find unzipped file at %1%. Unzipping of file has failed."), final_path.string());
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
continue;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Unzipped " << final_path;
|
||||
|
||||
if (!boost::algorithm::iends_with(filename, ".3mf") && !boost::algorithm::iends_with(filename, ".amf")) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
continue;
|
||||
}
|
||||
// if 3mf - read archive headers to find project file
|
||||
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(final_path.string())) ||
|
||||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
project_paths.emplace_back(final_path);
|
||||
++version;
|
||||
final_filename = just_filename + "(" + std::to_string(version) + ")";
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
filename = final_filename + extension;
|
||||
fs::path final_path = archive_dir / filename;
|
||||
std::string buffer((size_t)stat.m_uncomp_size, 0);
|
||||
// Decompress action. We already has correct file index in stat structure.
|
||||
mz_bool res = mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, (void*)buffer.data(), (size_t)stat.m_uncomp_size, 0);
|
||||
if (res == 0) {
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to unzip file to %1%: %2% "), final_path.string(), mz_zip_get_error_string(mz_zip_get_last_error(&archive)));
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
break;
|
||||
}
|
||||
// write buffer to file
|
||||
fs::fstream file(final_path, std::ios::out | std::ios::binary | std::ios::trunc);
|
||||
file.write(buffer.c_str(), buffer.size());
|
||||
file.close();
|
||||
if (!fs::exists(final_path)) {
|
||||
wxString error_log = GUI::format_wxstr(_L("Failed to find unzipped file at %1%. Unzipping of file has failed."), final_path.string());
|
||||
BOOST_LOG_TRIVIAL(error) << error_log;
|
||||
show_error(nullptr, error_log);
|
||||
break;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << "Unzipped " << final_path;
|
||||
if (!boost::algorithm::iends_with(filename, ".3mf") && !boost::algorithm::iends_with(filename, ".amf")) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
// if 3mf - read archive headers to find project file
|
||||
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(final_path.string())) ||
|
||||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf"))) {
|
||||
non_project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
|
||||
project_paths.emplace_back(final_path);
|
||||
break;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
// ensure the zip archive is closed and rethrow the exception
|
||||
close_zip_reader(&archive);
|
||||
throw Slic3r::FileIOError(e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5797,9 +5806,7 @@ protected:
|
|||
|
||||
ProjectDropDialog::ProjectDropDialog(const std::string& filename)
|
||||
: DPIDialog(static_cast<wxWindow*>(wxGetApp().mainframe), wxID_ANY,
|
||||
// #ysFIXME_delete_after_test_of_6377
|
||||
// from_u8((boost::format(_utf8(L("%s - Drop project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition,
|
||||
from_u8((boost::format(_utf8(L("%s - Load project file"))) % SLIC3R_APP_NAME).str()), wxDefaultPosition,
|
||||
format_wxstr("%1% - %2%", SLIC3R_APP_NAME, _L("Load project file")), wxDefaultPosition,
|
||||
wxDefaultSize, wxDEFAULT_DIALOG_STYLE)
|
||||
{
|
||||
SetFont(wxGetApp().normal_font());
|
||||
|
@ -5907,7 +5914,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
|
|||
std::string filename = (*it).filename().string();
|
||||
if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
|
||||
ProjectDropDialog::LoadType load_type = ProjectDropDialog::LoadType::Unknown;
|
||||
// if (!model().objects.empty()) { // #ysFIXME_delete_after_test_of_6377
|
||||
{
|
||||
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) ||
|
||||
(boost::algorithm::iends_with(filename, ".amf") && !boost::algorithm::iends_with(filename, ".zip.amf")))
|
||||
load_type = ProjectDropDialog::LoadType::LoadGeometry;
|
||||
|
@ -5924,11 +5931,7 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
|
|||
load_type = static_cast<ProjectDropDialog::LoadType>(std::clamp(std::stoi(wxGetApp().app_config->get("drop_project_action")),
|
||||
static_cast<int>(ProjectDropDialog::LoadType::OpenProject), static_cast<int>(ProjectDropDialog::LoadType::LoadConfig)));
|
||||
}
|
||||
/* // #ysFIXME_delete_after_test_of_6377
|
||||
}
|
||||
else
|
||||
load_type = ProjectDropDialog::LoadType::OpenProject;
|
||||
*/
|
||||
|
||||
if (load_type == ProjectDropDialog::LoadType::Unknown)
|
||||
return false;
|
||||
|
@ -6358,14 +6361,34 @@ void Plater::export_stl_obj(bool extended, bool selection_only)
|
|||
return;
|
||||
|
||||
// Following lambda generates a combined mesh for export with normals pointing outwards.
|
||||
auto mesh_to_export_fff = [](const ModelObject& mo, int instance_id) {
|
||||
auto mesh_to_export_fff = [this](const ModelObject& mo, int instance_id) {
|
||||
TriangleMesh mesh;
|
||||
for (const ModelVolume* v : mo.volumes)
|
||||
if (v->is_model_part()) {
|
||||
TriangleMesh vol_mesh(v->mesh());
|
||||
vol_mesh.transform(v->get_matrix(), true);
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
|
||||
std::vector<csg::CSGPart> csgmesh;
|
||||
csgmesh.reserve(2 * mo.volumes.size());
|
||||
csg::model_to_csgmesh(mo, Transform3d::Identity(), std::back_inserter(csgmesh),
|
||||
csg::mpartsPositive | csg::mpartsNegative | csg::mpartsDoSplits);
|
||||
|
||||
if (csg::check_csgmesh_booleans(range(csgmesh)) == csgmesh.end()) {
|
||||
try {
|
||||
auto cgalm = csg::perform_csgmesh_booleans(range(csgmesh));
|
||||
mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*cgalm);
|
||||
} catch (...) {}
|
||||
}
|
||||
|
||||
if (mesh.empty()) {
|
||||
get_notification_manager()->push_plater_error_notification(
|
||||
_u8L("Unable to perform boolean operation on model meshes. "
|
||||
"Only positive parts will be exported."));
|
||||
|
||||
for (const ModelVolume* v : mo.volumes)
|
||||
if (v->is_model_part()) {
|
||||
TriangleMesh vol_mesh(v->mesh());
|
||||
vol_mesh.transform(v->get_matrix(), true);
|
||||
mesh.merge(vol_mesh);
|
||||
}
|
||||
}
|
||||
|
||||
if (instance_id == -1) {
|
||||
TriangleMesh vols_mesh(mesh);
|
||||
mesh = TriangleMesh();
|
||||
|
@ -6478,9 +6501,9 @@ void Plater::export_stl_obj(bool extended, bool selection_only)
|
|||
}
|
||||
}
|
||||
|
||||
if (path.EndsWith(".stl"))
|
||||
if (path.Lower().EndsWith(".stl"))
|
||||
Slic3r::store_stl(path_u8.c_str(), &mesh, true);
|
||||
else if (path.EndsWith(".obj"))
|
||||
else if (path.Lower().EndsWith(".obj"))
|
||||
Slic3r::store_obj(path_u8.c_str(), &mesh);
|
||||
// p->statusbar()->set_status_text(format_wxstr(_L("STL file exported to %s"), path));
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue