1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
|
#ifndef slic3r_Model_hpp_
#define slic3r_Model_hpp_
#include "libslic3r.h"
#include "Geometry.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
#include "PrintConfig.hpp"
#include "Slicing.hpp"
#include "SLA/SupportPoint.hpp"
#include "SLA/Hollowing.hpp"
#include "TriangleMesh.hpp"
#include "Arrange.hpp"
#include "CustomGCode.hpp"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <chrono>
namespace cereal {
class BinaryInputArchive;
class BinaryOutputArchive;
template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr);
template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr);
template <class T> void load_by_value(BinaryInputArchive &ar, T &obj);
template <class T> void save_by_value(BinaryOutputArchive &ar, const T &obj);
}
namespace Slic3r {
class Model;
class ModelInstance;
class ModelMaterial;
class ModelObject;
class ModelVolume;
class ModelWipeTower;
class Print;
class SLAPrint;
class TriangleSelector;
namespace UndoRedo {
class StackImpl;
}
class ModelConfig : public ObjectBase, public DynamicPrintConfig
{
private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
friend class ModelObject;
friend class ModelVolume;
friend class ModelMaterial;
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit ModelConfig() {}
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit ModelConfig(int) : ObjectBase(-1) {}
// Copy constructor copies the ID.
explicit ModelConfig(const ModelConfig &cfg) : ObjectBase(-1), DynamicPrintConfig(cfg) { this->copy_id(cfg); }
// Move constructor copies the ID.
explicit ModelConfig(ModelConfig &&cfg) : ObjectBase(-1), DynamicPrintConfig(std::move(cfg)) { this->copy_id(cfg); }
ModelConfig& operator=(const ModelConfig &rhs) = default;
ModelConfig& operator=(ModelConfig &&rhs) = default;
template<class Archive> void serialize(Archive &ar) {
ar(cereal::base_class<DynamicPrintConfig>(this));
}
};
namespace Internal {
template<typename T>
class StaticSerializationWrapper
{
public:
StaticSerializationWrapper(T &wrap) : wrapped(wrap) {}
private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
template<class Archive> void load(Archive &ar) { cereal::load_by_value(ar, wrapped); }
template<class Archive> void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); }
T& wrapped;
};
}
typedef std::string t_model_material_id;
typedef std::string t_model_material_attribute;
typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
typedef std::map<t_model_material_id, ModelMaterial*> ModelMaterialMap;
typedef std::vector<ModelObject*> ModelObjectPtrs;
typedef std::vector<ModelVolume*> ModelVolumePtrs;
typedef std::vector<ModelInstance*> ModelInstancePtrs;
#define OBJECTBASE_DERIVED_COPY_MOVE_CLONE(TYPE) \
/* Copy a model, copy the IDs. The Print::apply() will call the TYPE::copy() method */ \
/* to make a private copy for background processing. */ \
static TYPE* new_copy(const TYPE &rhs) { auto *ret = new TYPE(rhs); assert(ret->id() == rhs.id()); return ret; } \
static TYPE* new_copy(TYPE &&rhs) { auto *ret = new TYPE(std::move(rhs)); assert(ret->id() == rhs.id()); return ret; } \
static TYPE make_copy(const TYPE &rhs) { TYPE ret(rhs); assert(ret.id() == rhs.id()); return ret; } \
static TYPE make_copy(TYPE &&rhs) { TYPE ret(std::move(rhs)); assert(ret.id() == rhs.id()); return ret; } \
TYPE& assign_copy(const TYPE &rhs); \
TYPE& assign_copy(TYPE &&rhs); \
/* Copy a TYPE, generate new IDs. The front end will use this call. */ \
static TYPE* new_clone(const TYPE &rhs) { \
/* Default constructor assigning an invalid ID. */ \
auto obj = new TYPE(-1); \
obj->assign_clone(rhs); \
assert(obj->id().valid() && obj->id() != rhs.id()); \
return obj; \
} \
TYPE make_clone(const TYPE &rhs) { \
/* Default constructor assigning an invalid ID. */ \
TYPE obj(-1); \
obj.assign_clone(rhs); \
assert(obj.id().valid() && obj.id() != rhs.id()); \
return obj; \
} \
TYPE& assign_clone(const TYPE &rhs) { \
this->assign_copy(rhs); \
assert(this->id().valid() && this->id() == rhs.id()); \
this->assign_new_unique_ids_recursive(); \
assert(this->id().valid() && this->id() != rhs.id()); \
return *this; \
}
// Material, which may be shared across multiple ModelObjects of a single Model.
class ModelMaterial final : public ObjectBase
{
public:
// Attributes are defined by the AMF file format, but they don't seem to be used by Slic3r for any purpose.
t_model_material_attributes attributes;
// Dynamic configuration storage for the object specific configuration values, overriding the global configuration.
ModelConfig config;
Model* get_model() const { return m_model; }
void apply(const t_model_material_attributes &attributes)
{ this->attributes.insert(attributes.begin(), attributes.end()); }
private:
// Parent, owning this material.
Model *m_model;
// To be accessed by the Model.
friend class Model;
// Constructor, which assigns a new unique ID to the material and to its config.
ModelMaterial(Model *model) : m_model(model) { assert(this->id().valid()); }
// Copy constructor copies the IDs of the ModelMaterial and its config, and m_model!
ModelMaterial(const ModelMaterial &rhs) = default;
void set_model(Model *model) { m_model = model; }
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
// To be accessed by the serialization and Undo/Redo code.
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Create an object for deserialization, don't allocate IDs for ModelMaterial and its config.
ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
template<class Archive> void serialize(Archive &ar) {
assert(this->id().invalid()); assert(this->config.id().invalid());
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
ar(attributes, config_wrapper);
// assert(this->id().valid()); assert(this->config.id().valid());
}
// Disabled methods.
ModelMaterial(ModelMaterial &&rhs) = delete;
ModelMaterial& operator=(const ModelMaterial &rhs) = delete;
ModelMaterial& operator=(ModelMaterial &&rhs) = delete;
};
// A printable object, possibly having multiple print volumes (each with its own set of parameters and materials),
// and possibly having multiple modifier volumes, each modifier volume with its set of parameters and materials.
// Each ModelObject may be instantiated mutliple times, each instance having different placement on the print bed,
// different rotation and different uniform scaling.
class ModelObject final : public ObjectBase
{
public:
std::string name;
std::string input_file; // XXX: consider fs::path
// Instances of this ModelObject. Each instance defines a shift on the print bed, rotation around the Z axis and a uniform scaling.
// Instances are owned by this ModelObject.
ModelInstancePtrs instances;
// Printable and modifier volumes, each with its material ID and a set of override parameters.
// ModelVolumes are owned by this ModelObject.
ModelVolumePtrs volumes;
// Configuration parameters specific to a single ModelObject, overriding the global Slic3r settings.
ModelConfig config;
// Variation of a layer thickness for spans of Z coordinates + optional parameter overrides.
t_layer_config_ranges layer_config_ranges;
// Profile of increasing z to a layer height, to be linearly interpolated when calculating the layers.
// The pairs of <z, layer_height> are packed into a 1D array.
std::vector<coordf_t> layer_height_profile;
// Whether or not this object is printable
bool printable;
// This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances.
// The format is (x, y, z, point_size, supports_island)
sla::SupportPoints sla_support_points;
// To keep track of where the points came from (used for synchronization between
// the SLA gizmo and the backend).
sla::PointsStatus sla_points_status = sla::PointsStatus::NoPoints;
// Holes to be drilled into the object so resin can flow out
sla::DrainHoles sla_drain_holes;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
to new volumes before adding them to this object in order to preserve alignment
when user expects that. */
Vec3d origin_translation;
Model* get_model() { return m_model; }
const Model* get_model() const { return m_model; }
ModelVolume* add_volume(const TriangleMesh &mesh);
ModelVolume* add_volume(TriangleMesh &&mesh);
ModelVolume* add_volume(const ModelVolume &volume);
ModelVolume* add_volume(const ModelVolume &volume, TriangleMesh &&mesh);
void delete_volume(size_t idx);
void clear_volumes();
bool is_multiparts() const { return volumes.size() > 1; }
ModelInstance* add_instance();
ModelInstance* add_instance(const ModelInstance &instance);
ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation, const Vec3d &mirror);
void delete_instance(size_t idx);
void delete_last_instance();
void clear_instances();
// Returns the bounding box of the transformed instances.
// This bounding box is approximate and not snug.
// This bounding box is being cached.
const BoundingBoxf3& bounding_box() const;
void invalidate_bounding_box() { m_bounding_box_valid = false; m_raw_bounding_box_valid = false; m_raw_mesh_bounding_box_valid = false; }
// A mesh containing all transformed instances of this object.
TriangleMesh mesh() const;
// Non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
// Currently used by ModelObject::mesh() and to calculate the 2D envelope for 2D plater.
TriangleMesh raw_mesh() const;
// Non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
TriangleMesh full_raw_mesh() const;
// A transformed snug bounding box around the non-modifier object volumes, without the translation applied.
// This bounding box is only used for the actual slicing.
const BoundingBoxf3& raw_bounding_box() const;
// A snug bounding box around the transformed non-modifier object volumes.
BoundingBoxf3 instance_bounding_box(size_t instance_idx, bool dont_translate = false) const;
// A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of non-modifier object volumes.
const BoundingBoxf3& raw_mesh_bounding_box() const;
// A snug bounding box of non-transformed (non-rotated, non-scaled, non-translated) sum of all object volumes.
BoundingBoxf3 full_raw_mesh_bounding_box() const;
// Calculate 2D convex hull of of a projection of the transformed printable volumes into the XY plane.
// This method is cheap in that it does not make any unnecessary copy of the volume meshes.
// This method is used by the auto arrange function.
Polygon convex_hull_2d(const Transform3d &trafo_instance) const;
void center_around_origin(bool include_modifiers = true);
void ensure_on_bed();
void translate_instances(const Vec3d& vector);
void translate_instance(size_t instance_idx, const Vec3d& vector);
void translate(const Vec3d &vector) { this->translate(vector(0), vector(1), vector(2)); }
void translate(double x, double y, double z);
void scale(const Vec3d &versor);
void scale(const double s) { this->scale(Vec3d(s, s, s)); }
void scale(double x, double y, double z) { this->scale(Vec3d(x, y, z)); }
/// Scale the current ModelObject to fit by altering the scaling factor of ModelInstances.
/// It operates on the total size by duplicating the object according to all the instances.
/// \param size Sizef3 the size vector
void scale_to_fit(const Vec3d &size);
void rotate(double angle, Axis axis);
void rotate(double angle, const Vec3d& axis);
void mirror(Axis axis);
// This method could only be called before the meshes of this ModelVolumes are not shared!
void scale_mesh_after_creation(const Vec3d& versor);
void convert_units(ModelObjectPtrs&new_objects, bool from_imperial, std::vector<int> volume_idxs);
size_t materials_count() const;
size_t facets_count() const;
bool needed_repair() const;
ModelObjectPtrs cut(size_t instance, coordf_t z, bool keep_upper = true, bool keep_lower = true, bool rotate_lower = false); // Note: z is in world coordinates
void split(ModelObjectPtrs* new_objects);
void merge();
// Support for non-uniform scaling of instances. If an instance is rotated by angles, which are not multiples of ninety degrees,
// then the scaling in world coordinate system is not representable by the Geometry::Transformation structure.
// This situation is solved by baking in the instance transformation into the mesh vertices.
// Rotation and mirroring is being baked in. In case the instance scaling was non-uniform, it is baked in as well.
void bake_xy_rotation_into_meshes(size_t instance_idx);
double get_min_z() const;
double get_instance_min_z(size_t instance_idx) const;
// Called by Print::validate() from the UI thread.
unsigned int check_instances_print_volume_state(const BoundingBoxf3& print_volume);
// Print object statistics to console.
void print_info() const;
std::string get_export_filename() const;
// Get full stl statistics for all object's meshes
stl_stats get_object_stl_stats() const;
// Get count of errors in the mesh( or all object's meshes, if volume index isn't defined)
int get_mesh_errors_count(const int vol_idx = -1) const;
private:
friend class Model;
// This constructor assigns new ID to this ModelObject and its config.
explicit ModelObject(Model* model) : m_model(model), printable(true), origin_translation(Vec3d::Zero()),
m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{ assert(this->id().valid()); }
explicit ModelObject(int) : ObjectBase(-1), config(-1), m_model(nullptr), printable(true), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false)
{ assert(this->id().invalid()); assert(this->config.id().invalid()); }
~ModelObject();
void assign_new_unique_ids_recursive() override;
// To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision"
// (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics).
ModelObject(const ModelObject &rhs) : ObjectBase(-1), config(-1), m_model(rhs.m_model) {
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
this->assign_copy(rhs);
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
}
explicit ModelObject(ModelObject &&rhs) : ObjectBase(-1), config(-1) {
assert(this->id().invalid()); assert(this->config.id().invalid()); assert(rhs.id() != rhs.config.id());
this->assign_copy(std::move(rhs));
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
}
ModelObject& operator=(const ModelObject &rhs) {
this->assign_copy(rhs);
m_model = rhs.m_model;
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
return *this;
}
ModelObject& operator=(ModelObject &&rhs) {
this->assign_copy(std::move(rhs));
m_model = rhs.m_model;
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == rhs.id()); assert(this->config.id() == rhs.config.id());
return *this;
}
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(ModelObject)
// Parent object, owning this ModelObject. Set to nullptr here, so the macros above will have it initialized.
Model *m_model = nullptr;
// Bounding box, cached.
mutable BoundingBoxf3 m_bounding_box;
mutable bool m_bounding_box_valid;
mutable BoundingBoxf3 m_raw_bounding_box;
mutable bool m_raw_bounding_box_valid;
mutable BoundingBoxf3 m_raw_mesh_bounding_box;
mutable bool m_raw_mesh_bounding_box_valid;
// Called by Print::apply() to set the model pointer after making a copy.
friend class Print;
friend class SLAPrint;
void set_model(Model *model) { m_model = model; }
// Undo / Redo through the cereal serialization library
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization -> Don't allocate any IDs for the ModelObject or its config.
ModelObject() : ObjectBase(-1), config(-1), m_model(nullptr), m_bounding_box_valid(false), m_raw_bounding_box_valid(false), m_raw_mesh_bounding_box_valid(false) {
assert(this->id().invalid()); assert(this->config.id().invalid());
}
template<class Archive> void serialize(Archive &ar) {
ar(cereal::base_class<ObjectBase>(this));
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, sla_drain_holes, printable, origin_translation,
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
}
};
// Declared outside of ModelVolume, so it could be forward declared.
enum class ModelVolumeType : int {
INVALID = -1,
MODEL_PART = 0,
PARAMETER_MODIFIER,
SUPPORT_ENFORCER,
SUPPORT_BLOCKER,
};
enum class FacetSupportType : int8_t {
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
NONE = 0,
ENFORCER = 1,
BLOCKER = 2
};
class FacetsAnnotation {
public:
using ClockType = std::chrono::steady_clock;
const std::map<int, std::vector<bool>>& get_data() const { return m_data; }
bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, FacetSupportType type) const;
void clear();
std::string get_triangle_as_string(int i) const;
void set_triangle_from_string(int triangle_id, const std::string& str);
ClockType::time_point get_timestamp() const { return timestamp; }
bool is_same_as(const FacetsAnnotation& other) const {
return timestamp == other.get_timestamp();
}
template<class Archive> void serialize(Archive &ar)
{
ar(m_data);
}
private:
std::map<int, std::vector<bool>> m_data;
ClockType::time_point timestamp;
void update_timestamp() {
timestamp = ClockType::now();
}
};
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
// ModelVolume instances are owned by a ModelObject.
class ModelVolume final : public ObjectBase
{
public:
std::string name;
// struct used by reload from disk command to recover data from disk
struct Source
{
std::string input_file;
int object_idx{ -1 };
int volume_idx{ -1 };
Vec3d mesh_offset{ Vec3d::Zero() };
Geometry::Transformation transform;
template<class Archive> void serialize(Archive& ar) { ar(input_file, object_idx, volume_idx, mesh_offset, transform); }
};
Source source;
// The triangular model.
const TriangleMesh& mesh() const { return *m_mesh.get(); }
void set_mesh(const TriangleMesh &mesh) { m_mesh = std::make_shared<const TriangleMesh>(mesh); }
void set_mesh(TriangleMesh &&mesh) { m_mesh = std::make_shared<const TriangleMesh>(std::move(mesh)); }
void set_mesh(std::shared_ptr<const TriangleMesh> &mesh) { m_mesh = mesh; }
void set_mesh(std::unique_ptr<const TriangleMesh> &&mesh) { m_mesh = std::move(mesh); }
void reset_mesh() { m_mesh = std::make_shared<const TriangleMesh>(); }
// Configuration parameters specific to an object model geometry or a modifier volume,
// overriding the global Slic3r settings and the ModelObject settings.
ModelConfig config;
// List of mesh facets to be supported/unsupported.
FacetsAnnotation m_supported_facets;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
void set_type(const ModelVolumeType t) { m_type = t; }
bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; }
bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; }
bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
t_model_material_id material_id() const { return m_material_id; }
void set_material_id(t_model_material_id material_id);
ModelMaterial* material() const;
void set_material(t_model_material_id material_id, const ModelMaterial &material);
// Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config.
// Extruder ID is only valid for FFF. Returns -1 for SLA or if the extruder ID is not applicable (support volumes).
int extruder_id() const;
bool is_splittable() const;
// Split this volume, append the result to the object owning this volume.
// Return the number of volumes created from this one.
// This is useful to assign different materials to different volumes of an object.
size_t split(unsigned int max_extruders);
void translate(double x, double y, double z) { translate(Vec3d(x, y, z)); }
void translate(const Vec3d& displacement);
void scale(const Vec3d& scaling_factors);
void scale(double x, double y, double z) { scale(Vec3d(x, y, z)); }
void scale(double s) { scale(Vec3d(s, s, s)); }
void rotate(double angle, Axis axis);
void rotate(double angle, const Vec3d& axis);
void mirror(Axis axis);
// This method could only be called before the meshes of this ModelVolumes are not shared!
void scale_geometry_after_creation(const Vec3d& versor);
// Translates the mesh and the convex hull so that the origin of their vertices is in the center of this volume's bounding box.
// Attention! This method may only be called just after ModelVolume creation! It must not be called once the TriangleMesh of this ModelVolume is shared!
void center_geometry_after_creation(bool update_source_offset = true);
void calculate_convex_hull();
const TriangleMesh& get_convex_hull() const;
std::shared_ptr<const TriangleMesh> get_convex_hull_shared_ptr() const { return m_convex_hull; }
// Get count of errors in the mesh
int get_mesh_errors_count() const;
// Helpers for loading / storing into AMF / 3MF files.
static ModelVolumeType type_from_string(const std::string &s);
static std::string type_to_string(const ModelVolumeType t);
const Geometry::Transformation& get_transformation() const { return m_transformation; }
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
void set_transformation(const Transform3d &trafo) { m_transformation.set_from_transform(trafo); }
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
Vec3d get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
void set_new_unique_id() { ObjectBase::set_new_unique_id(); this->config.set_new_unique_id(); }
protected:
friend class Print;
friend class SLAPrint;
friend class Model;
friend class ModelObject;
// Copies IDs of both the ModelVolume and its config.
explicit ModelVolume(const ModelVolume &rhs) = default;
void set_model_object(ModelObject *model_object) { object = model_object; }
void assign_new_unique_ids_recursive() override { ObjectBase::set_new_unique_id(); config.set_new_unique_id(); }
void transform_this_mesh(const Transform3d& t, bool fix_left_handed);
void transform_this_mesh(const Matrix3d& m, bool fix_left_handed);
private:
// Parent object owning this ModelVolume.
ModelObject* object;
// The triangular model.
std::shared_ptr<const TriangleMesh> m_mesh;
// Is it an object to be printed, or a modifier volume?
ModelVolumeType m_type;
t_model_material_id m_material_id;
// The convex hull of this model's mesh.
std::shared_ptr<const TriangleMesh> m_convex_hull;
Geometry::Transformation m_transformation;
// flag to optimize the checking if the volume is splittable
// -1 -> is unknown value (before first cheking)
// 0 -> is not splittable
// 1 -> is splittable
mutable int m_is_splittable{ -1 };
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : m_mesh(new TriangleMesh(mesh)), m_type(ModelVolumeType::MODEL_PART), object(object)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
}
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other) :
ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
m_supported_facets(other.m_supported_facets)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() == other.id() && this->config.id() == other.config.id());
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) :
name(other.name), source(other.source), m_mesh(new TriangleMesh(std::move(mesh))), config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation)
{
assert(this->id().valid()); assert(this->config.id().valid()); assert(this->id() != this->config.id());
assert(this->id() != other.id() && this->config.id() == other.config.id());
this->set_material_id(other.material_id());
this->config.set_new_unique_id();
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
assert(this->config.id().valid()); assert(this->config.id() != other.config.id()); assert(this->id() != this->config.id());
m_supported_facets.clear();
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) {
assert(this->id().invalid()); assert(this->config.id().invalid());
}
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation,
m_is_splittable, has_convex_hull, m_supported_facets);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
cereal::load_optional(ar, m_convex_hull);
if (! m_convex_hull && ! m_mesh->empty())
// The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
this->calculate_convex_hull();
} else
m_convex_hull.reset();
}
template<class Archive> void save(Archive &ar) const {
bool has_convex_hull = m_convex_hull.get() != nullptr;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation,
m_is_splittable, has_convex_hull, m_supported_facets);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
}
};
enum ModelInstanceEPrintVolumeState : unsigned char
{
ModelInstancePVS_Inside,
ModelInstancePVS_Partly_Outside,
ModelInstancePVS_Fully_Outside,
ModelInstanceNum_BedStates
};
// A single instance of a ModelObject.
// Knows the affine transformation of an object.
class ModelInstance final : public ObjectBase
{
private:
Geometry::Transformation m_transformation;
public:
// flag showing the position of this instance with respect to the print volume (set by Print::validate() using ModelObject::check_instances_print_volume_state())
ModelInstanceEPrintVolumeState print_volume_state;
// Whether or not this instance is printable
bool printable;
ModelObject* get_object() const { return this->object; }
const Geometry::Transformation& get_transformation() const { return m_transformation; }
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
const Vec3d& get_offset() const { return m_transformation.get_offset(); }
double get_offset(Axis axis) const { return m_transformation.get_offset(axis); }
void set_offset(const Vec3d& offset) { m_transformation.set_offset(offset); }
void set_offset(Axis axis, double offset) { m_transformation.set_offset(axis, offset); }
const Vec3d& get_rotation() const { return m_transformation.get_rotation(); }
double get_rotation(Axis axis) const { return m_transformation.get_rotation(axis); }
void set_rotation(const Vec3d& rotation) { m_transformation.set_rotation(rotation); }
void set_rotation(Axis axis, double rotation) { m_transformation.set_rotation(axis, rotation); }
const Vec3d& get_scaling_factor() const { return m_transformation.get_scaling_factor(); }
double get_scaling_factor(Axis axis) const { return m_transformation.get_scaling_factor(axis); }
void set_scaling_factor(const Vec3d& scaling_factor) { m_transformation.set_scaling_factor(scaling_factor); }
void set_scaling_factor(Axis axis, double scaling_factor) { m_transformation.set_scaling_factor(axis, scaling_factor); }
const Vec3d& get_mirror() const { return m_transformation.get_mirror(); }
double get_mirror(Axis axis) const { return m_transformation.get_mirror(axis); }
bool is_left_handed() const { return m_transformation.is_left_handed(); }
void set_mirror(const Vec3d& mirror) { m_transformation.set_mirror(mirror); }
void set_mirror(Axis axis, double mirror) { m_transformation.set_mirror(axis, mirror); }
// To be called on an external mesh
void transform_mesh(TriangleMesh* mesh, bool dont_translate = false) const;
// Calculate a bounding box of a transformed mesh. To be called on an external mesh.
BoundingBoxf3 transform_mesh_bounding_box(const TriangleMesh& mesh, bool dont_translate = false) const;
// Transform an external bounding box.
BoundingBoxf3 transform_bounding_box(const BoundingBoxf3 &bbox, bool dont_translate = false) const;
// Transform an external vector.
Vec3d transform_vector(const Vec3d& v, bool dont_translate = false) const;
// To be called on an external polygon. It does not translate the polygon, only rotates and scales.
void transform_polygon(Polygon* polygon) const;
const Transform3d& get_matrix(bool dont_translate = false, bool dont_rotate = false, bool dont_scale = false, bool dont_mirror = false) const { return m_transformation.get_matrix(dont_translate, dont_rotate, dont_scale, dont_mirror); }
bool is_printable() const { return object->printable && printable && (print_volume_state == ModelInstancePVS_Inside); }
// Getting the input polygon for arrange
arrangement::ArrangePolygon get_arrange_polygon() const;
// Apply the arrange result on the ModelInstance
void apply_arrange_result(const Vec2d& offs, double rotation)
{
// write the transformation data into the model instance
set_rotation(Z, rotation);
set_offset(X, unscale<double>(offs(X)));
set_offset(Y, unscale<double>(offs(Y)));
this->object->invalidate_bounding_box();
}
protected:
friend class Print;
friend class SLAPrint;
friend class Model;
friend class ModelObject;
explicit ModelInstance(const ModelInstance &rhs) = default;
void set_model_object(ModelObject *model_object) { object = model_object; }
private:
// Parent object, owning this instance.
ModelObject* object;
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject* object) : print_volume_state(ModelInstancePVS_Inside), printable(true), object(object) { assert(this->id().valid()); }
// Constructor, which assigns a new unique ID.
explicit ModelInstance(ModelObject *object, const ModelInstance &other) :
m_transformation(other.m_transformation), print_volume_state(ModelInstancePVS_Inside), printable(other.printable), object(object) { assert(this->id().valid() && this->id() != other.id()); }
explicit ModelInstance(ModelInstance &&rhs) = delete;
ModelInstance& operator=(const ModelInstance &rhs) = delete;
ModelInstance& operator=(ModelInstance &&rhs) = delete;
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
ModelInstance() : ObjectBase(-1), object(nullptr) { assert(this->id().invalid()); }
template<class Archive> void serialize(Archive &ar) {
ar(m_transformation, print_volume_state, printable);
}
};
class ModelWipeTower final : public ObjectBase
{
public:
Vec2d position;
double rotation;
private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
friend class Model;
// Constructors to be only called by derived classes.
// Default constructor to assign a unique ID.
explicit ModelWipeTower() {}
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
// by an existing ID copied from elsewhere.
explicit ModelWipeTower(int) : ObjectBase(-1) {}
// Copy constructor copies the ID.
explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
// Disabled methods.
ModelWipeTower(ModelWipeTower &&rhs) = delete;
ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete;
ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
// For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
};
// The print bed content.
// Description of a triangular model with multiple materials, multiple instances with various affine transformations
// and with multiple modifier meshes.
// A model groups multiple objects, each object having possibly multiple instances,
// all objects may share mutliple materials.
class Model final : public ObjectBase
{
public:
// Materials are owned by a model and referenced by objects through t_model_material_id.
// Single material may be shared by multiple models.
ModelMaterialMap materials;
// Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
ModelObjectPtrs objects;
// Wipe tower object.
ModelWipeTower wipe_tower;
// Extensions for color print
CustomGCode::Info custom_gcode_per_print_z;
// Default constructor assigns a new ID to the model.
Model() { assert(this->id().valid()); }
~Model() { this->clear_objects(); this->clear_materials(); }
/* To be able to return an object from own copy / clone methods. Hopefully the compiler will do the "Copy elision" */
/* (Omits copy and move(since C++11) constructors, resulting in zero - copy pass - by - value semantics). */
Model(const Model &rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); }
explicit Model(Model &&rhs) : ObjectBase(-1) { assert(this->id().invalid()); this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); }
Model& operator=(const Model &rhs) { this->assign_copy(rhs); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
Model& operator=(Model &&rhs) { this->assign_copy(std::move(rhs)); assert(this->id().valid()); assert(this->id() == rhs.id()); return *this; }
OBJECTBASE_DERIVED_COPY_MOVE_CLONE(Model)
static Model read_from_file(const std::string& input_file, DynamicPrintConfig* config = nullptr, bool add_default_instances = true, bool check_version = false);
static Model read_from_archive(const std::string& input_file, DynamicPrintConfig* config, bool add_default_instances = true, bool check_version = false);
// Add a new ModelObject to this Model, generate a new ID for this ModelObject.
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);
ModelObject* add_object(const ModelObject &other);
void delete_object(size_t idx);
bool delete_object(ObjectID id);
bool delete_object(ModelObject* object);
void clear_objects();
ModelMaterial* add_material(t_model_material_id material_id);
ModelMaterial* add_material(t_model_material_id material_id, const ModelMaterial &other);
ModelMaterial* get_material(t_model_material_id material_id) {
ModelMaterialMap::iterator i = this->materials.find(material_id);
return (i == this->materials.end()) ? nullptr : i->second;
}
void delete_material(t_model_material_id material_id);
void clear_materials();
bool add_default_instances();
// Returns approximate axis aligned bounding box of this model
BoundingBoxf3 bounding_box() const;
// Set the print_volume_state of PrintObject::instances,
// return total number of printable objects.
unsigned int update_print_volume_state(const BoundingBoxf3 &print_volume);
// Returns true if any ModelObject was modified.
bool center_instances_around_point(const Vec2d &point);
void translate(coordf_t x, coordf_t y, coordf_t z) { for (ModelObject *o : this->objects) o->translate(x, y, z); }
TriangleMesh mesh() const;
// Croaks if the duplicated objects do not fit the print bed.
void duplicate_objects_grid(size_t x, size_t y, coordf_t dist);
bool looks_like_multipart_object() const;
void convert_multipart_object(unsigned int max_extruders);
bool looks_like_imperial_units() const;
void convert_from_imperial_units();
// Ensures that the min z of the model is not negative
void adjust_min_z();
void print_info() const { for (const ModelObject *o : this->objects) o->print_info(); }
// Propose an output file name & path based on the first printable object's name and source input file's path.
std::string propose_export_file_name_and_path() const;
// Propose an output path, replace extension. The new_extension shall contain the initial dot.
std::string propose_export_file_name_and_path(const std::string &new_extension) const;
private:
explicit Model(int) : ObjectBase(-1) { assert(this->id().invalid()); }
void assign_new_unique_ids_recursive();
void update_links_bottom_up_recursive();
friend class cereal::access;
friend class UndoRedo::StackImpl;
template<class Archive> void serialize(Archive &ar) {
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
ar(materials, objects, wipe_tower_wrapper);
}
};
#undef OBJECTBASE_DERIVED_COPY_MOVE_CLONE
#undef OBJECTBASE_DERIVED_PRIVATE_COPY_MOVE
// Test whether the two models contain the same number of ModelObjects with the same set of IDs
// ordered in the same order. In that case it is not necessary to kill the background processing.
extern bool model_object_list_equal(const Model &model_old, const Model &model_new);
// Test whether the new model is just an extension of the old model (new objects were added
// to the end of the original list. In that case it is not necessary to kill the background processing.
extern bool model_object_list_extended(const Model &model_old, const Model &model_new);
// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order)
// than the old ModelObject.
extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
// Test whether the now ModelObject has newer custom supports data than the old one.
// The function assumes that volumes list is synchronized.
extern bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
extern bool model_has_multi_part_objects(const Model &model);
// If the model has advanced features, then it cannot be processed in simple mode.
extern bool model_has_advanced_features(const Model &model);
#ifndef NDEBUG
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
void check_model_ids_validity(const Model &model);
void check_model_ids_equal(const Model &model1, const Model &model2);
#endif /* NDEBUG */
} // namespace Slic3r
namespace cereal
{
template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {};
template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::member_serialize> {};
}
#endif /* slic3r_Model_hpp_ */
|