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;