I’m wondering if you can shed some light on what goes on under the hood in Rhino8 mesh boolean commands for the sake of us who are developing scripts that replicate them. Clearly the command line boolean operations are more robust than there were in R7.
I’m specifically interested in the new MeshBooleanOptions class. When I run a boolean difference from the command line with the automatic tolerance option set, the result is calculated correctly - although I do get a message saying ‘Original face index orig_fi is out of range.’ Running the same command with tolerance set to ‘document’ fails (in much the same way my current scripts are).
In the R8 api, there are now two methods to compute a boolean difference, one which uses MeshBooleanOptions and the older version which does not take a tolerance. I’m guessing that the command line tool depends on the new and improved version, but I’d be interested to know if there’s any pre-processing being done by the command line tool to the input meshes before the call, and exactly how ‘automatic’ tolerance is set to ensure a successful result.
I am sanity checking my input meshes before attempting a boolean with this function:
def CheckSolidMesh(mg):
“”“Fast sanity check on mesh which should be a good solid 03/24/2025 “””
return mg.IsClosed and mg.IsOriented and mg.IsSolid and mg.IsValid
My goal is to create a wrapper around Rhino.Geometry.Mesh.CreateBooleanDifference that works, most of the time.
I’m currently relaxing the tolerance of the MeshBooleanOptions object each time the result fails and checking its text log, which typically reads:
An intersection computed a wrong result. Hit is invalid.
A face required new points for splitting.
Original face index orig_fi is out of range.
But I still can’t get this code to replicate the good result from the command line tool. Any pointers would be welcome!
I ran a check on the input meshes to the boolean with:
c = mesh.Check(tl, Rhino.Geometry.MeshCheckParameters.Defaults())
which did reveal that one of the input meshes had unused vertices. These now get cleaned up with a call to:
mesh.Vertices.CullUnused().
This solves one of the problems with the TextLog issued by:
rg.Mesh.CreateBooleanDifference([source], [cutter], mbo) # mbo is a MeshBooleanOptions instance
In that it no longer reports any vertex related errors in the TextLog member of the MeshBooleanOptions instance above. All I get from the log is still:
A face required new points for splitting.
Which kind of makes me think that before attempting the mesh boolean I should run an MeshMesh intersection and add the resulting vertices to the input meshes before attempting the call to CreateBooleanDifference?
Thanks for your words of appreciation of the new mesh boolean functionality in RhinoCommon.
I am trying to figure out how to help.
On one note, the new RhinoCommon implementation tries to be fast but at the same time, keep compatibility with the old implementation, so that old plug-ins and scripts do not stop working.
At the same time, I try to provide access to new functionality deep-down in the C++ core.
With this being said, I’ll go into the details of what you asked:
This by itself is not a problem, but can still be fixed. However, the operation you’re doing also, as a side effect, is probably fixing a way more serious problem:
This means that a face was referencing a vertex that does not exist.
It is normal that, then, a hit (a instance of an intersection that will later be analyzed for splitting) does not make sense, and gets discarded so that Rhino does not crash.
This is a more subtle warning. I should probably not print it, unless a verbose output is requested. It simply means that it’s not possible to tessellate a face without adding a new vertex. This happens in case of multiple faces splitting a single face, for example, and, although it’s something that may require attention, is not necessarily a problem at all.
I hope this helps. If you need more details, probably you will need to attach a file example as well. Thanks,
Giulio
–
Giulio Piacentino
for Robert McNeel & Associates giulio@mcneel.com
When I run the attached code, the tolerance is repeatedly doubled until it reaches the absolute tolerance of the model * 256, and the logs seem to indicate that the result is getting worse.
What magic is the command line MeshBooleanDifference performing behind the scenes?
I’ll be looking at the sample later today. Generally, starting with a large tolerance is a very bad thing for the stability of the operation. If possible, the tolerance should be smaller than any feature that is being intersected.
OK; here is what tolerance values are used in the command line. It might well be that in the future, ‘Auto’ does something more, but for now it seems to work great.
internal static double GetAdmendedTolerance(RhinoDoc doc)
{
if (doc == null) return double.NaN;
if (CurrentToleranceListValue == "Zero") return RhinoMath.ZeroTolerance * 10; //old Rhino zero tolerance value
double tolerance = doc.ModelAbsoluteTolerance;
double coefficient = CurrentToleranceListValue == "Auto" ? Intersection.MeshIntersectionsTolerancesCoefficient : 1;
if (RhinoMath.IsValidDouble(coefficient))
tolerance *= coefficient;
return tolerance;
}
Success! Thanks very much. I implemented your ‘auto’ tolerance in python as:
def GetAmendedTolerance():
tol = Rhino.RhinoDoc.ActiveDoc.ModelAbsoluteTolerance
coeff = rg.Intersect.Intersection.MeshIntersectionsTolerancesCoefficient
return tol * coeff if Rhino.RhinoMath.IsValidDouble(coeff) else tol
and then initialize the MeshBooleanOperation with the return value of that function.
The resulting mesh from the call to rg.Mesh.CreateBooleanDifference([source], [cutter], mbo) is then checked and repaired if necessary. In this test case a few naked edges were found but repaired with:
mesh.HealNakedEdges(tol*10)
Will test more and see how robust this is for our application.