Merge pull request #22217 from PaddleStroke/sk_texttool

Sketcher: Text tool
This commit is contained in:
Benjamin Nauck
2026-03-10 18:07:50 +01:00
committed by GitHub
67 changed files with 6064 additions and 339 deletions
+16
View File
@@ -7,6 +7,22 @@ macro(SetupFreetype)
message("===============================================================\n"
"FreeType2 not found. Part module will lack of makeWireString().\n"
"===============================================================\n")
else()
# find_package(harfbuzz CONFIG) fails on windows
# lets do it the complicated way instead.
find_path(HARFBUZZ_INCLUDE_DIR hb.h PATH_SUFFIXES harfbuzz)
find_library(HARFBUZZ_LIBRARY NAMES harfbuzz)
if(HARFBUZZ_INCLUDE_DIR AND HARFBUZZ_LIBRARY)
if(NOT TARGET harfbuzz::harfbuzz)
add_library(harfbuzz::harfbuzz UNKNOWN IMPORTED)
set_target_properties(harfbuzz::harfbuzz PROPERTIES
IMPORTED_LOCATION "${HARFBUZZ_LIBRARY}"
INTERFACE_INCLUDE_DIRECTORIES "${HARFBUZZ_INCLUDE_DIR}"
)
endif()
else()
message(FATAL_ERROR "HarfBuzz not found")
endif()
endif(NOT FREETYPE_FOUND)
endif(FREECAD_USE_FREETYPE)
+2
View File
@@ -41,6 +41,8 @@ if(FREETYPE_FOUND)
${Part_LIBS}
${FREETYPE_LIBRARIES}
)
set(Part_LIBS ${Part_LIBS} harfbuzz::harfbuzz)
endif(FREETYPE_FOUND)
generate_from_py(Arc)
+428
View File
@@ -28,6 +28,9 @@
#include <BRepBuilderAPI_MakeEdge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <BRepBuilderAPI_MakeVertex.hxx>
#include <BRepBuilderAPI_MakeWire.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <BRepLib.hxx>
#include <BSplCLib.hxx>
#include <GC_MakeArcOfCircle.hxx>
#include <GC_MakeArcOfEllipse.hxx>
@@ -37,6 +40,7 @@
#include <GC_MakeEllipse.hxx>
#include <GC_MakeHyperbola.hxx>
#include <GC_MakeSegment.hxx>
#include <GCE2d_MakeSegment.hxx>
#include <GCPnts_AbscissaPoint.hxx>
#include <gce_ErrorType.hxx>
#include <gce_MakeParab.hxx>
@@ -63,6 +67,8 @@
#include <Geom_SurfaceOfRevolution.hxx>
#include <Geom_ToroidalSurface.hxx>
#include <Geom_TrimmedCurve.hxx>
#include <Geom2d_BezierCurve.hxx>
#include <Geom2d_TrimmedCurve.hxx>
#include <GeomAPI_ExtremaCurveCurve.hxx>
#include <GeomAPI_Interpolate.hxx>
#include <GeomAPI_PointsToBSpline.hxx>
@@ -86,6 +92,8 @@
#include <gp_Pnt.hxx>
#include <gp_Sphere.hxx>
#include <gp_Torus.hxx>
#include <gp_Trsf.hxx>
#include <gp_Vec.hxx>
#include <LProp_NotDefined.hxx>
#include <Precision.hxx>
#include <ShapeConstruct_Curve.hxx>
@@ -96,6 +104,8 @@
#include <TColgp_HArray1OfPnt.hxx>
#include <TColStd_Array1OfReal.hxx>
#include <TColStd_HArray1OfBoolean.hxx>
#include <TopExp_Explorer.hxx>
#include <ShapeFix_Wire.hxx>
#if OCC_VERSION_HEX < 0x070600
# include <GeomAdaptor_HSurface.hxx>
@@ -105,11 +115,31 @@
#include <boost/random.hpp>
#include <cmath>
#include <ctime>
#include <fstream>
#include <iterator>
#include <limits>
#include <memory>
#include <vector>
// FreeType Headers
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
#include FT_OUTLINE_H
#include <hb.h>
// headers to scale text correctly
#include <BRepBndLib.hxx>
#include <Bnd_Box.hxx>
#include <App/Application.h>
#include <App/PropertyStandard.h>
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Base/FileInfo.h>
#include <Base/Reader.h>
#include <Base/Tools.h>
#include <Base/Writer.h>
#include <BRep_Tool.hxx>
#include <TopoDS.hxx>
@@ -7305,4 +7335,402 @@ std::unique_ptr<GeomCurve> makeFromCurveAdaptor(const Adaptor3d_Curve& adapt, bo
return geoCurve;
}
// ===== TEXT TO EDGES (adapted from FT2FC.cpp) =====
namespace
{
// Context for FreeType decomposition callbacks
struct FTDC_Ctx
{
std::vector<TopoDS_Wire> Wires;
std::vector<TopoDS_Edge> Edges;
FT_Vector LastVert;
FT_Vector StartVert;
Handle(Geom_Surface) surf;
};
// Make a TopoDS_Wire from a list of TopoDS_Edges
TopoDS_Wire edgesToWire(std::vector<TopoDS_Edge>& Edges)
{
if (Edges.empty()) {
return TopoDS_Wire();
}
BRepBuilderAPI_MakeWire mkWire;
for (const auto& edge : Edges) {
mkWire.Add(edge);
}
if (mkWire.IsDone()) {
TopoDS_Wire wire = mkWire.Wire();
BRepLib::BuildCurves3d(wire);
// Ensure the wire is topologically closed and valid
ShapeFix_Wire sfw;
sfw.Load(wire);
sfw.FixClosed();
wire = sfw.Wire();
return wire;
}
else {
Base::Console().warning("edgesToWire: Failed to build a valid wire from edges.\n");
return TopoDS_Wire();
}
}
// Helper to close the current contour if needed and flush to Wires list
void flushContour(FTDC_Ctx* dc)
{
if (dc->Edges.empty()) {
return;
}
// Check if the contour is geometrically closed.
// If not, add a closing segment from LastVert to StartVert.
gp_Pnt2d pStart(dc->StartVert.x, dc->StartVert.y);
gp_Pnt2d pEnd(dc->LastVert.x, dc->LastVert.y);
if (!pStart.IsEqual(pEnd, Precision::Confusion())) {
Handle(Geom2d_TrimmedCurve) lseg = GCE2d_MakeSegment(pEnd, pStart);
TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(lseg, dc->surf);
dc->Edges.push_back(edge);
}
TopoDS_Wire newWire = edgesToWire(dc->Edges);
if (!newWire.IsNull()) {
dc->Wires.push_back(newWire);
}
dc->Edges.clear();
}
// FT Decompose callbacks
int move_cb(const FT_Vector* pt, void* p)
{
FTDC_Ctx* dc = static_cast<FTDC_Ctx*>(p);
flushContour(dc);
dc->StartVert = *pt;
dc->LastVert = *pt;
return 0;
}
int line_cb(const FT_Vector* pt, void* p)
{
FTDC_Ctx* dc = static_cast<FTDC_Ctx*>(p);
gp_Pnt2d v1(dc->LastVert.x, dc->LastVert.y);
gp_Pnt2d v2(pt->x, pt->y);
if (!v1.IsEqual(v2, Precision::Confusion())) {
Handle(Geom2d_TrimmedCurve) lseg = GCE2d_MakeSegment(v1, v2);
TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(lseg, dc->surf);
dc->Edges.push_back(edge);
}
dc->LastVert = *pt;
return 0;
}
int quad_cb(const FT_Vector* pt0, const FT_Vector* pt1, void* p)
{
FTDC_Ctx* dc = static_cast<FTDC_Ctx*>(p);
TColgp_Array1OfPnt2d Poles(1, 3);
Poles.SetValue(1, gp_Pnt2d(dc->LastVert.x, dc->LastVert.y));
Poles.SetValue(2, gp_Pnt2d(pt0->x, pt0->y));
Poles.SetValue(3, gp_Pnt2d(pt1->x, pt1->y));
Handle(Geom2d_BezierCurve) bcseg = new Geom2d_BezierCurve(Poles);
TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(bcseg, dc->surf);
dc->Edges.push_back(edge);
dc->LastVert = *pt1;
return 0;
}
int cubic_cb(const FT_Vector* pt0, const FT_Vector* pt1, const FT_Vector* pt2, void* p)
{
FTDC_Ctx* dc = static_cast<FTDC_Ctx*>(p);
TColgp_Array1OfPnt2d Poles(1, 4);
Poles.SetValue(1, gp_Pnt2d(dc->LastVert.x, dc->LastVert.y));
Poles.SetValue(2, gp_Pnt2d(pt0->x, pt0->y));
Poles.SetValue(3, gp_Pnt2d(pt1->x, pt1->y));
Poles.SetValue(4, gp_Pnt2d(pt2->x, pt2->y));
Handle(Geom2d_BezierCurve) bcseg = new Geom2d_BezierCurve(Poles);
TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(bcseg, dc->surf);
dc->Edges.push_back(edge);
dc->LastVert = *pt2;
return 0;
}
} // end anonymous namespace
/**
* @brief Takes a set of base shapes, transforms them to fit a two-point
* definition, and converts them to a vector of Part::Geometry.
*
* This is the core transformation logic shared by textToEdges and the Symbol tool.
*
* @param geos Output vector of unique_ptr to Part::Geometry.
* @param baseShapes Input vector of raw TopoDS_Shape objects at origin.
* @param p1 The start point (typically bottom-left) of placement.
* @param p2 The end point, which defines the size and orientation.
* @param height If true, the distance p1-p2 defines the height.
* If false, it defines the width.
*/
void transformAndConvertToGeometry(
std::vector<std::unique_ptr<Part::Geometry>>& geos,
const std::vector<TopoDS_Shape>& baseShapes,
const Base::Vector3d& p1,
const Base::Vector3d& p2,
bool height
)
{
if (baseShapes.empty()) {
return;
}
Base::Vector3d dir = p2 - p1;
double length = dir.Length();
if (length < Precision::Confusion()) {
return;
}
// 1. Calculate the bounding box of the base shapes
Bnd_Box bndBox;
for (const auto& shape : baseShapes) {
if (!shape.IsNull()) {
BRepBndLib::Add(shape, bndBox);
}
}
if (bndBox.IsVoid()) {
Base::Console().warning(
"transformAndConvertToGeometry: Could not determine bounds of generated geometry.\n"
);
return;
}
Standard_Real xmin, ymin, zmin, xmax, ymax, zmax;
bndBox.Get(xmin, ymin, zmin, xmax, ymax, zmax);
double baseWidth = xmax - xmin;
double baseHeight = ymax - ymin;
// This transform will move the geometry's bottom-left corner to the origin (0,0,0)
gp_Vec initialTranslationVec(-xmin, -ymin, 0.0);
// 2. Determine scale and rotation
double angle;
double scale;
if (height) {
if (baseHeight < Precision::Confusion()) {
return;
}
scale = length / baseHeight;
angle = std::atan2(dir.y, dir.x) - 0.5 * M_PI;
}
else { // Width mode
if (baseWidth < Precision::Confusion()) {
return;
}
scale = length / baseWidth;
angle = std::atan2(dir.y, dir.x);
}
// 3. Construct the final transformation matrix
gp_Trsf initialTranslate;
initialTranslate.SetTranslation(initialTranslationVec);
gp_Trsf scaleTrsf;
scaleTrsf.SetScale(gp::Origin(), scale);
gp_Trsf rotateTrsf;
rotateTrsf.SetRotation(gp::XOY().Axis(), angle);
gp_Trsf finalTranslate;
finalTranslate.SetTranslation(gp_Vec(p1.x, p1.y, 0.0));
gp_Trsf finalTrsf = finalTranslate * rotateTrsf * scaleTrsf * initialTranslate;
// 4. Apply transformation and convert to Sketcher geometry
for (const auto& shape : baseShapes) {
BRepBuilderAPI_Transform performer(shape, finalTrsf, true);
if (!performer.IsDone()) {
continue;
}
for (TopExp_Explorer explorer(performer.Shape(), TopAbs_EDGE); explorer.More();
explorer.Next()) {
Standard_Real first, last;
const TopoDS_Edge& edge = TopoDS::Edge(explorer.Current());
Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, first, last);
if (curve.IsNull()) {
continue;
}
std::unique_ptr<Part::GeomCurve> newGeo;
if (BRep_Tool::IsClosed(edge)) {
newGeo = Part::makeFromCurve(curve);
}
else {
if (curve->IsKind(STANDARD_TYPE(Geom_TrimmedCurve))) {
Handle(Geom_TrimmedCurve) trc = Handle(Geom_TrimmedCurve)::DownCast(curve);
curve = trc->BasisCurve();
}
if (curve->IsKind(STANDARD_TYPE(Geom_BezierCurve))) {
Handle(Geom_TrimmedCurve) tcurve
= new Geom_TrimmedCurve(curve, first, last, true, false);
Part::GeomTrimmedCurve geomcurve(tcurve);
newGeo.reset(geomcurve.toBSpline(first, last));
}
else {
newGeo = Part::makeFromTrimmedCurve(curve, first, last);
}
}
if (!newGeo) {
Base::Console().warning(
"transformAndConvertToGeometry: Could not create geometry from curve.\n"
);
continue;
}
try {
geos.emplace_back(std::move(newGeo));
}
catch (const Base::Exception& e) {
Base::Console().warning("BSpline conversion failed: %s\n", e.what());
}
}
}
}
// The core logic, refactored from FT2FC to be Python-independent
std::vector<TopoDS_Shape> makeTextWires(
std::string& text,
std::string& fontFile,
double height,
double tracking
)
{
if (text.empty()) {
return {};
}
if (fontFile.empty()) {
#if defined(FC_OS_LINUX)
fontFile = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";
#elif defined(FC_OS_WIN32)
fontFile = "C:/Windows/Fonts/Arial.ttf";
#elif defined(FC_OS_MAC)
fontFile = "/System/Library/Fonts/Helvetica.ttc"; // Common system default
#endif
}
std::vector<TopoDS_Shape> allWires;
FT_Library ftLib;
if (FT_Init_FreeType(&ftLib) != 0) {
Base::Console().error("makeTextWires: Could not initialize FreeType library\n");
return allWires;
}
std::ifstream fontStream(fontFile, std::ios::binary);
if (!fontStream) {
Base::Console().error("makeTextWires: Cannot open font file: %s\n", fontFile.c_str());
FT_Done_FreeType(ftLib);
return allWires;
}
std::vector<char> fontBuffer(
(std::istreambuf_iterator<char>(fontStream)),
std::istreambuf_iterator<char>()
);
FT_Face ftFace;
if (FT_New_Memory_Face(
ftLib,
reinterpret_cast<const FT_Byte*>(fontBuffer.data()),
fontBuffer.size(),
0,
&ftFace
)
!= 0) {
Base::Console().error("makeTextWires: Failed to load font face from %s\n", fontFile.c_str());
FT_Done_FreeType(ftLib);
return allWires;
}
// Use the font's native units for maximum precision
double unitsPerEM = static_cast<double>(ftFace->units_per_EM);
if (unitsPerEM < 1.0) {
unitsPerEM = 2048.0; // Fallback
}
// We want a nominal height of 1.0 for the base shapes
double scaleFactor = (height / unitsPerEM);
FT_Outline_Funcs ftCallbacks = {move_cb, line_cb, quad_cb, cubic_cb, 0, 0};
FT_UInt ftLoadFlags = FT_LOAD_NO_SCALE | FT_LOAD_NO_BITMAP;
// Use HarfBuzz for text shaping. Positions are in font design units (upem),
// matching our FT_LOAD_NO_SCALE outline decomposition.
auto* hbBlob = hb_blob_create(
fontBuffer.data(),
static_cast<unsigned int>(fontBuffer.size()),
HB_MEMORY_MODE_READONLY,
nullptr,
nullptr
);
auto* hbFace = hb_face_create(hbBlob, 0);
auto* hbFont = hb_font_create(hbFace);
auto* hbBuf = hb_buffer_create();
hb_buffer_add_utf8(hbBuf, text.c_str(), -1, 0, -1);
hb_buffer_guess_segment_properties(hbBuf);
hb_shape(hbFont, hbBuf, nullptr, 0);
unsigned int glyphCount = 0;
auto* glyphInfos = hb_buffer_get_glyph_infos(hbBuf, &glyphCount);
auto* glyphPositions = hb_buffer_get_glyph_positions(hbBuf, &glyphCount);
double penPos = 0.0;
double currentTracking = 0.0;
for (unsigned int i = 0; i < glyphCount; ++i) {
FT_UInt glyphIndex = glyphInfos[i].codepoint;
double xOffset = glyphPositions[i].x_offset;
double xAdvance = glyphPositions[i].x_advance;
if (FT_Load_Glyph(ftFace, glyphIndex, ftLoadFlags) != 0) {
penPos += xAdvance;
currentTracking += tracking;
continue;
}
if (ftFace->glyph->format == FT_GLYPH_FORMAT_OUTLINE
&& ftFace->glyph->outline.n_contours > 0) {
FTDC_Ctx ctx;
ctx.surf = new Geom_Plane(gp::Origin(), gp::DZ());
FT_Outline_Decompose(&ftFace->glyph->outline, &ftCallbacks, &ctx);
flushContour(&ctx);
if (!ctx.Wires.empty()) {
gp_Trsf charTransform;
charTransform.SetScale(gp::Origin(), scaleFactor);
gp_Vec translation((penPos + xOffset) * scaleFactor + currentTracking, 0.0, 0.0);
charTransform.SetTranslationPart(translation);
for (const auto& wire : ctx.Wires) {
BRepBuilderAPI_Transform performer(charTransform);
performer.Perform(wire, true); // true = create a copy
if (performer.IsDone()) {
allWires.push_back(performer.Shape());
}
}
}
}
penPos += xAdvance;
currentTracking += tracking;
}
hb_buffer_destroy(hbBuf);
hb_font_destroy(hbFont);
hb_face_destroy(hbFace);
hb_blob_destroy(hbBlob);
FT_Done_Face(ftFace);
FT_Done_FreeType(ftLib);
return allWires;
}
} // namespace Part
+30
View File
@@ -1417,4 +1417,34 @@ PartExport std::unique_ptr<GeomCurve> makeFromTrimmedCurve(
);
PartExport std::unique_ptr<GeomCurve> makeFromCurveAdaptor(const Adaptor3d_Curve&, bool silent = false);
/**
* @brief Creates a series of edges representing a text string.
*
* This function generates geometric edges for a given text string using a specified font file.
* The text is scaled and positioned within a bounding box defined by two points.
*
* @param geos Output vector to be populated with geometry (GeomTrimmedCurve). The vector is cleared
* first.
* @param p1 The origin point for the text's baseline.
* @param p2 A point used to define the height and orientation. The text height will be
* (p2-p1).Length() and its orientation angle will be the angle of the vector (p2-p1).
* @param plainText The string to be rendered.
* @param fontFile The absolute path to the TTF, OTF, etc., font file.
* @param tracking Additional spacing between characters.
*/
PartExport void transformAndConvertToGeometry(
std::vector<std::unique_ptr<Part::Geometry>>& geos,
const std::vector<TopoDS_Shape>& baseShapes,
const Base::Vector3d& p1,
const Base::Vector3d& p2,
bool height
);
PartExport std::vector<TopoDS_Shape> makeTextWires(
std::string& text,
std::string& fontFile,
double height = 1.0,
double tracking = 0.0
);
} // namespace Part
+4
View File
@@ -1,4 +1,8 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
include_directories(
SYSTEM
${CMAKE_SOURCE_DIR}/src/3rdParty/json/single_include/nlohmann/
)
add_library(Sketcher SHARED)
+271 -20
View File
@@ -31,6 +31,8 @@
#include <string>
#include <vector>
#include "json.hpp"
#include <fmt/ranges.h>
#include <Base/Reader.h>
@@ -90,6 +92,7 @@ Constraint* Constraint::copy() const
temp->isActive = this->isActive;
temp->elements = this->elements;
// Do not copy tag, otherwise it is considered a clone, and a "rename" by the expression engine.
temp->MetaData = this->MetaData;
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
temp->First = this->First;
@@ -149,8 +152,10 @@ unsigned int Constraint::getMemSize() const
void Constraint::Save(Writer& writer) const
{
std::string encodeName = encodeAttribute(Name);
std::string encodeMetaData = encodeAttribute(MetaData);
writer.Stream() << writer.ind() << "<Constrain "
<< "Name=\"" << encodeName << "\" "
<< "MetaData=\"" << encodeMetaData << "\" "
<< "Type=\"" << (int)Type << "\" ";
if (this->Type == InternalAlignment) {
writer.Stream() << "InternalAlignmentType=\"" << (int)AlignmentType << "\" "
@@ -195,6 +200,7 @@ void Constraint::Restore(XMLReader& reader)
{
reader.readElement("Constrain");
Name = reader.getAttribute<const char*>("Name");
MetaData = reader.hasAttribute("MetaData") ? reader.getAttribute<const char*>("MetaData") : "";
Type = reader.getAttribute<ConstraintType>("Type");
Value = reader.getAttribute<double>("Value");
@@ -391,32 +397,31 @@ GeoElementId Constraint::getElement(size_t index) const
#endif
return elements[index];
}
void Constraint::setElement(size_t index, GeoElementId element)
{
if (index >= elements.size()) {
throw Base::IndexError("Constraint::getElement index out of range");
}
elements[index] = element;
if (ensureElementExists(index)) {
elements[index] = element;
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
First = element.GeoId;
FirstPos = element.Pos;
break;
case 1:
Second = element.GeoId;
SecondPos = element.Pos;
break;
case 2:
Third = element.GeoId;
ThirdPos = element.Pos;
break;
if (index < 3) {
switch (index) {
case 0:
First = element.GeoId;
FirstPos = element.Pos;
break;
case 1:
Second = element.GeoId;
SecondPos = element.Pos;
break;
case 2:
Third = element.GeoId;
ThirdPos = element.Pos;
break;
}
}
}
#endif
}
}
size_t Constraint::getElementsSize() const
@@ -434,3 +439,249 @@ void Constraint::addElement(GeoElementId element)
elements.push_back(element);
#endif
}
int Constraint::getGeoId(int index) const
{
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
return First;
case 1:
return Second;
case 2:
return Third;
}
}
#endif
return hasElement(index) ? elements[index].GeoId : GeoEnum::GeoUndef;
}
PointPos Constraint::getPosId(int index) const
{
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
return FirstPos;
case 1:
return SecondPos;
case 2:
return ThirdPos;
}
}
#endif
return hasElement(index) ? elements[index].Pos : PointPos::none;
}
int Constraint::getPosIdAsInt(int index) const
{
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
return (int)FirstPos;
case 1:
return (int)SecondPos;
case 2:
return (int)ThirdPos;
}
}
#endif
return hasElement(index) ? elements[index].posIdAsInt() : 0;
}
bool Constraint::hasElement(int index) const
{
return index >= 0 && index < elements.size();
}
void Constraint::setGeoId(int index, int geoId)
{
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
First = geoId;
break;
case 1:
Second = geoId;
break;
case 2:
Third = geoId;
break;
}
}
#endif
if (ensureElementExists(index)) {
elements[index].GeoId = geoId;
}
}
void Constraint::setPosId(int index, PointPos pos)
{
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
FirstPos = pos;
break;
case 1:
SecondPos = pos;
break;
case 2:
ThirdPos = pos;
break;
}
}
#endif
if (ensureElementExists(index)) {
elements[index].Pos = pos;
}
}
void Constraint::setPosId(int index, int pos)
{
#if SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
if (index < 3) {
switch (index) {
case 0:
FirstPos = static_cast<PointPos>(pos);
break;
case 1:
SecondPos = static_cast<PointPos>(pos);
break;
case 2:
ThirdPos = static_cast<PointPos>(pos);
break;
}
}
#endif
if (ensureElementExists(index)) {
elements[index].Pos = static_cast<PointPos>(pos);
}
}
bool Constraint::ensureElementExists(int index)
{
if (index < 0) {
return false; // Indicate failure for an invalid index
}
if (index >= elements.size()) {
elements.resize(index + 1);
}
return true;
}
void Constraint::swapElements(int index1, int index2)
{
if (index1 == index2) {
return;
}
if (ensureElementExists(index1) && ensureElementExists(index2)) {
std::swap(elements[index1], elements[index2]);
}
}
bool Constraint::isElementsEmpty() const
{
return elements.empty();
}
void Constraint::truncateElements(size_t newSize)
{
if (newSize < elements.size()) {
elements.resize(newSize);
}
}
std::string Constraint::getText() const
{
if (MetaData.empty()) {
return {};
}
try {
auto j = nlohmann::json::parse(MetaData);
if (j.contains("text")) {
return j["text"].get<std::string>();
}
}
catch (...) {
// Handle JSON parsing errors or type mismatches silently
}
return {};
}
void Constraint::setText(const std::string& text)
{
nlohmann::json j;
if (!MetaData.empty()) {
try {
j = nlohmann::json::parse(MetaData);
}
catch (...) {
}
}
j["text"] = text;
MetaData = j.dump();
}
std::string Constraint::getFont() const
{
if (MetaData.empty()) {
return {};
}
try {
auto j = nlohmann::json::parse(MetaData);
if (j.contains("font")) {
return j["font"].get<std::string>();
}
}
catch (...) {
}
return {};
}
void Constraint::setFont(const std::string& font)
{
nlohmann::json j;
if (!MetaData.empty()) {
try {
j = nlohmann::json::parse(MetaData);
}
catch (...) {
}
}
j["font"] = font;
MetaData = j.dump();
}
bool Constraint::getIsTextHeight() const
{
if (MetaData.empty()) {
return true; // Default value
}
try {
auto j = nlohmann::json::parse(MetaData);
if (j.contains("isTextHeight")) {
return j["isTextHeight"].get<bool>();
}
}
catch (...) {
}
return true; // Default value
}
void Constraint::setIsTextHeight(bool isHeight)
{
nlohmann::json j;
if (!MetaData.empty()) {
try {
j = nlohmann::json::parse(MetaData);
}
catch (...) {
}
}
j["isTextHeight"] = isHeight;
MetaData = j.dump();
}
+25 -2
View File
@@ -69,6 +69,8 @@ enum ConstraintType : int
Block = 17,
Diameter = 18,
Weight = 19,
Group = 20,
Text = 21,
NumConstraintTypes // must be the last item!
};
@@ -189,7 +191,9 @@ private:
"SnellsLaw",
"Block",
"Diameter",
"Weight"}};
"Weight",
"Group",
"Text"}};
// clang-format on
constexpr static std::array<const char*, InternalAlignmentType::NumInternalAlignmentType>
@@ -212,6 +216,7 @@ public:
ConstraintType Type {None};
InternalAlignmentType AlignmentType {Undef};
std::string Name;
std::string MetaData;
float LabelDistance {10.F};
float LabelPosition {0.F};
bool isDriving {true};
@@ -225,8 +230,26 @@ public:
GeoElementId getElement(size_t index) const;
void setElement(size_t index, GeoElementId element);
size_t getElementsSize() const;
void addElement(GeoElementId element);
bool hasElement(int index) const;
size_t getElementsSize() const;
bool isElementsEmpty() const;
void truncateElements(size_t newSize);
int getGeoId(int index) const;
PointPos getPosId(int index) const;
int getPosIdAsInt(int index) const;
void setGeoId(int index, int geoId);
void setPosId(int index, PointPos pos);
void setPosId(int index, int pos);
void swapElements(int index1, int index2);
bool ensureElementExists(int index);
std::string getText() const;
void setText(const std::string& text);
std::string getFont() const;
void setFont(const std::string& font);
bool getIsTextHeight() const;
void setIsTextHeight(bool val);
#ifdef SKETCHER_CONSTRAINT_USE_LEGACY_ELEMENTS
// Deprecated, use getElement/setElement instead
+103
View File
@@ -97,13 +97,104 @@ int ConstraintPy::PyInit(PyObject* args, PyObject* /*kwd*/)
PyObject* index_or_value;
PyObject* oNumArg4;
PyObject* oNumArg5;
PyObject* py_elements_list = nullptr;
char* text_str = nullptr; // Variable for the text content
char* font_str = nullptr; // Variable for the font name
int any_index;
PyObject* activated;
PyObject* driving;
PyObject* py_is_height = nullptr;
Sketcher::Constraint* constraint = this->getConstraintPtr();
auto parseElementsList = [](PyObject* list, Sketcher::Constraint* constr_ptr) -> bool {
Py_ssize_t list_size = PyList_Size(list);
// The list should contain pairs of (geoId, posId), so its size must be even.
if (list_size % 2 != 0) {
PyErr_SetString(
PyExc_ValueError,
"Element list must have an even number of items (pairs of GeoId, PosId)."
);
return false; // Failure
}
constr_ptr->truncateElements(0);
for (Py_ssize_t i = 0; i < list_size; i += 2) {
PyObject* py_geoId_obj = PyList_GetItem(list, i);
PyObject* py_posId_obj = PyList_GetItem(list, i + 1);
// Perform crucial type checking on list items
if (!py_geoId_obj || !py_posId_obj || !PyLong_Check(py_geoId_obj)
|| !PyLong_Check(py_posId_obj)) {
PyErr_SetString(PyExc_TypeError, "Element list items must be integers.");
return false; // Failure
}
int geoId = PyLong_AsLong(py_geoId_obj);
int posId = PyLong_AsLong(py_posId_obj);
// Use the C++ API to populate the constraint
constr_ptr->setElement(
i / 2,
Sketcher::GeoElementId(geoId, static_cast<Sketcher::PointPos>(posId))
);
}
return true; // Success
};
// Attempt to parse (string, list) for 'Group'
if (PyArg_ParseTuple(args, "sO!", &ConstraintType, &PyList_Type, &py_elements_list)) {
if (strcmp(ConstraintType, "Group") == 0) {
constraint->Type = Sketcher::Group;
if (!parseElementsList(py_elements_list, constraint)) {
return -1; // The lambda set the Python error, so just return.
}
return 0; // Success!
}
}
PyErr_Clear();
// Attempt to parse (string, list, string, string, bool) for 'Text'
if (PyArg_ParseTuple(
args,
"sO!ss|O",
&ConstraintType,
&PyList_Type,
&py_elements_list,
&text_str,
&font_str,
&py_is_height
)) {
if (strcmp(ConstraintType, "Text") == 0) {
constraint->Type = Sketcher::Text;
// Call the shared lambda for list parsing
if (!parseElementsList(py_elements_list, constraint)) {
return -1; // The lambda set the Python error.
}
// Set the specific members for the Text constraint
constraint->setText(text_str);
constraint->setFont(font_str);
// Check and set the optional boolean
if (py_is_height && PyBool_Check(py_is_height)) {
constraint->setIsTextHeight(py_is_height == Py_True);
}
else {
constraint->setIsTextHeight(true);
}
return 0; // Success!
}
}
PyErr_Clear();
auto handleSi = [&]() -> bool {
if (strcmp("Horizontal", ConstraintType) == 0) {
constraint->Type = Horizontal;
@@ -904,6 +995,12 @@ std::string ConstraintPy::representation() const
result << "'PointOnObject' (" << getConstraintPtr()->First << ","
<< getConstraintPtr()->Second << ")>";
break;
case Group:
result << "'Group'>";
break;
case Text:
result << "'Text'>";
break;
default:
result << "'?'>";
break;
@@ -974,6 +1071,12 @@ Py::String ConstraintPy::getType() const
case PointOnObject:
return Py::String("PointOnObject");
break;
case Group:
return Py::String("Group");
break;
case Text:
return Py::String("Text");
break;
default:
return Py::String("Undefined");
break;
+175 -4
View File
@@ -197,6 +197,17 @@ int Sketch::setUpSketch(
clear();
// The geometries that are in groups are going to be ignored by the solver.
std::set<int> inGroupGeoIds;
for (const auto& c : ConstraintList) {
if (c->Type == Group || c->Type == Text) {
// Start from index 1, as 0 is the frame.
for (int i = 1; c->hasElement(i); ++i) {
inGroupGeoIds.insert(c->getGeoId(i));
}
}
}
std::vector<Part::Geometry*> intGeoList, extGeoList;
std::copy(GeoList.begin(), GeoList.end() - extGeoCount, std::back_inserter(intGeoList));
std::copy(GeoList.end() - extGeoCount, GeoList.end(), std::back_inserter(extGeoList));
@@ -248,7 +259,7 @@ int Sketch::setUpSketch(
buildInternalAlignmentGeometryMap(ConstraintList);
addGeometry(intGeoList, onlyBlockedGeometry);
addGeometry(intGeoList, onlyBlockedGeometry, inGroupGeoIds);
int extStart = Geoms.size();
addGeometry(extGeoList, true);
int extEnd = Geoms.size() - 1;
@@ -258,6 +269,22 @@ int Sketch::setUpSketch(
// The Geoms list might be empty after an undo/redo
if (!Geoms.empty()) {
// Disable any constraint that act on geometries that are in a group.
for (size_t i = 0; i < ConstraintList.size(); ++i) {
const auto& c = ConstraintList[i];
if (c->Type == Group || c->Type == Text) {
continue;
}
for (int j = 0; c->hasElement(j); ++j) {
if (inGroupGeoIds.count(c->getGeoId(j))) {
unenforceableConstraints[i] = true;
break;
}
}
}
addConstraints(ConstraintList, unenforceableConstraints);
}
clearTemporaryConstraints();
@@ -731,18 +758,33 @@ int Sketch::addGeometry(const std::vector<Part::Geometry*>& geos, bool fixed)
return ret;
}
int Sketch::addGeometry(const std::vector<Part::Geometry*>& geos, const std::vector<bool>& blockedGeometry)
int Sketch::addGeometry(
const std::vector<Part::Geometry*>& geos,
const std::vector<bool>& blockedGeometry,
const std::set<int>& inGroupGeoIds
)
{
assert(geos.size() == blockedGeometry.size());
int ret = -1;
int geoIdCounter = 0;
std::vector<Part::Geometry*>::const_iterator it;
std::vector<bool>::const_iterator bit;
for (it = geos.begin(), bit = blockedGeometry.begin();
it != geos.end() && bit != blockedGeometry.end();
++it, ++bit) {
ret = addGeometry(*it, *bit);
++it, ++bit, ++geoIdCounter) {
// Check if the current geometry is in group.
bool isInGroup = inGroupGeoIds.count(geoIdCounter);
if (isInGroup) {
GeoDef def;
def.geo = (*it)->clone();
Geoms.push_back(def);
}
else {
ret = addGeometry(*it, *bit);
}
}
return ret;
}
@@ -1866,6 +1908,7 @@ int Sketch::checkGeoId(int geoId) const
geoId += Geoms.size(); // convert negative external-geometry index to index into Geoms
}
if (!(geoId >= 0 && geoId < int(Geoms.size()))) {
Base::Console().warning("geoId %d Geoms.size %d\n", geoId, int(Geoms.size()));
throw Base::IndexError("Sketch::checkGeoId. GeoId index out range.");
}
return geoId;
@@ -2471,6 +2514,19 @@ int Sketch::addConstraint(const Constraint* constraint)
c.driving
);
} break;
case Text:
case Group: {
if (constraint->isElementsEmpty()) {
return -1;
}
// Check that the first element is correctly the group construction line
if (Geoms[checkGeoId(constraint->getGeoId(0))].type != Line) {
return -1;
}
rtn = ++ConstraintsCounter;
break;
}
case Sketcher::None: // ambiguous enum value
case Sketcher::Block: // handled separately while adding geometry
case NumConstraintTypes:
@@ -4759,6 +4815,8 @@ bool Sketch::updateNonDrivingConstraints()
int Sketch::solve()
{
captureGroupStates();
Base::TimeElapsed start_time;
std::string solvername;
@@ -4777,6 +4835,10 @@ int Sketch::solve()
SolveTime = Base::TimeElapsed::diffTimeF(start_time, end_time);
if (result == GCS::Success) {
applyGroupTransformations();
}
return result;
}
@@ -5513,3 +5575,112 @@ void Sketch::Save(Writer&) const
void Sketch::Restore(XMLReader&)
{}
// Group functions related -------------------------------------------------
Sketch::GroupLineState Sketch::getGroupLineState(int geoId) const
{
GroupLineState state;
state.startPoint = getPoint(geoId, PointPos::start);
state.endPoint = getPoint(geoId, PointPos::end);
return state;
}
void Sketch::captureGroupStates()
{
preSolveGroupStates.clear();
// A set to keep track of which parameters we've already moved.
std::set<double*> movedParams;
for (const auto& constrDef : Constrs) {
const Constraint* c = constrDef.constr;
if ((c->Type != Group && c->Type != Text) || !c->hasElement(1)) {
continue;
}
// --- Capture Frame State ---
int frameGeoId = c->getGeoId(0);
preSolveGroupStates[frameGeoId] = getGroupLineState(frameGeoId);
}
}
void Sketch::applyGroupTransformations()
{
if (preSolveGroupStates.empty()) {
return;
}
for (const auto& constrDef : Constrs) {
const Constraint* c = constrDef.constr;
if ((c->Type != Group && c->Type != Text) || !c->hasElement(1)) {
continue;
}
int frameGeoId = c->getGeoId(0);
// Get the "before" and "after" states of the frame line
GroupLineState preSolveFrame = preSolveGroupStates.at(frameGeoId);
GroupLineState postSolveFrame = getGroupLineState(frameGeoId);
// --- Calculate the Transformation ---
Base::Vector3d preVec = preSolveFrame.getVec();
Base::Vector3d postVec = postSolveFrame.getVec();
// Handle potential zero-length lines to avoid division by zero
double preLen = preVec.Length();
double scale = (preLen > Precision::Confusion()) ? postVec.Length() / preLen : 1.0;
// --- Create the Transformation Matrix ---
// 1. T1: Matrix to translate the group to the origin (using pre-solve start point)
Base::Matrix4D T1; // Identity
T1[0][3] = -preSolveFrame.startPoint.x;
T1[1][3] = -preSolveFrame.startPoint.y;
T1[2][3] = 0;
// 2. S: Matrix for scaling
Base::Matrix4D S; // Identity
S[0][0] = scale;
S[1][1] = scale;
S[2][2] = scale;
// 3. R: Matrix for rotation
Base::Matrix4D R; // Identity
if (preLen > Precision::Confusion()) {
// We can get the axis and angle from the two vectors and use rotLine
Base::Vector3d rotationAxis = preVec.Cross(postVec);
double rotationAngle = preVec.GetAngle(postVec);
// Only apply rotation if the vectors are not collinear
if (rotationAxis.Length() > Precision::Confusion()) {
R.rotLine(rotationAxis, rotationAngle);
}
}
// 4. T2: Matrix to translate the group to its new final position
Base::Matrix4D T2; // Identity
T2[0][3] = postSolveFrame.startPoint.x;
T2[1][3] = postSolveFrame.startPoint.y;
T2[2][3] = 0;
// 5. Combine the matrices in the correct order: T_final = T2 * R * S * T1
Base::Matrix4D transform = T2 * R * S * T1;
// --- Loop through grouped elements and apply the transform ---
for (int i = 1; c->hasElement(i); ++i) {
int groupedGeoId = c->getGeoId(i);
if (groupedGeoId == GeoEnum::GeoUndef) {
continue;
}
// Get the slave's current (pre-solve) state
Part::Geometry* groupedGeo = Geoms[checkGeoId(groupedGeoId)].geo;
// Apply the calculated transformation
groupedGeo->transform(transform);
}
}
preSolveGroupStates.clear();
}
+33 -1
View File
@@ -88,7 +88,11 @@ public:
int addGeometry(const std::vector<Part::Geometry*>& geos, bool fixed = false);
/// add unspecified geometry, where each element's "fixed" status is given by the
/// blockedGeometry array
int addGeometry(const std::vector<Part::Geometry*>& geos, const std::vector<bool>& blockedGeometry);
int addGeometry(
const std::vector<Part::Geometry*>& geos,
const std::vector<bool>& blockedGeometry,
const std::set<int>& inGroupGeoIds
);
/// get boolean list indicating whether the geometry is to be blocked or not
void getBlockedGeometry(
std::vector<bool>& blockedGeometry,
@@ -626,6 +630,34 @@ private:
Base::Vector3d initToPoint;
double moveStep;
// Group related things :
/// container to store information about groups
struct GroupLineState
{
Base::Vector3d startPoint;
Base::Vector3d endPoint;
// Convenience method to get the length (scale)
double getLength() const
{
return (endPoint - startPoint).Length();
}
// Convenience method to get the orientation vector
Base::Vector3d getVec() const
{
return endPoint - startPoint;
}
};
// This map stores the state of group lines just BEFORE a solve.
// We will use this to calculate the transformation AFTER the solve.
// Key: GeoId of the frame line.
// Value: it's initial position.
std::map<int, GroupLineState> preSolveGroupStates;
void captureGroupStates();
void applyGroupTransformations();
GroupLineState getGroupLineState(int geoId) const;
public:
GCS::Algorithm defaultSolver;
GCS::Algorithm defaultSolverRedundant;
+199 -8
View File
@@ -906,6 +906,118 @@ double SketchObject::getDatum(int ConstrId) const
return this->Constraints[ConstrId]->getValue();
}
int SketchObject::setTextAndFont(int ConstrId, std::string& newText, std::string& newFont, bool isHeight, bool isConstruction)
{
; // no need to check input data validity as this is an sketchobject managed operation.
Base::StateLocker lock(managedoperation, true);
// set the changed value for the constraint
if (this->Constraints.hasInvalidGeometry()) {
return -6;
}
const std::vector<Constraint*>& vals = this->Constraints.getValues();
if (ConstrId < 0 || ConstrId >= int(vals.size())) {
return -1;
}
auto* constr = vals[ConstrId];
if (constr->Type != Text || !constr->hasElement(0)) {
return -1;
}
// First we replace the old geometries by the new text.
const std::string oldText = constr->getText();
const std::string oldFont = constr->getFont();
const bool oldIsHeight = constr->getIsTextHeight();
int handleGeoId = constr->getGeoId(0);
int firstTextGeoId = constr->getGeoId(1);
bool hasExistingText = firstTextGeoId != GeoEnum::GeoUndef;
bool handleLast = handleGeoId > firstTextGeoId;
if (hasExistingText) {
// Check if text is construction or normal geos
auto* geo1 = getGeometry(firstTextGeoId);
isConstruction = GeometryFacade::getConstruction(geo1);
// Delete all the old text geos. Not the handle!
std::vector<int> geoIdsToDelete;
for (int i = 1; constr->hasElement(i); ++i) {
if (constr->getGeoId(i) == GeoEnum::GeoUndef) {
continue;
}
geoIdsToDelete.push_back(constr->getGeoId(i));
if (handleLast) {
--handleGeoId; // handle line is added after all text geos.
}
}
delGeometries(geoIdsToDelete);
}
auto* line = dynamic_cast<const Part::GeomLineSegment*>(getGeometry(handleGeoId));
if (!line) {
return -1;
}
// Generate text geos based on new text/font :
std::vector<std::unique_ptr<Part::Geometry>> newGeos;
std::vector<TopoDS_Shape> shapes = Part::makeTextWires(newText, newFont);
Part::transformAndConvertToGeometry(newGeos,
shapes,
line->getStartPoint(),
line->getEndPoint(),
isHeight);
// Add the geometries to sketch
int lastGeoid = getHighestCurveIndex();
std::vector<Part::Geometry*> newGeosRawPtrs;
newGeosRawPtrs.reserve(newGeos.size());
// Populate the raw pointer vector and release ownership from the unique_ptrs.
for (auto& geo_ptr : newGeos) {
if (isConstruction) {
Sketcher::GeometryFacade::setConstruction(geo_ptr.get(), isConstruction);
}
// Add the raw pointer to the new vector.
newGeosRawPtrs.push_back(geo_ptr.get());
// Release ownership from the unique_ptr. The SketchObject will now manage this memory.
geo_ptr.release();
}
newGeos.clear();
addGeometry(newGeosRawPtrs);
int newLastGeoid = getHighestCurveIndex();
// If there was text geos, they were deleted, which deleted the text constraint.
// In this case create a new constraint to replace it.
if (hasExistingText) {
constr = new Constraint();
constr->Type = Text;
constr->truncateElements(0); // remove the First/Second/Third that are created automatically
constr->addElement(GeoElementId(handleGeoId));
}
for (int i = lastGeoid + 1; i <= newLastGeoid; ++i) {
constr->addElement(GeoElementId(i));
}
constr->setText(newText);
constr->setFont(newFont);
constr->setIsTextHeight(isHeight);
if (hasExistingText) {
addConstraint(constr);
}
int err = solve();
if (err) {
constr->setText(oldText);
constr->setFont(oldFont);
constr->setIsTextHeight(oldIsHeight);
}
return err;
}
int SketchObject::setDriving(int ConstrId, bool isdriving)
{
// no need to check input data validity as this is an sketchobject managed operation.
@@ -1006,6 +1118,26 @@ int SketchObject::getActive(int ConstrId, bool& isactive)
return 0;
}
bool SketchObject::isConstraintActiveInSketch(const Sketcher::Constraint* cstr) const
{
// If the constraint is deactivated then it's over
if (!cstr || !cstr->isActive) {
return false;
}
if (cstr->Type == Group || cstr->Type == Text) {
return true;
}
// If the constraint is not deactivated, it could still constraint something in a group
for (int j = 0; cstr->hasElement(j); ++j) {
if (isInGroup(cstr->getGeoId(j), false)) {
return false;
}
}
return true;
}
int SketchObject::toggleActive(int ConstrId)
{
// no need to check input data validity as this is an sketchobject managed operation.
@@ -3084,6 +3216,14 @@ void SketchObject::changeConstraintAfterDeletingGeo(Constraint* constr,
return;
}
for (int i = 0; constr->hasElement(i); ++i) {
if (constr->getGeoId(i) == deletedGeoId){
constr->Type = ConstraintType::None;
return;
}
}
// legacy to make sure we're not missing something...
if (constr->involvesGeoId(deletedGeoId)) {
constr->Type = ConstraintType::None;
return;
@@ -3100,14 +3240,10 @@ void SketchObject::changeConstraintAfterDeletingGeo(Constraint* constr,
};
}
if (needsUpdate(constr->First)) {
constr->First -= step;
}
if (needsUpdate(constr->Second)) {
constr->Second -= step;
}
if (needsUpdate(constr->Third)) {
constr->Third -= step;
for (int i = 0; constr->hasElement(i); ++i) {
if (needsUpdate(constr->getGeoId(i))) {
constr->setGeoId(i, constr->getGeoId(i) - step);
}
}
}
@@ -10108,8 +10244,10 @@ bool SketchObject::evaluateConstraint(const Constraint* constraint) const
case Equal:
case PointOnObject:
case Angle:
case Text:
break;
case Tangent:
case Group:
requireSecond = true;
break;
case Symmetric:
@@ -10256,6 +10394,59 @@ std::string SketchObject::validateExpression(const App::ObjectIdentifier& path,
return "";
}
bool SketchObject::isInGroup(int geoId, bool includeHandle) const
{
const std::vector<Sketcher::Constraint*>& vals = Constraints.getValues();
for (const auto& constr : vals) {
if (constr->Type == Group || constr->Type == Text) {
// First is the group construction line. We include it or not in our search.
int iStart = includeHandle ? 0 : 1;
for (int i = iStart; constr->hasElement(i); ++i) {
if (constr->getGeoId(i) == geoId) {
return true;
}
}
}
}
return false;
}
bool SketchObject::isGroupHandle(int geoId) const
{
const std::vector<Sketcher::Constraint*>& vals = Constraints.getValues();
for (const auto& constr : vals) {
if (constr->Type == Group || constr->Type == Text) {
if (constr->getGeoId(0) == geoId) {
return true;
}
}
}
return false;
}
int SketchObject::getGroupHandleIfInGroup(int geoId)
{
const std::vector<Sketcher::Constraint*>& vals = Constraints.getValues();
for (const auto& constr : vals) {
if (constr->Type == Group || constr->Type == Text) {
// First is the group construction line.
int groupHandleGeoId = -1;
for (int i = 0; constr->hasElement(i); ++i) {
if (i == 0) {
groupHandleGeoId = constr->getGeoId(i);
}
else if (constr->getGeoId(i) == geoId) {
return groupHandleGeoId;
}
}
}
}
return geoId;
}
// This function is necessary for precalculation of an angle when adding
// an angle constraint. It is also used here, in SketchObject, to
// lock down the type of tangency/perpendicularity.
+25
View File
@@ -142,6 +142,21 @@ public:
\retval bool - true if the geometry is supported
*/
bool isSupportedGeometry(const Part::Geometry* geo) const;
/*!
\brief Returns true if the geometry is in a group
\param geoId - the geometry id in the sketch
\param includeHandle - return true if geoId is the group construction line handle
\retval bool - true if the geometry is supported
*/
bool isInGroup(int geoId, bool includeHandle = true) const;
bool isGroupHandle(int geoId) const;
/*!
\brief Returns geoId if it's not in a group. Or the group handle if it is in a group.
\param geoId - the geometry id in the sketch
*/
int getGroupHandleIfInGroup(int geoId);
/*!
\brief Add geometry to a sketch - It adds a copy with a different uuid (internally uses copy()
instead of clone()) \param geo - geometry to add \param construction - true for construction
@@ -338,6 +353,14 @@ public:
int setDatum(int ConstrId, double Datum);
/// get the datum of a Distance or Angle constraint
double getDatum(int ConstrId) const;
/// set the text and font of a text constraint
int setTextAndFont(
int ConstrId,
std::string& newText,
std::string& newFont,
bool isHeight,
bool isConstruction = false
);
/// set the driving status of this constraint and solve
int setDriving(int ConstrId, bool isdriving);
/// get the driving status of this constraint
@@ -352,6 +375,8 @@ public:
int setActive(int ConstrId, bool isactive);
/// get the driving status of this constraint
int getActive(int ConstrId, bool& isactive);
// Return true if the constraint is active, includes checking if it's not in a group
bool isConstraintActiveInSketch(const Sketcher::Constraint* cstr) const;
/// toggle the driving status of this constraint
int toggleActive(int ConstrId);
+17
View File
@@ -409,6 +409,23 @@ class SketchObject(Part2DObject):
"""
...
def setTextAndFont(
self, constraint: int, text: str, font: str, isheight: bool, isConstruction: bool
) -> None:
"""
Set the text and font of a Text constraint.
setTextAndFont(constraint: int, text: str, font: str, isHeight: bool, isConstruction: bool)
Args:
constraint: The index of the Text constraint.
text: The text string to display.
font: The full path to the font file (.ttf, .otf, etc.).
isHeight: Is the line handle of the group the height of the text.
isConstruction: Are text geometry construction of not.
"""
...
@constmethod
def getDatum(self, constraint: Union[int, str], /) -> Quantity:
"""
@@ -756,6 +756,61 @@ PyObject* SketchObjectPy::delConstraintsToExternal()
Py_Return;
}
PyObject* SketchObjectPy::setTextAndFont(PyObject* args, PyObject* kwd)
{
int constrIndex = -1;
char* textStr;
char* fontStr;
PyObject* isHeightObj = Py_True;
PyObject* isConstrObj = Py_False; // Default to null (parameter not provided)
// "iss|O!O!" (int, str, str, | bool, bool)
if (!PyArg_ParseTuple(
args,
"iss|O!O!",
&constrIndex,
&textStr,
&fontStr,
&PyBool_Type,
&isHeightObj,
&PyBool_Type,
&isConstrObj
)) {
return nullptr;
}
std::string text(textStr);
std::string font(fontStr);
// Call the C++ implementation
int err = this->getSketchObjectPtr()->setTextAndFont(
constrIndex,
text,
font,
Base::asBoolean(isHeightObj),
Base::asBoolean(isConstrObj)
);
// Handle errors returned from the C++ function
if (err) {
std::stringstream str;
if (err == -1) {
str << "Invalid constraint index or not a Text constraint: " << constrIndex;
}
else if (err == -6) {
str << "Cannot set text/font because of invalid geometry in the sketch";
}
else { // Generic error for solver failures etc.
str << "Failed to set text/font for constraint with index " << constrIndex
<< ". The operation would result in an invalid sketch.";
}
PyErr_SetString(PyExc_ValueError, str.str().c_str());
return nullptr;
}
Py_Return;
}
PyObject* SketchObjectPy::setDatum(PyObject* args)
{
double Datum;
+5
View File
@@ -21,6 +21,7 @@ set(SketcherGui_UIC_SRCS
TaskSketcherSolverAdvanced.ui
TaskSketcherValidation.ui
InsertDatum.ui
EditTextDialog.ui
SketchOrientationDialog.ui
SketchMirrorDialog.ui
SketcherSettings.ui
@@ -53,6 +54,7 @@ SET(SketcherGui_SRCS
DrawSketchHandlerArcOfHyperbola.h
DrawSketchHandlerArcOfParabola.h
DrawSketchHandlerArcSlot.h
DrawSketchHandlerText.h
DrawSketchHandlerBSpline.h
DrawSketchHandlerPoint.h
DrawSketchHandlerFillet.h
@@ -136,6 +138,9 @@ SET(SketcherGui_SRCS
Workbench.h
EditDatumDialog.cpp
EditDatumDialog.h
EditTextDialog.ui
EditTextDialog.cpp
EditTextDialog.h
PropertyVisualLayerList.cpp
PropertyVisualLayerList.h
SketchOrientationDialog.cpp
@@ -87,6 +87,7 @@ CmdSketcherToggleConstruction::CmdSketcherToggleConstruction()
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CreateSlot");
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CompSlot");
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CreateArc");
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CreateText");
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_Create3PointArc");
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CreateEllipseByCenter");
rcCmdMgr.addCommandMode("ToggleConstruction", "Sketcher_CreateEllipseBy3Points");
+246
View File
@@ -25,7 +25,12 @@
#include <limits>
#include <Precision.hxx>
#include <Bnd_Box.hxx>
#include <QPainter>
#include <algorithm>
#include <sstream>
#include <BRepBndLib.hxx>
#include <boost/range/adaptor/reversed.hpp>
@@ -10440,6 +10445,246 @@ bool CmdSketcherConstrainSnellsLaw::isActive()
return isCreateConstraintActive(getActiveGuiDocument());
}
// ======================================================================================
DEF_STD_CMD_A(CmdSketcherConstrainGroup)
CmdSketcherConstrainGroup::CmdSketcherConstrainGroup()
: Command("Sketcher_ConstrainGroup")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Group Constrain");
sToolTipText = QT_TR_NOOP("Constrains the selected geometries together as a single entity."
"The position and size of the grouped geometries can be defined by constraining the construction line that is generated."
"Constraints applied to grouped edges are ignored as long as the Group constraint is here.");
sWhatsThis = "Sketcher_ConstrainGroup";
sStatusTip = sToolTipText;
sPixmap = "Constraint_Group";
sAccel = "K, G";
eType = ForEdit;
}
void CmdSketcherConstrainGroup::activated(int iMsg)
{
Q_UNUSED(iMsg);
// get the selection
std::vector<Gui::SelectionObject> selection = getSelection().getSelectionEx();
// only one sketch with its subelements are allowed to be selected
if (selection.size() != 1
|| !selection[0].isObjectTypeOf(Sketcher::SketchObject::getClassTypeId())) {
const char dmbg[] = "Constraint_Group";
QString strError = QObject::tr("Selected objects are not just geometry "
"from one sketch.",
dmbg);
Gui::TranslatedUserWarning(getActiveGuiDocument()->getDocument(),
QObject::tr("Wrong selection"),
std::move(strError));
}
// get the needed lists and objects
auto* Obj = static_cast<Sketcher::SketchObject*>(selection[0].getObject());
const std::vector<std::string>& SubNames = selection[0].getSubNames();
if (SubNames.empty()) {
Gui::TranslatedUserWarning(Obj,
QObject::tr("Wrong selection"),
QObject::tr("No geometries selected"));
return;
}
std::vector<Sketcher::GeoElementId> elts;
for (auto& subName : SubNames) {
int geoId;
Sketcher::PointPos posId;
getIdsFromName(subName, Obj, geoId, posId);
bool alreadyAdded = std::any_of(elts.begin(), elts.end(),
[geoId](const Sketcher::GeoElementId& elem) {
return elem.GeoId == geoId;
});
if (geoId < 0 || alreadyAdded || Obj->getGeometryFacade(geoId)->isInternalAligned()) {
continue;
}
elts.push_back(Sketcher::GeoElementId(geoId, Sketcher::PointPos::none));
}
if (elts.size() < 2) {
Base::Console().warning("Cannot create group : minimum 2 geometries must be selected.\n");
return;
}
openCommand(QT_TRANSLATE_NOOP("Command", "Add Group constraint"));
if (!addListConstraint(Obj, elts, "Group")) {
abortCommand();
return;
}
tryAutoRecompute(Obj);
commitCommand();
getSelection().clearSelection();
}
/**
* @brief Escapes a string for safe embedding within a single-quoted Python string literal.
*
* This function handles backslashes and single quotes.
*
* @param input The raw string to escape.
* @return A new string with special characters escaped.
*/
std::string SketcherGui::escapeForPython(const std::string& input)
{
std::string result;
// Pre-allocating can be a small optimization if strings are long
result.reserve(input.length());
for (char c : input) {
if (c == '\\') {
result += "\\\\";
} else if (c == '\'') {
result += "\\'";
} else {
result += c;
}
}
return result;
}
bool SketcherGui::addListConstraint(Sketcher::SketchObject* Obj,
std::vector<Sketcher::GeoElementId>& elts,
const std::string& constraintType,
Base::Vector2d frame_p1,
Base::Vector2d frame_p2,
bool isTextHeight,
const std::string& text,
const std::string& font)
{
std::vector<int> geoIdsWithInternalGeos;
// The lambda defines the condition for REMOVAL.
// It returns 'true' if an element should be erased.
auto new_end = std::remove_if(elts.begin(), elts.end(),
[&](const Sketcher::GeoElementId& element) -> bool {
int geoId = element.GeoId;
// Condition 1: Check for invalid or already-aligned geometries.
// If true, this element should be removed.
if (geoId < 0 || Obj->getGeometryFacade(geoId)->isInternalAligned()) {
return true; // Mark for removal
}
// Condition 2: Check for internal geometries that need cleanup later.
// This does not mark the element for removal, but collects its ID.
const Part::Geometry* geo = Obj->getGeometry(geoId);
if (Obj->hasInternalGeometry(geo)) {
// Collect the ID for later processing.
geoIdsWithInternalGeos.push_back(geoId);
}
// If we reached here, the element is valid and should be kept.
return false; // Do not remove
});
// Actually erase the elements that were moved to the end.
elts.erase(new_end, elts.end());
if (elts.size() < 2) {
Base::Console().warning("Cannot create %s constraint: minimum 2 geometries.\n", constraintType.c_str());
return false;
}
if ((frame_p1 - frame_p2).Length() < Precision::Confusion()) {
// --- 1. Calculate Bounding Box ---
Bnd_Box totalBBox;
for (const auto& element : elts) {
const Part::Geometry* geo = Obj->getGeometry(element.GeoId);
if (geo) {
BRepBndLib::Add(geo->toShape(), totalBBox, false);
}
}
if (!totalBBox.HasFinitePart()) {
Base::Console().warning("Cannot create %s constraint: bounding box is infinite\n", constraintType.c_str());
return false;
}
gp_Pnt min_pnt = totalBBox.CornerMin();
gp_Pnt max_pnt = totalBBox.CornerMax();
// --- 2. Define and create the Construction Line "Frame" ---
frame_p1 = Base::Vector2d(min_pnt.X(), min_pnt.Y());
frame_p2 = Base::Vector2d(min_pnt.X(), max_pnt.Y());
}
Gui::cmdAppObjectArgs(Obj,
"addGeometry(Part.LineSegment(App.Vector(%f,%f,0), App.Vector(%f,%f,0)), True)",
frame_p1.x, frame_p1.y, frame_p2.x, frame_p2.y);
int frameGeoId = Obj->getHighestCurveIndex();
// --- 3. Prepend the Frame to the Element List ---
elts.insert(elts.begin(), Sketcher::GeoElementId(frameGeoId, Sketcher::PointPos::none));
// --- 4. Create the Python list of elements as a string ---
std::stringstream elements_list_ss;
elements_list_ss << "[";
if (!elts.empty()) {
for (size_t i = 0; i < elts.size() - 1; ++i) {
elements_list_ss << elts[i].GeoId << ", " << elts[i].posIdAsInt() << ", ";
}
elements_list_ss << elts.back().GeoId << ", " << elts.back().posIdAsInt();
}
elements_list_ss << "]";
std::string elements_list_string = elements_list_ss.str();
// --- 5. Add the appropriate constraint via Python command ---
if (constraintType == "Group") {
Gui::cmdAppObjectArgs(
Obj,
"addConstraint(Sketcher.Constraint('Group', %s))",
elements_list_string.c_str());
}
else if (constraintType == "Text") {
std::string escaped_text = escapeForPython(text);
std::string escaped_font = escapeForPython(font);
Gui::cmdAppObjectArgs(
Obj,
"addConstraint(Sketcher.Constraint('Text', %s, '%s', '%s', %s))",
elements_list_string.c_str(),
escaped_text.c_str(),
escaped_font.c_str(),
isTextHeight ? "True" : "False");
}
else {
Base::Console().error("Unsupported list constraint type: %s\n", constraintType.c_str());
return false;
}
// We remove the internal alignment of the geometries that were grouped.
std::sort(geoIdsWithInternalGeos.begin(), geoIdsWithInternalGeos.end(), std::greater<>());
for (auto& geoId : geoIdsWithInternalGeos) {
Obj->deleteUnusedInternalGeometry(geoId);
}
return true;
}
bool CmdSketcherConstrainGroup::isActive()
{
return isCreateConstraintActive(getActiveGuiDocument());
}
// ======================================================================================
DEF_STD_CMD_A(CmdSketcherChangeDimensionConstraint)
@@ -10765,6 +11010,7 @@ void CreateSketcherCommandsConstraints()
rcCmdMgr.addCommand(new CmdSketcherConstrainPointOnObject());
rcCmdMgr.addCommand(new CmdSketcherConstrainSymmetric());
rcCmdMgr.addCommand(new CmdSketcherConstrainSnellsLaw());
rcCmdMgr.addCommand(new CmdSketcherConstrainGroup());
rcCmdMgr.addCommand(new CmdSketcherChangeDimensionConstraint());
rcCmdMgr.addCommand(new CmdSketcherToggleDrivingConstraint());
rcCmdMgr.addCommand(new CmdSketcherToggleActiveConstraint());
+11
View File
@@ -137,4 +137,15 @@ void doEndpointToEdgeTangency(
/// notifications
void notifyConstraintSubstitutions(const QString& message);
std::string escapeForPython(const std::string& input);
bool addListConstraint(
Sketcher::SketchObject* Obj,
std::vector<Sketcher::GeoElementId>& elts,
const std::string& constraintType,
Base::Vector2d frame_p1 = Base::Vector2d(),
Base::Vector2d frame_p2 = Base::Vector2d(),
bool isTextHeight = true,
const std::string& text = "",
const std::string& font = ""
);
} // namespace SketcherGui
+39
View File
@@ -70,6 +70,7 @@
#include "DrawSketchHandlerRectangle.h"
#include "DrawSketchHandlerSlot.h"
#include "DrawSketchHandlerSplitting.h"
#include "DrawSketchHandlerText.h"
#include "DrawSketchHandlerTrimming.h"
@@ -1356,6 +1357,43 @@ public:
}
};
// Text ================================================================
DEF_STD_CMD_AU(CmdSketcherCreateText)
CmdSketcherCreateText::CmdSketcherCreateText()
: Command("Sketcher_CreateText")
{
sAppModule = "Sketcher";
sGroup = "Sketcher";
sMenuText = QT_TR_NOOP("Text");
sToolTipText = QT_TR_NOOP(
"Creates text geometries controlled by a Text constraint.\n"
"To Edit: Double-click the Text constraint to change the text content and font.\n"
"To Position/Size: Apply constraints to the group's construction line.\n"
"Note: While the Text constraint is active, any constraints applied directly to the text "
"geometries will be ignored.\n"
);
sWhatsThis = "Sketcher_CreateText";
sStatusTip = sToolTipText;
sPixmap = "Sketcher_CreateText";
sAccel = "G, T";
eType = ForEdit;
}
CONSTRUCTION_UPDATE_ACTION(CmdSketcherCreateText, "Sketcher_CreateText")
void CmdSketcherCreateText::activated(int iMsg)
{
Q_UNUSED(iMsg);
ActivateHandler(getActiveGuiDocument(), std::make_unique<DrawSketchHandlerText>());
}
bool CmdSketcherCreateText::isActive()
{
return isCommandActive(getActiveGuiDocument());
}
// B-spline ================================================================
DEF_STD_CMD_AU(CmdSketcherCreateBSpline)
@@ -1936,6 +1974,7 @@ void CreateSketcherCommandsCreateGeo()
rcCmdMgr.addCommand(new CmdSketcherCreateRegularPolygon());
rcCmdMgr.addCommand(new CmdSketcherCreateSlot());
rcCmdMgr.addCommand(new CmdSketcherCreateArcSlot());
rcCmdMgr.addCommand(new CmdSketcherCreateText());
rcCmdMgr.addCommand(new CmdSketcherCreateFillet());
rcCmdMgr.addCommand(new CmdSketcherCreateChamfer());
// rcCmdMgr.addCommand(new CmdSketcherCreateText());
+22 -14
View File
@@ -54,18 +54,20 @@ enum class FilterValue
Equality = 9,
Symmetric = 10,
Block = 11,
InternalAlignment = 12,
Datums = 13,
HorizontalDistance = 14,
VerticalDistance = 15,
Distance = 16,
Radius = 17,
Weight = 18,
Diameter = 19,
Angle = 20,
SnellsLaw = 21,
Named = 22,
NonDriving = 23,
Group = 12,
Text = 13,
InternalAlignment = 14,
Datums = 15,
HorizontalDistance = 16,
VerticalDistance = 17,
Distance = 18,
Radius = 19,
Weight = 20,
Diameter = 21,
Angle = 22,
SnellsLaw = 23,
Named = 24,
NonDriving = 25,
NumFilterValue // SpecialFilterValue shall start at the same index as this
};
@@ -75,8 +77,8 @@ constexpr auto FilterValueLength = static_cast<std::underlying_type_t<FilterValu
enum class SpecialFilterValue
{
Selection = FilterValueLength, // = 24
AssociatedConstraints, // = 25
Selection = FilterValueLength, // = 26
AssociatedConstraints, // = 27
NumSpecialFilterValue
};
@@ -127,6 +129,8 @@ constexpr std::array<FilterValueBitset, FilterValueLength> filterAggregates {
FilterValue::Equality,
FilterValue::Symmetric,
FilterValue::Block,
FilterValue::Group,
FilterValue::Text,
FilterValue::Datums,
FilterValue::Distance,
FilterValue::HorizontalDistance,
@@ -152,6 +156,8 @@ constexpr std::array<FilterValueBitset, FilterValueLength> filterAggregates {
FilterValue::Equality,
FilterValue::Symmetric,
FilterValue::Block,
FilterValue::Group,
FilterValue::Text,
FilterValue::InternalAlignment
), // Geometric = All others not being datums (1)
@@ -165,6 +171,8 @@ constexpr std::array<FilterValueBitset, FilterValueLength> filterAggregates {
buildBitset(FilterValue::Equality), // Equality = Just this (9)
buildBitset(FilterValue::Symmetric), // Symmetric = Just this (10)
buildBitset(FilterValue::Block), // Block = Just this (11)
buildBitset(FilterValue::Group), // Group = Just this (11)
buildBitset(FilterValue::Text), // Text = Just this (11)
buildBitset(FilterValue::InternalAlignment), // InternalAlignment = Just this (12)
buildBitset(
@@ -52,6 +52,12 @@ class WidgetComboboxes: public ControlAmount<sizes...>
{
};
/** @brief Type encapsulating the number of line edits in the widget*/
template<int... sizes> // Initial sizes for each mode
class WidgetLineEdits: public ControlAmount<sizes...>
{
};
namespace sp = std::placeholders;
/** @brief Class defining a handler controller making use of parameters provided by a widget of type
@@ -74,6 +80,7 @@ template<
typename WidgetParametersT, // The number of parameter spinboxes in the default widget
typename WidgetCheckboxesT, // The number of checkboxes in the default widget
typename WidgetComboboxesT, // The number of comboboxes in the default widget
typename WidgetLineEditsT, // The number of line edits in the default widget
typename ConstructionMethodT = ConstructionMethods::DefaultConstructionMethod,
bool PFirstComboboxIsConstructionMethod = false> // The handler template or class having this
// as inner class
@@ -91,6 +98,7 @@ private:
int nParameter = WidgetParametersT::defaultMethodSize();
int nCheckbox = WidgetCheckboxesT::defaultMethodSize();
int nCombobox = WidgetComboboxesT::defaultMethodSize();
int nLineEdit = WidgetLineEditsT::defaultMethodSize();
SketcherToolDefaultWidget* toolWidget;
@@ -100,12 +108,14 @@ private:
Connection connectionParameterValueChanged;
Connection connectionCheckboxCheckedChanged;
Connection connectionComboboxSelectionChanged;
Connection connectionLineEditTextChanged;
/** @name Named indices for controls of the default widget (SketcherToolDefaultWidget) */
//@{
using WParameter = SketcherToolDefaultWidget::Parameter;
using WCheckbox = SketcherToolDefaultWidget::Checkbox;
using WCombobox = SketcherToolDefaultWidget::Combobox;
using WLineEdit = SketcherToolDefaultWidget::LineEdit;
//@}
using SelectMode = SelectModeT;
@@ -128,6 +138,7 @@ public:
connectionParameterValueChanged.disconnect();
connectionCheckboxCheckedChanged.disconnect();
connectionComboboxSelectionChanged.disconnect();
connectionLineEditTextChanged.disconnect();
}
/** @name functions NOT intended for specialisation offering specialisation interface for
@@ -179,6 +190,24 @@ public:
}
//@}
/** boost slot triggering when a line edit has changed in the widget
* It is intended to remote control the DrawSketchDefaultWidgetHandler
*/
void lineEditTextChanged(int lineeditindex, const QString& value)
{
adaptDrawingToLineEditTextChange(lineeditindex, value); // specialisation interface
// Temporarily disable auto-passing focus to OVP.
// This prevents the focus from being stolen from the LineEdit when the text changes.
ControllerBase::focusAutoPassing = false;
ControllerBase::finishControlsChanged();
// Restore the default behavior.
ControllerBase::focusAutoPassing = true;
}
//@}
/** @name Specialisation Interface */
/** These functions offer a specialisation interface. Non-virtual functions are specific to
* this controller. Virtual functions may depend on input from a derived controller, and thus
@@ -202,8 +231,6 @@ public:
/// Change DSH to reflect a comboBox changed in the widget
void adaptDrawingToComboboxChange(int comboboxindex, [[maybe_unused]] int value)
{
Q_UNUSED(comboboxindex);
if constexpr (PFirstComboboxIsConstructionMethod == true) {
if (comboboxindex == WCombobox::FirstCombo && handler->ConstructionMethodsCount() > 1) {
@@ -212,6 +239,13 @@ public:
}
}
/// Change DSH to reflect a line edit changed in the widget
void adaptDrawingToLineEditTextChange(int lineeditindex, const QString& value)
{
Q_UNUSED(lineeditindex);
Q_UNUSED(value);
}
/// function to create constraints based on widget information.
void addConstraints() override
{}
@@ -303,14 +337,19 @@ protected:
void setFocusToParameter(unsigned int parameterindex)
{
// To be able to cycle through OVP and widget, we use a parameter index that goes from
// 0 to (onViewParameters.size() + nParameter)
// 0 to (onViewParameters.size() + nParameter + nLineEdit)
if (!ControllerBase::setFocusToOnViewParameter(parameterindex)) {
parameterindex = parameterindex - ControllerBase::onViewParameters.size();
unsigned int widgetIndex = parameterindex - ControllerBase::onViewParameters.size();
if (parameterindex < static_cast<unsigned int>(nParameter)) {
toolWidget->setParameterFocus(parameterindex);
ControllerBase::parameterWithFocus = ControllerBase::onViewParameters.size()
+ parameterindex;
if (widgetIndex < static_cast<unsigned int>(nParameter)) {
toolWidget->setParameterFocus(widgetIndex);
ControllerBase::parameterWithFocus = parameterindex;
}
// Check if the index corresponds to a LineEdit
else if (widgetIndex < static_cast<unsigned int>(nParameter + nLineEdit)) {
unsigned int lineEditIndex = widgetIndex - nParameter;
toolWidget->setLineEditFocus(lineEditIndex);
ControllerBase::parameterWithFocus = parameterindex;
}
}
}
@@ -320,7 +359,8 @@ protected:
{
unsigned int index = ControllerBase::parameterWithFocus + 1;
if (index >= ControllerBase::onViewParameters.size() + nParameter) {
// The total number of focusable items now includes LineEdits.
if (index >= ControllerBase::onViewParameters.size() + nParameter + nLineEdit) {
index = 0;
}
@@ -333,9 +373,21 @@ protected:
}
idx++;
}
// Check SpinBoxes
if (idx < ControllerBase::onViewParameters.size() + nParameter) {
setFocusToParameter(idx);
return true;
if (nParameter > 0) {
setFocusToParameter(idx);
return true;
}
// If no spinboxes, update index to check line edits
idx = ControllerBase::onViewParameters.size() + nParameter;
}
// Check LineEdits
if (idx < ControllerBase::onViewParameters.size() + nParameter + nLineEdit) {
if (nLineEdit > 0) {
setFocusToParameter(idx);
return true;
}
}
return false;
};
@@ -372,6 +424,10 @@ private:
connectionComboboxSelectionChanged = toolWidget->registerComboboxSelectionChanged(
std::bind(&DrawSketchDefaultWidgetController::comboboxSelectionChanged, this, sp::_1, sp::_2)
);
connectionLineEditTextChanged = toolWidget->registerLineEditTextChanged(
std::bind(&DrawSketchDefaultWidgetController::lineEditTextChanged, this, sp::_1, sp::_2)
);
}
/// Resets the widget
@@ -383,14 +439,17 @@ private:
fastsignals::shared_connection_block parameter_block(connectionParameterValueChanged);
fastsignals::shared_connection_block checkbox_block(connectionCheckboxCheckedChanged);
fastsignals::shared_connection_block combobox_block(connectionComboboxSelectionChanged);
fastsignals::shared_connection_block lineedit_block(connectionLineEditTextChanged);
nParameter = WidgetParametersT::size(handler->constructionMethod());
nCheckbox = WidgetCheckboxesT::size(handler->constructionMethod());
nCombobox = WidgetComboboxesT::size(handler->constructionMethod());
nLineEdit = WidgetLineEditsT::size(handler->constructionMethod());
toolWidget->initNParameters(nParameter, ControllerBase::getKeyManager());
toolWidget->initNCheckboxes(nCheckbox);
toolWidget->initNComboboxes(nCombobox);
toolWidget->initNLineEdits(nLineEdit);
configureToolWidget();
+2 -1
View File
@@ -62,7 +62,8 @@ using DSHArcController = DrawSketchDefaultWidgetController<
/*OnViewParametersT =*/OnViewParameters<5, 6>, // NOLINT
/*WidgetParametersT =*/WidgetParameters<0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0, 0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT,
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::CircleEllipseConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -68,6 +68,7 @@ using DSHArcSlotController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0, 0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::ArcSlotConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -65,6 +65,7 @@ using DSHBSplineController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<1, 1>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<1, 1>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::BSplineConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -57,6 +57,7 @@ using DSHCircleController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0, 0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::CircleEllipseConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -61,6 +61,7 @@ using DSHEllipseController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0, 0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::CircleEllipseConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -112,6 +112,7 @@ using DSHFilletController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<1, 1>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::FilletConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -70,6 +70,7 @@ using DSHLineController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0, 0, 0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0, 0>,
ConstructionMethods::LineConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -877,4 +877,5 @@ protected:
dirVec.Normalize();
}
};
} // namespace SketcherGui
@@ -105,6 +105,7 @@ using DSHOffsetController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<2, 2>,
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1>,
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0>,
ConstructionMethods::OffsetConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -57,7 +57,8 @@ using DSHPolygonController = DrawSketchDefaultWidgetController<
/*OnViewParametersT =*/OnViewParameters<4>,
/*WidgetParametersT =*/WidgetParameters<1>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<0>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
/*WidgetComboboxesT =*/WidgetComboboxes<0>,
/*WidgetLineEditsT =*/WidgetLineEdits<0>>;
using DSHPolygonControllerBase = DSHPolygonController::ControllerBase;
@@ -69,6 +69,7 @@ using DSHRectangleController = DrawSketchDefaultWidgetController<
/*WidgetParametersT =*/WidgetParameters<0, 0, 0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<2, 2, 2, 2>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<1, 1, 1, 1>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0, 0, 0, 0>, // NOLINT
ConstructionMethods::RectangleConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
@@ -53,7 +53,8 @@ using DSHRotateController = DrawSketchDefaultWidgetController<
/*OnViewParametersT =*/OnViewParameters<4>,
/*WidgetParametersT =*/WidgetParameters<1>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<1>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
/*WidgetComboboxesT =*/WidgetComboboxes<0>,
/*WidgetLineEditsT =*/WidgetLineEdits<0>>;
using DSHRotateControllerBase = DSHRotateController::ControllerBase;
@@ -60,7 +60,8 @@ using DSHScaleController = DrawSketchDefaultWidgetController<
/*OnViewParametersT =*/OnViewParameters<3>,
/*WidgetParametersT =*/WidgetParameters<0>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<1>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
/*WidgetComboboxesT =*/WidgetComboboxes<0>,
/*WidgetLineEditsT =*/WidgetLineEdits<0>>;
using DSHScaleControllerBase = DSHScaleController::ControllerBase;
+5 -4
View File
@@ -55,10 +55,11 @@ using DSHSlotController = DrawSketchDefaultWidgetController<
DrawSketchHandlerSlot,
StateMachines::ThreeSeekEnd,
/*PAutoConstraintSize =*/2,
/*OnViewParametersT =*/OnViewParameters<5>, // NOLINT
/*WidgetParametersT =*/WidgetParameters<0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<0>>; // NOLINT
/*OnViewParametersT =*/OnViewParameters<5>, // NOLINT
/*WidgetParametersT =*/WidgetParameters<0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<0>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0>>; // NOLINT
using DSHSlotControllerBase = DSHSlotController::ControllerBase;
@@ -54,10 +54,11 @@ using DSHSymmetryController = DrawSketchDefaultWidgetController<
DrawSketchHandlerSymmetry,
StateMachines::OneSeekEnd,
/*PAutoConstraintSize =*/0,
/*OnViewParametersT =*/OnViewParameters<0>,
/*WidgetParametersT =*/WidgetParameters<0>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<2>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
/*OnViewParametersT =*/OnViewParameters<0>, // NOLINT
/*WidgetParametersT =*/WidgetParameters<0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<2>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<0>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0>>; // NOLINT
using DSHSymmetryControllerBase = DSHSymmetryController::ControllerBase;
@@ -0,0 +1,771 @@
// SPDX - License - Identifier: LGPL - 2.1 - or -later
/****************************************************************************
* *
* Copyright (c) 2025 Pierre-Louis Boyer *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef SKETCHERGUI_DrawSketchHandlerText_H
#define SKETCHERGUI_DrawSketchHandlerText_H
#include <QMap>
#include <Gui/BitmapFactory.h>
#include <Gui/Notifications.h>
#include <Gui/Command.h>
#include <Gui/CommandT.h>
#include <Gui/InputHint.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "DrawSketchDefaultWidgetController.h"
#include "DrawSketchControllableHandler.h"
#include "GeometryCreationMode.h"
#include "Utils.h"
#include "CommandConstraints.h"
#include <vector>
#include <algorithm>
namespace SketcherGui
{
class DrawSketchHandlerText;
namespace ConstructionMethods
{
enum class TextConstructionMethod
{
Width,
Height,
End // Must be the last one
};
} // namespace ConstructionMethods
using DSHTextController = DrawSketchDefaultWidgetController<
DrawSketchHandlerText,
/*SelectModeT*/ StateMachines::TwoSeekEnd,
/*PAutoConstraintSize =*/2,
/*OnViewParametersT =*/OnViewParameters<4, 4>, // NOLINT
/*WidgetParametersT =*/WidgetParameters<0, 0>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<0, 0>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<2, 2>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<1, 1>, // NOLINT
ConstructionMethods::TextConstructionMethod,
/*bool PFirstComboboxIsConstructionMethod =*/true>;
using DSHTextControllerBase = DSHTextController::ControllerBase;
using DrawSketchHandlerTextBase = DrawSketchControllableHandler<DSHTextController>;
class DrawSketchHandlerText: public DrawSketchHandlerTextBase
{
friend DSHTextController;
friend DSHTextControllerBase;
public:
explicit DrawSketchHandlerText(ConstructionMethod constrMethod = ConstructionMethod::Width)
: DrawSketchHandlerTextBase(constrMethod)
, length(0.0)
, handleId(0)
, text(QObject::tr("Text").toStdString())
, font("")
, cachedTextName("")
, cachedFontName("")
, cachedBaseShapes({}) {};
~DrawSketchHandlerText() override = default;
private:
void updateDataAndDrawToPosition(Base::Vector2d onSketchPos) override
{
switch (state()) {
case SelectMode::SeekFirst: {
toolWidgetManager.drawPositionAtCursor(onSketchPos);
startPoint = onSketchPos;
seekAndRenderAutoConstraint(sugConstraints[0], onSketchPos, Base::Vector2d(0.f, 0.f));
} break;
case SelectMode::SeekSecond: {
toolWidgetManager.drawDirectionAtCursor(onSketchPos, startPoint);
endPoint = onSketchPos;
try {
CreateAndDrawShapeGeometry();
}
catch (const Base::ValueError&) {
} // equal points while hovering raise an objection that can be safely ignored
seekAndRenderAutoConstraint(sugConstraints[1], onSketchPos, onSketchPos - startPoint);
} break;
default:
break;
}
}
void executeCommands() override
{
try {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Add sketch Text"));
// Add the Handle Line
Gui::cmdAppObjectArgs(
getSketchObject(),
"addGeometry(Part.LineSegment(App.Vector(%f, %f,0), App.Vector(%f, %f,0)), True)",
startPoint.x,
startPoint.y,
endPoint.x,
endPoint.y
);
handleId = getHighestCurveIndex();
std::string escText = escapeForPython(text);
std::string escFont = escapeForPython(font);
bool isHeight = constructionMethod() == ConstructionMethod::Height;
const char* constrBoolStr = isConstructionMode() ? "True" : "False";
const char* heightBoolStr = isHeight ? "True" : "False";
// Add the 'Text' Constraint (Empty)
// We initialize the constraint containing ONLY the handle (element 0).
// We do not add the text geometry manually to avoid floating-point precision loss
// associated with Python serialization.
Gui::cmdAppObjectArgs(
getSketchObject(),
"addConstraint(Sketcher.Constraint('Text', [%d, 0], '%s', '%s', %s))",
handleId,
escText.c_str(),
escFont.c_str(),
heightBoolStr
);
// Generate Text Geometry by calling setTextAndFont on the new constraint.
// This triggers the C++ logic to generate the exact geometry and insert it
// into the sketch, ensuring closed wires and perfect precision.
Gui::cmdAppObjectArgs(
getSketchObject(),
"setTextAndFont(len(App.ActiveDocument.getObject('%s').Constraints)-1, '%s', '%s', "
"%s, %s)",
getSketchObject()->getNameInDocument(),
escText.c_str(),
escFont.c_str(),
heightBoolStr,
constrBoolStr
);
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Gui::NotifyError(
sketchgui,
QT_TRANSLATE_NOOP("Notifications", "Error"),
QT_TRANSLATE_NOOP("Notifications", "Failed to add text")
);
Gui::Command::abortCommand();
}
}
void generateAutoConstraints() override
{
// Generate temporary autoconstraints (but do not actually add them to the sketch)
if (avoidRedundants) {
removeRedundantHorizontalVertical(getSketchObject(), sugConstraints[0], sugConstraints[1]);
}
auto& ac1 = sugConstraints[0];
auto& ac2 = sugConstraints[1];
generateAutoConstraintsOnElement(ac1, handleId, Sketcher::PointPos::start);
generateAutoConstraintsOnElement(ac2, handleId, Sketcher::PointPos::end);
// Ensure temporary autoconstraints do not generate a redundancy and that the geometry
// parameters are accurate This is particularly important for adding widget mandated
// constraints.
removeRedundantAutoConstraints();
}
void createAutoConstraints() override
{
// execute python command to create autoconstraints
createGeneratedAutoConstraints(true);
sugConstraints[0].clear();
sugConstraints[1].clear();
}
std::string getToolName() const override
{
return "DSH_Text";
}
QString getCrosshairCursorSVGName() const override
{
return QStringLiteral("Sketcher_Pointer_Text.svg");
}
std::unique_ptr<QWidget> createWidget() const override
{
return std::make_unique<SketcherToolDefaultWidget>();
}
bool isWidgetVisible() const override
{
return true; // Text tool must show the line edit to make sense
};
QPixmap getToolIcon() const override
{
return Gui::BitmapFactory().pixmap("Sketcher_CreateText");
}
QString getToolWidgetText() const override
{
return QString(QObject::tr("Text parameters"));
}
bool canGoToNextMode() override
{
if (state() == SelectMode::SeekSecond && length < Precision::Confusion()) {
// Prevent validation of null Text.
return false;
}
return true;
}
void angleSnappingControl() override
{
if (state() == SelectMode::SeekSecond) {
setAngleSnapping(true, startPoint);
}
else {
setAngleSnapping(false);
}
}
private:
QMap<QString, QString> fontPathMap;
Base::Vector2d startPoint, endPoint;
double length;
int handleId;
std::string text;
std::string font;
std::string cachedTextName;
std::string cachedFontName;
std::vector<TopoDS_Shape> cachedBaseShapes;
void createShape(bool onlyeditoutline) override
{
ShapeGeometry.clear();
Base::Vector2d vecL = endPoint - startPoint;
length = vecL.Length();
if (length < Precision::Confusion()) {
return;
}
// 1. Check if the cache is valid. If the user selected a new file,
// or if the cache is empty, we need to re-load from the SVG.
if (cachedTextName != text || cachedFontName != font || cachedBaseShapes.empty()) {
if (!font.empty()) {
cachedTextName = text;
cachedFontName = font;
// This is the one-time slow operation to get the template shapes.
cachedBaseShapes = Part::makeTextWires(text, font);
}
else {
cachedBaseShapes.clear();
}
}
// 2. Call the generic helper to transform and create the final geometry.
transformAndConvertToGeometry(
ShapeGeometry,
cachedBaseShapes,
toVector3d(startPoint),
toVector3d(endPoint),
constructionMethod() == ConstructionMethod::Height
);
// 3. Set construction mode on the newly created geometry
if (isConstructionMode() && !onlyeditoutline) {
for (auto& geo : ShapeGeometry) {
Sketcher::GeometryFacade::setConstruction(geo.get(), true);
}
}
}
std::list<Gui::InputHint> getToolHints() const override
{
return lookupTextHints(static_cast<int>(constructionMethod()), static_cast<int>(state()));
}
struct HintEntry
{
int constructionMethod;
int state;
std::list<Gui::InputHint> hints;
};
using HintTable = std::vector<HintEntry>;
static Gui::InputHint switchModeHint();
static HintTable getTextHintTable();
static std::list<Gui::InputHint> lookupTextHints(int method, int state);
};
template<>
auto DSHTextControllerBase::getState(int labelindex) const
{
switch (labelindex) {
case OnViewParameter::First:
case OnViewParameter::Second:
return SelectMode::SeekFirst;
break;
case OnViewParameter::Third:
case OnViewParameter::Fourth:
return SelectMode::SeekSecond;
break;
default:
THROWM(Base::ValueError, "Label index without an associated machine state")
}
}
template<>
void DSHTextController::configureToolWidget()
{
if (!init) { // Code to be executed only upon initialisation
QStringList names = {
QApplication::translate("TaskSketcherTool_c1_text", "Width"),
QApplication::translate("TaskSketcherTool_c1_text", "Height")
};
toolWidget->setComboboxElements(WCombobox::FirstCombo, names);
toolWidget->setLineEditLabel(
WLineEdit::FirstEdit,
QApplication::translate("TaskSketcherTool_Text", "Text")
);
toolWidget->setLineEditText(WLineEdit::FirstEdit, QString::fromStdString(handler->text));
toolWidget->setComboboxLabel(
WCombobox::SecondCombo,
QApplication::translate("TaskSketcherTool_Text", "Font")
);
// 1. Scan for font files and store the map
handler->fontPathMap = findAvailableFontFiles();
// 2. Populate combobox with friendly names (the keys of the map)
QStringList fontNames = handler->fontPathMap.keys();
fontNames.sort(Qt::CaseInsensitive);
toolWidget->setComboboxElements(WCombobox::SecondCombo, fontNames);
// 3. Set a sensible default font
QString defaultFontName;
if (fontNames.contains(QString::fromUtf8("osifont-lgpl3fe"), Qt::CaseInsensitive)) {
defaultFontName = QString::fromUtf8("osifont-lgpl3fe");
}
else if (fontNames.contains(QString::fromUtf8("DejaVu Sans"), Qt::CaseInsensitive)) {
defaultFontName = QString::fromUtf8("DejaVu Sans");
}
else if (fontNames.contains(QString::fromUtf8("Arial"), Qt::CaseInsensitive)) {
defaultFontName = QString::fromUtf8("Arial");
}
else if (!fontNames.isEmpty()) {
defaultFontName = fontNames.first();
}
if (!defaultFontName.isEmpty()) {
// Find the actual case-sensitive key
for (const auto& key : fontNames) {
if (key.compare(defaultFontName, Qt::CaseInsensitive) == 0) {
handler->font = handler->fontPathMap.value(key).toStdString();
toolWidget->setComboboxCurrentText(WCombobox::SecondCombo, key);
break;
}
}
}
onViewParameters[OnViewParameter::First]->setLabelType(Gui::SoDatumLabel::DISTANCEX);
onViewParameters[OnViewParameter::Second]->setLabelType(Gui::SoDatumLabel::DISTANCEY);
onViewParameters[OnViewParameter::Third]->setLabelType(
Gui::SoDatumLabel::DISTANCE,
Gui::EditableDatumLabel::Function::Dimensioning
);
onViewParameters[OnViewParameter::Fourth]->setLabelType(
Gui::SoDatumLabel::ANGLE,
Gui::EditableDatumLabel::Function::Dimensioning
);
}
toolWidget->setLineEditText(
SketcherToolDefaultWidget::LineEdit::FirstEdit,
QString::fromStdString(handler->text)
);
}
template<>
void DSHTextController::adaptDrawingToLineEditTextChange(int lineeditindex, const QString& value)
{
if (lineeditindex == WLineEdit::FirstEdit) {
handler->text = value.toStdString();
// The redraw is handled by the controller's finishControlsChanged()
}
}
template<>
void DSHTextController::adaptDrawingToComboboxChange(int comboboxindex, int value)
{
if (comboboxindex == WCombobox::FirstCombo) {
handler->setConstructionMethod(static_cast<ConstructionMethod>(value));
}
else if (comboboxindex == WCombobox::SecondCombo) {
// Get the selected friendly name
QString fontName = toolWidget->getComboboxCurrentText(WCombobox::SecondCombo);
// Look up the full path in our map and update the handler
if (handler->fontPathMap.contains(fontName)) {
handler->font = handler->fontPathMap.value(fontName).toStdString();
}
// The redraw is handled by the controller's finishControlsChanged()
}
}
template<>
void DSHTextControllerBase::doEnforceControlParameters(Base::Vector2d& onSketchPos)
{
switch (handler->state()) {
case SelectMode::SeekFirst: {
auto& firstParam = onViewParameters[OnViewParameter::First];
auto& secondParam = onViewParameters[OnViewParameter::Second];
if (firstParam->isSet) {
onSketchPos.x = firstParam->getValue();
}
if (secondParam->isSet) {
onSketchPos.y = secondParam->getValue();
}
} break;
case SelectMode::SeekSecond: {
auto& thirdParam = onViewParameters[OnViewParameter::Third];
auto& fourthParam = onViewParameters[OnViewParameter::Fourth];
Base::Vector2d dir = onSketchPos - handler->startPoint;
if (dir.Length() < Precision::Confusion()) {
dir.x = 1.0; // if direction null, default to (1,0)
}
double length = dir.Length();
if (thirdParam->isSet) {
length = thirdParam->getValue();
if (length < Precision::Confusion()) {
unsetOnViewParameter(thirdParam.get());
return;
}
onSketchPos = handler->startPoint + length * dir.Normalize();
}
if (fourthParam->isSet) {
double angle = Base::toRadians(fourthParam->getValue());
if (handler->constructionMethod() == ConstructionMethod::Height) {
angle += M_PI * 0.5;
}
Base::Vector2d dir(cos(angle), sin(angle));
onSketchPos.ProjectToLine(onSketchPos - handler->startPoint, dir);
onSketchPos += handler->startPoint;
}
if (thirdParam->isSet && fourthParam->isSet
&& (onSketchPos - handler->startPoint).Length() < Precision::Confusion()) {
unsetOnViewParameter(thirdParam.get());
unsetOnViewParameter(fourthParam.get());
}
} break;
default:
break;
}
}
template<>
void DSHTextController::adaptParameters(Base::Vector2d onSketchPos)
{
switch (handler->state()) {
case SelectMode::SeekFirst: {
auto& firstParam = onViewParameters[OnViewParameter::First];
auto& secondParam = onViewParameters[OnViewParameter::Second];
if (!firstParam->isSet) {
setOnViewParameterValue(OnViewParameter::First, onSketchPos.x);
}
if (!secondParam->isSet) {
setOnViewParameterValue(OnViewParameter::Second, onSketchPos.y);
}
bool sameSign = onSketchPos.x * onSketchPos.y > 0.;
firstParam->setLabelAutoDistanceReverse(!sameSign);
secondParam->setLabelAutoDistanceReverse(sameSign);
firstParam->setPoints(Base::Vector3d(), toVector3d(onSketchPos));
secondParam->setPoints(Base::Vector3d(), toVector3d(onSketchPos));
} break;
case SelectMode::SeekSecond: {
auto& thirdParam = onViewParameters[OnViewParameter::Third];
auto& fourthParam = onViewParameters[OnViewParameter::Fourth];
Base::Vector3d start = toVector3d(handler->startPoint);
Base::Vector3d end = toVector3d(handler->endPoint);
Base::Vector3d vec = end - start;
if (!thirdParam->isSet) {
setOnViewParameterValue(OnViewParameter::Third, vec.Length());
}
double range;
if (handler->constructionMethod() == ConstructionMethod::Height) {
Base::Vector2d norm(vec.y, -vec.x);
Base::Vector2d textAlignPoint = handler->startPoint + norm;
range = (textAlignPoint - handler->startPoint).Angle();
}
else {
range = (handler->endPoint - handler->startPoint).Angle();
}
if (!fourthParam->isSet) {
setOnViewParameterValue(
OnViewParameter::Fourth,
Base::toDegrees(range),
Base::Unit::Angle
);
}
else if (vec.Length() > Precision::Confusion()) {
double ovpRange = Base::toRadians(fourthParam->getValue());
if (fabs(range - ovpRange) > Precision::Confusion()) {
setOnViewParameterValue(
OnViewParameter::Fourth,
Base::toDegrees(range),
Base::Unit::Angle
);
}
}
thirdParam->setPoints(start, end);
fourthParam->setPoints(start, Base::Vector3d());
fourthParam->setLabelRange(range);
} break;
default:
break;
}
}
template<>
void DSHTextController::computeNextDrawSketchHandlerMode()
{
switch (handler->state()) {
case SelectMode::SeekFirst: {
auto& firstParam = onViewParameters[OnViewParameter::First];
auto& secondParam = onViewParameters[OnViewParameter::Second];
if (firstParam->isSet && secondParam->isSet) {
handler->setNextState(SelectMode::SeekSecond);
}
} break;
case SelectMode::SeekSecond: {
auto& thirdParam = onViewParameters[OnViewParameter::Third];
auto& fourthParam = onViewParameters[OnViewParameter::Fourth];
if (thirdParam->hasFinishedEditing && fourthParam->hasFinishedEditing) {
handler->setNextState(SelectMode::End);
}
} break;
default:
break;
}
}
template<>
void DSHTextController::addConstraints()
{
App::DocumentObject* obj = handler->sketchgui->getObject();
int firstCurve = handler->handleId;
auto x0 = onViewParameters[OnViewParameter::First]->getValue();
auto y0 = onViewParameters[OnViewParameter::Second]->getValue();
auto p3 = onViewParameters[OnViewParameter::Third]->getValue();
auto p4 = onViewParameters[OnViewParameter::Fourth]->getValue();
auto x0set = onViewParameters[OnViewParameter::First]->isSet;
auto y0set = onViewParameters[OnViewParameter::Second]->isSet;
auto p3set = onViewParameters[OnViewParameter::Third]->isSet;
auto p4set = onViewParameters[OnViewParameter::Fourth]->isSet;
using namespace Sketcher;
auto constraintToOrigin = [&]() {
ConstraintToAttachment(GeoElementId(firstCurve, PointPos::start), GeoElementId::RtPnt, x0, obj);
};
auto constraintx0 = [&]() {
ConstraintToAttachment(GeoElementId(firstCurve, PointPos::start), GeoElementId::VAxis, x0, obj);
};
auto constrainty0 = [&]() {
ConstraintToAttachment(GeoElementId(firstCurve, PointPos::start), GeoElementId::HAxis, y0, obj);
};
auto constraintp3length = [&]() {
Gui::cmdAppObjectArgs(
obj,
"addConstraint(Sketcher.Constraint('Distance',%d,%f)) ",
firstCurve,
fabs(p3)
);
};
auto constraintp4angle = [&]() {
double angle = Base::toRadians(p4);
if (handler->constructionMethod() == ConstructionMethod::Height) {
angle += M_PI * 0.5;
}
ConstraintLineByAngle(firstCurve, angle, obj);
};
if (handler->AutoConstraints.empty()) { // No valid diagnosis. Every constraint can be added.
if (x0set && y0set && x0 == 0. && y0 == 0.) {
constraintToOrigin();
}
else {
if (x0set) {
constraintx0();
}
if (y0set) {
constrainty0();
}
}
if (p3set) {
constraintp3length();
}
if (p4set) {
constraintp4angle();
}
}
else { // Valid diagnosis. Must check which constraints may be added.
auto startpointinfo = handler->getPointInfo(GeoElementId(firstCurve, PointPos::start));
if (x0set && startpointinfo.isXDoF()) {
constraintx0();
handler->diagnoseWithAutoConstraints(); // ensure we have recalculated parameters after
// each constraint addition
startpointinfo = handler->getPointInfo(
GeoElementId(firstCurve, PointPos::start)
); // get updated point position
}
if (y0set && startpointinfo.isYDoF()) {
constrainty0();
handler->diagnoseWithAutoConstraints(); // ensure we have recalculated parameters after
// each constraint addition
startpointinfo = handler->getPointInfo(
GeoElementId(firstCurve, PointPos::start)
); // get updated point position
}
auto endpointinfo = handler->getPointInfo(GeoElementId(firstCurve, PointPos::end));
int DoFs = startpointinfo.getDoFs();
DoFs += endpointinfo.getDoFs();
if (p3set && DoFs > 0) {
constraintp3length();
DoFs--;
}
if (p4set && DoFs > 0) {
constraintp4angle();
}
}
}
Gui::InputHint DrawSketchHandlerText::switchModeHint()
{
return {QObject::tr("%1 switch mode"), {Gui::InputHint::UserInput::KeyM}};
}
DrawSketchHandlerText::HintTable DrawSketchHandlerText::getTextHintTable()
{
const auto switchHint = switchModeHint();
return {
// Structure: {constructionMethod, state, {hints...}}
{static_cast<int>(ConstructionMethod::Height),
0,
{{QObject::tr("%1 pick bottom-left point"), {Gui::InputHint::UserInput::MouseLeft}},
switchHint}},
{static_cast<int>(ConstructionMethod::Height),
1,
{{QObject::tr("%1 pick top-left point"), {Gui::InputHint::UserInput::MouseLeft}},
switchHint}},
{static_cast<int>(ConstructionMethod::Width),
0,
{{QObject::tr("%1 pick bottom-left point"), {Gui::InputHint::UserInput::MouseLeft}},
switchHint}},
{static_cast<int>(ConstructionMethod::Width),
1,
{{QObject::tr("%1 pick bottom-right point"), {Gui::InputHint::UserInput::MouseLeft}},
switchHint}}
};
}
std::list<Gui::InputHint> DrawSketchHandlerText::lookupTextHints(int method, int state)
{
const auto TextHintTable = getTextHintTable();
auto it = std::find_if(
TextHintTable.begin(),
TextHintTable.end(),
[method, state](const HintEntry& entry) {
return entry.constructionMethod == method && entry.state == state;
}
);
return (it != TextHintTable.end()) ? it->hints : std::list<Gui::InputHint> {};
}
} // namespace SketcherGui
#endif // SKETCHERGUI_DrawSketchHandlerText_H
@@ -57,10 +57,11 @@ using DSHTranslateController = DrawSketchDefaultWidgetController<
DrawSketchHandlerTranslate,
StateMachines::ThreeSeekEnd,
/*PAutoConstraintSize =*/0,
/*OnViewParametersT =*/OnViewParameters<6>,
/*WidgetParametersT =*/WidgetParameters<2>,
/*WidgetCheckboxesT =*/WidgetCheckboxes<1>,
/*WidgetComboboxesT =*/WidgetComboboxes<0>>;
/*OnViewParametersT =*/OnViewParameters<6>, // NOLINT
/*WidgetParametersT =*/WidgetParameters<2>, // NOLINT
/*WidgetCheckboxesT =*/WidgetCheckboxes<1>, // NOLINT
/*WidgetComboboxesT =*/WidgetComboboxes<0>, // NOLINT
/*WidgetLineEditsT =*/WidgetLineEdits<0>>; // NOLINT
using DSHTranslateControllerBase = DSHTranslateController::ControllerBase;
+8
View File
@@ -451,6 +451,14 @@ bool hasVisualFeature(App::DocumentObject* obj, App::DocumentObject* rootObj, Gu
void EditDatumDialog::performAutoScale(double newDatum)
{
const std::vector<Sketcher::Constraint*>& constraints = sketch->Constraints.getValues();
for (auto* constr : constraints) {
if (constr->Type == Sketcher::Group || constr->Type == Sketcher::Text) {
// Do not attempt to scale if there's a group
return;
}
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher/dimensioning"
);
@@ -26,6 +26,7 @@
#include <QPainter>
#include <QRegularExpression>
#include <Bnd_Box.hxx>
#include <limits>
#include <memory>
#include <map>
@@ -47,6 +48,8 @@
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTranslation.h>
#include <BRepBndLib.hxx>
#include <Base/Converter.h>
#include <Base/Exception.h>
#include <Base/Tools.h>
@@ -792,6 +795,87 @@ Restart:
// Reference Position that is scaled according to zoom
translation->translation = SbVec3f(relpos2.x - relpos1.x, relpos2.y - relpos1.y, 0);
} break;
case Text:
case Group: {
if (Constr->isElementsEmpty()) {
break; // Nothing to do if the group is empty
}
Bnd_Box totalBBox;
int elementIndex = 0;
while (Constr->hasElement(elementIndex)) {
auto element = Constr->getElement(elementIndex);
if (element.GeoId < -extGeoCount || element.GeoId >= intGeoCount) {
elementIndex++;
continue;
}
const Part::Geometry* geo = geolistfacade.getGeometryFromGeoId(element.GeoId);
if (!geo) {
elementIndex++;
continue;
}
TopoDS_Shape shape = geo->toShape();
if (!shape.IsNull()) {
BRepBndLib::Add(shape, totalBBox, false);
}
elementIndex++;
}
if (!totalBBox.HasFinitePart() || totalBBox.IsVoid()) {
// If no valid box, hide the geometry by setting all points to the origin.
SoCoordinate3* coords = static_cast<SoCoordinate3*>(sep->getChild(2));
// Use startEditing() to get a writable pointer to the internal array.
SbVec3f* points = coords->point.startEditing();
for (int j = 0; j < 5; ++j) {
points[j].setValue(0.0f, 0.0f, 0.0f);
}
coords->point.finishEditing();
}
// 1. Get the original min/max points and dimensions
gp_Pnt min_pnt_orig = totalBBox.CornerMin();
gp_Pnt max_pnt_orig = totalBBox.CornerMax();
double width = max_pnt_orig.X() - min_pnt_orig.X();
double height = max_pnt_orig.Y() - min_pnt_orig.Y();
// 2. Calculate the offset amount
// Using the average of width and height is a good heuristic for a uniform
// offset.
double offset = (width + height) / 2.0 * 0.05; // 5% of the average dimension
// 3. Create new, "inflated" corner points by applying the offset
gp_Pnt min_pnt(
min_pnt_orig.X() - offset,
min_pnt_orig.Y() - offset,
min_pnt_orig.Z()
);
gp_Pnt max_pnt(
max_pnt_orig.X() + offset,
max_pnt_orig.Y() + offset,
max_pnt_orig.Z()
);
// 4. Define the 4 corners of the rectangle using the inflated points
SbVec3f p0(min_pnt.X(), min_pnt.Y(), zConstrH); // bottom-left
SbVec3f p1(max_pnt.X(), min_pnt.Y(), zConstrH); // bottom-right
SbVec3f p2(max_pnt.X(), max_pnt.Y(), zConstrH); // top-right
SbVec3f p3(min_pnt.X(), max_pnt.Y(), zConstrH); // top-left
// 3. Get the SoCoordinate3 node we created in rebuildConstraintNodes
// Index 0: SoMaterial, Index 1: SoDrawStyle, Index 2: SoCoordinate3
SoCoordinate3* coords = static_cast<SoCoordinate3*>(sep->getChild(2));
// 4. Update the points in the node to draw the rectangle
SbVec3f* points = coords->point.startEditing();
points[0] = p0;
points[1] = p1;
points[2] = p2;
points[3] = p3;
points[4] = p0; // Repeat the first point to close the loop
coords->point.finishEditing();
} break;
case Distance:
case DistanceX:
@@ -1795,12 +1879,16 @@ void EditModeConstraintCoinManager::updateConstraintColor(
}
}
else {
bool isActive = ViewProviderSketchCoinAttorney::isConstraintActiveInSketch(
viewProvider,
constraint
);
if (hasDatumLabel) {
SoDatumLabel* l = static_cast<SoDatumLabel*>(
s->getChild(static_cast<int>(ConstraintNodePosition::DatumLabelIndex))
);
l->textColor = constraint->isActive
l->textColor = isActive
? ViewProviderSketchCoinAttorney::constraintHasExpression(viewProvider, i)
? drawingParameters.ExprBasedConstrDimColor
: (constraint->isDriving ? drawingParameters.ConstrDimColor
@@ -1808,7 +1896,7 @@ void EditModeConstraintCoinManager::updateConstraintColor(
: drawingParameters.DeactivatedConstrDimColor;
}
else if (hasMaterial) {
m->diffuseColor = constraint->isActive
m->diffuseColor = isActive
? (constraint->isDriving ? drawingParameters.ConstrDimColor
: drawingParameters.NonDrivingConstrDimColor)
: drawingParameters.DeactivatedConstrDimColor;
@@ -1884,7 +1972,8 @@ void EditModeConstraintCoinManager::rebuildConstraintNodes(
// every constrained visual node gets its own material for preselection and selection
SoMaterial* mat = new SoMaterial;
mat->ref();
mat->diffuseColor = (*it)->isActive
bool isActive = ViewProviderSketchCoinAttorney::isConstraintActiveInSketch(viewProvider, *it);
mat->diffuseColor = isActive
? ((*it)->isDriving ? drawingParameters.ConstrDimColor
: drawingParameters.NonDrivingConstrDimColor)
: drawingParameters.DeactivatedConstrDimColor;
@@ -1902,7 +1991,7 @@ void EditModeConstraintCoinManager::rebuildConstraintNodes(
SoDatumLabel* text = new SoDatumLabel();
text->norm.setValue(norm);
text->string = "";
text->textColor = (*it)->isActive
text->textColor = isActive
? ((*it)->isDriving ? drawingParameters.ConstrDimColor
: drawingParameters.NonDrivingConstrDimColor)
: drawingParameters.DeactivatedConstrDimColor;
@@ -1938,6 +2027,31 @@ void EditModeConstraintCoinManager::rebuildConstraintNodes(
// remember the type of this constraint node
vConstrType.push_back((*it)->Type);
} break;
case Group:
case Text: {
// For a group, we will draw a dashed rectangle.
// We need a Material, a DrawStyle, Coordinates, and a LineSet.
// 1. Material (for color, re-using the one already created)
sep->addChild(mat);
// 2. DrawStyle (to make the line dashed)
SoDrawStyle* drawStyle = new SoDrawStyle();
drawStyle->linePattern = 0x0F0F; // A standard 50% dashed pattern
sep->addChild(drawStyle);
// 3. Coordinates (for the 4 corners + 1 to close the loop)
SoCoordinate3* coords = new SoCoordinate3();
coords->point.setNum(5); // Pre-allocate 5 points for a closed rectangle
sep->addChild(coords);
// 4. LineSet (to connect the coordinates)
SoLineSet* lineSet = new SoLineSet();
lineSet->numVertices.set1Value(0, 5); // A single polyline of 5 vertices
sep->addChild(lineSet);
vConstrType.push_back((*it)->Type);
} break;
case Coincident: // no visual for coincident so far
vConstrType.push_back(Coincident);
break;
@@ -2861,6 +2975,10 @@ QColor EditModeConstraintCoinManager::constrColor(int constraintId)
};
const auto constraints = ViewProviderSketchCoinAttorney::getConstraints(viewProvider);
bool isActive = ViewProviderSketchCoinAttorney::isConstraintActiveInSketch(
viewProvider,
constraints[constraintId]
);
if (ViewProviderSketchCoinAttorney::isConstraintPreselected(viewProvider, constraintId)) {
return toQColor(drawingParameters.PreselectColor);
@@ -2868,7 +2986,7 @@ QColor EditModeConstraintCoinManager::constrColor(int constraintId)
else if (ViewProviderSketchCoinAttorney::isConstraintSelected(viewProvider, constraintId)) {
return toQColor(drawingParameters.SelectColor);
}
else if (!constraints[constraintId]->isActive) {
else if (!isActive) {
return toQColor(drawingParameters.DeactivatedConstrDimColor);
}
else if (!constraints[constraintId]->isDriving) {
@@ -27,6 +27,8 @@
#include <Base/Console.h>
#include <Base/Exception.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "EditModeCoinManagerParameters.h"
#include "EditModeGeometryCoinConverter.h"
#include "Utils.h"
@@ -89,85 +91,59 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli
int coinLayer,
EditModeGeometryCoinConverter::PointsMode pointmode,
int numberCurves,
int sublayer
int sublayer,
bool isGroupMember = false
) {
// Determine how many vertices this geometry has.
int numberPoints = 0;
if (pointmode == PointsMode::InsertSingle) {
numberPoints = 1;
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::start),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
}
else if (pointmode == PointsMode::InsertStartEnd) {
numberPoints = 2;
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::start),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::end),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
}
else if (pointmode == PointsMode::InsertMidOnly) {
numberPoints = 1;
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::mid),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
}
else if (pointmode == PointsMode::InsertStartEndMid) {
numberPoints = 3;
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::start),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::end),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, Sketcher::PointPos::mid),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
}
// This loop simulates the creation of vertices for THIS geometry.
// It runs for all geometries to keep vertexCounter in sync with SketchObject.
for (int i = 0; i < numberPoints; i++) {
coinMapping.PointIdToGeoId[coinLayer].push_back(geoId);
Sketcher::PointPos pos;
if (i == 0) {
if (pointmode == PointsMode::InsertMidOnly) {
pos = Sketcher::PointPos::mid;
// If the point is NOT part of a group member, we add it to the physical
// Coin maps that are used for drawing and picking.
if (!isGroupMember) {
// Determine the PointPos for this specific vertex of the geometry.
Sketcher::PointPos pos;
if (i == 0) {
pos = (pointmode == PointsMode::InsertMidOnly) ? Sketcher::PointPos::mid
: Sketcher::PointPos::start;
}
else if (i == 1) {
pos = Sketcher::PointPos::end;
}
else {
pos = Sketcher::PointPos::start;
pos = Sketcher::PointPos::mid;
}
}
else if (i == 1) {
pos = Sketcher::PointPos::end;
}
else {
pos = Sketcher::PointPos::mid;
// Map: (GeoId, PosId) -> (physicalIndex, layer)
coinMapping.GeoElementId2SetId.emplace(
std::piecewise_construct,
std::forward_as_tuple(geoId, pos),
std::forward_as_tuple(pointCounter[coinLayer]++, coinLayer)
);
// Map: physicalIndex -> logical info
coinMapping.PointIdToGeoId[coinLayer].push_back(geoId);
coinMapping.PointIdToPosId[coinLayer].push_back(pos);
// This is the key: store the correct, globally-incremented logical VertexId.
coinMapping.PointIdToVertexId[coinLayer].push_back(vertexCounter);
}
coinMapping.PointIdToPosId[coinLayer].push_back(pos);
coinMapping.PointIdToVertexId[coinLayer].push_back(vertexCounter++);
// ALWAYS increment the logical vertex counter to stay in sync with SketchObject.
vertexCounter++;
}
if (numberCurves > 0) { // insert the first segment of the curve into the map
@@ -198,6 +174,9 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli
auto coinLayer = geometryLayerParameters.getSafeCoinLayer(layerId);
auto* obj = viewProvider.getSketchObject();
bool isGroupMember = GeoId >= 0 && obj->isInGroup(GeoId, false);
if (type == Part::GeomPoint::getClassTypeId()) { // add a point
convert<
Part::GeomPoint,
@@ -217,13 +196,19 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli
Part::GeomLineSegment,
EditModeGeometryCoinConverter::PointsMode::InsertStartEnd,
EditModeGeometryCoinConverter::CurveMode::StartEndPointsOnly,
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, GeoId, subLayerId);
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(
geom,
GeoId,
subLayerId,
isGroupMember
);
setTracking(
GeoId,
coinLayer,
EditModeGeometryCoinConverter::PointsMode::InsertStartEnd,
1,
subLayerId
subLayerId,
isGroupMember
);
}
else if (type.isDerivedFrom(Part::GeomConic::getClassTypeId())) { // add a closed curve conic
@@ -231,13 +216,19 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli
Part::GeomConic,
EditModeGeometryCoinConverter::PointsMode::InsertMidOnly,
EditModeGeometryCoinConverter::CurveMode::ClosedCurve,
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, GeoId, subLayerId);
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(
geom,
GeoId,
subLayerId,
isGroupMember
);
setTracking(
GeoId,
coinLayer,
EditModeGeometryCoinConverter::PointsMode::InsertMidOnly,
1,
subLayerId
subLayerId,
isGroupMember
);
}
else if (type.isDerivedFrom(Part::GeomArcOfConic::getClassTypeId())) { // add an arc of conic
@@ -245,13 +236,19 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli
Part::GeomArcOfConic,
EditModeGeometryCoinConverter::PointsMode::InsertStartEndMid,
EditModeGeometryCoinConverter::CurveMode::OpenCurve,
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(geom, GeoId, subLayerId);
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitude>(
geom,
GeoId,
subLayerId,
isGroupMember
);
setTracking(
GeoId,
coinLayer,
EditModeGeometryCoinConverter::PointsMode::InsertStartEndMid,
1,
subLayerId
subLayerId,
isGroupMember
);
arcGeoIds.push_back(GeoId);
}
@@ -264,14 +261,16 @@ void EditModeGeometryCoinConverter::convert(const Sketcher::GeoListFacade& geoli
EditModeGeometryCoinConverter::AnalyseMode::BoundingBoxMagnitudeAndBSplineCurvature>(
geom,
GeoId,
subLayerId
subLayerId,
isGroupMember
);
setTracking(
GeoId,
coinLayer,
EditModeGeometryCoinConverter::PointsMode::InsertStartEnd,
1,
subLayerId
subLayerId,
isGroupMember
);
bsplineGeoIds.push_back(GeoId);
}
@@ -325,7 +324,8 @@ template<
void EditModeGeometryCoinConverter::convert(
const Sketcher::GeometryFacade* geometryfacade,
[[maybe_unused]] int geoid,
[[maybe_unused]] int subLayer
[[maybe_unused]] int subLayer,
bool isGroupMember
)
{
auto geo = static_cast<const GeoType*>(geometryfacade->getGeometry());
@@ -343,21 +343,23 @@ void EditModeGeometryCoinConverter::convert(
};
// Points
if constexpr (pointmode == PointsMode::InsertSingle) {
addPoint(Points[coinLayer], geo->getPoint());
}
else if constexpr (pointmode == PointsMode::InsertStartEnd) {
addPoint(Points[coinLayer], geo->getStartPoint());
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());
}
else if constexpr (pointmode == PointsMode::InsertMidOnly) {
addPoint(Points[coinLayer], geo->getCenter());
if (!isGroupMember) {
if constexpr (pointmode == PointsMode::InsertSingle) {
addPoint(Points[coinLayer], geo->getPoint());
}
else if constexpr (pointmode == PointsMode::InsertStartEnd) {
addPoint(Points[coinLayer], geo->getStartPoint());
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());
}
else if constexpr (pointmode == PointsMode::InsertMidOnly) {
addPoint(Points[coinLayer], geo->getCenter());
}
}
// Curves
@@ -158,7 +158,8 @@ private:
void convert(
const Sketcher::GeometryFacade* geometryfacade,
[[maybe_unused]] int geoId,
[[maybe_unused]] int subLayerId = 0
[[maybe_unused]] int subLayerId = 0,
bool isGroupMember = false
);
private:
@@ -168,6 +169,9 @@ private:
GeometryLayerNodes& geometryLayerNodes;
std::vector<std::vector<Base::Vector3d>> Points;
// To hide the points of geometries that are grouped, we make them transparent
// Just not adding the points would mess the indexes in Points
std::vector<std::vector<bool>> PointsHidden;
std::vector<std::vector<std::vector<Base::Vector3d>>> Coords;
std::vector<std::vector<std::vector<unsigned int>>> Index;
@@ -41,6 +41,7 @@
#include <Mod/Sketcher/App/GeoList.h>
#include <Mod/Sketcher/App/GeometryFacade.h>
#include <Mod/Sketcher/App/SolverGeometryExtension.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "EditModeGeometryCoinConverter.h"
#include "EditModeGeometryCoinManager.h"
@@ -463,8 +464,18 @@ void EditModeGeometryCoinManager::updateGeometryColor(
// edit->CurveSet->numVertices => [i] indicates number of vertex for line i.
int indexes = (editModeScenegraphNodes.CurveSet[l][t]->numVertices[i]);
bool selected = ViewProviderSketchCoinAttorney::isCurveSelected(viewProvider, GeoId);
bool preselected = (preselectcurve == GeoId);
auto* obj = viewProvider.getSketchObject();
bool isGroupMember = GeoId >= 0 && obj->isInGroup(GeoId, false);
if (isGroupMember) {
// We use the same color as group handle.
GeoId = obj->getGroupHandleIfInGroup(GeoId);
}
bool selected = ViewProviderSketchCoinAttorney::isCurveSelected(viewProvider, GeoId);
// if a grouped edge is preselected we still want it to be shown
preselected = preselected ? true : (preselectcurve == GeoId);
bool constrainedElement = isFullyConstraintElement(GeoId);
bool isExternal = GeoId < -1;
@@ -36,6 +36,8 @@
#include <Base/Exception.h>
#include <Base/UnitsApi.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "EditModeCoinManagerParameters.h"
#include "EditModeInformationOverlayCoinConverter.h"
#include "ViewProviderSketchCoinAttorney.h"
@@ -59,6 +61,17 @@ EditModeInformationOverlayCoinConverter::EditModeInformationOverlayCoinConverter
void EditModeInformationOverlayCoinConverter::convert(const Part::Geometry* geometry, int geoid)
{
if (geoid >= 0) {
// Get the SketchObject from the ViewProvider.
auto* obj = viewProvider.getSketchObject();
const bool isGroupMember = obj->isInGroup(geoid, false);
if (obj) {
if (obj->isInGroup(geoid, false)) {
return;
}
}
}
if (geometry->is<Part::GeomBSplineCurve>()) {
if (geoid < 0) {
+136
View File
@@ -0,0 +1,136 @@
// SPDX - License - Identifier: LGPL - 2.1 - or -later
/****************************************************************************
* *
* Copyright (c) 2025 Pierre-Louis Boyer *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QComboBox>
# include <QLineEdit>
#endif
#include <Gui/CommandT.h>
#include <Mod/Sketcher/App/SketchObject.h>
#include "CommandConstraints.h"
#include "EditTextDialog.h"
#include "ViewProviderSketch.h"
#include "Utils.h"
#include "ui_EditTextDialog.h"
using namespace SketcherGui;
EditTextDialog::EditTextDialog(ViewProviderSketch* viewProvider, int constraintIndex, QWidget* parent)
: QDialog(parent)
, ui(new Ui::EditTextDialog)
, sketchView(viewProvider)
, constrIndex(constraintIndex)
{
ui->setupUi(this);
ui->comboBox_font->setMaxVisibleItems(20);
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
const Sketcher::Constraint* constraint = sketch->Constraints[constrIndex];
// Initialize Text
ui->lineEdit_text->setText(QString::fromStdString(constraint->getText()));
ui->radioButton_height->setChecked(constraint->getIsTextHeight());
ui->radioButton_width->setChecked(!constraint->getIsTextHeight());
// Initialize Font
populateFontList();
QString currentFontName = findFontNameFromPath(QString::fromStdString(constraint->getFont()));
if (!currentFontName.isEmpty()) {
ui->comboBox_font->setCurrentText(currentFontName);
}
}
EditTextDialog::~EditTextDialog()
{
delete ui;
}
void EditTextDialog::populateFontList()
{
fontPathMap = findAvailableFontFiles();
QStringList fontNames = fontPathMap.keys();
fontNames.sort(Qt::CaseInsensitive);
ui->comboBox_font->addItems(fontNames);
}
QString EditTextDialog::findFontNameFromPath(const QString& path) const
{
return fontPathMap.key(path, QString());
}
void EditTextDialog::on_buttonBox_accepted()
{
const Sketcher::SketchObject* sketch = sketchView->getSketchObject();
// Get new values from the dialog
std::string newText = ui->lineEdit_text->text().toStdString();
QString selectedFontName = ui->comboBox_font->currentText();
std::string newFontPath = fontPathMap.value(selectedFontName).toStdString();
bool newIsHeight = ui->radioButton_height->isChecked();
const Sketcher::Constraint* constraint = sketch->Constraints[constrIndex];
// Check if anything changed
if (newText == constraint->getText() && newFontPath == constraint->getFont()
&& newIsHeight == constraint->getIsTextHeight()) {
return; // Nothing to do
}
// Open a command to make the change undo-able
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Modify sketch text constraint"));
try {
// Find if it was construction geometry to preserve that state
int firstTextGeoId = constraint->getGeoId(1);
bool isConstruction = false;
if (firstTextGeoId != Sketcher::GeoEnum::GeoUndef) {
isConstruction = Sketcher::GeometryFacade::getConstruction(
sketch->getGeometry(firstTextGeoId)
);
}
// Send the updated 5-parameter call to Python
std::string escText = escapeForPython(newText);
std::string escFont = escapeForPython(newFontPath);
Gui::cmdAppObjectArgs(
sketch,
"setTextAndFont(%i, '%s', '%s', %s, %s)",
constrIndex,
escText.c_str(),
escFont.c_str(),
newIsHeight ? "True" : "False",
isConstruction ? "True" : "False"
);
Gui::Command::commitCommand();
}
catch (const Base::Exception& e) {
Gui::Command::abortCommand();
Base::Console().error("Failed to modify text constraint: %s\n", e.what());
}
}
+71
View File
@@ -0,0 +1,71 @@
// SPDX - License - Identifier: LGPL - 2.1 - or -later
/****************************************************************************
* *
* Copyright (c) 2025 Pierre-Louis Boyer *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef SKETCHERGUI_EDITTEXTDIALOG_H
#define SKETCHERGUI_EDITTEXTDIALOG_H
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QDialog>
# include <QMap>
# include <QString>
#endif
namespace Ui
{
class EditTextDialog;
}
namespace SketcherGui
{
class ViewProviderSketch;
class EditTextDialog: public QDialog
{
Q_OBJECT
public:
explicit EditTextDialog(
ViewProviderSketch* viewProvider,
int constraintIndex,
QWidget* parent = nullptr
);
~EditTextDialog() override;
private Q_SLOTS:
void on_buttonBox_accepted();
private:
Ui::EditTextDialog* ui;
ViewProviderSketch* sketchView;
int constrIndex;
QMap<QString, QString> fontPathMap;
void populateFontList();
QString findFontNameFromPath(const QString& path) const;
};
} // namespace SketcherGui
#endif // SKETCHERGUI_EDITTEXTDIALOG_H
+106
View File
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditTextDialog</class>
<widget class="QDialog" name="EditTextDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>320</width>
<height>100</height>
</rect>
</property>
<property name="windowTitle">
<string>Edit Text</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_text">
<property name="text">
<string>Text:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_text"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_font">
<property name="text">
<string>Font:</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="comboBox_font"/>
</item>
<item row="2" column="0" colspan="2">
<layout class="QHBoxLayout" name="layoutType">
<item>
<widget class="QRadioButton" name="radioButton_height">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioButton_width">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>EditTextDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>EditTextDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
@@ -19,6 +19,8 @@
<file>icons/constraints/Constraint_Ellipse_Radii.svg</file>
<file>icons/constraints/Constraint_EqualLength.svg</file>
<file>icons/constraints/Constraint_ExternalAngle.svg</file>
<file>icons/constraints/Constraint_Group.svg</file>
<file>icons/constraints/Constraint_Text.svg</file>
<file>icons/constraints/Constraint_Horizontal.svg</file>
<file>icons/constraints/Constraint_HorizontalDistance.svg</file>
<file>icons/constraints/Constraint_HorizontalDistance_Driven.svg</file>
@@ -91,6 +93,8 @@
<file>icons/elements/Sketcher_Element_Line_Edge.svg</file>
<file>icons/elements/Sketcher_Element_Line_EndPoint.svg</file>
<file>icons/elements/Sketcher_Element_Line_StartingPoint.svg</file>
<file>icons/elements/Sketcher_Element_Text_EndPoint.svg</file>
<file>icons/elements/Sketcher_Element_Text_StartPoint.svg</file>
<file>icons/elements/Sketcher_Element_Parabolic_Arc_Centre_Point.svg</file>
<file>icons/elements/Sketcher_Element_Parabolic_Arc_Edge.svg</file>
<file>icons/elements/Sketcher_Element_Parabolic_Arc_End_Point.svg</file>
@@ -188,6 +192,7 @@
<file>icons/geometry/Sketcher_CreateSquare.svg</file>
<file>icons/geometry/Sketcher_CreateSquare_Constr.svg</file>
<file>icons/geometry/Sketcher_CreateText.svg</file>
<file>icons/geometry/Sketcher_CreateText_Constr.svg</file>
<file>icons/geometry/Sketcher_CreateTriangle.svg</file>
<file>icons/geometry/Sketcher_CreateTriangle_Constr.svg</file>
<file>icons/geometry/Sketcher_Extend.svg</file>
@@ -0,0 +1,529 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64px"
height="64px"
id="svg2816"
version="1.1"
sodipodi:docname="Constraint_Group.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="2.6425781"
inkscape:cx="79.467851"
inkscape:cy="69.818183"
inkscape:window-width="3840"
inkscape:window-height="1571"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2816" />
<defs
id="defs2818">
<linearGradient
id="linearGradient3602">
<stop
style="stop-color:#ff2600;stop-opacity:1"
offset="0"
id="stop3604" />
<stop
style="stop-color:#ff5f00;stop-opacity:1"
offset="1"
id="stop3606" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3602"
id="linearGradient3608"
x1="3.909091"
y1="14.363636"
x2="24.818181"
y2="14.363636"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient3608-5"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3602-7">
<stop
style="stop-color:#c51900;stop-opacity:1"
offset="0"
id="stop3604-1" />
<stop
style="stop-color:#ff5f00;stop-opacity:1"
offset="1"
id="stop3606-3" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3602-5"
id="linearGradient3608-1"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3602-5">
<stop
style="stop-color:#c51900;stop-opacity:1"
offset="0"
id="stop3604-9" />
<stop
style="stop-color:#ff5f00;stop-opacity:1"
offset="1"
id="stop3606-9" />
</linearGradient>
<linearGradient
y2="14.363636"
x2="24.81818"
y1="14.363636"
x1="3.909091"
gradientUnits="userSpaceOnUse"
id="linearGradient3686"
xlink:href="#linearGradient3602-5" />
<linearGradient
xlink:href="#linearGradient3602-58"
id="linearGradient3608-8"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3602-58">
<stop
style="stop-color:#c51900;stop-opacity:1"
offset="0"
id="stop3604-2" />
<stop
style="stop-color:#ff5f00;stop-opacity:1"
offset="1"
id="stop3606-2" />
</linearGradient>
<linearGradient
y2="14.363636"
x2="24.81818"
y1="14.363636"
x1="3.909091"
gradientUnits="userSpaceOnUse"
id="linearGradient3726"
xlink:href="#linearGradient3602-58" />
<linearGradient
id="linearGradient3787">
<stop
style="stop-color:#0619c0;stop-opacity:1"
offset="0"
id="stop3789" />
<stop
style="stop-color:#379cfb;stop-opacity:1"
offset="1"
id="stop3791" />
</linearGradient>
<linearGradient
y2="100.10708"
x2="609.54919"
y1="126.79625"
x1="581.26331"
gradientUnits="userSpaceOnUse"
id="linearGradient3524"
xlink:href="#linearGradient3602-58"
gradientTransform="matrix(1.3310616,0,0,1.2539521,-770.75488,-101.53511)" />
<linearGradient
id="linearGradient3144-6">
<stop
offset="0"
style="stop-color:#ffffff;stop-opacity:1"
id="stop3146-9" />
<stop
offset="1"
style="stop-color:#ffffff;stop-opacity:0"
id="stop3148-2" />
</linearGradient>
<radialGradient
r="34.345188"
fy="672.79736"
fx="225.26402"
cy="672.79736"
cx="225.26402"
gradientTransform="matrix(1,0,0,0.6985294,0,202.82863)"
gradientUnits="userSpaceOnUse"
id="radialGradient3688"
xlink:href="#linearGradient3144-6" />
<linearGradient
id="linearGradient3377">
<stop
style="stop-color:#ffaa00;stop-opacity:1"
offset="0"
id="stop3379" />
<stop
style="stop-color:#faff2b;stop-opacity:1"
offset="1"
id="stop3381" />
</linearGradient>
<linearGradient
id="linearGradient5048">
<stop
id="stop5050"
offset="0"
style="stop-color:black;stop-opacity:0" />
<stop
style="stop-color:black;stop-opacity:1"
offset="0.5"
id="stop5056" />
<stop
id="stop5052"
offset="1"
style="stop-color:black;stop-opacity:0" />
</linearGradient>
<radialGradient
id="aigrd2"
cx="20.892099"
cy="114.5684"
r="5.256"
fx="20.892099"
fy="114.5684"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F0F0F0"
id="stop15566" />
<stop
offset="1.0000000"
style="stop-color:#9a9a9a;stop-opacity:1"
id="stop15568" />
</radialGradient>
<radialGradient
id="aigrd3"
cx="20.892099"
cy="64.567902"
r="5.257"
fx="20.892099"
fy="64.567902"
gradientUnits="userSpaceOnUse">
<stop
offset="0"
style="stop-color:#F0F0F0"
id="stop15573" />
<stop
offset="1.0000000"
style="stop-color:#9a9a9a;stop-opacity:1"
id="stop15575" />
</radialGradient>
<linearGradient
id="linearGradient15662">
<stop
id="stop15664"
offset="0.0000000"
style="stop-color:#ffffff;stop-opacity:1" />
<stop
id="stop15666"
offset="1.0000000"
style="stop-color:#f8f8f8;stop-opacity:1" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient259"
id="radialGradient4452"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96049297,0,0,1.041132,-52.144249,-702.33158)"
cx="33.966679"
cy="35.736916"
fx="33.966679"
fy="35.736916"
r="86.70845" />
<linearGradient
id="linearGradient259">
<stop
id="stop260"
offset="0.0000000"
style="stop-color:#fafafa;stop-opacity:1" />
<stop
id="stop261"
offset="1.0000000"
style="stop-color:#bbbbbb;stop-opacity:1" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient269"
id="radialGradient4454"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.96827297,0,0,1.032767,-48.790699,-701.68513)"
cx="8.824419"
cy="3.7561285"
fx="8.824419"
fy="3.7561285"
r="37.751713" />
<linearGradient
id="linearGradient269">
<stop
id="stop270"
offset="0.0000000"
style="stop-color:#a3a3a3;stop-opacity:1" />
<stop
id="stop271"
offset="1.0000000"
style="stop-color:#4c4c4c;stop-opacity:1" />
</linearGradient>
<linearGradient
y2="1190.875"
x2="1267.9062"
y1="1190.875"
x1="901.1875"
gradientTransform="matrix(0.10456791,0,0,0.10456791,420.90006,-32.97638)"
gradientUnits="userSpaceOnUse"
id="linearGradient4937"
xlink:href="#linearGradient4095" />
<linearGradient
id="linearGradient4095">
<stop
id="stop4097"
offset="0"
style="stop-color:#005bff;stop-opacity:1" />
<stop
id="stop4099"
offset="1"
style="stop-color:#c1e3f7;stop-opacity:1" />
</linearGradient>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="14.363636"
x2="24.818181"
y1="14.363636"
x1="3.909091"
id="linearGradient4396"
xlink:href="#linearGradient3602" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient4400"
gradientUnits="userSpaceOnUse"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient4403"
gradientUnits="userSpaceOnUse"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636" />
<linearGradient
xlink:href="#linearGradient3787"
id="linearGradient4429"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.3180277,0,0,1.2416733,-743.43621,-121.30586)"
x1="581.26331"
y1="126.79625"
x2="609.54919"
y2="100.10708" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="14.363636"
x2="24.818181"
y1="14.363636"
x1="3.909091"
id="linearGradient4527"
xlink:href="#linearGradient3602" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient4531"
gradientUnits="userSpaceOnUse"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient4534"
gradientUnits="userSpaceOnUse"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="14.363636"
x2="24.818181"
y1="14.363636"
x1="3.909091"
id="linearGradient3483"
xlink:href="#linearGradient3602" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient3487"
gradientUnits="userSpaceOnUse"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636" />
<linearGradient
xlink:href="#linearGradient3602-7"
id="linearGradient3490"
gradientUnits="userSpaceOnUse"
x1="3.909091"
y1="14.363636"
x2="24.81818"
y2="14.363636" />
</defs>
<metadata
id="metadata2821">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[Abdullah Tahiri]</dc:title>
</cc:Agent>
</dc:creator>
<dc:date>2015-05-26</dc:date>
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_ToggleConstraint.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<cc:license>https://www.gnu.org/copyleft/lesser.html</cc:license>
<dc:contributor>
<cc:Agent>
<dc:title>[agryson] Alexander Gryson</dc:title>
</cc:Agent>
</dc:contributor>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g6"
transform="translate(-4.2813147,13.579795)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6" />
</g>
<g
id="g6-8"
transform="rotate(90,27.48765,22.71893)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5-8" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-2" />
</g>
<g
id="g6-8-5"
transform="rotate(180,34.21529,25.143605)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5-8-1" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-2-7" />
</g>
<g
id="g6-8-52"
transform="rotate(-90,40.965184,27.49795)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5-8-7" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-2-6" />
</g>
<g
id="g1942"
transform="matrix(0.56082348,0.00382887,-0.00382887,0.56082348,-30.824178,24.889141)">
<g
id="g1639">
<path
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1"
d="m 85.705652,39.254404 36.510728,0.03213 -0.0643,-36.0755714"
id="path3042" />
<g
id="g5444"
transform="matrix(0.12582856,0,0,0.13656888,-116.075,-78.506633)">
<rect
style="fill:none;fill-opacity:1;stroke:#2e3436;stroke-width:61.1235;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3579"
width="320.34793"
height="290.3475"
x="1581.9229"
y="577.53864"
ry="72.587051" />
<rect
style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-width:15.2809;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3579-9-4"
width="334.35117"
height="306.85471"
x="1574.9211"
y="569.28497"
ry="76.713875" />
<rect
style="fill:none;fill-opacity:1;stroke:#d3d7cf;stroke-width:15.2809;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect3579-9"
width="308.51923"
height="283.05432"
x="1587.837"
y="581.18518"
ry="70.763763" />
</g>
</g>
<g
id="g1893"
transform="matrix(0.92702216,0,0,0.92702216,60.747574,-46.780667)">
<path
style="fill:none;stroke:#2e3436;stroke-width:7.99999;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.9;stroke-dasharray:none;stroke-opacity:1"
id="path3064"
d="m 87.99875,70.15496 -24,12.819254 L 40.058512,69.793468 40.118273,43.793467 64.118272,30.974213 88.058511,44.15496 Z" />
<path
style="fill:none;stroke:#d3d7cf;stroke-width:4.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.9;stroke-dasharray:none;stroke-opacity:1"
id="path3064-3"
d="m 87.99875,70.15496 -24,12.819254 L 40.058512,69.793468 40.118273,43.793467 64.118272,30.974213 88.058511,44.15496 Z" />
<path
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.9;stroke-opacity:1"
d="m 39.058511,70.286516 0.06225,-27 25,-13.312301 24.93775,13.687698"
id="path3064-3-6" />
<path
style="fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.9;stroke-opacity:1"
d="m 87.058511,44.648008 -0.05727,25 -23,12.326206 -22.94273,-12.673794"
id="path3064-3-6-7" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 18 KiB

@@ -0,0 +1,330 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
id="svg2869"
version="1.1"
viewBox="0 0 64 64"
sodipodi:docname="Constraint_Text.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#50505000"
bordercolor="#eeeeeeff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050ff"
inkscape:zoom="5.2851563"
inkscape:cx="12.487805"
inkscape:cy="43.612712"
inkscape:window-width="3840"
inkscape:window-height="1571"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2869" />
<defs
id="defs2871">
<linearGradient
id="linearGradient5">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop19" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop20" />
</linearGradient>
<linearGradient
id="swatch18">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop18" />
</linearGradient>
<linearGradient
id="swatch15">
<stop
style="stop-color:#3d0000;stop-opacity:1;"
offset="0"
id="stop15" />
</linearGradient>
<linearGradient
id="linearGradient5-1">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop5" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop6" />
</linearGradient>
<linearGradient
id="linearGradient3836-9">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1" />
</linearGradient>
<linearGradient
id="linearGradient3836-9-3">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-5" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-6" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
id="linearGradient3836-9-7">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-0" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-9" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082-3"
xlink:href="#linearGradient3836-9-7" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient9"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.76342439,0,0,0.75750425,-4.596389,2.7525637)" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-2"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.84956703,0,0,0.84301394,-2.927337,1.7790378)" />
<linearGradient
xlink:href="#linearGradient3838"
id="linearGradient3844"
x1="36"
y1="1039.3622"
x2="32"
y2="1003.3622"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(2.0563921e-6,-988.36218)" />
<linearGradient
id="linearGradient3838">
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="0"
id="stop3840" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3842" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3830"
id="linearGradient3836"
x1="36"
y1="1037.3622"
x2="32"
y2="1005.3622"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(2.0563921e-6,-988.36218)" />
<linearGradient
id="linearGradient3830">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3832" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="1"
id="stop3834" />
</linearGradient>
<linearGradient
gradientTransform="translate(12.126952,12.126971)"
y2="1734.2576"
x2="4157.8677"
y1="1473.5258"
x1="4115.4229"
gradientUnits="userSpaceOnUse"
id="linearGradient3009"
xlink:href="#linearGradient3010" />
<linearGradient
id="linearGradient3010">
<stop
id="stop3012"
offset="0"
style="stop-color:#ef2929;stop-opacity:1" />
<stop
id="stop3014"
offset="1"
style="stop-color:#a40000;stop-opacity:1" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.1649204,0,0,0.1649204,-632.71718,-223.01448)"
y2="1734.2576"
x2="4157.8677"
y1="1473.5258"
x1="4115.4229"
gradientUnits="userSpaceOnUse"
id="linearGradient3009-2"
xlink:href="#linearGradient3010-2" />
<linearGradient
id="linearGradient3010-2">
<stop
id="stop3012-1"
offset="0"
style="stop-color:#d3d7cf;stop-opacity:1;" />
<stop
id="stop3014-6"
offset="1"
style="stop-color:#d3d7cf;stop-opacity:1;" />
</linearGradient>
</defs>
<metadata
id="metadata2874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[maxwxyz]</dc:title>
</cc:Agent>
</dc:creator>
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateArc.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:date>2023-12-19</dc:date>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g2"
transform="translate(-2.1206965,1.6845105)">
<g
id="g6"
transform="translate(-2.0747215,12.048077)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-23" />
</g>
<g
id="g6-8"
transform="rotate(90,29.356805,23.056368)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5-8" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-2" />
</g>
<g
id="g6-8-5"
transform="rotate(180,35.318587,24.377746)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5-8-1" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-2-7" />
</g>
<g
id="g6-8-52"
transform="rotate(-90,41.302622,25.628794)">
<path
style="baseline-shift:baseline;display:inline;overflow:visible;fill:#cc0000;fill-opacity:1;stroke:#280000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;enable-background:accumulate;stop-color:#000000"
d="M 10.691188,-10.079592 A 2.7002703,2.7002703 0 0 0 7.990017,-7.3784198 V 4.1625958 a 2.7000003,2.7000003 0 0 0 2.701171,2.6992188 2.7000003,2.7000003 0 0 0 2.699219,-2.6992188 v -8.8417969 h 8.179688 a 2.7000003,2.7000003 0 0 0 2.699218,-2.6992187 2.7000003,2.7000003 0 0 0 -2.699218,-2.7011722 z"
id="path5-8-7" />
<path
style="fill:none;fill-opacity:1;stroke:#ef2929;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 9.8336448,4.6826881 V -8.2281518 H 22.108977"
id="path6-2-6" />
</g>
</g>
<g
id="g3"
transform="matrix(0.67985712,0,0,0.67985712,3.8942753,5.1844051)">
<path
id="text2714"
d="M 52.000007,50.000005 H 34.000001 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 h -16 v -2.000006 c 0.901917,-0.171781 2.463152,-0.570509 3.000001,-0.999995 0.536849,-0.429485 0.999995,-0.999994 2.000006,-2.000006 L 41.999993,24.000007 c 0.730086,-1.073664 0.871126,-1.355744 1.000011,-2.000006 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08587,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:url(#linearGradient3009-2);fill-opacity:1;stroke:#2e0000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="M 53.27692,47.999999 H 33.148154 l -5.964082,7.216455 c -1.480672,1.914033 -1.084253,2.914242 -1.253082,3.124467 0,0.931816 1.519016,1.409822 2.746634,1.749986 l 0.204551,-0.02274 -12.882164,-0.06818 -0.613636,0.477279 c 0.818401,-0.160071 3.848715,-0.895254 4.335857,-1.295449 0.487142,-0.400213 1.225573,-1.386371 2.019368,-2.545481 L 41.066494,28.909094 c 0.662485,-1.000473 2.608645,-3.695124 3.134675,-4.704552 0.55847,-0.920405 1.508461,-3.713398 0.456236,-4.113626 l -1.681842,-0.09092 H 57.01682 l -2.159105,-0.113615 c -1.814784,0 -1.770899,0.86415 -1.904039,1.52271 -0.116912,0.580339 0.106819,1.286907 0.340709,2.027235 l 9.167925,31.189218 c 0.389674,1.340655 0.401268,1.601493 0.631975,2.465379 0.594192,1.639325 0.217134,2.044469 0.996614,2.20454 l 2.909097,0.70454 H 53.000002 l 2.068168,-1.022721 c 0.821749,-1.4701 0.851418,-1.070415 1.046321,-2.09091 0.07791,-0.420217 -0.42533,-2.088667 -0.659121,-2.90908 l -2.132998,-5.84092 m 0.735776,-3.036333 -5.501761,-20.213667 -14.812359,20.190941 20.125154,0.004"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#ffffff;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="M 52.000007,50.000005 H 34.000001 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 h -16 v -2.000006 c 0.901917,-0.171781 2.463152,-0.570509 3.000001,-0.999995 0.536849,-0.429485 0.999995,-0.999994 2.000006,-2.000006 L 41.999993,24.000007 c 0.730086,-1.073664 0.871126,-1.355744 1.000011,-2.000006 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08586,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#280000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,587 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
id="svg2869"
version="1.1"
viewBox="0 0 64 64"
sodipodi:docname="Sketcher_Element_Text_EndPoint.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#50505000"
bordercolor="#eeeeeeff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050ff"
inkscape:zoom="3.7371698"
inkscape:cx="88.302115"
inkscape:cy="-27.962336"
inkscape:window-width="3840"
inkscape:window-height="1571"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2869" />
<defs
id="defs2871">
<linearGradient
id="linearGradient34">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop33" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="1"
id="stop34" />
</linearGradient>
<linearGradient
id="linearGradient32">
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="0"
id="stop31" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop32" />
</linearGradient>
<linearGradient
id="linearGradient17">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop16" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop17" />
</linearGradient>
<linearGradient
id="linearGradient5">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop19" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop20" />
</linearGradient>
<linearGradient
id="swatch18">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop18" />
</linearGradient>
<linearGradient
id="swatch15">
<stop
style="stop-color:#3d0000;stop-opacity:1;"
offset="0"
id="stop15" />
</linearGradient>
<linearGradient
id="linearGradient5-1">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop5" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop6" />
</linearGradient>
<linearGradient
id="linearGradient3836-9">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1" />
</linearGradient>
<linearGradient
id="linearGradient3836-9-3">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-5" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-6" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
id="linearGradient3836-9-7">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-0" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-9" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082-3"
xlink:href="#linearGradient3836-9-7" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.76342439,0,0,0.75750425,-4.596389,2.7525637)" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-2"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.84956703,0,0,0.84301394,-2.927337,1.7790378)" />
<radialGradient
xlink:href="#linearGradient3809"
id="radialGradient3815"
cx="225.93762"
cy="91.956673"
fx="225.93762"
fy="91.956673"
r="22"
gradientTransform="matrix(-1.4090915,3.8636359,-0.97565325,-0.35582669,437.08461,-816.22007)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3809">
<stop
style="stop-color:#729fcf;stop-opacity:1"
offset="0"
id="stop3811" />
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="1"
id="stop3813" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3444"
xlink:href="#linearGradient3836-9-3"
gradientTransform="matrix(0.90206746,0,0,0.90216902,-1.9060863,1.1084289)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3857"
xlink:href="#linearGradient3836-9-3"
gradientTransform="matrix(0.89262616,0,0,0.89258466,72.894067,1.2176306)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.93724177,0,0,0.93725692,-1.2227671,0.70650014)"
gradientUnits="userSpaceOnUse"
id="linearGradient3148"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient17"
id="linearGradient3898"
x1="37.429146"
y1="41.590584"
x2="24.483221"
y2="4.9104676"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3892">
<stop
style="stop-color:#bdd2e9;stop-opacity:1;"
offset="0"
id="stop3894" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop3896" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3892"
id="linearGradient3856"
x1="22.84341"
y1="4.8241611"
x2="30.783579"
y2="28.644661"
gradientUnits="userSpaceOnUse" />
<radialGradient
r="22"
fy="91.956673"
fx="225.93762"
cy="91.956673"
cx="225.93762"
gradientTransform="matrix(-1.7064667,4.6731721,-1.1815555,-0.43038201,776.9032,-933.08315)"
gradientUnits="userSpaceOnUse"
id="radialGradient3163"
xlink:href="#linearGradient3809" />
<linearGradient
xlink:href="#linearGradient4104"
id="linearGradient4110"
x1="-23.070524"
y1="18.383886"
x2="-24.194258"
y2="6.534451"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4104">
<stop
style="stop-color:#d3d7cf;stop-opacity:1"
offset="0"
id="stop4106" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop4108" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient4096"
id="linearGradient4102"
x1="-24.035076"
y1="16.85112"
x2="-23.821426"
y2="7.2881389"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4096">
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="0"
id="stop4098" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1"
offset="1"
id="stop4100" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient4216"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.71430595,0,0,0.71426219,259.5032,71.709694)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<radialGradient
r="22"
fy="91.956673"
fx="225.93762"
cy="91.956673"
cx="225.93762"
gradientTransform="matrix(-1.7064667,4.6731721,-1.1815555,-0.43038201,776.9032,-933.08315)"
gradientUnits="userSpaceOnUse"
id="radialGradient3163-9"
xlink:href="#linearGradient3809" />
<linearGradient
xlink:href="#linearGradient4104"
id="linearGradient4347"
x1="-21.31983"
y1="18.008659"
x2="-24.907471"
y2="6.9377007"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#linearGradient4096"
id="linearGradient4339"
x1="-21.141161"
y1="17.489555"
x2="-24.733845"
y2="7.0083036"
gradientUnits="userSpaceOnUse" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.65313239,0,0,0.65304994,258.31758,72.40809)"
gradientUnits="userSpaceOnUse"
id="linearGradient4235-2"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-7"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient32"
id="linearGradient3901"
gradientUnits="userSpaceOnUse"
x1="30.202745"
y1="56.729507"
x2="35.013981"
y2="19.843365"
gradientTransform="matrix(0.62354045,-0.62354046,0.62354039,0.62354042,-12.205772,28.459494)" />
<linearGradient
xlink:href="#linearGradient34"
id="linearGradient3903"
gradientUnits="userSpaceOnUse"
x1="29.993565"
y1="54.846851"
x2="36.094769"
y2="20.854424"
gradientTransform="matrix(0.57365718,-0.57365723,0.57365712,0.57365719,-8.6693108,28.742734)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3922"
xlink:href="#linearGradient3836-9-3"
gradientTransform="matrix(0.85221122,0,0,0.85228409,-2.8500258,1.6945624)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3096"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-7"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="translate(-90,-5.9999999)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082-3-8"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-7-3"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-7-3-0"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3203"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient47"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient49"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-0"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient2"
id="linearGradient6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
id="linearGradient2">
<stop
style="stop-color:#73d216;stop-opacity:1;"
offset="0"
id="stop1" />
<stop
style="stop-color:#8ae234;stop-opacity:1;"
offset="1"
id="stop2" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.1649204,0,0,0.1649204,-641.71718,-230.01448)"
y2="1734.2576"
x2="4157.8677"
y1="1473.5258"
x1="4115.4229"
gradientUnits="userSpaceOnUse"
id="linearGradient3009"
xlink:href="#linearGradient3010" />
<linearGradient
id="linearGradient3010">
<stop
id="stop3012"
offset="0"
style="stop-color:#d3d7cf;stop-opacity:1;" />
<stop
id="stop3014"
offset="1"
style="stop-color:#d3d7cf;stop-opacity:1;" />
</linearGradient>
</defs>
<metadata
id="metadata2874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[maxwxyz]</dc:title>
</cc:Agent>
</dc:creator>
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateArc.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:date>2023-12-19</dc:date>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g1"
transform="translate(-5.05e-6,-3.000006)">
<path
id="text2714"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08587,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:url(#linearGradient3009);fill-opacity:1;stroke:#2e0000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="M 44.276919,40.999998 H 24.148153 l -5.964082,7.216455 c -1.480672,1.914033 -1.084253,2.914242 -1.253082,3.124467 0,0.931816 1.519016,1.409822 2.746634,1.749986 l 0.204551,-0.02274 -12.8821638,-0.06818 -0.6136358,0.477279 c 0.818401,-0.160071 3.8487146,-0.895254 4.3358566,-1.295449 0.487142,-0.400213 1.225573,-1.386371 2.019368,-2.545481 L 32.066493,21.909093 c 0.662485,-1.000473 2.608645,-3.695124 3.134675,-4.704552 0.55847,-0.920405 1.508461,-3.713398 0.456236,-4.113626 l -1.681842,-0.09092 H 48.016819 L 45.857714,12.88638 c -1.814784,0 -1.770899,0.86415 -1.904039,1.52271 -0.116912,0.580339 0.106819,1.286907 0.340709,2.027235 l 9.167925,31.189218 c 0.389674,1.340655 0.401268,1.601493 0.631975,2.465379 0.594192,1.639325 0.217134,2.044469 0.996614,2.20454 l 2.909097,0.70454 H 44.000001 l 2.068168,-1.022721 c 0.821749,-1.4701 0.851418,-1.070415 1.046321,-2.09091 0.07791,-0.420217 -0.42533,-2.088667 -0.659121,-2.90908 l -2.132998,-5.84092 m 0.735776,-3.036333 -5.501761,-20.213667 -14.812359,20.190941 20.125154,0.004"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#ffffff;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08586,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#280000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
id="layer1-5"
transform="rotate(-135,42.659784,41.549307)">
<path
style="fill:#3465a4;stroke:#091425;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 24.393399,44.849229 20.150758,40.60659 54.091883,6.6654658 l 4.24264,4.2426392 z"
id="path3061-2"
sodipodi:nodetypes="ccccc" />
<path
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 22.487284,41.09849 54.583793,9.0019854"
id="path3063-7"
sodipodi:nodetypes="cc" />
</g>
<g
transform="matrix(-0.55062023,-0.55062023,0.55062013,-0.55062013,-8.8460455,50.079206)"
id="g47-6"
style="stroke-width:1.2842">
<path
style="fill:none;stroke:#2e0000;stroke-width:2.56841;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path46-1"
d="M -26.310778,5.3580033 A 8.3519646,8.3515832 0.02039876 1 1 -13.623399,16.222662 8.3519646,8.3515832 0.02039876 1 1 -26.310778,5.3580033 Z" />
<path
style="fill:url(#linearGradient47);fill-opacity:1;stroke:#ef2929;stroke-width:2.56842;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path47-4"
d="m -24.358888,7.0362241 a 5.782493,5.7773415 0 1 1 8.784093,7.5158349 5.782493,5.7773415 0 0 1 -8.784093,-7.5158349 z" />
</g>
<g
transform="matrix(-0.55062023,0.55062023,-0.55062013,-0.55062013,50.287573,71.929186)"
id="g6"
style="display:inline;stroke-width:1.2842">
<path
style="fill:none;stroke:#162c02;stroke-width:2.56841;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path5"
d="M -26.310778,5.3580033 A 8.3519646,8.3515832 0.02039876 1 1 -13.623399,16.222662 8.3519646,8.3515832 0.02039876 1 1 -26.310778,5.3580033 Z" />
<path
style="fill:url(#linearGradient6);fill-opacity:1;stroke:#8ae234;stroke-width:2.56842;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path6-7"
d="m -24.358888,7.0362241 a 5.782493,5.7773415 0 1 1 8.784093,7.5158349 5.782493,5.7773415 0 0 1 -8.784093,-7.5158349 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

@@ -0,0 +1,597 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
id="svg2869"
version="1.1"
viewBox="0 0 64 64"
sodipodi:docname="Sketcher_Element_Text_StartPoint.svg"
inkscape:version="1.4.2 (f4327f4, 2025-05-13)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#50505000"
bordercolor="#eeeeeeff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050ff"
inkscape:zoom="10.570313"
inkscape:cx="35.476718"
inkscape:cy="26.725795"
inkscape:window-width="3840"
inkscape:window-height="1571"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2869" />
<defs
id="defs2871">
<linearGradient
id="linearGradient34">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop33" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="1"
id="stop34" />
</linearGradient>
<linearGradient
id="linearGradient32">
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="0"
id="stop31" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop32" />
</linearGradient>
<linearGradient
id="linearGradient17">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop16" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop17" />
</linearGradient>
<linearGradient
id="linearGradient5">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop19" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop20" />
</linearGradient>
<linearGradient
id="swatch18">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop18" />
</linearGradient>
<linearGradient
id="swatch15">
<stop
style="stop-color:#3d0000;stop-opacity:1;"
offset="0"
id="stop15" />
</linearGradient>
<linearGradient
id="linearGradient5-1">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop5" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop6" />
</linearGradient>
<linearGradient
id="linearGradient3836-9">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1" />
</linearGradient>
<linearGradient
id="linearGradient3836-9-3">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-5" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-6" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
id="linearGradient3836-9-7">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-0" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-9" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082-3"
xlink:href="#linearGradient3836-9-7" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.76342439,0,0,0.75750425,-4.596389,2.7525637)" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-2"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.84956703,0,0,0.84301394,-2.927337,1.7790378)" />
<radialGradient
xlink:href="#linearGradient3809"
id="radialGradient3815"
cx="225.93762"
cy="91.956673"
fx="225.93762"
fy="91.956673"
r="22"
gradientTransform="matrix(-1.4090915,3.8636359,-0.97565325,-0.35582669,437.08461,-816.22007)"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3809">
<stop
style="stop-color:#729fcf;stop-opacity:1"
offset="0"
id="stop3811" />
<stop
style="stop-color:#204a87;stop-opacity:1"
offset="1"
id="stop3813" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3444"
xlink:href="#linearGradient3836-9-3"
gradientTransform="matrix(0.90206746,0,0,0.90216902,-1.9060863,1.1084289)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3857"
xlink:href="#linearGradient3836-9-3"
gradientTransform="matrix(0.89262616,0,0,0.89258466,72.894067,1.2176306)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.93724177,0,0,0.93725692,-1.2227671,0.70650014)"
gradientUnits="userSpaceOnUse"
id="linearGradient3148"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient17"
id="linearGradient3898"
x1="37.429146"
y1="41.590584"
x2="24.483221"
y2="4.9104676"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient3892">
<stop
style="stop-color:#bdd2e9;stop-opacity:1;"
offset="0"
id="stop3894" />
<stop
style="stop-color:#ffffff;stop-opacity:0"
offset="1"
id="stop3896" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3892"
id="linearGradient3856"
x1="22.84341"
y1="4.8241611"
x2="30.783579"
y2="28.644661"
gradientUnits="userSpaceOnUse" />
<radialGradient
r="22"
fy="91.956673"
fx="225.93762"
cy="91.956673"
cx="225.93762"
gradientTransform="matrix(-1.7064667,4.6731721,-1.1815555,-0.43038201,776.9032,-933.08315)"
gradientUnits="userSpaceOnUse"
id="radialGradient3163"
xlink:href="#linearGradient3809" />
<linearGradient
xlink:href="#linearGradient4104"
id="linearGradient4110"
x1="-23.070524"
y1="18.383886"
x2="-24.194258"
y2="6.534451"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4104">
<stop
style="stop-color:#d3d7cf;stop-opacity:1"
offset="0"
id="stop4106" />
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="1"
id="stop4108" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient4096"
id="linearGradient4102"
x1="-24.035076"
y1="16.85112"
x2="-23.821426"
y2="7.2881389"
gradientUnits="userSpaceOnUse" />
<linearGradient
id="linearGradient4096">
<stop
style="stop-color:#ffffff;stop-opacity:1"
offset="0"
id="stop4098" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1"
offset="1"
id="stop4100" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient4216"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.71430595,0,0,0.71426219,259.5032,71.709694)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<radialGradient
r="22"
fy="91.956673"
fx="225.93762"
cy="91.956673"
cx="225.93762"
gradientTransform="matrix(-1.7064667,4.6731721,-1.1815555,-0.43038201,776.9032,-933.08315)"
gradientUnits="userSpaceOnUse"
id="radialGradient3163-9"
xlink:href="#linearGradient3809" />
<linearGradient
xlink:href="#linearGradient4104"
id="linearGradient4347"
x1="-21.31983"
y1="18.008659"
x2="-24.907471"
y2="6.9377007"
gradientUnits="userSpaceOnUse" />
<linearGradient
xlink:href="#linearGradient4096"
id="linearGradient4339"
x1="-21.141161"
y1="17.489555"
x2="-24.733845"
y2="7.0083036"
gradientUnits="userSpaceOnUse" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientTransform="matrix(0.65313239,0,0,0.65304994,258.31758,72.40809)"
gradientUnits="userSpaceOnUse"
id="linearGradient4235-2"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-7"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient32"
id="linearGradient3901"
gradientUnits="userSpaceOnUse"
x1="30.202745"
y1="56.729507"
x2="35.013981"
y2="19.843365"
gradientTransform="matrix(0.62354045,-0.62354046,0.62354039,0.62354042,-12.205772,28.459494)" />
<linearGradient
xlink:href="#linearGradient34"
id="linearGradient3903"
gradientUnits="userSpaceOnUse"
x1="29.993565"
y1="54.846851"
x2="36.094769"
y2="20.854424"
gradientTransform="matrix(0.57365718,-0.57365723,0.57365712,0.57365719,-8.6693108,28.742734)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3922"
xlink:href="#linearGradient3836-9-3"
gradientTransform="matrix(0.85221122,0,0,0.85228409,-2.8500258,1.6945624)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3096"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-7"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="translate(-90,-5.9999999)" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082-3-8"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-7-3"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-7-3-0"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3203"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient47"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient49"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-0"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
xlink:href="#linearGradient2"
id="linearGradient6"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
<linearGradient
id="linearGradient2">
<stop
style="stop-color:#73d216;stop-opacity:1;"
offset="0"
id="stop1" />
<stop
style="stop-color:#8ae234;stop-opacity:1;"
offset="1"
id="stop2" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.1649204,0,0,0.1649204,-641.71718,-230.01448)"
y2="1734.2576"
x2="4157.8677"
y1="1473.5258"
x1="4115.4229"
gradientUnits="userSpaceOnUse"
id="linearGradient3009"
xlink:href="#linearGradient3010" />
<linearGradient
id="linearGradient3010">
<stop
id="stop3012"
offset="0"
style="stop-color:#d3d7cf;stop-opacity:1;" />
<stop
id="stop3014"
offset="1"
style="stop-color:#d3d7cf;stop-opacity:1;" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient3836-9-3"
id="linearGradient1"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)"
x1="-18"
y1="18"
x2="-22"
y2="5" />
</defs>
<metadata
id="metadata2874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[maxwxyz]</dc:title>
</cc:Agent>
</dc:creator>
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateArc.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:date>2023-12-19</dc:date>
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="g1"
transform="translate(-5.05e-6,-3.000006)">
<path
id="text2714"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08587,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:url(#linearGradient3009);fill-opacity:1;stroke:#2e0000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="M 44.276919,40.999998 H 24.148153 l -5.964082,7.216455 c -1.480672,1.914033 -1.084253,2.914242 -1.253082,3.124467 0,0.931816 1.519016,1.409822 2.746634,1.749986 l 0.204551,-0.02274 -12.8821638,-0.06818 -0.6136358,0.477279 c 0.818401,-0.160071 3.8487146,-0.895254 4.3358566,-1.295449 0.487142,-0.400213 1.225573,-1.386371 2.019368,-2.545481 L 32.066493,21.909093 c 0.662485,-1.000473 2.608645,-3.695124 3.134675,-4.704552 0.55847,-0.920405 1.508461,-3.713398 0.456236,-4.113626 l -1.681842,-0.09092 H 48.016819 L 45.857714,12.88638 c -1.814784,0 -1.770899,0.86415 -1.904039,1.52271 -0.116912,0.580339 0.106819,1.286907 0.340709,2.027235 l 9.167925,31.189218 c 0.389674,1.340655 0.401268,1.601493 0.631975,2.465379 0.594192,1.639325 0.217134,2.044469 0.996614,2.20454 l 2.909097,0.70454 H 44.000001 l 2.068168,-1.022721 c 0.821749,-1.4701 0.851418,-1.070415 1.046321,-2.09091 0.07791,-0.420217 -0.42533,-2.088667 -0.659121,-2.90908 l -2.132998,-5.84092 m 0.735776,-3.036333 -5.501761,-20.213667 -14.812359,20.190941 20.125154,0.004"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#ffffff;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08586,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#280000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
<g
id="layer1-5"
transform="rotate(45,-1.3443654,28.861417)">
<path
style="fill:#3465a4;stroke:#091425;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 24.393399,44.849229 20.150758,40.60659 54.091883,6.6654658 l 4.24264,4.2426392 z"
id="path3061-2"
sodipodi:nodetypes="ccccc" />
<path
style="fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 22.487284,41.09849 54.583793,9.0019854"
id="path3063-7"
sodipodi:nodetypes="cc" />
</g>
<g
transform="matrix(0.55062023,0.55062023,-0.55062013,0.55062013,72.305403,60.418843)"
id="g47-6"
style="stroke-width:1.2842">
<path
style="fill:none;stroke:#2e0000;stroke-width:2.56841;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path46-1"
d="M -26.310778,5.3580033 A 8.3519646,8.3515832 0.02039876 1 1 -13.623399,16.222662 8.3519646,8.3515832 0.02039876 1 1 -26.310778,5.3580033 Z" />
<path
style="fill:url(#linearGradient1);fill-opacity:1;stroke:#ef2929;stroke-width:2.56842;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path47-4"
d="m -24.358888,7.0362241 a 5.782493,5.7773415 0 1 1 8.784093,7.5158349 5.782493,5.7773415 0 0 1 -8.784093,-7.5158349 z" />
</g>
<g
transform="matrix(0.55062023,-0.55062023,0.55062013,0.55062013,13.171784,38.568863)"
id="g6"
style="display:inline;stroke-width:1.2842">
<path
style="fill:none;stroke:#162c02;stroke-width:2.56841;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path5"
d="M -26.310778,5.3580033 A 8.3519646,8.3515832 0.02039876 1 1 -13.623399,16.222662 8.3519646,8.3515832 0.02039876 1 1 -26.310778,5.3580033 Z" />
<path
style="fill:url(#linearGradient6);fill-opacity:1;stroke:#8ae234;stroke-width:2.56842;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path6-7"
d="m -24.358888,7.0362241 a 5.782493,5.7773415 0 1 1 8.784093,7.5158349 5.782493,5.7773415 0 0 1 -8.784093,-7.5158349 z" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 21 KiB

@@ -7,12 +7,34 @@
id="svg2869"
version="1.1"
viewBox="0 0 64 64"
sodipodi:docname="Sketcher_CreateText.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="10.570313"
inkscape:cx="2.9327421"
inkscape:cy="33.537324"
inkscape:window-width="3840"
inkscape:window-height="1571"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2869" />
<defs
id="defs2871">
<linearGradient
@@ -168,7 +190,7 @@
id="stop3834" />
</linearGradient>
<linearGradient
gradientTransform="translate(12.126952,12.126971)"
gradientTransform="matrix(0.1649204,0,0,0.1649204,-641.71718,-230.01448)"
y2="1734.2576"
x2="4157.8677"
y1="1473.5258"
@@ -181,11 +203,11 @@
<stop
id="stop3012"
offset="0"
style="stop-color:#ef2929;stop-opacity:1" />
style="stop-color:#d3d7cf;stop-opacity:1;" />
<stop
id="stop3014"
offset="1"
style="stop-color:#a40000;stop-opacity:1" />
style="stop-color:#d3d7cf;stop-opacity:1;" />
</linearGradient>
</defs>
<metadata
@@ -217,77 +239,16 @@
</cc:Work>
</rdf:RDF>
</metadata>
<g
id="layer4">
<g
id="g1"
style="display:inline"
transform="translate(-44,13)">
<path
style="fill:none;stroke:#151819;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
d="M 10,32 H 32"
id="path1" />
<path
style="display:inline;fill:none;stroke:#d3d7cf;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:markers fill stroke"
d="M 10,32 H 32"
id="path1-7" />
<path
style="display:inline;fill:none;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
d="M 10,31 H 32"
id="path1-7-0" />
</g>
<g
id="g8"
style="display:inline"
transform="translate(-44,27)">
<path
style="fill:none;stroke:#091425;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
d="M 10,32 H 32"
id="path6" />
<path
style="display:inline;fill:none;stroke:#3465a4;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;paint-order:markers fill stroke"
d="M 10,32 H 32"
id="path7" />
<path
style="display:inline;fill:none;stroke:#729fcf;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:markers fill stroke"
d="M 10,31 H 32"
id="path8" />
</g>
<g
transform="matrix(0.77869459,0,0,0.77869445,3.548264,36.597628)"
id="g3797-9-5-5"
style="stroke-width:1.2842">
<path
style="fill:none;stroke:#2e0000;stroke-width:2.56841;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-71-6-6"
d="M -26.310778,5.3580033 A 8.3519646,8.3515832 0.02039876 1 1 -13.623399,16.222662 8.3519646,8.3515832 0.02039876 1 1 -26.310778,5.3580033 Z" />
<path
style="fill:url(#linearGradient9);fill-opacity:1;stroke:#ef2929;stroke-width:2.56842;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4250-7-3-2-2"
d="m -24.358888,7.0362241 a 5.782493,5.7773415 0 1 1 8.784093,7.5158349 5.782493,5.7773415 0 0 1 -8.784093,-7.5158349 z" />
</g>
</g>
<g
id="layer3"
style="display:inline">
<g
id="layer1">
<g
id="g3491"
transform="matrix(0.1649204,0,0,0.1649204,-643.71716,-232.01446)">
<path
id="text2714"
d="m 4163.931,1667.5588 h -109.1436 l -33.4751,40.7475 c -3.5156,5.2084 -2.3852,4.766 -2.9061,7.7608 0,6.0635 3.9239,9.9135 12.1271,12.127 v 12.1271 h -97.0165 v -12.1271 c 5.4688,-1.0416 14.9354,-3.4593 18.1906,-6.0635 3.2552,-2.6042 6.0635,-6.0635 12.1271,-12.1271 l 139.4611,-200.0965 c 4.4269,-6.5102 5.2821,-8.2206 6.0636,-12.1271 1.3019,-5.9893 0.9676,-9.5226 -6.0636,-12.127 v -12.1271 h 97.0165 v 12.1271 c -12.127,0 -17.1492,0.595 -18.1906,6.0635 -0.7814,3.7763 -0.6531,6.3037 0.9097,11.1211 l 60.3514,205.4688 c 2.6039,8.724 2.8333,10.5692 5.4378,13.8243 2.6038,3.1251 6.9183,5.0219 12.1271,6.0635 v 12.1271 h -97.0165 v -12.1271 c 8.984,-1.4322 10.8247,-5.4864 12.127,-12.127 0.5207,-2.7344 1.5623,-6.7885 0,-12.1271 l -12.127,-36.3812 m 0,-36.3812 -24.2542,-97.0165 -72.7623,97.0165 h 97.0165"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:url(#linearGradient3009);fill-opacity:1;stroke:#2e0000;stroke-width:12.1271;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="m 4171.6736,1655.4317 h -122.0514 l -36.1634,43.7572 c -8.9781,11.6058 -6.5744,17.6706 -7.5981,18.9453 0,5.6501 9.2106,8.5485 16.6543,10.6111 l 1.2403,-0.1379 -78.1114,-0.4134 -3.7208,2.894 c 4.9624,-0.9706 23.3368,-5.4284 26.2906,-7.855 2.9538,-2.4267 7.4313,-8.4063 12.2445,-15.4346 l 117.1771,-168.125 c 4.017,-6.0664 15.8176,-22.4055 19.0072,-28.5262 3.3863,-5.5809 9.1466,-22.5163 2.7664,-24.9431 l -10.1979,-0.5513 h 85.1396 l -13.0918,-0.6889 c -11.004,0 -10.7379,5.2398 -11.5452,9.233 -0.7089,3.5189 0.6477,7.8032 2.0659,12.2922 l 55.59,189.1168 c 2.3628,8.1291 2.4331,9.7107 3.832,14.9489 3.6029,9.9401 1.3166,12.3967 6.043,13.3673 l 17.6394,4.272 h -84.8894 l 12.5404,-6.2013 c 4.9827,-8.914 5.1626,-6.4905 6.3444,-12.6783 0.4724,-2.548 -2.579,-12.6647 -3.9966,-17.6393 l -12.9335,-35.4166 m 4.4614,-18.4109 -33.3601,-122.5662 -89.8152,122.4284 122.0295,0.024"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#ef2929;stroke-width:12.1271;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="m 4163.931,1667.5588 h -109.1436 l -33.4751,40.7475 c -3.5156,5.2084 -2.3852,4.766 -2.9061,7.7608 0,6.0635 3.9239,9.9135 12.1271,12.127 v 12.1271 h -97.0165 v -12.1271 c 5.4688,-1.0416 14.9354,-3.4593 18.1906,-6.0635 3.2552,-2.6042 6.0635,-6.0635 12.1271,-12.1271 l 139.4611,-200.0965 c 4.4269,-6.5102 5.2821,-8.2206 6.0636,-12.1271 1.3019,-5.9893 0.9676,-9.5226 -6.0636,-12.127 v -12.1271 h 97.0165 v 12.1271 c -12.127,0 -17.1492,0.595 -18.1906,6.0635 -0.7814,3.7763 -0.6531,6.3037 0.9097,11.1211 l 60.3514,205.4688 c 2.6039,8.724 2.8333,10.5692 5.4378,13.8243 2.6038,3.1251 6.9183,5.0219 12.1271,6.0635 v 12.1271 h -97.0165 v -12.1271 c 8.984,-1.4322 10.8247,-5.4864 12.127,-12.127 0.5206,-2.7344 1.5623,-6.7885 0,-12.1271 l -12.127,-36.3812 m 0,-36.3812 -24.2542,-97.0165 -72.7623,97.0165 h 97.0165"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#280000;stroke-width:12.1271;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</g>
<path
id="text2714"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08587,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:url(#linearGradient3009);fill-opacity:1;stroke:#2e0000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="M 44.276919,40.999998 H 24.148153 l -5.964082,7.216455 c -1.480672,1.914033 -1.084253,2.914242 -1.253082,3.124467 0,0.931816 1.519016,1.409822 2.746634,1.749986 l 0.204551,-0.02274 -12.8821638,-0.06818 -0.6136358,0.477279 c 0.818401,-0.160071 3.8487146,-0.895254 4.3358566,-1.295449 0.487142,-0.400213 1.225573,-1.386371 2.019368,-2.545481 L 32.066493,21.909093 c 0.662485,-1.000473 2.608645,-3.695124 3.134675,-4.704552 0.55847,-0.920405 1.508461,-3.713398 0.456236,-4.113626 l -1.681842,-0.09092 H 48.016819 L 45.857714,12.88638 c -1.814784,0 -1.770899,0.86415 -1.904039,1.52271 -0.116912,0.580339 0.106819,1.286907 0.340709,2.027235 l 9.167925,31.189218 c 0.389674,1.340655 0.401268,1.601493 0.631975,2.465379 0.594192,1.639325 0.217134,2.044469 0.996614,2.20454 l 2.909097,0.70454 H 44.000001 l 2.068168,-1.022721 c 0.821749,-1.4701 0.851418,-1.070415 1.046321,-2.09091 0.07791,-0.420217 -0.42533,-2.088667 -0.659121,-2.90908 l -2.132998,-5.84092 m 0.735776,-3.036333 -5.501761,-20.213667 -14.812359,20.190941 20.125154,0.004"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#ffffff;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08586,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#280000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 11 KiB

@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="64"
height="64"
id="svg2869"
version="1.1"
viewBox="0 0 64 64"
sodipodi:docname="Sketcher_CreateText_Constr.svg"
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="10.570313"
inkscape:cx="-24.691795"
inkscape:cy="23.509238"
inkscape:window-width="3840"
inkscape:window-height="1571"
inkscape:window-x="-9"
inkscape:window-y="-9"
inkscape:window-maximized="1"
inkscape:current-layer="svg2869" />
<defs
id="defs2871">
<linearGradient
id="linearGradient5">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop19" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop20" />
</linearGradient>
<linearGradient
id="swatch18">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop18" />
</linearGradient>
<linearGradient
id="swatch15">
<stop
style="stop-color:#3d0000;stop-opacity:1;"
offset="0"
id="stop15" />
</linearGradient>
<linearGradient
id="linearGradient5-1">
<stop
style="stop-color:#ef2929;stop-opacity:1;"
offset="0"
id="stop5" />
<stop
style="stop-color:#ef2929;stop-opacity:0;"
offset="1"
id="stop6" />
</linearGradient>
<linearGradient
id="linearGradient3836-9">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1" />
</linearGradient>
<linearGradient
id="linearGradient3836-9-3">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-5" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-6" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082"
xlink:href="#linearGradient3836-9-3" />
<linearGradient
id="linearGradient3836-9-7">
<stop
style="stop-color:#a40000;stop-opacity:1"
offset="0"
id="stop3838-8-0" />
<stop
style="stop-color:#ef2929;stop-opacity:1"
offset="1"
id="stop3840-1-9" />
</linearGradient>
<linearGradient
y2="5"
x2="-22"
y1="18"
x1="-18"
gradientUnits="userSpaceOnUse"
id="linearGradient3082-3"
xlink:href="#linearGradient3836-9-7" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient9"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.82607043,0,0,0.82533448,-4.0098079,1.346708)" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.76342439,0,0,0.75750425,-4.596389,2.7525637)" />
<linearGradient
xlink:href="#linearGradient3836-9-3"
id="linearGradient3801-1-3-2"
gradientUnits="userSpaceOnUse"
x1="-18"
y1="18"
x2="-22"
y2="5"
gradientTransform="matrix(0.84956703,0,0,0.84301394,-2.927337,1.7790378)" />
<linearGradient
xlink:href="#linearGradient3838"
id="linearGradient3844"
x1="36"
y1="1039.3622"
x2="32"
y2="1003.3622"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(2.0563921e-6,-988.36218)" />
<linearGradient
id="linearGradient3838">
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="0"
id="stop3840" />
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="1"
id="stop3842" />
</linearGradient>
<linearGradient
xlink:href="#linearGradient3830"
id="linearGradient3836"
x1="36"
y1="1037.3622"
x2="32"
y2="1005.3622"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(2.0563921e-6,-988.36218)" />
<linearGradient
id="linearGradient3830">
<stop
style="stop-color:#ffffff;stop-opacity:1;"
offset="0"
id="stop3832" />
<stop
style="stop-color:#d3d7cf;stop-opacity:1;"
offset="1"
id="stop3834" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.1649204,0,0,0.1649204,-641.71718,-230.01448)"
y2="1734.2576"
x2="4157.8677"
y1="1473.5258"
x1="4115.4229"
gradientUnits="userSpaceOnUse"
id="linearGradient3009"
xlink:href="#linearGradient3010" />
<linearGradient
id="linearGradient3010">
<stop
id="stop3012"
offset="0"
style="stop-color:#d3d7cf;stop-opacity:1;" />
<stop
id="stop3014"
offset="1"
style="stop-color:#d3d7cf;stop-opacity:1;" />
</linearGradient>
</defs>
<metadata
id="metadata2874">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>[maxwxyz]</dc:title>
</cc:Agent>
</dc:creator>
<dc:relation>https://www.freecad.org/wiki/index.php?title=Artwork</dc:relation>
<dc:publisher>
<cc:Agent>
<dc:title>FreeCAD</dc:title>
</cc:Agent>
</dc:publisher>
<dc:identifier>FreeCAD/src/Mod/Sketcher/Gui/Resources/icons/Sketcher_CreateArc.svg</dc:identifier>
<dc:rights>
<cc:Agent>
<dc:title>FreeCAD LGPL2+</dc:title>
</cc:Agent>
</dc:rights>
<dc:date>2023-12-19</dc:date>
</cc:Work>
</rdf:RDF>
</metadata>
<path
id="text2714"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08587,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:#3465a4;fill-opacity:1;stroke:#2e0000;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-1"
d="M 44.276919,40.999998 H 24.148153 l -5.964082,7.216455 c -1.480672,1.914033 -1.084253,2.914242 -1.253082,3.124467 0,0.931816 1.519016,1.409822 2.746634,1.749986 l 0.204551,-0.02274 -12.8821638,-0.06818 -0.6136358,0.477279 c 0.818401,-0.160071 3.8487146,-0.895254 4.3358566,-1.295449 0.487142,-0.400213 1.225573,-1.386371 2.019368,-2.545481 L 32.066493,21.909093 c 0.662485,-1.000473 2.608645,-3.695124 3.134675,-4.704552 0.55847,-0.920405 1.508461,-3.713398 0.456236,-4.113626 l -1.681842,-0.09092 H 48.016819 L 45.857714,12.88638 c -1.814784,0 -1.770899,0.86415 -1.904039,1.52271 -0.116912,0.580339 0.106819,1.286907 0.340709,2.027235 l 9.167925,31.189218 c 0.389674,1.340655 0.401268,1.601493 0.631975,2.465379 0.594192,1.639325 0.217134,2.044469 0.996614,2.20454 l 2.909097,0.70454 H 44.000001 l 2.068168,-1.022721 c 0.821749,-1.4701 0.851418,-1.070415 1.046321,-2.09091 0.07791,-0.420217 -0.42533,-2.088667 -0.659121,-2.90908 l -2.132998,-5.84092 m 0.735776,-3.036333 -5.501761,-20.213667 -14.812359,20.190941 20.125154,0.004"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#729fcf;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="text2714-7"
d="M 43.000006,43.000004 H 25 l -5.520727,6.720094 c -0.579794,0.858972 -0.393368,0.786011 -0.479275,1.279915 0,0.999994 0.647131,1.634938 2.000006,1.999989 v 2.000006 H 5.0000041 V 53.000002 C 5.9019207,52.828221 7.4631562,52.429493 8.0000051,52.000007 8.536854,51.570522 8.9999999,51.000013 10.000011,50.000001 L 32.999992,17.000006 C 33.730078,15.926342 33.871118,15.644262 34.000003,15 c 0.21471,-0.987758 0.159577,-1.570471 -1.000011,-1.999989 v -2.000007 h 16 v 2.000007 c -1.99999,0 -2.828253,0.09813 -3.000001,0.999994 -0.128869,0.622789 -0.10771,1.039609 0.150028,1.834097 l 9.953177,33.885996 c 0.429436,1.438766 0.467269,1.743077 0.896804,2.279909 0.42942,0.515393 1.140969,0.828214 2.000006,0.999995 v 2.000006 h -16 v -2.000006 c 1.481645,-0.236199 1.785214,-0.904819 1.99999,-1.999989 0.08586,-0.450959 0.257655,-1.119563 0,-2.000007 l -1.99999,-6.000002 m 0,-6.000002 -4.000012,-16 -11.999988,16 h 16"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:40px;font-family:'Copperplate Gothic Bold';-inkscape-font-specification:'Copperplate Gothic Bold, Bold';fill:none;stroke:#091425;stroke-width:2.00001;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

@@ -26,7 +26,7 @@
#include <Inventor/events/SoKeyboardEvent.h>
#include <QApplication>
#include <QEvent>
#include <QLineEdit>
#include "ui_SketcherToolDefaultWidget.h"
#include <Gui/Application.h>
@@ -56,6 +56,10 @@ SketcherToolDefaultWidget::SketcherToolDefaultWidget(QWidget* parent)
{
ui->setupUi(this);
ui->comboBox1->setMaxVisibleItems(25);
ui->comboBox2->setMaxVisibleItems(25);
ui->comboBox3->setMaxVisibleItems(25);
// connecting the needed signals
setupConnections();
@@ -66,6 +70,9 @@ SketcherToolDefaultWidget::SketcherToolDefaultWidget(QWidget* parent)
ui->parameterFive->installEventFilter(this);
ui->parameterSix->installEventFilter(this);
ui->lineEdit1->installEventFilter(this);
ui->lineEdit2->installEventFilter(this);
reset();
}
@@ -155,6 +162,8 @@ void SketcherToolDefaultWidget::setupConnections()
this,
&SketcherToolDefaultWidget::comboBox3_currentIndexChanged
);
connect(ui->lineEdit1, &QLineEdit::textChanged, this, &SketcherToolDefaultWidget::lineEdit1_textChanged);
connect(ui->lineEdit2, &QLineEdit::textChanged, this, &SketcherToolDefaultWidget::lineEdit2_textChanged);
}
// preselect the number of the spinbox when it gets the focus.
@@ -179,6 +188,13 @@ bool SketcherToolDefaultWidget::eventFilter(QObject* object, QEvent* event)
return true;
}
}
for (int i = 0; i < nLineEdit; i++) {
if (object == getLineEdit(i)) {
signalParameterTabOrEnterPressed(i);
return true;
}
}
}
}
@@ -206,6 +222,11 @@ void SketcherToolDefaultWidget::reset()
setComboboxIndex(i, 0);
getComboBox(i)->clear();
}
for (int i = 0; i < nLineEdit; i++) {
setLineEditVisible(i, false);
QString str;
setLineEditText(i, str);
}
setNoticeVisible(false);
}
@@ -735,21 +756,18 @@ void SketcherToolDefaultWidget::comboBox1_currentIndexChanged(int val)
if (!blockParameterSlots) {
signalComboboxSelectionChanged(Combobox::FirstCombo, val);
}
ui->comboBox1->onSave();
}
void SketcherToolDefaultWidget::comboBox2_currentIndexChanged(int val)
{
if (!blockParameterSlots) {
signalComboboxSelectionChanged(Combobox::SecondCombo, val);
}
ui->comboBox2->onSave();
}
void SketcherToolDefaultWidget::comboBox3_currentIndexChanged(int val)
{
if (!blockParameterSlots) {
signalComboboxSelectionChanged(Combobox::ThirdCombo, val);
}
ui->comboBox3->onSave();
}
void SketcherToolDefaultWidget::initNComboboxes(int ncombobox)
@@ -833,6 +851,116 @@ int SketcherToolDefaultWidget::getComboboxIndex(int comboboxindex)
THROWM(Base::IndexError, "ToolWidget combobox index out of range");
}
QString SketcherToolDefaultWidget::getComboboxCurrentText(int comboboxindex)
{
if (comboboxindex < nCombobox) {
return getComboBox(comboboxindex)->currentText();
}
THROWM(Base::IndexError, "ToolWidget combobox index out of range");
}
int SketcherToolDefaultWidget::setComboboxCurrentText(int comboboxindex, const QString& text)
{
if (comboboxindex < nCombobox) {
int index = getComboBox(comboboxindex)->findText(text, Qt::MatchFixedString);
if (index != -1) {
getComboBox(comboboxindex)->setCurrentIndex(index);
}
return index;
}
THROWM(Base::IndexError, "ToolWidget combobox index out of range");
}
void SketcherToolDefaultWidget::lineEdit1_textChanged(const QString& text)
{
if (!blockParameterSlots) {
signalLineEditTextChanged(LineEdit::FirstEdit, text);
}
}
void SketcherToolDefaultWidget::lineEdit2_textChanged(const QString& text)
{
if (!blockParameterSlots) {
signalLineEditTextChanged(LineEdit::SecondEdit, text);
}
}
void SketcherToolDefaultWidget::initNLineEdits(int nlineedit)
{
Base::StateLocker lock(blockParameterSlots, true);
for (int i = 0; i < nLineEdit; ++i) {
setLineEditVisible(i, i < nlineedit);
QString str;
setLineEditText(i, str);
}
}
void SketcherToolDefaultWidget::setLineEditVisible(int lineeditindex, bool visible)
{
if (lineeditindex < nLineEdit) {
getLineEdit(lineeditindex)->setVisible(visible);
getLineEditLabel(lineeditindex)->setVisible(visible);
}
}
void SketcherToolDefaultWidget::setLineEditText(int lineeditindex, const QString& text)
{
if (lineeditindex < nLineEdit) {
getLineEdit(lineeditindex)->setText(text);
return;
}
THROWM(Base::IndexError, "ToolWidget line edit index out of range");
}
void SketcherToolDefaultWidget::setLineEditLabel(int lineeditindex, const QString& string)
{
if (lineeditindex < nLineEdit) {
getLineEditLabel(lineeditindex)->setText(string);
}
}
void SketcherToolDefaultWidget::setLineEditFocus(int lineeditindex)
{
if (lineeditindex < nLineEdit) {
QLineEdit* lineEdit = getLineEdit(lineeditindex);
lineEdit->setFocus(Qt::OtherFocusReason);
lineEdit->selectAll();
return;
}
THROWM(Base::IndexError, "ToolWidget line edit index out of range");
}
QString SketcherToolDefaultWidget::getLineEditText(int lineeditindex)
{
if (lineeditindex < nLineEdit) {
return getLineEdit(lineeditindex)->text();
}
THROWM(Base::IndexError, "ToolWidget line edit index out of range");
}
QLabel* SketcherToolDefaultWidget::getLineEditLabel(int lineeditindex)
{
switch (lineeditindex) {
case LineEdit::FirstEdit:
return ui->lineEditLabel1;
case LineEdit::SecondEdit:
return ui->lineEditLabel2;
default:
THROWM(Base::IndexError, "ToolWidget line edit index out of range");
}
}
QLineEdit* SketcherToolDefaultWidget::getLineEdit(int lineeditindex)
{
switch (lineeditindex) {
case LineEdit::FirstEdit:
return ui->lineEdit1;
case LineEdit::SecondEdit:
return ui->lineEdit2;
default:
THROWM(Base::IndexError, "ToolWidget line edit index out of range");
}
}
void SketcherToolDefaultWidget::changeEvent(QEvent* ev)
{
@@ -33,6 +33,7 @@
class QComboBox;
class QLineEdit;
namespace App
{
@@ -102,6 +103,14 @@ public:
nCombobox // Must Always be the last one
};
/// LineEdit number/label
enum LineEdit
{
FirstEdit,
SecondEdit,
nLineEdit // Must Always be the last one
};
explicit SketcherToolDefaultWidget(QWidget* parent = nullptr);
~SketcherToolDefaultWidget() override;
@@ -149,11 +158,20 @@ public:
void setComboboxIndex(int comboboxindex, int value);
void setComboboxLabel(int comboboxindex, const QString& string);
int getComboboxIndex(int comboboxindex);
QString getComboboxCurrentText(int comboboxindex);
int setComboboxCurrentText(int comboboxIndex, const QString& text);
void setComboboxElements(int comboboxindex, const QStringList& names);
void setComboboxItemIcon(int comboboxindex, int index, QIcon icon);
void setComboboxPrefEntry(int comboboxindex, const std::string& prefEntry);
void restoreComboboxPref(int comboboxindex);
void initNLineEdits(int nlineedit);
void setLineEditVisible(int lineeditindex, bool visible);
void setLineEditText(int lineeditindex, const QString& text);
void setLineEditLabel(int lineeditindex, const QString& string);
QString getLineEditText(int lineeditindex);
void setLineEditFocus(int lineeditindex);
template<typename F>
fastsignals::advanced_connection registerParameterTabOrEnterPressed(F&& fn)
{
@@ -181,6 +199,11 @@ public:
return signalComboboxSelectionChanged.connect(std::forward<F>(fn), fastsignals::advanced_tag());
}
template<typename F>
fastsignals::advanced_connection registerLineEditTextChanged(F&& fn)
{
return signalLineEditTextChanged.connect(std::forward<F>(fn), fastsignals::advanced_tag());
}
// Q_SIGNALS:
protected Q_SLOTS:
@@ -201,6 +224,8 @@ protected Q_SLOTS:
void comboBox1_currentIndexChanged(int val);
void comboBox2_currentIndexChanged(int val);
void comboBox3_currentIndexChanged(int val);
void lineEdit1_textChanged(const QString& text);
void lineEdit2_textChanged(const QString& text);
protected:
void changeEvent(QEvent* ev) override;
@@ -212,6 +237,8 @@ private:
Gui::PrefCheckBox* getCheckBox(int checkboxindex);
Gui::PrefComboBox* getComboBox(int comboboxindex);
QLabel* getComboBoxLabel(int comboboxindex);
QLabel* getLineEditLabel(int lineeditindex);
QLineEdit* getLineEdit(int lineeditindex);
void setParameterFontStyle(int parameterindex, FontStyle fontStyle);
@@ -224,6 +251,7 @@ private:
fastsignals::signal<void(int parameterindex, double value)> signalParameterValueChanged;
fastsignals::signal<void(int checkboxindex, bool value)> signalCheckboxCheckedChanged;
fastsignals::signal<void(int comboindex, int value)> signalComboboxSelectionChanged;
fastsignals::signal<void(int lineeditindex, const QString& text)> signalLineEditTextChanged;
/// lock to block QT slots
bool blockParameterSlots;
@@ -86,6 +86,34 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="lineEditLayout1">
<item>
<widget class="QLabel" name="lineEditLabel1">
<property name="text">
<string>Line edit 1</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit1"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="lineEditLayout2">
<item>
<widget class="QLabel" name="lineEditLabel2">
<property name="text">
<string>Line edit 2</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit2"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
@@ -52,6 +52,7 @@
#include <Mod/Sketcher/App/SketchObject.h>
#include "EditDatumDialog.h"
#include "EditTextDialog.h"
#include "TaskSketcherConstraints.h"
#include "Utils.h"
#include "ViewProviderSketch.h"
@@ -318,6 +319,8 @@ public:
// Gui::BitmapFactory().iconFromTheme("Constraint_Ellipse_Axis_Angle") );
static QIcon equal(Gui::BitmapFactory().iconFromTheme("Constraint_EqualLength"));
static QIcon pntoo(Gui::BitmapFactory().iconFromTheme("Constraint_PointOnObject"));
static QIcon group(Gui::BitmapFactory().iconFromTheme("Constraint_Group"));
static QIcon text(Gui::BitmapFactory().iconFromTheme("Constraint_Text"));
static QIcon symm(Gui::BitmapFactory().iconFromTheme("Constraint_Symmetric"));
static QIcon snell(Gui::BitmapFactory().iconFromTheme("Constraint_SnellsLaw"));
static QIcon iaellipseminoraxis(Gui::BitmapFactory().iconFromTheme(
@@ -349,7 +352,7 @@ public:
auto selicon = [this](const Sketcher::Constraint* constr,
const QIcon& normal,
const QIcon& driven) -> QIcon {
if (!constr->isActive) {
if (!sketch->isConstraintActiveInSketch(constr)) {
QIcon darkIcon;
int w = listWidget()->style()->pixelMetric(QStyle::PM_ListViewIconSize);
darkIcon.addPixmap(normal.pixmap(w, w, QIcon::Disabled, QIcon::Off),
@@ -378,6 +381,10 @@ public:
return selicon(constraint, block, block);
case Sketcher::PointOnObject:
return selicon(constraint, pntoo, pntoo);
case Sketcher::Group:
return selicon(constraint, group, group);
case Sketcher::Text:
return selicon(constraint, text, text);
case Sketcher::Parallel:
return selicon(constraint, para, para);
case Sketcher::Perpendicular:
@@ -461,6 +468,8 @@ public:
case Sketcher::Tangent:
case Sketcher::Equal:
case Sketcher::Symmetric:
case Sketcher::Group:
case Sketcher::Text:
return true;
case Sketcher::Distance:
case Sketcher::DistanceX:
@@ -825,6 +834,10 @@ ConstraintFilterList::ConstraintFilterList(QWidget* parent)
it->setCheckState(isChecked ? Qt::Checked : Qt::Unchecked);
filterState = filterState >> 1;// shift right to get rid of the used bit.
}
// Text constraint filter is hidden from the user
item(static_cast<int>(ConstraintFilter::FilterValue::Text))->setHidden(true);
languageChange();
setPartiallyChecked();
@@ -1261,6 +1274,11 @@ void TaskSketcherConstraints::onListWidgetConstraintsItemActivated(QListWidgetIt
editDatumDialog->exec(false);
delete editDatumDialog;
}
else if (it->constraintType() == Sketcher::Text) {
auto* editDialog = new EditTextDialog(this->sketchView, it->ConstraintNbr);
editDialog->exec();
delete editDialog;
}
}
void TaskSketcherConstraints::onListWidgetConstraintsItemChanged(QListWidgetItem* item)
@@ -1694,6 +1712,10 @@ bool TaskSketcherConstraints::isConstraintFiltered(QListWidgetItem* item)
ConstraintItem* it = static_cast<ConstraintItem*>(item);
const Sketcher::Constraint* constraint = vals[it->ConstraintNbr];
// Text constraint is hidden from the list widget.
if (constraint->Type == Sketcher::Text) {
return true;
}
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Sketcher");
@@ -1716,6 +1738,12 @@ bool TaskSketcherConstraints::isConstraintFiltered(QListWidgetItem* item)
case Sketcher::PointOnObject:
visible = checkFilterBitset(multiFilterStatus, FilterValue::PointOnObject);
break;
case Sketcher::Group:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Group);
break;
case Sketcher::Text:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Text);
break;
case Sketcher::Parallel:
visible = checkFilterBitset(multiFilterStatus, FilterValue::Parallel);
break;
@@ -119,6 +119,8 @@ private:
{QT_TR_NOOP("Equality"), 1},
{QT_TR_NOOP("Symmetric"), 1},
{QT_TR_NOOP("Block"), 1},
{QT_TR_NOOP("Group"), 1},
{QT_TR_NOOP("Text"), 1},
{QT_TR_NOOP("Internal Alignment"), 1},
{QT_TR_NOOP("Datums"), 0},
{QT_TR_NOOP("Horizontal Distance"), 1},
+210 -86
View File
@@ -54,6 +54,7 @@
#include <Mod/Sketcher/App/SketchObject.h>
#include "TaskSketcherElements.h"
#include "EditTextDialog.h"
#include "Utils.h"
#include "ViewProviderSketch.h"
#include "ui_TaskSketcherElements.h"
@@ -84,6 +85,8 @@ QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Symmetric Constraint");
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Block Constraint");
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Group Constraint");
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Lock Position");
QT_TRANSLATE_NOOP("SketcherGui::ElementView", "Horizontal Dimension");
@@ -201,7 +204,8 @@ public:
Base::Type geometryType,
GeometryState state,
const QString& lab,
ViewProviderSketch* sketchView
ViewProviderSketch* sketchView,
bool isTextHandle = false
)
: ElementNbr(elementnr)
, StartingVertex(startingVertex)
@@ -216,6 +220,7 @@ public:
, clickedOn(SubElementType::none)
, hovered(SubElementType::none)
, rightClicked(false)
, isTextHandle(isTextHandle)
, label(lab)
, sketchView(sketchView)
{
@@ -316,6 +321,7 @@ public:
SubElementType clickedOn;
SubElementType hovered;
bool rightClicked;
bool isTextHandle;
QString label;
@@ -374,11 +380,12 @@ public:
static const QIcon&
getIcon(Base::Type type, Sketcher::PointPos pos,
ElementItem::GeometryState icontype = ElementItem::GeometryState::Normal)
ElementItem::GeometryState icontype = ElementItem::GeometryState::Normal,
bool isTextHandle = false)
{
static ElementWidgetIcons elementicons;
return elementicons.getIconImpl(type, pos, icontype);
return elementicons.getIconImpl(type, pos, icontype, isTextHandle);
}
private:
@@ -502,11 +509,34 @@ private:
{Sketcher::PointPos::none,
getMultIcon("Sketcher_Element_SelectionTypeInvalid")},
}));
// Text Handle Icons
textIcons[Sketcher::PointPos::none] = Gui::BitmapFactory().iconFromTheme("Sketcher_CreateText");
textIcons[Sketcher::PointPos::start] = Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Text_StartPoint");
textIcons[Sketcher::PointPos::end] = Gui::BitmapFactory().iconFromTheme("Sketcher_Element_Text_EndPoint");
}
const QIcon& getIconImpl(Base::Type type, Sketcher::PointPos pos,
ElementItem::GeometryState icontype)
ElementItem::GeometryState icontype,
bool isTextHandle)
{
if (isTextHandle) {
auto it = textIcons.find(pos);
if (it != textIcons.end()) {
return it->second;
}
// Fallback for Midpoint or other positions: use normal line icon without blue filter
if (type == Part::GeomLineSegment::getClassTypeId()) {
auto typekey = icons.find(type);
if (typekey != icons.end()) {
auto poskey = typekey->second.find(pos);
if (poskey != typekey->second.end()) {
// Return the 'Normal' variant (index 0) to avoid blue construction color
return std::get<0>(poskey->second);
}
}
}
}
auto typekey = icons.find(type);
@@ -582,6 +612,7 @@ private:
private:
std::map<Base::Type, std::map<Sketcher::PointPos, std::tuple<QIcon, QIcon, QIcon, QIcon>>>
icons;
std::map<Sketcher::PointPos, QIcon> textIcons;
};
ElementView::ElementView(QWidget* parent)
@@ -717,6 +748,14 @@ void ElementView::contextMenuEvent(QContextMenuEvent* event)
QMenu menu;
QList<QListWidgetItem*> items = selectedItems();
if (items.size() == 1) {
ElementItem* item = static_cast<ElementItem*>(items.first());
if (item->isTextHandle) {
menu.addSeparator();
menu.addAction(tr("Convert to geometries"), this, SLOT(doConvertToGeometries()));
}
}
// NOTE: If extending this context menu, be sure to add the items to the translation block at
// the top of this file
@@ -768,7 +807,8 @@ void ElementView::contextMenuEvent(QContextMenuEvent* event)
true)
CONTEXT_ITEM(
"Constraint_Block", "Block Constraint", "Sketcher_ConstrainBlock", doBlockConstraint, true)
CONTEXT_ITEM(
"Constraint_Group", "Group constraint", "Sketcher_ConstrainGroup", doGroupConstraint, true)
CONTEXT_ITEM("Constraint_HorizontalDistance",
"Horizontal Dimension",
"Sketcher_ConstrainDistanceX",
@@ -886,6 +926,7 @@ CONTEXT_MEMBER_DEF("Sketcher_ConstrainTangent", doTangentConstraint)
CONTEXT_MEMBER_DEF("Sketcher_ConstrainEqual", doEqualConstraint)
CONTEXT_MEMBER_DEF("Sketcher_ConstrainSymmetric", doSymmetricConstraint)
CONTEXT_MEMBER_DEF("Sketcher_ConstrainBlock", doBlockConstraint)
CONTEXT_MEMBER_DEF("Sketcher_ConstrainGroup", doGroupConstraint)
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistanceX", doHorizontalDistance)
CONTEXT_MEMBER_DEF("Sketcher_ConstrainDistanceY", doVerticalDistance)
@@ -939,6 +980,25 @@ ElementItem* ElementView::itemFromIndex(const QModelIndex& index)
return static_cast<ElementItem*>(QListWidget::itemFromIndex(index));
}
void ElementView::doConvertToGeometries()
{
QList<QListWidgetItem*> items = selectedItems();
if (items.isEmpty())
return;
ElementItem* item = static_cast<ElementItem*>(items.first());
if (!item->isTextHandle)
return;
int geoId = item->ElementNbr;
Sketcher::SketchObject* sketch = item->getSketchObject();
// Deleting the handle geometry will automatically remove the Text constraint
// (turning it to None), leaving the generated text geometries intact.
Gui::Selection().clearSelection();
sketch->delGeometry(geoId);
}
// clang-format on
/* ElementItem delegate ---------------------------------------------------- */
ElementItemDelegate::ElementItemDelegate(ElementView* parent)
@@ -1033,7 +1093,8 @@ void ElementItemDelegate::drawSubControl(
auto isHovered = rect.contains(mousePos);
auto drawSelectIcon = [&](Sketcher::PointPos pos) {
auto icon = ElementWidgetIcons::getIcon(item->GeometryType, pos, item->State);
auto icon
= ElementWidgetIcons::getIcon(item->GeometryType, pos, item->State, item->isTextHandle);
auto isOptionSelected = option.state & QStyle::State_Selected;
auto isOptionHovered = option.state & QStyle::State_MouseOver;
@@ -1322,6 +1383,10 @@ void TaskSketcherElements::connectSignals()
&ElementView::onItemHovered,
this,
&TaskSketcherElements::onListWidgetElementsMouseMoveOnItem);
QObject::connect(ui->listWidgetElements,
&ElementView::itemActivated,
this,
&TaskSketcherElements::onListWidgetItemActivated);
QObject::connect(filterList,
&QListWidget::itemChanged,
this,
@@ -1419,10 +1484,16 @@ void TaskSketcherElements::onListMultiFilterItemChanged(QListWidgetItem* item)
updateVisibility();
}
void TaskSketcherElements::setItemVisibility(QListWidgetItem* it)
void TaskSketcherElements::setItemVisibility(QListWidgetItem* it, const std::set<int>& groupedGeoIds)
{
auto* item = static_cast<ElementItem*>(it);
// First, check if the item is a member of a group. If so, it must be hidden.
if (groupedGeoIds.count(item->ElementNbr)) {
item->setHidden(true);
return;
}
if (ui->filterBox->checkState() == Qt::Unchecked) {
item->setHidden(false);
return;
@@ -1477,8 +1548,20 @@ void TaskSketcherElements::setItemVisibility(QListWidgetItem* it)
void TaskSketcherElements::updateVisibility()
{
// Calculate the set of grouped geometries that should be hidden.
std::set<int> groupedGeoIds;
const auto& constraints = sketchView->getSketchObject()->Constraints.getValues();
for (const auto* c : constraints) {
if (c->Type == Sketcher::Group || c->Type == Sketcher::Text) {
// Member geometries start from index 1.
for (int j = 1; c->hasElement(j); ++j) {
groupedGeoIds.insert(c->getGeoId(j));
}
}
}
for (int i = 0; i < ui->listWidgetElements->count(); i++) {
setItemVisibility(ui->listWidgetElements->item(i));
setItemVisibility(ui->listWidgetElements->item(i), groupedGeoIds);
}
}
@@ -1859,6 +1942,34 @@ void TaskSketcherElements::onListWidgetElementsMouseMoveOnItem(QListWidgetItem*
previouslyHoveredType = item->hovered;
}
void TaskSketcherElements::onListWidgetItemActivated(QListWidgetItem* item)
{
auto* elementItem = static_cast<ElementItem*>(item);
if (!elementItem) {
return;
}
Sketcher::SketchObject* sketch = sketchView->getSketchObject();
int geoId = elementItem->ElementNbr;
const auto& constraints = sketch->Constraints.getValues();
for (size_t i = 0; i < constraints.size(); ++i) {
const auto* constraint = constraints[i];
// Check if the constraint is a Text constraint and if the activated element
// is its handle (the geometry at index 0).
if (constraint->Type == Sketcher::Text && constraint->hasElement(0)) {
if (constraint->getGeoId(0) == geoId) {
// The item is a handle for a text constraint. Open the edit dialog.
auto* editDialog = new EditTextDialog(this->sketchView, i);
editDialog->exec();
delete editDialog;
return;
}
}
}
}
void TaskSketcherElements::leaveEvent(QEvent* event)
{
Q_UNUSED(event);
@@ -1871,6 +1982,23 @@ void TaskSketcherElements::slotElementsChanged()
assert(sketchView);
// Build up ListView with the elements
Sketcher::SketchObject* sketch = sketchView->getSketchObject();
// Pre-process constraints to identify grouped elements and their handles
const auto& constraints = sketch->Constraints.getValues();
std::set<int> groupedGeoIds;
std::map<int, Sketcher::ConstraintType> handleIdToType;
for (const auto* c : constraints) {
if (c->Type == Sketcher::Group || c->Type == Sketcher::Text) {
if (c->hasElement(0)) {
handleIdToType[c->getGeoId(0)] = c->Type;
}
// Elements from index 1 onwards are the members.
for (int j = 1; c->hasElement(j); ++j) {
groupedGeoIds.insert(c->getGeoId(j));
}
}
}
const std::vector<Part::Geometry*>& vals = sketch->Geometry.getValues();
ui->listWidgetElements->clear();
@@ -1880,15 +2008,14 @@ void TaskSketcherElements::slotElementsChanged()
using GeometryState = ElementItem::GeometryState;
int i = 1;
for (std::vector<Part::Geometry*>::const_iterator it = vals.begin(); it != vals.end();
++it, ++i) {
Base::Type type = (*it)->getTypeId();
for (auto* geo : vals) {
Base::Type type = geo->getTypeId();
GeometryState state = GeometryState::Normal;
bool construction = Sketcher::GeometryFacade::getConstruction(*it);
bool internalAligned = Sketcher::GeometryFacade::isInternalAligned(*it);
bool construction = Sketcher::GeometryFacade::getConstruction(geo);
bool internalAligned = Sketcher::GeometryFacade::isInternalAligned(geo);
auto layerId = getSafeGeomLayerId(*it);
auto layerId = getSafeGeomLayerId(geo);
if (internalAligned)
state = GeometryState::InternalAlignment;
@@ -1903,6 +2030,69 @@ void TaskSketcherElements::slotElementsChanged()
return QStringLiteral("(Edge%1#ID%2)").arg(i).arg(i - 1);
};
QString label;
// This is a regular geometry. Get its type name.
QString baseName;
bool isTextHandle = false;
if (type == Part::GeomPoint::getClassTypeId()) {
baseName = tr("Point");
}
else if (type == Part::GeomLineSegment::getClassTypeId()) {
int geoId = i - 1;
auto handle_it = handleIdToType.find(geoId);
if (handle_it != handleIdToType.end()) {
// This is a group/text handle
if (handle_it->second == Sketcher::Group) {
baseName = tr("Group");
}
else {
baseName = tr("Text");
isTextHandle = true;
}
}
else {
baseName = tr("Line");
}
}
else if (type == Part::GeomArcOfCircle::getClassTypeId()) {
baseName = tr("Arc");
}
else if (type == Part::GeomCircle::getClassTypeId()) {
baseName = tr("Circle");
}
else if (type == Part::GeomEllipse::getClassTypeId()) {
baseName = tr("Ellipse");
}
else if (type == Part::GeomArcOfEllipse::getClassTypeId()) {
baseName = tr("Elliptical Arc");
}
else if (type == Part::GeomArcOfHyperbola::getClassTypeId()) {
baseName = tr("Hyperbolic Arc");
}
else if (type == Part::GeomArcOfParabola::getClassTypeId()) {
baseName = tr("Parabolic arc");
}
else if (type == Part::GeomBSplineCurve::getClassTypeId()) {
baseName = tr("B-spline");
}
else {
baseName = tr("Other");
}
// Reconstruct the label using the baseName.
if (isNamingBoxChecked) {
label = baseName + IdInformation();
if (state == GeometryState::Construction) {
label += QStringLiteral("-") + tr("Construction");
}
else if (state == GeometryState::InternalAlignment) {
label += QStringLiteral("-") + tr("Internal");
}
}
else {
label = QStringLiteral("%1-").arg(i) + baseName;
}
auto* itemN = new ElementItem(
i - 1,
sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::start),
@@ -1910,82 +2100,16 @@ void TaskSketcherElements::slotElementsChanged()
sketchView->getSketchObject()->getVertexIndexGeoPos(i - 1, Sketcher::PointPos::end),
type,
state,
type == Part::GeomPoint::getClassTypeId()
? (isNamingBoxChecked ? (tr("Point") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Point")))
: type == Part::GeomLineSegment::getClassTypeId()
? (isNamingBoxChecked ? (tr("Line") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Line")))
: type == Part::GeomArcOfCircle::getClassTypeId()
? (isNamingBoxChecked ? (tr("Arc") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Arc")))
: type == Part::GeomCircle::getClassTypeId()
? (isNamingBoxChecked ? (tr("Circle") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Circle")))
: type == Part::GeomEllipse::getClassTypeId()
? (isNamingBoxChecked ? (tr("Ellipse") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Ellipse")))
: type == Part::GeomArcOfEllipse::getClassTypeId()
? (isNamingBoxChecked ? (tr("Elliptical Arc") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Elliptical arc")))
: type == Part::GeomArcOfHyperbola::getClassTypeId()
? (isNamingBoxChecked ? (tr("Hyperbolic Arc") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Hyperbolic arc")))
: type == Part::GeomArcOfParabola::getClassTypeId()
? (isNamingBoxChecked ? (tr("Parabolic Arc") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Parabolic arc")))
: type == Part::GeomBSplineCurve::getClassTypeId()
? (isNamingBoxChecked ? (tr("B-spline") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("B-spline")))
: (isNamingBoxChecked ? (tr("Other") + IdInformation())
+ (construction
? (QStringLiteral("-") + tr("Construction"))
: (internalAligned ? (QStringLiteral("-") + tr("Internal"))
: QStringLiteral("")))
: (QStringLiteral("%1-").arg(i) + tr("Other"))),
sketchView);
label,
sketchView,
isTextHandle);
ui->listWidgetElements->addItem(itemN);
elementMap[itemN->ElementNbr] = itemN;
setItemVisibility(itemN);
setItemVisibility(itemN, groupedGeoIds);
++i;
}
const std::vector<Part::Geometry*>& ext_vals =
@@ -2091,7 +2215,7 @@ void TaskSketcherElements::slotElementsChanged()
elementMap[itemN->ElementNbr] = itemN;
setItemVisibility(itemN);
setItemVisibility(itemN, groupedGeoIds);
}
}
}
+4 -1
View File
@@ -84,6 +84,8 @@ protected Q_SLOTS:
void doEqualConstraint();
void doSymmetricConstraint();
void doBlockConstraint();
void doGroupConstraint();
void doConvertToGeometries();
void doLockConstraint();
void doHorizontalConstraint();
@@ -133,7 +135,7 @@ public:
private:
void slotElementsChanged();
void updateVisibility();
void setItemVisibility(QListWidgetItem* item);
void setItemVisibility(QListWidgetItem* item, const std::set<int>& groupedGeoIds);
void clearWidget();
void createFilterButtonActions();
void createSettingsButtonActions();
@@ -143,6 +145,7 @@ public Q_SLOTS:
void onListWidgetElementsItemPressed(QListWidgetItem* item);
void onListWidgetElementsItemEntered(QListWidgetItem* item);
void onListWidgetElementsMouseMoveOnItem(QListWidgetItem* item);
void onListWidgetItemActivated(QListWidgetItem* item);
void onSettingsExtendedInformationChanged();
void onFilterBoxStateChanged(int val);
void onListMultiFilterItemChanged(QListWidgetItem* item);
+46
View File
@@ -25,6 +25,9 @@
#include <QCursor>
#include <QLocale>
#include <QRegularExpression>
#include <QDir>
#include <QDirIterator>
#include <QFileInfo>
#include <App/Application.h>
#include <Base/Quantity.h>
@@ -994,3 +997,46 @@ int SketcherGui::indexOfGeoId(const std::vector<int>& vec, int elem)
}
return -1;
}
QMap<QString, QString> SketcherGui::findAvailableFontFiles()
{
QMap<QString, QString> fontMap;
QStringList fontPaths;
// 0. Include FreeCAD bundled fonts
fontPaths << QString::fromStdString(
App::Application::getResourceDir() + "Mod/TechDraw/Resources/fonts/"
);
#if defined(Q_OS_WIN)
fontPaths << QString::fromUtf8("C:/Windows/Fonts");
#elif defined(Q_OS_MACOS)
fontPaths << QString::fromUtf8("/System/Library/Fonts") << QString::fromUtf8("/Library/Fonts")
<< QDir::homePath() + QString::fromUtf8("/Library/Fonts");
#else // Linux and other Unix-like systems
fontPaths << QString::fromUtf8("/usr/share/fonts") << QString::fromUtf8("/usr/local/share/fonts")
<< QDir::homePath() + QString::fromUtf8("/.fonts");
#endif
for (const QString& path : fontPaths) {
if (!QDir(path).exists()) {
continue;
}
QDirIterator it(
path,
QStringList() << QString::fromUtf8("*.ttf") << QString::fromUtf8("*.otf")
<< QString::fromUtf8("*.ttc"),
QDir::Files,
QDirIterator::Subdirectories
);
while (it.hasNext()) {
QString filePath = it.next();
QFileInfo fileInfo(filePath);
// Use the base name as a "friendly name".
// We store in a map to avoid duplicates from different paths (e.g. ttf vs otf).
fontMap[fileInfo.baseName()] = filePath;
}
}
return fontMap;
}
+4
View File
@@ -29,6 +29,8 @@
#include <Base/Tools2D.h>
#include <Mod/Sketcher/App/GeoEnum.h>
#include <QListWidget>
#include <QMap>
#include <QString>
#include "AutoConstraint.h"
#include "ViewProviderSketchGeometryExtension.h"
@@ -248,6 +250,8 @@ inline void scrollTo(QListWidget* list, int i, bool select)
}
}
QMap<QString, QString> findAvailableFontFiles();
} // namespace SketcherGui
/// converts a 2D vector into a 3D vector in the XY plane
+84 -4
View File
@@ -73,6 +73,7 @@
#include "DrawSketchHandler.h"
#include "EditDatumDialog.h"
#include "EditTextDialog.h"
#include "EditModeCoinManager.h"
#include "SnapManager.h"
#include "StyleParameters.h"
@@ -1335,9 +1336,32 @@ void ViewProviderSketch::editDoubleClicked()
Base::Console().log("double click point:%d\n", preselection.PreselectPoint);
}
else if (preselection.isPreselectCurveValid()) {
// We cannot do toggleWireSelelection directly here because the released event with
//STATUS_NONE return false which clears the selection.
setSketchMode(STATUS_SELECT_Wire);
int geoId = preselection.PreselectCurve;
Sketcher::SketchObject* sketch = getSketchObject();
// Check if the preselected edge is the handle of a Text constraint
int textConstrId = -1;
const auto& constraints = sketch->Constraints.getValues();
for (int i = 0; i < static_cast<int>(constraints.size()); ++i) {
if (constraints[i]->Type == Sketcher::Text && constraints[i]->hasElement(0)) {
if (constraints[i]->getGeoId(0) == geoId) {
textConstrId = i;
break;
}
}
}
if (textConstrId != -1) {
Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Modify Text constraint"));
EditTextDialog editTextDialog(this, textConstrId);
editTextDialog.exec();
setSketchMode(STATUS_NONE);
}
else {
// We cannot do toggleWireSelelection directly here because the released event with
//STATUS_NONE return false which clears the selection.
setSketchMode(STATUS_SELECT_Wire);
}
}
else if (preselection.isCrossPreselected()) {
Base::Console().log("double click cross:%d\n",
@@ -1360,6 +1384,12 @@ void ViewProviderSketch::editDoubleClicked()
EditDatumDialog editDatumDialog(this, id);
editDatumDialog.exec();
}
else if (Constr->Type == Sketcher::Text) {
Gui::Command::openCommand(
QT_TRANSLATE_NOOP("Command", "Modify Text constraint"));
EditTextDialog editTextDialog(this, id);
editTextDialog.exec();
}
}
}
}
@@ -1601,16 +1631,27 @@ void ViewProviderSketch::initDragging(int geoId, Sketcher::PointPos pos, Gui::Vi
return; // don't drag externals
}
// If we are trying to drag an edge that is in a group, we drag the group handle instead.
int oldgeoId = geoId;
geoId = getSketchObject()->getGroupHandleIfInGroup(geoId);
if (oldgeoId != geoId) {
// if replaced then we want to move the edge of the handle, not a point.
pos = PointPos::none;
}
drag.reset();
setSketchMode(STATUS_SKETCH_Drag);
drag.Dragged.emplace_back(geoId, pos);
// Adding selected geos that should be dragged as well.
for (auto& geoIdi : selection.SelCurvSet) {
for (auto geoIdi : selection.SelCurvSet) {
if (geoIdi < 0) {
continue; //skip externals
}
// If in a group, we drag the group handle instead.
geoIdi = getSketchObject()->getGroupHandleIfInGroup(geoIdi);
if (geoIdi == geoId) {
// geoId is already added because it was the preselected.
// 2 cases : either the edge was added or a point of it.
@@ -2281,6 +2322,16 @@ void ViewProviderSketch::onSelectionChanged(const Gui::SelectionChanges& msg)
if (shapetype.size() > 4 && shapetype.substr(0, 4) == "Edge") {
int GeoId = std::atoi(&shapetype[4]) - 1;
selection.SelCurvSet.insert(GeoId);
// Check if this is in a group.
// If so we cancel this addition and select the group instead
int handleId = getSketchObject()->getGroupHandleIfInGroup(GeoId);
if (handleId != GeoId) {
// Remove the selected edge
Gui::Selection().rmvSelection(msg.pDocName, msg.pObjectName, msg.pSubName);
std::string sub = "Edge" + std::to_string(handleId + 1);
Gui::Selection().addSelection(msg.pDocName, msg.pObjectName, sub.c_str());
}
}
else if (shapetype.size() > 12 && shapetype.substr(0, 12) == "ExternalEdge") {
int GeoId = std::atoi(&shapetype[12]) - 1;
@@ -2471,6 +2522,16 @@ bool ViewProviderSketch::detectAndShowPreselection(SoPickedPoint* Point)
}
else if (result.GeoIndex != -1
&& result.GeoIndex != preselection.PreselectCurve) {// if a new curve is hit
// If the picked edge is part of a text/group, treat the handle as the preselected item
int handleId = getSketchObject()->getGroupHandleIfInGroup(result.GeoIndex);
if (handleId != result.GeoIndex) {
if (handleId == preselection.PreselectCurve) {
return false;
}
result.GeoIndex = handleId;
}
std::stringstream ss;
if (result.GeoIndex >= 0)
ss << "Edge" << result.GeoIndex + 1;
@@ -4120,6 +4181,8 @@ bool ViewProviderSketch::onDelete(const std::vector<std::string>& subList)
Gui::Selection().clearSelection();
resetPreselectPoint();
const auto& constraints = getSketchObject()->Constraints.getValues();
std::set<int> delInternalGeometries, delExternalGeometries, delCoincidents, delConstraints;
// go through the selected subelements
for (std::vector<std::string>::const_iterator it = SubNames.begin(); it != SubNames.end();
@@ -4128,6 +4191,18 @@ bool ViewProviderSketch::onDelete(const std::vector<std::string>& subList)
int GeoId = std::atoi(it->substr(4, 4000).c_str()) - 1;
if (GeoId >= 0) {
delInternalGeometries.insert(GeoId);
// Handle group deletion
for (const auto* c : constraints) {
if ((c->Type == Sketcher::Text || c->Type == Sketcher::Group)
&& c->hasElement(0) && c->getGeoId(0) == GeoId) {
// This is a group handle. Add all members to the delete list.
for (int j = 1; c->hasElement(j); ++j) {
delInternalGeometries.insert(c->getGeoId(j));
}
break; // A geo can only be a handle for one constraint.
}
}
}
else
delExternalGeometries.insert(Sketcher::GeoEnum::RefExt - GeoId);
@@ -4422,6 +4497,11 @@ Sketcher::Constraint* ViewProviderSketch::getConstraint(int constid) const
return nullptr;
}
bool ViewProviderSketch::isConstraintActiveInSketch(const Sketcher::Constraint* cstr) const
{
return getSketchObject()->isConstraintActiveInSketch(cstr);
}
const GeoList ViewProviderSketch::getGeoList() const
{
const std::vector<Part::Geometry*> tempGeo =
@@ -888,6 +888,9 @@ private:
/// or null if it doesn't exist.
Sketcher::Constraint* getConstraint(int constid) const;
// Return true if the constraint is active, includes checking if it's not in a group
bool isConstraintActiveInSketch(const Sketcher::Constraint* cstr) const;
// gets the list of geometry of the sketchobject or of the solver instance
const GeoList getGeoList() const;
@@ -85,6 +85,10 @@ class ViewProviderSketchCoinAttorney
private:
static inline bool constraintHasExpression(const ViewProviderSketch& vp, int constrid);
static inline const std::vector<Sketcher::Constraint*> getConstraints(const ViewProviderSketch& vp);
static inline bool isConstraintActiveInSketch(
const ViewProviderSketch& vp,
const Sketcher::Constraint* cstr
);
static inline const GeoList getGeoList(const ViewProviderSketch& vp);
static inline const GeoListFacade getGeoListFacade(const ViewProviderSketch& vp);
static inline Base::Placement getEditingPlacement(const ViewProviderSketch& vp);
@@ -144,6 +148,14 @@ inline const std::vector<Sketcher::Constraint*> ViewProviderSketchCoinAttorney::
return vp.getConstraints();
}
inline bool ViewProviderSketchCoinAttorney::isConstraintActiveInSketch(
const ViewProviderSketch& vp,
const Sketcher::Constraint* cstr
)
{
return vp.isConstraintActiveInSketch(cstr);
}
inline const GeoList ViewProviderSketchCoinAttorney::getGeoList(const ViewProviderSketch& vp)
{
return vp.getGeoList();
+5 -2
View File
@@ -455,7 +455,8 @@ inline void SketcherAddWorkbenchGeometries(T& geom)
SketcherAddWorkspaceRectangles(geom);
SketcherAddWorkspaceRegularPolygon(geom);
SketcherAddWorkspaceslots(geom);
geom << "Separator"
geom << "Sketcher_CreateText"
<< "Separator"
<< "Sketcher_ToggleConstruction";
/*<< "Sketcher_CreateText"*/
/*<< "Sketcher_CreateDraftLine"*/;
@@ -487,6 +488,7 @@ inline void SketcherAddWorkbenchConstraints<Gui::MenuItem>(Gui::MenuItem& cons)
<< "Sketcher_ConstrainEqual"
<< "Sketcher_ConstrainSymmetric"
<< "Sketcher_ConstrainBlock"
<< "Sketcher_ConstrainGroup"
<< "Separator"
<< "Sketcher_Dimension"
<< "Sketcher_ConstrainDistanceX"
@@ -553,7 +555,8 @@ inline void SketcherAddWorkbenchConstraints<Gui::ToolBarItem>(Gui::ToolBarItem&
<< "Sketcher_ConstrainTangent"
<< "Sketcher_ConstrainEqual"
<< "Sketcher_ConstrainSymmetric"
<< "Sketcher_ConstrainBlock";
<< "Sketcher_ConstrainBlock"
<< "Sketcher_ConstrainGroup";
cons << "Separator"
<< "Sketcher_CompToggleConstraints";