This commit is contained in:
YuSanka 2019-07-31 09:49:45 +02:00
commit 8d3d4b3ae7
35 changed files with 1174 additions and 1111 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -5,16 +5,30 @@ const vec3 back_color_light = vec3(0.365, 0.365, 0.365);
uniform sampler2D texture; uniform sampler2D texture;
uniform bool transparent_background; uniform bool transparent_background;
uniform bool svg_source;
varying vec2 tex_coords; varying vec2 tex_coords;
void main() vec4 svg_color()
{ {
// takes foreground from texture
vec4 fore_color = texture2D(texture, tex_coords);
// calculates radial gradient // calculates radial gradient
vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5))))); vec3 back_color = vec3(mix(back_color_light, back_color_dark, smoothstep(0.0, 0.5, length(abs(tex_coords.xy) - vec2(0.5)))));
vec4 fore_color = texture2D(texture, tex_coords);
// blends foreground with background // blends foreground with background
gl_FragColor = vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0); return vec4(mix(back_color, fore_color.rgb, fore_color.a), transparent_background ? fore_color.a : 1.0);
}
vec4 non_svg_color()
{
// takes foreground from texture
vec4 color = texture2D(texture, tex_coords);
return vec4(color.rgb, transparent_background ? color.a * 0.25 : color.a);
}
void main()
{
gl_FragColor = svg_source ? svg_color() : non_svg_color();
} }

View file

@ -165,6 +165,7 @@ add_library(libslic3r STATIC
MinAreaBoundingBox.cpp MinAreaBoundingBox.cpp
miniz_extension.hpp miniz_extension.hpp
miniz_extension.cpp miniz_extension.cpp
SLA/SLACommon.hpp
SLA/SLABoilerPlate.hpp SLA/SLABoilerPlate.hpp
SLA/SLABasePool.hpp SLA/SLABasePool.hpp
SLA/SLABasePool.cpp SLA/SLABasePool.cpp

View file

@ -51,6 +51,16 @@ void PrintConfigDef::init_common_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) }); def->set_default_value(new ConfigOptionPoints{ Vec2d(0, 0), Vec2d(200, 0), Vec2d(200, 200), Vec2d(0, 200) });
def = this->add("bed_custom_texture", coString);
def->label = L("Bed custom texture");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("bed_custom_model", coString);
def->label = L("Bed custom model");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
def = this->add("layer_height", coFloat); def = this->add("layer_height", coFloat);
def->label = L("Layer height"); def->label = L("Layer height");
def->category = L("Layers and Perimeters"); def->category = L("Layers and Perimeters");

View file

@ -24,37 +24,51 @@ enum class PointsStatus {
UserModified // User has done some edits. UserModified // User has done some edits.
}; };
struct SupportPoint { struct SupportPoint
{
Vec3f pos; Vec3f pos;
float head_front_radius; float head_front_radius;
bool is_new_island; bool is_new_island;
SupportPoint() : SupportPoint()
pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {} : pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false)
{}
SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) : SupportPoint(float pos_x,
pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {} float pos_y,
float pos_z,
float head_radius,
bool new_island)
: pos(pos_x, pos_y, pos_z)
, head_front_radius(head_radius)
, is_new_island(new_island)
{}
SupportPoint(Vec3f position, float head_radius, bool new_island) : SupportPoint(Vec3f position, float head_radius, bool new_island)
pos(position), head_front_radius(head_radius), is_new_island(new_island) {} : pos(position)
, head_front_radius(head_radius)
, is_new_island(new_island)
{}
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data) : SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data)
pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {} : pos(data(0), data(1), data(2))
, head_front_radius(data(3))
, is_new_island(data(4) != 0.f)
{}
bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; } bool operator==(const SupportPoint &sp) const
bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); } {
return (pos == sp.pos) && head_front_radius == sp.head_front_radius &&
is_new_island == sp.is_new_island;
}
bool operator!=(const SupportPoint &sp) const { return !(sp == (*this)); }
template<class Archive> void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); } template<class Archive> void serialize(Archive &ar)
{
ar(pos, head_front_radius, is_new_island);
}
}; };
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree
/*struct EigenMesh3D {
Eigen::MatrixXd V;
Eigen::MatrixXi F;
double ground_level = 0;
};*/
/// An index-triangle structure for libIGL functions. Also serves as an /// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree /// alternative (raw) input format for the SLASupportTree
class EigenMesh3D { class EigenMesh3D {
@ -161,9 +175,6 @@ public:
} }
}; };
} // namespace sla } // namespace sla
} // namespace Slic3r } // namespace Slic3r

View file

@ -83,6 +83,42 @@ const unsigned SupportConfig::max_bridges_on_pillar = 3;
using Coordf = double; using Coordf = double;
using Portion = std::tuple<double, double>; using Portion = std::tuple<double, double>;
// Set this to true to enable full parallelism in this module.
// Only the well tested parts will be concurrent if this is set to false.
const constexpr bool USE_FULL_CONCURRENCY = false;
template<bool> struct _ccr {};
template<> struct _ccr<true>
{
using Mutex = SpinMutex;
template<class It, class Fn>
static inline void enumerate(It from, It to, Fn fn)
{
using TN = size_t;
auto iN = to - from;
TN N = iN < 0 ? 0 : TN(iN);
tbb::parallel_for(TN(0), N, [from, fn](TN n) { fn(*(from + n), n); });
}
};
template<> struct _ccr<false>
{
struct Mutex { inline void lock() {} inline void unlock() {} };
template<class It, class Fn>
static inline void enumerate(It from, It to, Fn fn)
{
for (auto it = from; it != to; ++it) fn(*it, it - from);
}
};
using ccr = _ccr<USE_FULL_CONCURRENCY>;
using ccr_seq = _ccr<false>;
using ccr_par = _ccr<true>;
inline Portion make_portion(double a, double b) { inline Portion make_portion(double a, double b) {
return std::make_tuple(a, b); return std::make_tuple(a, b);
} }
@ -677,6 +713,7 @@ struct Pad {
} }
tmesh.translate(0, 0, float(zlevel)); tmesh.translate(0, 0, float(zlevel));
tmesh.require_shared_vertices();
} }
bool empty() const { return tmesh.facets_count() == 0; } bool empty() const { return tmesh.facets_count() == 0; }
@ -735,63 +772,84 @@ ClusteredPoints cluster(
// The support pad is considered an auxiliary geometry and is not part of the // The support pad is considered an auxiliary geometry and is not part of the
// merged mesh. It can be retrieved using a dedicated method (pad()) // merged mesh. It can be retrieved using a dedicated method (pad())
class SLASupportTree::Impl { class SLASupportTree::Impl {
std::map<unsigned, Head> m_heads; // For heads it is beneficial to use the same IDs as for the support points.
std::vector<Head> m_heads;
std::vector<size_t> m_head_indices;
std::vector<Pillar> m_pillars; std::vector<Pillar> m_pillars;
std::vector<Junction> m_junctions; std::vector<Junction> m_junctions;
std::vector<Bridge> m_bridges; std::vector<Bridge> m_bridges;
std::vector<CompactBridge> m_compact_bridges; std::vector<CompactBridge> m_compact_bridges;
Controller m_ctl; Controller m_ctl;
Pad m_pad; Pad m_pad;
using Mutex = ccr::Mutex;
mutable Mutex m_mutex;
mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable TriangleMesh meshcache; mutable bool meshcache_valid = false;
mutable double model_height = 0; // the full height of the model mutable double model_height = 0; // the full height of the model
public: public:
double ground_level = 0; double ground_level = 0;
Impl() = default; Impl() = default;
inline Impl(const Controller& ctl): m_ctl(ctl) {} inline Impl(const Controller& ctl): m_ctl(ctl) {}
const Controller& ctl() const { return m_ctl; } const Controller& ctl() const { return m_ctl; }
template<class...Args> Head& add_head(unsigned id, Args&&... args) { template<class...Args> Head& add_head(unsigned id, Args&&... args)
auto el = m_heads.emplace(std::piecewise_construct, {
std::forward_as_tuple(id), std::lock_guard<Mutex> lk(m_mutex);
std::forward_as_tuple(std::forward<Args>(args)...)); m_heads.emplace_back(std::forward<Args>(args)...);
el.first->second.id = id; m_heads.back().id = id;
if (id >= m_head_indices.size()) m_head_indices.resize(id + 1);
m_head_indices[id] = m_heads.size() - 1;
meshcache_valid = false; meshcache_valid = false;
return el.first->second; return m_heads.back();
} }
template<class...Args> Pillar& add_pillar(unsigned headid, Args&&... args) { template<class...Args> Pillar& add_pillar(unsigned headid, Args&&... args)
auto it = m_heads.find(headid); {
assert(it != m_heads.end()); std::lock_guard<Mutex> lk(m_mutex);
Head& head = it->second;
assert(headid < m_head_indices.size());
Head &head = m_heads[m_head_indices[headid]];
m_pillars.emplace_back(head, std::forward<Args>(args)...); m_pillars.emplace_back(head, std::forward<Args>(args)...);
Pillar& pillar = m_pillars.back(); Pillar& pillar = m_pillars.back();
pillar.id = long(m_pillars.size() - 1); pillar.id = long(m_pillars.size() - 1);
head.pillar_id = pillar.id; head.pillar_id = pillar.id;
pillar.start_junction_id = head.id; pillar.start_junction_id = head.id;
pillar.starts_from_head = true; pillar.starts_from_head = true;
meshcache_valid = false; meshcache_valid = false;
return m_pillars.back(); return m_pillars.back();
} }
void increment_bridges(const Pillar& pillar) { void increment_bridges(const Pillar& pillar)
{
std::lock_guard<Mutex> lk(m_mutex);
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
m_pillars[size_t(pillar.id)].bridges++; m_pillars[size_t(pillar.id)].bridges++;
} }
void increment_links(const Pillar& pillar) { void increment_links(const Pillar& pillar)
{
std::lock_guard<Mutex> lk(m_mutex);
assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()); assert(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size());
if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size()) if(pillar.id >= 0 && size_t(pillar.id) < m_pillars.size())
m_pillars[size_t(pillar.id)].links++; m_pillars[size_t(pillar.id)].links++;
} }
template<class...Args> Pillar& add_pillar(Args&&...args) template<class...Args> Pillar& add_pillar(Args&&...args)
{ {
std::lock_guard<Mutex> lk(m_mutex);
m_pillars.emplace_back(std::forward<Args>(args)...); m_pillars.emplace_back(std::forward<Args>(args)...);
Pillar& pillar = m_pillars.back(); Pillar& pillar = m_pillars.back();
pillar.id = long(m_pillars.size() - 1); pillar.id = long(m_pillars.size() - 1);
@ -799,161 +857,175 @@ public:
meshcache_valid = false; meshcache_valid = false;
return m_pillars.back(); return m_pillars.back();
} }
const Head& pillar_head(long pillar_id) const { const Head& pillar_head(long pillar_id) const
{
std::lock_guard<Mutex> lk(m_mutex);
assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size()));
const Pillar& p = m_pillars[size_t(pillar_id)]; const Pillar& p = m_pillars[size_t(pillar_id)];
assert(p.starts_from_head && p.start_junction_id >= 0); assert(p.starts_from_head && p.start_junction_id >= 0);
auto it = m_heads.find(unsigned(p.start_junction_id)); assert(size_t(p.start_junction_id) < m_head_indices.size());
assert(it != m_heads.end());
return it->second; return m_heads[m_head_indices[p.start_junction_id]];
} }
const Pillar& head_pillar(unsigned headid) const { const Pillar& head_pillar(unsigned headid) const
auto it = m_heads.find(headid); {
assert(it != m_heads.end()); std::lock_guard<Mutex> lk(m_mutex);
const Head& h = it->second; assert(headid < m_head_indices.size());
const Head& h = m_heads[m_head_indices[headid]];
assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size())); assert(h.pillar_id >= 0 && h.pillar_id < long(m_pillars.size()));
return pillar(h.pillar_id);
return m_pillars[size_t(h.pillar_id)];
} }
template<class...Args> const Junction& add_junction(Args&&... args) { template<class...Args> const Junction& add_junction(Args&&... args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_junctions.emplace_back(std::forward<Args>(args)...); m_junctions.emplace_back(std::forward<Args>(args)...);
m_junctions.back().id = long(m_junctions.size() - 1); m_junctions.back().id = long(m_junctions.size() - 1);
meshcache_valid = false; meshcache_valid = false;
return m_junctions.back(); return m_junctions.back();
} }
template<class...Args> const Bridge& add_bridge(Args&&... args) { template<class...Args> const Bridge& add_bridge(Args&&... args)
{
std::lock_guard<Mutex> lk(m_mutex);
m_bridges.emplace_back(std::forward<Args>(args)...); m_bridges.emplace_back(std::forward<Args>(args)...);
m_bridges.back().id = long(m_bridges.size() - 1); m_bridges.back().id = long(m_bridges.size() - 1);
meshcache_valid = false; meshcache_valid = false;
return m_bridges.back(); return m_bridges.back();
} }
template<class...Args> template<class...Args> const CompactBridge& add_compact_bridge(Args&&...args)
const CompactBridge& add_compact_bridge(Args&&...args) { {
std::lock_guard<Mutex> lk(m_mutex);
m_compact_bridges.emplace_back(std::forward<Args>(args)...); m_compact_bridges.emplace_back(std::forward<Args>(args)...);
m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); m_compact_bridges.back().id = long(m_compact_bridges.size() - 1);
meshcache_valid = false; meshcache_valid = false;
return m_compact_bridges.back(); return m_compact_bridges.back();
} }
const std::map<unsigned, Head>& heads() const { return m_heads; } Head &head(unsigned id)
Head& head(unsigned idx) { {
std::lock_guard<Mutex> lk(m_mutex);
assert(id < m_head_indices.size());
meshcache_valid = false; meshcache_valid = false;
auto it = m_heads.find(idx); return m_heads[m_head_indices[id]];
assert(it != m_heads.end());
return it->second;
} }
const std::vector<Pillar>& pillars() const { return m_pillars; }
const std::vector<Bridge>& bridges() const { return m_bridges; } inline size_t pillarcount() const {
const std::vector<Junction>& junctions() const { return m_junctions; } std::lock_guard<Mutex> lk(m_mutex);
const std::vector<CompactBridge>& compact_bridges() const { return m_pillars.size();
return m_compact_bridges;
} }
template<class T> inline const Pillar& pillar(T id) const { template<class T> inline IntegerOnly<T, const Pillar&> pillar(T id) const
static_assert(std::is_integral<T>::value, "Invalid index type"); {
std::lock_guard<Mutex> lk(m_mutex);
assert(id >= 0 && size_t(id) < m_pillars.size() && assert(id >= 0 && size_t(id) < m_pillars.size() &&
size_t(id) < std::numeric_limits<size_t>::max()); size_t(id) < std::numeric_limits<size_t>::max());
return m_pillars[size_t(id)]; return m_pillars[size_t(id)];
} }
const Pad& create_pad(const TriangleMesh& object_supports, const Pad &create_pad(const TriangleMesh &object_supports,
const ExPolygons& modelbase, const ExPolygons & modelbase,
const PoolConfig& cfg) { const PoolConfig & cfg)
{
m_pad = Pad(object_supports, modelbase, ground_level, cfg); m_pad = Pad(object_supports, modelbase, ground_level, cfg);
return m_pad; return m_pad;
} }
void remove_pad() { void remove_pad() { m_pad = Pad(); }
m_pad = Pad();
}
const Pad& pad() const { return m_pad; } const Pad& pad() const { return m_pad; }
// WITHOUT THE PAD!!! // WITHOUT THE PAD!!!
const TriangleMesh& merged_mesh() const { const TriangleMesh &merged_mesh() const
if(meshcache_valid) return meshcache; {
if (meshcache_valid) return meshcache;
Contour3D merged; Contour3D merged;
for(auto& headel : heads()) { for (auto &head : m_heads) {
if(m_ctl.stopcondition()) break; if (m_ctl.stopcondition()) break;
if(headel.second.is_valid()) if (head.is_valid()) merged.merge(head.mesh);
merged.merge(headel.second.mesh);
} }
for(auto& stick : pillars()) { for (auto &stick : m_pillars) {
if(m_ctl.stopcondition()) break; if (m_ctl.stopcondition()) break;
merged.merge(stick.mesh); merged.merge(stick.mesh);
merged.merge(stick.base); merged.merge(stick.base);
} }
for(auto& j : junctions()) { for (auto &j : m_junctions) {
if(m_ctl.stopcondition()) break; if (m_ctl.stopcondition()) break;
merged.merge(j.mesh); merged.merge(j.mesh);
} }
for(auto& cb : compact_bridges()) { for (auto &cb : m_compact_bridges) {
if(m_ctl.stopcondition()) break; if (m_ctl.stopcondition()) break;
merged.merge(cb.mesh); merged.merge(cb.mesh);
} }
for(auto& bs : bridges()) { for (auto &bs : m_bridges) {
if(m_ctl.stopcondition()) break; if (m_ctl.stopcondition()) break;
merged.merge(bs.mesh); merged.merge(bs.mesh);
} }
if(m_ctl.stopcondition()) { if (m_ctl.stopcondition()) {
// In case of failure we have to return an empty mesh // In case of failure we have to return an empty mesh
meshcache = TriangleMesh(); meshcache = TriangleMesh();
return meshcache; return meshcache;
} }
meshcache = mesh(merged); meshcache = mesh(merged);
// The mesh will be passed by const-pointer to TriangleMeshSlicer, // The mesh will be passed by const-pointer to TriangleMeshSlicer,
// which will need this. // which will need this.
if (!meshcache.empty()) meshcache.require_shared_vertices(); if (!meshcache.empty()) meshcache.require_shared_vertices();
// TODO: Is this necessary? BoundingBoxf3 &&bb = meshcache.bounding_box();
//meshcache.repair(); model_height = bb.max(Z) - bb.min(Z);
BoundingBoxf3&& bb = meshcache.bounding_box();
model_height = bb.max(Z) - bb.min(Z);
meshcache_valid = true; meshcache_valid = true;
return meshcache; return meshcache;
} }
// WITH THE PAD // WITH THE PAD
double full_height() const { double full_height() const
if(merged_mesh().empty() && !pad().empty()) {
if (merged_mesh().empty() && !pad().empty())
return get_pad_fullheight(pad().cfg); return get_pad_fullheight(pad().cfg);
double h = mesh_height(); double h = mesh_height();
if(!pad().empty()) h += sla::get_pad_elevation(pad().cfg); if (!pad().empty()) h += sla::get_pad_elevation(pad().cfg);
return h; return h;
} }
// WITHOUT THE PAD!!! // WITHOUT THE PAD!!!
double mesh_height() const { double mesh_height() const
if(!meshcache_valid) merged_mesh(); {
if (!meshcache_valid) merged_mesh();
return model_height; return model_height;
} }
// Intended to be called after the generation is fully complete // Intended to be called after the generation is fully complete
void clear_support_data() { void merge_and_cleanup()
{
merged_mesh(); // in case the mesh is not generated, it should be... merged_mesh(); // in case the mesh is not generated, it should be...
m_heads.clear();
m_pillars.clear(); // Doing clear() does not garantee to release the memory.
m_junctions.clear(); m_heads = {};
m_bridges.clear(); m_head_indices = {};
m_compact_bridges.clear(); m_pillars = {};
m_junctions = {};
m_bridges = {};
m_compact_bridges = {};
} }
}; };
// This function returns the position of the centroid in the input 'clust' // This function returns the position of the centroid in the input 'clust'
@ -1122,11 +1194,10 @@ class SLASupportTree::Algorithm {
// Now a and b vectors are perpendicular to v and to each other. // Now a and b vectors are perpendicular to v and to each other.
// Together they define the plane where we have to iterate with the // Together they define the plane where we have to iterate with the
// given angles in the 'phis' vector // given angles in the 'phis' vector
tbb::parallel_for(size_t(0), phis.size(), ccr_par::enumerate(phis.begin(), phis.end(),
[&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] [&hits, &m, sd, r_pin, r_back, s, a, b, c]
(size_t i) (double phi, size_t i)
{ {
double& phi = phis[i];
double sinphi = std::sin(phi); double sinphi = std::sin(phi);
double cosphi = std::cos(phi); double cosphi = std::cos(phi);
@ -1225,12 +1296,11 @@ class SLASupportTree::Algorithm {
// Hit results // Hit results
std::array<HitResult, SAMPLES> hits; std::array<HitResult, SAMPLES> hits;
tbb::parallel_for(size_t(0), phis.size(), ccr_par::enumerate(phis.begin(), phis.end(),
[&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] [&m, a, b, sd, dir, r, s, ins_check, &hits]
(size_t i) (double phi, size_t i)
{ {
double& phi = phis[i];
double sinphi = std::sin(phi); double sinphi = std::sin(phi);
double cosphi = std::cos(phi); double cosphi = std::cos(phi);
@ -1458,7 +1528,7 @@ class SLASupportTree::Algorithm {
if(nearest_id >= 0) { if(nearest_id >= 0) {
auto nearpillarID = unsigned(nearest_id); auto nearpillarID = unsigned(nearest_id);
if(nearpillarID < m_result.pillars().size()) { if(nearpillarID < m_result.pillarcount()) {
if(!connect_to_nearpillar(head, nearpillarID)) { if(!connect_to_nearpillar(head, nearpillarID)) {
nearest_id = -1; // continue searching nearest_id = -1; // continue searching
spindex.remove(ne); // without the current pillar spindex.remove(ne); // without the current pillar
@ -1646,46 +1716,52 @@ public:
using libnest2d::opt::initvals; using libnest2d::opt::initvals;
using libnest2d::opt::GeneticOptimizer; using libnest2d::opt::GeneticOptimizer;
using libnest2d::opt::StopCriteria; using libnest2d::opt::StopCriteria;
for(unsigned i = 0, fidx = 0; i < filtered_indices.size(); ++i) ccr::Mutex mutex;
auto addfn = [&mutex](PtIndices &container, unsigned val) {
std::lock_guard<ccr::Mutex> lk(mutex);
container.emplace_back(val);
};
ccr::enumerate(filtered_indices.begin(), filtered_indices.end(),
[this, &nmls, addfn](unsigned fidx, size_t i)
{ {
m_thr(); m_thr();
fidx = filtered_indices[i];
auto n = nmls.row(i); auto n = nmls.row(i);
// for all normals we generate the spherical coordinates and // for all normals we generate the spherical coordinates and
// saturate the polar angle to 45 degrees from the bottom then // saturate the polar angle to 45 degrees from the bottom then
// convert back to standard coordinates to get the new normal. // convert back to standard coordinates to get the new normal.
// Then we just create a quaternion from the two normals // Then we just create a quaternion from the two normals
// (Quaternion::FromTwoVectors) and apply the rotation to the // (Quaternion::FromTwoVectors) and apply the rotation to the
// arrow head. // arrow head.
double z = n(2); double z = n(2);
double r = 1.0; // for normalized vector double r = 1.0; // for normalized vector
double polar = std::acos(z / r); double polar = std::acos(z / r);
double azimuth = std::atan2(n(1), n(0)); double azimuth = std::atan2(n(1), n(0));
// skip if the tilt is not sane // skip if the tilt is not sane
if(polar >= PI - m_cfg.normal_cutoff_angle) { if(polar >= PI - m_cfg.normal_cutoff_angle) {
// We saturate the polar angle to 3pi/4 // We saturate the polar angle to 3pi/4
polar = std::max(polar, 3*PI / 4); polar = std::max(polar, 3*PI / 4);
// save the head (pinpoint) position // save the head (pinpoint) position
Vec3d hp = m_points.row(fidx); Vec3d hp = m_points.row(fidx);
double w = m_cfg.head_width_mm + double w = m_cfg.head_width_mm +
m_cfg.head_back_radius_mm + m_cfg.head_back_radius_mm +
2*m_cfg.head_front_radius_mm; 2*m_cfg.head_front_radius_mm;
double pin_r = double(m_support_pts[fidx].head_front_radius); double pin_r = double(m_support_pts[fidx].head_front_radius);
// Reassemble the now corrected normal // Reassemble the now corrected normal
auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), auto nn = Vec3d(std::cos(azimuth) * std::sin(polar),
std::sin(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar),
std::cos(polar)).normalized(); std::cos(polar)).normalized();
// check available distance // check available distance
EigenMesh3D::hit_result t EigenMesh3D::hit_result t
= pinhead_mesh_intersect(hp, // touching point = pinhead_mesh_intersect(hp, // touching point
@ -1693,37 +1769,37 @@ public:
pin_r, pin_r,
m_cfg.head_back_radius_mm, m_cfg.head_back_radius_mm,
w); w);
if(t.distance() <= w) { if(t.distance() <= w) {
// Let's try to optimize this angle, there might be a // Let's try to optimize this angle, there might be a
// viable normal that doesn't collide with the model // viable normal that doesn't collide with the model
// geometry and its very close to the default. // geometry and its very close to the default.
StopCriteria stc; StopCriteria stc;
stc.max_iterations = m_cfg.optimizer_max_iterations; stc.max_iterations = m_cfg.optimizer_max_iterations;
stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff;
stc.stop_score = w; // space greater than w is enough stc.stop_score = w; // space greater than w is enough
GeneticOptimizer solver(stc); GeneticOptimizer solver(stc);
solver.seed(0); // we want deterministic behavior solver.seed(0); // we want deterministic behavior
auto oresult = solver.optimize_max( auto oresult = solver.optimize_max(
[this, pin_r, w, hp](double plr, double azm) [this, pin_r, w, hp](double plr, double azm)
{ {
auto n = Vec3d(std::cos(azm) * std::sin(plr), auto n = Vec3d(std::cos(azm) * std::sin(plr),
std::sin(azm) * std::sin(plr), std::sin(azm) * std::sin(plr),
std::cos(plr)).normalized(); std::cos(plr)).normalized();
double score = pinhead_mesh_intersect( hp, n, pin_r, double score = pinhead_mesh_intersect(
m_cfg.head_back_radius_mm, w); hp, n, pin_r, m_cfg.head_back_radius_mm, w);
return score;
},
initvals(polar, azimuth), // start with what we have
bound(3*PI/4, PI), // Must not exceed the tilt limit
bound(-PI, PI) // azimuth can be a full search
);
return score;
},
initvals(polar, azimuth), // start with what we have
bound(3*PI/4, PI), // Must not exceed the tilt limit
bound(-PI, PI) // azimuth can be a full search
);
if(oresult.score > w) { if(oresult.score > w) {
polar = std::get<0>(oresult.optimum); polar = std::get<0>(oresult.optimum);
azimuth = std::get<1>(oresult.optimum); azimuth = std::get<1>(oresult.optimum);
@ -1733,25 +1809,25 @@ public:
t = oresult.score; t = oresult.score;
} }
} }
// save the verified and corrected normal // save the verified and corrected normal
m_support_nmls.row(fidx) = nn; m_support_nmls.row(fidx) = nn;
if (t.distance() > w) { if (t.distance() > w) {
// Check distance from ground, we might have zero elevation. // Check distance from ground, we might have zero elevation.
if (hp(Z) + w * nn(Z) < m_result.ground_level) { if (hp(Z) + w * nn(Z) < m_result.ground_level) {
m_iheadless.emplace_back(fidx); addfn(m_iheadless, fidx);
} else { } else {
// mark the point for needing a head. // mark the point for needing a head.
m_iheads.emplace_back(fidx); addfn(m_iheads, fidx);
} }
} else if (polar >= 3 * PI / 4) { } else if (polar >= 3 * PI / 4) {
// Headless supports do not tilt like the headed ones // Headless supports do not tilt like the headed ones
// so the normal should point almost to the ground. // so the normal should point almost to the ground.
m_iheadless.emplace_back(fidx); addfn(m_iheadless, fidx);
} }
} }
} });
m_thr(); m_thr();
} }
@ -1939,11 +2015,17 @@ public:
}; };
std::vector<unsigned> modelpillars; std::vector<unsigned> modelpillars;
ccr::Mutex mutex;
// TODO: connect these to the ground pillars if possible // TODO: connect these to the ground pillars if possible
for(auto item : m_iheads_onmodel) { m_thr(); ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(),
unsigned idx = item.first; [this, routedown, &modelpillars, &mutex]
EigenMesh3D::hit_result hit = item.second; (const std::pair<unsigned, EigenMesh3D::hit_result> &el,
size_t)
{
m_thr();
unsigned idx = el.first;
EigenMesh3D::hit_result hit = el.second;
auto& head = m_result.head(idx); auto& head = m_result.head(idx);
Vec3d hjp = head.junction_point(); Vec3d hjp = head.junction_point();
@ -1952,7 +2034,7 @@ public:
// Search nearby pillar // Search nearby pillar
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
if(search_pillar_and_connect(head)) { head.transform(); continue; } if(search_pillar_and_connect(head)) { head.transform(); return; }
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
// Try straight path // Try straight path
@ -1974,7 +2056,7 @@ public:
} }
if(std::isinf(tdown)) { // we heave found a route to the ground if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, head.dir, d); continue; routedown(head, head.dir, d); return;
} }
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
@ -2036,7 +2118,7 @@ public:
} }
if(std::isinf(tdown)) { // we heave found a route to the ground if(std::isinf(tdown)) { // we heave found a route to the ground
routedown(head, bridgedir, d); continue; routedown(head, bridgedir, d); return;
} }
// ///////////////////////////////////////////////////////////////// // /////////////////////////////////////////////////////////////////
@ -2079,8 +2161,9 @@ public:
pill.base = tailhead.mesh; pill.base = tailhead.mesh;
// Experimental: add the pillar to the index for cascading // Experimental: add the pillar to the index for cascading
std::lock_guard<ccr::Mutex> lk(mutex);
modelpillars.emplace_back(unsigned(pill.id)); modelpillars.emplace_back(unsigned(pill.id));
continue; return;
} }
// We have failed to route this head. // We have failed to route this head.
@ -2088,7 +2171,7 @@ public:
<< "Failed to route model facing support point." << "Failed to route model facing support point."
<< " ID: " << idx; << " ID: " << idx;
head.invalidate(); head.invalidate();
} });
for(auto pillid : modelpillars) { for(auto pillid : modelpillars) {
auto& pillar = m_result.pillar(pillid); auto& pillar = m_result.pillar(pillid);
@ -2175,8 +2258,8 @@ public:
// Search for the pair amongst the remembered pairs // Search for the pair amongst the remembered pairs
if(pairs.find(hashval) != pairs.end()) continue; if(pairs.find(hashval) != pairs.end()) continue;
const Pillar& neighborpillar = m_result.pillars()[re.second]; const Pillar& neighborpillar = m_result.pillar(re.second);
// this neighbor is occupied, skip // this neighbor is occupied, skip
if(neighborpillar.links >= neighbors) continue; if(neighborpillar.links >= neighbors) continue;
@ -2212,7 +2295,7 @@ public:
// lonely pillars. One or even two additional pillar might get inserted // lonely pillars. One or even two additional pillar might get inserted
// depending on the length of the lonely pillar. // depending on the length of the lonely pillar.
size_t pillarcount = m_result.pillars().size(); size_t pillarcount = m_result.pillarcount();
// Again, go through all pillars, this time in the whole support tree // Again, go through all pillars, this time in the whole support tree
// not just the index. // not just the index.
@ -2364,6 +2447,8 @@ public:
m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist)); m_result.add_compact_bridge(sp, ej, n, R, !std::isinf(dist));
} }
} }
void merge_result() { m_result.merge_and_cleanup(); }
}; };
bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points, bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
@ -2372,9 +2457,9 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
const Controller &ctl) const Controller &ctl)
{ {
if(support_points.empty()) return false; if(support_points.empty()) return false;
Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn);
// Let's define the individual steps of the processing. We can experiment // Let's define the individual steps of the processing. We can experiment
// later with the ordering and the dependencies between them. // later with the ordering and the dependencies between them.
enum Steps { enum Steps {
@ -2386,55 +2471,58 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
ROUTING_NONGROUND, ROUTING_NONGROUND,
CASCADE_PILLARS, CASCADE_PILLARS,
HEADLESS, HEADLESS,
MERGE_RESULT,
DONE, DONE,
ABORT, ABORT,
NUM_STEPS NUM_STEPS
//... //...
}; };
// Collect the algorithm steps into a nice sequence // Collect the algorithm steps into a nice sequence
std::array<std::function<void()>, NUM_STEPS> program = { std::array<std::function<void()>, NUM_STEPS> program = {
[] () { [] () {
// Begin... // Begin...
// Potentially clear up the shared data (not needed for now) // Potentially clear up the shared data (not needed for now)
}, },
std::bind(&Algorithm::filter, &alg), std::bind(&Algorithm::filter, &alg),
std::bind(&Algorithm::add_pinheads, &alg), std::bind(&Algorithm::add_pinheads, &alg),
std::bind(&Algorithm::classify, &alg), std::bind(&Algorithm::classify, &alg),
std::bind(&Algorithm::routing_to_ground, &alg), std::bind(&Algorithm::routing_to_ground, &alg),
std::bind(&Algorithm::routing_to_model, &alg), std::bind(&Algorithm::routing_to_model, &alg),
std::bind(&Algorithm::interconnect_pillars, &alg), std::bind(&Algorithm::interconnect_pillars, &alg),
std::bind(&Algorithm::routing_headless, &alg), std::bind(&Algorithm::routing_headless, &alg),
std::bind(&Algorithm::merge_result, &alg),
[] () { [] () {
// Done // Done
}, },
[] () { [] () {
// Abort // Abort
} }
}; };
Steps pc = BEGIN; Steps pc = BEGIN;
if(cfg.ground_facing_only) { if(cfg.ground_facing_only) {
program[ROUTING_NONGROUND] = []() { program[ROUTING_NONGROUND] = []() {
BOOST_LOG_TRIVIAL(info) BOOST_LOG_TRIVIAL(info)
<< "Skipping model-facing supports as requested."; << "Skipping model-facing supports as requested.";
}; };
program[HEADLESS] = []() { program[HEADLESS] = []() {
BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as"
" requested."; " requested.";
}; };
} }
// Let's define a simple automaton that will run our program. // Let's define a simple automaton that will run our program.
auto progress = [&ctl, &pc] () { auto progress = [&ctl, &pc] () {
static const std::array<std::string, NUM_STEPS> stepstr { static const std::array<std::string, NUM_STEPS> stepstr {
@ -2446,10 +2534,11 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
"Routing supports to model surface", "Routing supports to model surface",
"Interconnecting pillars", "Interconnecting pillars",
"Processing small holes", "Processing small holes",
"Merging support mesh",
"Done", "Done",
"Abort" "Abort"
}; };
static const std::array<unsigned, NUM_STEPS> stepstate { static const std::array<unsigned, NUM_STEPS> stepstate {
0, 0,
10, 10,
@ -2458,13 +2547,14 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
60, 60,
70, 70,
80, 80,
90, 85,
99,
100, 100,
0 0
}; };
if(ctl.stopcondition()) pc = ABORT; if(ctl.stopcondition()) pc = ABORT;
switch(pc) { switch(pc) {
case BEGIN: pc = FILTER; break; case BEGIN: pc = FILTER; break;
case FILTER: pc = PINHEADS; break; case FILTER: pc = PINHEADS; break;
@ -2473,20 +2563,22 @@ bool SLASupportTree::generate(const std::vector<SupportPoint> &support_points,
case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_GROUND: pc = ROUTING_NONGROUND; break;
case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break; case ROUTING_NONGROUND: pc = CASCADE_PILLARS; break;
case CASCADE_PILLARS: pc = HEADLESS; break; case CASCADE_PILLARS: pc = HEADLESS; break;
case HEADLESS: pc = DONE; break; case HEADLESS: pc = MERGE_RESULT; break;
case MERGE_RESULT: pc = DONE; break;
case DONE: case DONE:
case ABORT: break; case ABORT: break;
default: ; default: ;
} }
ctl.statuscb(stepstate[pc], stepstr[pc]); ctl.statuscb(stepstate[pc], stepstr[pc]);
}; };
// Just here we run the computation... // Just here we run the computation...
while(pc < DONE) { while(pc < DONE) {
progress(); progress();
program[pc](); program[pc]();
} }
return pc == ABORT; return pc == ABORT;
} }
@ -2504,44 +2596,40 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const {
outmesh.merge(get_pad()); outmesh.merge(get_pad());
} }
std::vector<ExPolygons> SLASupportTree::slice(float layerh, float init_layerh) const std::vector<ExPolygons> SLASupportTree::slice(
const std::vector<float> &heights, float cr) const
{ {
if(init_layerh < 0) init_layerh = layerh; const TriangleMesh &sup_mesh = m_impl->merged_mesh();
auto& stree = get(); const TriangleMesh &pad_mesh = get_pad();
const auto modelh = float(stree.full_height()); std::vector<ExPolygons> sup_slices;
auto gndlvl = float(this->m_impl->ground_level); if (!sup_mesh.empty()) {
const Pad& pad = m_impl->pad(); TriangleMeshSlicer sup_slicer(&sup_mesh);
if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg)); sup_slicer.slice(heights, cr, &sup_slices, m_impl->ctl().cancelfn);
std::vector<float> heights;
heights.reserve(size_t(modelh/layerh) + 1);
for(float h = gndlvl + init_layerh; h < gndlvl + modelh; h += layerh) {
heights.emplace_back(h);
} }
TriangleMesh fullmesh = m_impl->merged_mesh(); auto bb = pad_mesh.bounding_box();
fullmesh.merge(get_pad()); auto maxzit = std::upper_bound(heights.begin(), heights.end(), bb.max.z());
if (!fullmesh.empty()) fullmesh.require_shared_vertices();
TriangleMeshSlicer slicer(&fullmesh); auto padgrid = reserve_vector<float>(heights.end() - maxzit);
std::vector<ExPolygons> ret; std::copy(heights.begin(), maxzit, std::back_inserter(padgrid));
slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn);
std::vector<ExPolygons> pad_slices;
return ret; if (!pad_mesh.empty()) {
} TriangleMeshSlicer pad_slicer(&pad_mesh);
pad_slicer.slice(padgrid, cr, &pad_slices, m_impl->ctl().cancelfn);
std::vector<ExPolygons> SLASupportTree::slice(const std::vector<float> &heights, }
float cr) const
{ size_t len = std::min(heights.size(), pad_slices.size());
TriangleMesh fullmesh = m_impl->merged_mesh(); len = std::min(len, sup_slices.size());
fullmesh.merge(get_pad());
if (!fullmesh.empty()) fullmesh.require_shared_vertices(); for (size_t i = 0; i < len; ++i) {
TriangleMeshSlicer slicer(&fullmesh); std::copy(pad_slices[i].begin(), pad_slices[i].end(),
std::vector<ExPolygons> ret; std::back_inserter(sup_slices[i]));
slicer.slice(heights, cr, &ret, get().ctl().cancelfn); pad_slices[i] = {};
}
return ret;
return sup_slices;
} }
const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase,
@ -2568,16 +2656,6 @@ SLASupportTree::SLASupportTree(const std::vector<SupportPoint> &points,
{ {
m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm; m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm;
generate(points, emesh, cfg, ctl); generate(points, emesh, cfg, ctl);
m_impl->clear_support_data();
}
SLASupportTree::SLASupportTree(const SLASupportTree &c):
m_impl(new Impl(*c.m_impl)) {}
SLASupportTree &SLASupportTree::operator=(const SLASupportTree &c)
{
m_impl = make_unique<Impl>(*c.m_impl);
return *this;
} }
SLASupportTree::~SLASupportTree() {} SLASupportTree::~SLASupportTree() {}

View file

@ -171,9 +171,9 @@ public:
const EigenMesh3D& em, const EigenMesh3D& em,
const SupportConfig& cfg = {}, const SupportConfig& cfg = {},
const Controller& ctl = {}); const Controller& ctl = {});
SLASupportTree(const SLASupportTree&); SLASupportTree(const SLASupportTree&) = delete;
SLASupportTree& operator=(const SLASupportTree&); SLASupportTree& operator=(const SLASupportTree&) = delete;
~SLASupportTree(); ~SLASupportTree();
@ -183,9 +183,6 @@ public:
void merged_mesh_with_pad(TriangleMesh&) const; void merged_mesh_with_pad(TriangleMesh&) const;
/// Get the sliced 2d layers of the support geometry.
std::vector<ExPolygons> slice(float layerh, float init_layerh = -1.0) const;
std::vector<ExPolygons> slice(const std::vector<float> &, std::vector<ExPolygons> slice(const std::vector<float> &,
float closing_radius) const; float closing_radius) const;

View file

@ -928,12 +928,14 @@ void SLAPrint::process()
double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
double init = m_report_status.status(); double init = m_report_status.status();
ctl.statuscb = [this, d, init](unsigned st, const std::string&) ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg)
{ {
double current = init + st * d; double current = init + st * d;
if(std::round(m_report_status.status()) < std::round(current)) if(std::round(m_report_status.status()) < std::round(current))
m_report_status(*this, current, m_report_status(*this, current,
OBJ_STEP_LABELS(slaposSupportTree)); OBJ_STEP_LABELS(slaposSupportTree),
SlicingStatus::DEFAULT,
logmsg);
}; };
@ -1914,11 +1916,17 @@ std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in)
return final_path; return final_path;
} }
void SLAPrint::StatusReporter::operator()( void SLAPrint::StatusReporter::operator()(SLAPrint & p,
SLAPrint &p, double st, const std::string &msg, unsigned flags) double st,
const std::string &msg,
unsigned flags,
const std::string &logmsg)
{ {
m_st = st; m_st = st;
BOOST_LOG_TRIVIAL(info) << st << "% " << msg << log_memory_info(); BOOST_LOG_TRIVIAL(info)
<< st << "% " << msg << (logmsg.empty() ? "" : ": ") << logmsg
<< log_memory_info();
p.set_status(int(std::round(st)), msg, flags); p.set_status(int(std::round(st)), msg, flags);
} }

View file

@ -447,16 +447,21 @@ private:
// Estimated print time, material consumed. // Estimated print time, material consumed.
SLAPrintStatistics m_print_statistics; SLAPrintStatistics m_print_statistics;
class StatusReporter { class StatusReporter
{
double m_st = 0; double m_st = 0;
public: public:
void operator() (SLAPrint& p, double st, const std::string& msg, void operator()(SLAPrint & p,
unsigned flags = SlicingStatus::DEFAULT); double st,
const std::string &msg,
unsigned flags = SlicingStatus::DEFAULT,
const std::string &logmsg = "");
double status() const { return m_st; } double status() const { return m_st; }
} m_report_status; } m_report_status;
friend SLAPrintObject; friend SLAPrintObject;
}; };

View file

@ -32,13 +32,4 @@
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1) #define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1)
//====================
// 1.42.0.alpha7 techs
//====================
#define ENABLE_1_42_0_ALPHA7 1
// Printbed textures generated from svg files
#define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7)
#endif // _technologies_h_ #endif // _technologies_h_

View file

@ -18,10 +18,9 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), -
#ifdef __APPLE__ #ifdef __APPLE__
m_user_drawn_background = false; m_user_drawn_background = false;
#endif /*__APPLE__*/ #endif /*__APPLE__*/
Bind(wxEVT_PAINT, ([this](wxPaintEvent &/* e */) { repaint(); }));
Bind(wxEVT_SIZE, ([this](wxSizeEvent & /* e */) { Refresh(); }));
} }
void Bed_2D::repaint()
void Bed_2D::repaint(const std::vector<Vec2d>& shape)
{ {
wxAutoBufferedPaintDC dc(this); wxAutoBufferedPaintDC dc(this);
auto cw = GetSize().GetWidth(); auto cw = GetSize().GetWidth();
@ -41,7 +40,7 @@ void Bed_2D::repaint()
dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight());
} }
if (m_bed_shape.empty()) if (shape.empty())
return; return;
// reduce size to have some space around the drawn shape // reduce size to have some space around the drawn shape
@ -52,10 +51,9 @@ void Bed_2D::repaint()
auto ccenter = cbb.center(); auto ccenter = cbb.center();
// get bounding box of bed shape in G - code coordinates // get bounding box of bed shape in G - code coordinates
auto bed_shape = m_bed_shape; auto bed_polygon = Polygon::new_scale(shape);
auto bed_polygon = Polygon::new_scale(m_bed_shape); auto bb = BoundingBoxf(shape);
auto bb = BoundingBoxf(m_bed_shape); bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area
bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area
auto bw = bb.size()(0); auto bw = bb.size()(0);
auto bh = bb.size()(1); auto bh = bb.size()(1);
auto bcenter = bb.center(); auto bcenter = bb.center();
@ -73,8 +71,8 @@ void Bed_2D::repaint()
// draw bed fill // draw bed fill
dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID));
wxPointList pt_list; wxPointList pt_list;
for (auto pt: m_bed_shape) for (auto pt : shape)
{ {
Point pt_pix = to_pixels(pt, ch); Point pt_pix = to_pixels(pt, ch);
pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1))); pt_list.push_back(new wxPoint(pt_pix(0), pt_pix(1)));
} }
@ -155,13 +153,13 @@ void Bed_2D::repaint()
// convert G - code coordinates into pixels // convert G - code coordinates into pixels
Point Bed_2D::to_pixels(Vec2d point, int height) Point Bed_2D::to_pixels(const Vec2d& point, int height)
{ {
auto p = point * m_scale_factor + m_shift; auto p = point * m_scale_factor + m_shift;
return Point(p(0) + Border, height - p(1) + Border); return Point(p(0) + Border, height - p(1) + Border);
} }
void Bed_2D::set_pos(Vec2d pos) void Bed_2D::set_pos(const Vec2d& pos)
{ {
m_pos = pos; m_pos = pos;
Refresh(); Refresh();

View file

@ -17,16 +17,13 @@ class Bed_2D : public wxPanel
Vec2d m_shift = Vec2d::Zero(); Vec2d m_shift = Vec2d::Zero();
Vec2d m_pos = Vec2d::Zero(); Vec2d m_pos = Vec2d::Zero();
Point to_pixels(Vec2d point, int height); Point to_pixels(const Vec2d& point, int height);
void repaint(); void set_pos(const Vec2d& pos);
void set_pos(Vec2d pos);
public: public:
Bed_2D(wxWindow* parent); explicit Bed_2D(wxWindow* parent);
~Bed_2D() {}
std::vector<Vec2d> m_bed_shape; void repaint(const std::vector<Vec2d>& shape);
}; };

View file

@ -14,6 +14,7 @@
#include <GL/glew.h> #include <GL/glew.h>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <boost/filesystem/operations.hpp>
static const float GROUND_Z = -0.02f; static const float GROUND_Z = -0.02f;
@ -22,7 +23,6 @@ namespace GUI {
bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords) bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords)
{ {
#if ENABLE_TEXTURES_FROM_SVG
m_vertices.clear(); m_vertices.clear();
unsigned int v_size = 3 * (unsigned int)triangles.size(); unsigned int v_size = 3 * (unsigned int)triangles.size();
@ -82,75 +82,12 @@ bool GeometryBuffer::set_from_triangles(const Polygons& triangles, float z, bool
} }
} }
} }
#else
m_vertices.clear();
m_tex_coords.clear();
unsigned int v_size = 9 * (unsigned int)triangles.size();
unsigned int t_size = 6 * (unsigned int)triangles.size();
if (v_size == 0)
return false;
m_vertices = std::vector<float>(v_size, 0.0f);
if (generate_tex_coords)
m_tex_coords = std::vector<float>(t_size, 0.0f);
float min_x = unscale<float>(triangles[0].points[0](0));
float min_y = unscale<float>(triangles[0].points[0](1));
float max_x = min_x;
float max_y = min_y;
unsigned int v_coord = 0;
unsigned int t_coord = 0;
for (const Polygon& t : triangles)
{
for (unsigned int v = 0; v < 3; ++v)
{
const Point& p = t.points[v];
float x = unscale<float>(p(0));
float y = unscale<float>(p(1));
m_vertices[v_coord++] = x;
m_vertices[v_coord++] = y;
m_vertices[v_coord++] = z;
if (generate_tex_coords)
{
m_tex_coords[t_coord++] = x;
m_tex_coords[t_coord++] = y;
min_x = std::min(min_x, x);
max_x = std::max(max_x, x);
min_y = std::min(min_y, y);
max_y = std::max(max_y, y);
}
}
}
if (generate_tex_coords)
{
float size_x = max_x - min_x;
float size_y = max_y - min_y;
if ((size_x != 0.0f) && (size_y != 0.0f))
{
float inv_size_x = 1.0f / size_x;
float inv_size_y = -1.0f / size_y;
for (unsigned int i = 0; i < m_tex_coords.size(); i += 2)
{
m_tex_coords[i] = (m_tex_coords[i] - min_x) * inv_size_x;
m_tex_coords[i + 1] = (m_tex_coords[i + 1] - min_y) * inv_size_y;
}
}
}
#endif // ENABLE_TEXTURES_FROM_SVG
return true; return true;
} }
bool GeometryBuffer::set_from_lines(const Lines& lines, float z) bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
{ {
#if ENABLE_TEXTURES_FROM_SVG
m_vertices.clear(); m_vertices.clear();
unsigned int v_size = 2 * (unsigned int)lines.size(); unsigned int v_size = 2 * (unsigned int)lines.size();
@ -174,37 +111,14 @@ bool GeometryBuffer::set_from_lines(const Lines& lines, float z)
v2.position[2] = z; v2.position[2] = z;
++v_count; ++v_count;
} }
#else
m_vertices.clear();
m_tex_coords.clear();
unsigned int size = 6 * (unsigned int)lines.size();
if (size == 0)
return false;
m_vertices = std::vector<float>(size, 0.0f);
unsigned int coord = 0;
for (const Line& l : lines)
{
m_vertices[coord++] = unscale<float>(l.a(0));
m_vertices[coord++] = unscale<float>(l.a(1));
m_vertices[coord++] = z;
m_vertices[coord++] = unscale<float>(l.b(0));
m_vertices[coord++] = unscale<float>(l.b(1));
m_vertices[coord++] = z;
}
#endif // ENABLE_TEXTURES_FROM_SVG
return true; return true;
} }
#if ENABLE_TEXTURES_FROM_SVG
const float* GeometryBuffer::get_vertices_data() const const float* GeometryBuffer::get_vertices_data() const
{ {
return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr; return (m_vertices.size() > 0) ? (const float*)m_vertices.data() : nullptr;
} }
#endif // ENABLE_TEXTURES_FROM_SVG
const double Bed3D::Axes::Radius = 0.5; const double Bed3D::Axes::Radius = 0.5;
const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius; const double Bed3D::Axes::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius;
@ -274,22 +188,41 @@ void Bed3D::Axes::render_axis(double length) const
Bed3D::Bed3D() Bed3D::Bed3D()
: m_type(Custom) : m_type(Custom)
#if ENABLE_TEXTURES_FROM_SVG , m_custom_texture("")
, m_custom_model("")
, m_requires_canvas_update(false) , m_requires_canvas_update(false)
, m_vbo_id(0) , m_vbo_id(0)
#endif // ENABLE_TEXTURES_FROM_SVG
, m_scale_factor(1.0f) , m_scale_factor(1.0f)
{ {
} }
bool Bed3D::set_shape(const Pointfs& shape) bool Bed3D::set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
{ {
EType new_type = detect_type(shape); EType new_type = detect_type(shape);
if (m_shape == shape && m_type == new_type)
// check that the passed custom texture filename is valid
std::string cst_texture(custom_texture);
if (!cst_texture.empty())
{
if ((!boost::algorithm::iends_with(custom_texture, ".png") && !boost::algorithm::iends_with(custom_texture, ".svg")) || !boost::filesystem::exists(custom_texture))
cst_texture = "";
}
// check that the passed custom texture filename is valid
std::string cst_model(custom_model);
if (!cst_model.empty())
{
if (!boost::algorithm::iends_with(custom_model, ".stl") || !boost::filesystem::exists(custom_model))
cst_model = "";
}
if ((m_shape == shape) && (m_type == new_type) && (m_custom_texture == cst_texture) && (m_custom_model == cst_model))
// No change, no need to update the UI. // No change, no need to update the UI.
return false; return false;
m_shape = shape; m_shape = shape;
m_custom_texture = cst_texture;
m_custom_model = cst_model;
m_type = new_type; m_type = new_type;
calc_bounding_boxes(); calc_bounding_boxes();
@ -307,9 +240,9 @@ bool Bed3D::set_shape(const Pointfs& shape)
m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour; m_polygon = offset_ex(poly.contour, (float)bed_bbox.radius() * 1.7f, jtRound, scale_(0.5))[0].contour;
#if ENABLE_TEXTURES_FROM_SVG
reset(); reset();
#endif // ENABLE_TEXTURES_FROM_SVG m_texture.reset();
m_model.reset();
// Set the origin and size for painting of the coordinate system axes. // Set the origin and size for painting of the coordinate system axes.
m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z); m_axes.origin = Vec3d(0.0, 0.0, (double)GROUND_Z);
@ -329,11 +262,12 @@ Point Bed3D::point_projection(const Point& point) const
return m_polygon.point_projection(point); return m_polygon.point_projection(point);
} }
#if ENABLE_TEXTURES_FROM_SVG void Bed3D::render(GLCanvas3D& canvas, float theta, float scale_factor) const
void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const
{ {
m_scale_factor = scale_factor; m_scale_factor = scale_factor;
render_axes();
switch (m_type) switch (m_type)
{ {
case MK2: case MK2:
@ -354,51 +288,11 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const
default: default:
case Custom: case Custom:
{ {
render_custom(); render_custom(canvas, theta > 90.0f);
break; break;
} }
} }
} }
#else
void Bed3D::render(float theta, float scale_factor) const
{
m_scale_factor = scale_factor;
if (m_shape.empty())
return;
switch (m_type)
{
case MK2:
{
render_prusa("mk2", theta);
break;
}
case MK3:
{
render_prusa("mk3", theta);
break;
}
case SL1:
{
render_prusa("sl1", theta);
break;
}
default:
case Custom:
{
render_custom();
break;
}
}
}
#endif // ENABLE_TEXTURES_FROM_SVG
void Bed3D::render_axes() const
{
if (!m_shape.empty())
m_axes.render();
}
void Bed3D::calc_bounding_boxes() const void Bed3D::calc_bounding_boxes() const
{ {
@ -423,7 +317,7 @@ void Bed3D::calc_triangles(const ExPolygon& poly)
Polygons triangles; Polygons triangles;
poly.triangulate(&triangles); poly.triangulate(&triangles);
if (!m_triangles.set_from_triangles(triangles, GROUND_Z, m_type != Custom)) if (!m_triangles.set_from_triangles(triangles, GROUND_Z, true))
printf("Unable to create bed triangles\n"); printf("Unable to create bed triangles\n");
} }
@ -495,31 +389,76 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const
return type; return type;
} }
#if ENABLE_TEXTURES_FROM_SVG void Bed3D::render_axes() const
void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const
{ {
std::string tex_path = resources_dir() + "/icons/bed/" + key; if (!m_shape.empty())
m_axes.render();
}
std::string model_path = resources_dir() + "/models/" + key; void Bed3D::render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const
{
if (!bottom)
render_model(m_custom_model.empty() ? resources_dir() + "/models/" + key + "_bed.stl" : m_custom_model);
// use higher resolution images if graphic card and opengl version allow render_texture(m_custom_texture.empty() ? resources_dir() + "/icons/bed/" + key + ".svg" : m_custom_texture, bottom, canvas);
GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); }
std::string filename = tex_path + ".svg"; void Bed3D::render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const
{
if (filename.empty())
{
m_texture.reset();
render_default(bottom);
return;
}
if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename)) if ((m_texture.get_id() == 0) || (m_texture.get_source() != filename))
{ {
// generate a temporary lower resolution texture to show while no main texture levels have been compressed m_texture.reset();
if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8))
{
render_custom();
return;
}
// starts generating the main texture, compression will run asynchronously if (boost::algorithm::iends_with(filename, ".svg"))
if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size))
{ {
render_custom(); // use higher resolution images if graphic card and opengl version allow
GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size();
if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename))
{
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if (!m_temp_texture.load_from_svg_file(filename, false, false, false, max_tex_size / 8))
{
render_default(bottom);
return;
}
}
// starts generating the main texture, compression will run asynchronously
if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size))
{
render_default(bottom);
return;
}
}
else if (boost::algorithm::iends_with(filename, ".png"))
{
// generate a temporary lower resolution texture to show while no main texture levels have been compressed
if ((m_temp_texture.get_id() == 0) || (m_temp_texture.get_source() != filename))
{
if (!m_temp_texture.load_from_file(filename, false, GLTexture::None, false))
{
render_default(bottom);
return;
}
}
// starts generating the main texture, compression will run asynchronously
if (!m_texture.load_from_file(filename, true, GLTexture::MultiThreaded, true))
{
render_default(bottom);
return;
}
}
else
{
render_default(bottom);
return; return;
} }
} }
@ -536,292 +475,161 @@ void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom
} }
else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu()) else if (m_requires_canvas_update && m_texture.all_compressed_data_sent_to_gpu())
{ {
if (canvas != nullptr) canvas.stop_keeping_dirty();
canvas->stop_keeping_dirty();
m_requires_canvas_update = false; m_requires_canvas_update = false;
} }
if (!bottom) if (m_triangles.get_vertices_count() > 0)
{ {
filename = model_path + "_bed.stl"; if (m_shader.get_shader_program_id() == 0)
if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) { m_shader.init("printbed.vs", "printbed.fs");
Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2));
if (key == "mk2")
// hardcoded value to match the stl model
offset += Vec3d(0.0, 7.5, -0.03);
else if (key == "mk3")
// hardcoded value to match the stl model
offset += Vec3d(0.0, 5.5, 2.43);
else if (key == "sl1")
// hardcoded value to match the stl model
offset += Vec3d(0.0, 0.0, -0.03);
m_model.center_around(offset); if (m_shader.is_initialized())
// update extended bounding box
calc_bounding_boxes();
}
if (!m_model.get_filename().empty())
{ {
glsafe(::glEnable(GL_LIGHTING)); m_shader.start_using();
m_model.render(); m_shader.set_uniform("transparent_background", bottom);
glsafe(::glDisable(GL_LIGHTING)); m_shader.set_uniform("svg_source", boost::algorithm::iends_with(m_texture.get_source(), ".svg"));
}
}
unsigned int triangles_vcount = m_triangles.get_vertices_count(); if (m_vbo_id == 0)
if (triangles_vcount > 0) {
{ glsafe(::glGenBuffers(1, &m_vbo_id));
if (m_vbo_id == 0) glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
{ glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW));
glsafe(::glGenBuffers(1, &m_vbo_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (bottom)
glsafe(::glFrontFace(GL_CW));
unsigned int stride = m_triangles.get_vertex_data_size();
GLint position_id = m_shader.get_attrib_location("v_position");
GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords");
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)m_temp_texture.get_id();
if (tex_id == 0)
tex_id = (GLuint)m_texture.get_id();
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
glsafe(::glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)m_triangles.get_vertices_data_size(), (const GLvoid*)m_triangles.get_vertices_data(), GL_STATIC_DRAW));
if (position_id != -1)
{
glsafe(::glEnableVertexAttribArray(position_id));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
}
if (tex_coords_id != -1)
{
glsafe(::glEnableVertexAttribArray(tex_coords_id));
glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
}
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count()));
if (tex_coords_id != -1)
glsafe(::glDisableVertexAttribArray(tex_coords_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0)); glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
}
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
if (bottom)
glsafe(::glFrontFace(GL_CW));
render_prusa_shader(bottom);
if (bottom)
glsafe(::glFrontFace(GL_CCW));
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
}
}
void Bed3D::render_prusa_shader(bool transparent) const
{
if (m_shader.get_shader_program_id() == 0)
m_shader.init("printbed.vs", "printbed.fs");
if (m_shader.is_initialized())
{
m_shader.start_using();
m_shader.set_uniform("transparent_background", transparent);
unsigned int stride = m_triangles.get_vertex_data_size();
GLint position_id = m_shader.get_attrib_location("v_position");
GLint tex_coords_id = m_shader.get_attrib_location("v_tex_coords");
// show the temporary texture while no compressed data is available
GLuint tex_id = (GLuint)m_temp_texture.get_id();
if (tex_id == 0)
tex_id = (GLuint)m_texture.get_id();
glsafe(::glBindTexture(GL_TEXTURE_2D, tex_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, m_vbo_id));
if (position_id != -1)
{
glsafe(::glEnableVertexAttribArray(position_id));
glsafe(::glVertexAttribPointer(position_id, 3, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_position_offset()));
}
if (tex_coords_id != -1)
{
glsafe(::glEnableVertexAttribArray(tex_coords_id));
glsafe(::glVertexAttribPointer(tex_coords_id, 2, GL_FLOAT, GL_FALSE, stride, (GLvoid*)(intptr_t)m_triangles.get_tex_coords_offset()));
}
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_triangles.get_vertices_count()));
if (tex_coords_id != -1)
glsafe(::glDisableVertexAttribArray(tex_coords_id));
if (position_id != -1)
glsafe(::glDisableVertexAttribArray(position_id));
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
m_shader.stop_using();
}
}
#else
void Bed3D::render_prusa(const std::string& key, float theta) const
{
std::string tex_path = resources_dir() + "/icons/bed/" + key;
// use higher resolution images if graphic card allows
GLint max_tex_size;
glsafe(::glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_tex_size));
// temporary set to lowest resolution
max_tex_size = 2048;
if (max_tex_size >= 8192)
tex_path += "_8192";
else if (max_tex_size >= 4096)
tex_path += "_4096";
std::string model_path = resources_dir() + "/models/" + key;
// use anisotropic filter if graphic card allows
GLfloat max_anisotropy = 0.0f;
if (glewIsSupported("GL_EXT_texture_filter_anisotropic"))
glsafe(::glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy));
std::string filename = tex_path + "_top.png";
if ((m_top_texture.get_id() == 0) || (m_top_texture.get_source() != filename))
{
if (!m_top_texture.load_from_file(filename, true, true))
{
render_custom();
return;
}
if (max_anisotropy > 0.0f)
{
glsafe(::glBindTexture(GL_TEXTURE_2D, m_top_texture.get_id()));
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
if (bottom)
glsafe(::glFrontFace(GL_CCW));
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
m_shader.stop_using();
} }
} }
filename = tex_path + "_bottom.png";
if ((m_bottom_texture.get_id() == 0) || (m_bottom_texture.get_source() != filename))
{
if (!m_bottom_texture.load_from_file(filename, true, true))
{
render_custom();
return;
}
if (max_anisotropy > 0.0f)
{
glsafe(::glBindTexture(GL_TEXTURE_2D, m_bottom_texture.get_id()));
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
}
}
if (theta <= 90.0f)
{
filename = model_path + "_bed.stl";
if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) {
Vec3d offset = m_bounding_box.center() - Vec3d(0.0, 0.0, 0.5 * m_model.get_bounding_box().size()(2));
if (key == "mk2")
// hardcoded value to match the stl model
offset += Vec3d(0.0, 7.5, -0.03);
else if (key == "mk3")
// hardcoded value to match the stl model
offset += Vec3d(0.0, 5.5, 2.43);
else if (key == "sl1")
// hardcoded value to match the stl model
offset += Vec3d(0.0, 0.0, -0.03);
m_model.center_around(offset);
}
if (!m_model.get_filename().empty())
{
glsafe(::glEnable(GL_LIGHTING));
m_model.render();
glsafe(::glDisable(GL_LIGHTING));
}
}
unsigned int triangles_vcount = m_triangles.get_vertices_count();
if (triangles_vcount > 0)
{
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glDepthMask(GL_FALSE));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnable(GL_TEXTURE_2D));
glsafe(::glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glEnableClientState(GL_TEXTURE_COORD_ARRAY));
if (theta > 90.0f)
glsafe(::glFrontFace(GL_CW));
glsafe(::glBindTexture(GL_TEXTURE_2D, (theta <= 90.0f) ? (GLuint)m_top_texture.get_id() : (GLuint)m_bottom_texture.get_id()));
glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()));
glsafe(::glTexCoordPointer(2, GL_FLOAT, 0, (GLvoid*)m_triangles.get_tex_coords()));
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount));
if (theta > 90.0f)
glsafe(::glFrontFace(GL_CCW));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
glsafe(::glDisableClientState(GL_TEXTURE_COORD_ARRAY));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisable(GL_TEXTURE_2D));
glsafe(::glDisable(GL_BLEND));
glsafe(::glDepthMask(GL_TRUE));
}
} }
#endif // ENABLE_TEXTURES_FROM_SVG
void Bed3D::render_custom() const void Bed3D::render_model(const std::string& filename) const
{ {
#if ENABLE_TEXTURES_FROM_SVG if (filename.empty())
m_texture.reset(); return;
#else
m_top_texture.reset();
m_bottom_texture.reset();
#endif // ENABLE_TEXTURES_FROM_SVG
unsigned int triangles_vcount = m_triangles.get_vertices_count(); if ((m_model.get_filename() != filename) && m_model.init_from_file(filename))
if (triangles_vcount > 0) {
// move the model so that its origin (0.0, 0.0, 0.0) goes into the bed shape center and a bit down to avoid z-fighting with the texture quad
Vec3d shift = m_bounding_box.center();
shift(2) = -0.03;
m_model.set_offset(shift);
// update extended bounding box
calc_bounding_boxes();
}
if (!m_model.get_filename().empty())
{ {
glsafe(::glEnable(GL_LIGHTING)); glsafe(::glEnable(GL_LIGHTING));
glsafe(::glDisable(GL_DEPTH_TEST)); m_model.render();
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f));
glsafe(::glNormal3d(0.0f, 0.0f, 1.0f));
#if ENABLE_TEXTURES_FROM_SVG
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data()));
#else
glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_triangles.get_vertices()));
#endif // ENABLE_TEXTURES_FROM_SVG
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount));
// draw grid
unsigned int gridlines_vcount = m_gridlines.get_vertices_count();
// we need depth test for grid, otherwise it would disappear when looking the object from below
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glLineWidth(3.0f * m_scale_factor));
glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f));
#if ENABLE_TEXTURES_FROM_SVG
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data()));
#else
glsafe(::glVertexPointer(3, GL_FLOAT, 0, (GLvoid*)m_gridlines.get_vertices()));
#endif // ENABLE_TEXTURES_FROM_SVG
glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)gridlines_vcount));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisable(GL_BLEND));
glsafe(::glDisable(GL_LIGHTING)); glsafe(::glDisable(GL_LIGHTING));
} }
} }
#if ENABLE_TEXTURES_FROM_SVG void Bed3D::render_custom(GLCanvas3D& canvas, bool bottom) const
{
if (m_custom_texture.empty() && m_custom_model.empty())
{
render_default(bottom);
return;
}
if (!bottom)
render_model(m_custom_model);
render_texture(m_custom_texture, bottom, canvas);
}
void Bed3D::render_default(bool bottom) const
{
m_texture.reset();
unsigned int triangles_vcount = m_triangles.get_vertices_count();
if (triangles_vcount > 0)
{
bool has_model = !m_model.get_filename().empty();
glsafe(::glEnable(GL_DEPTH_TEST));
glsafe(::glEnable(GL_BLEND));
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
if (!has_model && !bottom)
{
// draw background
glsafe(::glColor4f(0.35f, 0.35f, 0.35f, 0.4f));
glsafe(::glNormal3d(0.0f, 0.0f, 1.0f));
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_triangles.get_vertices_data()));
glsafe(::glDrawArrays(GL_TRIANGLES, 0, (GLsizei)triangles_vcount));
}
// draw grid
glsafe(::glLineWidth(3.0f * m_scale_factor));
if (has_model && !bottom)
glsafe(::glColor4f(0.75f, 0.75f, 0.75f, 1.0f));
else
glsafe(::glColor4f(0.2f, 0.2f, 0.2f, 0.4f));
glsafe(::glVertexPointer(3, GL_FLOAT, m_triangles.get_vertex_data_size(), (GLvoid*)m_gridlines.get_vertices_data()));
glsafe(::glDrawArrays(GL_LINES, 0, (GLsizei)m_gridlines.get_vertices_count()));
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
glsafe(::glDisable(GL_BLEND));
}
}
void Bed3D::reset() void Bed3D::reset()
{ {
if (m_vbo_id > 0) if (m_vbo_id > 0)
@ -830,7 +638,6 @@ void Bed3D::reset()
m_vbo_id = 0; m_vbo_id = 0;
} }
} }
#endif // ENABLE_TEXTURES_FROM_SVG
} // GUI } // GUI
} // Slic3r } // Slic3r

View file

@ -3,9 +3,7 @@
#include "GLTexture.hpp" #include "GLTexture.hpp"
#include "3DScene.hpp" #include "3DScene.hpp"
#if ENABLE_TEXTURES_FROM_SVG
#include "GLShader.hpp" #include "GLShader.hpp"
#endif // ENABLE_TEXTURES_FROM_SVG
class GLUquadric; class GLUquadric;
typedef class GLUquadric GLUquadricObj; typedef class GLUquadric GLUquadricObj;
@ -17,7 +15,6 @@ class GLCanvas3D;
class GeometryBuffer class GeometryBuffer
{ {
#if ENABLE_TEXTURES_FROM_SVG
struct Vertex struct Vertex
{ {
float position[3]; float position[3];
@ -31,27 +28,17 @@ class GeometryBuffer
}; };
std::vector<Vertex> m_vertices; std::vector<Vertex> m_vertices;
#else
std::vector<float> m_vertices;
std::vector<float> m_tex_coords;
#endif // ENABLE_TEXTURES_FROM_SVG
public: public:
bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords);
bool set_from_lines(const Lines& lines, float z); bool set_from_lines(const Lines& lines, float z);
#if ENABLE_TEXTURES_FROM_SVG
const float* get_vertices_data() const; const float* get_vertices_data() const;
unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); } unsigned int get_vertices_data_size() const { return (unsigned int)m_vertices.size() * get_vertex_data_size(); }
unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } unsigned int get_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); }
size_t get_position_offset() const { return 0; } size_t get_position_offset() const { return 0; }
size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); } size_t get_tex_coords_offset() const { return (size_t)(3 * sizeof(float)); }
unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); } unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size(); }
#else
const float* get_vertices() const { return m_vertices.data(); }
const float* get_tex_coords() const { return m_tex_coords.data(); }
unsigned int get_vertices_count() const { return (unsigned int)m_vertices.size() / 3; }
#endif // ENABLE_TEXTURES_FROM_SVG
}; };
class Bed3D class Bed3D
@ -87,12 +74,13 @@ public:
private: private:
EType m_type; EType m_type;
Pointfs m_shape; Pointfs m_shape;
std::string m_custom_texture;
std::string m_custom_model;
mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_bounding_box;
mutable BoundingBoxf3 m_extended_bounding_box; mutable BoundingBoxf3 m_extended_bounding_box;
Polygon m_polygon; Polygon m_polygon;
GeometryBuffer m_triangles; GeometryBuffer m_triangles;
GeometryBuffer m_gridlines; GeometryBuffer m_gridlines;
#if ENABLE_TEXTURES_FROM_SVG
mutable GLTexture m_texture; mutable GLTexture m_texture;
// temporary texture shown until the main texture has still no levels compressed // temporary texture shown until the main texture has still no levels compressed
mutable GLTexture m_temp_texture; mutable GLTexture m_temp_texture;
@ -100,10 +88,6 @@ private:
mutable bool m_requires_canvas_update; mutable bool m_requires_canvas_update;
mutable Shader m_shader; mutable Shader m_shader;
mutable unsigned int m_vbo_id; mutable unsigned int m_vbo_id;
#else
mutable GLTexture m_top_texture;
mutable GLTexture m_bottom_texture;
#endif // ENABLE_TEXTURES_FROM_SVG
mutable GLBed m_model; mutable GLBed m_model;
Axes m_axes; Axes m_axes;
@ -111,9 +95,7 @@ private:
public: public:
Bed3D(); Bed3D();
#if ENABLE_TEXTURES_FROM_SVG
~Bed3D() { reset(); } ~Bed3D() { reset(); }
#endif // ENABLE_TEXTURES_FROM_SVG
EType get_type() const { return m_type; } EType get_type() const { return m_type; }
@ -122,34 +104,26 @@ public:
const Pointfs& get_shape() const { return m_shape; } const Pointfs& get_shape() const { return m_shape; }
// Return true if the bed shape changed, so the calee will update the UI. // Return true if the bed shape changed, so the calee will update the UI.
bool set_shape(const Pointfs& shape); bool set_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; } const BoundingBoxf3& get_bounding_box(bool extended) const { return extended ? m_extended_bounding_box : m_bounding_box; }
bool contains(const Point& point) const; bool contains(const Point& point) const;
Point point_projection(const Point& point) const; Point point_projection(const Point& point) const;
#if ENABLE_TEXTURES_FROM_SVG void render(GLCanvas3D& canvas, float theta, float scale_factor) const;
void render(GLCanvas3D* canvas, float theta, float scale_factor) const;
#else
void render(float theta, float scale_factor) const;
#endif // ENABLE_TEXTURES_FROM_SVG
void render_axes() const;
private: private:
void calc_bounding_boxes() const; void calc_bounding_boxes() const;
void calc_triangles(const ExPolygon& poly); void calc_triangles(const ExPolygon& poly);
void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox);
EType detect_type(const Pointfs& shape) const; EType detect_type(const Pointfs& shape) const;
#if ENABLE_TEXTURES_FROM_SVG void render_axes() const;
void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const;
void render_prusa_shader(bool transparent) const; void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const;
#else void render_model(const std::string& filename) const;
void render_prusa(const std::string& key, float theta) const; void render_custom(GLCanvas3D& canvas, bool bottom) const;
#endif // ENABLE_TEXTURES_FROM_SVG void render_default(bool bottom) const;
void render_custom() const;
#if ENABLE_TEXTURES_FROM_SVG
void reset(); void reset();
#endif // ENABLE_TEXTURES_FROM_SVG
}; };
} // GUI } // GUI

View file

@ -1736,13 +1736,7 @@ bool GLBed::on_init_from_file(const std::string& filename)
m_filename = filename; m_filename = filename;
ModelObject* model_object = model.objects.front(); m_volume.indexed_vertex_array.load_mesh(model.mesh());
model_object->center_around_origin();
TriangleMesh mesh = model.mesh();
mesh.repair();
m_volume.indexed_vertex_array.load_mesh(mesh);
float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f };
set_color(color, 4); set_color(color, 4);

View file

@ -4,23 +4,25 @@
#include <wx/numformatter.h> #include <wx/numformatter.h>
#include <wx/sizer.h> #include <wx/sizer.h>
#include <wx/statbox.h> #include <wx/statbox.h>
#include <wx/tooltip.h>
#include "libslic3r/BoundingBox.hpp" #include "libslic3r/BoundingBox.hpp"
#include "libslic3r/Model.hpp" #include "libslic3r/Model.hpp"
#include "libslic3r/Polygon.hpp" #include "libslic3r/Polygon.hpp"
#include "boost/nowide/iostream.hpp" #include "boost/nowide/iostream.hpp"
#include <boost/algorithm/string/predicate.hpp>
#include <algorithm> #include <algorithm>
namespace Slic3r { namespace Slic3r {
namespace GUI { namespace GUI {
void BedShapeDialog::build_dialog(ConfigOptionPoints* default_pt) void BedShapeDialog::build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
{ {
SetFont(wxGetApp().normal_font()); SetFont(wxGetApp().normal_font());
m_panel = new BedShapePanel(this); m_panel = new BedShapePanel(this);
m_panel->build_panel(default_pt); m_panel->build_panel(default_pt, custom_texture, custom_model);
auto main_sizer = new wxBoxSizer(wxVERTICAL); auto main_sizer = new wxBoxSizer(wxVERTICAL);
main_sizer->Add(m_panel, 1, wxEXPAND); main_sizer->Add(m_panel, 1, wxEXPAND);
@ -51,59 +53,69 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect)
Refresh(); Refresh();
} }
void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) const std::string BedShapePanel::NONE = "None";
const std::string BedShapePanel::EMPTY_STRING = "";
void BedShapePanel::build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model)
{ {
m_shape = default_pt.values;
m_custom_texture = custom_texture.value.empty() ? NONE : custom_texture.value;
m_custom_model = custom_model.value.empty() ? NONE : custom_model.value;
auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape"))); auto sbsizer = new wxStaticBoxSizer(wxVERTICAL, this, _(L("Shape")));
sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font());
// shape options // shape options
m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP);
sbsizer->Add(m_shape_options_book); sbsizer->Add(m_shape_options_book);
auto optgroup = init_shape_options_page(_(L("Rectangular"))); auto optgroup = init_shape_options_page(_(L("Rectangular")));
ConfigOptionDef def; ConfigOptionDef def;
def.type = coPoints; def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) });
def.label = L("Size"); def.label = L("Size");
def.tooltip = L("Size in X and Y of the rectangular plate."); def.tooltip = L("Size in X and Y of the rectangular plate.");
Option option(def, "rect_size"); Option option(def, "rect_size");
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
def.type = coPoints; def.type = coPoints;
def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) });
def.label = L("Origin"); def.label = L("Origin");
def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle.");
option = Option(def, "rect_origin"); option = Option(def, "rect_origin");
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_(L("Circular"))); optgroup = init_shape_options_page(_(L("Circular")));
def.type = coFloat; def.type = coFloat;
def.set_default_value(new ConfigOptionFloat(200)); def.set_default_value(new ConfigOptionFloat(200));
def.sidetext = L("mm"); def.sidetext = L("mm");
def.label = L("Diameter"); def.label = L("Diameter");
def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center.");
option = Option(def, "diameter"); option = Option(def, "diameter");
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option);
optgroup = init_shape_options_page(_(L("Custom"))); optgroup = init_shape_options_page(_(L("Custom")));
Line line{ "", "" }; Line line{ "", "" };
line.full_width = 1; line.full_width = 1;
line.widget = [this](wxWindow* parent) { line.widget = [this](wxWindow* parent) {
auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL...")));
wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL);
shape_sizer->Add(shape_btn, 1, wxEXPAND); shape_sizer->Add(shape_btn, 1, wxEXPAND);
wxSizer* sizer = new wxBoxSizer(wxVERTICAL); wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(shape_sizer, 1, wxEXPAND); sizer->Add(shape_sizer, 1, wxEXPAND);
shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
{ {
load_stl(); load_stl();
})); }));
return sizer; return sizer;
}; };
optgroup->append_line(line); optgroup->append_line(line);
wxPanel* texture_panel = init_texture_panel();
wxPanel* model_panel = init_model_panel();
Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e)
{ {
@ -112,13 +124,17 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
// right pane with preview canvas // right pane with preview canvas
m_canvas = new Bed_2D(this); m_canvas = new Bed_2D(this);
m_canvas->m_bed_shape = default_pt->values; m_canvas->Bind(wxEVT_PAINT, [this](wxPaintEvent& e) { m_canvas->repaint(m_shape); });
m_canvas->Bind(wxEVT_SIZE, [this](wxSizeEvent& e) { m_canvas->Refresh(); });
// main sizer wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL);
auto top_sizer = new wxBoxSizer(wxHORIZONTAL); left_sizer->Add(sbsizer, 0, wxEXPAND);
top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); left_sizer->Add(texture_panel, 1, wxEXPAND);
if (m_canvas) left_sizer->Add(model_panel, 1, wxEXPAND);
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ;
wxSizer* top_sizer = new wxBoxSizer(wxHORIZONTAL);
top_sizer->Add(left_sizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10);
top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10);
SetSizerAndFit(top_sizer); SetSizerAndFit(top_sizer);
@ -134,20 +150,155 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt)
// Create a panel for a rectangular / circular / custom bed shape. // Create a panel for a rectangular / circular / custom bed shape.
ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title)
{ {
auto panel = new wxPanel(m_shape_options_book); wxPanel* panel = new wxPanel(m_shape_options_book);
ConfigOptionsGroupShp optgroup; ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Settings")));
optgroup->label_width = 10; optgroup->label_width = 10;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
update_shape(); update_shape();
}; };
m_optgroups.push_back(optgroup); m_optgroups.push_back(optgroup);
panel->SetSizerAndFit(optgroup->sizer); panel->SetSizerAndFit(optgroup->sizer);
m_shape_options_book->AddPage(panel, title); m_shape_options_book->AddPage(panel, title);
return optgroup; return optgroup;
}
wxPanel* BedShapePanel::init_texture_panel()
{
wxPanel* panel = new wxPanel(this);
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Texture")));
optgroup->label_width = 10;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
update_shape();
};
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
load_sizer->Add(load_btn, 1, wxEXPAND);
wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL);
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
remove_sizer->Add(remove_btn, 1, wxEXPAND);
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(filename_sizer, 1, wxEXPAND);
sizer->Add(load_sizer, 1, wxEXPAND);
sizer->Add(remove_sizer, 1, wxEXPAND);
load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
{
load_texture();
}));
remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
{
m_custom_texture = NONE;
update_shape();
}));
filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
{
e.SetText(_(boost::filesystem::path(m_custom_texture).filename().string()));
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
if (lbl != nullptr)
{
wxString tooltip_text = (m_custom_texture == NONE) ? _(L("")) : _(m_custom_texture);
wxToolTip* tooltip = lbl->GetToolTip();
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
lbl->SetToolTip(tooltip_text);
}
}));
remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
{
e.Enable(m_custom_texture != NONE);
}));
return sizer;
};
optgroup->append_line(line);
panel->SetSizerAndFit(optgroup->sizer);
return panel;
}
wxPanel* BedShapePanel::init_model_panel()
{
wxPanel* panel = new wxPanel(this);
ConfigOptionsGroupShp optgroup = std::make_shared<ConfigOptionsGroup>(panel, _(L("Model")));
optgroup->label_width = 10;
optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) {
update_shape();
};
Line line{ "", "" };
line.full_width = 1;
line.widget = [this](wxWindow* parent) {
wxButton* load_btn = new wxButton(parent, wxID_ANY, _(L("Load...")));
wxSizer* load_sizer = new wxBoxSizer(wxHORIZONTAL);
load_sizer->Add(load_btn, 1, wxEXPAND);
wxStaticText* filename_lbl = new wxStaticText(parent, wxID_ANY, _(NONE));
wxSizer* filename_sizer = new wxBoxSizer(wxHORIZONTAL);
filename_sizer->Add(filename_lbl, 1, wxEXPAND);
wxButton* remove_btn = new wxButton(parent, wxID_ANY, _(L("Remove")));
wxSizer* remove_sizer = new wxBoxSizer(wxHORIZONTAL);
remove_sizer->Add(remove_btn, 1, wxEXPAND);
wxSizer* sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(filename_sizer, 1, wxEXPAND);
sizer->Add(load_sizer, 1, wxEXPAND);
sizer->Add(remove_sizer, 1, wxEXPAND);
load_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
{
load_model();
}));
remove_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e)
{
m_custom_model = NONE;
update_shape();
}));
filename_lbl->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
{
e.SetText(_(boost::filesystem::path(m_custom_model).filename().string()));
wxStaticText* lbl = dynamic_cast<wxStaticText*>(e.GetEventObject());
if (lbl != nullptr)
{
wxString tooltip_text = (m_custom_model == NONE) ? _(L("")) : _(m_custom_model);
wxToolTip* tooltip = lbl->GetToolTip();
if ((tooltip == nullptr) || (tooltip->GetTip() != tooltip_text))
lbl->SetToolTip(tooltip_text);
}
}));
remove_btn->Bind(wxEVT_UPDATE_UI, ([this](wxUpdateUIEvent& e)
{
e.Enable(m_custom_model != NONE);
}));
return sizer;
};
optgroup->append_line(line);
panel->SetSizerAndFit(optgroup->sizer);
return panel;
} }
// Called from the constructor. // Called from the constructor.
@ -155,20 +306,20 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit
// Deduce the bed shape type(rect, circle, custom) // Deduce the bed shape type(rect, circle, custom)
// This routine shall be smart enough if the user messes up // This routine shall be smart enough if the user messes up
// with the list of points in the ini file directly. // with the list of points in the ini file directly.
void BedShapePanel::set_shape(ConfigOptionPoints* points) void BedShapePanel::set_shape(const ConfigOptionPoints& points)
{ {
auto polygon = Polygon::new_scale(points->values); auto polygon = Polygon::new_scale(points.values);
// is this a rectangle ? // is this a rectangle ?
if (points->size() == 4) { if (points.size() == 4) {
auto lines = polygon.lines(); auto lines = polygon.lines();
if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) {
// okay, it's a rectangle // okay, it's a rectangle
// find origin // find origin
coordf_t x_min, x_max, y_min, y_max; coordf_t x_min, x_max, y_min, y_max;
x_max = x_min = points->values[0](0); x_max = x_min = points.values[0](0);
y_max = y_min = points->values[0](1); y_max = y_min = points.values[0](1);
for (auto pt : points->values) for (auto pt : points.values)
{ {
x_min = std::min(x_min, pt(0)); x_min = std::min(x_min, pt(0));
x_max = std::max(x_max, pt(0)); x_max = std::max(x_max, pt(0));
@ -219,8 +370,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
} }
} }
if (points->size() < 3) { if (points.size() < 3) {
// Invalid polygon.Revert to default bed dimensions. // Invalid polygon.Revert to default bed dimensions.
m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); m_shape_options_book->SetSelection(SHAPE_RECTANGULAR);
auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; auto optgroup = m_optgroups[SHAPE_RECTANGULAR];
optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) }); optgroup->set_value("rect_size", new ConfigOptionPoints{ Vec2d(200, 200) });
@ -232,7 +383,7 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points)
// This is a custom bed shape, use the polygon provided. // This is a custom bed shape, use the polygon provided.
m_shape_options_book->SetSelection(SHAPE_CUSTOM); m_shape_options_book->SetSelection(SHAPE_CUSTOM);
// Copy the polygon to the canvas, make a copy of the array. // Copy the polygon to the canvas, make a copy of the array.
m_loaded_bed_shape = points->values; m_loaded_shape = points.values;
update_shape(); update_shape();
} }
@ -277,11 +428,11 @@ void BedShapePanel::update_shape()
x1 -= dx; x1 -= dx;
y0 -= dy; y0 -= dy;
y1 -= dy; y1 -= dy;
m_canvas->m_bed_shape = { Vec2d(x0, y0), m_shape = { Vec2d(x0, y0),
Vec2d(x1, y0), Vec2d(x1, y0),
Vec2d(x1, y1), Vec2d(x1, y1),
Vec2d(x0, y1)}; Vec2d(x0, y1) };
} }
else if(page_idx == SHAPE_CIRCULAR) { else if(page_idx == SHAPE_CIRCULAR) {
double diameter; double diameter;
try{ try{
@ -293,16 +444,16 @@ void BedShapePanel::update_shape()
if (diameter == 0.0) return ; if (diameter == 0.0) return ;
auto r = diameter / 2; auto r = diameter / 2;
auto twopi = 2 * PI; auto twopi = 2 * PI;
auto edges = 60; auto edges = 72;
std::vector<Vec2d> points; std::vector<Vec2d> points;
for (size_t i = 1; i <= 60; ++i) { for (size_t i = 1; i <= edges; ++i) {
auto angle = i * twopi / edges; auto angle = i * twopi / edges;
points.push_back(Vec2d(r*cos(angle), r*sin(angle))); points.push_back(Vec2d(r*cos(angle), r*sin(angle)));
} }
m_canvas->m_bed_shape = points; m_shape = points;
} }
else if (page_idx == SHAPE_CUSTOM) else if (page_idx == SHAPE_CUSTOM)
m_canvas->m_bed_shape = m_loaded_bed_shape; m_shape = m_loaded_shape;
update_preview(); update_preview();
} }
@ -310,27 +461,27 @@ void BedShapePanel::update_shape()
// Loads an stl file, projects it to the XY plane and calculates a polygon. // Loads an stl file, projects it to the XY plane and calculates a polygon.
void BedShapePanel::load_stl() void BedShapePanel::load_stl()
{ {
auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); if (dialog.ShowModal() != wxID_OK)
if (dialog->ShowModal() != wxID_OK) { return;
dialog->Destroy();
return;
}
wxArrayString input_file;
dialog->GetPaths(input_file);
dialog->Destroy();
std::string file_name = input_file[0].ToUTF8().data(); std::string file_name = dialog.GetPath().ToUTF8().data();
if (!boost::algorithm::iends_with(file_name, ".stl"))
{
show_error(this, _(L("Invalid file format.")));
return;
}
wxBusyCursor wait;
Model model; Model model;
try { try {
model = Model::read_from_file(file_name); model = Model::read_from_file(file_name);
}
catch (std::exception &e) {
auto msg = _(L("Error!")) + " " + file_name + " : " + e.what() + ".";
show_error(this, msg);
exit(1);
} }
catch (std::exception &) {
show_error(this, _(L("Error! Invalid model")));
return;
}
auto mesh = model.mesh(); auto mesh = model.mesh();
auto expolygons = mesh.horizontal_projection(); auto expolygons = mesh.horizontal_projection();
@ -349,7 +500,53 @@ void BedShapePanel::load_stl()
for (auto pt : polygon.points) for (auto pt : polygon.points)
points.push_back(unscale(pt)); points.push_back(unscale(pt));
m_loaded_bed_shape = points; m_loaded_shape = points;
update_shape();
}
void BedShapePanel::load_texture()
{
wxFileDialog dialog(this, _(L("Choose a file to import bed texture from (PNG/SVG):")), "", "",
file_wildcards(FT_TEX), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
m_custom_texture = NONE;
std::string file_name = dialog.GetPath().ToUTF8().data();
if (!boost::algorithm::iends_with(file_name, ".png") && !boost::algorithm::iends_with(file_name, ".svg"))
{
show_error(this, _(L("Invalid file format.")));
return;
}
wxBusyCursor wait;
m_custom_texture = file_name;
update_shape();
}
void BedShapePanel::load_model()
{
wxFileDialog dialog(this, _(L("Choose an STL file to import bed model from:")), "", "",
file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
if (dialog.ShowModal() != wxID_OK)
return;
m_custom_model = NONE;
std::string file_name = dialog.GetPath().ToUTF8().data();
if (!boost::algorithm::iends_with(file_name, ".stl"))
{
show_error(this, _(L("Invalid file format.")));
return;
}
wxBusyCursor wait;
m_custom_model = file_name;
update_shape(); update_shape();
} }

View file

@ -16,27 +16,40 @@ namespace GUI {
using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>; using ConfigOptionsGroupShp = std::shared_ptr<ConfigOptionsGroup>;
class BedShapePanel : public wxPanel class BedShapePanel : public wxPanel
{ {
static const std::string NONE;
static const std::string EMPTY_STRING;
Bed_2D* m_canvas; Bed_2D* m_canvas;
std::vector<Vec2d> m_loaded_bed_shape; std::vector<Vec2d> m_shape;
std::vector<Vec2d> m_loaded_shape;
std::string m_custom_texture;
std::string m_custom_model;
public: public:
BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE), m_custom_model(NONE) {}
~BedShapePanel() {}
void build_panel(ConfigOptionPoints* default_pt); void build_panel(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model);
// Returns the resulting bed shape polygon. This value will be stored to the ini file.
const std::vector<Vec2d>& get_shape() const { return m_shape; }
const std::string& get_custom_texture() const { return (m_custom_texture != NONE) ? m_custom_texture : EMPTY_STRING; }
const std::string& get_custom_model() const { return (m_custom_model != NONE) ? m_custom_model : EMPTY_STRING; }
private:
ConfigOptionsGroupShp init_shape_options_page(const wxString& title); ConfigOptionsGroupShp init_shape_options_page(const wxString& title);
void set_shape(ConfigOptionPoints* points); wxPanel* init_texture_panel();
void update_preview(); wxPanel* init_model_panel();
void set_shape(const ConfigOptionPoints& points);
void update_preview();
void update_shape(); void update_shape();
void load_stl(); void load_stl();
void load_texture();
// Returns the resulting bed shape polygon. This value will be stored to the ini file. void load_model();
std::vector<Vec2d> GetValue() { return m_canvas->m_bed_shape; }
wxChoicebook* m_shape_options_book; wxChoicebook* m_shape_options_book;
std::vector <ConfigOptionsGroupShp> m_optgroups; std::vector <ConfigOptionsGroupShp> m_optgroups;
friend class BedShapeDialog;
}; };
class BedShapeDialog : public DPIDialog class BedShapeDialog : public DPIDialog
@ -45,10 +58,12 @@ class BedShapeDialog : public DPIDialog
public: public:
BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")), BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")),
wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {}
~BedShapeDialog() {}
void build_dialog(ConfigOptionPoints* default_pt); void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model);
std::vector<Vec2d> GetValue() { return m_panel->GetValue(); }
const std::vector<Vec2d>& get_shape() const { return m_panel->get_shape(); }
const std::string& get_custom_texture() const { return m_panel->get_custom_texture(); }
const std::string& get_custom_model() const { return m_panel->get_custom_model(); }
protected: protected:
void on_dpi_changed(const wxRect &suggested_rect) override; void on_dpi_changed(const wxRect &suggested_rect) override;

View file

@ -532,15 +532,21 @@ PageBedShape::PageBedShape(ConfigWizard *parent)
{ {
append_text(_(L("Set the shape of your printer's bed."))); append_text(_(L("Set the shape of your printer's bed.")));
shape_panel->build_panel(wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape")); shape_panel->build_panel(*wizard_p()->custom_config->option<ConfigOptionPoints>("bed_shape"),
*wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_texture"),
*wizard_p()->custom_config->option<ConfigOptionString>("bed_custom_model"));
append(shape_panel); append(shape_panel);
} }
void PageBedShape::apply_custom_config(DynamicPrintConfig &config) void PageBedShape::apply_custom_config(DynamicPrintConfig &config)
{ {
const auto points(shape_panel->GetValue()); const std::vector<Vec2d>& points = shape_panel->get_shape();
auto *opt = new ConfigOptionPoints(points); const std::string& custom_texture = shape_panel->get_custom_texture();
config.set_key_value("bed_shape", opt); const std::string& custom_model = shape_panel->get_custom_model();
config.set_key_value("bed_shape", new ConfigOptionPoints(points));
config.set_key_value("bed_custom_texture", new ConfigOptionString(custom_texture));
config.set_key_value("bed_custom_model", new ConfigOptionString(custom_model));
} }
PageDiameters::PageDiameters(ConfigWizard *parent) PageDiameters::PageDiameters(ConfigWizard *parent)
@ -1086,7 +1092,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason)
p->load_vendors(); p->load_vendors();
p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({ p->custom_config.reset(DynamicPrintConfig::new_from_defaults_keys({
"gcode_flavor", "bed_shape", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature", "gcode_flavor", "bed_shape", "bed_custom_texture", "bed_custom_model", "nozzle_diameter", "filament_diameter", "temperature", "bed_temperature",
})); }));
p->index = new ConfigWizardIndex(this); p->index = new ConfigWizardIndex(this);

View file

@ -116,87 +116,6 @@ void Size::set_scale_factor(int scale_factor)
m_scale_factor = scale_factor; m_scale_factor = scale_factor;
} }
#if !ENABLE_TEXTURES_FROM_SVG
GLCanvas3D::Shader::Shader()
: m_shader(nullptr)
{
}
GLCanvas3D::Shader::~Shader()
{
_reset();
}
bool GLCanvas3D::Shader::init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename)
{
if (is_initialized())
return true;
m_shader = new GLShader();
if (m_shader != nullptr)
{
if (!m_shader->load_from_file(fragment_shader_filename.c_str(), vertex_shader_filename.c_str()))
{
std::cout << "Compilaton of shader failed:" << std::endl;
std::cout << m_shader->last_error << std::endl;
_reset();
return false;
}
}
return true;
}
bool GLCanvas3D::Shader::is_initialized() const
{
return (m_shader != nullptr);
}
bool GLCanvas3D::Shader::start_using() const
{
if (is_initialized())
{
m_shader->enable();
return true;
}
else
return false;
}
void GLCanvas3D::Shader::stop_using() const
{
if (m_shader != nullptr)
m_shader->disable();
}
void GLCanvas3D::Shader::set_uniform(const std::string& name, float value) const
{
if (m_shader != nullptr)
m_shader->set_uniform(name.c_str(), value);
}
void GLCanvas3D::Shader::set_uniform(const std::string& name, const float* matrix) const
{
if (m_shader != nullptr)
m_shader->set_uniform(name.c_str(), matrix);
}
const GLShader* GLCanvas3D::Shader::get_shader() const
{
return m_shader;
}
void GLCanvas3D::Shader::_reset()
{
if (m_shader != nullptr)
{
m_shader->release();
delete m_shader;
m_shader = nullptr;
}
}
#endif // !ENABLE_TEXTURES_FROM_SVG
GLCanvas3D::LayersEditing::LayersEditing() GLCanvas3D::LayersEditing::LayersEditing()
: m_enabled(false) : m_enabled(false)
, m_z_texture_id(0) , m_z_texture_id(0)
@ -383,7 +302,7 @@ void GLCanvas3D::LayersEditing::_render_tooltip_texture(const GLCanvas3D& canvas
if (m_tooltip_texture.get_id() == 0) if (m_tooltip_texture.get_id() == 0)
{ {
std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png"; std::string filename = resources_dir() + "/icons/variable_layer_height_tooltip.png";
if (!m_tooltip_texture.load_from_file(filename, false, true)) if (!m_tooltip_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false))
return; return;
} }
@ -415,7 +334,7 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co
if (m_reset_texture.get_id() == 0) if (m_reset_texture.get_id() == 0)
{ {
std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png"; std::string filename = resources_dir() + "/icons/variable_layer_height_reset.png";
if (!m_reset_texture.load_from_file(filename, false, true)) if (!m_reset_texture.load_from_file(filename, false, GLTexture::SingleThreaded, false))
return; return;
} }
@ -1642,19 +1561,10 @@ void GLCanvas3D::render()
glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT));
_render_background(); _render_background();
// textured bed needs to be rendered after objects if the texture is transparent
bool early_bed_render = m_bed.is_custom() || (theta <= 90.0f);
if (early_bed_render)
_render_bed(theta);
_render_objects(); _render_objects();
_render_sla_slices(); _render_sla_slices();
_render_selection(); _render_selection();
_render_bed(theta);
_render_axes();
if (!early_bed_render)
_render_bed(theta);
#if ENABLE_RENDER_SELECTION_CENTER #if ENABLE_RENDER_SELECTION_CENTER
_render_selection_center(); _render_selection_center();
@ -3986,16 +3896,7 @@ void GLCanvas3D::_render_bed(float theta) const
#if ENABLE_RETINA_GL #if ENABLE_RETINA_GL
scale_factor = m_retina_helper->get_scale_factor(); scale_factor = m_retina_helper->get_scale_factor();
#endif // ENABLE_RETINA_GL #endif // ENABLE_RETINA_GL
#if ENABLE_TEXTURES_FROM_SVG m_bed.render(const_cast<GLCanvas3D&>(*this), theta, scale_factor);
m_bed.render(const_cast<GLCanvas3D*>(this), theta, scale_factor);
#else
m_bed.render(theta, scale_factor);
#endif // ENABLE_TEXTURES_FROM_SVG
}
void GLCanvas3D::_render_axes() const
{
m_bed.render_axes();
} }
void GLCanvas3D::_render_objects() const void GLCanvas3D::_render_objects() const

View file

@ -158,32 +158,6 @@ class GLCanvas3D
void reset() { first_volumes.clear(); } void reset() { first_volumes.clear(); }
}; };
#if !ENABLE_TEXTURES_FROM_SVG
class Shader
{
GLShader* m_shader;
public:
Shader();
~Shader();
bool init(const std::string& vertex_shader_filename, const std::string& fragment_shader_filename);
bool is_initialized() const;
bool start_using() const;
void stop_using() const;
void set_uniform(const std::string& name, float value) const;
void set_uniform(const std::string& name, const float* matrix) const;
const GLShader* get_shader() const;
private:
void _reset();
};
#endif // !ENABLE_TEXTURES_FROM_SVG
class LayersEditing class LayersEditing
{ {
public: public:
@ -688,7 +662,6 @@ private:
void _rectangular_selection_picking_pass() const; void _rectangular_selection_picking_pass() const;
void _render_background() const; void _render_background() const;
void _render_bed(float theta) const; void _render_bed(float theta) const;
void _render_axes() const;
void _render_objects() const; void _render_objects() const;
void _render_selection() const; void _render_selection() const;
#if ENABLE_RENDER_SELECTION_CENTER #if ENABLE_RENDER_SELECTION_CENTER

View file

@ -268,7 +268,6 @@ sub SetMatrix
} }
*/ */
#if ENABLE_TEXTURES_FROM_SVG
Shader::Shader() Shader::Shader()
: m_shader(nullptr) : m_shader(nullptr)
{ {
@ -363,6 +362,5 @@ void Shader::reset()
m_shader = nullptr; m_shader = nullptr;
} }
} }
#endif // ENABLE_TEXTURES_FROM_SVG
} // namespace Slic3r } // namespace Slic3r

View file

@ -37,7 +37,6 @@ public:
std::string last_error; std::string last_error;
}; };
#if ENABLE_TEXTURES_FROM_SVG
class Shader class Shader
{ {
GLShader* m_shader; GLShader* m_shader;
@ -66,7 +65,6 @@ public:
private: private:
void reset(); void reset();
}; };
#endif // ENABLE_TEXTURES_FROM_SVG
} }

View file

@ -134,7 +134,7 @@ GLTexture::~GLTexture()
reset(); reset();
} }
bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bool compress) bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy)
{ {
reset(); reset();
@ -142,7 +142,7 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bo
return false; return false;
if (boost::algorithm::iends_with(filename, ".png")) if (boost::algorithm::iends_with(filename, ".png"))
return load_from_png(filename, use_mipmaps, compress); return load_from_png(filename, use_mipmaps, compression_type, apply_anisotropy);
else else
return false; return false;
} }
@ -354,49 +354,10 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right,
glsafe(::glDisable(GL_BLEND)); glsafe(::glDisable(GL_BLEND));
} }
unsigned int GLTexture::generate_mipmaps(wxImage& image, bool compress) bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy)
{ {
int w = image.GetWidth(); bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc;
int h = image.GetHeight();
GLint level = 0;
std::vector<unsigned char> data(w * h * 4, 0);
while ((w > 1) || (h > 1))
{
++level;
w = std::max(w / 2, 1);
h = std::max(h / 2, 1);
int n_pixels = w * h;
image = image.ResampleBicubic(w, h);
unsigned char* img_rgb = image.GetData();
unsigned char* img_alpha = image.GetAlpha();
data.resize(n_pixels * 4);
for (int i = 0; i < n_pixels; ++i)
{
int data_id = i * 4;
int img_id = i * 3;
data[data_id + 0] = img_rgb[img_id + 0];
data[data_id + 1] = img_rgb[img_id + 1];
data[data_id + 2] = img_rgb[img_id + 2];
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)w, (GLsizei)h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
return (unsigned int)level;
}
bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, bool compress)
{
// Load a PNG with an alpha channel. // Load a PNG with an alpha channel.
wxImage image; wxImage image;
if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG)) if (!image.LoadFile(wxString::FromUTF8(filename.c_str()), wxBITMAP_TYPE_PNG))
@ -407,8 +368,32 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo
m_width = image.GetWidth(); m_width = image.GetWidth();
m_height = image.GetHeight(); m_height = image.GetHeight();
int n_pixels = m_width * m_height;
bool requires_rescale = false;
if (compression_enabled && (compression_type == MultiThreaded))
{
// the stb_dxt compression library seems to like only texture sizes which are a multiple of 4
int width_rem = m_width % 4;
int height_rem = m_height % 4;
if (width_rem != 0)
{
m_width += (4 - width_rem);
requires_rescale = true;
}
if (height_rem != 0)
{
m_height += (4 - height_rem);
requires_rescale = true;
}
}
if (requires_rescale)
image = image.ResampleBicubic(m_width, m_height);
int n_pixels = m_width * m_height;
if (n_pixels <= 0) if (n_pixels <= 0)
{ {
reset(); reset();
@ -440,28 +425,99 @@ bool GLTexture::load_from_png(const std::string& filename, bool use_mipmaps, boo
glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1)); glsafe(::glPixelStorei(GL_UNPACK_ALIGNMENT, 1));
glsafe(::glGenTextures(1, &m_id)); glsafe(::glGenTextures(1, &m_id));
glsafe(::glBindTexture(GL_TEXTURE_2D, m_id)); glsafe(::glBindTexture(GL_TEXTURE_2D, m_id));
if (compress && GLEW_EXT_texture_compression_s3tc)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); if (apply_anisotropy)
{
GLfloat max_anisotropy = GLCanvas3DManager::get_gl_info().get_max_anisotropy();
if (max_anisotropy > 1.0f)
glsafe(::glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, max_anisotropy));
}
if (compression_enabled)
{
if (compression_type == SingleThreaded)
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
{
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)m_width, (unsigned int)m_height, data);
}
}
else else
glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data())); glsafe(::glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
if (use_mipmaps) if (use_mipmaps)
{ {
// we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards
unsigned int levels_count = generate_mipmaps(image, compress); int lod_w = m_width;
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels_count)); int lod_h = m_height;
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); GLint level = 0;
// we do not need to generate all levels down to 1x1
while ((lod_w > 16) || (lod_h > 16))
{
++level;
lod_w = std::max(lod_w / 2, 1);
lod_h = std::max(lod_h / 2, 1);
n_pixels = lod_w * lod_h;
image = image.ResampleBicubic(lod_w, lod_h);
data.resize(n_pixels * 4);
img_rgb = image.GetData();
img_alpha = image.GetAlpha();
for (int i = 0; i < n_pixels; ++i)
{
int data_id = i * 4;
int img_id = i * 3;
data[data_id + 0] = img_rgb[img_id + 0];
data[data_id + 1] = img_rgb[img_id + 1];
data[data_id + 2] = img_rgb[img_id + 2];
data[data_id + 3] = (img_alpha != nullptr) ? img_alpha[i] : 255;
}
if (compression_enabled)
{
if (compression_type == SingleThreaded)
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)m_width, (GLsizei)m_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
else
{
// initializes the texture on GPU
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0));
// and send the uncompressed data to the compressor
m_compressor.add_level((unsigned int)lod_w, (unsigned int)lod_h, data);
}
}
else
glsafe(::glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, (GLsizei)lod_w, (GLsizei)lod_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, (const void*)data.data()));
}
if (!compression_enabled)
{
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
}
} }
else else
{ {
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
} }
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0));
m_source = filename; m_source = filename;
if (compression_enabled && (compression_type == MultiThreaded))
// start asynchronous compression
m_compressor.start_compressing();
return true; return true;
} }
@ -472,7 +528,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f); NSVGimage* image = nsvgParseFromFile(filename.c_str(), "px", 96.0f);
if (image == nullptr) if (image == nullptr)
{ {
// printf("Could not open SVG image.\n");
reset(); reset();
return false; return false;
} }
@ -507,7 +562,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
NSVGrasterizer* rast = nsvgCreateRasterizer(); NSVGrasterizer* rast = nsvgCreateRasterizer();
if (rast == nullptr) if (rast == nullptr)
{ {
// printf("Could not init rasterizer.\n");
nsvgDelete(image); nsvgDelete(image);
reset(); reset();
return false; return false;
@ -579,6 +633,7 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0));
} }
glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0));

View file

@ -48,6 +48,13 @@ namespace GUI {
}; };
public: public:
enum ECompressionType : unsigned char
{
None,
SingleThreaded,
MultiThreaded
};
struct UV struct UV
{ {
float u; float u;
@ -75,7 +82,7 @@ namespace GUI {
GLTexture(); GLTexture();
virtual ~GLTexture(); virtual ~GLTexture();
bool load_from_file(const std::string& filename, bool use_mipmaps, bool compress); bool load_from_file(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); bool load_from_svg_file(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
// meanings of states: (std::pair<int, bool>) // meanings of states: (std::pair<int, bool>)
// first field (int): // first field (int):
@ -101,11 +108,8 @@ namespace GUI {
static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top); static void render_texture(unsigned int tex_id, float left, float right, float bottom, float top);
static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs); static void render_sub_texture(unsigned int tex_id, float left, float right, float bottom, float top, const Quad_UVs& uvs);
protected:
unsigned int generate_mipmaps(wxImage& image, bool compress);
private: private:
bool load_from_png(const std::string& filename, bool use_mipmaps, bool compress); bool load_from_png(const std::string& filename, bool use_mipmaps, ECompressionType compression_type, bool apply_anisotropy);
bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px); bool load_from_svg(const std::string& filename, bool use_mipmaps, bool compress, bool apply_anisotropy, unsigned int max_size_px);
friend class Compressor; friend class Compressor;

View file

@ -174,7 +174,7 @@ bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture)
bool res = false; bool res = false;
if (!background_texture.filename.empty()) if (!background_texture.filename.empty())
res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, true); res = m_background_texture.texture.load_from_file(path + background_texture.filename, false, GLTexture::SingleThreaded, false);
if (res) if (res)
m_background_texture.metadata = background_texture; m_background_texture.metadata = background_texture;

View file

@ -67,9 +67,12 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
/* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA", /* FT_MODEL */ "Known files (*.stl, *.obj, *.amf, *.xml, *.3mf, *.prusa)|*.stl;*.STL;*.obj;*.OBJ;*.amf;*.AMF;*.xml;*.XML;*.3mf;*.3MF;*.prusa;*.PRUSA",
/* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", /* FT_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF",
/* FT_INI */ "INI files (*.ini)|*.ini;*.INI", /* FT_INI */ "INI files (*.ini)|*.ini;*.INI",
/* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG",
/* FT_PNGZIP */"Masked SLA files (*.sl1)|*.sl1;*.SL1",
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
/* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
}; };
std::string out = defaults[file_type]; std::string out = defaults[file_type];

View file

@ -44,6 +44,9 @@ enum FileType
FT_INI, FT_INI,
FT_SVG, FT_SVG,
FT_TEX,
FT_PNGZIP, FT_PNGZIP,
FT_SIZE, FT_SIZE,

View file

@ -43,7 +43,7 @@ bool GLGizmosManager::init()
if (!m_background_texture.metadata.filename.empty()) if (!m_background_texture.metadata.filename.empty())
{ {
if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, true)) if (!m_background_texture.texture.load_from_file(resources_dir() + "/icons/" + m_background_texture.metadata.filename, false, GLTexture::SingleThreaded, false))
{ {
reset(); reset();
return false; return false;

View file

@ -1853,7 +1853,7 @@ struct Plater::priv
// triangulate the bed and store the triangles into m_bed.m_triangles, // triangulate the bed and store the triangles into m_bed.m_triangles,
// fills the m_bed.m_grid_lines and sets m_bed.m_origin. // fills the m_bed.m_grid_lines and sets m_bed.m_origin.
// Sets m_bed.m_polygon to limit the object placement. // Sets m_bed.m_polygon to limit the object placement.
void set_bed_shape(const Pointfs& shape); void set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model);
bool can_delete() const; bool can_delete() const;
bool can_delete_all() const; bool can_delete_all() const;
@ -1914,7 +1914,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
: q(q) : q(q)
, main_frame(main_frame) , main_frame(main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({ , config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance", "bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host", "brim_width", "variable_layer_height", "serial_port", "serial_speed", "host_type", "print_host",
"printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material", "printhost_apikey", "printhost_cafile", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle",
@ -2017,11 +2017,21 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this); view3D_canvas->Bind(EVT_GLTOOLBAR_SPLIT_VOLUMES, &priv::on_action_split_volumes, this);
view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this); view3D_canvas->Bind(EVT_GLTOOLBAR_LAYERSEDITING, &priv::on_action_layersediting, this);
view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); }); view3D_canvas->Bind(EVT_GLCANVAS_INIT, [this](SimpleEvent&) { init_view_toolbar(); });
view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); }); view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
config->option<ConfigOptionString>("bed_custom_texture")->value,
config->option<ConfigOptionString>("bed_custom_model")->value);
});
// Preview events: // Preview events:
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_QUESTION_MARK, [this](SimpleEvent&) { wxGetApp().keyboard_shortcuts(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) { set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&)
{
set_bed_shape(config->option<ConfigOptionPoints>("bed_shape")->values,
config->option<ConfigOptionString>("bed_custom_texture")->value,
config->option<ConfigOptionString>("bed_custom_model")->value);
});
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_TAB, [this](SimpleEvent&) { select_next_view_3D(); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_MOVE_DOUBLE_SLIDER, [this](wxKeyEvent& evt) { preview->move_double_slider(evt); });
preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); }); preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_EDIT_COLOR_CHANGE, [this](wxKeyEvent& evt) { preview->edit_double_slider(evt); });
@ -3625,9 +3635,9 @@ bool Plater::priv::can_mirror() const
return get_selection().is_from_single_instance(); return get_selection().is_from_single_instance();
} }
void Plater::priv::set_bed_shape(const Pointfs& shape) void Plater::priv::set_bed_shape(const Pointfs& shape, const std::string& custom_texture, const std::string& custom_model)
{ {
bool new_shape = bed.set_shape(shape); bool new_shape = bed.set_shape(shape, custom_texture, custom_model);
if (new_shape) if (new_shape)
{ {
if (view3D) view3D->bed_shape_changed(); if (view3D) view3D->bed_shape_changed();
@ -4559,7 +4569,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
p->config->set_key_value(opt_key, config.option(opt_key)->clone()); p->config->set_key_value(opt_key, config.option(opt_key)->clone());
if (opt_key == "printer_technology") if (opt_key == "printer_technology")
this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key)); this->set_printer_technology(config.opt_enum<PrinterTechnology>(opt_key));
else if (opt_key == "bed_shape") { else if ((opt_key == "bed_shape") || (opt_key == "bed_custom_texture") || (opt_key == "bed_custom_model")) {
bed_shape_changed = true; bed_shape_changed = true;
update_scheduled = true; update_scheduled = true;
} }
@ -4593,7 +4603,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config)
} }
if (bed_shape_changed) if (bed_shape_changed)
p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values); p->set_bed_shape(p->config->option<ConfigOptionPoints>("bed_shape")->values,
p->config->option<ConfigOptionString>("bed_custom_texture")->value,
p->config->option<ConfigOptionString>("bed_custom_model")->value);
if (update_scheduled) if (update_scheduled)
update(); update();

View file

@ -411,7 +411,7 @@ const std::vector<std::string>& Preset::printer_options()
if (s_opts.empty()) { if (s_opts.empty()) {
s_opts = { s_opts = {
"printer_technology", "printer_technology",
"bed_shape", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed", "bed_shape", "bed_custom_texture", "bed_custom_model", "z_offset", "gcode_flavor", "use_relative_e_distances", "serial_port", "serial_speed",
"use_firmware_retraction", "use_volumetric_e", "variable_layer_height", "use_firmware_retraction", "use_volumetric_e", "variable_layer_height",
"host_type", "print_host", "printhost_apikey", "printhost_cafile", "host_type", "print_host", "printhost_apikey", "printhost_cafile",
"single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode",
@ -512,6 +512,7 @@ const std::vector<std::string>& Preset::sla_printer_options()
if (s_opts.empty()) { if (s_opts.empty()) {
s_opts = { s_opts = {
"printer_technology", "printer_technology",
"bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height",
"bed_shape", "max_print_height", "bed_shape", "max_print_height",
"display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_width", "display_height", "display_pixels_x", "display_pixels_y",
"display_mirror_x", "display_mirror_y", "display_mirror_x", "display_mirror_y",

View file

@ -14,20 +14,20 @@
namespace Slic3r { namespace Slic3r {
ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id)
self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, : self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe,
id == -1? wxID_ANY : id)), id == -1 ? wxID_ANY : id)}
m_timer(new wxTimer(self)), , m_prog{new wxGauge(self,
m_prog (new wxGauge(self, wxGA_HORIZONTAL,
wxGA_HORIZONTAL, 100,
100, wxDefaultPosition,
wxDefaultPosition, wxDefaultSize)}
wxDefaultSize)), , m_cancelbutton{new wxButton(self,
m_cancelbutton(new wxButton(self, -1,
-1, _(L("Cancel")),
_(L("Cancel")), wxDefaultPosition,
wxDefaultPosition, wxDefaultSize)}
wxDefaultSize)) , m_timer{new wxTimer(self)}
{ {
m_prog->Hide(); m_prog->Hide();
m_cancelbutton->Hide(); m_cancelbutton->Hide();

View file

@ -25,9 +25,9 @@ namespace Slic3r {
class ProgressStatusBar class ProgressStatusBar
{ {
wxStatusBar *self; // we cheat! It should be the base class but: perl! wxStatusBar *self; // we cheat! It should be the base class but: perl!
wxTimer *m_timer;
wxGauge *m_prog; wxGauge *m_prog;
wxButton *m_cancelbutton; wxButton *m_cancelbutton;
std::unique_ptr<wxTimer> m_timer;
public: public:
/// Cancel callback function type /// Cancel callback function type

View file

@ -1862,12 +1862,18 @@ void TabPrinter::build_fff()
btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
{ {
BedShapeDialog dlg(this); BedShapeDialog dlg(this);
dlg.build_dialog(m_config->option<ConfigOptionPoints>("bed_shape")); dlg.build_dialog(*m_config->option<ConfigOptionPoints>("bed_shape"),
*m_config->option<ConfigOptionString>("bed_custom_texture"),
*m_config->option<ConfigOptionString>("bed_custom_model"));
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {
std::vector<Vec2d> shape = dlg.GetValue(); const std::vector<Vec2d>& shape = dlg.get_shape();
const std::string& custom_texture = dlg.get_custom_texture();
const std::string& custom_model = dlg.get_custom_model();
if (!shape.empty()) if (!shape.empty())
{ {
load_key_value("bed_shape", shape); load_key_value("bed_shape", shape);
load_key_value("bed_custom_texture", custom_texture);
load_key_value("bed_custom_model", custom_model);
update_changed_ui(); update_changed_ui();
} }
} }
@ -2095,12 +2101,18 @@ void TabPrinter::build_sla()
btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e)
{ {
BedShapeDialog dlg(this); BedShapeDialog dlg(this);
dlg.build_dialog(m_config->option<ConfigOptionPoints>("bed_shape")); dlg.build_dialog(*m_config->option<ConfigOptionPoints>("bed_shape"),
*m_config->option<ConfigOptionString>("bed_custom_texture"),
*m_config->option<ConfigOptionString>("bed_custom_model"));
if (dlg.ShowModal() == wxID_OK) { if (dlg.ShowModal() == wxID_OK) {
std::vector<Vec2d> shape = dlg.GetValue(); const std::vector<Vec2d>& shape = dlg.get_shape();
const std::string& custom_texture = dlg.get_custom_texture();
const std::string& custom_model = dlg.get_custom_model();
if (!shape.empty()) if (!shape.empty())
{ {
load_key_value("bed_shape", shape); load_key_value("bed_shape", shape);
load_key_value("bed_custom_texture", custom_texture);
load_key_value("bed_custom_model", custom_model);
update_changed_ui(); update_changed_ui();
} }
} }