diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 8ced1018d..747edc7d1 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -21,6 +21,8 @@ namespace Slic3r { unsigned int Model::s_auto_extruder_id = 1; +ModelID ModelBase::s_last_id = 0; + Model::Model(const Model &other) { // copy materials @@ -503,6 +505,7 @@ void Model::reset_auto_extruder_id() } ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volumes) : + ModelBase(other), // copy the id name(other.name), input_file(other.input_file), instances(), @@ -518,13 +521,13 @@ ModelObject::ModelObject(Model *model, const ModelObject &other, bool copy_volum { if (copy_volumes) { this->volumes.reserve(other.volumes.size()); - for (ModelVolumePtrs::const_iterator i = other.volumes.begin(); i != other.volumes.end(); ++i) - this->add_volume(**i); + for (ModelVolume *model_volume : other.volumes) + this->add_volume(*model_volume); } this->instances.reserve(other.instances.size()); - for (ModelInstancePtrs::const_iterator i = other.instances.begin(); i != other.instances.end(); ++i) - this->add_instance(**i); + for (const ModelInstance *model_instance : other.instances) + this->add_instance(*model_instance); } ModelObject& ModelObject::operator=(ModelObject other) @@ -535,6 +538,7 @@ ModelObject& ModelObject::operator=(ModelObject other) void ModelObject::swap(ModelObject &other) { + std::swap(this->m_id, other.m_id); std::swap(this->input_file, other.input_file); std::swap(this->instances, other.instances); std::swap(this->volumes, other.volumes); @@ -553,6 +557,13 @@ ModelObject::~ModelObject() this->clear_instances(); } +// Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original. +// Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing. +ModelObject* ModelObject::clone(Model *parent) +{ + return new ModelObject(parent, *this, true); +} + ModelVolume* ModelObject::add_volume(const TriangleMesh &mesh) { ModelVolume* v = new ModelVolume(this, mesh); @@ -903,7 +914,7 @@ void ModelObject::split(ModelObjectPtrs* new_objects) new_volume->name = volume->name; new_volume->config = volume->config; new_volume->set_type(volume->type()); - new_volume->material_id(volume->material_id()); + new_volume->set_material_id(volume->material_id()); new_objects->push_back(new_object); delete mesh; @@ -982,22 +993,22 @@ void ModelObject::print_info() const cout << "volume = " << mesh.volume() << endl; } -void ModelVolume::material_id(t_model_material_id material_id) +void ModelVolume::set_material_id(t_model_material_id material_id) { - this->_material_id = material_id; + m_material_id = material_id; - // ensure this->_material_id references an existing material + // ensure m_material_id references an existing material (void)this->object->get_model()->add_material(material_id); } ModelMaterial* ModelVolume::material() const { - return this->object->get_model()->get_material(this->_material_id); + return this->object->get_model()->get_material(m_material_id); } void ModelVolume::set_material(t_model_material_id material_id, const ModelMaterial &material) { - this->_material_id = material_id; + m_material_id = material_id; (void)this->object->get_model()->add_material(material_id, material); } @@ -1006,8 +1017,8 @@ ModelMaterial* ModelVolume::assign_unique_material() Model* model = this->get_object()->get_model(); // as material-id "0" is reserved by the AMF spec we start from 1 - this->_material_id = 1 + model->materials.size(); // watchout for implicit cast - return model->add_material(this->_material_id); + m_material_id = 1 + model->materials.size(); // watchout for implicit cast + return model->add_material(m_material_id); } void ModelVolume::calculate_convex_hull() diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index 695de6127..4c3c03426 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -20,6 +20,7 @@ class ModelMaterial; class ModelObject; class ModelVolume; class PresetBundle; +class Print; typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; @@ -30,8 +31,27 @@ typedef std::vector ModelObjectPtrs; typedef std::vector ModelVolumePtrs; typedef std::vector ModelInstancePtrs; +// Unique identifier of a Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial. +// Used to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject) +typedef size_t ModelID; + +// Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID +// to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). +class ModelBase +{ +public: + ModelID id() const { return m_id; } + +protected: + ModelID m_id = generate_new_id(); + +private: + static inline ModelID generate_new_id() { return s_last_id ++; } + static ModelID s_last_id; +}; + // Material, which may be shared across multiple ModelObjects of a single Model. -class ModelMaterial +class ModelMaterial : public ModelBase { friend class Model; public: @@ -56,7 +76,7 @@ private: // 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 +class ModelObject : public ModelBase { friend class Model; public: @@ -85,21 +105,21 @@ public: to new volumes before adding them to this object in order to preserve alignment when user expects that. */ Vec3d origin_translation; - - Model* get_model() const { return m_model; }; - - ModelVolume* add_volume(const TriangleMesh &mesh); - ModelVolume* add_volume(TriangleMesh &&mesh); - ModelVolume* add_volume(const ModelVolume &volume); - void delete_volume(size_t idx); - void clear_volumes(); - ModelInstance* add_instance(); - ModelInstance* add_instance(const ModelInstance &instance); - ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation); - void delete_instance(size_t idx); - void delete_last_instance(); - void clear_instances(); + Model* get_model() const { return m_model; }; + + ModelVolume* add_volume(const TriangleMesh &mesh); + ModelVolume* add_volume(TriangleMesh &&mesh); + ModelVolume* add_volume(const ModelVolume &volume); + void delete_volume(size_t idx); + void clear_volumes(); + + ModelInstance* add_instance(); + ModelInstance* add_instance(const ModelInstance &instance); + ModelInstance* add_instance(const Vec3d &offset, const Vec3d &scaling_factor, const Vec3d &rotation); + 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. @@ -137,8 +157,15 @@ public: // Print object statistics to console. void print_info() const; - -private: + +protected: + friend class Print; + // Clone this ModelObject including its volumes and instances, keep the IDs of the copies equal to the original. + // Called by Print::apply() to clone the Model / ModelObject hierarchy to the back end for background processing. + ModelObject* clone(Model *parent); + void set_model(Model *model) { m_model = model; } + +private: ModelObject(Model *model) : layer_height_profile_valid(false), m_model(model), origin_translation(Vec3d::Zero()), m_bounding_box_valid(false) {} ModelObject(Model *model, const ModelObject &other, bool copy_volumes = true); ModelObject& operator= (ModelObject other); @@ -146,7 +173,7 @@ private: ~ModelObject(); // Parent object, owning this ModelObject. - Model *m_model; + Model *m_model; // Bounding box, cached. mutable BoundingBoxf3 m_bounding_box; @@ -155,20 +182,20 @@ private: // 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 +class ModelVolume : public ModelBase { friend class ModelObject; // The convex hull of this model's mesh. - TriangleMesh m_convex_hull; + TriangleMesh m_convex_hull; public: - std::string name; + std::string name; // The triangular model. - TriangleMesh mesh; + TriangleMesh mesh; // Configuration parameters specific to an object model geometry or a modifier volume, // overriding the global Slic3r settings and the ModelObject settings. - DynamicPrintConfig config; + DynamicPrintConfig config; enum Type { MODEL_TYPE_INVALID = -1, @@ -178,6 +205,9 @@ public: SUPPORT_BLOCKER, }; + // Clone this ModelVolume, keep the ID identical, set the parent to the cloned volume. + ModelVolume* clone(ModelObject *parent) { return new ModelVolume(parent, *this); } + // A parent object owning this modifier volume. ModelObject* get_object() const { return this->object; }; Type type() const { return m_type; } @@ -186,8 +216,9 @@ public: bool is_modifier() const { return m_type == PARAMETER_MODIFIER; } bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; } bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; } - t_model_material_id material_id() const { return this->_material_id; } - void material_id(t_model_material_id material_id); + bool is_support_modifier() const { return m_type == SUPPORT_BLOCKER || m_type == 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); // Split this volume, append the result to the object owning this volume. @@ -199,7 +230,7 @@ public: void calculate_convex_hull(); const TriangleMesh& get_convex_hull() const; - TriangleMesh& get_convex_hull(); + TriangleMesh& get_convex_hull(); // Helpers for loading / storing into AMF / 3MF files. static Type type_from_string(const std::string &s); @@ -210,23 +241,26 @@ private: ModelObject* object; // Is it an object to be printed, or a modifier volume? Type m_type; - t_model_material_id _material_id; + t_model_material_id m_material_id; ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object) { if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } - ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} + ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) : + mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {} ModelVolume(ModelObject *object, const ModelVolume &other) : + ModelBase(other), // copy the ID name(other.name), mesh(other.mesh), m_convex_hull(other.m_convex_hull), config(other.config), m_type(other.m_type), object(object) { - this->material_id(other.material_id()); + this->set_material_id(other.material_id()); } ModelVolume(ModelObject *object, const ModelVolume &other, const TriangleMesh &&mesh) : + ModelBase(other), // copy the ID name(other.name), mesh(std::move(mesh)), config(other.config), m_type(other.m_type), object(object) { - this->material_id(other.material_id()); + this->set_material_id(other.material_id()); if (mesh.stl.stats.number_of_facets > 1) calculate_convex_hull(); } @@ -234,7 +268,7 @@ private: // A single instance of a ModelObject. // Knows the affine transformation of an object. -class ModelInstance +class ModelInstance : public ModelBase { public: enum EPrintVolumeState : unsigned char @@ -321,7 +355,7 @@ private: // and with multiple modifier meshes. // A model groups multiple objects, each object having possibly multiple instances, // all objects may share mutliple materials. -class Model +class Model : public ModelBase { static unsigned int s_auto_extruder_id; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 37b55f87d..82dc089d4 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -571,6 +571,415 @@ exit_for_rearrange_regions: return invalidated; } +// 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. +static inline bool model_object_list_equal(const Model &model_old, const Model &model_new) +{ + if (model_old.objects.size() != model_new.objects.size()) + return false; + for (size_t i = 0; i < model_old.objects.size(); ++ i) + if (model_old.objects[i]->id() != model_new.objects[i]->id()) + return false; + return true; +} + +// 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. +static inline bool model_object_list_extended(const Model &model_old, const Model &model_new) +{ + if (model_old.objects.size() >= model_new.objects.size()) + return false; + for (size_t i = 0; i < model_old.objects.size(); ++ i) + if (model_old.objects[i]->id() != model_new.objects[i]->id()) + return false; + return true; +} + +static inline bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type) +{ + bool modifiers_differ = false; + size_t i_old, i_new; + for (i_old = 0, i_new = 0; i_old < model_object_old.volumes.size() && i_new < model_object_new.volumes.size();) { + const ModelVolume &mv_old = *model_object_old.volumes[i_old]; + const ModelVolume &mv_new = *model_object_new.volumes[i_old]; + if (mv_old.type() != type) { + ++ i_old; + continue; + } + if (mv_new.type() != type) { + ++ i_new; + continue; + } + if (mv_old.id() != mv_new.id()) + return true; + //FIXME test for the content of the mesh! + //FIXME test for the transformation matrices! + ++ i_old; + ++ i_new; + } + for (; i_old < model_object_old.volumes.size(); ++ i_old) { + const ModelVolume &mv_old = *model_object_old.volumes[i_old]; + if (mv_old.type() == type) + // ModelVolume was deleted. + return true; + } + for (; i_new < model_object_new.volumes.size(); ++ i_new) { + const ModelVolume &mv_new = *model_object_new.volumes[i_new]; + if (mv_new.type() == type) + // ModelVolume was added. + return true; + } + return false; +} + +static inline void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src) +{ + // 1) Delete the support volumes from model_object_dst. + { + std::vector dst; + dst.reserve(model_object_dst.volumes.size()); + for (ModelVolume *vol : model_object_dst.volumes) { + if (vol->is_support_modifier()) + dst.emplace_back(vol); + else + delete vol; + } + model_object_dst.volumes = std::move(dst); + } + // 2) Copy the support volumes from model_object_src to the end of model_object_dst. + for (ModelVolume *vol : model_object_src.volumes) { + if (vol->is_support_modifier()) + model_object_dst.volumes.emplace_back(vol->clone(&model_object_dst)); + } +} + +static inline bool transform3d_lower(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) { + if (*lv < *rv) + return true; + else if (*lv > *rv) + return false; + } + return false; +} + +static inline bool transform3d_equal(const Transform3d &lhs, const Transform3d &rhs) +{ + typedef Transform3d::Scalar T; + const T *lv = lhs.data(); + const T *rv = rhs.data(); + for (size_t i = 0; i < 16; ++ i, ++ lv, ++ rv) + if (*lv != *rv) + return false; + return true; +} + +struct PrintInstances +{ + Transform3d trafo; + Points instances; + bool operator<(const PrintInstances &rhs) const { return transform3d_lower(this->trafo, rhs.trafo); } +}; + +// Generate a list of trafos and XY offsets for instances of a ModelObject +static std::vector print_objects_from_model_object(const ModelObject &model_object) +{ + std::set trafos; + PrintInstances trafo; + trafo.instances.assign(1, Point()); + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + const Vec3d &offst = model_instance->get_offset(); + trafo.trafo = model_instance->world_matrix(true); + trafo.instances.front() = Point::new_scale(offst(0), offst(1)); + auto it = trafos.find(trafo); + if (it == trafos.end()) + trafos.emplace(trafo); + else + const_cast(*it).instances.emplace_back(trafo.instances.front()); + } + return std::vector(trafos.begin(), trafos.end()); +} + +bool Print::apply(const Model &model, const DynamicPrintConfig &config) +{ + // Grab the lock for the Print / PrintObject milestones. + tbb::mutex::scoped_lock lock(m_mutex); + + struct ModelObjectStatus { + enum Status { + Unknown, + Old, + New, + Moved, + Deleted, + }; + ModelObjectStatus(ModelID id, Status status = Unknown) : id(id), status(status) {} + ModelID id; + Status status; + // Search by id. + bool operator<(const ModelObjectStatus &rhs) const { return id < rhs.id; } + }; + std::set model_object_status; + + // 1) Synchronize model objects. + if (model.id() != m_model.id()) { + // Kill everything, initialize from scratch. + // The following call shall kill any computation if running. + this->invalidate_all_steps(); + for (PrintObject *object : m_objects) { + model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); + delete object; + } + m_objects.clear(); + for (PrintRegion *region : m_regions) + delete region; + m_regions.clear(); + m_model = model; + } else { + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + this->invalidate_step(psGCodeExport); + for (size_t i = m_model.objects.size(); i < model.objects.size(); ++ i) { + model_object_status.emplace(model.objects[i]->id(), ModelObjectStatus::New); + m_model.objects.emplace_back(model.objects[i]->clone(&m_model)); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + m_cancel_callback(); + this->invalidate_step(psGCodeExport); + // Second create a new list of objects. + std::vector old(std::move(m_model.objects)); + m_model.objects.clear(); + m_model.objects.reserve(model.objects.size()); + auto by_id_lower = [](const ModelObject *lhs, const ModelObject *rhs){ return lhs->id() < rhs->id(); }; + std::sort(old.begin(), old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(old.begin(), old.end(), mobj, by_id_lower); + if (it == old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back((*it)->clone(&m_model)); + model_object_status.emplace(mobj->id(), ModelObjectStatus::New); + } else { + // Existing ModelObject re-added (possibly moved in the list). + m_model.objects.emplace_back(*it); + model_object_status.emplace(mobj->id(), ModelObjectStatus::Old); + } + } + bool deleted_any = false; + for (ModelObject *mobj : old) + if (model_object_status.find(ModelObjectStatus(mobj->id())) == model_object_status.end()) { + model_object_status.emplace(mobj->id(), ModelObjectStatus::Deleted); + delete mobj; + deleted_any = true; + } + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + std::vector old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(old.size()); + for (PrintObject *print_object : old) { + auto it_status = model_object_status.find(ModelObjectStatus(print_object->model_object()->id())); + assert(it_status != model_object_status.end()); + if (it_status->status == ModelObjectStatus::Deleted) { + print_object->invalidate_all_steps(); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + } + } + } + + // 2) Map print objects including their transformation matrices. + struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + New + }; + PrintObjectStatus(PrintObject *print_object, Status status = Unknown) : + id(print_object->model_object()->id()), + print_object(print_object), + trafo(print_object->trafo()), + status(status) {} + PrintObjectStatus(ModelID id) : id(id), print_object(nullptr), trafo(Transform3d::Identity()), status(Unknown) {} + // ID of the ModelObject & PrintObject + ModelID id; + // Pointer to the old PrintObject + PrintObject *print_object; + // Trafo generated with model_object->world_matrix(true) + Transform3d trafo; + Status status; + // Search by id. + bool operator<(const PrintObjectStatus &rhs) const { return id < rhs.id; } + }; + std::multiset print_object_status; + for (PrintObject *print_object : m_objects) + print_object_status.emplace(PrintObjectStatus(print_object)); + + // 3) Synchronize ModelObjects & PrintObjects. + for (size_t idx_model_object = 0; idx_model_object < model.objects.size(); ++ idx_model_object) { + ModelObject &model_object = *m_model.objects[idx_model_object]; + auto it_status = model_object_status.find(ModelObjectStatus(model_object.id())); + assert(it_status != model_object_status.end()); + assert(it_status->status != ModelObjectStatus::Deleted); + if (it_status->status == ModelObjectStatus::New) + // PrintObject instances will be added in the next loop. + continue; + // Update the ModelObject instance, possibly invalidate the linked PrintObjects. + assert(it_status->status == ModelObjectStatus::Moved); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + // Check whether a model part volume was added or removed, their transformations or order changed. + bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART); + bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::PARAMETER_MODIFIER); + bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_BLOCKER); + bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_ENFORCER); + if (model_parts_differ || modifiers_differ) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) { + it->print_object->invalidate_all_steps(); + const_cast(*it).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, reset the parent. + model_object = model_object_new; + model_object.set_model(&m_model); + } else if (support_blockers_differ || support_enforcers_differ) { + // First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list. + m_cancel_callback(); + // Invalidate just the supports step. + auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id())); + for (auto it = range.first; it != range.second; ++ it) + it->print_object->invalidate_step(posSupportMaterial); + // Copy just the support volumes. + model_volume_list_update_supports(model_object, model_object_new); + } + if (! model_parts_differ && ! modifiers_differ) { + // Synchronize the remaining data of ModelVolumes (name, config, m_type, m_material_id) + } + } + + // 4) Generate PrintObjects from ModelObjects and their instances. + std::vector print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + // Walk over all new model objects and check, whether there are matching PrintObjects. + for (ModelObject *model_object : m_model.objects) { + auto range = print_object_status.equal_range(PrintObjectStatus(model_object->id())); + std::vector old; + if (range.first != range.second) { + old.reserve(print_object_status.count(PrintObjectStatus(model_object->id()))); + for (auto it = range.first; it != range.second; ++ it) + if (it->status != PrintObjectStatus::Deleted) + old.emplace_back(&(*it)); + } + // Generate a list of trafos and XY offsets for instances of a ModelObject + std::vector new_print_instances = print_objects_from_model_object(*model_object); + if (old.empty()) { + // Simple case, just generate new instances. + for (const PrintInstances &print_instances : new_print_instances) { + PrintObject *print_object = new PrintObject(this, model_object, model_object->raw_bounding_box()); + print_objects_new.emplace_back(print_object); + print_object_status.emplace(PrintObjectStatus(print_object, PrintObjectStatus::New)); + } + continue; + } + // Complex case, try to merge the two lists. + // Sort the old lexicographically by their trafos. + std::sort(old.begin(), old.end(), [](const PrintObjectStatus *lhs, const PrintObjectStatus *rhs){ return transform3d_lower(lhs->trafo, rhs->trafo); }); + // Merge the old / new lists. + + } + if (m_objects != print_objects_new) { + m_cancel_callback(); + m_objects = print_objects_new; + } + + // Synchronize materials. + +#if 0 + { + m_model = model; + for (const ModelObject *model_object : m_model.objects) { + PrintObject *object = new PrintObject(this, model_object, model_object->raw_bounding_box()); + m_objects.emplace_back(object); + size_t volume_id = 0; + for (const ModelVolume *volume : model_object->volumes) { + if (! volume->is_model_part() && ! volume->is_modifier()) + continue; + // Get the config applied to this volume. + PrintRegionConfig config = this->_region_config_from_model_volume(*volume); + // Find an existing print region with the same config. + size_t region_id = size_t(-1); + for (size_t i = 0; i < m_regions.size(); ++ i) + if (config.equals(m_regions[i]->config())) { + region_id = i; + break; + } + // If no region exists with the same config, create a new one. + if (region_id == size_t(-1)) { + region_id = m_regions.size(); + this->add_region(config); + } + // Assign volume to a region. + object->add_region_volume(region_id, volume_id); + ++ volume_id; + } + // Apply config to print object. + object->config_apply(this->default_object_config()); + { + //normalize_and_apply_config(object->config(), model_object->config); + DynamicPrintConfig src_normalized(model_object->config); + src_normalized.normalize(); + object->config_apply(src_normalized, true); + } + } + } else { + // Synchronize m_model.objects with model.objects + } +#endif + + this->update_object_placeholders(); +} + +// Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects. +void Print::update_object_placeholders() +{ + // get the first input file name + std::string input_file; + std::vector v_scale; + for (const PrintObject *object : m_objects) { + const ModelObject &mobj = *object->model_object(); +#if ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM + // CHECK_ME -> Is the following correct ? + v_scale.push_back("x:" + boost::lexical_cast(mobj.instances[0]->get_scaling_factor(X) * 100) + + "% y:" + boost::lexical_cast(mobj.instances[0]->get_scaling_factor(Y) * 100) + + "% z:" + boost::lexical_cast(mobj.instances[0]->get_scaling_factor(Z) * 100) + "%"); +#else + v_scale.push_back(boost::lexical_cast(mobj.instances[0]->scaling_factor * 100) + "%"); +#endif // ENABLE_MODELINSTANCE_3D_FULL_TRANSFORM + if (input_file.empty()) + input_file = mobj.input_file; + } + + PlaceholderParser &pp = m_placeholder_parser; + pp.set("scale", v_scale); + if (! input_file.empty()) { + // get basename with and without suffix + const std::string input_basename = boost::filesystem::path(input_file).filename().string(); + pp.set("input_filename", input_basename); + const std::string input_basename_base = input_basename.substr(0, input_basename.find_last_of(".")); + pp.set("input_filename_base", input_basename_base); + } +} + bool Print::has_infinite_skirt() const { return (m_config.skirt_height == -1 && m_config.skirts > 0) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5bbeb3f6a..d539053b3 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -194,6 +194,7 @@ public: void config_apply_only(const ConfigBase &other, const t_config_option_keys &keys, bool ignore_nonexistent = false) { this->m_config.apply_only(other, keys, ignore_nonexistent); } const LayerPtrs& layers() const { return m_layers; } const SupportLayerPtrs& support_layers() const { return m_support_layers; } + const Transform3d& trafo() const { return m_trafo; } const Points& copies() const { return m_copies; } bool add_copy(const Vec2d &point); @@ -285,6 +286,8 @@ private: Print *m_print; ModelObject *m_model_object; PrintObjectConfig m_config; + // Translation in Z + Rotation + Scaling / Mirroring. + Transform3d m_trafo = Transform3d::Identity(); // Slic3r::Point objects in scaled G-code coordinates Points m_copies; // scaled coordinates to add to copies (to compensate for the alignment @@ -382,6 +385,8 @@ public: bool reload_model_instances(); void add_model_object(ModelObject* model_object, int idx = -1); bool apply_config(DynamicPrintConfig config); + bool apply(const Model &model, const DynamicPrintConfig &config); + void process(); void export_gcode(const std::string &path_template, GCodePreviewData *preview_data); // SLA export, temporary. @@ -477,6 +482,9 @@ protected: PrintRegion* add_region(const PrintRegionConfig &config); private: + // Update "scale", "input_filename", "input_filename_base" placeholders from the current m_objects. + void update_object_placeholders(); + bool invalidate_state_by_config_options(const std::vector &opt_keys); PrintRegionConfig _region_config_from_model_volume(const ModelVolume &volume); @@ -503,6 +511,7 @@ private: // Callback to be evoked to stop the background processing before a state is updated. cancel_callback_type m_cancel_callback = [](){}; + Model m_model; PrintConfig m_config; PrintObjectConfig m_default_object_config; PrintRegionConfig m_default_region_config; diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index 505a45125..2793cb876 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -159,7 +159,7 @@ void MainFrame::init_tabpanel() if (m_plater) { // load initial config auto full_config = wxGetApp().preset_bundle->full_config(); - m_plater->on_config_change(&full_config); + m_plater->on_config_change(full_config); // Show a correct number of filament fields. // nozzle_diameter is undefined when SLA printer is selected @@ -530,9 +530,10 @@ void MainFrame::quick_slice(const int qs){ // Slic3r::GUI::catch_error(this, [](){ if (m_progress_dialog) m_progress_dialog->Destroy(); }); } -void MainFrame::reslice_now(){ -// if (m_plater) -// m_plater->reslice(); +void MainFrame::reslice_now() +{ + if (m_plater) + m_plater->reslice(); } void MainFrame::repair_stl() @@ -611,12 +612,13 @@ void MainFrame::load_config_file(wxString file/* = wxEmptyString*/) file = dlg->GetPath(); dlg->Destroy(); } -// eval{ + try { wxGetApp().preset_bundle->load_config_file(file.ToStdString()); -// }; - // Dont proceed further if the config file cannot be loaded. -// if (Slic3r::GUI::catch_error(this)) -// return; + } catch (std::exception & /* ex */) { + // Dont proceed further if the config file cannot be loaded. + // if (Slic3r::GUI::catch_error(this)) + // return; + } for (auto tab : m_options_tabs ) tab.second->load_current_preset(); wxGetApp().app_config->update_config_dir(get_dir_name(file)); @@ -644,18 +646,20 @@ void MainFrame::export_configbundle() dlg->Destroy(); if (!file.IsEmpty()) { // Export the config bundle. - wxGetApp().app_config->update_config_dir(get_dir_name(file)); -// eval{ + wxGetApp().app_config->update_config_dir(get_dir_name(file)); + try { wxGetApp().preset_bundle->export_configbundle(file.ToStdString()); -// }; + } catch (std::exception & /* ex */) { // Slic3r::GUI::catch_error(this); + } } } // Loading a config bundle with an external file name used to be used // to auto - install a config bundle on a fresh user account, // but that behavior was not documented and likely buggy. -void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/){ +void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool reset_user_profile*/) +{ if (!wxGetApp().check_unsaved_changes()) return; if (file.IsEmpty()) { @@ -671,10 +675,11 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re wxGetApp().app_config->update_config_dir(get_dir_name(file)); auto presets_imported = 0; -// eval{ + try { presets_imported = wxGetApp().preset_bundle->load_configbundle(file.ToStdString()); -// }; + } catch (std::exception & /* ex */) { // Slic3r::GUI::catch_error(this) and return; + } // Load the currently selected preset into the GUI, update the preset selection box. for (auto tab : m_options_tabs) @@ -686,20 +691,24 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re // Load a provied DynamicConfig into the Print / Filament / Printer tabs, thus modifying the active preset. // Also update the platter with the new presets. -void MainFrame::load_config(const DynamicPrintConfig& config){ +void MainFrame::load_config(const DynamicPrintConfig& config) +{ for (auto tab : m_options_tabs) tab.second->load_config(config); -// if (m_plater) m_plater->on_config_change(config); + if (m_plater) + m_plater->on_config_change(config); } -void MainFrame::select_tab(size_t tab) const{ +void MainFrame::select_tab(size_t tab) const +{ m_tabpanel->SetSelection(tab); } // Set a camera direction, zoom to all objects. -void MainFrame::select_view(const std::string& direction){ -// if (m_plater) -// m_plater->select_view(direction); +void MainFrame::select_view(const std::string& direction) +{ +// if (m_plater) +// m_plater->select_view(direction); } wxMenuItem* MainFrame::append_menu_item(wxMenu* menu, @@ -743,8 +752,7 @@ void MainFrame::on_presets_changed(SimpleEvent &event) if (preset_type == Slic3r::Preset::TYPE_PRINTER) { // Printer selected at the Printer tab, update "compatible" marks at the print and filament selectors. // XXX: Do this in a more C++ way - std::vector tab_names_other = { "print", "filament", "sla_material" }; - for (const auto tab_name_other : tab_names_other) { + for (const auto tab_name_other : { "print", "filament", "sla_material" }) { Tab* cur_tab = m_options_tabs[tab_name_other]; // If the printer tells us that the print or filament preset has been switched or invalidated, // refresh the print or filament tab page.Otherwise just refresh the combo box. @@ -756,8 +764,7 @@ void MainFrame::on_presets_changed(SimpleEvent &event) cur_tab->load_current_preset(); } } - // XXX: ? - // m_plater->on_config_change(tab->get_config()); + m_plater->on_config_change(*tab->get_config()); } } @@ -769,18 +776,19 @@ void MainFrame::on_value_changed(wxCommandEvent& event) return; auto opt_key = event.GetString(); - auto config = tab->get_config(); if (m_plater) { - m_plater->on_config_change(config); // propagate config change events to the plater - if (opt_key == "extruders_count"){ - auto value = event.GetInt(); - m_plater->on_extruders_change(value); - } + m_plater->on_config_change(*tab->get_config()); // propagate config change events to the plater + if (opt_key == "extruders_count"){ + auto value = event.GetInt(); + m_plater->on_extruders_change(value); + } + } + // Don't save while loading for the first time. + if (m_loaded) { + AppConfig &cfg = *wxGetApp().app_config; + if (cfg.get("autosave") == "1") + cfg.save(); } - // don't save while loading for the first time - // #ys_FIXME ?autosave? -// if (wxGetApp().autosave && m_loaded) -// m_config->save(wxGetApp().autosave); } // Called after the Preferences dialog is closed and the program settings are saved. diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f22b70074..9d8d3ce0f 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -209,6 +209,7 @@ PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : if (dialog->ShowModal() == wxID_OK) { DynamicPrintConfig cfg = *wxGetApp().get_tab(Preset::TYPE_PRINTER)->get_config(); + //FIXME this is too expensive to call full_config to get just the extruder color! auto colors = static_cast(wxGetApp().preset_bundle->full_config().option("extruder_colour")->clone()); colors->values[extruder_idx] = dialog->GetColourData().GetColour().GetAsString(wxC2S_HTML_SYNTAX); @@ -216,7 +217,7 @@ PresetComboBox::PresetComboBox(wxWindow *parent, Preset::Type preset_type) : wxGetApp().get_tab(Preset::TYPE_PRINTER)->load_config(cfg); wxGetApp().preset_bundle->update_platter_filament_ui(extruder_idx, this); - wxGetApp().plater()->on_config_change(&cfg); + wxGetApp().plater()->on_config_change(cfg); } dialog->Destroy(); }); @@ -741,7 +742,8 @@ struct Plater::priv Sidebar *sidebar; wxGLCanvas *canvas3D; // TODO: Use GLCanvas3D when we can Preview *preview; - BackgroundSlicingProcess background_process; + BackgroundSlicingProcess background_process; + wxTimer background_process_timer; static const std::regex pattern_bundle; static const std::regex pattern_3mf; @@ -855,15 +857,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : _3DScene::enable_shader(canvas3D, true); _3DScene::enable_force_zoom_to_bed(canvas3D, true); - // XXX: apply_config_timer - // { - // my $timer_id = Wx::NewId(); - // $self->{apply_config_timer} = Wx::Timer->new($self, $timer_id); - // EVT_TIMER($self, $timer_id, sub { - // my ($self, $event) = @_; - // $self->async_apply_config; - // }); - // } + background_process_timer.Bind(wxEVT_TIMER, [this](wxTimerEvent &evt){ this->async_apply_config(); }, 0); auto *bed_shape = config->opt("bed_shape"); _3DScene::set_bed_shape(canvas3D, bed_shape->values); @@ -961,7 +955,7 @@ void Plater::priv::update(bool force_autocenter) preview->reset_gcode_preview_data(); preview->reload_print(); - // schedule_background_process(); // TODO + schedule_background_process(); } void Plater::priv::update_ui_from_settings() @@ -1172,7 +1166,7 @@ std::vector Plater::priv::load_model_objects(const ModelObjectPtrs &mode _3DScene::zoom_to_volumes(canvas3D); object_list_changed(); - // $self->schedule_background_process; + this->schedule_background_process(); return obj_idxs; } @@ -1449,12 +1443,40 @@ void Plater::priv::split_object() void Plater::priv::schedule_background_process() { - // TODO + // Trigger the timer event after 0.5s + this->background_process_timer.Start(500, wxTIMER_ONE_SHOT); } void Plater::priv::async_apply_config() { - // TODO + // Apply new config to the possibly running background task. + bool was_running = this->background_process.running(); + bool invalidated = this->background_process.apply_config(wxGetApp().preset_bundle->full_config()); + // Just redraw the 3D canvas without reloading the scene to consume the update of the layer height profile. + if (Slic3r::_3DScene::is_layers_editing_enabled(this->canvas3D)) + this->canvas3D->Refresh(); + // If the apply_config caused the calculation to stop, or it was not running yet: + if (invalidated) { + if (was_running) { + // Hide the slicing results if the current slicing status is no more valid. + this->sidebar->show_info_sizers(false); + } + if (this->get_config("background_processing") == "1") + this->background_process.start(); + if (was_running) { + // Reset preview canvases. If the print has been invalidated, the preview canvases will be cleared. + // Otherwise they will be just refreshed. + this->gcode_preview_data.reset(); + if (this->preview != nullptr) + this->preview->reload_print(); + // We also need to reload 3D scene because of the wipe tower preview box + if (this->config->opt_bool("wipe_tower")) { + std::vector selections = this->collect_selections(); + Slic3r::_3DScene::set_objects_selections(this->canvas3D, selections); + Slic3r::_3DScene::reload_scene(this->canvas3D, 1); + } + } + } } void Plater::priv::start_background_process() @@ -1542,8 +1564,7 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt) // Synchronize config.ini with the current selections. wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config); // update plater with new config - auto config = wxGetApp().preset_bundle->full_config(); - wxGetApp().plater()->on_config_change(&config); + wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config()); } void Plater::priv::on_progress_event() @@ -1677,7 +1698,8 @@ void Plater::priv::on_scale_uniformly(SimpleEvent&) // $self->selection_changed(1); # refresh info (size, volume etc.) // $self->update; -// $self->schedule_background_process; + + this->schedule_background_process(); } void Plater::priv::on_wipetower_moved(Vec3dEvent &evt) @@ -1756,7 +1778,7 @@ void Plater::increase(size_t num) p->selection_changed(); - // $self->schedule_background_process; + this->p->schedule_background_process(); } void Plater::decrease(size_t num) @@ -1933,11 +1955,11 @@ void Plater::on_extruders_change(int num_extruders) GetParent()->Layout(); } -void Plater::on_config_change(DynamicPrintConfig* config) +void Plater::on_config_change(const DynamicPrintConfig &config) { bool update_scheduled = false; - for ( auto opt_key: p->config->diff(*config)) { - p->config->set_key_value(opt_key, config->option(opt_key)->clone()); + for (auto opt_key : p->config->diff(config)) { + p->config->set_key_value(opt_key, config.option(opt_key)->clone()); if (opt_key == "bed_shape") { if (p->canvas3D) _3DScene::set_bed_shape(p->canvas3D, p->config->option(opt_key)->values); if (p->preview) p->preview->set_bed_shape(p->config->option(opt_key)->values); @@ -1948,7 +1970,7 @@ void Plater::on_config_change(DynamicPrintConfig* config) update_scheduled = true; } // else if(opt_key == "serial_port") { -// sidebar()->p->btn_print->Show(config->get("serial_port")); // ???: btn_print is removed +// sidebar()->p->btn_print->Show(config.get("serial_port")); // ???: btn_print is removed // Layout(); // } else if (opt_key == "print_host") { @@ -1982,10 +2004,8 @@ void Plater::on_config_change(DynamicPrintConfig* config) if (update_scheduled) update(); - if (!p->main_frame->is_loaded()) return ; - - // (re)start timer -// schedule_background_process(); + if (p->main_frame->is_loaded()) + this->p->schedule_background_process(); } wxGLCanvas* Plater::canvas3D() @@ -2012,7 +2032,7 @@ void Plater::changed_object_settings(int obj_idx) if (list->is_parts_changed() || list->is_part_settings_changed()) { // stop_background_process(); // $self->{print}->reload_object($obj_idx); -// schedule_background_process(); + this->p->schedule_background_process(); #if !ENABLE_EXTENDED_SELECTION if (p->canvas3D) _3DScene::reload_scene(p->canvas3D, true); auto selections = p->collect_selections(); @@ -2021,7 +2041,7 @@ void Plater::changed_object_settings(int obj_idx) _3DScene::reload_scene(p->canvas3D, false); } else { -// schedule_background_process(); + this->p->schedule_background_process(); } } diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index fba2e0dc0..93c88e131 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -123,7 +123,7 @@ public: void send_gcode(); void on_extruders_change(int extruders_count); - void on_config_change(DynamicPrintConfig* config); + void on_config_change(const DynamicPrintConfig &config); wxGLCanvas* canvas3D(); private: diff --git a/xs/xsp/Model.xsp b/xs/xsp/Model.xsp index 62ccf2b06..f458a3b92 100644 --- a/xs/xsp/Model.xsp +++ b/xs/xsp/Model.xsp @@ -262,7 +262,7 @@ ModelMaterial::attributes() %code%{ THIS->name = value; %}; t_model_material_id material_id(); void set_material_id(t_model_material_id material_id) - %code%{ THIS->material_id(material_id); %}; + %code%{ THIS->set_material_id(material_id); %}; Ref material(); Ref config()