From c061658d1cd759d7d00b3795085e488fbec5becf Mon Sep 17 00:00:00 2001 From: phantomcake Date: Mon, 5 Oct 2026 23:20:36 -0400 Subject: [PATCH] Sketcher: experimental line-midpoint vertex Expose each line segment's midpoint as a real solver vertex so it can be snapped to and constrained like any other point: - Sketch::addLineSegment adds a free parameter for the midpoint, tied to the endpoints by an internal P2PSymmetric constraint. The constraint is solver-only (rebuilt from the geometry on load) and never serialized, so no file-format change is needed. - Vertex mapping (SketchObjectGeometry::rebuildVertexIndex, GeoList) and the point getters expose PointPos::mid for lines. - The Sketcher edit-mode coin converter draws start/end/mid for lines and keeps the midpoint hidden (per-vertex transparency) until it is hovered/selected. - SketcherGui: Vulkan-only hover-midpoint marker (gated by FREECAD_USE_VULKAN). Validated with Sketcher_tests_run (107/107) and FreeCADCmd/probe checks: Coincident(line.mid, root) moves the line so its midpoint lands on the root; the midpoint is transparent until the cursor is near it, then preselected. REVERT NOTE ----------- This adds a vertex per line, so sketch "Vertex N" sub-element numbering shifts for every line after the first (a line's mid vertex is inserted between its end and the following geometry's vertices). Documents that reference line vertices externally by number may therefore need review. To revert, drop this single commit; there is no serialized state to migrate. --- src/Mod/Sketcher/App/GeoList.cpp | 8 +++ src/Mod/Sketcher/App/Sketch.cpp | 20 ++++++ src/Mod/Sketcher/App/SketchGeometry.cpp | 4 ++ src/Mod/Sketcher/App/SketchObjectGeometry.cpp | 3 + src/Mod/Sketcher/Gui/CMakeLists.txt | 7 +++ src/Mod/Sketcher/Gui/EditModeCoinManager.cpp | 52 ++++++++++++++++ src/Mod/Sketcher/Gui/EditModeCoinManager.h | 18 ++++++ .../Gui/EditModeCoinManagerParameters.h | 15 +++++ .../Gui/EditModeGeometryCoinConverter.cpp | 28 +++++++-- .../Gui/EditModeGeometryCoinManager.cpp | 61 +++++++++++++++++++ src/Mod/Sketcher/Gui/ViewProviderSketch.cpp | 18 +++++- src/Mod/Sketcher/Gui/ViewProviderSketch.h | 8 +++ 12 files changed, 235 insertions(+), 7 deletions(-) diff --git a/src/Mod/Sketcher/App/GeoList.cpp b/src/Mod/Sketcher/App/GeoList.cpp index 5eb2c38f82fc..361cfb59d500 100644 --- a/src/Mod/Sketcher/App/GeoList.cpp +++ b/src/Mod/Sketcher/App/GeoList.cpp @@ -185,6 +185,10 @@ Base::Vector3d GeoListModel::getPoint(const Part::Geometry* geo, Sketcher::Po else if (pos == PointPos::end) { return lineSeg->getEndPoint(); } + else if (pos == PointPos::mid) { + // Experimental line-midpoint vertex (see commit revert note). + return (lineSeg->getStartPoint() + lineSeg->getEndPoint()) / 2.0; + } } else if (geo->is()) { const Part::GeomCircle* circle = static_cast(geo); @@ -304,6 +308,10 @@ void GeoListModel::rebuildVertexIndex() const ) { addGeoElement(geoId, PointPos::start); addGeoElement(geoId, PointPos::end); + // Experimental line-midpoint vertex (see commit revert note). + if (type == Part::GeomLineSegment::getClassTypeId()) { + addGeoElement(geoId, PointPos::mid); + } } else if ( type == Part::GeomCircle::getClassTypeId() || type == Part::GeomEllipse::getClassTypeId() diff --git a/src/Mod/Sketcher/App/Sketch.cpp b/src/Mod/Sketcher/App/Sketch.cpp index fd1bb420d0b9..0ebffac9dc58 100644 --- a/src/Mod/Sketcher/App/Sketch.cpp +++ b/src/Mod/Sketcher/App/Sketch.cpp @@ -944,6 +944,19 @@ int Sketch::addLineSegment(const Part::GeomLineSegment& lineSegment, bool fixed) Points.push_back(p1); Points.push_back(p2); + // Expose the line midpoint as a real vertex so it can be snapped to and + // constrained like any other point. It is a free parameter tied to the + // endpoints by an internal (solver-only, never serialized) symmetric + // constraint below, so constraining the midpoint moves the line. + // (Experimental: see the revert note in the commit message.) + GCS::Point pmid; + params.push_back(new double((start.x + end.x) / 2)); + params.push_back(new double((start.y + end.y) / 2)); + pmid.x = params[params.size() - 2]; + pmid.y = params[params.size() - 1]; + def.midPointId = Points.size(); + Points.push_back(pmid); + // set the line for later constraints GCS::Line l; l.p1 = p1; @@ -954,6 +967,13 @@ int Sketch::addLineSegment(const Part::GeomLineSegment& lineSegment, bool fixed) // store complete set Geoms.push_back(def); + // Internal midpoint rule (like the internal ArcRules of arcs): pmid is the + // midpoint of p1 and p2. Lives only in the solver, so it is rebuilt from + // the geometry on load and needs no file-format change. + if (!fixed) { + GCSsys.addConstraintP2PSymmetric(p1, p2, pmid); + } + if (!fixed) { param2geoelement.emplace( std::piecewise_construct, diff --git a/src/Mod/Sketcher/App/SketchGeometry.cpp b/src/Mod/Sketcher/App/SketchGeometry.cpp index aecf73c8daff..80c8ceae8de5 100644 --- a/src/Mod/Sketcher/App/SketchGeometry.cpp +++ b/src/Mod/Sketcher/App/SketchGeometry.cpp @@ -69,6 +69,10 @@ class SketchLineSegment: public SketchGeometryT case PointPos::end: { return lineSeg->getEndPoint(); } + case PointPos::mid: { + // Experimental line-midpoint vertex (see commit revert note). + return (lineSeg->getStartPoint() + lineSeg->getEndPoint()) / 2.0; + } default: break; } diff --git a/src/Mod/Sketcher/App/SketchObjectGeometry.cpp b/src/Mod/Sketcher/App/SketchObjectGeometry.cpp index bcffdd9d75b8..0a18da9cd1dc 100644 --- a/src/Mod/Sketcher/App/SketchObjectGeometry.cpp +++ b/src/Mod/Sketcher/App/SketchObjectGeometry.cpp @@ -1499,6 +1499,9 @@ void SketchObject::rebuildVertexIndex() VertexId2PosId.push_back(PointPos::start); VertexId2GeoId.push_back(i); VertexId2PosId.push_back(PointPos::end); + // Experimental line-midpoint vertex (see commit revert note). + VertexId2GeoId.push_back(i); + VertexId2PosId.push_back(PointPos::mid); } else if ((*it)->is()) { VertexId2GeoId.push_back(i); diff --git a/src/Mod/Sketcher/Gui/CMakeLists.txt b/src/Mod/Sketcher/Gui/CMakeLists.txt index a1d4e4767ff5..b45046a2d420 100644 --- a/src/Mod/Sketcher/Gui/CMakeLists.txt +++ b/src/Mod/Sketcher/Gui/CMakeLists.txt @@ -211,6 +211,13 @@ target_include_directories( target_link_libraries(SketcherGui ${SketcherGui_LIBS}) +# Mirror FreeCADGui's private Vulkan define so the SketcherGui-only Vulkan aids +# (e.g. the hover line-midpoint marker) compile in the Vulkan build and are +# compiled out entirely in the GL/upstream build. +if(FREECAD_USE_VULKAN) + target_compile_definitions(SketcherGui PRIVATE FREECAD_USE_VULKAN=1) +endif() + if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|FreeBSD|OpenBSD|NetBSD)$") find_package(Fontconfig REQUIRED) target_link_libraries(SketcherGui Fontconfig::Fontconfig) diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp index 816298fe49bd..5e69158bf91c 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.cpp @@ -30,6 +30,8 @@ #include #include +#include + #include #include #include @@ -47,6 +49,7 @@ #include #include #include +#include #include #include @@ -339,6 +342,28 @@ struct GeometryScreenPreselector startPoint[0] + (endPoint[0] - startPoint[0]) * interpolation, startPoint[1] + (endPoint[1] - startPoint[1]) * interpolation )); + +#ifdef FREECAD_USE_VULKAN + // Vulkan-only Sketcher aid: when the hovered curve is a + // line and the cursor is close to its center, ask for the + // hover-midpoint marker. Only this hovered line is ever + // considered, so the cost is O(1) per hover. + if (const Part::Geometry* geom = geolist.getGeometryFromGeoId(geoIndex); + geom && geom->is()) { + const SbVec3f midpoint = (startPoint + endPoint) * 0.5F; + const SbVec2f midpointScreen = projectToScreen(midpoint); + const float mdx = cursorPoint[0] - midpointScreen[0]; + const float mdy = cursorPoint[1] - midpointScreen[1]; + const float midpointRadius = std::max( + curveHitRadius, + static_cast(drawingParameters.markerSize) + ); + if (mdx * mdx + mdy * mdy <= midpointRadius * midpointRadius) { + result.LineMidpoint = Base::Vector3d(midpoint[0], midpoint[1], 0.0); + } + } +#endif + bestDistanceSquared = bestCurveDistanceSquared; found = true; } @@ -1582,6 +1607,33 @@ EditModeCoinManager::PreselectionResult EditModeCoinManager::detectPreselection( ); } +void EditModeCoinManager::setHoverMidpoint(const std::optional& midpoint) +{ +#ifdef FREECAD_USE_VULKAN + if (!editModeScenegraphNodes.HoverMidpointSwitch) { + return; + } + + if (midpoint) { + editModeScenegraphNodes.HoverMidpointCoordinate->point.set1Value( + 0, + SbVec3f( + static_cast(midpoint->x), + static_cast(midpoint->y), + ViewProviderSketchCoinAttorney::getViewOrientationFactor(viewProvider) + * drawingParameters.zHighlight + ) + ); + editModeScenegraphNodes.HoverMidpointSwitch->whichChild = SO_SWITCH_ALL; + } + else { + editModeScenegraphNodes.HoverMidpointSwitch->whichChild = SO_SWITCH_NONE; + } +#else + (void)midpoint; +#endif +} + SoGroup* EditModeCoinManager::getSelectedConstraints() { SoGroup* group = new SoGroup(); diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManager.h b/src/Mod/Sketcher/Gui/EditModeCoinManager.h index 740945d9f42c..7144726e91ac 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManager.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManager.h @@ -222,6 +222,10 @@ class SketcherGuiExport EditModeCoinManager std::set ConstrIndices; ConstraintHitKind ConstraintKind = ConstraintHitKind::None; std::optional PickedPoint; + //! Sketch-plane midpoint (z unused) of the hovered line when the cursor + //! is close enough to its center to show the hover-midpoint marker + //! (Vulkan-only aid; see EditModeGeometryCoinManager). + std::optional LineMidpoint; [[nodiscard]] inline bool hasWinner() const { @@ -250,6 +254,7 @@ class SketcherGuiExport EditModeCoinManager ConstrIndices.clear(); ConstraintKind = ConstraintHitKind::None; PickedPoint.reset(); + LineMidpoint.reset(); } }; @@ -366,6 +371,19 @@ class SketcherGuiExport EditModeCoinManager void updateGeometryColor(const GeoListFacade& geolistfacade, bool issketchinvalid); +public: + /** @name Vulkan-only hover line-midpoint marker + * + * Show the marker at the given sketch-plane point (the center of the line + * under the mouse) or hide it when empty. Only the hovered line's center is + * ever handed in, so no per-line geometry is produced. A no-op without + * Vulkan (the marker nodes do not exist). + */ + //@{ + void setHoverMidpoint(const std::optional& midpoint); + //@} + +private: // causes the ViewProvider to draw void redrawViewProvider(); diff --git a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h index bd390e952d8e..311bb3765755 100644 --- a/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h +++ b/src/Mod/Sketcher/Gui/EditModeCoinManagerParameters.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -497,6 +498,20 @@ struct EditModeScenegraphNodes SoDrawStyle* EditMarkersDrawStyle; //@} + /** @name Hover line-midpoint marker (Vulkan-only Sketcher aid) + * + * A single reusable marker drawn at the center of the line under the mouse + * when the cursor gets close to that center. It is only computed for the + * hovered line (never for every line), so the cost stays O(1) per hover. + * Created and driven from EditModeGeometryCoinManager. + */ + //@{ + SoSwitch* HoverMidpointSwitch = nullptr; + SoMaterial* HoverMidpointMaterial = nullptr; + SoCoordinate3* HoverMidpointCoordinate = nullptr; + SoMarkerSet* HoverMidpointSet = nullptr; + //@} + /** @name Temporal edit text nodes*/ //@{ SoText2* textX; diff --git a/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp b/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp index db01a021ca51..929efc2a0bc5 100644 --- a/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp +++ b/src/Mod/Sketcher/Gui/EditModeGeometryCoinConverter.cpp @@ -192,9 +192,11 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli ); } else if (type == Part::GeomLineSegment::getClassTypeId()) { // add a line + // Experimental: line midpoints are exposed as vertices (see commit + // revert note), so a line draws start, end and mid. convert< Part::GeomLineSegment, - EditModeGeometryCoinConverter::PointsMode::InsertStartEnd, + EditModeGeometryCoinConverter::PointsMode::InsertStartEndMid, EditModeGeometryCoinConverter::CurveMode::StartEndPointsOnly, EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>( geom, @@ -205,7 +207,7 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli setTracking( GeoId, coinLayer, - EditModeGeometryCoinConverter::PointsMode::InsertStartEnd, + EditModeGeometryCoinConverter::PointsMode::InsertStartEndMid, 1, subLayerId, isGroupMember @@ -284,6 +286,9 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli for (auto l = 0; l < geometryLayerParameters.getCoinLayerCount(); l++) { geometryLayerNodes.PointsCoordinate[l]->point.setNum(Points[l].size()); geometryLayerNodes.PointsMaterials[l]->diffuseColor.setNum(Points[l].size()); + // Per-vertex transparency: line midpoints are hidden until hovered (see + // EditModeGeometryCoinManager::updateGeometryColor). + geometryLayerNodes.PointsMaterials[l]->transparency.setNum(Points[l].size()); SbVec3f* pverts = geometryLayerNodes.PointsCoordinate[l]->point.startEditing(); int i = 0; // setting up the point set @@ -354,10 +359,21 @@ void EditModeGeometryCoinConverter::convert( addPoint(Points[coinLayer], geo->getEndPoint()); } else if constexpr (pointmode == PointsMode::InsertStartEndMid) { - // All in this group are Trimmed Curves (see Geometry.h) - addPoint(Points[coinLayer], geo->getStartPoint(/*emulateCCW=*/true)); - addPoint(Points[coinLayer], geo->getEndPoint(/*emulateCCW=*/true)); - addPoint(Points[coinLayer], geo->getCenter()); + if constexpr (std::is_same::value) { + // Experimental line-midpoint vertex: plain start/end plus the + // geometric midpoint (lines have no getCenter()). + const Base::Vector3d start = geo->getStartPoint(); + const Base::Vector3d end = geo->getEndPoint(); + addPoint(Points[coinLayer], start); + addPoint(Points[coinLayer], end); + addPoint(Points[coinLayer], (start + end) / 2.0); + } + else { + // All in this group are Trimmed Curves (see Geometry.h) + addPoint(Points[coinLayer], geo->getStartPoint(/*emulateCCW=*/true)); + addPoint(Points[coinLayer], geo->getEndPoint(/*emulateCCW=*/true)); + addPoint(Points[coinLayer], geo->getCenter()); + } } else if constexpr (pointmode == PointsMode::InsertMidOnly) { addPoint(Points[coinLayer], geo->getCenter()); diff --git a/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp b/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp index 10668ce5f573..819047cfff0e 100644 --- a/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp +++ b/src/Mod/Sketcher/Gui/EditModeGeometryCoinManager.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -231,8 +232,26 @@ void EditModeGeometryCoinManager::updateGeometryColor( float x, y, z; int PtNum = editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.getNum(); SbColor* pcolor = editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.startEditing(); + float* ptrans = editModeScenegraphNodes.PointsMaterials[l]->transparency.startEditing(); SbVec3f* pverts = editModeScenegraphNodes.PointsCoordinate[l]->point.startEditing(); + // A line midpoint is a real vertex (so it can be snapped to/constrained) + // but must not be drawn until the mouse is near it. Hide every line-mid + // point by default; the preselection/selection blocks below make the + // hovered/selected one opaque again. + for (int i = 0; i < PtNum; i++) { + ptrans[i] = 0.0f; + if (!coinMapping.isValidPointId(i, l) + || coinMapping.getPointPosId(i, l) != Sketcher::PointPos::mid) { + continue; + } + auto midgeom = geolistfacade.getGeometryFacadeFromGeoId(coinMapping.getPointGeoId(i, l)); + if (midgeom && midgeom->getGeometry() + && midgeom->getGeometry()->is()) { + ptrans[i] = 1.0f; // fully transparent + } + } + // colors of the point set for (int i = 0; i < PtNum; i++) { if (!coinMapping.isValidPointId(i, l)) { @@ -390,6 +409,7 @@ void EditModeGeometryCoinManager::updateGeometryColor( && preselectpointmfid.fieldIndex < PtNum) { pcolor[preselectpointmfid.fieldIndex] = drawingParameters.PreselectColor; + ptrans[preselectpointmfid.fieldIndex] = 0.0f; raisePoint( pverts[preselectpointmfid.fieldIndex], @@ -402,6 +422,7 @@ void EditModeGeometryCoinManager::updateGeometryColor( viewProvider, [this, pcolor, + ptrans, pverts, PtNum, preselectpointmfid, @@ -423,6 +444,7 @@ void EditModeGeometryCoinManager::updateGeometryColor( pcolor[pointindex.fieldIndex] = (preselectpointmfid == pointindex) ? drawingParameters.PreselectSelectedColor : drawingParameters.SelectColor; + ptrans[pointindex.fieldIndex] = 0.0f; raisePoint( pverts[pointindex.fieldIndex], @@ -598,6 +620,7 @@ void EditModeGeometryCoinManager::updateGeometryColor( } editModeScenegraphNodes.PointsMaterials[l]->diffuseColor.finishEditing(); + editModeScenegraphNodes.PointsMaterials[l]->transparency.finishEditing(); editModeScenegraphNodes.PointsCoordinate[l]->point.finishEditing(); } @@ -645,6 +668,44 @@ void EditModeGeometryCoinManager::createEditModeInventorNodes() createEditModePointInventorNodes(); createEditModeCurveInventorNodes(); + +#ifdef FREECAD_USE_VULKAN + // Vulkan-only Sketcher aid: a single reusable marker for the center of the + // line under the mouse. Only one point exists and it is moved in place for + // the hovered line, so no per-line geometry/mapping is added and the cost + // stays O(1) per hover. The GL/upstream path never creates or touches it. + auto* hoverSwitch = new SoSwitch; + hoverSwitch->whichChild = SO_SWITCH_NONE; + hoverSwitch->setName("HoverMidpointSwitch"); + editModeScenegraphNodes.EditRoot->addChild(hoverSwitch); + editModeScenegraphNodes.HoverMidpointSwitch = hoverSwitch; + + auto* hoverMaterial = new SoMaterial; + hoverMaterial->diffuseColor = drawingParameters.PreselectColor; + hoverMaterial->setName("HoverMidpointMaterial"); + hoverSwitch->addChild(hoverMaterial); + editModeScenegraphNodes.HoverMidpointMaterial = hoverMaterial; + + auto* hoverCoords = new SoCoordinate3; + hoverCoords->point.setNum(1); + hoverCoords->point.set1Value(0, SbVec3f(0.0f, 0.0f, 0.0f)); + hoverCoords->setName("HoverMidpointCoordinate"); + hoverSwitch->addChild(hoverCoords); + editModeScenegraphNodes.HoverMidpointCoordinate = hoverCoords; + + auto* hoverStyle = new SoDrawStyle; + hoverStyle->pointSize = 8 * drawingParameters.pixelScalingFactor; + hoverSwitch->addChild(hoverStyle); + + auto* hoverSet = new SoMarkerSet; + hoverSet->setName("HoverMidpointSet"); + hoverSet->markerIndex = Gui::Inventor::MarkerBitmaps::getMarkerIndex( + "CIRCLE_FILLED", + drawingParameters.markerSize + ); + hoverSwitch->addChild(hoverSet); + editModeScenegraphNodes.HoverMidpointSet = hoverSet; +#endif } void EditModeGeometryCoinManager::createGeometryRootNodes() diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp index f1dfbf5ed3cd..4205233f8529 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp @@ -38,6 +38,8 @@ #include #include +#include + #include #include #include @@ -2932,6 +2934,18 @@ bool ViewProviderSketch::detectAndShowPreselection( } }; + // Vulkan-only: reflect the hover line-midpoint marker. Tracked here so a + // change as the cursor enters/leaves a line center forces a redraw even when + // the hovered edge itself did not change. + bool midpointChanged = false; +#ifdef FREECAD_USE_VULKAN + if (result.LineMidpoint != hoverMidpoint) { + editCoinManager->setHoverMidpoint(result.LineMidpoint); + hoverMidpoint = result.LineMidpoint; + midpointChanged = true; + } +#endif + if (result.hasWinner()) { if (result.Kind == EditModeCoinManager::PreselectionResult::HitKind::Point && result.PointIndex != preselection.PreselectPoint) {// if a new point is hit @@ -3103,7 +3117,7 @@ bool ViewProviderSketch::detectAndShowPreselection( return true; } - return false; + return midpointChanged; } void ViewProviderSketch::centerSelection() @@ -4227,6 +4241,7 @@ bool ViewProviderSketch::setEdit(int ModNum) assert(!isInEditMode()); preselection.reset(); selection.reset(); + hoverMidpoint.reset(); editCoinManager = std::make_unique(*this); snapManager = std::make_unique(*this); @@ -4548,6 +4563,7 @@ void ViewProviderSketch::unsetEdit(int ModNum) snapManager = nullptr; preselection.reset(); selection.reset(); + hoverMidpoint.reset(); if (editingCancelled) { App::AutoTransaction trans(getDocument()->getDocument(), "Cancel sketch editing"); diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.h b/src/Mod/Sketcher/Gui/ViewProviderSketch.h index 84f166c50132..ce544b390080 100644 --- a/src/Mod/Sketcher/Gui/ViewProviderSketch.h +++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.h @@ -1062,6 +1062,14 @@ class SketcherGuiExport ViewProviderSketch: public PartGui::ViewProvider2DObject Preselection preselection; Selection selection; + //! Vulkan-only hover-midpoint marker: the last midpoint handed to the edit + //! coin manager. Tracked by value, not just presence, so both entering/ + //! leaving a line center and a midpoint that moves while still hovered (e.g. + //! dragging an endpoint) update the marker. Reset on edit enter/exit so a + //! stale marker cannot suppress the first hover of a new edit session. + //! Unused without Vulkan. + std::optional hoverMidpoint; + std::unique_ptr rubberband; std::string editDocName;