Likely to be expensive in terms of processing so probably better to check for flat.edwilliams16 wrote: ↑Tue Jun 28, 2022 8:06 pm So, given a method powerful enough to match curved surfaces, surely it could handle flat ones?
REVISIT: Help with computing shared face.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
Be nice to others! Respect the FreeCAD code of conduct!
-
- Veteran
- Posts: 2764
- Joined: Mon Feb 27, 2012 5:31 pm
Re: Help with computing shared face.
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
Possibly. To quote Donald Knuth:keithsloan52 wrote: ↑Tue Jun 28, 2022 9:06 pm Likely to be expensive in terms of processing so probably better to check for flat.
"We should forget about small efficiencies, say about 97% of the time: premature optimization is the root of all evil. Yet we should not pass up our opportunities in that critical 3%"
Optimizing a routine that doesn't yet exist is premature IMO.
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
This finds the common faces between two objects with general B-Spline surfaces. It's very slow - ~30 sec on my 12yo laptop, but it works.
You can optimize special cases to your heart's content, or try to find another faster, general face-matching algorithm.
It was frustrating to find that Part 3DOffset only implements one direction of offset. However, the makeOffsetShape()
method does not have this limitation.
There's probably at least a factor of two in speed immediately available by offsetting the two surfaces in opposite directions rather than both ways and fusing them. I didn't check if the direction was always determinate.
https://www.dropbox.com/s/bzzzi6bz6989v ... FCStd?dl=0
You can optimize special cases to your heart's content, or try to find another faster, general face-matching algorithm.
It was frustrating to find that Part 3DOffset only implements one direction of offset. However, the makeOffsetShape()
method does not have this limitation.
There's probably at least a factor of two in speed immediately available by offsetting the two surfaces in opposite directions rather than both ways and fusing them. I didn't check if the direction was always determinate.
Code: Select all
#find the any common face areas between Cut and Extrude001
doc = App.ActiveDocument
obj1 = doc.getObject("Cut")
obj2 = doc.getObject("Extrude001")
faces1 = obj1.Shape.Faces
faces2 = obj2.Shape.Faces
#shared are faces1[2] and faces1[5] with faces2[0]
def makeThickFace(obj, thick = 0.1):
offsets =[]
for face in obj.Shape.Faces:
fplus = face.makeOffsetShape(thick/2, thick/5,fill = True)
fminus = face.makeOffsetShape(-thick/2, thick/5,fill = True)
f = fplus.fuse(fminus)
offsets.append((f, face))
return offsets
obj1offsets = makeThickFace(obj1)
obj2offsets = makeThickFace(obj2)
#show the ones that will be in common
Part.show(obj1offsets[2][0], 'ThickFace')
Part.show(obj1offsets[5][0], 'ThickFace')
Part.show(obj2offsets[0][0], 'ThickFace')
com =[]
for o1, f1 in obj1offsets:
for o2, f2 in obj2offsets:
c = o1.common(o2)
if c.Area > 0:
com.append((c,f1,f2))
print(f' Volume and Area of Intersections {[(c.Volume, c.Area) for c, f1, f2 in com]}')
# [max([face.Area for face in c.Faces]) for c, f1, f2 in common]
#This indicates that common[1] and common[4] are surface intersections, the rest are
#edge/surface intersection with much smaller volume
#some tweaking will be required to distinguish these
#one possibility is the volumes of the latter will scale quadratically with thick
#To return the faces intersect the thickened intersections back with their parent face
Part.show(com[1][0].common(com[1][1]), 'SharedFace')
Part.show(com[4][0].common(com[4][1]), 'SharedFace')
-
- Veteran
- Posts: 2764
- Joined: Mon Feb 27, 2012 5:31 pm
Re: Help with computing shared face.
If I understand your proposed algorithm correctly in the attached sketch it would find the edge between the Box and the Cylinder and that is not a Face, or do I not understand your algorithm.edwilliams16 wrote: ↑Wed Jun 29, 2022 3:08 am This finds the common faces between two objects with general B-Spline surfaces. It's very slow - ~30 sec on my 12yo laptop, but it works.
You can optimize special cases to your heart's content, or try to find another faster, general face-matching algorithm.
Also what thickness to use, some physicists model GDML objects at the micro and nano meter level
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
If you look at the example in my file, with two objects A and B, it finds the common face between AcutB and B. It was a convenient way of generating an arbitary matching surface.
The thickness would have to scale with the model dimensions.
The thickness would have to scale with the model dimensions.
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
It is more efficient to only thicken one surface and the intersect it with an unthickened one. It works well and quickly with simple surfaces like flat and cylindrical ones. Here we have a cube corner penetrating another cube:
The algorithm quickly generates the three common faces: with the following script
However the thickened face intersection with the surfaces also creates ribbon-like intersections associated the edges. I sorted the intersections by area. The first three are the desired ones, the rest are spurious. This needs to be automated.
Setting
in the script and using the next file which cuts a chunk of cylinder from a cube. The script generates the required single face plus the edge-related detritus. (see next post - file limit...)
The algorithm quickly generates the three common faces: with the following script
Code: Select all
makecom1 = False #if True thicken obj1 faces as well
visibleNumber = 3 #toggle remaining invisible to reduce clutter
thickness = 1.0 #how much to thicken faces
def sortbyarea(comlist):
''' sort list of commons by descending order of area '''
paired = [(c, c[0].Area) for c in comlist]
spaired = sorted(paired, key = lambda paired: -paired[1])
return [c for c, area in spaired]
#find the any common face areas beteen Cut and Extrude001
doc = App.ActiveDocument
obj1 = doc.getObject("Cut")
#choose the tool object here
#obj2 = doc.getObject("Extrude001")
obj2 = doc.getObject("Box")
#obj2 = doc.getObject('Cylinder')
faces1 = obj1.Shape.Faces
faces2 = obj2.Shape.Faces
#shared is faces1[3] with faces2[0]
def makeThickFace(obj, thick = 1, pm = 0):
#pm =0 both, =1 plus , = -1 minus
offsets =[]
for face in obj.Shape.Faces:
if pm >= 0:
fplus = face.makeOffsetShape(thick/2, thick/50, fill = True)
f = fplus
if pm <= 0:
fminus = face.makeOffsetShape(-thick/2, thick/50, fill = True)
f = fminus
if pm == 0:
f = fplus.fuse(fminus)
offsets.append((f, face))
return offsets
if makecom1:
obj1offsets = makeThickFace(obj1, thick = thickness, pm = 0)
obj2offsets = makeThickFace(obj2, thick = thickness, pm = 0)
#show the ones that will be in common
#Part.show(obj1offsets[3][0], 'ThickFace')
#Part.show(obj2offsets[0][0], 'ThickFace')
com =[]
for f1 in faces1:
for o2, f2 in obj2offsets:
#c = f1.common(o2)
c = o2.common(f1) #does order matter?
#if c.Area != 0:
if len(c.Faces) != 0:
com.append((c,f1))
App.Console.PrintMessage(f' Area of Intersections com {[c.Area for c, f in sortbyarea(com)]}\n')
if makecom1:
com1 =[]
for f2 in faces2:
for o1, f1 in obj1offsets:
#c = f2.common(o1)
c = o1.common(f2)
#if c.Area != 0:
if len(c.Faces) != 0:
com1.append((c,f2))
App.Console.PrintMessage(f' Area of Intersections com1 {[c.Area for c, f in sortbyarea(com1)]}\n')
for i, cc in enumerate(sortbyarea(com)):
p = Part.show(cc[0],'Com')
if i >= visibleNumber:
p.Visibility = False
if makecom1:
for i, cc in enumerate(sortbyarea(com1)):
p = Part.show(cc[0],'Com1')
if i >= visibleNumber:
p.Visibility = False
Setting
Code: Select all
obj2 = doc.getObject('Cylinder')
- Attachments
-
- Screen Shot 2022-06-29 at 5.41.55 PM.png (37.87 KiB) Viewed 1016 times
-
- sharedfacetester4.FCStd
- (23.77 KiB) Downloaded 15 times
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
This is a 3.6MB file, so:
https://www.dropbox.com/s/b0l2zba163ndr ... FCStd?dl=0
Intersecting two thickened surfaces seems a bit more robust with these very complex faces, but it is even slower.
We seem to be running up against the limitations of Open Cascade's Boolean Common.
Re: Help with computing shared face.
Maybe this
Code: Select all
import FreeCAD
import FreeCADGui
import Part
doc = FreeCAD.newDocument("Face")
b = doc.addObject("Part::Box", "Box")
c1 = doc.addObject("Part::Cylinder", "Cylinder_1")
c2 = doc.addObject("Part::Cylinder", "Cylinder_2")
c1.Placement = FreeCAD.Placement(FreeCAD.Vector( 0,0,1),FreeCAD.Rotation(FreeCAD.Vector(0,0,1),0))
c2.Placement = FreeCAD.Placement(FreeCAD.Vector(10,0,1),FreeCAD.Rotation(FreeCAD.Vector(0,0,1),0))
doc.recompute()
s1 = doc.addObject("Part::Feature", "Shell_1")
s2 = doc.addObject("Part::Feature", "Shell_2")
s1.Shape = Part.Shell(c1.Shape.Faces).removeSplitter()
s2.Shape = Part.Shell(c2.Shape.Faces).removeSplitter()
doc.recompute()
r1 = doc.addObject("Part::MultiCommon", "Result_1")
r2 = doc.addObject("Part::MultiCommon", "Result_2")
r1.Shapes = [s1, b]
r2.Shapes = [s2, b]
doc.recompute()
rr2 = doc.addObject("Part::Reverse","Result-2_Rev").Source = r2
b.Visibility = False
c1.Visibility = False
c2.Visibility = False
s1.Visibility = False
s2.Visibility = False
r2.Visibility = False
FreeCADGui.activeDocument().activeView().viewIsometric()
FreeCADGui.runCommand('Std_OrthographicCamera',1)
FreeCADGui.runCommand('Std_PerspectiveCamera',0)
doc.recompute()
FreeCADGui.SendMsgToActiveView("ViewFit")
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
@keithsloan52
The OP wants to find shared faces between pairs of objects - not necessarily created by a cut, so I had to modify quite a bit. I used the Part::MultiCommon (which may be based on the common() method for shapes, but it seems more robust) intersecting a shell with the faces of the second object.
So the following script works quite well. Select the two candidate objects and run it.
Code: Select all
import FreeCAD
import FreeCADGui
import Part
sel = Gui.Selection.getSelection()
if len(sel) == 2:
obj1, obj2 = sel
else:
App.Console.PrintMessage('Select two objects in the Tree View')
doc = App.ActiveDocument
#obj1 = doc.getObject("Extrude001")
sh1 = doc.addObject("Part::Feature", "Shell_1")
sh1.Shape = Part.Shell(obj1.Shape.Faces).removeSplitter()
sh1.Visibility = False
#obj2 = doc.getObject("Cut")
indexlist =[]
for i, face in enumerate(obj2.Shape.Faces):
sh2 = doc.addObject("Part::Feature", "Shell_2")
sh2.Shape = Part.Shell([face]).removeSplitter()
sh2.Visibility = False
doc.recompute(None,True,True)
rcom = doc.addObject("Part::MultiCommon", "Result_Common")
rcom.Shapes = [sh2, sh1]
doc.recompute(None,True,True)
if len(rcom.Shape.Faces) == 0: # checks for null intersections, but get warning message
doc.removeObject(rcom.Name)
doc.removeObject(sh2.Name)
else:
indexlist.append(i)
indexlist1 =[]
for i, face1 in enumerate(obj1.Shape.Faces):
sh1 = doc.addObject("Part::Feature", "Shell_1")
sh1.Shape = Part.Shell([face1]).removeSplitter()
sh1.Visibility = False
for face2 in [obj2.Shape.Faces[k] for k in indexlist]:
sh2 = doc.addObject("Part::Feature", "Shell_2")
sh2.Shape = Part.Shell([face2]).removeSplitter()
sh2.Visibility = False
doc.recompute(None,True,True)
rcom = doc.addObject("Part::MultiCommon", "Result_Common")
rcom.Shapes = [sh2, sh1]
doc.recompute(None,True,True)
if len(rcom.Shape.Faces) != 0:
indexlist1.append(i)
doc.removeObject(rcom.Name)
doc.removeObject(sh2.Name)
doc.removeObject(sh1.Name)
App.Console.PrintMessage(f' {obj2.Name} Face indices {indexlist}\n')
App.Console.PrintMessage(f' {obj1.Name} Face indices {indexlist1}\n')
It creates the shared interface and lists the face indices that are members of it.
There's one annoyance, when the common between two objects is null, I get an error message in the report view, which I didn't yet figure how to suppress - some try/except block, I expect.
- Attachments
-
- sharedfacetester5.FCStd
- (53.66 KiB) Downloaded 18 times
-
- Veteran
- Posts: 3194
- Joined: Thu Sep 24, 2020 10:31 pm
- Location: Hawaii
- Contact:
Re: Help with computing shared face.
Since I started working on this, it appears I've obtained an improved common() method, which was failing earlier, causing the diversion into thickening faces. This appears to be unnecessary now.
Help now says
Help on built-in function common:
common(...) method of Part.Solid instance
Intersection of this and a given (list of) topo shape.
common(tool) -> Shape
or
common((tool1,tool2,...),[tolerance=0.0]) -> Shape
--
Supports:
- Fuzzy Boolean operations (global tolerance for a Boolean operation)
- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s3))
- Parallelization of Boolean Operations algorithm
OCC 6.9.0 or later is required.
and (non-solid!) objects on which it failed earlier now succeed. All my test files now succeed using default tolerance. Assuming that the Part::MultiCommon in my previous post was based on this new common, I rewrote the script to avoid making and deleting document objects. It is now quite fast in all tested cases, and is free of warning messages.
Help now says
Help on built-in function common:
common(...) method of Part.Solid instance
Intersection of this and a given (list of) topo shape.
common(tool) -> Shape
or
common((tool1,tool2,...),[tolerance=0.0]) -> Shape
--
Supports:
- Fuzzy Boolean operations (global tolerance for a Boolean operation)
- Support of multiple arguments for a single Boolean operation (s1 AND (s2 OR s3))
- Parallelization of Boolean Operations algorithm
OCC 6.9.0 or later is required.
and (non-solid!) objects on which it failed earlier now succeed. All my test files now succeed using default tolerance. Assuming that the Part::MultiCommon in my previous post was based on this new common, I rewrote the script to avoid making and deleting document objects. It is now quite fast in all tested cases, and is free of warning messages.
Code: Select all
'''
Script to create common face(s) of two objects. Select them and run.
A compound of the shell intersection is created and the corresponding participating faces
are enumerated in the report window.
Ed Williams (edwilliams16) 7/1/22
'''
import FreeCAD
import FreeCADGui
import Part
tolerance = 0 #default to global tolerance - tweak if required
sel = Gui.Selection.getSelection()
if len(sel) == 2:
obj1, obj2 = sel
else:
App.Console.PrintMessage('Select two objects in the Tree View')
doc = App.ActiveDocument
shp1 = Part.Shell(obj1.Shape.Faces).removeSplitter()
comlist =[]
for i, face in enumerate(obj2.Shape.Faces):
shp2 = Part.Shell([face]).removeSplitter()
#doc.recompute(None,True,True)
comshp = shp1.common(shp2, tolerance)
if len(comshp.Faces) != 0:
comlist.append((comshp, i))
indexlist1 =[]
for i, face1 in enumerate(obj1.Shape.Faces):
for face2, j in comlist:
comshp = face1.common(face2, tolerance)
if len(comshp.Faces) != 0:
indexlist1.append(i)
if len(comlist) > 0:
comdoc = doc.addObject('Part::Feature', obj1.Name + '_and_' + obj2.Name)
comdoc.Shape = Part.Compound([l[0] for l in comlist]).removeSplitter()
doc.recompute()
App.Console.PrintMessage(f' {obj2.Name} Face indices {[com[1] for com in comlist]}\n')
App.Console.PrintMessage(f' {obj1.Name} Face indices {indexlist1}\n')
else:
App.Console.PrintMessage('No common faces found\n')