Boolean operations : in search for a robust process

Hi friends,

I'm heavily using BOPAlgo_Builder class to find split parts of a CAD generated scene, where object zoology is very wide, from big large simple solids (6 faces, square angles, high volume), to very detailed solids with edge length as small as 1E-4.
Solids can be misaligned, sightly overlapping, totally disjoint, etc ...

To that purpose I'm using a 2 steps approach :
1. Pass all objects to the BOPAlgo_Builder.
2. If step 1 fails, build the result iteratively by adding solids one by one.
One iteration is a BOPAlgo_Builder between a compound of already split solids, and the one to be added.

My goal is to identify what solids cause the global "step 1" process failure, in order to either ignore them, make special treatment, or tell the user.
To illustrate, let say I have 5 solids to add (I'm using SetNonDestructive(true) to preserve operands) :
step 1 : s1 + s2 => BOPAlgo_Builder ok => gives s1* and s2*
step 2 : Compound(s1* + s2*) + s3 => ok
step 3 : Compound(s1* + s2* + s3*) + s4 => ko => s4 discarded
step 4 : Compound(s1* + s2* + s3*) + s5 => ko because BOPAlgo_AlertSelfInterferingShape on Compound !

If Compound(s1* + s2* + s3*) is bad, I should have had an alert in step 2.
Compound(s1* + s2* + s3*) being bad, I cannot add any solid to it, and the whole process is lost.

I don't understand why Compound(s1* + s2* + s3*) gets corrupted at step 3 with SetNonDestructive(true) ?
Does somebody have any advice on this one ?

Eugeny's picture

You're right about the compound of (s1* + s2* + s3*) to be correct after the operation and if it is there had to be some warning about invalidity. It is interesting to see how you obtain this compound and get the result from BOPAlgo_Builder. Could you please share a code snippet performing these operations and some shapes that you use?

o b's picture

Hi Eugeny,

I'll have to make some dedicated code because I'm using a homemade C# wrapper, and I guess you are asking for native C++.
By the way I'll provide you with C++ code and BRep quickly !

o b's picture

Eugeny,

While writing the following piece of code, I've realized that my assertion was indeed wrong.

The Compound is not becoming "bad" by trying to make a GFA with it and the next solid.

My first batch of solid was not wide enough, hence my error.

That being said, here is a code (+ attached brep) that produces many errors, despite solids being all valid by the mean of BOPAlgo_ArgumentAnalyzer.

If you have time to spend looking at it, it would be very appreciated.

//load Compound
				BRep_Builder brb;
				TopoDS_Shape brep;
				std::ifstream file;
				file.open("solids.brep");
				BRepTools::Read(brep, file, brb);
				file.close();

				//retrieve solids
				TopTools_IndexedMapOfShape map;
				TopExp::MapShapes(brep, TopAbs_SOLID, map);

				//fuse solids
				TopoDS_Compound errors;
				brb.MakeCompound(errors);
				TopoDS_Compound fused;
				brb.MakeCompound(fused);
				brb.Add(fused, map(1));
				for (int i=2; i<=map.Extent(); i++)
				{
					BOPAlgo_Builder gfa;
					gfa.SetCheckInverted(false);
					gfa.SetNonDestructive(true);
					gfa.SetParallelMode(true);
					gfa.SetRunParallel(true);
					gfa.SetToFillHistory(false);
					gfa.SetUseOBB(true);
					gfa.SetFuzzyValue(1E-3);
					gfa.SetGlue(BOPAlgo_GlueOff);

					gfa.AddArgument(fused);
					gfa.AddArgument(map(i));
					gfa.Perform();
					
					if (gfa.HasErrors() || gfa.HasWarnings())
					{
						brb.Add(errors, map(i));
						continue;
					}

					brb.MakeCompound(fused);
					for (TopExp_Explorer expl(gfa.Shape(), TopAbs_SOLID); expl.More(); expl.Next()) brb.Add(fused, expl.Current());
				}

				std::ofstream fileOK;
				fileOK.open("solidsOK.brep");
				BRepTools::Write(fused, fileOK);
				fileOK.close();

				std::ofstream fileKO;
				fileKO.open("solidsKO.brep");
				BRepTools::Write(errors, fileKO);
				fileKO.close();
Attachments: 
Eugeny's picture

The code looks OK and seems to be correct, and I was able to obtain faulty shapes while fusing shapes one by one.

The problem is in a very high fuzzy value (high for some of these shapes), which is higher than the length of some of the edges of the arguments. Try decreasing the fuzzy value to 1.e-7 and you will get the more correct results.

There is another 'problem' with non-destructive mode while fusing the 21st solid the warning about acquired self-intersection is printed, and with non-destructive turned off no warning is given at all. But even though the warning is printed the resulting shape is valid, so it is not actually a problem in the algorithm (but it is probably worth looking into).

Worth saying that fused all together in one operation the shapes produce invalid shape, you're welcome to register an issue in mantis (for both cases - one by one and all together).

Here is the tcl script that I used for testing and which you can play with (based on your code sample):

restore solids.brep sx

set nb_solids [llength [explode sx so]]

copy sx_1 result

compound errors

bfuzzyvalue 1.e-3
bnondestructive 1
bcheckinverted 0
buseobb 1

set to $nb_solids
#set to 18

for {set i 2} {$i <= $to} {incr i} {
  bclearobjects
  bcleartools
  baddobjects result sx_$i
  set msg [bfillds]
  if {[llength $msg] != 0} {
    puts "$i: ERROR:"
    puts "$msg"
    add sx_$i errors
    continue
  }

  set msg [bbuild rx]
  if {[llength $msg] != 0} {
    puts "$i: ERROR:"
    puts "$msg"
    add sx_$i errors
    continue
  }

  puts "$i : OK"
  eval compound [explode rx so] result
}

BTW, if all of your shapes look similar to this set, there is no need to use OBB, as all of the args are axis-aligned.

You say that all shapes are valid in terms of BOPAlgo_ArgumentAnalyzer, but did you set all the same options for this tool as for the BOPAlgo_Builder? I guess no because setting the fuzzy value for the Argument analyzer to 1.e-3 will make many solids invalid.

Many of the warnings printed by the algorithm are treated correctly inside and used just to draw the attention of the user. So it looks a bit strict to reject the shape after the warning is printed. The problem here is that it may be a bit hard to define which warnings to ignore and which not.

Hope it will help, Eugeny

o b's picture

Eugeny,

Thank for this huge answer, and for your precious tests.
You have pointed out almost all my problematic !
GFA not being able to perform a fuse is bad news, but I can cope with it if at least I can identify the faulty shapes.

OOBB -> agree of course.

Fuzzy -> the value of 1E-3 (one millimeter) is very high indeed, but is here to avoid very thin overlapping shapes that would ruin later processes.
I could put a very low Fuzzy (< 1E-6) and take care of thin overlaps in a second step by merging it with touching solid.
But there are still some cases where a low Fuzzy produces shapes with a high volume & a very thin part somewhere, and I can not detect that easily.
Moreover, I want to avoid small gaps between solids, and it seams that only Fuzzy can do that.

I've tested global fuzzy values from 1E-5 to 1E-3, without many luck.
In the hope of increasing global reliability, I'm currently testing a process where I group solids by mean of there smallest edge length, and then performing independent GFAs with Fuzzy = (smallest edge of the group / 1.05), and finally merging all groups in a GFA without fuzzy.

Ignoring warnings -> yes I could, but how can I then validate the produced shape ?
BOPAlgo_ArgumentAnalyzer is for internal use (as stated somewhere in the forum) as it can gives false positives.
BRepCheck_Analyzer is not able to detect problems related to BOP.

Thank you again for answering Eugeny.

Eugeny's picture

For validating the result you can use e.g. BRepAlgoAPI_Check which combines some parts of BRepCheck_Analyzer and BOPAlgo_ArgumentAnalyzer. But using it after every operation will be too costly. So you can try using it only if some warnings appear.

o b's picture

Will test it !