diff --git a/src/App/ComplexGeoDataPy.xml b/src/App/ComplexGeoDataPy.xml
index c60f905826..7b094cb3dd 100644
--- a/src/App/ComplexGeoDataPy.xml
+++ b/src/App/ComplexGeoDataPy.xml
@@ -64,6 +64,37 @@
Apply a transformation to the underlying geometry
+
+
+
+ setElementName(element,name=None,postfix=None,overwrite=False,sid=None), Set an element name
+
+ element : the original element name, e.g. Edge1, Vertex2
+ name : the new name for the element, None to remove the mapping
+ postfix : postfix of the name that will not be hashed
+ overwrite: if true, it will overwrite exiting name
+ sid : to hash the name any way you want, provide your own string id(s) in this parameter
+
+ An element can have multiple mapped names. However, a name can only be mapped
+ to one element
+
+
+
+
+
+ getElementName(name,direction=0) - Return a mapped element name or reverse
+
+
+
+
+ getElementIndexedName(name) - Return the indexed element name
+
+
+
+
+ getElementMappedName(name) - Return the mapped element name
+
+
Get the bounding box (BoundBox) of the complex geometric data.
@@ -88,5 +119,35 @@
+
+
+ Get/Set the string hasher of this object
+
+
+
+
+
+ Get the current element map size
+
+
+
+
+
+ Get/Set a dict of element mapping
+
+
+
+
+
+ Get a dict of element reverse mapping
+
+
+
+
+
+ Element map version
+
+
+
diff --git a/src/App/ComplexGeoDataPyImp.cpp b/src/App/ComplexGeoDataPyImp.cpp
index e6c9fe69a5..9cc26fe0c5 100644
--- a/src/App/ComplexGeoDataPyImp.cpp
+++ b/src/App/ComplexGeoDataPyImp.cpp
@@ -27,13 +27,17 @@
#endif
#include "ComplexGeoData.h"
+#include "StringHasher.h"
// inclusion of the generated files (generated out of ComplexGeoDataPy.xml)
#include
#include
+#include
+#include
#include
#include
#include
+#include "Base/PyWrapParseTupleAndKeywords.h"
#include
#include
@@ -292,6 +296,231 @@ PyObject* ComplexGeoDataPy::transformGeometry(PyObject *args)
}
}
+PyObject* ComplexGeoDataPy::getElementName(PyObject* args)
+{
+ char* input;
+ int direction = 0;
+ if (!PyArg_ParseTuple(args, "s|i", &input, &direction)) {
+ return NULL;
+ }
+
+ Data::MappedElement res = getComplexGeoDataPtr()->getElementName(input);
+ std::string s;
+ if (direction == 1) {
+ return Py::new_reference_to(Py::String(res.name.appendToBuffer(s)));
+ }
+ else if (direction == 0) {
+ return Py::new_reference_to(Py::String(res.index.appendToStringBuffer(s)));
+ }
+ else if (Data::IndexedName(input)) {
+ return Py::new_reference_to(Py::String(res.name.appendToBuffer(s)));
+ }
+ else {
+ return Py::new_reference_to(Py::String(res.index.appendToStringBuffer(s)));
+ }
+}
+
+PyObject* ComplexGeoDataPy::getElementIndexedName(PyObject* args)
+{
+ char* input;
+ PyObject* returnID = Py_False;
+ if (!PyArg_ParseTuple(args, "s|O", &input, &returnID)) {
+ return NULL;
+ }
+
+ ElementIDRefs ids;
+ Data::MappedElement res =
+ getComplexGeoDataPtr()->getElementName(input, PyObject_IsTrue(returnID) ? &ids : nullptr);
+ std::string s;
+ Py::String name(res.index.appendToStringBuffer(s));
+ if (!PyObject_IsTrue(returnID)) {
+ return Py::new_reference_to(name);
+ }
+
+ Py::List list;
+ for (auto& id : ids) {
+ list.append(Py::Long(id.value()));
+ }
+ return Py::new_reference_to(Py::TupleN(name, list));
+}
+
+PyObject* ComplexGeoDataPy::getElementMappedName(PyObject* args)
+{
+ char* input;
+ PyObject* returnID = Py_False;
+ if (!PyArg_ParseTuple(args, "s|O", &input, &returnID)) {
+ return NULL;
+ }
+
+ ElementIDRefs ids;
+ Data::MappedElement res =
+ getComplexGeoDataPtr()->getElementName(input, PyObject_IsTrue(returnID) ? &ids : nullptr);
+ std::string s;
+ Py::String name(res.name.appendToBuffer(s));
+ if (!PyObject_IsTrue(returnID)) {
+ return Py::new_reference_to(name);
+ }
+
+ Py::List list;
+ for (auto& id : ids) {
+ list.append(Py::Long(id.value()));
+ }
+ return Py::new_reference_to(Py::TupleN(name, list));
+}
+
+PyObject* ComplexGeoDataPy::setElementName(PyObject* args, PyObject* kwds)
+{
+ const char* element;
+ const char* name = 0;
+ const char* postfix = 0;
+ int tag = 0;
+ PyObject* pySid = Py_None;
+ PyObject* overwrite = Py_False;
+
+ const std::array kwlist = {"element", "name", "postfix", "overwrite", "sid", "tag", nullptr};
+ if (!Wrapped_ParseTupleAndKeywords(args,
+ kwds,
+ "s|sssOOi",
+ kwlist,
+ &element,
+ &name,
+ &postfix,
+ &overwrite,
+ &pySid,
+ &tag)) {
+ return NULL;
+ }
+ ElementIDRefs sids;
+ if (pySid != Py_None) {
+ if (PyObject_TypeCheck(pySid, &App::StringIDPy::Type)) {
+ sids.push_back(static_cast(pySid)->getStringIDPtr());
+ }
+ else if (PySequence_Check(pySid)) {
+ Py::Sequence seq(pySid);
+ for (auto it = seq.begin(); it != seq.end(); ++it) {
+ auto ptr = (*it).ptr();
+ if (PyObject_TypeCheck(ptr, &App::StringIDPy::Type)) {
+ sids.push_back(static_cast(ptr)->getStringIDPtr());
+ }
+ else {
+ throw Py::TypeError("expect StringID in sid sequence");
+ }
+ }
+ }
+ else {
+ throw Py::TypeError("expect sid to contain either StringID or sequence of StringID");
+ }
+ }
+ PY_TRY
+ {
+ Data::IndexedName index(element, getComplexGeoDataPtr()->getElementTypes());
+ Data::MappedName mapped = Data::MappedName::fromRawData(name);
+ std::ostringstream ss;
+ ElementMapPtr map = getComplexGeoDataPtr()->resetElementMap();
+ map->encodeElementName(getComplexGeoDataPtr()->elementType(index),
+ mapped,
+ ss,
+ &sids,
+ tag,
+ postfix,
+ tag);
+ Data::MappedName res =
+ map->setElementName(index, mapped, tag, &sids, PyObject_IsTrue(overwrite));
+ return Py::new_reference_to(Py::String(res.toString(0)));
+ }
+ PY_CATCH
+}
+
+Py::Object ComplexGeoDataPy::getHasher() const
+{
+ auto self = getComplexGeoDataPtr();
+ if (!self->Hasher) {
+ return Py::None();
+ }
+ return Py::Object(self->Hasher->getPyObject(), true);
+}
+
+Py::Dict ComplexGeoDataPy::getElementMap() const
+{
+ Py::Dict ret;
+ std::string s;
+ for (auto& v : getComplexGeoDataPtr()->getElementMap()) {
+ s.clear();
+ ret.setItem(v.name.toString(0), Py::String(v.index.appendToStringBuffer(s)));
+ }
+ return ret;
+}
+
+void ComplexGeoDataPy::setElementMap(Py::Dict dict)
+{
+ std::vector map;
+ const auto& types = getComplexGeoDataPtr()->getElementTypes();
+ for (auto it = dict.begin(); it != dict.end(); ++it) {
+ const auto& value = *it;
+ if (!value.first.isString() || !value.second.isString()) {
+ throw Py::TypeError("expect only strings in the dict");
+ }
+ map.emplace_back(Data::MappedName(value.first.as_string().c_str()),
+ Data::IndexedName(Py::Object(value.second).as_string().c_str(), types));
+ }
+ getComplexGeoDataPtr()->setElementMap(map);
+}
+
+Py::Dict ComplexGeoDataPy::getElementReverseMap() const
+{
+ Py::Dict ret;
+ std::string s;
+ for (auto& v : getComplexGeoDataPtr()->getElementMap()) {
+ s.clear();
+ auto value = ret[Py::String(v.index.appendToStringBuffer(s))];
+ Py::Object item(value);
+ if (item.isNone()) {
+ s.clear();
+ value = Py::String(v.name.appendToBuffer(s));
+ }
+ else if (item.isList()) {
+ Py::List list(item);
+ s.clear();
+ list.append(Py::String(v.name.appendToBuffer(s)));
+ }
+ else {
+ Py::List list;
+ list.append(item);
+ s.clear();
+ list.append(Py::String(v.name.appendToBuffer(s)));
+ value = list;
+ }
+ }
+ return ret;
+}
+
+Py::Int ComplexGeoDataPy::getElementMapSize() const
+{
+ return Py::Int((long)getComplexGeoDataPtr()->getElementMapSize());
+}
+
+void ComplexGeoDataPy::setHasher(Py::Object obj)
+{
+ auto self = getComplexGeoDataPtr();
+ if (obj.isNone()) {
+ if (self->Hasher) {
+ self->Hasher = App::StringHasherRef();
+ self->resetElementMap();
+ }
+ }
+ else if (PyObject_TypeCheck(obj.ptr(), &App::StringHasherPy::Type)) {
+ App::StringHasherRef ref(
+ static_cast(obj.ptr())->getStringHasherPtr());
+ if (self->Hasher != ref) {
+ self->Hasher = ref;
+ self->resetElementMap();
+ }
+ }
+ else {
+ throw Py::TypeError("invalid type");
+ }
+}
+
Py::Object ComplexGeoDataPy::getBoundBox() const
{
return Py::BoundingBox(getComplexGeoDataPtr()->getBoundBox());
@@ -324,6 +553,19 @@ void ComplexGeoDataPy::setPlacement(Py::Object arg)
}
}
+Py::String ComplexGeoDataPy::getElementMapVersion() const
+{
+#ifdef FC_USE_TNP_FIX
+ return Py::String(getComplexGeoDataPtr()->getElementMapVersion());
+#else
+ // This is to allow python level tests visibility into whether element maps are in use, so that
+ // expectations can be adjusted. Eventually this ifdef and clause should be removed, and at the
+ // same time all python tests checking for ElementMapVersion != '' should also be removed.
+ return Py::String();
+#endif
+}
+
+
Py::Int ComplexGeoDataPy::getTag() const
{
return Py::Int(getComplexGeoDataPtr()->Tag);
diff --git a/src/Mod/Part/CMakeLists.txt b/src/Mod/Part/CMakeLists.txt
index 767b1a43d9..6fa26eda2b 100644
--- a/src/Mod/Part/CMakeLists.txt
+++ b/src/Mod/Part/CMakeLists.txt
@@ -70,6 +70,7 @@ set(Part_tests
parttests/TopoShapeListTest.py
parttests/ColorPerFaceTest.py
parttests/ColorTransparencyTest.py
+ parttests/TopoShapeTest.py
)
add_custom_target(PartScripts ALL SOURCES
diff --git a/src/Mod/Part/TestPartApp.py b/src/Mod/Part/TestPartApp.py
index 1031c71b80..b1f4e92fed 100644
--- a/src/Mod/Part/TestPartApp.py
+++ b/src/Mod/Part/TestPartApp.py
@@ -30,6 +30,7 @@ App = FreeCAD
from parttests.Geom2d_tests import Geom2dTests
from parttests.regression_tests import RegressionTests
from parttests.TopoShapeListTest import TopoShapeListTest
+from parttests.TopoShapeTest import TopoShapeTest
#---------------------------------------------------------------------------
# define the test cases to test the FreeCAD Part module
diff --git a/src/Mod/Part/parttests/TopoShapeTest.py b/src/Mod/Part/parttests/TopoShapeTest.py
new file mode 100644
index 0000000000..57861cf132
--- /dev/null
+++ b/src/Mod/Part/parttests/TopoShapeTest.py
@@ -0,0 +1,402 @@
+import FreeCAD as App
+import Part
+
+import unittest
+
+class TopoShapeAssertions:
+ def assertAttrEqual(self, toposhape, attr_value_list, msg=None):
+ for attr, value in attr_value_list:
+ result = toposhape.__getattribute__(
+ attr
+ ) # Look up each attribute by string name
+ if result.__str__() != value.__str__():
+ if msg == None:
+ msg = f"TopoShape {attr} is incorrect: {result} should be {value}",
+ raise AssertionError(msg)
+
+ def assertAttrAlmostEqual(self, toposhape, attr_value_list, places=5, msg=None):
+ range = 1 / 10 ** places
+ for attr, value in attr_value_list:
+ result = toposhape.__getattribute__(
+ attr
+ ) # Look up each attribute by string name
+ if abs(result - value) > range:
+ if msg == None:
+ msg = f"TopoShape {attr} is incorrect: {result} should be {value}"
+ raise AssertionError(msg)
+
+ def assertAttrCount(self, toposhape, attr_value_list, msg=None):
+ for attr, value in attr_value_list:
+ result = toposhape.__getattribute__(
+ attr
+ ) # Look up each attribute by string name
+ if len(result) != value:
+ if msg == None:
+ msg = f"TopoShape {attr} is incorrect: {result} should have {value} elements"
+ raise AssertionError(msg)
+
+ def assertKeysInMap(self, map, keys, msg=None):
+ for key in keys:
+ if not key in map:
+ if msg == None:
+ msg = f"Key {key} not found in map: {map}"
+ raise AssertionError(msg)
+class TopoShapeTest(unittest.TestCase, TopoShapeAssertions):
+ def setUp(self):
+ """Create a document and some TopoShapes of various types"""
+ self.doc = App.newDocument("TopoShape")
+ self.box = Part.makeBox(1, 2, 2)
+ Part.show(self.box, "Box1")
+ self.box2 = Part.makeBox(2, 1, 2)
+ Part.show(self.box2, "Box2")
+
+ def tearDown(self):
+ App.closeDocument("TopoShape")
+
+ def testTopoShapeBox(self):
+ # Arrange our test TopoShape
+ box2_toposhape = self.doc.Box2.Shape
+ # Arrange list of attributes and values to string match
+ attr_value_list = [
+ ["BoundBox", App.BoundBox(0, 0, 0, 2, 1, 2)],
+ ["CenterOfGravity", App.Vector(1, 0.5, 1)],
+ ["CenterOfMass", App.Vector(1, 0.5, 1)],
+ ["CompSolids", []],
+ ["Compounds", []],
+ ["Content", ""],
+ ["ElementMap", {}],
+ ["ElementReverseMap", {}],
+ # ['Hasher', {}], # Todo: Should this exist? Different implementation?
+ [
+ "MatrixOfInertia",
+ App.Matrix(
+ 1.66667, 0, 0, 0, 0, 2.66667, 0, 0, 0, 0, 1.66667, 0, 0, 0, 0, 1
+ ),
+ ],
+ ["Module", "Part"],
+ ["Orientation", "Forward"],
+ # ['OuterShell', {}], # Todo: Could verify that a Shell Object is returned
+ ["Placement", App.Placement()],
+ [
+ "PrincipalProperties",
+ {
+ "SymmetryAxis": True,
+ "SymmetryPoint": False,
+ "Moments": (
+ 2.666666666666666,
+ 1.666666666666667,
+ 1.666666666666667,
+ ),
+ "FirstAxisOfInertia": App.Vector(0.0, 1.0, 0.0),
+ "SecondAxisOfInertia": App.Vector(0.0, 0.0, 1.0),
+ "ThirdAxisOfInertia": App.Vector(1.0, 0.0, 0.0),
+ "RadiusOfGyration": (
+ 0.816496580927726,
+ 0.6454972243679029,
+ 0.6454972243679029,
+ ),
+ },
+ ],
+ ["ShapeType", "Solid"],
+ [
+ "StaticMoments",
+ (3.999999999999999, 1.9999999999999996, 3.999999999999999),
+ ],
+ # ['Tag', 0], # Gonna vary, so can't really assert, except maybe != 0?
+ ["TypeId", "Part::TopoShape"],
+ ]
+ # Assert all the expected values match when converted to strings.
+ self.assertAttrEqual(box2_toposhape, attr_value_list)
+
+ # Arrange list of attributes and values to match within 5 decimal places
+ attr_value_list = [
+ ["Area", 16.0],
+ ["ElementMapSize", 0],
+ # ['ElementMapVersion', 4 ], # Todo: Not until TNP on.
+ ["Length", 40.0], # Sum of all edges of each face, so some redundancy.
+ ["Mass", 4.0],
+ # ['MemSize', 13824], # Platform variations in this size.
+ ["Volume", 4.0],
+ ]
+ # Assert all the expected values match
+ self.assertAttrAlmostEqual(box2_toposhape, attr_value_list, 5)
+
+ # Arrange list of attributes to check list lengths
+ attr_value_list = [
+ ["Edges", 12],
+ ["Faces", 6],
+ ["Shells", 1],
+ ["Solids", 1],
+ ["SubShapes", 1],
+ ["Vertexes", 8],
+ ["Wires", 6],
+ ]
+ # Assert all the expected values match
+ self.assertAttrCount(box2_toposhape, attr_value_list)
+
+ def testTopoShapeElementMap(self):
+ """Tests TopoShape elementMap"""
+ # Arrange
+ # Act No elementMaps exist in base shapes until we perform an operation.
+ compound1 = Part.Compound(
+ [self.doc.Objects[-1].Shape, self.doc.Objects[-2].Shape]
+ )
+ self.doc.addObject("Part::Compound", "Compound")
+ self.doc.Compound.Links = [
+ App.activeDocument().Box1,
+ App.activeDocument().Box2,
+ ]
+ self.doc.recompute()
+ compound2 = self.doc.Compound.Shape
+ # Assert
+ # This is a flag value to indicate that ElementMaps are supported under the current C++ build:
+ if compound1.ElementMapVersion != "": # Should be '4' as of Mar 2023.
+ # 52 is 2 cubes of 26 each: 6 Faces, 12 Edges, 8 Vertexes
+ # Todo: This should contain something as soon as the Python interface for Part.Compound TNP exists
+ # self.assertEqual(len(compound1.ElementMap), 52, "ElementMap is Incorrect: {0}".format(compound1.ElementMap))
+ self.assertEqual(
+ len(compound2.ElementReverseMap),
+ 52,
+ "ElementMap is Incorrect: {0}".format(compound2.ElementMap),
+ )
+
+ # def testTopoShapeOperations(self):
+ # compound1 = Part.Compound([self.doc.Box1.Shape, self.doc.Box2.Shape])
+ # box1ts = self.doc.Box1.Shape
+ # box2ts = self.doc.Box2.Shape
+ # face1 = Part.Face(Part.Wire([Part.makeCircle(10)]))
+ # cut1 = box1ts.cut(box2ts)
+ # common1 = box1ts.common(box2ts)
+ # fuse1 = box1ts.fuse(box2ts)
+ # fuse2 = box1ts.generalFuse([box2ts])
+ # fuse3 = box1ts.multiFuse([box2ts])
+ # mirror1 = box1ts.mirror(App.Vector(0, 0, 0), App.Vector(1, 0, 0))
+ # clean1 = box1ts.cleaned()
+ # # complement1 = box1ts.complement()
+ # # fix1 = box1ts.fix()
+ # rotate1 = box1ts.rotated(App.Vector(0, 0, 0), App.Vector(1, 0, 0), 45)
+ # scale1 = box1ts.scaled(2)
+ # translate1 = box1ts.translated((2, 0, 0))
+ # section1 = box1ts.section(face1)
+ # slice1 = box1ts.slice(App.Vector(1, 0, 0), 2)
+ # slice2 = box1ts.slices(App.Vector(1, 0, 0), [2, 3])
+ # # clean, complement, fix, reverse, scale,
+
+ def testPartCommon(self):
+ self.doc.addObject("Part::MultiCommon", "Common")
+ self.doc.Common.Shapes = [self.doc.Box1, self.doc.Box2]
+ self.doc.recompute()
+ names = list(self.doc.Common.Shape.ElementReverseMap.keys())
+ names.sort()
+ if self.doc.Common.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023.
+ self.assertKeysInMap(self.doc.Common.Shape.ElementReverseMap,
+ [
+ "Edge1",
+ "Edge2",
+ "Edge3",
+ "Edge4",
+ "Edge5",
+ "Edge6",
+ "Edge7",
+ "Edge8",
+ "Edge9",
+ "Edge10",
+ "Edge11",
+ "Edge12",
+ "Face1",
+ "Face2",
+ "Face3",
+ "Face4",
+ "Face5",
+ "Face6",
+ "Vertex1",
+ "Vertex2",
+ "Vertex3",
+ "Vertex4",
+ "Vertex5",
+ "Vertex6",
+ "Vertex7",
+ "Vertex8",
+ ],
+ )
+
+ def testPartCut(self):
+ self.doc.addObject("Part::Cut", "Cut")
+ self.doc.Cut.Base = self.doc.Box1
+ self.doc.Cut.Tool = self.doc.Box2
+ self.doc.recompute()
+ if self.doc.Cut.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023.
+ self.assertKeysInMap(self.doc.Cut.Shape.ElementReverseMap,
+ [
+ "Edge1",
+ "Edge2",
+ "Edge3",
+ "Edge4",
+ "Edge5",
+ "Edge6",
+ "Edge7",
+ "Edge8",
+ "Edge9",
+ "Edge10",
+ "Edge11",
+ "Edge12",
+ "Face1",
+ "Face2",
+ "Face3",
+ "Face4",
+ "Face5",
+ "Face6",
+ "Vertex1",
+ "Vertex2",
+ "Vertex3",
+ "Vertex4",
+ "Vertex5",
+ "Vertex6",
+ "Vertex7",
+ "Vertex8",
+ ],
+ )
+
+ def testPartFuse(self):
+ self.doc.addObject("Part::Fuse", "Fuse")
+ self.doc.Fuse.Base = self.doc.Box1
+ self.doc.Fuse.Tool = self.doc.Box2
+ self.doc.recompute()
+ if self.doc.Fuse.Shape.ElementMapVersion != "": # Should be '4' as of Mar 2023.
+ self.assertEqual(len(self.doc.Fuse.Shape.ElementReverseMap), 58)
+ self.doc.Fuse.Refine = True
+ self.doc.recompute()
+ self.assertEqual(len(self.doc.Fuse.Shape.ElementReverseMap), 38)
+ # Shape is an extruded L, with 8 Faces, 12 Vertexes, 18 Edges
+
+
+# TODO: Consider the following possible test objects:
+# Part::AttachExtension ::init();
+# Part::AttachExtensionPython ::init();
+# Part::PrismExtension ::init();
+# Part::Feature ::init();
+# Part::FeatureExt ::init();
+# Part::BodyBase ::init();
+# Part::FeaturePython ::init();
+# Part::FeatureGeometrySet ::init();
+# Part::CustomFeature ::init();
+# Part::CustomFeaturePython ::init();
+# Part::Boolean ::init();
+# Part::Common ::init();
+# Part::MultiCommon ::init();
+# Part::Cut ::init();
+# Part::Fuse ::init();
+# Part::MultiFuse ::init();
+# Part::Section ::init();
+# Part::FilletBase ::init();
+# Part::Fillet ::init();
+# Part::Chamfer ::init();
+# Part::Compound ::init();
+# Part::Compound2 ::init();
+# Part::Extrusion ::init();
+# Part::Scale ::init();
+# Part::Revolution ::init();
+# Part::Mirroring ::init();
+# TopoShape calls to be consider testing
+# 'add',
+# 'ancestorsOfType',
+# 'applyRotation',
+# 'applyTranslation',
+# 'check',
+# 'childShapes',
+# 'cleaned',
+# 'common',
+# 'complement',
+# 'connectEdgesToWires',
+# 'copy',
+# 'countElement',
+# 'countSubElements',
+# 'cut',
+# 'defeaturing',
+# 'distToShape',
+# 'dumpContent',
+# 'dumpToString',
+# 'dumps',
+# 'exportBinary',
+# 'exportBrep',
+# 'exportBrepToString',
+# 'exportIges',
+# 'exportStep',
+# 'exportStl',
+# 'extrude',
+# 'findPlane',
+# 'fix',
+# 'fixTolerance',
+# 'fuse',
+# 'generalFuse',
+# 'getAllDerivedFrom',
+# 'getElement',
+# 'getElementTypes',
+# 'getFaces',
+# 'getFacesFromSubElement',
+# 'getLines',
+# 'getLinesFromSubElement',
+# 'getPoints',
+# 'getTolerance',
+# 'globalTolerance',
+# 'hashCode',
+# 'importBinary',
+# 'importBrep',
+# 'importBrepFromString',
+# 'inTolerance',
+# 'isClosed',
+# 'isCoplanar',
+# 'isDerivedFrom',
+# 'isEqual',
+# 'isInfinite',
+# 'isInside',
+# 'isNull',
+# 'isPartner',
+# 'isSame',
+# 'isValid',
+# 'limitTolerance',
+# 'loads',
+# 'makeChamfer',
+# 'makeFillet',
+# 'makeOffset2D',
+# 'makeOffsetShape',
+# 'makeParallelProjection',
+# 'makePerspectiveProjection',
+# 'makeShapeFromMesh',
+# 'makeThickness',
+# 'makeWires',
+# 'mirror',
+# 'multiFuse',
+# 'nullify',
+# 'oldFuse',
+# 'optimalBoundingBox',
+# 'overTolerance',
+# 'project',
+# 'proximity',
+# 'read',
+# 'reflectLines',
+# 'removeInternalWires',
+# 'removeShape',
+# 'removeSplitter',
+# 'replaceShape',
+# 'restoreContent',
+# 'reverse',
+# 'reversed',
+# 'revolve',
+# 'rotate',
+# 'rotated',
+# 'scale',
+# 'scaled',
+# 'section',
+# 'setFaces',
+# 'sewShape',
+# 'slice',
+# 'slices',
+# 'tessellate',
+# 'toNurbs',
+# 'transformGeometry',
+# 'transformShape',
+# 'transformed',
+# 'translate',
+# 'translated',
+# 'writeInventor'
diff --git a/tests/src/Mod/Part/App/FeaturePartCut.cpp b/tests/src/Mod/Part/App/FeaturePartCut.cpp
index b5148d68ce..1bf2b69b0b 100644
--- a/tests/src/Mod/Part/App/FeaturePartCut.cpp
+++ b/tests/src/Mod/Part/App/FeaturePartCut.cpp
@@ -173,4 +173,21 @@ TEST_F(FeaturePartCutTest, testGetProviderName)
EXPECT_STREQ(name, "PartGui::ViewProviderBoolean");
}
+TEST_F(FeaturePartCutTest, testMapping)
+{
+
+ // Arrange
+ _cut->Base.setValue(_boxes[0]);
+ _cut->Tool.setValue(_boxes[1]);
+ // Act
+ _cut->execute();
+ const Part::TopoShape& ts1 = _cut->Shape.getShape();
+ // Assert
+#ifndef FC_USE_TNP_FIX
+ EXPECT_EQ(ts1.getElementMap().size(), 0);
+#else
+ EXPECT_EQ(ts1.getElementMap().size(), 26);
+#endif
+}
+
// See FeaturePartCommon.cpp for a history test. It would be exactly the same and redundant here.