Blender add-on: DumpMesh [Update]

In a previous article I presented a small add-on that could dump all sorts of mesh characteristics like vertex coordinates, faces, edges, seams, etc. as Python code. This code could then be included in other add-ons for example to be used as basic building blocks for parameterized objects.
I have now added the edge selection status and the active uv-map to the list of items that can be dumped. I also fixed a couple of bugs and made dumping most characteristics optional. DumpMesh not only dumps mesh information into a text block but optionally also generates complete add-on code to regenerate the mesh. It serves both as a way to test the generated Python data and might serve as an example of how to create a bmesh object from scratch, including how to add data layers for edges (crease) and loops (uvs).
From a Python point of view might be interesting because I had to find a way to include a large chunk of Python code as a string (the source code for the CreateMesh add-on). I solved this by including this source code as a base64 encoded string that is decoded on the fly. This way any quotes or other nasty stuff won't cause the same challenge as trying to create a string literal (which would contain quotes but also escaped quotes etc...).
CreateMesh was even more challenging in a way: DumpMesh generates source code that defines all sorts of list. However, many of these list are optional and all might have a suffix chosen by the user. This means that the CreateMesh add-on must have a way to check if the lists are defined. Fortunately this information is readily accessible in Python because any globally defined variabele is an entry in the dict returned by globals(). Anyway, some annotations can be found at the end of this article.


The updated code for DumpMesh and CreateMesh can be found on my GitHub repository. DumpMesh can be downloaded directly from this link, as can CreateMesh if your are interested to look at the code.

CreateMesh Python tricks

The lists and dicts with data created by DumpMesh look like this:
suffix = ""

verts = [(-1,-1,-1),(-1.0365,-1,1.0122), ... ]

faces = [(1, 3, 2, 0),(3, 7, 6, 2), ... ]

edges = [(0, 1),(1, 3),(3, 2),(2, 0), ... ]

seams = {0: False,1: False,2: False,3: ... }

crease = {0: 0.0,1: 0.0,2: 0.0,3: 0.0,4: ... }

selected = {0: True,1: True,2: True,3: ... }

uv = {0: {1: (0,0),3: (1,0),2: (1,1),0: (0,1),}, 1: ... }
With a suffix the code would look something like this:
suffix = "_cube"

verts_cube = [(-1,-1,-1),(-1.0365,-1,1.0122), ... ]

faces_cube = [(1, 3, 2, 0),(3, 7, 6, 2), ... ]

edges_cube = [(0, 1),(1, 3),(3, 2),(2, 0), ... ]

The geometry() function that creates a bmesh from this data has be clever because for example seams might not be defined at all or might not be called seams but seams_cube for example. This is solved by checking the dict of global symbols returned by globals():
def geometry():

 # we check if certain lists and dicts are defined
 have_seams  = 'seams' + suffix in globals()
 have_crease  = 'crease' + suffix in globals()
 have_selected  = 'selected' + suffix in globals()
 have_uv  = 'uv' + suffix in globals()

 # we deliberately shadow the global entries so we don't have to deal
 # with the suffix if it's there
 verts = globals()['verts' + suffix]
 edges = globals()['edges' + suffix]
 faces = globals()['faces' + suffix]
 if have_seams: seams = globals()['seams' + suffix]
 if have_crease: crease = globals()['crease' + suffix]
 if have_selected: selected = globals()['selected' + suffix]
 if have_uv: uv = globals()['uv' + suffix]

BMesh pitfalls

A peculiarity of the way BMesh works is that adding elements like verts or edges to their respective lists does not guarantee that they are indexable! That means that after adding a number of vertices you must call bm.verts.ensure_lookup_table() in order to access a vertex as for example bm.verts[3]. Now they rela tricky part is that being able to index the list of verts is not sufficient to get the index of a vertex! That is, if you want to retrieve bm.verts[3].index you need to call bm.verts.index_update() first. Even though this is documented here and here, it baffled me at first. (note: what goes for verts, goes for edges and faces as well). So for verts the complete code looks like this:
 bm =

 for v in verts:
        # ensure bm.verts can be indexed
        # ensures all bm.verts have an index (= different thing!)

No comments:

Post a Comment