Merge branch 'vb_3dscene_partial_update'

This commit is contained in:
bubnikv 2018-11-20 17:07:31 +01:00
commit 18f14482d0
37 changed files with 1061 additions and 711 deletions

View file

@ -152,7 +152,7 @@ if(SLIC3R_STATIC)
# set(Boost_USE_STATIC_RUNTIME ON)
endif()
#set(Boost_DEBUG ON)
set(Boost_COMPILER "-vc120")
# set(Boost_COMPILER "-vc120")
find_package(Boost REQUIRED COMPONENTS system filesystem thread log locale regex)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})

View file

@ -1,2 +1 @@
add_subdirectory(slabasebed)
add_subdirectory(slasupporttree)

View file

@ -1,2 +0,0 @@
add_executable(slasupporttree EXCLUDE_FROM_ALL slasupporttree.cpp)
target_link_libraries(slasupporttree libslic3r)

View file

@ -1,48 +0,0 @@
#include <iostream>
#include <string>
#include <libslic3r.h>
#include "TriangleMesh.hpp"
#include "Model.hpp"
#include "callback.hpp"
#include "SLA/SLASupportTree.hpp"
#include "benchmark.h"
const std::string USAGE_STR = {
"Usage: slasupporttree stlfilename.stl"
};
void confess_at(const char * /*file*/,
int /*line*/,
const char * /*func*/,
const char * /*pat*/,
...) {}
namespace Slic3r {
void PerlCallback::deregister_callback() {}
}
int main(const int argc, const char *argv[]) {
using namespace Slic3r;
using std::cout; using std::endl;
if(argc < 2) {
cout << USAGE_STR << endl;
return EXIT_SUCCESS;
}
Benchmark bench;
TriangleMesh result;
bench.start();
sla::create_head(result, 3, 1, 4);
bench.stop();
cout << "Support tree creation time: " << std::setprecision(10)
<< bench.getElapsedSec() << " seconds." << endl;
result.write_ascii("out.stl");
return EXIT_SUCCESS;
}

View file

@ -80,7 +80,7 @@ elseif (MSVC)
# Manifest is provided through slic3r.rc, don't generate your own.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO")
else ()
target_link_libraries(slic3r -ldl -lstdc++)
target_link_libraries(slic3r ${CMAKE_DL_LIBS} -lstdc++)
endif ()
# Add the Slic3r GUI library, libcurl, OpenGL and GLU libraries.

View file

@ -66,7 +66,7 @@ set(AVRDUDE_SOURCES
avrdude-slic3r.hpp
avrdude-slic3r.cpp
)
if (WIN32)
if (MSVC)
set(AVRDUDE_SOURCES ${AVRDUDE_SOURCES}
windows/unistd.cpp
windows/getopt.c

View file

@ -43,7 +43,7 @@
#include <ctype.h>
#include <sys/types.h>
#if !defined(WIN32NATIVE)
#if !defined(WIN32NATIVE) || defined(__GNUC__)
# include <sys/time.h>
# include <sys/types.h>
# include <sys/stat.h>

View file

@ -126,8 +126,16 @@ private:
static bool is_end_of_line(char c) { return c == '\r' || c == '\n' || c == 0; }
static bool is_end_of_gcode_line(char c) { return c == ';' || is_end_of_line(c); }
static bool is_end_of_word(char c) { return is_whitespace(c) || is_end_of_gcode_line(c); }
static const char* skip_whitespaces(const char *c) { for (; is_whitespace(*c); ++ c); return c; }
static const char* skip_word(const char *c) { for (; ! is_end_of_word(*c); ++ c); return c; }
static const char* skip_whitespaces(const char *c) {
for (; is_whitespace(*c); ++ c)
; // silence -Wempty-body
return c;
}
static const char* skip_word(const char *c) {
for (; ! is_end_of_word(*c); ++ c)
; // silence -Wempty-body
return c;
}
GCodeConfig m_config;
char m_extrusion_axis;

View file

@ -1513,4 +1513,52 @@ Transform3d ModelInstance::get_matrix(bool dont_translate, bool dont_rotate, boo
}
#endif // !ENABLE_MODELVOLUME_TRANSFORM
#ifdef _DEBUG
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
void check_model_ids_validity(const Model &model)
{
std::set<ModelID> ids;
auto check = [&ids](ModelID id) {
assert(id.id > 0);
assert(ids.find(id) == ids.end());
ids.insert(id);
};
for (const ModelObject *model_object : model.objects) {
check(model_object->id());
for (const ModelVolume *model_volume : model_object->volumes)
check(model_volume->id());
for (const ModelInstance *model_instance : model_object->instances)
check(model_instance->id());
}
for (const auto mm : model.materials)
check(mm.second->id());
}
void check_model_ids_equal(const Model &model1, const Model &model2)
{
// Verify whether the IDs of model1 and model match.
assert(model1.objects.size() == model2.objects.size());
for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) {
const ModelObject &model_object1 = *model1.objects[idx_model];
const ModelObject &model_object2 = * model2.objects[idx_model];
assert(model_object1.id() == model_object2.id());
assert(model_object1.volumes.size() == model_object2.volumes.size());
assert(model_object1.instances.size() == model_object2.instances.size());
for (size_t i = 0; i < model_object1.volumes.size(); ++ i)
assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
for (size_t i = 0; i < model_object1.instances.size(); ++ i)
assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
}
assert(model1.materials.size() == model2.materials.size());
{
auto it1 = model1.materials.begin();
auto it2 = model2.materials.begin();
for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
assert(it1->first == it2->first); // compare keys
assert(it1->second->id() == it2->second->id());
}
}
}
#endif /* _DEBUG */
}

View file

@ -652,6 +652,12 @@ private:
#undef MODELBASE_DERIVED_COPY_MOVE_CLONE
#undef MODELBASE_DERIVED_PRIVATE_COPY_MOVE
#ifdef _DEBUG
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
void check_model_ids_validity(const Model &model);
void check_model_ids_equal(const Model &model1, const Model &model2);
#endif /* _DEBUG */
}
#endif

View file

@ -27,7 +27,7 @@ template class PrintState<PrintObjectStep, posCount>;
void Print::clear()
{
tbb::mutex::scoped_lock lock(this->cancel_mutex());
tbb::mutex::scoped_lock lock(this->state_mutex());
// The following call should stop background processing if it is running.
this->invalidate_all_steps();
for (PrintObject *object : m_objects)
@ -43,7 +43,7 @@ void Print::reload_object(size_t /* idx */)
{
ModelObjectPtrs model_objects;
{
tbb::mutex::scoped_lock lock(this->cancel_mutex());
tbb::mutex::scoped_lock lock(this->state_mutex());
// The following call should stop background processing if it is running.
this->invalidate_all_steps();
/* TODO: this method should check whether the per-object config and per-material configs
@ -271,8 +271,9 @@ bool Print::is_step_done(PrintObjectStep step) const
{
if (m_objects.empty())
return false;
tbb::mutex::scoped_lock lock(this->state_mutex());
for (const PrintObject *object : m_objects)
if (!object->m_state.is_done(step))
if (! object->m_state.is_done_unguarded(step))
return false;
return true;
}
@ -374,7 +375,7 @@ static PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig
// and have explicit instance positions.
void Print::add_model_object(ModelObject* model_object, int idx)
{
tbb::mutex::scoped_lock lock(this->cancel_mutex());
tbb::mutex::scoped_lock lock(this->state_mutex());
// Initialize a new print object and store it at the given position.
PrintObject *object = new PrintObject(this, model_object);
if (idx != -1) {
@ -435,7 +436,7 @@ void Print::add_model_object(ModelObject* model_object, int idx)
bool Print::apply_config(DynamicPrintConfig config)
{
tbb::mutex::scoped_lock lock(this->cancel_mutex());
tbb::mutex::scoped_lock lock(this->state_mutex());
// we get a copy of the config object so we can modify it safely
config.normalize();
@ -734,54 +735,6 @@ static std::vector<PrintInstances> print_objects_from_model_object(const ModelOb
return std::vector<PrintInstances>(trafos.begin(), trafos.end());
}
#ifdef _DEBUG
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.
static inline void check_model_ids_validity(const Model &model)
{
std::set<ModelID> ids;
auto check = [&ids](ModelID id) {
assert(id.id > 0);
assert(ids.find(id) == ids.end());
ids.insert(id);
};
for (const ModelObject *model_object : model.objects) {
check(model_object->id());
for (const ModelVolume *model_volume : model_object->volumes)
check(model_volume->id());
for (const ModelInstance *model_instance : model_object->instances)
check(model_instance->id());
}
for (const auto mm : model.materials)
check(mm.second->id());
}
static inline void check_model_ids_equal(const Model &model1, const Model &model2)
{
// Verify whether the IDs of model1 and model match.
assert(model1.objects.size() == model2.objects.size());
for (size_t idx_model = 0; idx_model < model2.objects.size(); ++ idx_model) {
const ModelObject &model_object1 = *model1.objects[idx_model];
const ModelObject &model_object2 = * model2.objects[idx_model];
assert(model_object1.id() == model_object2.id());
assert(model_object1.volumes.size() == model_object2.volumes.size());
assert(model_object1.instances.size() == model_object2.instances.size());
for (size_t i = 0; i < model_object1.volumes.size(); ++ i)
assert(model_object1.volumes[i]->id() == model_object2.volumes[i]->id());
for (size_t i = 0; i < model_object1.instances.size(); ++ i)
assert(model_object1.instances[i]->id() == model_object2.instances[i]->id());
}
assert(model1.materials.size() == model2.materials.size());
{
auto it1 = model1.materials.begin();
auto it2 = model2.materials.begin();
for (; it1 != model1.materials.end(); ++ it1, ++ it2) {
assert(it1->first == it2->first); // compare keys
assert(it1->second->id() == it2->second->id());
}
}
}
#endif /* _DEBUG */
Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &config_in)
{
#ifdef _DEBUG
@ -804,7 +757,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
update_apply_status(false);
// Grab the lock for the Print / PrintObject milestones.
tbb::mutex::scoped_lock lock(this->cancel_mutex());
tbb::mutex::scoped_lock lock(this->state_mutex());
// The following call may stop the background processing.
update_apply_status(this->invalidate_state_by_config_options(print_diff));
@ -1579,16 +1532,12 @@ void Print::process()
BOOST_LOG_TRIVIAL(info) << "Staring the slicing process.";
for (PrintObject *obj : m_objects)
obj->make_perimeters();
this->throw_if_canceled();
this->set_status(70, "Infilling layers");
for (PrintObject *obj : m_objects)
obj->infill();
this->throw_if_canceled();
for (PrintObject *obj : m_objects)
obj->generate_support_material();
this->throw_if_canceled();
if (! this->is_step_done(psSkirt)) {
this->set_started(psSkirt);
if (this->set_started(psSkirt)) {
m_skirt.clear();
if (this->has_skirt()) {
this->set_status(88, "Generating skirt");
@ -1596,9 +1545,7 @@ void Print::process()
}
this->set_done(psSkirt);
}
this->throw_if_canceled();
if (! this->is_step_done(psBrim)) {
this->set_started(psBrim);
if (this->set_started(psBrim)) {
m_brim.clear();
if (m_config.brim_width > 0) {
this->set_status(88, "Generating brim");
@ -1606,9 +1553,7 @@ void Print::process()
}
this->set_done(psBrim);
}
this->throw_if_canceled();
if (! this->is_step_done(psWipeTower)) {
this->set_started(psWipeTower);
if (this->set_started(psWipeTower)) {
m_wipe_tower_data.clear();
if (this->has_wipe_tower()) {
//this->set_status(95, "Generating wipe tower");
@ -1625,9 +1570,6 @@ void Print::process()
// It is up to the caller to show an error message.
void Print::export_gcode(const std::string &path_template, GCodePreviewData *preview_data)
{
// prerequisites
this->process();
// output everything to a G-code file
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);

View file

@ -97,8 +97,6 @@ public:
Vec3crd size; // XYZ in scaled coordinates
const ModelObject* model_object() const { return m_model_object; }
ModelObject* model_object() { return m_model_object; }
const PrintObjectConfig& config() const { return m_config; }
const LayerPtrs& layers() const { return m_layers; }
const SupportLayerPtrs& support_layers() const { return m_support_layers; }
@ -197,7 +195,6 @@ private:
void combine_infill();
void _generate_support_material();
ModelObject *m_model_object;
PrintObjectConfig m_config;
// Translation in Z + Rotation + Scaling / Mirroring.
Transform3d m_trafo = Transform3d::Identity();
@ -381,7 +378,6 @@ private:
// Declared here to have access to Model / ModelObject / ModelInstance
static void model_volume_list_update_supports(ModelObject &model_object_dst, const ModelObject &model_object_src);
Model m_model;
PrintConfig m_config;
PrintObjectConfig m_default_object_config;
PrintRegionConfig m_default_region_config;

View file

@ -3,9 +3,11 @@
namespace Slic3r
{
tbb::mutex& PrintObjectBase::cancel_mutex(PrintBase *print)
size_t PrintStateBase::g_last_timestamp = 0;
tbb::mutex& PrintObjectBase::state_mutex(PrintBase *print)
{
return print->cancel_mutex();
return print->state_mutex();
}
std::function<void()> PrintObjectBase::cancel_callback(PrintBase *print)

View file

@ -2,13 +2,11 @@
#define slic3r_PrintBase_hpp_
#include "libslic3r.h"
#include <atomic>
#include <set>
#include <vector>
#include <string>
#include <functional>
#include "tbb/atomic.h"
// tbb/mutex.h includes Windows, which in turn defines min/max macros. Convince Windows.h to not define these min/max macros.
#ifndef NOMINMAX
#define NOMINMAX
@ -25,68 +23,120 @@ public:
const char* what() const throw() { return "Background processing has been canceled"; }
};
// To be instantiated over PrintStep or PrintObjectStep enums.
template <class StepType, size_t COUNT>
class PrintState
{
class PrintStateBase {
public:
PrintState() { for (size_t i = 0; i < COUNT; ++ i) m_state[i].store(INVALID, std::memory_order_relaxed); }
enum State {
INVALID,
STARTED,
DONE,
};
// With full memory barrier.
bool is_done(StepType step) const { return m_state[step] == DONE; }
typedef size_t TimeStamp;
// A new unique timestamp is being assigned to the step every time the step changes its state.
struct StateWithTimeStamp
{
StateWithTimeStamp() : state(INVALID), timestamp(0) {}
State state;
TimeStamp timestamp;
};
protected:
//FIXME last timestamp is shared between Print & SLAPrint,
// and if multiple Print or SLAPrint instances are executed in parallel, modification of g_last_timestamp
// is not synchronized!
static size_t g_last_timestamp;
};
// To be instantiated over PrintStep or PrintObjectStep enums.
template <class StepType, size_t COUNT>
class PrintState : public PrintStateBase
{
public:
PrintState() {}
StateWithTimeStamp state_with_timestamp(StepType step, tbb::mutex &mtx) const {
tbb::mutex::scoped_lock lock(mtx);
StateWithTimeStamp state = m_state[step];
return state;
}
bool is_done(StepType step, tbb::mutex &mtx) const {
return this->state_with_timestamp(step, mtx).state == DONE;
}
StateWithTimeStamp state_with_timestamp_unguarded(StepType step) const {
return m_state[step];
}
bool is_done_unguarded(StepType step) const {
return this->state_with_timestamp_unguarded(step).state == DONE;
}
// Set the step as started. Block on mutex while the Print / PrintObject / PrintRegion objects are being
// modified by the UI thread.
// This is necessary to block until the Print::apply_config() updates its state, which may
// influence the processing step being entered.
void set_started(StepType step, tbb::mutex &mtx) {
mtx.lock();
m_state[step].store(STARTED, std::memory_order_relaxed);
mtx.unlock();
template<typename ThrowIfCanceled>
bool set_started(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
tbb::mutex::scoped_lock lock(mtx);
// If canceled, throw before changing the step state.
throw_if_canceled();
if (m_state[step].state == DONE)
return false;
m_state[step].state = STARTED;
m_state[step].timestamp = ++ g_last_timestamp;
return true;
}
// Set the step as done. Block on mutex while the Print / PrintObject / PrintRegion objects are being
// modified by the UI thread.
void set_done(StepType step, tbb::mutex &mtx) {
mtx.lock();
m_state[step].store(DONE, std::memory_order_relaxed);
mtx.unlock();
template<typename ThrowIfCanceled>
TimeStamp set_done(StepType step, tbb::mutex &mtx, ThrowIfCanceled throw_if_canceled) {
tbb::mutex::scoped_lock lock(mtx);
// If canceled, throw before changing the step state.
throw_if_canceled();
assert(m_state[step].state != DONE);
m_state[step].state = DONE;
m_state[step].timestamp = ++ g_last_timestamp;
return m_state[step].timestamp;
}
// Make the step invalid.
// The provided mutex should be locked at this point, guarding access to m_state.
// PrintBase::m_state_mutex should be locked at this point, guarding access to m_state.
// In case the step has already been entered or finished, cancel the background
// processing by calling the cancel callback.
template<typename CancelationCallback>
bool invalidate(StepType step, tbb::mutex &mtx, CancelationCallback cancel) {
bool invalidated = m_state[step].load(std::memory_order_relaxed) != INVALID;
bool invalidate(StepType step, CancelationCallback cancel) {
bool invalidated = m_state[step].state != INVALID;
if (invalidated) {
#if 0
if (mtx.state != mtx.HELD) {
printf("Not held!\n");
}
#endif
m_state[step].state = INVALID;
m_state[step].timestamp = ++ g_last_timestamp;
// Raise the mutex, so that the following cancel() callback could cancel
// the background processing.
mtx.unlock();
// Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let
// the working thread to proceed.
cancel();
m_state[step] = INVALID;
mtx.lock();
}
return invalidated;
}
template<typename CancelationCallback, typename StepTypeIterator>
bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, tbb::mutex &mtx, CancelationCallback cancel) {
bool invalidate_multiple(StepTypeIterator step_begin, StepTypeIterator step_end, CancelationCallback cancel) {
bool invalidated = false;
for (StepTypeIterator it = step_begin; ! invalidated && it != step_end; ++ it)
invalidated = m_state[*it].load(std::memory_order_relaxed) != INVALID;
for (StepTypeIterator it = step_begin; it != step_end; ++ it) {
StateWithTimeStamp &state = m_state[*it];
if (state.state != INVALID) {
invalidated = true;
state.state = INVALID;
state.timestamp = ++ g_last_timestamp;
}
}
if (invalidated) {
#if 0
if (mtx.state != mtx.HELD) {
@ -95,50 +145,53 @@ public:
#endif
// Raise the mutex, so that the following cancel() callback could cancel
// the background processing.
mtx.unlock();
// Internally the cancel() callback shall unlock the PrintBase::m_status_mutex to let
// the working thread to proceed.
cancel();
for (StepTypeIterator it = step_begin; it != step_end; ++ it)
m_state[*it] = INVALID;
mtx.lock();
}
return invalidated;
}
// Make all steps invalid.
// The provided mutex should be locked at this point, guarding access to m_state.
// PrintBase::m_state_mutex should be locked at this point, guarding access to m_state.
// In case any step has already been entered or finished, cancel the background
// processing by calling the cancel callback.
template<typename CancelationCallback>
bool invalidate_all(tbb::mutex &mtx, CancelationCallback cancel) {
bool invalidate_all(CancelationCallback cancel) {
bool invalidated = false;
for (size_t i = 0; i < COUNT; ++ i)
if (m_state[i].load(std::memory_order_relaxed) != INVALID) {
for (size_t i = 0; i < COUNT; ++ i) {
StateWithTimeStamp &state = m_state[i];
if (state.state != INVALID) {
invalidated = true;
break;
state.state = INVALID;
state.timestamp = ++ g_last_timestamp;
}
if (invalidated) {
mtx.unlock();
cancel();
for (size_t i = 0; i < COUNT; ++ i)
m_state[i].store(INVALID, std::memory_order_relaxed);
mtx.lock();
}
if (invalidated)
cancel();
return invalidated;
}
private:
std::atomic<State> m_state[COUNT];
StateWithTimeStamp m_state[COUNT];
};
class PrintBase;
class PrintObjectBase
{
public:
const ModelObject* model_object() const { return m_model_object; }
ModelObject* model_object() { return m_model_object; }
protected:
PrintObjectBase(ModelObject *model_object) : m_model_object(model_object) {}
virtual ~PrintObjectBase() {}
// Declared here to allow access from PrintBase through friendship.
static tbb::mutex& cancel_mutex(PrintBase *print);
static tbb::mutex& state_mutex(PrintBase *print);
static std::function<void()> cancel_callback(PrintBase *print);
ModelObject *m_model_object;
};
/**
@ -179,19 +232,31 @@ public:
APPLY_STATUS_INVALIDATED,
};
virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0;
const Model& model() const { return m_model; }
virtual void process() = 0;
typedef std::function<void(int, const std::string&)> status_callback_type;
struct SlicingStatus {
SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {}
int percent;
std::string text;
// Bitmap of flags.
enum FlagBits {
RELOAD_SCENE = 1,
};
// Bitmap of FlagBits
unsigned int flags;
};
typedef std::function<void(const SlicingStatus&)> status_callback_type;
// Default status console print out in the form of percent => message.
void set_status_default() { m_status_callback = nullptr; }
// No status output or callback whatsoever, useful mostly for automatic tests.
void set_status_silent() { m_status_callback = [](int, const std::string&){}; }
void set_status_silent() { m_status_callback = [](const SlicingStatus&){}; }
// Register a custom status callback.
void set_status_callback(status_callback_type cb) { m_status_callback = cb; }
// Calls a registered callback to update the status, or print out the default message.
void set_status(int percent, const std::string &message) {
if (m_status_callback) m_status_callback(percent, message);
void set_status(int percent, const std::string &message, unsigned int flags = 0) {
if (m_status_callback) m_status_callback(SlicingStatus(percent, message, flags));
else printf("%d => %s\n", percent, message.c_str());
}
@ -220,8 +285,9 @@ public:
protected:
friend class PrintObjectBase;
friend class BackgroundSlicingProcess;
tbb::mutex& cancel_mutex() { return m_cancel_mutex; }
tbb::mutex& state_mutex() const { return m_state_mutex; }
std::function<void()> cancel_callback() { return m_cancel_callback; }
void call_cancell_callback() { m_cancel_callback(); }
@ -229,6 +295,8 @@ protected:
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() const { if (m_cancel_status) throw CanceledException(); }
Model m_model;
private:
tbb::atomic<CancelStatus> m_cancel_status;
// Callback to be evoked regularly to update state of the UI thread.
@ -240,27 +308,28 @@ private:
// Mutex used for synchronization of the worker thread with the UI thread:
// The mutex will be used to guard the worker thread against entering a stage
// while the data influencing the stage is modified.
mutable tbb::mutex m_cancel_mutex;
mutable tbb::mutex m_state_mutex;
};
template<typename PrintStepEnum, const size_t COUNT>
class PrintBaseWithState : public PrintBase
{
public:
bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step); }
bool is_step_done(PrintStepEnum step) const { return m_state.is_done(step, this->state_mutex()); }
PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintStepEnum step) const { return m_state.state_with_timestamp(step, this->state_mutex()); }
protected:
void set_started(PrintStepEnum step) { m_state.set_started(step, this->cancel_mutex()); throw_if_canceled(); }
void set_done(PrintStepEnum step) { m_state.set_done(step, this->cancel_mutex()); throw_if_canceled(); }
bool set_started(PrintStepEnum step) { return m_state.set_started(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
PrintStateBase::TimeStamp set_done(PrintStepEnum step) { return m_state.set_done(step, this->state_mutex(), [this](){ this->throw_if_canceled(); }); }
bool invalidate_step(PrintStepEnum step)
{ return m_state.invalidate(step, this->cancel_mutex(), this->cancel_callback()); }
{ return m_state.invalidate(step, this->cancel_callback()); }
template<typename StepTypeIterator>
bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end)
{ return m_state.invalidate_multiple(step_begin, step_end, this->cancel_mutex(), this->cancel_callback()); }
{ return m_state.invalidate_multiple(step_begin, step_end, this->cancel_callback()); }
bool invalidate_steps(std::initializer_list<PrintStepEnum> il)
{ return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_mutex(), this->cancel_callback()); }
{ return m_state.invalidate_multiple(il.begin(), il.end(), this->cancel_callback()); }
bool invalidate_all_steps()
{ return m_state.invalidate_all(this->cancel_mutex(), this->cancel_callback()); }
{ return m_state.invalidate_all(this->cancel_callback()); }
private:
PrintState<PrintStepEnum, COUNT> m_state;
@ -273,24 +342,33 @@ public:
PrintType* print() { return m_print; }
const PrintType* print() const { return m_print; }
bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step); }
typedef PrintState<PrintObjectStepEnum, COUNT> PrintObjectState;
bool is_step_done(PrintObjectStepEnum step) const { return m_state.is_done(step, PrintObjectBase::state_mutex(m_print)); }
PrintStateBase::StateWithTimeStamp step_state_with_timestamp(PrintObjectStepEnum step) const { return m_state.state_with_timestamp(step, PrintObjectBase::state_mutex(m_print)); }
protected:
PrintObjectBaseWithState(PrintType *print) : m_print(print) {}
PrintObjectBaseWithState(PrintType *print, ModelObject *model_object) : PrintObjectBase(model_object), m_print(print) {}
void set_started(PrintObjectStepEnum step) { m_state.set_started(step, PrintObjectBase::cancel_mutex(m_print)); }
void set_done(PrintObjectStepEnum step) { m_state.set_done(step, PrintObjectBase::cancel_mutex(m_print)); }
bool set_started(PrintObjectStepEnum step)
{ return m_state.set_started(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); }
PrintStateBase::TimeStamp set_done(PrintObjectStepEnum step)
{ return m_state.set_done(step, PrintObjectBase::state_mutex(m_print), [this](){ this->throw_if_canceled(); }); }
bool invalidate_step(PrintObjectStepEnum step)
{ return m_state.invalidate(step, PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
{ return m_state.invalidate(step, PrintObjectBase::cancel_callback(m_print)); }
template<typename StepTypeIterator>
bool invalidate_steps(StepTypeIterator step_begin, StepTypeIterator step_end)
{ return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
{ return m_state.invalidate_multiple(step_begin, step_end, PrintObjectBase::cancel_callback(m_print)); }
bool invalidate_steps(std::initializer_list<PrintObjectStepEnum> il)
{ return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
bool invalidate_all_steps() { return m_state.invalidate_all(PrintObjectBase::cancel_mutex(m_print), PrintObjectBase::cancel_callback(m_print)); }
{ return m_state.invalidate_multiple(il.begin(), il.end(), PrintObjectBase::cancel_callback(m_print)); }
bool invalidate_all_steps()
{ return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); }
protected:
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
void throw_if_canceled() { if (m_print->canceled()) throw CanceledException(); }
friend PrintType;
PrintType *m_print;

View file

@ -2515,7 +2515,22 @@ void PrintConfigDef::init_sla_params()
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->default_value = new ConfigOptionFloat();
def->default_value = new ConfigOptionFloat(15.0);
def = this->add("support_object_elevation", coFloat);
def->label = L("Object elevation");
def->tooltip = L("How much the supports should lift up the supported object.");
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
def->default_value = new ConfigOptionFloat(5.0);
def = this->add("pad_enable", coBool);
def->label = L("Use pad");
def->tooltip = L("Add a pad underneath the supported model");
def->sidetext = L("");
def->cli = "";
def->default_value = new ConfigOptionBool(true);
def = this->add("pad_wall_thickness", coFloat);
def->label = L("Pad wall thickness");
@ -2542,7 +2557,7 @@ void PrintConfigDef::init_sla_params()
def->default_value = new ConfigOptionFloat(50.0);
def = this->add("pad_edge_radius", coFloat);
def->label = L("pad edge radius");
def->label = L("Pad edge radius");
def->tooltip = L("");
def->sidetext = L("mm");
def->cli = "";

View file

@ -31,7 +31,7 @@ enum PrinterTechnology
};
enum GCodeFlavor {
gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
gcfRepRap, gcfRepetier, gcfTeacup, gcfMakerWare, gcfMarlin, gcfSailfish, gcfMach3, gcfMachinekit,
gcfSmoothie, gcfNoExtrusion,
};
@ -167,7 +167,7 @@ private:
// This definition is constant.
extern const PrintConfigDef print_config_def;
// Slic3r dynamic configuration, used to override the configuration
// Slic3r dynamic configuration, used to override the configuration
// per object, per modification volume or per printing material.
// The dynamic configuration is also used to store user modifications of the print global parameters,
// so the modified configuration values may be diffed against the active configuration
@ -274,12 +274,12 @@ protected:
m_defaults = defaults;
m_keys.clear();
m_keys.reserve(m_map_name_to_offset.size());
for (const auto &kvp : defs->options) {
// Find the option given the option name kvp.first by an offset from (char*)m_defaults.
ConfigOption *opt = this->optptr(kvp.first, m_defaults);
if (opt == nullptr)
// This option is not defined by the ConfigBase of type T.
continue;
for (const auto &kvp : defs->options) {
// Find the option given the option name kvp.first by an offset from (char*)m_defaults.
ConfigOption *opt = this->optptr(kvp.first, m_defaults);
if (opt == nullptr)
// This option is not defined by the ConfigBase of type T.
continue;
m_keys.emplace_back(kvp.first);
const ConfigOptionDef *def = defs->get(kvp.first);
assert(def != nullptr);
@ -463,7 +463,7 @@ public:
ConfigOptionInt top_solid_layers;
ConfigOptionFloatOrPercent top_solid_infill_speed;
ConfigOptionBool wipe_into_infill;
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
@ -768,7 +768,7 @@ public:
ConfigOptionInt pixel_height;
ConfigOptionFloat exp_time;
ConfigOptionFloat exp_time_first;
protected:
PrintConfig(int) : GCodeConfig(1) {}
void initialize(StaticCacheBase &cache, const char *base_ptr)
@ -859,7 +859,7 @@ public:
ConfigOptionString printhost_cafile;
ConfigOptionString serial_port;
ConfigOptionInt serial_speed;
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
@ -873,14 +873,14 @@ protected:
};
// This object is mapped to Perl as Slic3r::Config::Full.
class FullPrintConfig :
public PrintObjectConfig,
class FullPrintConfig :
public PrintObjectConfig,
public PrintRegionConfig,
public PrintConfig,
public HostConfig
{
STATIC_PRINT_CONFIG_CACHE_DERIVED(FullPrintConfig)
FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
FullPrintConfig() : PrintObjectConfig(0), PrintRegionConfig(0), PrintConfig(0), HostConfig(0) { initialize_cache(); *this = s_cache_FullPrintConfig.defaults(); }
public:
// Validate the FullPrintConfig. Returns an empty string on success, otherwise an error message is returned.
@ -910,14 +910,15 @@ public:
// How much the pinhead has to penetrate the model surface
ConfigOptionFloat support_head_penetration /*= 0.2*/;
// Radius of the back side of the 3d arrow.
// Radius of the back side of the 3d arrow. TODO: consider renaming this
// to actual pillar radius, because that's what it boils down to.
ConfigOptionFloat support_head_back_radius /*= 0.5*/;
// Width in mm from the back sphere center to the front sphere center.
ConfigOptionFloat support_head_width /*= 1.0*/;
// Radius in mm of the support pillars.
// TODO: This parameter is invalid. The pillar radius will be dynamic in
// TODO: This parameter is questionable. The pillar radius will be dynamic in
// nature. Merged pillars will have an increased thickness. This parameter
// may serve as the maximum radius, or maybe an increase when two are merged
// The default radius will be derived from head_back_radius_mm
@ -930,17 +931,18 @@ public:
ConfigOptionFloat support_base_height /*= 1.0*/;
// The default angle for connecting support sticks and junctions.
ConfigOptionFloat support_critical_angle /*= M_PI/4*/;
ConfigOptionFloat support_critical_angle /*= 45*/;
// The max length of a bridge in mm
ConfigOptionFloat support_max_bridge_length /*= 15.0*/;
// The elevation in Z direction upwards. This is the space between the pad
// and the model object's bounding box bottom.
ConfigOptionFloat support_object_elevation;
// and the model object's bounding box bottom. Units in mm.
ConfigOptionFloat support_object_elevation /*= 5.0*/;
// Now for the base pool (plate) ///////////////////////////////////////////
// Now for the base pool (pad) /////////////////////////////////////////////
ConfigOptionBool pad_enable;
ConfigOptionFloat pad_wall_thickness /*= 2*/;
ConfigOptionFloat pad_wall_height /*= 5*/;
ConfigOptionFloat pad_max_merge_distance /*= 50*/;
@ -959,6 +961,8 @@ protected:
OPT_PTR(support_base_height);
OPT_PTR(support_critical_angle);
OPT_PTR(support_max_bridge_length);
OPT_PTR(support_object_elevation);
OPT_PTR(pad_enable);
OPT_PTR(pad_wall_thickness);
OPT_PTR(pad_wall_height);
OPT_PTR(pad_max_merge_distance);
@ -1068,7 +1072,7 @@ public:
ConfigOptionFloat scale;
// ConfigOptionPoint3 scale_to_fit;
ConfigOptionBool slice;
CLIConfig() : ConfigBase(), StaticConfig()
{
this->set_defaults();
@ -1076,7 +1080,7 @@ public:
// Overrides ConfigBase::def(). Static configuration definition. Any value stored into this ConfigBase shall have its definition here.
const ConfigDef* def() const override { return &cli_config_def; }
t_config_option_keys keys() const override { return cli_config_def.keys(); }
t_config_option_keys keys() const override { return cli_config_def.keys(); }
ConfigOption* optptr(const t_config_option_key &opt_key, bool create = false) override
{
@ -1118,7 +1122,7 @@ private:
class PrintAndCLIConfigDef : public ConfigDef
{
public:
PrintAndCLIConfigDef() {
PrintAndCLIConfigDef() {
this->options.insert(print_config_def.options.begin(), print_config_def.options.end());
this->options.insert(cli_config_def.options.begin(), cli_config_def.options.end());
}

View file

@ -35,9 +35,8 @@
namespace Slic3r {
PrintObject::PrintObject(Print* print, ModelObject* model_object) :
PrintObjectBaseWithState(print),
PrintObjectBaseWithState(print, model_object),
typed_slices(false),
m_model_object(model_object),
size(Vec3crd::Zero()),
layer_height_profile_valid(false)
{
@ -103,9 +102,8 @@ bool PrintObject::set_copies(const Points &points)
// this should be idempotent
void PrintObject::slice()
{
if (this->is_step_done(posSlice))
if (! this->set_started(posSlice))
return;
this->set_started(posSlice);
m_print->set_status(10, "Processing triangulated mesh");
this->_slice();
m_print->throw_if_canceled();
@ -131,10 +129,9 @@ void PrintObject::make_perimeters()
// prerequisites
this->slice();
if (this->is_step_done(posPerimeters))
if (! this->set_started(posPerimeters))
return;
this->set_started(posPerimeters);
m_print->set_status(20, "Generating perimeters");
BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";
@ -242,10 +239,9 @@ void PrintObject::make_perimeters()
void PrintObject::prepare_infill()
{
if (this->is_step_done(posPrepareInfill))
if (! this->set_started(posPrepareInfill))
return;
this->set_started(posPrepareInfill);
m_print->set_status(30, "Preparing infill");
// This will assign a type (top/bottom/internal) to $layerm->slices.
@ -361,8 +357,7 @@ void PrintObject::infill()
// prerequisites
this->prepare_infill();
if (! this->is_step_done(posInfill)) {
this->set_started(posInfill);
if (this->set_started(posInfill)) {
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
tbb::parallel_for(
tbb::blocked_range<size_t>(0, m_layers.size()),
@ -384,8 +379,7 @@ void PrintObject::infill()
void PrintObject::generate_support_material()
{
if (! this->is_step_done(posSupportMaterial)) {
this->set_started(posSupportMaterial);
if (this->set_started(posSupportMaterial)) {
this->clear_support_layers();
if ((m_config.support_material || m_config.raft_layers > 0) && m_layers.size() > 1) {
m_print->set_status(85, "Generating support material");
@ -1706,9 +1700,8 @@ void PrintObject::_simplify_slices(double distance)
void PrintObject::_make_perimeters()
{
if (this->is_step_done(posPerimeters))
if (! this->set_started(posPerimeters))
return;
this->set_started(posPerimeters);
BOOST_LOG_TRIVIAL(info) << "Generating perimeters...";

View file

@ -424,12 +424,6 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
TriangleMesh m = mesh;
TriangleMeshSlicer slicer(&m);
// TriangleMesh upper, lower;
// slicer.cut(h, &upper, &lower);
// TODO: this might be slow (in fact it was)
// output = lower.horizontal_projection();
auto bb = mesh.bounding_box();
float gnd = float(bb.min(Z));
std::vector<float> heights = {float(bb.min(Z))};

View file

@ -23,6 +23,13 @@ struct PoolConfig {
double min_wall_height_mm = 5;
double max_merge_distance_mm = 50;
double edge_radius_mm = 1;
inline PoolConfig() {}
inline PoolConfig(double wt, double wh, double md, double er):
min_wall_thickness_mm(wt),
min_wall_height_mm(wh),
max_merge_distance_mm(md),
edge_radius_mm(er) {}
};
/// Calculate the pool for the mesh for SLA printing
@ -31,6 +38,15 @@ void create_base_pool(const ExPolygons& base_plate,
const PoolConfig& = PoolConfig()
);
/// TODO: Currently the base plate of the pool will have half the height of the
/// whole pool. So the carved out space has also half the height. This is not
/// a particularly elegant solution, the thickness should be exactly
/// min_wall_thickness and it should be corrected in the future. This method
/// will return the correct value for further processing.
inline double get_pad_elevation(const PoolConfig& cfg) {
return cfg.min_wall_height_mm / 2.0;
}
}
}

View file

@ -213,6 +213,7 @@ struct Head {
double r_back_mm = 1;
double r_pin_mm = 0.5;
double width_mm = 2;
double penetration_mm = 0.5;
// For identification purposes. This will be used as the index into the
// container holding the head structures. See SLASupportTree::Impl
@ -224,11 +225,13 @@ struct Head {
Head(double r_big_mm,
double r_small_mm,
double length_mm,
double penetration,
Vec3d direction = {0, 0, -1}, // direction (normal to the dull end )
Vec3d offset = {0, 0, 0}, // displacement
const size_t circlesteps = 45):
steps(circlesteps), dir(direction), tr(offset),
r_back_mm(r_big_mm), r_pin_mm(r_small_mm), width_mm(length_mm)
r_back_mm(r_big_mm), r_pin_mm(r_small_mm), width_mm(length_mm),
penetration_mm(penetration)
{
// We create two spheres which will be connected with a robe that fits
@ -281,7 +284,7 @@ struct Head {
// To simplify further processing, we translate the mesh so that the
// last vertex of the pointing sphere (the pinpoint) will be at (0,0,0)
for(auto& p : mesh.points) { z(p) -= (h + 0.5 * r_small_mm); }
for(auto& p : mesh.points) z(p) -= (h + r_small_mm - penetration_mm);
}
void transform()
@ -298,11 +301,11 @@ struct Head {
}
double fullwidth() const {
return 1.5 * r_pin_mm + width_mm + 2*r_back_mm;
return 2 * r_pin_mm + width_mm + 2*r_back_mm - penetration_mm;
}
Vec3d junction_point() const {
return tr + ( 1.5 * r_pin_mm + width_mm + r_back_mm)*dir;
return tr + ( 2 * r_pin_mm + width_mm + r_back_mm - penetration_mm)*dir;
}
double request_pillar_radius(double radius) const {
@ -507,7 +510,9 @@ struct Pad {
Pad(const TriangleMesh& object_support_mesh,
const ExPolygons& baseplate,
double ground_level,
const PoolConfig& cfg) : zlevel(ground_level + cfg.min_wall_height_mm/2)
const PoolConfig& pcfg) :
cfg(pcfg),
zlevel(ground_level + sla::get_pad_elevation(pcfg))
{
ExPolygons basep;
base_plate(object_support_mesh, basep,
@ -538,19 +543,6 @@ EigenMesh3D to_eigenmesh(const Contour3D& cntr) {
return emesh;
}
void create_head(TriangleMesh& out, double r1_mm, double r2_mm, double width_mm)
{
Head head(r1_mm, r2_mm, width_mm, {0, std::sqrt(0.5), -std::sqrt(0.5)},
{0, 0, 30});
out.merge(mesh(head.mesh));
Pillar cst(head, {0, 0, 0});
cst.add_base();
out.merge(mesh(cst.mesh));
out.merge(mesh(cst.base));
}
// The minimum distance for two support points to remain valid.
static const double /*constexpr*/ D_SP = 0.1;
@ -593,21 +585,6 @@ EigenMesh3D to_eigenmesh(const ModelObject& modelobj) {
return to_eigenmesh(modelobj.raw_mesh());
}
EigenMesh3D to_eigenmesh(const Model& model) {
TriangleMesh combined_mesh;
for(ModelObject *o : model.objects) {
TriangleMesh tmp = o->raw_mesh();
for(ModelInstance * inst: o->instances) {
TriangleMesh ttmp(tmp);
inst->transform_mesh(&ttmp);
combined_mesh.merge(ttmp);
}
}
return to_eigenmesh(combined_mesh);
}
PointSet to_point_set(const std::vector<Vec3d> &v)
{
PointSet ret(v.size(), 3);
@ -619,43 +596,6 @@ Vec3d model_coord(const ModelInstance& object, const Vec3f& mesh_coord) {
return object.transform_vector(mesh_coord.cast<double>());
}
PointSet support_points(const Model& model) {
size_t sum = 0;
for(auto *o : model.objects)
sum += o->instances.size() * o->sla_support_points.size();
PointSet ret(sum, 3);
for(ModelObject *o : model.objects)
for(ModelInstance *inst : o->instances) {
int i = 0;
for(Vec3f& msource : o->sla_support_points) {
ret.row(i++) = model_coord(*inst, msource);
}
}
return ret;
}
PointSet support_points(const ModelObject& modelobject)
{
PointSet ret(modelobject.sla_support_points.size(), 3);
auto rot = modelobject.instances.front()->get_rotation();
// auto scaling = modelobject.instances.front()->get_scaling_factor();
// Transform3d tr;
// tr.rotate(Eigen::AngleAxisd(rot(X), Vec3d::UnitX()) *
// Eigen::AngleAxisd(rot(Y), Vec3d::UnitY()));
long i = 0;
for(const Vec3f& msource : modelobject.sla_support_points) {
Vec3d&& p = msource.cast<double>();
// p = tr * p;
ret.row(i++) = p;
}
return ret;
}
double ray_mesh_intersect(const Vec3d& s,
const Vec3d& dir,
const EigenMesh3D& m);
@ -1154,6 +1094,7 @@ bool SLASupportTree::generate(const PointSet &points,
cfg.head_back_radius_mm,
cfg.head_front_radius_mm,
cfg.head_width_mm,
cfg.head_penetration_mm,
nmls.row(i), // dir
head_pos.row(i) // displacement
);
@ -1521,6 +1462,7 @@ bool SLASupportTree::generate(const PointSet &points,
Head base_head(cfg.head_back_radius_mm,
cfg.head_front_radius_mm,
cfg.head_width_mm,
cfg.head_penetration_mm,
{0.0, 0.0, 1.0},
{headend(X), headend(Y), headend(Z) - gh});
@ -1692,7 +1634,7 @@ SlicedSupports SLASupportTree::slice(float layerh, float init_layerh) const
const auto modelh = float(stree.full_height());
auto gndlvl = float(this->m_impl->ground_level);
const Pad& pad = m_impl->pad();
if(!pad.empty()) gndlvl -= float(pad.cfg.min_wall_height_mm/2);
if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg));
std::vector<float> heights = {gndlvl};
heights.reserve(size_t(modelh/layerh) + 1);
@ -1719,10 +1661,10 @@ const TriangleMesh &SLASupportTree::add_pad(const SliceLayer& baseplate,
TriangleMesh mm;
merged_mesh(mm);
PoolConfig pcfg;
// pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
// pcfg.min_wall_height_mm = min_wall_height_mm;
// pcfg.max_merge_distance_mm = max_merge_distance_mm;
// pcfg.edge_radius_mm = edge_radius_mm;
pcfg.min_wall_thickness_mm = min_wall_thickness_mm;
pcfg.min_wall_height_mm = min_wall_height_mm;
pcfg.max_merge_distance_mm = max_merge_distance_mm;
pcfg.edge_radius_mm = edge_radius_mm;
return m_impl->create_pad(mm, baseplate, pcfg).tmesh;
}
@ -1731,21 +1673,6 @@ const TriangleMesh &SLASupportTree::get_pad() const
return m_impl->pad().tmesh;
}
double SLASupportTree::get_elevation() const
{
double ph = m_impl->pad().empty()? 0 :
m_impl->pad().cfg.min_wall_height_mm/2.0;
return -m_impl->ground_level + ph;
}
SLASupportTree::SLASupportTree(const Model& model,
const SupportConfig& cfg,
const Controller& ctl):
m_impl(new Impl()), m_ctl(ctl)
{
generate(support_points(model), to_eigenmesh(model), cfg, ctl);
}
SLASupportTree::SLASupportTree(const PointSet &points,
const EigenMesh3D& emesh,
const SupportConfig &cfg,
@ -1767,66 +1694,5 @@ SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c)
SLASupportTree::~SLASupportTree() {}
void add_sla_supports(Model &model,
const SupportConfig &cfg,
const Controller &ctl)
{
Benchmark bench;
bench.start();
SLASupportTree _stree(model, cfg, ctl);
bench.stop();
std::cout << "Support tree creation time: " << bench.getElapsedSec()
<< " seconds" << std::endl;
bench.start();
ModelObject* o = model.add_object();
o->add_instance();
TriangleMesh streemsh;
_stree.merged_mesh(streemsh);
o->add_volume(streemsh);
bench.stop();
std::cout << "support tree added to model in: " << bench.getElapsedSec()
<< " seconds" << std::endl;
// TODO this would roughly be the code for the base pool
ExPolygons plate;
auto modelmesh = model.mesh();
TriangleMesh poolmesh;
sla::PoolConfig poolcfg;
poolcfg.min_wall_height_mm = 1;
poolcfg.edge_radius_mm = 0.1;
poolcfg.min_wall_thickness_mm = 0.8;
bench.start();
sla::base_plate(modelmesh, plate);
bench.stop();
std::cout << "Base plate calculation time: " << bench.getElapsedSec()
<< " seconds." << std::endl;
bench.start();
sla::create_base_pool(plate, poolmesh, poolcfg);
bench.stop();
std::cout << "Pool generation completed in " << bench.getElapsedSec()
<< " second." << std::endl;
bench.start();
poolmesh.translate(.0f, .0f, float(poolcfg.min_wall_height_mm / 2));
o->add_volume(poolmesh);
bench.stop();
// TODO: will cause incorrect placement of the model;
// o->translate({0, 0, poolcfg.min_wall_height_mm / 2});
std::cout << "Added pool to model in " << bench.getElapsedSec()
<< " seconds." << std::endl;
}
}
}

View file

@ -33,7 +33,7 @@ struct SupportConfig {
double head_front_radius_mm = 0.2;
// How much the pinhead has to penetrate the model surface
double head_penetraiton = 0.2;
double head_penetration_mm = 0.5;
// Radius of the back side of the 3d arrow.
double head_back_radius_mm = 0.5;
@ -90,34 +90,17 @@ struct EigenMesh3D {
Eigen::MatrixXd V;
Eigen::MatrixXi F;
double ground_level = 0;
// igl crashes with the following data types:
// Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign> V;
// Eigen::Matrix<int, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign> F;
};
//using PointSet = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign>; //Eigen::MatrixXd;
using PointSet = Eigen::MatrixXd;
/* ************************************************************************** */
/* TODO: May not be needed: */
/* ************************************************************************** */
void create_head(TriangleMesh&, double r1_mm, double r2_mm, double width_mm);
/// Add support volumes to the model directly
void add_sla_supports(Model& model, const SupportConfig& cfg = {},
const Controller& ctl = {});
EigenMesh3D to_eigenmesh(const TriangleMesh& m);
PointSet to_point_set(const std::vector<Vec3d>&);
// obsolete, not used anymore
EigenMesh3D to_eigenmesh(const Model& model);
// needed for find best rotation
EigenMesh3D to_eigenmesh(const ModelObject& model);
PointSet support_points(const ModelObject& modelobject);
PointSet support_points(const Model& model);
// Simple conversion of 'vector of points' to an Eigen matrix
PointSet to_point_set(const std::vector<Vec3d>&);
/* ************************************************************************** */
@ -149,11 +132,6 @@ class SLASupportTree {
const Controller& ctl = {});
public:
// Constructors will throw if the stop condition becomes true.
SLASupportTree(const Model& model,
const SupportConfig& cfg = {},
const Controller& ctl = {});
SLASupportTree(const PointSet& pts,
const EigenMesh3D& em,
const SupportConfig& cfg = {},

View file

@ -3,6 +3,8 @@
#include "SLA/SLABasePool.hpp"
#include <tbb/parallel_for.h>
#include <boost/log/trivial.hpp>
//#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
#include "I18N.hpp"
@ -63,7 +65,7 @@ const std::array<std::string, slapsCount> PRINT_STEP_LABELS =
void SLAPrint::clear()
{
tbb::mutex::scoped_lock lock(this->cancel_mutex());
tbb::mutex::scoped_lock lock(this->state_mutex());
// The following call should stop background processing if it is running.
this->invalidate_all_steps();
@ -78,14 +80,15 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model,
// return APPLY_STATUS_UNCHANGED;
// Grab the lock for the Print / PrintObject milestones.
tbb::mutex::scoped_lock lock(this->cancel_mutex());
if(m_objects.empty() && model.objects.empty())
tbb::mutex::scoped_lock lock(this->state_mutex());
if (m_objects.empty() && model.objects.empty() && m_model.objects.empty())
return APPLY_STATUS_UNCHANGED;
// Temporary: just to have to correct layer height for the rasterization
DynamicPrintConfig config(config_in);
config.normalize();
auto lh = config.opt<ConfigOptionFloat>("layer_height");
m_material_config.initial_layer_height.set(
config.opt<ConfigOptionFloat>("initial_layer_height"));
// Temporary quick fix, just invalidate everything.
{
@ -102,7 +105,10 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model,
// Generate new SLAPrintObjects.
for (ModelObject *model_object : m_model.objects) {
auto po = new SLAPrintObject(this, model_object);
po->m_config.layer_height.set(lh);
// po->m_config.layer_height.set(lh);
po->m_config.apply(config, true);
m_objects.emplace_back(po);
for (ModelInstance *oinst : model_object->instances) {
Point tr = Point::new_scale(oinst->get_offset()(X),
@ -124,26 +130,36 @@ void SLAPrint::process()
// the model objects we have to process and the instances are also filtered
// shortcut to initial layer height
auto ilh = float(m_material_config.initial_layer_height.getFloat());
double ilhd = m_material_config.initial_layer_height.getFloat();
auto ilh = float(ilhd);
// Slicing the model object. This method is oversimplified and needs to
// be compared with the fff slicing algorithm for verification
auto slice_model = [this, ilh](SLAPrintObject& po) {
auto lh = float(po.m_config.layer_height.getFloat());
auto slice_model = [this, ilh, ilhd](SLAPrintObject& po) {
double lh = po.m_config.layer_height.getFloat();
TriangleMesh mesh = po.transformed_mesh();
TriangleMeshSlicer slicer(&mesh);
auto bb3d = mesh.bounding_box();
auto H = bb3d.max(Z) - bb3d.min(Z);
double elevation = po.get_elevation();
float minZ = float(bb3d.min(Z)) - float(elevation);
float maxZ = float(bb3d.max(Z)) ;
auto flh = float(lh);
auto gnd = float(bb3d.min(Z));
std::vector<float> heights = {gnd};
for(float h = gnd + ilh; h < gnd + H; h += lh) heights.emplace_back(h);
std::vector<float> heights;
// The first layer (the one before the initial height) is added only
// if the there is no pad and no elevation value
if(minZ >= gnd) heights.emplace_back(minZ);
for(float h = minZ + ilh; h < maxZ; h += flh)
if(h >= gnd) heights.emplace_back(h);
auto& layers = po.m_model_slices;
slicer.slice(heights, &layers, [this](){
throw_if_canceled();
});
slicer.slice(heights, &layers, [this](){ throw_if_canceled(); });
};
auto support_points = [](SLAPrintObject& po) {
@ -169,7 +185,17 @@ void SLAPrint::process()
auto& emesh = po.m_supportdata->emesh;
auto& pts = po.m_supportdata->support_points; // nowhere filled yet
try {
SupportConfig scfg; // TODO fill or replace with po.m_config
sla::SupportConfig scfg;
SLAPrintObjectConfig& c = po.m_config;
scfg.head_front_radius_mm = c.support_head_front_radius.getFloat();
scfg.head_back_radius_mm = c.support_head_back_radius.getFloat();
scfg.head_penetration_mm = c.support_head_penetration.getFloat();
scfg.head_width_mm = c.support_head_width.getFloat();
scfg.object_elevation_mm = c.support_object_elevation.getFloat();
scfg.tilt = c.support_critical_angle.getFloat() * PI / 180.0 ;
scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
scfg.pillar_radius_mm = c.support_pillar_radius.getFloat();
sla::Controller ctl;
ctl.statuscb = [this](unsigned st, const std::string& msg) {
@ -196,6 +222,7 @@ void SLAPrint::process()
// repeated)
if(po.is_step_done(slaposSupportTree) &&
po.m_config.pad_enable.getBool() &&
po.m_supportdata &&
po.m_supportdata->support_tree_ptr)
{
@ -205,11 +232,12 @@ void SLAPrint::process()
double er = po.m_config.pad_edge_radius.getFloat();
double lh = po.m_config.layer_height.getFloat();
double elevation = po.m_config.support_object_elevation.getFloat();
sla::PoolConfig pcfg(wt, h, md, er);
sla::ExPolygons bp;
if(elevation < h/2)
sla::base_plate(po.transformed_mesh(), bp,
float(h/2), float(lh));
double pad_h = sla::get_pad_elevation(pcfg);
if(elevation < pad_h) sla::base_plate(po.transformed_mesh(), bp,
float(pad_h), float(lh));
po.m_supportdata->support_tree_ptr->add_pad(bp, wt, h, md, er);
}
@ -227,7 +255,7 @@ void SLAPrint::process()
};
// Rasterizing the model objects, and their supports
auto rasterize = [this, ilh]() {
auto rasterize = [this, ilh, ilhd]() {
using Layer = sla::ExPolygons;
using LayerCopies = std::vector<SLAPrintObject::Instance>;
struct LayerRef {
@ -237,43 +265,68 @@ void SLAPrint::process()
lref(std::cref(lyr)), copies(std::cref(cp)) {}
};
using LevelID = long long;
using LayerRefs = std::vector<LayerRef>;
// layers according to quantized height levels
std::map<long long, LayerRefs> levels;
std::map<LevelID, LayerRefs> levels;
auto sih = LevelID(scale_(ilh));
// For all print objects, go through its initial layers and place them
// into the layers hash
for(SLAPrintObject *o : m_objects) {
double gndlvl = o->transformed_mesh().bounding_box().min(Z);
auto bb = o->transformed_mesh().bounding_box();
double modelgnd = bb.min(Z);
double elevation = o->get_elevation();
double lh = o->m_config.layer_height.getFloat();
SlicedModel & oslices = o->m_model_slices;
for(int i = 0; i < oslices.size(); ++i) {
int a = i == 0 ? 0 : 1;
int b = i == 0 ? 0 : i - 1;
double minZ = modelgnd - elevation;
double h = gndlvl + ilh * a + b * lh;
long long lyridx = static_cast<long long>(scale_(h));
auto& lyrs = levels[lyridx]; // this initializes a new record
// scaled values:
auto sminZ = LevelID(scale_(minZ));
auto smaxZ = LevelID(scale_(bb.max(Z)));
auto smodelgnd = LevelID(scale_(modelgnd));
auto slh = LevelID(scale_(lh));
// It is important that the next levels math the levels in
// model_slice method. Only difference is that here it works with
// scaled coordinates
std::vector<LevelID> levelids;
if(sminZ >= smodelgnd) levelids.emplace_back(sminZ);
for(LevelID h = sminZ + sih; h < smaxZ; h += slh)
if(h >= smodelgnd) levelids.emplace_back(h);
SlicedModel & oslices = o->m_model_slices;
// If everything went well this code should not run at all, but
// let's be robust...
assert(levelids.size() == oslices.size());
if(levelids.size() < oslices.size()) { // extend the levels until...
BOOST_LOG_TRIVIAL(warning)
<< "Height level mismatch at rasterization!\n";
LevelID lastlvl = levelids.back();
while(levelids.size() < oslices.size()) {
lastlvl += slh;
levelids.emplace_back(lastlvl);
}
}
for(int i = 0; i < oslices.size(); ++i) {
LevelID h = levelids[i];
auto& lyrs = levels[h]; // this initializes a new record
lyrs.emplace_back(oslices[i], o->m_instances);
}
if(o->m_supportdata) { // deal with the support slices if present
auto& sslices = o->m_supportdata->support_slices;
double el = o->m_config.support_object_elevation.getFloat();
//TODO: remove next line:
el = SupportConfig().object_elevation_mm;
for(int i = 0; i < sslices.size(); ++i) {
int a = i == 0 ? 0 : 1;
int b = i == 0 ? 0 : i - 1;
LevelID h = sminZ + a * sih + b * slh;
double h = gndlvl - el + ilh * a + b * lh;
long long lyridx = static_cast<long long>(scale_(h));
auto& lyrs = levels[lyridx];
auto& lyrs = levels[h];
lyrs.emplace_back(sslices[i], o->m_instances);
}
}
@ -430,14 +483,73 @@ void SLAPrint::process()
}
SLAPrintObject::SLAPrintObject(SLAPrint *print, ModelObject *model_object):
Inherited(print),
m_model_object(model_object),
Inherited(print, model_object),
m_stepmask(slaposCount, true)
{
}
SLAPrintObject::~SLAPrintObject() {}
double SLAPrintObject::get_elevation() const {
double ret = m_config.support_object_elevation.getFloat();
// if the pad is enabled, then half of the pad height is its base plate
if(m_config.pad_enable.getBool()) {
// Normally the elevation for the pad itself would be the thickness of
// its walls but currently it is half of its thickness. Whatever it
// will be in the future, we provide the config to the get_pad_elevation
// method and we will have the correct value
sla::PoolConfig pcfg;
pcfg.min_wall_height_mm = m_config.pad_wall_height.getFloat();
pcfg.min_wall_thickness_mm = m_config.pad_wall_thickness.getFloat();
pcfg.edge_radius_mm = m_config.pad_edge_radius.getFloat();
pcfg.max_merge_distance_mm = m_config.pad_max_merge_distance.getFloat();
ret += sla::get_pad_elevation(pcfg);
}
return ret;
}
//const std::vector<ExPolygons> &SLAPrintObject::get_support_slices() const
//{
// // I don't want to return a copy but the points may not exist, so ...
// static const std::vector<ExPolygons> dummy_empty;
// if(!m_supportdata) return dummy_empty;
// return m_supportdata->support_slices;
//}
//const std::vector<ExPolygons> &SLAPrintObject::get_model_slices() const
//{
// return m_model_slices;
//}
bool SLAPrintObject::has_mesh(SLAPrintObjectStep step) const
{
switch (step) {
case slaposSupportTree:
// return m_supportdata && m_supportdata->support_tree_ptr && ! m_supportdata->support_tree_ptr->get().merged_mesh().empty();
return ! this->support_mesh().empty();
case slaposBasePool:
// return m_supportdata && m_supportdata->support_tree_ptr && ! m_supportdata->support_tree_ptr->get_pad().empty();
return ! this->pad_mesh().empty();
default:
return false;
}
}
TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
{
switch (step) {
case slaposSupportTree:
return this->support_mesh();
case slaposBasePool:
return this->pad_mesh();
default:
return TriangleMesh();
}
}
TriangleMesh SLAPrintObject::support_mesh() const
{
TriangleMesh trm;

View file

@ -35,8 +35,6 @@ private: // Prevents erroneous use by other classes.
using Inherited = _SLAPrintObjectBase;
public:
const ModelObject* model_object() const { return m_model_object; }
ModelObject* model_object() { return m_model_object; }
const Transform3d& trafo() const { return m_trafo; }
struct Instance {
@ -50,6 +48,9 @@ public:
};
const std::vector<Instance>& instances() const { return m_instances; }
bool has_mesh(SLAPrintObjectStep step) const;
TriangleMesh get_mesh(SLAPrintObjectStep step) const;
// Get a support mesh centered around origin in XY, and with zero rotation around Z applied.
// Support mesh is only valid if this->is_step_done(slaposSupportTree) is true.
TriangleMesh support_mesh() const;
@ -62,6 +63,15 @@ public:
std::vector<Vec3d> transformed_support_points() const;
// Get the needed Z elevation for the model geometry if supports should be
// displayed. This Z offset should also be applied to the support
// geometries. Note that this is not the same as the value stored in config
// as the pad height also needs to be considered.
double get_elevation() const;
// const std::vector<ExPolygons>& get_support_slices() const;
// const std::vector<ExPolygons>& get_model_slices() const;
// I refuse to grantee copying (Tamas)
SLAPrintObject(const SLAPrintObject&) = delete;
SLAPrintObject& operator=(const SLAPrintObject&) = delete;
@ -83,8 +93,7 @@ protected:
bool invalidate_step(SLAPrintObjectStep step);
private:
// Points to the instance owned by a Model stored at the parent SLAPrint instance.
ModelObject *m_model_object;
// Object specific configuration, pulled from the configuration layer.
SLAPrintObjectConfig m_config;
// Translation in Z + Rotation by Y and Z + Scaling / Mirroring.
@ -147,7 +156,6 @@ private:
using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
using SLAPrinterPtr = std::unique_ptr<SLAPrinter>;
Model m_model;
SLAPrinterConfig m_printer_config;
SLAMaterialConfig m_material_config;
PrintObjects m_objects;

View file

@ -521,6 +521,7 @@ BoundingBoxf3 TriangleMesh::transformed_bounding_box(const Transform3d& t) const
if (stl.stats.shared_vertices > 0)
{
assert(stl.v_shared != nullptr);
stl_vertex* vertex_ptr = stl.v_shared;
for (int i = 0; i < stl.stats.shared_vertices; ++i)
{

View file

@ -65,6 +65,7 @@ public:
void reset_repair_stats();
bool needed_repair() const;
size_t facets_count() const { return this->stl.stats.number_of_facets; }
bool empty() const { return this->facets_count() == 0; }
// Returns true, if there are two and more connected patches in the mesh.
// Returns false, if one or zero connected patch is in the mesh.

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,9 @@ 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)
, m_convex_hull_owned(false)
// geometry_id == 0 -> invalid
, geometry_id(std::pair<size_t, size_t>(0, 0))
, extruder_id(0)
, selected(false)
, disabled(false)
@ -234,6 +240,12 @@ GLVolume::GLVolume(float r, float g, float b, float a)
set_render_color(r, g, b, a);
}
GLVolume::~GLVolume()
{
if (m_convex_hull_owned)
delete m_convex_hull;
}
void GLVolume::set_render_color(float r, float g, float b, float a)
{
render_color[0] = r;
@ -360,9 +372,10 @@ void GLVolume::set_mirror(Axis axis, double mirror)
}
#endif // !ENABLE_MODELVOLUME_TRANSFORM
void GLVolume::set_convex_hull(const TriangleMesh& convex_hull)
void GLVolume::set_convex_hull(const TriangleMesh *convex_hull, bool owned)
{
m_convex_hull = &convex_hull;
m_convex_hull = convex_hull;
m_convex_hull_owned = owned;
}
#if !ENABLE_MODELVOLUME_TRANSFORM
@ -705,6 +718,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 +745,117 @@ 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())
{
// GLVolume will reference a convex hull from model_volume!
v.set_convex_hull(&model_volume->get_convex_hull(), false);
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,
// Timestamp of the last change of the milestone
size_t timestamp,
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.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
// Create a copy of the convex hull mesh for each instance. Use a move operator on the last instance.
v.set_convex_hull((&instance_idx == &instances.back()) ? new TriangleMesh(std::move(convex_hull)) : new TriangleMesh(convex_hull), true);
v.is_modifier = false;
v.shader_outside_printer_detection_enabled = true;
//FIXME adjust with print_instance?
v.set_instance_transformation(model_instance.get_transformation());
// Leave the volume transformation at identity.
// v.set_volume_transformation(model_volume->get_transformation());
}
return volumes_idx;
}
int GLVolumeCollection::load_wipe_tower_preview(
@ -911,9 +928,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

@ -256,6 +256,7 @@ public:
GLVolume(float r = 1.f, float g = 1.f, float b = 1.f, float a = 1.f);
GLVolume(const float *rgba) : GLVolume(rgba[0], rgba[1], rgba[2], rgba[3]) {}
~GLVolume();
private:
#if ENABLE_MODELVOLUME_TRANSFORM
@ -280,7 +281,9 @@ private:
// Whether or not is needed to recalculate the transformed bounding box.
mutable bool m_transformed_bounding_box_dirty;
// Pointer to convex hull of the original mesh, if any.
// This object may or may not own the convex hull instance based on m_convex_hull_owned
const TriangleMesh* m_convex_hull;
bool m_convex_hull_owned;
// Bounding box of this volume, in unscaled coordinates.
mutable BoundingBoxf3 m_transformed_convex_hull_bounding_box;
// Whether or not is needed to recalculate the transformed convex hull bounding box.
@ -293,15 +296,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?
@ -412,11 +425,11 @@ public:
void set_offset(const Vec3d& offset);
#endif // ENABLE_MODELVOLUME_TRANSFORM
void set_convex_hull(const TriangleMesh& convex_hull);
void set_convex_hull(const TriangleMesh *convex_hull, bool owned);
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 +512,26 @@ 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 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,
// Timestamp of the last change of the milestone
size_t timestamp,
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

@ -2,7 +2,6 @@
#include "GUI_App.hpp"
#include <wx/app.h>
#include <wx/event.h>
#include <wx/panel.h>
#include <wx/stdpaths.h>
@ -60,24 +59,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_slicing_completed_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 +116,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 +242,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 +279,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 +332,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 +343,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

@ -6,6 +6,8 @@
#include <mutex>
#include <thread>
#include <wx/event.h>
#include "Print.hpp"
namespace Slic3r {
@ -15,6 +17,18 @@ class GCodePreviewData;
class Model;
class SLAPrint;
class SlicingStatusEvent : public wxEvent
{
public:
SlicingStatusEvent(wxEventType eventType, int winid, const PrintBase::SlicingStatus &status) :
wxEvent(winid, eventType), status(std::move(status)) {}
virtual wxEvent *Clone() const { return new SlicingStatusEvent(*this); }
PrintBase::SlicingStatus status;
};
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
// Print step IDs for keeping track of the print state.
enum BackgroundSlicingProcessStep {
bspsGCodeFinalize, bspsCount,
@ -35,7 +49,7 @@ public:
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the slicing is finished
// and the background processing will transition into G-code export.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_sliced_event(int event_id) { m_event_sliced_id = event_id; }
void set_slicing_completed_event(int event_id) { m_event_slicing_completed_id = event_id; }
// The following wxCommandEvent will be sent to the UI thread / Platter window, when the G-code export is finished.
// The wxCommandEvent is sent to the UI thread asynchronously without waiting for the event to be processed.
void set_finished_event(int event_id) { m_event_finished_id = event_id; }
@ -125,16 +139,18 @@ 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;
int m_event_slicing_completed_id = 0;
// wxWidgets command ID to be sent to the platter to inform that the task finished.
int m_event_finished_id = 0;
int m_event_finished_id = 0;
};
}; // namespace Slic3r

View file

@ -1329,6 +1329,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)
@ -1774,7 +1815,10 @@ void GLCanvas3D::Selection::erase()
for (unsigned int i : m_list)
{
const GLVolume* v = (*m_volumes)[i];
volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx()));
// Only remove volumes associated with ModelVolumes from the object list.
// Temporary meshes (SLA supports or pads) are not managed by the object list.
if (v->volume_idx() >= 0)
volumes_idxs.insert(std::make_pair(v->object_idx(), v->volume_idx()));
}
std::vector<ItemForDelete> items;
@ -1997,10 +2041,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;
}
@ -3665,13 +3705,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);
@ -3679,6 +3712,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))
@ -3689,39 +3728,231 @@ 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) {
if (! print_object->has_mesh(sla_steps[istep]))
// Consider the DONE step without a valid mesh as invalid for the purpose
// of mesh visualization.
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());
}
}
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)
{
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);
}
}
// stores the current volumes count
size_t volumes_count = m_volumes.volumes.size();
for (size_t istep = 0; istep < sla_steps.size(); ++istep)
if (!instances[istep].empty())
m_volumes.load_object_auxiliary(print_object, object_idx, instances[istep], sla_steps[istep], state.step[istep].timestamp, m_use_VBOs && m_initialized);
if (volumes_count != m_volumes.volumes.size())
{
// If any volume has been added
// Shift-up all volumes of the object so that it has the right elevation with respect to the print bed
Vec3d shift_z(0.0, 0.0, print_object->get_elevation());
for (GLVolume* volume : m_volumes.volumes)
{
if (volume->object_idx() == object_idx)
volume->set_instance_offset(volume->get_instance_offset() + shift_z);
}
}
}
}
if (printer_technology == ptFFF && m_config->has("nozzle_diameter"))
{
// Should the wipe tower be visualized ?
@ -3750,7 +3981,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())
@ -3781,6 +4019,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

@ -482,6 +482,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; }
@ -700,6 +703,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;
@ -814,9 +818,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);

View file

@ -1306,7 +1306,10 @@ void ObjectList::update_selections()
const auto gl_vol = selection.get_volume(idx);
if (selection.is_multiple_full_object())
sels.Add(m_objects_model->GetItemById(gl_vol->object_idx()));
else
else if (gl_vol->volume_idx() >= 0)
// Only add GLVolumes with non-negative volume_ids. GLVolumes with negative volume ids
// are not associated with ModelVolumes, but they are temporarily generated by the backend
// (for example, SLA supports or SLA pad).
sels.Add(m_objects_model->GetItemByVolumeId(gl_vol->object_idx(), gl_vol->volume_idx()));
}
}

View file

@ -65,8 +65,7 @@ using Slic3r::Preset;
namespace Slic3r {
namespace GUI {
wxDEFINE_EVENT(EVT_PROGRESS_BAR, wxCommandEvent);
wxDEFINE_EVENT(EVT_SLICING_UPDATE, SlicingStatusEvent);
wxDEFINE_EVENT(EVT_SLICING_COMPLETED, wxCommandEvent);
wxDEFINE_EVENT(EVT_PROCESS_COMPLETED, wxCommandEvent);
@ -981,8 +980,8 @@ struct Plater::priv
void on_notebook_changed(wxBookCtrlEvent&);
void on_select_preset(wxCommandEvent&);
void on_progress_event(wxCommandEvent&);
void on_update_print_preview(wxCommandEvent&);
void on_slicing_update(SlicingStatusEvent&);
void on_slicing_completed(wxCommandEvent&);
void on_process_completed(wxCommandEvent&);
void on_layer_editing_toggled(bool enable);
@ -1040,21 +1039,18 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
background_process.set_fff_print(&print);
background_process.set_sla_print(&sla_print);
background_process.set_gcode_preview_data(&gcode_preview_data);
background_process.set_sliced_event(EVT_SLICING_COMPLETED);
background_process.set_slicing_completed_event(EVT_SLICING_COMPLETED);
background_process.set_finished_event(EVT_PROCESS_COMPLETED);
// Default printer technology for default config.
background_process.select_technology(this->printer_technology);
// Register progress callback from the Print class to the Platter.
auto statuscb = [this](int percent, const std::string &message) {
wxCommandEvent event(EVT_PROGRESS_BAR);
event.SetInt(percent);
event.SetString(message);
wxQueueEvent(this->q, event.Clone());
auto statuscb = [this](const Slic3r::PrintBase::SlicingStatus &status) {
wxQueueEvent(this->q, new Slic3r::SlicingStatusEvent(EVT_SLICING_UPDATE, 0, status));
};
print.set_status_callback(statuscb);
sla_print.set_status_callback(statuscb);
this->q->Bind(EVT_PROGRESS_BAR, &priv::on_progress_event, this);
this->q->Bind(EVT_SLICING_UPDATE, &priv::on_slicing_update, this);
_3DScene::add_canvas(canvas3D);
_3DScene::allow_multisample(canvas3D, GLCanvas3DManager::can_multisample());
@ -1138,7 +1134,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
// Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_VIEWPORT_CHANGED, &priv::on_viewport_changed, this);
q->Bind(EVT_SLICING_COMPLETED, &priv::on_update_print_preview, this);
q->Bind(EVT_SLICING_COMPLETED, &priv::on_slicing_completed, this);
q->Bind(EVT_PROCESS_COMPLETED, &priv::on_process_completed, this);
// Drop target:
@ -1906,21 +1902,43 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
wxGetApp().plater()->on_config_change(wxGetApp().preset_bundle->full_config());
}
void Plater::priv::on_progress_event(wxCommandEvent &evt)
void Plater::priv::on_slicing_update(SlicingStatusEvent &evt)
{
this->statusbar()->set_progress(evt.GetInt());
this->statusbar()->set_status_text(evt.GetString() + wxString::FromUTF8(""));
this->statusbar()->set_progress(evt.status.percent);
this->statusbar()->set_status_text(_(L(evt.status.text)) + wxString::FromUTF8(""));
if (evt.status.flags & PrintBase::SlicingStatus::RELOAD_SCENE) {
switch (this->printer_technology) {
case ptFFF:
if (this->preview != nullptr)
this->preview->reload_print();
break;
case ptSLA:
// Refresh the scene lazily by updating only SLA meshes.
//FIXME update SLAPrint?
_3DScene::reload_scene(canvas3D, true);
break;
}
}
}
void Plater::priv::on_update_print_preview(wxCommandEvent &)
void Plater::priv::on_slicing_completed(wxCommandEvent &)
{
if (this->preview != nullptr)
this->preview->reload_print();
// in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth:
// auto selections = collect_selections();
// _3DScene::set_objects_selections(canvas3D, selections);
// if (canvas3D)
// _3DScene::reload_scene(canvas3D, true);
switch (this->printer_technology) {
case ptFFF:
if (this->preview != nullptr)
this->preview->reload_print();
// in case this was MM print, wipe tower bounding box on 3D tab might need redrawing with exact depth:
// auto selections = collect_selections();
// _3DScene::set_objects_selections(canvas3D, selections);
// if (canvas3D)
// _3DScene::reload_scene(canvas3D, true);
break;
case ptSLA:
// Refresh the scene lazily by updating only SLA meshes.
//FIXME update SLAPrint?
_3DScene::reload_scene(canvas3D, true);
break;
}
}
void Plater::priv::on_process_completed(wxCommandEvent &evt)
@ -2567,6 +2585,8 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
if (opt_key == "printer_technology") {
p->printer_technology = config.opt_enum<PrinterTechnology>(opt_key);
p->background_process.select_technology(this->printer_technology());
//FIXME for SLA synchronize
//p->background_process.apply(Model)!
}
else if (opt_key == "bed_shape") {
if (p->canvas3D) _3DScene::set_bed_shape(p->canvas3D, p->config->option<ConfigOptionPoints>(opt_key)->values);

View file

@ -411,6 +411,8 @@ const std::vector<std::string>& Preset::sla_print_options()
"support_base_height",
"support_critical_angle",
"support_max_bridge_length",
"support_object_elevation",
"pad_enable",
"pad_wall_thickness",
"pad_wall_height",
"pad_max_merge_distance",

View file

@ -3005,12 +3005,14 @@ void TabSLAPrint::build()
optgroup->append_single_option_line("support_pillar_radius");
optgroup->append_single_option_line("support_base_radius");
optgroup->append_single_option_line("support_base_height");
optgroup->append_single_option_line("support_object_elevation");
optgroup = page->new_optgroup(_(L("Connection of the support sticks and junctions")));
optgroup->append_single_option_line("support_critical_angle");
optgroup->append_single_option_line("support_max_bridge_length");
optgroup = page->new_optgroup(_(L("Pad")));
optgroup->append_single_option_line("pad_enable");
optgroup->append_single_option_line("pad_wall_thickness");
optgroup->append_single_option_line("pad_wall_height");
optgroup->append_single_option_line("pad_max_merge_distance");

View file

@ -308,7 +308,7 @@ bool PrusaCollapsiblePaneMSW::Create(wxWindow *parent, wxWindowID id, const wxSt
m_pPane = new wxPanel(this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
wxTAB_TRAVERSAL | wxNO_BORDER, wxT("wxCollapsiblePanePane"));
wxColour& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
wxColour&& clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
m_pDisclosureTriangleButton->SetBackgroundColour(clr);
this->SetBackgroundColour(clr);
m_pPane->SetBackgroundColour(clr);

View file

@ -200,7 +200,7 @@ DECLARE_VARIANT_OBJECT(PrusaDataViewBitmapText)
// PrusaObjectDataViewModelNode: a node inside PrusaObjectDataViewModel
// ----------------------------------------------------------------------------
enum ItemType{
enum ItemType {
itUndef = 0,
itObject = 1,
itVolume = 2,