Initial Draco write support.

Currently always exports with 16-bit precision and speed 0 (best compression).
The back-end function does have arguments to specify them, it's just not hooked into the GUI.
This commit is contained in:
Maeyanie
2025-10-04 18:07:49 -04:00
parent 5e48ed0c5c
commit 69a1614a4c
9 changed files with 321 additions and 10 deletions

View File

@@ -4,6 +4,7 @@
#include <boost/iostreams/device/mapped_file.hpp> #include <boost/iostreams/device/mapped_file.hpp>
#include <draco/compression/encode.h>
#include <draco/compression/decode.h> #include <draco/compression/decode.h>
#include <draco/io/mesh_io.h> #include <draco/io/mesh_io.h>
#include <draco/mesh/mesh.h> #include <draco/mesh/mesh.h>
@@ -96,22 +97,69 @@ bool load_drc(const char *path, Model *model, const char *object_name_in)
return ret; return ret;
} }
bool store_drc(const char *path, TriangleMesh *mesh, bool binary) bool store_drc(const char *path, TriangleMesh *mesh, int bits, int speed)
{ {
// TODO try {
return false; const std::vector<stl_triangle_vertex_indices>* indices = &(mesh->its.indices);
const std::vector<stl_vertex>* vertices = &(mesh->its.vertices);
Mesh dracoMesh;
dracoMesh.set_num_points(vertices->size());
GeometryAttribute gaPos;
gaPos.Init(GeometryAttribute::POSITION, nullptr, 3, DT_FLOAT32, false, sizeof(float)*3, 0);
int32_t idPos = dracoMesh.AddAttribute(gaPos, true, indices->size() * 3);
dracoMesh.attribute(idPos)->Resize(vertices->size());
for (size_t i = 0; i < vertices->size(); ++ i) {
float vertex[3];
vertex[0] = vertices->at(i)(0);
vertex[1] = vertices->at(i)(1);
vertex[2] = vertices->at(i)(2);
dracoMesh.attribute(idPos)->SetAttributeValue(AttributeValueIndex(i), vertex);
}
dracoMesh.SetNumFaces(indices->size());
for (size_t i = 0; i < indices->size(); ++ i) {
Mesh::Face face;
face[0] = PointIndex(indices->at(i)[0]);
face[1] = PointIndex(indices->at(i)[1]);
face[2] = PointIndex(indices->at(i)[2]);
dracoMesh.SetFace(FaceIndex(i), face);
}
Encoder encoder;
encoder.SetSpeedOptions(speed, speed);
encoder.SetAttributeQuantization(GeometryAttribute::POSITION, bits);
EncoderBuffer buffer;
encoder.EncodeMeshToBuffer(dracoMesh, &buffer);
FILE* fp = boost::nowide::fopen(path, "wb");
if (!fp) return false;
size_t written = fwrite(buffer.data(), 1, buffer.size(), fp);
fclose(fp);
if (written != buffer.size()) return false;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "store_drc: " << e.what();
return false;
}
return true;
} }
bool store_drc(const char *path, ModelObject *model_object, bool binary) bool store_drc(const char *path, ModelObject *model_object, int bits, int speed)
{ {
TriangleMesh mesh = model_object->mesh(); TriangleMesh mesh = model_object->mesh();
return store_drc(path, &mesh, binary); return store_drc(path, &mesh, bits, speed);
} }
bool store_drc(const char *path, Model *model, bool binary) bool store_drc(const char *path, Model *model, int bits, int speed)
{ {
TriangleMesh mesh = model->mesh(); TriangleMesh mesh = model->mesh();
return store_drc(path, &mesh, binary); return store_drc(path, &mesh, bits, speed);
} }
}; // namespace Slic3r }; // namespace Slic3r

View File

@@ -11,9 +11,9 @@ class Model;
extern bool load_drc(const char *path, TriangleMesh *meshptr); extern bool load_drc(const char *path, TriangleMesh *meshptr);
extern bool load_drc(const char *path, Model *model, const char *object_name = nullptr); extern bool load_drc(const char *path, Model *model, const char *object_name = nullptr);
extern bool store_drc(const char *path, TriangleMesh *mesh, bool binary); extern bool store_drc(const char *path, TriangleMesh *mesh, int bits, int speed);
extern bool store_drc(const char *path, ModelObject *model_object, bool binary); extern bool store_drc(const char *path, ModelObject *model_object, int bits, int speed);
extern bool store_drc(const char *path, Model *model, bool binary); extern bool store_drc(const char *path, Model *model, int bits, int speed);
}; // namespace Slic3r }; // namespace Slic3r

View File

@@ -529,6 +529,7 @@ static const FileWildcards file_wildcards_by_type[FT_SIZE] = {
/* FT_SVG */ { "SVG files"sv, { ".svg"sv } }, /* FT_SVG */ { "SVG files"sv, { ".svg"sv } },
/* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } }, /* FT_TEX */ { "Texture"sv, { ".png"sv, ".svg"sv } },
/* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } }, /* FT_SL1 */ { "Masked SLA files"sv, { ".sl1"sv, ".sl1s"sv } },
/* FT_DRC */ { "Draco files"sv, { ".drc"sv } },
}; };
// This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms. // This function produces a Win32 file dialog file template mask to be consumed by wxWidgets on all platforms.

View File

@@ -105,6 +105,8 @@ enum FileType
FT_SL1, FT_SL1,
FT_DRC,
FT_SIZE, FT_SIZE,
}; };

View File

@@ -855,6 +855,27 @@ void MenuFactory::append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu)
}, m_parent); }, m_parent);
} }
void MenuFactory::append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu)
{
append_menu_item(menu, wxID_ANY, _L("Export as one DRC") + dots, "",
[](wxCommandEvent&) { plater()->export_drc(false, true); }, "", nullptr,
[is_mulity_menu]() {
const Selection& selection = plater()->canvas3D()->get_selection();
if (is_mulity_menu)
return selection.is_multiple_full_instance() || selection.is_multiple_full_object();
else
return selection.is_single_full_instance() || selection.is_single_full_object();
}, m_parent);
if (!is_mulity_menu)
return;
append_menu_item(menu, wxID_ANY, _L("Export as DRCs") + dots, "",
[](wxCommandEvent&) { plater()->export_drc(false, true, true); }, "", nullptr,
[]() {
const Selection& selection = plater()->canvas3D()->get_selection();
return selection.is_multiple_full_instance() || selection.is_multiple_full_object();
}, m_parent);
}
void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu) void MenuFactory::append_menu_item_reload_from_disk(wxMenu* menu)
{ {
append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected parts from disk"), append_menu_item(menu, wxID_ANY, _L("Reload from disk"), _L("Reload the selected parts from disk"),
@@ -1269,6 +1290,7 @@ void MenuFactory::create_common_object_menu(wxMenu* menu)
// BBS // BBS
append_menu_item_reload_from_disk(menu); append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu); append_menu_item_export_stl(menu);
append_menu_item_export_drc(menu);
// "Scale to print volume" makes a sense just for whole object // "Scale to print volume" makes a sense just for whole object
append_menu_item_scale_selection_to_fit_print_volume(menu); append_menu_item_scale_selection_to_fit_print_volume(menu);
@@ -1348,6 +1370,7 @@ void MenuFactory::create_extra_object_menu()
append_menu_item_reload_from_disk(&m_object_menu); append_menu_item_reload_from_disk(&m_object_menu);
append_menu_item_replace_with_stl(&m_object_menu); append_menu_item_replace_with_stl(&m_object_menu);
append_menu_item_export_stl(&m_object_menu); append_menu_item_export_stl(&m_object_menu);
append_menu_item_export_drc(&m_object_menu);
} }
void MenuFactory::create_bbl_assemble_object_menu() void MenuFactory::create_bbl_assemble_object_menu()
@@ -1382,6 +1405,7 @@ void MenuFactory::create_part_menu()
append_menu_item_delete(menu); append_menu_item_delete(menu);
append_menu_item_reload_from_disk(menu); append_menu_item_reload_from_disk(menu);
append_menu_item_export_stl(menu); append_menu_item_export_stl(menu);
append_menu_item_export_drc(menu);
append_menu_item_fix_through_netfabb(menu); append_menu_item_fix_through_netfabb(menu);
append_menu_items_mirror(menu); append_menu_items_mirror(menu);
append_menu_item_merge_parts_to_single_part(menu); append_menu_item_merge_parts_to_single_part(menu);
@@ -1697,6 +1721,7 @@ wxMenu* MenuFactory::multi_selection_menu()
append_menu_item_change_filament(menu); append_menu_item_change_filament(menu);
menu->AppendSeparator(); menu->AppendSeparator();
append_menu_item_export_stl(menu, true); append_menu_item_export_stl(menu, true);
append_menu_item_export_drc(menu, true);
} }
else { else {
append_menu_item_center(menu); append_menu_item_center(menu);

View File

@@ -135,6 +135,7 @@ private:
wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu); wxMenuItem* append_menu_item_fix_through_netfabb(wxMenu* menu);
//wxMenuItem* append_menu_item_simplify(wxMenu* menu); //wxMenuItem* append_menu_item_simplify(wxMenu* menu);
void append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu = false); void append_menu_item_export_stl(wxMenu* menu, bool is_mulity_menu = false);
void append_menu_item_export_drc(wxMenu* menu, bool is_mulity_menu = false);
void append_menu_item_reload_from_disk(wxMenu* menu); void append_menu_item_reload_from_disk(wxMenu* menu);
void append_menu_item_replace_with_stl(wxMenu* menu); void append_menu_item_replace_with_stl(wxMenu* menu);
void append_menu_item_change_extruder(wxMenu* menu); void append_menu_item_change_extruder(wxMenu* menu);

View File

@@ -2403,6 +2403,12 @@ void MainFrame::init_menubar_as_editor()
append_menu_item(export_menu, wxID_ANY, _L("Export all objects as STLs") + dots, _L("Export all objects as STLs"), append_menu_item(export_menu, wxID_ANY, _L("Export all objects as STLs") + dots, _L("Export all objects as STLs"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(false, false, true); }, "menu_export_stl", nullptr, [this](wxCommandEvent&) { if (m_plater) m_plater->export_stl(false, false, true); }, "menu_export_stl", nullptr,
[this](){return can_export_model(); }, this); [this](){return can_export_model(); }, this);
append_menu_item(export_menu, wxID_ANY, _L("Export all objects as one DRC") + dots, _L("Export all objects as one DRC"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_drc(); }, "menu_export_stl", nullptr,
[this](){return can_export_model(); }, this);
append_menu_item(export_menu, wxID_ANY, _L("Export all objects as DRCs") + dots, _L("Export all objects as DRCs"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_drc(false, false, true); }, "menu_export_stl", nullptr,
[this](){return can_export_model(); }, this);
append_menu_item(export_menu, wxID_ANY, _L("Export Generic 3MF") + dots/* + "\tCtrl+G"*/, _L("Export 3mf file without using some 3mf-extensions"), append_menu_item(export_menu, wxID_ANY, _L("Export Generic 3MF") + dots/* + "\tCtrl+G"*/, _L("Export 3mf file without using some 3mf-extensions"),
[this](wxCommandEvent&) { if (m_plater) m_plater->export_core_3mf(); }, "menu_export_sliced_file", nullptr, [this](wxCommandEvent&) { if (m_plater) m_plater->export_core_3mf(); }, "menu_export_sliced_file", nullptr,
[this](){return can_export_model(); }, this); [this](){return can_export_model(); }, this);

View File

@@ -47,6 +47,7 @@
#include "libslic3r/libslic3r.h" #include "libslic3r/libslic3r.h"
#include "libslic3r/Format/STL.hpp" #include "libslic3r/Format/STL.hpp"
#include "libslic3r/Format/DRC.hpp"
#include "libslic3r/Format/STEP.hpp" #include "libslic3r/Format/STEP.hpp"
#include "libslic3r/Format/AMF.hpp" #include "libslic3r/Format/AMF.hpp"
//#include "libslic3r/Format/3mf.hpp" //#include "libslic3r/Format/3mf.hpp"
@@ -4766,6 +4767,7 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
wxString wildcard; wxString wildcard;
switch (file_type) { switch (file_type) {
case FT_STL: case FT_STL:
case FT_DRC:
case FT_AMF: case FT_AMF:
case FT_3MF: case FT_3MF:
case FT_GCODE: case FT_GCODE:
@@ -4787,6 +4789,12 @@ wxString Plater::priv::get_export_file(GUI::FileType file_type)
dlg_title = _L("Export STL file:"); dlg_title = _L("Export STL file:");
break; break;
} }
case FT_DRC:
{
output_file.replace_extension("drc");
dlg_title = _L("Export Draco file:");
break;
}
case FT_AMF: case FT_AMF:
{ {
// XXX: Problem on OS X with double extension? // XXX: Problem on OS X with double extension?
@@ -12135,6 +12143,225 @@ void Plater::export_stl(bool extended, bool selection_only, bool multi_stls)
} }
}*/ }*/
void Plater::export_drc(bool extended, bool selection_only, bool multi_drcs)
{
if (p->model.objects.empty()) { return; }
wxString path;
int bits = 16;
int speed = 0;
if (multi_drcs) {
wxDirDialog dlg(this, _L("Choose a directory"), from_u8(wxGetApp().app_config->get_last_dir()),
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST);
if (dlg.ShowModal() == wxID_OK) {
path = dlg.GetPath() + "/";
}
} else {
path = p->get_export_file(FT_DRC);
}
if (path.empty()) { return; }
const std::string path_u8 = into_u8(path);
wxBusyCursor wait;
const auto& selection = p->get_selection();
const auto obj_idx = selection.get_object_idx();
#if EXPORT_WITH_BOOLEAN
if (selection_only && (obj_idx == -1 || selection.is_wipe_tower()))
return;
#else
// BBS support selecting multiple objects
if (selection_only && selection.is_wipe_tower()) return;
// BBS
if (selection_only) {
// only support selection single full object and mulitiple full object
if (!selection.is_single_full_object() && !selection.is_multiple_full_object()) return;
}
// Following lambda generates a combined mesh for export with normals pointing outwards.
auto mesh_to_export_fff_no_boolean = [this](const ModelObject &mo, int instance_id) {
TriangleMesh mesh;
//Prusa export negative parts
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);
auto csgrange = range(csgmesh);
if (csg::is_all_positive(csgrange)) {
mesh = TriangleMesh{csg::csgmesh_merge_positive_parts(csgrange)};
} else if (std::get<2>(csg::check_csgmesh_booleans(csgrange)) == csgrange.end()) {
try {
auto cgalm = csg::perform_csgmesh_booleans(csgrange);
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();
for (const ModelInstance *i : mo.instances) {
TriangleMesh m = vols_mesh;
m.transform(i->get_matrix(), true);
mesh.merge(m);
}
} else if (0 <= instance_id && instance_id < int(mo.instances.size()))
mesh.transform(mo.instances[instance_id]->get_matrix(), true);
return mesh;
};
#endif
auto mesh_to_export_sla = [&, this](const ModelObject& mo, int instance_id) {
TriangleMesh mesh;
const SLAPrintObject *object = this->p->sla_print.get_print_object_by_model_object_id(mo.id());
if (auto m = object->get_mesh_to_print(); m.empty())
mesh = combine_mesh_fff(mo, instance_id, [this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); });
else {
const Transform3d mesh_trafo_inv = object->trafo().inverse();
const bool is_left_handed = object->is_left_handed();
auto pad_mesh = extended? object->pad_mesh() : TriangleMesh{};
pad_mesh.transform(mesh_trafo_inv);
auto supports_mesh = extended ? object->support_mesh() : TriangleMesh{};
supports_mesh.transform(mesh_trafo_inv);
const std::vector<SLAPrintObject::Instance>& obj_instances = object->instances();
for (const SLAPrintObject::Instance& obj_instance : obj_instances) {
auto it = std::find_if(object->model_object()->instances.begin(), object->model_object()->instances.end(),
[&obj_instance](const ModelInstance *mi) { return mi->id() == obj_instance.instance_id; });
assert(it != object->model_object()->instances.end());
if (it != object->model_object()->instances.end()) {
const bool one_inst_only = selection_only && ! selection.is_single_full_object();
const int instance_idx = it - object->model_object()->instances.begin();
const Transform3d& inst_transform = one_inst_only
? Transform3d::Identity()
: object->model_object()->instances[instance_idx]->get_transformation().get_matrix();
TriangleMesh inst_mesh;
if (!pad_mesh.empty()) {
TriangleMesh inst_pad_mesh = pad_mesh;
inst_pad_mesh.transform(inst_transform, is_left_handed);
inst_mesh.merge(inst_pad_mesh);
}
if (!supports_mesh.empty()) {
TriangleMesh inst_supports_mesh = supports_mesh;
inst_supports_mesh.transform(inst_transform, is_left_handed);
inst_mesh.merge(inst_supports_mesh);
}
TriangleMesh inst_object_mesh = object->get_mesh_to_print();
inst_object_mesh.transform(mesh_trafo_inv);
inst_object_mesh.transform(inst_transform, is_left_handed);
inst_mesh.merge(inst_object_mesh);
// ensure that the instance lays on the bed
inst_mesh.translate(0.0f, 0.0f, -inst_mesh.bounding_box().min.z());
// merge instance with global mesh
mesh.merge(inst_mesh);
if (one_inst_only)
break;
}
}
}
return mesh;
};
std::function<TriangleMesh(const ModelObject& mo, int instance_id)>
mesh_to_export;
if (p->printer_technology == ptFFF)
#if EXPORT_WITH_BOOLEAN
mesh_to_export = [this](const ModelObject& mo, int instance_id) {return Plater::combine_mesh_fff(mo, instance_id,
[this](const std::string& msg) {return get_notification_manager()->push_plater_error_notification(msg); }); };
#else
mesh_to_export = mesh_to_export_fff_no_boolean;
#endif
else
mesh_to_export = mesh_to_export_sla;
auto get_save_file = [](std::string const & dir, std::string const & name) {
auto path = dir + name + ".drc";
int n = 1;
while (boost::filesystem::exists(path))
path = dir + name + "(" + std::to_string(n++) + ").drc";
return path;
};
TriangleMesh mesh;
if (selection_only) {
if (selection.is_single_full_object()) {
const auto obj_idx = selection.get_object_idx();
const ModelObject* model_object = p->model.objects[obj_idx];
if (selection.get_mode() == Selection::Instance)
mesh = mesh_to_export(*model_object, (model_object->instances.size() > 1) ? -1 : selection.get_instance_idx());
else {
const GLVolume* volume = selection.get_first_volume();
mesh = model_object->volumes[volume->volume_idx()]->mesh();
mesh.transform(volume->get_volume_transformation().get_matrix(), true);
}
if (model_object->instances.size() == 1) mesh.translate(-model_object->origin_translation.cast<float>());
}
else if (selection.is_multiple_full_object() && !multi_drcs) {
const std::set<std::pair<int, int>>& instances_idxs = p->get_selection().get_selected_object_instances();
for (const std::pair<int, int>& i : instances_idxs) {
ModelObject* object = p->model.objects[i.first];
mesh.merge(mesh_to_export(*object, i.second));
}
}
else if (selection.is_multiple_full_object() && multi_drcs) {
const std::set<std::pair<int, int>> &instances_idxs = p->get_selection().get_selected_object_instances();
for (const std::pair<int, int> &i : instances_idxs) {
ModelObject *object = p->model.objects[i.first];
auto mesh = mesh_to_export(*object, i.second);
mesh.translate(-object->origin_translation.cast<float>());
Slic3r::store_drc(get_save_file(path_u8, object->name).c_str(), &mesh, bits, speed);
}
return;
}
}
else if (!multi_drcs) {
for (const ModelObject* o : p->model.objects) {
mesh.merge(mesh_to_export(*o, -1));
}
} else {
for (const ModelObject* o : p->model.objects) {
auto mesh = mesh_to_export(*o, -1);
mesh.translate(-o->origin_translation.cast<float>());
Slic3r::store_drc(get_save_file(path_u8, o->name).c_str(), &mesh, bits, speed);
}
return;
}
Slic3r::store_drc(path_u8.c_str(), &mesh, bits, speed);
}
namespace { namespace {
std::string get_file_name(const std::string &file_path) std::string get_file_name(const std::string &file_path)
{ {

View File

@@ -414,6 +414,7 @@ public:
void export_core_3mf(); void export_core_3mf();
static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {}); static TriangleMesh combine_mesh_fff(const ModelObject& mo, int instance_id, std::function<void(const std::string&)> notify_func = {});
void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false); void export_stl(bool extended = false, bool selection_only = false, bool multi_stls = false);
void export_drc(bool extended = false, bool selection_only = false, bool multi_drcs = false);
//BBS: remove amf //BBS: remove amf
//void export_amf(); //void export_amf();
//BBS add extra param for exporting 3mf silence //BBS add extra param for exporting 3mf silence