diff --git a/src/Mod/Part/App/TopoShapeExpansion.cpp b/src/Mod/Part/App/TopoShapeExpansion.cpp
index d0109d4e72..f6abf55423 100644
--- a/src/Mod/Part/App/TopoShapeExpansion.cpp
+++ b/src/Mod/Part/App/TopoShapeExpansion.cpp
@@ -384,6 +384,9 @@ void TopoShape::mapSubElementTypeForShape(const TopoShape& other,
}
std::ostringstream ss;
char elementType {shapeName(type)[0]};
+ if ( ! elementMap() ) {
+ FC_THROWM(NullShapeException, "No element map");
+ }
elementMap()->encodeElementName(elementType, name, ss, &sids, Tag, op, other.Tag);
elementMap()->setElementName(element, name, Tag, &sids);
}
diff --git a/src/Mod/Part/App/TopoShapeMapper.cpp b/src/Mod/Part/App/TopoShapeMapper.cpp
index 69f9653d18..4268a433d2 100644
--- a/src/Mod/Part/App/TopoShapeMapper.cpp
+++ b/src/Mod/Part/App/TopoShapeMapper.cpp
@@ -1,3 +1,5 @@
+#include "PreCompiled.h"
+
#include "TopoShapeMapper.h"
namespace Part
diff --git a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg
index 66e92f507b..e0c8e5ac32 100644
--- a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg
+++ b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineApproximate.svg
@@ -15,6 +15,17 @@
xmlns:dc="http://purl.org/dc/elements/1.1/">
+
+
+
+
+
@@ -464,7 +484,8 @@
transform="matrix(0.1460346,0,0,0.1460346,-220.10298,-55.131225)">
+ id="g4428-3-2"
+ style="display:inline">
diff --git a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg
index fa676a240d..569e11ffd3 100644
--- a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg
+++ b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_BSplineInsertKnot.svg
@@ -380,7 +380,7 @@
id="path3826" />
+
diff --git a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg
index 0ac32bc5e1..3cf03594f5 100644
--- a/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg
+++ b/src/Mod/Sketcher/Gui/Resources/icons/splines/Sketcher_JoinCurves.svg
@@ -261,11 +261,11 @@
@@ -316,6 +316,15 @@
y1="35.978416"
x2="25.988253"
y2="29.916241" />
+
@@ -368,7 +377,7 @@
+ transform="matrix(-1,0,0,1,47.987644,0)">
+
+
+
+
diff --git a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp
index dd3b963bc0..05f7db40c7 100644
--- a/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp
+++ b/src/Mod/Sketcher/Gui/ViewProviderSketch.cpp
@@ -3886,6 +3886,11 @@ void ViewProviderSketch::generateContextMenu()
int selectedConics = 0;
int selectedPoints = 0;
int selectedConstraints = 0;
+ int selectedBsplines = 0;
+ int selectedBsplineKnots = 0;
+ int selectedOrigin = 0;
+ int selectedEndPoints = 0;
+ bool onlyOrigin = false;
Gui::MenuItem menu;
menu.setCommand("Sketcher context");
@@ -3896,31 +3901,70 @@ void ViewProviderSketch::generateContextMenu()
// if something is selected, count different elements in the current selection
if (selection.size() > 0) {
const std::vector SubNames = selection[0].getSubNames();
-
- for (auto& name : SubNames) {
- if (name.substr(0, 4) == "Edge") {
- ++selectedEdges;
-
+ const Sketcher::SketchObject* obj;
+ if (selection[0].getObject()->isDerivedFrom()) {
+ obj = static_cast(selection[0].getObject());
+ for (auto& name : SubNames) {
int geoId = std::atoi(name.substr(4, 4000).c_str()) - 1;
- if (geoId >= 0) {
- const Part::Geometry* geo = getSketchObject()->getGeometry(geoId);
- if (isLineSegment(*geo)) {
- ++selectedLines;
- }
- else {
- ++selectedConics;
+ const Part::Geometry* geo = getSketchObject()->getGeometry(geoId);
+ if (name.substr(0, 4) == "Edge") {
+ ++selectedEdges;
+
+ if (geoId >= 0) {
+ if (isLineSegment(*geo)) {
+ ++selectedLines;
+ }
+ else if (geo->is()) {
+ ++selectedBsplines;
+ }
+ else {
+ ++selectedConics;
+ }
}
}
- }
- else if (name.substr(0, 4) == "Vert") {
- ++selectedPoints;
- }
- else if (name.substr(0, 4) == "Cons") {
- ++selectedConstraints;
+ else if (name.substr(0, 4) == "Vert") {
+ ++selectedPoints;
+ Sketcher::PointPos posId;
+ getIdsFromName(name, obj, geoId, posId);
+ if (isBsplineKnotOrEndPoint(obj, geoId, posId)) {
+ ++selectedBsplineKnots;
+ }
+ if (Sketcher::PointPos::start != posId || Sketcher::PointPos::end != posId) {
+ ++selectedEndPoints;
+ }
+ }
+ else if (name.substr(0, 4) == "Cons") {
+ ++selectedConstraints;
+ }
+ else if (name.substr(2, 5) == "Axis") {
+ ++selectedEdges;
+ ++selectedLines;
+ ++selectedOrigin;
+ }
+ else if (name.substr(0, 4) == "Root") {
+ ++selectedPoints;
+ ++selectedOrigin;
+ }
}
}
+ if (selectedPoints + selectedEdges == selectedOrigin) {
+ onlyOrigin = true;
+ }
// build context menu items depending on the selection
- if (selectedEdges >= 1 && selectedPoints == 0) {
+ if (selectedBsplines > 0 && selectedBsplines == selectedEdges && selectedPoints == 0
+ && !onlyOrigin) {
+ menu << "Sketcher_BSplineInsertKnot"
+ << "Sketcher_BSplineIncreaseDegree"
+ << "Sketcher_BSplineDecreaseDegree";
+ }
+ else if (selectedBsplineKnots > 0 && selectedBsplineKnots == selectedPoints
+ && selectedEdges == 0 && !onlyOrigin) {
+ if (selectedBsplineKnots == 1) {
+ menu << "Sketcher_BSplineIncreaseKnotMultiplicity"
+ << "Sketcher_BSplineDecreaseKnotMultiplicity";
+ }
+ }
+ if (selectedEdges >= 1 && selectedPoints == 0 && selectedBsplines == 0 && !onlyOrigin) {
menu << "Sketcher_Dimension";
if (selectedConics == 0) {
menu << "Sketcher_ConstrainHorVer"
@@ -3948,9 +3992,9 @@ void ViewProviderSketch::generateContextMenu()
menu << "Sketcher_ConstrainTangent";
}
}
- else if (selectedEdges == 1 && selectedPoints >= 1) {
+ else if (selectedEdges == 1 && selectedPoints >= 1 && !onlyOrigin) {
menu << "Sketcher_Dimension";
- if (selectedConics == 0) {
+ if (selectedConics == 0 && selectedBsplines == 0) {
menu << "Sketcher_ConstrainCoincidentUnified"
<< "Sketcher_ConstrainHorVer"
<< "Sketcher_ConstrainVertical"
@@ -3958,6 +4002,10 @@ void ViewProviderSketch::generateContextMenu()
if (selectedPoints == 2) {
menu << "Sketcher_ConstrainSymmetric";
}
+ if (selectedPoints == 1) {
+ menu << "Sketcher_ConstrainPerpendicular"
+ << "Sketcher_ConstrainTangent";
+ }
}
else {
menu << "Sketcher_ConstrainCoincidentUnified"
@@ -3965,7 +4013,7 @@ void ViewProviderSketch::generateContextMenu()
<< "Sketcher_ConstrainTangent";
}
}
- else if (selectedEdges == 0 && selectedPoints >= 1) {
+ else if (selectedEdges == 0 && selectedPoints >= 1 && !onlyOrigin) {
menu << "Sketcher_Dimension";
if (selectedPoints > 1) {
@@ -3974,8 +4022,15 @@ void ViewProviderSketch::generateContextMenu()
<< "Sketcher_ConstrainVertical"
<< "Sketcher_ConstrainHorizontal";
}
+ if (selectedPoints == 2) {
+ menu << "Sketcher_ConstrainPerpendicular"
+ << "Sketcher_ConstrainTangent";
+ if (selectedEndPoints == 2) {
+ menu << "Sketcher_JoinCurves";
+ }
+ }
}
- else if (selectedLines >= 1 && selectedPoints >= 1) {
+ else if (selectedLines >= 1 && selectedPoints >= 1 && !onlyOrigin) {
menu << "Sketcher_Dimension"
<< "Sketcher_ConstrainHorVer"
<< "Sketcher_ConstrainVertical"
diff --git a/src/Mod/TechDraw/App/Cosmetic.cpp b/src/Mod/TechDraw/App/Cosmetic.cpp
index aa47615f21..5b65996ff1 100644
--- a/src/Mod/TechDraw/App/Cosmetic.cpp
+++ b/src/Mod/TechDraw/App/Cosmetic.cpp
@@ -257,6 +257,8 @@ unsigned int CosmeticEdge::getMemSize () const
void CosmeticEdge::Save(Base::Writer &writer) const
{
+ // TODO: this should be using m_format->Save(writer) instead of saving the individual
+ // fields.
writer.Stream() << writer.ind() << "" << endl;
writer.Stream() << writer.ind() << "" << endl;
writer.Stream() << writer.ind() << "" << endl;
@@ -276,6 +278,9 @@ void CosmeticEdge::Save(Base::Writer &writer) const
} else {
Base::Console().Warning("CE::Save - unimplemented geomType: %d\n", static_cast(m_geometry->getGeomType()));
}
+
+ writer.Stream() << writer.ind() << "" << endl;
+
}
void CosmeticEdge::Restore(Base::XMLReader &reader)
@@ -293,6 +298,7 @@ void CosmeticEdge::Restore(Base::XMLReader &reader)
m_format.m_color.fromHexString(temp);
reader.readElement("Visible");
m_format.m_visible = reader.getAttributeAsInteger("value") != 0;
+
reader.readElement("GeometryType");
TechDraw::GeomType gType = static_cast(reader.getAttributeAsInteger("value"));
@@ -322,6 +328,19 @@ void CosmeticEdge::Restore(Base::XMLReader &reader)
} else {
Base::Console().Warning("CE::Restore - unimplemented geomType: %d\n", static_cast(gType));
}
+ // older documents may not have the LineNumber element, so we need to check the
+ // next entry. if it is a start element, then we check if it is a start element
+ // for LineNumber.
+ if (reader.readNextElement()) {
+ if(strcmp(reader.localName(),"LineNumber") == 0 ) {
+ // this CosmeticEdge has an LineNumber attribute
+ m_format.setLineNumber(reader.getAttributeAsInteger("value"));
+ } else {
+ // LineNumber not found.
+ // TODO: line number should be set to DashedLineGenerator.fromQtStyle(m_format.m_style)
+ m_format.setLineNumber(LineFormat::InvalidLine);
+ }
+ }
}
boost::uuids::uuid CosmeticEdge::getTag() const
diff --git a/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp b/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp
index 0f068ef88c..04c88c578f 100644
--- a/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp
+++ b/src/Mod/TechDraw/Gui/Widgets/CompassWidget.cpp
@@ -179,7 +179,7 @@ void CompassWidget::paintEvent(QPaintEvent* event)
QWidget::paintEvent(event);
}
-//general purpose angle update from external source.
+// set the compass dial and spinbox to a new angle
void CompassWidget::setDialAngle(double newAngle)
{
// Base::Console().Message("CW::setDialAngle(%.3f)\n", newAngle);
@@ -212,6 +212,7 @@ void CompassWidget::slotCWAdvance()
angle = angle + 360.0;
}
setDialAngle(angle);
+ Q_EMIT angleChanged(angle);
}
void CompassWidget::slotCCWAdvance()
@@ -224,6 +225,7 @@ void CompassWidget::slotCCWAdvance()
angle = angle + dsbAngle->minimum();
}
setDialAngle(angle);
+ Q_EMIT angleChanged(angle);
}
void CompassWidget::setAdvanceIncrement(double newIncrement) { m_advanceIncrement = newIncrement; }
diff --git a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp
index 1255a91da6..95f5026bad 100644
--- a/tests/src/Mod/Part/App/TopoShapeExpansion.cpp
+++ b/tests/src/Mod/Part/App/TopoShapeExpansion.cpp
@@ -18,6 +18,7 @@
// NOLINTBEGIN(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)
+
class TopoShapeExpansionTest: public ::testing::Test
{
protected:
@@ -436,4 +437,151 @@ TEST_F(TopoShapeExpansionTest, splitWires)
// splitWires with allfour reorientation values NoReorient, ReOrient, ReorientForward,
// ReorientRevesed
+TEST_F(TopoShapeExpansionTest, mapSubElementInvalidParm)
+{
+ // Arrange
+ auto [cube1, cube2] = CreateTwoCubes();
+ Part::TopoShape cube1TS {cube1};
+ cube1TS.Tag = 1;
+
+ // Act
+ std::vector subShapes = cube1TS.getSubTopoShapes(TopAbs_FACE);
+ Part::TopoShape face1 = subShapes.front();
+ face1.Tag = 2;
+
+ // Assert
+ EXPECT_THROW(cube1TS.mapSubElement(face1), Part::NullShapeException); // No subshapes
+}
+
+TEST_F(TopoShapeExpansionTest, mapSubElementFindShapeByNames)
+{
+ // Arrange
+ auto [cube1, cube2] = CreateTwoCubes();
+ Part::TopoShape cube1TS {cube1};
+ Part::TopoShape cube2TS {cube2};
+ cube1TS.Tag = 1;
+ cube2TS.Tag = 2;
+ Part::TopoShape topoShape, topoShape1;
+
+ // Act
+ int fs1 = topoShape1.findShape(cube1);
+ topoShape.setShape(cube2TS);
+ topoShape1.makeElementCompound({cube1TS, cube2TS});
+ int fs2 = topoShape1.findShape(cube1);
+
+ TopoDS_Shape tds1 = topoShape.findShape("SubShape1");
+ TopoDS_Shape tds2 = topoShape.findShape("SubShape2"); // Nonexistent
+ TopoDS_Shape tds3 = topoShape1.findShape("SubShape1");
+ TopoDS_Shape tds4 = topoShape1.findShape("SubShape2");
+ TopoDS_Shape tds5 = topoShape1.findShape("NonExistentName"); // Invalid Name
+
+ // Assert
+ EXPECT_EQ(fs1, 0);
+ EXPECT_EQ(fs2, 1);
+ EXPECT_FALSE(tds1.IsNull());
+ EXPECT_TRUE(tds2.IsNull());
+ EXPECT_FALSE(tds3.IsNull());
+ EXPECT_FALSE(tds4.IsNull());
+ EXPECT_TRUE(tds5.IsNull());
+}
+
+TEST_F(TopoShapeExpansionTest, mapSubElementFindShapeByType)
+{
+ // Arrange
+ auto [cube1, cube2] = CreateTwoCubes();
+ Part::TopoShape cube1TS {cube1};
+ Part::TopoShape cube2TS {cube2};
+ cube1TS.Tag = 1;
+ cube2TS.Tag = 2;
+ Part::TopoShape topoShape;
+ topoShape.makeElementCompound({cube1TS, cube2TS});
+ topoShape.mapSubElement(cube2TS, "Name", false);
+
+ // Act, Assert
+ for (int i = 1; i <= 12; i++) {
+ TopoDS_Shape dshape1 = topoShape.findShape(TopAbs_FACE, i);
+ EXPECT_FALSE(dshape1.IsNull()) << "Face num " << i;
+ }
+ TopoDS_Shape dshape1 = topoShape.findShape(TopAbs_FACE, 13);
+ EXPECT_TRUE(dshape1.IsNull());
+}
+
+
+TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestor)
+{
+ // Arrange
+ auto [cube1, cube2] = CreateTwoCubes();
+ Part::TopoShape cube1TS {cube1};
+ Part::TopoShape cube2TS {cube2};
+ cube1TS.Tag = 1;
+ cube2TS.Tag = 2;
+ Part::TopoShape topoShape;
+ topoShape.makeElementCompound({cube1TS, cube2TS});
+ topoShape.mapSubElement(cube2TS, "Name", false);
+
+ // Act
+ int fa1 = topoShape.findAncestor(cube2, TopAbs_COMPOUND);
+ TopoDS_Shape tds1 = topoShape.findAncestorShape(cube1, TopAbs_COMPOUND);
+
+ // Assert
+ EXPECT_EQ(fa1, 1);
+ EXPECT_TRUE(tds1.IsEqual(topoShape.getShape()));
+}
+
+
+TEST_F(TopoShapeExpansionTest, mapSubElementFindAncestors)
+{
+ // Arrange
+ auto [cube1, cube2] = CreateTwoCubes();
+ auto [cube3, cube4] = CreateTwoCubes();
+ auto tr {gp_Trsf()};
+ tr.SetTranslation(gp_Vec(gp_XYZ(0, 1, 0)));
+ cube3.Move(TopLoc_Location(tr));
+ cube4.Move(TopLoc_Location(tr));
+ Part::TopoShape cube1TS {cube1};
+ Part::TopoShape cube2TS {cube2};
+ Part::TopoShape cube3TS {cube3};
+ Part::TopoShape cube4TS {cube4};
+ cube1TS.Tag = 1;
+ cube2TS.Tag = 2;
+ cube3TS.Tag = 3;
+ cube4TS.Tag = 4;
+ Part::TopoShape topoShape, topoShape1, topoShape2;
+ Part::TopoShape topoShape3, topoShape4, topoShape5, topoShape6;
+ topoShape.makeElementCompound({cube1TS, cube2TS});
+ topoShape1.makeElementCompound({cube3TS, cube4TS});
+ topoShape2.makeElementCompound({cube1TS, cube3TS});
+ topoShape3.makeElementCompound({cube2TS, cube4TS});
+ topoShape4.makeElementCompound({topoShape, topoShape1});
+ topoShape5.makeElementCompound({topoShape2, topoShape3});
+ topoShape6.makeElementCompound({topoShape4, topoShape5});
+ topoShape6.mapSubElement(cube2TS, nullptr, false);
+
+ // Act
+ auto ancestorList = topoShape6.findAncestors(cube3, TopAbs_COMPOUND);
+ auto ancestorShapeList = topoShape6.findAncestorsShapes(cube3, TopAbs_COMPOUND);
+
+ // FIXME: It seems very strange that both of these ancestors calls return lists of two items
+ // that contain the same thing twice. What I expect is that the ancestors of cube3 would be
+ // topoShape6 topoShape5, topoShape3, topoShape2, and topoShape1.
+ //
+ // This is a very convoluted hierarchy, and the only way I could get more than one result from
+ // findAncestors. I guess it's possible that it's only intended to return a single result in
+ // almost all cases; that would mean that what it returns is the shape at the top of the tree.
+ // But that's exactly the shape we use to call it in the first place, so we already have it.
+ //
+ // Note that in the RT branch, findAncestorsShapes is called by GenericShapeMapper::init,
+ // TopoShape::makEChamfer and MapperPrism
+ // findAncestors is used in a dozen places.
+ //
+
+ // Assert
+ EXPECT_EQ(ancestorList.size(), 2);
+ EXPECT_EQ(ancestorList.front(), 1);
+ EXPECT_EQ(ancestorList.back(), 1);
+ EXPECT_EQ(ancestorShapeList.size(), 2);
+ EXPECT_TRUE(ancestorShapeList.front().IsEqual(topoShape6.getShape()));
+ EXPECT_TRUE(ancestorShapeList.back().IsEqual(topoShape6.getShape()));
+}
+
// NOLINTEND(readability-magic-numbers,cppcoreguidelines-avoid-magic-numbers)