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
This commit is contained in:
Tommaso Bianchi
2026-08-17 14:48:05 +02:00
parent b4d6abc57a
commit 6fc3c99e31
5 changed files with 61 additions and 1 deletions
+6
View File
@@ -1001,6 +1001,12 @@ void DesignCanvas::set_mate_connectors(std::vector<DesignSketchTool::MateConnect
request_repaint();
}
void DesignCanvas::set_mate_links(std::vector<std::pair<Vec3d, Vec3d>> 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();
+1
View File
@@ -197,6 +197,7 @@ public:
std::vector<Vec2d> 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<DesignSketchTool::MateConnectorGlyph> g);
void set_mate_links(std::vector<std::pair<Vec3d, Vec3d>> 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
+19
View File
@@ -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<DesignSketchTool::MateConnectorGlyph> 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<int, Vec3d> 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<std::pair<Vec3d, Vec3d>> links;
auto add_link = [&links](const std::map<int, Vec3d>& 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()
+29
View File
@@ -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<std::pair<Vec2d, Vec2d>> 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;
}
+6 -1
View File
@@ -337,7 +337,11 @@ public:
bool roll_undefined{false};
};
void set_mate_connectors(std::vector<MateConnectorGlyph> 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<std::pair<Vec3d, Vec3d>> 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<MateConnectorGlyph> m_mate_connectors;
std::vector<std::pair<Vec3d, Vec3d>> m_mate_links;
GLModel m_mc_stroke_model;
GLModel m_mc_fill_model; // the face treatment's shaded facets
GLModel m_solid_face_model;