diff --git a/resources/models/mk2_bed.stl b/resources/models/mk2_bed.stl index 07b7655ea..8a74a2349 100644 Binary files a/resources/models/mk2_bed.stl and b/resources/models/mk2_bed.stl differ diff --git a/resources/models/mk3_bed.stl b/resources/models/mk3_bed.stl index 1bd1a8faa..6aff36f0b 100644 Binary files a/resources/models/mk3_bed.stl and b/resources/models/mk3_bed.stl differ diff --git a/resources/models/sl1_bed.stl b/resources/models/sl1_bed.stl index b2cadde4b..4a6980b09 100644 Binary files a/resources/models/sl1_bed.stl and b/resources/models/sl1_bed.stl differ diff --git a/resources/shaders/printbed.fs b/resources/shaders/printbed.fs index be14347c2..d1316ca2f 100644 --- a/resources/shaders/printbed.fs +++ b/resources/shaders/printbed.fs @@ -5,16 +5,30 @@ const vec3 back_color_light = vec3(0.365, 0.365, 0.365); uniform sampler2D texture; uniform bool transparent_background; +uniform bool svg_source; varying vec2 tex_coords; -void main() +vec4 svg_color() { + // takes foreground from texture + vec4 fore_color = texture2D(texture, tex_coords); + // 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))))); - vec4 fore_color = texture2D(texture, tex_coords); - // 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(); } \ No newline at end of file diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt index ac40e99bc..4842b5123 100644 --- a/src/libslic3r/CMakeLists.txt +++ b/src/libslic3r/CMakeLists.txt @@ -165,6 +165,7 @@ add_library(libslic3r STATIC MinAreaBoundingBox.cpp miniz_extension.hpp miniz_extension.cpp + SLA/SLACommon.hpp SLA/SLABoilerPlate.hpp SLA/SLABasePool.hpp SLA/SLABasePool.cpp diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 1c0fa914e..8e091fb80 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -51,6 +51,16 @@ void PrintConfigDef::init_common_params() def->mode = comAdvanced; 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->label = L("Layer height"); def->category = L("Layers and Perimeters"); diff --git a/src/libslic3r/SLA/SLACommon.hpp b/src/libslic3r/SLA/SLACommon.hpp index d95397fed..d8e258035 100644 --- a/src/libslic3r/SLA/SLACommon.hpp +++ b/src/libslic3r/SLA/SLACommon.hpp @@ -24,37 +24,51 @@ enum class PointsStatus { UserModified // User has done some edits. }; -struct SupportPoint { +struct SupportPoint +{ Vec3f pos; float head_front_radius; - bool is_new_island; + bool is_new_island; - SupportPoint() : - pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {} + SupportPoint() + : 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) : - pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {} + SupportPoint(float pos_x, + 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) : - pos(position), head_front_radius(head_radius), is_new_island(new_island) {} + SupportPoint(Vec3f position, float head_radius, bool new_island) + : pos(position) + , head_front_radius(head_radius) + , is_new_island(new_island) + {} - SupportPoint(Eigen::Matrix data) : - pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {} + SupportPoint(Eigen::Matrix data) + : 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 { return !(sp == (*this)); } + 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 { return !(sp == (*this)); } - template void serialize(Archive &ar) { ar(pos, head_front_radius, is_new_island); } + template 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 /// alternative (raw) input format for the SLASupportTree class EigenMesh3D { @@ -161,9 +175,6 @@ public: } }; - - - } // namespace sla } // namespace Slic3r diff --git a/src/libslic3r/SLA/SLASupportTree.cpp b/src/libslic3r/SLA/SLASupportTree.cpp index d89836cb9..6f4ac6c21 100644 --- a/src/libslic3r/SLA/SLASupportTree.cpp +++ b/src/libslic3r/SLA/SLASupportTree.cpp @@ -83,6 +83,42 @@ const unsigned SupportConfig::max_bridges_on_pillar = 3; using Coordf = double; using Portion = std::tuple; +// 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 struct _ccr {}; + +template<> struct _ccr +{ + using Mutex = SpinMutex; + + template + 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 +{ + struct Mutex { inline void lock() {} inline void unlock() {} }; + + template + static inline void enumerate(It from, It to, Fn fn) + { + for (auto it = from; it != to; ++it) fn(*it, it - from); + } +}; + +using ccr = _ccr; +using ccr_seq = _ccr; +using ccr_par = _ccr; + inline Portion make_portion(double a, double b) { return std::make_tuple(a, b); } @@ -677,6 +713,7 @@ struct Pad { } tmesh.translate(0, 0, float(zlevel)); + tmesh.require_shared_vertices(); } 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 // merged mesh. It can be retrieved using a dedicated method (pad()) class SLASupportTree::Impl { - std::map m_heads; + // For heads it is beneficial to use the same IDs as for the support points. + std::vector m_heads; + std::vector m_head_indices; + std::vector m_pillars; std::vector m_junctions; std::vector m_bridges; std::vector m_compact_bridges; Controller m_ctl; - + Pad m_pad; + + using Mutex = ccr::Mutex; + + mutable Mutex m_mutex; mutable TriangleMesh meshcache; mutable bool meshcache_valid = false; mutable double model_height = 0; // the full height of the model + public: double ground_level = 0; - + Impl() = default; inline Impl(const Controller& ctl): m_ctl(ctl) {} - + const Controller& ctl() const { return m_ctl; } - - template Head& add_head(unsigned id, Args&&... args) { - auto el = m_heads.emplace(std::piecewise_construct, - std::forward_as_tuple(id), - std::forward_as_tuple(std::forward(args)...)); - el.first->second.id = id; + + template Head& add_head(unsigned id, Args&&... args) + { + std::lock_guard lk(m_mutex); + m_heads.emplace_back(std::forward(args)...); + 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; - return el.first->second; + return m_heads.back(); } - - template Pillar& add_pillar(unsigned headid, Args&&... args) { - auto it = m_heads.find(headid); - assert(it != m_heads.end()); - Head& head = it->second; + + template Pillar& add_pillar(unsigned headid, Args&&... args) + { + std::lock_guard lk(m_mutex); + + assert(headid < m_head_indices.size()); + Head &head = m_heads[m_head_indices[headid]]; + m_pillars.emplace_back(head, std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); head.pillar_id = pillar.id; pillar.start_junction_id = head.id; pillar.starts_from_head = true; + meshcache_valid = false; return m_pillars.back(); } - - void increment_bridges(const Pillar& pillar) { + + void increment_bridges(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); 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++; } - - void increment_links(const Pillar& pillar) { + + void increment_links(const Pillar& pillar) + { + std::lock_guard lk(m_mutex); assert(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 Pillar& add_pillar(Args&&...args) { + std::lock_guard lk(m_mutex); m_pillars.emplace_back(std::forward(args)...); Pillar& pillar = m_pillars.back(); pillar.id = long(m_pillars.size() - 1); @@ -799,161 +857,175 @@ public: meshcache_valid = false; return m_pillars.back(); } - - const Head& pillar_head(long pillar_id) const { + + const Head& pillar_head(long pillar_id) const + { + std::lock_guard lk(m_mutex); assert(pillar_id >= 0 && pillar_id < long(m_pillars.size())); + const Pillar& p = m_pillars[size_t(pillar_id)]; assert(p.starts_from_head && p.start_junction_id >= 0); - auto it = m_heads.find(unsigned(p.start_junction_id)); - assert(it != m_heads.end()); - return it->second; + assert(size_t(p.start_junction_id) < m_head_indices.size()); + + return m_heads[m_head_indices[p.start_junction_id]]; } - - const Pillar& head_pillar(unsigned headid) const { - auto it = m_heads.find(headid); - assert(it != m_heads.end()); - const Head& h = it->second; + + const Pillar& head_pillar(unsigned headid) const + { + std::lock_guard lk(m_mutex); + 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())); - return pillar(h.pillar_id); + + return m_pillars[size_t(h.pillar_id)]; } - - template const Junction& add_junction(Args&&... args) { + + template const Junction& add_junction(Args&&... args) + { + std::lock_guard lk(m_mutex); m_junctions.emplace_back(std::forward(args)...); m_junctions.back().id = long(m_junctions.size() - 1); meshcache_valid = false; return m_junctions.back(); } - - template const Bridge& add_bridge(Args&&... args) { + + template const Bridge& add_bridge(Args&&... args) + { + std::lock_guard lk(m_mutex); m_bridges.emplace_back(std::forward(args)...); m_bridges.back().id = long(m_bridges.size() - 1); meshcache_valid = false; return m_bridges.back(); } - - template - const CompactBridge& add_compact_bridge(Args&&...args) { + + template const CompactBridge& add_compact_bridge(Args&&...args) + { + std::lock_guard lk(m_mutex); m_compact_bridges.emplace_back(std::forward(args)...); m_compact_bridges.back().id = long(m_compact_bridges.size() - 1); meshcache_valid = false; return m_compact_bridges.back(); } - - const std::map& heads() const { return m_heads; } - Head& head(unsigned idx) { + + Head &head(unsigned id) + { + std::lock_guard lk(m_mutex); + assert(id < m_head_indices.size()); + meshcache_valid = false; - auto it = m_heads.find(idx); - assert(it != m_heads.end()); - return it->second; + return m_heads[m_head_indices[id]]; } - const std::vector& pillars() const { return m_pillars; } - const std::vector& bridges() const { return m_bridges; } - const std::vector& junctions() const { return m_junctions; } - const std::vector& compact_bridges() const { - return m_compact_bridges; + + inline size_t pillarcount() const { + std::lock_guard lk(m_mutex); + return m_pillars.size(); } - - template inline const Pillar& pillar(T id) const { - static_assert(std::is_integral::value, "Invalid index type"); + + template inline IntegerOnly pillar(T id) const + { + std::lock_guard lk(m_mutex); assert(id >= 0 && size_t(id) < m_pillars.size() && size_t(id) < std::numeric_limits::max()); + return m_pillars[size_t(id)]; } - - const Pad& create_pad(const TriangleMesh& object_supports, - const ExPolygons& modelbase, - const PoolConfig& cfg) { + + const Pad &create_pad(const TriangleMesh &object_supports, + const ExPolygons & modelbase, + const PoolConfig & cfg) + { m_pad = Pad(object_supports, modelbase, ground_level, cfg); return m_pad; } - - void remove_pad() { - m_pad = Pad(); - } - + + void remove_pad() { m_pad = Pad(); } + const Pad& pad() const { return m_pad; } - + // WITHOUT THE PAD!!! - const TriangleMesh& merged_mesh() const { - if(meshcache_valid) return meshcache; - + const TriangleMesh &merged_mesh() const + { + if (meshcache_valid) return meshcache; + Contour3D merged; - - for(auto& headel : heads()) { - if(m_ctl.stopcondition()) break; - if(headel.second.is_valid()) - merged.merge(headel.second.mesh); + + for (auto &head : m_heads) { + if (m_ctl.stopcondition()) break; + if (head.is_valid()) merged.merge(head.mesh); } - - for(auto& stick : pillars()) { - if(m_ctl.stopcondition()) break; + + for (auto &stick : m_pillars) { + if (m_ctl.stopcondition()) break; merged.merge(stick.mesh); merged.merge(stick.base); } - - for(auto& j : junctions()) { - if(m_ctl.stopcondition()) break; + + for (auto &j : m_junctions) { + if (m_ctl.stopcondition()) break; merged.merge(j.mesh); } - - for(auto& cb : compact_bridges()) { - if(m_ctl.stopcondition()) break; + + for (auto &cb : m_compact_bridges) { + if (m_ctl.stopcondition()) break; merged.merge(cb.mesh); } - - for(auto& bs : bridges()) { - if(m_ctl.stopcondition()) break; + + for (auto &bs : m_bridges) { + if (m_ctl.stopcondition()) break; merged.merge(bs.mesh); } - - if(m_ctl.stopcondition()) { + + if (m_ctl.stopcondition()) { // In case of failure we have to return an empty mesh meshcache = TriangleMesh(); return meshcache; } - + meshcache = mesh(merged); - + // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. if (!meshcache.empty()) meshcache.require_shared_vertices(); - - // TODO: Is this necessary? - //meshcache.repair(); - - BoundingBoxf3&& bb = meshcache.bounding_box(); - model_height = bb.max(Z) - bb.min(Z); - + + BoundingBoxf3 &&bb = meshcache.bounding_box(); + model_height = bb.max(Z) - bb.min(Z); + meshcache_valid = true; return meshcache; } - + // WITH THE PAD - double full_height() const { - if(merged_mesh().empty() && !pad().empty()) + double full_height() const + { + if (merged_mesh().empty() && !pad().empty()) return get_pad_fullheight(pad().cfg); - + 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; } - + // WITHOUT THE PAD!!! - double mesh_height() const { - if(!meshcache_valid) merged_mesh(); + double mesh_height() const + { + if (!meshcache_valid) merged_mesh(); return model_height; } // 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... - m_heads.clear(); - m_pillars.clear(); - m_junctions.clear(); - m_bridges.clear(); - m_compact_bridges.clear(); + + // Doing clear() does not garantee to release the memory. + m_heads = {}; + m_head_indices = {}; + m_pillars = {}; + m_junctions = {}; + m_bridges = {}; + m_compact_bridges = {}; } - }; // 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. // Together they define the plane where we have to iterate with the // given angles in the 'phis' vector - tbb::parallel_for(size_t(0), phis.size(), - [&phis, &hits, &m, sd, r_pin, r_back, s, a, b, c] - (size_t i) + ccr_par::enumerate(phis.begin(), phis.end(), + [&hits, &m, sd, r_pin, r_back, s, a, b, c] + (double phi, size_t i) { - double& phi = phis[i]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -1225,12 +1296,11 @@ class SLASupportTree::Algorithm { // Hit results std::array hits; - - tbb::parallel_for(size_t(0), phis.size(), - [&m, &phis, a, b, sd, dir, r, s, ins_check, &hits] - (size_t i) + + ccr_par::enumerate(phis.begin(), phis.end(), + [&m, a, b, sd, dir, r, s, ins_check, &hits] + (double phi, size_t i) { - double& phi = phis[i]; double sinphi = std::sin(phi); double cosphi = std::cos(phi); @@ -1458,7 +1528,7 @@ class SLASupportTree::Algorithm { if(nearest_id >= 0) { auto nearpillarID = unsigned(nearest_id); - if(nearpillarID < m_result.pillars().size()) { + if(nearpillarID < m_result.pillarcount()) { if(!connect_to_nearpillar(head, nearpillarID)) { nearest_id = -1; // continue searching spindex.remove(ne); // without the current pillar @@ -1646,46 +1716,52 @@ public: using libnest2d::opt::initvals; using libnest2d::opt::GeneticOptimizer; 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 lk(mutex); + container.emplace_back(val); + }; + + ccr::enumerate(filtered_indices.begin(), filtered_indices.end(), + [this, &nmls, addfn](unsigned fidx, size_t i) { m_thr(); - - fidx = filtered_indices[i]; + auto n = nmls.row(i); - + // for all normals we generate the spherical coordinates and // saturate the polar angle to 45 degrees from the bottom then // convert back to standard coordinates to get the new normal. // Then we just create a quaternion from the two normals // (Quaternion::FromTwoVectors) and apply the rotation to the // arrow head. - + double z = n(2); double r = 1.0; // for normalized vector double polar = std::acos(z / r); double azimuth = std::atan2(n(1), n(0)); - + // skip if the tilt is not sane if(polar >= PI - m_cfg.normal_cutoff_angle) { - + // We saturate the polar angle to 3pi/4 polar = std::max(polar, 3*PI / 4); - + // save the head (pinpoint) position Vec3d hp = m_points.row(fidx); - + double w = m_cfg.head_width_mm + m_cfg.head_back_radius_mm + 2*m_cfg.head_front_radius_mm; - + double pin_r = double(m_support_pts[fidx].head_front_radius); - + // Reassemble the now corrected normal auto nn = Vec3d(std::cos(azimuth) * std::sin(polar), std::sin(azimuth) * std::sin(polar), std::cos(polar)).normalized(); - + // check available distance EigenMesh3D::hit_result t = pinhead_mesh_intersect(hp, // touching point @@ -1693,37 +1769,37 @@ public: pin_r, m_cfg.head_back_radius_mm, w); - + if(t.distance() <= w) { - + // Let's try to optimize this angle, there might be a // viable normal that doesn't collide with the model // geometry and its very close to the default. - + StopCriteria stc; stc.max_iterations = m_cfg.optimizer_max_iterations; stc.relative_score_difference = m_cfg.optimizer_rel_score_diff; stc.stop_score = w; // space greater than w is enough GeneticOptimizer solver(stc); solver.seed(0); // we want deterministic behavior - + auto oresult = solver.optimize_max( [this, pin_r, w, hp](double plr, double azm) - { - auto n = Vec3d(std::cos(azm) * std::sin(plr), - std::sin(azm) * std::sin(plr), - std::cos(plr)).normalized(); + { + auto n = Vec3d(std::cos(azm) * std::sin(plr), + std::sin(azm) * std::sin(plr), + std::cos(plr)).normalized(); - double score = pinhead_mesh_intersect( 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 - ); + double score = pinhead_mesh_intersect( + 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 + ); + if(oresult.score > w) { polar = std::get<0>(oresult.optimum); azimuth = std::get<1>(oresult.optimum); @@ -1733,25 +1809,25 @@ public: t = oresult.score; } } - + // save the verified and corrected normal m_support_nmls.row(fidx) = nn; - + if (t.distance() > w) { // Check distance from ground, we might have zero elevation. if (hp(Z) + w * nn(Z) < m_result.ground_level) { - m_iheadless.emplace_back(fidx); + addfn(m_iheadless, fidx); } else { // mark the point for needing a head. - m_iheads.emplace_back(fidx); + addfn(m_iheads, fidx); } } else if (polar >= 3 * PI / 4) { // Headless supports do not tilt like the headed ones // so the normal should point almost to the ground. - m_iheadless.emplace_back(fidx); + addfn(m_iheadless, fidx); } } - } + }); m_thr(); } @@ -1939,11 +2015,17 @@ public: }; std::vector modelpillars; + ccr::Mutex mutex; // TODO: connect these to the ground pillars if possible - for(auto item : m_iheads_onmodel) { m_thr(); - unsigned idx = item.first; - EigenMesh3D::hit_result hit = item.second; + ccr::enumerate(m_iheads_onmodel.begin(), m_iheads_onmodel.end(), + [this, routedown, &modelpillars, &mutex] + (const std::pair &el, + size_t) + { + m_thr(); + unsigned idx = el.first; + EigenMesh3D::hit_result hit = el.second; auto& head = m_result.head(idx); Vec3d hjp = head.junction_point(); @@ -1952,7 +2034,7 @@ public: // Search nearby pillar // ///////////////////////////////////////////////////////////////// - if(search_pillar_and_connect(head)) { head.transform(); continue; } + if(search_pillar_and_connect(head)) { head.transform(); return; } // ///////////////////////////////////////////////////////////////// // Try straight path @@ -1974,7 +2056,7 @@ public: } 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 - routedown(head, bridgedir, d); continue; + routedown(head, bridgedir, d); return; } // ///////////////////////////////////////////////////////////////// @@ -2079,8 +2161,9 @@ public: pill.base = tailhead.mesh; // Experimental: add the pillar to the index for cascading + std::lock_guard lk(mutex); modelpillars.emplace_back(unsigned(pill.id)); - continue; + return; } // We have failed to route this head. @@ -2088,7 +2171,7 @@ public: << "Failed to route model facing support point." << " ID: " << idx; head.invalidate(); - } + }); for(auto pillid : modelpillars) { auto& pillar = m_result.pillar(pillid); @@ -2175,8 +2258,8 @@ public: // Search for the pair amongst the remembered pairs 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 if(neighborpillar.links >= neighbors) continue; @@ -2212,7 +2295,7 @@ public: // lonely pillars. One or even two additional pillar might get inserted // 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 // not just the index. @@ -2364,6 +2447,8 @@ public: 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 &support_points, @@ -2372,9 +2457,9 @@ bool SLASupportTree::generate(const std::vector &support_points, const Controller &ctl) { if(support_points.empty()) return false; - + Algorithm alg(cfg, mesh, support_points, *m_impl, ctl.cancelfn); - + // Let's define the individual steps of the processing. We can experiment // later with the ordering and the dependencies between them. enum Steps { @@ -2386,55 +2471,58 @@ bool SLASupportTree::generate(const std::vector &support_points, ROUTING_NONGROUND, CASCADE_PILLARS, HEADLESS, + MERGE_RESULT, DONE, ABORT, NUM_STEPS //... }; - + // Collect the algorithm steps into a nice sequence std::array, NUM_STEPS> program = { [] () { // Begin... // Potentially clear up the shared data (not needed for now) }, - + std::bind(&Algorithm::filter, &alg), - + std::bind(&Algorithm::add_pinheads, &alg), - + std::bind(&Algorithm::classify, &alg), - + std::bind(&Algorithm::routing_to_ground, &alg), - + std::bind(&Algorithm::routing_to_model, &alg), - + std::bind(&Algorithm::interconnect_pillars, &alg), - + std::bind(&Algorithm::routing_headless, &alg), - + + std::bind(&Algorithm::merge_result, &alg), + [] () { // Done }, - + [] () { // Abort } }; - + Steps pc = BEGIN; - + if(cfg.ground_facing_only) { program[ROUTING_NONGROUND] = []() { BOOST_LOG_TRIVIAL(info) - << "Skipping model-facing supports as requested."; + << "Skipping model-facing supports as requested."; }; program[HEADLESS] = []() { BOOST_LOG_TRIVIAL(info) << "Skipping headless stick generation as" " requested."; }; } - + // Let's define a simple automaton that will run our program. auto progress = [&ctl, &pc] () { static const std::array stepstr { @@ -2446,10 +2534,11 @@ bool SLASupportTree::generate(const std::vector &support_points, "Routing supports to model surface", "Interconnecting pillars", "Processing small holes", + "Merging support mesh", "Done", "Abort" }; - + static const std::array stepstate { 0, 10, @@ -2458,13 +2547,14 @@ bool SLASupportTree::generate(const std::vector &support_points, 60, 70, 80, - 90, + 85, + 99, 100, 0 }; - + if(ctl.stopcondition()) pc = ABORT; - + switch(pc) { case BEGIN: pc = FILTER; break; case FILTER: pc = PINHEADS; break; @@ -2473,20 +2563,22 @@ bool SLASupportTree::generate(const std::vector &support_points, case ROUTING_GROUND: pc = ROUTING_NONGROUND; break; case ROUTING_NONGROUND: pc = CASCADE_PILLARS; 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 ABORT: break; default: ; } + ctl.statuscb(stepstate[pc], stepstr[pc]); }; - + // Just here we run the computation... while(pc < DONE) { progress(); program[pc](); } - + return pc == ABORT; } @@ -2504,44 +2596,40 @@ void SLASupportTree::merged_mesh_with_pad(TriangleMesh &outmesh) const { outmesh.merge(get_pad()); } -std::vector SLASupportTree::slice(float layerh, float init_layerh) const +std::vector SLASupportTree::slice( + const std::vector &heights, float cr) const { - if(init_layerh < 0) init_layerh = layerh; - auto& stree = get(); - - const auto modelh = float(stree.full_height()); - auto gndlvl = float(this->m_impl->ground_level); - const Pad& pad = m_impl->pad(); - if(!pad.empty()) gndlvl -= float(get_pad_elevation(pad.cfg)); - - std::vector heights; - heights.reserve(size_t(modelh/layerh) + 1); - - for(float h = gndlvl + init_layerh; h < gndlvl + modelh; h += layerh) { - heights.emplace_back(h); + const TriangleMesh &sup_mesh = m_impl->merged_mesh(); + const TriangleMesh &pad_mesh = get_pad(); + + std::vector sup_slices; + if (!sup_mesh.empty()) { + TriangleMeshSlicer sup_slicer(&sup_mesh); + sup_slicer.slice(heights, cr, &sup_slices, m_impl->ctl().cancelfn); } - - TriangleMesh fullmesh = m_impl->merged_mesh(); - fullmesh.merge(get_pad()); - if (!fullmesh.empty()) fullmesh.require_shared_vertices(); - TriangleMeshSlicer slicer(&fullmesh); - std::vector ret; - slicer.slice(heights, 0.f, &ret, get().ctl().cancelfn); - - return ret; -} - -std::vector SLASupportTree::slice(const std::vector &heights, - float cr) const -{ - TriangleMesh fullmesh = m_impl->merged_mesh(); - fullmesh.merge(get_pad()); - if (!fullmesh.empty()) fullmesh.require_shared_vertices(); - TriangleMeshSlicer slicer(&fullmesh); - std::vector ret; - slicer.slice(heights, cr, &ret, get().ctl().cancelfn); - - return ret; + + auto bb = pad_mesh.bounding_box(); + auto maxzit = std::upper_bound(heights.begin(), heights.end(), bb.max.z()); + + auto padgrid = reserve_vector(heights.end() - maxzit); + std::copy(heights.begin(), maxzit, std::back_inserter(padgrid)); + + std::vector pad_slices; + if (!pad_mesh.empty()) { + TriangleMeshSlicer pad_slicer(&pad_mesh); + pad_slicer.slice(padgrid, cr, &pad_slices, m_impl->ctl().cancelfn); + } + + size_t len = std::min(heights.size(), pad_slices.size()); + len = std::min(len, sup_slices.size()); + + for (size_t i = 0; i < len; ++i) { + std::copy(pad_slices[i].begin(), pad_slices[i].end(), + std::back_inserter(sup_slices[i])); + pad_slices[i] = {}; + } + + return sup_slices; } const TriangleMesh &SLASupportTree::add_pad(const ExPolygons& modelbase, @@ -2568,16 +2656,6 @@ SLASupportTree::SLASupportTree(const std::vector &points, { m_impl->ground_level = emesh.ground_level() - cfg.object_elevation_mm; 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(*c.m_impl); - return *this; } SLASupportTree::~SLASupportTree() {} diff --git a/src/libslic3r/SLA/SLASupportTree.hpp b/src/libslic3r/SLA/SLASupportTree.hpp index 8602d8a46..d7f15c17b 100644 --- a/src/libslic3r/SLA/SLASupportTree.hpp +++ b/src/libslic3r/SLA/SLASupportTree.hpp @@ -171,9 +171,9 @@ public: const EigenMesh3D& em, const SupportConfig& cfg = {}, const Controller& ctl = {}); - - SLASupportTree(const SLASupportTree&); - SLASupportTree& operator=(const SLASupportTree&); + + SLASupportTree(const SLASupportTree&) = delete; + SLASupportTree& operator=(const SLASupportTree&) = delete; ~SLASupportTree(); @@ -183,9 +183,6 @@ public: void merged_mesh_with_pad(TriangleMesh&) const; - /// Get the sliced 2d layers of the support geometry. - std::vector slice(float layerh, float init_layerh = -1.0) const; - std::vector slice(const std::vector &, float closing_radius) const; diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp index b3a075e26..5669307ac 100644 --- a/src/libslic3r/SLAPrint.cpp +++ b/src/libslic3r/SLAPrint.cpp @@ -928,12 +928,14 @@ void SLAPrint::process() double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0; 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; if(std::round(m_report_status.status()) < std::round(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; } -void SLAPrint::StatusReporter::operator()( - SLAPrint &p, double st, const std::string &msg, unsigned flags) +void SLAPrint::StatusReporter::operator()(SLAPrint & p, + double st, + const std::string &msg, + unsigned flags, + const std::string &logmsg) { 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); } diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp index 9620e9b68..c4e58ab39 100644 --- a/src/libslic3r/SLAPrint.hpp +++ b/src/libslic3r/SLAPrint.hpp @@ -447,16 +447,21 @@ private: // Estimated print time, material consumed. SLAPrintStatistics m_print_statistics; - - class StatusReporter { + + class StatusReporter + { double m_st = 0; + public: - void operator() (SLAPrint& p, double st, const std::string& msg, - unsigned flags = SlicingStatus::DEFAULT); + void operator()(SLAPrint & p, + double st, + const std::string &msg, + unsigned flags = SlicingStatus::DEFAULT, + const std::string &logmsg = ""); + double status() const { return m_st; } } m_report_status; - friend SLAPrintObject; }; diff --git a/src/libslic3r/Technologies.hpp b/src/libslic3r/Technologies.hpp index ae43369d2..51d092094 100644 --- a/src/libslic3r/Technologies.hpp +++ b/src/libslic3r/Technologies.hpp @@ -32,13 +32,4 @@ #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_ diff --git a/src/slic3r/GUI/2DBed.cpp b/src/slic3r/GUI/2DBed.cpp index a339f3e66..d2075f673 100644 --- a/src/slic3r/GUI/2DBed.cpp +++ b/src/slic3r/GUI/2DBed.cpp @@ -18,10 +18,9 @@ wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(25 * wxGetApp().em_unit(), - #ifdef __APPLE__ m_user_drawn_background = false; #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& shape) { wxAutoBufferedPaintDC dc(this); auto cw = GetSize().GetWidth(); @@ -41,7 +40,7 @@ void Bed_2D::repaint() dc.DrawRectangle(rect.GetLeft(), rect.GetTop(), rect.GetWidth(), rect.GetHeight()); } - if (m_bed_shape.empty()) + if (shape.empty()) return; // reduce size to have some space around the drawn shape @@ -52,10 +51,9 @@ void Bed_2D::repaint() auto ccenter = cbb.center(); // get bounding box of bed shape in G - code coordinates - auto bed_shape = m_bed_shape; - auto bed_polygon = Polygon::new_scale(m_bed_shape); - auto bb = BoundingBoxf(m_bed_shape); - bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area + auto bed_polygon = Polygon::new_scale(shape); + auto bb = BoundingBoxf(shape); + bb.merge(Vec2d(0, 0)); // origin needs to be in the visible area auto bw = bb.size()(0); auto bh = bb.size()(1); auto bcenter = bb.center(); @@ -73,8 +71,8 @@ void Bed_2D::repaint() // draw bed fill dc.SetBrush(wxBrush(wxColour(255, 255, 255), wxBRUSHSTYLE_SOLID)); wxPointList pt_list; - for (auto pt: m_bed_shape) - { + for (auto pt : shape) + { Point pt_pix = to_pixels(pt, ch); 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 -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; 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; Refresh(); diff --git a/src/slic3r/GUI/2DBed.hpp b/src/slic3r/GUI/2DBed.hpp index a61fb313d..80926bea7 100644 --- a/src/slic3r/GUI/2DBed.hpp +++ b/src/slic3r/GUI/2DBed.hpp @@ -17,16 +17,13 @@ class Bed_2D : public wxPanel Vec2d m_shift = Vec2d::Zero(); Vec2d m_pos = Vec2d::Zero(); - Point to_pixels(Vec2d point, int height); - void repaint(); - void set_pos(Vec2d pos); + Point to_pixels(const Vec2d& point, int height); + void set_pos(const Vec2d& pos); public: - Bed_2D(wxWindow* parent); - ~Bed_2D() {} + explicit Bed_2D(wxWindow* parent); - std::vector m_bed_shape; - + void repaint(const std::vector& shape); }; diff --git a/src/slic3r/GUI/3DBed.cpp b/src/slic3r/GUI/3DBed.cpp index 482cd58ad..97e200b38 100644 --- a/src/slic3r/GUI/3DBed.cpp +++ b/src/slic3r/GUI/3DBed.cpp @@ -14,6 +14,7 @@ #include #include +#include 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) { -#if ENABLE_TEXTURES_FROM_SVG m_vertices.clear(); 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(v_size, 0.0f); - if (generate_tex_coords) - m_tex_coords = std::vector(t_size, 0.0f); - - float min_x = unscale(triangles[0].points[0](0)); - float min_y = unscale(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(p(0)); - float y = unscale(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; } bool GeometryBuffer::set_from_lines(const Lines& lines, float z) { -#if ENABLE_TEXTURES_FROM_SVG m_vertices.clear(); 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; ++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(size, 0.0f); - - unsigned int coord = 0; - for (const Line& l : lines) - { - m_vertices[coord++] = unscale(l.a(0)); - m_vertices[coord++] = unscale(l.a(1)); - m_vertices[coord++] = z; - m_vertices[coord++] = unscale(l.b(0)); - m_vertices[coord++] = unscale(l.b(1)); - m_vertices[coord++] = z; - } -#endif // ENABLE_TEXTURES_FROM_SVG return true; } -#if ENABLE_TEXTURES_FROM_SVG const float* GeometryBuffer::get_vertices_data() const { 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::ArrowBaseRadius = 2.5 * Bed3D::Axes::Radius; @@ -274,22 +188,41 @@ void Bed3D::Axes::render_axis(double length) const Bed3D::Bed3D() : m_type(Custom) -#if ENABLE_TEXTURES_FROM_SVG + , m_custom_texture("") + , m_custom_model("") , m_requires_canvas_update(false) , m_vbo_id(0) -#endif // ENABLE_TEXTURES_FROM_SVG , 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); - 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. return false; m_shape = shape; + m_custom_texture = cst_texture; + m_custom_model = cst_model; m_type = new_type; 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; -#if ENABLE_TEXTURES_FROM_SVG reset(); -#endif // ENABLE_TEXTURES_FROM_SVG + m_texture.reset(); + m_model.reset(); // Set the origin and size for painting of the coordinate system axes. 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); } -#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; + render_axes(); + switch (m_type) { case MK2: @@ -354,51 +288,11 @@ void Bed3D::render(GLCanvas3D* canvas, float theta, float scale_factor) const default: case Custom: { - render_custom(); + render_custom(canvas, theta > 90.0f); 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 { @@ -423,7 +317,7 @@ void Bed3D::calc_triangles(const ExPolygon& poly) Polygons 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"); } @@ -495,31 +389,76 @@ Bed3D::EType Bed3D::detect_type(const Pointfs& shape) const return type; } -#if ENABLE_TEXTURES_FROM_SVG -void Bed3D::render_prusa(GLCanvas3D* canvas, const std::string &key, bool bottom) const +void Bed3D::render_axes() 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 - GLint max_tex_size = GLCanvas3DManager::get_gl_info().get_max_tex_size(); + render_texture(m_custom_texture.empty() ? resources_dir() + "/icons/bed/" + key + ".svg" : m_custom_texture, bottom, canvas); +} - 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)) { - // 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_custom(); - return; - } + m_texture.reset(); - // starts generating the main texture, compression will run asynchronously - if (!m_texture.load_from_svg_file(filename, true, true, true, max_tex_size)) + if (boost::algorithm::iends_with(filename, ".svg")) { - 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; } } @@ -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()) { - if (canvas != nullptr) - canvas->stop_keeping_dirty(); - + canvas.stop_keeping_dirty(); m_requires_canvas_update = false; } - if (!bottom) + if (m_triangles.get_vertices_count() > 0) { - 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); + if (m_shader.get_shader_program_id() == 0) + m_shader.init("printbed.vs", "printbed.fs"); - m_model.center_around(offset); - - // update extended bounding box - calc_bounding_boxes(); - } - - if (!m_model.get_filename().empty()) + if (m_shader.is_initialized()) { - glsafe(::glEnable(GL_LIGHTING)); - m_model.render(); - glsafe(::glDisable(GL_LIGHTING)); - } - } + m_shader.start_using(); + m_shader.set_uniform("transparent_background", bottom); + 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 (triangles_vcount > 0) - { - if (m_vbo_id == 0) - { - glsafe(::glGenBuffers(1, &m_vbo_id)); + if (m_vbo_id == 0) + { + glsafe(::glGenBuffers(1, &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)); + 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(::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(::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)); + + 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 - m_texture.reset(); -#else - m_top_texture.reset(); - m_bottom_texture.reset(); -#endif // ENABLE_TEXTURES_FROM_SVG + if (filename.empty()) + return; - unsigned int triangles_vcount = m_triangles.get_vertices_count(); - if (triangles_vcount > 0) + if ((m_model.get_filename() != filename) && m_model.init_from_file(filename)) + { + // 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(::glDisable(GL_DEPTH_TEST)); - - 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)); + m_model.render(); 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() { if (m_vbo_id > 0) @@ -830,7 +638,6 @@ void Bed3D::reset() m_vbo_id = 0; } } -#endif // ENABLE_TEXTURES_FROM_SVG } // GUI } // Slic3r \ No newline at end of file diff --git a/src/slic3r/GUI/3DBed.hpp b/src/slic3r/GUI/3DBed.hpp index 6c8913a9b..c9a30e6ec 100644 --- a/src/slic3r/GUI/3DBed.hpp +++ b/src/slic3r/GUI/3DBed.hpp @@ -3,9 +3,7 @@ #include "GLTexture.hpp" #include "3DScene.hpp" -#if ENABLE_TEXTURES_FROM_SVG #include "GLShader.hpp" -#endif // ENABLE_TEXTURES_FROM_SVG class GLUquadric; typedef class GLUquadric GLUquadricObj; @@ -17,7 +15,6 @@ class GLCanvas3D; class GeometryBuffer { -#if ENABLE_TEXTURES_FROM_SVG struct Vertex { float position[3]; @@ -31,27 +28,17 @@ class GeometryBuffer }; std::vector m_vertices; -#else - std::vector m_vertices; - std::vector m_tex_coords; -#endif // ENABLE_TEXTURES_FROM_SVG public: bool set_from_triangles(const Polygons& triangles, float z, bool generate_tex_coords); bool set_from_lines(const Lines& lines, float z); -#if ENABLE_TEXTURES_FROM_SVG 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_vertex_data_size() const { return (unsigned int)(5 * sizeof(float)); } size_t get_position_offset() const { return 0; } 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(); } -#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 @@ -87,12 +74,13 @@ public: private: EType m_type; Pointfs m_shape; + std::string m_custom_texture; + std::string m_custom_model; mutable BoundingBoxf3 m_bounding_box; mutable BoundingBoxf3 m_extended_bounding_box; Polygon m_polygon; GeometryBuffer m_triangles; GeometryBuffer m_gridlines; -#if ENABLE_TEXTURES_FROM_SVG mutable GLTexture m_texture; // temporary texture shown until the main texture has still no levels compressed mutable GLTexture m_temp_texture; @@ -100,10 +88,6 @@ private: mutable bool m_requires_canvas_update; mutable Shader m_shader; 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; Axes m_axes; @@ -111,9 +95,7 @@ private: public: Bed3D(); -#if ENABLE_TEXTURES_FROM_SVG ~Bed3D() { reset(); } -#endif // ENABLE_TEXTURES_FROM_SVG EType get_type() const { return m_type; } @@ -122,34 +104,26 @@ public: const Pointfs& get_shape() const { return m_shape; } // 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; } bool contains(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; -#else - void render(float theta, float scale_factor) const; -#endif // ENABLE_TEXTURES_FROM_SVG - void render_axes() const; + void render(GLCanvas3D& canvas, float theta, float scale_factor) const; private: void calc_bounding_boxes() const; void calc_triangles(const ExPolygon& poly); void calc_gridlines(const ExPolygon& poly, const BoundingBox& bed_bbox); EType detect_type(const Pointfs& shape) const; -#if ENABLE_TEXTURES_FROM_SVG - void render_prusa(GLCanvas3D* canvas, const std::string& key, bool bottom) const; - void render_prusa_shader(bool transparent) const; -#else - void render_prusa(const std::string& key, float theta) const; -#endif // ENABLE_TEXTURES_FROM_SVG - void render_custom() const; -#if ENABLE_TEXTURES_FROM_SVG + void render_axes() const; + void render_prusa(GLCanvas3D& canvas, const std::string& key, bool bottom) const; + void render_texture(const std::string& filename, bool bottom, GLCanvas3D& canvas) const; + void render_model(const std::string& filename) const; + void render_custom(GLCanvas3D& canvas, bool bottom) const; + void render_default(bool bottom) const; void reset(); -#endif // ENABLE_TEXTURES_FROM_SVG }; } // GUI diff --git a/src/slic3r/GUI/3DScene.cpp b/src/slic3r/GUI/3DScene.cpp index 9e3eaf41d..dba595846 100644 --- a/src/slic3r/GUI/3DScene.cpp +++ b/src/slic3r/GUI/3DScene.cpp @@ -1736,13 +1736,7 @@ bool GLBed::on_init_from_file(const std::string& filename) m_filename = filename; - ModelObject* model_object = model.objects.front(); - model_object->center_around_origin(); - - TriangleMesh mesh = model.mesh(); - mesh.repair(); - - m_volume.indexed_vertex_array.load_mesh(mesh); + m_volume.indexed_vertex_array.load_mesh(model.mesh()); float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; set_color(color, 4); diff --git a/src/slic3r/GUI/BedShapeDialog.cpp b/src/slic3r/GUI/BedShapeDialog.cpp index d52204d4a..1177c9180 100644 --- a/src/slic3r/GUI/BedShapeDialog.cpp +++ b/src/slic3r/GUI/BedShapeDialog.cpp @@ -4,23 +4,25 @@ #include #include #include +#include #include "libslic3r/BoundingBox.hpp" #include "libslic3r/Model.hpp" #include "libslic3r/Polygon.hpp" #include "boost/nowide/iostream.hpp" +#include #include namespace Slic3r { 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()); 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); main_sizer->Add(m_panel, 1, wxEXPAND); @@ -51,59 +53,69 @@ void BedShapeDialog::on_dpi_changed(const wxRect &suggested_rect) 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"))); + sbsizer->GetStaticBox()->SetFont(wxGetApp().bold_font()); // shape options - m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, - wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); + m_shape_options_book = new wxChoicebook(this, wxID_ANY, wxDefaultPosition, wxSize(25*wxGetApp().em_unit(), -1), wxCHB_TOP); sbsizer->Add(m_shape_options_book); auto optgroup = init_shape_options_page(_(L("Rectangular"))); - ConfigOptionDef def; - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); - def.label = L("Size"); - def.tooltip = L("Size in X and Y of the rectangular plate."); - Option option(def, "rect_size"); - optgroup->append_single_option_line(option); + ConfigOptionDef def; + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(200, 200) }); + def.label = L("Size"); + def.tooltip = L("Size in X and Y of the rectangular plate."); + Option option(def, "rect_size"); + optgroup->append_single_option_line(option); - def.type = coPoints; - def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); - def.label = L("Origin"); - def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); - option = Option(def, "rect_origin"); - optgroup->append_single_option_line(option); + def.type = coPoints; + def.set_default_value(new ConfigOptionPoints{ Vec2d(0, 0) }); + def.label = L("Origin"); + def.tooltip = L("Distance of the 0,0 G-code coordinate from the front left corner of the rectangle."); + option = Option(def, "rect_origin"); + optgroup->append_single_option_line(option); - optgroup = init_shape_options_page(_(L("Circular"))); - def.type = coFloat; - def.set_default_value(new ConfigOptionFloat(200)); - def.sidetext = L("mm"); - def.label = L("Diameter"); - def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); - option = Option(def, "diameter"); - optgroup->append_single_option_line(option); + optgroup = init_shape_options_page(_(L("Circular"))); + def.type = coFloat; + def.set_default_value(new ConfigOptionFloat(200)); + def.sidetext = L("mm"); + def.label = L("Diameter"); + def.tooltip = L("Diameter of the print bed. It is assumed that origin (0,0) is located in the center."); + option = Option(def, "diameter"); + optgroup->append_single_option_line(option); - optgroup = init_shape_options_page(_(L("Custom"))); - Line line{ "", "" }; - line.full_width = 1; - line.widget = [this](wxWindow* parent) { - auto shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); - wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); - shape_sizer->Add(shape_btn, 1, wxEXPAND); + optgroup = init_shape_options_page(_(L("Custom"))); + Line line{ "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + wxButton* shape_btn = new wxButton(parent, wxID_ANY, _(L("Load shape from STL..."))); + wxSizer* shape_sizer = new wxBoxSizer(wxHORIZONTAL); + shape_sizer->Add(shape_btn, 1, wxEXPAND); - wxSizer* sizer = new wxBoxSizer(wxVERTICAL); - sizer->Add(shape_sizer, 1, wxEXPAND); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + sizer->Add(shape_sizer, 1, wxEXPAND); - shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) - { - load_stl(); - })); + shape_btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent& e) + { + load_stl(); + })); - return sizer; - }; - optgroup->append_line(line); + return sizer; + }; + optgroup->append_line(line); + + wxPanel* texture_panel = init_texture_panel(); + wxPanel* model_panel = init_model_panel(); Bind(wxEVT_CHOICEBOOK_PAGE_CHANGED, ([this](wxCommandEvent& e) { @@ -112,13 +124,17 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // right pane with preview canvas 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 - auto top_sizer = new wxBoxSizer(wxHORIZONTAL); - top_sizer->Add(sbsizer, 0, wxEXPAND | wxLEFT | wxTOP | wxBOTTOM, 10); - if (m_canvas) - top_sizer->Add(m_canvas, 1, wxEXPAND | wxALL, 10) ; + wxSizer* left_sizer = new wxBoxSizer(wxVERTICAL); + left_sizer->Add(sbsizer, 0, wxEXPAND); + left_sizer->Add(texture_panel, 1, wxEXPAND); + left_sizer->Add(model_panel, 1, wxEXPAND); + + 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); @@ -134,20 +150,155 @@ void BedShapePanel::build_panel(ConfigOptionPoints* default_pt) // Create a panel for a rectangular / circular / custom bed shape. ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& title) { - auto panel = new wxPanel(m_shape_options_book); - ConfigOptionsGroupShp optgroup; - optgroup = std::make_shared(panel, _(L("Settings"))); + wxPanel* panel = new wxPanel(m_shape_options_book); + ConfigOptionsGroupShp optgroup = std::make_shared(panel, _(L("Settings"))); optgroup->label_width = 10; - optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { - update_shape(); - }; - - m_optgroups.push_back(optgroup); - panel->SetSizerAndFit(optgroup->sizer); - m_shape_options_book->AddPage(panel, title); + optgroup->m_on_change = [this](t_config_option_key opt_key, boost::any value) { + update_shape(); + }; + + m_optgroups.push_back(optgroup); + panel->SetSizerAndFit(optgroup->sizer); + 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(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(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(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(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. @@ -155,20 +306,20 @@ ConfigOptionsGroupShp BedShapePanel::init_shape_options_page(const wxString& tit // Deduce the bed shape type(rect, circle, custom) // This routine shall be smart enough if the user messes up // 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 ? - if (points->size() == 4) { - auto lines = polygon.lines(); + if (points.size() == 4) { + auto lines = polygon.lines(); if (lines[0].parallel_to(lines[2]) && lines[1].parallel_to(lines[3])) { // okay, it's a rectangle // find origin coordf_t x_min, x_max, y_min, y_max; - x_max = x_min = points->values[0](0); - y_max = y_min = points->values[0](1); - for (auto pt : points->values) + x_max = x_min = points.values[0](0); + y_max = y_min = points.values[0](1); + for (auto pt : points.values) { x_min = std::min(x_min, pt(0)); x_max = std::max(x_max, pt(0)); @@ -219,8 +370,8 @@ void BedShapePanel::set_shape(ConfigOptionPoints* points) } } - if (points->size() < 3) { - // Invalid polygon.Revert to default bed dimensions. + if (points.size() < 3) { + // Invalid polygon.Revert to default bed dimensions. m_shape_options_book->SetSelection(SHAPE_RECTANGULAR); auto optgroup = m_optgroups[SHAPE_RECTANGULAR]; 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. m_shape_options_book->SetSelection(SHAPE_CUSTOM); // 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(); } @@ -277,11 +428,11 @@ void BedShapePanel::update_shape() x1 -= dx; y0 -= dy; y1 -= dy; - m_canvas->m_bed_shape = { Vec2d(x0, y0), - Vec2d(x1, y0), - Vec2d(x1, y1), - Vec2d(x0, y1)}; - } + m_shape = { Vec2d(x0, y0), + Vec2d(x1, y0), + Vec2d(x1, y1), + Vec2d(x0, y1) }; + } else if(page_idx == SHAPE_CIRCULAR) { double diameter; try{ @@ -293,16 +444,16 @@ void BedShapePanel::update_shape() if (diameter == 0.0) return ; auto r = diameter / 2; auto twopi = 2 * PI; - auto edges = 60; - std::vector points; - for (size_t i = 1; i <= 60; ++i) { - auto angle = i * twopi / edges; + auto edges = 72; + std::vector points; + for (size_t i = 1; i <= edges; ++i) { + auto angle = i * twopi / edges; 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) - m_canvas->m_bed_shape = m_loaded_bed_shape; + m_shape = m_loaded_shape; update_preview(); } @@ -310,27 +461,27 @@ void BedShapePanel::update_shape() // Loads an stl file, projects it to the XY plane and calculates a polygon. void BedShapePanel::load_stl() { - auto dialog = new wxFileDialog(this, _(L("Choose a file to import bed shape from (STL/OBJ/AMF/3MF/PRUSA):")), "", "", - file_wildcards(FT_MODEL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); - if (dialog->ShowModal() != wxID_OK) { - dialog->Destroy(); - return; - } - wxArrayString input_file; - dialog->GetPaths(input_file); - dialog->Destroy(); + wxFileDialog dialog(this, _(L("Choose an STL file to import bed shape from:")), "", "", file_wildcards(FT_STL), wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() != wxID_OK) + return; - 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; try { - 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); + model = Model::read_from_file(file_name); } + catch (std::exception &) { + show_error(this, _(L("Error! Invalid model"))); + return; + } auto mesh = model.mesh(); auto expolygons = mesh.horizontal_projection(); @@ -349,7 +500,53 @@ void BedShapePanel::load_stl() for (auto pt : polygon.points) 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(); } diff --git a/src/slic3r/GUI/BedShapeDialog.hpp b/src/slic3r/GUI/BedShapeDialog.hpp index 6600a1c84..bf12cc893 100644 --- a/src/slic3r/GUI/BedShapeDialog.hpp +++ b/src/slic3r/GUI/BedShapeDialog.hpp @@ -16,27 +16,40 @@ namespace GUI { using ConfigOptionsGroupShp = std::shared_ptr; class BedShapePanel : public wxPanel { + static const std::string NONE; + static const std::string EMPTY_STRING; + Bed_2D* m_canvas; - std::vector m_loaded_bed_shape; + std::vector m_shape; + std::vector m_loaded_shape; + std::string m_custom_texture; + std::string m_custom_model; public: - BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY) {} - ~BedShapePanel() {} + BedShapePanel(wxWindow* parent) : wxPanel(parent, wxID_ANY), m_custom_texture(NONE), m_custom_model(NONE) {} - 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& 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); - void set_shape(ConfigOptionPoints* points); - void update_preview(); + wxPanel* init_texture_panel(); + wxPanel* init_model_panel(); + void set_shape(const ConfigOptionPoints& points); + void update_preview(); void update_shape(); void load_stl(); - - // Returns the resulting bed shape polygon. This value will be stored to the ini file. - std::vector GetValue() { return m_canvas->m_bed_shape; } + void load_texture(); + void load_model(); wxChoicebook* m_shape_options_book; std::vector m_optgroups; + friend class BedShapeDialog; }; class BedShapeDialog : public DPIDialog @@ -45,10 +58,12 @@ class BedShapeDialog : public DPIDialog public: BedShapeDialog(wxWindow* parent) : DPIDialog(parent, wxID_ANY, _(L("Bed Shape")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) {} - ~BedShapeDialog() {} - void build_dialog(ConfigOptionPoints* default_pt); - std::vector GetValue() { return m_panel->GetValue(); } + void build_dialog(const ConfigOptionPoints& default_pt, const ConfigOptionString& custom_texture, const ConfigOptionString& custom_model); + + const std::vector& 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: void on_dpi_changed(const wxRect &suggested_rect) override; diff --git a/src/slic3r/GUI/ConfigWizard.cpp b/src/slic3r/GUI/ConfigWizard.cpp index 8b08f6f7f..6a70cb9fd 100644 --- a/src/slic3r/GUI/ConfigWizard.cpp +++ b/src/slic3r/GUI/ConfigWizard.cpp @@ -532,15 +532,21 @@ PageBedShape::PageBedShape(ConfigWizard *parent) { append_text(_(L("Set the shape of your printer's bed."))); - shape_panel->build_panel(wizard_p()->custom_config->option("bed_shape")); + shape_panel->build_panel(*wizard_p()->custom_config->option("bed_shape"), + *wizard_p()->custom_config->option("bed_custom_texture"), + *wizard_p()->custom_config->option("bed_custom_model")); + append(shape_panel); } void PageBedShape::apply_custom_config(DynamicPrintConfig &config) { - const auto points(shape_panel->GetValue()); - auto *opt = new ConfigOptionPoints(points); - config.set_key_value("bed_shape", opt); + const std::vector& points = shape_panel->get_shape(); + const std::string& custom_texture = shape_panel->get_custom_texture(); + 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) @@ -1086,7 +1092,7 @@ ConfigWizard::ConfigWizard(wxWindow *parent, RunReason reason) p->load_vendors(); 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); diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index a24455205..5e505bb41 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -116,87 +116,6 @@ void Size::set_scale_factor(int 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() : m_enabled(false) , 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) { 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; } @@ -415,7 +334,7 @@ void GLCanvas3D::LayersEditing::_render_reset_texture(const Rect& reset_rect) co if (m_reset_texture.get_id() == 0) { 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; } @@ -1642,19 +1561,10 @@ void GLCanvas3D::render() glsafe(::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)); _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_sla_slices(); _render_selection(); - - _render_axes(); - - if (!early_bed_render) - _render_bed(theta); + _render_bed(theta); #if ENABLE_RENDER_SELECTION_CENTER _render_selection_center(); @@ -3986,16 +3896,7 @@ void GLCanvas3D::_render_bed(float theta) const #if ENABLE_RETINA_GL scale_factor = m_retina_helper->get_scale_factor(); #endif // ENABLE_RETINA_GL -#if ENABLE_TEXTURES_FROM_SVG - m_bed.render(const_cast(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(); + m_bed.render(const_cast(*this), theta, scale_factor); } void GLCanvas3D::_render_objects() const diff --git a/src/slic3r/GUI/GLCanvas3D.hpp b/src/slic3r/GUI/GLCanvas3D.hpp index 941eeaaf6..c9803f9db 100644 --- a/src/slic3r/GUI/GLCanvas3D.hpp +++ b/src/slic3r/GUI/GLCanvas3D.hpp @@ -158,32 +158,6 @@ class GLCanvas3D 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 { public: @@ -688,7 +662,6 @@ private: void _rectangular_selection_picking_pass() const; void _render_background() const; void _render_bed(float theta) const; - void _render_axes() const; void _render_objects() const; void _render_selection() const; #if ENABLE_RENDER_SELECTION_CENTER diff --git a/src/slic3r/GUI/GLShader.cpp b/src/slic3r/GUI/GLShader.cpp index 7f3bc0893..577f6e1b5 100644 --- a/src/slic3r/GUI/GLShader.cpp +++ b/src/slic3r/GUI/GLShader.cpp @@ -268,7 +268,6 @@ sub SetMatrix } */ -#if ENABLE_TEXTURES_FROM_SVG Shader::Shader() : m_shader(nullptr) { @@ -363,6 +362,5 @@ void Shader::reset() m_shader = nullptr; } } -#endif // ENABLE_TEXTURES_FROM_SVG } // namespace Slic3r diff --git a/src/slic3r/GUI/GLShader.hpp b/src/slic3r/GUI/GLShader.hpp index 58e2678d0..df2a23f15 100644 --- a/src/slic3r/GUI/GLShader.hpp +++ b/src/slic3r/GUI/GLShader.hpp @@ -37,7 +37,6 @@ public: std::string last_error; }; -#if ENABLE_TEXTURES_FROM_SVG class Shader { GLShader* m_shader; @@ -66,7 +65,6 @@ public: private: void reset(); }; -#endif // ENABLE_TEXTURES_FROM_SVG } diff --git a/src/slic3r/GUI/GLTexture.cpp b/src/slic3r/GUI/GLTexture.cpp index 171a5c885..f3421f150 100644 --- a/src/slic3r/GUI/GLTexture.cpp +++ b/src/slic3r/GUI/GLTexture.cpp @@ -134,7 +134,7 @@ GLTexture::~GLTexture() 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(); @@ -142,7 +142,7 @@ bool GLTexture::load_from_file(const std::string& filename, bool use_mipmaps, bo return false; 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 return false; } @@ -354,49 +354,10 @@ void GLTexture::render_sub_texture(unsigned int tex_id, float left, float right, 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(); - int h = image.GetHeight(); - GLint level = 0; - std::vector data(w * h * 4, 0); + bool compression_enabled = (compression_type != None) && GLEW_EXT_texture_compression_s3tc; - 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. wxImage image; 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_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) { 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(::glGenTextures(1, &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 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) { // we manually generate mipmaps because glGenerateMipmap() function is not reliable on all graphics cards - unsigned int levels_count = generate_mipmaps(image, compress); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, levels_count)); - glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)); + int lod_w = m_width; + int lod_h = m_height; + 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 { 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_MAG_FILTER, GL_LINEAR)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); m_source = filename; + if (compression_enabled && (compression_type == MultiThreaded)) + // start asynchronous compression + m_compressor.start_compressing(); + 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); if (image == nullptr) { -// printf("Could not open SVG image.\n"); reset(); return false; } @@ -507,7 +562,6 @@ bool GLTexture::load_from_svg(const std::string& filename, bool use_mipmaps, boo NSVGrasterizer* rast = nsvgCreateRasterizer(); if (rast == nullptr) { -// printf("Could not init rasterizer.\n"); nsvgDelete(image); reset(); 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_MAX_LEVEL, 0)); } + glsafe(::glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); glsafe(::glBindTexture(GL_TEXTURE_2D, 0)); diff --git a/src/slic3r/GUI/GLTexture.hpp b/src/slic3r/GUI/GLTexture.hpp index f4dc05a16..7fc5b8fcf 100644 --- a/src/slic3r/GUI/GLTexture.hpp +++ b/src/slic3r/GUI/GLTexture.hpp @@ -48,6 +48,13 @@ namespace GUI { }; public: + enum ECompressionType : unsigned char + { + None, + SingleThreaded, + MultiThreaded + }; + struct UV { float u; @@ -75,7 +82,7 @@ namespace GUI { 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); // meanings of states: (std::pair) // 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_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: - 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); friend class Compressor; diff --git a/src/slic3r/GUI/GLToolbar.cpp b/src/slic3r/GUI/GLToolbar.cpp index ca5248417..63387bf2d 100644 --- a/src/slic3r/GUI/GLToolbar.cpp +++ b/src/slic3r/GUI/GLToolbar.cpp @@ -174,7 +174,7 @@ bool GLToolbar::init(const BackgroundTexture::Metadata& background_texture) bool res = false; 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) m_background_texture.metadata = background_texture; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 1a864917d..8a785760c 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -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_PROJECT */ "Project files (*.3mf, *.amf)|*.3mf;*.3MF;*.amf;*.AMF", - /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", - /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", - /* FT_PNGZIP */"Masked SLA files (*.sl1)|*.sl1;*.SL1", + /* FT_INI */ "INI files (*.ini)|*.ini;*.INI", + /* FT_SVG */ "SVG files (*.svg)|*.svg;*.SVG", + + /* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG", + + /* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1", }; std::string out = defaults[file_type]; diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 5c897a701..b74762586 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -44,6 +44,9 @@ enum FileType FT_INI, FT_SVG, + + FT_TEX, + FT_PNGZIP, FT_SIZE, diff --git a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp index d5a3a93c4..38af63464 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmosManager.cpp @@ -43,7 +43,7 @@ bool GLGizmosManager::init() 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(); return false; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 387fd34d2..38ad58037 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1853,7 +1853,7 @@ struct Plater::priv // 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. // 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_all() const; @@ -1914,7 +1914,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) : q(q) , main_frame(main_frame) , 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", "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", @@ -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_LAYERSEDITING, &priv::on_action_layersediting, this); 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("bed_shape")->values); }); + view3D_canvas->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) + { + set_bed_shape(config->option("bed_shape")->values, + config->option("bed_custom_texture")->value, + config->option("bed_custom_model")->value); + }); // Preview events: 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("bed_shape")->values); }); + preview->get_wxglcanvas()->Bind(EVT_GLCANVAS_UPDATE_BED_SHAPE, [this](SimpleEvent&) + { + set_bed_shape(config->option("bed_shape")->values, + config->option("bed_custom_texture")->value, + config->option("bed_custom_model")->value); + }); 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_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(); } -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 (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()); if (opt_key == "printer_technology") this->set_printer_technology(config.opt_enum(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; update_scheduled = true; } @@ -4593,7 +4603,9 @@ void Plater::on_config_change(const DynamicPrintConfig &config) } if (bed_shape_changed) - p->set_bed_shape(p->config->option("bed_shape")->values); + p->set_bed_shape(p->config->option("bed_shape")->values, + p->config->option("bed_custom_texture")->value, + p->config->option("bed_custom_model")->value); if (update_scheduled) update(); diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 3d467d7f8..e34c9be28 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -411,7 +411,7 @@ const std::vector& Preset::printer_options() if (s_opts.empty()) { s_opts = { "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", "host_type", "print_host", "printhost_apikey", "printhost_cafile", "single_extruder_multi_material", "start_gcode", "end_gcode", "before_layer_gcode", "layer_gcode", "toolchange_gcode", @@ -512,6 +512,7 @@ const std::vector& Preset::sla_printer_options() if (s_opts.empty()) { s_opts = { "printer_technology", + "bed_shape", "bed_custom_texture", "bed_custom_model", "max_print_height", "bed_shape", "max_print_height", "display_width", "display_height", "display_pixels_x", "display_pixels_y", "display_mirror_x", "display_mirror_y", diff --git a/src/slic3r/GUI/ProgressStatusBar.cpp b/src/slic3r/GUI/ProgressStatusBar.cpp index f848e663d..33e4855cd 100644 --- a/src/slic3r/GUI/ProgressStatusBar.cpp +++ b/src/slic3r/GUI/ProgressStatusBar.cpp @@ -14,20 +14,20 @@ namespace Slic3r { -ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id): - self(new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, - id == -1? wxID_ANY : id)), - m_timer(new wxTimer(self)), - m_prog (new wxGauge(self, - wxGA_HORIZONTAL, - 100, - wxDefaultPosition, - wxDefaultSize)), - m_cancelbutton(new wxButton(self, - -1, - _(L("Cancel")), - wxDefaultPosition, - wxDefaultSize)) +ProgressStatusBar::ProgressStatusBar(wxWindow *parent, int id) + : self{new wxStatusBar(parent ? parent : GUI::wxGetApp().mainframe, + id == -1 ? wxID_ANY : id)} + , m_prog{new wxGauge(self, + wxGA_HORIZONTAL, + 100, + wxDefaultPosition, + wxDefaultSize)} + , m_cancelbutton{new wxButton(self, + -1, + _(L("Cancel")), + wxDefaultPosition, + wxDefaultSize)} + , m_timer{new wxTimer(self)} { m_prog->Hide(); m_cancelbutton->Hide(); diff --git a/src/slic3r/GUI/ProgressStatusBar.hpp b/src/slic3r/GUI/ProgressStatusBar.hpp index 413c6ffee..1aab67d5a 100644 --- a/src/slic3r/GUI/ProgressStatusBar.hpp +++ b/src/slic3r/GUI/ProgressStatusBar.hpp @@ -25,9 +25,9 @@ namespace Slic3r { class ProgressStatusBar { wxStatusBar *self; // we cheat! It should be the base class but: perl! - wxTimer *m_timer; wxGauge *m_prog; wxButton *m_cancelbutton; + std::unique_ptr m_timer; public: /// Cancel callback function type diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 26bd1116b..a00f7902f 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1862,12 +1862,18 @@ void TabPrinter::build_fff() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture"), + *m_config->option("bed_custom_model")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.GetValue(); + const std::vector& 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()) { 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(); } } @@ -2095,12 +2101,18 @@ void TabPrinter::build_sla() btn->Bind(wxEVT_BUTTON, ([this](wxCommandEvent e) { BedShapeDialog dlg(this); - dlg.build_dialog(m_config->option("bed_shape")); + dlg.build_dialog(*m_config->option("bed_shape"), + *m_config->option("bed_custom_texture"), + *m_config->option("bed_custom_model")); if (dlg.ShowModal() == wxID_OK) { - std::vector shape = dlg.GetValue(); + const std::vector& 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()) { 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(); } }