How can I split mesh by edges?

Cut Cut (18.0 KB)

So, I have a Pikachu, I like to split it into two parts, the bun and the body.
Now I have the cutter edges as lines and also indexes of the edges.
How can I split the mesh with these two data?

I can do it in many ways, but I really like to know how to split a mesh with its edges.

Edit: This guy is not Pikachu, he is Bulbasaur.
Pokémon_Bulbasaur_art (1)

This is not a Pikachu, but a Bulbasaur (Lvl. 1). It’s the first Pokémon in the Pokédex, if I remember right. :rofl:


Yes, you are right! Bulbasaur


This could be done using some recursive on the topology of faces. Make an array of taken faces.
Take a face
Go to all faces not having a fence edges
Do it until tou are stopped

If you need it I could make a c# script

As Laurent (C#) said.


  1. Check Mesh (always do that) AND the edges “path” (ditto).
  2. Get the EF Connectivity (i.e. TopologyEdges VS Faces).
  3. Do a first pass sampling all the related Faces.
  4. Classify/Cluster Faces: i.e. the ones “in one side” of a given edge … and the others. Create 2 bool[ ] arrays for the indices of the sampled Faces. That said a similar Array has all items false at definition phase.
  5. Then is a matter of elementary Recursive “expansion” until no new Face is found. If you want to monitor the evolving state of things (I always do that) use a DT (as public var) and access (per Loop) the last branch (i.e the Loop-1 one).
1 Like

Here a little tool

Cut Cut Mesh Index (20.2 KB)

Not tested a lot, it works here with awful edges BUT it will crash Rhino with big meshes (surely too much recursion)

  private void RunScript(Mesh mesh, List<int> edgesIndexes, ref object A)
    List<List<int>> lst_lst_faces = new List<List<int>>();

    bool[] isFacesTaken = new  bool[mesh.Faces.Count];

    int nextIndex = 0;
    int count = 0;
    int limit = mesh.Faces.Count * 3;//Surely too much
    while (count < limit && NextFace(isFacesTaken, ref  nextIndex))
      List < int> lst_faces = new List<int>{nextIndex};
      Recursive(mesh, ref isFacesTaken, ref  lst_faces, nextIndex, edgesIndexes);

    //Construction of meshes
    List<Mesh> meshes = new List<Mesh>();
    foreach (List<int> lst_faces in lst_lst_faces)
      Mesh newMesh = mesh.DuplicateMesh();
      foreach (int face in lst_faces)

    A = meshes;

  // <Custom additional code> 

  /// <summary>
  /// Recursive method to go from one face to other faces that are not crossing a list of fence edges and faces that are not already taken
  /// </summary>
  /// <param name="mesh">A mesh</param>
  /// <param name="isFacesTaken">Array to know if a face is already taken</param>
  /// <param name="lst_faces">List of face for a new mesh</param>
  /// <param name="curentFace">Current face index</param>
  /// <param name="edgesIndexes">List of index of edges that are fence edges</param>
  /// <returns></returns>
  void Recursive(Mesh mesh, ref bool[] isFacesTaken, ref List < int> lst_faces, int curentFace, List<int> edgesIndexes)
    isFacesTaken[curentFace] = true;

    int[] facesConnexted = mesh.TopologyEdges.GetEdgesForFace(curentFace);

    int[] edgesOnFace = mesh.TopologyEdges.GetEdgesForFace(curentFace);
    foreach (int edgeIndex in edgesOnFace)
      int[] faces = mesh.TopologyEdges.GetConnectedFaces(edgeIndex);
      foreach (int faceIndex in faces )
        if (!isFacesTaken[faceIndex] && !IsInList(edgesIndexes, edgeIndex))
          Recursive(mesh, ref isFacesTaken, ref lst_faces, faceIndex, edgesIndexes);

  /// <summary>
  /// Return true is index is in list
  /// </summary>
  /// <param name="list_indexes">List of indexes</param>
  /// <param name="index">Index to test</param>
  /// <returns>return true if index is on list_indexes</returns>
  bool IsInList(List<int> list_indexes, int index)
    bool output = false;
    foreach (int indexTest in list_indexes)
      if (indexTest == index)
        output = true;
    return output;

  /// <summary>
  /// Return true is a face is not taken
  /// </summary>
  /// <param name="isFacesTaken"></param>
  /// <param name="nextIndex">Index of next face if return is true</param>
  /// <returns>Return true is a face is not taken</returns>
  bool NextFace(bool[] isFacesTaken, ref int nextIndex)
    bool output = false;

    for (int i = 0; i < isFacesTaken.Length; i++)
      if (!isFacesTaken[i])
        output = true;
        nextIndex = i;
    return output;

Thank you all!
I found this. Seems promising.
I am now trying to implement it with my very very limited C# knowledge.

Here’s a way simpler Python script that splits a mesh along an edge loop:

"""Splits a mesh along an edge loop."""

__version__ = "0.01 (2023-12-29)"
__author__ = "diff-arch ("

import Rhino.Geometry as rg
import scriptcontext as sc
import copy

MTOL = sc.doc.ModelAbsoluteTolerance

def get_topo_vertex_indices_rec(mesh, edge, __vindices=[], __count=0):
    """Returns exactly two indices of topology vertices of the mesh
        that are closest to the end points of the edge."""
    if len(__vindices) == 2 or __count > 1:
        return __vindices
    curr_indices = copy.deepcopy(__vindices)
    end_pts = [edge.From, edge.To]

    for i in range(M.TopologyVertices.Count):
        pt = rg.Point3d(M.TopologyVertices[i])
        if end_pts[len(curr_indices)].DistanceToSquared(pt) < MTOL**2:
            return get_topo_vertex_indices_rec(
                mesh, edge, curr_indices, __count=__count+1

if __name__ == "__main__":
    edge_indices = []
    loop_segments = L.GetSegments()

    for segment in loop_segments:
        vertex_indices = get_topo_vertex_indices_rec(M, segment)
        if len(vertex_indices) != 2:
        v1, v2 = vertex_indices
        edge_idx = M.TopologyEdges.GetEdgeIndex(v1, v2)

    if len(edge_indices) != len(loop_segments):
        raise RuntimeError("Unable to detect all loop edges.")

    M.UnweldEdge(edge_indices, True)

    F = M.ExplodeAtUnweldedEdges()

The mesh has to be fully welded in order for this to work!

Cut Cut (24.7 KB)



Here’s a more capable, improved version of my script!

It now supports multiple loops to split a mesh and it it doesn’t rely on a completely welded input mesh any more, which has several benefits. Instead it makes a copy of the input mesh, welds it internally, splits that one with the edge loops, and reinstates the unwelded edges of the initial mesh on the split mesh fragments.
As previously, the edge loops are polylines that correspond to connected mesh edges. Whether they are closed loops or open chains of edges doesn’t matter.


Cut Cut (25.6 KB)


For some reason it doesn’t work on R8

Very strange GetSegments is there since R5

There is also another way I implemented in order to keep a unique mesh. I added a material index to each face. This allow to subdivide a mesh and keeping the border connected. Binary pattern - #3 by laurent_delrieu
Mesh clustering - #10 by laurent_delrieu

Works fine for me! It’s been created in Rhino 8 (8.2.23346.13002, 2023-12-12) for macOS.

I guess ‘unwelding’ isn’t ‘splitting’, but:

I copied the script in R7 and it works. Seems to be a Rhino/Python bug in R8 Windows. @eirannejad does I miss something (a library to install ?)

Rhino 8 SR3 2023-12-26 (Rhino 8, 8.3.23360.13001, Git hash:master @ eb46d24144907458c3e3bb82d501b5499b1c4a49)
License type: Commerciale, build 2023-12-26
License details: Cloud Zoo

Windows 10 (10.0.19045 SR0.0) or greater (Physical RAM: 32Gb)
.NET 7.0.1

Computer platform: DESKTOP

Standard graphics configuration.
Primary display and OpenGL: NVIDIA Quadro P2000 (NVidia) Memory: 5GB, Driver date: 3-28-2023 (M-D-Y). OpenGL Ver: 4.6.0 NVIDIA 528.89
> Accelerated graphics device with 4 adapter port(s)
- Windows Main Display attached to adapter port #0

OpenGL Settings
Safe mode: Off
Use accelerated hardware modes: On
Redraw scene when viewports are exposed: On
Graphics level being used: OpenGL 4.6 (primary GPU’s maximum)

Anti-alias mode: 4x
Mip Map Filtering: Linear
Anisotropic Filtering Mode: High

Vendor Name: NVIDIA Corporation
Render version: 4.6
Shading Language: 4.60 NVIDIA
Driver Date: 3-28-2023
Driver Version:
Maximum Texture size: 32768 x 32768
Z-Buffer depth: 24 bits
Maximum Viewport size: 32768 x 32768
Total Video Memory: 5 GB

Rhino plugins that do not ship with Rhino
C:\Program Files\SimLab\Plugins\SimLab 3D PDF From Rhino 6\plugins\SimLabPDFExporter.rhp “SimLab PDF Exporter”
C:\Users\LaurentDelrieu\AppData\Roaming\McNeel\Rhinoceros\packages\8.0\Grasshopper2\2.0.8715-wip.22923\Grasshopper2Plugin.rhp “Grasshopper2” 2.0.8715.22923
C:\Program Files\Geometry Gym\Rhino8\BullAnt.rhp “bullant”
C:\Program Files\Chaos Group\V-Ray\V-Ray for Rhinoceros 6\VRayForRhino.rhp “V-Ray for Rhino”

Rhino plugins that ship with Rhino
C:\Program Files\Rhino 8\Plug-ins\Commands.rhp “Commands” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\rdk.rhp “Renderer Development Kit”
C:\Program Files\Rhino 8\Plug-ins\AnimationTools.rhp “AnimationTools”
C:\Program Files\Rhino 8\Plug-ins\RhinoRenderCycles.rhp “Rhino Render” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\rdk_etoui.rhp “RDK_EtoUI” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\NamedSnapshots.rhp “Snapshots”
C:\Program Files\Rhino 8\Plug-ins\MeshCommands.rhp “MeshCommands” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\RhinoCycles.rhp “RhinoCycles” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\Grasshopper\GrasshopperPlugin.rhp “Grasshopper” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\RhinoCode\RhinoCodePlugin.rhp “RhinoCodePlugin” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\Toolbars\Toolbars.rhp “Toolbars” 8.3.23360.13001
C:\Program Files\Rhino 8\Plug-ins\3dxrhino.rhp “3Dconnexion 3D Mouse”
C:\Program Files\Rhino 8\Plug-ins\Displacement.rhp “Displacement”
C:\Program Files\Rhino 8\Plug-ins\SectionTools.rhp “SectionTools”

Interesting. I guess there’s no conflict between IronPython and Python 3 then in this case, because it’s a Python 3 script. :wink:

You’re running Rhino 8.3! Is this a nightly build? I’m on Rhino 8.2, which seems to be the current version for macOS.

Hi this bug is in my Rhino8 too.(in python script)windows 11 rhino_sr4

cut mesh by edge (1.1 MB)

Thank you all for your help!
I found one works exceptionally well. It was written by @Rh-3d-p. I cannot find the original post.
Here I use projected curve, but you can also use edge loops.
Not tested in R8.

private void RunScript(Mesh M, List Edges, ref object Mout)

PolylineCurve pullcrv = new PolylineCurve();
List<PolylineCurve> polylinecrv = new List<PolylineCurve>();

for (int i = 0; i < Edges.Count; i++)
  pullcrv = M.PullCurve(Edges[i], 0.001);
  polylinecrv.Add((PolylineCurve) pullcrv);

Mesh[] meshsplit = M.SplitWithProjectedPolylines(polylinecrv, 0.001);

Mout = meshsplit;

Not at all: Recursion is kinda riding (WOT) a proper Ducati: do it wrongly and hospital/morgue is waiting for you.


  1. Mastermind a way to define (as test cases etc) Edge indices paths (and check’m … for validity).

  2. Then make the FF/FE conn trees and ride that Ducati (who wants to live for ever?).

Tip: avoid any 999 (uglier Duc ever)

Moral: Ducati Uber Alles

Thanks Peter, I know the way you are doing things. I do it quite the same for others tools I have but it is always interesting to see the way you code as you exploit more .Net/Linq methods than me.

My recursive way for each face was just a lazy/easy way to implement it.

Anyway … when walking the Recursion walk try to use a (public) Tree (instead of loading again and again some growing List [of Face indices in this case]). That’s mem (heap) efficient plus it’s also handy for animations (i.e. viz the progress/Loops - so to speak).


BTW: In the above C# (the 2nd, that is) I’ve used a different approach: start from any face index, grow and then … either get this “part” of the Mesh or the other(s) [ case: more than one valid edge paths]. The only tricky thing is the validation of paths (if they are wrong you’ll get - obviously - bananas). That’s why using a Plane (in the 1st C#) is - more or less - safe (Karma helps as well).

1 Like

Cut Cut V2 (19.6 KB)

Thank you very much!
But it shows an error at my end.
I am using the latest version of Rhino 7.

  1. Solution exception:‘Guid’ object has no attribute ‘GetSegments’