Sketcher: Add contextual input hints to constraint commands (InputHints Phase 2) (#21751)
* Sketcher: Extend InputHints infrastructure to constraint tools - Implement DrawSketchHandler::getToolHints() for constraint workflows - Add centralized hint table mapping constraint commands to step-specific InputHints - Integrate hint lookup in DrawSketchHandlerGenConstraint and dimension handler - Provide step-by-step user guidance for: - Coincident, PointOnObject, Distance (X/Y) - Horizontal, Vertical, HorVer, Lock, Block - Equal, Symmetric, Radius, Diameter, Angle - Tangent, Perpendicular, Parallel This continues the InputHints work started for drawing tools by enabling consistent, contextual guidance for constraint creation, including multi-step workflows like tangent-via-point. * Call updateHint() after selection reset to re-arm the first-step prompt when the tool stays active after apply. * Add comments to hints table structure * Sketcher: Update constraint hint text to use "pick" instead of "Select" Change constraint hint text from "Select" to "pick" to maintain consistency with existing FreeCAD UI style. This affects the DrawSketchHandlerGenConstraint hint system for various constraint operations including coincident, distance, horizontal/vertical, block, lock, symmetry, tangent, perpendicular, parallel, and distance constraints. The hints now follow the pattern: - "%1 pick first point or edge" - "%1 pick second point or edge" - "%1 pick line or two points" etc. This provides consistent terminology throughout the sketcher constraint creation workflow. * - Remove redundant 'first' from initial selection hints - Improve consistency in hint text formatting per Developer Guidelines - Add consistent spacing in comment sections" * Per PR feedback for DrawSketchHandlerDimension hints: * Change 'Click to' to "pick" * Simplify hint wording * Combine redundant else * Use direct return pattern instead of building hints list * Update lookupConstraintHints() to use C++20 std:ranges::find_if form per PR review feedback * Sketcher: Refine constraint hints per PR feedback - Use consistent 'point or edge' phrasing in Distance and DistanceX/Y tools - Reword Horizontal/Vertical step 0 to avoid misleading 'two points' - Generalize Tangent and Perpendicular hints to 'edge' with optional point - Simplify legacy Distance to 'point or edge' * Add dynamic hint handling for PointOnObject constraint - Implemented contextual hints in getToolHints() to generate an appropriate step 2 hint based on step 1 selection type - Preserved static lookupConstraintHints() for all other tools * Sketcher: Convert constraint hint table to C++20 designated initializer syntax - Refactored static constraint hint table to follow Sketcher hint development guidelines - Uses C++20 designated initializers for clarity and maintainability - No changes to hint logic or behavior; content is identical to previous version
This commit is contained in:
@@ -1089,6 +1089,9 @@ public:
|
||||
selSeq.clear();
|
||||
resetOngoingSequences();
|
||||
|
||||
// Re-arm hint for next operation
|
||||
updateHint();
|
||||
|
||||
return true;
|
||||
}
|
||||
_tempOnSequences.insert(*token);
|
||||
@@ -1102,11 +1105,211 @@ public:
|
||||
seqIndex++;
|
||||
selFilterGate->setAllowedSelTypes(allowedSelTypes);
|
||||
}
|
||||
updateHint();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::list<Gui::InputHint> getToolHints() const override {
|
||||
const std::string commandName = cmd->getName();
|
||||
const int selectionStep = seqIndex;
|
||||
|
||||
// Special case for Sketcher_ConstrainPointOnObject to generate dynamic step hint
|
||||
if (commandName == "Sketcher_ConstrainPointOnObject") {
|
||||
if (selectionStep == 0) {
|
||||
return {{QObject::tr("%1 pick point or edge"), {Gui::InputHint::UserInput::MouseLeft}}};
|
||||
} else if (selectionStep == 1 && !selSeq.empty()) {
|
||||
if (isVertex(selSeq[0].GeoId, selSeq[0].PosId)) {
|
||||
return {{QObject::tr("%1 pick edge"), {Gui::InputHint::UserInput::MouseLeft}}};
|
||||
} else {
|
||||
return {{QObject::tr("%1 pick point"), {Gui::InputHint::UserInput::MouseLeft}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For everything else, use the static table
|
||||
return lookupConstraintHints(commandName, selectionStep);
|
||||
}
|
||||
|
||||
private:
|
||||
struct ConstraintHintEntry {
|
||||
std::string commandName; // FreeCAD command name (e.g., "Sketcher_ConstrainSymmetric")
|
||||
int selectionStep; // 0-indexed step in the selection sequence
|
||||
std::list<Gui::InputHint> hints; // Hint text and input types for this step
|
||||
};
|
||||
|
||||
using ConstraintHintTable = std::vector<ConstraintHintEntry>;
|
||||
|
||||
// Constraint hint lookup table
|
||||
// Format: {command_name, selection_step, {hint_text, input_types}}
|
||||
// Steps are 0-indexed and correspond to DrawSketchHandlerGenConstraint::seqIndex
|
||||
// Each step provides contextual guidance for what the user should select next
|
||||
static ConstraintHintTable getConstraintHintTable() {
|
||||
return {
|
||||
// Coincident
|
||||
{.commandName = "Sketcher_ConstrainCoincidentUnified",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainCoincidentUnified",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Distance X/Y
|
||||
{.commandName = "Sketcher_ConstrainDistanceX",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainDistanceX",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainDistanceY",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainDistanceY",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Horizontal/Vertical
|
||||
{.commandName = "Sketcher_ConstrainHorizontal",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge or first point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainHorizontal",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainVertical",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge or first point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainVertical",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainHorVer",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge or first point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainHorVer",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Block/Lock
|
||||
{.commandName = "Sketcher_ConstrainBlock",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge to block"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainLock",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point to lock"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Coincident (individual)
|
||||
{.commandName = "Sketcher_ConstrainCoincident",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point or curve"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainCoincident",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point or curve"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainEqual",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainEqual",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Radius/Diameter
|
||||
{.commandName = "Sketcher_ConstrainRadius",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick circle or arc"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainDiameter",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick circle or arc"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Angle
|
||||
{.commandName = "Sketcher_ConstrainAngle",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick line"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainAngle",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second line"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Symmetry
|
||||
{.commandName = "Sketcher_ConstrainSymmetric",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainSymmetric",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainSymmetric",
|
||||
.selectionStep = 2,
|
||||
.hints = {{QObject::tr("%1 pick symmetry line"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Tangent
|
||||
{.commandName = "Sketcher_ConstrainTangent",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainTangent",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainTangent",
|
||||
.selectionStep = 2,
|
||||
.hints = {{QObject::tr("%1 pick optional tangent point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Perpendicular
|
||||
{.commandName = "Sketcher_ConstrainPerpendicular",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainPerpendicular",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainPerpendicular",
|
||||
.selectionStep = 2,
|
||||
.hints = {{QObject::tr("%1 pick optional perpendicular point"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Parallel
|
||||
{.commandName = "Sketcher_ConstrainParallel",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick line"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainParallel",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second line"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
// Distance
|
||||
{.commandName = "Sketcher_ConstrainDistance",
|
||||
.selectionStep = 0,
|
||||
.hints = {{QObject::tr("%1 pick point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
|
||||
{.commandName = "Sketcher_ConstrainDistance",
|
||||
.selectionStep = 1,
|
||||
.hints = {{QObject::tr("%1 pick second point or edge"), {Gui::InputHint::UserInput::MouseLeft}}}},
|
||||
};
|
||||
}
|
||||
|
||||
static std::list<Gui::InputHint> lookupConstraintHints(const std::string& commandName, int selectionStep) {
|
||||
const auto constraintHintTable = getConstraintHintTable();
|
||||
auto it = std::ranges::find_if(constraintHintTable,
|
||||
[&commandName, selectionStep](const ConstraintHintEntry& entry) {
|
||||
return entry.commandName == commandName && entry.selectionStep == selectionStep;
|
||||
});
|
||||
|
||||
return (it != constraintHintTable.end()) ? it->hints : std::list<Gui::InputHint>{};
|
||||
}
|
||||
|
||||
void activated() override
|
||||
{
|
||||
selFilterGate = new GenericConstraintSelection(sketchgui->getObject());
|
||||
@@ -1602,6 +1805,8 @@ public:
|
||||
ss.str().c_str());
|
||||
sketchgui->draw(false, false); // Redraw
|
||||
}
|
||||
|
||||
updateHint();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1616,6 +1821,17 @@ public:
|
||||
DrawSketchHandler::quit();
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Gui::InputHint> getToolHints() const override {
|
||||
if (selectionEmpty()) {
|
||||
return {{QObject::tr("%1 pick geometry"), {Gui::InputHint::UserInput::MouseLeft}}};
|
||||
} else if (selPoints.size() == 1 && selLine.empty() && selCircleArc.empty()) {
|
||||
return {{QObject::tr("%1 pick second point or geometry"), {Gui::InputHint::UserInput::MouseLeft}}};
|
||||
} else {
|
||||
return {{QObject::tr("%1 place dimension"), {Gui::InputHint::UserInput::MouseLeft}}};
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
SpecialConstraint specialConstraint;
|
||||
AvailableConstraint availableConstraint;
|
||||
@@ -1759,7 +1975,7 @@ protected:
|
||||
&& !contains(selEllipseAndCo, elem);
|
||||
}
|
||||
|
||||
bool selectionEmpty()
|
||||
bool selectionEmpty() const
|
||||
{
|
||||
return selPoints.empty() && selLine.empty() && selCircleArc.empty() && selEllipseAndCo.empty();
|
||||
}
|
||||
|
||||
@@ -32,6 +32,8 @@
|
||||
#include <Base/Tools2D.h>
|
||||
#include <Gui/Selection/Selection.h>
|
||||
#include <Gui/ToolHandler.h>
|
||||
#include <Gui/InputHint.h>
|
||||
|
||||
#include <Mod/Part/App/Geometry.h>
|
||||
#include <Mod/Sketcher/App/Constraint.h>
|
||||
|
||||
@@ -160,6 +162,10 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual std::list<Gui::InputHint> getToolHints() const
|
||||
{
|
||||
return {};
|
||||
}
|
||||
void quit() override;
|
||||
|
||||
friend class ViewProviderSketch;
|
||||
|
||||
Reference in New Issue
Block a user