From 9a5d934eab7e197872e4f9088f950956c75d35ac Mon Sep 17 00:00:00 2001 From: Ladislav Michl Date: Wed, 25 Jun 2025 13:51:57 +0200 Subject: [PATCH 1/2] PD: Fix loft between curved faces Fixes issue 19183 also reported in the forum: https://forum.freecad.org/viewtopic.php?p=806495 https://forum.freecad.org/viewtopic.php?t=88234 The actual reason for the failure is that the new code uses FaceMakerBullseye method which expects coplanar wires. The code was using FaceMakerCheese previously which changed in LinkStage with their commit 93ce3edfe7ff ("PD: use FaceMakerBullseye for feature Loft") and was imported here with TNP mitigations. As changing it back would cause different regressions, lets try different facemakers until one succeeds. Fixes: fa8f29aed489 ("Toponaming/Part: Fix all getBaseTopoShape calls...") Co-authored-by: Benjamin Nauck Co-authored-by: Werner Mayer --- src/Mod/PartDesign/App/FeatureLoft.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Mod/PartDesign/App/FeatureLoft.cpp b/src/Mod/PartDesign/App/FeatureLoft.cpp index 25bd92c722..0d27b9c89f 100644 --- a/src/Mod/PartDesign/App/FeatureLoft.cpp +++ b/src/Mod/PartDesign/App/FeatureLoft.cpp @@ -213,7 +213,23 @@ App::DocumentObjectExecReturn *Loft::execute() std::vector backwires; for(auto& sectionWires : wiresections) backwires.push_back(sectionWires.back()); - back = TopoShape(0).makeElementFace(backwires); + const char *faceMaker[] = { + "Part::FaceMakerBullseye", + "Part::FaceMakerCheese", + "Part::FaceMakerSimple", + }; + for (size_t i = 0; i < std::size(faceMaker); i++) { + try { + back = TopoShape(0).makeElementFace(backwires, nullptr, faceMaker[i]); + break; + } + catch (...) { + if (i == std::size(faceMaker) - 1) { + throw; + } + continue; + } + } } if (!front.isNull() || !back.isNull()) { From 123b3b066bb4f86007cddc6d1ec1abe84c225ebf Mon Sep 17 00:00:00 2001 From: Syres916 <46537884+Syres916@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:05:18 +0000 Subject: [PATCH 2/2] Tests: Add test for two face loft --- .../PartDesign/PartDesignTests/TestLoft.py | 181 ++++++++++++++++++ 1 file changed, 181 insertions(+) diff --git a/src/Mod/PartDesign/PartDesignTests/TestLoft.py b/src/Mod/PartDesign/PartDesignTests/TestLoft.py index 3eebc637f3..c314b7e101 100644 --- a/src/Mod/PartDesign/PartDesignTests/TestLoft.py +++ b/src/Mod/PartDesign/PartDesignTests/TestLoft.py @@ -250,6 +250,187 @@ class TestLoft(unittest.TestCase): 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 testTwoFacesAdditiveLoftCase(self): + """Test issue #19183: Loft tool "Loft between faces no longer works""" + body = self.Doc.addObject("PartDesign::Body", "Body") + + sketch1 = body.newObject("Sketcher::SketchObject", "Sketch") + + sketch1.addGeometry( + Part.LineSegment( + Base.Vector(-2.060394, -1.332045, 0), + Base.Vector(-19.922129, -27.589359, 0), + ), + False, + ) + sketch1.addGeometry( + Part.LineSegment( + Base.Vector(1.940183, -1.501086, 0), + Base.Vector(16.928263, -28.265512, 0), + ), + False, + ) + sketch1.addGeometry( + Part.ArcOfCircle( + Part.Circle( + Base.Vector(0.418837, -1.275699, 0), Base.Vector(0, 0, 1), 32.879378 + ), + -1.751723, + -1.454683, + ), + False, + ) + sketch1.addGeometry( + Part.ArcOfCircle( + Part.Circle( + Base.Vector(-9.385396, -30.124937, 0), + Base.Vector(0, 0, 1), + 8.359959, + ), + 2.847554, + 4.831818, + ), + False, + ) + sketch1.addGeometry( + Part.ArcOfCircle( + Part.Circle( + Base.Vector(3.236143, -29.279745, 0), + Base.Vector(0, 0, 1), + 11.449505, + ), + -1.076432, + 0.044306, + ), + False, + ) + sketch1.addConstraint(Sketcher.Constraint("Coincident", 0, 1, 1, 1)) + sketch1.addConstraint(Sketcher.Constraint("Coincident", 0, 1, 2, 3)) + sketch1.addConstraint(Sketcher.Constraint("Coincident", 0, 1, -1, 1)) + sketch1.addConstraint(Sketcher.Constraint("Tangent", 0, 2, 3, 1)) + sketch1.addConstraint(Sketcher.Constraint("Tangent", 1, 2, 4, 2)) + sketch1.addConstraint(Sketcher.Constraint("Tangent", 2, 1, 3, 2)) + sketch1.addConstraint(Sketcher.Constraint("Tangent", 2, 2, 4, 1)) + sketch1.addConstraint(Sketcher.Constraint("Symmetric", 0, 2, 1, 2, -2)) + sketch1.addConstraint(Sketcher.Constraint("Symmetric", 2, 1, 2, 2, -2)) + sketch1.delConstraint(8) + sketch1.addConstraint(Sketcher.Constraint("Radius", 2, 39.936694)) + sketch1.setDatum(8, Units.Quantity("40.000000 mm")) + sketch1.addConstraint(Sketcher.Constraint("DistanceX", 2, 1, 2, 2, 16.771341)) + sketch1.setDatum(9, Units.Quantity("10.000000 mm")) + sketch1.addConstraint(Sketcher.Constraint("Distance", 0, 30.965710)) + sketch1.setDatum(10, Units.Quantity("30.000000 mm")) + + sketch2 = body.newObject("Sketcher::SketchObject", "Sketch001") + sketch2.addGeometry( + Part.LineSegment( + Base.Vector(-2.060394, -1.332045, 0), + Base.Vector(-19.922129, -27.589359, 0), + ), + False, + ) + sketch2.addGeometry( + Part.LineSegment( + Base.Vector(1.940183, -1.501086, 0), + Base.Vector(16.928263, -28.265512, 0), + ), + False, + ) + sketch2.addGeometry( + Part.ArcOfCircle( + Part.Circle( + Base.Vector(0.418837, -1.275699, 0), Base.Vector(0, 0, 1), 32.879378 + ), + -1.751723, + -1.454683, + ), + False, + ) + sketch2.addGeometry( + Part.ArcOfCircle( + Part.Circle( + Base.Vector(-9.385396, -30.124937, 0), + Base.Vector(0, 0, 1), + 8.359959, + ), + 2.847554, + 4.831818, + ), + False, + ) + sketch2.addGeometry( + Part.ArcOfCircle( + Part.Circle( + Base.Vector(3.236143, -29.279745, 0), + Base.Vector(0, 0, 1), + 11.449505, + ), + -1.076432, + 0.044306, + ), + False, + ) + sketch2.addConstraint(Sketcher.Constraint("Coincident", 0, 1, 1, 1)) + sketch2.addConstraint(Sketcher.Constraint("Coincident", 0, 1, 2, 3)) + sketch2.addConstraint(Sketcher.Constraint("Coincident", 0, 1, -1, 1)) + sketch2.addConstraint(Sketcher.Constraint("Tangent", 0, 2, 3, 1)) + sketch2.addConstraint(Sketcher.Constraint("Tangent", 1, 2, 4, 2)) + sketch2.addConstraint(Sketcher.Constraint("Tangent", 2, 1, 3, 2)) + sketch2.addConstraint(Sketcher.Constraint("Tangent", 2, 2, 4, 1)) + sketch2.addConstraint(Sketcher.Constraint("Symmetric", 0, 2, 1, 2, -2)) + sketch2.addConstraint(Sketcher.Constraint("Symmetric", 2, 1, 2, 2, -2)) + sketch2.delConstraint(8) + sketch2.addConstraint(Sketcher.Constraint("Radius", 2, 39.936694)) + sketch2.setDatum(8, Units.Quantity("30.000000 mm")) + sketch2.addConstraint(Sketcher.Constraint("Radius", 3, 1.020288)) + sketch2.setDatum(9, Units.Quantity("3.600000 mm")) + sketch2.addConstraint(Sketcher.Constraint("DistanceX", 2, 1, 2, 2, 10.926759)) + sketch2.setDatum(10, Units.Quantity("10.000000 mm")) + self.Doc.recompute() + + pad1 = body.newObject("PartDesign::Pad", "Pad") + + pad1.Profile = sketch1 + pad1.Length = 10.000000 + pad1.TaperAngle = 0.000000 + pad1.Reversed = 1 + self.Doc.recompute() + sketch1.Visibility = False + + pad2 = body.newObject("PartDesign::Pad", "Pad001") + + pad2.Profile = sketch2 + pad2.Length = 10.000000 + pad2.TaperAngle = 0.000000 + self.Doc.recompute() + sketch2.Visibility = False + pad1.Visibility = False + pad2.Visibility = True + body.Tip = pad2 + self.assertGreater(pad2.Shape.Volume, 8715.0) # 8720.024151557787 pre-Loft + + loft = body.newObject("PartDesign::AdditiveLoft", "AdditiveLoft") + loft.Profile = ( + self.Doc.getObject("Pad001"), + [ + "Face7", + ], + ) + loft.Sections = [ + ( + self.Doc.getObject("Pad001"), + [ + "Face10", + ], + ) + ] + loft.Closed = False + + self.Doc.recompute() + body.Tip = loft + self.assertGreater(body.Shape.Volume, 9220.0) # 9221.776241582389 post-Loft + self.Doc.recompute() + def tearDown(self): #closing doc FreeCAD.closeDocument("PartDesignTestLoft")