I have use case where I need split a large mesh into small pieces. When I select and separate each of the desired pieces separately, the consolidation of the indices of the large mesh (i.e. building new packed vertex and face lists) takes alot of time. The consolidation is not really necessary in my use case, as I will be splitting off more pieces right away. If I mark all the different split regions using different materials and separate by material, the runtime suggest to me that this approach is also splitting of the regions piece by piece and consolidating the larger mesh in each iteration. So what is the most efficient way to tackle this problem?
4 Answers
The general case can get pretty messy, but if you just want to copy geometry and materials you can start from the code at http://web.purplefrog.com/~thoth/blender/python-cookbook/split-copy-mesh.html . It splits the mesh according to the material. It avoids bpy.ops and instead uses mesh.from_python to build the geometry of each fragment from a subset of the original.
Here is a copy of the relevant section:
import bpy
from math import *
from mathutils import *
def split_copy_mesh(obj, discriminant):
accumulator = {}
for polygon in obj.data.polygons:
tag = discriminant(polygon)
acc = accumulator.get(tag)
if acc is None:
acc = {
"tag":tag,
"verts":[],
"faces":[],
"materials":[],
"vertMap": {}
}
accumulator[tag] = acc
face = []
for vi in polygon.vertices:
vi2 = acc["vertMap"].get(vi)
if vi2 is None:
verts = acc["verts"]
vi2 = len( verts )
acc["vertMap"][vi] = vi2
verts.append(obj.data.vertices[vi].co)
face.append(vi2)
acc["faces"].append(face)
acc["materials"].append(polygon.material_index)
rval = []
for tag,acc in accumulator.items():
print(tag)
mesh = bpy.data.meshes.new(tag)
#print(acc)
mesh.from_pydata(acc["verts"], [], acc["faces"])
mesh.validate(True)
for mat in obj.data.materials:
mesh.materials.append(mat)
for i in range(len(mesh.polygons)):
mesh.polygons[i].material_index = acc["materials"][i]
obj = bpy.data.objects.new(tag, mesh)
bpy.context.scene.objects.link(obj)
rval.append(obj)
return rval
#fabricate_test_mesh(bpy.context.scene, 3, 8)
copies = split_copy_mesh(bpy.data.objects['multisphere'], lambda p: "m%d"%p.material_index)
for obj in copies:
obj.location = (5,0,0)
If you want to preserve modifiers or uv maps, or shape keys or other information, you will have to add more code to copy this information from the original mesh.
This can be done by separating the big mesh into small meshes using the below script:
import bpy
ob = bpy.data.objects['MyMesh'] #### replace MyMesh with the name of your Mesh object
bpy.ops.object.select_all(action='DESELECT')
ob.select = True
bpy.context.scene.objects.active = ob
bpy.ops.mesh.separate(type='LOOSE')
UPDATE
If the object has no loose parts and you know the list of faces to separate then you can loop on them then separate using selection using the script below which I tested and it is working:
import bpy
ob = bpy.data.objects['MyMesh'] #### replace MyMesh with the name of your Mesh object
bpy.ops.object.select_all(action='DESELECT')
ob.select = True
bpy.context.scene.objects.active = ob
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_all(action='DESELECT')
faces_to_separate = [0,2,4,6,8,10] #### add the list of faces you want to separate
mesh = ob.data
bpy.ops.object.mode_set(mode='OBJECT')
for face in mesh.polygons:
if face.index in faces_to_separate:
face.select = True
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.separate(type='SELECTED')
bpy.ops.object.mode_set(mode='OBJECT')
-
-
1$\begingroup$ This wont work if
ob
is hidden, or its layer isn't visible - better to pass in a custom context. $\endgroup$ Feb 6, 2017 at 2:37 -
$\begingroup$ I don't understand your suggestion. Just to clarify: The input mesh has no loose elements, it's a connected mesh that is to be split in specific parts. I know the indices of the faces that go into the split meshes and right now assign different materials to those faces and separate using the 'MATERIAL' type. $\endgroup$ Feb 8, 2017 at 12:36
-
$\begingroup$ @DrPepperJo answer updated to separate using a list of faces $\endgroup$– TakFeb 8, 2017 at 23:32
-
1$\begingroup$ I have voted down because you don't get what DrPepperJo is asking for and so your reply is irrelevant - the task is to separate a mesh EFFICIENTLY, which can't be achieved by calling the separate() method on the each iteration. Try to test your script in a case when you need to split a mesh to thousand objects, this can take even an hour or more. $\endgroup$ Jul 12, 2019 at 8:47
This topic is pretty old, but I was just dealing with this issue, and this post comes up when searching.
I also found that selecting faces and separating them with bpy.ops.mesh.separate(type='SELECTED')
was frustratingly slow. I tried the approach above, of trying to copy the selected faces into a new object, instead of merely separating. That's definitely faster, but it has other drawbacks. (Doesn't automatically merge vertices, doesn't carry over UVs, doesn't carry over Modifiers). After account for all those other operations, copying ended up being slower than just separating.
But, I found an approach that seems to be much faster: A single call to bpy.ops.mesh.separate(type='MATERIAL')
. This was easy to fit into my existing code that was looping through and selecting faces. However, instead of selecting faces and then separating them, I would instead create a new material for each "chunk", and assign that new material to the faces I wanted in that chunk. Eventually I had a mesh with dozens of materials on it. Then, a single call to bpy.ops.mesh.separate(type='MATERIAL')
quickly creates all the new objects without the same performance cost of calling separate once per chunk.
It sounds like kind of a clumsy approach, creating all these materials just to inform the Separate call. But it does seem to perform much faster. Also note that this is probably only viable if you can accept the materials on your object getting all messed up. In my case, the whole object is a single material, so it's simple to reassign the material to the newly-created objects after creation. But if your model initially has several materials, this approach would need some refinement to ensure the newly created objects end up with the right materials.
I had a similar problem with a giant object (a baseball stadium) with more than 40 million vertices. My solution was a mix of all the answers above.
import bpy
ob = bpy.data.objects['Baseball_Stadium_obj'] #### replace MyMesh with the name of your Mesh object
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.separate(type='MATERIAL')
bpy.ops.object.mode_set(mode='OBJECT')
When running it, blender seemed to hang but there were peaks of activity in the cpu so I let it run until it finally worked.