BooleanSplit produces closed solid polysurface while more primitive splits produce open polysurfaces. How to wind up with closed solid polysurfaces?

Given a closed solid polysurface, when split with BooleanSplit (say in two for simplicity), you wind up with closed solid polysurfaces. But when you do the same thing with Split, or SplitBrep, you wind up with open polysurfaces.

How does BooleanSplit close these open polysurfaces? More specifically, what do I have to do to produce (or at least, wind up with) closed solid polysurfaces when using SplitBrep?

UPDATE FOR CLARITY: I didn’t explicitly state this, but sort of implied it by mentioning the use of SplitBrep, but my objective is to do all the splitting within a script, not from the Rhino command prompt.

Split the cutting object with the formerly closed polysurface and join the appropriate part of the now split cutting object to the appropriate part of the formerly closed polysurface.

@davidcockey That does work, but I can’t figure out how to do this programatically (i.e. in a script). When I cut the cutting object with the object to be cut, I get two pieces (of the cutting object, in the simple case). Then, as you described, I cut the object to be cut with the (uncut) cutting object. Again, assume this results in two pieces. Now, I join the “appropriate” parts. The problem is, when the cutting object was cut with the object to be cut, resulting in two pieces, either piece can be joined with the appropriate part of the object to be cut. Anyway to tell, within a script, which pieces / parts I want?

BooleanSplit of A by B just does BooleanIntersection of A and B, and BooleanDifference A-B. It doesn’t take as long as doing two boolean operations because all booleans do a merge of the polysurfaces into one, and then decide what to throw away. The hard part is the merge, which only has to be done once internally.

@chuck The documentation for BooleanIntersection sates:

“The BooleanIntersection command trims the unshared areas of selected polysurfaces or surfaces.”

When I use BooleanIntersection with a solid object as the first set of polysurfaces and a bisecting surface as the second set (see pictures below) I get one portion of the solid, as though the solid had been split with BooleanSplit and the other portion removed.

Before BooleanIntersection:

Split%20Part%201

After BooleanIntersection:

Split%20Part%202

Based on the definition of BooleanIntersection, what makes the “surviving” part of the solid the “shared” area and the part of the solid that was removed the “unshared” area? In other words, why did one part of the solid survive while the other part didn’t? Before invoking BooleanIntersection, both parts had the same “relationship” with the cutting surface.

Because my use of BooleanIntersection or SplitBrep (or however I end up slicing and dicing the pieces / parts) will be in a script, I need BooleanIntersection to behave predictably and currently I don’t understand how it chooses which split part survives and which doesn’t.

Hi - for the closed object things are clear as the normals are pointing outward. For the plane, you will have to check which way the normals point. In your picture, the normals of the plane were pointing in the positive direction of the X-axis. The shared part of the box is on the “inside” of the plane, i.e. in the negative direction of the X-axis.
-wim

@wim Indeed. Rotating the plane 180 degrees about the Z axis confirms this; the other half now “survives.” So how does BooleanSplit work when the cutting object is a surface (something other than an object with all normals pointing in)? @chuck stated that "BooleanSplit of A by B just does BooleanIntersection of A and B, and BooleanDifference A-B." According to that description, BooleanSplit would always leave part of A missing (when the cutting object is a surface), but in reality, it does not (i.e. it leaves TWO surviving pieces that can be “unioned” back into the original piece).

Intersection leaves one part, difference leaves the other. You can see this by making copies of your inputs. Do intersection on one copy, and difference on the other.

@chuck Yes! So, why then, does scripting provide a BooleanIntersection and a BooleanDifference, but no BooleanSplit?

It’s not in rhinoscriptsyntax (yet) but it is in RhinoCommon

So it is possible to create your own function in the meantime.

I want to thank everyone who contributed to this discussion. It was very enlightening and very beneficial.

I’ve added this to the list.
https://mcneel.myjetbrains.com/youtrack/issue/RH-55776.

Thanks for pointing it out.

@chuck, @dale This is practically trivial to add to rhinoscriptsyntax, there are maybe 3 lines of code to change from the BooleanDifference method in surface.py:

def BooleanSplit(input0, input1, delete_input=True):
    """Performs a boolean split operation on two sets of input surfaces
    and polysurfaces. For more details, see the BooleanSplit command in
    the Rhino help file
    Parameters:
        input0 ([guid, ...]): list of surfaces to subtract from
        input1 ([guid, ...]): list of surfaces to be subtracted
        delete_input (bool, optional): delete all input objects
    Returns:
        list(guid, ...): of identifiers of newly created objects on success
        None: on error
    Example:
      import rhinoscriptsyntax as rs
      filter = rs.filter.surface | rs.filter.polysurface
      input0 = rs.GetObjects("Select first set of surfaces or polysurfaces", filter)
      if input0:
          input1 = rs.GetObjects("Select second set of surfaces or polysurfaces", filter)
          if input1: rs.BooleanSplit(input0, input1)
    See Also:
      BooleanDifference
      BooleanIntersection
      BooleanUnion
    """
    if type(input0) is list or type(input0) is tuple: pass
    else: input0 = [input0]
    
    if type(input1) is list or type(input1) is tuple: pass
    else: input1 = [input1]

    breps0 = [rhutil.coercebrep(id, True) for id in input0]
    breps1 = [rhutil.coercebrep(id, True) for id in input1]
    tolerance = scriptcontext.doc.ModelAbsoluteTolerance
    newbreps = Rhino.Geometry.Brep.CreateBooleanSplit(breps0, breps1, tolerance)
    if newbreps is None: return scriptcontext.errorhandler()
    
    rc = [scriptcontext.doc.Objects.AddBrep(brep) for brep in newbreps]
    if delete_input:
        for id in input0: scriptcontext.doc.Objects.Delete(id, True)
    scriptcontext.doc.Views.Redraw()
    return rc

The most work that needs to be done is adding the item to the online help file…

@mike15 Below is a “portable” version that one can use in a current script - you may need to change a couple of lines depending on how you import rhinoscriptsyntax, scriptcontext and Rhino…

BooleanSplit.py (2.0 KB)

So, I notice 3 differences between the supplied script (BooleanSplit.py) and Rhino’s behavior when BooleanSplit is entered from the command line:

  1. When DeleteInput is True, the cutter is deleted. This doesn’t happen when using the command line version.
  2. The resulting split objects do not have their respective object names set, even if the input object has a name set.
  3. And finally, if the cutting object does not intersect with the object to be split, the object to be split is still deleted if DeleteInput is True. This does not happen when using the command line version.

Why are there these differences? Isn’t the script (BooleanSplit.py) the code used when BooleanSplit is entered from the command line?

No, the native BooleanSplit command does not call rhinoscript methods, it calls a bunch of code directly into the Rhino core; the action is not the same.

First, rhinoscriptsyntax methods are not exact duplicates of native Rhino commands. Native Rhino commands are higher level and have more “bells and whistles” than rhinoscriptsyntax methods, which are more basic building blocks. Native Rhino commands can include UI (naturally), acceptance of multiple types of input objects and context-sensitive situations, object layer management, etc.

For example the Native _Split command can take points, curves, surfaces and polysurfaces as inputs and figure out all the different results possible. Whereas in rhinoscriptsyntax we have SplitCurve - which only accepts parameters (locations on a curve) as split points, and SplitBrep - which only accepts two surface or polysurface objects.

Now to answer specifics:

Yes, that was an error on my part - I just copied over BooleanDifference and modified a couple of lines, forgot one:

This line deletes the cutter:
for id in input1: scriptcontext.doc.Objects.Delete(id, True)

If you remove that line, when delete_input is true, only the original base brep will be deleted. I fixed the code in the post above.

This is one of the bells and whistles of the native Rhino command - none of the rhinoscriptsyntax Boolean methods deal with object name, color or any other object attributes - try it with rs.BooleanDifference and see. If you want to transfer object attributes, you must write that code yourself - get the original object attribs before the Boolean operation and re-attribute them to the results. For example write your code so that it does not delete the originals in the Boolean operation, then use MatchObjectAttributes with the original and the result, then finally delete the originals afterwards.

That is also like the other rs.Boolean… methods. Try rs.BooleanDifference with delete_input=True using two input objects that do not intersect. Both will disappear! In this case I believe it would be nice if the rs method did take that fact into account - but it doesn’t currently. It can be relatively easily added.

I’m having a new (since my last post) problem with the BooleanSplit.py script. I’ve posted the script, along with my modifications to it (identified by comments starting with #mbmast and the calling code to invoke the script:

import rhinoscriptsyntax as rs
import scriptcontext
import Rhino

def MySplit(sObjToCut, sCutter):
    aObjToCut = rs.ObjectsByName(sObjToCut)
    aCutter = rs.ObjectsByName(sCutter)
    
    for objToCut in aObjToCut:
        for cutter in aCutter:
            BooleanSplit(objToCut, cutter)
    
def BooleanSplit(input0, input1, delete_input=True):

    """Performs a boolean split operation on two sets of input surfaces

    and polysurfaces. For more details, see the BooleanSplit command in

    the Rhino help file

    Parameters:

        input0 ([guid, ...]): list of surfaces to subtract from

        input1 ([guid, ...]): list of surfaces to be subtracted

        delete_input (bool, optional): delete all input objects

    Returns:

        list(guid, ...): of identifiers of newly created objects on success

        None: on error

    Example:

      import rhinoscriptsyntax as rs

      filter = rs.filter.surface | rs.filter.polysurface

      input0 = rs.GetObjects("Select first set of surfaces or polysurfaces", filter)

      if input0:

          input1 = rs.GetObjects("Select second set of surfaces or polysurfaces", filter)

          if input1: rs.BooleanSplit(input0, input1)

    See Also:

      BooleanDifference

      BooleanIntersection

      BooleanUnion

    """

    if type(input0) is list or type(input0) is tuple: pass

    else: input0 = [input0]

    

    if type(input1) is list or type(input1) is tuple: pass

    else: input1 = [input1]



    breps0 = [rs.coercebrep(id, True) for id in input0]

    breps1 = [rs.coercebrep(id, True) for id in input1]

    tolerance = scriptcontext.doc.ModelAbsoluteTolerance

    newbreps = Rhino.Geometry.Brep.CreateBooleanSplit(breps0, breps1, tolerance)

    if newbreps is None: return scriptcontext.errorhandler()

    rc = [scriptcontext.doc.Objects.AddBrep(brep) for brep in newbreps]

    #mbmast added check for emptiness
    if not rc:
        return rc

    #mbmast - kludge, I'm sure there's a better way to do this.
    for newObj in rc:
        rs.ObjectName(newObj, rs.ObjectName(input0))

    if delete_input:
        for id in input0: scriptcontext.doc.Objects.Delete(id, True)

        #mbmast don't delete the cutter
        #for id in input1: scriptcontext.doc.Objects.Delete(id, True)

    scriptcontext.doc.Views.Redraw()

    return rc
    
if __name__ == "__main__":
    # call the function defined above
    #MySplit("Block", "Plane")
    MySplit("RectTrackInteriorWide", "ShellEyeExterior")
    MySplit("RectTrackInteriorWide", "ShellTrackOverhangInterior")
    MySplit("RectTrackExterior", "ShellEyeExterior")
    MySplit("RectTrackExterior", "ShellTrackOverhangExterior")
    
    aObjToSubtractFrom = rs.ObjectsByName("RectTrackExterior")
    aObjToSubtractWith = rs.ObjectsByName("RectTrackInteriorWide")
    
    for objToSubtractFrom in aObjToSubtractFrom:
        if not rs.IsObjectNormal(objToSubtractFrom):
            continue
    
        if not rs.IsObjectValid(objToSubtractFrom):
            continue
    
        for objToSubtractWith in aObjToSubtractWith:
            if not rs.IsObjectNormal(objToSubtractWith):
                continue
    
            if not rs.IsObjectValid(objToSubtractWith):
                continue
                
            print "sub from type: " + str(rs.ObjectType(objToSubtractFrom)) + ", sub with type: " + str(rs.ObjectType(objToSubtractWith))
     
            rs.BooleanDifference(objToSubtractFrom, objToSubtractWith)

The problem I’m having is:

Message: e1d89d15-db0d-4f7a-80a4-9cd40b2f5dfb does not exist in ObjectTable

Traceback:
  line 1065, in coercerhinoobject, "C:\Users\mbmas\AppData\Roaming\McNeel\Rhinoceros\6.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript\utility.py"
  line 1364, in ObjectType, "C:\Users\mbmas\AppData\Roaming\McNeel\Rhinoceros\6.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript\object.py"
  line 126, in <module>, "C:\Users\mbmas\Documents\3D Objects\Martian Eye\MyScript.py"

Line 126, in my calling code, is the print statement.

What does it mean when an object does not exist in the ObjectTable?

Didn’t check your script for functionality, but the message usually means that the object associated with the referenced GUID no longer exists - i.e. it got deleted somewhere along the line inside your script. Note that in a lot of object modification operations a new object is produced with a new GUID and the old object is deleted along with its GUID. If you reference the old GUID after, you will get a message like the above.

Yeah, it’s totally due to my mistake, which i see now very clearly. In the words of Roseanne Roseannadanna, never mind.