From 31085fb1d70510e3d73c64c0b7fc7a394b6e6f72 Mon Sep 17 00:00:00 2001 From: bubnikv Date: Wed, 2 Aug 2017 16:05:18 +0200 Subject: [PATCH] Ported some ModelObject methods from Perl to C++. Added some utility functions to TriangleMesh, thanks to @alexrj Some porting to C++ based on work by @alexrj. --- lib/Slic3r/Model.pm | 77 ------------------- lib/Slic3r/Print.pm | 1 + xs/src/libslic3r/Model.cpp | 121 +++++++++++++++++++++++++++++- xs/src/libslic3r/Model.hpp | 13 +++- xs/src/libslic3r/TriangleMesh.cpp | 41 ++++++++++ xs/src/libslic3r/TriangleMesh.hpp | 3 + xs/xsp/Model.xsp | 16 ++++ 7 files changed, 191 insertions(+), 81 deletions(-) diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 7a2767885..8028a44f6 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -1,27 +1,8 @@ # extends C++ class Slic3r::Model package Slic3r::Model; -use File::Basename qw(basename); use List::Util qw(first max any); -sub read_from_file { - my ($class, $input_file, $add_default_instances) = @_; - $add_default_instances //= 1; - - my $model = $input_file =~ /\.[sS][tT][lL]$/ ? Slic3r::Model->load_stl(Slic3r::encode_path($input_file), basename($input_file)) - : $input_file =~ /\.[oO][bB][jJ]$/ ? Slic3r::Model->load_obj(Slic3r::encode_path($input_file), basename($input_file)) - : $input_file =~ /\.[aA][mM][fF](\.[xX][mM][lL])?$/ ? Slic3r::Model->load_amf(Slic3r::encode_path($input_file)) - : $input_file =~ /\.[pP][rR][uU][sS][aA]$/ ? Slic3r::Model->load_prus(Slic3r::encode_path($input_file)) - : die "Input file must have .stl, .obj or .amf(.xml) extension\n"; - - die "The supplied file couldn't be read because it's empty.\n" - if $model->objects_count == 0; - - $_->set_input_file($input_file) for @{$model->objects}; - $model->add_default_instances if $add_default_instances; - return $model; -} - sub merge { my $class = shift; my @models = @_; @@ -70,38 +51,6 @@ sub set_material { return $material; } -sub print_info { - my $self = shift; - $_->print_info for @{$self->objects}; -} - -sub looks_like_multipart_object { - my ($self) = @_; - - return 0 if $self->objects_count == 1; - return 0 if any { $_->volumes_count > 1 } @{$self->objects}; - return 0 if any { @{$_->config->get_keys} > 1 } @{$self->objects}; - - my %heights = map { $_ => 1 } map $_->mesh->bounding_box->z_min, map @{$_->volumes}, @{$self->objects}; - return scalar(keys %heights) > 1; -} - -sub convert_multipart_object { - my ($self) = @_; - - my @objects = @{$self->objects}; - my $object = $self->add_object( - input_file => $objects[0]->input_file, - ); - foreach my $v (map @{$_->volumes}, @objects) { - my $volume = $object->add_volume($v); - $volume->set_name($v->object->name); - } - $object->add_instance($_) for map @{$_->instances}, @objects; - - $self->delete_object($_) for reverse 0..($self->objects_count-2); -} - # Extends C++ class Slic3r::ModelMaterial package Slic3r::Model::Material; @@ -113,7 +62,6 @@ sub apply { # Extends C++ class Slic3r::ModelObject package Slic3r::Model::Object; -use File::Basename qw(basename); use List::Util qw(first sum); sub add_volume { @@ -193,29 +141,4 @@ sub mesh_stats { return $self->volumes->[0]->mesh->stats; } -sub print_info { - my $self = shift; - - printf "Info about %s:\n", basename($self->input_file); - printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->raw_mesh->bounding_box->size}; - if (my $stats = $self->mesh_stats) { - printf " number of facets: %d\n", $stats->{number_of_facets}; - printf " number of shells: %d\n", $stats->{number_of_parts}; - printf " volume: %.3f\n", $stats->{volume}; - if ($self->needed_repair) { - printf " needed repair: yes\n"; - printf " degenerate facets: %d\n", $stats->{degenerate_facets}; - printf " edges fixed: %d\n", $stats->{edges_fixed}; - printf " facets removed: %d\n", $stats->{facets_removed}; - printf " facets added: %d\n", $stats->{facets_added}; - printf " facets reversed: %d\n", $stats->{facets_reversed}; - printf " backwards edges: %d\n", $stats->{backwards_edges}; - } else { - printf " needed repair: no\n"; - } - } else { - printf " number of facets: %d\n", scalar(map @{$_->facets}, grep !$_->modifier, @{$self->volumes}); - } -} - 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 038af32e7..0c0bc4b0e 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -64,6 +64,7 @@ sub process { Slic3r::trace(3, "Slicing process finished.") } +# G-code export process, running at a background thread. sub export_gcode { my $self = shift; my %params = @_; diff --git a/xs/src/libslic3r/Model.cpp b/xs/src/libslic3r/Model.cpp index 028513d59..5d0de1576 100644 --- a/xs/src/libslic3r/Model.cpp +++ b/xs/src/libslic3r/Model.cpp @@ -1,8 +1,16 @@ #include "Model.hpp" #include "Geometry.hpp" +#include "Format/AMF.hpp" +#include "Format/OBJ.hpp" +#include "Format/PRUS.hpp" +#include "Format/STL.hpp" + #include +#include +#include + namespace Slic3r { Model::Model(const Model &other) @@ -28,10 +36,36 @@ void Model::swap(Model &other) std::swap(this->objects, other.objects); } -Model::~Model() +Model Model::read_from_file(const std::string &input_file, bool add_default_instances) { - this->clear_objects(); - this->clear_materials(); + Model model; + + bool result = false; + if (boost::algorithm::iends_with(input_file, ".stl")) + result = load_stl(input_file.c_str(), &model); + else if (boost::algorithm::iends_with(input_file, ".obj")) + result = load_obj(input_file.c_str(), &model); + else if (boost::algorithm::iends_with(input_file, ".amf") || + boost::algorithm::iends_with(input_file, ".amf.xml")) + result = load_amf(input_file.c_str(), &model); + else if (boost::algorithm::iends_with(input_file, ".prusa")) + result = load_prus(input_file.c_str(), &model); + else + throw std::runtime_error("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension."); + + if (! result) + throw std::runtime_error("Loading of a model file failed."); + + if (model.objects.empty()) + throw std::runtime_error("The supplied file couldn't be read because it's empty"); + + for (ModelObject *o : model.objects) + o->input_file = input_file; + + if (add_default_instances) + model.add_default_instances(); + + return model; } ModelObject* Model::add_object() @@ -279,6 +313,45 @@ void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist) } } +bool Model::looks_like_multipart_object() const +{ + if (this->objects.size() <= 1) + return false; + double zmin = std::numeric_limits::max(); + for (const ModelObject *obj : this->objects) { + if (obj->volumes.size() > 1 || obj->config.keys().size() > 1) + return false; + for (const ModelVolume *vol : obj->volumes) { + double zmin_this = vol->mesh.bounding_box().min.z; + if (zmin == std::numeric_limits::max()) + zmin = zmin_this; + else if (std::abs(zmin - zmin_this) > EPSILON) + // The volumes don't share zmin. + return true; + } + } + return false; +} + +void Model::convert_multipart_object() +{ + if (this->objects.empty()) + return; + + ModelObject* object = this->add_object(); + object->input_file = this->objects.front()->input_file; + + for (const ModelObject* o : this->objects) + for (const ModelVolume* v : o->volumes) + object->add_volume(*v)->name = o->name; + + for (const ModelInstance* i : this->objects.front()->instances) + object->add_instance(*i); + + while (this->objects.size() > 1) + this->delete_object(0); +} + ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) : name(other.name), input_file(other.input_file), @@ -662,6 +735,48 @@ void ModelObject::split(ModelObjectPtrs* new_objects) return; } +void ModelObject::print_info() const +{ + using namespace std; + cout << fixed; + cout << "[" << boost::filesystem::path(this->input_file).filename().string() << "]" << endl; + + TriangleMesh mesh = this->raw_mesh(); + mesh.check_topology(); + BoundingBoxf3 bb = mesh.bounding_box(); + Sizef3 size = bb.size(); + cout << "size_x = " << size.x << endl; + cout << "size_y = " << size.y << endl; + cout << "size_z = " << size.z << endl; + cout << "min_x = " << bb.min.x << endl; + cout << "min_y = " << bb.min.y << endl; + cout << "min_z = " << bb.min.z << endl; + cout << "max_x = " << bb.max.x << endl; + cout << "max_y = " << bb.max.y << endl; + cout << "max_z = " << bb.max.z << endl; + cout << "number_of_facets = " << mesh.stl.stats.number_of_facets << endl; + cout << "manifold = " << (mesh.is_manifold() ? "yes" : "no") << endl; + + mesh.repair(); // this calculates number_of_parts + if (mesh.needed_repair()) { + mesh.repair(); + if (mesh.stl.stats.degenerate_facets > 0) + cout << "degenerate_facets = " << mesh.stl.stats.degenerate_facets << endl; + if (mesh.stl.stats.edges_fixed > 0) + cout << "edges_fixed = " << mesh.stl.stats.edges_fixed << endl; + if (mesh.stl.stats.facets_removed > 0) + cout << "facets_removed = " << mesh.stl.stats.facets_removed << endl; + if (mesh.stl.stats.facets_added > 0) + cout << "facets_added = " << mesh.stl.stats.facets_added << endl; + if (mesh.stl.stats.facets_reversed > 0) + cout << "facets_reversed = " << mesh.stl.stats.facets_reversed << endl; + if (mesh.stl.stats.backwards_edges > 0) + cout << "backwards_edges = " << mesh.stl.stats.backwards_edges << endl; + } + cout << "number_of_parts = " << mesh.stl.stats.number_of_parts << endl; + cout << "volume = " << mesh.volume() << endl; +} + void ModelVolume::material_id(t_model_material_id material_id) { this->_material_id = material_id; diff --git a/xs/src/libslic3r/Model.hpp b/xs/src/libslic3r/Model.hpp index 5f3b0eb01..d025f83c9 100644 --- a/xs/src/libslic3r/Model.hpp +++ b/xs/src/libslic3r/Model.hpp @@ -125,6 +125,9 @@ public: bool needed_repair() const; void cut(coordf_t z, Model* model) const; void split(ModelObjectPtrs* new_objects); + + // Print object statistics to console. + void print_info() const; private: ModelObject(Model *model) : m_model(model), m_bounding_box_valid(false), layer_height_profile_valid(false) {} @@ -232,7 +235,10 @@ public: Model(const Model &other); Model& operator= (Model other); void swap(Model &other); - ~Model(); + ~Model() { this->clear_objects(); this->clear_materials(); } + + static Model read_from_file(const std::string &input_file, bool add_default_instances = true); + ModelObject* add_object(); ModelObject* add_object(const char *name, const char *path, const TriangleMesh &mesh); ModelObject* add_object(const char *name, const char *path, TriangleMesh &&mesh); @@ -259,6 +265,11 @@ public: void duplicate(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects(size_t copies_num, coordf_t dist, const BoundingBoxf* bb = NULL); void duplicate_objects_grid(size_t x, size_t y, coordf_t dist); + + bool looks_like_multipart_object() const; + void convert_multipart_object(); + + void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); } }; } diff --git a/xs/src/libslic3r/TriangleMesh.cpp b/xs/src/libslic3r/TriangleMesh.cpp index ee1dc7315..f2f05a841 100644 --- a/xs/src/libslic3r/TriangleMesh.cpp +++ b/xs/src/libslic3r/TriangleMesh.cpp @@ -215,6 +215,47 @@ TriangleMesh::repair() { BOOST_LOG_TRIVIAL(debug) << "TriangleMesh::repair() finished"; } + +float TriangleMesh::volume() +{ + if (this->stl.stats.volume == -1) + stl_calculate_volume(&this->stl); + return this->stl.stats.volume; +} + +void TriangleMesh::check_topology() +{ + // checking exact + stl_check_facets_exact(&stl); + stl.stats.facets_w_1_bad_edge = (stl.stats.connected_facets_2_edge - stl.stats.connected_facets_3_edge); + stl.stats.facets_w_2_bad_edge = (stl.stats.connected_facets_1_edge - stl.stats.connected_facets_2_edge); + stl.stats.facets_w_3_bad_edge = (stl.stats.number_of_facets - stl.stats.connected_facets_1_edge); + + // checking nearby + //int last_edges_fixed = 0; + float tolerance = stl.stats.shortest_edge; + float increment = stl.stats.bounding_diameter / 10000.0; + int iterations = 2; + if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + for (int i = 0; i < iterations; i++) { + if (stl.stats.connected_facets_3_edge < stl.stats.number_of_facets) { + //printf("Checking nearby. Tolerance= %f Iteration=%d of %d...", tolerance, i + 1, iterations); + stl_check_facets_nearby(&stl, tolerance); + //printf(" Fixed %d edges.\n", stl.stats.edges_fixed - last_edges_fixed); + //last_edges_fixed = stl.stats.edges_fixed; + tolerance += increment; + } else { + break; + } + } + } +} + +bool TriangleMesh::is_manifold() const +{ + return this->stl.stats.connected_facets_3_edge == this->stl.stats.number_of_facets; +} + void TriangleMesh::reset_repair_stats() { this->stl.stats.degenerate_facets = 0; diff --git a/xs/src/libslic3r/TriangleMesh.hpp b/xs/src/libslic3r/TriangleMesh.hpp index 8072059cf..b28f3a310 100644 --- a/xs/src/libslic3r/TriangleMesh.hpp +++ b/xs/src/libslic3r/TriangleMesh.hpp @@ -32,6 +32,9 @@ public: void write_ascii(const char* output_file); void write_binary(const char* output_file); void repair(); + float volume(); + void check_topology(); + bool is_manifold() const; void WriteOBJFile(char* output_file); void scale(float factor); void scale(const Pointf3 &versor); diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 855d40d32..c96c56386 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -15,6 +15,15 @@ Model(); ~Model(); + %name{read_from_file} Model(std::string input_file, bool add_default_instances = true) + %code%{ + try { + RETVAL = new Model(Model::read_from_file(input_file, add_default_instances)); + } catch (std::exception& e) { + croak("Error while opening %s: %s\n", input_file.c_str(), e.what()); + } + %}; + Clone clone() %code%{ RETVAL = THIS; %}; @@ -73,6 +82,11 @@ void duplicate_objects(unsigned int copies_num, double dist, BoundingBoxf* bb = NULL); void duplicate_objects_grid(unsigned int x, unsigned int y, double dist); + bool looks_like_multipart_object() const; + void convert_multipart_object(); + + void print_info() const; + bool store_stl(char *path, bool binary) %code%{ TriangleMesh mesh = THIS->mesh(); RETVAL = Slic3r::store_stl(path, &mesh, binary); %}; bool store_amf(char *path) @@ -272,6 +286,8 @@ ModelMaterial::attributes() RETVAL = new ModelObjectPtrs(); // leak? THIS->split(RETVAL); %}; + + void print_info() const; };