Showing posts with label mesh. Show all posts
Showing posts with label mesh. Show all posts

Ortho, a mesh manipulation tool: February 2018 update

Just a couple of weeks ago I introduced a new add-on to manipulate mesh part with the help of a reference plane: Ortho.

Based on user feedback I added some new features that are in this new February release of the Add-on. Besides some tweaks and bug fixes, the most important new feature is the ability to work with more than one reference plane. You can create as many reference planes as you need and a new panel lets you select any of those planes. You can also manipulate the list of reference planes itself, for example providing them with meaningful names.

A short demo of this new feature is available in this new video:

Availability

Ortho is available from my Blender Market store.

New add-on: Ortho, a mesh manipulation tool

I am happy to announce a new add-on that is designed to make your life as an arch-viz or hard surface modeler a little bit easier.

Ortho, a mesh manipulation tool

Ortho offers a collection of tools that allows you to move, rotate, scale, snap and align selections of a mesh relative to a user defined reference plane. Working relative to a reference plane can greatly simplify the positioning of mesh parts and can help clean up distorted meshes. Blender already offers several tools to transform and snap mesh parts but they work in the context of predefined coordinates, which is makes it difficult to position or align mesh parts in meshes that are transformed with respect to their local coordinates or in situations where orthogonal coordinates are not sufficient, for example when positioning a window inside a slanted roof.

Ortho offers a simple and interactive way to define a plane that fits a selection of vertices in a mesh and offers a set of tools that operate with respect to this reference plane. You can for example align and snap a selection to the reference plane or move this selection along its normal or its surface. Scaling is also an option, offering ways to rectify slightly distorted meshes even in situations where such a distorted plane is not aligned with any axis and scaling along individual normals with Alt-S gives strange results.

Availability

Ortho is available from my Blender Market store.

DumpMesh: clean, complete, sexy

The previous bugfix release of DumpMesh was already able to dump quite a number attributes and attribute layers but in the latest version I went the whole way and dumped every attribute layer that happens to be defined. And I really mean every layer: if you have added a skin modifier the BMSkinVert layer will be dumped for example and even if you assign values to the generic float, string, etc. attribute layers programmatically the will be dumped as well.

Note that vertex groups will not be dumped: vertex groups are not part of a mesh but of an object. (yes, if you didn't know that already, you can have more than one object pointing to the same mesh data, each with its own vertex groups)

This is done by code that is a lot more Pythonic (whatever that means, for me pragmatism is more important than style, as long as it is readable and documented). It uses a lot of introspection to keep the code short and sweet as well. Of course it helps that Blender attribute layers (aka CustomData) is very flexible and well designed (well,... mostly, see Bugs below)

Functionally the add-on hasn't changed much: the interface is cleaner (no more separate selection of what will be dumped, you either choose just geometry or everything), and now all objects that are selected are dumped. This might give a slightly faster workflow.

Availability

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.

Bugs

Blenders Python API is generally well designed and adequately documented but there are still some bugs and strange design issues:
BMVertSkin
not documented at all but it is used by the skin modifier. And the bm.verts.layers.skin layer the skin modifier creates has a name that is an empty string
BMTexPoly
has a documentation entry but is marked as todo. Fair enough, because this might be for Ptex stuff that is sort of shelved for now. In the add-on it is mostly ignored.
bm.loops.index_update()
If you add vertices, edges or faces to a bmesh you must ensure that the sequences they are members of can be indexed. You can do this with bm.verts.ensure_lookup_table() or equivalent. If you not only need to be able to do bm.verts[i] but need the actual index as well, like bm.verts[i].index you need to call bm.verts.index_update() otherwise all indices are -1. So far so good, however for loops this appears to be either unfinished or I don't understand it: there is no ensure_lookup_table() for loops, apparently this is taken care of by the faces, but any index attribute of a BMLoop is -1. now bm.faces[i].loops does have an index_update() but it will generate only indices for the loops of that particular face and will start at 0. However when transfering a bmesh to a regular mesh the indices of all loops in the whole mesh are properly initialized, so there is apparently hidden functionality to take care of this. Of course most of the time you can do without but in the addon I dump loop layers as dictionaries of dictionaries that can be indexed as d[faceindex][loopindex] where loopindex is initialy derived from the complete mesh and therefore numbered as one unbroken sequence of all the loops.
factories instead of instantiation from a class
BMesh, BMVert, BMSinVert, etc. are not fully fledged classes. You cannot subclass them and you cannot instantiate them with BMesh(). also most have no __repr__() function and also cannot be pickled. This means that especially for layer attributes like BMSkinVert with more than one attribute you have to know what to assign to which attribute, which is cumbersome. introspection is no help here because you cannot know which attribute is to be saved and which one is just for temporary use.
These aren't deal breakers, just things to keep in mind when you are designing an add-on.

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 DumpMesh.py 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.

Availability

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 = bmesh.new()

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



Blender add-on: DumpMesh

DumpMesh is an add-on that will write a lot of information about a currently selected mesh object in the form of Python code. The resulting code is stored as a text object in the Text Editor.
Of course it is far more effective to store an object by saving the .blend file and reuse it afterward but the purpose of this add-on is to get access to mesh data in a form that can easily be used in other add-ons, for example as the basis of some parameterized object, where creating lists of vertex coordinates by hand is very tedious and error prone. It therefore produces Python definitions for
  • a list of vertex coordinates
  • a list of edges
  • a list of faces
  • a dictionary with edge seams
  • a dictionary with edge creases
A small, shortened, example of what is produced is shown below:
verts = [
 (-1.0, -1.0, -1.0),
 (-1.0, -1.0, 1.0),
...
 (1.0, 1.0, 1.0),
]

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

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

seams = {
 0: True,
 1: True,
...
 11: False,
}

crease = {
 0: 0.0,
 1: 1.0,
...    
 11: 0.0,
}
By default it also produces code for an operator that recreates a mesh object based on the values in the lists and dictionaries mentioned above.
The latter may sound a bit strange, an add-on that produces code for an add-on, but this way it is very simple to verify that the dumped data indeed produces the desired object when used. Your work flow may look like this:
  • Select the object to dump in the 3D view,
  • Select Object -> DumpMesh, the code will show up in the text editor,
  • Run the script in the text editor by clicking Run script, it will create a new menu entry
  • Select Add -> Mesh -> CreateMesh, and a duplicate mesh should be added to your scene.
If satisfied with the result you can save the generated code for reuse in your own add-on.

Availability

The code for DumpMesh is available on GitHub. Just download the file and use File -> User preferences -> Add-ons -> Install from file in the usual manner. In the code I have embedded the code for the CreateMesh operator as base64 encoded strings. This is in my opinion the simplest way to store a chunk of Python in antother Python program without bothering about quotes of all kinds. The original non-encoded version is on GitHub as well.