From a348a1bc82fb98ace08be466b64c3f3f2bbd9ef1 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Sun, 28 Jul 2024 21:18:56 -0400 Subject: [PATCH] Toponaming: Update tests, implement missing subtractive operation tests, fix helix and revolution --- src/Mod/PartDesign/App/Feature.cpp | 3 +- src/Mod/PartDesign/App/Feature.h | 2 + src/Mod/PartDesign/App/FeatureAddSub.cpp | 2 + src/Mod/PartDesign/App/FeatureAddSub.h | 4 +- src/Mod/PartDesign/App/FeatureBoolean.cpp | 54 +++- src/Mod/PartDesign/App/FeatureBoolean.h | 2 +- src/Mod/PartDesign/App/FeatureHelix.cpp | 12 +- src/Mod/PartDesign/App/FeaturePipe.cpp | 9 +- src/Mod/PartDesign/App/FeatureRevolution.cpp | 4 +- .../TestTopologicalNamingProblem.py | 232 ++++++++++++++++-- 10 files changed, 283 insertions(+), 41 deletions(-) diff --git a/src/Mod/PartDesign/App/Feature.cpp b/src/Mod/PartDesign/App/Feature.cpp index d45d37590a..8a6dff75d6 100644 --- a/src/Mod/PartDesign/App/Feature.cpp +++ b/src/Mod/PartDesign/App/Feature.cpp @@ -147,6 +147,7 @@ short Feature::mustExecute() const return Part::Feature::mustExecute(); } +#ifndef FC_USE_TNP_FIX // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) { @@ -167,7 +168,7 @@ TopoDS_Shape Feature::getSolid(const TopoDS_Shape& shape) return {}; } - +#endif TopoShape Feature::getSolid(const TopoShape& shape) { if (shape.isNull()) { diff --git a/src/Mod/PartDesign/App/Feature.h b/src/Mod/PartDesign/App/Feature.h index 06713a94be..bddea63b99 100644 --- a/src/Mod/PartDesign/App/Feature.h +++ b/src/Mod/PartDesign/App/Feature.h @@ -99,8 +99,10 @@ protected: /** * Get a solid of the given shape. If no solid is found an exception is raised. */ +#ifndef FC_USE_TNP_FIX // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape getSolid(const TopoDS_Shape&); +#endif TopoShape getSolid(const TopoShape&); static int countSolids(const TopoDS_Shape&, TopAbs_ShapeEnum type = TopAbs_SOLID); diff --git a/src/Mod/PartDesign/App/FeatureAddSub.cpp b/src/Mod/PartDesign/App/FeatureAddSub.cpp index d9e1bce889..a1a8b14517 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.cpp +++ b/src/Mod/PartDesign/App/FeatureAddSub.cpp @@ -64,6 +64,7 @@ short FeatureAddSub::mustExecute() const return PartDesign::Feature::mustExecute(); } +#ifndef FC_USE_TNP_FIX // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) const { @@ -83,6 +84,7 @@ TopoDS_Shape FeatureAddSub::refineShapeIfActive(const TopoDS_Shape& oldShape) co return oldShape; } +#endif TopoShape FeatureAddSub::refineShapeIfActive(const TopoShape& oldShape) const { diff --git a/src/Mod/PartDesign/App/FeatureAddSub.h b/src/Mod/PartDesign/App/FeatureAddSub.h index 19f0baf578..0a6bd18962 100644 --- a/src/Mod/PartDesign/App/FeatureAddSub.h +++ b/src/Mod/PartDesign/App/FeatureAddSub.h @@ -54,9 +54,11 @@ public: protected: Type addSubType{Additive}; +#ifndef FC_USE_TNP_FIX // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; - TopoShape refineShapeIfActive(const TopoShape&) const; +#endif + TopoShape refineShapeIfActive(const TopoShape&) const; }; using FeatureAddSubPython = App::FeaturePythonT; diff --git a/src/Mod/PartDesign/App/FeatureBoolean.cpp b/src/Mod/PartDesign/App/FeatureBoolean.cpp index 22f429d4d6..a3e0ec520c 100644 --- a/src/Mod/PartDesign/App/FeatureBoolean.cpp +++ b/src/Mod/PartDesign/App/FeatureBoolean.cpp @@ -33,10 +33,12 @@ #include #include #include +#include #include "FeatureBoolean.h" #include "Body.h" +FC_LOG_LEVEL_INIT("PartDesign", true, true); using namespace PartDesign; @@ -106,7 +108,15 @@ App::DocumentObjectExecReturn *Boolean::execute() if(!baseBody) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Cannot do boolean on feature which is not in a body")); - TopoDS_Shape result = baseTopShape.getShape(); + std::vector shapes; + shapes.push_back(baseTopShape); + for(auto it=tools.begin(); itglobalPlacement().inverse(); for (auto tool : tools) @@ -121,12 +131,36 @@ App::DocumentObjectExecReturn *Boolean::execute() TopoDS_Shape boolOp; // Must not pass null shapes to the boolean operations - if (result.IsNull()) + if (result.isNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Base shape is null")); if (shape.IsNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Tool shape is null")); +#ifdef FC_USE_TNP_FIX + const char *op = nullptr; + if (type == "Fuse") + op = Part::OpCodes::Fuse; + else if(type == "Cut") + op = Part::OpCodes::Cut; + else if(type == "Common") + op = Part::OpCodes::Common; + // LinkStage3 defines these other types of Boolean operations. Removed for now pending + // decision to bring them in or not. + // else if(type == "Compound") + // op = Part::OpCodes::Compound; + // else if(type == "Section") + // op = Part::OpCodes::Section; + else + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Unsupported boolean operation")); + + try { + result.makeElementBoolean(op, shapes); + } catch (Standard_Failure &e) { + FC_ERR("Boolean operation failed: " << e.GetMessageString()); + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Boolean operation failed")); + } +#else if (type == "Fuse") { BRepAlgoAPI_Fuse mkFuse(result, shape); if (!mkFuse.IsDone()) @@ -149,11 +183,12 @@ App::DocumentObjectExecReturn *Boolean::execute() } result = boolOp; // Use result of this operation for fuse/cut of next body +#endif } result = refineShapeIfActive(result); - if (!isSingleSolidRuleSatisfied(result)) { + if (!isSingleSolidRuleSatisfied(result.getShape())) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Result has multiple solids: that is not currently supported.")); } @@ -178,13 +213,18 @@ void Boolean::handleChangedPropertyName(Base::XMLReader &reader, const char * Ty } } -TopoDS_Shape Boolean::refineShapeIfActive(const TopoDS_Shape& oldShape) const + +// FIXME: This method ( and the Refine property it depends on ) is redundant with the exact same +// thing in FeatureAddSub, but cannot reasonably be moved up an inheritance level to Feature as +// there are inheritors like FeatureBox for which a refine Property does not make sense. A +// solution like moving Refine and refineShapeIfActive to a new FeatureRefine class that sits +// between Feature and FeatureBoolean / FeatureAddSub is a possibility, or maybe [ew!] hiding the +// property in Feature and only enabling it in the places it is relevant. +TopoShape Boolean::refineShapeIfActive(const TopoShape& oldShape) const { if (this->Refine.getValue()) { try { - Part::BRepBuilderAPI_RefineModel mkRefine(oldShape); - TopoDS_Shape resShape = mkRefine.Shape(); - return resShape; + return oldShape.makeElementRefine(); } catch (Standard_Failure&) { return oldShape; diff --git a/src/Mod/PartDesign/App/FeatureBoolean.h b/src/Mod/PartDesign/App/FeatureBoolean.h index c1e33b501e..2810574605 100644 --- a/src/Mod/PartDesign/App/FeatureBoolean.h +++ b/src/Mod/PartDesign/App/FeatureBoolean.h @@ -63,7 +63,7 @@ public: protected: void handleChangedPropertyName(Base::XMLReader &reader, const char * TypeName, const char *PropName) override; - TopoDS_Shape refineShapeIfActive(const TopoDS_Shape&) const; + TopoShape refineShapeIfActive(const TopoShape&) const; private: diff --git a/src/Mod/PartDesign/App/FeatureHelix.cpp b/src/Mod/PartDesign/App/FeatureHelix.cpp index 24b727deff..3aa6e0c9e9 100644 --- a/src/Mod/PartDesign/App/FeatureHelix.cpp +++ b/src/Mod/PartDesign/App/FeatureHelix.cpp @@ -265,14 +265,14 @@ App::DocumentObjectExecReturn* Helix::execute() if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Adding the helix failed")); // we have to get the solids (fuse sometimes creates compounds) - TopoDS_Shape boolOp = this->getSolid(mkFuse.Shape()); + TopoShape boolOp = this->getSolid(mkFuse.Shape()); // lets check if the result is a solid - if (boolOp.IsNull()) + if (boolOp.isNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Result is not a solid")); - if (!isSingleSolidRuleSatisfied(boolOp)) { + if (!isSingleSolidRuleSatisfied(boolOp.getShape())) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Result has multiple solids")); } @@ -281,7 +281,7 @@ App::DocumentObjectExecReturn* Helix::execute() } else if (getAddSubType() == FeatureAddSub::Subtractive) { - TopoDS_Shape boolOp; + TopoShape boolOp; if (Outside.getValue()) { // are we subtracting the inside or the outside of the profile. BRepAlgoAPI_Common mkCom(result, base.getShape()); @@ -298,10 +298,10 @@ App::DocumentObjectExecReturn* Helix::execute() } // lets check if the result is a solid - if (boolOp.IsNull()) + if (boolOp.isNull()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Result is not a solid")); - if (!isSingleSolidRuleSatisfied(boolOp)) { + if (!isSingleSolidRuleSatisfied(boolOp.getShape())) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Error: Result has multiple solids")); } diff --git a/src/Mod/PartDesign/App/FeaturePipe.cpp b/src/Mod/PartDesign/App/FeaturePipe.cpp index b4b9ccf5cb..7483adac21 100644 --- a/src/Mod/PartDesign/App/FeaturePipe.cpp +++ b/src/Mod/PartDesign/App/FeaturePipe.cpp @@ -690,8 +690,7 @@ App::DocumentObjectExecReturn *Pipe::execute() sewer.Add(s); sewer.Perform(); - mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); - } else { + mkSolid.Add(TopoDS::Shell(sewer.SewedShape())); } else { // shells are already closed - add them directly for (TopoDS_Shape& s : shells) { mkSolid.Add(TopoDS::Shell(s)); @@ -709,15 +708,15 @@ App::DocumentObjectExecReturn *Pipe::execute() } //result.Move(invObjLoc); - AddSubShape.setValue(result); // TODO: Do we need to toposhape here? + AddSubShape.setValue(result); // Converts result to a TopoShape, but no tag. if (base.isNull()) { if (getAddSubType() == FeatureAddSub::Subtractive) return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Pipe: There is nothing to subtract from")); - result = refineShapeIfActive(result); - Shape.setValue(getSolid(result)); + auto ts_result = refineShapeIfActive(result); + Shape.setValue(getSolid(ts_result)); return App::DocumentObject::StdReturn; } diff --git a/src/Mod/PartDesign/App/FeatureRevolution.cpp b/src/Mod/PartDesign/App/FeatureRevolution.cpp index 82565d9f20..caf15c33ab 100644 --- a/src/Mod/PartDesign/App/FeatureRevolution.cpp +++ b/src/Mod/PartDesign/App/FeatureRevolution.cpp @@ -374,7 +374,9 @@ void Revolution::generateRevolution(TopoDS_Shape& revol, } #ifdef FC_USE_TNP_FIX - revol = TopoShape(from).makeElementRevolve(revolAx,angleTotal); + revol = TopoShape(from); + // revol.Tag = getID(); + revol = revol.makeElementRevolve(revolAx,angleTotal); revol.Tag = -getID(); #else // revolve the face to a solid diff --git a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py index 4e628ace44..5af1cfb398 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py +++ b/src/Mod/PartDesign/PartDesignTests/TestTopologicalNamingProblem.py @@ -41,6 +41,12 @@ class TestTopologicalNamingProblem(unittest.TestCase): """ Create a document for each test in the test suite """ self.Doc = App.newDocument("PartDesignTestTNP."+self._testMethodName) + def countFacesEdgesVertexes(self, map): + faces = [name for name in map.keys() if name.startswith("Face")] + edges = [name for name in map.keys() if name.startswith("Edge")] + vertexes = [name for name in map.keys() if name.startswith("Vertex")] + return ( len(faces), len(edges), len(vertexes) ) + def testPadsOnBaseObject(self): """ Simple TNP test case By creating three Pads dependent on each other in succession, and then moving the @@ -516,32 +522,40 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(sketch) body.addObject(pad) self.Doc.recompute() - # Assert - # self.assertEqual(len(body.Shape.childShapes()), 1) + # Assert + self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30) self.assertEqual(body.Shape.ElementMapSize,30) self.assertEqual(sketch.Shape.ElementMapSize,12) self.assertEqual(pad.Shape.ElementMapSize,30) - # Todo: Assert that the names in the ElementMap are good; - # in particular that they are hashed with a # starting + self.assertNotEqual(pad.Shape.ElementReverseMap['Vertex1'],"Vertex1") # NewName, not OldName + self.assertEqual(self.countFacesEdgesVertexes(pad.Shape.ElementReverseMap),(6,12,8)) + + # Todo: Offer a way to turn on hashing and check that with a # starting + # Pad -> Extrusion -> makes compounds and does booleans, thus the resulting newName element maps + # See if we can turn those off, or try them on the other types? def testPartDesignElementMapRevolution(self): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') - TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) + TestSketcherApp.CreateRectangleSketch(sketch, (1, 1), (2, 2)) # (pt), (w,l) if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act revolution = self.Doc.addObject('PartDesign::Revolution', 'Revolution') revolution.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis']) - revolution.Profile = sketch # Causing segfault + revolution.Profile = sketch body.addObject(sketch) body.addObject(revolution) self.Doc.recompute() # Assert self.assertEqual(len(body.Shape.childShapes()), 1) - self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 8) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 14 ) + self.assertEqual(revolution.Shape.ElementMapSize, 14 ) + self.assertEqual(self.countFacesEdgesVertexes(revolution.Shape.ElementReverseMap),(4,6,4)) + volume = ( math.pi * 3 * 3 - math.pi * 1 * 1 ) * 2 # 50.26548245743668 + self.assertAlmostEqual(revolution.Shape.Volume, volume) def testPartDesignElementMapLoft(self): # Arrange @@ -564,14 +578,31 @@ class TestTopologicalNamingProblem(unittest.TestCase): # Assert self.assertEqual(len(body.Shape.childShapes()), 1) self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 30) + self.assertEqual(loft.Shape.ElementMapSize, 30) + revMap = body.Shape.childShapes()[0].ElementReverseMap + # 4 vertexes and 4 edges in each of the two sketches = 16. + # Loft is a rectangular prism and so 26. + # End map is 12 Edge + 6 face + 8 vertexes (with 4 duplicates) + # Why only 4 dup vertexes in the map, not 8 and 8 dup edges? + # Theory: only vertexes on the actual profile matter to the mapper. + # Has newnames, so we must have done a boolean to attach the ends to the pipe. + self.assertNotEqual(loft.Shape.ElementReverseMap['Vertex1'],"Vertex1") + self.assertNotEqual(revMap['Vertex1'],"Vertex1") + self.assertEqual(self.countFacesEdgesVertexes(loft.Shape.ElementReverseMap),(6,12,8)) + volume = 7.0 + self.assertAlmostEqual(loft.Shape.Volume, volume) def testPartDesignElementMapPipe(self): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) - sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') - TestSketcherApp.CreateRectangleSketch(sketch2, (0, 0), (2, 2)) + sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch001') + sketch2.AttachmentSupport = (self.Doc.getObject("XZ_Plane"), ['']) + sketch2.addGeometry(Part.LineSegment(App.Vector(0, 0, 0), + App.Vector(0, 1, 0))) + sketch2.Placement=App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(1.00,0.00,0.00),90.00)) + # Need to set sketch2 placement? if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Act @@ -583,8 +614,26 @@ class TestTopologicalNamingProblem(unittest.TestCase): body.addObject(pipe) self.Doc.recompute() # Assert + self.assertAlmostEqual(body.Shape.Volume,1) + self.assertAlmostEqual(body.Shape.BoundBox.XMin,0) + self.assertAlmostEqual(body.Shape.BoundBox.YMin,0) + self.assertAlmostEqual(body.Shape.BoundBox.ZMin,0) + self.assertAlmostEqual(body.Shape.BoundBox.XMax,1) + self.assertAlmostEqual(body.Shape.BoundBox.YMax,1) + self.assertAlmostEqual(body.Shape.BoundBox.ZMax,1) self.assertEqual(len(body.Shape.childShapes()), 1) - self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 64) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + revMap = body.Shape.childShapes()[0].ElementReverseMap + # TODO: This is a child of the body and not the actual Pipe. + # 1: is that okay and normal, or should the pipe have an element map + # 2: Should these be newNames and not oldNames? + self.assertEqual(revMap['Vertex1'],"Vertex1") + self.assertEqual(revMap['Vertex8'],"Vertex8") + self.assertEqual(revMap['Edge1'],"Edge1") + self.assertEqual(revMap['Edge12'],"Edge12") + self.assertEqual(revMap['Face1'],"Face1") + self.assertEqual(revMap['Face6'],"Face6") + self.assertEqual(self.countFacesEdgesVertexes(revMap),(6,12,8)) def testPartDesignElementMapHelix(self): # Arrange @@ -602,21 +651,78 @@ class TestTopologicalNamingProblem(unittest.TestCase): self.Doc.recompute() # Assert self.assertEqual(len(body.Shape.childShapes()), 1) - # The next size can vary based on tOCCT version (26 or 30), so we accept having entries. self.assertGreaterEqual(body.Shape.childShapes()[0].ElementMapSize, 26) + revMap = body.Shape.childShapes()[0].ElementReverseMap + self.assertEqual(self.countFacesEdgesVertexes(revMap),(6,12,8)) + volume = 9.424696540407776 # TODO: math formula to calc this. + self.assertAlmostEqual(helix.Shape.Volume, volume) def testPartDesignElementMapPocket(self): - pass # TODO + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') + body.addObject(box) + sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + sketch.AttachmentSupport = (box, 'Face6') + sketch.MapMode = 'FlatFace' + TestSketcherApp.CreateRectangleSketch(sketch, (1, 1), (1, 1)) + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + # Act + pocket = self.Doc.addObject('PartDesign::Pocket', 'Pocket') + pocket.Profile = sketch + pocket.Length = 5 + pocket.Direction = ( 0,0,-1) + pocket.ReferenceAxis = (sketch, ['N_Axis']) + + body.addObject(sketch) + body.addObject(pocket) + self.Doc.recompute() + # Assert + self.assertEqual(len(body.Shape.childShapes()), 1) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 55) + self.assertEqual(body.Shape.ElementMapSize,55) + self.assertEqual(sketch.Shape.ElementMapSize,12) + self.assertEqual(pocket.Shape.ElementMapSize,55) + self.assertNotEqual(pocket.Shape.ElementReverseMap['Vertex1'],"Vertex1") # NewName, not OldName + self.assertEqual(self.countFacesEdgesVertexes(pocket.Shape.ElementReverseMap),(11,24,16)) + volume = 1000 - 5 * 1 * 1 + self.assertAlmostEqual(pocket.Shape.Volume, volume) def testPartDesignElementMapHole(self): - pass # TODO + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') + body.addObject(box) + sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + sketch.AttachmentSupport = (box, 'Face6') + sketch.MapMode = 'FlatFace' + TestSketcherApp.CreateCircleSketch(sketch, (5, 5), 1) + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + # Act + hole = self.Doc.addObject('PartDesign::Hole', 'Hole') + hole.Profile = sketch + + body.addObject(sketch) + body.addObject(hole) + self.Doc.recompute() + # Assert + self.assertEqual(len(body.Shape.childShapes()), 1) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 32) + self.assertEqual(body.Shape.ElementMapSize,32) + self.assertEqual(sketch.Shape.ElementMapSize,2) + self.assertEqual(hole.Shape.ElementMapSize,32) + # self.assertNotEqual(hole.Shape.ElementReverseMap['Vertex1'],"Vertex1") # NewName, not OldName + self.assertEqual(self.countFacesEdgesVertexes(hole.Shape.ElementReverseMap),(7,15,10)) + volume = 1000 - 10 * math.pi * 3 * 3 + self.assertAlmostEqual(hole.Shape.Volume, volume) def testPartDesignElementMapGroove(self): # Arrange body = self.Doc.addObject('PartDesign::Body', 'Body') box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') body.addObject(box) - groove = self.Doc.addObject('PartDesign::Groove', 'Groove') body.addObject(groove) groove.ReferenceAxis = (self.Doc.getObject('Y_Axis'), ['']) @@ -630,17 +736,105 @@ class TestTopologicalNamingProblem(unittest.TestCase): if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. return # Assert - # print(groove.Shape.childShapes()[0].ElementMap) - # TODO: Complete me as part of the subtractive features + revMap = groove.Shape.ElementReverseMap # body.Shape.childShapes()[0].ElementReverseMap + self.assertEqual(self.countFacesEdgesVertexes(revMap),(5,9,6)) + volume = 785.3981633974482 # TODO: math formula to calc this. Maybe make a sketch as the Profile. + self.assertAlmostEqual(groove.Shape.Volume, volume) def testPartDesignElementMapSubLoft(self): - pass # TODO + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') + body.addObject(box) + sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + TestSketcherApp.CreateRectangleSketch(sketch, (1, 1), (1, 1)) + sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + TestSketcherApp.CreateRectangleSketch(sketch2, (1, 1), (2, 2)) + sketch2.Placement.move(App.Vector(0, 0, 3)) + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + # Act + loft = self.Doc.addObject('PartDesign::SubtractiveLoft', 'SubLoft') + loft.Profile = sketch + loft.Sections = [sketch2] + body.addObject(loft) + self.Doc.recompute() + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + # Assert + revMap = loft.Shape.ElementReverseMap # body.Shape.childShapes()[0].ElementReverseMap + self.assertEqual(self.countFacesEdgesVertexes(revMap),(11,24,16)) + volume = 993 # TODO: math formula to calc this. + self.assertAlmostEqual(loft.Shape.Volume, volume) def testPartDesignElementMapSubPipe(self): - pass # TODO + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') + body.addObject(box) + sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + TestSketcherApp.CreateRectangleSketch(sketch, (0, 0), (1, 1)) + sketch2 = self.Doc.addObject('Sketcher::SketchObject', 'Sketch001') + sketch2.AttachmentSupport = (self.Doc.getObject("XZ_Plane"), ['']) + sketch2.addGeometry(Part.LineSegment(App.Vector(0, 0, 0), + App.Vector(0, 1, 0))) + sketch2.Placement=App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(1.00,0.00,0.00),90.00)) + # Need to set sketch2 placement? + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + # Act + pipe = self.Doc.addObject('PartDesign::SubtractivePipe', 'SubPipe') + pipe.Profile = sketch + pipe.Spine = sketch2 + body.addObject(sketch) + body.addObject(sketch2) + body.addObject(pipe) + self.Doc.recompute() + # Assert + self.assertAlmostEqual(body.Shape.Volume,999) + self.assertAlmostEqual(body.Shape.BoundBox.XMin,0) + self.assertAlmostEqual(body.Shape.BoundBox.YMin,0) + self.assertAlmostEqual(body.Shape.BoundBox.ZMin,0) + self.assertAlmostEqual(body.Shape.BoundBox.XMax,10) + self.assertAlmostEqual(body.Shape.BoundBox.YMax,10) + self.assertAlmostEqual(body.Shape.BoundBox.ZMax,10) + self.assertEqual(len(body.Shape.childShapes()), 1) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 44) + revMap = body.Shape.childShapes()[0].ElementReverseMap + # TODO: This is a child of the body and not the actual Pipe. + # 1: is that okay and normal, or should the pipe have an element map + # 2: Should these be newNames and not oldNames? + self.assertEqual(revMap['Vertex1'],"Vertex1") + self.assertEqual(revMap['Vertex8'],"Vertex8") + self.assertEqual(revMap['Edge1'],"Edge1") + self.assertEqual(revMap['Edge12'],"Edge12") + self.assertEqual(revMap['Face1'],"Face1") + self.assertEqual(revMap['Face6'],"Face6") + self.assertEqual(self.countFacesEdgesVertexes(revMap),(9,21,14)) def testPartDesignElementMapSubHelix(self): - pass # TODO + # Arrange + body = self.Doc.addObject('PartDesign::Body', 'Body') + box = self.Doc.addObject('PartDesign::AdditiveBox', 'Box') + body.addObject(box) + sketch = self.Doc.addObject('Sketcher::SketchObject', 'Sketch') + TestSketcherApp.CreateRectangleSketch(sketch, (5, 5), (1, 1)) + if body.Shape.ElementMapVersion == "": # Should be '4' as of Mar 2023. + return + # Act + helix = self.Doc.addObject('PartDesign::SubtractiveHelix', 'SubHelix') + helix.Profile = sketch + helix.ReferenceAxis = (self.Doc.getObject('Sketch'),['V_Axis']) + body.addObject(sketch) + body.addObject(helix) + self.Doc.recompute() + # Assert + self.assertEqual(len(body.Shape.childShapes()), 1) + self.assertEqual(body.Shape.childShapes()[0].ElementMapSize, 50) + revMap = body.Shape.childShapes()[0].ElementReverseMap + self.assertEqual(self.countFacesEdgesVertexes(revMap),(10,24,16)) + volume = 991.3606270276762 # TODO: math formula to calc this. + self.assertAlmostEqual(helix.Shape.Volume, volume) def testPartDesignElementMapChamfer(self): """ Test Chamfer ( and FeatureDressup )"""