From 4925957185c4e8bf85c327f8b52f5c600db755d7 Mon Sep 17 00:00:00 2001 From: bgbsww Date: Tue, 6 Aug 2024 22:22:44 -0400 Subject: [PATCH] Toponaming: fix loft mistake and complete test --- src/Mod/PartDesign/App/FeatureLoft.cpp | 131 ++++++++---------- .../PartDesign/PartDesignTests/TestLoft.py | 88 +++++++----- 2 files changed, 107 insertions(+), 112 deletions(-) diff --git a/src/Mod/PartDesign/App/FeatureLoft.cpp b/src/Mod/PartDesign/App/FeatureLoft.cpp index 051bc0aa2f..e15eeb4988 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.cpp +++ b/src/Mod/PartDesign/App/FeatureLoft.cpp @@ -20,17 +20,13 @@ * * ***************************************************************************/ +#include "FeatureLoft.h" -#include "PreCompiled.h" +// NOLINTNEXTLINE(CppUnusedIncludeDirective) +#include "PreCompiled.h" // NOLINT(misc-include-cleaner) #ifndef _PreComp_ -# include -# include -# include -# include -# include # include # include -# include # include # include #endif @@ -42,7 +38,6 @@ #include #include -#include "FeatureLoft.h" #include "Mod/Part/App/TopoShapeOpCode.h" @@ -360,7 +355,7 @@ Loft::getSectionShape(const char *name, auto compound = TopoShape(0).makeElementCompound(shapes, "", TopoShape::SingleShapeCompoundCreationPolicy::returnShape); auto wires = compound.getSubTopoShapes(TopAbs_WIRE); auto edges = compound.getSubTopoShapes(TopAbs_EDGE, TopAbs_WIRE); // get free edges and make wires from it - if (edges.size()) { + if ( ! edges.empty()) { auto extra = TopoShape(0).makeElementWires(edges).getSubTopoShapes(TopAbs_WIRE); wires.insert(wires.end(), extra.begin(), extra.end()); } @@ -378,7 +373,7 @@ Loft::getSectionShape(const char *name, return vertices; } -App::DocumentObjectExecReturn *Loft::execute(void) +App::DocumentObjectExecReturn *Loft::execute() { std::vector wires; try { @@ -397,13 +392,13 @@ App::DocumentObjectExecReturn *Loft::execute(void) auto hasher = getDocument()->getStringHasher(); try { - //setup the location + // setup the location this->positionByPrevious(); auto invObjLoc = this->getLocation().Inverted(); if(!base.isNull()) base.move(invObjLoc); - //build up multisections + // build up multisections auto multisections = Sections.getSubListValues(); if(multisections.empty()) return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: At least one section is needed")); @@ -422,68 +417,59 @@ App::DocumentObjectExecReturn *Loft::execute(void) TopoShape result(0,hasher); std::vector shapes; -// if (SplitProfile.getValue()) { -// for (auto &wires : wiresections) { -// for(auto& wire : wires) -// wire.move(invObjLoc); -// shapes.push_back(TopoShape(0).makeElementLoft( -// wires, Part::IsSolid::solid, Ruled.getValue(), Closed.getValue())); -// } -// } else { - //build all shells - std::vector shells; - for (auto &wires : wiresections) { - for(auto& wire : wires) - wire.move(invObjLoc); - shells.push_back(TopoShape(0, hasher).makeElementLoft( - wires, Part::IsSolid::notSolid, Ruled.getValue()? Part::IsRuled::ruled : Part::IsRuled::notRuled, Closed.getValue() ? Part::IsClosed::closed : Part::IsClosed::notClosed)); -// } - - //build the top and bottom face, sew the shell and build the final solid - TopoShape front; - if (wiresections[0].front().shapeType() != TopAbs_VERTEX) { - front = getTopoShapeVerifiedFace(); - if (front.isNull()) - return new App::DocumentObjectExecReturn( - QT_TRANSLATE_NOOP("Exception", "Loft: Creating a face from sketch failed")); - front.move(invObjLoc); - } - - TopoShape back; - if (wiresections[0].back().shapeType() != TopAbs_VERTEX) { - std::vector backwires; - for(auto& wires : wiresections) - backwires.push_back(wires.back()); - back = TopoShape(0).makeElementFace(backwires); - } - - if (!front.isNull() || !back.isNull()) { - BRepBuilderAPI_Sewing sewer; - sewer.SetTolerance(Precision::Confusion()); - if (!front.isNull()) - sewer.Add(front.getShape()); - if (!back.isNull()) - sewer.Add(back.getShape()); - for(auto& s : shells) - sewer.Add(s.getShape()); - - sewer.Perform(); - - if (!front.isNull()) - shells.push_back(front); - if (!back.isNull()) - shells.push_back(back); -// result = result.makeElementShape(sewer,shells); - result = result.makeShapeWithElementMap(sewer.SewedShape(), Part::MapperSewing(sewer), shells, Part::OpCodes::Sewing); - } - - if(!result.countSubShapes(TopAbs_SHELL)) - return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: Failed to create shell")); - shapes = result.getSubTopoShapes(TopAbs_SHELL); + // build all shells + std::vector shells; + for (auto §ionWires : wiresections) { + for(auto& wire : sectionWires) + wire.move(invObjLoc); + shells.push_back(TopoShape(0, hasher).makeElementLoft( + sectionWires, Part::IsSolid::notSolid, Ruled.getValue()? Part::IsRuled::ruled : Part::IsRuled::notRuled, Closed.getValue() ? Part::IsClosed::closed : Part::IsClosed::notClosed)); } + // build the top and bottom face, sew the shell and build the final solid + TopoShape front; + if (wiresections[0].front().shapeType() != TopAbs_VERTEX) { + front = getTopoShapeVerifiedFace(); + if (front.isNull()) + return new App::DocumentObjectExecReturn( + QT_TRANSLATE_NOOP("Exception", "Loft: Creating a face from sketch failed")); + front.move(invObjLoc); + } + + TopoShape back; + if (wiresections[0].back().shapeType() != TopAbs_VERTEX) { + std::vector backwires; + for(auto& sectionWires : wiresections) + backwires.push_back(sectionWires.back()); + back = TopoShape(0).makeElementFace(backwires); + } + + if (!front.isNull() || !back.isNull()) { + BRepBuilderAPI_Sewing sewer; + sewer.SetTolerance(Precision::Confusion()); + if (!front.isNull()) + sewer.Add(front.getShape()); + if (!back.isNull()) + sewer.Add(back.getShape()); + for(auto& s : shells) + sewer.Add(s.getShape()); + + sewer.Perform(); + + if (!front.isNull()) + shells.push_back(front); + if (!back.isNull()) + shells.push_back(back); + // equivalent of the removed: result = result.makeElementShape(sewer,shells); + result = result.makeShapeWithElementMap(sewer.SewedShape(), Part::MapperSewing(sewer), shells, Part::OpCodes::Sewing); + } + + if(!result.countSubShapes(TopAbs_SHELL)) + return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Loft: Failed to create shell")); + shapes = result.getSubTopoShapes(TopAbs_SHELL); + for (auto &s : shapes) { - //build the solid + // build the solid s = s.makeElementSolid(); BRepClass3d_SolidClassifier SC(s.getShape()); SC.PerformInfinitePoint(Precision::Confusion()); @@ -514,9 +500,6 @@ App::DocumentObjectExecReturn *Loft::execute(void) case Subtractive: maker = Part::OpCodes::Cut; break; -// case Intersecting: -// maker = Part::OpCodes::Common; -// break; default: return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "Unknown operation type")); } diff --git a/src/Mod/PartDesign/PartDesignTests/TestLoft.py b/src/Mod/PartDesign/PartDesignTests/TestLoft.py index 3fedd361cf..3eebc637f3 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestLoft.py +++ b/src/Mod/PartDesign/PartDesignTests/TestLoft.py @@ -30,6 +30,7 @@ import Sketcher import TestSketcherApp class TestLoft(unittest.TestCase): + """ Loft Tests """ def setUp(self): self.Doc = FreeCAD.newDocument("PartDesignTestLoft") @@ -144,63 +145,73 @@ class TestLoft(unittest.TestCase): def testPadOnLoftBetweenCones(self): """ Test issue #15138 adapted from a script by chennes """ - body = self.Doc.addObject('PartDesign::Body','Body') + body = self.Doc.addObject('PartDesign::Body', 'Body') body.Label = 'Body' - coneBottomSketch = body.newObject('Sketcher::SketchObject','ConeBottomSketch') - coneBottomSketch.AttachmentSupport = (self.Doc.getObject('XY_Plane'),['']) + coneBottomSketch = body.newObject('Sketcher::SketchObject', 'ConeBottomSketch') + coneBottomSketch.AttachmentSupport = (self.Doc.getObject('XY_Plane'), ['']) coneBottomSketch.MapMode = 'FlatFace' geoList = [] - geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), Base.Vector(0.000000, 0.000000, 1.000000), 25.000000)) - coneBottomSketch.addGeometry(geoList,False) + geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), + Base.Vector(0.000000, 0.000000, 1.000000), + 25.000000)) + coneBottomSketch.addGeometry(geoList, False) del geoList constraintList = [] - coneBottomSketch.addConstraint(Sketcher.Constraint('Diameter',0,25.000000)) + coneBottomSketch.addConstraint(Sketcher.Constraint('Diameter', 0, 25.000000)) coneBottomSketch.addConstraint(Sketcher.Constraint('Coincident', 0, 3, -1, 1)) geoList = [] - geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), Base.Vector(0.000000, 0.000000, 1.000000), 40.000000)) - coneBottomSketch.addGeometry(geoList,False) + geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), + Base.Vector(0.000000, 0.000000, 1.000000), + 40.000000)) + coneBottomSketch.addGeometry(geoList, False) del geoList constraintList = [] - coneBottomSketch.addConstraint(Sketcher.Constraint('Diameter',1,40.000000)) + coneBottomSketch.addConstraint(Sketcher.Constraint('Diameter', 1, 40.000000)) coneBottomSketch.addConstraint(Sketcher.Constraint('Coincident', 1, 3, 0, 3)) - coneTopSketch = body.newObject('Sketcher::SketchObject','ConeTopSketch') - coneTopSketch.AttachmentSupport = (self.Doc.getObject('XY_Plane'),['']) + coneTopSketch = body.newObject('Sketcher::SketchObject', 'ConeTopSketch') + coneTopSketch.AttachmentSupport = (self.Doc.getObject('XY_Plane'), ['']) coneTopSketch.MapMode = 'FlatFace' geoList = [] - geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), Base.Vector(0.000000, 0.000000, 1.000000), 8.000000)) - coneTopSketch.addGeometry(geoList,False) + geoList.append( + Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), + Base.Vector(0.000000, 0.000000, 1.000000), + 8.000000)) + coneTopSketch.addGeometry(geoList, False) del geoList constraintList = [] - coneTopSketch.addConstraint(Sketcher.Constraint('Diameter',0,8.000000)) + coneTopSketch.addConstraint(Sketcher.Constraint('Diameter', 0, 8.000000)) coneTopSketch.addConstraint(Sketcher.Constraint('Coincident', 0, 3, -1, 1)) geoList = [] - geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), Base.Vector(0.000000, 0.000000, 1.000000), 15.000000)) - coneTopSketch.addGeometry(geoList,False) + geoList.append(Part.Circle(Base.Vector(0.000000, 0.000000, 0.000000), + Base.Vector(0.000000, 0.000000, 1.000000), + 15.000000)) + coneTopSketch.addGeometry(geoList, False) del geoList constraintList = [] - coneTopSketch.addConstraint(Sketcher.Constraint('Diameter',1,15.000000)) + coneTopSketch.addConstraint(Sketcher.Constraint('Diameter', 1, 15.000000)) coneTopSketch.addConstraint(Sketcher.Constraint('Coincident', 1, 3, 0, 3)) - coneTopSketch.AttachmentOffset = Base.Placement(Base.Vector(0,0,20),Base.Rotation(Base.Vector(0,0,1),0)) + coneTopSketch.AttachmentOffset = Base.Placement(Base.Vector(0, 0, 20), + Base.Rotation(Base.Vector(0, 0, 1), 0)) self.Doc.recompute() - cone = body.newObject('PartDesign::AdditiveLoft','Cone') + cone = body.newObject('PartDesign::AdditiveLoft', 'Cone') cone.Profile = coneBottomSketch cone.Sections = [(coneTopSketch, [''])] coneBottomSketch.Visibility = False coneTopSketch.Visibility = False self.Doc.recompute() - pad = body.newObject('PartDesign::Pad','Pad') - pad.Profile = (cone, ['Face4',]) + pad = body.newObject('PartDesign::Pad', 'Pad') + pad.Profile = (cone, ['Face3', ]) pad.Length = 10.000000 pad.TaperAngle = 0.000000 pad.UseCustomVector = 0 @@ -209,36 +220,37 @@ class TestLoft(unittest.TestCase): pad.AlongSketchNormal = 1 pad.Type = 0 pad.UpToFace = None - pad.Reversed = 1 + pad.Reversed = False pad.Midplane = 0 pad.Offset = 0 cone.Visibility = True self.Doc.recompute() - # TODO: why doesn't this volume math match up? outerConeBottomRadius = 40 / 2 outerConeTopRadius = 15 / 2 innerConeBottomRadius = 25 / 2 innerConeTopRadius = 8 / 2 coneHeight = 20.0 padHeight = 10.0 - # Frustum volumes 12697 - 4655 = 8042 - outerConeVolume = 1.0/3.0 * math.pi * coneHeight * ( outerConeBottomRadius**2 + outerConeTopRadius**2 + outerConeBottomRadius * outerConeTopRadius) - innerConeVolume = 1.0/3.0 * math.pi * coneHeight * ( innerConeBottomRadius**2 + innerConeTopRadius**2 + innerConeBottomRadius * innerConeTopRadius) - coneVolume = outerConeVolume - innerConeVolume - topArea = math.pi * (outerConeTopRadius**2) - math.pi * (innerConeTopRadius**2) + # Frustum volumes + outerConeVolume = 1.0 / 3.0 * math.pi * coneHeight * ( + outerConeBottomRadius ** 2 + outerConeTopRadius ** 2 + + outerConeBottomRadius * outerConeTopRadius ) + innerConeVolume = 1.0 / 3.0 * math.pi * coneHeight * ( + innerConeBottomRadius ** 2 + innerConeTopRadius ** 2 + + innerConeBottomRadius * innerConeTopRadius ) + frustumVolume = outerConeVolume - innerConeVolume + topArea = math.pi * (outerConeTopRadius ** 2) - math.pi * (innerConeTopRadius ** 2) + bottomArea = math.pi * (outerConeBottomRadius ** 2) - math.pi * (innerConeBottomRadius ** 2) padVolume = topArea * padHeight - self.assertAlmostEqual(cone.Shape.Faces[3].Area, topArea) - # TODO: Next test currently showing 5 faces instead of 4, and then everything goes ugly. Not focus of this PR - # so I will leave that for a loft fix PR, but capture the math WIP here on making the assertiong correct. - # self.assertEqual(len(cone.Shape.Faces), 4) # Inner, Outer, Bottom, Top - # self.assertAlmostEqual(pad.Shape.Volume, padVolume) // TODO Why so radically wrong? - # self.assertAlmostEqual(cone.Shape.Volume, 5854.5823094398365) # coneVolume) // TODO Wrong - # self.assertAlmostEqual(body.Shape.Volume, 14745.409518818293 ) # coneVolume + padVolume) // TODO Wrong - # self.assertAlmostEqual(pad.Shape.Volume, 14745.409518818293 ) # coneVolume + padVolume) // TODO Wrong + self.assertEqual(len(cone.Shape.Faces), 4) # Bottom, Inner, Top, Outer + self.assertAlmostEqual(cone.Shape.Faces[2].Area, topArea) + self.assertAlmostEqual(cone.Shape.Faces[0].Area, bottomArea) + self.assertAlmostEqual(cone.Shape.Volume, frustumVolume) + self.assertAlmostEqual(pad.Shape.Volume, frustumVolume + padVolume) # contains volume of previous in Body + self.assertAlmostEqual(body.Shape.Volume, frustumVolume + padVolume) # Overall body volume matches def tearDown(self): #closing doc FreeCAD.closeDocument("PartDesignTestLoft") - #print ("omit closing document for debugging") - + # print ("omit closing document for debugging")