Partial update of 3D scene (GLVolumes derived from Model and from

the SLAPrintObject).
Updated PrintBase to generate a unique timestamp at each step invalidation.
This commit is contained in:
bubnikv 2018-11-16 18:28:50 +01:00
parent a468078df3
commit c9a4c6c73c
15 changed files with 670 additions and 373 deletions

View file

@ -98,12 +98,18 @@ void GLIndexedVertexArray::finalize_geometry(bool use_VBOs)
void GLIndexedVertexArray::release_geometry()
{
if (this->vertices_and_normals_interleaved_VBO_id)
if (this->vertices_and_normals_interleaved_VBO_id) {
glDeleteBuffers(1, &this->vertices_and_normals_interleaved_VBO_id);
if (this->triangle_indices_VBO_id)
this->vertices_and_normals_interleaved_VBO_id = 0;
}
if (this->triangle_indices_VBO_id) {
glDeleteBuffers(1, &this->triangle_indices_VBO_id);
if (this->quad_indices_VBO_id)
this->triangle_indices_VBO_id = 0;
}
if (this->quad_indices_VBO_id) {
glDeleteBuffers(1, &this->quad_indices_VBO_id);
this->quad_indices_VBO_id = 0;
}
this->clear();
this->shrink_to_fit();
}
@ -210,9 +216,8 @@ GLVolume::GLVolume(float r, float g, float b, float a)
#endif // ENABLE_MODELVOLUME_TRANSFORM
, m_transformed_convex_hull_bounding_box_dirty(true)
, m_convex_hull(nullptr)
, object_id(-1)
, volume_id(-1)
, instance_id(-1)
// geometry_id == 0 -> invalid
, geometry_id(std::pair<size_t, size_t>(0, 0))
, extruder_id(0)
, selected(false)
, disabled(false)
@ -705,6 +710,25 @@ std::vector<int> GLVolumeCollection::load_object(
const std::vector<int> &instance_idxs,
const std::string &color_by,
bool use_VBOs)
{
// Object will share a single common layer height texture between all printable volumes.
std::shared_ptr<LayersTexture> layer_height_texture = std::make_shared<LayersTexture>();
std::vector<int> volumes_idx;
for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx)
for (int instance_idx : instance_idxs)
volumes_idx.emplace_back(this->GLVolumeCollection::load_object_volume(model_object, layer_height_texture, obj_idx, volume_idx, instance_idx, color_by, use_VBOs));
return volumes_idx;
}
int GLVolumeCollection::load_object_volume(
const ModelObject *model_object,
// Layer height texture is shared between all printable volumes of a single ModelObject.
std::shared_ptr<LayersTexture> &layer_height_texture,
int obj_idx,
int volume_idx,
int instance_idx,
const std::string &color_by,
bool use_VBOs)
{
static float colors[4][4] = {
{ 1.0f, 1.0f, 0.0f, 1.f },
@ -713,132 +737,112 @@ std::vector<int> GLVolumeCollection::load_object(
{ 0.5f, 0.5f, 1.0f, 1.f }
};
// Object will have a single common layer height texture for all volumes.
std::shared_ptr<LayersTexture> layer_height_texture = std::make_shared<LayersTexture>();
const ModelVolume *model_volume = model_object->volumes[volume_idx];
std::vector<int> volumes_idx;
for (int volume_idx = 0; volume_idx < int(model_object->volumes.size()); ++ volume_idx) {
const ModelVolume *model_volume = model_object->volumes[volume_idx];
int extruder_id = -1;
if (model_volume->is_model_part())
{
extruder_id = model_volume->config.has("extruder") ? model_volume->config.option("extruder")->getInt() : 0;
if (extruder_id == 0)
extruder_id = model_object->config.has("extruder") ? model_object->config.option("extruder")->getInt() : 0;
}
for (int instance_idx : instance_idxs) {
const ModelInstance *instance = model_object->instances[instance_idx];
#if ENABLE_MODELVOLUME_TRANSFORM
const TriangleMesh& mesh = model_volume->mesh;
#else
TriangleMesh mesh = model_volume->mesh;
#endif // ENABLE_MODELVOLUME_TRANSFORM
volumes_idx.push_back(int(this->volumes.size()));
float color[4];
memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
if (model_volume->is_support_blocker()) {
color[0] = 1.0f;
color[1] = 0.2f;
color[2] = 0.2f;
} else if (model_volume->is_support_enforcer()) {
color[0] = 0.2f;
color[1] = 0.2f;
color[2] = 1.0f;
}
color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
v.indexed_vertex_array.load_mesh_full_shading(mesh);
else
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(use_VBOs);
v.object_id = obj_idx;
v.volume_id = volume_idx;
v.instance_id = instance_idx;
if (model_volume->is_model_part())
{
v.set_convex_hull(model_volume->get_convex_hull());
v.layer_height_texture = layer_height_texture;
if (extruder_id != -1)
v.extruder_id = extruder_id;
}
v.is_modifier = ! model_volume->is_model_part();
v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
#if ENABLE_MODELVOLUME_TRANSFORM
v.set_instance_transformation(instance->get_transformation());
v.set_volume_transformation(model_volume->get_transformation());
#else
v.set_offset(instance->get_offset());
v.set_rotation(instance->get_rotation());
v.set_scaling_factor(instance->get_scaling_factor());
v.set_mirror(instance->get_mirror());
#endif // ENABLE_MODELVOLUME_TRANSFORM
}
int extruder_id = -1;
if (model_volume->is_model_part())
{
const ConfigOption *opt = model_volume->config.option("extruder");
if (opt == nullptr)
opt = model_object->config.option("extruder");
extruder_id = (opt == nullptr) ? 0 : opt->getInt();
}
return volumes_idx;
const ModelInstance *instance = model_object->instances[instance_idx];
#if ENABLE_MODELVOLUME_TRANSFORM
const TriangleMesh& mesh = model_volume->mesh;
#else
TriangleMesh mesh = model_volume->mesh;
#endif // ENABLE_MODELVOLUME_TRANSFORM
float color[4];
memcpy(color, colors[((color_by == "volume") ? volume_idx : obj_idx) % 4], sizeof(float) * 3);
if (model_volume->is_support_blocker()) {
color[0] = 1.0f;
color[1] = 0.2f;
color[2] = 0.2f;
} else if (model_volume->is_support_enforcer()) {
color[0] = 0.2f;
color[1] = 0.2f;
color[2] = 1.0f;
}
color[3] = model_volume->is_model_part() ? 1.f : 0.5f;
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
v.indexed_vertex_array.load_mesh_full_shading(mesh);
else
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(use_VBOs);
v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
if (model_volume->is_model_part())
{
v.set_convex_hull(model_volume->get_convex_hull());
if (extruder_id != -1)
v.extruder_id = extruder_id;
v.layer_height_texture = layer_height_texture;
}
v.is_modifier = ! model_volume->is_model_part();
v.shader_outside_printer_detection_enabled = model_volume->is_model_part();
#if ENABLE_MODELVOLUME_TRANSFORM
v.set_instance_transformation(instance->get_transformation());
v.set_volume_transformation(model_volume->get_transformation());
#else
v.set_offset(instance->get_offset());
v.set_rotation(instance->get_rotation());
v.set_scaling_factor(instance->get_scaling_factor());
v.set_mirror(instance->get_mirror());
#endif // ENABLE_MODELVOLUME_TRANSFORM
return int(this->volumes.size() - 1);
}
// Load SLA auxiliary GLVolumes (for support trees or pad).
std::vector<int> GLVolumeCollection::load_object_auxiliary(
const ModelObject *model_object,
const SLAPrintObject *print_object,
int obj_idx,
SLAPrintObjectStep milestone,
bool use_VBOs)
// This function produces volumes for multiple instances in a single shot,
// as some object specific mesh conversions may be expensive.
void GLVolumeCollection::load_object_auxiliary(
const SLAPrintObject *print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>> &instances,
SLAPrintObjectStep milestone,
bool use_VBOs)
{
std::vector<int> volumes_idx;
// Find the SLAPrintObject's instance to it.
if (print_object->is_step_done(milestone)) {
// Get the support mesh.
TriangleMesh mesh;
switch (milestone) {
case slaposSupportTree: mesh = print_object->support_mesh(); break;
case slaposBasePool: mesh = print_object->pad_mesh(); break;
default:
assert(false);
}
// Convex hull is required for out of print bed detection.
TriangleMesh convex_hull = mesh.convex_hull_3d();
const std::vector<SLAPrintObject::Instance> &instances = print_object->instances();
std::map<ModelID, int> map_instances;
for (int i = 0; i < (int)model_object->instances.size(); ++ i)
map_instances[model_object->instances[i]->id()] = i;
for (const SLAPrintObject::Instance &instance : instances) {
auto model_instance_it = map_instances.find(instance.instance_id);
assert(model_instance_it != map_instances.end());
const int instance_idx = model_instance_it->second;
const ModelInstance *model_instance = model_object->instances[instance_idx];
volumes_idx.push_back(int(this->volumes.size()));
float color[4] { 0.f, 0.f, 1.f, 1.f };
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
v.indexed_vertex_array.load_mesh_full_shading(mesh);
else
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(use_VBOs);
v.object_id = obj_idx;
v.volume_id = -1; // SLA supports
v.instance_id = instance_idx;
v.set_convex_hull(convex_hull);
v.is_modifier = false;
v.shader_outside_printer_detection_enabled = true;
v.set_instance_transformation(model_instance->get_transformation());
// Leave the volume transformation at identity.
// v.set_volume_transformation(model_volume->get_transformation());
}
assert(print_object->is_step_done(milestone));
// Get the support mesh.
TriangleMesh mesh;
switch (milestone) {
case slaposSupportTree: mesh = print_object->support_mesh(); break;
case slaposBasePool: mesh = print_object->pad_mesh(); break;
default:
assert(false);
}
// Convex hull is required for out of print bed detection.
TriangleMesh convex_hull = mesh.convex_hull_3d();
for (const std::pair<size_t, size_t> &instance_idx : instances) {
const ModelInstance &model_instance = *print_object->model_object()->instances[instance_idx.first];
const SLAPrintObject::Instance &print_instance = print_object->instances()[instance_idx.second];
float color[4] { 0.f, 0.f, 1.f, 1.f };
this->volumes.emplace_back(new GLVolume(color));
GLVolume &v = *this->volumes.back();
if (use_VBOs)
v.indexed_vertex_array.load_mesh_full_shading(mesh);
else
v.indexed_vertex_array.load_mesh_flat_shading(mesh);
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(use_VBOs);
v.composite_id = GLVolume::CompositeID(obj_idx, -1, (int)instance_idx.first);
v.set_convex_hull(convex_hull);
v.is_modifier = false;
v.shader_outside_printer_detection_enabled = true;
//FIXME adjust with print_instance?
v.set_instance_transformation(model_instance.get_transformation());
// Leave the volume transformation at identity.
// v.set_volume_transformation(model_volume->get_transformation());
}
return volumes_idx;
}
int GLVolumeCollection::load_wipe_tower_preview(
@ -911,9 +915,7 @@ int GLVolumeCollection::load_wipe_tower_preview(
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(use_VBOs);
v.object_id = obj_idx;
v.volume_id = 0;
v.instance_id = 0;
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
v.is_wipe_tower = true;
v.shader_outside_printer_detection_enabled = ! size_unknown;
return int(this->volumes.size() - 1);

View file

@ -293,15 +293,25 @@ public:
float color[4];
// Color used to render this volume.
float render_color[4];
// Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
int object_id;
// Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
// If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
// and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
// Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
int volume_id;
// Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
int instance_id;
struct CompositeID {
CompositeID(int object_id, int volume_id, int instance_id) : object_id(object_id), volume_id(volume_id), instance_id(instance_id) {}
CompositeID() : object_id(-1), volume_id(-1), instance_id(-1) {}
// Object ID, which is equal to the index of the respective ModelObject in Model.objects array.
int object_id;
// Volume ID, which is equal to the index of the respective ModelVolume in ModelObject.volumes array.
// If negative, it is an index of a geometry produced by the PrintObject for the respective ModelObject,
// and which has no associated ModelVolume in ModelObject.volumes. For example, SLA supports.
// Volume with a negative volume_id cannot be picked independently, it will pick the associated instance.
int volume_id;
// Instance ID, which is equal to the index of the respective ModelInstance in ModelObject.instances array.
int instance_id;
};
CompositeID composite_id;
// Fingerprint of the source geometry. For ModelVolumes, it is the ModelVolume::ID and ModelInstanceID,
// for generated volumes it is the timestamp generated by PrintState::invalidate() or PrintState::set_done(),
// and the associated ModelInstanceID.
// Valid geometry_id should always be positive.
std::pair<size_t, size_t> geometry_id;
// An ID containing the extruder ID (used to select color).
int extruder_id;
// Is this object selected?
@ -414,9 +424,9 @@ public:
void set_convex_hull(const TriangleMesh& convex_hull);
int object_idx() const { return this->object_id; }
int volume_idx() const { return this->volume_id; }
int instance_idx() const { return this->instance_id; }
int object_idx() const { return this->composite_id.object_id; }
int volume_idx() const { return this->composite_id.volume_id; }
int instance_idx() const { return this->composite_id.instance_id; }
#if ENABLE_MODELVOLUME_TRANSFORM
Transform3d world_matrix() const { return m_instance_transformation.get_matrix() * m_volume_transformation.get_matrix(); }
@ -499,14 +509,24 @@ public:
const std::string &color_by,
bool use_VBOs);
// Load SLA auxiliary GLVolumes (for support trees or pad).
std::vector<int> load_object_auxiliary(
int load_object_volume(
const ModelObject *model_object,
const SLAPrintObject *print_object,
std::shared_ptr<LayersTexture> &layer_height_texture,
int obj_idx,
SLAPrintObjectStep milestone,
int volume_idx,
int instance_idx,
const std::string &color_by,
bool use_VBOs);
// Load SLA auxiliary GLVolumes (for support trees or pad).
void GLVolumeCollection::load_object_auxiliary(
const SLAPrintObject *print_object,
int obj_idx,
// pairs of <instance_idx, print_instance_idx>
const std::vector<std::pair<size_t, size_t>> &instances,
SLAPrintObjectStep milestone,
bool use_VBOs);
int load_wipe_tower_preview(
int obj_idx, float pos_x, float pos_y, float width, float depth, float height, float rotation_angle, bool use_VBOs, bool size_unknown, float brim_width);

View file

@ -60,24 +60,21 @@ void BackgroundSlicingProcess::process_fff()
{
assert(m_print == m_fff_print);
m_print->process();
if (! m_print->canceled()) {
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id));
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
if (! m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) {
this->set_step_started(bspsGCodeFinalize);
if (! m_export_path.empty()) {
//FIXME localize the messages
if (copy_file(m_temp_output_path, m_export_path) != 0)
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(m_export_path, m_fff_print->config());
m_print->set_status(100, "G-code file exported to " + m_export_path);
} else {
m_print->set_status(100, "Slicing complete");
}
this->set_step_done(bspsGCodeFinalize);
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_sliced_id));
m_fff_print->export_gcode(m_temp_output_path, m_gcode_preview_data);
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
//FIXME localize the messages
if (copy_file(m_temp_output_path, m_export_path) != 0)
throw std::runtime_error("Copying of the temporary G-code to the output G-code failed");
m_print->set_status(95, "Running post-processing scripts");
run_post_process_scripts(m_export_path, m_fff_print->config());
m_print->set_status(100, "G-code file exported to " + m_export_path);
} else {
m_print->set_status(100, "Slicing complete");
}
}
this->set_step_done(bspsGCodeFinalize);
}
}
// Pseudo type for specializing LayerWriter trait class
@ -120,11 +117,11 @@ public:
}
};
void BackgroundSlicingProcess::process_sla() {
void BackgroundSlicingProcess::process_sla()
{
assert(m_print == m_sla_print);
m_print->process();
if(!m_print->canceled() && ! this->is_step_done(bspsGCodeFinalize)) {
this->set_step_started(bspsGCodeFinalize);
if (this->set_step_started(bspsGCodeFinalize)) {
if (! m_export_path.empty()) {
m_sla_print->export_raster<SLAZipFmt>(m_export_path);
m_print->set_status(100, "Zip file exported to " + m_export_path);
@ -246,6 +243,7 @@ bool BackgroundSlicingProcess::start()
bool BackgroundSlicingProcess::stop()
{
// m_print->state_mutex() shall NOT be held. Unfortunately there is no interface to test for it.
std::unique_lock<std::mutex> lck(m_mutex);
if (m_state == STATE_INITIAL) {
// this->m_export_path.clear();
@ -282,12 +280,23 @@ bool BackgroundSlicingProcess::reset()
// This function shall not trigger any UI update through the wxWidgets event.
void BackgroundSlicingProcess::stop_internal()
{
// m_print->state_mutex() shall be held. Unfortunately there is no interface to test for it.
if (m_state == STATE_IDLE)
// The worker thread is waiting on m_mutex/m_condition for wake up. The following lock of the mutex would block.
return;
std::unique_lock<std::mutex> lck(m_mutex);
assert(m_state == STATE_STARTED || m_state == STATE_RUNNING || m_state == STATE_FINISHED || m_state == STATE_CANCELED);
if (m_state == STATE_STARTED || m_state == STATE_RUNNING) {
// At this point of time the worker thread may be blocking on m_print->state_mutex().
// Set the print state to canceled before unlocking the state_mutex(), so when the worker thread wakes up,
// it throws the CanceledException().
m_print->cancel_internal();
// Allow the worker thread to wake up if blocking on a milestone.
m_print->state_mutex().unlock();
// Wait until the background processing stops by being canceled.
m_condition.wait(lck, [this](){ return m_state == STATE_CANCELED; });
// Lock it back to be in a consistent state.
m_print->state_mutex().lock();
}
// In the "Canceled" state. Reset the state to "Idle".
m_state = STATE_IDLE;
@ -324,7 +333,7 @@ void BackgroundSlicingProcess::schedule_export(const std::string &path)
return;
// Guard against entering the export step before changing the export path.
tbb::mutex::scoped_lock lock(m_step_state_mutex);
tbb::mutex::scoped_lock lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
m_export_path = path;
}
@ -335,34 +344,35 @@ void BackgroundSlicingProcess::reset_export()
if (! this->running()) {
m_export_path.clear();
// invalidate_step expects the mutex to be locked.
tbb::mutex::scoped_lock lock(m_step_state_mutex);
tbb::mutex::scoped_lock lock(m_print->state_mutex());
this->invalidate_step(bspsGCodeFinalize);
}
}
void BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
bool BackgroundSlicingProcess::set_step_started(BackgroundSlicingProcessStep step)
{
m_step_state.set_started(step, m_step_state_mutex);
if (m_print->canceled())
throw CanceledException();
return m_step_state.set_started(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
}
void BackgroundSlicingProcess::set_step_done(BackgroundSlicingProcessStep step)
{
m_step_state.set_done(step, m_step_state_mutex);
if (m_print->canceled())
throw CanceledException();
m_step_state.set_done(step, m_print->state_mutex(), [this](){ this->throw_if_canceled(); });
}
bool BackgroundSlicingProcess::is_step_done(BackgroundSlicingProcessStep step) const
{
return m_step_state.is_done(step, m_print->state_mutex());
}
bool BackgroundSlicingProcess::invalidate_step(BackgroundSlicingProcessStep step)
{
bool invalidated = m_step_state.invalidate(step, m_step_state_mutex, [this](){ this->stop(); });
bool invalidated = m_step_state.invalidate(step, [this](){ this->stop_internal(); });
return invalidated;
}
bool BackgroundSlicingProcess::invalidate_all_steps()
{
return m_step_state.invalidate_all(m_step_state_mutex, [this](){ this->stop(); });
return m_step_state.invalidate_all([this](){ this->stop_internal(); });
}
}; // namespace Slic3r

View file

@ -125,11 +125,14 @@ private:
PrintState<BackgroundSlicingProcessStep, bspsCount> m_step_state;
mutable tbb::mutex m_step_state_mutex;
void set_step_started(BackgroundSlicingProcessStep step);
bool set_step_started(BackgroundSlicingProcessStep step);
void set_step_done(BackgroundSlicingProcessStep step);
bool is_step_done(BackgroundSlicingProcessStep step) const { return m_step_state.is_done(step); }
bool is_step_done(BackgroundSlicingProcessStep step) const;
bool invalidate_step(BackgroundSlicingProcessStep step);
bool invalidate_all_steps();
// If the background processing stop was requested, throw CanceledException.
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
// wxWidgets command ID to be sent to the platter to inform that the slicing is finished, and the G-code export will continue.
int m_event_sliced_id = 0;

View file

@ -1390,6 +1390,47 @@ void GLCanvas3D::Selection::clear()
m_bounding_box_dirty = true;
}
// Update the selection based on the map from old indices to new indices after m_volumes changed.
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
void GLCanvas3D::Selection::volumes_changed(const std::vector<size_t> &map_volume_old_to_new)
{
assert(m_valid);
// 1) Update the selection set.
IndicesList list_new;
std::vector<std::pair<unsigned int, unsigned int>> model_instances;
for (unsigned int idx : m_list) {
if (map_volume_old_to_new[idx] != size_t(-1)) {
unsigned int new_idx = (unsigned int)map_volume_old_to_new[idx];
list_new.insert(new_idx);
if (m_mode == Instance) {
// Save the object_idx / instance_idx pair of selected old volumes,
// so we may add the newly added volumes of the same object_idx / instance_idx pair
// to the selection.
const GLVolume *volume = (*m_volumes)[new_idx];
model_instances.emplace_back(volume->object_idx(), volume->instance_idx());
}
}
}
m_list = std::move(list_new);
if (! model_instances.empty()) {
// Instance selection mode. Add the newly added volumes of the same object_idx / instance_idx pair
// to the selection.
assert(m_mode == Instance);
sort_remove_duplicates(model_instances);
for (unsigned int i = 0; i < (unsigned int)m_volumes->size(); ++ i) {
const GLVolume* volume = (*m_volumes)[i];
for (const std::pair<int, int> &model_instance : model_instances)
if (volume->object_idx() == model_instance.first && volume->instance_idx() == model_instance.second)
this->_add_volume(i);
}
}
_update_type();
m_bounding_box_dirty = true;
}
bool GLCanvas3D::Selection::is_single_full_instance() const
{
if (m_type == SingleFullInstance)
@ -2042,10 +2083,6 @@ void GLCanvas3D::Selection::_set_caches()
void GLCanvas3D::Selection::_add_volume(unsigned int volume_idx)
{
// check if the given idx is already selected
if (m_list.find(volume_idx) != m_list.end())
return;
m_list.insert(volume_idx);
(*m_volumes)[volume_idx]->selected = true;
}
@ -3656,13 +3693,6 @@ std::vector<int> GLCanvas3D::load_object(const Model& model, int obj_idx)
return std::vector<int>();
}
std::vector<int> GLCanvas3D::load_support_meshes(const Model& model, int obj_idx)
{
std::vector<int> volumes = m_volumes.load_object_auxiliary(model.objects[obj_idx], m_sla_print->objects()[obj_idx], obj_idx, slaposSupportTree, m_use_VBOs && m_initialized);
append(volumes, m_volumes.load_object_auxiliary(model.objects[obj_idx], m_sla_print->objects()[obj_idx], obj_idx, slaposBasePool, m_use_VBOs && m_initialized));
return volumes;
}
void GLCanvas3D::mirror_selection(Axis axis)
{
m_selection.mirror(axis);
@ -3670,6 +3700,12 @@ void GLCanvas3D::mirror_selection(Axis axis)
wxGetApp().obj_manipul()->update_settings_value(m_selection);
}
// Reload the 3D scene of
// 1) Model / ModelObjects / ModelInstances / ModelVolumes
// 2) Print bed
// 3) SLA support meshes for their respective ModelObjects / ModelInstances
// 4) Wipe tower preview
// 5) Out of bed collision status & message overlay (texture)
void GLCanvas3D::reload_scene(bool force)
{
if ((m_canvas == nullptr) || (m_config == nullptr) || (m_model == nullptr))
@ -3680,40 +3716,208 @@ void GLCanvas3D::reload_scene(bool force)
return;
#endif // !ENABLE_USE_UNIQUE_GLCONTEXT
if (m_regenerate_volumes)
{
reset_volumes();
struct ModelVolumeState {
ModelVolumeState(const GLVolume *volume) :
geometry_id(volume->geometry_id), volume_idx(-1) {}
ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id, const GLVolume::CompositeID &composite_id) :
geometry_id(std::make_pair(volume_id.id, instance_id.id)), composite_id(composite_id), volume_idx(-1) {}
ModelVolumeState(const ModelID &volume_id, const ModelID &instance_id) :
geometry_id(std::make_pair(volume_id.id, instance_id.id)), volume_idx(-1) {}
bool new_geometry() const { return this->volume_idx == size_t(-1); }
// ModelID of ModelVolume + ModelID of ModelInstance
// or timestamp of an SLAPrintObjectStep + ModelID of ModelInstance
std::pair<size_t, size_t> geometry_id;
GLVolume::CompositeID composite_id;
// Volume index in the new GLVolume vector.
size_t volume_idx;
};
std::vector<ModelVolumeState> model_volume_state;
std::vector<ModelVolumeState> aux_volume_state;
// to update the toolbar
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
}
// SLA steps to pull the preview meshes for.
typedef std::array<SLAPrintObjectStep, 2> SLASteps;
SLASteps sla_steps = { slaposSupportTree, slaposBasePool };
struct SLASupportState {
std::array<PrintStateBase::StateWithTimeStamp, std::tuple_size<SLASteps>::value> step;
};
// State of the sla_steps for all SLAPrintObjects.
std::vector<SLASupportState> sla_support_state;
set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
std::vector<size_t> map_glvolume_old_to_new(m_volumes.volumes.size(), size_t(-1));
std::vector<GLVolume*> glvolumes_new;
glvolumes_new.reserve(m_volumes.volumes.size());
auto model_volume_state_lower = [](const ModelVolumeState &m1, const ModelVolumeState &m2) { return m1.geometry_id < m2.geometry_id; };
if (!m_canvas->IsShown() && !force)
{
m_reload_delayed = true;
return;
}
m_reload_delayed = false;
m_reload_delayed = ! m_canvas->IsShown() && ! force;
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
if (m_regenerate_volumes)
{
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++obj_idx)
{
load_object(*m_model, obj_idx);
if (printer_technology == ptSLA)
load_support_meshes(*m_model, obj_idx);
// Release invalidated volumes to conserve GPU memory in case of delayed refresh (see m_reload_delayed).
// First initialize model_volumes_new_sorted & model_instances_new_sorted.
for (int object_idx = 0; object_idx < (int)m_model->objects.size(); ++ object_idx) {
const ModelObject *model_object = m_model->objects[object_idx];
for (int instance_idx = 0; instance_idx < (int)model_object->instances.size(); ++ instance_idx) {
const ModelInstance *model_instance = model_object->instances[instance_idx];
for (int volume_idx = 0; volume_idx < (int)model_object->volumes.size(); ++ volume_idx) {
const ModelVolume *model_volume = model_object->volumes[volume_idx];
model_volume_state.emplace_back(model_volume->id(), model_instance->id(), GLVolume::CompositeID(object_idx, volume_idx, instance_idx));
}
}
}
if (printer_technology == ptSLA) {
#ifdef _DEBUG
// Verify that the SLAPrint object is synchronized with m_model.
check_model_ids_equal(*m_model, m_sla_print->model());
#endif /* _DEBUG */
sla_support_state.reserve(m_sla_print->objects().size());
for (const SLAPrintObject *print_object : m_sla_print->objects()) {
SLASupportState state;
for (size_t istep = 0; istep < sla_steps.size(); ++ istep) {
state.step[istep] = print_object->step_state_with_timestamp(sla_steps[istep]);
if (state.step[istep].state == PrintStateBase::DONE)
for (const ModelInstance *model_instance : print_object->model_object()->instances)
aux_volume_state.emplace_back(state.step[istep].timestamp, model_instance->id());
}
sla_support_state.emplace_back(state);
}
}
std::sort(model_volume_state.begin(), model_volume_state.end(), model_volume_state_lower);
std::sort(aux_volume_state .begin(), aux_volume_state .end(), model_volume_state_lower);
// Release all ModelVolume based GLVolumes not found in the current Model.
for (size_t volume_id = 0; volume_id < m_volumes.volumes.size(); ++ volume_id) {
GLVolume *volume = m_volumes.volumes[volume_id];
ModelVolumeState key(volume);
ModelVolumeState *mvs = nullptr;
if (volume->volume_idx() < 0) {
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
if (it != aux_volume_state.end() && it->geometry_id == key.geometry_id)
mvs = &(*it);
} else {
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
if (it != model_volume_state.end() && it->geometry_id == key.geometry_id)
mvs = &(*it);
}
if (mvs == nullptr) {
// This GLVolume will be released.
volume->release_geometry();
if (! m_reload_delayed)
delete volume;
} else {
// This GLVolume will be reused.
map_glvolume_old_to_new[volume_id] = glvolumes_new.size();
mvs->volume_idx = glvolumes_new.size();
glvolumes_new.emplace_back(volume);
}
}
}
_update_gizmos_data();
if (m_reload_delayed)
return;
set_bed_shape(dynamic_cast<const ConfigOptionPoints*>(m_config->option("bed_shape"))->values);
if (m_regenerate_volumes)
{
PrinterTechnology printer_technology = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
m_volumes.volumes = std::move(glvolumes_new);
for (unsigned int obj_idx = 0; obj_idx < (unsigned int)m_model->objects.size(); ++ obj_idx) {
const ModelObject &model_object = *m_model->objects[obj_idx];
// Object will share a single common layer height texture between all printable volumes.
std::shared_ptr<LayersTexture> layer_height_texture;
for (int volume_idx = 0; volume_idx < (int)model_object.volumes.size(); ++ volume_idx) {
const ModelVolume &model_volume = *model_object.volumes[volume_idx];
for (int instance_idx = 0; instance_idx < (int)model_object.instances.size(); ++ instance_idx) {
const ModelInstance &model_instance = *model_object.instances[instance_idx];
ModelVolumeState key(model_volume.id(), model_instance.id());
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
if (it->new_geometry()) {
// New volume.
if (model_volume.is_model_part() && ! layer_height_texture) {
// New object part needs to have the layer height texture assigned, which is shared with the other volumes of the same part.
// Search for the layer height texture in the other volumes.
for (int iv = volume_idx; iv < (int)model_object.volumes.size(); ++ iv) {
const ModelVolume &mv = *model_object.volumes[iv];
if (mv.is_model_part())
for (int ii = instance_idx; ii < (int)model_object.instances.size(); ++ ii) {
const ModelInstance &mi = *model_object.instances[ii];
ModelVolumeState key(mv.id(), mi.id());
auto it = std::lower_bound(model_volume_state.begin(), model_volume_state.end(), key, model_volume_state_lower);
assert(it != model_volume_state.end() && it->geometry_id == key.geometry_id);
if (! it->new_geometry()) {
// Found an old printable GLVolume (existing before this function was called).
assert(m_volumes.volumes[it->volume_idx]->geometry_id == key.geometry_id);
// Reuse the layer height texture.
const GLVolume *volume = m_volumes.volumes[it->volume_idx];
assert(volume->layer_height_texture);
layer_height_texture = volume->layer_height_texture;
goto iv_end;
}
}
}
iv_end:
if (! layer_height_texture)
layer_height_texture = std::make_shared<LayersTexture>();
}
m_volumes.load_object_volume(&model_object, layer_height_texture, obj_idx, volume_idx, instance_idx, m_color_by, m_use_VBOs && m_initialized);
m_volumes.volumes.back()->geometry_id = key.geometry_id;
} else {
// Recycling an old GLVolume.
GLVolume &existing_volume = *m_volumes.volumes[it->volume_idx];
assert(existing_volume.geometry_id == key.geometry_id);
// Update the Object/Volume/Instance indices into the current Model.
existing_volume.composite_id = it->composite_id;
if (model_volume.is_model_part() && ! layer_height_texture) {
assert(existing_volume.layer_height_texture);
// cache its layer height texture
layer_height_texture = existing_volume.layer_height_texture;
}
}
}
}
}
if (printer_technology == ptSLA) {
size_t idx = 0;
for (const SLAPrintObject *print_object : m_sla_print->objects()) {
SLASupportState &state = sla_support_state[idx ++];
const ModelObject *model_object = print_object->model_object();
// Find an index of the ModelObject
int object_idx;
if (! std::all_of(state.step.begin(), state.step.end(), [](const PrintStateBase::StateWithTimeStamp &state){ return state.state != PrintStateBase::DONE; }))
continue;
// There may be new SLA volumes added to the scene for this print_object.
// Find the object index of this print_object in the Model::objects list.
auto it = std::find(m_sla_print->model().objects.begin(), m_sla_print->model().objects.end(), model_object);
assert(it != m_sla_print->model().objects.end());
object_idx = it - m_sla_print->model().objects.begin();
// Collect indices of this print_object's instances, for which the SLA support meshes are to be added to the scene.
// pairs of <instance_idx, print_instance_idx>
std::vector<std::pair<size_t, size_t>> instances[std::tuple_size<SLASteps>::value];
for (size_t print_instance_idx = 0; print_instance_idx < print_object->instances().size(); ++ print_instance_idx) {
const SLAPrintObject::Instance &instance = print_object->instances()[print_instance_idx];
// Find index of ModelInstance corresponding to this SLAPrintObject::Instance.
auto it = std::find_if(model_object->instances.begin(), model_object->instances.end(),
[&instance](const ModelInstance *mi) { return mi->id() == instance.instance_id; });
assert(it != model_object->instances.end());
int instance_idx = it - model_object->instances.begin();
for (size_t istep = 0; istep < sla_steps.size(); ++ istep)
if (state.step[istep].state == PrintStateBase::DONE) {
ModelVolumeState key(state.step[istep].timestamp, instance.instance_id.id);
auto it = std::lower_bound(aux_volume_state.begin(), aux_volume_state.end(), key, model_volume_state_lower);
assert(it != aux_volume_state.end() && it->geometry_id == key.geometry_id);
if (it->new_geometry())
instances[istep].emplace_back(std::pair<size_t, size_t>(instance_idx, print_instance_idx));
else
// Recycling an old GLVolume. Update the Object/Instance indices into the current Model.
m_volumes.volumes[it->volume_idx]->composite_id = GLVolume::CompositeID(object_idx, -1, instance_idx);
}
}
for (size_t istep = 0; istep < sla_steps.size(); ++ istep)
m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], m_use_VBOs && m_initialized);
}
}
if (printer_technology == ptFFF && m_config->has("nozzle_diameter"))
{
// Should the wipe tower be visualized ?
@ -3742,7 +3946,14 @@ void GLCanvas3D::reload_scene(bool force)
}
update_volumes_colors_by_extruder();
}
// Update selection indices based on the old/new GLVolumeCollection.
m_selection.volumes_changed(map_glvolume_old_to_new);
}
_update_gizmos_data();
// Update the toolbar
post_event(SimpleEvent(EVT_GLCANVAS_OBJECT_SELECT));
// checks for geometry outside the print volume to render it accordingly
if (!m_volumes.empty())
@ -3773,6 +3984,8 @@ void GLCanvas3D::reload_scene(bool force)
// restore to default value
m_regenerate_volumes = true;
// and force this canvas to be redrawn.
m_dirty = true;
}
void GLCanvas3D::load_gcode_preview(const GCodePreviewData& preview_data, const std::vector<std::string>& str_tool_colors)

View file

@ -493,6 +493,9 @@ public:
void add_volume(unsigned int object_idx, unsigned int volume_idx, int instance_idx, bool as_single_selection = true);
void remove_volume(unsigned int object_idx, unsigned int volume_idx);
// Update the selection based on the map from old indices to new indices after m_volumes changed.
// If the current selection is by instance, this call may select newly added volumes, if they belong to already selected instances.
void volumes_changed(const std::vector<size_t> &map_volume_old_to_new);
void clear();
bool is_empty() const { return m_type == Empty; }
@ -706,6 +709,7 @@ private:
SLAPrint* m_sla_print;
Model* m_model;
// Screen is only refreshed from the OnIdle handler if it is dirty.
bool m_dirty;
bool m_initialized;
bool m_use_VBOs;
@ -820,9 +824,6 @@ public:
std::vector<int> load_object(const ModelObject& model_object, int obj_idx, std::vector<int> instance_idxs);
std::vector<int> load_object(const Model& model, int obj_idx);
// Load SLA support tree and SLA pad meshes into the scene, if available at the respective SLAPrintObject instances.
std::vector<int> load_support_meshes(const Model& model, int obj_idx);
void mirror_selection(Axis axis);
void reload_scene(bool force);