From 6fc3c99e310e36aa8e31e11c5ec604571831f5b9 Mon Sep 17 00:00:00 2001 From: Tommaso Bianchi Date: Mon, 17 Aug 2026 14:48:05 +0200 Subject: [PATCH] Mate connectors: draw the dashed pair line between the two origins The last unbuilt element of snaporca-wgsc. Two connectors a mate binds are one object with a gap still in it; drawn as two separate frames they read as unrelated, and "which two of these five frames are the mate?" had no answer on screen at all. Dashed, grey, drawn IN WORLD along the segment joining the origins -- so it foreshortens with the model and its length is the gap the mate has left to close. A Fastened mate therefore draws nothing, which is correct: the gap is zero. Screen-constant dash pitch (6 px dash, 4 px gap) like every other gizmo here, with the pitch opening up beyond 400 dashes so a mate across a large assembly cannot emit thousands of segments. Depth test off: the line's job is to say "these two belong together", and it has to say it even when a part sits between the camera and one end. Fed from BOTH sources of polarity truth, the same two the role colours already use: every committed Mate feature (connectors named by feature index), and the live pick of an open Mate card (named by combo ordinal). Only resolved frames are eligible, so an unresolved end draws no line rather than a line to the origin of the world. RIG-VERIFIED on :10 with the fresh binary (/OrcaSlicer/build/src/Release, 2026-08-17 12:43): two imported bodies, a face-and-direction connector on each, Planar mate offset 40. Sampling the segment between the two origins gives a regular dash/gap alternation of 13:10 sample units -- the 6:4 px pitch -- in the stroke grey (107,117,133), over both solids. The grey end keeps its open collar head and the blue end its filled one, so polarity and pair now read together. Refs snaporca-wgsc --- src/slic3r/GUI/DesignCanvas.cpp | 6 ++++++ src/slic3r/GUI/DesignCanvas.hpp | 1 + src/slic3r/GUI/DesignPanel.cpp | 19 +++++++++++++++++++ src/slic3r/GUI/DesignSketchTool.cpp | 29 +++++++++++++++++++++++++++++ src/slic3r/GUI/DesignSketchTool.hpp | 7 ++++++- 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/DesignCanvas.cpp b/src/slic3r/GUI/DesignCanvas.cpp index e5a1800ff3..e04f0d1b5f 100644 --- a/src/slic3r/GUI/DesignCanvas.cpp +++ b/src/slic3r/GUI/DesignCanvas.cpp @@ -1001,6 +1001,12 @@ void DesignCanvas::set_mate_connectors(std::vector> l) +{ + m_sketch_tool.set_mate_links(std::move(l)); + request_repaint(); +} + bool DesignCanvas::toggle_planes() { const bool on = m_sketch_tool.toggle_show_planes(); diff --git a/src/slic3r/GUI/DesignCanvas.hpp b/src/slic3r/GUI/DesignCanvas.hpp index d4bca46e29..3acb03f20e 100644 --- a/src/slic3r/GUI/DesignCanvas.hpp +++ b/src/slic3r/GUI/DesignCanvas.hpp @@ -197,6 +197,7 @@ public: std::vector sizes = {}); // draw datum/reference planes (u/v extents) // Mate connectors, drawn as frames so their verse and polarity are visible (snaporca-wgsc). void set_mate_connectors(std::vector g); + void set_mate_links(std::vector> l); void set_body_highlight(bool on); // tint the solid when its feature is tree-selected // The status line, shown along the BASE OF THE VIEWPORT rather than in the side panel: // the panel clips it at ~73 characters with no warning (snaporca-8cc), the viewport's diff --git a/src/slic3r/GUI/DesignPanel.cpp b/src/slic3r/GUI/DesignPanel.cpp index 1a31a27643..a2d65fa891 100644 --- a/src/slic3r/GUI/DesignPanel.cpp +++ b/src/slic3r/GUI/DesignPanel.cpp @@ -9728,6 +9728,10 @@ void DesignPanel::refresh_mate_connectors() const int cs_b = (m_active == Tool::Mate && m_mate_cs_b) ? m_mate_cs_b->GetSelection() : -1; std::vector out; + // Origins, keyed both ways: a committed mate names its connectors by FEATURE index, the open + // card's combos by ordinal. Only resolved frames land here, so an unresolved end simply draws + // no pair line rather than a line to the origin of the world. + std::map origin_by_feature, origin_by_ordinal; const auto frames = m_doc.resolve_datum_coordsys(); size_t k = 0; for (size_t i = 0; i < m_doc.features.size() && k < frames.size(); ++i) { @@ -9748,9 +9752,24 @@ void DesignPanel::refresh_mate_connectors() // is the case the glyph has to confess rather than absorb. g.roll_undefined = (f.coordsys_type == CoordSysType::FaceAndDirection && f.coordsys_edge < 0); + origin_by_feature[int(i)] = g.origin; + origin_by_ordinal[ordinal] = g.origin; out.push_back(g); } + + std::vector> links; + auto add_link = [&links](const std::map& m, int a, int b) { + const auto ia = m.find(a), ib = m.find(b); + if (ia != m.end() && ib != m.end()) links.emplace_back(ia->second, ib->second); + }; + for (const CadFeature& mf : m_doc.features) + if (mf.type == CadFeatureType::Mate && mf.enabled) + add_link(origin_by_feature, mf.mate_cs_a, mf.mate_cs_b); + if (cs_a >= 0 && cs_b >= 0 && cs_a != cs_b) + add_link(origin_by_ordinal, cs_a, cs_b); // the live pick, not yet committed + m_viewport->set_mate_connectors(std::move(out)); + m_viewport->set_mate_links(std::move(links)); } void DesignPanel::update_datum_gizmo() diff --git a/src/slic3r/GUI/DesignSketchTool.cpp b/src/slic3r/GUI/DesignSketchTool.cpp index 4fe6e038ec..a290336cbd 100644 --- a/src/slic3r/GUI/DesignSketchTool.cpp +++ b/src/slic3r/GUI/DesignSketchTool.cpp @@ -3680,6 +3680,35 @@ void DesignSketchTool::render_mate_connectors() arrow(Z, R * 2.10, body, g.role == 2); // +Z only. Nothing is ever drawn on -Z. } } + + // ---- the pair line. Dashed, drawn IN WORLD along the segment joining the two origins, so it + // foreshortens with the model and its length is the gap the mate has still to close. Screen- + // constant dashes like everything else here; the dash pitch opens up on a long span so a mate + // across a large assembly cannot emit thousands of segments. + for (const auto& lnk : m_mate_links) { + const Vec3d d = lnk.second - lnk.first; + const double L = d.norm(); + if (L < 1e-9) continue; + SketchPlane lp; + lp.origin = lnk.first; + lp.x_axis = d / L; + lp.y_axis = lp.x_axis.cross(cam.get_dir_forward().normalized()); + if (lp.y_axis.norm() < 1e-6) lp.y_axis = lp.x_axis.cross(up); // link along the view axis + lp.y_axis.normalize(); + lp.normal = lp.x_axis.cross(lp.y_axis); + m_plane = lp; + + std::vector> segs; + const double dash = 6.0 * upp; + const double step = std::max(dash + 4.0 * upp, L / 400.0); + for (double t = 0.0; t < L; t += step) + segs.emplace_back(Vec2d(t, 0.0), Vec2d(std::min(t + dash, L), 0.0)); + // Depth off: the line's job is to say "these two belong together", and it has to say it + // even when the parts it spans are between the camera and one of the ends. + glsafe(::glDisable(GL_DEPTH_TEST)); + draw_strokes(m_mc_stroke_model, segs, lw, grey); + } + m_plane = saved; } diff --git a/src/slic3r/GUI/DesignSketchTool.hpp b/src/slic3r/GUI/DesignSketchTool.hpp index c236a10085..df05085a8e 100644 --- a/src/slic3r/GUI/DesignSketchTool.hpp +++ b/src/slic3r/GUI/DesignSketchTool.hpp @@ -337,7 +337,11 @@ public: bool roll_undefined{false}; }; void set_mate_connectors(std::vector g) { m_mate_connectors = std::move(g); } - void clear_mate_connectors() { m_mate_connectors.clear(); } + // The pair line: the two origins of every mate — committed ones, plus the live pick of an open + // Mate card. Two connectors a mate binds are ONE object with a gap still in it; drawn as two + // separate frames they read as unrelated. + void set_mate_links(std::vector> l) { m_mate_links = std::move(l); } + void clear_mate_connectors() { m_mate_connectors.clear(); m_mate_links.clear(); } // Visual Revolve gizmo. The panel feeds the sketch plane + profile centroid + axis (0=plane X, // 1=plane Y) + angle + flip while its Revolve card is open; an angle-arc is drawn in the @@ -1085,6 +1089,7 @@ private: void render_mate_face(const Vec3d& origin, const Vec3d& X, const Vec3d& Y, const Vec3d& Z, double R, const ColorRGBA& body); std::vector m_mate_connectors; + std::vector> m_mate_links; GLModel m_mc_stroke_model; GLModel m_mc_fill_model; // the face treatment's shaded facets GLModel m_solid_face_model;