FreeCAD / FreeCAD

This is the official source code of FreeCAD, a free and opensource multiplatform 3D parametric modeler.
https://www.freecad.org
Other
19.85k stars 4.06k forks source link

Exactly meeting seams in intersecting AdditivePipes can break model #16915

Open bgbsww opened 1 month ago

bgbsww commented 1 month ago

Is there an existing issue for this?

Problem description

Found while investigating various issues in #16634; this is a simplification of a problem that causes significant breakage in the larger multi file model. Test below shows that is you create two intersecting AdditivePipes, ensuring that the "seams" on the pipes are on the side, then if you try to place an AdditiveCone such that a face lands exactly at the pipe seams ( at the halfway depth of the pipes ), the cone starts to fail to build. I think this is probably an AdditivePipes issue, but can't be sure.

Full version info

Affects most 0.22/1.0 builds.  Does not affect 0.21

Subproject(s) affected?

PartDesign

Anything else?

This test shows the issue:

    def testCrossedAdditivePipesWithConeCase(self):
        App.KeepTestDoc = True
        self.Body = self.Doc.addObject('PartDesign::Body','Body')
        self.ProfileSketch = self.Doc.addObject('Sketcher::SketchObject', 'ProfileSketch')
        self.Body.addObject(self.ProfileSketch)
        TestSketcherApp.CreateCircleSketch(self.ProfileSketch, (0, 0), 1)
        self.Doc.recompute()
        self.SpineSketch = self.Doc.addObject('Sketcher::SketchObject', 'SpineSketch')
        self.Body.addObject(self.SpineSketch)
        self.SpineSketch.MapMode = 'FlatFace'
        self.SpineSketch.AttachmentSupport = (self.Doc.XZ_Plane, [''])
        self.Doc.recompute()
        self.SpineSketch.addGeometry(Part.LineSegment(App.Vector(0.0,-10,0),App.Vector(0,10,0)),False)
        self.SpineSketch.addConstraint(Sketcher.Constraint('PointOnObject',0,2,-2))
        self.SpineSketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-2))
        self.SpineSketch.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,20))
        self.SpineSketch.addConstraint(Sketcher.Constraint('DistanceY',0,1,-1,1,10))
        self.Doc.recompute()
        self.ProfileSketch2 = self.Doc.addObject('Sketcher::SketchObject', 'ProfileSketch2')
        self.ProfileSketch2.MapMode = 'FlatFace'
        self.ProfileSketch2.AttachmentSupport = (self.Doc.YZ_Plane, [''])
        # put the "seam" on the "side" to trigger bug
        self.ProfileSketch2.AttachmentOffset = App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(0,0,1),90))
        self.Body.addObject(self.ProfileSketch2)
        TestSketcherApp.CreateCircleSketch(self.ProfileSketch2, (0, 0), 1)
        self.SpineSketch2 = self.Doc.addObject('Sketcher::SketchObject', 'SpineSketch2')
        self.Body.addObject(self.SpineSketch2)
        self.SpineSketch2.MapMode = 'FlatFace'
        self.SpineSketch2.AttachmentSupport = (self.Doc.XZ_Plane, [''])
        self.Doc.recompute()
        self.SpineSketch2.addGeometry(Part.LineSegment(App.Vector(-10,0,0),App.Vector(10,0,0)),False)
        self.SpineSketch2.addConstraint(Sketcher.Constraint('PointOnObject',0,2,-1))
        self.SpineSketch2.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-1))
        self.SpineSketch2.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,20))
        self.SpineSketch2.addConstraint(Sketcher.Constraint('DistanceX',0,1,-1,1,10))
        self.Doc.recompute()
        self.AdditivePipe = self.Doc.addObject("PartDesign::AdditivePipe","AdditivePipe")
        self.Body.addObject(self.AdditivePipe)
        self.AdditivePipe.Profile = self.ProfileSketch
        self.AdditivePipe.Spine = self.SpineSketch
        self.Doc.recompute()
        self.assertAlmostEqual(self.AdditivePipe.Shape.Volume, math.pi * 1 * 1 * 20)
        self.AdditivePipe2 = self.Doc.addObject("PartDesign::AdditivePipe","AdditivePipe2")
        self.Body.addObject(self.AdditivePipe2)
        self.AdditivePipe2.Profile = self.ProfileSketch2
        self.AdditivePipe2.Spine = self.SpineSketch2
        self.Doc.recompute()
        # self.assertAlmostEqual(self.AdditivePipe2.Shape.Volume, math.pi * 1 * 1 * 36 + (16*1*1*1)/3 )
        self.AdditiveCone = self.Doc.addObject("PartDesign::AdditiveCone","AdditiveCone")
        self.Body.addObject(self.AdditiveCone)
        self.AdditiveCone.Radius1 = 1.5
        self.AdditiveCone.Radius2 = 3
        self.AdditiveCone.Height = 4
        self.AdditiveCone.AttachmentSupport = [(self.Doc.getObject('XZ_Plane'),'')]
        self.AdditiveCone.MapMode = 'FlatFace'
        self.Doc.recompute()
        self.assertTrue(self.AdditiveCone.isValid())

Code of Conduct

bgbsww commented 1 month ago

Does not immediately replicate with Additive Cylinder or Additive Box used instead of Additive Cone. But there's essentially no code in Additive Cone, and zooming in on the original broken case shows the geometry of the intersecting Pipes is messed up. So strongly points at PD pipes.

mwganson commented 1 month ago

This is likely an upstream OCCT issue. It is often grouped within a class of issues known as "coplanar issues". Typically, the workaround for the overlapped seams is to rotate one of the parts slightly. Here you can set the attachment offset rotation angle of the 2nd profile sketch to 91 degrees (from 90).

Your code modified to run as a macro, with the workaround added near the bottom:

import TestSketcherApp
class Test:
    def __init__(self):
        self.Doc = FreeCAD.ActiveDocument
    def testCrossedAdditivePipesWithConeCase(self):
        App.KeepTestDoc = True
        self.Body = self.Doc.addObject('PartDesign::Body','Body')
        self.ProfileSketch = self.Doc.addObject('Sketcher::SketchObject', 'ProfileSketch')
        self.Body.addObject(self.ProfileSketch)
        TestSketcherApp.CreateCircleSketch(self.ProfileSketch, (0, 0), 1)
        self.Doc.recompute()
        self.SpineSketch = self.Doc.addObject('Sketcher::SketchObject', 'SpineSketch')
        self.Body.addObject(self.SpineSketch)
        self.SpineSketch.MapMode = 'FlatFace'
        self.SpineSketch.AttachmentSupport = (self.Doc.XZ_Plane, [''])
        self.Doc.recompute()
        self.SpineSketch.addGeometry(Part.LineSegment(App.Vector(0.0,-10,0),App.Vector(0,10,0)),False)
        self.SpineSketch.addConstraint(Sketcher.Constraint('PointOnObject',0,2,-2))
        self.SpineSketch.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-2))
        self.SpineSketch.addConstraint(Sketcher.Constraint('DistanceY',0,1,0,2,20))
        self.SpineSketch.addConstraint(Sketcher.Constraint('DistanceY',0,1,-1,1,10))
        self.Doc.recompute()
        self.ProfileSketch2 = self.Doc.addObject('Sketcher::SketchObject', 'ProfileSketch2')
        self.ProfileSketch2.MapMode = 'FlatFace'
        self.ProfileSketch2.AttachmentSupport = (self.Doc.YZ_Plane, [''])
        # put the "seam" on the "side" to trigger bug
        self.ProfileSketch2.AttachmentOffset = App.Placement(App.Vector(0,0,0),App.Rotation(App.Vector(0,0,1),90))
        self.Body.addObject(self.ProfileSketch2)
        TestSketcherApp.CreateCircleSketch(self.ProfileSketch2, (0, 0), 1)
        self.SpineSketch2 = self.Doc.addObject('Sketcher::SketchObject', 'SpineSketch2')
        self.Body.addObject(self.SpineSketch2)
        self.SpineSketch2.MapMode = 'FlatFace'
        self.SpineSketch2.AttachmentSupport = (self.Doc.XZ_Plane, [''])
        self.Doc.recompute()
        self.SpineSketch2.addGeometry(Part.LineSegment(App.Vector(-10,0,0),App.Vector(10,0,0)),False)
        self.SpineSketch2.addConstraint(Sketcher.Constraint('PointOnObject',0,2,-1))
        self.SpineSketch2.addConstraint(Sketcher.Constraint('PointOnObject',0,1,-1))
        self.SpineSketch2.addConstraint(Sketcher.Constraint('DistanceX',0,1,0,2,20))
        self.SpineSketch2.addConstraint(Sketcher.Constraint('DistanceX',0,1,-1,1,10))
        self.Doc.recompute()
        self.AdditivePipe = self.Doc.addObject("PartDesign::AdditivePipe","AdditivePipe")
        self.Body.addObject(self.AdditivePipe)
        self.AdditivePipe.Profile = self.ProfileSketch
        self.AdditivePipe.Spine = self.SpineSketch
        self.Doc.recompute()
        # self.assertAlmostEqual(self.AdditivePipe.Shape.Volume, math.pi * 1 * 1 * 20)
        self.AdditivePipe2 = self.Doc.addObject("PartDesign::AdditivePipe","AdditivePipe2")
        self.Body.addObject(self.AdditivePipe2)
        self.AdditivePipe2.Profile = self.ProfileSketch2
        self.AdditivePipe2.Spine = self.SpineSketch2
        self.Doc.recompute()
        # self.assertAlmostEqual(self.AdditivePipe2.Shape.Volume, math.pi * 1 * 1 * 36 + (16*1*1*1)/3 )
        self.AdditiveCone = self.Doc.addObject("PartDesign::AdditiveCone","AdditiveCone")
        self.Body.addObject(self.AdditiveCone)
        self.AdditiveCone.Radius1 = 1.5
        self.AdditiveCone.Radius2 = 3
        self.AdditiveCone.Height = 4
        self.AdditiveCone.AttachmentSupport = [(self.Doc.getObject('XZ_Plane'),'')]
        self.AdditiveCone.MapMode = 'FlatFace'
        self.Doc.recompute()
        # self.assertTrue(self.AdditiveCone.isValid())
        self.ProfileSketch2.AttachmentOffset.Rotation.Angle = 91
        self.Doc.recompute()

t = Test()
t.testCrossedAdditivePipesWithConeCase()

I do not believe this is limited to PartDesign::AdditivePipe, but would also affect Part::Sweep and objects generated with python scripting.

adrianinsaval commented 1 month ago

yeah it's just strange that this particular case used to work but now doesn't