#include #include #include namespace Slic3r { namespace sla { Contour3D sphere(double rho, Portion portion, double fa) { Contour3D ret; // prohibit close to zero radius if(rho <= 1e-6 && rho >= -1e-6) return ret; auto& vertices = ret.points; auto& facets = ret.faces3; // Algorithm: // Add points one-by-one to the sphere grid and form facets using relative // coordinates. Sphere is composed effectively of a mesh of stacked circles. // adjust via rounding to get an even multiple for any provided angle. double angle = (2*PI / floor(2*PI / fa)); // Ring to be scaled to generate the steps of the sphere std::vector ring; for (double i = 0; i < 2*PI; i+=angle) ring.emplace_back(i); const auto sbegin = size_t(2*std::get<0>(portion)/angle); const auto send = size_t(2*std::get<1>(portion)/angle); const size_t steps = ring.size(); const double increment = 1.0 / double(steps); // special case: first ring connects to 0,0,0 // insert and form facets. if(sbegin == 0) vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*sbegin*2.0*rho)); auto id = coord_t(vertices.size()); for (size_t i = 0; i < ring.size(); i++) { // Fixed scaling const double z = -rho + increment*rho*2.0 * (sbegin + 1.0); // radius of the circle for this step. const double r = std::sqrt(std::abs(rho*rho - z*z)); Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); vertices.emplace_back(Vec3d(b(0), b(1), z)); if (sbegin == 0) (i == 0) ? facets.emplace_back(coord_t(ring.size()), 0, 1) : facets.emplace_back(id - 1, 0, id); ++id; } // General case: insert and form facets for each step, // joining it to the ring below it. for (size_t s = sbegin + 2; s < send - 1; s++) { const double z = -rho + increment*double(s*2.0*rho); const double r = std::sqrt(std::abs(rho*rho - z*z)); for (size_t i = 0; i < ring.size(); i++) { Vec2d b = Eigen::Rotation2Dd(ring[i]) * Eigen::Vector2d(0, r); vertices.emplace_back(Vec3d(b(0), b(1), z)); auto id_ringsize = coord_t(id - int(ring.size())); if (i == 0) { // wrap around facets.emplace_back(id - 1, id, id + coord_t(ring.size() - 1) ); facets.emplace_back(id - 1, id_ringsize, id); } else { facets.emplace_back(id_ringsize - 1, id_ringsize, id); facets.emplace_back(id - 1, id_ringsize - 1, id); } id++; } } // special case: last ring connects to 0,0,rho*2.0 // only form facets. if(send >= size_t(2*PI / angle)) { vertices.emplace_back(Vec3d(0.0, 0.0, -rho + increment*send*2.0*rho)); for (size_t i = 0; i < ring.size(); i++) { auto id_ringsize = coord_t(id - int(ring.size())); if (i == 0) { // third vertex is on the other side of the ring. facets.emplace_back(id - 1, id_ringsize, id); } else { auto ci = coord_t(id_ringsize + coord_t(i)); facets.emplace_back(ci - 1, ci, id); } } } id++; return ret; } Contour3D cylinder(double r, double h, size_t ssteps, const Vec3d &sp) { Contour3D ret; auto steps = int(ssteps); auto& points = ret.points; auto& indices = ret.faces3; points.reserve(2*ssteps); double a = 2*PI/steps; Vec3d jp = sp; Vec3d endp = {sp(X), sp(Y), sp(Z) + h}; // Upper circle points for(int i = 0; i < steps; ++i) { double phi = i*a; double ex = endp(X) + r*std::cos(phi); double ey = endp(Y) + r*std::sin(phi); points.emplace_back(ex, ey, endp(Z)); } // Lower circle points for(int i = 0; i < steps; ++i) { double phi = i*a; double x = jp(X) + r*std::cos(phi); double y = jp(Y) + r*std::sin(phi); points.emplace_back(x, y, jp(Z)); } // Now create long triangles connecting upper and lower circles indices.reserve(2*ssteps); auto offs = steps; for(int i = 0; i < steps - 1; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); } // Last triangle connecting the first and last vertices auto last = steps - 1; indices.emplace_back(0, last, offs); indices.emplace_back(last, offs + last, offs); // According to the slicing algorithms, we need to aid them with generating // a watertight body. So we create a triangle fan for the upper and lower // ending of the cylinder to close the geometry. points.emplace_back(jp); int ci = int(points.size() - 1); for(int i = 0; i < steps - 1; ++i) indices.emplace_back(i + offs + 1, i + offs, ci); indices.emplace_back(offs, steps + offs - 1, ci); points.emplace_back(endp); ci = int(points.size() - 1); for(int i = 0; i < steps - 1; ++i) indices.emplace_back(ci, i, i + 1); indices.emplace_back(steps - 1, 0, ci); return ret; } Contour3D pinhead(double r_pin, double r_back, double length, size_t steps) { assert(length > 0.); assert(r_back > 0.); assert(r_pin > 0.); Contour3D mesh; // We create two spheres which will be connected with a robe that fits // both circles perfectly. // Set up the model detail level const double detail = 2*PI/steps; // We don't generate whole circles. Instead, we generate only the // portions which are visible (not covered by the robe) To know the // exact portion of the bottom and top circles we need to use some // rules of tangent circles from which we can derive (using simple // triangles the following relations: // The height of the whole mesh const double h = r_back + r_pin + length; double phi = PI / 2. - std::acos((r_back - r_pin) / h); // To generate a whole circle we would pass a portion of (0, Pi) // To generate only a half horizontal circle we can pass (0, Pi/2) // The calculated phi is an offset to the half circles needed to smooth // the transition from the circle to the robe geometry auto&& s1 = sphere(r_back, make_portion(0, PI/2 + phi), detail); auto&& s2 = sphere(r_pin, make_portion(PI/2 + phi, PI), detail); for(auto& p : s2.points) p.z() += h; mesh.merge(s1); mesh.merge(s2); for(size_t idx1 = s1.points.size() - steps, idx2 = s1.points.size(); idx1 < s1.points.size() - 1; idx1++, idx2++) { coord_t i1s1 = coord_t(idx1), i1s2 = coord_t(idx2); coord_t i2s1 = i1s1 + 1, i2s2 = i1s2 + 1; mesh.faces3.emplace_back(i1s1, i2s1, i2s2); mesh.faces3.emplace_back(i1s1, i2s2, i1s2); } auto i1s1 = coord_t(s1.points.size()) - coord_t(steps); auto i2s1 = coord_t(s1.points.size()) - 1; auto i1s2 = coord_t(s1.points.size()); auto i2s2 = coord_t(s1.points.size()) + coord_t(steps) - 1; mesh.faces3.emplace_back(i2s2, i2s1, i1s1); mesh.faces3.emplace_back(i1s2, i2s2, i1s1); return mesh; } Contour3D pedestal(const Vec3d &endpt, double baseheight, double radius, size_t steps) { if(baseheight <= 0) return {}; assert(steps >= 0); auto last = int(steps - 1); Contour3D base; double a = 2*PI/steps; double z = endpt(Z) + baseheight; for(size_t i = 0; i < steps; ++i) { double phi = i*a; double x = endpt(X) + radius * std::cos(phi); double y = endpt(Y) + radius * std::sin(phi); base.points.emplace_back(x, y, z); } for(size_t i = 0; i < steps; ++i) { double phi = i*a; double x = endpt(X) + radius*std::cos(phi); double y = endpt(Y) + radius*std::sin(phi); base.points.emplace_back(x, y, z - baseheight); } auto ep = endpt; ep(Z) += baseheight; base.points.emplace_back(endpt); base.points.emplace_back(ep); auto& indices = base.faces3; auto hcenter = int(base.points.size() - 1); auto lcenter = int(base.points.size() - 2); auto offs = int(steps); for(int i = 0; i < last; ++i) { indices.emplace_back(i, i + offs, offs + i + 1); indices.emplace_back(i, offs + i + 1, i + 1); indices.emplace_back(i, i + 1, hcenter); indices.emplace_back(lcenter, offs + i + 1, offs + i); } indices.emplace_back(0, last, offs); indices.emplace_back(last, offs + last, offs); indices.emplace_back(hcenter, last, 0); indices.emplace_back(offs, offs + last, lcenter); return base; } Head::Head(double r_big_mm, double r_small_mm, double length_mm, double penetration, const Vec3d &direction, const Vec3d &offset, const size_t circlesteps) : steps(circlesteps) , dir(direction) , pos(offset) , r_back_mm(r_big_mm) , r_pin_mm(r_small_mm) , width_mm(length_mm) , penetration_mm(penetration) { // mesh = pinhead(r_pin_mm, r_back_mm, width_mm, steps); // To simplify further processing, we translate the mesh so that the // last vertex of the pointing sphere (the pinpoint) will be at (0,0,0) // for(auto& p : mesh.points) p.z() -= (fullwidth() - r_back_mm); } //Pillar::Pillar(const Vec3d &endp, double h, double radius, size_t st): // height{h}, r(radius), steps(st), endpt(endp), starts_from_head(false) //{ // assert(steps > 0); // if(height > EPSILON) { // Endpoint is below the starting point // // We just create a bridge geometry with the pillar parameters and // // move the data. // Contour3D body = cylinder(radius, height, st, endp); // mesh.points.swap(body.points); // mesh.faces3.swap(body.faces3); // } //} //Pillar &Pillar::add_base(double baseheight, double radius) //{ // if(baseheight <= 0) return *this; // if(baseheight > height) baseheight = height; // assert(steps >= 0); // auto last = int(steps - 1); // if(radius < r ) radius = r; // double a = 2*PI/steps; // double z = endpt(Z) + baseheight; // for(size_t i = 0; i < steps; ++i) { // double phi = i*a; // double x = endpt(X) + r*std::cos(phi); // double y = endpt(Y) + r*std::sin(phi); // base.points.emplace_back(x, y, z); // } // for(size_t i = 0; i < steps; ++i) { // double phi = i*a; // double x = endpt(X) + radius*std::cos(phi); // double y = endpt(Y) + radius*std::sin(phi); // base.points.emplace_back(x, y, z - baseheight); // } // auto ep = endpt; ep(Z) += baseheight; // base.points.emplace_back(endpt); // base.points.emplace_back(ep); // auto& indices = base.faces3; // auto hcenter = int(base.points.size() - 1); // auto lcenter = int(base.points.size() - 2); // auto offs = int(steps); // for(int i = 0; i < last; ++i) { // indices.emplace_back(i, i + offs, offs + i + 1); // indices.emplace_back(i, offs + i + 1, i + 1); // indices.emplace_back(i, i + 1, hcenter); // indices.emplace_back(lcenter, offs + i + 1, offs + i); // } // indices.emplace_back(0, last, offs); // indices.emplace_back(last, offs + last, offs); // indices.emplace_back(hcenter, last, 0); // indices.emplace_back(offs, offs + last, lcenter); // return *this; //} Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r_mm, size_t steps): r(r_mm), startp(j1), endp(j2) { using Quaternion = Eigen::Quaternion; Vec3d dir = (j2 - j1).normalized(); double d = distance(j2, j1); mesh = cylinder(r, d, steps); auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); for(auto& p : mesh.points) p = quater * p + j1; } Bridge::Bridge(const Vec3d &j1, const Vec3d &j2, double r1_mm, double r2_mm, size_t steps) { Vec3d dir = (j2 - j1); mesh = pinhead(r1_mm, r2_mm, dir.norm(), steps); dir.normalize(); using Quaternion = Eigen::Quaternion; auto quater = Quaternion::FromTwoVectors(Vec3d{0,0,1}, dir); for(auto& p : mesh.points) p = quater * p + j1; } Pad::Pad(const TriangleMesh &support_mesh, const ExPolygons & model_contours, double ground_level, const PadConfig & pcfg, ThrowOnCancel thr) : cfg(pcfg) , zlevel(ground_level + pcfg.full_height() - pcfg.required_elevation()) { thr(); ExPolygons sup_contours; float zstart = float(zlevel); float zend = zstart + float(pcfg.full_height() + EPSILON); pad_blueprint(support_mesh, sup_contours, grid(zstart, zend, 0.1f), thr); create_pad(sup_contours, model_contours, tmesh, pcfg); tmesh.translate(0, 0, float(zlevel)); if (!tmesh.empty()) tmesh.require_shared_vertices(); } const TriangleMesh &SupportTreeBuilder::add_pad(const ExPolygons &modelbase, const PadConfig & cfg) { m_pad = Pad{merged_mesh(), modelbase, ground_level, cfg, ctl().cancelfn}; return m_pad.tmesh; } SupportTreeBuilder::SupportTreeBuilder(SupportTreeBuilder &&o) : m_heads(std::move(o.m_heads)) , m_head_indices{std::move(o.m_head_indices)} , m_pillars{std::move(o.m_pillars)} , m_bridges{std::move(o.m_bridges)} , m_crossbridges{std::move(o.m_crossbridges)} , m_pad{std::move(o.m_pad)} , m_meshcache{std::move(o.m_meshcache)} , m_meshcache_valid{o.m_meshcache_valid} , m_model_height{o.m_model_height} , ground_level{o.ground_level} {} SupportTreeBuilder::SupportTreeBuilder(const SupportTreeBuilder &o) : m_heads(o.m_heads) , m_head_indices{o.m_head_indices} , m_pillars{o.m_pillars} , m_bridges{o.m_bridges} , m_crossbridges{o.m_crossbridges} , m_pad{o.m_pad} , m_meshcache{o.m_meshcache} , m_meshcache_valid{o.m_meshcache_valid} , m_model_height{o.m_model_height} , ground_level{o.ground_level} {} SupportTreeBuilder &SupportTreeBuilder::operator=(SupportTreeBuilder &&o) { m_heads = std::move(o.m_heads); m_head_indices = std::move(o.m_head_indices); m_pillars = std::move(o.m_pillars); m_bridges = std::move(o.m_bridges); m_crossbridges = std::move(o.m_crossbridges); m_pad = std::move(o.m_pad); m_meshcache = std::move(o.m_meshcache); m_meshcache_valid = o.m_meshcache_valid; m_model_height = o.m_model_height; ground_level = o.ground_level; return *this; } SupportTreeBuilder &SupportTreeBuilder::operator=(const SupportTreeBuilder &o) { m_heads = o.m_heads; m_head_indices = o.m_head_indices; m_pillars = o.m_pillars; m_bridges = o.m_bridges; m_crossbridges = o.m_crossbridges; m_pad = o.m_pad; m_meshcache = o.m_meshcache; m_meshcache_valid = o.m_meshcache_valid; m_model_height = o.m_model_height; ground_level = o.ground_level; return *this; } const TriangleMesh &SupportTreeBuilder::merged_mesh() const { if (m_meshcache_valid) return m_meshcache; Contour3D merged; for (auto &head : m_heads) { if (ctl().stopcondition()) break; if (head.is_valid()) merged.merge(get_mesh(head)); } for (auto &stick : m_pillars) { if (ctl().stopcondition()) break; merged.merge(stick.mesh); merged.merge(stick.base); } for (auto &j : m_junctions) { if (ctl().stopcondition()) break; merged.merge(j.mesh); } for (auto &bs : m_bridges) { if (ctl().stopcondition()) break; merged.merge(bs.mesh); } for (auto &bs : m_crossbridges) { if (ctl().stopcondition()) break; merged.merge(bs.mesh); } if (ctl().stopcondition()) { // In case of failure we have to return an empty mesh m_meshcache = TriangleMesh(); return m_meshcache; } m_meshcache = to_triangle_mesh(merged); // The mesh will be passed by const-pointer to TriangleMeshSlicer, // which will need this. if (!m_meshcache.empty()) m_meshcache.require_shared_vertices(); BoundingBoxf3 &&bb = m_meshcache.bounding_box(); m_model_height = bb.max(Z) - bb.min(Z); m_meshcache_valid = true; return m_meshcache; } double SupportTreeBuilder::full_height() const { if (merged_mesh().empty() && !pad().empty()) return pad().cfg.full_height(); double h = mesh_height(); if (!pad().empty()) h += pad().cfg.required_elevation(); return h; } const TriangleMesh &SupportTreeBuilder::merge_and_cleanup() { // in case the mesh is not generated, it should be... auto &ret = merged_mesh(); // Doing clear() does not garantee to release the memory. m_heads = {}; m_head_indices = {}; m_pillars = {}; m_junctions = {}; m_bridges = {}; return ret; } const TriangleMesh &SupportTreeBuilder::retrieve_mesh(MeshType meshtype) const { switch(meshtype) { case MeshType::Support: return merged_mesh(); case MeshType::Pad: return pad().tmesh; } return m_meshcache; } template static Hit min_hit(const C &hits) { auto mit = std::min_element(hits.begin(), hits.end(), [](const Hit &h1, const Hit &h2) { return h1.distance() < h2.distance(); }); return *mit; } }} // namespace Slic3r::sla