# Rhino 9 Beta: MeshBooleanSplit regression with coincident faces

**URL:** <https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094>\
**Category:** Rhino for Windows\
**Tags:** windows, mesh, meshbooleansplit\
**Created:** [September 28, 2026, 7:49pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094 "2026-09-28T19:49:19Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![Teo](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/teo/32/769361_2.png) [@Teo](https://discourse.mcneel.com/u/Teo)\
**Post date:** [September 28, 2026, 7:49pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/1 "2026-09-28T19:49:19Z")

</div>

Tested in Rhino 7.38, Rhino 8.35 and Rhino 9.0.26251.12303 (Beta), Windows x64. Unless noted, the input meshes are Brep boxes converted with `Mesh.CreateFromBrep` using `MeshingParameters.Coarse` (6 quads per box). The attached script runs unmodified in all three versions and judges every result against the geometrically correct answer, never against another Rhino version.

## What happens

In Rhino 9 Beta, `Mesh.CreateBooleanSplit` / `_MeshBooleanSplit` returns a wrong result whenever the cutter shares a box face with the target. It gives no warning. In the main case (same normal orientation):

- The **outer piece** keeps the full shared box faces of the target, untrimmed. It is `IsClosed=True` but has non-manifold edges.
- The **inner piece** consists only of the cutter’s non-shared box faces. It has no mesh faces on the shared box faces, so it is an open shell.

`CreateBooleanDifference` and `CreateBooleanIntersection` on the same input are correct in Rhino 9 Beta. The same Split cases (T01–T06) are correct in Rhino 7. With a single shared face, Split is also correct in Rhino 8, so that case is a regression in Rhino 9.

The report focuses on the Split regression in Rhino 9. The extended table below also shows cases where Rhino 7 or Rhino 8 are wrong (T09, T12, T13, T16–T18) and cases where all three versions fail (T17); those are noted for context but are not the subject of this report.

## Reproduction

1. A = mesh box `(-5,-5,-5)..(5,5,5)`, B = mesh box `(0,0,0)..(5,5,5)`. B is inside A and shares its +X, +Y and +Z box faces with A.
2. `Mesh.CreateBooleanSplit([A], [B])`, or `_MeshBooleanSplit` with A as the object to split and B as the cutter.
3. Check both pieces for closedness, naked and non-manifold edges, and the mesh face area on each box face.

The attached script builds all the geometry itself, runs every case below and prints CORRECT / WRONG for Split, Difference and Intersection. It also leaves inputs and results in the document for inspection.

## Measurement

Main case in Rhino 9 Beta: mesh face area on A’s +X box face (x = 5). The +Y and +Z box faces give identical results. Outer piece: 54 vertices, 27 mesh faces. Inner piece: 12 vertices, 6 mesh faces, all on B’s box faces x=0, y=0, z=0.

```auto
                     Split outer Split inner Difference Intersection
area on +X @ x=5 100.00 0.00 75.00 25.00
closed True False True True
naked edges 0 6 (len 30) 0 0
non-manifold edges 6 (len 30) 0 0 0
volume (1000) (62.5) 875 125

```

Expected: two closed pieces, 875 and 125. The six non-manifold edges of the outer piece and the six naked edges of the inner piece are the same line: the inner boundary of the three 5×5 patches, total length 30. The volumes of the Split pieces are shown in brackets because they are not meaningful for a non-manifold or open mesh.

Results across versions (Split / Difference / Intersection, C = correct, W = wrong). Only tests that run in all three versions are listed; the `MeshBooleanOptions` tests (GT01–GT06) are described separately, because Rhino 7 does not have that API.

```auto
case Rhino 7.38 Rhino 8.35 Rhino 9 Beta
T01 B=(0..5)^3, 3 shared faces C C C W W W W C C
T02 B=(0,0,0)..(4,4,5), 1 shared face C C C C C C W C C
T03 B=(0,0,0)..(4,5,5), 2 shared faces C C C W W C W C C
T04 B=(0,0,0)..(5,5,3), 2 shared faces C C C W W W W C C
T05 B=(-2,-2,0)..(2,2,5), patch mid-face C C C C C C W C C
T06 B=(0,0,0)..(5,5,8), passes through z=5 C C C W W W W C C
T07 roles swapped, cutter A (nothing to cut) W W C W W W W C C
T08 B touches A from outside (nothing to cut) W C W W W W W C C
T09 B=(-1..1)^3 strictly inside A, no shared W W W C C C C C C
T10 script-built boxes, double, welded C C C W W W W C C
T11 script-built boxes, double, unwelded C C C W W W W C C
T12 triangulated boxes W W C W W W W C C
T13 subdivided, vertices aligned W W W W W W W C C
T14 subdivided, vertices not aligned C C C W W W W C C
T15 rotated 90 deg (exact) C C C W W W W C C
T16 double precision, rotated 37 deg W W W W W W W C C
T17 single precision (CreateFromBrep), rot 37 W W W W W W W W W
T18 CreateFromBrep + UseDoublePrecision, rot 37 W W W W W W W C C
G01 d = 2e-09 C C C W W W W C C
G02 d = 3e-09 C C C W W W C C C
G03 d = 5e-09 C C C W W W C C C
G04 d = 1e-08 C C C W W W W W W
G05 d = 3e-08 C C C W W W W W W
G06 d = 7e-08 C C C W W W W W W
G07 d = 1e-07 C C C C C C C C C
G08 d = 1e-06 C C C C C C C C C
G09 d = 0.001 C C C C C C C C C

```

27 tests × 3 operations = 81 results. The Rhino 7 log reports `60 of 81 operations correct`.

Rows T07–T08 are the two edge cases that belong to the same family as the main case (nothing to cut). They are wrong in all three versions, with different failure modes in each. Row T09 (control, no shared faces) is wrong only in Rhino 7; it is correct in Rhino 8 and Rhino 9. In rows T12, T13, T16 and T18 the Rhino 7 and Rhino 8 failures are unrelated to this report; the Rhino 9 column shows the same Split bug as the main case. T17 is different: all three operations fail in all three versions; it is discussed under **Separately**.

In Rhino 9 Beta, Split is wrong in every tested case with a shared face (T01–T08, T10–T18 and G01), and Difference and Intersection are correct in all of them, except the single-precision rotated case described under **Separately** (T17). This holds for welded and unwelded boxes, single- and double-precision vertices, triangulated and subdivided meshes (aligned and non-aligned vertices), and rotations of 90° and 37°. The results are deterministic (three identical runs). On the main case, `_-MeshBooleanSplit` gives the same pieces as the API (same vertex and mesh face counts, topology and volumes).

In Rhino 8, Split returns the same pieces as Difference + Intersection in most tested cases — but not in T13 (subdivided, aligned), T14 (subdivided, non-aligned), T16 (rotated 37°), nor in GT02 and GT04 among the tolerance tests. Every case with shared faces except T02 and T05 has at least one wrong operation. In Rhino 9, Difference and Intersection were fixed, but Split was not.

Two related symptoms in Rhino 9 Beta, probably from the same cause (Difference and Intersection are correct in both):

- **Target inside the cutter** (split B = (0..5)^3 by A, 3 shared faces): nothing should be cut. Split returns B as two open shells of 6 mesh faces each: its 3 box faces lying on A’s boundary, and its 3 interior box faces.
- **Opposite orientation** (B = (5,0,0)..(10,5,5) touching A’s +X box face from outside): Split returns A correctly, plus a spurious zero-volume piece at x = 5 — V8 F4 in Rhino 9, i.e. two back-to-back 5×5 patches, triangulated.

In Rhino 7 these two cases return the correct geometry plus an empty mesh (V0 F0) appended to the result. The geometry is right; the empty mesh is cosmetic. In Rhino 8 both cases return open, wrong meshes. For the opposite-orientation case (T08), the Rhino 9 behaviour is closest to Rhino 7 — correct geometry, spurious extra piece — but the extra piece now has volume 0 and 4 mesh faces instead of being empty. For the target-inside-cutter case (T07), Rhino 9 returns B split into two open shells, so the geometry is not correct.

## Possible lead

- **Where the patch goes.** A marker vertex on A’s shared box face shows that Rhino 9’s Split puts A’s mesh faces of the patch into the **outer** piece, and B’s coincident box face is discarded. Intersection keeps the same A mesh faces but puts them in the correct (inner) piece, and so does Rhino 7 Split. In Rhino 8, the same marker ends up in **both** pieces (`piece0=True piece1=True`), which is a third, also wrong, behaviour.
- **What triggers it.** The coincidence test itself. With `B = (0..5-d)^3`, Split fails for every d below `MeshBooleanOptions.Tolerance` and is correct above it. This was tested for t = 1e-12, 2.3283064365386963e-09 (the default), 1e-6, 1e-4 and 1e-2, in both Rhino 8 and Rhino 9. At d = t the result varies.

This suggests that the Split path classifies patches lying on the cutter’s boundary as “outside”, instead of routing them the way Difference and Intersection do. This is an inference from measurements only.

## Expected

Split in Rhino 9 should return the same two pieces as Difference + Intersection, as it does in Rhino 7 for all T01–T06 cases and in Rhino 8 for T02 and T05.

## Workaround

In Rhino 9 Beta, `CreateBooleanDifference` + `CreateBooleanIntersection` give the correct result in every tested case with shared faces, except the single-precision rotated case described under **Separately**.

## Separately

Nearly coincident faces break all three mesh booleans in Rhino 8 and Rhino 9, but not in Rhino 7. With B = (0..5+d)^3 protruding from A by d, the behaviour is not monotone in d (see G01–G09 above). These gap tests (G01–G09, GT01–GT05) and the tolerance test use script-built double-precision boxes, because single-precision vertices cannot store gaps this small.

- d = 2e-9: Rhino 9 Split wrong, Difference/Intersection correct.
- d = 3e-9, 5e-9: all three correct in Rhino 9.
- d = 1e-8 … 7e-8: all three return open meshes in Rhino 8 and Rhino 9.
- d ≥ 1e-7: all three correct in every version.

Rhino 7 is correct in this band, but is wrong in T09, T12, T13 and T16–T18 (see table).

In Rhino 9, `MeshBooleanOptions.Tolerance = 1e-6` makes these gaps count as coincident. Difference and Intersection are then correct, while Split shows the bug above. This is shown by GT01–GT06, which are only run in Rhino 8 and Rhino 9 because Rhino 7 has no `MeshBooleanOptions` API:

```auto
case Rhino 8.35 Rhino 9 Beta
GT01 d = 2e-09, Tolerance = 1e-6 W W W W C C
GT02 d = 1e-08, Tolerance = 1e-6 W W W W C C
GT03 d = 3e-08, Tolerance = 1e-6 W W W W C C
GT04 d = 7e-08, Tolerance = 1e-6 W W W W C C
GT05 d = 1e-07, Tolerance = 1e-6 W W W W C C
GT06 single precision rot 37, Tolerance = 1e-6 W W W W C C

```

This case is easy to hit in practice. Meshes from `Mesh.CreateFromBrep` have single-precision vertices, and rotating the main case by 37° moves the vertices by about 2.5e-7. After that, all three operations fail in all three versions (T17). In Rhino 9, setting `UseDoublePrecisionVertices = True` before the transform makes Difference and Intersection correct again — but **not** Split (T18: Split W, Diff/Int C).

Possibly related: [RH-87453](https://mcneel.myjetbrains.com/youtrack/issue/RH-87453) (MeshBooleanIntersection: Results in Open Mesh), from [this thread](https://discourse.mcneel.com/t/mesh-booleans/194553).

Python script: [MeshBooleanSplit\_coincident\_faces.py](https://discourse.mcneel.com/uploads/short-url/ipIOu7nI6LIMxVn63DzSLLyPFyC.py) (24.5 KB)

---

<div class="post-metadata">

**Author:** ![wim](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/wim/32/747482_2.png) [@wim](https://discourse.mcneel.com/u/wim)\
**Post date:** [September 29, 2026, 4:28pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/2 "2026-09-29T16:28:30Z")

</div>

Hi Teo -

> [@Teo](#):
>
> In Rhino 9 Beta, `Mesh.CreateBooleanSplit` / `_MeshBooleanSplit` returns a wrong result whenever the cutter shares a box face with the target.

I’ve put this on the list as [RH-99121](https://mcneel.myjetbrains.com/youtrack/issue/RH-99121) MeshBooleanSplit: Failure Case  
-wim

---

<div class="post-metadata">

**Author:** ![piac](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/piac/32/769366_2.png) [@piac](https://discourse.mcneel.com/u/piac)\
**Post date:** [October 1, 2026, 3:11pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/3 "2026-10-01T15:11:26Z")

</div>

I’m looking at your report right now. More info will follow soon.

---

<div class="post-metadata">

**Author:** ![piac](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/piac/32/769366_2.png) [@piac](https://discourse.mcneel.com/u/piac)\
**Post date:** [October 1, 2026, 9:59pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/4 "2026-10-01T21:59:12Z")

</div>

Updates. The fix for most cases is a single small bug. This will go out immediately. I am looking at the remaining 3-4 cases, which do not look as simple unfortunately.

EDIT: We like your tests. Can we adapt them and run them in the public [MxTesting](https://github.com/mcneel/mx_testing) suite and internally? This will ensure all the tests you care about will be considered for any change.

---

<div class="post-metadata">

**Author:** ![Teo](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/teo/32/769361_2.png) [@Teo](https://discourse.mcneel.com/u/Teo)\
**Post date:** [October 2, 2026, 7:10am UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/5 "2026-10-02T07:10:21Z")

</div>

Hi Giulio,

Feel free to use the script for further modification and testing,  
in MxTesting, internally, or anywhere else. Glad it’s useful.  
To be honest, I wouldn’t have found this bug without DeepSeek and Claude  
helping me dig through the measurements and narrow it down.  
Credit where it’s due. Thanks for tracking down the quick fix on the main cases.

---

<div class="post-metadata">

**Author:** ![brian](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/brian/32/504171_2.png) [@brian](https://discourse.mcneel.com/u/brian)\
**Post date:** [October 6, 2026, 9:55pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/6 "2026-10-06T21:55:21Z")

</div>

[RH-99121](https://mcneel.myjetbrains.com/youtrack/issue/RH-99121) is fixed in Rhino [BETA](https://discourse.mcneel.com/t/welcome-to-serengeti/9612)

---

<div class="post-metadata">

**Author:** ![piac](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/piac/32/769366_2.png) [@piac](https://discourse.mcneel.com/u/piac)\
**Post date:** [October 8, 2026, 1:26pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/7 "2026-10-08T13:26:31Z")

</div>

I should add a small caveat to this.  
We fixed the cases in your tests more, but mostly, there’s also some additional tweaks to know about.

> [@piac](#):
>
> I am looking at the remaining 3-4 cases, which do not look as simple unfortunately.

For these cases, our engine was already computing something different because some faces were overlapping under the tolerance of the test, and the volume was computed from the answer without tolerance. We thus relaxed these cases internally a little bit. If you feel they need more tweaks, please do let me know!

---

<div class="post-metadata">

**Author:** ![Teo](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/teo/32/769361_2.png) [@Teo](https://discourse.mcneel.com/u/Teo)\
**Post date:** [October 8, 2026, 2:59pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/8 "2026-10-08T14:59:41Z")

</div>

Hi Giulio,

Thanks for the fix and the explanation, makes sense given the ecosystem around Rhino.  
All 99 operations are correct now, including G04–G06, T17 and T07/T08.  
One small thing: with Tolerance = 0.01, d = 1e-3 and d = 5e-3 still return wrong. Side effect of the relaxation? Happy for the tests to stay in MxTesting. Good to see them useful.

---

<div class="post-metadata">

**Author:** ![piac](https://sea3.discourse-cdn.com/mcneel/user_avatar/discourse.mcneel.com/piac/32/769366_2.png) [@piac](https://discourse.mcneel.com/u/piac)\
**Post date:** [October 8, 2026, 9:09pm UTC](https://discourse.mcneel.com/t/rhino-9-beta-meshbooleansplit-regression-with-coincident-faces/223094/9 "2026-10-08T21:09:30Z")

</div>

> [@Teo](#):
>
> Side effect of the relaxation?

Yes, correct. (it’s a good thing, I’ll try to come back with a deeper explanation soon, but see your [adapted mx\_testing tests](https://github.com/mcneel/mx_testing/blob/rhino-9.x/MxTests/MeshBooleanCoincidentFacesSdkTests.cs))
