New Book: Creating add-ons for Blender

Yeah, it's there! I am happy to announce my new, short-and-sweet, information packed book on creating add-ons for Blender.

It might be small, but it is beautifully formed :-) You might want to sample it (or buy of course) from the location below:

A sample of the book is also available as pdf

Small fix to basket arch add-on

Something in the 2.77 Blender API of object_utils.py was changed and broke the basket arch add-on. I fixed it and a new version is available on GitHub.

troubles, troubles, troubles ...

I forgot to renew the lease on the swineworld.org domain with NetworkSolutions. This sucks big time because I cannot remember/ find my login credentials and apparently I never updated my primary email address :-( Asking a question requires that you login and the telephone number listed for international users connects but is not answered!

Well too bad. The old blenderthings.blogspot.com keeps on working but unfortunately all links pointing to www.swineworld.org are now broken, even the ones inside articles that link to other articles on the same blog. I'll try and fiz that as much as I can but I am certainly not renewing this domain with Network Solutions because the first thing they did is auctioning it of for a ridiculous price.

Floor boards add-on: random thickness

Acting on a question from Ákos I added a random thickness option to the add-on.

The new version is of course available on GitHub. (this is a single python file: select File->Save As... from your browser and save it somewhere as planks.py. Then in Blender, select File->User preferences->Add-ons->install from file... (don't forget to remove an earlier version if you have one).

If you select a non-zero thinkness each plank gets a random extra thickness up to the chosen maximum.

The option is located together with the other randomization options (which can be applied in parallel.)

Weightlifter Watershed Demo

I just uploaded a new version of my WeightLifter add-on to BlenderMarket.

The video gives a short impression of the new watershed mode. You could use this watershed mode for example to calculate a realistic distribution of vegetation in large scale landscapes.

Performance of ray casting in Blender Python

The Python API for ray casts on objects and scenes was changed in a breaking manner in Blender 2.77. And because several of my add-ons depend on it, I not only had to fix them but I decided to have a look at the performance as well.

The central use case for ray casting in these add-ons is to check whether a point lies inside a mesh. This is determined by casting a ray and checking if the number of intersections is even or odd (where we count 0 as even). An even number of crossings means the point lies outside the mesh (true for the red points in the illustration) while an odd number means that the point lies inside the mesh (the green point). And the fun part is that this is true for any direction of the ray!


Code

We can easily convert this to Python using Blenders API: given an object and point to test, we take some direction for our ray (here right along the x-direction) and check if there is an intersection. If so, we we test again but start at the intersection point, taking care to move a tiny bit along the ray cast direction to avoid getting 'stuck' at the intersection point due to numerical imprecision. We count the number of intersections and return True if this number is odd.

import bpy
from mathutils import Vector
from mathutils.bvhtree import BVHTree

def q_inside(ob, point_in_object_space):
direction = Vector((1,0,0))
epsilon = direction * 1e-6
count = 0
result, point_in_object_space, normal, index = ob.ray_cast(point_in_object_space, direction)
while result:
count += 1
result, point_in_object_space, normal, index = ob.ray_cast(point_in_object_space + epsilon, direction)
return (count % 2) == 1

Intersection with an object

We can now use this function to determine if a point lies within a mesh object:

q_inside(ob, (0,0,0))
Note that the point to check is in object space so if you want to check if a point in world space lies within the mesh you should convert it first using the inverted wordl matrix of the object.

Intersection with a precalculated BVH tree

To determine if a ray hits any of the possibly millions of polygons that make up a mesh, the ray_cast() method internally constructs something called a BVH tree. This is a rather costly operation but fortunately a BVH tree is cached so when we perform many intersection tests this overhead should pay off.

BVH trees however can also be constructed from a mesh object separately and offers exactly the same signature ray_cast() method. This means we can perform the same check to see if a point lies inside a mesh with following code:


sc = bpy.context.scene

bvhtree = BVHTree.FromObject(ob, sc)

q_inside(bvhtree, (0,0,0))

Performance

When doing millions of tests on the same mesh a small amount of overhead averages out so the question is if it is meaningful to create a separate BVH tree. If we look at the time it takes to do a million tests for meshes of different sizes we see that the overhead apparently is not the same but for larger meshes this difference loses significance:

inside irregular mesh (subdivided Suzanne)

facesobjectbvhtreegain%
1280003.5473.2418.6
320003.5261.89246.3
80003.4731.71550.6
20003.3931.63951.7
5003.3911.59752.9

outside irregular mesh with mostly immediate initial hit

The initial data was for a point inside a mesh: such a point will always need at least two ray casts: the first will intersect some polygon, the second will veer off into infinity. If we test a point outside a mesh we sometimes will have only one ray cast and sometimes two. if we look at the latter scenario we see similar behavior:
facesobjectbvhtreegain%
1280003.6083.14812.7
320003.4341.83646.5
80003.3811.63751.6
20003.2771.55852.4
5003.2941.52853.6

outside irregular mesh with mostly no initial hits

A strange thing happens when the point to test is outside the mesh but the ray happens to point away from the mesh so that we will have zero intersections. Now for large meshes the overhead of a separate BVH tree appears larger!
facesobjectbvhtreegain%
1280001.5342.417-57.6
320001.5441.01134.5
80001.520.83245.2
20001.5230.79647.7
5001.5340.78349

Conclusion

i cannot quite explain the behavior of the ray_cast() performance for larger meshes when the point is outside the mesh and the test ray points away, but in arbitrary situations and moderately sized meshes a perfomance gain of 40% for the BVH tree is worthwhile when doing millions of tests.

Floor boards add-on: another small feature

Again prompted by a question I added a feature to guarantee minimum plank lengths at the sides when using random plank lengths, to mimic real life behavior where you would not allow too short ends when laying a wooden floor.

In practice you would probably also try to avoid seams to line up in adjacent rows but if this happens you can easily switch to another random seed already.

The images below show the vertex color map (for clarity) with (left) and without a minimum offset of 12 cm, resulting in less short 'stubs'.

Availability

As usual the latest version is available on GitHub.

Other floor board articles

This blog already sports quite a number of articles on this add-on. The easiest way to see them all is to select the floor board tag (on the right, or click the link).