OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::SketchEngine Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/libslic3r/CAD/SketchEngine.hpp>

Static Public Member Functions

static TopoDS_Shape make_prism (const TopoDS_Shape &base, const gp_Vec &vec)
static TopoDS_Shape make_extrude (const TopoDS_Wire &wire, const SketchPlane &plane, double length, bool symmetric=false, double taper_deg=0.0)
static TopoDS_Shape make_extrude (const TopoDS_Face &face, const SketchPlane &plane, double length, bool symmetric=false, double taper_deg=0.0)
static TopoDS_Shape make_extrude_taper (const TopoDS_Wire &wire, const SketchPlane &plane, double length, double taper_deg)
static TopoDS_Shape make_extrude_two_sided (const TopoDS_Wire &wire, const SketchPlane &plane, double up, double down)
static TopoDS_Shape make_extrude_two_sided (const TopoDS_Face &face, const SketchPlane &plane, double up, double down)
static TopoDS_Shape make_extrude_face (const TopoDS_Face &face, const SketchPlane &plane, double length, bool symmetric=false, double taper_deg=0.0)
static TopoDS_Shape make_extrude_regions (const std::vector< std::vector< std::vector< Vec2d > > > &regions, const SketchPlane &plane, double length, bool symmetric=false)
static TopoDS_Shape make_revolve (const TopoDS_Wire &wire, const gp_Ax1 &axis, double angle_deg=360.0)
static TopoDS_Shape make_sweep (const TopoDS_Wire &profile, const TopoDS_Wire &path)
static TopoDS_Shape make_loft (const std::vector< TopoDS_Wire > &profiles, bool ruled)
static TopoDS_Shape make_loft_surface (const std::vector< TopoDS_Wire > &profiles, bool ruled)
static TopoDS_Shape make_pocket (const TopoDS_Wire &wire, const SketchPlane &plane, const TopoDS_Shape &target, double depth)
static TriangleMesh tessellate (const TopoDS_Shape &shape, double linear_deflection=0.01, double angular_deflection=0.5)
static TriangleMesh tessellate (const TopoDS_Shape &shape, std::vector< int > &tri_face, double linear_deflection=0.01, double angular_deflection=0.5)
static TopoDS_Wire entities_to_wire (const std::vector< SketchEntity > &entities, const SketchPlane &plane, bool closed_only=false)
static std::vector< TopoDS_Wire > entities_to_wires (const std::vector< SketchEntity > &entities, const SketchPlane &plane, bool closed_only=false)
static TopoDS_Face wires_to_face (const std::vector< TopoDS_Wire > &wires, const SketchPlane &plane)
static std::vector< SketchEntity > mirror_entities (const std::vector< SketchEntity > &src, const Vec2d &a, const Vec2d &b)
static std::vector< SketchEntity > offset_entities (const std::vector< SketchEntity > &src, double d)
static std::vector< SketchEntity > array_entities (const std::vector< SketchEntity > &src, int count, const Vec2d &step, double angle_step, const Vec2d &pivot)
static std::vector< SketchEntity > transform_entities (const std::vector< SketchEntity > &src, const Vec2d &move, double angle, double scale, const Vec2d &pivot)
static bool fillet_lines (const SketchEntity &a, const SketchEntity &b, double r, SketchEntity &a_out, SketchEntity &b_out, SketchEntity &arc_out)
static bool chamfer_lines (const SketchEntity &a, const SketchEntity &b, double d, SketchEntity &a_out, SketchEntity &b_out, SketchEntity &seg_out)
static bool trim_entity (SketchEntity &e, const std::vector< SketchEntity > &others, const Vec2d &pick)
static bool extend_entity (SketchEntity &e, const std::vector< SketchEntity > &others, const Vec2d &pick)
static SketchEntity make_bridge (const SketchEntity &a, int a_end, const SketchEntity &b, int b_end)

Detailed Description

Member Function Documentation

◆ array_entities()

std::vector< SketchEntity > Slic3r::SketchEngine::array_entities ( const std::vector< SketchEntity > & src,
int count,
const Vec2d & step,
double angle_step,
const Vec2d & pivot )
static
1441{
1442 std::vector<SketchEntity> out;
1443 if (count < 2) return out;
1444
1445 for (int i = 1; i < count; ++i) {
1446 const double ang = i * angle_step;
1447 const double ca = std::cos(ang), sa = std::sin(ang);
1448 const Vec2d tr = double(i) * step;
1449 // Rigid map: rotate about pivot by `ang`, then translate by `tr`.
1450 auto xf = [&](const Vec2d& p) -> Vec2d {
1451 const Vec2d d = p - pivot;
1452 return Vec2d(pivot.x() + ca * d.x() - sa * d.y(),
1453 pivot.y() + sa * d.x() + ca * d.y()) + tr;
1454 };
1455
1456 for (const auto& e : src) {
1457 SketchEntity m = e; // carry construction flag, radii, etc.
1458 switch (e.type) {
1460 m.p0 = xf(e.p0); m.p1 = xf(e.p1);
1461 break;
1463 m.p0 = xf(e.p0);
1464 break;
1466 m.center = xf(e.center); m.p0 = m.center; // radius unchanged
1467 break;
1469 m.center = xf(e.center);
1470 m.start_angle = e.start_angle + ang;
1471 m.end_angle = e.end_angle + ang; // rigid: sweep preserved
1472 m.p0 = m.center + e.radius * Vec2d(std::cos(m.start_angle), std::sin(m.start_angle));
1473 m.p1 = m.center + e.radius * Vec2d(std::cos(m.end_angle), std::sin(m.end_angle));
1474 break;
1475 }
1478 m.center = xf(e.center);
1479 m.rotation = e.rotation + ang; // major axis rotates with the body
1480 if (e.type == SketchEntity::Type::Ellipse) {
1481 m.p0 = m.center;
1482 } else {
1483 m.p0 = xf(e.p0); m.p1 = xf(e.p1);
1484 // Parametric angles are in the (rotated) body frame -> unchanged.
1485 }
1486 break;
1488 for (auto& cp : m.ctrl) cp = xf(cp);
1489 m.p0 = xf(e.p0); m.p1 = xf(e.p1);
1490 break;
1491 }
1492 out.push_back(m);
1493 }
1494 }
1495 return out;
1496}
static unsigned int count
Definition AuxiliaryDataViewModel.cpp:246
Eigen::Matrix< double, 2, 1, Eigen::DontAlign > Vec2d
Definition Point.hpp:67
void step(Coord &crd, const Dir d, const long n=1)
Definition MarchingSquares.hpp:213
@ Ellipse
Definition SketchEngine.hpp:31
@ EllipseArc
Definition SketchEngine.hpp:31
@ Point
Definition SketchEngine.hpp:31
@ Arc
Definition SketchEngine.hpp:31
@ Circle
Definition SketchEngine.hpp:31
@ Line
Definition SketchEngine.hpp:31
@ BSpline
Definition SketchEngine.hpp:31

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::BSpline, Slic3r::SketchEntity::center, Slic3r::SketchEntity::Circle, count, Slic3r::SketchEntity::ctrl, Slic3r::SketchEntity::Ellipse, Slic3r::SketchEntity::EllipseArc, Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::Point, Slic3r::SketchEntity::rotation, and Slic3r::SketchEntity::start_angle.

Referenced by Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::confirm_transform(), and Slic3r::GUI::DesignSketchTool::recompute_tf_ghost().

Here is the caller graph for this function:

◆ chamfer_lines()

bool Slic3r::SketchEngine::chamfer_lines ( const SketchEntity & a,
const SketchEntity & b,
double d,
SketchEntity & a_out,
SketchEntity & b_out,
SketchEntity & seg_out )
static
1663{
1664 if (a.type != SketchEntity::Type::Line || b.type != SketchEntity::Type::Line || d <= 1e-9)
1665 return false;
1666
1667 Vec2d da = a.p1 - a.p0;
1668 Vec2d db = b.p1 - b.p0;
1669 double denom = da.x() * db.y() - da.y() * db.x();
1670 if (std::abs(denom) < 1e-12)
1671 return false; // parallel: no corner to chamfer
1672
1673 // Shared corner = line/line intersection.
1674 Vec2d diff = b.p0 - a.p0;
1675 double s = (diff.x() * db.y() - diff.y() * db.x()) / denom;
1676 Vec2d C = a.p0 + s * da;
1677
1678 // For each line, unit vector pointing from the corner toward its far endpoint
1679 // (the endpoint that is kept); the near endpoint is the one trimmed back.
1680 auto pick = [&](const SketchEntity& ln, int& near_idx, Vec2d& u) -> bool {
1681 double d0 = (ln.p0 - C).norm();
1682 double d1 = (ln.p1 - C).norm();
1683 if (d0 <= d1) { near_idx = 0; u = ln.p1 - C; }
1684 else { near_idx = 1; u = ln.p0 - C; }
1685 if (u.norm() < 1e-12) return false;
1686 u.normalize();
1687 return true;
1688 };
1689 int a_near_idx, b_near_idx;
1690 Vec2d ua, ub;
1691 if (!pick(a, a_near_idx, ua)) return false;
1692 if (!pick(b, b_near_idx, ub)) return false;
1693
1694 // Reject collinear (no real corner) and ensure the setback fits both lines.
1695 double cosT = std::max(-1.0, std::min(1.0, ua.dot(ub)));
1696 if (cosT > 1.0 - 1e-9 || cosT < -1.0 + 1e-9)
1697 return false;
1698 {
1699 Vec2d a_far = (a_near_idx == 0) ? a.p1 : a.p0;
1700 Vec2d b_far = (b_near_idx == 0) ? b.p1 : b.p0;
1701 if (d > (a_far - C).norm() || d > (b_far - C).norm())
1702 return false;
1703 }
1704
1705 Vec2d Ta = C + d * ua;
1706 Vec2d Tb = C + d * ub;
1707
1708 a_out = a;
1709 b_out = b;
1710 if (a_near_idx == 0) a_out.p0 = Ta; else a_out.p1 = Ta;
1711 if (b_near_idx == 0) b_out.p0 = Tb; else b_out.p1 = Tb;
1712
1713 seg_out = SketchEntity{};
1714 seg_out.type = SketchEntity::Type::Line;
1715 seg_out.p0 = Ta;
1716 seg_out.p1 = Tb;
1717 return true;
1718}
Slic3r::Polygons diff(const Slic3r::Polygon &subject, const Slic3r::Polygon &clip, ApplySafetyOffset do_safety_offset)
Definition ClipperUtils.cpp:731

References Slic3r::diff(), Slic3r::SketchEntity::Line, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, and Slic3r::SketchEntity::type.

Referenced by Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::confirm_op(), and Slic3r::GUI::DesignSketchTool::recompute_op_ghost().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ entities_to_wire()

TopoDS_Wire Slic3r::SketchEngine::entities_to_wire ( const std::vector< SketchEntity > & entities,
const SketchPlane & plane,
bool closed_only = false )
static
865{
866 const std::vector<TopoDS_Wire> w = entities_to_wires(entities, plane, closed_only);
867 return w.size() == 1 ? w[0] : TopoDS_Wire{};
868}
static std::vector< TopoDS_Wire > entities_to_wires(const std::vector< SketchEntity > &entities, const SketchPlane &plane, bool closed_only=false)
Definition SketchEngine.cpp:562

References entities_to_wires().

Referenced by Slic3r::CadDocument::build_sketch_wire().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ entities_to_wires()

std::vector< TopoDS_Wire > Slic3r::SketchEngine::entities_to_wires ( const std::vector< SketchEntity > & entities,
const SketchPlane & plane,
bool closed_only = false )
static
565{
566 // Effective weld tolerance: kSketchJoinTol when auto-close is on, 0.0 when off.
567 // Read ONCE so the union-find, the node weld and the vertex tolerance below all
568 // agree. With 0.0 the comparisons use <= so exactly coincident endpoints still join.
569 const double tol = sketch_join_tol();
570
571 struct Item { const SketchEntity* e; size_t idx; };
572 std::vector<Item> valid;
573 valid.reserve(entities.size());
574 for (size_t i = 0; i < entities.size(); ++i) {
575 const SketchEntity& e = entities[i];
576 if (e.construction) continue;
577 if (e.type == SketchEntity::Type::Point) continue;
578 valid.push_back({&e, i});
579 }
580 if (valid.empty()) return {};
581
582 // Build an OCCT ellipse (gp_Elips) in the sketch plane from an Ellipse(Arc)
583 // entity. Major-axis direction = plane-rotated (cos phi, sin phi). Enforces
584 // a >= b (OCCT requirement); the GUI builder already guarantees this.
585 auto make_elips = [&](const SketchEntity& c) -> gp_Elips {
586 Vec3d c3 = plane.to_world(c.center);
587 gp_Pnt center(c3.x(), c3.y(), c3.z());
588 // The frame's normal is x_axis x y_axis, not plane.normal: OCCT takes the ellipse's Y
589 // as N x X, and the parametric angles were measured in the sketch's own (x, y). The two
590 // agree on XY and YZ; the XZ base plane stores normal = +Y while x x y = -Y, so there
591 // every elliptical arc came out mirrored against what the sketch showed.
592 const Vec3d nz = plane.x_axis.cross(plane.y_axis);
593 gp_Dir n(nz.x(), nz.y(), nz.z());
594 Vec2d maj2(std::cos(c.rotation), std::sin(c.rotation));
595 Vec3d x3 = plane.to_world(c.center + maj2) - c3;
596 gp_Dir xdir(x3.x(), x3.y(), x3.z());
597 double a = c.radius, b = c.rminor;
598 if (a < b) std::swap(a, b);
599 return gp_Elips(gp_Ax2(center, n, xdir), a, b);
600 };
601
602 // Clamped uniform B-spline (degree min(3, n-1)) through the control poles. The
603 // knot construction is mirrored in DesignSketchTool's GUI sampler so the on-screen
604 // curve matches the extruded geometry exactly.
605 auto make_bspline = [&](const SketchEntity& c) -> Handle(Geom_BSplineCurve) {
606 const int n = int(c.ctrl.size());
607 const int p = n >= 4 ? 3 : (n >= 2 ? n - 1 : 0);
608 if (p < 1) return Handle(Geom_BSplineCurve)();
609 NCollection_Array1<gp_Pnt> poles(1, n);
610 for (int i = 0; i < n; ++i) {
611 Vec3d w = plane.to_world(c.ctrl[i]);
612 poles.SetValue(i + 1, gp_Pnt(w.x(), w.y(), w.z()));
613 }
614 const int interior = n - p - 1; // count of single interior knots
615 const int nknots = interior + 2;
616 NCollection_Array1<double> knots(1, nknots);
617 NCollection_Array1<int> mults(1, nknots);
618 knots.SetValue(1, 0.0); mults.SetValue(1, p + 1);
619 for (int i = 1; i <= interior; ++i) { knots.SetValue(i + 1, double(i)); mults.SetValue(i + 1, 1); }
620 knots.SetValue(nknots, double(interior + 1)); mults.SetValue(nknots, p + 1);
621 return new Geom_BSplineCurve(poles, knots, mults, p);
622 };
623
624 auto is_chain = [](const SketchEntity& e) {
625 return e.type == SketchEntity::Type::Line || e.type == SketchEntity::Type::Arc ||
627 };
628
629 // Endpoints of a chain entity in SKETCH coordinates (before to_world). False on a
630 // degenerate (fewer than two control points) BSpline, which can never close a loop.
631 auto endpoints = [&](const SketchEntity& e, Vec2d& a, Vec2d& b) -> bool {
632 if (e.type == SketchEntity::Type::BSpline) {
633 if (e.ctrl.size() < 2) return false;
634 a = e.ctrl.front(); b = e.ctrl.back();
635 return true;
636 }
637 a = e.p0; b = e.p1;
638 return true;
639 };
640
641 // Sketch weld tolerance. Nothing legitimate in a mm-scale sketch is 1 um apart, so two
642 // endpoints within this distance count as one joint. The union-find grouping and the wire
643 // build below MUST use the SAME number, or a joint can be united into a loop and then
644 // rejected by the wire builder (which silently drops the edge — see the wire build).
645 // `<=` (not `<`) so exactly coincident endpoints still join when tol == 0 (auto-close off).
646 auto same = [&](const Vec2d& p, const Vec2d& q) { return (p - q).norm() <= tol; };
647
648 // Union-find over the valid index list: chain entities sharing an endpoint belong to one loop.
649 std::vector<int> parent(valid.size());
650 for (size_t i = 0; i < valid.size(); ++i) parent[i] = int(i);
651 auto find = [&](int x) {
652 while (parent[x] != x) { parent[x] = parent[parent[x]]; x = parent[x]; }
653 return x;
654 };
655 auto unite = [&](int x, int y) {
656 int rx = find(x), ry = find(y);
657 if (rx != ry) parent[rx] = ry;
658 };
659
660 std::vector<size_t> chain_idx;
661 chain_idx.reserve(valid.size());
662 for (size_t i = 0; i < valid.size(); ++i)
663 if (is_chain(*valid[i].e)) chain_idx.push_back(i);
664
665 for (size_t a = 0; a < chain_idx.size(); ++a) {
666 const size_t i = chain_idx[a];
667 Vec2d i0, i1;
668 if (!endpoints(*valid[i].e, i0, i1)) continue;
669 for (size_t b = a + 1; b < chain_idx.size(); ++b) {
670 const size_t j = chain_idx[b];
671 Vec2d j0, j1;
672 if (!endpoints(*valid[j].e, j0, j1)) continue;
673 if (same(i0, j0) || same(i0, j1) || same(i1, j0) || same(i1, j1))
674 unite(int(i), int(j));
675 }
676 }
677
678 // Group chain entities by connected-component root.
679 std::map<int, std::vector<size_t>> comps;
680 for (size_t i = 0; i < valid.size(); ++i) {
681 if (!is_chain(*valid[i].e)) continue;
682 comps[find(int(i))].push_back(i);
683 }
684
685 // Loop descriptors: each Circle/Ellipse is its own loop; each chain component is a loop.
686 struct Loop { size_t min_idx{0}; bool closed_single{false}; size_t member{0}; std::vector<size_t> members; };
687 std::vector<Loop> loops;
688 for (size_t i = 0; i < valid.size(); ++i) {
689 const SketchEntity& e = *valid[i].e;
690 if (e.type == SketchEntity::Type::Circle || e.type == SketchEntity::Type::Ellipse) {
691 Loop l; l.min_idx = valid[i].idx; l.closed_single = true; l.member = i;
692 loops.push_back(l);
693 }
694 }
695 for (const auto& kv : comps) {
696 Loop l; l.closed_single = false; l.members = kv.second;
697 size_t mn = std::numeric_limits<size_t>::max();
698 for (size_t m : kv.second) mn = std::min(mn, valid[m].idx);
699 l.min_idx = mn;
700 loops.push_back(l);
701 }
702 // Deterministic order: by the index of each loop's first entity.
703 std::sort(loops.begin(), loops.end(), [](const Loop& x, const Loop& y) { return x.min_idx < y.min_idx; });
704
705 // Build each loop. All-or-nothing: one failed loop poisons the whole result.
706 std::vector<TopoDS_Wire> out;
707 out.reserve(loops.size());
708 for (const Loop& loop : loops) {
709 BRepBuilderAPI_MakeWire wm;
710 if (loop.closed_single) {
711 const SketchEntity& c = *valid[loop.member].e;
712 TopoDS_Edge e;
713 if (c.type == SketchEntity::Type::Ellipse) {
714 if (c.radius <= 1e-9 || c.rminor <= 1e-9) return {};
715 e = BRepBuilderAPI_MakeEdge(make_elips(c)).Edge();
716 } else {
717 // Same guard as the ellipse above: a zero radius would reach OCCT and throw from
718 // .Edge() instead of reporting a sketch that cannot be built.
719 if (c.radius <= 1e-9) return {};
720 Vec3d c3 = plane.to_world(c.center);
721 gp_Pnt center(c3.x(), c3.y(), c3.z());
722 gp_Dir n(plane.normal.x(), plane.normal.y(), plane.normal.z());
723 gp_Circ circ(gp_Ax2(center, n), c.radius);
724 e = BRepBuilderAPI_MakeEdge(circ).Edge();
725 }
726 wm.Add(e);
727 } else {
728 // loop.members is in ENTITY-CREATION order, not loop-traversal order. A partial
729 // wire rejects an out-of-order edge even for a perfectly closed sketch, so first
730 // weld every endpoint into a shared node, then traverse the chain in order. Each
731 // endpoint snaps to an existing node when within `tol`, and every edge
732 // touching a node shares ONE TopoDS_Vertex: the wire builder then sees vertex
733 // identity, not geometric proximity, so a joint open by a few um (larger than
734 // OCCT's default 1e-7 vertex tolerance) still connects.
735 struct Member { size_t v; int a; int b; }; // v = index into `valid`, a/b = node ids
736 std::vector<Vec2d> node_pt; // welded sketch point per node
737 std::vector<int> node_deg; // endpoint count per node
738 auto node_id = [&](const Vec2d& p) -> int {
739 for (size_t i = 0; i < node_pt.size(); ++i)
740 if ((node_pt[i] - p).norm() <= tol) return int(i);
741 node_pt.push_back(p);
742 node_deg.push_back(0);
743 return int(node_pt.size()) - 1;
744 };
745 std::vector<Member> ms;
746 ms.reserve(loop.members.size());
747 for (size_t m : loop.members) {
748 Vec2d p0, p1;
749 if (!endpoints(*valid[m].e, p0, p1)) return {};
750 int a = node_id(p0), b = node_id(p1);
751 node_deg[a]++; node_deg[b]++;
752 ms.push_back({m, a, b});
753 }
754
755 // Traverse: begin at a degree-1 node for an open chain, else at any member, then
756 // repeatedly take the unused member sharing the current open node.
757 std::vector<size_t> order;
758 order.reserve(ms.size());
759 std::vector<char> used(ms.size(), 0);
760 size_t start = 0;
761 for (size_t i = 0; i < ms.size(); ++i)
762 if (node_deg[ms[i].a] == 1 || node_deg[ms[i].b] == 1) { start = i; break; }
763 const int start_node = (node_deg[ms[start].a] == 1) ? ms[start].a
764 : (node_deg[ms[start].b] == 1) ? ms[start].b
765 : ms[start].a;
766 int open = start_node;
767 for (;;) {
768 size_t next = ms.size();
769 for (size_t k = 0; k < ms.size(); ++k) {
770 if (used[k]) continue;
771 if (ms[k].a == open || ms[k].b == open) { next = k; break; }
772 }
773 if (next == ms.size()) break;
774 order.push_back(next);
775 used[next] = 1;
776 open = (ms[next].a == open) ? ms[next].b : ms[next].a;
777 }
778 if (order.size() != ms.size()) return {};
779
780 // The walk ends on the node it could not leave; that node equals the starting
781 // node exactly when the chain is a closed cycle. When closed_only is set, an open
782 // chain is DISCARDED (skipped), never an error: the viewport discards open chains
783 // when it decides a region is extrudable (region_loops exists to find EXTRUDABLE
784 // regions), so the kernel must discard them too, or the two disagree about what
785 // belongs to the profile and a stray click breaks a feature that looks perfect on
786 // screen. Reuses the welded node ids — no second tolerance.
787 // A component is OPEN exactly when some welded node has only one edge on it —
788 // that free endpoint is where the chain stops. Do NOT define it as "the walk
789 // returned to its starting node": a legitimately closed loop that also carries an
790 // extra edge between two of its nodes (a bridge added across a C profile, so the
791 // gap is spanned twice) has no free endpoint but its Eulerian walk still ends
792 // somewhere else, and that definition discarded it. Degree-1 is the property that
793 // actually distinguishes a stray segment from a closed profile.
794 if (closed_only) {
795 bool has_free_end = false;
796 for (const Member& mm : ms)
797 if (node_deg[mm.a] == 1 || node_deg[mm.b] == 1) { has_free_end = true; break; }
798 if (has_free_end) continue;
799 }
800
801 // One TopoDS_Vertex per node at the welded world point. Tolerance widened to
802 // `tol` because MakeEdge(curve, va, vb) projects each vertex onto the
803 // curve within the vertex tolerance (BRepLib_MakeEdge::Init), and a welded node
804 // can be up to the weld gap off another entity's curve. OCCT must never be handed
805 // a zero vertex tolerance, so clamp at Precision::Confusion() when tol == 0.
806 std::vector<TopoDS_Vertex> verts(node_pt.size());
807 BRep_Builder B;
808 for (size_t i = 0; i < node_pt.size(); ++i) {
809 Vec3d w = plane.to_world(node_pt[i]);
810 verts[i] = BRepBuilderAPI_MakeVertex(gp_Pnt(w.x(), w.y(), w.z())).Vertex();
811 B.UpdateVertex(verts[i], std::max(tol, Precision::Confusion()));
812 }
813
814 for (size_t o : order) {
815 const Member& mm = ms[o];
816 const SketchEntity* e = valid[mm.v].e;
817 const TopoDS_Vertex& va = verts[mm.a];
818 const TopoDS_Vertex& vb = verts[mm.b];
819 if (e->type == SketchEntity::Type::Line) {
820 wm.Add(BRepBuilderAPI_MakeEdge(va, vb).Edge());
821 } else if (e->type == SketchEntity::Type::EllipseArc) {
822 if (e->radius <= 1e-9 || e->rminor <= 1e-9) return {};
823 GC_MakeArcOfEllipse arc_maker(make_elips(*e), e->start_angle, e->end_angle, true);
824 if (!arc_maker.IsDone()) return {};
825 wm.Add(BRepBuilderAPI_MakeEdge(arc_maker.Value(), va, vb).Edge());
826 } else if (e->type == SketchEntity::Type::Arc) {
827 Vec3d p0 = plane.to_world(e->p0);
828 Vec3d p1 = plane.to_world(e->p1);
829 double mid_angle = (e->start_angle + e->end_angle) * 0.5;
830 Vec2d mid_2d(e->center.x() + e->radius * std::cos(mid_angle),
831 e->center.y() + e->radius * std::sin(mid_angle));
832 Vec3d mid_3d = plane.to_world(mid_2d);
833 gp_Pnt pa(p0.x(), p0.y(), p0.z());
834 gp_Pnt pm(mid_3d.x(), mid_3d.y(), mid_3d.z());
835 gp_Pnt pb(p1.x(), p1.y(), p1.z());
836 GC_MakeArcOfCircle arc_maker(pa, pm, pb);
837 if (!arc_maker.IsDone()) return {};
838 Handle(Geom_TrimmedCurve) curve = arc_maker.Value();
839 wm.Add(BRepBuilderAPI_MakeEdge(curve, va, vb).Edge());
840 } else if (e->type == SketchEntity::Type::BSpline) {
841 Handle(Geom_BSplineCurve) crv = make_bspline(*e);
842 if (crv.IsNull()) return {};
843 wm.Add(BRepBuilderAPI_MakeEdge(crv, va, vb).Edge());
844 }
845 }
846 }
847 wm.Build();
848 // IsDone() reflects only the LAST Add: BRepLib_MakeWire::Add sets BRepLib_DisconnectedWire
849 // + NotDone() and returns on a disconnected edge (dropping it), but every successful Add
850 // finishes with BRepLib_WireDone + Done(), overwriting that failure (BRepLib_MakeWire.cxx).
851 // So dropped edges go unnoticed — count the edges actually in the wire instead.
852 if (!wm.IsDone()) return {};
853 const TopoDS_Wire wire = wm.Wire();
854 size_t edge_count = 0;
855 for (TopExp_Explorer ex(wire, TopAbs_EDGE); ex.More(); ex.Next()) ++edge_count;
856 if (edge_count != (loop.closed_single ? 1 : loop.members.size())) return {};
857 out.push_back(wire);
858 }
859 return out;
860}
@ Edge
Definition Measure.hpp:26
_Item< ExPolygon > Item
Definition Arrange.cpp:93
Eigen::Matrix< double, 3, 1, Eigen::DontAlign > Vec3d
Definition Point.hpp:68
double sketch_join_tol()
Definition SketchEngine.cpp:80
void center(ExPolygonsWithIds &e)
Definition Emboss.cpp:1382
RaycastManager::TrItems::iterator find(RaycastManager::TrItems &items, const RaycastManager::TrKey &key)
Definition RaycastManager.cpp:298
std::vector< VI > Loop
Definition CutSurface.cpp:301

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::BSpline, Slic3r::center(), Slic3r::SketchEntity::center, Slic3r::SketchEntity::Circle, Slic3r::SketchEntity::construction, Slic3r::SketchEntity::ctrl, Slic3r::SketchEntity::Ellipse, Slic3r::SketchEntity::EllipseArc, Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, Slic3r::SketchPlane::normal, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::Point, Slic3r::SketchEntity::radius, Slic3r::SketchEntity::rminor, Slic3r::sketch_join_tol(), Slic3r::SketchEntity::start_angle, Slic3r::SketchPlane::to_world(), Slic3r::SketchEntity::type, Slic3r::SketchPlane::x_axis, and Slic3r::SketchPlane::y_axis.

Referenced by Slic3r::CadDocument::build_sketch_face(), and entities_to_wire().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ extend_entity()

bool Slic3r::SketchEngine::extend_entity ( SketchEntity & e,
const std::vector< SketchEntity > & others,
const Vec2d & pick )
static
2010{
2011 // Arc subject: grow the sweep toward the picked end up to the nearest crossing,
2012 // capped at a full turn so the arc never self-overlaps. (A Circle is already
2013 // closed — nothing to extend.)
2014 if (e.type == SketchEntity::Type::Arc) {
2015 double sweep = e.end_angle - e.start_angle;
2016 double mag = std::abs(sweep);
2017 if (mag < 1e-12) return false;
2018 double sgn = (sweep >= 0.0) ? 1.0 : -1.0;
2019 double room = 2.0 * M_PI - mag; // max extra sweep before a full turn
2020 if (room <= 1e-9) return false;
2021
2022 double phi_pick = std::atan2(pick.y() - e.center.y(), pick.x() - e.center.x());
2023 double up = wrap_2pi(sgn * (phi_pick - e.start_angle)) / mag; // pick fraction on arc
2024 const bool extend_end = (up > 0.5);
2025
2026 double best = std::numeric_limits<double>::max();
2027 for (const auto& other : others) {
2028 for (double phi : circle_cross_angles(e.center, e.radius, other)) {
2029 double adv = extend_end ? wrap_2pi(sgn * (phi - e.end_angle))
2030 : wrap_2pi(sgn * (e.start_angle - phi));
2031 if (adv > 1e-9 && adv <= room + 1e-9 && adv < best) best = adv;
2032 }
2033 }
2034 if (best == std::numeric_limits<double>::max()) return false;
2035 if (extend_end) e.end_angle += sgn * best;
2036 else e.start_angle -= sgn * best;
2037 sync_arc_ends(e);
2038 return true;
2039 }
2040
2041 if (e.type != SketchEntity::Type::Line) return false;
2042
2043 Vec2d adir = e.p1 - e.p0;
2044 double La2 = adir.dot(adir);
2045 if (La2 < 1e-18) return false;
2046
2047 double t_pick = (pick - e.p0).dot(adir) / La2;
2048
2049 std::vector<double> hits;
2050 for (const auto& other : others) {
2051 auto h = line_entity_hits(e.p0, adir, other);
2052 hits.insert(hits.end(), h.begin(), h.end());
2053 }
2054 if (hits.empty()) return false;
2055
2056 if (t_pick > 0.5) {
2057 double tc = std::numeric_limits<double>::max();
2058 for (double t : hits) {
2059 if (t > 1.0 + 1e-9 && t < tc)
2060 tc = t;
2061 }
2062 if (tc == std::numeric_limits<double>::max()) return false;
2063 e.p1 = e.p0 + tc * adir;
2064 } else {
2065 double tc = -std::numeric_limits<double>::max();
2066 for (double t : hits) {
2067 if (t < -1e-9 && t > tc)
2068 tc = t;
2069 }
2070 if (tc == -std::numeric_limits<double>::max()) return false;
2071 e.p0 = e.p0 + tc * adir;
2072 }
2073
2074 return true;
2075}
#define M_PI
Definition ExtrusionSimulator.cpp:31
static std::vector< double > circle_cross_angles(const Vec2d &C, double R, const SketchEntity &other)
Definition SketchEngine.cpp:1794
static std::vector< double > line_entity_hits(const Vec2d &a0, const Vec2d &adir, const SketchEntity &other)
Definition SketchEngine.cpp:1720
static void sync_arc_ends(SketchEntity &e)
Definition SketchEngine.cpp:1892
static double wrap_2pi(double x)
Definition SketchEngine.cpp:1882
int sgn(T val)
Definition Fill3DHoneycomb.cpp:24
T dot(const boost::geometry::model::d2::point_xy< T > &v1, const boost::geometry::model::d2::point_xy< T > &v2)
Definition ExtrusionSimulator.cpp:154
T mag(const boost::geometry::model::d2::point_xy< T > &v)
Definition ExtrusionSimulator.cpp:184
@ up
Definition MarchingSquares.hpp:192

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::center, Slic3r::circle_cross_angles(), Slic3r::dot(), Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, Slic3r::line_entity_hits(), M_PI, Slic3r::mag(), Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::radius, Slic3r::sgn(), Slic3r::SketchEntity::start_angle, Slic3r::sync_arc_ends(), Slic3r::SketchEntity::type, and Slic3r::wrap_2pi().

Referenced by Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::apply_live_trim(), and Slic3r::GUI::DesignSketchTool::compute_trim_preview().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fillet_lines()

bool Slic3r::SketchEngine::fillet_lines ( const SketchEntity & a,
const SketchEntity & b,
double r,
SketchEntity & a_out,
SketchEntity & b_out,
SketchEntity & arc_out )
static
1562{
1563 if (a.type != SketchEntity::Type::Line || b.type != SketchEntity::Type::Line || r <= 1e-9)
1564 return false;
1565
1566 Vec2d da = a.p1 - a.p0;
1567 Vec2d db = b.p1 - b.p0;
1568 double denom = da.x() * db.y() - da.y() * db.x();
1569 if (std::abs(denom) < 1e-12)
1570 return false;
1571
1572 Vec2d diff = b.p0 - a.p0;
1573 double s = (diff.x() * db.y() - diff.y() * db.x()) / denom;
1574 Vec2d C = a.p0 + s * da;
1575
1576 Vec2d ua;
1577 int a_near_idx;
1578 {
1579 double d0 = (a.p0 - C).norm();
1580 double d1 = (a.p1 - C).norm();
1581 if (d0 <= d1) {
1582 a_near_idx = 0;
1583 ua = a.p1 - C;
1584 } else {
1585 a_near_idx = 1;
1586 ua = a.p0 - C;
1587 }
1588 }
1589 if (ua.norm() < 1e-12) return false;
1590 ua.normalize();
1591
1592 Vec2d ub;
1593 int b_near_idx;
1594 {
1595 double d0 = (b.p0 - C).norm();
1596 double d1 = (b.p1 - C).norm();
1597 if (d0 <= d1) {
1598 b_near_idx = 0;
1599 ub = b.p1 - C;
1600 } else {
1601 b_near_idx = 1;
1602 ub = b.p0 - C;
1603 }
1604 }
1605 if (ub.norm() < 1e-12) return false;
1606 ub.normalize();
1607
1608 double cosT = ua.dot(ub);
1609 cosT = std::max(-1.0, std::min(1.0, cosT));
1610 double theta = std::acos(cosT);
1611 if (theta < 1e-6 || theta > M_PI - 1e-6)
1612 return false;
1613
1614 double t = r / std::tan(theta / 2.0);
1615 {
1616 Vec2d a_far = (a_near_idx == 0) ? a.p1 : a.p0;
1617 Vec2d b_far = (b_near_idx == 0) ? b.p1 : b.p0;
1618 if (t > (a_far - C).norm() || t > (b_far - C).norm())
1619 return false;
1620 }
1621
1622 Vec2d Ta = C + t * ua;
1623 Vec2d Tb = C + t * ub;
1624
1625 Vec2d bis = ua + ub;
1626 if (bis.norm() < 1e-12) return false;
1627 bis.normalize();
1628
1629 double dCO = r / std::sin(theta / 2.0);
1630 Vec2d O = C + dCO * bis;
1631
1632 a_out = a;
1633 b_out = b;
1634 if (a_near_idx == 0)
1635 a_out.p0 = Ta;
1636 else
1637 a_out.p1 = Ta;
1638
1639 if (b_near_idx == 0)
1640 b_out.p0 = Tb;
1641 else
1642 b_out.p1 = Tb;
1643
1644 arc_out = SketchEntity{};
1645 arc_out.type = SketchEntity::Type::Arc;
1646 arc_out.center = O;
1647 arc_out.radius = r;
1648 arc_out.p0 = Ta;
1649 arc_out.p1 = Tb;
1650 arc_out.start_angle = std::atan2(Ta.y() - O.y(), Ta.x() - O.x());
1651
1652 double sb = std::atan2(Tb.y() - O.y(), Tb.x() - O.x());
1653 double sweep = sb - arc_out.start_angle;
1654 while (sweep <= -M_PI) sweep += 2.0 * M_PI;
1655 while (sweep > M_PI) sweep -= 2.0 * M_PI;
1656 arc_out.end_angle = arc_out.start_angle + sweep;
1657
1658 return true;
1659}

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::center, Slic3r::diff(), Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, M_PI, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::radius, Slic3r::SketchEntity::start_angle, and Slic3r::SketchEntity::type.

Referenced by Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::confirm_op(), and Slic3r::GUI::DesignSketchTool::recompute_op_ghost().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_bridge()

SketchEntity Slic3r::SketchEngine::make_bridge ( const SketchEntity & a,
int a_end,
const SketchEntity & b,
int b_end )
static
2083{
2084 auto endpoint = [](const SketchEntity& e, int end) -> Vec2d {
2085 return end == 0 ? e.p0 : e.p1;
2086 };
2087
2088 auto tangent = [](const SketchEntity& e, int end, const Vec2d& fallback_dir) -> Vec2d {
2089 switch (e.type) {
2091 Vec2d dir = end == 1 ? e.p1 - e.p0 : e.p0 - e.p1;
2092 double len = dir.norm();
2093 if (len < 1e-12) return fallback_dir;
2094 return dir / len;
2095 }
2097 double theta = end == 1 ? e.end_angle : e.start_angle;
2098 // Tangent to the circle at angle theta, CCW: (-sin θ, cos θ).
2099 // At end=1 (end_angle), the outward direction is along the sweep direction.
2100 // At end=0 (start_angle), outward is opposite the sweep direction.
2101 double sweep = e.end_angle - e.start_angle;
2102 int sign = end == 1 ? (sweep >= 0 ? 1 : -1) : (sweep >= 0 ? -1 : 1);
2103 Vec2d t(-std::sin(theta), std::cos(theta));
2104 return t * double(sign);
2105 }
2106 default:
2107 // ponytail: straight-ish bridge for unsupported entity types.
2108 return fallback_dir;
2109 }
2110 };
2111
2112 const Vec2d Pa = endpoint(a, a_end);
2113 const Vec2d Pb = endpoint(b, b_end);
2114 const double d = (Pb - Pa).norm();
2115
2116 // Fallback tangent direction: point toward the other endpoint.
2117 Vec2d fallback = d < 1e-9 ? Vec2d(1, 0) : (Pb - Pa) / d;
2118 Vec2d Ta = tangent(a, a_end, fallback);
2119 Vec2d Tb = tangent(b, b_end, fallback * -1.0);
2120
2121 const double k = std::max(d, 1e-9) / 3.0;
2122
2123 SketchEntity e;
2125 e.construction = false;
2126 // Pb + Tb*k, not minus: with Tb outward, minus put the last inner pole on b's side, so the
2127 // curve overshot Pb (and against a line arrived with a cusp instead of continuing into it).
2128 e.ctrl = { Pa, Pa + Ta * k, Pb + Tb * k, Pb };
2129 e.p0 = e.ctrl.front();
2130 e.p1 = e.ctrl.back();
2131 return e;
2132}
T * end(Slic3r::Mat< N, M, T > &mat)
Definition Point.hpp:841

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::BSpline, Slic3r::SketchEntity::construction, Slic3r::SketchEntity::ctrl, Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::start_angle, and Slic3r::SketchEntity::type.

Referenced by Slic3r::CadDocument::add_bridge().

Here is the caller graph for this function:

◆ make_extrude() [1/2]

TopoDS_Shape Slic3r::SketchEngine::make_extrude ( const TopoDS_Face & face,
const SketchPlane & plane,
double length,
bool symmetric = false,
double taper_deg = 0.0 )
static
189{
190 gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
191 return extrude_face_internal(face, dir, length, symmetric);
192}
static TopoDS_Shape extrude_face_internal(const TopoDS_Face &face, const gp_Dir &dir, double length, bool symmetric)
Definition SketchEngine.cpp:168
double length(const Points &pts)
Definition MultiPoint.hpp:181

References Slic3r::extrude_face_internal(), Slic3r::length(), and Slic3r::SketchPlane::normal.

Here is the call graph for this function:

◆ make_extrude() [2/2]

TopoDS_Shape Slic3r::SketchEngine::make_extrude ( const TopoDS_Wire & wire,
const SketchPlane & plane,
double length,
bool symmetric = false,
double taper_deg = 0.0 )
static
181{
182 BRepBuilderAPI_MakeFace fm(wire);
183 if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
184 return make_extrude(fm.Face(), plane, length, symmetric, taper_deg);
185}
static TopoDS_Shape make_extrude(const TopoDS_Wire &wire, const SketchPlane &plane, double length, bool symmetric=false, double taper_deg=0.0)
Definition SketchEngine.cpp:179
#define _u8L(s)
Definition I18N.hpp:11

References _u8L, Slic3r::length(), and make_extrude().

Referenced by Slic3r::CadDocument::apply_cut(), Slic3r::CadDocument::apply_feature(), and make_extrude().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_extrude_face()

TopoDS_Shape Slic3r::SketchEngine::make_extrude_face ( const TopoDS_Face & face,
const SketchPlane & plane,
double length,
bool symmetric = false,
double taper_deg = 0.0 )
static
256{
257 gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
258 return extrude_face_internal(face, dir, length, symmetric);
259}

References Slic3r::extrude_face_internal(), Slic3r::length(), and Slic3r::SketchPlane::normal.

Referenced by Slic3r::CadDocument::apply_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_extrude_regions()

TopoDS_Shape Slic3r::SketchEngine::make_extrude_regions ( const std::vector< std::vector< std::vector< Vec2d > > > & regions,
const SketchPlane & plane,
double length,
bool symmetric = false )
static
264{
265 // The loop below skips regions that fail, so a zero depth is rejected before it.
266 if (std::abs(length) < 1e-9) throw std::runtime_error(_u8L("extrude depth is zero"));
267
268 // Drop consecutive coincident points and the closing duplicate. FreeType /
269 // SVG flattening routinely emits repeated points which would build a
270 // degenerate OCCT edge and make the wire builder throw — sanitising keeps a
271 // single bad glyph from killing the whole extrude.
272 auto clean = [](const std::vector<Vec2d>& pts) {
273 // The sketch's own joint tolerance: points closer than this are one point everywhere
274 // else in the sketcher, so they must not become a sub-micron edge here either.
275 const double eps2 = kSketchJoinTol * kSketchJoinTol;
276 std::vector<Vec2d> out;
277 out.reserve(pts.size());
278 for (const Vec2d& p : pts)
279 if (out.empty() || (p - out.back()).squaredNorm() > eps2)
280 out.push_back(p);
281 while (out.size() >= 2 && (out.front() - out.back()).squaredNorm() <= eps2)
282 out.pop_back();
283 return out;
284 };
285
286 // Build a closed planar wire from a contour. Never throws — returns a null
287 // wire on any failure so the caller can skip just that contour. Winding is
288 // NOT normalised here: ShapeFix_Face::FixOrientation() below classifies outer
289 // vs hole by geometry and fixes orientations, which is robust to the
290 // inconsistent winding Emboss/NSVG glyph contours arrive with (manual
291 // winding guesses extrude holed glyphs (P, e, o, 8) inverted or solid).
292 auto contour_wire = [&](const std::vector<Vec2d>& raw) -> TopoDS_Wire {
293 std::vector<Vec2d> pts = clean(raw);
294 if (pts.size() < 3) return TopoDS_Wire{};
295 SketchProfile prof;
296 prof.points = std::move(pts);
297 prof.closed = true;
298 try { return prof.to_occt_wire(plane); }
299 catch (...) { return TopoDS_Wire{}; }
300 };
301
302 gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
303 // Faces are built ON the sketch plane, as wires_to_face does: a surface inferred from the
304 // outer wire need not share the sketch normal, and then the extrude direction and the hole
305 // classification disagree with it.
306 const gp_Pln pln(gp_Pnt(plane.origin.x(), plane.origin.y(), plane.origin.z()), dir);
307
308 // Accumulate each region's solid into a compound rather than boolean-fusing:
309 // glyphs are independent profiles, so a compound avoids every boolean-failure
310 // mode (and is faster). Holes are still handled per region by MakeFace.
311 BRep_Builder bb;
312 TopoDS_Compound comp;
313 bb.MakeCompound(comp);
314 int count = 0;
315 TopoDS_Shape last;
316
317 for (const auto& region : regions) {
318 if (region.empty()) continue;
319 try {
320 TopoDS_Wire outer = contour_wire(region[0]);
321 if (outer.IsNull()) continue;
322
323 // Add the outer loop + every hole loop as-is, then let ShapeFix_Face
324 // classify outer vs holes by area/containment and set correct wire
325 // orientations. This is winding-independent, so holed glyphs extrude
326 // with a solid body and empty counters regardless of source winding.
327 // Probe on the inferred surface first: naming a plane makes MakeFace accept a wire
328 // that bounds nothing, and this check is what skips such a contour.
329 if (!BRepBuilderAPI_MakeFace(outer).IsDone()) continue;
330 BRepBuilderAPI_MakeFace fm(pln, outer);
331 if (!fm.IsDone()) continue;
332 for (size_t h = 1; h < region.size(); ++h) {
333 TopoDS_Wire hole = contour_wire(region[h]);
334 if (hole.IsNull()) continue;
335 fm.Add(hole);
336 }
337 if (!fm.IsDone()) continue;
338
339 ShapeFix_Face sff(fm.Face());
340 sff.FixOrientation();
341 const TopoDS_Face face = sff.Face();
342
343 TopoDS_Shape solid = extrude_face_internal(face, dir, length, symmetric);
344 bb.Add(comp, solid);
345 last = solid;
346 ++count;
347 } catch (...) {
348 continue; // skip one bad glyph rather than fail the whole insert
349 }
350 }
351
352 if (count == 0) throw std::runtime_error(_u8L("imported regions produced no extrudable geometry"));
353 return count == 1 ? last : TopoDS_Shape(comp); // avoid a compound-of-one
354}
constexpr double kSketchJoinTol
Definition SketchEngine.hpp:89

References _u8L, Slic3r::SketchProfile::closed, count, Slic3r::extrude_face_internal(), Slic3r::kSketchJoinTol, Slic3r::length(), Slic3r::SketchPlane::normal, Slic3r::SketchPlane::origin, Slic3r::SketchProfile::points, and Slic3r::SketchProfile::to_occt_wire().

Referenced by Slic3r::CadDocument::apply_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_extrude_taper()

TopoDS_Shape Slic3r::SketchEngine::make_extrude_taper ( const TopoDS_Wire & wire,
const SketchPlane & plane,
double length,
double taper_deg )
static
216{
217 gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
218 auto straight = [&]() -> TopoDS_Shape {
219 BRepBuilderAPI_MakeFace fm(wire);
220 if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
221 return make_prism(fm.Face(), gp_Vec(dir) * length);
222 };
223 if (std::abs(taper_deg) >= 89.0 || std::abs(length) < 1e-9) return straight();
224 const double off = length * std::tan(taper_deg * M_PI / 180.0);
225 if (std::abs(off) < 1e-7) return straight();
226 try {
227 // 1) offset the planar base wire in its own plane by `off`
228 BRepOffsetAPI_MakeOffset mko(wire, GeomAbs_Arc);
229 mko.Perform(off);
230 if (!mko.IsDone()) return straight();
231 TopoDS_Shape offShape = mko.Shape();
232 TopoDS_Wire topFlat;
233 if (offShape.ShapeType() == TopAbs_WIRE) topFlat = TopoDS::Wire(offShape);
234 else { for (TopExp_Explorer ex(offShape, TopAbs_WIRE); ex.More(); ex.Next()) { topFlat = TopoDS::Wire(ex.Current()); break; } }
235 if (topFlat.IsNull()) return straight();
236 // 2) lift it along the normal by `length`
237 gp_Trsf tr; tr.SetTranslation(gp_Vec(dir) * length);
238 BRepBuilderAPI_Transform xf(topFlat, tr, true);
239 TopoDS_Wire topWire = TopoDS::Wire(xf.Shape());
240 // 3) loft base -> top into a solid
241 BRepOffsetAPI_ThruSections loft(true /*solid*/, false /*ruled*/);
242 loft.AddWire(wire);
243 loft.AddWire(topWire);
244 loft.Build();
245 if (!loft.IsDone()) return straight();
246 TopoDS_Shape s = loft.Shape();
247 if (s.IsNull()) return straight();
248 return s;
249 } catch (const Standard_Failure&) {
250 return straight();
251 }
252}
static TopoDS_Shape make_prism(const TopoDS_Shape &base, const gp_Vec &vec)
Definition SketchEngine.cpp:160

References _u8L, Slic3r::length(), M_PI, make_prism(), and Slic3r::SketchPlane::normal.

Referenced by Slic3r::CadDocument::apply_feature().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_extrude_two_sided() [1/2]

TopoDS_Shape Slic3r::SketchEngine::make_extrude_two_sided ( const TopoDS_Face & face,
const SketchPlane & plane,
double up,
double down )
static
204{
205 gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
206 const double u = std::abs(up), d = std::abs(down);
207 if (d < 1e-9) return make_prism(face, gp_Vec(dir) * u);
208 if (u < 1e-9) return make_prism(face, gp_Vec(dir) * -d);
209 BRepAlgoAPI_Fuse fuse(make_prism(face, gp_Vec(dir) * u), make_prism(face, gp_Vec(dir) * -d));
210 if (!fuse.IsDone()) throw std::runtime_error(_u8L("two-sided extrude fuse failed"));
211 return fuse.Shape();
212}

References _u8L, make_prism(), and Slic3r::SketchPlane::normal.

Here is the call graph for this function:

◆ make_extrude_two_sided() [2/2]

TopoDS_Shape Slic3r::SketchEngine::make_extrude_two_sided ( const TopoDS_Wire & wire,
const SketchPlane & plane,
double up,
double down )
static
196{
197 BRepBuilderAPI_MakeFace fm(wire);
198 if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
199 return make_extrude_two_sided(fm.Face(), plane, up, down);
200}
static TopoDS_Shape make_extrude_two_sided(const TopoDS_Wire &wire, const SketchPlane &plane, double up, double down)
Definition SketchEngine.cpp:194

References _u8L, and make_extrude_two_sided().

Referenced by Slic3r::CadDocument::apply_feature(), and make_extrude_two_sided().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ make_loft()

TopoDS_Shape Slic3r::SketchEngine::make_loft ( const std::vector< TopoDS_Wire > & profiles,
bool ruled )
static
412{
413 if (profiles.size() < 2)
414 throw std::runtime_error(_u8L("loft needs at least 2 profiles"));
415 BRepOffsetAPI_ThruSections loft(true /*solid*/, ruled);
416 for (const TopoDS_Wire& w : profiles) {
417 if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
418 loft.AddWire(w);
419 }
420 loft.Build();
421 if (!loft.IsDone()) throw std::runtime_error(_u8L("loft failed"));
422 TopoDS_Shape s = loft.Shape();
423 if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no solid"));
424 return s;
425}

References _u8L.

Referenced by Slic3r::CadDocument::apply_feature().

Here is the caller graph for this function:

◆ make_loft_surface()

TopoDS_Shape Slic3r::SketchEngine::make_loft_surface ( const std::vector< TopoDS_Wire > & profiles,
bool ruled )
static
429{
430 if (profiles.size() < 2)
431 throw std::runtime_error(_u8L("loft needs at least 2 profiles"));
432 BRepOffsetAPI_ThruSections loft(false /*shell, no end caps*/, ruled);
433 for (const TopoDS_Wire& w : profiles) {
434 if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
435 loft.AddWire(w);
436 }
437 loft.Build();
438 if (!loft.IsDone()) throw std::runtime_error(_u8L("loft failed"));
439 TopoDS_Shape s = loft.Shape();
440 if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no shape"));
441 return s;
442}

References _u8L.

Referenced by Slic3r::CadDocument::apply_feature().

Here is the caller graph for this function:

◆ make_pocket()

TopoDS_Shape Slic3r::SketchEngine::make_pocket ( const TopoDS_Wire & wire,
const SketchPlane & plane,
const TopoDS_Shape & target,
double depth )
static
446{
447 BRepBuilderAPI_MakeFace fm(wire);
448 if (!fm.IsDone()) throw std::runtime_error(_u8L("Pocket face failed"));
449 TopoDS_Shape tool = extrude_face_internal(fm.Face(),
450 gp_Dir(plane.normal.x(), plane.normal.y(), plane.normal.z()), depth + 1.0, false);
451 BRepAlgoAPI_Cut cut(target, tool);
452 if (!cut.IsDone()) throw std::runtime_error(_u8L("Pocket cut failed"));
453 return cut.Shape();
454}

References _u8L, Slic3r::extrude_face_internal(), and Slic3r::SketchPlane::normal.

Here is the call graph for this function:

◆ make_prism()

TopoDS_Shape Slic3r::SketchEngine::make_prism ( const TopoDS_Shape & base,
const gp_Vec & vec )
static
161{
162 if (vec.Magnitude() < 1e-9) throw std::runtime_error(_u8L("extrude depth is zero"));
163 BRepPrimAPI_MakePrism prism(base, vec);
164 if (!prism.IsDone()) throw std::runtime_error(_u8L("extrude failed"));
165 return prism.Shape();
166}

References _u8L.

Referenced by Slic3r::CadDocument::apply_feature(), Slic3r::extrude_face_internal(), make_extrude_taper(), and make_extrude_two_sided().

Here is the caller graph for this function:

◆ make_revolve()

TopoDS_Shape Slic3r::SketchEngine::make_revolve ( const TopoDS_Wire & wire,
const gp_Ax1 & axis,
double angle_deg = 360.0 )
static
357{
358 BRepBuilderAPI_MakeFace faceMaker(wire);
359 if (!faceMaker.IsDone())
360 throw std::runtime_error(_u8L("Failed to make face from wire"));
361 TopoDS_Face face = faceMaker.Face();
362
363 // A profile on both sides of the axis sweeps through itself; MakeRevol then fails with no
364 // reason, or builds an invalid solid. Sample every edge and name the cause instead.
365 {
366 const gp_Pnt o = axis_in.Location();
367 const gp_Dir d = axis_in.Direction();
368 bool pos = false, neg = false;
369 gp_Vec side_ref;
370 for (TopExp_Explorer ex(wire, TopAbs_EDGE); ex.More(); ex.Next()) {
371 BRepAdaptor_Curve c(TopoDS::Edge(ex.Current()));
372 for (int i = 0; i <= 16; ++i) {
373 const gp_Pnt p = c.Value(c.FirstParameter() + (c.LastParameter() - c.FirstParameter()) * i / 16.0);
374 const gp_Vec off = gp_Vec(o, p) - gp_Vec(d) * gp_Vec(o, p).Dot(gp_Vec(d)); // from the axis
375 if (off.Magnitude() < 1e-6)
376 continue;
377 if (side_ref.Magnitude() == 0.0) { side_ref = off; pos = true; continue; }
378 (off.Dot(side_ref) > 0.0 ? pos : neg) = true;
379 }
380 }
381 if (pos && neg)
382 throw std::runtime_error(_u8L("the profile crosses the revolve axis — it must lie on one side of it"));
383 }
384 gp_Ax1 axis = axis_in;
385 double angle_rad = angle_deg * M_PI / 180.0;
386 // A negative angle is expressed as a positive sweep about the reversed axis,
387 // since BRepPrimAPI_MakeRevol expects an angle in (0, 2*pi].
388 if (angle_rad < 0) { axis.Reverse(); angle_rad = -angle_rad; }
389 BRepPrimAPI_MakeRevol rev(face, axis, angle_rad);
390 if (!rev.IsDone())
391 throw std::runtime_error(_u8L("Failed to revolve"));
392 if (!BRepCheck_Analyzer(rev.Shape()).IsValid())
393 throw std::runtime_error(_u8L("the profile crosses the revolve axis — it must lie on one side of it"));
394 return rev.Shape();
395}
Vec3d pos(const Pt &p)
Definition ReprojectPointsOnMesh.hpp:16

References _u8L, and M_PI.

Referenced by Slic3r::CadDocument::apply_feature().

Here is the caller graph for this function:

◆ make_sweep()

TopoDS_Shape Slic3r::SketchEngine::make_sweep ( const TopoDS_Wire & profile,
const TopoDS_Wire & path )
static
398{
399 BRepBuilderAPI_MakeFace faceMaker(profile);
400 if (!faceMaker.IsDone())
401 throw std::runtime_error(_u8L("Failed to make face from sweep profile"));
402 TopoDS_Face face = faceMaker.Face();
403
404 BRepOffsetAPI_MakePipe pipe(path, face);
405 pipe.Build();
406 if (!pipe.IsDone())
407 throw std::runtime_error(_u8L("Failed to sweep profile along path"));
408 return pipe.Shape();
409}

References _u8L.

Referenced by Slic3r::CadDocument::apply_feature().

Here is the caller graph for this function:

◆ mirror_entities()

std::vector< SketchEntity > Slic3r::SketchEngine::mirror_entities ( const std::vector< SketchEntity > & src,
const Vec2d & a,
const Vec2d & b )
static
1010{
1011 Vec2d dir = b - a;
1012 if (dir.norm() < 1e-12)
1013 return src;
1014
1015 dir.normalize();
1016
1017 auto reflect = [&](const Vec2d& p) -> Vec2d {
1018 Vec2d v = p - a;
1019 return a + (2.0 * v.dot(dir)) * dir - v;
1020 };
1021
1022 std::vector<SketchEntity> out;
1023 out.reserve(src.size());
1024
1025 for (const auto& e : src) {
1026 SketchEntity m = e;
1027 switch (e.type) {
1029 m.p0 = reflect(e.p0);
1030 m.p1 = reflect(e.p1);
1031 break;
1033 m.p0 = reflect(e.p0);
1034 break;
1036 m.center = reflect(e.center);
1037 m.p0 = m.center;
1038 break;
1040 m.p0 = reflect(e.p0);
1041 m.p1 = reflect(e.p1);
1042 m.center = reflect(e.center);
1043
1044 const Vec2d& c = m.center;
1045 m.start_angle = std::atan2(m.p0.y() - c.y(), m.p0.x() - c.x());
1046 double raw_end = std::atan2(m.p1.y() - c.y(), m.p1.x() - c.x());
1047
1048 double s = e.end_angle - e.start_angle;
1049
1050 double sweep = raw_end - m.start_angle;
1051 while (sweep <= -2.0 * M_PI) sweep += 2.0 * M_PI;
1052 while (sweep >= 2.0 * M_PI) sweep -= 2.0 * M_PI;
1053
1054 if (s != 0.0 && sweep * s > 0.0) {
1055 if (sweep > 0.0)
1056 sweep -= 2.0 * M_PI;
1057 else
1058 sweep += 2.0 * M_PI;
1059 }
1060
1061 m.end_angle = m.start_angle + sweep;
1062 m.radius = e.radius;
1063 break;
1064 }
1067 m.center = reflect(e.center);
1068 // Reflect the major-axis direction; a/b unchanged.
1069 const Vec2d majdir(std::cos(e.rotation), std::sin(e.rotation));
1070 const Vec2d rdir = reflect(e.center + majdir) - m.center;
1071 m.rotation = std::atan2(rdir.y(), rdir.x());
1072 if (e.type == SketchEntity::Type::Ellipse) {
1073 m.p0 = m.center;
1074 } else {
1075 m.p0 = reflect(e.p0);
1076 m.p1 = reflect(e.p1);
1077 // Reflection reverses orientation. Like the Arc branch above, this pass keeps
1078 // each angle with ITS point (start with p0) and lets the sweep run clockwise;
1079 // the reversal pass below then swaps points and angles together, giving a CCW
1080 // arc whose start is p0. Pairing them crosswise here, as this used to, was
1081 // undone by that same swap and produced the complementary arc.
1082 auto param = [&](const Vec2d& P) {
1083 const Vec2d d = P - m.center;
1084 const double cu = std::cos(m.rotation), su = std::sin(m.rotation);
1085 const double u = d.x() * cu + d.y() * su;
1086 const double v = -d.x() * su + d.y() * cu;
1087 return std::atan2(v / std::max(e.rminor, 1e-9), u / std::max(e.radius, 1e-9));
1088 };
1089 m.start_angle = param(m.p0);
1090 m.end_angle = param(m.p1);
1091 while (m.end_angle >= m.start_angle) m.end_angle -= 2.0 * M_PI;
1092 }
1093 break;
1094 }
1096 for (auto& cp : m.ctrl) cp = reflect(cp);
1097 m.p0 = reflect(e.p0);
1098 m.p1 = reflect(e.p1);
1099 break;
1100 }
1101 out.push_back(m);
1102 }
1103
1104 // A REFLECTION REVERSES ORIENTATION, so the reflected half is handed back reversed — in
1105 // order, and each entity flipped — or it does not CONTINUE the chain it was made from.
1106 //
1107 // Draw half a stadium left-to-right along the bottom, round the cap, right-to-left along the
1108 // top, ending at (0, R). Reflecting each entity in place gives a half whose top run STARTS at
1109 // (-L, R) and ENDS at (0, R): it meets the original head-to-head, not head-to-tail. Every
1110 // consumer that walks the loop then has to cope, and two already had to be taught — the loop
1111 // area cancelled its own arc correction against the negated sweep, and offset put the
1112 // reflected half on the wrong side because it read each entity's STORED direction. Reversed
1113 // here, the two halves are one walkable chain and a mirrored CCW loop stays CCW.
1114 std::reverse(out.begin(), out.end());
1115 for (SketchEntity& m : out) {
1116 switch (m.type) {
1118 std::swap(m.p0, m.p1);
1119 break;
1122 std::swap(m.p0, m.p1);
1123 std::swap(m.start_angle, m.end_angle); // what "walked the other way" means
1124 break;
1126 std::swap(m.p0, m.p1);
1127 std::reverse(m.ctrl.begin(), m.ctrl.end());
1128 break;
1129 default:
1130 break; // circle, ellipse, point: no direction to reverse
1131 }
1132 }
1133
1134 return out;
1135}
@ P
Definition libslic3r.h:149

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::BSpline, Slic3r::SketchEntity::center, Slic3r::SketchEntity::Circle, Slic3r::SketchEntity::ctrl, Slic3r::SketchEntity::Ellipse, Slic3r::SketchEntity::EllipseArc, Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, M_PI, Slic3r::P, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::Point, Slic3r::SketchEntity::radius, Slic3r::SketchEntity::rotation, and Slic3r::SketchEntity::start_angle.

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_sketch_mirror(), Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::confirm_op(), and Slic3r::GUI::DesignSketchTool::recompute_op_ghost().

Here is the caller graph for this function:

◆ offset_entities()

std::vector< SketchEntity > Slic3r::SketchEngine::offset_entities ( const std::vector< SketchEntity > & src,
double d )
static
1331{
1332 std::vector<SketchEntity> out;
1333
1334 // Closed and unchainable kinds first: they carry no seams, so they pass straight through.
1335 std::vector<int> open_idx;
1336 for (int i = 0; i < int(src.size()); ++i) {
1337 if (off_is_open_curve(src[i])) { open_idx.push_back(i); continue; }
1338 SketchEntity o;
1339 if (off_one(src[i], d, o)) out.push_back(o);
1340 }
1341
1342 std::vector<bool> used(open_idx.size(), false);
1343
1344 // Stored endpoints of the k-th open entity.
1345 auto ends = [&](int k, Vec2d& p0, Vec2d& p1) { p0 = src[open_idx[k]].p0; p1 = src[open_idx[k]].p1; };
1346
1347 // An endpoint shared by no OTHER unused open curve is a FREE end: the loose end of an open
1348 // chain rather than a seam. (`used` matters — entities already pulled into a chain must not
1349 // count, otherwise the far end of the chain we just walked would look shared.)
1350 auto is_free = [&](int k, const Vec2d& pt) {
1351 for (size_t j = 0; j < open_idx.size(); ++j) {
1352 if (int(j) == k || used[j]) continue;
1353 Vec2d q0, q1; ends(int(j), q0, q1);
1354 if (off_same(pt, q0) || off_same(pt, q1)) return false;
1355 }
1356 return true;
1357 };
1358
1359 struct Chain {
1360 std::vector<std::pair<int, bool>> items; // (entity index, reversed)
1361 Vec2d start, end; // traversal start/end points
1362 };
1363
1364 // Walk one chain from the unused seed `s`, traversing AWAY from its free end. `reversed`
1365 // means the traversal enters at the entity's p1 and leaves at its p0, i.e. the entity is
1366 // travelled opposite to its STORED direction. An entity whose p1 is free (but p0 is not)
1367 // is the head of an open chain and must start reversed; an isolated entity or a closed
1368 // loop starts forward.
1369 auto walk = [&](int s) -> Chain {
1370 Chain c;
1371 Vec2d s0, s1; ends(s, s0, s1);
1372 const bool rev = !is_free(s, s0) && is_free(s, s1);
1373 c.items.emplace_back(s, rev);
1374 used[s] = true;
1375 c.start = rev ? s1 : s0;
1376 c.end = rev ? s0 : s1;
1377 for (;;) {
1378 int nxt = -1;
1379 bool nrev = false;
1380 for (size_t j = 0; j < open_idx.size(); ++j) {
1381 if (used[j]) continue;
1382 Vec2d q0, q1; ends(int(j), q0, q1);
1383 if (off_same(c.end, q0)) { nxt = int(j); nrev = false; break; }
1384 if (off_same(c.end, q1)) { nxt = int(j); nrev = true; break; }
1385 }
1386 if (nxt < 0) break;
1387 c.items.emplace_back(nxt, nrev);
1388 used[nxt] = true;
1389 Vec2d q0, q1; ends(nxt, q0, q1);
1390 c.end = nrev ? q0 : q1;
1391 }
1392 return c;
1393 };
1394
1395 // Offset one chain as traversed: every entity is offset with an EFFECTIVE distance that
1396 // already accounts for how it was walked, then reversed entities have their stored ends
1397 // swapped so the emitted chain is head-to-tail in traversal order (which is what keeps the
1398 // seam repair below — and any later offset/mirror — well-oriented). A chain whose final
1399 // traversal end coincides with its first traversal start is CLOSED.
1400 auto emit = [&](const Chain& c) {
1401 const bool closed = c.items.size() > 1 && off_same(c.end, c.start);
1402 std::vector<SketchEntity> off;
1403 off.reserve(c.items.size());
1404 for (const auto& it : c.items) {
1405 // A reversed entity offsets with -d rather than +d:
1406 // * a line walked backwards has its left-hand side on the other side, so -d;
1407 // * an arc walked backwards has its sweep sign effectively flipped, which is exactly
1408 // the `sgn` term off_one reads, so -d again.
1409 const double ed = it.second ? -d : d;
1410 SketchEntity o;
1411 if (!off_one(src[open_idx[it.first]], ed, o)) continue;
1412 if (it.second) off_reverse(o);
1413 off.push_back(o);
1414 }
1415 if (off.empty()) return;
1416 for (size_t i = 0; i + 1 < off.size(); ++i) off_join(off[i], off[i + 1]);
1417 if (closed && off.size() > 1) off_join(off.back(), off.front());
1418 for (auto& o : off) out.push_back(o);
1419 };
1420
1421 // Pass 1: open chains, seeded at a free end so they are never entered mid-way (which would
1422 // split one open chain in two and lose a seam).
1423 for (size_t s = 0; s < open_idx.size(); ++s) {
1424 if (used[s]) continue;
1425 Vec2d s0, s1; ends(int(s), s0, s1);
1426 if (!is_free(int(s), s0) && !is_free(int(s), s1)) continue;
1427 emit(walk(int(s)));
1428 }
1429 // Pass 2: what is left has no free end and is a CLOSED loop; start anywhere, forward.
1430 for (size_t s = 0; s < open_idx.size(); ++s) {
1431 if (used[s]) continue;
1432 emit(walk(int(s)));
1433 }
1434
1435 return out;
1436}
void off_reverse(SketchEntity &e)
Definition SketchEngine.cpp:1320
bool off_join(SketchEntity &a, SketchEntity &b)
Definition SketchEngine.cpp:1297
bool off_is_open_curve(const SketchEntity &e)
Definition SketchEngine.cpp:1161
bool off_one(const SketchEntity &e, double d, SketchEntity &out)
Definition SketchEngine.cpp:1250
bool off_same(const Vec2d &a, const Vec2d &b)
Definition SketchEngine.cpp:1154

Referenced by Slic3r::GUI::anonymous_namespace{McpControl.cpp}::action_sketch_offset(), Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::confirm_op(), and Slic3r::GUI::DesignSketchTool::recompute_op_ghost().

Here is the caller graph for this function:

◆ tessellate() [1/2]

TriangleMesh Slic3r::SketchEngine::tessellate ( const TopoDS_Shape & shape,
double linear_deflection = 0.01,
double angular_deflection = 0.5 )
static
459{
460 std::vector<int> dummy;
461 return tessellate(shape, dummy, linear_deflection, angular_deflection);
462}
static TriangleMesh tessellate(const TopoDS_Shape &shape, double linear_deflection=0.01, double angular_deflection=0.5)
Definition SketchEngine.cpp:456

References tessellate().

Referenced by tessellate(), and Slic3r::tessellate_bodies().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ tessellate() [2/2]

TriangleMesh Slic3r::SketchEngine::tessellate ( const TopoDS_Shape & shape,
std::vector< int > & tri_face,
double linear_deflection = 0.01,
double angular_deflection = 0.5 )
static
468{
469 tri_face.clear();
470 BRepMesh_IncrementalMesh mesh(shape, linear_deflection, false, angular_deflection, true);
471
472 int nbNodes = 0, nbTriangles = 0;
473 for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
474 TopLoc_Location loc;
475 Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc);
476 if (!tri.IsNull()) {
477 nbNodes += tri->NbNodes();
478 nbTriangles += tri->NbTriangles();
479 }
480 }
481
482 if (nbTriangles == 0 || nbNodes == 0)
483 return TriangleMesh{};
484
485 indexed_triangle_set raw;
486 raw.vertices.reserve(nbNodes);
487 raw.indices.reserve(nbTriangles);
488 tri_face.reserve(nbTriangles);
489
490 int faceIdx = -1;
491 int nodeOff = 0;
492 for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) {
493 ++faceIdx;
494
495 TopLoc_Location loc;
496 Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(TopoDS::Face(exp.Current()), loc);
497 if (tri.IsNull()) continue;
498
499 gp_Trsf trsf = loc.Transformation();
500 for (int i = 1; i <= tri->NbNodes(); ++i) {
501 gp_Pnt p = tri->Node(i);
502 p.Transform(trsf);
503 raw.vertices.emplace_back(Vec3f(p.X(), p.Y(), p.Z()));
504 }
505
506 TopAbs_Orientation orient = exp.Current().Orientation();
507 int ids[3];
508 for (int i = 1; i <= tri->NbTriangles(); ++i) {
509 Poly_Triangle t = tri->Triangle(i);
510 t.Get(ids[0], ids[1], ids[2]);
511 if (orient == TopAbs_REVERSED)
512 std::swap(ids[1], ids[2]);
513
514 raw.indices.emplace_back(nodeOff + ids[0] - 1,
515 nodeOff + ids[1] - 1,
516 nodeOff + ids[2] - 1);
517 tri_face.push_back(faceIdx);
518 }
519 nodeOff += tri->NbNodes();
520 }
521
522 std::map<std::tuple<float, float, float>, int> vmap;
523 indexed_triangle_set its;
524 its.indices.reserve(raw.indices.size());
525 its.vertices.reserve(raw.vertices.size() / 2);
526
527 std::vector<int> kept_face;
528 kept_face.reserve(tri_face.size());
529
530 for (size_t ti = 0; ti < raw.indices.size(); ++ti) {
531 const auto& tri = raw.indices[ti];
532 stl_triangle_vertex_indices new_tri;
533 for (int j = 0; j < 3; ++j) {
534 const stl_vertex& v = raw.vertices[tri[j]];
535 auto key = std::make_tuple(v.x(), v.y(), v.z());
536 auto it = vmap.find(key);
537 if (it == vmap.end()) {
538 int new_id = static_cast<int>(its.vertices.size());
539 vmap[key] = new_id;
540 its.vertices.push_back(v);
541 new_tri[j] = new_id;
542 } else {
543 new_tri[j] = it->second;
544 }
545 }
546 // Drop triangles that welding collapsed to a repeated vertex. OCCT emits one at the
547 // pole of every degenerate surface parameterization — a sphere patch at a filleted
548 // corner has exactly one — and its v->v edge can never pair with a neighbour, so the
549 // mesh reports an open edge per corner and the slicer declares the model non-manifold
550 // and tells the user to repair it elsewhere. The triangle has zero area: removing it
551 // changes no geometry, only the mesh's bookkeeping.
552 if (new_tri[0] == new_tri[1] || new_tri[1] == new_tri[2] || new_tri[0] == new_tri[2])
553 continue;
554 its.indices.push_back(new_tri);
555 kept_face.push_back(tri_face[ti]);
556 }
557 tri_face.swap(kept_face); // tri_face stays index-aligned with its.indices
558
559 return TriangleMesh(std::move(its));
560}
static Orientation orient(const Point &a, const Point &b, const Point &c)
Definition Geometry.hpp:44
Eigen::Matrix< float, 3, 1, Eigen::DontAlign > Vec3f
Definition Point.hpp:65

References Slic3r::Geometry::orient().

Here is the call graph for this function:

◆ transform_entities()

std::vector< SketchEntity > Slic3r::SketchEngine::transform_entities ( const std::vector< SketchEntity > & src,
const Vec2d & move,
double angle,
double scale,
const Vec2d & pivot )
static
1501{
1502 std::vector<SketchEntity> out;
1503 out.reserve(src.size());
1504 const double ca = std::cos(angle), sa = std::sin(angle);
1505 const double rs = std::abs(scale); // radii are unsigned magnitudes
1506 // A negative scale is |scale| plus a half turn about the pivot: points get that from xf()
1507 // below, and angle-valued fields (arc ends, ellipse axis) need the same extra pi.
1508 const double turn = angle + (scale < 0.0 ? M_PI : 0.0);
1509 // Affine map: translate pivot to origin, scale, rotate, then translate by `move`.
1510 auto xf = [&](const Vec2d& p) -> Vec2d {
1511 const Vec2d d = scale * (p - pivot);
1512 return Vec2d(pivot.x() + ca * d.x() - sa * d.y(),
1513 pivot.y() + sa * d.x() + ca * d.y()) + move;
1514 };
1515
1516 for (const auto& e : src) {
1517 SketchEntity m = e; // carry construction flag, etc.
1518 switch (e.type) {
1520 m.p0 = xf(e.p0); m.p1 = xf(e.p1);
1521 break;
1523 m.p0 = xf(e.p0);
1524 break;
1526 m.center = xf(e.center); m.radius = e.radius * rs; m.p0 = m.center;
1527 break;
1529 m.center = xf(e.center);
1530 m.radius = e.radius * rs;
1531 m.start_angle = e.start_angle + turn;
1532 m.end_angle = e.end_angle + turn; // rigid sweep, shifted by rotation
1533 m.p0 = m.center + m.radius * Vec2d(std::cos(m.start_angle), std::sin(m.start_angle));
1534 m.p1 = m.center + m.radius * Vec2d(std::cos(m.end_angle), std::sin(m.end_angle));
1535 break;
1536 }
1539 m.center = xf(e.center);
1540 m.radius = e.radius * rs;
1541 m.rminor = e.rminor * rs;
1542 m.rotation = e.rotation + turn; // major axis rotates with the body
1543 if (e.type == SketchEntity::Type::Ellipse) {
1544 m.p0 = m.center;
1545 } else {
1546 m.p0 = xf(e.p0); m.p1 = xf(e.p1);
1547 // Parametric angles live in the (rotated) body frame -> unchanged.
1548 }
1549 break;
1551 for (auto& cp : m.ctrl) cp = xf(cp);
1552 m.p0 = xf(e.p0); m.p1 = xf(e.p1);
1553 break;
1554 }
1555 out.push_back(m);
1556 }
1557 return out;
1558}
int scale(const int val)
Definition WipeTowerDialog.cpp:58
double angle(const Eigen::MatrixBase< Derived > &v1, const Eigen::MatrixBase< Derived2 > &v2)
Definition Point.hpp:138

References Slic3r::angle(), Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::BSpline, Slic3r::SketchEntity::center, Slic3r::SketchEntity::Circle, Slic3r::SketchEntity::ctrl, Slic3r::SketchEntity::Ellipse, Slic3r::SketchEntity::EllipseArc, Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, M_PI, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::Point, Slic3r::SketchEntity::radius, Slic3r::SketchEntity::rminor, Slic3r::SketchEntity::rotation, scale(), and Slic3r::SketchEntity::start_angle.

Referenced by Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::confirm_transform(), and Slic3r::GUI::DesignSketchTool::recompute_tf_ghost().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ trim_entity()

bool Slic3r::SketchEngine::trim_entity ( SketchEntity & e,
const std::vector< SketchEntity > & others,
const Vec2d & pick )
static
1900{
1901 // Arc subject: parametrize by sweep fraction u in [0,1]; cut on the picked side.
1902 if (e.type == SketchEntity::Type::Arc) {
1903 double sweep = e.end_angle - e.start_angle;
1904 if (std::abs(sweep) < 1e-12) return false;
1905 double phi_pick = std::atan2(pick.y() - e.center.y(), pick.x() - e.center.x());
1906 double u_pick = (phi_pick - e.start_angle) / sweep;
1907 // Bring the pick onto the arc's [0,1] domain.
1908 while (u_pick < -1e-9) u_pick += (2.0 * M_PI) / std::abs(sweep);
1909 u_pick = std::max(0.0, std::min(1.0, u_pick));
1910
1911 std::vector<double> cuts;
1912 for (const auto& other : others) {
1913 for (double phi : circle_cross_angles(e.center, e.radius, other)) {
1914 double u = (phi - e.start_angle) / sweep;
1915 while (u < -1e-9) u += (2.0 * M_PI) / std::abs(sweep);
1916 if (u > 1e-9 && u < 1.0 - 1e-9) cuts.push_back(u);
1917 }
1918 }
1919 if (cuts.empty()) return false;
1920
1921 if (u_pick <= 0.5) {
1922 double uc = std::numeric_limits<double>::max();
1923 for (double u : cuts) if (u > u_pick + 1e-9 && u < uc) uc = u;
1924 if (uc == std::numeric_limits<double>::max()) return false;
1925 e.start_angle = e.start_angle + uc * sweep; // drop [0, uc)
1926 } else {
1927 double uc = -std::numeric_limits<double>::max();
1928 for (double u : cuts) if (u < u_pick - 1e-9 && u > uc) uc = u;
1929 if (uc == -std::numeric_limits<double>::max()) return false;
1930 e.end_angle = e.start_angle + uc * sweep; // drop (uc, 1]
1931 }
1932 sync_arc_ends(e);
1933 return true;
1934 }
1935
1936 // Circle subject: trimming opens it into an Arc that excludes the picked gap.
1937 if (e.type == SketchEntity::Type::Circle) {
1938 std::vector<double> ang;
1939 for (const auto& other : others)
1940 for (double phi : circle_cross_angles(e.center, e.radius, other))
1941 ang.push_back(wrap_2pi(phi));
1942 std::sort(ang.begin(), ang.end());
1943 ang.erase(std::unique(ang.begin(), ang.end(),
1944 [](double a, double b){ return std::abs(a - b) < 1e-7; }),
1945 ang.end());
1946 if (ang.size() < 2) return false;
1947
1948 double pk = wrap_2pi(std::atan2(pick.y() - e.center.y(), pick.x() - e.center.x()));
1949 const int n = int(ang.size());
1950 int idx = -1;
1951 for (int i = 0; i < n; ++i) {
1952 double lo = ang[i];
1953 double hi = (i + 1 < n) ? ang[i + 1] : ang[0] + 2.0 * M_PI;
1954 double p = (pk < lo - 1e-12) ? pk + 2.0 * M_PI : pk;
1955 if (p >= lo - 1e-12 && p < hi + 1e-12) { idx = i; break; }
1956 }
1957 if (idx < 0) return false;
1958 double lo = ang[idx];
1959 double hi = (idx + 1 < n) ? ang[idx + 1] : ang[0] + 2.0 * M_PI;
1960 // Keep the complement of the (lo,hi) gap: sweep ccw from hi back round to lo.
1961 e.type = SketchEntity::Type::Arc;
1962 e.start_angle = hi;
1963 e.end_angle = lo + 2.0 * M_PI;
1964 sync_arc_ends(e); // p0 was the circle's centre (circle convention); now the arc's start
1965 return true;
1966 }
1967
1968 if (e.type != SketchEntity::Type::Line) return false;
1969
1970 Vec2d adir = e.p1 - e.p0;
1971 double La2 = adir.dot(adir);
1972 if (La2 < 1e-18) return false;
1973
1974 double t_pick = (pick - e.p0).dot(adir) / La2;
1975 t_pick = std::max(0.0, std::min(1.0, t_pick));
1976
1977 std::vector<double> cuts;
1978 for (const auto& other : others) {
1979 auto h = line_entity_hits(e.p0, adir, other);
1980 for (double t : h) {
1981 if (t > 1e-9 && t < 1.0 - 1e-9)
1982 cuts.push_back(t);
1983 }
1984 }
1985 if (cuts.empty()) return false;
1986
1987 if (t_pick <= 0.5) {
1988 double tc = std::numeric_limits<double>::max();
1989 for (double t : cuts) {
1990 if (t > t_pick + 1e-9 && t < tc)
1991 tc = t;
1992 }
1993 if (tc == std::numeric_limits<double>::max()) return false;
1994 e.p0 = e.p0 + tc * adir;
1995 } else {
1996 double tc = -std::numeric_limits<double>::max();
1997 for (double t : cuts) {
1998 if (t < t_pick - 1e-9 && t > tc)
1999 tc = t;
2000 }
2001 if (tc == -std::numeric_limits<double>::max()) return false;
2002 e.p1 = e.p0 + tc * adir;
2003 }
2004
2005 return true;
2006}

References Slic3r::SketchEntity::Arc, Slic3r::SketchEntity::center, Slic3r::SketchEntity::Circle, Slic3r::circle_cross_angles(), Slic3r::dot(), Slic3r::SketchEntity::end_angle, Slic3r::SketchEntity::Line, Slic3r::line_entity_hits(), M_PI, Slic3r::SketchEntity::p0, Slic3r::SketchEntity::p1, Slic3r::SketchEntity::radius, Slic3r::SketchEntity::start_angle, Slic3r::sync_arc_ends(), Slic3r::SketchEntity::type, and Slic3r::wrap_2pi().

Referenced by Slic3r::GUI::DesignPanel::apply_edit_op(), Slic3r::GUI::DesignSketchTool::apply_live_trim(), and Slic3r::GUI::DesignSketchTool::compute_trim_preview().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ wires_to_face()

TopoDS_Face Slic3r::SketchEngine::wires_to_face ( const std::vector< TopoDS_Wire > & wires,
const SketchPlane & plane )
static
932{
933 if (wires.empty()) throw std::runtime_error(_u8L("sketch has no closed loop"));
934
935 // The ASSEMBLED face below is built on the SKETCH's own plane rather than on a surface OCCT
936 // infers from the outer wire. The inferred plane has no reason to share the sketch's normal,
937 // and when they disagree the hole classification produces no hole: a plate sketched on a
938 // plane whose normal points -Z came out as the full box PLUS a disc (measured 220274 mm3
939 // where 163726 was due — 192000 box + 28274 disc). `plane` was a parameter this function
940 // never used. Only the assembly is named: the single-wire and per-wire-area builds keep the
941 // inferred surface, because naming a plane also makes MakeFace accept a wire that does not
942 // bound a face, and that failure is the check an open stray line is caught by.
943 const gp_Pln pln(gp_Pnt(plane.origin.x(), plane.origin.y(), plane.origin.z()),
944 gp_Dir(plane.normal.x(), plane.normal.y(), plane.normal.z()));
945
946 if (wires.size() == 1) {
947 BRepBuilderAPI_MakeFace fm(wires[0]);
948 if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
949 return checked_profile_face(fm.Face());
950 }
951
952 // Two or more loops: build a face per wire and let the largest area be the outer
953 // boundary; every other loop is a candidate hole inside it.
954 std::vector<TopoDS_Face> faces;
955 faces.reserve(wires.size());
956 std::vector<double> areas;
957 areas.reserve(wires.size());
958 for (const TopoDS_Wire& w : wires) {
959 BRepBuilderAPI_MakeFace fm(w);
960 if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
961 faces.push_back(fm.Face());
962 GProp_GProps props;
963 BRepGProp::SurfaceProperties(faces.back(), props);
964 areas.push_back(props.Mass());
965 }
966
967 size_t outer = 0;
968 for (size_t i = 1; i < areas.size(); ++i)
969 if (areas[i] > areas[outer]) outer = i;
970
971 // Note: NOT MakeFace(faces[outer], wires[outer]) — that constructor copies the outer face
972 // (including its existing boundary wire) and then adds the wire again, doubling the outer
973 // boundary. The wire-only constructor starts clean and the reversed holes follow.
974 BRepBuilderAPI_MakeFace fm(pln, wires[outer]);
975 for (size_t i = 0; i < wires.size(); ++i) {
976 if (i == outer) continue;
977 // Containment is checked, not assumed: a vertex of the inner wire must lie strictly
978 // inside the outer face. A loop outside the largest one is a second island, not a hole.
979 gp_Pnt p;
980 bool got = false;
981 for (TopExp_Explorer ex(wires[i], TopAbs_VERTEX); ex.More(); ex.Next()) {
982 p = BRep_Tool::Pnt(TopoDS::Vertex(ex.Current()));
983 got = true;
984 break;
985 }
986 if (!got) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
987 BRepClass_FaceClassifier fc(faces[outer], p, 1e-7);
988 if (fc.State() != TopAbs_IN)
989 throw std::runtime_error(_u8L("sketch has two disjoint regions; put each in its own sketch"));
990 // Add the hole loop AS-IS and let ShapeFix_Face sort the orientations out below.
991 // Reversing it here only works when the sketch happened to wind both loops the same
992 // way: a circle drawn clockwise inside a counter-clockwise rectangle comes out matching
993 // the outer boundary, OCCT sweeps it as a second contour, and the prism is the plate
994 // with the bore FILLED and the disc's volume counted twice. Measured on the rig:
995 // bbox 67.17 x 219.67 x 10 (the whole plate) with volume 152088 mm3 against a solid-box
996 // 147520 — a body larger than its own bounding box, which is the signature of it.
997 fm.Add(wires[i]);
998 }
999 if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
1000 // Winding-independent classification of outer vs holes — the same idiom make_extrude_regions
1001 // already uses for imported glyphs, which is why holed TEXT extruded correctly all along
1002 // while a holed SKETCH did not.
1003 ShapeFix_Face sff(fm.Face());
1004 sff.FixOrientation();
1005 return checked_profile_face(sff.Face());
1006}
static TopoDS_Face checked_profile_face(const TopoDS_Face &f)
Definition SketchEngine.cpp:922

References _u8L, Slic3r::checked_profile_face(), Slic3r::SketchPlane::normal, and Slic3r::SketchPlane::origin.

Referenced by Slic3r::CadDocument::build_sketch_face().

Here is the call graph for this function:
Here is the caller graph for this function:

The documentation for this class was generated from the following files: