diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp index 3aa04f3b2..6b9151ff8 100644 --- a/src/libslic3r/Model.cpp +++ b/src/libslic3r/Model.cpp @@ -1232,6 +1232,19 @@ void ModelVolume::set_material(t_model_material_id material_id, const ModelMater this->object->get_model()->add_material(material_id, material); } +// Extract the current extruder ID based on this ModelVolume's config and the parent ModelObject's config. +int ModelVolume::extruder_id() const +{ + int extruder_id = -1; + if (this->is_model_part()) { + const ConfigOption *opt = this->config.option("extruder"); + if (opt == nullptr) + opt = this->object->config.option("extruder"); + extruder_id = (opt == nullptr) ? 0 : opt->getInt(); + } + return extruder_id; +} + #if ENABLE_MODELVOLUME_TRANSFORM void ModelVolume::center_geometry() { @@ -1525,6 +1538,71 @@ Transform3d ModelInstance::get_matrix(bool dont_translate, bool dont_rotate, boo } #endif // !ENABLE_MODELVOLUME_TRANSFORM +// 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. +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. +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; +} + +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_new]; + 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! + +#if ENABLE_MODELVOLUME_TRANSFORM + if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) + return true; +#endif // ENABLE_MODELVOLUME_TRANSFORM + ++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; +} + #ifdef _DEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model) diff --git a/src/libslic3r/Model.hpp b/src/libslic3r/Model.hpp index bf6656cfd..c288010d0 100644 --- a/src/libslic3r/Model.hpp +++ b/src/libslic3r/Model.hpp @@ -23,6 +23,7 @@ class ModelMaterial; class ModelObject; class ModelVolume; class Print; +class SLAPrint; typedef std::string t_model_material_id; typedef std::string t_model_material_attribute; @@ -252,6 +253,7 @@ public: protected: friend class Print; + friend class SLAPrint; // Called by Print::apply() to set the model pointer after making a copy. void set_model(Model *model) { m_model = model; } @@ -310,6 +312,10 @@ public: 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; + // 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. @@ -368,6 +374,7 @@ public: protected: friend class Print; + friend class SLAPrint; friend class ModelObject; explicit ModelVolume(const ModelVolume &rhs) = default; @@ -535,6 +542,7 @@ public: protected: friend class Print; + friend class SLAPrint; friend class ModelObject; explicit ModelInstance(const ModelInstance &rhs) = default; @@ -652,6 +660,18 @@ private: #undef MODELBASE_DERIVED_COPY_MOVE_CLONE #undef MODELBASE_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 ModelVolume::Type type); + #ifdef _DEBUG // Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique. void check_model_ids_validity(const Model &model); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 4e1685e45..b14132ad6 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -550,71 +550,6 @@ 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_new]; - 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! - -#if ENABLE_MODELVOLUME_TRANSFORM - if (!mv_old.get_matrix().isApprox(mv_new.get_matrix())) - return true; -#endif // ENABLE_MODELVOLUME_TRANSFORM - ++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; -} - // Add or remove support modifier ModelVolumes from model_object_dst to match the ModelVolumes of model_object_new // in the exact order and with the same IDs. // It is expected, that the model_object_dst already contains the non-support volumes of model_object_new in the correct order. diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 0f6c0afbd..b5c693ee4 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -131,9 +131,6 @@ public: SupportLayerPtrs::const_iterator insert_support_layer(SupportLayerPtrs::const_iterator pos, int id, coordf_t height, coordf_t print_z, coordf_t slice_z); void delete_support_layer(int idx); - // methods for handling state - bool invalidate_state_by_config_options(const std::vector &opt_keys); - // To be used over the layer_height_profile of both the PrintObject and ModelObject // to initialize the height profile with the height ranges. bool update_layer_height_profile(std::vector &layer_height_profile) const; @@ -174,6 +171,8 @@ protected: bool invalidate_step(PrintObjectStep step); // Invalidates all PrintObject and Print steps. bool invalidate_all_steps(); + // Invalidate steps based on a set of parameters changed. + bool invalidate_state_by_config_options(const std::vector &opt_keys); private: void make_perimeters(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 055d7e46a..739b7ee22 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -2447,6 +2447,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_head_front_radius", coFloat); def->label = L("Support head front radius"); + def->category = L("Supports"); def->tooltip = L("Radius of the pointing side of the head"); def->sidetext = L("mm"); def->cli = ""; @@ -2455,6 +2456,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_head_penetration", coFloat); def->label = L("Support head penetration"); + def->category = L("Supports"); def->tooltip = L("How much the pinhead has to penetrate the model surface"); def->sidetext = L("mm"); def->cli = ""; @@ -2463,6 +2465,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_head_back_radius", coFloat); def->label = L("Support head back radius"); + def->category = L("Supports"); def->tooltip = L("Radius of the back side of the 3d arrow"); def->sidetext = L("mm"); def->cli = ""; @@ -2471,6 +2474,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_head_width", coFloat); def->label = L("Support head width"); + def->category = L("Supports"); def->tooltip = L("Width from the back sphere center to the front sphere center"); def->sidetext = L("mm"); def->cli = ""; @@ -2479,6 +2483,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_pillar_radius", coFloat); def->label = L("Support pillar radius"); + def->category = L("Supports"); def->tooltip = L("Radius in mm of the support pillars"); def->sidetext = L("mm"); def->cli = ""; @@ -2487,6 +2492,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_base_radius", coFloat); def->label = L("Support base radius"); + def->category = L("Supports"); def->tooltip = L("Radius in mm of the pillar base"); def->sidetext = L("mm"); def->cli = ""; @@ -2495,6 +2501,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_base_height", coFloat); def->label = L("Support base height"); + def->category = L("Supports"); def->tooltip = L("The height of the pillar base cone"); def->sidetext = L("mm"); def->cli = ""; @@ -2503,6 +2510,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_critical_angle", coFloat); def->label = L("Critical angle"); + def->category = L("Supports"); def->tooltip = L("The default angle for connecting support sticks and junctions."); def->sidetext = L("°"); def->cli = ""; @@ -2511,6 +2519,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_max_bridge_length", coFloat); def->label = L("Max bridge length"); + def->category = L("Supports"); def->tooltip = L("The max length of a bridge"); def->sidetext = L("mm"); def->cli = ""; @@ -2519,6 +2528,7 @@ void PrintConfigDef::init_sla_params() def = this->add("support_object_elevation", coFloat); def->label = L("Object elevation"); + def->category = L("Supports"); def->tooltip = L("How much the supports should lift up the supported object."); def->sidetext = L("mm"); def->cli = ""; @@ -2527,6 +2537,7 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_enable", coBool); def->label = L("Use pad"); + def->category = L("Pad"); def->tooltip = L("Add a pad underneath the supported model"); def->sidetext = L(""); def->cli = ""; @@ -2534,6 +2545,7 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_wall_thickness", coFloat); def->label = L("Pad wall thickness"); + def->category = L("Pad"); def->tooltip = L(""); def->sidetext = L("mm"); def->cli = ""; @@ -2542,6 +2554,7 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_wall_height", coFloat); def->label = L("Pad wall height"); + def->category = L("Pad"); def->tooltip = L(""); def->sidetext = L("mm"); def->cli = ""; @@ -2550,6 +2563,7 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_max_merge_distance", coFloat); def->label = L("Max merge distance"); + def->category = L("Pad"); def->tooltip = L(""); def->sidetext = L("mm"); def->cli = ""; @@ -2558,6 +2572,7 @@ void PrintConfigDef::init_sla_params() def = this->add("pad_edge_radius", coFloat); def->label = L("Pad edge radius"); + def->category = L("Pad"); def->tooltip = L(""); def->sidetext = L("mm"); def->cli = ""; diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index f1d1c9784..a3c79f4a4 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -902,7 +902,7 @@ class SLAPrintObjectConfig : public StaticPrintConfig { STATIC_PRINT_CONFIG_CACHE(SLAPrintObjectConfig) public: - ConfigOptionFloat layer_height; + ConfigOptionFloat layer_height; // Radius in mm of the pointing side of the head. ConfigOptionFloat support_head_front_radius /*= 0.2*/; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index 085e490e7..9f1ba4cc0 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -3,6 +3,8 @@ #include "SLA/SLABasePool.hpp" #include "MTUtils.hpp" +#include + #include #include @@ -75,53 +77,296 @@ void SLAPrint::clear() m_objects.clear(); } +// Transformation without rotation around Z and without a shift by X and Y. +static Transform3d sla_trafo(const ModelObject &model_object) +{ + ModelInstance &model_instance = *model_object.instances.front(); + Vec3d offset = model_instance.get_offset(); + Vec3d rotation = model_instance.get_rotation(); + offset(0) = 0.; + offset(1) = 0.; + rotation(2) = 0.; + return Geometry::assemble_transform(offset, rotation, model_instance.get_scaling_factor(), model_instance.get_mirror()); +} + +// List of instances, where the ModelInstance transformation is a composite of sla_trafo and the transformation defined by SLAPrintObject::Instance. +static std::vector sla_instances(const ModelObject &model_object) +{ + std::vector instances; + for (ModelInstance *model_instance : model_object.instances) + if (model_instance->is_printable()) { + instances.emplace_back(SLAPrintObject::Instance( + model_instance->id(), + Point::new_scale(model_instance->get_offset(X), model_instance->get_offset(Y)), + float(model_instance->get_rotation(Z)))); + } + return instances; +} + SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConfig &config_in) { -// if (m_objects.empty()) -// return APPLY_STATUS_UNCHANGED; +#ifdef _DEBUG + check_model_ids_validity(model); +#endif /* _DEBUG */ + + // Make a copy of the config, normalize it. + DynamicPrintConfig config(config_in); + config.normalize(); + // Collect changes to print config. + t_config_option_keys printer_diff = m_printer_config.diff(config); + t_config_option_keys material_diff = m_material_config.diff(config); + t_config_option_keys object_diff = m_default_object_config.diff(config); + + // Do not use the ApplyStatus as we will use the max function when updating apply_status. + unsigned int apply_status = APPLY_STATUS_UNCHANGED; + auto update_apply_status = [&apply_status](bool invalidated) + { apply_status = std::max(apply_status, invalidated ? APPLY_STATUS_INVALIDATED : APPLY_STATUS_CHANGED); }; + if (! (printer_diff.empty() && material_diff.empty() && object_diff.empty())) + update_apply_status(false); // Grab the lock for the Print / PrintObject milestones. tbb::mutex::scoped_lock lock(this->state_mutex()); - if (m_objects.empty() && model.objects.empty() && m_model.objects.empty()) - return APPLY_STATUS_UNCHANGED; - // Temporary: just to have to correct layer height for the rasterization - DynamicPrintConfig config(config_in); - config.normalize(); - m_material_config.initial_layer_height.set( - config.opt("initial_layer_height")); + // The following call may stop the background processing. + update_apply_status(this->invalidate_state_by_config_options(printer_diff)); + update_apply_status(this->invalidate_state_by_config_options(material_diff)); - // Temporary quick fix, just invalidate everything. - { - for (SLAPrintObject *print_object : m_objects) { - print_object->invalidate_all_steps(); - delete print_object; - } - m_objects.clear(); - this->invalidate_all_steps(); + // It is also safe to change m_config now after this->invalidate_state_by_config_options() call. + m_printer_config.apply_only(config, printer_diff, true); + // Handle changes to material config. + m_material_config.apply_only(config, material_diff, true); + // Handle changes to object config defaults + m_default_object_config.apply_only(config, object_diff, true); - // Copy the model by value (deep copy), - // keep the Model / ModelObject / ModelInstance / ModelVolume IDs. + 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. + // Stop background processing. + this->call_cancell_callback(); + update_apply_status(this->invalidate_all_steps()); + for (SLAPrintObject *object : m_objects) { + model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted); + delete object; + } + m_objects.clear(); m_model.assign_copy(model); - // Generate new SLAPrintObjects. - for (ModelObject *model_object : m_model.objects) { - auto po = new SLAPrintObject(this, model_object); - - // po->m_config.layer_height.set(lh); - po->m_config.apply(config, true); - - m_objects.emplace_back(po); - for (ModelInstance *oinst : model_object->instances) { - Point tr = Point::new_scale(oinst->get_offset()(X), - oinst->get_offset()(Y)); - auto rotZ = float(oinst->get_rotation()(Z)); - po->m_instances.emplace_back(oinst->id(), tr, rotZ); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::New); + } else { + if (model_object_list_equal(m_model, model)) { + // The object list did not change. + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + } else if (model_object_list_extended(m_model, model)) { + // Add new objects. Their volumes and configs will be synchronized later. + update_apply_status(this->invalidate_step(slapsRasterize)); + for (const ModelObject *model_object : m_model.objects) + model_object_status.emplace(model_object->id(), ModelObjectStatus::Old); + 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(ModelObject::new_copy(*model.objects[i])); + m_model.objects.back()->set_model(&m_model); + } + } else { + // Reorder the objects, add new objects. + // First stop background processing before shuffling or deleting the PrintObjects in the object list. + this->call_cancell_callback(); + update_apply_status(this->invalidate_step(slapsRasterize)); + // Second create a new list of objects. + std::vector model_objects_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(model_objects_old.begin(), model_objects_old.end(), by_id_lower); + for (const ModelObject *mobj : model.objects) { + auto it = std::lower_bound(model_objects_old.begin(), model_objects_old.end(), mobj, by_id_lower); + if (it == model_objects_old.end() || (*it)->id() != mobj->id()) { + // New ModelObject added. + m_model.objects.emplace_back(ModelObject::new_copy(*mobj)); + m_model.objects.back()->set_model(&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::Moved); + } + } + bool deleted_any = false; + for (ModelObject *&model_object : model_objects_old) { + if (model_object_status.find(ModelObjectStatus(model_object->id())) == model_object_status.end()) { + model_object_status.emplace(model_object->id(), ModelObjectStatus::Deleted); + deleted_any = true; + } else + // Do not delete this ModelObject instance. + model_object = nullptr; + } + if (deleted_any) { + // Delete PrintObjects of the deleted ModelObjects. + std::vector print_objects_old = std::move(m_objects); + m_objects.clear(); + m_objects.reserve(print_objects_old.size()); + for (SLAPrintObject *print_object : print_objects_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) { + update_apply_status(print_object->invalidate_all_steps()); + delete print_object; + } else + m_objects.emplace_back(print_object); + } + for (ModelObject *model_object : model_objects_old) + delete model_object; } } } - return APPLY_STATUS_INVALIDATED; + // 2) Map print objects including their transformation matrices. + struct PrintObjectStatus { + enum Status { + Unknown, + Deleted, + Reused, + New + }; + PrintObjectStatus(SLAPrintObject *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 + SLAPrintObject *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 (SLAPrintObject *print_object : m_objects) + print_object_status.emplace(PrintObjectStatus(print_object)); + + // 3) Synchronize ModelObjects & PrintObjects. + std::vector print_objects_new; + print_objects_new.reserve(std::max(m_objects.size(), m_model.objects.size())); + bool new_objects = false; + 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::Old || it_status->status == ModelObjectStatus::Moved); + const ModelObject &model_object_new = *model.objects[idx_model_object]; + auto it_print_object_status = print_object_status.lower_bound(PrintObjectStatus(model_object.id())); + if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id()) + it_print_object_status = print_object_status.end(); + // 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 sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() || + (! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new))); + if (model_parts_differ || sla_trafo_differs) { + // The very first step (the slicing step) is invalidated. One may freely remove all associated PrintObjects. + if (it_print_object_status != print_object_status.end()) { + update_apply_status(it_print_object_status->print_object->invalidate_all_steps()); + const_cast(*it_print_object_status).status = PrintObjectStatus::Deleted; + } + // Copy content of the ModelObject including its ID, do not change the parent. + model_object.assign_copy(model_object_new); + } else { + // Synchronize Object's config. + bool object_config_changed = model_object.config != model_object_new.config; + if (object_config_changed) + model_object.config = model_object_new.config; + if (! object_diff.empty() || object_config_changed) { + SLAPrintObjectConfig new_config = m_default_object_config; + normalize_and_apply_config(new_config, model_object.config); + if (it_print_object_status != print_object_status.end()) { + t_config_option_keys diff = it_print_object_status->print_object->config().diff(new_config); + if (! diff.empty()) { + update_apply_status(it_print_object_status->print_object->invalidate_state_by_config_options(diff)); + it_print_object_status->print_object->config_apply_only(new_config, diff, true); + } + } + } + if (model_object.sla_support_points != model_object_new.sla_support_points) { + model_object.sla_support_points = model_object_new.sla_support_points; + if (it_print_object_status != print_object_status.end()) + update_apply_status(it_print_object_status->print_object->invalidate_step(slaposSupportPoints)); + } + // Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step. + model_object.name = model_object_new.name; + model_object.input_file = model_object_new.input_file; + model_object.clear_instances(); + model_object.instances.reserve(model_object_new.instances.size()); + for (const ModelInstance *model_instance : model_object_new.instances) { + model_object.instances.emplace_back(new ModelInstance(*model_instance)); + model_object.instances.back()->set_model_object(&model_object); + } + } + + std::vector new_instances = sla_instances(model_object); + if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) { + // The SLAPrintObject is already there. + if (new_instances != it_print_object_status->print_object->instances()) { + // Instances changed. + it_print_object_status->print_object->set_instances(new_instances); + update_apply_status(this->invalidate_step(slapsRasterize)); + } + print_objects_new.emplace_back(it_print_object_status->print_object); + const_cast(*it_print_object_status).status = PrintObjectStatus::Reused; + } else { + auto print_object = new SLAPrintObject(this, &model_object); + print_object->set_trafo(sla_trafo(model_object)); + print_object->set_instances(new_instances); + print_object->config_apply(config, true); + print_objects_new.emplace_back(print_object); + new_objects = true; + } + } + + if (m_objects != print_objects_new) { + this->call_cancell_callback(); + update_apply_status(this->invalidate_all_steps()); + m_objects = print_objects_new; + // Delete the PrintObjects marked as Unknown or Deleted. + bool deleted_objects = false; + for (auto &pos : print_object_status) + if (pos.status == PrintObjectStatus::Unknown || pos.status == PrintObjectStatus::Deleted) { + // update_apply_status(pos.print_object->invalidate_all_steps()); + delete pos.print_object; + deleted_objects = true; + } + update_apply_status(new_objects); + } + +#ifdef _DEBUG + check_model_ids_equal(m_model, model); +#endif /* _DEBUG */ + + return static_cast(apply_status); } void SLAPrint::process() @@ -418,12 +663,12 @@ void SLAPrint::process() // Status indication auto st = ist + unsigned(sd*level_id*slot/levels.size()); - slck.lock(); + { std::lock_guard lck(slck); if( st > pst) { set_status(st, PRINT_STEP_LABELS[slapsRasterize]); pst = st; } - slck.unlock(); + } }; // last minute escape @@ -534,6 +779,61 @@ void SLAPrint::process() set_status(100, L("Slicing done")); } +bool SLAPrint::invalidate_state_by_config_options(const std::vector &opt_keys) +{ + if (opt_keys.empty()) + return false; + + // Cache the plenty of parameters, which influence the final rasterization only, + // or they are only notes not influencing the rasterization step. + static std::unordered_set steps_rasterize = { + "exposure_time", + "initial_exposure_time", + "material_correction_printing", + "material_correction_curing", + "display_width", + "display_height", + "display_pixels_x", + "display_pixels_y", + "printer_correction" + }; + + static std::unordered_set steps_ignore = { + "bed_shape", + "max_print_height", + "printer_technology", + }; + + std::vector steps; + std::vector osteps; + bool invalidated = false; + + for (const t_config_option_key &opt_key : opt_keys) { + if (steps_rasterize.find(opt_key) != steps_rasterize.end()) { + // These options only affect the final rasterization, or they are just notes without influence on the output, + // so there is nothing to invalidate. + steps.emplace_back(slapsRasterize); + } else if (steps_ignore.find(opt_key) != steps_ignore.end()) { + // These steps have no influence on the output. Just ignore them. + } else if (opt_key == "initial_layer_height") { + steps.emplace_back(slapsRasterize); + osteps.emplace_back(slaposObjectSlice); + } else { + // All values should be covered. + assert(false); + } + } + + sort_remove_duplicates(steps); + for (SLAPrintStep step : steps) + invalidated |= this->invalidate_step(step); + sort_remove_duplicates(osteps); + for (SLAPrintObjectStep ostep : osteps) + for (SLAPrintObject *object : m_objects) + invalidated |= object->invalidate_step(ostep); + return invalidated; +} + SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): Inherited(print, model_object), m_stepmask(slaposCount, true), @@ -545,6 +845,75 @@ SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object): SLAPrintObject::~SLAPrintObject() {} +// Called by SLAPrint::apply_config(). +// This method only accepts SLAPrintObjectConfig option keys. +bool SLAPrintObject::invalidate_state_by_config_options(const std::vector &opt_keys) +{ + if (opt_keys.empty()) + return false; + + std::vector steps; + bool invalidated = false; + for (const t_config_option_key &opt_key : opt_keys) { + if ( opt_key == "support_head_front_radius" + || opt_key == "support_head_penetration" + || opt_key == "support_head_back_radius" + || opt_key == "support_head_width" + || opt_key == "support_pillar_radius" + || opt_key == "support_base_radius" + || opt_key == "support_base_height" + || opt_key == "support_critical_angle" + || opt_key == "support_max_bridge_length" + || opt_key == "support_object_elevation") { + steps.emplace_back(slaposSupportTree); + } else if ( + opt_key == "pad_enable" + || opt_key == "pad_wall_thickness" + || opt_key == "pad_wall_height" + || opt_key == "pad_max_merge_distance" + || opt_key == "pad_edge_radius") { + steps.emplace_back(slaposBasePool); + } else { + // All keys should be covered. + assert(false); + } + } + + sort_remove_duplicates(steps); + for (SLAPrintObjectStep step : steps) + invalidated |= this->invalidate_step(step); + return invalidated; +} + +bool SLAPrintObject::invalidate_step(SLAPrintObjectStep step) +{ + bool invalidated = Inherited::invalidate_step(step); + // propagate to dependent steps + if (step == slaposObjectSlice) { + invalidated |= this->invalidate_all_steps(); + } else if (step == slaposSupportIslands) { + invalidated |= this->invalidate_steps({ slaposSupportPoints, slaposSupportTree, slaposBasePool, slaposSliceSupports }); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposSupportPoints) { + invalidated |= this->invalidate_steps({ slaposSupportTree, slaposBasePool, slaposSliceSupports }); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposSupportTree) { + invalidated |= this->invalidate_steps({ slaposBasePool, slaposSliceSupports }); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposBasePool) { + invalidated |= this->invalidate_step(slaposSliceSupports); + invalidated |= m_print->invalidate_step(slapsRasterize); + } else if (step == slaposSliceSupports) { + invalidated |= m_print->invalidate_step(slapsRasterize); + } + return invalidated; +} + +bool SLAPrintObject::invalidate_all_steps() +{ + return Inherited::invalidate_all_steps() | m_print->invalidate_all_steps(); +} + double SLAPrintObject::get_elevation() const { double ret = m_config.support_object_elevation.getFloat(); @@ -565,6 +934,18 @@ double SLAPrintObject::get_elevation() const { return ret; } +double SLAPrintObject::get_current_elevation() const +{ + bool has_supports = is_step_done(slaposSupportTree); + bool has_pad = is_step_done(slaposBasePool); + if(!has_supports && !has_pad) return 0; + else if(has_supports && !has_pad) + return m_config.support_object_elevation.getFloat(); + else return get_elevation(); + + return 0; +} + namespace { // dummy empty static containers for return values in some methods const std::vector EMPTY_SLICES; const TriangleMesh EMPTY_MESH; diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index ae797e9fb..704508278 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -38,16 +38,18 @@ private: // Prevents erroneous use by other classes. using Inherited = _SLAPrintObjectBase; public: - const Transform3d& trafo() const { return m_trafo; } + const SLAPrintObjectConfig& config() const { return m_config; } + const Transform3d& trafo() const { return m_trafo; } struct Instance { Instance(ModelID instance_id, const Point &shift, float rotation) : instance_id(instance_id), shift(shift), rotation(rotation) {} + bool operator==(const Instance &rhs) const { return this->instance_id == rhs.instance_id && this->shift == rhs.shift && this->rotation == rhs.rotation; } // ID of the corresponding ModelInstance. ModelID instance_id; // Slic3r::Point objects in scaled G-code coordinates Point shift; // Rotation along the Z axis, in radians. - float rotation; + float rotation; }; const std::vector& instances() const { return m_instances; } @@ -72,6 +74,11 @@ public: // as the pad height also needs to be considered. double get_elevation() const; + // This method returns the needed elevation according to the processing + // status. If the supports are not ready, it is zero, if they are and the + // pad is not, then without the pad, otherwise the full value is returned. + double get_current_elevation() const; + // Should be obvious const std::vector& get_support_slices() const; const std::vector& get_model_slices() const; @@ -95,12 +102,14 @@ protected: m_transformed_rmesh.invalidate([this, &trafo](){ m_trafo = trafo; }); } - bool set_instances(const std::vector &instances); + void set_instances(const std::vector &instances) { m_instances = instances; } // Invalidates the step, and its depending steps in SLAPrintObject and SLAPrint. bool invalidate_step(SLAPrintObjectStep step); + bool invalidate_all_steps(); + // Invalidate steps based on a set of parameters changed. + bool invalidate_state_by_config_options(const std::vector &opt_keys); private: - // Object specific configuration, pulled from the configuration layer. SLAPrintObjectConfig m_config; // Translation in Z + Rotation by Y and Z + Scaling / Mirroring. @@ -143,7 +152,7 @@ public: PrinterTechnology technology() const noexcept { return ptSLA; } void clear() override; - bool empty() const override { return false; } + bool empty() const override { return m_objects.empty(); } ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override; void process() override; @@ -156,8 +165,13 @@ private: using SLAPrinter = FilePrinter; using SLAPrinterPtr = std::unique_ptr; + // Invalidate steps based on a set of parameters changed. + bool invalidate_state_by_config_options(const std::vector &opt_keys); + SLAPrinterConfig m_printer_config; SLAMaterialConfig m_material_config; + SLAPrintObjectConfig m_default_object_config; + PrintObjects m_objects; std::vector m_stepmask; SLAPrinterPtr m_printer; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index f7ab20088..17459dde5 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -31,6 +31,8 @@ #define ENABLE_NEW_MENU_LAYOUT (1 && ENABLE_1_42_0) // All rotations made using the rotate gizmo are done with respect to the world reference system #define ENABLE_WORLD_ROTATIONS (1 && ENABLE_1_42_0) +// Enables shortcut keys for gizmos +#define ENABLE_GIZMOS_SHORTCUT (1 && ENABLE_1_42_0) #endif // _technologies_h_ diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 8635c2a57..193673260 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -201,6 +201,8 @@ const float GLVolume::HOVER_COLOR[4] = { 0.4f, 0.9f, 0.1f, 1.0f }; const float GLVolume::OUTSIDE_COLOR[4] = { 0.0f, 0.38f, 0.8f, 1.0f }; const float GLVolume::SELECTED_OUTSIDE_COLOR[4] = { 0.19f, 0.58f, 1.0f, 1.0f }; const float GLVolume::DISABLED_COLOR[4] = { 0.25f, 0.25f, 0.25f, 1.0f }; +const float GLVolume::SLA_SUPPORT_COLOR[4] = { 0.75f, 0.75f, 0.75f, 1.0f }; +const float GLVolume::SLA_PAD_COLOR[4] = { 0.0f, 0.2f, 0.0f, 1.0f }; GLVolume::GLVolume(float r, float g, float b, float a) #if ENABLE_MODELVOLUME_TRANSFORM @@ -214,6 +216,7 @@ GLVolume::GLVolume(float r, float g, float b, float a) , m_world_matrix_dirty(true) , m_transformed_bounding_box_dirty(true) #endif // ENABLE_MODELVOLUME_TRANSFORM + , m_sla_shift_z(0.0) , m_transformed_convex_hull_bounding_box_dirty(true) , m_convex_hull(nullptr) , m_convex_hull_owned(false) @@ -378,7 +381,14 @@ void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned) m_convex_hull_owned = owned; } -#if !ENABLE_MODELVOLUME_TRANSFORM +#if ENABLE_MODELVOLUME_TRANSFORM +Transform3d GLVolume::world_matrix() const +{ + Transform3d m = m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); + m.translation()(2) += m_sla_shift_z; + return m; +} +#else const Transform3f& GLVolume::world_matrix() const { if (m_world_matrix_dirty) @@ -388,7 +398,7 @@ const Transform3f& GLVolume::world_matrix() const } return m_world_matrix; } -#endif // !ENABLE_MODELVOLUME_TRANSFORM +#endif // ENABLE_MODELVOLUME_TRANSFORM const BoundingBoxf3& GLVolume::transformed_bounding_box() const { @@ -745,18 +755,9 @@ int GLVolumeCollection::load_object_volume( { 0.5f, 0.5f, 1.0f, 1.f } }; - const ModelVolume *model_volume = model_object->volumes[volume_idx]; - - int extruder_id = -1; - if (model_volume->is_model_part()) - { - const ConfigOption *opt = model_volume->config.option("extruder"); - if (opt == nullptr) - opt = model_object->config.option("extruder"); - extruder_id = (opt == nullptr) ? 0 : opt->getInt(); - } - - const ModelInstance *instance = model_object->instances[instance_idx]; + const ModelVolume *model_volume = model_object->volumes[volume_idx]; + const int extruder_id = model_volume->extruder_id(); + const ModelInstance *instance = model_object->instances[instance_idx]; #if ENABLE_MODELVOLUME_TRANSFORM const TriangleMesh& mesh = model_volume->mesh; #else @@ -822,21 +823,17 @@ void GLVolumeCollection::load_object_auxiliary( bool use_VBOs) { assert(print_object->is_step_done(milestone)); + Transform3d mesh_trafo_inv = print_object->trafo().inverse(); // Get the support mesh. - TriangleMesh mesh; - switch (milestone) { - case slaposSupportTree: mesh = print_object->support_mesh(); break; - case slaposBasePool: mesh = print_object->pad_mesh(); break; - default: - assert(false); - } + TriangleMesh mesh = print_object->get_mesh(milestone); + mesh.transform(mesh_trafo_inv); // Convex hull is required for out of print bed detection. TriangleMesh convex_hull = mesh.convex_hull_3d(); + convex_hull.transform(mesh_trafo_inv); for (const std::pair &instance_idx : instances) { const ModelInstance &model_instance = *print_object->model_object()->instances[instance_idx.first]; const SLAPrintObject::Instance &print_instance = print_object->instances()[instance_idx.second]; - float color[4] { 0.f, 0.f, 1.f, 1.f }; - this->volumes.emplace_back(new GLVolume(color)); + this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); GLVolume &v = *this->volumes.back(); if (use_VBOs) v.indexed_vertex_array.load_mesh_full_shading(mesh); @@ -845,13 +842,12 @@ void GLVolumeCollection::load_object_auxiliary( // finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry(). v.bounding_box = v.indexed_vertex_array.bounding_box(); v.indexed_vertex_array.finalize_geometry(use_VBOs); - v.composite_id = GLVolume::CompositeID(obj_idx, -1, (int)instance_idx.first); + v.composite_id = GLVolume::CompositeID(obj_idx, -milestone, (int)instance_idx.first); v.geometry_id = std::pair(timestamp, model_instance.id().id); // Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance. v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true); v.is_modifier = false; v.shader_outside_printer_detection_enabled = true; - //FIXME adjust with print_instance? v.set_instance_transformation(model_instance.get_transformation()); // Leave the volume transformation at identity. // v.set_volume_transformation(model_volume->get_transformation()); @@ -1106,7 +1102,7 @@ void GLVolumeCollection::update_colors_by_extruder(const DynamicPrintConfig* con for (GLVolume* volume : volumes) { - if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower) + if ((volume == nullptr) || volume->is_modifier || volume->is_wipe_tower || (volume->volume_idx() < 0)) continue; int extruder_id = volume->extruder_id - 1; diff --git a/src/slic3r/GUI/3DScene.hpp b/src/slic3r/GUI/3DScene.hpp index 28541cea2..eb6bb9311 100644 --- a/src/slic3r/GUI/3DScene.hpp +++ b/src/slic3r/GUI/3DScene.hpp @@ -253,6 +253,8 @@ public: static const float OUTSIDE_COLOR[4]; static const float SELECTED_OUTSIDE_COLOR[4]; static const float DISABLED_COLOR[4]; + static const float SLA_SUPPORT_COLOR[4]; + static const float SLA_PAD_COLOR[4]; GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f); GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {} @@ -276,6 +278,8 @@ private: // Whether or not is needed to recalculate the world matrix. mutable bool m_world_matrix_dirty; #endif // ENABLE_MODELVOLUME_TRANSFORM + // Shift in z required by sla supports+pad + double m_sla_shift_z; // Bounding box of this volume, in unscaled coordinates. mutable BoundingBoxf3 m_transformed_bounding_box; // Whether or not is needed to recalculate the transformed bounding box. @@ -424,6 +428,9 @@ public: const Vec3d& get_offset() const; void set_offset(const Vec3d& offset); #endif // ENABLE_MODELVOLUME_TRANSFORM + + double get_sla_shift_z() const { return m_sla_shift_z; } + void set_sla_shift_z(double z) { m_sla_shift_z = z; } void set_convex_hull(const TriangleMesh *convex_hull, bool owned); @@ -432,7 +439,7 @@ public: int instance_idx() const { return this->composite_id.instance_id; } #if ENABLE_MODELVOLUME_TRANSFORM - Transform3d world_matrix() const { return m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); } + Transform3d world_matrix() const; #else const Transform3f& world_matrix() const; #endif // ENABLE_MODELVOLUME_TRANSFORM diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 17959af03..f4fc163a0 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1075,7 +1075,6 @@ GLCanvas3D::Selection::VolumeCache::TransformCache::TransformCache() , scaling_factor(Vec3d::Ones()) , rotation_matrix(Transform3d::Identity()) , scale_matrix(Transform3d::Identity()) - , no_position_matrix(Transform3d::Identity()) { } @@ -1086,7 +1085,6 @@ GLCanvas3D::Selection::VolumeCache::TransformCache::TransformCache(const Geometr { rotation_matrix = Geometry::assemble_transform(Vec3d::Zero(), rotation); scale_matrix = Geometry::assemble_transform(Vec3d::Zero(), Vec3d::Zero(), scaling_factor); - no_position_matrix = transform.get_matrix(true); } GLCanvas3D::Selection::VolumeCache::VolumeCache(const Geometry::Transformation& volume_transform, const Geometry::Transformation& instance_transform) @@ -1456,7 +1454,7 @@ void GLCanvas3D::Selection::translate(const Vec3d& displacement) (*m_volumes)[i]->set_instance_offset(m_cache.volumes_data[i].get_instance_position() + displacement); else if (m_mode == Volume) { - Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_no_position_matrix() * m_cache.volumes_data[i].get_volume_no_position_matrix()).inverse() * displacement; + Vec3d local_displacement = (m_cache.volumes_data[i].get_instance_rotation_matrix() * m_cache.volumes_data[i].get_volume_rotation_matrix()).inverse() * displacement; (*m_volumes)[i]->set_volume_offset(m_cache.volumes_data[i].get_volume_position() + local_displacement); } #else @@ -1940,7 +1938,14 @@ void GLCanvas3D::Selection::_update_type() if (m_cache.content.size() == 1) // single object { const ModelObject* model_object = m_model->objects[m_cache.content.begin()->first]; - unsigned int volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int model_volumes_count = (unsigned int)model_object->volumes.size(); + unsigned int sla_volumes_count = 0; + for (unsigned int i : m_list) + { + if ((*m_volumes)[i]->volume_idx() < 0) + ++sla_volumes_count; + } + unsigned int volumes_count = model_volumes_count + sla_volumes_count; unsigned int instances_count = (unsigned int)model_object->instances.size(); unsigned int selected_instances_count = (unsigned int)m_cache.content.begin()->second.size(); if (volumes_count * instances_count == (unsigned int)m_list.size()) @@ -2659,6 +2664,43 @@ bool GLCanvas3D::Gizmos::is_running() const return (curr != nullptr) ? (curr->get_state() == GLGizmoBase::On) : false; } +#if ENABLE_GIZMOS_SHORTCUT +bool GLCanvas3D::Gizmos::handle_shortcut(int key, const Selection& selection) +{ + if (!m_enabled) + return false; + + bool handled = false; + for (GizmosMap::iterator it = m_gizmos.begin(); it != m_gizmos.end(); ++it) + { + if ((it->second == nullptr) || !it->second->is_selectable()) + continue; + + int it_key = it->second->get_shortcut_key(); + + if (it->second->is_activable(selection) && ((it_key == key - 64) || (it_key == key - 96))) + { + if ((it->second->get_state() == GLGizmoBase::On)) + { + it->second->set_state(GLGizmoBase::Off); + m_current = Undefined; + handled = true; + } + else if ((it->second->get_state() == GLGizmoBase::Off)) + { + it->second->set_state(GLGizmoBase::On); + m_current = it->first; + handled = true; + } + } + else + it->second->set_state(GLGizmoBase::Off); + } + + return handled; +} +#endif // ENABLE_GIZMOS_SHORTCUT + bool GLCanvas3D::Gizmos::is_dragging() const { if (!m_enabled) @@ -3800,12 +3842,13 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re struct ModelVolumeState { ModelVolumeState(const GLVolume *volume) : - geometry_id(volume->geometry_id), volume_idx(-1) {} - ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) : - geometry_id(std::make_pair(volume_id.id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} + model_volume(nullptr), geometry_id(volume->geometry_id), volume_idx(-1) {} + ModelVolumeState(const ModelVolume *model_volume, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) : + model_volume(model_volume), geometry_id(std::make_pair(model_volume->id().id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {} ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) : - geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} + model_volume(nullptr), geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {} bool new_geometry() const { return this->volume_idx == size_t(-1); } + const ModelVolume *model_volume; // ModelID of ModelVolume + ModelID of ModelInstance // or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance std::pair geometry_id; @@ -3844,7 +3887,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re const ModelInstance *model_instance = model_object->instances[instance_idx]; for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) { const ModelVolume *model_volume = model_object->volumes[volume_idx]; - model_volume_state.emplace_back(model_volume->id(), model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); + model_volume_state.emplace_back(model_volume, model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx)); } } } @@ -3894,9 +3937,16 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re delete volume; } else { // This GLVolume will be reused. + volume->set_sla_shift_z(0.0); map_glvolume_old_to_new[volume_id] = glvolumes_new.size(); mvs->volume_idx = glvolumes_new.size(); glvolumes_new.emplace_back(volume); + // Update color of the volume based on the current extruder. + if (mvs->model_volume != nullptr) { + int extruder_id = mvs->model_volume->extruder_id(); + if (extruder_id != -1) + volume->extruder_id = extruder_id; + } } } } @@ -3998,7 +4048,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re instances[istep].emplace_back(std::pair(instance_idx, print_instance_idx)); else // Recycling an old GLVolume. Update the Object/Instance indices into the current Model. - m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, -1, instance_idx); + m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, m_volumes.volumes[it->volume_idx]->volume_idx(), instance_idx); } } @@ -4013,11 +4063,11 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re { // If any volume has been added // Shift-up all volumes of the object so that it has the right elevation with respect to the print bed - Vec3d shift_z(0.0, 0.0, print_object->get_elevation()); + double shift_z = print_object->get_elevation(); for (GLVolume* volume : m_volumes.volumes) { if (volume->object_idx() == object_idx) - volume->set_instance_offset(volume->get_instance_offset() + shift_z); + volume->set_sla_shift_z(shift_z); } } } @@ -4273,7 +4323,16 @@ void GLCanvas3D::on_char(wxKeyEvent& evt) #endif // ENABLE_MODIFIED_CAMERA_TARGET default: { - evt.Skip(); +#if ENABLE_GIZMOS_SHORTCUT + if (m_gizmos.handle_shortcut(keyCode, m_selection)) + { + _update_gizmos_data(); + render(); + } + else +#endif // ENABLE_GIZMOS_SHORTCUT + evt.Skip(); + break; } } @@ -4761,16 +4820,14 @@ void GLCanvas3D::on_key_down(wxKeyEvent& evt) else { int key = evt.GetKeyCode(); +#ifdef __WXOSX__ + if (key == WXK_BACK) +#else if (key == WXK_DELETE) +#endif // __WXOSX__ post_event(SimpleEvent(EVT_GLCANVAS_REMOVE_OBJECT)); else - { -#ifdef __WXOSX__ - if (key == WXK_BACK) - post_event(SimpleEvent(EVT_GLCANVAS_REMOVE_OBJECT)); -#endif - evt.Skip(); - } + evt.Skip(); } } diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 5404701f8..7dac13d72 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -384,7 +384,6 @@ public: Vec3d scaling_factor; Transform3d rotation_matrix; Transform3d scale_matrix; - Transform3d no_position_matrix; TransformCache(); explicit TransformCache(const Geometry::Transformation& transform); @@ -415,14 +414,12 @@ public: const Vec3d& get_volume_scaling_factor() const { return m_volume.scaling_factor; } const Transform3d& get_volume_rotation_matrix() const { return m_volume.rotation_matrix; } const Transform3d& get_volume_scale_matrix() const { return m_volume.scale_matrix; } - const Transform3d& get_volume_no_position_matrix() const { return m_volume.no_position_matrix; } const Vec3d& get_instance_position() const { return m_instance.position; } const Vec3d& get_instance_rotation() const { return m_instance.rotation; } const Vec3d& get_instance_scaling_factor() const { return m_instance.scaling_factor; } const Transform3d& get_instance_rotation_matrix() const { return m_instance.rotation_matrix; } const Transform3d& get_instance_scale_matrix() const { return m_instance.scale_matrix; } - const Transform3d& get_instance_no_position_matrix() const { return m_instance.no_position_matrix; } #else const Vec3d& get_position() const { return m_position; } const Vec3d& get_rotation() const { return m_rotation; } @@ -611,6 +608,9 @@ private: EType get_current_type() const; bool is_running() const; +#if ENABLE_GIZMOS_SHORTCUT + bool handle_shortcut(int key, const Selection& selection); +#endif // ENABLE_GIZMOS_SHORTCUT bool is_dragging() const; void start_dragging(const Selection& selection); diff --git a/src/slic3r/GUI/GLGizmo.cpp b/src/slic3r/GUI/GLGizmo.cpp index 906e064e0..40c1f09fe 100644 --- a/src/slic3r/GUI/GLGizmo.cpp +++ b/src/slic3r/GUI/GLGizmo.cpp @@ -30,6 +30,10 @@ #include "GUI_Utils.hpp" #include "GUI_App.hpp" +#if ENABLE_GIZMOS_SHORTCUT +#include +#endif // ENABLE_GIZMOS_SHORTCUT + // TODO: Display tooltips quicker on Linux static const float DEFAULT_BASE_COLOR[3] = { 0.625f, 0.625f, 0.625f }; @@ -161,6 +165,9 @@ GLGizmoBase::GLGizmoBase(GLCanvas3D& parent) : m_parent(parent) , m_group_id(-1) , m_state(Off) +#if ENABLE_GIZMOS_SHORTCUT + , m_shortcut_key(0) +#endif // ENABLE_GIZMOS_SHORTCUT , m_hover_id(-1) , m_dragging(false) { @@ -639,6 +646,10 @@ bool GLGizmoRotate3D::on_init() if (!m_textures[On].load_from_file(path + "rotate_on.png", false)) return false; +#if ENABLE_GIZMOS_SHORTCUT + m_shortcut_key = WXK_CONTROL_R; +#endif // ENABLE_GIZMOS_SHORTCUT + return true; } @@ -713,6 +724,10 @@ bool GLGizmoScale3D::on_init() m_grabbers[2].angles(0) = half_pi; m_grabbers[3].angles(0) = half_pi; +#if ENABLE_GIZMOS_SHORTCUT + m_shortcut_key = WXK_CONTROL_S; +#endif // ENABLE_GIZMOS_SHORTCUT + return true; } @@ -1057,6 +1072,10 @@ bool GLGizmoMove3D::on_init() m_grabbers.push_back(Grabber()); } +#if ENABLE_GIZMOS_SHORTCUT + m_shortcut_key = WXK_CONTROL_M; +#endif // ENABLE_GIZMOS_SHORTCUT + return true; } @@ -1213,6 +1232,10 @@ bool GLGizmoFlatten::on_init() if (!m_textures[On].load_from_file(path + "layflat_on.png", false)) return false; +#if ENABLE_GIZMOS_SHORTCUT + m_shortcut_key = WXK_CONTROL_F; +#endif // ENABLE_GIZMOS_SHORTCUT + return true; } @@ -1542,6 +1565,10 @@ bool GLGizmoSlaSupports::on_init() if (!m_textures[On].load_from_file(path + "sla_support_points_on.png", false)) return false; +#if ENABLE_GIZMOS_SHORTCUT + m_shortcut_key = WXK_CONTROL_L; +#endif // ENABLE_GIZMOS_SHORTCUT + return true; } @@ -1893,6 +1920,10 @@ bool GLGizmoCut::on_init() m_grabbers.emplace_back(); +#if ENABLE_GIZMOS_SHORTCUT + m_shortcut_key = WXK_CONTROL_C; +#endif // ENABLE_GIZMOS_SHORTCUT + return true; } diff --git a/src/slic3r/GUI/GLGizmo.hpp b/src/slic3r/GUI/GLGizmo.hpp index 647d870e4..a8073bd78 100644 --- a/src/slic3r/GUI/GLGizmo.hpp +++ b/src/slic3r/GUI/GLGizmo.hpp @@ -77,6 +77,9 @@ protected: int m_group_id; EState m_state; +#if ENABLE_GIZMOS_SHORTCUT + int m_shortcut_key; +#endif // ENABLE_GIZMOS_SHORTCUT // textures are assumed to be square and all with the same size in pixels, no internal check is done GLTexture m_textures[Num_States]; int m_hover_id; @@ -100,6 +103,11 @@ public: EState get_state() const { return m_state; } void set_state(EState state) { m_state = state; on_set_state(); } +#if ENABLE_GIZMOS_SHORTCUT + int get_shortcut_key() const { return m_shortcut_key; } + void set_shortcut_key(int key) { m_shortcut_key = key; } +#endif // ENABLE_GIZMOS_SHORTCUT + bool is_activable(const GLCanvas3D::Selection& selection) const { return on_is_activable(selection); } bool is_selectable() const { return on_is_selectable(); } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index da358670b..3cfcdad0f 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -25,6 +25,7 @@ ObjectList::ObjectList(wxWindow* parent) : { // Fill CATEGORY_ICON { + // ptFFF CATEGORY_ICON[L("Layers and Perimeters")] = wxBitmap(from_u8(var("layers.png")), wxBITMAP_TYPE_PNG); CATEGORY_ICON[L("Infill")] = wxBitmap(from_u8(var("infill.png")), wxBITMAP_TYPE_PNG); CATEGORY_ICON[L("Support material")] = wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); @@ -34,6 +35,9 @@ ObjectList::ObjectList(wxWindow* parent) : // CATEGORY_ICON[L("Skirt and brim")] = wxBitmap(from_u8(var("box.png")), wxBITMAP_TYPE_PNG); // CATEGORY_ICON[L("Speed > Acceleration")] = wxBitmap(from_u8(var("time.png")), wxBITMAP_TYPE_PNG); CATEGORY_ICON[L("Advanced")] = wxBitmap(from_u8(var("wand.png")), wxBITMAP_TYPE_PNG); + // ptSLA + CATEGORY_ICON[L("Supports")] = wxBitmap(from_u8(var("building.png")), wxBITMAP_TYPE_PNG); + CATEGORY_ICON[L("Pad")] = wxBitmap(from_u8(var("brick.png")), wxBITMAP_TYPE_PNG); } // create control @@ -310,8 +314,8 @@ void ObjectList::show_context_menu() if (!(m_objects_model->GetItemType(item) & (itObject | itVolume))) return; const auto menu = m_objects_model->GetParent(item) == wxDataViewItem(0) ? - create_add_part_popupmenu() : - create_part_settings_popupmenu(); + create_object_popupmenu() : + create_part_popupmenu(); wxGetApp().tab_panel()->GetPage(0)->PopupMenu(menu); } } @@ -436,6 +440,13 @@ void ObjectList::on_drop(wxDataViewEvent &event) std::vector get_options(const bool is_part) { + if (wxGetApp().plater()->printer_technology() == ptSLA) { + SLAPrintObjectConfig full_sla_config; + auto options = full_sla_config.keys(); + options.erase(find(options.begin(), options.end(), "layer_height")); + return options; + } + PrintRegionConfig reg_config; auto options = reg_config.keys(); if (!is_part) { @@ -463,7 +474,10 @@ void get_options_menu(settings_menu_hierarchy& settings_menu, bool is_part) if (category.empty() || (category == "Extruders" && extruders_cnt == 1)) continue; - std::pair option_label(option, opt->label); + const std::string& label = opt->label.empty() ? opt->full_label : + opt->full_label.empty() ? opt->label : + opt->full_label + " " + opt->label;; + std::pair option_label(option, label); std::vector< std::pair > new_category; auto& cat_opt_label = settings_menu.find(category) == settings_menu.end() ? new_category : settings_menu.at(category); cat_opt_label.push_back(option_label); @@ -575,14 +589,27 @@ wxMenuItem* ObjectList::menu_item_settings(wxMenu* menu, int id, const bool is_p auto menu_item = new wxMenuItem(menu, id, _(L("Add settings"))); menu_item->SetBitmap(m_bmp_cog); - auto sub_menu = create_add_settings_popupmenu(is_part); + auto sub_menu = create_settings_popupmenu(is_part); menu_item->SetSubMenu(sub_menu); return menu_item; } -wxMenu* ObjectList::create_add_part_popupmenu() +wxMenu* ObjectList::create_object_popupmenu() { wxMenu *menu = new wxMenu; + if (wxGetApp().plater()->printer_technology() == ptSLA) + { + wxWindowID config_id_base = NewControlId(1); + // Append settings popupmenu + menu->Append(menu_item_settings(menu, config_id_base, false)); + + menu->Bind(wxEVT_MENU, [menu, this](wxEvent &event) { + get_settings_choice(menu, event.GetId(), false); + }); + + return menu; + } + // Note: id accords to type of the sub-object, so sequence of the menu items is important std::vector menu_object_types_items = {L("Add part"), // ~ModelVolume::MODEL_PART L("Add modifier"), // ~ModelVolume::PARAMETER_MODIFIER @@ -635,7 +662,7 @@ wxMenu* ObjectList::create_add_part_popupmenu() return menu; } -wxMenu* ObjectList::create_part_settings_popupmenu() +wxMenu* ObjectList::create_part_popupmenu() { wxMenu *menu = new wxMenu; wxWindowID config_id_base = NewControlId(3); @@ -671,7 +698,7 @@ wxMenu* ObjectList::create_part_settings_popupmenu() return menu; } -wxMenu* ObjectList::create_add_settings_popupmenu(bool is_part) +wxMenu* ObjectList::create_settings_popupmenu(bool is_part) { wxMenu *menu = new wxMenu; diff --git a/src/slic3r/GUI/GUI_ObjectList.hpp b/src/slic3r/GUI/GUI_ObjectList.hpp index 31ae5f5ed..862e0237b 100644 --- a/src/slic3r/GUI/GUI_ObjectList.hpp +++ b/src/slic3r/GUI/GUI_ObjectList.hpp @@ -98,9 +98,9 @@ public: void menu_item_add_generic(wxMenuItem* &menu, int id, const int type); wxMenuItem* menu_item_split(wxMenu* menu, int id); wxMenuItem* menu_item_settings(wxMenu* menu, int id, const bool is_part); - wxMenu* create_add_part_popupmenu(); - wxMenu* create_part_settings_popupmenu(); - wxMenu* create_add_settings_popupmenu(bool is_part); + wxMenu* create_object_popupmenu(); + wxMenu* create_part_popupmenu(); + wxMenu* create_settings_popupmenu(bool is_part); void load_subobject(int type); void load_part(ModelObject* model_object, wxArrayString& part_names, int type); diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index b0d0c178b..608c78e0e 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -211,6 +211,11 @@ bool MainFrame::can_delete() const { return (m_plater != nullptr) ? !m_plater->is_selection_empty() : false; } + +bool MainFrame::can_delete_all() const +{ + return (m_plater != nullptr) ? !m_plater->model().objects.empty() : false; +} #endif // ENABLE_NEW_MENU_LAYOUT void MainFrame::init_menubar() @@ -319,11 +324,15 @@ void MainFrame::init_menubar() editMenu = new wxMenu(); wxMenuItem* item_select_all = append_menu_item(editMenu, wxID_ANY, L("Select all\tCtrl+A"), L("Selects all objects"), [this](wxCommandEvent&) { m_plater->select_all(); }, ""); + editMenu->AppendSeparator(); wxMenuItem* item_delete_sel = append_menu_item(editMenu, wxID_ANY, L("Delete selected\tDel"), L("Deletes the current selection"), [this](wxCommandEvent&) { m_plater->remove_selected(); }, ""); + wxMenuItem* item_delete_all = append_menu_item(editMenu, wxID_ANY, L("Delete all\tCtrl+Del"), L("Deletes all objects"), + [this](wxCommandEvent&) { m_plater->reset(); }, ""); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_select()); }, item_select_all->GetId()); Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete()); }, item_delete_sel->GetId()); + Bind(wxEVT_UPDATE_UI, [this](wxUpdateUIEvent& evt) { evt.Enable(can_delete_all()); }, item_delete_all->GetId()); } #endif // ENABLE_NEW_MENU_LAYOUT diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp index 488d0bd04..2ee46cbb1 100644 --- a/src/slic3r/GUI/MainFrame.hpp +++ b/src/slic3r/GUI/MainFrame.hpp @@ -73,6 +73,7 @@ class MainFrame : public wxFrame bool can_change_view() const; bool can_select() const; bool can_delete() const; + bool can_delete_all() const; #endif // ENABLE_NEW_MENU_LAYOUT public: diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index f3b3318db..0dcc61542 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1771,9 +1771,7 @@ unsigned int Plater::priv::update_background_process() } break; case ptSLA: - //FIXME as of now the Print::APPLY_STATUS_INVALIDATED is not reliable, and - // currently the scene refresh is expensive and loses selection. - //return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; + return_state |= UPDATE_BACKGROUND_PROCESS_REFRESH_SCENE; break; } } @@ -1984,13 +1982,8 @@ void Plater::priv::on_process_completed(wxCommandEvent &evt) case ptSLA: // Update the SLAPrint from the current Model, so that the reload_scene() // pulls the correct data. - - // FIXME: SLAPrint::apply is not ready for this. At this stage it would - // invalidate the previous result and the supports would not be available - // for rendering. -// if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART) -// this->schedule_background_process(); - + if (this->update_background_process() & UPDATE_BACKGROUND_PROCESS_RESTART) + this->schedule_background_process(); _3DScene::reload_scene(canvas3D, true); break; } @@ -2281,6 +2274,8 @@ void Plater::select_view(const std::string& direction) { p->select_view(directio void Plater::select_all() { p->select_all(); } void Plater::remove(size_t obj_idx) { p->remove(obj_idx); } +void Plater::reset() { p->reset(); } + void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_model(obj_idx); } void Plater::remove_selected() diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index b9cc9d528..5a5585935 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -130,6 +130,7 @@ public: void select_all(); void remove(size_t obj_idx); + void reset(); void delete_object_from_model(size_t obj_idx); void remove_selected(); void increase_instances(size_t num = 1); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 85220b8ad..2cc24eca5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3011,6 +3011,7 @@ void TabSLAPrint::build() optgroup->append_single_option_line("support_critical_angle"); optgroup->append_single_option_line("support_max_bridge_length"); + page = add_options_page(_(L("Pad")), "brick.png"); optgroup = page->new_optgroup(_(L("Pad"))); optgroup->append_single_option_line("pad_enable"); optgroup->append_single_option_line("pad_wall_thickness");