Merge branch 'master' into tm_arrange_selection
This commit is contained in:
commit
8d66b51e8c
31 changed files with 1156 additions and 441 deletions
|
@ -430,6 +430,8 @@ private:
|
|||
// floats of the first edge matches all six floats of the second edge.
|
||||
void stl_check_facets_exact(stl_file *stl)
|
||||
{
|
||||
assert(stl->facet_start.size() == stl->neighbors_start.size());
|
||||
|
||||
stl->stats.connected_edges = 0;
|
||||
stl->stats.connected_facets_1_edge = 0;
|
||||
stl->stats.connected_facets_2_edge = 0;
|
||||
|
|
|
@ -127,6 +127,10 @@ struct stl_file {
|
|||
this->stats.reset();
|
||||
}
|
||||
|
||||
size_t memsize() const {
|
||||
return sizeof(*this) + sizeof(stl_facet) * facet_start.size() + sizeof(stl_neighbors) * neighbors_start.size();
|
||||
}
|
||||
|
||||
std::vector<stl_facet> facet_start;
|
||||
std::vector<stl_neighbors> neighbors_start;
|
||||
// Statistics
|
||||
|
@ -139,6 +143,10 @@ struct indexed_triangle_set
|
|||
|
||||
void clear() { indices.clear(); vertices.clear(); }
|
||||
|
||||
size_t memsize() const {
|
||||
return sizeof(*this) + sizeof(stl_triangle_vertex_indices) * indices.size() + sizeof(stl_vertex) * vertices.size();
|
||||
}
|
||||
|
||||
std::vector<stl_triangle_vertex_indices> indices;
|
||||
std::vector<stl_vertex> vertices;
|
||||
//FIXME add normals once we get rid of the stl_file from TriangleMesh completely.
|
||||
|
|
|
@ -93,7 +93,7 @@ enum ConfigOptionMode {
|
|||
comExpert
|
||||
};
|
||||
|
||||
enum PrinterTechnology
|
||||
enum PrinterTechnology : unsigned char
|
||||
{
|
||||
// Fused Filament Fabrication
|
||||
ptFFF,
|
||||
|
|
|
@ -169,6 +169,9 @@ static inline Point wipe_tower_point_to_object_point(GCode &gcodegen, const Vec2
|
|||
|
||||
std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const
|
||||
{
|
||||
if (new_extruder_id != -1 && new_extruder_id != tcr.new_tool)
|
||||
throw std::invalid_argument("Error: WipeTowerIntegration::append_tcr was asked to do a toolchange it didn't expect.");
|
||||
|
||||
std::string gcode;
|
||||
|
||||
// Toolchangeresult.gcode assumes the wipe tower corner is at the origin
|
||||
|
@ -182,8 +185,11 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
end_pos = Eigen::Rotation2Df(alpha) * end_pos;
|
||||
end_pos += m_wipe_tower_pos;
|
||||
}
|
||||
std::string tcr_rotated_gcode = tcr.priming ? tcr.gcode : rotate_wipe_tower_moves(tcr.gcode, tcr.start_pos, m_wipe_tower_pos, alpha);
|
||||
|
||||
|
||||
Vec2f wipe_tower_offset = tcr.priming ? Vec2f::Zero() : m_wipe_tower_pos;
|
||||
float wipe_tower_rotation = tcr.priming ? 0.f : alpha;
|
||||
|
||||
std::string tcr_rotated_gcode = post_process_wipe_tower_moves(tcr, wipe_tower_offset, wipe_tower_rotation);
|
||||
|
||||
// Disable linear advance for the wipe tower operations.
|
||||
gcode += (gcodegen.config().gcode_flavor == gcfRepRap ? std::string("M572 D0 S0\n") : std::string("M900 K0\n"));
|
||||
|
@ -285,17 +291,21 @@ std::string WipeTowerIntegration::append_tcr(GCode &gcodegen, const WipeTower::T
|
|||
|
||||
// This function postprocesses gcode_original, rotates and moves all G1 extrusions and returns resulting gcode
|
||||
// Starting position has to be supplied explicitely (otherwise it would fail in case first G1 command only contained one coordinate)
|
||||
std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const
|
||||
std::string WipeTowerIntegration::post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const
|
||||
{
|
||||
std::istringstream gcode_str(gcode_original);
|
||||
Vec2f extruder_offset = m_extruder_offsets[tcr.initial_tool].cast<float>();
|
||||
|
||||
std::istringstream gcode_str(tcr.gcode);
|
||||
std::string gcode_out;
|
||||
std::string line;
|
||||
Vec2f pos = start_pos;
|
||||
Vec2f transformed_pos;
|
||||
Vec2f pos = tcr.start_pos;
|
||||
Vec2f transformed_pos = pos;
|
||||
Vec2f old_pos(-1000.1f, -1000.1f);
|
||||
|
||||
while (gcode_str) {
|
||||
std::getline(gcode_str, line); // we read the gcode line by line
|
||||
|
||||
// All G1 commands should be translated and rotated
|
||||
if (line.find("G1 ") == 0) {
|
||||
std::ostringstream line_out;
|
||||
std::istringstream line_str(line);
|
||||
|
@ -317,17 +327,34 @@ std::string WipeTowerIntegration::rotate_wipe_tower_moves(const std::string& gco
|
|||
|
||||
if (transformed_pos != old_pos) {
|
||||
line = line_out.str();
|
||||
char buf[2048] = "G1";
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3) << "G1 ";
|
||||
if (transformed_pos.x() != old_pos.x())
|
||||
sprintf(buf + strlen(buf), " X%.3f", transformed_pos.x());
|
||||
oss << " X" << transformed_pos.x() - extruder_offset.x();
|
||||
if (transformed_pos.y() != old_pos.y())
|
||||
sprintf(buf + strlen(buf), " Y%.3f", transformed_pos.y());
|
||||
oss << " Y" << transformed_pos.y() - extruder_offset.y();
|
||||
|
||||
line.replace(line.find("G1 "), 3, buf);
|
||||
line.replace(line.find("G1 "), 3, oss.str());
|
||||
old_pos = transformed_pos;
|
||||
}
|
||||
}
|
||||
|
||||
gcode_out += line + "\n";
|
||||
|
||||
// If this was a toolchange command, we should change current extruder offset
|
||||
if (line == "[toolchange_gcode]") {
|
||||
extruder_offset = m_extruder_offsets[tcr.new_tool].cast<float>();
|
||||
|
||||
// If the extruder offset changed, add an extra move so everything is continuous
|
||||
if (extruder_offset != m_extruder_offsets[tcr.initial_tool].cast<float>()) {
|
||||
std::ostringstream oss;
|
||||
oss << std::fixed << std::setprecision(3)
|
||||
<< "G1 X" << transformed_pos.x() - extruder_offset.x()
|
||||
<< " Y" << transformed_pos.y() - extruder_offset.y()
|
||||
<< "\n";
|
||||
gcode_out += oss.str();
|
||||
}
|
||||
}
|
||||
}
|
||||
return gcode_out;
|
||||
}
|
||||
|
@ -2784,7 +2811,17 @@ std::string GCode::set_extruder(unsigned int extruder_id, double print_z)
|
|||
// if we are running a single-extruder setup, just set the extruder and return nothing
|
||||
if (!m_writer.multiple_extruders) {
|
||||
m_placeholder_parser.set("current_extruder", extruder_id);
|
||||
return m_writer.toolchange(extruder_id);
|
||||
|
||||
std::string gcode;
|
||||
// Append the filament start G-code.
|
||||
const std::string &start_filament_gcode = m_config.start_filament_gcode.get_at(extruder_id);
|
||||
if (! start_filament_gcode.empty()) {
|
||||
// Process the start_filament_gcode for the filament.
|
||||
gcode += this->placeholder_parser_process("start_filament_gcode", start_filament_gcode, extruder_id);
|
||||
check_add_eol(gcode);
|
||||
}
|
||||
gcode += m_writer.toolchange(extruder_id);
|
||||
return gcode;
|
||||
}
|
||||
|
||||
// prepend retraction on the current extruder
|
||||
|
|
|
@ -90,6 +90,7 @@ public:
|
|||
m_right(float(/*print_config.wipe_tower_x.value +*/ print_config.wipe_tower_width.value)),
|
||||
m_wipe_tower_pos(float(print_config.wipe_tower_x.value), float(print_config.wipe_tower_y.value)),
|
||||
m_wipe_tower_rotation(float(print_config.wipe_tower_rotation_angle)),
|
||||
m_extruder_offsets(print_config.extruder_offset.values),
|
||||
m_priming(priming),
|
||||
m_tool_changes(tool_changes),
|
||||
m_final_purge(final_purge),
|
||||
|
@ -107,14 +108,16 @@ private:
|
|||
WipeTowerIntegration& operator=(const WipeTowerIntegration&);
|
||||
std::string append_tcr(GCode &gcodegen, const WipeTower::ToolChangeResult &tcr, int new_extruder_id) const;
|
||||
|
||||
// Postprocesses gcode: rotates and moves all G1 extrusions and returns result
|
||||
std::string rotate_wipe_tower_moves(const std::string& gcode_original, const Vec2f& start_pos, const Vec2f& translation, float angle) const;
|
||||
// Postprocesses gcode: rotates and moves G1 extrusions and returns result
|
||||
std::string post_process_wipe_tower_moves(const WipeTower::ToolChangeResult& tcr, const Vec2f& translation, float angle) const;
|
||||
|
||||
// Left / right edges of the wipe tower, for the planning of wipe moves.
|
||||
const float m_left;
|
||||
const float m_right;
|
||||
const Vec2f m_wipe_tower_pos;
|
||||
const float m_wipe_tower_rotation;
|
||||
const std::vector<Vec2d> m_extruder_offsets;
|
||||
|
||||
// Reference to cached values at the Printer class.
|
||||
const std::vector<WipeTower::ToolChangeResult> &m_priming;
|
||||
const std::vector<std::vector<WipeTower::ToolChangeResult>> &m_tool_changes;
|
||||
|
|
|
@ -553,7 +553,7 @@ std::vector<WipeTower::ToolChangeResult> WipeTower::prime(
|
|||
result.elapsed_time = writer.elapsed_time();
|
||||
result.extrusions = writer.extrusions();
|
||||
result.start_pos = writer.start_pos_rotated();
|
||||
result.end_pos = writer.pos_rotated();
|
||||
result.end_pos = writer.pos();
|
||||
|
||||
results.push_back(std::move(result));
|
||||
|
||||
|
@ -643,7 +643,7 @@ WipeTower::ToolChangeResult WipeTower::tool_change(unsigned int tool, bool last_
|
|||
m_is_first_layer ? m_filpar[tool].first_layer_temperature : m_filpar[tool].temperature);
|
||||
toolchange_Change(writer, tool, m_filpar[tool].material); // Change the tool, set a speed override for soluble and flex materials.
|
||||
toolchange_Load(writer, cleaning_box);
|
||||
writer.travel(writer.x(),writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
||||
writer.travel(writer.x(), writer.y()-m_perimeter_width); // cooling and loading were done a bit down the road
|
||||
toolchange_Wipe(writer, cleaning_box, wipe_volume); // Wipe the newly loaded filament until the end of the assigned wipe area.
|
||||
++ m_num_tool_changes;
|
||||
} else
|
||||
|
|
|
@ -139,13 +139,15 @@ public:
|
|||
|
||||
m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter
|
||||
|
||||
std::stringstream stream{m_semm ? ramming_parameters : std::string()};
|
||||
float speed = 0.f;
|
||||
stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator;
|
||||
m_filpar[idx].ramming_line_width_multiplicator /= 100;
|
||||
m_filpar[idx].ramming_step_multiplicator /= 100;
|
||||
while (stream >> speed)
|
||||
m_filpar[idx].ramming_speed.push_back(speed);
|
||||
if (m_semm) {
|
||||
std::stringstream stream{ramming_parameters};
|
||||
float speed = 0.f;
|
||||
stream >> m_filpar[idx].ramming_line_width_multiplicator >> m_filpar[idx].ramming_step_multiplicator;
|
||||
m_filpar[idx].ramming_line_width_multiplicator /= 100;
|
||||
m_filpar[idx].ramming_step_multiplicator /= 100;
|
||||
while (stream >> speed)
|
||||
m_filpar[idx].ramming_speed.push_back(speed);
|
||||
}
|
||||
|
||||
m_used_filament_length.resize(std::max(m_used_filament_length.size(), idx + 1)); // makes sure that the vector is big enough so we don't have to check later
|
||||
}
|
||||
|
@ -241,8 +243,8 @@ public:
|
|||
int cooling_moves = 0;
|
||||
float cooling_initial_speed = 0.f;
|
||||
float cooling_final_speed = 0.f;
|
||||
float ramming_line_width_multiplicator = 0.f;
|
||||
float ramming_step_multiplicator = 0.f;
|
||||
float ramming_line_width_multiplicator = 1.f;
|
||||
float ramming_step_multiplicator = 1.f;
|
||||
float max_e_speed = std::numeric_limits<float>::max();
|
||||
std::vector<float> ramming_speed;
|
||||
float nozzle_diameter;
|
||||
|
|
|
@ -1918,6 +1918,31 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO
|
|||
return false;
|
||||
}
|
||||
|
||||
extern bool model_has_multi_part_objects(const Model &model)
|
||||
{
|
||||
for (const ModelObject *model_object : model.objects)
|
||||
if (model_object->volumes.size() != 1 || ! model_object->volumes.front()->is_model_part())
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
extern bool model_has_advanced_features(const Model &model)
|
||||
{
|
||||
auto config_is_advanced = [](const DynamicPrintConfig &config) {
|
||||
return ! (config.empty() || (config.size() == 1 && config.cbegin()->first == "extruder"));
|
||||
};
|
||||
for (const ModelObject *model_object : model.objects) {
|
||||
// Is there more than one instance or advanced config data?
|
||||
if (model_object->instances.size() > 1 || config_is_advanced(model_object->config))
|
||||
return true;
|
||||
// Is there any modifier or advanced config data?
|
||||
for (const ModelVolume* model_volume : model_object->volumes)
|
||||
if (! model_volume->is_model_part() || config_is_advanced(model_volume->config))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
|
||||
void check_model_ids_validity(const Model &model)
|
||||
|
|
|
@ -18,6 +18,15 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace cereal {
|
||||
class BinaryInputArchive;
|
||||
class BinaryOutputArchive;
|
||||
template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr);
|
||||
template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr);
|
||||
template <class T> void load_by_value(BinaryInputArchive &ar, T &obj);
|
||||
template <class T> void save_by_value(BinaryOutputArchive &ar, const T &obj);
|
||||
}
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
|
@ -25,6 +34,7 @@ class ModelInstance;
|
|||
class ModelMaterial;
|
||||
class ModelObject;
|
||||
class ModelVolume;
|
||||
class ModelWipeTower;
|
||||
class Print;
|
||||
class SLAPrint;
|
||||
|
||||
|
@ -60,6 +70,21 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
namespace Internal {
|
||||
template<typename T>
|
||||
class StaticSerializationWrapper
|
||||
{
|
||||
public:
|
||||
StaticSerializationWrapper(T &wrap) : wrapped(wrap) {}
|
||||
private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
template<class Archive> void load(Archive &ar) { cereal::load_by_value(ar, wrapped); }
|
||||
template<class Archive> void save(Archive &ar) const { cereal::save_by_value(ar, wrapped); }
|
||||
T& wrapped;
|
||||
};
|
||||
}
|
||||
|
||||
typedef std::string t_model_material_id;
|
||||
typedef std::string t_model_material_attribute;
|
||||
typedef std::map<t_model_material_attribute, std::string> t_model_material_attributes;
|
||||
|
@ -134,7 +159,8 @@ private:
|
|||
ModelMaterial() : ObjectBase(-1), config(-1), m_model(nullptr) { assert(this->id().invalid()); assert(this->config.id().invalid()); }
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid());
|
||||
ar(attributes, config);
|
||||
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
|
||||
ar(attributes, config_wrapper);
|
||||
// assert(this->id().valid()); assert(this->config.id().valid());
|
||||
}
|
||||
|
||||
|
@ -343,7 +369,8 @@ private:
|
|||
}
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(cereal::base_class<ObjectBase>(this));
|
||||
ar(name, input_file, instances, volumes, config, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation,
|
||||
Internal::StaticSerializationWrapper<ModelConfig> config_wrapper(config);
|
||||
ar(name, input_file, instances, volumes, config_wrapper, layer_config_ranges, layer_height_profile, sla_support_points, sla_points_status, origin_translation,
|
||||
m_bounding_box, m_bounding_box_valid, m_raw_bounding_box, m_raw_bounding_box_valid, m_raw_mesh_bounding_box, m_raw_mesh_bounding_box_valid);
|
||||
}
|
||||
};
|
||||
|
@ -527,8 +554,25 @@ private:
|
|||
ModelVolume() : ObjectBase(-1), config(-1), object(nullptr) {
|
||||
assert(this->id().invalid()); assert(this->config.id().invalid());
|
||||
}
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(name, config, m_mesh, m_type, m_material_id, m_convex_hull, m_transformation, m_is_splittable);
|
||||
template<class Archive> void load(Archive &ar) {
|
||||
bool has_convex_hull;
|
||||
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
||||
cereal::load_by_value(ar, config);
|
||||
assert(m_mesh);
|
||||
if (has_convex_hull) {
|
||||
cereal::load_optional(ar, m_convex_hull);
|
||||
if (! m_convex_hull && ! m_mesh->empty())
|
||||
// The convex hull was released from the Undo / Redo stack to conserve memory. Recalculate it.
|
||||
this->calculate_convex_hull();
|
||||
} else
|
||||
m_convex_hull.reset();
|
||||
}
|
||||
template<class Archive> void save(Archive &ar) const {
|
||||
bool has_convex_hull = m_convex_hull.get() != nullptr;
|
||||
ar(name, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
|
||||
cereal::save_by_value(ar, config);
|
||||
if (has_convex_hull)
|
||||
cereal::save_optional(ar, m_convex_hull);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -641,6 +685,35 @@ private:
|
|||
}
|
||||
};
|
||||
|
||||
class ModelWipeTower final : public ObjectBase
|
||||
{
|
||||
public:
|
||||
Vec2d position;
|
||||
double rotation;
|
||||
|
||||
private:
|
||||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
friend class Model;
|
||||
|
||||
// Constructors to be only called by derived classes.
|
||||
// Default constructor to assign a unique ID.
|
||||
explicit ModelWipeTower() {}
|
||||
// Constructor with ignored int parameter to assign an invalid ID, to be replaced
|
||||
// by an existing ID copied from elsewhere.
|
||||
explicit ModelWipeTower(int) : ObjectBase(-1) {}
|
||||
// Copy constructor copies the ID.
|
||||
explicit ModelWipeTower(const ModelWipeTower &cfg) = default;
|
||||
|
||||
// Disabled methods.
|
||||
ModelWipeTower(ModelWipeTower &&rhs) = delete;
|
||||
ModelWipeTower& operator=(const ModelWipeTower &rhs) = delete;
|
||||
ModelWipeTower& operator=(ModelWipeTower &&rhs) = delete;
|
||||
|
||||
// For serialization / deserialization of ModelWipeTower composed into another class into the Undo / Redo stack as a separate object.
|
||||
template<typename Archive> void serialize(Archive &ar) { ar(position, rotation); }
|
||||
};
|
||||
|
||||
// The print bed content.
|
||||
// Description of a triangular model with multiple materials, multiple instances with various affine transformations
|
||||
// and with multiple modifier meshes.
|
||||
|
@ -656,6 +729,8 @@ public:
|
|||
ModelMaterialMap materials;
|
||||
// Objects are owned by a model. Each model may have multiple instances, each instance having its own transformation (shift, scale, rotation).
|
||||
ModelObjectPtrs objects;
|
||||
// Wipe tower object.
|
||||
ModelWipeTower wipe_tower;
|
||||
|
||||
// Default constructor assigns a new ID to the model.
|
||||
Model() { assert(this->id().valid()); }
|
||||
|
@ -733,7 +808,8 @@ private:
|
|||
friend class cereal::access;
|
||||
friend class UndoRedo::StackImpl;
|
||||
template<class Archive> void serialize(Archive &ar) {
|
||||
ar(materials, objects);
|
||||
Internal::StaticSerializationWrapper<ModelWipeTower> wipe_tower_wrapper(wipe_tower);
|
||||
ar(materials, objects, wipe_tower_wrapper);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -752,6 +828,12 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode
|
|||
// than the old ModelObject.
|
||||
extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
|
||||
|
||||
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
|
||||
// Either the model cannot be loaded, or a SLA printer has to be activated.
|
||||
extern bool model_has_multi_part_objects(const Model &model);
|
||||
// If the model has advanced features, then it cannot be processed in simple mode.
|
||||
extern bool model_has_advanced_features(const Model &model);
|
||||
|
||||
#ifndef NDEBUG
|
||||
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
|
||||
void check_model_ids_validity(const Model &model);
|
||||
|
@ -760,4 +842,10 @@ void check_model_ids_equal(const Model &model1, const Model &model2);
|
|||
|
||||
} // namespace Slic3r
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelVolume, cereal::specialization::member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::member_serialize> {};
|
||||
}
|
||||
|
||||
#endif /* slic3r_Model_hpp_ */
|
||||
|
|
|
@ -607,6 +607,40 @@ void TriangleMesh::require_shared_vertices()
|
|||
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::require_shared_vertices - end";
|
||||
}
|
||||
|
||||
size_t TriangleMesh::memsize() const
|
||||
{
|
||||
size_t memsize = 8 + this->stl.memsize() + this->its.memsize();
|
||||
return memsize;
|
||||
}
|
||||
|
||||
// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
|
||||
size_t TriangleMesh::release_optional()
|
||||
{
|
||||
size_t memsize_released = sizeof(stl_neighbors) * this->stl.neighbors_start.size() + this->its.memsize();
|
||||
// The indexed triangle set may be recalculated using the stl_generate_shared_vertices() function.
|
||||
this->its.clear();
|
||||
// The neighbors structure may be recalculated using the stl_check_facets_exact() function.
|
||||
this->stl.neighbors_start.clear();
|
||||
return memsize_released;
|
||||
}
|
||||
|
||||
// Restore optional data possibly released by release_optional().
|
||||
void TriangleMesh::restore_optional()
|
||||
{
|
||||
if (! this->stl.facet_start.empty()) {
|
||||
// Save the old stats before calling stl_check_faces_exact, as it may modify the statistics.
|
||||
stl_stats stats = this->stl.stats;
|
||||
if (this->stl.neighbors_start.empty()) {
|
||||
stl_reallocate(&this->stl);
|
||||
stl_check_facets_exact(&this->stl);
|
||||
}
|
||||
if (this->its.vertices.empty())
|
||||
stl_generate_shared_vertices(&this->stl, this->its);
|
||||
// Restore the old statistics.
|
||||
this->stl.stats = stats;
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleMeshSlicer::init(const TriangleMesh *_mesh, throw_on_cancel_callback_type throw_on_cancel)
|
||||
{
|
||||
mesh = _mesh;
|
||||
|
|
|
@ -67,6 +67,12 @@ public:
|
|||
size_t facets_count() const { return this->stl.stats.number_of_facets; }
|
||||
bool empty() const { return this->facets_count() == 0; }
|
||||
bool is_splittable() const;
|
||||
// Estimate of the memory occupied by this structure, important for keeping an eye on the Undo / Redo stack allocation.
|
||||
size_t memsize() const;
|
||||
// Release optional data from the mesh if the object is on the Undo / Redo stack only. Returns the amount of memory released.
|
||||
size_t release_optional();
|
||||
// Restore optional data possibly released by release_optional().
|
||||
void restore_optional();
|
||||
|
||||
stl_file stl;
|
||||
indexed_triangle_set its;
|
||||
|
|
|
@ -21,6 +21,8 @@ extern std::string format_memsize_MB(size_t n);
|
|||
// The string is non-empty only if the loglevel >= info (3).
|
||||
extern std::string log_memory_info();
|
||||
extern void disable_multi_threading();
|
||||
// Returns the size of physical memory (RAM) in bytes.
|
||||
extern size_t total_physical_memory();
|
||||
|
||||
// Set a path with GUI resource files.
|
||||
void set_var_dir(const std::string &path);
|
||||
|
|
|
@ -7,10 +7,15 @@
|
|||
#include <stdio.h>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#include <windows.h>
|
||||
#include <psapi.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/param.h>
|
||||
#ifdef BSD
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <boost/log/core.hpp>
|
||||
|
@ -467,4 +472,75 @@ std::string log_memory_info()
|
|||
}
|
||||
#endif
|
||||
|
||||
// Returns the size of physical memory (RAM) in bytes.
|
||||
// http://nadeausoftware.com/articles/2012/09/c_c_tip_how_get_physical_memory_size_system
|
||||
size_t total_physical_memory()
|
||||
{
|
||||
#if defined(_WIN32) && (defined(__CYGWIN__) || defined(__CYGWIN32__))
|
||||
// Cygwin under Windows. ------------------------------------
|
||||
// New 64-bit MEMORYSTATUSEX isn't available. Use old 32.bit
|
||||
MEMORYSTATUS status;
|
||||
status.dwLength = sizeof(status);
|
||||
GlobalMemoryStatus( &status );
|
||||
return (size_t)status.dwTotalPhys;
|
||||
#elif defined(_WIN32)
|
||||
// Windows. -------------------------------------------------
|
||||
// Use new 64-bit MEMORYSTATUSEX, not old 32-bit MEMORYSTATUS
|
||||
MEMORYSTATUSEX status;
|
||||
status.dwLength = sizeof(status);
|
||||
GlobalMemoryStatusEx( &status );
|
||||
return (size_t)status.ullTotalPhys;
|
||||
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
||||
// UNIX variants. -------------------------------------------
|
||||
// Prefer sysctl() over sysconf() except sysctl() HW_REALMEM and HW_PHYSMEM
|
||||
|
||||
#if defined(CTL_HW) && (defined(HW_MEMSIZE) || defined(HW_PHYSMEM64))
|
||||
int mib[2];
|
||||
mib[0] = CTL_HW;
|
||||
#if defined(HW_MEMSIZE)
|
||||
mib[1] = HW_MEMSIZE; // OSX. ---------------------
|
||||
#elif defined(HW_PHYSMEM64)
|
||||
mib[1] = HW_PHYSMEM64; // NetBSD, OpenBSD. ---------
|
||||
#endif
|
||||
int64_t size = 0; // 64-bit
|
||||
size_t len = sizeof( size );
|
||||
if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
|
||||
return (size_t)size;
|
||||
return 0L; // Failed?
|
||||
|
||||
#elif defined(_SC_AIX_REALMEM)
|
||||
// AIX. -----------------------------------------------------
|
||||
return (size_t)sysconf( _SC_AIX_REALMEM ) * (size_t)1024L;
|
||||
|
||||
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGESIZE)
|
||||
// FreeBSD, Linux, OpenBSD, and Solaris. --------------------
|
||||
return (size_t)sysconf( _SC_PHYS_PAGES ) *
|
||||
(size_t)sysconf( _SC_PAGESIZE );
|
||||
|
||||
#elif defined(_SC_PHYS_PAGES) && defined(_SC_PAGE_SIZE)
|
||||
// Legacy. --------------------------------------------------
|
||||
return (size_t)sysconf( _SC_PHYS_PAGES ) *
|
||||
(size_t)sysconf( _SC_PAGE_SIZE );
|
||||
|
||||
#elif defined(CTL_HW) && (defined(HW_PHYSMEM) || defined(HW_REALMEM))
|
||||
// DragonFly BSD, FreeBSD, NetBSD, OpenBSD, and OSX. --------
|
||||
int mib[2];
|
||||
mib[0] = CTL_HW;
|
||||
#if defined(HW_REALMEM)
|
||||
mib[1] = HW_REALMEM; // FreeBSD. -----------------
|
||||
#elif defined(HW_PYSMEM)
|
||||
mib[1] = HW_PHYSMEM; // Others. ------------------
|
||||
#endif
|
||||
unsigned int size = 0; // 32-bit
|
||||
size_t len = sizeof( size );
|
||||
if ( sysctl( mib, 2, &size, &len, NULL, 0 ) == 0 )
|
||||
return (size_t)size;
|
||||
return 0L; // Failed?
|
||||
#endif // sysctl and sysconf variants
|
||||
|
||||
#else
|
||||
return 0L; // Unknown OS.
|
||||
#endif
|
||||
}
|
||||
|
||||
}; // namespace Slic3r
|
||||
|
|
|
@ -1202,7 +1202,8 @@ GLCanvas3D::GLCanvas3D(wxGLCanvas* canvas, Bed3D& bed, Camera& camera, GLToolbar
|
|||
, m_bed(bed)
|
||||
, m_camera(camera)
|
||||
, m_view_toolbar(view_toolbar)
|
||||
, m_toolbar(GLToolbar::Normal, "Top")
|
||||
, m_main_toolbar(GLToolbar::Normal, "Top")
|
||||
, m_undoredo_toolbar(GLToolbar::Normal, "Top")
|
||||
, m_gizmos(*this)
|
||||
, m_use_clipping_planes(false)
|
||||
, m_sidebar_field("")
|
||||
|
@ -1309,7 +1310,7 @@ bool GLCanvas3D::init()
|
|||
return false;
|
||||
}
|
||||
|
||||
if (m_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
|
||||
if (m_main_toolbar.is_enabled() && !m_layers_editing.init("variable_layer_height.vs", "variable_layer_height.fs"))
|
||||
{
|
||||
std::cout << "Unable to initialize variable_layer_height shader: please, check that the files variable_layer_height.vs and variable_layer_height.fs are available" << std::endl;
|
||||
return false;
|
||||
|
@ -1323,7 +1324,7 @@ bool GLCanvas3D::init()
|
|||
if (m_gizmos.is_enabled() && !m_gizmos.init())
|
||||
std::cout << "Unable to initialize gizmos: please, check that all the required textures are available" << std::endl;
|
||||
|
||||
if (!_init_toolbar())
|
||||
if (!_init_toolbars())
|
||||
return false;
|
||||
|
||||
if (m_selection.is_enabled() && !m_selection.init())
|
||||
|
@ -1514,9 +1515,14 @@ void GLCanvas3D::enable_selection(bool enable)
|
|||
m_selection.set_enabled(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3D::enable_toolbar(bool enable)
|
||||
void GLCanvas3D::enable_main_toolbar(bool enable)
|
||||
{
|
||||
m_toolbar.set_enabled(enable);
|
||||
m_main_toolbar.set_enabled(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3D::enable_undoredo_toolbar(bool enable)
|
||||
{
|
||||
m_undoredo_toolbar.set_enabled(enable);
|
||||
}
|
||||
|
||||
void GLCanvas3D::enable_dynamic_background(bool enable)
|
||||
|
@ -1852,6 +1858,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||
PrinterTechnology printer_technology = m_process->current_printer_technology();
|
||||
int volume_idx_wipe_tower_old = -1;
|
||||
|
||||
if (printer_technology == ptSLA)
|
||||
// Always do the full refresh in SLA mode to show / hide SLA support structures when an object is moved outside / inside the build volume.
|
||||
m_regenerate_volumes = true;
|
||||
|
||||
if (m_regenerate_volumes)
|
||||
{
|
||||
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
|
||||
|
@ -1884,7 +1894,10 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||
state.step[istep].state = PrintStateBase::INVALID;
|
||||
else
|
||||
for (const ModelInstance *model_instance : print_object->model_object()->instances)
|
||||
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
|
||||
// Only the instances, which are currently printable, will have the SLA support structures kept.
|
||||
// The instances outside the print bed will have the GLVolumes of their support structures released.
|
||||
if (model_instance->is_printable())
|
||||
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
|
||||
}
|
||||
}
|
||||
sla_support_state.emplace_back(state);
|
||||
|
@ -2036,7 +2049,8 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||
|
||||
// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
|
||||
for (GLVolume* volume : m_volumes.volumes)
|
||||
volume->set_sla_shift_z(shift_zs[volume->object_idx()]);
|
||||
if (volume->object_idx() < m_model->objects.size() && m_model->objects[volume->object_idx()]->instances[volume->instance_idx()]->is_printable())
|
||||
volume->set_sla_shift_z(shift_zs[volume->object_idx()]);
|
||||
}
|
||||
|
||||
if (printer_technology == ptFFF && m_config->has("nozzle_diameter"))
|
||||
|
@ -2192,7 +2206,8 @@ void GLCanvas3D::load_sla_preview()
|
|||
if ((m_canvas != nullptr) && (print != nullptr))
|
||||
{
|
||||
_set_current();
|
||||
_load_sla_shells();
|
||||
// Reload the SLA support structures into GLVolumes.
|
||||
this->reload_scene(true, true);
|
||||
_update_sla_shells_outside_state();
|
||||
_show_warning_texture_if_needed(WarningTexture::SlaSupportsOutside);
|
||||
}
|
||||
|
@ -2295,7 +2310,8 @@ void GLCanvas3D::on_idle(wxIdleEvent& evt)
|
|||
if (!m_initialized)
|
||||
return;
|
||||
|
||||
m_dirty |= m_toolbar.update_items_state();
|
||||
m_dirty |= m_main_toolbar.update_items_state();
|
||||
m_dirty |= m_undoredo_toolbar.update_items_state();
|
||||
m_dirty |= m_view_toolbar.update_items_state();
|
||||
|
||||
if (!m_dirty)
|
||||
|
@ -2674,7 +2690,15 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
#endif /* SLIC3R_DEBUG_MOUSE_EVENTS */
|
||||
}
|
||||
|
||||
if (m_toolbar.on_mouse(evt, *this))
|
||||
if (m_main_toolbar.on_mouse(evt, *this))
|
||||
{
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
m_mouse.set_start_position_3D_as_invalid();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_undoredo_toolbar.on_mouse(evt, *this))
|
||||
{
|
||||
if (evt.LeftUp() || evt.MiddleUp() || evt.RightUp())
|
||||
mouse_up_cleanup();
|
||||
|
@ -3015,7 +3039,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
|||
tooltip = m_gizmos.get_tooltip();
|
||||
|
||||
if (tooltip.empty())
|
||||
tooltip = m_toolbar.get_tooltip();
|
||||
tooltip = m_main_toolbar.get_tooltip();
|
||||
|
||||
if (tooltip.empty())
|
||||
tooltip = m_undoredo_toolbar.get_tooltip();
|
||||
|
||||
if (tooltip.empty())
|
||||
tooltip = m_view_toolbar.get_tooltip();
|
||||
|
@ -3468,16 +3495,19 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
|
|||
ImGuiWrapper* imgui = wxGetApp().imgui();
|
||||
|
||||
const float x = pos_x * (float)get_camera().get_zoom() + 0.5f * (float)get_canvas_size().get_width();
|
||||
imgui->set_next_window_pos(x, m_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
|
||||
imgui->set_next_window_pos(x, m_undoredo_toolbar.get_height(), ImGuiCond_Always, 0.5f, 0.0f);
|
||||
imgui->set_next_window_bg_alpha(0.5f);
|
||||
imgui->begin(wxString::Format(_(L("%s Stack")), stack_name),
|
||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse);
|
||||
|
||||
int hovered = m_imgui_undo_redo_hovered_pos;
|
||||
int selected = -1;
|
||||
const float em = static_cast<float>(wxGetApp().em_unit());
|
||||
float em = static_cast<float>(wxGetApp().em_unit());
|
||||
#if ENABLE_RETINA_GL
|
||||
em *= m_retina_helper->get_scale_factor();
|
||||
#endif
|
||||
|
||||
if (imgui->undo_redo_list(ImVec2(12 * em, 20 * em), is_undo, &string_getter, hovered, selected))
|
||||
if (imgui->undo_redo_list(ImVec2(18 * em, 26 * em), is_undo, &string_getter, hovered, selected))
|
||||
m_imgui_undo_redo_hovered_pos = hovered;
|
||||
else
|
||||
m_imgui_undo_redo_hovered_pos = -1;
|
||||
|
@ -3490,9 +3520,20 @@ void GLCanvas3D::_render_undo_redo_stack(const bool is_undo, float pos_x)
|
|||
imgui->end();
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_init_toolbar()
|
||||
bool GLCanvas3D::_init_toolbars()
|
||||
{
|
||||
if (!m_toolbar.is_enabled())
|
||||
if (!_init_main_toolbar())
|
||||
return false;
|
||||
|
||||
if (!_init_undoredo_toolbar())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_init_main_toolbar()
|
||||
{
|
||||
if (!m_main_toolbar.is_enabled())
|
||||
return true;
|
||||
|
||||
BackgroundTexture::Metadata background_data;
|
||||
|
@ -3502,19 +3543,19 @@ bool GLCanvas3D::_init_toolbar()
|
|||
background_data.right = 16;
|
||||
background_data.bottom = 16;
|
||||
|
||||
if (!m_toolbar.init(background_data))
|
||||
if (!m_main_toolbar.init(background_data))
|
||||
{
|
||||
// unable to init the toolbar texture, disable it
|
||||
m_toolbar.set_enabled(false);
|
||||
m_main_toolbar.set_enabled(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// m_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
|
||||
m_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
||||
m_toolbar.set_layout_orientation(GLToolbar::Layout::Top);
|
||||
m_toolbar.set_border(5.0f);
|
||||
m_toolbar.set_separator_size(5);
|
||||
m_toolbar.set_gap_size(2);
|
||||
// m_main_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
|
||||
m_main_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
||||
m_main_toolbar.set_layout_orientation(GLToolbar::Layout::Top);
|
||||
m_main_toolbar.set_border(5.0f);
|
||||
m_main_toolbar.set_separator_size(5);
|
||||
m_main_toolbar.set_gap_size(2);
|
||||
|
||||
GLToolbarItem::Data item;
|
||||
|
||||
|
@ -3523,7 +3564,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.tooltip = _utf8(L("Add...")) + " [" + GUI::shortkey_ctrl_prefix() + "I]";
|
||||
item.sprite_id = 0;
|
||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ADD)); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "delete";
|
||||
|
@ -3532,7 +3573,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.sprite_id = 1;
|
||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE)); };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "deleteall";
|
||||
|
@ -3541,7 +3582,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.sprite_id = 2;
|
||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_DELETE_ALL)); };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_delete_all(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "arrange";
|
||||
|
@ -3550,10 +3591,10 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.sprite_id = 3;
|
||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_ARRANGE)); };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_arrange(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
if (!m_toolbar.add_separator())
|
||||
if (!m_main_toolbar.add_separator())
|
||||
return false;
|
||||
|
||||
item.name = "copy";
|
||||
|
@ -3562,7 +3603,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.sprite_id = 4;
|
||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_COPY)); };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_copy_to_clipboard(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "paste";
|
||||
|
@ -3571,10 +3612,10 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.sprite_id = 5;
|
||||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_PASTE)); };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_paste_from_clipboard(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
if (!m_toolbar.add_separator())
|
||||
if (!m_main_toolbar.add_separator())
|
||||
return false;
|
||||
|
||||
item.name = "more";
|
||||
|
@ -3585,7 +3626,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_increase_instances(); };
|
||||
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "fewer";
|
||||
|
@ -3595,10 +3636,10 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_FEWER)); };
|
||||
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_decrease_instances(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
if (!m_toolbar.add_separator())
|
||||
if (!m_main_toolbar.add_separator())
|
||||
return false;
|
||||
|
||||
item.name = "splitobjects";
|
||||
|
@ -3608,7 +3649,7 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_OBJECTS)); };
|
||||
item.visibility_callback = GLToolbarItem::Default_Visibility_Callback;
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_objects(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "splitvolumes";
|
||||
|
@ -3618,10 +3659,10 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_SPLIT_VOLUMES)); };
|
||||
item.visibility_callback = []()->bool { return wxGetApp().get_mode() != comSimple; };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_split_to_volumes(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
if (!m_toolbar.add_separator())
|
||||
if (!m_main_toolbar.add_separator())
|
||||
return false;
|
||||
|
||||
item.name = "layersediting";
|
||||
|
@ -3632,16 +3673,44 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.left.action_callback = [this]() { if (m_canvas != nullptr) wxPostEvent(m_canvas, SimpleEvent(EVT_GLTOOLBAR_LAYERSEDITING)); };
|
||||
item.visibility_callback = [this]()->bool { return m_process->current_printer_technology() == ptFFF; };
|
||||
item.enabling_callback = []()->bool { return wxGetApp().plater()->can_layers_editing(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_main_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
if (!m_toolbar.add_separator())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GLCanvas3D::_init_undoredo_toolbar()
|
||||
{
|
||||
if (!m_undoredo_toolbar.is_enabled())
|
||||
return true;
|
||||
|
||||
BackgroundTexture::Metadata background_data;
|
||||
background_data.filename = "toolbar_background.png";
|
||||
background_data.left = 16;
|
||||
background_data.top = 16;
|
||||
background_data.right = 16;
|
||||
background_data.bottom = 16;
|
||||
|
||||
if (!m_undoredo_toolbar.init(background_data))
|
||||
{
|
||||
// unable to init the toolbar texture, disable it
|
||||
m_undoredo_toolbar.set_enabled(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
// m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Vertical);
|
||||
m_undoredo_toolbar.set_layout_type(GLToolbar::Layout::Horizontal);
|
||||
m_undoredo_toolbar.set_layout_orientation(GLToolbar::Layout::Top);
|
||||
m_undoredo_toolbar.set_border(5.0f);
|
||||
m_undoredo_toolbar.set_separator_size(5);
|
||||
m_undoredo_toolbar.set_gap_size(2);
|
||||
|
||||
GLToolbarItem::Data item;
|
||||
|
||||
item.name = "undo";
|
||||
item.icon_filename = "undo_toolbar.svg";
|
||||
item.tooltip = _utf8(L("Undo")) + " [" + GUI::shortkey_ctrl_prefix() + "Z]";
|
||||
item.sprite_id = 11;
|
||||
item.sprite_id = 0;
|
||||
item.left.toggable = false;
|
||||
item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_UNDO)); };
|
||||
item.right.toggable = true;
|
||||
|
@ -3649,18 +3718,18 @@ bool GLCanvas3D::_init_toolbar()
|
|||
item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(true, 0.5f * (left + right)); };
|
||||
item.visibility_callback = []()->bool { return true; };
|
||||
item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_undo(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_undoredo_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
item.name = "redo";
|
||||
item.icon_filename = "redo_toolbar.svg";
|
||||
item.tooltip = _utf8(L("Redo")) + " [" + GUI::shortkey_ctrl_prefix() + "Y]";
|
||||
item.sprite_id = 12;
|
||||
item.sprite_id = 1;
|
||||
item.left.action_callback = [this]() { post_event(SimpleEvent(EVT_GLCANVAS_REDO)); };
|
||||
item.right.action_callback = [this]() { m_imgui_undo_redo_hovered_pos = -1; };
|
||||
item.right.render_callback = [this](float left, float right, float, float) { if (m_canvas != nullptr) _render_undo_redo_stack(false, 0.5f * (left + right)); };
|
||||
item.enabling_callback = [this]()->bool { return wxGetApp().plater()->can_redo(); };
|
||||
if (!m_toolbar.add_item(item))
|
||||
if (!m_undoredo_toolbar.add_item(item))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -3991,7 +4060,8 @@ void GLCanvas3D::_render_overlays() const
|
|||
_render_gizmos_overlay();
|
||||
_render_warning_texture();
|
||||
_render_legend_texture();
|
||||
_render_toolbar();
|
||||
_render_main_toolbar();
|
||||
_render_undoredo_toolbar();
|
||||
_render_view_toolbar();
|
||||
|
||||
if ((m_layers_editing.last_object_id >= 0) && (m_layers_editing.object_max_z() > 0.0f))
|
||||
|
@ -4081,64 +4151,57 @@ void GLCanvas3D::_render_gizmos_overlay() const
|
|||
m_gizmos.render_overlay();
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_toolbar() const
|
||||
void GLCanvas3D::_render_main_toolbar() const
|
||||
{
|
||||
if (!m_main_toolbar.is_enabled())
|
||||
return;
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
// m_toolbar.set_scale(m_retina_helper->get_scale_factor());
|
||||
// m_main_toolbar.set_scale(m_retina_helper->get_scale_factor());
|
||||
const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
|
||||
m_toolbar.set_scale(scale); //! #ys_FIXME_experiment
|
||||
m_main_toolbar.set_scale(scale); //! #ys_FIXME_experiment
|
||||
#else
|
||||
// m_toolbar.set_scale(m_canvas->GetContentScaleFactor());
|
||||
// m_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
|
||||
// m_main_toolbar.set_scale(m_canvas->GetContentScaleFactor());
|
||||
// m_main_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
|
||||
const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
|
||||
m_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
|
||||
m_main_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
|
||||
#endif // ENABLE_RETINA_GL
|
||||
|
||||
Size cnv_size = get_canvas_size();
|
||||
float zoom = (float)m_camera.get_zoom();
|
||||
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
|
||||
|
||||
GLToolbar::Layout::EOrientation orientation = m_toolbar.get_layout_orientation();
|
||||
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
||||
float left = -0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width()) * inv_zoom;
|
||||
|
||||
float top = 0.0f;
|
||||
float left = 0.0f;
|
||||
switch (m_toolbar.get_layout_type())
|
||||
{
|
||||
default:
|
||||
case GLToolbar::Layout::Horizontal:
|
||||
{
|
||||
// centers the toolbar on the top edge of the 3d scene
|
||||
if (orientation == GLToolbar::Layout::Top)
|
||||
{
|
||||
top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
||||
left = -0.5f * m_toolbar.get_width() * inv_zoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
top = (-0.5f * (float)cnv_size.get_height() + m_view_toolbar.get_height()) * inv_zoom;
|
||||
left = -0.5f * m_toolbar.get_width() * inv_zoom;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case GLToolbar::Layout::Vertical:
|
||||
{
|
||||
// centers the toolbar on the right edge of the 3d scene
|
||||
if (orientation == GLToolbar::Layout::Left)
|
||||
{
|
||||
top = 0.5f * m_toolbar.get_height() * inv_zoom;
|
||||
left = (-0.5f * (float)cnv_size.get_width()) * inv_zoom;
|
||||
}
|
||||
else
|
||||
{
|
||||
top = 0.5f * m_toolbar.get_height() * inv_zoom;
|
||||
left = (0.5f * (float)cnv_size.get_width() - m_toolbar.get_width()) * inv_zoom;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_toolbar.set_position(top, left);
|
||||
m_main_toolbar.set_position(top, left);
|
||||
m_main_toolbar.render(*this);
|
||||
}
|
||||
|
||||
m_toolbar.render(*this);
|
||||
void GLCanvas3D::_render_undoredo_toolbar() const
|
||||
{
|
||||
if (!m_undoredo_toolbar.is_enabled())
|
||||
return;
|
||||
|
||||
#if ENABLE_RETINA_GL
|
||||
// m_undoredo_toolbar.set_scale(m_retina_helper->get_scale_factor());
|
||||
const float scale = m_retina_helper->get_scale_factor() * wxGetApp().toolbar_icon_scale(true);
|
||||
m_undoredo_toolbar.set_scale(scale); //! #ys_FIXME_experiment
|
||||
#else
|
||||
// m_undoredo_toolbar.set_scale(m_canvas->GetContentScaleFactor());
|
||||
// m_undoredo_toolbar.set_scale(wxGetApp().em_unit()*0.1f);
|
||||
const float size = int(GLToolbar::Default_Icons_Size * wxGetApp().toolbar_icon_scale(true));
|
||||
m_undoredo_toolbar.set_icons_size(size); //! #ys_FIXME_experiment
|
||||
#endif // ENABLE_RETINA_GL
|
||||
|
||||
Size cnv_size = get_canvas_size();
|
||||
float zoom = (float)m_camera.get_zoom();
|
||||
float inv_zoom = (zoom != 0.0f) ? 1.0f / zoom : 0.0f;
|
||||
|
||||
float top = 0.5f * (float)cnv_size.get_height() * inv_zoom;
|
||||
float left = (m_main_toolbar.get_width() - 0.5f * (m_main_toolbar.get_width() + m_undoredo_toolbar.get_width())) * inv_zoom;
|
||||
m_undoredo_toolbar.set_position(top, left);
|
||||
m_undoredo_toolbar.render(*this);
|
||||
}
|
||||
|
||||
void GLCanvas3D::_render_view_toolbar() const
|
||||
|
@ -5395,56 +5458,6 @@ void GLCanvas3D::_load_fff_shells()
|
|||
}
|
||||
}
|
||||
|
||||
void GLCanvas3D::_load_sla_shells()
|
||||
{
|
||||
//FIXME use reload_scene
|
||||
#if 1
|
||||
const SLAPrint* print = this->sla_print();
|
||||
if (print->objects().empty())
|
||||
// nothing to render, return
|
||||
return;
|
||||
|
||||
auto add_volume = [this](const SLAPrintObject &object, int volume_id, const SLAPrintObject::Instance& instance,
|
||||
const TriangleMesh &mesh, const float color[4], bool outside_printer_detection_enabled) {
|
||||
m_volumes.volumes.emplace_back(new GLVolume(color));
|
||||
GLVolume& v = *m_volumes.volumes.back();
|
||||
v.indexed_vertex_array.load_mesh(mesh);
|
||||
v.shader_outside_printer_detection_enabled = outside_printer_detection_enabled;
|
||||
v.composite_id.volume_id = volume_id;
|
||||
v.set_instance_offset(unscale(instance.shift(0), instance.shift(1), 0));
|
||||
v.set_instance_rotation(Vec3d(0.0, 0.0, (double)instance.rotation));
|
||||
v.set_instance_mirror(X, object.is_left_handed() ? -1. : 1.);
|
||||
v.set_convex_hull(mesh.convex_hull_3d());
|
||||
};
|
||||
|
||||
// adds objects' volumes
|
||||
for (const SLAPrintObject* obj : print->objects())
|
||||
if (obj->is_step_done(slaposSliceSupports)) {
|
||||
unsigned int initial_volumes_count = (unsigned int)m_volumes.volumes.size();
|
||||
for (const SLAPrintObject::Instance& instance : obj->instances()) {
|
||||
add_volume(*obj, 0, instance, obj->transformed_mesh(), GLVolume::MODEL_COLOR[0], true);
|
||||
// Set the extruder_id and volume_id to achieve the same color as in the 3D scene when
|
||||
// through the update_volumes_colors_by_extruder() call.
|
||||
m_volumes.volumes.back()->extruder_id = obj->model_object()->volumes.front()->extruder_id();
|
||||
if (obj->is_step_done(slaposSupportTree) && obj->has_mesh(slaposSupportTree))
|
||||
add_volume(*obj, -int(slaposSupportTree), instance, obj->support_mesh(), GLVolume::SLA_SUPPORT_COLOR, true);
|
||||
if (obj->is_step_done(slaposBasePool) && obj->has_mesh(slaposBasePool))
|
||||
add_volume(*obj, -int(slaposBasePool), instance, obj->pad_mesh(), GLVolume::SLA_PAD_COLOR, false);
|
||||
}
|
||||
double shift_z = obj->get_current_elevation();
|
||||
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
|
||||
GLVolume& v = *m_volumes.volumes[i];
|
||||
// apply shift z
|
||||
v.set_sla_shift_z(shift_z);
|
||||
}
|
||||
}
|
||||
|
||||
update_volumes_colors_by_extruder();
|
||||
#else
|
||||
this->reload_scene(true, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GLCanvas3D::_update_gcode_volumes_visibility(const GCodePreviewData& preview_data)
|
||||
{
|
||||
unsigned int size = (unsigned int)m_gcode_preview_volume_index.first_volumes.size();
|
||||
|
@ -5660,14 +5673,14 @@ void GLCanvas3D::_update_selection_from_hover()
|
|||
|
||||
bool GLCanvas3D::_deactivate_undo_redo_toolbar_items()
|
||||
{
|
||||
if (m_toolbar.is_item_pressed("undo"))
|
||||
if (m_undoredo_toolbar.is_item_pressed("undo"))
|
||||
{
|
||||
m_toolbar.force_right_action(m_toolbar.get_item_id("undo"), *this);
|
||||
m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("undo"), *this);
|
||||
return true;
|
||||
}
|
||||
else if (m_toolbar.is_item_pressed("redo"))
|
||||
else if (m_undoredo_toolbar.is_item_pressed("redo"))
|
||||
{
|
||||
m_toolbar.force_right_action(m_toolbar.get_item_id("redo"), *this);
|
||||
m_undoredo_toolbar.force_right_action(m_undoredo_toolbar.get_item_id("redo"), *this);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -437,7 +437,8 @@ private:
|
|||
Shader m_shader;
|
||||
Mouse m_mouse;
|
||||
mutable GLGizmosManager m_gizmos;
|
||||
mutable GLToolbar m_toolbar;
|
||||
mutable GLToolbar m_main_toolbar;
|
||||
mutable GLToolbar m_undoredo_toolbar;
|
||||
ClippingPlane m_clipping_planes[2];
|
||||
mutable ClippingPlane m_camera_clipping_plane;
|
||||
bool m_use_clipping_planes;
|
||||
|
@ -550,7 +551,8 @@ public:
|
|||
void enable_moving(bool enable);
|
||||
void enable_gizmos(bool enable);
|
||||
void enable_selection(bool enable);
|
||||
void enable_toolbar(bool enable);
|
||||
void enable_main_toolbar(bool enable);
|
||||
void enable_undoredo_toolbar(bool enable);
|
||||
void enable_dynamic_background(bool enable);
|
||||
void allow_multisample(bool allow);
|
||||
|
||||
|
@ -662,10 +664,16 @@ public:
|
|||
void start_keeping_dirty() { m_keep_dirty = true; }
|
||||
void stop_keeping_dirty() { m_keep_dirty = false; }
|
||||
|
||||
unsigned int get_main_toolbar_item_id(const std::string& name) const { return m_main_toolbar.get_item_id(name); }
|
||||
void force_main_toolbar_left_action(unsigned int item_id) { m_main_toolbar.force_left_action(item_id, *this); }
|
||||
void force_main_toolbar_right_action(unsigned int item_id) { m_main_toolbar.force_right_action(item_id, *this); }
|
||||
|
||||
private:
|
||||
bool _is_shown_on_screen() const;
|
||||
|
||||
bool _init_toolbar();
|
||||
bool _init_toolbars();
|
||||
bool _init_main_toolbar();
|
||||
bool _init_undoredo_toolbar();
|
||||
|
||||
bool _set_current();
|
||||
void _resize(unsigned int w, unsigned int h);
|
||||
|
@ -692,7 +700,8 @@ private:
|
|||
void _render_volumes_for_picking() const;
|
||||
void _render_current_gizmo() const;
|
||||
void _render_gizmos_overlay() const;
|
||||
void _render_toolbar() const;
|
||||
void _render_main_toolbar() const;
|
||||
void _render_undoredo_toolbar() const;
|
||||
void _render_view_toolbar() const;
|
||||
#if ENABLE_SHOW_CAMERA_TARGET
|
||||
void _render_camera_target() const;
|
||||
|
@ -739,8 +748,6 @@ private:
|
|||
void _load_gcode_unretractions(const GCodePreviewData& preview_data);
|
||||
// generates objects and wipe tower geometry
|
||||
void _load_fff_shells();
|
||||
// generates objects geometry for sla
|
||||
void _load_sla_shells();
|
||||
// sets gcode geometry visibility according to user selection
|
||||
void _update_gcode_volumes_visibility(const GCodePreviewData& preview_data);
|
||||
void _update_toolpath_volumes_outside_state();
|
||||
|
|
|
@ -403,6 +403,9 @@ void GLToolbar::render(const GLCanvas3D& parent) const
|
|||
|
||||
bool GLToolbar::on_mouse(wxMouseEvent& evt, GLCanvas3D& parent)
|
||||
{
|
||||
if (!m_enabled)
|
||||
return false;
|
||||
|
||||
Vec2d mouse_pos((double)evt.GetX(), (double)evt.GetY());
|
||||
bool processed = false;
|
||||
|
||||
|
@ -1009,9 +1012,6 @@ void GLToolbar::render_horizontal(const GLCanvas3D& parent) const
|
|||
float bg_right = right;
|
||||
float bg_top = top;
|
||||
float bg_bottom = bottom;
|
||||
float bg_width = right - left;
|
||||
float bg_height = top - bottom;
|
||||
float bg_min_size = std::min(bg_width, bg_height);
|
||||
|
||||
float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width;
|
||||
float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width;
|
||||
|
@ -1139,9 +1139,6 @@ void GLToolbar::render_vertical(const GLCanvas3D& parent) const
|
|||
float bg_right = right;
|
||||
float bg_top = top;
|
||||
float bg_bottom = bottom;
|
||||
float bg_width = right - left;
|
||||
float bg_height = top - bottom;
|
||||
float bg_min_size = std::min(bg_width, bg_height);
|
||||
|
||||
float bg_uv_i_left = (float)m_background_texture.metadata.left * inv_bg_tex_width;
|
||||
float bg_uv_i_right = 1.0f - (float)m_background_texture.metadata.right * inv_bg_tex_width;
|
||||
|
|
|
@ -135,8 +135,7 @@ void config_wizard(int reason)
|
|||
|
||||
wxGetApp().load_current_presets();
|
||||
|
||||
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA &&
|
||||
wxGetApp().obj_list()->has_multi_part_objects())
|
||||
if (wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology() == ptSLA && model_has_multi_part_objects(wxGetApp().model()))
|
||||
{
|
||||
show_info(nullptr,
|
||||
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
|
||||
|
|
|
@ -534,7 +534,7 @@ void GUI_App::persist_window_geometry(wxTopLevelWindow *window, bool default_max
|
|||
});
|
||||
}
|
||||
|
||||
void GUI_App::load_project(wxWindow *parent, wxString& input_file)
|
||||
void GUI_App::load_project(wxWindow *parent, wxString& input_file) const
|
||||
{
|
||||
input_file.Clear();
|
||||
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
||||
|
@ -546,7 +546,7 @@ void GUI_App::load_project(wxWindow *parent, wxString& input_file)
|
|||
input_file = dialog.GetPath();
|
||||
}
|
||||
|
||||
void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files)
|
||||
void GUI_App::import_model(wxWindow *parent, wxArrayString& input_files) const
|
||||
{
|
||||
input_files.Clear();
|
||||
wxFileDialog dialog(parent ? parent : GetTopWindow(),
|
||||
|
@ -854,7 +854,7 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
|
||||
// This is called when closing the application, when loading a config file or when starting the config wizard
|
||||
// to notify the user whether he is aware that some preset changes will be lost.
|
||||
bool GUI_App::check_unsaved_changes()
|
||||
bool GUI_App::check_unsaved_changes(const wxString &header)
|
||||
{
|
||||
wxString dirty;
|
||||
PrinterTechnology printer_technology = preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
|
@ -868,8 +868,12 @@ bool GUI_App::check_unsaved_changes()
|
|||
// No changes, the application may close or reload presets.
|
||||
return true;
|
||||
// Ask the user.
|
||||
wxString message;
|
||||
if (! header.empty())
|
||||
message = header + "\n\n";
|
||||
message += _(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?"));
|
||||
wxMessageDialog dialog(mainframe,
|
||||
_(L("The presets on the following tabs were modified")) + ": " + dirty + "\n\n" + _(L("Discard changes and continue anyway?")),
|
||||
message,
|
||||
wxString(SLIC3R_APP_NAME) + " - " + _(L("Unsaved Presets")),
|
||||
wxICON_QUESTION | wxYES_NO | wxNO_DEFAULT);
|
||||
return dialog.ShowModal() == wxID_YES;
|
||||
|
@ -944,9 +948,9 @@ Plater* GUI_App::plater()
|
|||
return plater_;
|
||||
}
|
||||
|
||||
ModelObjectPtrs* GUI_App::model_objects()
|
||||
Model& GUI_App::model()
|
||||
{
|
||||
return &plater_->model().objects;
|
||||
return plater_->model();
|
||||
}
|
||||
|
||||
wxNotebook* GUI_App::tab_panel() const
|
||||
|
|
|
@ -121,8 +121,8 @@ public:
|
|||
void recreate_GUI();
|
||||
void system_info();
|
||||
void keyboard_shortcuts();
|
||||
void load_project(wxWindow *parent, wxString& input_file);
|
||||
void import_model(wxWindow *parent, wxArrayString& input_files);
|
||||
void load_project(wxWindow *parent, wxString& input_file) const;
|
||||
void import_model(wxWindow *parent, wxArrayString& input_files) const;
|
||||
static bool catch_error(std::function<void()> cb, const std::string& err);
|
||||
|
||||
void persist_window_geometry(wxTopLevelWindow *window, bool default_maximized = false);
|
||||
|
@ -139,7 +139,7 @@ public:
|
|||
void update_mode();
|
||||
|
||||
void add_config_menu(wxMenuBar *menu);
|
||||
bool check_unsaved_changes();
|
||||
bool check_unsaved_changes(const wxString &header = wxString());
|
||||
bool checked_tab(Tab* tab);
|
||||
void load_current_presets();
|
||||
|
||||
|
@ -158,7 +158,7 @@ public:
|
|||
ObjectList* obj_list();
|
||||
ObjectLayers* obj_layers();
|
||||
Plater* plater();
|
||||
std::vector<ModelObject*> *model_objects();
|
||||
Model& model();
|
||||
|
||||
AppConfig* app_config{ nullptr };
|
||||
PresetBundle* preset_bundle{ nullptr };
|
||||
|
|
|
@ -71,9 +71,6 @@ static void take_snapshot(const wxString& snapshot_name)
|
|||
wxGetApp().plater()->take_snapshot(snapshot_name);
|
||||
}
|
||||
|
||||
static void suppress_snapshots(){ wxGetApp().plater()->suppress_snapshots(); }
|
||||
static void allow_snapshots() { wxGetApp().plater()->allow_snapshots(); }
|
||||
|
||||
ObjectList::ObjectList(wxWindow* parent) :
|
||||
wxDataViewCtrl(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxDV_MULTIPLE),
|
||||
m_parent(parent)
|
||||
|
@ -2406,8 +2403,7 @@ void ObjectList::remove()
|
|||
|
||||
wxDataViewItem parent = wxDataViewItem(0);
|
||||
|
||||
take_snapshot(_(L("Delete Selected")));
|
||||
suppress_snapshots();
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Delete Selected")));
|
||||
|
||||
for (auto& item : sels)
|
||||
{
|
||||
|
@ -2429,8 +2425,6 @@ void ObjectList::remove()
|
|||
|
||||
if (parent)
|
||||
select_item(parent);
|
||||
|
||||
allow_snapshots();
|
||||
}
|
||||
|
||||
void ObjectList::del_layer_range(const t_layer_height_range& range)
|
||||
|
@ -2505,8 +2499,7 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
|
|||
|
||||
t_layer_height_range new_range = { midl_layer, next_range.second };
|
||||
|
||||
take_snapshot(_(L("Add New Layers Range")));
|
||||
suppress_snapshots();
|
||||
Plater::TakeSnapshot snapshot(wxGetApp().plater(), _(L("Add New Layers Range")));
|
||||
|
||||
// create new 2 layers instead of deleted one
|
||||
|
||||
|
@ -2521,7 +2514,6 @@ void ObjectList::add_layer_range_after_current(const t_layer_height_range& curre
|
|||
new_range = { current_range.second, midl_layer };
|
||||
ranges[new_range] = get_default_layer_config(obj_idx);
|
||||
add_layer_item(new_range, layers_item, layer_idx);
|
||||
allow_snapshots();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2611,7 +2603,7 @@ bool ObjectList::edit_layer_range(const t_layer_height_range& range, const t_lay
|
|||
|
||||
void ObjectList::init_objects()
|
||||
{
|
||||
m_objects = wxGetApp().model_objects();
|
||||
m_objects = &wxGetApp().model().objects;
|
||||
}
|
||||
|
||||
bool ObjectList::multiple_selection() const
|
||||
|
@ -3088,19 +3080,6 @@ void ObjectList::last_volume_is_deleted(const int obj_idx)
|
|||
volume->config.set_key_value("extruder", new ConfigOptionInt(0));
|
||||
}
|
||||
|
||||
bool ObjectList::has_multi_part_objects()
|
||||
{
|
||||
if (!m_objects_model->IsEmpty()) {
|
||||
wxDataViewItemArray items;
|
||||
m_objects_model->GetChildren(wxDataViewItem(0), items);
|
||||
|
||||
for (auto& item : items)
|
||||
if (m_objects_model->GetItemByType(item, itVolume))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* #lm_FIXME_delete_after_testing
|
||||
void ObjectList::update_settings_items()
|
||||
{
|
||||
|
@ -3531,7 +3510,7 @@ void ObjectList::update_after_undo_redo()
|
|||
m_prevent_list_events = true;
|
||||
m_prevent_canvas_selection_update = true;
|
||||
|
||||
suppress_snapshots();
|
||||
Plater::SuppressSnapshots suppress(wxGetApp().plater());
|
||||
|
||||
// Unselect all objects before deleting them, so that no change of selection is emitted during deletion.
|
||||
this->UnselectAll();
|
||||
|
@ -3543,8 +3522,6 @@ void ObjectList::update_after_undo_redo()
|
|||
++obj_idx;
|
||||
}
|
||||
|
||||
allow_snapshots();
|
||||
|
||||
#ifndef __WXOSX__
|
||||
selection_changed();
|
||||
#endif /* __WXOSX__ */
|
||||
|
|
|
@ -317,7 +317,6 @@ public:
|
|||
void change_part_type();
|
||||
|
||||
void last_volume_is_deleted(const int obj_idx);
|
||||
bool has_multi_part_objects();
|
||||
void update_settings_items();
|
||||
void update_and_show_object_settings_item();
|
||||
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
|
||||
|
|
|
@ -25,7 +25,7 @@ static double get_volume_min_z(const GLVolume* volume)
|
|||
const Transform3f& world_matrix = volume->world_matrix().cast<float>();
|
||||
|
||||
// need to get the ModelVolume pointer
|
||||
const ModelObject* mo = wxGetApp().model_objects()->at(volume->composite_id.object_id);
|
||||
const ModelObject* mo = wxGetApp().model().objects[volume->composite_id.object_id];
|
||||
const ModelVolume* mv = mo->volumes[volume->composite_id.volume_id];
|
||||
const TriangleMesh& hull = mv->get_convex_hull();
|
||||
|
||||
|
@ -199,18 +199,17 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
m_mirror_bitmap_off = ScalableBitmap(parent, "mirroring_off.png");
|
||||
m_mirror_bitmap_hidden = ScalableBitmap(parent, "mirroring_transparent.png");
|
||||
|
||||
for (const std::string axis : { "x", "y", "z" }) {
|
||||
const std::string label = boost::algorithm::to_upper_copy(axis);
|
||||
def.set_default_value(new ConfigOptionString{ " " + label });
|
||||
Option option = Option(def, axis + "_axis_legend");
|
||||
|
||||
unsigned int axis_idx = (axis[0] - 'x'); // 0, 1 or 2
|
||||
static const char axes[] = { 'X', 'Y', 'Z' };
|
||||
for (size_t axis_idx = 0; axis_idx < sizeof(axes); axis_idx++) {
|
||||
const char label = axes[axis_idx];
|
||||
def.set_default_value(new ConfigOptionString{ std::string(" ") + label });
|
||||
Option option(def, std::string() + label + "_axis_legend");
|
||||
|
||||
// We will add a button to toggle mirroring to each axis:
|
||||
auto mirror_button = [this, mirror_btn_width, axis_idx, &label](wxWindow* parent) {
|
||||
auto mirror_button = [this, mirror_btn_width, axis_idx, label](wxWindow* parent) {
|
||||
wxSize btn_size(em_unit(parent) * mirror_btn_width, em_unit(parent) * mirror_btn_width);
|
||||
auto btn = new ScalableButton(parent, wxID_ANY, "mirroring_off.png", wxEmptyString, btn_size, wxDefaultPosition, wxBU_EXACTFIT | wxNO_BORDER | wxTRANSPARENT_WINDOW);
|
||||
btn->SetToolTip(wxString::Format(_(L("Toggle %s axis mirroring")), label));
|
||||
btn->SetToolTip(wxString::Format(_(L("Toggle %c axis mirroring")), (int)label));
|
||||
|
||||
m_mirror_buttons[axis_idx].first = btn;
|
||||
m_mirror_buttons[axis_idx].second = mbShown;
|
||||
|
@ -245,7 +244,8 @@ ObjectManipulation::ObjectManipulation(wxWindow* parent) :
|
|||
canvas->do_mirror(L("Set Mirror"));
|
||||
UpdateAndShow(true);
|
||||
});
|
||||
return sizer;
|
||||
|
||||
return sizer;
|
||||
};
|
||||
|
||||
option.side_widget = mirror_button;
|
||||
|
@ -466,7 +466,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
|
|||
m_new_scale = m_new_size.cwiseProduct(selection.get_unscaled_instance_bounding_box().size().cwiseInverse()) * 100.;
|
||||
} else {
|
||||
m_new_rotation = volume->get_instance_rotation() * (180. / M_PI);
|
||||
m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct((*wxGetApp().model_objects())[volume->object_idx()]->raw_mesh_bounding_box().size());
|
||||
m_new_size = volume->get_instance_transformation().get_scaling_factor().cwiseProduct(wxGetApp().model().objects[volume->object_idx()]->raw_mesh_bounding_box().size());
|
||||
m_new_scale = volume->get_instance_scaling_factor() * 100.;
|
||||
}
|
||||
|
||||
|
@ -779,7 +779,7 @@ void ObjectManipulation::change_size_value(int axis, double value)
|
|||
else if (selection.is_single_full_instance())
|
||||
ref_size = m_world_coordinates ?
|
||||
selection.get_unscaled_instance_bounding_box().size() :
|
||||
(*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
|
||||
wxGetApp().model().objects[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();
|
||||
|
||||
this->do_scale(axis, 100. * Vec3d(size(0) / ref_size(0), size(1) / ref_size(1), size(2) / ref_size(2)));
|
||||
|
||||
|
@ -902,7 +902,7 @@ void ObjectManipulation::set_uniform_scaling(const bool new_value)
|
|||
return;
|
||||
}
|
||||
// Bake the rotation into the meshes of the object.
|
||||
(*wxGetApp().model_objects())[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
|
||||
wxGetApp().model().objects[volume->composite_id.object_id]->bake_xy_rotation_into_meshes(volume->composite_id.instance_id);
|
||||
// Update the 3D scene, selections etc.
|
||||
wxGetApp().plater()->update();
|
||||
// Recalculate cached values at this panel, refresh the screen.
|
||||
|
|
|
@ -63,7 +63,8 @@ bool View3D::init(wxWindow* parent, Bed3D& bed, Camera& camera, GLToolbar& view_
|
|||
m_canvas->set_config(config);
|
||||
m_canvas->enable_gizmos(true);
|
||||
m_canvas->enable_selection(true);
|
||||
m_canvas->enable_toolbar(true);
|
||||
m_canvas->enable_main_toolbar(true);
|
||||
m_canvas->enable_undoredo_toolbar(true);
|
||||
|
||||
wxBoxSizer* main_sizer = new wxBoxSizer(wxVERTICAL);
|
||||
main_sizer->Add(m_canvas_widget, 1, wxALL | wxEXPAND, 0);
|
||||
|
|
|
@ -29,7 +29,7 @@ void GLGizmoFlatten::on_set_state()
|
|||
// m_model_object pointer can be invalid (for instance because of undo/redo action),
|
||||
// we should recover it from the object id
|
||||
m_model_object = nullptr;
|
||||
for (const auto mo : *wxGetApp().model_objects()) {
|
||||
for (const auto mo : wxGetApp().model().objects) {
|
||||
if (mo->id() == m_model_object_id) {
|
||||
m_model_object = mo;
|
||||
break;
|
||||
|
|
|
@ -1049,7 +1049,7 @@ void GLGizmoSlaSupports::on_set_state()
|
|||
// we should recover it from the object id
|
||||
const ModelObject* old_model_object = m_model_object;
|
||||
m_model_object = nullptr;
|
||||
for (const auto mo : *wxGetApp().model_objects()) {
|
||||
for (const auto mo : wxGetApp().model().objects) {
|
||||
if (mo->id() == m_current_mesh_object_id) {
|
||||
m_model_object = mo;
|
||||
break;
|
||||
|
|
|
@ -1255,13 +1255,9 @@ const std::regex PlaterDropTarget::pattern_drop(".*[.](stl|obj|amf|3mf|prusa)",
|
|||
|
||||
bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &filenames)
|
||||
{
|
||||
plater->take_snapshot(_(L("Load Files")));
|
||||
|
||||
std::vector<fs::path> paths;
|
||||
|
||||
for (const auto &filename : filenames) {
|
||||
fs::path path(into_path(filename));
|
||||
|
||||
if (std::regex_match(path.string(), pattern_drop)) {
|
||||
paths.push_back(std::move(path));
|
||||
} else {
|
||||
|
@ -1269,6 +1265,23 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
|
|||
}
|
||||
}
|
||||
|
||||
wxString snapshot_label;
|
||||
assert(! paths.empty());
|
||||
if (paths.size() == 1) {
|
||||
snapshot_label = _(L("Load File"));
|
||||
snapshot_label += ": ";
|
||||
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
|
||||
} else {
|
||||
snapshot_label = _(L("Load Files"));
|
||||
snapshot_label += ": ";
|
||||
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
|
||||
for (size_t i = 1; i < paths.size(); ++ i) {
|
||||
snapshot_label += ", ";
|
||||
snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
|
||||
}
|
||||
}
|
||||
Plater::TakeSnapshot snapshot(plater, snapshot_label);
|
||||
|
||||
// FIXME: when drag and drop is done on a .3mf or a .amf file we should clear the plater for consistence with the open project command
|
||||
// (the following call to plater->load_files() will load the config data, if present)
|
||||
|
||||
|
@ -1736,7 +1749,7 @@ struct Plater::priv
|
|||
priv(Plater *q, MainFrame *main_frame);
|
||||
~priv();
|
||||
|
||||
void update(bool force_full_scene_refresh = false);
|
||||
void update(bool force_full_scene_refresh = false, bool force_background_processing_update = false);
|
||||
void select_view(const std::string& direction);
|
||||
void select_view_3D(const std::string& name);
|
||||
void select_next_view_3D();
|
||||
|
@ -1772,19 +1785,13 @@ struct Plater::priv
|
|||
void split_volume();
|
||||
void scale_selection_to_fit_print_volume();
|
||||
|
||||
void take_snapshot(const std::string& snapshot_name)
|
||||
{
|
||||
if (this->m_prevent_snapshots > 0)
|
||||
return;
|
||||
assert(this->m_prevent_snapshots >= 0);
|
||||
this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager());
|
||||
}
|
||||
void take_snapshot(const std::string& snapshot_name);
|
||||
void take_snapshot(const wxString& snapshot_name) { this->take_snapshot(std::string(snapshot_name.ToUTF8().data())); }
|
||||
int get_active_snapshot_index();
|
||||
void undo();
|
||||
void redo();
|
||||
void undo_to(size_t time_to_load);
|
||||
void redo_to(size_t time_to_load);
|
||||
void undo_redo_to(size_t time_to_load);
|
||||
|
||||
void suppress_snapshots() { this->m_prevent_snapshots++; }
|
||||
void allow_snapshots() { this->m_prevent_snapshots--; }
|
||||
|
||||
|
@ -1878,10 +1885,13 @@ private:
|
|||
|
||||
void update_fff_scene();
|
||||
void update_sla_scene();
|
||||
void update_after_undo_redo();
|
||||
void undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot);
|
||||
void update_after_undo_redo(bool temp_snapshot_was_taken = false);
|
||||
|
||||
// path to project file stored with no extension
|
||||
wxString m_project_filename;
|
||||
std::string m_last_fff_printer_profile_name;
|
||||
std::string m_last_sla_printer_profile_name;
|
||||
};
|
||||
|
||||
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf|prusa)", std::regex::icase);
|
||||
|
@ -2032,7 +2042,7 @@ Plater::priv::~priv()
|
|||
delete config;
|
||||
}
|
||||
|
||||
void Plater::priv::update(bool force_full_scene_refresh)
|
||||
void Plater::priv::update(bool force_full_scene_refresh, bool force_background_processing_update)
|
||||
{
|
||||
// the following line, when enabled, causes flickering on NVIDIA graphics cards
|
||||
// wxWindowUpdateLocker freeze_guard(q);
|
||||
|
@ -2045,7 +2055,7 @@ void Plater::priv::update(bool force_full_scene_refresh)
|
|||
}
|
||||
|
||||
unsigned int update_status = 0;
|
||||
if (this->printer_technology == ptSLA)
|
||||
if (this->printer_technology == ptSLA || force_background_processing_update)
|
||||
// Update the SLAPrint from the current Model, so that the reload_scene()
|
||||
// pulls the correct data.
|
||||
update_status = this->update_background_process(false);
|
||||
|
@ -2231,66 +2241,22 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
}
|
||||
}
|
||||
}
|
||||
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf))
|
||||
{
|
||||
bool advanced = false;
|
||||
for (const ModelObject* model_object : model.objects)
|
||||
else if ((wxGetApp().get_mode() == comSimple) && (type_3mf || type_any_amf) && model_has_advanced_features(model)) {
|
||||
wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
|
||||
_(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dlg.ShowModal() == wxID_YES)
|
||||
{
|
||||
// is there more than one instance ?
|
||||
if (model_object->instances.size() > 1)
|
||||
{
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// is there any advanced config data ?
|
||||
auto opt_keys = model_object->config.keys();
|
||||
if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
|
||||
{
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// is there any modifier ?
|
||||
for (const ModelVolume* model_volume : model_object->volumes)
|
||||
{
|
||||
if (!model_volume->is_model_part())
|
||||
{
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// is there any advanced config data ?
|
||||
opt_keys = model_volume->config.keys();
|
||||
if (!opt_keys.empty() && !((opt_keys.size() == 1) && (opt_keys[0] == "extruder")))
|
||||
{
|
||||
advanced = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (advanced)
|
||||
break;
|
||||
}
|
||||
|
||||
if (advanced)
|
||||
{
|
||||
wxMessageDialog dlg(q, _(L("This file cannot be loaded in a simple mode. Do you want to switch to an advanced mode?\n")),
|
||||
_(L("Detected advanced data")), wxICON_WARNING | wxYES | wxNO);
|
||||
if (dlg.ShowModal() == wxID_YES)
|
||||
{
|
||||
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
|
||||
view3D->set_as_dirty();
|
||||
}
|
||||
else
|
||||
return obj_idxs;
|
||||
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
|
||||
view3D->set_as_dirty();
|
||||
}
|
||||
else
|
||||
return obj_idxs;
|
||||
}
|
||||
|
||||
for (ModelObject* model_object : model.objects) {
|
||||
model_object->center_around_origin(false);
|
||||
model_object->ensure_on_bed();
|
||||
}
|
||||
for (ModelObject* model_object : model.objects) {
|
||||
model_object->center_around_origin(false);
|
||||
model_object->ensure_on_bed();
|
||||
}
|
||||
|
||||
// check multi-part object adding for the SLA-printing
|
||||
if (printer_technology == ptSLA)
|
||||
|
@ -2603,7 +2569,10 @@ void Plater::priv::remove(size_t obj_idx)
|
|||
|
||||
void Plater::priv::delete_object_from_model(size_t obj_idx)
|
||||
{
|
||||
this->take_snapshot(_(L("Delete Object")));
|
||||
wxString snapshot_label = _(L("Delete Object"));
|
||||
if (! model.objects[obj_idx]->name.empty())
|
||||
snapshot_label += ": " + wxString::FromUTF8(model.objects[obj_idx]->name.c_str());
|
||||
Plater::TakeSnapshot snapshot(q, snapshot_label);
|
||||
model.delete_object(obj_idx);
|
||||
update();
|
||||
object_list_changed();
|
||||
|
@ -2611,7 +2580,7 @@ void Plater::priv::delete_object_from_model(size_t obj_idx)
|
|||
|
||||
void Plater::priv::reset()
|
||||
{
|
||||
this->take_snapshot(_(L("Reset Project")));
|
||||
Plater::TakeSnapshot snapshot(q, _(L("Reset Project")));
|
||||
|
||||
set_project_filename(wxEmptyString);
|
||||
|
||||
|
@ -2804,7 +2773,7 @@ void Plater::priv::split_object()
|
|||
Slic3r::GUI::warning_catcher(q, _(L("The selected object couldn't be split because it contains only one part.")));
|
||||
else
|
||||
{
|
||||
this->take_snapshot(_(L("Split to Objects")));
|
||||
Plater::TakeSnapshot snapshot(q, _(L("Split to Objects")));
|
||||
|
||||
unsigned int counter = 1;
|
||||
for (ModelObject* m : new_objects)
|
||||
|
@ -3044,7 +3013,7 @@ void Plater::priv::update_sla_scene()
|
|||
|
||||
void Plater::priv::reload_from_disk()
|
||||
{
|
||||
this->take_snapshot(_(L("Reload from Disk")));
|
||||
Plater::TakeSnapshot snapshot(q, _(L("Reload from Disk")));
|
||||
|
||||
const auto &selection = get_selection();
|
||||
const auto obj_orig_idx = selection.get_object_idx();
|
||||
|
@ -3080,7 +3049,7 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
|
|||
if (obj_idx < 0)
|
||||
return;
|
||||
|
||||
this->take_snapshot(_(L("Fix Throught NetFabb")));
|
||||
Plater::TakeSnapshot snapshot(q, _(L("Fix Throught NetFabb")));
|
||||
|
||||
fix_model_by_win10_sdk_gui(*model.objects[obj_idx], vol_idx);
|
||||
this->update();
|
||||
|
@ -3744,48 +3713,152 @@ void Plater::priv::show_action_buttons(const bool is_ready_to_slice) const
|
|||
|
||||
int Plater::priv::get_active_snapshot_index()
|
||||
{
|
||||
const size_t& active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
|
||||
const size_t active_snapshot_time = this->undo_redo_stack.active_snapshot_time();
|
||||
const std::vector<UndoRedo::Snapshot>& ss_stack = this->undo_redo_stack.snapshots();
|
||||
const auto it = std::lower_bound(ss_stack.begin(), ss_stack.end(), UndoRedo::Snapshot(active_snapshot_time));
|
||||
return it - ss_stack.begin();
|
||||
}
|
||||
|
||||
void Plater::priv::take_snapshot(const std::string& snapshot_name)
|
||||
{
|
||||
if (this->m_prevent_snapshots > 0)
|
||||
return;
|
||||
assert(this->m_prevent_snapshots >= 0);
|
||||
unsigned int flags = 0;
|
||||
if (this->view3D->is_layers_editing_enabled())
|
||||
flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE;
|
||||
//FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
|
||||
// This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
|
||||
if (this->printer_technology == ptFFF) {
|
||||
const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
|
||||
model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
|
||||
}
|
||||
this->undo_redo_stack.take_snapshot(snapshot_name, model, view3D->get_canvas3d()->get_selection(), view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, flags);
|
||||
this->undo_redo_stack.release_least_recently_used();
|
||||
// Save the last active preset name of a particular printer technology.
|
||||
((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
|
||||
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot taken: " << snapshot_name << ", Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info();
|
||||
}
|
||||
|
||||
void Plater::priv::undo()
|
||||
{
|
||||
if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager()))
|
||||
this->update_after_undo_redo();
|
||||
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots();
|
||||
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time()));
|
||||
if (-- it_current != snapshots.begin())
|
||||
this->undo_redo_to(it_current);
|
||||
}
|
||||
|
||||
void Plater::priv::redo()
|
||||
{
|
||||
if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager()))
|
||||
this->update_after_undo_redo();
|
||||
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots();
|
||||
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(this->undo_redo_stack.active_snapshot_time()));
|
||||
if (++ it_current != snapshots.end())
|
||||
this->undo_redo_to(it_current);
|
||||
}
|
||||
|
||||
void Plater::priv::undo_to(size_t time_to_load)
|
||||
void Plater::priv::undo_redo_to(size_t time_to_load)
|
||||
{
|
||||
if (this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load))
|
||||
this->update_after_undo_redo();
|
||||
const std::vector<UndoRedo::Snapshot> &snapshots = this->undo_redo_stack.snapshots();
|
||||
auto it_current = std::lower_bound(snapshots.begin(), snapshots.end(), UndoRedo::Snapshot(time_to_load));
|
||||
assert(it_current != snapshots.end());
|
||||
this->undo_redo_to(it_current);
|
||||
}
|
||||
|
||||
void Plater::priv::redo_to(size_t time_to_load)
|
||||
{
|
||||
if (this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), time_to_load))
|
||||
this->update_after_undo_redo();
|
||||
}
|
||||
|
||||
void Plater::priv::update_after_undo_redo()
|
||||
void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator it_snapshot)
|
||||
{
|
||||
this->view3D->get_canvas3d()->get_selection().clear();
|
||||
this->update(false); // update volumes from the deserializd model
|
||||
bool temp_snapshot_was_taken = this->undo_redo_stack.temp_snapshot_active();
|
||||
PrinterTechnology new_printer_technology = it_snapshot->printer_technology;
|
||||
bool printer_technology_changed = this->printer_technology != new_printer_technology;
|
||||
if (printer_technology_changed) {
|
||||
// Switching the printer technology when jumping forwards / backwards in time. Switch to the last active printer profile of the other type.
|
||||
std::string s_pt = (it_snapshot->printer_technology == ptFFF) ? "FFF" : "SLA";
|
||||
if (! wxGetApp().check_unsaved_changes(from_u8((boost::format(_utf8(
|
||||
L("%1% printer was active at the time the target Undo / Redo snapshot was taken. Switching to %1% printer requires reloading of %1% presets."))) % s_pt).str())))
|
||||
// Don't switch the profiles.
|
||||
return;
|
||||
}
|
||||
// Save the last active preset name of a particular printer technology.
|
||||
((this->printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name) = wxGetApp().preset_bundle->printers.get_selected_preset_name();
|
||||
//FIXME updating the Wipe tower config values at the ModelWipeTower from the Print config.
|
||||
// This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
|
||||
if (this->printer_technology == ptFFF) {
|
||||
const DynamicPrintConfig &config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
model.wipe_tower.position = Vec2d(config.opt_float("wipe_tower_x"), config.opt_float("wipe_tower_y"));
|
||||
model.wipe_tower.rotation = config.opt_float("wipe_tower_rotation_angle");
|
||||
}
|
||||
// Flags made of Snapshot::Flags enum values.
|
||||
unsigned int new_flags = it_snapshot->flags;
|
||||
unsigned int top_snapshot_flags = 0;
|
||||
if (this->view3D->is_layers_editing_enabled())
|
||||
top_snapshot_flags |= UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE;
|
||||
bool new_variable_layer_editing_active = (new_flags & UndoRedo::Snapshot::VARIABLE_LAYER_EDITING_ACTIVE) != 0;
|
||||
// Disable layer editing before the Undo / Redo jump.
|
||||
if (!new_variable_layer_editing_active && view3D->is_layers_editing_enabled())
|
||||
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
|
||||
// Do the jump in time.
|
||||
if (it_snapshot->timestamp < this->undo_redo_stack.active_snapshot_time() ?
|
||||
this->undo_redo_stack.undo(model, this->view3D->get_canvas3d()->get_selection(), this->view3D->get_canvas3d()->get_gizmos_manager(), this->printer_technology, top_snapshot_flags, it_snapshot->timestamp) :
|
||||
this->undo_redo_stack.redo(model, this->view3D->get_canvas3d()->get_gizmos_manager(), it_snapshot->timestamp)) {
|
||||
if (printer_technology_changed) {
|
||||
// Switch to the other printer technology. Switch to the last printer active for that particular technology.
|
||||
AppConfig *app_config = wxGetApp().app_config;
|
||||
app_config->set("presets", "printer", (new_printer_technology == ptFFF) ? m_last_fff_printer_profile_name : m_last_sla_printer_profile_name);
|
||||
wxGetApp().preset_bundle->load_presets(*app_config);
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
// This also switches the printer technology based on the printer technology of the active printer profile.
|
||||
wxGetApp().load_current_presets();
|
||||
}
|
||||
//FIXME updating the Print config from the Wipe tower config values at the ModelWipeTower.
|
||||
// This is a workaround until we refactor the Wipe Tower position / orientation to live solely inside the Model, not in the Print config.
|
||||
if (this->printer_technology == ptFFF) {
|
||||
const DynamicPrintConfig ¤t_config = wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||
Vec2d current_position(current_config.opt_float("wipe_tower_x"), current_config.opt_float("wipe_tower_y"));
|
||||
double current_rotation = current_config.opt_float("wipe_tower_rotation_angle");
|
||||
if (current_position != model.wipe_tower.position || current_rotation != model.wipe_tower.rotation) {
|
||||
DynamicPrintConfig new_config;
|
||||
new_config.set_key_value("wipe_tower_x", new ConfigOptionFloat(model.wipe_tower.position.x()));
|
||||
new_config.set_key_value("wipe_tower_y", new ConfigOptionFloat(model.wipe_tower.position.y()));
|
||||
new_config.set_key_value("wipe_tower_rotation_angle", new ConfigOptionFloat(model.wipe_tower.rotation));
|
||||
Tab *tab_print = wxGetApp().get_tab(Preset::TYPE_PRINT);
|
||||
tab_print->load_config(new_config);
|
||||
tab_print->update_dirty();
|
||||
}
|
||||
}
|
||||
this->update_after_undo_redo(temp_snapshot_was_taken);
|
||||
// Enable layer editing after the Undo / Redo jump.
|
||||
if (! view3D->is_layers_editing_enabled() && this->layers_height_allowed() && new_variable_layer_editing_active)
|
||||
view3D->get_canvas3d()->force_main_toolbar_left_action(view3D->get_canvas3d()->get_main_toolbar_item_id("layersediting"));
|
||||
}
|
||||
}
|
||||
|
||||
void Plater::priv::update_after_undo_redo(bool /* temp_snapshot_was_taken */)
|
||||
{
|
||||
this->view3D->get_canvas3d()->get_selection().clear();
|
||||
// Update volumes from the deserializd model, always stop / update the background processing (for both the SLA and FFF technologies).
|
||||
this->update(false, true);
|
||||
// Release old snapshots if the memory allocated is excessive. This may remove the top most snapshot if jumping to the very first snapshot.
|
||||
//if (temp_snapshot_was_taken)
|
||||
// Release the old snapshots always, as it may have happened, that some of the triangle meshes got deserialized from the snapshot, while some
|
||||
// triangle meshes may have gotten released from the scene or the background processing, therefore now being calculated into the Undo / Redo stack size.
|
||||
this->undo_redo_stack.release_least_recently_used();
|
||||
//YS_FIXME update obj_list from the deserialized model (maybe store ObjectIDs into the tree?) (no selections at this point of time)
|
||||
this->view3D->get_canvas3d()->get_selection().set_deserialized(GUI::Selection::EMode(this->undo_redo_stack.selection_deserialized().mode), this->undo_redo_stack.selection_deserialized().volumes_and_instances);
|
||||
this->view3D->get_canvas3d()->get_gizmos_manager().update_after_undo_redo();
|
||||
|
||||
wxGetApp().obj_list()->update_after_undo_redo();
|
||||
|
||||
if (wxGetApp().get_mode() == comSimple && model_has_advanced_features(this->model)) {
|
||||
// If the user jumped to a snapshot that require user interface with advanced features, switch to the advanced mode without asking.
|
||||
// There is a little risk of surprising the user, as he already must have had the advanced or expert mode active for such a snapshot to be taken.
|
||||
Slic3r::GUI::wxGetApp().save_mode(comAdvanced);
|
||||
view3D->set_as_dirty();
|
||||
}
|
||||
|
||||
//FIXME what about the state of the manipulators?
|
||||
//FIXME what about the focus? Cursor in the side panel?
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << "Undo / Redo snapshot reloaded. Undo / Redo stack memory: " << Slic3r::format_memsize_MB(this->undo_redo_stack.memsize()) << log_memory_info();
|
||||
}
|
||||
|
||||
void Sidebar::set_btn_label(const ActionButtonType btn_type, const wxString& label) const
|
||||
|
@ -3825,10 +3898,12 @@ void Plater::new_project()
|
|||
|
||||
void Plater::load_project()
|
||||
{
|
||||
this->take_snapshot(_(L("Load Project")));
|
||||
|
||||
// Ask user for a project file name.
|
||||
wxString input_file;
|
||||
wxGetApp().load_project(this, input_file);
|
||||
// Take the Undo / Redo snapshot.
|
||||
Plater::TakeSnapshot snapshot(this, _(L("Load Project")) + ": " + wxString::FromUTF8(into_path(input_file).stem().string().c_str()));
|
||||
// And finally load the new project.
|
||||
load_project(input_file);
|
||||
}
|
||||
|
||||
|
@ -3852,13 +3927,28 @@ void Plater::add_model()
|
|||
if (input_files.empty())
|
||||
return;
|
||||
|
||||
this->take_snapshot(_(L("Add object(s)")));
|
||||
std::vector<fs::path> paths;
|
||||
for (const auto &file : input_files)
|
||||
paths.push_back(into_path(file));
|
||||
|
||||
std::vector<fs::path> input_paths;
|
||||
for (const auto &file : input_files) {
|
||||
input_paths.push_back(into_path(file));
|
||||
}
|
||||
load_files(input_paths, true, false);
|
||||
wxString snapshot_label;
|
||||
assert(! paths.empty());
|
||||
if (paths.size() == 1) {
|
||||
snapshot_label = _(L("Import Object"));
|
||||
snapshot_label += ": ";
|
||||
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
|
||||
} else {
|
||||
snapshot_label = _(L("Import Objects"));
|
||||
snapshot_label += ": ";
|
||||
snapshot_label += wxString::FromUTF8(paths.front().filename().string().c_str());
|
||||
for (size_t i = 1; i < paths.size(); ++ i) {
|
||||
snapshot_label += ", ";
|
||||
snapshot_label += wxString::FromUTF8(paths[i].filename().string().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
Plater::TakeSnapshot snapshot(this, snapshot_label);
|
||||
load_files(paths, true, false);
|
||||
}
|
||||
|
||||
void Plater::extract_config_from_project()
|
||||
|
@ -3911,17 +4001,15 @@ void Plater::delete_object_from_model(size_t obj_idx) { p->delete_object_from_mo
|
|||
|
||||
void Plater::remove_selected()
|
||||
{
|
||||
this->take_snapshot(_(L("Delete Selected Objects")));
|
||||
this->suppress_snapshots();
|
||||
Plater::TakeSnapshot snapshot(this, _(L("Delete Selected Objects")));
|
||||
this->p->view3D->delete_selected();
|
||||
this->allow_snapshots();
|
||||
}
|
||||
|
||||
void Plater::increase_instances(size_t num)
|
||||
{
|
||||
if (! can_increase_instances()) { return; }
|
||||
|
||||
this->take_snapshot(_(L("Increase Instances")));
|
||||
Plater::TakeSnapshot snapshot(this, _(L("Increase Instances")));
|
||||
|
||||
int obj_idx = p->get_selected_object_idx();
|
||||
|
||||
|
@ -3957,7 +4045,7 @@ void Plater::decrease_instances(size_t num)
|
|||
{
|
||||
if (! can_decrease_instances()) { return; }
|
||||
|
||||
this->take_snapshot(_(L("Decrease Instances")));
|
||||
Plater::TakeSnapshot snapshot(this, _(L("Decrease Instances")));
|
||||
|
||||
int obj_idx = p->get_selected_object_idx();
|
||||
|
||||
|
@ -3993,16 +4081,13 @@ void Plater::set_number_of_copies(/*size_t num*/)
|
|||
if (num < 0)
|
||||
return;
|
||||
|
||||
this->take_snapshot(wxString::Format(_(L("Set numbers of copies to %d")), num));
|
||||
this->suppress_snapshots();
|
||||
Plater::TakeSnapshot snapshot(this, wxString::Format(_(L("Set numbers of copies to %d")), num));
|
||||
|
||||
int diff = (int)num - (int)model_object->instances.size();
|
||||
if (diff > 0)
|
||||
increase_instances(diff);
|
||||
else if (diff < 0)
|
||||
decrease_instances(-diff);
|
||||
|
||||
this->allow_snapshots();
|
||||
}
|
||||
|
||||
bool Plater::is_selection_empty() const
|
||||
|
@ -4026,7 +4111,7 @@ void Plater::cut(size_t obj_idx, size_t instance_idx, coordf_t z, bool keep_uppe
|
|||
return;
|
||||
}
|
||||
|
||||
this->take_snapshot(_(L("Gizmo-Cut")));
|
||||
Plater::TakeSnapshot snapshot(this, _(L("Cut by Plane")));
|
||||
|
||||
wxBusyCursor wait;
|
||||
const auto new_objects = object->cut(instance_idx, z, keep_upper, keep_lower, rotate_lower);
|
||||
|
@ -4331,7 +4416,7 @@ void Plater::undo_to(int selection)
|
|||
}
|
||||
|
||||
const int idx = p->get_active_snapshot_index() - selection - 1;
|
||||
p->undo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
|
||||
p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
|
||||
}
|
||||
void Plater::redo_to(int selection)
|
||||
{
|
||||
|
@ -4341,7 +4426,7 @@ void Plater::redo_to(int selection)
|
|||
}
|
||||
|
||||
const int idx = p->get_active_snapshot_index() + selection + 1;
|
||||
p->redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
|
||||
p->undo_redo_to(p->undo_redo_stack.snapshots()[idx].timestamp);
|
||||
}
|
||||
bool Plater::undo_redo_string_getter(const bool is_undo, int idx, const char** out_text)
|
||||
{
|
||||
|
@ -4650,15 +4735,9 @@ bool Plater::can_copy_to_clipboard() const
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Plater::can_undo() const
|
||||
{
|
||||
return p->undo_redo_stack.has_undo_snapshot();
|
||||
}
|
||||
|
||||
bool Plater::can_redo() const
|
||||
{
|
||||
return p->undo_redo_stack.has_redo_snapshot();
|
||||
}
|
||||
bool Plater::can_undo() const { return p->undo_redo_stack.has_undo_snapshot(); }
|
||||
bool Plater::can_redo() const { return p->undo_redo_stack.has_redo_snapshot(); }
|
||||
const UndoRedo::Stack& Plater::undo_redo_stack() const { return p->undo_redo_stack; }
|
||||
|
||||
SuppressBackgroundProcessingUpdate::SuppressBackgroundProcessingUpdate() :
|
||||
m_was_running(wxGetApp().plater()->is_background_process_running())
|
||||
|
|
|
@ -27,6 +27,11 @@ class ModelObject;
|
|||
class Print;
|
||||
class SLAPrint;
|
||||
|
||||
namespace UndoRedo {
|
||||
class Stack;
|
||||
struct Snapshot;
|
||||
};
|
||||
|
||||
namespace GUI {
|
||||
|
||||
class MainFrame;
|
||||
|
@ -186,13 +191,12 @@ public:
|
|||
|
||||
void take_snapshot(const std::string &snapshot_name);
|
||||
void take_snapshot(const wxString &snapshot_name);
|
||||
void suppress_snapshots();
|
||||
void allow_snapshots();
|
||||
void undo();
|
||||
void redo();
|
||||
void undo_to(int selection);
|
||||
void redo_to(int selection);
|
||||
bool undo_redo_string_getter(const bool is_undo, int idx, const char** out_text);
|
||||
const Slic3r::UndoRedo::Stack& undo_redo_stack() const;
|
||||
|
||||
void on_extruders_change(int extruders_count);
|
||||
void on_config_change(const DynamicPrintConfig &config);
|
||||
|
@ -235,10 +239,46 @@ public:
|
|||
|
||||
const Camera& get_camera() const;
|
||||
|
||||
// ROII wrapper for suppressing the Undo / Redo snapshot to be taken.
|
||||
class SuppressSnapshots
|
||||
{
|
||||
public:
|
||||
SuppressSnapshots(Plater *plater) : m_plater(plater)
|
||||
{
|
||||
m_plater->suppress_snapshots();
|
||||
}
|
||||
~SuppressSnapshots()
|
||||
{
|
||||
m_plater->allow_snapshots();
|
||||
}
|
||||
private:
|
||||
Plater *m_plater;
|
||||
};
|
||||
|
||||
// ROII wrapper for taking an Undo / Redo snapshot while disabling the snapshot taking by the methods called from inside this snapshot.
|
||||
class TakeSnapshot
|
||||
{
|
||||
public:
|
||||
TakeSnapshot(Plater *plater, const wxString &snapshot_name) : m_plater(plater)
|
||||
{
|
||||
m_plater->take_snapshot(snapshot_name);
|
||||
m_plater->suppress_snapshots();
|
||||
}
|
||||
~TakeSnapshot()
|
||||
{
|
||||
m_plater->allow_snapshots();
|
||||
}
|
||||
private:
|
||||
Plater *m_plater;
|
||||
};
|
||||
|
||||
private:
|
||||
struct priv;
|
||||
std::unique_ptr<priv> p;
|
||||
|
||||
void suppress_snapshots();
|
||||
void allow_snapshots();
|
||||
|
||||
friend class SuppressBackgroundProcessingUpdate;
|
||||
};
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "I18N.hpp"
|
||||
#include "3DScene.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "../Utils/UndoRedo.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -10,6 +11,14 @@
|
|||
#include "GUI_App.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
// The standard Windows includes.
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#include <psapi.h>
|
||||
#endif /* _WIN32 */
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
|
@ -36,10 +45,37 @@ std::string get_main_info(bool format_as_html)
|
|||
"System Version: "
|
||||
#endif
|
||||
<< b_end << wxPlatformInfo::Get().GetOperatingSystemDescription() << line_end;
|
||||
out << b_start << "Total RAM size [MB]: " << b_end << Slic3r::format_memsize_MB(Slic3r::total_physical_memory());
|
||||
|
||||
return out.str();
|
||||
}
|
||||
|
||||
std::string get_mem_info(bool format_as_html)
|
||||
{
|
||||
std::stringstream out;
|
||||
|
||||
std::string b_start = format_as_html ? "<b>" : "";
|
||||
std::string b_end = format_as_html ? "</b>" : "";
|
||||
std::string line_end = format_as_html ? "<br>" : "\n";
|
||||
|
||||
const Slic3r::UndoRedo::Stack &stack = wxGetApp().plater()->undo_redo_stack();
|
||||
out << b_start << "RAM size reserved for the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.get_memory_limit()) << line_end;
|
||||
out << b_start << "RAM size occupied by the Undo / Redo stack [MB]: " << b_end << Slic3r::format_memsize_MB(stack.memsize()) << line_end << line_end;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, ::GetCurrentProcessId());
|
||||
if (hProcess != nullptr) {
|
||||
PROCESS_MEMORY_COUNTERS_EX pmc;
|
||||
if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc)))
|
||||
out << b_start << "WorkingSet [MB]: " << b_end << format_memsize_MB(pmc.WorkingSetSize) << line_end
|
||||
<< b_start << "PrivateBytes [MB]: " << b_end << format_memsize_MB(pmc.PrivateUsage) << line_end
|
||||
<< b_start << "Pagefile(peak) [MB]: " << b_end << format_memsize_MB(pmc.PagefileUsage) << "(" << format_memsize_MB(pmc.PeakPagefileUsage) << ")" << line_end;
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
#endif
|
||||
return out.str();
|
||||
}
|
||||
|
||||
SysInfoDialog::SysInfoDialog()
|
||||
: DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("System Information")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
|
||||
{
|
||||
|
@ -111,7 +147,7 @@ SysInfoDialog::SysInfoDialog()
|
|||
"</font>"
|
||||
"</body>"
|
||||
"</html>", bgr_clr_str, text_clr_str, text_clr_str,
|
||||
_3DScene::get_gl_info(true, true));
|
||||
get_mem_info(true) + "<br>" + _3DScene::get_gl_info(true, true));
|
||||
m_opengl_info_html->SetPage(text);
|
||||
main_sizer->Add(m_opengl_info_html, 1, wxEXPAND | wxBOTTOM, 15);
|
||||
}
|
||||
|
|
|
@ -2904,7 +2904,7 @@ bool Tab::may_discard_current_dirty_preset(PresetCollection* presets /*= nullptr
|
|||
// Because of we can't to print the multi-part objects with SLA technology.
|
||||
bool Tab::may_switch_to_SLA_preset()
|
||||
{
|
||||
if (wxGetApp().obj_list()->has_multi_part_objects())
|
||||
if (model_has_multi_part_objects(wxGetApp().model()))
|
||||
{
|
||||
show_info( parent(),
|
||||
_(L("It's impossible to print multi-part object(s) with SLA technology.")) + "\n\n" +
|
||||
|
|
|
@ -17,20 +17,29 @@
|
|||
#define CEREAL_FUTURE_EXPERIMENTAL
|
||||
#include <cereal/archives/adapters.hpp>
|
||||
|
||||
#include <libslic3r/ObjectID.hpp>
|
||||
#include <libslic3r/Utils.hpp>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
#ifndef NDEBUG
|
||||
// #define SLIC3R_UNDOREDO_DEBUG
|
||||
#endif /* NDEBUG */
|
||||
#if 0
|
||||
// Stop at a fraction of the normal Undo / Redo stack size.
|
||||
#define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 10000
|
||||
#else
|
||||
#define UNDO_REDO_DEBUG_LOW_MEM_FACTOR 1
|
||||
#endif
|
||||
|
||||
namespace Slic3r {
|
||||
namespace UndoRedo {
|
||||
|
||||
static std::string topmost_snapsnot_name = "@@@ Topmost @@@";
|
||||
static std::string topmost_snapshot_name = "@@@ Topmost @@@";
|
||||
|
||||
bool Snapshot::is_topmost() const
|
||||
{
|
||||
return this->name == topmost_snapsnot_name;
|
||||
return this->name == topmost_snapshot_name;
|
||||
}
|
||||
|
||||
// Time interval, start is closed, end is open.
|
||||
|
@ -51,9 +60,12 @@ public:
|
|||
bool operator<(const Interval &rhs) const { return (m_begin < rhs.m_begin) || (m_begin == rhs.m_begin && m_end < rhs.m_end); }
|
||||
bool operator==(const Interval &rhs) const { return m_begin == rhs.m_begin && m_end == rhs.m_end; }
|
||||
|
||||
void trim_begin(size_t new_begin) { m_begin = std::max(m_begin, new_begin); }
|
||||
void trim_end(size_t new_end) { m_end = std::min(m_end, new_end); }
|
||||
void extend_end(size_t new_end) { assert(new_end >= m_end); m_end = new_end; }
|
||||
|
||||
size_t memsize() const { return sizeof(this); }
|
||||
|
||||
private:
|
||||
size_t m_begin;
|
||||
size_t m_end;
|
||||
|
@ -68,13 +80,27 @@ public:
|
|||
// Is the object captured by this history mutable or immutable?
|
||||
virtual bool is_mutable() const = 0;
|
||||
virtual bool is_immutable() const = 0;
|
||||
// The object is optional, it may be released if the Undo / Redo stack memory grows over the limits.
|
||||
virtual bool is_optional() const { return false; }
|
||||
// If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer.
|
||||
virtual const void* immutable_object_ptr() const { return nullptr; }
|
||||
|
||||
// If the history is empty, the ObjectHistory object could be released.
|
||||
virtual bool empty() = 0;
|
||||
|
||||
// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
|
||||
// Return the amount of memory released.
|
||||
virtual size_t release_before_timestamp(size_t timestamp) = 0;
|
||||
// Release all data after the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
|
||||
virtual void release_after_timestamp(size_t timestamp) = 0;
|
||||
// Return the amount of memory released.
|
||||
virtual size_t release_after_timestamp(size_t timestamp) = 0;
|
||||
// Release all optional data of this history.
|
||||
virtual size_t release_optional() = 0;
|
||||
// Restore optional data possibly released by release_optional.
|
||||
virtual void restore_optional() = 0;
|
||||
|
||||
// Estimated size in memory, to be used to drop least recently used snapshots.
|
||||
virtual size_t memsize() const = 0;
|
||||
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
// Human readable debug information.
|
||||
|
@ -94,21 +120,54 @@ public:
|
|||
// If the history is empty, the ObjectHistory object could be released.
|
||||
bool empty() override { return m_history.empty(); }
|
||||
|
||||
// Release all data after the given timestamp. The shared pointer is NOT released.
|
||||
void release_after_timestamp(size_t timestamp) override {
|
||||
assert(! m_history.empty());
|
||||
assert(this->valid());
|
||||
// it points to an interval which either starts with timestamp, or follows the timestamp.
|
||||
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
|
||||
if (it != m_history.begin()) {
|
||||
auto it_prev = it;
|
||||
-- it_prev;
|
||||
assert(it_prev->begin() < timestamp);
|
||||
// Trim the last interval with timestamp.
|
||||
it_prev->trim_end(timestamp);
|
||||
// Release all data before the given timestamp. For the ImmutableObjectHistory, the shared pointer is NOT released.
|
||||
size_t release_before_timestamp(size_t timestamp) override {
|
||||
size_t mem_released = 0;
|
||||
if (! m_history.empty()) {
|
||||
assert(this->valid());
|
||||
// it points to an interval which either starts with timestamp, or follows the timestamp.
|
||||
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
|
||||
// Find the first iterator with begin() < timestamp.
|
||||
if (it == m_history.end())
|
||||
-- it;
|
||||
while (it != m_history.begin() && it->begin() >= timestamp)
|
||||
-- it;
|
||||
if (it->begin() < timestamp && it->end() > timestamp) {
|
||||
it->trim_begin(timestamp);
|
||||
if (it != m_history.begin())
|
||||
-- it;
|
||||
}
|
||||
if (it->end() <= timestamp) {
|
||||
auto it_end = ++ it;
|
||||
for (it = m_history.begin(); it != it_end; ++ it)
|
||||
mem_released += it->memsize();
|
||||
m_history.erase(m_history.begin(), it_end);
|
||||
}
|
||||
assert(this->valid());
|
||||
}
|
||||
m_history.erase(it, m_history.end());
|
||||
assert(this->valid());
|
||||
return mem_released;
|
||||
}
|
||||
|
||||
// Release all data after the given timestamp. The shared pointer is NOT released.
|
||||
size_t release_after_timestamp(size_t timestamp) override {
|
||||
size_t mem_released = 0;
|
||||
if (! m_history.empty()) {
|
||||
assert(this->valid());
|
||||
// it points to an interval which either starts with timestamp, or follows the timestamp.
|
||||
auto it = std::lower_bound(m_history.begin(), m_history.end(), T(timestamp, timestamp));
|
||||
if (it != m_history.begin()) {
|
||||
auto it_prev = it;
|
||||
-- it_prev;
|
||||
assert(it_prev->begin() < timestamp);
|
||||
// Trim the last interval with timestamp.
|
||||
it_prev->trim_end(timestamp);
|
||||
}
|
||||
for (auto it2 = it; it2 != m_history.end(); ++ it2)
|
||||
mem_released += it2->memsize();
|
||||
m_history.erase(it, m_history.end());
|
||||
assert(this->valid());
|
||||
}
|
||||
return mem_released;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -129,13 +188,27 @@ template<typename T>
|
|||
class ImmutableObjectHistory : public ObjectHistory<Interval>
|
||||
{
|
||||
public:
|
||||
ImmutableObjectHistory(std::shared_ptr<const T> shared_object) : m_shared_object(shared_object) {}
|
||||
ImmutableObjectHistory(std::shared_ptr<const T> shared_object, bool optional) : m_shared_object(shared_object), m_optional(optional) {}
|
||||
~ImmutableObjectHistory() override {}
|
||||
|
||||
bool is_mutable() const override { return false; }
|
||||
bool is_immutable() const override { return true; }
|
||||
bool is_optional() const override { return m_optional; }
|
||||
// If it is an immutable object, return its pointer. There is a map assigning a temporary ObjectID to the immutable object pointer.
|
||||
const void* immutable_object_ptr() const { return (const void*)m_shared_object.get(); }
|
||||
|
||||
// Estimated size in memory, to be used to drop least recently used snapshots.
|
||||
size_t memsize() const override {
|
||||
size_t memsize = sizeof(*this);
|
||||
if (this->is_serialized())
|
||||
memsize += m_serialized.size();
|
||||
else if (m_shared_object.use_count() == 1)
|
||||
// Only count the shared object's memsize into the total Undo / Redo stack memsize if it is referenced from the Undo / Redo stack only.
|
||||
memsize += m_shared_object->memsize();
|
||||
memsize += m_history.size() * sizeof(Interval);
|
||||
return memsize;
|
||||
}
|
||||
|
||||
void save(size_t active_snapshot_time, size_t current_time) {
|
||||
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time ||
|
||||
// The snapshot of an immutable object may have already been taken from another mutable object.
|
||||
|
@ -158,6 +231,37 @@ public:
|
|||
return timestamp >= it->begin() && timestamp < it->end();
|
||||
}
|
||||
|
||||
// Release all optional data of this history.
|
||||
size_t release_optional() override {
|
||||
size_t mem_released = 0;
|
||||
if (m_optional) {
|
||||
bool released = false;
|
||||
if (this->is_serialized()) {
|
||||
mem_released += m_serialized.size();
|
||||
m_serialized.clear();
|
||||
released = true;
|
||||
} else if (m_shared_object.use_count() == 1) {
|
||||
mem_released += m_shared_object->memsize();
|
||||
m_shared_object.reset();
|
||||
released = true;
|
||||
}
|
||||
if (released) {
|
||||
mem_released += m_history.size() * sizeof(Interval);
|
||||
m_history.clear();
|
||||
}
|
||||
} else if (m_shared_object.use_count() == 1) {
|
||||
// The object is in memory, but it is not shared with the scene. Let the object decide whether there is any optional data to release.
|
||||
const_cast<T*>(m_shared_object.get())->release_optional();
|
||||
}
|
||||
return mem_released;
|
||||
}
|
||||
|
||||
// Restore optional data possibly released by this->release_optional().
|
||||
void restore_optional() override {
|
||||
if (m_shared_object.use_count() == 1)
|
||||
const_cast<T*>(m_shared_object.get())->restore_optional();
|
||||
}
|
||||
|
||||
bool is_serialized() const { return m_shared_object.get() == nullptr; }
|
||||
const std::string& serialized_data() const { return m_serialized; }
|
||||
std::shared_ptr<const T>& shared_ptr(StackImpl &stack);
|
||||
|
@ -182,6 +286,8 @@ private:
|
|||
// Either the source object is held by a shared pointer and the m_serialized field is empty,
|
||||
// or the shared pointer is null and the object is being serialized into m_serialized.
|
||||
std::shared_ptr<const T> m_shared_object;
|
||||
// If this object is optional, then it may be deleted from the Undo / Redo stack and recalculated from other data (for example mesh convex hull).
|
||||
bool m_optional;
|
||||
std::string m_serialized;
|
||||
};
|
||||
|
||||
|
@ -228,7 +334,8 @@ public:
|
|||
const Interval& interval() const { return m_interval; }
|
||||
size_t begin() const { return m_interval.begin(); }
|
||||
size_t end() const { return m_interval.end(); }
|
||||
void trim_end(size_t timestamp) { m_interval.trim_end(timestamp); }
|
||||
void trim_begin(size_t timestamp) { m_interval.trim_begin(timestamp); }
|
||||
void trim_end (size_t timestamp) { m_interval.trim_end(timestamp); }
|
||||
void extend_end(size_t timestamp) { m_interval.extend_end(timestamp); }
|
||||
|
||||
bool operator<(const MutableHistoryInterval& rhs) const { return m_interval < rhs.m_interval; }
|
||||
|
@ -238,6 +345,13 @@ public:
|
|||
size_t size() const { return m_data->size; }
|
||||
size_t refcnt() const { return m_data->refcnt; }
|
||||
bool matches(const std::string& data) { return m_data->matches(data); }
|
||||
size_t memsize() const {
|
||||
return m_data->refcnt == 1 ?
|
||||
// Count just the size of the snapshot data.
|
||||
m_data->size :
|
||||
// Count the size of the snapshot data divided by the number of references, rounded up.
|
||||
(m_data->size + m_data->refcnt - 1) / m_data->refcnt;
|
||||
}
|
||||
|
||||
private:
|
||||
MutableHistoryInterval(const MutableHistoryInterval &rhs);
|
||||
|
@ -268,6 +382,15 @@ public:
|
|||
bool is_mutable() const override { return true; }
|
||||
bool is_immutable() const override { return false; }
|
||||
|
||||
// Estimated size in memory, to be used to drop least recently used snapshots.
|
||||
size_t memsize() const override {
|
||||
size_t memsize = sizeof(*this);
|
||||
memsize += m_history.size() * sizeof(MutableHistoryInterval);
|
||||
for (const MutableHistoryInterval &interval : m_history)
|
||||
memsize += interval.memsize();
|
||||
return memsize;
|
||||
}
|
||||
|
||||
void save(size_t active_snapshot_time, size_t current_time, const std::string &data) {
|
||||
assert(m_history.empty() || m_history.back().end() <= active_snapshot_time);
|
||||
if (m_history.empty() || m_history.back().end() < active_snapshot_time) {
|
||||
|
@ -300,6 +423,11 @@ public:
|
|||
return std::string(it->data(), it->data() + it->size());
|
||||
}
|
||||
|
||||
// Currently all mutable snapshots are mandatory.
|
||||
size_t release_optional() override { return 0; }
|
||||
// Currently there is no way to release optional data from the mutable objects.
|
||||
void restore_optional() override {}
|
||||
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
std::string format() override {
|
||||
std::string out = typeid(T).name();
|
||||
|
@ -354,30 +482,43 @@ class StackImpl
|
|||
{
|
||||
public:
|
||||
// Stack needs to be initialized. An empty stack is not valid, there must be a "New Project" status stored at the beginning.
|
||||
StackImpl() : m_active_snapshot_time(0), m_current_time(0) {}
|
||||
// Initially enable Undo / Redo stack to occupy maximum 10% of the total system physical memory.
|
||||
StackImpl() : m_memory_limit(std::min(Slic3r::total_physical_memory() / 10, size_t(1 * 16384 * 65536 / UNDO_REDO_DEBUG_LOW_MEM_FACTOR))), m_active_snapshot_time(0), m_current_time(0) {}
|
||||
|
||||
void set_memory_limit(size_t memsize) { m_memory_limit = memsize; }
|
||||
size_t get_memory_limit() const { return m_memory_limit; }
|
||||
|
||||
size_t memsize() const {
|
||||
size_t memsize = 0;
|
||||
for (const auto &object : m_objects)
|
||||
memsize += object.second->memsize();
|
||||
return memsize;
|
||||
}
|
||||
|
||||
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
|
||||
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos);
|
||||
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags);
|
||||
void load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos);
|
||||
|
||||
bool has_undo_snapshot() const;
|
||||
bool has_redo_snapshot() const;
|
||||
bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time);
|
||||
bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t jump_to_time);
|
||||
bool undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t jump_to_time);
|
||||
bool redo(Slic3r::Model &model, Slic3r::GUI::GLGizmosManager &gizmos, size_t jump_to_time);
|
||||
void release_least_recently_used();
|
||||
|
||||
// Snapshot history (names with timestamps).
|
||||
const std::vector<Snapshot>& snapshots() const { return m_snapshots; }
|
||||
// Timestamp of the active snapshot.
|
||||
size_t active_snapshot_time() const { return m_active_snapshot_time; }
|
||||
bool temp_snapshot_active() const { return m_snapshots.back().timestamp == m_active_snapshot_time && ! m_snapshots.back().is_topmost_captured(); }
|
||||
|
||||
const Selection& selection_deserialized() const { return m_selection; }
|
||||
const Selection& selection_deserialized() const { return m_selection; }
|
||||
|
||||
//protected:
|
||||
template<typename T, typename T_AS> ObjectID save_mutable_object(const T &object);
|
||||
template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object);
|
||||
template<typename T> ObjectID save_mutable_object(const T &object);
|
||||
template<typename T> ObjectID save_immutable_object(std::shared_ptr<const T> &object, bool optional);
|
||||
template<typename T> T* load_mutable_object(const Slic3r::ObjectID id);
|
||||
template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id);
|
||||
template<typename T, typename T_AS> void load_mutable_object(const Slic3r::ObjectID id, T &target);
|
||||
template<typename T> std::shared_ptr<const T> load_immutable_object(const Slic3r::ObjectID id, bool optional);
|
||||
template<typename T> void load_mutable_object(const Slic3r::ObjectID id, T &target);
|
||||
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
std::string format() const {
|
||||
|
@ -394,6 +535,7 @@ public:
|
|||
if (m_active_snapshot_time > m_snapshots.back().timestamp)
|
||||
out += ">>>\n";
|
||||
out += "Current time: " + std::to_string(m_current_time) + "\n";
|
||||
out += "Total memory occupied: " + std::to_string(this->memsize()) + "\n";
|
||||
return out;
|
||||
}
|
||||
void print() const {
|
||||
|
@ -406,9 +548,10 @@ public:
|
|||
#ifndef NDEBUG
|
||||
bool valid() const {
|
||||
assert(! m_snapshots.empty());
|
||||
assert(m_snapshots.back().is_topmost());
|
||||
auto it = std::lower_bound(m_snapshots.begin(), m_snapshots.end(), Snapshot(m_active_snapshot_time));
|
||||
assert(it != m_snapshots.begin() && it != m_snapshots.end() && it->timestamp == m_active_snapshot_time);
|
||||
assert(m_active_snapshot_time < m_snapshots.back().timestamp || m_snapshots.back().is_topmost());
|
||||
assert(m_active_snapshot_time <= m_snapshots.back().timestamp);
|
||||
for (auto it = m_objects.begin(); it != m_objects.end(); ++ it)
|
||||
assert(it->second->valid());
|
||||
return true;
|
||||
|
@ -430,6 +573,9 @@ private:
|
|||
}
|
||||
void collect_garbage();
|
||||
|
||||
// Maximum memory allowed to be occupied by the Undo / Redo stack. If the limit is exceeded,
|
||||
// least recently used snapshots will be released.
|
||||
size_t m_memory_limit;
|
||||
// Each individual object (Model, ModelObject, ModelInstance, ModelVolume, Selection, TriangleMesh)
|
||||
// is stored with its own history, referenced by the ObjectID. Immutable objects do not provide
|
||||
// their own IDs, therefore there are temporary IDs generated for them and stored to m_shared_ptr_to_object_id.
|
||||
|
@ -455,7 +601,6 @@ class ModelObject;
|
|||
class ModelVolume;
|
||||
class ModelInstance;
|
||||
class ModelMaterial;
|
||||
class ModelConfig;
|
||||
class DynamicPrintConfig;
|
||||
class TriangleMesh;
|
||||
|
||||
|
@ -470,14 +615,13 @@ namespace cereal
|
|||
template <class Archive> struct specialize<Archive, Slic3r::ModelVolume*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelInstance*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelMaterial*, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, Slic3r::ModelConfig, cereal::specialization::non_member_load_save> {};
|
||||
template <class Archive> struct specialize<Archive, std::shared_ptr<Slic3r::TriangleMesh>, cereal::specialization::non_member_load_save> {};
|
||||
|
||||
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
|
||||
// store just the ObjectID to this stream.
|
||||
template <class T> void save(BinaryOutputArchive& ar, T* const& ptr)
|
||||
{
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T, T>(*ptr));
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(*ptr));
|
||||
}
|
||||
|
||||
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
|
||||
|
@ -509,26 +653,29 @@ namespace cereal
|
|||
|
||||
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
|
||||
// store just the ObjectID to this stream.
|
||||
void save(BinaryOutputArchive& ar, const Slic3r::ModelConfig &cfg)
|
||||
template<class T> void save_by_value(BinaryOutputArchive& ar, const T &cfg)
|
||||
{
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(cfg));
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_mutable_object<T>(cfg));
|
||||
}
|
||||
|
||||
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
|
||||
// based on the ObjectID loaded from this stream.
|
||||
void load(BinaryInputArchive& ar, Slic3r::ModelConfig &cfg)
|
||||
template<class T> void load_by_value(BinaryInputArchive& ar, T &cfg)
|
||||
{
|
||||
Slic3r::UndoRedo::StackImpl& stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
|
||||
size_t id;
|
||||
ar(id);
|
||||
stack.load_mutable_object<Slic3r::ModelConfig, Slic3r::DynamicPrintConfig>(Slic3r::ObjectID(id), cfg);
|
||||
stack.load_mutable_object<T>(Slic3r::ObjectID(id), cfg);
|
||||
}
|
||||
|
||||
// Store ObjectBase derived class onto the Undo / Redo stack as a separate object,
|
||||
// store just the ObjectID to this stream.
|
||||
template <class T> void save(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr)
|
||||
{
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr)));
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr), false));
|
||||
}
|
||||
template <class T> void save_optional(BinaryOutputArchive &ar, const std::shared_ptr<const T> &ptr)
|
||||
{
|
||||
ar(cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar).save_immutable_object<T>(const_cast<std::shared_ptr<const T>&>(ptr), true));
|
||||
}
|
||||
|
||||
// Load ObjectBase derived class from the Undo / Redo stack as a separate object
|
||||
|
@ -538,7 +685,14 @@ namespace cereal
|
|||
Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
|
||||
size_t id;
|
||||
ar(id);
|
||||
ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id));
|
||||
ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id), false);
|
||||
}
|
||||
template <class T> void load_optional(BinaryInputArchive &ar, std::shared_ptr<const T> &ptr)
|
||||
{
|
||||
Slic3r::UndoRedo::StackImpl &stack = cereal::get_user_data<Slic3r::UndoRedo::StackImpl>(ar);
|
||||
size_t id;
|
||||
ar(id);
|
||||
ptr = stack.load_immutable_object<T>(Slic3r::ObjectID(id), true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -566,7 +720,7 @@ template<typename T> std::shared_ptr<const T>& ImmutableObjectHistory<T>::share
|
|||
return m_shared_object;
|
||||
}
|
||||
|
||||
template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(const T &object)
|
||||
template<typename T> ObjectID StackImpl::save_mutable_object(const T &object)
|
||||
{
|
||||
// First find or allocate a history stack for the ObjectID of this object instance.
|
||||
auto it_object_history = m_objects.find(object.id());
|
||||
|
@ -577,20 +731,22 @@ template<typename T, typename T_AS> ObjectID StackImpl::save_mutable_object(cons
|
|||
std::ostringstream oss;
|
||||
{
|
||||
Slic3r::UndoRedo::OutputArchive archive(*this, oss);
|
||||
archive(static_cast<const T_AS&>(object));
|
||||
archive(object);
|
||||
}
|
||||
object_history->save(m_active_snapshot_time, m_current_time, oss.str());
|
||||
return object.id();
|
||||
}
|
||||
|
||||
template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object)
|
||||
template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<const T> &object, bool optional)
|
||||
{
|
||||
// First allocate a temporary ObjectID for this pointer.
|
||||
ObjectID object_id = this->immutable_object_id(object);
|
||||
// and find or allocate a history stack for the ObjectID associated to this shared_ptr.
|
||||
auto it_object_history = m_objects.find(object_id);
|
||||
if (it_object_history == m_objects.end())
|
||||
it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object)));
|
||||
it_object_history = m_objects.emplace_hint(it_object_history, object_id, std::unique_ptr<ImmutableObjectHistory<T>>(new ImmutableObjectHistory<T>(object, optional)));
|
||||
else
|
||||
assert(it_object_history->second.get()->is_optional() == optional);
|
||||
// Then save the interval.
|
||||
static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get())->save(m_active_snapshot_time, m_current_time);
|
||||
return object_id;
|
||||
|
@ -599,21 +755,24 @@ template<typename T> ObjectID StackImpl::save_immutable_object(std::shared_ptr<c
|
|||
template<typename T> T* StackImpl::load_mutable_object(const Slic3r::ObjectID id)
|
||||
{
|
||||
T *target = new T();
|
||||
this->load_mutable_object<T, T>(id, *target);
|
||||
this->load_mutable_object<T>(id, *target);
|
||||
return target;
|
||||
}
|
||||
|
||||
template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id)
|
||||
template<typename T> std::shared_ptr<const T> StackImpl::load_immutable_object(const Slic3r::ObjectID id, bool optional)
|
||||
{
|
||||
// First find a history stack for the ObjectID of this object instance.
|
||||
auto it_object_history = m_objects.find(id);
|
||||
assert(it_object_history != m_objects.end());
|
||||
assert(optional || it_object_history != m_objects.end());
|
||||
if (it_object_history == m_objects.end())
|
||||
return std::shared_ptr<const T>();
|
||||
auto *object_history = static_cast<ImmutableObjectHistory<T>*>(it_object_history->second.get());
|
||||
assert(object_history->has_snapshot(m_active_snapshot_time));
|
||||
object_history->restore_optional();
|
||||
return object_history->shared_ptr(*this);
|
||||
}
|
||||
|
||||
template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
|
||||
template<typename T> void StackImpl::load_mutable_object(const Slic3r::ObjectID id, T &target)
|
||||
{
|
||||
// First find a history stack for the ObjectID of this object instance.
|
||||
auto it_object_history = m_objects.find(id);
|
||||
|
@ -623,11 +782,11 @@ template<typename T, typename T_AS> void StackImpl::load_mutable_object(const Sl
|
|||
std::istringstream iss(object_history->load(m_active_snapshot_time));
|
||||
Slic3r::UndoRedo::InputArchive archive(*this, iss);
|
||||
target.m_id = id;
|
||||
archive(static_cast<T_AS&>(target));
|
||||
archive(target);
|
||||
}
|
||||
|
||||
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
|
||||
void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos)
|
||||
void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags)
|
||||
{
|
||||
// Release old snapshot data.
|
||||
assert(m_active_snapshot_time <= m_current_time);
|
||||
|
@ -638,20 +797,20 @@ void StackImpl::take_snapshot(const std::string& snapshot_name, const Slic3r::Mo
|
|||
m_snapshots.erase(it, m_snapshots.end());
|
||||
}
|
||||
// Take new snapshots.
|
||||
this->save_mutable_object<Slic3r::Model, Slic3r::Model>(model);
|
||||
this->save_mutable_object<Slic3r::Model>(model);
|
||||
m_selection.volumes_and_instances.clear();
|
||||
m_selection.volumes_and_instances.reserve(selection.get_volume_idxs().size());
|
||||
m_selection.mode = selection.get_mode();
|
||||
for (unsigned int volume_idx : selection.get_volume_idxs())
|
||||
m_selection.volumes_and_instances.emplace_back(selection.get_volume(volume_idx)->geometry_id);
|
||||
this->save_mutable_object<Selection, Selection>(m_selection);
|
||||
this->save_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos);
|
||||
this->save_mutable_object<Selection>(m_selection);
|
||||
this->save_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos);
|
||||
// Save the snapshot info.
|
||||
m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id);
|
||||
m_snapshots.emplace_back(snapshot_name, m_current_time ++, model.id().id, printer_technology, flags);
|
||||
m_active_snapshot_time = m_current_time;
|
||||
// Save snapshot info of the last "current" aka "top most" state, that is only being serialized
|
||||
// if undoing an action. Such a snapshot has an invalid Model ID assigned if it was not taken yet.
|
||||
m_snapshots.emplace_back(topmost_snapsnot_name, m_active_snapshot_time, 0);
|
||||
m_snapshots.emplace_back(topmost_snapshot_name, m_active_snapshot_time, 0, printer_technology, flags);
|
||||
// Release empty objects from the history.
|
||||
this->collect_garbage();
|
||||
assert(this->valid());
|
||||
|
@ -671,12 +830,12 @@ void StackImpl::load_snapshot(size_t timestamp, Slic3r::Model& model, Slic3r::GU
|
|||
m_active_snapshot_time = timestamp;
|
||||
model.clear_objects();
|
||||
model.clear_materials();
|
||||
this->load_mutable_object<Slic3r::Model, Slic3r::Model>(ObjectID(it_snapshot->model_id), model);
|
||||
this->load_mutable_object<Slic3r::Model>(ObjectID(it_snapshot->model_id), model);
|
||||
model.update_links_bottom_up_recursive();
|
||||
m_selection.volumes_and_instances.clear();
|
||||
this->load_mutable_object<Selection, Selection>(m_selection.id(), m_selection);
|
||||
this->load_mutable_object<Selection>(m_selection.id(), m_selection);
|
||||
gizmos.reset_all_states();
|
||||
this->load_mutable_object<Slic3r::GUI::GLGizmosManager, Slic3r::GUI::GLGizmosManager>(gizmos.id(), gizmos);
|
||||
this->load_mutable_object<Slic3r::GUI::GLGizmosManager>(gizmos.id(), gizmos);
|
||||
// Sort the volumes so that we may use binary search.
|
||||
std::sort(m_selection.volumes_and_instances.begin(), m_selection.volumes_and_instances.end());
|
||||
this->m_active_snapshot_time = timestamp;
|
||||
|
@ -697,7 +856,7 @@ bool StackImpl::has_redo_snapshot() const
|
|||
return ++ it != m_snapshots.end();
|
||||
}
|
||||
|
||||
bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load)
|
||||
bool StackImpl::undo(Slic3r::Model &model, const Slic3r::GUI::Selection &selection, Slic3r::GUI::GLGizmosManager &gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load)
|
||||
{
|
||||
assert(this->valid());
|
||||
if (time_to_load == SIZE_MAX) {
|
||||
|
@ -708,9 +867,10 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti
|
|||
}
|
||||
assert(time_to_load < m_active_snapshot_time);
|
||||
assert(std::binary_search(m_snapshots.begin(), m_snapshots.end(), Snapshot(time_to_load)));
|
||||
bool new_snapshot_taken = false;
|
||||
if (m_active_snapshot_time == m_snapshots.back().timestamp && ! m_snapshots.back().is_topmost_captured()) {
|
||||
// The current state is temporary. The current state needs to be captured to be redoable.
|
||||
this->take_snapshot(topmost_snapsnot_name, model, selection, gizmos);
|
||||
this->take_snapshot(topmost_snapshot_name, model, selection, gizmos, printer_technology, flags);
|
||||
// The line above entered another topmost_snapshot_name.
|
||||
assert(m_snapshots.back().is_topmost());
|
||||
assert(! m_snapshots.back().is_topmost_captured());
|
||||
|
@ -720,8 +880,15 @@ bool StackImpl::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selecti
|
|||
//-- m_current_time;
|
||||
assert(m_snapshots.back().is_topmost());
|
||||
assert(m_snapshots.back().is_topmost_captured());
|
||||
new_snapshot_taken = true;
|
||||
}
|
||||
this->load_snapshot(time_to_load, model, gizmos);
|
||||
if (new_snapshot_taken) {
|
||||
// Release old snapshots if the memory allocated due to capturing the top most state is excessive.
|
||||
// Don't release the snapshots here, release them first after the scene and background processing gets updated, as this will release some references
|
||||
// to the shared TriangleMeshes.
|
||||
//this->release_least_recently_used();
|
||||
}
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
std::cout << "After undo" << std::endl;
|
||||
this->print();
|
||||
|
@ -762,18 +929,108 @@ void StackImpl::collect_garbage()
|
|||
}
|
||||
}
|
||||
|
||||
void StackImpl::release_least_recently_used()
|
||||
{
|
||||
assert(this->valid());
|
||||
size_t current_memsize = this->memsize();
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
bool released = false;
|
||||
#endif
|
||||
// First try to release the optional immutable data (for example the convex hulls),
|
||||
// or the shared vertices of triangle meshes.
|
||||
for (auto it = m_objects.begin(); current_memsize > m_memory_limit && it != m_objects.end();) {
|
||||
const void *ptr = it->second->immutable_object_ptr();
|
||||
size_t mem_released = it->second->release_optional();
|
||||
if (it->second->empty()) {
|
||||
if (ptr != nullptr)
|
||||
// Release the immutable object from the ptr to ObjectID map.
|
||||
m_shared_ptr_to_object_id.erase(ptr);
|
||||
mem_released += it->second->memsize();
|
||||
it = m_objects.erase(it);
|
||||
} else
|
||||
++ it;
|
||||
assert(current_memsize >= mem_released);
|
||||
if (current_memsize >= mem_released)
|
||||
current_memsize -= mem_released;
|
||||
else
|
||||
current_memsize = 0;
|
||||
}
|
||||
while (current_memsize > m_memory_limit && m_snapshots.size() >= 3) {
|
||||
// From which side to remove a snapshot?
|
||||
assert(m_snapshots.front().timestamp < m_active_snapshot_time);
|
||||
size_t mem_released = 0;
|
||||
if (m_snapshots[1].timestamp == m_active_snapshot_time) {
|
||||
// Remove the last snapshot.
|
||||
#if 0
|
||||
for (auto it = m_objects.begin(); it != m_objects.end();) {
|
||||
mem_released += it->second->release_after_timestamp(m_snapshots.back().timestamp);
|
||||
if (it->second->empty()) {
|
||||
if (it->second->immutable_object_ptr() != nullptr)
|
||||
// Release the immutable object from the ptr to ObjectID map.
|
||||
m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
|
||||
mem_released += it->second->memsize();
|
||||
it = m_objects.erase(it);
|
||||
} else
|
||||
++ it;
|
||||
}
|
||||
m_snapshots.pop_back();
|
||||
m_snapshots.back().name = topmost_snapshot_name;
|
||||
#else
|
||||
// Rather don't release the last snapshot as it will be very confusing to the user
|
||||
// as of why he cannot jump to the top most state. The Undo / Redo stack maximum size
|
||||
// should be set low enough to accomodate for the top most snapshot.
|
||||
break;
|
||||
#endif
|
||||
} else {
|
||||
// Remove the first snapshot.
|
||||
for (auto it = m_objects.begin(); it != m_objects.end();) {
|
||||
mem_released += it->second->release_before_timestamp(m_snapshots[1].timestamp);
|
||||
if (it->second->empty()) {
|
||||
if (it->second->immutable_object_ptr() != nullptr)
|
||||
// Release the immutable object from the ptr to ObjectID map.
|
||||
m_shared_ptr_to_object_id.erase(it->second->immutable_object_ptr());
|
||||
mem_released += it->second->memsize();
|
||||
it = m_objects.erase(it);
|
||||
} else
|
||||
++ it;
|
||||
}
|
||||
m_snapshots.erase(m_snapshots.begin());
|
||||
}
|
||||
assert(current_memsize >= mem_released);
|
||||
if (current_memsize >= mem_released)
|
||||
current_memsize -= mem_released;
|
||||
else
|
||||
current_memsize = 0;
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
released = true;
|
||||
#endif
|
||||
}
|
||||
assert(this->valid());
|
||||
#ifdef SLIC3R_UNDOREDO_DEBUG
|
||||
std::cout << "After release_least_recently_used" << std::endl;
|
||||
this->print();
|
||||
#endif /* SLIC3R_UNDOREDO_DEBUG */
|
||||
}
|
||||
|
||||
// Wrappers of the private implementation.
|
||||
Stack::Stack() : pimpl(new StackImpl()) {}
|
||||
Stack::~Stack() {}
|
||||
void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos) { pimpl->take_snapshot(snapshot_name, model, selection, gizmos); }
|
||||
void Stack::set_memory_limit(size_t memsize) { pimpl->set_memory_limit(memsize); }
|
||||
size_t Stack::get_memory_limit() const { return pimpl->get_memory_limit(); }
|
||||
size_t Stack::memsize() const { return pimpl->memsize(); }
|
||||
void Stack::release_least_recently_used() { pimpl->release_least_recently_used(); }
|
||||
void Stack::take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags)
|
||||
{ pimpl->take_snapshot(snapshot_name, model, selection, gizmos, printer_technology, flags); }
|
||||
bool Stack::has_undo_snapshot() const { return pimpl->has_undo_snapshot(); }
|
||||
bool Stack::has_redo_snapshot() const { return pimpl->has_redo_snapshot(); }
|
||||
bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->undo(model, selection, gizmos, time_to_load); }
|
||||
bool Stack::undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load)
|
||||
{ return pimpl->undo(model, selection, gizmos, printer_technology, flags, time_to_load); }
|
||||
bool Stack::redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load) { return pimpl->redo(model, gizmos, time_to_load); }
|
||||
const Selection& Stack::selection_deserialized() const { return pimpl->selection_deserialized(); }
|
||||
|
||||
const std::vector<Snapshot>& Stack::snapshots() const { return pimpl->snapshots(); }
|
||||
size_t Stack::active_snapshot_time() const { return pimpl->active_snapshot_time(); }
|
||||
bool Stack::temp_snapshot_active() const { return pimpl->temp_snapshot_active(); }
|
||||
|
||||
} // namespace UndoRedo
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
namespace Slic3r {
|
||||
|
||||
class Model;
|
||||
enum PrinterTechnology : unsigned char;
|
||||
|
||||
namespace GUI {
|
||||
class Selection;
|
||||
|
@ -23,11 +24,20 @@ namespace UndoRedo {
|
|||
struct Snapshot
|
||||
{
|
||||
Snapshot(size_t timestamp) : timestamp(timestamp) {}
|
||||
Snapshot(const std::string &name, size_t timestamp, size_t model_id) : name(name), timestamp(timestamp), model_id(model_id) {}
|
||||
Snapshot(const std::string &name, size_t timestamp, size_t model_id, Slic3r::PrinterTechnology printer_technology, unsigned int flags) :
|
||||
name(name), timestamp(timestamp), model_id(model_id), printer_technology(printer_technology), flags(flags) {}
|
||||
|
||||
// Bitmask of various binary flags to be stored with the snapshot.
|
||||
enum Flags {
|
||||
VARIABLE_LAYER_EDITING_ACTIVE = 1,
|
||||
};
|
||||
|
||||
std::string name;
|
||||
size_t timestamp;
|
||||
size_t model_id;
|
||||
std::string name;
|
||||
size_t timestamp;
|
||||
size_t model_id;
|
||||
PrinterTechnology printer_technology;
|
||||
// Bitmap of Flags (see the Flags enum).
|
||||
unsigned int flags;
|
||||
|
||||
bool operator< (const Snapshot &rhs) const { return this->timestamp < rhs.timestamp; }
|
||||
bool operator==(const Snapshot &rhs) const { return this->timestamp == rhs.timestamp; }
|
||||
|
@ -56,8 +66,18 @@ public:
|
|||
Stack();
|
||||
~Stack();
|
||||
|
||||
// Set maximum memory threshold. If the threshold is exceeded, least recently used snapshots are released.
|
||||
void set_memory_limit(size_t memsize);
|
||||
size_t get_memory_limit() const;
|
||||
|
||||
// Estimate size of the RAM consumed by the Undo / Redo stack.
|
||||
size_t memsize() const;
|
||||
|
||||
// Release least recently used snapshots up to the memory limit set above.
|
||||
void release_least_recently_used();
|
||||
|
||||
// Store the current application state onto the Undo / Redo stack, remove all snapshots after m_active_snapshot_time.
|
||||
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos);
|
||||
void take_snapshot(const std::string& snapshot_name, const Slic3r::Model& model, const Slic3r::GUI::Selection& selection, const Slic3r::GUI::GLGizmosManager& gizmos, Slic3r::PrinterTechnology printer_technology, unsigned int flags);
|
||||
|
||||
// To be queried to enable / disable the Undo / Redo buttons at the UI.
|
||||
bool has_undo_snapshot() const;
|
||||
|
@ -65,7 +85,7 @@ public:
|
|||
|
||||
// Roll back the time. If time_to_load is SIZE_MAX, the previous snapshot is activated.
|
||||
// Undoing an action may need to take a snapshot of the current application state, so that redo to the current state is possible.
|
||||
bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
|
||||
bool undo(Slic3r::Model& model, const Slic3r::GUI::Selection& selection, Slic3r::GUI::GLGizmosManager& gizmos, PrinterTechnology printer_technology, unsigned int flags, size_t time_to_load = SIZE_MAX);
|
||||
|
||||
// Jump forward in time. If time_to_load is SIZE_MAX, the next snapshot is activated.
|
||||
bool redo(Slic3r::Model& model, Slic3r::GUI::GLGizmosManager& gizmos, size_t time_to_load = SIZE_MAX);
|
||||
|
@ -79,6 +99,9 @@ public:
|
|||
// The snapshot time indicates start of an operation, which is finished at the time of the following snapshot, therefore
|
||||
// the active snapshot is the successive snapshot. The same logic applies to the time_to_load parameter of undo() and redo() operations.
|
||||
size_t active_snapshot_time() const;
|
||||
// Temporary snapshot is active if the topmost snapshot is active and it has not been captured yet.
|
||||
// In that case the Undo action will capture the last snapshot.
|
||||
bool temp_snapshot_active() const;
|
||||
|
||||
// After load_snapshot() / undo() / redo() the selection is deserialized into a list of ObjectIDs, which needs to be converted
|
||||
// into the list of GLVolume pointers once the 3D scene is updated.
|
||||
|
|
Loading…
Reference in a new issue