1 #include "Exception.hpp"
2 #include "Model.hpp"
3 #include "ModelArrange.hpp"
4 #include "Geometry.hpp"
5 #include "MTUtils.hpp"
6 #include "TriangleSelector.hpp"
7
8 #include "Format/AMF.hpp"
9 #include "Format/OBJ.hpp"
10 #include "Format/PRUS.hpp"
11 #include "Format/STL.hpp"
12 #include "Format/3mf.hpp"
13
14 #include <float.h>
15
16 #include <boost/algorithm/string/predicate.hpp>
17 #include <boost/algorithm/string/replace.hpp>
18 #include <boost/filesystem.hpp>
19 #include <boost/log/trivial.hpp>
20 #include <boost/nowide/iostream.hpp>
21
22 #include "SVG.hpp"
23 #include <Eigen/Dense>
24 #include "GCodeWriter.hpp"
25
26 namespace Slic3r {
27
assign_copy(const Model & rhs)28 Model& Model::assign_copy(const Model &rhs)
29 {
30 this->copy_id(rhs);
31 // copy materials
32 this->clear_materials();
33 this->materials = rhs.materials;
34 for (std::pair<const t_model_material_id, ModelMaterial*> &m : this->materials) {
35 // Copy including the ID and m_model.
36 m.second = new ModelMaterial(*m.second);
37 m.second->set_model(this);
38 }
39 // copy objects
40 this->clear_objects();
41 this->objects.reserve(rhs.objects.size());
42 for (const ModelObject *model_object : rhs.objects) {
43 // Copy including the ID, leave ID set to invalid (zero).
44 auto mo = ModelObject::new_copy(*model_object);
45 mo->set_model(this);
46 this->objects.emplace_back(mo);
47 }
48
49 // copy custom code per height
50 this->custom_gcode_per_print_z = rhs.custom_gcode_per_print_z;
51 return *this;
52 }
53
assign_copy(Model && rhs)54 Model& Model::assign_copy(Model &&rhs)
55 {
56 this->copy_id(rhs);
57 // Move materials, adjust the parent pointer.
58 this->clear_materials();
59 this->materials = std::move(rhs.materials);
60 for (std::pair<const t_model_material_id, ModelMaterial*> &m : this->materials)
61 m.second->set_model(this);
62 rhs.materials.clear();
63 // Move objects, adjust the parent pointer.
64 this->clear_objects();
65 this->objects = std::move(rhs.objects);
66 for (ModelObject *model_object : this->objects)
67 model_object->set_model(this);
68 rhs.objects.clear();
69
70 // copy custom code per height
71 this->custom_gcode_per_print_z = std::move(rhs.custom_gcode_per_print_z);
72 return *this;
73 }
74
assign_new_unique_ids_recursive()75 void Model::assign_new_unique_ids_recursive()
76 {
77 this->set_new_unique_id();
78 for (std::pair<const t_model_material_id, ModelMaterial*> &m : this->materials)
79 m.second->assign_new_unique_ids_recursive();
80 for (ModelObject *model_object : this->objects)
81 model_object->assign_new_unique_ids_recursive();
82 }
83
update_links_bottom_up_recursive()84 void Model::update_links_bottom_up_recursive()
85 {
86 for (std::pair<const t_model_material_id, ModelMaterial*> &kvp : this->materials)
87 kvp.second->set_model(this);
88 for (ModelObject *model_object : this->objects) {
89 model_object->set_model(this);
90 for (ModelInstance *model_instance : model_object->instances)
91 model_instance->set_model_object(model_object);
92 for (ModelVolume *model_volume : model_object->volumes)
93 model_volume->set_model_object(model_object);
94 }
95 }
96
97 // Loading model from a file, it may be a simple geometry file as STL or OBJ, however it may be a project file as well.
read_from_file(const std::string & input_file,DynamicPrintConfig * config,ConfigSubstitutionContext * config_substitutions,LoadAttributes options)98 Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
99 {
100 Model model;
101
102 DynamicPrintConfig temp_config;
103 ConfigSubstitutionContext temp_config_substitutions_context(ForwardCompatibilitySubstitutionRule::EnableSilent);
104 if (config == nullptr)
105 config = &temp_config;
106 if (config_substitutions == nullptr)
107 config_substitutions = &temp_config_substitutions_context;
108
109 bool result = false;
110 if (boost::algorithm::iends_with(input_file, ".stl"))
111 result = load_stl(input_file.c_str(), &model);
112 else if (boost::algorithm::iends_with(input_file, ".obj"))
113 result = load_obj(input_file.c_str(), &model);
114 else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
115 result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
116 else if (boost::algorithm::iends_with(input_file, ".3mf"))
117 //FIXME options & LoadAttribute::CheckVersion ?
118 result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
119 else if (boost::algorithm::iends_with(input_file, ".prusa"))
120 result = load_prus(input_file.c_str(), &model);
121 else
122 throw Slic3r::RuntimeError("Unknown file format. Input file must have .stl, .obj, .amf(.xml) or .prusa extension.");
123
124 if (! result)
125 throw Slic3r::RuntimeError("Loading of a model file failed.");
126
127 if (model.objects.empty())
128 throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
129
130 for (ModelObject *o : model.objects)
131 o->input_file = input_file;
132
133 if (options & LoadAttribute::AddDefaultInstances)
134 model.add_default_instances();
135
136 CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
137 CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
138
139 sort_remove_duplicates(config_substitutions->substitutions);
140 return model;
141 }
142
143 // Loading model from a file (3MF or AMF), not from a simple geometry file (STL or OBJ).
read_from_archive(const std::string & input_file,DynamicPrintConfig * config,ConfigSubstitutionContext * config_substitutions,LoadAttributes options)144 Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig* config, ConfigSubstitutionContext* config_substitutions, LoadAttributes options)
145 {
146 assert(config != nullptr);
147 assert(config_substitutions != nullptr);
148
149 Model model;
150
151 bool result = false;
152 if (boost::algorithm::iends_with(input_file, ".3mf"))
153 result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion);
154 else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
155 result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
156 else
157 throw Slic3r::RuntimeError("Unknown file format. Input file must have .3mf or .zip.amf extension.");
158
159 if (!result)
160 throw Slic3r::RuntimeError("Loading of a model file failed.");
161
162 if (model.objects.empty())
163 throw Slic3r::RuntimeError("The supplied file couldn't be read because it's empty");
164
165 for (ModelObject *o : model.objects)
166 {
167 // if (boost::algorithm::iends_with(input_file, ".zip.amf"))
168 // {
169 // // we remove the .zip part of the extension to avoid it be added to filenames when exporting
170 // o->input_file = boost::ireplace_last_copy(input_file, ".zip.", ".");
171 // }
172 // else
173 o->input_file = input_file;
174 }
175
176 if (options & LoadAttribute::AddDefaultInstances)
177 model.add_default_instances();
178
179 CustomGCode::update_custom_gcode_per_print_z_from_config(model.custom_gcode_per_print_z, config);
180 CustomGCode::check_mode_for_custom_gcode_per_print_z(model.custom_gcode_per_print_z);
181
182 return model;
183 }
184
add_object()185 ModelObject* Model::add_object()
186 {
187 this->objects.emplace_back(new ModelObject(this));
188 return this->objects.back();
189 }
190
add_object(const char * name,const char * path,const TriangleMesh & mesh)191 ModelObject* Model::add_object(const char *name, const char *path, const TriangleMesh &mesh)
192 {
193 ModelObject* new_object = new ModelObject(this);
194 this->objects.push_back(new_object);
195 new_object->name = name;
196 new_object->input_file = path;
197 ModelVolume *new_volume = new_object->add_volume(mesh);
198 new_volume->name = name;
199 new_volume->source.input_file = path;
200 new_volume->source.object_idx = (int)this->objects.size() - 1;
201 new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
202 new_object->invalidate_bounding_box();
203 return new_object;
204 }
205
add_object(const char * name,const char * path,TriangleMesh && mesh)206 ModelObject* Model::add_object(const char *name, const char *path, TriangleMesh &&mesh)
207 {
208 ModelObject* new_object = new ModelObject(this);
209 this->objects.push_back(new_object);
210 new_object->name = name;
211 new_object->input_file = path;
212 ModelVolume *new_volume = new_object->add_volume(std::move(mesh));
213 new_volume->name = name;
214 new_volume->source.input_file = path;
215 new_volume->source.object_idx = (int)this->objects.size() - 1;
216 new_volume->source.volume_idx = (int)new_object->volumes.size() - 1;
217 new_object->invalidate_bounding_box();
218 return new_object;
219 }
220
add_object(const ModelObject & other)221 ModelObject* Model::add_object(const ModelObject &other)
222 {
223 ModelObject* new_object = ModelObject::new_clone(other);
224 new_object->set_model(this);
225 this->objects.push_back(new_object);
226 return new_object;
227 }
228
delete_object(size_t idx)229 void Model::delete_object(size_t idx)
230 {
231 ModelObjectPtrs::iterator i = this->objects.begin() + idx;
232 delete *i;
233 this->objects.erase(i);
234 }
235
delete_object(ModelObject * object)236 bool Model::delete_object(ModelObject* object)
237 {
238 if (object != nullptr) {
239 size_t idx = 0;
240 for (ModelObject *model_object : objects) {
241 if (model_object == object) {
242 delete model_object;
243 objects.erase(objects.begin() + idx);
244 return true;
245 }
246 ++ idx;
247 }
248 }
249 return false;
250 }
251
delete_object(ObjectID id)252 bool Model::delete_object(ObjectID id)
253 {
254 if (id.id != 0) {
255 size_t idx = 0;
256 for (ModelObject *model_object : objects) {
257 if (model_object->id() == id) {
258 delete model_object;
259 objects.erase(objects.begin() + idx);
260 return true;
261 }
262 ++ idx;
263 }
264 }
265 return false;
266 }
267
clear_objects()268 void Model::clear_objects()
269 {
270 for (ModelObject *o : this->objects)
271 delete o;
272 this->objects.clear();
273 }
274
delete_material(t_model_material_id material_id)275 void Model::delete_material(t_model_material_id material_id)
276 {
277 ModelMaterialMap::iterator i = this->materials.find(material_id);
278 if (i != this->materials.end()) {
279 delete i->second;
280 this->materials.erase(i);
281 }
282 }
283
clear_materials()284 void Model::clear_materials()
285 {
286 for (auto &m : this->materials)
287 delete m.second;
288 this->materials.clear();
289 }
290
add_material(t_model_material_id material_id)291 ModelMaterial* Model::add_material(t_model_material_id material_id)
292 {
293 assert(! material_id.empty());
294 ModelMaterial* material = this->get_material(material_id);
295 if (material == nullptr)
296 material = this->materials[material_id] = new ModelMaterial(this);
297 return material;
298 }
299
add_material(t_model_material_id material_id,const ModelMaterial & other)300 ModelMaterial* Model::add_material(t_model_material_id material_id, const ModelMaterial &other)
301 {
302 assert(! material_id.empty());
303 // delete existing material if any
304 ModelMaterial* material = this->get_material(material_id);
305 delete material;
306 // set new material
307 material = new ModelMaterial(other);
308 material->set_model(this);
309 this->materials[material_id] = material;
310 return material;
311 }
312
313 // makes sure all objects have at least one instance
add_default_instances()314 bool Model::add_default_instances()
315 {
316 // apply a default position to all objects not having one
317 for (ModelObject *o : this->objects)
318 if (o->instances.empty())
319 o->add_instance();
320 return true;
321 }
322
323 // this returns the bounding box of the *transformed* instances
bounding_box() const324 BoundingBoxf3 Model::bounding_box() const
325 {
326 BoundingBoxf3 bb;
327 for (ModelObject *o : this->objects)
328 bb.merge(o->bounding_box());
329 return bb;
330 }
331
update_print_volume_state(const BoundingBoxf3 & print_volume)332 unsigned int Model::update_print_volume_state(const BoundingBoxf3 &print_volume)
333 {
334 unsigned int num_printable = 0;
335 for (ModelObject *model_object : this->objects)
336 num_printable += model_object->check_instances_print_volume_state(print_volume);
337 return num_printable;
338 }
339
center_instances_around_point(const Vec2d & point)340 bool Model::center_instances_around_point(const Vec2d &point)
341 {
342 BoundingBoxf3 bb;
343 for (ModelObject *o : this->objects)
344 for (size_t i = 0; i < o->instances.size(); ++ i)
345 bb.merge(o->instance_bounding_box(i, false));
346
347 Vec2d shift2 = point - to_2d(bb.center());
348 if (std::abs(shift2(0)) < EPSILON && std::abs(shift2(1)) < EPSILON)
349 // No significant shift, don't do anything.
350 return false;
351
352 Vec3d shift3 = Vec3d(shift2(0), shift2(1), 0.0);
353 for (ModelObject *o : this->objects) {
354 for (ModelInstance *i : o->instances)
355 i->set_offset(i->get_offset() + shift3);
356 o->invalidate_bounding_box();
357 }
358 return true;
359 }
360
361 // flattens everything to a single mesh
mesh() const362 TriangleMesh Model::mesh() const
363 {
364 TriangleMesh mesh;
365 for (const ModelObject *o : this->objects)
366 mesh.merge(o->mesh());
367 return mesh;
368 }
369
duplicate_objects_grid(size_t x,size_t y,coordf_t dist)370 void Model::duplicate_objects_grid(size_t x, size_t y, coordf_t dist)
371 {
372 if (this->objects.size() > 1) throw "Grid duplication is not supported with multiple objects";
373 if (this->objects.empty()) throw "No objects!";
374
375 ModelObject* object = this->objects.front();
376 object->clear_instances();
377
378 Vec3d ext_size = object->bounding_box().size() + dist * Vec3d::Ones();
379
380 for (size_t x_copy = 1; x_copy <= x; ++x_copy) {
381 for (size_t y_copy = 1; y_copy <= y; ++y_copy) {
382 ModelInstance* instance = object->add_instance();
383 instance->set_offset(Vec3d(ext_size(0) * (double)(x_copy - 1), ext_size(1) * (double)(y_copy - 1), 0.0));
384 }
385 }
386 }
387
looks_like_multipart_object() const388 bool Model::looks_like_multipart_object() const
389 {
390 if (this->objects.size() <= 1)
391 return false;
392 double zmin = std::numeric_limits<double>::max();
393 for (const ModelObject *obj : this->objects) {
394 if (obj->volumes.size() > 1 || obj->config.keys().size() > 1)
395 return false;
396 for (const ModelVolume *vol : obj->volumes) {
397 double zmin_this = vol->mesh().bounding_box().min(2);
398 if (zmin == std::numeric_limits<double>::max())
399 zmin = zmin_this;
400 else if (std::abs(zmin - zmin_this) > EPSILON)
401 // The volumes don't share zmin.
402 return true;
403 }
404 }
405 return false;
406 }
407
408 // Generate next extruder ID string, in the range of (1, max_extruders).
auto_extruder_id(unsigned int max_extruders,unsigned int & cntr)409 static inline int auto_extruder_id(unsigned int max_extruders, unsigned int &cntr)
410 {
411 int out = ++ cntr;
412 if (cntr == max_extruders)
413 cntr = 0;
414 return out;
415 }
416
convert_multipart_object(unsigned int max_extruders)417 void Model::convert_multipart_object(unsigned int max_extruders)
418 {
419 assert(this->objects.size() >= 2);
420 if (this->objects.size() < 2)
421 return;
422
423 ModelObject* object = new ModelObject(this);
424 object->input_file = this->objects.front()->input_file;
425 object->name = this->objects.front()->name;
426 //FIXME copy the config etc?
427
428 unsigned int extruder_counter = 0;
429 for (const ModelObject* o : this->objects)
430 for (const ModelVolume* v : o->volumes) {
431 // If there are more than one object, put all volumes together
432 // Each object may contain any number of volumes and instances
433 // The volumes transformations are relative to the object containing them...
434 Geometry::Transformation trafo_volume = v->get_transformation();
435 // Revert the centering operation.
436 trafo_volume.set_offset(trafo_volume.get_offset() - o->origin_translation);
437 int counter = 1;
438 auto copy_volume = [o, max_extruders, &counter, &extruder_counter](ModelVolume *new_v) {
439 assert(new_v != nullptr);
440 new_v->name = o->name + "_" + std::to_string(counter++);
441 new_v->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
442 return new_v;
443 };
444 if (o->instances.empty()) {
445 copy_volume(object->add_volume(*v))->set_transformation(trafo_volume);
446 } else {
447 for (const ModelInstance* i : o->instances)
448 // ...so, transform everything to a common reference system (world)
449 copy_volume(object->add_volume(*v))->set_transformation(i->get_transformation() * trafo_volume);
450 }
451 }
452
453 // commented-out to fix #2868
454 // object->add_instance();
455 // object->instances[0]->set_offset(object->raw_mesh_bounding_box().center());
456
457 this->clear_objects();
458 this->objects.push_back(object);
459 }
460
looks_like_imperial_units() const461 bool Model::looks_like_imperial_units() const
462 {
463 if (this->objects.size() == 0)
464 return false;
465
466 for (ModelObject* obj : this->objects)
467 if (obj->get_object_stl_stats().volume < 9.0) // 9 = 3*3*3;
468 return true;
469
470 return false;
471 }
472
convert_from_imperial_units(bool only_small_volumes)473 void Model::convert_from_imperial_units(bool only_small_volumes)
474 {
475 double in_to_mm = 25.4;
476 for (ModelObject* obj : this->objects)
477 if (! only_small_volumes || obj->get_object_stl_stats().volume < 9.0) { // 9 = 3*3*3;
478 obj->scale_mesh_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
479 for (ModelVolume* v : obj->volumes)
480 v->source.is_converted_from_inches = true;
481 }
482 }
483
adjust_min_z()484 void Model::adjust_min_z()
485 {
486 if (objects.empty())
487 return;
488
489 if (bounding_box().min(2) < 0.0)
490 {
491 for (ModelObject* obj : objects)
492 {
493 if (obj != nullptr)
494 {
495 coordf_t obj_min_z = obj->bounding_box().min(2);
496 if (obj_min_z < 0.0)
497 obj->translate_instances(Vec3d(0.0, 0.0, -obj_min_z));
498 }
499 }
500 }
501 }
502
503 // Propose a filename including path derived from the ModelObject's input path.
504 // If object's name is filled in, use the object name, otherwise use the input name.
propose_export_file_name_and_path() const505 std::string Model::propose_export_file_name_and_path() const
506 {
507 std::string input_file;
508 for (const ModelObject *model_object : this->objects)
509 for (ModelInstance *model_instance : model_object->instances)
510 if (model_instance->is_printable()) {
511 input_file = model_object->get_export_filename();
512
513 if (!input_file.empty())
514 goto end;
515 // Other instances will produce the same name, skip them.
516 break;
517 }
518 end:
519 return input_file;
520 }
521
propose_export_file_name_and_path(const std::string & new_extension) const522 std::string Model::propose_export_file_name_and_path(const std::string &new_extension) const
523 {
524 return boost::filesystem::path(this->propose_export_file_name_and_path()).replace_extension(new_extension).string();
525 }
526
~ModelObject()527 ModelObject::~ModelObject()
528 {
529 this->clear_volumes();
530 this->clear_instances();
531 }
532
533 // maintains the m_model pointer
assign_copy(const ModelObject & rhs)534 ModelObject& ModelObject::assign_copy(const ModelObject &rhs)
535 {
536 assert(this->id().invalid() || this->id() == rhs.id());
537 assert(this->config.id().invalid() || this->config.id() == rhs.config.id());
538 this->copy_id(rhs);
539
540 this->name = rhs.name;
541 this->input_file = rhs.input_file;
542 // Copies the config's ID
543 this->config = rhs.config;
544 assert(this->config.id() == rhs.config.id());
545 this->sla_support_points = rhs.sla_support_points;
546 this->sla_points_status = rhs.sla_points_status;
547 this->sla_drain_holes = rhs.sla_drain_holes;
548 this->layer_config_ranges = rhs.layer_config_ranges; // #ys_FIXME_experiment
549 this->layer_height_profile = rhs.layer_height_profile;
550 this->printable = rhs.printable;
551 this->origin_translation = rhs.origin_translation;
552 m_bounding_box = rhs.m_bounding_box;
553 m_bounding_box_valid = rhs.m_bounding_box_valid;
554 m_raw_bounding_box = rhs.m_raw_bounding_box;
555 m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid;
556 m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box;
557 m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid;
558
559 this->clear_volumes();
560 this->volumes.reserve(rhs.volumes.size());
561 for (ModelVolume *model_volume : rhs.volumes) {
562 this->volumes.emplace_back(new ModelVolume(*model_volume));
563 this->volumes.back()->set_model_object(this);
564 }
565 this->clear_instances();
566 this->instances.reserve(rhs.instances.size());
567 for (const ModelInstance *model_instance : rhs.instances) {
568 this->instances.emplace_back(new ModelInstance(*model_instance));
569 this->instances.back()->set_model_object(this);
570 }
571
572 return *this;
573 }
574
575 // maintains the m_model pointer
assign_copy(ModelObject && rhs)576 ModelObject& ModelObject::assign_copy(ModelObject &&rhs)
577 {
578 assert(this->id().invalid());
579 this->copy_id(rhs);
580
581 this->name = std::move(rhs.name);
582 this->input_file = std::move(rhs.input_file);
583 // Moves the config's ID
584 this->config = std::move(rhs.config);
585 assert(this->config.id() == rhs.config.id());
586 this->sla_support_points = std::move(rhs.sla_support_points);
587 this->sla_points_status = std::move(rhs.sla_points_status);
588 this->sla_drain_holes = std::move(rhs.sla_drain_holes);
589 this->layer_config_ranges = std::move(rhs.layer_config_ranges); // #ys_FIXME_experiment
590 this->layer_height_profile = std::move(rhs.layer_height_profile);
591 this->origin_translation = std::move(rhs.origin_translation);
592 m_bounding_box = std::move(rhs.m_bounding_box);
593 m_bounding_box_valid = std::move(rhs.m_bounding_box_valid);
594 m_raw_bounding_box = rhs.m_raw_bounding_box;
595 m_raw_bounding_box_valid = rhs.m_raw_bounding_box_valid;
596 m_raw_mesh_bounding_box = rhs.m_raw_mesh_bounding_box;
597 m_raw_mesh_bounding_box_valid = rhs.m_raw_mesh_bounding_box_valid;
598
599 this->clear_volumes();
600 this->volumes = std::move(rhs.volumes);
601 rhs.volumes.clear();
602 for (ModelVolume *model_volume : this->volumes)
603 model_volume->set_model_object(this);
604 this->clear_instances();
605 this->instances = std::move(rhs.instances);
606 rhs.instances.clear();
607 for (ModelInstance *model_instance : this->instances)
608 model_instance->set_model_object(this);
609
610 return *this;
611 }
612
assign_new_unique_ids_recursive()613 void ModelObject::assign_new_unique_ids_recursive()
614 {
615 this->set_new_unique_id();
616 for (ModelVolume *model_volume : this->volumes)
617 model_volume->assign_new_unique_ids_recursive();
618 for (ModelInstance *model_instance : this->instances)
619 model_instance->assign_new_unique_ids_recursive();
620 this->layer_height_profile.set_new_unique_id();
621 }
622
623 // Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original.
624 // Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing.
625 //ModelObject* ModelObject::clone(Model *parent)
626 //{
627 // return new ModelObject(parent, *this, true);
628 //}
629
add_volume(const TriangleMesh & mesh)630 ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh)
631 {
632 ModelVolume* v = new ModelVolume(this, mesh);
633 this->volumes.push_back(v);
634 v->center_geometry_after_creation();
635 this->invalidate_bounding_box();
636 return v;
637 }
638
add_volume(TriangleMesh && mesh)639 ModelVolume* ModelObject::add_volume(TriangleMesh &&mesh)
640 {
641 ModelVolume* v = new ModelVolume(this, std::move(mesh));
642 this->volumes.push_back(v);
643 v->center_geometry_after_creation();
644 this->invalidate_bounding_box();
645 return v;
646 }
647
add_volume(const ModelVolume & other)648 ModelVolume* ModelObject::add_volume(const ModelVolume &other)
649 {
650 ModelVolume* v = new ModelVolume(this, other);
651 this->volumes.push_back(v);
652 // The volume should already be centered at this point of time when copying shared pointers of the triangle mesh and convex hull.
653 // v->center_geometry_after_creation();
654 // this->invalidate_bounding_box();
655 return v;
656 }
657
add_volume(const ModelVolume & other,TriangleMesh && mesh)658 ModelVolume* ModelObject::add_volume(const ModelVolume &other, TriangleMesh &&mesh)
659 {
660 ModelVolume* v = new ModelVolume(this, other, std::move(mesh));
661 this->volumes.push_back(v);
662 v->center_geometry_after_creation();
663 this->invalidate_bounding_box();
664 return v;
665 }
666
delete_volume(size_t idx)667 void ModelObject::delete_volume(size_t idx)
668 {
669 ModelVolumePtrs::iterator i = this->volumes.begin() + idx;
670 delete *i;
671 this->volumes.erase(i);
672
673 if (this->volumes.size() == 1)
674 {
675 // only one volume left
676 // we need to collapse the volume transform into the instances transforms because now when selecting this volume
677 // it will be seen as a single full instance ans so its volume transform may be ignored
678 ModelVolume* v = this->volumes.front();
679 Transform3d v_t = v->get_transformation().get_matrix();
680 for (ModelInstance* inst : this->instances)
681 {
682 inst->set_transformation(Geometry::Transformation(inst->get_transformation().get_matrix() * v_t));
683 }
684 Geometry::Transformation t;
685 v->set_transformation(t);
686 v->set_new_unique_id();
687 }
688
689 this->invalidate_bounding_box();
690 }
691
clear_volumes()692 void ModelObject::clear_volumes()
693 {
694 for (ModelVolume *v : this->volumes)
695 delete v;
696 this->volumes.clear();
697 this->invalidate_bounding_box();
698 }
699
add_instance()700 ModelInstance* ModelObject::add_instance()
701 {
702 ModelInstance* i = new ModelInstance(this);
703 this->instances.push_back(i);
704 this->invalidate_bounding_box();
705 return i;
706 }
707
add_instance(const ModelInstance & other)708 ModelInstance* ModelObject::add_instance(const ModelInstance &other)
709 {
710 ModelInstance* i = new ModelInstance(this, other);
711 this->instances.push_back(i);
712 this->invalidate_bounding_box();
713 return i;
714 }
715
add_instance(const Vec3d & offset,const Vec3d & scaling_factor,const Vec3d & rotation,const Vec3d & mirror)716 ModelInstance* ModelObject::add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror)
717 {
718 auto *instance = add_instance();
719 instance->set_offset(offset);
720 instance->set_scaling_factor(scaling_factor);
721 instance->set_rotation(rotation);
722 instance->set_mirror(mirror);
723 return instance;
724 }
725
delete_instance(size_t idx)726 void ModelObject::delete_instance(size_t idx)
727 {
728 ModelInstancePtrs::iterator i = this->instances.begin() + idx;
729 delete *i;
730 this->instances.erase(i);
731 this->invalidate_bounding_box();
732 }
733
delete_last_instance()734 void ModelObject::delete_last_instance()
735 {
736 this->delete_instance(this->instances.size() - 1);
737 }
738
clear_instances()739 void ModelObject::clear_instances()
740 {
741 for (ModelInstance *i : this->instances)
742 delete i;
743 this->instances.clear();
744 this->invalidate_bounding_box();
745 }
746
747 // Returns the bounding box of the transformed instances.
748 // This bounding box is approximate and not snug.
bounding_box() const749 const BoundingBoxf3& ModelObject::bounding_box() const
750 {
751 if (! m_bounding_box_valid) {
752 m_bounding_box_valid = true;
753 BoundingBoxf3 raw_bbox = this->raw_mesh_bounding_box();
754 m_bounding_box.reset();
755 for (const ModelInstance *i : this->instances)
756 m_bounding_box.merge(i->transform_bounding_box(raw_bbox));
757 }
758 return m_bounding_box;
759 }
760
761 // A mesh containing all transformed instances of this object.
mesh() const762 TriangleMesh ModelObject::mesh() const
763 {
764 TriangleMesh mesh;
765 TriangleMesh raw_mesh = this->raw_mesh();
766 for (const ModelInstance *i : this->instances) {
767 TriangleMesh m = raw_mesh;
768 i->transform_mesh(&m);
769 mesh.merge(m);
770 }
771 return mesh;
772 }
773
774 // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
775 // Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater
776 // and to display the object statistics at ModelObject::print_info().
raw_mesh() const777 TriangleMesh ModelObject::raw_mesh() const
778 {
779 TriangleMesh mesh;
780 for (const ModelVolume *v : this->volumes)
781 if (v->is_model_part())
782 {
783 TriangleMesh vol_mesh(v->mesh());
784 vol_mesh.transform(v->get_matrix());
785 mesh.merge(vol_mesh);
786 }
787 return mesh;
788 }
789
790 // Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
791 // Currently used by ModelObject::mesh(), to calculate the 2D envelope for 2D plater
792 // and to display the object statistics at ModelObject::print_info().
raw_indexed_triangle_set() const793 indexed_triangle_set ModelObject::raw_indexed_triangle_set() const
794 {
795 size_t num_vertices = 0;
796 size_t num_faces = 0;
797 for (const ModelVolume *v : this->volumes)
798 if (v->is_model_part()) {
799 num_vertices += v->mesh().its.vertices.size();
800 num_faces += v->mesh().its.indices.size();
801 }
802 indexed_triangle_set out;
803 out.vertices.reserve(num_vertices);
804 out.indices.reserve(num_faces);
805 for (const ModelVolume *v : this->volumes)
806 if (v->is_model_part()) {
807 size_t i = out.vertices.size();
808 size_t j = out.indices.size();
809 append(out.vertices, v->mesh().its.vertices);
810 append(out.indices, v->mesh().its.indices);
811 auto m = v->get_matrix();
812 for (; i < out.vertices.size(); ++ i)
813 out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval();
814 if (v->is_left_handed()) {
815 for (; j < out.indices.size(); ++ j)
816 std::swap(out.indices[j][0], out.indices[j][1]);
817 }
818 }
819 return out;
820 }
821
822 // Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
full_raw_mesh() const823 TriangleMesh ModelObject::full_raw_mesh() const
824 {
825 TriangleMesh mesh;
826 for (const ModelVolume *v : this->volumes)
827 {
828 TriangleMesh vol_mesh(v->mesh());
829 vol_mesh.transform(v->get_matrix());
830 mesh.merge(vol_mesh);
831 }
832 return mesh;
833 }
834
raw_mesh_bounding_box() const835 const BoundingBoxf3& ModelObject::raw_mesh_bounding_box() const
836 {
837 if (! m_raw_mesh_bounding_box_valid) {
838 m_raw_mesh_bounding_box_valid = true;
839 m_raw_mesh_bounding_box.reset();
840 for (const ModelVolume *v : this->volumes)
841 if (v->is_model_part())
842 m_raw_mesh_bounding_box.merge(v->mesh().transformed_bounding_box(v->get_matrix()));
843 }
844 return m_raw_mesh_bounding_box;
845 }
846
full_raw_mesh_bounding_box() const847 BoundingBoxf3 ModelObject::full_raw_mesh_bounding_box() const
848 {
849 BoundingBoxf3 bb;
850 for (const ModelVolume *v : this->volumes)
851 bb.merge(v->mesh().transformed_bounding_box(v->get_matrix()));
852 return bb;
853 }
854
855 // A transformed snug bounding box around the non-modifier object volumes, without the translation applied.
856 // This bounding box is only used for the actual slicing and for layer editing UI to calculate the layers.
raw_bounding_box() const857 const BoundingBoxf3& ModelObject::raw_bounding_box() const
858 {
859 if (! m_raw_bounding_box_valid) {
860 m_raw_bounding_box_valid = true;
861 m_raw_bounding_box.reset();
862 if (this->instances.empty())
863 throw Slic3r::InvalidArgument("Can't call raw_bounding_box() with no instances");
864
865 const Transform3d& inst_matrix = this->instances.front()->get_transformation().get_matrix(true);
866 for (const ModelVolume *v : this->volumes)
867 if (v->is_model_part())
868 m_raw_bounding_box.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
869 }
870 return m_raw_bounding_box;
871 }
872
873 // This returns an accurate snug bounding box of the transformed object instance, without the translation applied.
instance_bounding_box(size_t instance_idx,bool dont_translate) const874 BoundingBoxf3 ModelObject::instance_bounding_box(size_t instance_idx, bool dont_translate) const
875 {
876 BoundingBoxf3 bb;
877 const Transform3d& inst_matrix = this->instances[instance_idx]->get_transformation().get_matrix(dont_translate);
878 for (ModelVolume *v : this->volumes)
879 {
880 if (v->is_model_part())
881 bb.merge(v->mesh().transformed_bounding_box(inst_matrix * v->get_matrix()));
882 }
883 return bb;
884 }
885
886 // Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane.
887 // This method is cheap in that it does not make any unnecessary copy of the volume meshes.
888 // This method is used by the auto arrange function.
convex_hull_2d(const Transform3d & trafo_instance) const889 Polygon ModelObject::convex_hull_2d(const Transform3d &trafo_instance) const
890 {
891 Points pts;
892 for (const ModelVolume *v : this->volumes)
893 if (v->is_model_part()) {
894 Transform3d trafo = trafo_instance * v->get_matrix();
895 const indexed_triangle_set &its = v->mesh().its;
896 if (its.vertices.empty()) {
897 // Using the STL faces.
898 const stl_file& stl = v->mesh().stl;
899 for (const stl_facet &facet : stl.facet_start)
900 for (size_t j = 0; j < 3; ++ j) {
901 Vec3d p = trafo * facet.vertex[j].cast<double>();
902 pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
903 }
904 } else {
905 // Using the shared vertices should be a bit quicker than using the STL faces.
906 for (size_t i = 0; i < its.vertices.size(); ++ i) {
907 Vec3d p = trafo * its.vertices[i].cast<double>();
908 pts.emplace_back(coord_t(scale_(p.x())), coord_t(scale_(p.y())));
909 }
910 }
911 }
912 std::sort(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) < b(0) || (a(0) == b(0) && a(1) < b(1)); });
913 pts.erase(std::unique(pts.begin(), pts.end(), [](const Point& a, const Point& b) { return a(0) == b(0) && a(1) == b(1); }), pts.end());
914
915 Polygon hull;
916 int n = (int)pts.size();
917 if (n >= 3) {
918 int k = 0;
919 hull.points.resize(2 * n);
920 // Build lower hull
921 for (int i = 0; i < n; ++ i) {
922 while (k >= 2 && pts[i].ccw(hull[k-2], hull[k-1]) <= 0)
923 -- k;
924 hull[k ++] = pts[i];
925 }
926 // Build upper hull
927 for (int i = n-2, t = k+1; i >= 0; i--) {
928 while (k >= t && pts[i].ccw(hull[k-2], hull[k-1]) <= 0)
929 -- k;
930 hull[k ++] = pts[i];
931 }
932 hull.points.resize(k);
933 assert(hull.points.front() == hull.points.back());
934 hull.points.pop_back();
935 }
936 return hull;
937 }
938
center_around_origin(bool include_modifiers)939 void ModelObject::center_around_origin(bool include_modifiers)
940 {
941 // calculate the displacements needed to
942 // center this object around the origin
943 BoundingBoxf3 bb = include_modifiers ? full_raw_mesh_bounding_box() : raw_mesh_bounding_box();
944
945 // Shift is the vector from the center of the bounding box to the origin
946 Vec3d shift = -bb.center();
947
948 this->translate(shift);
949 this->origin_translation += shift;
950 }
951
ensure_on_bed()952 void ModelObject::ensure_on_bed()
953 {
954 translate_instances(Vec3d(0.0, 0.0, -get_min_z()));
955 }
956
translate_instances(const Vec3d & vector)957 void ModelObject::translate_instances(const Vec3d& vector)
958 {
959 for (size_t i = 0; i < instances.size(); ++i)
960 {
961 translate_instance(i, vector);
962 }
963 }
964
translate_instance(size_t instance_idx,const Vec3d & vector)965 void ModelObject::translate_instance(size_t instance_idx, const Vec3d& vector)
966 {
967 ModelInstance* i = instances[instance_idx];
968 i->set_offset(i->get_offset() + vector);
969 invalidate_bounding_box();
970 }
971
translate(double x,double y,double z)972 void ModelObject::translate(double x, double y, double z)
973 {
974 for (ModelVolume *v : this->volumes)
975 {
976 v->translate(x, y, z);
977 }
978
979 if (m_bounding_box_valid)
980 m_bounding_box.translate(x, y, z);
981 }
982
scale(const Vec3d & versor)983 void ModelObject::scale(const Vec3d &versor)
984 {
985 for (ModelVolume *v : this->volumes)
986 {
987 v->scale(versor);
988 }
989 this->invalidate_bounding_box();
990 }
991
rotate(double angle,Axis axis)992 void ModelObject::rotate(double angle, Axis axis)
993 {
994 for (ModelVolume *v : this->volumes)
995 {
996 v->rotate(angle, axis);
997 }
998
999 center_around_origin();
1000 this->invalidate_bounding_box();
1001 }
1002
rotate(double angle,const Vec3d & axis)1003 void ModelObject::rotate(double angle, const Vec3d& axis)
1004 {
1005 for (ModelVolume *v : this->volumes)
1006 {
1007 v->rotate(angle, axis);
1008 }
1009
1010 center_around_origin();
1011 this->invalidate_bounding_box();
1012 }
1013
mirror(Axis axis)1014 void ModelObject::mirror(Axis axis)
1015 {
1016 for (ModelVolume *v : this->volumes)
1017 {
1018 v->mirror(axis);
1019 }
1020
1021 this->invalidate_bounding_box();
1022 }
1023
1024 // This method could only be called before the meshes of this ModelVolumes are not shared!
scale_mesh_after_creation(const Vec3d & versor)1025 void ModelObject::scale_mesh_after_creation(const Vec3d &versor)
1026 {
1027 for (ModelVolume *v : this->volumes)
1028 {
1029 v->scale_geometry_after_creation(versor);
1030 v->set_offset(versor.cwiseProduct(v->get_offset()));
1031 }
1032 this->invalidate_bounding_box();
1033 }
1034
convert_units(ModelObjectPtrs & new_objects,bool from_imperial,std::vector<int> volume_idxs)1035 void ModelObject::convert_units(ModelObjectPtrs& new_objects, bool from_imperial, std::vector<int> volume_idxs)
1036 {
1037 BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - start";
1038
1039 ModelObject* new_object = new_clone(*this);
1040
1041 double koef = from_imperial ? 25.4 : 0.0393700787;
1042 const Vec3d versor = Vec3d(koef, koef, koef);
1043
1044 new_object->set_model(nullptr);
1045 new_object->sla_support_points.clear();
1046 new_object->sla_drain_holes.clear();
1047 new_object->sla_points_status = sla::PointsStatus::NoPoints;
1048 new_object->clear_volumes();
1049 new_object->input_file.clear();
1050
1051 int vol_idx = 0;
1052 for (ModelVolume* volume : volumes)
1053 {
1054 if (!volume->mesh().empty()) {
1055 TriangleMesh mesh(volume->mesh());
1056 mesh.require_shared_vertices();
1057
1058 ModelVolume* vol = new_object->add_volume(mesh);
1059 vol->name = volume->name;
1060 vol->set_type(volume->type());
1061 // Don't copy the config's ID.
1062 vol->config.assign_config(volume->config);
1063 assert(vol->config.id().valid());
1064 assert(vol->config.id() != volume->config.id());
1065 vol->set_material(volume->material_id(), *volume->material());
1066 vol->source.input_file = volume->source.input_file;
1067 vol->source.object_idx = (int)new_objects.size();
1068 vol->source.volume_idx = vol_idx;
1069
1070 vol->supported_facets.assign(volume->supported_facets);
1071 vol->seam_facets.assign(volume->seam_facets);
1072
1073 // Perform conversion only if the target "imperial" state is different from the current one.
1074 // This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
1075 if (//vol->source.is_converted_from_inches != from_imperial &&
1076 (volume_idxs.empty() ||
1077 std::find(volume_idxs.begin(), volume_idxs.end(), vol_idx) != volume_idxs.end())) {
1078 vol->scale_geometry_after_creation(versor);
1079 vol->set_offset(versor.cwiseProduct(volume->get_offset()));
1080 vol->source.is_converted_from_inches = from_imperial;
1081 }
1082 else
1083 vol->set_offset(volume->get_offset());
1084 }
1085 vol_idx ++;
1086 }
1087 new_object->invalidate_bounding_box();
1088
1089 new_objects.push_back(new_object);
1090
1091 BOOST_LOG_TRIVIAL(trace) << "ModelObject::convert_units - end";
1092 }
1093
materials_count() const1094 size_t ModelObject::materials_count() const
1095 {
1096 std::set<t_model_material_id> material_ids;
1097 for (const ModelVolume *v : this->volumes)
1098 material_ids.insert(v->material_id());
1099 return material_ids.size();
1100 }
1101
facets_count() const1102 size_t ModelObject::facets_count() const
1103 {
1104 size_t num = 0;
1105 for (const ModelVolume *v : this->volumes)
1106 if (v->is_model_part())
1107 num += v->mesh().stl.stats.number_of_facets;
1108 return num;
1109 }
1110
needed_repair() const1111 bool ModelObject::needed_repair() const
1112 {
1113 for (const ModelVolume *v : this->volumes)
1114 if (v->is_model_part() && v->mesh().needed_repair())
1115 return true;
1116 return false;
1117 }
1118
cut(size_t instance,coordf_t z,bool keep_upper,bool keep_lower,bool rotate_lower)1119 ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, bool keep_lower, bool rotate_lower)
1120 {
1121 if (!keep_upper && !keep_lower) { return {}; }
1122
1123 BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - start";
1124
1125 // Clone the object to duplicate instances, materials etc.
1126 ModelObject* upper = keep_upper ? ModelObject::new_clone(*this) : nullptr;
1127 ModelObject* lower = keep_lower ? ModelObject::new_clone(*this) : nullptr;
1128
1129 if (keep_upper) {
1130 upper->set_model(nullptr);
1131 upper->sla_support_points.clear();
1132 upper->sla_drain_holes.clear();
1133 upper->sla_points_status = sla::PointsStatus::NoPoints;
1134 upper->clear_volumes();
1135 upper->input_file.clear();
1136 }
1137
1138 if (keep_lower) {
1139 lower->set_model(nullptr);
1140 lower->sla_support_points.clear();
1141 lower->sla_drain_holes.clear();
1142 lower->sla_points_status = sla::PointsStatus::NoPoints;
1143 lower->clear_volumes();
1144 lower->input_file.clear();
1145 }
1146
1147 // Because transformations are going to be applied to meshes directly,
1148 // we reset transformation of all instances and volumes,
1149 // except for translation and Z-rotation on instances, which are preserved
1150 // in the transformation matrix and not applied to the mesh transform.
1151
1152 // const auto instance_matrix = instances[instance]->get_matrix(true);
1153 const auto instance_matrix = Geometry::assemble_transform(
1154 Vec3d::Zero(), // don't apply offset
1155 instances[instance]->get_rotation().cwiseProduct(Vec3d(1.0, 1.0, 0.0)), // don't apply Z-rotation
1156 instances[instance]->get_scaling_factor(),
1157 instances[instance]->get_mirror()
1158 );
1159
1160 z -= instances[instance]->get_offset()(2);
1161
1162 // Lower part per-instance bounding boxes
1163 std::vector<BoundingBoxf3> lower_bboxes { instances.size() };
1164
1165 for (ModelVolume *volume : volumes) {
1166 const auto volume_matrix = volume->get_matrix();
1167
1168 volume->supported_facets.clear();
1169 volume->seam_facets.clear();
1170
1171 if (! volume->is_model_part()) {
1172 // Modifiers are not cut, but we still need to add the instance transformation
1173 // to the modifier volume transformation to preserve their shape properly.
1174
1175 volume->set_transformation(Geometry::Transformation(instance_matrix * volume_matrix));
1176
1177 if (keep_upper) { upper->add_volume(*volume); }
1178 if (keep_lower) { lower->add_volume(*volume); }
1179 }
1180 else if (! volume->mesh().empty()) {
1181
1182 TriangleMesh upper_mesh, lower_mesh;
1183
1184 // Transform the mesh by the combined transformation matrix.
1185 // Flip the triangles in case the composite transformation is left handed.
1186 TriangleMesh mesh(volume->mesh());
1187 mesh.transform(instance_matrix * volume_matrix, true);
1188 volume->reset_mesh();
1189
1190 mesh.require_shared_vertices();
1191
1192 // Perform cut
1193 TriangleMeshSlicer tms(&mesh);
1194 tms.cut(float(z), &upper_mesh, &lower_mesh);
1195
1196 // Reset volume transformation except for offset
1197 const Vec3d offset = volume->get_offset();
1198 volume->set_transformation(Geometry::Transformation());
1199 volume->set_offset(offset);
1200
1201 if (keep_upper) {
1202 upper_mesh.repair();
1203 upper_mesh.reset_repair_stats();
1204 }
1205 if (keep_lower) {
1206 lower_mesh.repair();
1207 lower_mesh.reset_repair_stats();
1208 }
1209
1210 if (keep_upper && upper_mesh.facets_count() > 0) {
1211 ModelVolume* vol = upper->add_volume(upper_mesh);
1212 vol->name = volume->name;
1213 // Don't copy the config's ID.
1214 vol->config.assign_config(volume->config);
1215 assert(vol->config.id().valid());
1216 assert(vol->config.id() != volume->config.id());
1217 vol->set_material(volume->material_id(), *volume->material());
1218 }
1219 if (keep_lower && lower_mesh.facets_count() > 0) {
1220 ModelVolume* vol = lower->add_volume(lower_mesh);
1221 vol->name = volume->name;
1222 // Don't copy the config's ID.
1223 vol->config.assign_config(volume->config);
1224 assert(vol->config.id().valid());
1225 assert(vol->config.id() != volume->config.id());
1226 vol->set_material(volume->material_id(), *volume->material());
1227
1228 // Compute the lower part instances' bounding boxes to figure out where to place
1229 // the upper part
1230 if (keep_upper) {
1231 for (size_t i = 0; i < instances.size(); i++) {
1232 lower_bboxes[i].merge(instances[i]->transform_mesh_bounding_box(lower_mesh, true));
1233 }
1234 }
1235 }
1236 }
1237 }
1238
1239 ModelObjectPtrs res;
1240
1241 if (keep_upper && upper->volumes.size() > 0) {
1242 upper->invalidate_bounding_box();
1243 upper->center_around_origin();
1244
1245 // Reset instance transformation except offset and Z-rotation
1246 for (size_t i = 0; i < instances.size(); i++) {
1247 auto &instance = upper->instances[i];
1248 const Vec3d offset = instance->get_offset();
1249 const double rot_z = instance->get_rotation()(2);
1250 // The upper part displacement is set to half of the lower part bounding box
1251 // this is done in hope at least a part of the upper part will always be visible and draggable
1252 const Vec3d displace = lower_bboxes[i].size().cwiseProduct(Vec3d(-0.5, -0.5, 0.0));
1253
1254 instance->set_transformation(Geometry::Transformation());
1255 instance->set_offset(offset + displace);
1256 instance->set_rotation(Vec3d(0.0, 0.0, rot_z));
1257 }
1258
1259 res.push_back(upper);
1260 }
1261 if (keep_lower && lower->volumes.size() > 0) {
1262 lower->invalidate_bounding_box();
1263 lower->center_around_origin();
1264
1265 // Reset instance transformation except offset and Z-rotation
1266 for (auto *instance : lower->instances) {
1267 const Vec3d offset = instance->get_offset();
1268 const double rot_z = instance->get_rotation()(2);
1269
1270 instance->set_transformation(Geometry::Transformation());
1271 instance->set_offset(offset);
1272 instance->set_rotation(Vec3d(rotate_lower ? Geometry::deg2rad(180.0) : 0.0, 0.0, rot_z));
1273 }
1274
1275 res.push_back(lower);
1276 }
1277
1278 BOOST_LOG_TRIVIAL(trace) << "ModelObject::cut - end";
1279
1280 return res;
1281 }
1282
split(ModelObjectPtrs * new_objects)1283 void ModelObject::split(ModelObjectPtrs* new_objects)
1284 {
1285 if (this->volumes.size() > 1) {
1286 // We can't split meshes if there's more than one volume, because
1287 // we can't group the resulting meshes by object afterwards
1288 new_objects->emplace_back(this);
1289 return;
1290 }
1291
1292 ModelVolume* volume = this->volumes.front();
1293 TriangleMeshPtrs meshptrs = volume->mesh().split();
1294 for (TriangleMesh *mesh : meshptrs) {
1295
1296 // FIXME: crashes if not satisfied
1297 if (mesh->facets_count() < 3) continue;
1298
1299 mesh->repair();
1300
1301 // XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
1302 ModelObject* new_object = m_model->add_object();
1303 new_object->name = this->name;
1304 // Don't copy the config's ID.
1305 new_object->config.assign_config(this->config);
1306 assert(new_object->config.id().valid());
1307 assert(new_object->config.id() != this->config.id());
1308 new_object->instances.reserve(this->instances.size());
1309 for (const ModelInstance *model_instance : this->instances)
1310 new_object->add_instance(*model_instance);
1311 ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh));
1312
1313 for (ModelInstance* model_instance : new_object->instances)
1314 {
1315 Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
1316 model_instance->set_offset(model_instance->get_offset() + shift);
1317 }
1318
1319 new_vol->set_offset(Vec3d::Zero());
1320 // reset the source to disable reload from disk
1321 new_vol->source = ModelVolume::Source();
1322 new_objects->emplace_back(new_object);
1323 delete mesh;
1324 }
1325
1326 return;
1327 }
1328
merge()1329 void ModelObject::merge()
1330 {
1331 if (this->volumes.size() == 1) {
1332 // We can't merge meshes if there's just one volume
1333 return;
1334 }
1335
1336 TriangleMesh mesh;
1337
1338 for (ModelVolume* volume : volumes)
1339 if (!volume->mesh().empty())
1340 mesh.merge(volume->mesh());
1341 mesh.repair();
1342
1343 this->clear_volumes();
1344 ModelVolume* vol = this->add_volume(mesh);
1345
1346 if (!vol)
1347 return;
1348 }
1349
1350 // Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
1351 // then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
1352 // This situation is solved by baking in the instance transformation into the mesh vertices.
1353 // Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
bake_xy_rotation_into_meshes(size_t instance_idx)1354 void ModelObject::bake_xy_rotation_into_meshes(size_t instance_idx)
1355 {
1356 assert(instance_idx < this->instances.size());
1357
1358 const Geometry::Transformation reference_trafo = this->instances[instance_idx]->get_transformation();
1359 if (Geometry::is_rotation_ninety_degrees(reference_trafo.get_rotation()))
1360 // nothing to do, scaling in the world coordinate space is possible in the representation of Geometry::Transformation.
1361 return;
1362
1363 bool left_handed = reference_trafo.is_left_handed();
1364 bool has_mirrorring = ! reference_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.));
1365 bool uniform_scaling = std::abs(reference_trafo.get_scaling_factor().x() - reference_trafo.get_scaling_factor().y()) < EPSILON &&
1366 std::abs(reference_trafo.get_scaling_factor().x() - reference_trafo.get_scaling_factor().z()) < EPSILON;
1367 double new_scaling_factor = uniform_scaling ? reference_trafo.get_scaling_factor().x() : 1.;
1368
1369 // Adjust the instances.
1370 for (size_t i = 0; i < this->instances.size(); ++ i) {
1371 ModelInstance &model_instance = *this->instances[i];
1372 model_instance.set_rotation(Vec3d(0., 0., Geometry::rotation_diff_z(reference_trafo.get_rotation(), model_instance.get_rotation())));
1373 model_instance.set_scaling_factor(Vec3d(new_scaling_factor, new_scaling_factor, new_scaling_factor));
1374 model_instance.set_mirror(Vec3d(1., 1., 1.));
1375 }
1376
1377 // Adjust the meshes.
1378 // Transformation to be applied to the meshes.
1379 Eigen::Matrix3d mesh_trafo_3x3 = reference_trafo.get_matrix(true, false, uniform_scaling, ! has_mirrorring).matrix().block<3, 3>(0, 0);
1380 Transform3d volume_offset_correction = this->instances[instance_idx]->get_transformation().get_matrix().inverse() * reference_trafo.get_matrix();
1381 for (ModelVolume *model_volume : this->volumes) {
1382 const Geometry::Transformation volume_trafo = model_volume->get_transformation();
1383 bool volume_left_handed = volume_trafo.is_left_handed();
1384 bool volume_has_mirrorring = ! volume_trafo.get_mirror().isApprox(Vec3d(1., 1., 1.));
1385 bool volume_uniform_scaling = std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().y()) < EPSILON &&
1386 std::abs(volume_trafo.get_scaling_factor().x() - volume_trafo.get_scaling_factor().z()) < EPSILON;
1387 double volume_new_scaling_factor = volume_uniform_scaling ? volume_trafo.get_scaling_factor().x() : 1.;
1388 // Transform the mesh.
1389 Matrix3d volume_trafo_3x3 = volume_trafo.get_matrix(true, false, volume_uniform_scaling, !volume_has_mirrorring).matrix().block<3, 3>(0, 0);
1390 // Following method creates a new shared_ptr<TriangleMesh>
1391 model_volume->transform_this_mesh(mesh_trafo_3x3 * volume_trafo_3x3, left_handed != volume_left_handed);
1392 // Reset the rotation, scaling and mirroring.
1393 model_volume->set_rotation(Vec3d(0., 0., 0.));
1394 model_volume->set_scaling_factor(Vec3d(volume_new_scaling_factor, volume_new_scaling_factor, volume_new_scaling_factor));
1395 model_volume->set_mirror(Vec3d(1., 1., 1.));
1396 // Move the reference point of the volume to compensate for the change of the instance trafo.
1397 model_volume->set_offset(volume_offset_correction * volume_trafo.get_offset());
1398 // reset the source to disable reload from disk
1399 model_volume->source = ModelVolume::Source();
1400 }
1401
1402 this->invalidate_bounding_box();
1403 }
1404
get_min_z() const1405 double ModelObject::get_min_z() const
1406 {
1407 if (instances.empty())
1408 return 0.0;
1409 else
1410 {
1411 double min_z = DBL_MAX;
1412 for (size_t i = 0; i < instances.size(); ++i)
1413 {
1414 min_z = std::min(min_z, get_instance_min_z(i));
1415 }
1416 return min_z;
1417 }
1418 }
1419
get_instance_min_z(size_t instance_idx) const1420 double ModelObject::get_instance_min_z(size_t instance_idx) const
1421 {
1422 double min_z = DBL_MAX;
1423
1424 ModelInstance* inst = instances[instance_idx];
1425 const Transform3d& mi = inst->get_matrix(true);
1426
1427 for (const ModelVolume* v : volumes)
1428 {
1429 if (!v->is_model_part())
1430 continue;
1431
1432 Transform3d mv = mi * v->get_matrix();
1433 const TriangleMesh& hull = v->get_convex_hull();
1434 for (const stl_facet &facet : hull.stl.facet_start)
1435 for (int i = 0; i < 3; ++ i)
1436 min_z = std::min(min_z, (mv * facet.vertex[i].cast<double>()).z());
1437 }
1438
1439 return min_z + inst->get_offset(Z);
1440 }
1441
check_instances_print_volume_state(const BoundingBoxf3 & print_volume)1442 unsigned int ModelObject::check_instances_print_volume_state(const BoundingBoxf3& print_volume)
1443 {
1444 unsigned int num_printable = 0;
1445 enum {
1446 INSIDE = 1,
1447 OUTSIDE = 2
1448 };
1449 for (ModelInstance *model_instance : this->instances) {
1450 unsigned int inside_outside = 0;
1451 for (const ModelVolume *vol : this->volumes)
1452 if (vol->is_model_part()) {
1453 BoundingBoxf3 bb = vol->get_convex_hull().transformed_bounding_box(model_instance->get_matrix() * vol->get_matrix());
1454 if (print_volume.contains(bb))
1455 inside_outside |= INSIDE;
1456 else if (print_volume.intersects(bb))
1457 inside_outside |= INSIDE | OUTSIDE;
1458 else
1459 inside_outside |= OUTSIDE;
1460 }
1461 model_instance->print_volume_state =
1462 (inside_outside == (INSIDE | OUTSIDE)) ? ModelInstancePVS_Partly_Outside :
1463 (inside_outside == INSIDE) ? ModelInstancePVS_Inside : ModelInstancePVS_Fully_Outside;
1464 if (inside_outside == INSIDE)
1465 ++ num_printable;
1466 }
1467 return num_printable;
1468 }
1469
print_info() const1470 void ModelObject::print_info() const
1471 {
1472 using namespace std;
1473 cout << fixed;
1474 boost::nowide::cout << "[" << boost::filesystem::path(this->input_file).filename().string() << "]" << endl;
1475
1476 TriangleMesh mesh = this->raw_mesh();
1477 mesh.check_topology();
1478 BoundingBoxf3 bb = mesh.bounding_box();
1479 Vec3d size = bb.size();
1480 cout << "size_x = " << size(0) << endl;
1481 cout << "size_y = " << size(1) << endl;
1482 cout << "size_z = " << size(2) << endl;
1483 cout << "min_x = " << bb.min(0) << endl;
1484 cout << "min_y = " << bb.min(1) << endl;
1485 cout << "min_z = " << bb.min(2) << endl;
1486 cout << "max_x = " << bb.max(0) << endl;
1487 cout << "max_y = " << bb.max(1) << endl;
1488 cout << "max_z = " << bb.max(2) << endl;
1489 cout << "number_of_facets = " << mesh.stl.stats.number_of_facets << endl;
1490 cout << "manifold = " << (mesh.is_manifold() ? "yes" : "no") << endl;
1491
1492 mesh.repair(); // this calculates number_of_parts
1493 if (mesh.needed_repair()) {
1494 mesh.repair();
1495 if (mesh.stl.stats.degenerate_facets > 0)
1496 cout << "degenerate_facets = " << mesh.stl.stats.degenerate_facets << endl;
1497 if (mesh.stl.stats.edges_fixed > 0)
1498 cout << "edges_fixed = " << mesh.stl.stats.edges_fixed << endl;
1499 if (mesh.stl.stats.facets_removed > 0)
1500 cout << "facets_removed = " << mesh.stl.stats.facets_removed << endl;
1501 if (mesh.stl.stats.facets_added > 0)
1502 cout << "facets_added = " << mesh.stl.stats.facets_added << endl;
1503 if (mesh.stl.stats.facets_reversed > 0)
1504 cout << "facets_reversed = " << mesh.stl.stats.facets_reversed << endl;
1505 if (mesh.stl.stats.backwards_edges > 0)
1506 cout << "backwards_edges = " << mesh.stl.stats.backwards_edges << endl;
1507 }
1508 cout << "number_of_parts = " << mesh.stl.stats.number_of_parts << endl;
1509 cout << "volume = " << mesh.volume() << endl;
1510 }
1511
get_export_filename() const1512 std::string ModelObject::get_export_filename() const
1513 {
1514 std::string ret = input_file;
1515
1516 if (!name.empty())
1517 {
1518 if (ret.empty())
1519 // input_file was empty, just use name
1520 ret = name;
1521 else
1522 {
1523 // Replace file name in input_file with name, but keep the path and file extension.
1524 ret = (boost::filesystem::path(name).parent_path().empty()) ?
1525 (boost::filesystem::path(ret).parent_path() / name).make_preferred().string() : name;
1526 }
1527 }
1528
1529 return ret;
1530 }
1531
get_object_stl_stats() const1532 stl_stats ModelObject::get_object_stl_stats() const
1533 {
1534 if (this->volumes.size() == 1)
1535 return this->volumes[0]->mesh().stl.stats;
1536
1537 stl_stats full_stats;
1538 full_stats.volume = 0.f;
1539
1540 // fill full_stats from all objet's meshes
1541 for (ModelVolume* volume : this->volumes)
1542 {
1543 const stl_stats& stats = volume->mesh().stl.stats;
1544
1545 // initialize full_stats (for repaired errors)
1546 full_stats.degenerate_facets += stats.degenerate_facets;
1547 full_stats.edges_fixed += stats.edges_fixed;
1548 full_stats.facets_removed += stats.facets_removed;
1549 full_stats.facets_added += stats.facets_added;
1550 full_stats.facets_reversed += stats.facets_reversed;
1551 full_stats.backwards_edges += stats.backwards_edges;
1552
1553 // another used satistics value
1554 if (volume->is_model_part()) {
1555 full_stats.volume += stats.volume;
1556 full_stats.number_of_parts += stats.number_of_parts;
1557 }
1558 }
1559
1560 return full_stats;
1561 }
1562
get_mesh_errors_count(const int vol_idx) const1563 int ModelObject::get_mesh_errors_count(const int vol_idx /*= -1*/) const
1564 {
1565 if (vol_idx >= 0)
1566 return this->volumes[vol_idx]->get_mesh_errors_count();
1567
1568 const stl_stats& stats = get_object_stl_stats();
1569
1570 return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
1571 stats.facets_added + stats.facets_reversed + stats.backwards_edges;
1572 }
1573
set_material_id(t_model_material_id material_id)1574 void ModelVolume::set_material_id(t_model_material_id material_id)
1575 {
1576 m_material_id = material_id;
1577 // ensure m_material_id references an existing material
1578 if (! material_id.empty())
1579 this->object->get_model()->add_material(material_id);
1580 }
1581
material() const1582 ModelMaterial* ModelVolume::material() const
1583 {
1584 return this->object->get_model()->get_material(m_material_id);
1585 }
1586
set_material(t_model_material_id material_id,const ModelMaterial & material)1587 void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material)
1588 {
1589 m_material_id = material_id;
1590 if (! material_id.empty())
1591 this->object->get_model()->add_material(material_id, material);
1592 }
1593
1594 // Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config.
extruder_id() const1595 int ModelVolume::extruder_id() const
1596 {
1597 int extruder_id = -1;
1598 if (this->is_model_part()) {
1599 const ConfigOption *opt = this->config.option("extruder");
1600 if ((opt == nullptr) || (opt->getInt() == 0))
1601 opt = this->object->config.option("extruder");
1602 extruder_id = (opt == nullptr) ? 0 : opt->getInt();
1603 }
1604 return extruder_id;
1605 }
1606
is_splittable() const1607 bool ModelVolume::is_splittable() const
1608 {
1609 // the call mesh.is_splittable() is expensive, so cache the value to calculate it only once
1610 if (m_is_splittable == -1)
1611 m_is_splittable = (int)this->mesh().is_splittable();
1612
1613 return m_is_splittable == 1;
1614 }
1615
center_geometry_after_creation(bool update_source_offset)1616 void ModelVolume::center_geometry_after_creation(bool update_source_offset)
1617 {
1618 Vec3d shift = this->mesh().bounding_box().center();
1619 if (!shift.isApprox(Vec3d::Zero()))
1620 {
1621 if (m_mesh)
1622 const_cast<TriangleMesh*>(m_mesh.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
1623 if (m_convex_hull)
1624 const_cast<TriangleMesh*>(m_convex_hull.get())->translate(-(float)shift(0), -(float)shift(1), -(float)shift(2));
1625 translate(shift);
1626 }
1627
1628 if (update_source_offset)
1629 source.mesh_offset = shift;
1630 }
1631
calculate_convex_hull()1632 void ModelVolume::calculate_convex_hull()
1633 {
1634 m_convex_hull = std::make_shared<TriangleMesh>(this->mesh().convex_hull_3d());
1635 }
1636
get_mesh_errors_count() const1637 int ModelVolume::get_mesh_errors_count() const
1638 {
1639 const stl_stats& stats = this->mesh().stl.stats;
1640
1641 return stats.degenerate_facets + stats.edges_fixed + stats.facets_removed +
1642 stats.facets_added + stats.facets_reversed + stats.backwards_edges;
1643 }
1644
get_convex_hull() const1645 const TriangleMesh& ModelVolume::get_convex_hull() const
1646 {
1647 return *m_convex_hull.get();
1648 }
1649
type_from_string(const std::string & s)1650 ModelVolumeType ModelVolume::type_from_string(const std::string &s)
1651 {
1652 // Legacy support
1653 if (s == "1")
1654 return ModelVolumeType::PARAMETER_MODIFIER;
1655 // New type (supporting the support enforcers & blockers)
1656 if (s == "ModelPart")
1657 return ModelVolumeType::MODEL_PART;
1658 if (s == "ParameterModifier")
1659 return ModelVolumeType::PARAMETER_MODIFIER;
1660 if (s == "SupportEnforcer")
1661 return ModelVolumeType::SUPPORT_ENFORCER;
1662 if (s == "SupportBlocker")
1663 return ModelVolumeType::SUPPORT_BLOCKER;
1664 assert(s == "0");
1665 // Default value if invalud type string received.
1666 return ModelVolumeType::MODEL_PART;
1667 }
1668
type_to_string(const ModelVolumeType t)1669 std::string ModelVolume::type_to_string(const ModelVolumeType t)
1670 {
1671 switch (t) {
1672 case ModelVolumeType::MODEL_PART: return "ModelPart";
1673 case ModelVolumeType::PARAMETER_MODIFIER: return "ParameterModifier";
1674 case ModelVolumeType::SUPPORT_ENFORCER: return "SupportEnforcer";
1675 case ModelVolumeType::SUPPORT_BLOCKER: return "SupportBlocker";
1676 default:
1677 assert(false);
1678 return "ModelPart";
1679 }
1680 }
1681
1682 // Split this volume, append the result to the object owning this volume.
1683 // Return the number of volumes created from this one.
1684 // This is useful to assign different materials to different volumes of an object.
split(unsigned int max_extruders)1685 size_t ModelVolume::split(unsigned int max_extruders)
1686 {
1687 TriangleMeshPtrs meshptrs = this->mesh().split();
1688 if (meshptrs.size() <= 1) {
1689 delete meshptrs.front();
1690 return 1;
1691 }
1692
1693 size_t idx = 0;
1694 size_t ivolume = std::find(this->object->volumes.begin(), this->object->volumes.end(), this) - this->object->volumes.begin();
1695 std::string name = this->name;
1696
1697 unsigned int extruder_counter = 0;
1698 Vec3d offset = this->get_offset();
1699
1700 for (TriangleMesh *mesh : meshptrs) {
1701 mesh->repair();
1702 if (idx == 0)
1703 {
1704 this->set_mesh(std::move(*mesh));
1705 this->calculate_convex_hull();
1706 // Assign a new unique ID, so that a new GLVolume will be generated.
1707 this->set_new_unique_id();
1708 // reset the source to disable reload from disk
1709 this->source = ModelVolume::Source();
1710 }
1711 else
1712 this->object->volumes.insert(this->object->volumes.begin() + (++ivolume), new ModelVolume(object, *this, std::move(*mesh)));
1713
1714 this->object->volumes[ivolume]->set_offset(Vec3d::Zero());
1715 this->object->volumes[ivolume]->center_geometry_after_creation();
1716 this->object->volumes[ivolume]->translate(offset);
1717 this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
1718 this->object->volumes[ivolume]->config.set("extruder", auto_extruder_id(max_extruders, extruder_counter));
1719 delete mesh;
1720 ++ idx;
1721 }
1722
1723 return idx;
1724 }
1725
translate(const Vec3d & displacement)1726 void ModelVolume::translate(const Vec3d& displacement)
1727 {
1728 set_offset(get_offset() + displacement);
1729 }
1730
scale(const Vec3d & scaling_factors)1731 void ModelVolume::scale(const Vec3d& scaling_factors)
1732 {
1733 set_scaling_factor(get_scaling_factor().cwiseProduct(scaling_factors));
1734 }
1735
scale_to_fit(const Vec3d & size)1736 void ModelObject::scale_to_fit(const Vec3d &size)
1737 {
1738 /*
1739 BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
1740 Vec3d orig_size = this->bounding_box().size();
1741 float factor = fminf(
1742 size.x / orig_size.x,
1743 fminf(
1744 size.y / orig_size.y,
1745 size.z / orig_size.z
1746 )
1747 );
1748 this->scale(factor);
1749 */
1750 }
1751
assign_new_unique_ids_recursive()1752 void ModelVolume::assign_new_unique_ids_recursive()
1753 {
1754 ObjectBase::set_new_unique_id();
1755 config.set_new_unique_id();
1756 supported_facets.set_new_unique_id();
1757 seam_facets.set_new_unique_id();
1758 }
1759
rotate(double angle,Axis axis)1760 void ModelVolume::rotate(double angle, Axis axis)
1761 {
1762 switch (axis)
1763 {
1764 case X: { rotate(angle, Vec3d::UnitX()); break; }
1765 case Y: { rotate(angle, Vec3d::UnitY()); break; }
1766 case Z: { rotate(angle, Vec3d::UnitZ()); break; }
1767 default: break;
1768 }
1769 }
1770
rotate(double angle,const Vec3d & axis)1771 void ModelVolume::rotate(double angle, const Vec3d& axis)
1772 {
1773 set_rotation(get_rotation() + Geometry::extract_euler_angles(Eigen::Quaterniond(Eigen::AngleAxisd(angle, axis)).toRotationMatrix()));
1774 }
1775
mirror(Axis axis)1776 void ModelVolume::mirror(Axis axis)
1777 {
1778 Vec3d mirror = get_mirror();
1779 switch (axis)
1780 {
1781 case X: { mirror(0) *= -1.0; break; }
1782 case Y: { mirror(1) *= -1.0; break; }
1783 case Z: { mirror(2) *= -1.0; break; }
1784 default: break;
1785 }
1786 set_mirror(mirror);
1787 }
1788
1789 // This method could only be called before the meshes of this ModelVolumes are not shared!
scale_geometry_after_creation(const Vec3d & versor)1790 void ModelVolume::scale_geometry_after_creation(const Vec3d& versor)
1791 {
1792 const_cast<TriangleMesh*>(m_mesh.get())->scale(versor);
1793 const_cast<TriangleMesh*>(m_convex_hull.get())->scale(versor);
1794 }
1795
transform_this_mesh(const Transform3d & mesh_trafo,bool fix_left_handed)1796 void ModelVolume::transform_this_mesh(const Transform3d &mesh_trafo, bool fix_left_handed)
1797 {
1798 TriangleMesh mesh = this->mesh();
1799 mesh.transform(mesh_trafo, fix_left_handed);
1800 this->set_mesh(std::move(mesh));
1801 TriangleMesh convex_hull = this->get_convex_hull();
1802 convex_hull.transform(mesh_trafo, fix_left_handed);
1803 this->m_convex_hull = std::make_shared<TriangleMesh>(std::move(convex_hull));
1804 // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
1805 this->set_new_unique_id();
1806 }
1807
transform_this_mesh(const Matrix3d & matrix,bool fix_left_handed)1808 void ModelVolume::transform_this_mesh(const Matrix3d &matrix, bool fix_left_handed)
1809 {
1810 TriangleMesh mesh = this->mesh();
1811 mesh.transform(matrix, fix_left_handed);
1812 this->set_mesh(std::move(mesh));
1813 TriangleMesh convex_hull = this->get_convex_hull();
1814 convex_hull.transform(matrix, fix_left_handed);
1815 this->m_convex_hull = std::make_shared<TriangleMesh>(std::move(convex_hull));
1816 // Let the rest of the application know that the geometry changed, so the meshes have to be reloaded.
1817 this->set_new_unique_id();
1818 }
1819
convert_from_imperial_units()1820 void ModelVolume::convert_from_imperial_units()
1821 {
1822 double in_to_mm = 25.4;
1823 this->scale_geometry_after_creation(Vec3d(in_to_mm, in_to_mm, in_to_mm));
1824 this->set_offset(Vec3d(0, 0, 0));
1825 this->source.is_converted_from_inches = true;
1826 }
1827
transform_mesh(TriangleMesh * mesh,bool dont_translate) const1828 void ModelInstance::transform_mesh(TriangleMesh* mesh, bool dont_translate) const
1829 {
1830 mesh->transform(get_matrix(dont_translate));
1831 }
1832
transform_mesh_bounding_box(const TriangleMesh & mesh,bool dont_translate) const1833 BoundingBoxf3 ModelInstance::transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate) const
1834 {
1835 // Rotate around mesh origin.
1836 TriangleMesh copy(mesh);
1837 copy.transform(get_matrix(true, false, true, true));
1838 BoundingBoxf3 bbox = copy.bounding_box();
1839
1840 if (!empty(bbox)) {
1841 // Scale the bounding box along the three axes.
1842 for (unsigned int i = 0; i < 3; ++i)
1843 {
1844 if (std::abs(get_scaling_factor((Axis)i)-1.0) > EPSILON)
1845 {
1846 bbox.min(i) *= get_scaling_factor((Axis)i);
1847 bbox.max(i) *= get_scaling_factor((Axis)i);
1848 }
1849 }
1850
1851 // Translate the bounding box.
1852 if (! dont_translate) {
1853 bbox.min += get_offset();
1854 bbox.max += get_offset();
1855 }
1856 }
1857 return bbox;
1858 }
1859
transform_bounding_box(const BoundingBoxf3 & bbox,bool dont_translate) const1860 BoundingBoxf3 ModelInstance::transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate) const
1861 {
1862 return bbox.transformed(get_matrix(dont_translate));
1863 }
1864
transform_vector(const Vec3d & v,bool dont_translate) const1865 Vec3d ModelInstance::transform_vector(const Vec3d& v, bool dont_translate) const
1866 {
1867 return get_matrix(dont_translate) * v;
1868 }
1869
transform_polygon(Polygon * polygon) const1870 void ModelInstance::transform_polygon(Polygon* polygon) const
1871 {
1872 // CHECK_ME -> Is the following correct or it should take in account all three rotations ?
1873 polygon->rotate(get_rotation(Z)); // rotate around polygon origin
1874 // CHECK_ME -> Is the following correct ?
1875 polygon->scale(get_scaling_factor(X), get_scaling_factor(Y)); // scale around polygon origin
1876 }
1877
get_arrange_polygon() const1878 arrangement::ArrangePolygon ModelInstance::get_arrange_polygon() const
1879 {
1880 // static const double SIMPLIFY_TOLERANCE_MM = 0.1;
1881
1882 Vec3d rotation = get_rotation();
1883 rotation.z() = 0.;
1884 Transform3d trafo_instance =
1885 Geometry::assemble_transform(Vec3d::Zero(), rotation,
1886 get_scaling_factor(), get_mirror());
1887
1888 Polygon p = get_object()->convex_hull_2d(trafo_instance);
1889
1890 assert(!p.points.empty());
1891
1892 // if (!p.points.empty()) {
1893 // Polygons pp{p};
1894 // pp = p.simplify(scaled<double>(SIMPLIFY_TOLERANCE_MM));
1895 // if (!pp.empty()) p = pp.front();
1896 // }
1897
1898 arrangement::ArrangePolygon ret;
1899 ret.poly.contour = std::move(p);
1900 ret.translation = Vec2crd{scaled(get_offset(X)), scaled(get_offset(Y))};
1901 ret.rotation = get_rotation(Z);
1902
1903 return ret;
1904 }
1905
get_facets(const ModelVolume & mv,EnforcerBlockerType type) const1906 indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, EnforcerBlockerType type) const
1907 {
1908 TriangleSelector selector(mv.mesh());
1909 selector.deserialize(m_data);
1910 indexed_triangle_set out = selector.get_facets(type);
1911 return out;
1912 }
1913
set(const TriangleSelector & selector)1914 bool FacetsAnnotation::set(const TriangleSelector& selector)
1915 {
1916 std::map<int, std::vector<bool>> sel_map = selector.serialize();
1917 if (sel_map != m_data) {
1918 m_data = sel_map;
1919 this->touch();
1920 return true;
1921 }
1922 return false;
1923 }
1924
clear()1925 void FacetsAnnotation::clear()
1926 {
1927 m_data.clear();
1928 this->reset_timestamp();
1929 }
1930
1931 // Following function takes data from a triangle and encodes it as string
1932 // of hexadecimal numbers (one digit per triangle). Used for 3MF export,
1933 // changing it may break backwards compatibility !!!!!
get_triangle_as_string(int triangle_idx) const1934 std::string FacetsAnnotation::get_triangle_as_string(int triangle_idx) const
1935 {
1936 std::string out;
1937
1938 auto triangle_it = m_data.find(triangle_idx);
1939 if (triangle_it != m_data.end()) {
1940 const std::vector<bool>& code = triangle_it->second;
1941 int offset = 0;
1942 while (offset < int(code.size())) {
1943 int next_code = 0;
1944 for (int i=3; i>=0; --i) {
1945 next_code = next_code << 1;
1946 next_code |= int(code[offset + i]);
1947 }
1948 offset += 4;
1949
1950 assert(next_code >=0 && next_code <= 15);
1951 char digit = next_code < 10 ? next_code + '0' : (next_code-10)+'A';
1952 out.insert(out.begin(), digit);
1953 }
1954 }
1955 return out;
1956 }
1957
1958 // Recover triangle splitting & state from string of hexadecimal values previously
1959 // generated by get_triangle_as_string. Used to load from 3MF.
set_triangle_from_string(int triangle_id,const std::string & str)1960 void FacetsAnnotation::set_triangle_from_string(int triangle_id, const std::string& str)
1961 {
1962 assert(! str.empty());
1963 m_data[triangle_id] = std::vector<bool>(); // zero current state or create new
1964 std::vector<bool>& code = m_data[triangle_id];
1965
1966 for (auto it = str.crbegin(); it != str.crend(); ++it) {
1967 const char ch = *it;
1968 int dec = 0;
1969 if (ch >= '0' && ch<='9')
1970 dec = int(ch - '0');
1971 else if (ch >='A' && ch <= 'F')
1972 dec = 10 + int(ch - 'A');
1973 else
1974 assert(false);
1975
1976 // Convert to binary and append into code.
1977 for (int i=0; i<4; ++i) {
1978 code.insert(code.end(), bool(dec & (1 << i)));
1979 }
1980 }
1981 }
1982
1983 // Test whether the two models contain the same number of ModelObjects with the same set of IDs
1984 // ordered in the same order. In that case it is not necessary to kill the background processing.
model_object_list_equal(const Model & model_old,const Model & model_new)1985 bool model_object_list_equal(const Model &model_old, const Model &model_new)
1986 {
1987 if (model_old.objects.size() != model_new.objects.size())
1988 return false;
1989 for (size_t i = 0; i < model_old.objects.size(); ++ i)
1990 if (model_old.objects[i]->id() != model_new.objects[i]->id())
1991 return false;
1992 return true;
1993 }
1994
1995 // Test whether the new model is just an extension of the old model (new objects were added
1996 // to the end of the original list. In that case it is not necessary to kill the background processing.
model_object_list_extended(const Model & model_old,const Model & model_new)1997 bool model_object_list_extended(const Model &model_old, const Model &model_new)
1998 {
1999 if (model_old.objects.size() >= model_new.objects.size())
2000 return false;
2001 for (size_t i = 0; i < model_old.objects.size(); ++ i)
2002 if (model_old.objects[i]->id() != model_new.objects[i]->id())
2003 return false;
2004 return true;
2005 }
2006
model_volume_list_changed(const ModelObject & model_object_old,const ModelObject & model_object_new,const ModelVolumeType type)2007 bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type)
2008 {
2009 size_t i_old, i_new;
2010 for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) {
2011 const ModelVolume &mv_old = *model_object_old.volumes[i_old];
2012 const ModelVolume &mv_new = *model_object_new.volumes[i_new];
2013 if (mv_old.type() != type) {
2014 ++ i_old;
2015 continue;
2016 }
2017 if (mv_new.type() != type) {
2018 ++ i_new;
2019 continue;
2020 }
2021 if (mv_old.id() != mv_new.id())
2022 return true;
2023 //FIXME test for the content of the mesh!
2024
2025 if (!mv_old.get_matrix().isApprox(mv_new.get_matrix()))
2026 return true;
2027
2028 ++ i_old;
2029 ++ i_new;
2030 }
2031 for (; i_old < model_object_old.volumes.size(); ++ i_old) {
2032 const ModelVolume &mv_old = *model_object_old.volumes[i_old];
2033 if (mv_old.type() == type)
2034 // ModelVolume was deleted.
2035 return true;
2036 }
2037 for (; i_new < model_object_new.volumes.size(); ++ i_new) {
2038 const ModelVolume &mv_new = *model_object_new.volumes[i_new];
2039 if (mv_new.type() == type)
2040 // ModelVolume was added.
2041 return true;
2042 }
2043 return false;
2044 }
2045
model_custom_supports_data_changed(const ModelObject & mo,const ModelObject & mo_new)2046 bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
2047 assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
2048 assert(mo.volumes.size() == mo_new.volumes.size());
2049 for (size_t i=0; i<mo.volumes.size(); ++i) {
2050 if (! mo_new.volumes[i]->supported_facets.timestamp_matches(mo.volumes[i]->supported_facets))
2051 return true;
2052 }
2053 return false;
2054 }
2055
model_custom_seam_data_changed(const ModelObject & mo,const ModelObject & mo_new)2056 bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
2057 assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
2058 assert(mo.volumes.size() == mo_new.volumes.size());
2059 for (size_t i=0; i<mo.volumes.size(); ++i) {
2060 if (! mo_new.volumes[i]->seam_facets.timestamp_matches(mo.volumes[i]->seam_facets))
2061 return true;
2062 }
2063 return false;
2064 }
2065
model_has_multi_part_objects(const Model & model)2066 extern bool model_has_multi_part_objects(const Model &model)
2067 {
2068 for (const ModelObject *model_object : model.objects)
2069 if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part())
2070 return true;
2071 return false;
2072 }
2073
model_has_advanced_features(const Model & model)2074 extern bool model_has_advanced_features(const Model &model)
2075 {
2076 auto config_is_advanced = [](const ModelConfig &config) {
2077 return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder"));
2078 };
2079 for (const ModelObject *model_object : model.objects) {
2080 // Is there more than one instance or advanced config data?
2081 if (model_object->instances.size() > 1 || config_is_advanced(model_object->config))
2082 return true;
2083 // Is there any modifier or advanced config data?
2084 for (const ModelVolume* model_volume : model_object->volumes)
2085 if (! model_volume->is_model_part() || config_is_advanced(model_volume->config))
2086 return true;
2087 }
2088 return false;
2089 }
2090
2091 #ifndef NDEBUG
2092 // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
check_model_ids_validity(const Model & model)2093 void check_model_ids_validity(const Model &model)
2094 {
2095 std::set<ObjectID> ids;
2096 auto check = [&ids](ObjectID id) {
2097 assert(id.valid());
2098 assert(ids.find(id) == ids.end());
2099 ids.insert(id);
2100 };
2101 for (const ModelObject *model_object : model.objects) {
2102 check(model_object->id());
2103 check(model_object->config.id());
2104 for (const ModelVolume *model_volume : model_object->volumes) {
2105 check(model_volume->id());
2106 check(model_volume->config.id());
2107 }
2108 for (const ModelInstance *model_instance : model_object->instances)
2109 check(model_instance->id());
2110 }
2111 for (const auto mm : model.materials) {
2112 check(mm.second->id());
2113 check(mm.second->config.id());
2114 }
2115 }
2116
check_model_ids_equal(const Model & model1,const Model & model2)2117 void check_model_ids_equal(const Model &model1, const Model &model2)
2118 {
2119 // Verify whether the IDs of model1 and model match.
2120 assert(model1.objects.size() == model2.objects.size());
2121 for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) {
2122 const ModelObject &model_object1 = *model1.objects[idx_model];
2123 const ModelObject &model_object2 = * model2.objects[idx_model];
2124 assert(model_object1.id() == model_object2.id());
2125 assert(model_object1.config.id() == model_object2.config.id());
2126 assert(model_object1.volumes.size() == model_object2.volumes.size());
2127 assert(model_object1.instances.size() == model_object2.instances.size());
2128 for (size_t i = 0; i < model_object1.volumes.size(); ++ i) {
2129 assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
2130 assert(model_object1.volumes[i]->config.id() == model_object2.volumes[i]->config.id());
2131 }
2132 for (size_t i = 0; i < model_object1.instances.size(); ++ i)
2133 assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
2134 }
2135 assert(model1.materials.size() == model2.materials.size());
2136 {
2137 auto it1 = model1.materials.begin();
2138 auto it2 = model2.materials.begin();
2139 for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
2140 assert(it1->first == it2->first); // compare keys
2141 assert(it1->second->id() == it2->second->id());
2142 assert(it1->second->config.id() == it2->second->config.id());
2143 }
2144 }
2145 }
2146
2147 #endif /* NDEBUG */
2148
2149 }
2150
2151 #if 0
2152 CEREAL_REGISTER_TYPE(Slic3r::ModelObject)
2153 CEREAL_REGISTER_TYPE(Slic3r::ModelVolume)
2154 CEREAL_REGISTER_TYPE(Slic3r::ModelInstance)
2155 CEREAL_REGISTER_TYPE(Slic3r::Model)
2156
2157 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelObject)
2158 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelVolume)
2159 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::ModelInstance)
2160 CEREAL_REGISTER_POLYMORPHIC_RELATION(Slic3r::ObjectBase, Slic3r::Model)
2161 #endif
2162