I think I'm nearly there, thanks to two super scripts from CoDEmanX.
The first script shows the bounding box around an object:
import bpy
import bgl
from mathutils import Vector
def draw_callback_px(self, context):
ob = context.object
if ob is None or ob.type not in ('MESH', 'CURVE', 'SURFACE', 'META', 'FONT'):
# Type that doesn't support .bound_box
return
mat = ob.matrix_world
# 50% alpha, 2 pixel width line
bgl.glEnable(bgl.GL_BLEND)
bgl.glColor4f(1.0, 0.0, 0.0, 0.5)
bgl.glLineWidth(2)
bbox = [mat * Vector(b) for b in ob.bound_box]
bgl.glBegin(bgl.GL_LINE_STRIP)
bgl.glVertex3f(*bbox[0])
bgl.glVertex3f(*bbox[1])
bgl.glVertex3f(*bbox[2])
bgl.glVertex3f(*bbox[3])
bgl.glVertex3f(*bbox[0])
bgl.glVertex3f(*bbox[4])
bgl.glVertex3f(*bbox[5])
bgl.glVertex3f(*bbox[6])
bgl.glVertex3f(*bbox[7])
bgl.glVertex3f(*bbox[4])
bgl.glEnd()
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex3f(*bbox[1])
bgl.glVertex3f(*bbox[5])
bgl.glVertex3f(*bbox[2])
bgl.glVertex3f(*bbox[6])
bgl.glVertex3f(*bbox[3])
bgl.glVertex3f(*bbox[7])
bgl.glEnd()
min_x = min(b.x for b in bbox)
max_x = max(b.x for b in bbox)
min_y = min(b.y for b in bbox)
max_y = max(b.y for b in bbox)
min_z = min(b.z for b in bbox)
max_z = max(b.z for b in bbox)
bgl.glColor4f(0.0, 1.0, 0.0, 0.5)
bgl.glBegin(bgl.GL_LINE_STRIP)
bgl.glVertex3f(min_x, min_y, min_z)
bgl.glVertex3f(min_x, max_y, min_z)
bgl.glVertex3f(max_x, max_y, min_z)
bgl.glVertex3f(max_x, min_y, min_z)
bgl.glVertex3f(min_x, min_y, min_z)
bgl.glVertex3f(min_x, min_y, max_z)
bgl.glVertex3f(min_x, max_y, max_z)
bgl.glVertex3f(max_x, max_y, max_z)
bgl.glVertex3f(max_x, min_y, max_z)
bgl.glVertex3f(min_x, min_y, max_z)
bgl.glEnd()
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex3f(max_x, min_y, min_z)
bgl.glVertex3f(max_x, min_y, max_z)
bgl.glVertex3f(min_x, max_y, min_z)
bgl.glVertex3f(min_x, max_y, max_z)
bgl.glVertex3f(max_x, max_y, min_z)
bgl.glVertex3f(max_x, max_y, max_z)
bgl.glEnd()
# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
class ModalDrawOperator(bpy.types.Operator):
"""Draw a line with the mouse"""
bl_idname = "view3d.modal_operator"
bl_label = "Simple Modal View3D Operator"
def modal(self, context, event):
context.area.tag_redraw()
if event.type in {'ESC'}:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
return {'CANCELLED'}
return {'PASS_THROUGH'}
def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
# the arguments we pass the the callback
args = (self, context)
# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_VIEW')
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
else:
self.report({'WARNING'}, "View3D not found, cannot run operator")
return {'CANCELLED'}
def register():
bpy.utils.register_class(ModalDrawOperator)
def unregister():
bpy.utils.unregister_class(ModalDrawOperator)
if __name__ == "__main__":
register()
for area in bpy.context.screen.areas:
if area.type == 'VIEW_3D':
context = bpy.context.copy()
context['area'] = area
bpy.ops.view3d.modal_operator(context, 'INVOKE_DEFAULT')
break
The second script generates a point cloud within that bounding box:
import bpy
from mathutils import Vector
from random import uniform
def main(context, num):
ob = context.object
mat = ob.matrix_world.copy()
mat.col[3][:3] = [0] * 3 # reset translation
bbox = [Vector(b) for b in ob.bound_box]
xmin, xmax = bbox[0].x, bbox[6].x
ymin, ymax = bbox[0].y, bbox[6].y
zmin, zmax = bbox[0].z, bbox[6].z
me = bpy.data.meshes.new("Random points")
me.vertices.add(num)
for v in me.vertices:
v.co = mat * Vector((uniform(xmin, xmax), uniform(ymin, ymax), uniform(zmin, zmax)))
ob_new = bpy.data.objects.new("Random points", me)
ob_new.location = ob.matrix_world.translation
ob_new.show_x_ray = True
bpy.context.scene.objects.link(ob_new)
bpy.context.scene.update()
class SimpleOperator(bpy.types.Operator):
"""Tooltip"""
bl_idname = "object.simple_operator"
bl_label = "Simple Object Operator"
bl_options = {'REGISTER', 'UNDO'}
amount = bpy.props.IntProperty(name="Amount",
description="Number of random points to generate",
default=100,
min=1, max=10000
)
@classmethod
def poll(cls, context):
return context.object is not None
def execute(self, context):
main(context, self.amount)
return {'FINISHED'}
def register():
bpy.utils.register_class(SimpleOperator)
def unregister():
bpy.utils.unregister_class(SimpleOperator)
if __name__ == "__main__":
register()
(NOTE: of course the first script is completely unnecessary, but it looks pretty!)
Result:
What I'm trying to do is insert a check to see whether the generated point is within the object.
The way I'm doing this is to generate a pair of points within the bounding box, A and B. now I raycast along that line.
Imagine you had a ray starting inside the object. When it hits the surface, the normal vector of the surface at that point will be aligned with the ray. Specifically the angle between the ray and the normal vector will be < 90°.
I've got it to run without producing errors, but now it isn't producing points either.
NOTE: Instructions: paste script in the blender text editor, text -> run script
Do this for both scripts
Now with the mouse over the 3-D view, hit the spacebar -> 'simple object operator'