diff --git a/src/App/ComplexGeoDataPyImp.cpp b/src/App/ComplexGeoDataPyImp.cpp
index b3469ddc1a..9cc26fe0c5 100644
--- a/src/App/ComplexGeoDataPyImp.cpp
+++ b/src/App/ComplexGeoDataPyImp.cpp
@@ -37,6 +37,7 @@
#include
#include
#include
+#include "Base/PyWrapParseTupleAndKeywords.h"
#include
#include
@@ -295,153 +296,194 @@ PyObject* ComplexGeoDataPy::transformGeometry(PyObject *args)
}
}
-PyObject* ComplexGeoDataPy::getElementName(PyObject *args)
+PyObject* ComplexGeoDataPy::getElementName(PyObject* args)
{
char* input;
int direction = 0;
- if (!PyArg_ParseTuple(args, "s|i", &input,&direction))
+ if (!PyArg_ParseTuple(args, "s|i", &input, &direction)) {
return NULL;
+ }
Data::MappedElement res = getComplexGeoDataPtr()->getElementName(input);
std::string s;
- if (direction == 1)
+ if (direction == 1) {
return Py::new_reference_to(Py::String(res.name.appendToBuffer(s)));
- else if (direction == 0)
+ }
+ else if (direction == 0) {
return Py::new_reference_to(Py::String(res.index.appendToStringBuffer(s)));
- else if (Data::IndexedName(input))
+ }
+ else if (Data::IndexedName(input)) {
return Py::new_reference_to(Py::String(res.name.appendToBuffer(s)));
- else
+ }
+ else {
return Py::new_reference_to(Py::String(res.index.appendToStringBuffer(s)));
+ }
}
-PyObject* ComplexGeoDataPy::getElementIndexedName(PyObject *args)
+PyObject* ComplexGeoDataPy::getElementIndexedName(PyObject* args)
{
char* input;
- PyObject *returnID = Py_False;
- if (!PyArg_ParseTuple(args, "s|O", &input,&returnID))
+ 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);
+ 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))
+ if (!PyObject_IsTrue(returnID)) {
return Py::new_reference_to(name);
+ }
Py::List list;
- for (auto &id : ids)
+ for (auto& id : ids) {
list.append(Py::Long(id.value()));
+ }
return Py::new_reference_to(Py::TupleN(name, list));
}
-PyObject* ComplexGeoDataPy::getElementMappedName(PyObject *args)
+PyObject* ComplexGeoDataPy::getElementMappedName(PyObject* args)
{
char* input;
- PyObject *returnID = Py_False;
- if (!PyArg_ParseTuple(args, "s|O", &input,&returnID))
+ 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);
+ 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))
+ if (!PyObject_IsTrue(returnID)) {
return Py::new_reference_to(name);
+ }
Py::List list;
- for (auto &id : ids)
+ 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;
+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;
+ PyObject* pySid = Py_None;
+ PyObject* overwrite = Py_False;
- static char *kwlist[] = {"element", "name", "postfix", "overwrite", "sid", "tag", NULL};
- if (!PyArg_ParseTupleAndKeywords(args, kwds, "s|sssOOi", kwlist,
- &element,&name,&postfix,&overwrite,&pySid,&tag))
+ 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 {
+ 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));
+ 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_CATCH
}
-Py::Object ComplexGeoDataPy::getHasher() const {
+Py::Object ComplexGeoDataPy::getHasher() const
+{
auto self = getComplexGeoDataPtr();
- if(!self->Hasher)
+ if (!self->Hasher) {
return Py::None();
- return Py::Object(self->Hasher->getPyObject(),true);
+ }
+ return Py::Object(self->Hasher->getPyObject(), true);
}
-Py::Dict ComplexGeoDataPy::getElementMap() const {
+Py::Dict ComplexGeoDataPy::getElementMap() const
+{
Py::Dict ret;
std::string s;
- for(auto &v : getComplexGeoDataPtr()->getElementMap()) {
+ 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) {
+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())
+ 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 ComplexGeoDataPy::getElementReverseMap() const
+{
Py::Dict ret;
std::string s;
- for(auto &v : getComplexGeoDataPtr()->getElementMap()) {
+ for (auto& v : getComplexGeoDataPtr()->getElementMap()) {
s.clear();
auto value = ret[Py::String(v.index.appendToStringBuffer(s))];
Py::Object item(value);
- if(item.isNone()) {
+ if (item.isNone()) {
s.clear();
value = Py::String(v.name.appendToBuffer(s));
- } else if(item.isList()) {
+ }
+ else if (item.isList()) {
Py::List list(item);
s.clear();
list.append(Py::String(v.name.appendToBuffer(s)));
- } else {
+ }
+ else {
Py::List list;
list.append(item);
s.clear();
@@ -452,25 +494,31 @@ Py::Dict ComplexGeoDataPy::getElementReverseMap() const {
return ret;
}
-Py::Int ComplexGeoDataPy::getElementMapSize() const {
+Py::Int ComplexGeoDataPy::getElementMapSize() const
+{
return Py::Int((long)getComplexGeoDataPtr()->getElementMapSize());
}
-void ComplexGeoDataPy::setHasher(Py::Object obj) {
+void ComplexGeoDataPy::setHasher(Py::Object obj)
+{
auto self = getComplexGeoDataPtr();
- if(obj.isNone()) {
- if(self->Hasher) {
+ 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) {
+ }
+ 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
+ }
+ else {
throw Py::TypeError("invalid type");
+ }
}
Py::Object ComplexGeoDataPy::getBoundBox() const
@@ -507,7 +555,14 @@ 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
}
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.