What does native Rhino Join do that I can't in RhinoCommon?

In the file below, there is another one of my bad volumes.

FixBadBrep.3dm (2.7 MB)

If one simply runs Explode - you will get a bad object warning if you have CheckNewObjects turned on - and then Join, you will end up with one good, closed polysurface and one bad surface which you can delete.

Now, if I try to do it script-wise - I can mimic the procedure with rhinoscriptsyntax and rs.Command():

import rhinoscriptsyntax as rs

def ExplodeRejoinObject(obj_id,tol=sc.doc.ModelAbsoluteTolerance):
    rs.UnselectAllObjects()
    rs.SelectObject(obj_id)
    rs.Command("_Explode",False)
    lco=rs.LastCreatedObjects()
    rs.UnselectAllObjects()
    rs.SelectObjects(lco)
    rs.Command("_Join",False)
    return rs.LastCreatedObjects()

obj_id=rs.GetObject("Select a polysurface",16,preselect=True)
if obj_id:
    rs.EnableRedraw(False)
    result=ExplodeRejoinObject(obj_id)
    if result:
        solid_count=0
        for item in result:
            if rs.IsObjectSolid(item):
                solid_count+=1
            else:
                rs.DeleteObject(item)
        if solid_count>0:
            rs.DeleteObject(obj_id)

The main problem is that the CheckNewObjects dialog will come up and stop the script until I hit OK. So I need to turn it off if I wnat the script to run to completion without having to do this. But, because of this issue, I have no way of knowing if it was turned on before, so if I automatically turn it back on after the script ends, that is maybe unwanted by the user. But it does succe

So, I tried to duplicate what it does with RhinoCommon and avoid the CheckNewObjects coming up by hopefully eliminating invalid objects before I add them to the document, but I was spectacularly unsuccessful up to now.

So for example just trying to explode and rejoin via RhinoCommon, but it fails to create a solid.

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def ExplodeRejoinBrep(brep,tol=sc.doc.ModelAbsoluteTolerance):
    face_coll=[face.DuplicateFace(False) for face in brep.Faces]
    joined=Rhino.Geometry.Brep.JoinBreps(face_coll,tol)
    return joined

obj_id=rs.GetObject("Select a polysurface",16,preselect=True)
if obj_id:
    obj=rs.coercebrep(obj_id)
    br_fix=ExplodeRejoinBrep(obj)
    if br_fix:
        solid_count=0
        for brep in br_fix:
            if brep.IsValid:
                sc.doc.Objects.AddBrep(brep)
                solid_count+=1
        print("{} solids added to doc".format(solid_count))
        sc.doc.Views.Redraw()

I get two valid open breps and one invalid one.

I also tried with Brep.CreateSolid:

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def ExplodeCreateSolidBrep(brep,tol=sc.doc.ModelAbsoluteTolerance):
    face_coll=[face.DuplicateFace(False) for face in brep.Faces]
    cs=Rhino.Geometry.Brep.CreateSolid(face_coll,tol)
    return cs

obj_id=rs.GetObject("Select a polysurface",16,preselect=True)
if obj_id:
    obj=rs.coercebrep(obj_id)
    br_fix=ExplodeCreateSolidBrep(obj)
    if br_fix:
        solid_count=0
        for brep in br_fix:
            if brep.IsValid and brep.IsSolid:
                sc.doc.Objects.AddBrep(brep)
                solid_count+=1
        print("{} solids added to doc".format(solid_count))
        sc.doc.Views.Redraw()

This makes a solid, but unfortunately it is invalid again.

So what does native Join magically do that I can’t do in RhinoCommon? I have a feeling it allows for joining of edges that are further out of tolerance than the file tolerance.

Probably not much. The native join command often makes bad objects. In this case it may just be dumb luck that it excludes the bad surfaces.

One thing I would suggest is that you check if the faces you extract have zero area before including them in the join. That’s one thing that will give your code a leg up on the native Rhino code.

Hi @Helvetosaur,

In V8, Brep.JoinBreps pretty much does everything the Join command does. The command does not split kinky Breps when adding them to the document. You can do that too by calling the appropriate ObjectTable.AddBrep override.

A sample - add any Brep.IsValid checks you want.

import Rhino
import scriptcontext as sc

def TestJoinBreps():
    filter = Rhino.DocObjects.ObjectType.PolysrfFilter
    rc, objref = Rhino.Input.RhinoGet.GetOneObject("Select bad polysurface", False, filter)
    if not objref or rc != Rhino.Commands.Result.Success:
        return
    
    brep_obj = objref.Object()
    if not isinstance(brep_obj, Rhino.DocObjects.BrepObject):
        return
        
    brep = brep_obj.BrepGeometry 
    if not brep:
        return
        
    if brep.IsValid:
        print("The polysurface is valid.")
        return
        
    # Explode
    subobjects = brep_obj.GetSubObjects()
    in_breps = []
    for obj in subobjects:
        if isinstance(obj, Rhino.DocObjects.BrepObject):
            in_breps.append(obj.BrepGeometry)
    
    # Join
    tol = 1.8 * sc.doc.ModelAbsoluteTolerance
    atol = sc.doc.ModelAngleToleranceRadians
    out_breps = Rhino.Geometry.Brep.JoinBreps(in_breps, tol, atol)
    if out_breps:
        for i in range(len(out_breps)):
            if i == 0:
                sc.doc.Objects.Replace(objref, out_breps[i])
            else:
                atts = brep_obj.Attributes.Duplicate()
                sc.doc.Objects.Add(out_breps[i], atts)
    
    sc.doc.Views.Redraw()

if __name__ == "__main__":
    TestJoinBreps()

– Dale

Yeah, I think you’re right about that. I somehow stumbled on one case where it actually works, most of the others I have don’t… I think it depends on the order the stuff is joined, because if I explode several of these independent objects and then re-join all, even the example where it worked doesn’t anymore.

Yes, I am in the process of integrating that, plus a few other checks. One problem is not zero area surfaces, but ones that do have an area, but very small. In the example there are several of them and they fill areas where the edges are out of tolerance. If you take them out, then you end up with an object that has naked edges.

Basically I think there’s no automatic way out of this for a certain class of objects. ShrinkWrap would be nice if it worked without breaking all the sharp edges and creating monster heavy meshes, but in its current state it’s not really usable either.

That’s not what I’m seeing.
In Rhino with no script involved, I exploded your object and then manually selected and joined all the surfaces that looked like they belonged. That is to say, the thin ones that I couldn’t see from a distance were not individual selected for joining. When I got done I ended up with what Rhino called a closed solid plus 13 surfaces that were not used in the join. I picked and joined all the surfaces one by one except the " 235" at the bottom which I window selected so there might be more bad surfaces in that group.
BadBrepExtras.3dm (525.9 KB)

After I joined it into what Rhino says is a closed solid, I ran Pascal’s testMarkOTEdges. The only edge that was connected to the 13 orphan surfaces was the one edge that your “ExplodeRejoinBrep” script refused to join. That one Pascal’s script reports has a join tolerance of .037. However. if you untrim and extend the 2 surfaces involved in that edge you will find the true intersection of those 2 surfaces is about .06mm from where the edge is.

Also, just to see what would happen I ran the script a second time on the result from the first run. The second time it joined with no naked edges.

I’m hoping to have time to work on this for Rhino 9.

– Dale

1 Like

Hopefully I’ll still be around then… :stuck_out_tongue_winking_eye: