Showing posts with label bug. Show all posts
Showing posts with label bug. Show all posts

EnumProperty callback problems - a better workaround

A frequent use case of EnumProperties in Blender addons is to display a dropdown with a choice of scene objects. Of course which objects are available in a scene is only known when invoking the addon so a fixed list of choices is of no use here. For this scenario a callback option is provided. The callback can be any function that returns a list of choices. Unfortunately there are a number of severe bugs associated with using a callback: if your python code doesn't keep a reference to the items in the list it returns, Blender may crash. This bug is documented and a workaround suggested but this doesn't solve the problem completely. Have a look at the code below:
import bpy
from bpy.props import EnumProperty

available_objects = []

def availableObjects(self, context):
 available_objects.clear()
 for ob in bpy.data.objects:
  name = ob.name
  available_objects.append((name, name, name))
 return available_objects
 
class TestCase(bpy.types.Operator):
 
 bl_idname = "object.testcase"
 bl_label = "TestCase"
 bl_options = {'REGISTER', 'UNDO'}

 objects = EnumProperty(name="Objects", items = availableObjects)
if we hover the mouse over the second or third option we see that the description shows garbage, i.e. the description part of another item!
The really annoying part is that even if we make a full copy of the object's name (with ob.name[:]) the problem isn't solved. So probably the internal callback code trashes memory even outside its own allocated memory.
def availableObjects(self, context):
 available_objects.clear()
 for ob in bpy.data.objects:
  name = ob.name[:] # a copy, not just a reference
  available_objects.append((name, name, name))
 return available_objects
A way around this is to let the callback just return a list that is filled with values outside the callback code, for example in the code thst is executed when the user clicks on the menu item that selects the addon:
available_objects = []

def availableObjectsInit(self, context):
 available_objects.clear()
 for ob in bpy.data.objects:
  name = ob.name[:] # a copy, not just a reference
  available_objects.append((name, name, name))
 return available_objects
 
class TestCase(bpy.types.Operator):

        ... stuff omitted ... 

 objects = EnumProperty(name="Objects",
                    items = lambda self,context: available_objects)

  
def menu_func(self, context):
 availableObjectsInit(self, context)
 self.layout.operator(TestCase.bl_idname, 
                             text="TestCase",icon='PLUGIN')

def register():
 bpy.utils.register_module(__name__)
 bpy.types.VIEW3D_MT_object.append(menu_func)
This is usable even if the addon changes the number of objects. The only downside is that the code isn't reentrant because in its present form does not guard thread access to the global variable but as far as I know the Blender UI runs as part of a single Python interpreter so there's only one thread, which renders this point moot. The other issue is that it looks ugly, but as long as it works that's a minor issue :-) Note: you might wonder why we need the lambda here but that's because if we would point to just the list here, the list would be empty at the point where the EnumProperty was defined and apparently it makes a copy of that list so it would stay empty.

A replacement for the missing OSL distance to line segment function

The OSL implementation defines but apparently doesn't actually implement the three argument variant of the distance() function. In this short article I present a simple alternative.
While working on a new shader I noticed I got runtime errors that crashed Blender when I used the three argument variant of the distance() function to compute the shortest distance of a point to a line segment. I filed a bug report and although the Blender devs were very quick to verify and respond, the fact remains that it is a gap in the OSL libraries that won't be fixed by the Blender devs (although they have implemented a fix that will prevent Blender from crashing). That leaves us with the task of providing a workaround. Fortunately that is not too difficult.
Finding the shortest distance to a line segment is of course bssic geometry so it was easy enough to find numerous online resources. Based on those (references are in the code below) I came up with the following code:
#include "stdosl.h"

// replacement for the missing distance(p, p1, p2) function

// shortest distance to a line segment from p1 -> p2
// based on information from the following two sites:
// http://stackoverflow.com/questions/849211/shortest-distance-between-a-point-and-a-line-segment
// http://paulbourke.net/geometry/pointlineplane/

float minimum_distance(point v, point w, point p) {
  // Return minimum distance between line segment vw and point p
  vector s = w - v;
  float l2 = dot(s,s);
  if (l2 == 0.0) return distance(p, v);
  float t = dot(p - v, s) / l2;
  if (t < 0.0) return distance(p, v);
  else if (t > 1.0) return distance(p, w);
  vector projection = v + t * (s);
  return distance(p, projection);
}
I will not explain it here, refer to the sources mentioned in the code if you are interested, especially Paul Bourke's site is a treasure trove of useful information.