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.
Thanks!
Hello
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
Check Mesh (always do that) AND the edges âpathâ (ditto).
Get the EF Connectivity (i.e. TopologyEdges VS Faces).
Do a first pass sampling all the related Faces.
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.
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).
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);
lst_lst_faces.Add(lst_faces);
count++;
};
//Construction of meshes
List<Mesh> meshes = new List<Mesh>();
foreach (List<int> lst_faces in lst_lst_faces)
{
Mesh newMesh = mesh.DuplicateMesh();
newMesh.Faces.Clear();
foreach (int face in lst_faces)
{
newMesh.Faces.AddFace(mesh.Faces[face]);
}
newMesh.RebuildNormals();
meshes.Add(newMesh);
}
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)
{
lst_faces.Add(curentFace);
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;
break;
}
}
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;
break;
}
}
return output;
}
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 (diff-arch.xyz)"
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:
curr_indices.append(i)
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:
continue
v1, v2 = vertex_indices
edge_idx = M.TopologyEdges.GetEdgeIndex(v1, v2)
edge_indices.append(edge_idx)
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!
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.
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: 31.0.15.2889
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â 1.5.6.0
C:\Program Files\Chaos Group\V-Ray\V-Ray for Rhinoceros 6\VRayForRhino.rhp âV-Ray for Rhinoâ
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;
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).
Say:
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).