How do I 1). copy an object 2). triangulate the copy’s mesh 3). then remove the copy when all is done all inside a python script. Blender 2.63a.
In the export routine I’m working on, I use the bmesh module:
tempmesh = bmesh.new()
tempmesh.from_mesh(sourcemesh.data)
#
#perform the triangulation and the other functions required for export...
#
tempmesh.free
This way, the original mesh object in Blender is not touched. In the code above, sourcemesh is the original object.
Ok I have been doing some serious fiddling and found out how to copy an object, separate it, return to the original object, and triangulate the original object, but there seems to be a bug or something I am still missing.
When I triangulate using bpy.ops.mesh.quads_convert_to_tris() it will triangulate the mesh, but when I loop through the vert data it still prints the original set of faces and verts as though the triangulation never happened.
I am lost, but still looking around.
I found a post about mesh.calc_normals() and mesh.calc_tessFace(), but those never worked for me. Maybe they are old API code. The poster on the forum never specified.
Ok, I didn’t know anything about BMesh. Where can I find docs on BMesh so I might be able to fix my code?
I reckon you’re using that for exporting purposes to use in a game engine or something. Well, to tell you the truth, it’s always best to manually triangulate the mesh and then export it. But, a quick and dirty soulution which I have already employed in an exporter for an Android game I’m working on is to take NGon’s vertices and create tris from them, like so:
# this takes a list of vertices, for example [47, 16, 28, 99] and makes a matrix like so:
# pairs = [
# [47, 16],
# [16, 28],
# [28, 99]
# ]
# of course, i used this notation to make it clearer :)
def split_in_pairs(l):
pairs = []
for e in range(len(l)-1):
pairs.append([l[e], l[e+1]])
return pairs
# later when you're exporting your mesh data, you can do it like this:
obj = bpy.context.active_object # replace with whatever you want
uv_layer = obj.data.uv_layers.active.data
uv_index = 0
for i in range (len (obj.data.polygons)):
# vv are vertex indices of current polygon
vv = obj.data.polygons[i].vertices
# vcount holds info on the number of vertices in the polygon, can be 3 or more
vcount = len (vv)
# uvs hold info on corresponding UV data for current polygon
uvs = uv_layer[uv_index:uv_index+vcount]
# MAGIJA!!1:
split_vv = [[vv[0]]+v for v in split_in_pairs(vv[1:])]
# ^ ^ ^ ^
# \ \ \ \_ subset verteksa, bez prvog
# \ \ \_ taj subset podijeljen u parove tipa [n, n+1]
# \ \_ svakom od parova...
# \_ dodajemo prvi verteks
# ???
# PROFIT!!!
#
# disregard these comments above; if vv = [1, 47, 16, 28, 99]
# then
# split_vv = [
# [1, 47, 16],
# [1, 16, 28],
# [1, 28, 99]
# ]
split_uvs = [[uvs[0]]+uv for uv in split_in_pairs(uvs[1:])]
# ^
# \_ same thing for UVs
if len (split_vv) != len (split_uvs):
# this never happens
print ('ERROR!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1')
else:
for j in range (len (split_vv)):
for v in split_vv[j]:
m.write (packInt (v)) # vertex's index
for uv in split_uvs[j]:
m.write (packFloat (uv.uv[0])) # U
m.write (packFloat (uv.uv[1])) # V
uv_index += vcount
You should be able to adapt this code for yourself. m here is an object whose class definition looks like this:
class PartData(io.BytesIO):
def __init__(self):
self.contents=b''
def write(self, s):
if type(s)==bytes:
self.contents += s
else:
raise TypeError ("You should REALLY add a sequence of bytes")
def dump(self):
print (self.contents)
def excrete(self):
return (self.contents.decode())
And I have a couple of functions that supply byte representation to PartData:
def packInt (i):
return struct.pack('i', i)
def packFloat (f):
return struct.pack ('f', f)
Be sure to
import struct
if you’re going to use it.
Hope this helps 
We need to see your code to know what is wrong with it. I can think of 3-4 ways to get a duplicate mesh off the top of my head.
Just based on what you have said you can try this:
- bpy.ops.mesh.quads_convert_to_tris() will run on the currently selected objects. Ensure that your duplicate mesh is the selected mesh when you run this op.
- Some ops are sensitive to being run in Object mode or Edit mode. Double check the docs and ensure that you are in the correct mode when you call the op.
- Sometimes you have to toggle your object between Object mode and Edit mode get get a change to fully propagate. Try toggling your mode on-and-off before you iterate your object.
Sample of code from my exporter using the bmesh commands. This (bmesh method) is very quick as compared to using the ops commands-- one sample export went from 180 seconds to 4 seconds.
def ProcessQuadSource(BMesh, WorldMatrix, QuadMax):
#Initialize Vertex Data array to an empty list
FullVertexData = {}
FullVertexData.clear()
#Initialize tri-face and vertex index counts
TCount = 0
VCount = 0
V = {}
#Run through each existing face, all quads, else this particular routine would not be running.
for QuadCount in range(QuadMax):
for Quad in BMesh.faces:
if (Quad.index == QuadCount):
break
V.clear()
#Create six vertices derived from the quad, which will be used to create
#The two new tris later.
#Also, generate the vertex data to be written to the file.
for c, VPick in enumerate(QuadVertList):
V[c] = BMesh.verts.new(Quad.verts[VPick].co,Quad.verts[VPick])
V[c].index = VCount
V[c].normal = Quad.verts[VPick].normal
WorkVert = TFullVertexData()
#Generate Vertex Data to Write for the above vertex...
WorkVert.Index = V[c].index
TempCo = WorldMatrix * V[c].co
WorkVert.Coords = TempCo
WorkVert.Normal = V[c].normal
WorkVert.FaceNormal = Quad.normal
WorkVert.UV0 = Quad.loops[VPick][UVLayer0].uv.copy()
WorkVert.UV1 = Quad.loops[VPick][UVLayer1].uv.copy()
WorkVert.UV2 = Quad.loops[VPick][UVLayer2].uv.copy()
WorkVert.UV3 = Quad.loops[VPick][UVLayer3].uv.copy()
WorkVert.DiffColor = Quad.loops[VPick][DCLayer]
WorkVert.DiffAlpha = Quad.loops[VPick][DALayer]
WorkVert.SpecColor = Quad.loops[VPick][SCLayer]
WorkVert.SpecAlpha = Quad.loops[VPick][SALayer]
WorkVert.Tangent = mathutils.Vector((0.0,0.0,0.0))
WorkVert.BiTangent = mathutils.Vector((0.0,0.0,0.0))
WorkVert.Tan = mathutils.Vector((0.0,0.0,0.0))
WorkVert.BiTan = mathutils.Vector((0.0,0.0,0.0))
WorkVert.Norm = mathutils.Vector((0.0,0.0,0.0))
FullVertexData[V[c].index] = WorkVert
VCount += 1
#End Vertex Data
#Create the two tris from the vertices created, based on the source quad face.
Tri0 = BMesh.faces.new((V[0],V[1],V[2]),Quad)
Tri0.index = TCount
Tri0.normal = Quad.normal
TCount += 1
Tri1 = BMesh.faces.new((V[3],V[4],V[5]),Quad)
Tri1.index = TCount
Tri1.normal = Quad.normal
TCount += 1
#Mark quad verts for deletion later.
Quad.verts[0].index = -1
Quad.verts[1].index = -1
Quad.verts[2].index = -1
Quad.verts[3].index = -1
#Mark the Quad face for deletion later.
Quad.index = -1
#Remove all unneeded faces and vertices.
for Face in BMesh.faces:
if Face.index == -1:
BMesh.faces.remove(Face)
for Vert in BMesh.verts:
if Vert.index == -1:
BMesh.verts.remove(Vert)
return BMesh, FullVertexData
You should be able to follow it. I have to export quite a bit of info. There may be stuff there that you don’t need. One other thing:
QuadVertList = (0,1,2,0,2,3)
Again, this operates on the bmesh copy of the existing object’s mesh. The existing object is not touched with this method. You can call the bmesh free command to get rid of it at the end of the run.
BMesh info can be found in the “Standalone Modules” section of the Blender documentation.
import math
import bpy
current_obj = bpy.context.active_object
objectNameList0 = []
objectNameList1 = []
inc = 0
for objs in bpy.data.objects:
print(bpy.data.objects[inc].name)
objectNameList0.append(bpy.data.objects[inc].name)
print(objectNameList0)
inc = inc + 1
print(current_obj.name)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.select_random()
bpy.ops.mesh.select_all()
bpy.ops.mesh.select_all()
bpy.ops.mesh.duplicate_move()
bpy.ops.mesh.separate()
bpy.ops.mesh.select_all()
bpy.ops.mesh.quads_convert_to_tris()
print("=" * 40) # Printing marker.
for polygon in current_obj.data.polygons:
verts_in_face = polygon.vertices[:]
vertCount = len(verts_in_face)
print(vertCount)
print("Face Index: ", polygon.index)
print("Normal: ", polygon.normal)
for vert in verts_in_face:
print("Vert: ", vert, " Vert Coordinates: ", current_obj.data.vertices[vert].co)
inc = 0
for objs in bpy.data.objects:
print(bpy.data.objects[inc].name)
objectNameList1.append(bpy.data.objects[inc].name)
print(objectNameList1)
inc = inc + 1
s = set(objectNameList0)
Name = [x for x in objectNameList1 if x not in s]
print(Name)
No. I am not trying to export anything. I have an algorithm designed, but not yet coded, that needs a triangulated mesh instead of quads or ngons. I have tried using quads and ngons, but the results of the algorithm or grossly inaccurate using said faces. Triangles are the way to go for this algorithm. That is why I am wanting to make a copy and modify it instead of the original. The changes in geometry from quads and ngons to all tris is still accurate enough for my needs.
I know this code is sloppy. I am still learning both Python and the new Blender API. This is not a copy and paste snippet of code from other website. I have had to stop and learn as I go.
With the above code pasted into the console I get no errors. The above code does EVERYTHING I desired, so far, except for one thing. When the mesh is triangulated it shows up in the 3dview truly triangulated, but in the console where I have the loop printing the mesh data, it still shows the mesh data for the default cube, which I am using for these tests. The only thing I can think of that is happening is I may have to update the object data. Is this true and how to?
This code doesn’t do anything useful yet. Like I said I am learning and I have hit the first serious bump with this triangulation deal.
Please critique my code. I do not want to learn bad code and I can take constructive criticism. I can answer any questions you may have.
Posting on a new reply rather than editing my above reply because I was just now able to get my computer back on after a network crash. I tried Kastoria’s idea about toggling the mode. It seems to have worked. It makes me wonder what is going on that I have to toggle the mode from EDIT to OBJECT and back to EDIT just to get the triangulation to stick. Does the Blender docs talk about this? I would hate to have to guess whether or not an OPS is going to stick based on chance. I would never have guessed to do this.
You can do this like so:
# iterate through all objects:
for obj in bpy.data.objects:
# obj becomes an object in bpy.data.objects set so you can access it directly
print (obj.name)
objectNameList0.append (obj.name)
# no need for inc variable, which, btw, you can increase by:
# inc += 1
# :D
You can also chech if the object is a mesh like so:
for obj in bpy.data.objects:
if obj.type == 'MESH':
print ("{} is a mesh!".format (obj.name))
# or just print (obj.name+' is a mesh!')
Keep Blendin’!
I found this out though trial and error. I think the basic issue is that Blender uses different data structures in the C++ code when the object is in edit mode or object mode and the Python representation gets updated when they change. That’s just a guess though.
I think you’re right. Remember the ‘good ole days’ when there was no Undo option in Blender, and you had only Reload original data (I think it was mapped to [U]) and it would set your model back in the state when you were out of edit mode? I guess that’s still kinda the case, i.e. you can access only that, ‘original’ data with Python, that is until you switch back and forth from edit mode to object.
So, like the original post, I’ve got some models that I want to, within python, make a copy of, convert all quads to triangles, then export it using code I already have. I cant figure out how to do the quads to tris part of things.
I’ve already sorted out how to access the mesh and copy it and transform it with the object.matrix_world, but the even with requesting it calculate the tessfaces it doesn’t seem to, and even if it did, I’ve never had to access the tessfaces object.
Has anyone sorted out a method for doing this using the built-in quads_convert_to_tris() operator, hopefully operating on the mesh COPY?
Straightforward solution:
import bpy
ob = bpy.context.object
scene = bpy.context.scene
me = ob.to_mesh(scene, apply_modifiers=True, settings='PREVIEW', calc_tessface=True, calc_undeformed=False)
me.transform(ob.matrix_world)
for face in me.tessfaces:
fv = face.vertices
if len(fv) == 3:
tris = (fv[0], fv[1], fv[2]),
else:
tris = (fv[0], fv[1], fv[2]), (fv[0], fv[2], fv[3])
for vert_indices in tris:
# export triangle, e.g.
#me.vertices[vert_indices[0]].co
#me.vertices[vert_indices[1]].co
#me.vertices[vert_indices[2]].co
bpy.data.meshes.remove(me)
With more control over triangulation (beauty option, using bmesh module):
import bpy
import bmesh
ob = bpy.context.object
scene = bpy.context.scene
bm = bmesh.new()
bm.from_object(ob, scene, deform=True, render=False, cage=False, face_normals=True)
bm.transform(ob.matrix_world)
# Note: triangulation will have different options in 2.7x
bmesh.ops.triangulate(bm, faces=bm.faces, use_beauty=True)
for face in bm.faces:
# export tris, e.g.
#face.verts[0].co
#face.verts[1].co
#face.verts[2].co
bm.free()
del bm
I just found your post through google instead of through the forum (lol), and that is exactly what I needed. Thanks so much as always!
How can I figure out the indexes of the points which those coordinates correspond to?
so far, I’m doing the following:
tuple_list=[]
for vert in bm.verts:
tuple_list.append(vert.co)
#now search for the index
vert1 = tuple_list.index(bm.faces[r].verts[0].co)
vert2 = tuple_list.index(bm.faces[r].verts[1].co)
vert3 = tuple_list.index(bm.faces[r].verts[2].co)
Which seems to be working somewhat, but also seems quite inefficient. I’m not 100% sure that i need to copy the points into a list to do the indexing(read: im confident there’s a better way), but it does give me the result I needed.
you mean bm.faces[#].verts[#].index ?