Boolean cutting of tubes not always working [Solved]

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Doug
Posts: 46
Joined: Sun Feb 26, 2017 8:51 pm

Boolean cutting of tubes not always working [Solved]

Post by Doug »

Hi,

I built a model that has several intersecting tubes. Where two tubes intersect, I have used a Boolean cut function to cope one of the tubes into the other. In general, the operation works as expected, but sometimes it does not work properly. I have built a simplified model with two tubes to illustrate an instance where it does not work (Python code below). On the coped tube, there are lines where the cuts should be, but no material has been removed. I have found other people who have observed similar problems (https://forum.freecadweb.org/viewtopic.php?t=16824 & https://forum.freecadweb.org/viewtopic.php?t=10225), but the solutions proposed don’t work for this case. I observe the same results in v0.18.16110. Any suggestions on how to fix this problem would be much appreciated! Thanks.

Cheers,
Doug

Code: Select all

import os
import sys
import Part
import PartDesign
import Draft
import DraftGeomUtils
import math
import numpy as np
import time
from FreeCAD import Base
from Part import Wire, Face, Solid
from math import cos, sin, tan, radians, asin, acos, atan, atan2, degrees

d = App.newDocument("Tube_Cutting")
g = FreeCADGui.ActiveDocument

V = App.Vector
x = V(1,0,0)
y = V(0,1,0)
z = V(0,0,1)
Origin = V(0,0,0)

def makeMember(MemberName, MemberCL, MemberOR, MemberIR, Transparency, TubeVisibility=True):
    # print('MakeMember: ',MemberName)
    TubeName = MemberName+'Tube'
    OuterName = MemberName+'Outer'
    MemberOuterCyl = MemberOuter(MemberCL, MemberOR)
    MemberInnerCyl = MemberOuter(MemberCL, MemberIR)
    MemberTube = MemberOuterCyl.cut(MemberInnerCyl)
    d.addObject("Part::Feature",OuterName).Shape = Part.Solid(MemberOuterCyl)
    d.addObject("Part::Feature",TubeName).Shape = MemberTube
    MemberGUI = getattr(g,TubeName)
    MemberGUI.Transparency=Transparency
    MemberGUI.Visibility=TubeVisibility
    MemberOuterGUI = getattr(g,OuterName)
    MemberOuterGUI.Visibility=False
    return(MemberTube)

def MemberOuter(MemberCL, MemberOR):
    # print('MemberOuter')
    OuterShellSegmentL = []
    StartP = MemberCL.Vertexes[0].Point
    EndP = MemberCL.Vertexes[-1].Point
    MemberLen = StartP.distanceToPoint(EndP)
    Tolerance = MemberLen/10**6
    
    for CLEdge in MemberCL.Edges:
        OuterShellSegmentL.append((Part.makeTube(CLEdge,MemberOR)))
    # print(OuterShell)
    EndWire=makeEndFace(OuterShellSegmentL[0], StartP, Tolerance).OuterWire
    Tube = Part.Wire(MemberCL).makePipeShell([EndWire],True,True)
    
    return(Tube)

def makeEndFace(Shell, StartP, Tolerance):
    # print(MemberName)
    for ShellEdge in Shell.Edges:
        # print('ShellEdge ',ShellEdge)
        if ShellEdge.Closed:    #Found end edge
            # print('Closed Edge')
            CircularFace = Part.Face(Part.Wire(ShellEdge))   # Make circular end face
            if CircularFace.isInside(StartP,Tolerance,True): #Check if face contains start point
                # print('StartFace')
                return(CircularFace)
            else:
                print('No face found')
    return()
  
MemberVisibility = False
MemberTransparency = 0
CLVisibility = False

MemberOR = 35/2
MemberIR = 33.5/2

MemberStart = V(1430.0, -415.3,  909.0)
MemberEnd = V(804.2,  478.4,  897.0)
MemberMid = V((MemberStart+MemberEnd)/2+V(0,0,36))
MemberCL = Part.Wire(Part.Arc(MemberStart,MemberMid,MemberEnd).toShape())
d.addObject("Part::Feature",'MemberCL').Shape = MemberCL
MemberTube = makeMember('Member', MemberCL, MemberOR, MemberIR, MemberTransparency, MemberVisibility)

d.addObject("Part::Feature",'Member1CL').Shape = d.MemberCL.Shape.mirror(Origin,y)
Member1Tube = makeMember('Member1', d.Member1CL.Shape, MemberOR, MemberIR, MemberTransparency, MemberVisibility)
   
print('Cut tubes')
Member1Cut = d.Member1Tube.Shape.cut(d.MemberOuter.Shape)
d.addObject("Part::Feature",'Member1Cut').Shape = Member1Cut

g.MemberCL.Visibility=CLVisibility
g.Member1CL.Visibility=CLVisibility
# g.MemberTube.Visibility=False
# g.Member1Tube.Visibility=False
g.activeView().viewIsometric()
g.ActiveView.fitAll()
d.recompute()
Screenshot of cut tube
Tube Cutting.png
Tube Cutting.png (185.74 KiB) Viewed 1052 times
OS: Windows 10 (10.0)
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.19.16945 (Git)
Build type: Release
Branch: master
Hash: d818a9638424a934bd9da74d187a1af4cb773f05
Python version: 3.6.8
Qt version: 5.12.1
Coin version: 4.0.0a
OCC version: 7.3.0
Locale: English/Canada (en_CA)
Attachments
Tube Cutting v1.FCStd
(81.96 KiB) Downloaded 29 times
Last edited by Doug on Tue Jun 25, 2019 5:15 am, edited 1 time in total.
chrisb
Veteran
Posts: 53785
Joined: Tue Mar 17, 2015 9:14 am

Re: Boolean cutting of tubes not always working

Post by chrisb »

The uploaded file has no cut or other history so it is not clear what you tried to cut.
The Python code yields an error.

My guess is, that you have one of those coplanar issues. They occur sometimes but not always. Avoid equal radii, make one of them a tiny amount smaller or larger.
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
Doug
Posts: 46
Joined: Sun Feb 26, 2017 8:51 pm

Re: Boolean cutting of tubes not always working

Post by Doug »

Hi Chris,

Thanks very much for looking at my files. I ran the Python code from the console and saved the FCStd file, so there isn't really any history. The operation that fails is in line 96:

Code: Select all

Member1Cut = d.Member1Tube.Shape.cut(d.MemberOuter.Shape)
This operation is supposed to cut MemberOuter (a solid with the same centerline as MemberTube) from Member1Tube. The resulting Member1Cut should be missing material near the midpoint.

What was the error you got when you ran the Python code?

I tried decreasing the OD of Member1Tube by 0.01% and it worked fine :o Why would that make a difference?

Thanks again for your help :)

Cheers,
Doug
Tube Cutting v2.jpeg
Tube Cutting v2.jpeg (45.06 KiB) Viewed 1012 times
chrisb
Veteran
Posts: 53785
Joined: Tue Mar 17, 2015 9:14 am

Re: Boolean cutting of tubes not always working

Post by chrisb »

Doug wrote: Tue Jun 25, 2019 1:56 am What was the error you got when you ran the Python code?

I tried decreasing the OD of Member1Tube by 0.01% and it worked fine :o Why would that make a difference?
You're welcome. This is a well known issue coming from the geometric OCC kernel.

Weird guess: I think it is due to rounding errors which are due to finite representation of numbers in the computer. It cannot always be determeined if planes are touching or intersecting.

The python error was:

Code: Select all

 def makeEndFace(Shell, StartP, Tolerance):
...     # print(MemberName)
...     for ShellEdge in Shell.Edges:
...         # print('ShellEdge ',ShellEdge)
...         if ShellEdge.Closed:    #Found end edge
...             # print('Closed Edge')
...             CircularFace = Part.Face(Part.Wire(ShellEdge))   # Make circular end face
...             if CircularFace.isInside(StartP,Tolerance,True): #Check if face contains start point
...                 # print('StartFace')
...                 return(CircularFace)
...             else:
...                 print('No face found')
...     return()
...   
... MemberVisibility = False
  File "<input>", line 15
    MemberVisibility = False
                   ^
SyntaxError: invalid syntax
>>>   
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
Doug
Posts: 46
Joined: Sun Feb 26, 2017 8:51 pm

Re: Boolean cutting of tubes not always working

Post by Doug »

Hi Chris,
chrisb wrote: Tue Jun 25, 2019 4:35 am
Weird guess: I think it is due to rounding errors which are due to finite representation of numbers in the computer. It cannot always be determeined if planes are touching or intersecting.
Thanks - I'll keep that in mind if I run into similar issues in the future.
chrisb wrote: Tue Jun 25, 2019 4:35 am The python error was:

Code: Select all

 def makeEndFace(Shell, StartP, Tolerance):
...     # print(MemberName)
...     for ShellEdge in Shell.Edges:
...         # print('ShellEdge ',ShellEdge)
...         if ShellEdge.Closed:    #Found end edge
...             # print('Closed Edge')
...             CircularFace = Part.Face(Part.Wire(ShellEdge))   # Make circular end face
...             if CircularFace.isInside(StartP,Tolerance,True): #Check if face contains start point
...                 # print('StartFace')
...                 return(CircularFace)
...             else:
...                 print('No face found')
...     return()
...   
... MemberVisibility = False
  File "<input>", line 15
    MemberVisibility = False
                   ^
SyntaxError: invalid syntax
>>>   
I'm guessing you pasted the Python code into the console, because I just tried that and got the same error. Normally, I run macros from a file (e.g. from the menu Macro/Macros.../Execute), and then it runs fine. I don't think the Python console can handle function definitions. If I remove the functions and load them separately, the code runs when pasted into the console.

I very much appreciate your help.

Cheers,
Doug
chrisb
Veteran
Posts: 53785
Joined: Tue Mar 17, 2015 9:14 am

Re: Boolean cutting of tubes not always working [Solved]

Post by chrisb »

Yes I ran the code in console, but it's not the function definitions that pose problems. It is the two blanks on the empty line before the line
MemberVisibility = False
A Sketcher Lecture with in-depth information is available in English, auf Deutsch, en français, en español.
Doug
Posts: 46
Joined: Sun Feb 26, 2017 8:51 pm

Re: Boolean cutting of tubes not always working [Solved]

Post by Doug »

Hi Chris,

Good sleuthing! It’s odd that the spaces cause a problem in the console, but not executing from a file :?

I changed the settings in my text editor to show spaces so that I can hopefully avoid such stranded spaces in the future.

Cheers,
Doug
User avatar
Chris_G
Veteran
Posts: 2572
Joined: Tue Dec 31, 2013 4:10 pm
Location: France
Contact:

Re: Boolean cutting of tubes not always working [Solved]

Post by Chris_G »

Doug wrote: Tue Jun 25, 2019 7:55 pm Good sleuthing! It’s odd that the spaces cause a problem in the console, but not executing from a file :?
I think that because the python console executes each line one after the other, it implies that each indented block must be "ended" by a blank line.
Doug
Posts: 46
Joined: Sun Feb 26, 2017 8:51 pm

Re: Boolean cutting of tubes not always working [Solved]

Post by Doug »

Chris_G wrote: Tue Jun 25, 2019 8:25 pm I think that because the python console executes each line one after the other, it implies that each indented block must be "ended" by a blank line.
Hi Chris_G,

Indeed, you are correct! If I add a blank line before the line with two spaces, it works fine from the console. Thanks for the tip. My journey of learning Python continues :)

Cheers,
Doug
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Boolean cutting of tubes not always working [Solved]

Post by TheMarkster »

I wonder if the issue could be the 2 seamlines are intersecting? If you view them in wireframe mode you can see this better. If I add this line just before the line where the cut is made (has the effect of moving one of the pipes up in z direction by .0000001 mm)

Code: Select all

d.Member1Tube.Placement.Base.z += .0000001
it seems to work.

full code

Code: Select all

import os
import sys
import Part
import PartDesign
import Draft
import DraftGeomUtils
import math
import numpy as np
import time
from FreeCAD import Base
from Part import Wire, Face, Solid
from math import cos, sin, tan, radians, asin, acos, atan, atan2, degrees

d = App.newDocument("Tube_Cutting")
g = FreeCADGui.ActiveDocument

V = App.Vector
x = V(1,0,0)
y = V(0,1,0)
z = V(0,0,1)
Origin = V(0,0,0)

def makeMember(MemberName, MemberCL, MemberOR, MemberIR, Transparency, TubeVisibility=True):
    # print('MakeMember: ',MemberName)
    TubeName = MemberName+'Tube'
    OuterName = MemberName+'Outer'
    MemberOuterCyl = MemberOuter(MemberCL, MemberOR)
    MemberInnerCyl = MemberOuter(MemberCL, MemberIR)
    MemberTube = MemberOuterCyl.cut(MemberInnerCyl)
    d.addObject("Part::Feature",OuterName).Shape = Part.Solid(MemberOuterCyl)
    d.addObject("Part::Feature",TubeName).Shape = MemberTube
    MemberGUI = getattr(g,TubeName)
    MemberGUI.Transparency=Transparency
    MemberGUI.Visibility=TubeVisibility
    MemberOuterGUI = getattr(g,OuterName)
    MemberOuterGUI.Visibility=False
    return(MemberTube)

def MemberOuter(MemberCL, MemberOR):
    # print('MemberOuter')
    OuterShellSegmentL = []
    StartP = MemberCL.Vertexes[0].Point
    EndP = MemberCL.Vertexes[-1].Point
    MemberLen = StartP.distanceToPoint(EndP)
    Tolerance = MemberLen/10**6
    
    for CLEdge in MemberCL.Edges:
        OuterShellSegmentL.append((Part.makeTube(CLEdge,MemberOR)))
    # print(OuterShell)
    EndWire=makeEndFace(OuterShellSegmentL[0], StartP, Tolerance).OuterWire
    Tube = Part.Wire(MemberCL).makePipeShell([EndWire],True,True)
    
    return(Tube)

def makeEndFace(Shell, StartP, Tolerance):
    # print(MemberName)
    for ShellEdge in Shell.Edges:
        # print('ShellEdge ',ShellEdge)
        if ShellEdge.Closed:    #Found end edge
            # print('Closed Edge')
            CircularFace = Part.Face(Part.Wire(ShellEdge))   # Make circular end face
            if CircularFace.isInside(StartP,Tolerance,True): #Check if face contains start point
                # print('StartFace')
                return(CircularFace)
            else:
                print('No face found')
    return()
  
MemberVisibility = False
MemberTransparency = 0
CLVisibility = False

MemberOR = 35/2
MemberIR = 33.5/2

MemberStart = V(1430.0, -415.3,  909.0)
MemberEnd = V(804.2,  478.4,  897.0)
MemberMid = V((MemberStart+MemberEnd)/2+V(0,0,36))
MemberCL = Part.Wire(Part.Arc(MemberStart,MemberMid,MemberEnd).toShape())
d.addObject("Part::Feature",'MemberCL').Shape = MemberCL
MemberTube = makeMember('Member', MemberCL, MemberOR, MemberIR, MemberTransparency, MemberVisibility)

d.addObject("Part::Feature",'Member1CL').Shape = d.MemberCL.Shape.mirror(Origin,y)
Member1Tube = makeMember('Member1', d.Member1CL.Shape, MemberOR, MemberIR, MemberTransparency, MemberVisibility)
   
print('Cut tubes')
d.Member1Tube.Placement.Base.z += .0000001
Member1Cut = d.Member1Tube.Shape.cut(d.MemberOuter.Shape)
d.addObject("Part::Feature",'Member1Cut').Shape = Member1Cut

g.MemberCL.Visibility=CLVisibility
g.Member1CL.Visibility=CLVisibility
# g.MemberTube.Visibility=False
# g.Member1Tube.Visibility=False
g.activeView().viewIsometric()
g.ActiveView.fitAll()
d.recompute()
Post Reply