Different behaviour of boolean difference between GUI and PythonScript

Hi everyone !

I’m having an issue with BooleanDifference. Basically i have two closed polysurfaces: a symmetrical boat (X-axis) and a box all over the port side of the boat (Y>=0). So as to be clear, port side (‘left side’) of the boat is inside the box while starboard side (‘right side’) is outside the box. I want to perform a boolean difference so as to get the box minus the boat.

My issue is: if i do it in the GUI with boolean difference selecting subtract from BOX and subtract with BOAT it works fine. But, if I use the following script, it deletes everything … (I already verified that the 2 first lines were selecting the right solid)
box = rs.FirstObject(select = True) #select the box
ship = rs.InvertSelectedObjects() #select the boat
bool = rs.BooleanDifference(box, ship)

And if I print “bool” it shows me: [<System.Guid object at 0x000000000000008C [00000000-0000-0000-0000-000000000000]>]

Any thoughts ? Am i clear enough ?

I also tried to do it with simple geometries (a box and a sphere), trying to subtract half of the sphere from the box, and this time, the result with the script is quite random … Or i get the right thing, i.e the box minus half oh the sphere, or i get the “other part” of the sphere, the one which does not intersect the box.

Thanks a lot,
Nathan

Normally the volume to be subtracted “from” is the first chosen, then the volume to subtract. So in your first procedure, the box will remain and if the boat was a closed volume you will have a hollow in the box in the shape of half the boat.

In rhinoscriptsyntax, the order is the same. The first argument should be the GUID representing the box, the second the GUID of the boat. The result should be the same as the normal Rhino function.

If you are getting “reversed” results in either, usually it means one or more of the objects involved are not closed. But in theory, both Rhino native and rhinoscriptsyntax should give the same result with the objects in the same order.

rs.FirstObject() will select the last object added to the file. Are you sure you are selecting things in the right order in your script? Check by using rs.GetObject() instead and manually selecting each object.

–Mitch

Thank you for the answer. So I double (even triple) checked and I can confirm you this:

  • the two bodies are closed

  • called in the good order in BooleanDifference

  • rs.FirstObject() was targeting the box and rs.InvertSelectedObjects() the ship, as wanted

So as to be sure, i tried with rs.GetObject() (script below) and i got the same result, i.e no output, the operation delete the inputs.

box = rs.GetObject(“Box”) # select the box
ship = rs.GetObject(“Ship”) # select the boat
bool = rs.BooleanDifference(box, ship)

Is it possible that the rs.BooleanDifference has a problem ? Or am I missing a point ?

Nathan

OK, sorry, in that case, without having the file to test, it will be hard to determine the problem…

–Mitch

You will find attached my geometry with the box.

I have few things to tell you about it. As it is, I am facing a classical problem of boolean difference, i.e as the symmetrical plan of the boat is combined with the face of my box, I have to perform slight moves in -Y direction. That I understand.
But I thought that one slight move with a value of E-6 (which is my document’s absolute tolerance) should be enough, at my surprise it wasn’t. Well, if someone has an explanation?
So, I have to successively move the boat up to a total displacement of 0.000126 in -Y direction for the boolean difference to perform, but then the result of the boolean is what I explained in my first post: nothing.

NEW:
As I was losing patience I performed a move in -Y direction of 0.001 and tadaaa the boolean finally worked …
So, from zero move to 0.000125 boolean difference fails. From 0.000126 to something like 0.001 all my objects disappear. More than 0.001, it works.
EDIT: To be exact, from 0 to 0.000125, boolean difference fails, result = None ; from 0.000126 to 0.000249, boolean difference is performed but everything disappear ; from 0.00025 boolean difference is a success.
Moreover, I have no way to know, before the BooleanDifference, if it is going to result in nothing or the correct output.

Is there a tolerance I can’t find, that I need to change ? Or is there limitations within Rhino ?

The main problem, obviously, is that I need this kind of E-06 precision for the next steps on another software …

Nathan
Boolean_Difference.3dm (1.4 MB)

The best thing to do is stop using Booleans. That alone will be a huge step forward.

Booleans make you stupid. They actually destroy brain cells and will eventually turn you into a complete idiot if you remain addicted to them.
Booleans are also a huge time waster. People spend hours a even days setting things up to do a boolean instead of spending a few minutes to take the necessary steps to go directly to the result.

The reason the Boolean fails is due to the degenerate surfaces that you used to make the bow of your boat with.

If you want to live with degenerate geometry you can still easily divide this in half.

Given that everything but the flat stern are symetrical surfaces, you can just extract the surfaces in one half and split the stern with a line.

Hi Jim,

Actually i’m working on a ‘quasi’ automatic script which won’t be used by me, so it’s not in my interest to intervene manually on the geometry to do such things (like dividing the geometry in half).

Also I don’t understand where you see degenerate surfaces on the bow, could you lighten me on that point ? How do you see them ?

I am pretty sure there is a tolerance somewhere I could play on … This can’t be a coincidence that I have to Move the boat in -Y axis by exactly 0.00025 to make the Boolean Difference properly work …

Nathan

Booleans are already automated processes. What part are you trying to automate?

The Boolean difference you are asking Rhino to perform means you are asking Rhino to trim off the half of the boat that is outside the box and cut a hole in the box face that intersects the boat and then join the parts into one.
You can do this process yourself if you first extract the problematic bow surfaces. After that you can trim off the rest of the surfaces that are outside the box. You can then join the one bow surface to the half you want to keep and use the boundary curve to trim a hole in the Box face. After that the parts can be joined to a solid closed polysurface

The first indication is when you notice commands failing. An effective way to identify degenerate surfaces is to use OffsetSrf. A degenerate surface will have a haywire offset. If you ever would want to add thickness to the hull that would also fail.

But you said that doesn’t produce the result you want so how can you call that working properly?
If you move the geometry far enough off the centerline then Rhino can find a complete intersection and then it can do the rest of the steps of trimming off what is not part of the result and joining what remains.

Hi everyone,

Hate to do that but I have to resurrect this old topic as I still face this weird problem. I am also taking this opportunity to clarify the issue.

Context:
I need an automatic script to:

  1. take a full ship geometry (closed poly-surface)
  2. add a domain box that covers half of the ship
  3. perform a boolean difference to get dom - ship
    The goal is to prepare the geometry for half-body simulation in CFD. This script aims at being used by many users using their own ship geometries and manual intervention is out of question, hence the necessity to have the Boolean Difference working consistently.

Inputs:

  1. a closed polysurface: no naked edge, no non-manifold, not a bad object
  2. a domain box covering half the ship

Issue:

  • running the following code, the value bool_result returns: [<System.Guid object at 0x00000000000000BC [00000000-0000-0000-0000-000000000000]>], and the boolean difference result has not been created.

geom = rs.AllObjects() # Gets the ship
print “geom: " + str(geom)
rs.Command(”-_Box _Diagonal -9,0,-4.5 7.5,6,3 ")
dom_box = rs.LastCreatedObjects(select=True) # Gets the box
print "dom_box: " + str(dom_box)
bool_result = rs.BooleanDifference(dom_box, geom, delete_input=False)
print bool_result

  • performing the boolean difference operation manually with the exact same inputs succeeds, the Boolean difference exists and has a proper GUID.

Question:
Isn’t it a defect in the python command?
If not, what could be the reason for such a difference?

I attach my input geometry (ship) for you to see for yourself.

Best regards,
Nathan
onr5613-full_appendage_f2.3dm (898.3 KB)

Hi Nathan,

i will try to explain what happens and why. First, you are using the RhinoScript method rs.BooleanDifference which behind the scenes uses RhinoCommon to make the actual Boolean and tries to add the result to the document. Sometime within the Rhino 6 development cycle, the developers decided that bad objects are not allowed to be added to the document. So you get a System.Guid.Empty as result. This happens if you try to add a bad object to the document.

Lets dig a bit deeper and show some code to ask for the ship, create the box and then do the boolean difference in order to analyze the result:

import Rhino
import scriptcontext
import rhinoscriptsyntax as rs

def DoSomething():
    
    ship_id = rs.GetObject("Select Ship", 16, True, False)
    if not ship_id: return
    
    ship_brep = rs.coercebrep(ship_id, True)
    
    a = Rhino.Geometry.Point3d(-9.0, 0.0, -4.5)
    b = Rhino.Geometry.Point3d( 7.5, 6.0, 3.0)
    bbox = Rhino.Geometry.BoundingBox(a, b)
    box_brep = bbox.ToBrep()
    
    tolerance = scriptcontext.doc.ModelAbsoluteTolerance
    results = Rhino.Geometry.Brep.CreateBooleanDifference(ship_brep, box_brep, tolerance)
    if not results: print "No Boolean result"; return
    
    for brep in results:
        rc, log = brep.IsValidWithLog()
        if not rc:
            print "Invalid Brep created: {}".format(log)

DoSomething()

If you run this with your file, you’ll see that this gives a boolean result, but it is not valid. The error in the brep is:

Invalid Brep created: brep.m_L[9] loop is not valid.
loop.m_type = 0 (must be 1=outer, 2=inner, or 3=slit)
brep.m_F[5] face is not valid.
brep.m_L[face.m_li[3]=9] is not valid.
ON_Brep.m_F[5] is invalid.

Now why does this happen with Python but not when the Boolean is created manually in Rhino ? Rhino does clean up certain things during commands which code does not, unless you code it.

If you inspect your geometry visually, some areas appear suspicious to me, eg. look at the isocurves at the front of the ship, the overshoot their trim curves:

Or look at the rendermesh near the aft section, some faces are not created:

Both errors indicate that something is wrong with tolerances in your file and the geometry was probably created with insufficient accuracy.

In generaly i would expect booleans to fail if you’re trying to bool exactly or near exactly cooincident edges, which is what you do.

To get over this without too much involvement, you could either script Rhino’s _BooleanDifference command using rs.Command, or make the script control the boolean tolerance and make it detect an invalid brep in order to attempt a repair, so the result can be added to the document like in below example.

Nathan.py (1.1 KB)

_
c.

Hi Clement,

Thanks for your swift answer!

Indeed this makes a lot of sense, thanks for the input.
I see the issues indeed… It is a bit annoying not to be able to catch them programmatically (I would expect a bad object here for instance), but I guess this is not an easy task.

I agree with you, unfortunately ships that we try to cut in half are by definition symmetric so we always have edges at the symmetry plane. I guess we can only recommend extra attention for the construction at the symmetry plane. Here the geometry comes from an IGES format which adds an extra layer of possible problems…

Thanks anyway, I will try to see if I can script a function using _BooleanDifference in case rs.BooleanDifference fails.

Best regards,
Nathan

Hi guys,

If anyone falls in the same case, here is the rs.Command allowing me to throw the GUI command via python:

rs.Command("-_BooleanDifference _SelID {} _Enter _DeleteInput=No _SelID {} _Enter".format(dom_box, " _SelID ".join(str(obj_el) for obj_el in ship)))
bool_result = rs.LastCreatedObjects(select=True)

Note: in my case dom_box is always a single object, while ship is always a list of objects.

To any developer, I think it would make sense to align the Python command behavior with the GUI one.

Best regards,
Nathan