Nodeset: more flexibility

Prompted by a question from Packsod 百草头 I've added the option to disable filtering the list of files. It's still enabled by default and in fact you can now specify a fragment to be used in the filter pattern.

This means that if all your texture sets normally contain an albedo map with _alb in its name, that you not only can configure which files to load but also you can restrict the list of visible files to pick from, to anything you like. This greatly reduces the clutter when selecting a texture collection from a folder that contains many texture sets. (remember that even though you see a limited list from which you only pick one file, other textures with the same name and matching configured suffixes will still be loaded).

This means you can configure any set of suffixes that your favorite texture editing program uses in these settings and save it along with your user preferences, an example is shown below.


I also added the option to make suffix filtering case sensitive or insensitive. This is set to insensitive by default.

Code availability

The latest version of the code (201702051650) is available on GitHub (right click and select save as ... , then in Blender File -> user preferences ... -> Add-ons -> Install from file .... Don't forget to remove the previously installed version first)

Previous articles

Previous articles about the Nodeset add-on:
NODESET: IMPORT SUBSTANCE PAINTER TEXTURES INTO BLENDER
NODESET: TINY UPDATE MIGHT SAVE EVEN SOME MORE TIME
NODESET: SUPPORT FOR AMBIENT OCCLUSION MAPS

Add-on: Creating a chain of objects, nearest neighbor approximation

In a previous article I started with a very naive solution to create a chain of objects that have a parent-child relation along the shortest path.

This naive solution works but is so slow that even for 10 objects it starts getting unworkable. So the improved version uses a simplistic nearest neighbor approximation that works well with even thousands of objects. It has one drawback, you have to make sure that the active object is at one of the ends of the collection of selected objects because that is where our algorithm starts. This works quite well for artistic use, but in the future I might still try to add the Chistofides algorithm to make it more general.

Anyway, the code is simple enough and the relevant function is shown below:

def object_list2(objects, active=0):
 """
 Return an approximate shortest path through objects starting at the
 active index using the nearest neighbor heuristic.
 """

 s = time()

 # calculate a kd tree to quickly answer nearest neighbor queries
 kd = kdtree.KDTree(len(objects))
 for i, ob in enumerate(objects):
  kd.insert(ob.location, i)
 kd.balance()

 current = objects[active]
 chain = [current]  # we start at the chosen object
 added = {active}
 for i in range(1,len(objects)):  # we know how many objects to add
  # when looking for the nearest neighbor we start with two neigbors
  # (because we include the object itself in the search) and if
  # the other neigbors is not yet in the chain we add it, otherwise
  # we expand our search to a maximum of the total number of objects
  for n in range(2,len(objects)):
   neighbors = { index for _,index,_ in kd.find_n(current.location, n) }
   neighbors -= added
   if neighbors:  # strictly speaking we shoudl assert that len(neighbors) == 1
    chain.extend(objects[i] for i in neighbors)
    added |= neighbors
    break
  current = chain[-1]

 print("{n:d} objects {t:.1f}s".format(t=time()-s, n=len(objects)))

 return chain

Code availability

The full improved version is available on the same GitHub location. (click 'Raw' to download the Python file)

Add-on: Creating a chain of objects

While working on a project I came across a problem that is surprisingly hard to tackle: chaining a collection of objects along the shortest path.

What I want for this specific example is to parent a collection of objects to each other in such a way that we have a linear chain of parent-child relations. On top of that I want this chain to be as short as possible, that is, going from parent to child along the chain of objects, I want the length of this path to be minimal.

To illustrate what I mean, the first image shows what I am after while the second image shows a decidedly sub-optimal chain of parent-child relations:

The problem itself is well known (finding the shortest Hamiltonian path, closely related to the Traveling Salesman problem) but unfortunately solving this problem exactly is very costly in computational terms.

The code below shows a working but very naive implementation that I intend to use as a starting point for later improvements. It works in the sense that it finds the shortest path between a collection of selected objects and creates the chain of parent-child relations but the time to compute the solution increases more that exponentially (factorial to be precise: 8 objects will for example take 0.1 seconds, 9 objects will take nine times as much, i.e. almost 1 second and 10 objects will take ten times as much still, i.e. 10 seconds and so on). To make this remotely useful, for example to chain a necklace of 100 beads, we will have to implement some clever heuristics. That is something I intend to cover in future articles.

Code availability

The current code is shown in full below but the add-on as it evolves will be available on GitHub. (click 'Raw' to download the Python file)
import bpy
from mathutils import kdtree
from itertools import permutations as perm
from functools import lru_cache
from time import time
from math import factorial as fac

bl_info = {
 "name": "Chain selected objects",
 "author": "Michel Anders (varkenvarken)",
 "version": (0, 0, 201701220957),
 "blender": (2, 78, 0),
 "location": "View3D > Object > Chain selected objects",
 "description": """Combine selected objects to a list of parent-child relations based on proximity""",
 "category": "Object"}

def object_list(objects):
 """
 Return the shortest Hamiltonian path through a collection of objects.

 This is calculated using a brute force method that is certainly not
 intented for real life use because for example going from ten to
 eleven objects will increase the running time elevenfold and even
 with caching expensive distance calculations this quickly becomes
 completely unworkable.

 But this routine is intended as our baseline algorithm that is meant
 to be replaced with an approximation algorithm that is 'good enough'
 for our purposes.
 """
 @lru_cache()
 def distance_squared(a,b):
  return (objects[a].location-objects[b].location).length_squared

 def length_squared(chain):
  sum = 0.0
  for i in range(len(chain)-1):
   sum += distance_squared(chain[i],chain[i+1])
  return sum

 s = time()

 shortest_d2 = 1e30
 shortest_chain = None

 n_half = fac(len(objects))//2
 for i,chain in enumerate(perm(range(len(objects)))):
  if i >= n_half:
   break
  d2 = length_squared(chain)
  if d2 < shortest_d2:
   shortest_d2 = d2
   shortest_chain = chain

 print("{n:d} objects {t:.1f}s".format(t=time()-s, n=len(objects)))

 return [objects[i] for i in shortest_chain]

class ChainSelectedObjects(bpy.types.Operator):
 bl_idname = 'object.chainselectedobjects'
 bl_label = 'Chain selected objects'
 bl_options = {'REGISTER', 'UNDO'}

 @classmethod
 def poll(self, context):
  return (context.mode == 'OBJECT' 
   and len(context.selected_objects) > 1)

 def execute(self, context):
  objects = object_list(context.selected_objects.copy())
  for ob in objects:
   ob.select = False

  ob = objects.pop()
  first = ob
  while objects:
   context.scene.objects.active = ob
   child = objects.pop()
   child.select = True
   bpy.ops.object.parent_set(keep_transform=True)
   child.select = False
   ob = child
  first.select = True
  context.scene.objects.active = first
  return {"FINISHED"}


def menu_func(self, context):
 self.layout.operator(
  ChainSelectedObjects.bl_idname,
  text=ChainSelectedObjects.bl_label,
  icon='PLUGIN')


def register():
 bpy.utils.register_module(__name__)
 bpy.types.VIEW3D_MT_object.append(menu_func)


def unregister():
 bpy.types.VIEW3D_MT_object.remove(menu_func)
 bpy.utils.unregister_module(__name__)

Profiling: using kernprof in Blender add-ons

When implementing add-ons that contain very time consuming activities, profiling can be a tremendous help in pinpointing the hotspots in your code. And if you use Numpy a lot, where a lot of hard work is hidden in a few lines of code, a line profiler is a godsend. I used it for instance in researching bottlenecks in some tree related code.

Now the excellent kernprof package offers basically all we need and works fine for stand-alone Python programs but I want to use it inside Blender add-ons and this article illustrates what is needed to get it working. It's a bit fiddly but well worth it if you really want to optimize your code.

Installing the kernprof package

This works under Linux if your python3 version is the same as the one inside Blender (3.5.x as I am working with Blender 2.78a, I am not sure this works under Windows).

Installation

First we have to install the kernprof package:
pip3 install line_profiler
This will install the package in whatever location you use as default with pip, in my case /home/michel/.local/lib/python3.5/site-packages

Now currently the developer of kernprof decided to make IPython a hard requirement which in our case makes things more difficult because we want to use the profiler just in Blender and do not want to rely on any extra stuff. It is however simple enough to remove these dependencies and leave us a LineProfiler class that just works.

This stripped version is available on GitHub and you should download this version to replace the one that was installed by pip (in my case /home/michel/.local/lib/python3.5/site-packages/line_profiler.py)

Now we can start creating add-ons where functions can be profiled on a line by line basis and I will highlight the relevant pieces of code.

Because we installed the kernprof package outside Blender's Python libraries, we have to make sure we can find the modules by adding the path:

sys.path.append('/home/michel/.local/lib/python3.5/site-packages')

from line_profiler import LineProfiler
profile = LineProfiler()
After adding the path we can import the LineProfiler class and create an instance of it.

This instance can be used as a decorator on functions that we want to profile. Note that it is currently not possible to add this decorator to the execute() function of an operator otherwise Blender will complain. Apparently there is something weird going on when the function is wrapped.

 @profile
 def expensive_stuff(self,a,b):
  for i in range(a):
   c = b * b

 def execute(self, context):
  self.expensive_stuff(1000,100)
Currently I have chosen to dump the statistics when we disable the add-on. On disabling the unregister() function is called automatically and we use this to write the statistics in the same directory as where our module is located and use a filename with the same name but with a .prof extension:
def unregister():
 bpy.types.INFO_MT_mesh_add.remove(menu_func)
 bpy.utils.unregister_module(__name__)
 profile.dump_stats(splitext(__file__)[0]+'.prof')
If our addon file was called exampleaddon.py we could (after disabling the add-on) use the same line_profiler module outside of Blender to show the accumulated statistics:
python3 -m line_profiler /home/michel/.config/blender/2.78/scripts/addons/exampleaddon.prof
The result for our silly example function looks like the listing below, interpreting the results is up to you (please refer to the documentation on the kernprof site.)
Timer unit: 1e-06 s

Total time: 0.002952 s
File: /home/michel/.config/blender/2.78/scripts/addons/exampleaddon.py
Function: expensive_stuff at line 33

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    33                                            @profile
    34                                            def expensive_stuff(self,a,b):
    35      3003         1411      0.5     47.8    for i in range(a):
    36      3000         1541      0.5     52.2     c = b * b

Full example code

The complete code for a tiny example add-on that provided the code snippets showed earlier is listed below:
bl_info = {
 "name": "Expensive Operation",
 "author": "Michel Anders (varkenvarken)",
 "version": (0, 0, 201612231212),
 "blender": (2, 78, 0),
 "location": "View3D > Add > Mesh > Expensive Op",
 "description": "A time consuming operator to test profiling options within Blender add-ons",
 "warning": "",
 "wiki_url": "",
 "tracker_url": "",
 "category": "Add Mesh"}
         
import bpy
import sys
from os.path import splitext

# make sure we can find (the hacked version of) line_profiler
sys.path.append('/home/michel/.local/lib/python3.5/site-packages')

from line_profiler import LineProfiler
profile = LineProfiler()


class ExpensiveOp(bpy.types.Operator):
 bl_idname = 'mesh.expensiveop'
 bl_label = 'Expensive Operator'
 bl_options = {'REGISTER','UNDO'}

 @classmethod
 def poll(self, context):
  return (context.mode == 'OBJECT')

 @profile
 def expensive_stuff(self,a,b):
  for i in range(a):
   c = b * b

 def execute(self, context):
  self.expensive_stuff(1000,100)
  # this is just to show something happened
  bpy.ops.mesh.primitive_cube_add()
  context.active_object.name = 'Expensive Cube'
  return {"FINISHED"}

def menu_func(self, context):
 self.layout.operator(ExpensiveOp.bl_idname, text=ExpensiveOp.bl_label, icon='PLUGIN')

def register():
 bpy.utils.register_module(__name__)
 bpy.types.INFO_MT_mesh_add.append(menu_func)

def unregister():
 bpy.types.INFO_MT_mesh_add.remove(menu_func)
 bpy.utils.unregister_module(__name__)
 profile.dump_stats(splitext(__file__)[0]+'.prof')

Nodeset: support for ambient occlusion maps

A couple of weeks ago I created a small add-on that lets you load whole sets of texture maps as generated by Substance Painter in one go.

I left out support for ambient occlusion maps but now I have corrected that :-)

The latest version is available on GitHub.

It now also loads textures that end in _ambient_occlusion (if present). This suffix is of course configurable in the user preferences, just like all other suffixes.

ColorRampPicker, a new Blender add-on

Got this idea from Substance Designer which offers an option to sample a whole palette of colors from a reference image for their gradient node. Now you can do the same for Blender's color ramp node. With a color ramp node selected, just go to the Node menu and select Color Ramp Picker. An eye dropper will appear and you can sample from anywhere within your Blender window by clicking and dragging. Unfortunately it is not possible to sample outside the Blender window so you should have your reference image loaded in Blendeś uv-image editor.

I have also made a small video that illustrates the proces:

Availability

You can download the add-on from GitHub. (click right on the Raw button and save the python file anywhere you like and then in Blender select File->User preference, Add-ons, Install from file... Don't forget to enable the add-on as well after installing it.)

If you would like to write add-ons yourself, you might want to take a look a my books on BlenderMarket.

Alternative

After publication I learned about a similar and much more versatile tool to generate (and sample) gradients. Check this BlenderArtists thread to learn more.

Add-on: Selecting similar vertices

If you select the Similar sub menu when in vertex edit mode Blender already offers a few options to extend your current selection of vertices.

And although useful I frequently find myself in a situation where the available options are not sufficient. Especially in hard edge modeling of objects with identical sub parts I often want to be able to select vertices that not just share the same number of faces but where the these shared faces have something else in common. For example in the object shown below we have two different collections of bumps but the is no way to select just all the spiky tips in one go.

The simple add-on I present here fills this gap: it checks not only if vertices have the same number of surrounding faces but also whether the average angle between the face normals and the vertex normals is similar. This will allow you to distinguish between flat and spiky. The amount of similarity can be tweaked to allow for round-off errors or non smooth meshes.

Availability and usage

After downloading, installing and enabling the add-on from GitHub, the new selection option is available in vertex edit mode from the Select -> Select_similar -> Neighborhood