Toponaming: fix loft mistake and complete test
This commit is contained in:
@@ -20,17 +20,13 @@
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "FeatureLoft.h"
|
||||
|
||||
#include "PreCompiled.h"
|
||||
// NOLINTNEXTLINE(CppUnusedIncludeDirective)
|
||||
#include "PreCompiled.h" // NOLINT(misc-include-cleaner)
|
||||
#ifndef _PreComp_
|
||||
# include <TopoDS_Wire.hxx>
|
||||
# include <TopExp_Explorer.hxx>
|
||||
# include <BRepAlgoAPI_Cut.hxx>
|
||||
# include <BRepAlgoAPI_Fuse.hxx>
|
||||
# include <BRepBuilderAPI_MakeSolid.hxx>
|
||||
# include <BRepBuilderAPI_Sewing.hxx>
|
||||
# include <BRepClass3d_SolidClassifier.hxx>
|
||||
# include <BRepOffsetAPI_ThruSections.hxx>
|
||||
# include <TopoDS.hxx>
|
||||
# include <Precision.hxx>
|
||||
#endif
|
||||
@@ -42,7 +38,6 @@
|
||||
#include <Base/Reader.h>
|
||||
#include <Mod/Part/App/FaceMakerCheese.h>
|
||||
|
||||
#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<TopoShape> 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<TopoShape> 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<TopoShape> 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<TopoShape> 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<TopoShape> 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<TopoShape> 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"));
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user