3
$\begingroup$

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: enter image description here

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'

$\endgroup$
5
$\begingroup$

Woot!

EDIT: new version: just put it in your /scripts/addons/ and load it in through blender's user preferences -> add-ons

enter image description here

PointCloud Generator

#
# CoDEmanX, pi
# 19.1.2014
#
# Generate random points within the volume of a mesh
#
# Point testing is done by projecting the point in the positive X direction;
#    if the first collision is with the inside of the mesh, the point is considered to be inside
#
# To load:
#   To load, Either:
#        paste into blender's inbuilt text editor, which has a menu option for "run script", which you need to do.
#   Or type into blender python prompt:
#        f='/Users/pi/Dev/blenderAddons/pointcloud.py'; exec(   compile( open(f).read(), f, 'exec' )   )
#
# To use:
#    * With target object selected
#    * in edit mode
#    * with the mouse over the 3-D view
#   Hit SpaceBar
#
#   ...and search for "PointCloud"
#
#   Adjust the number of points in the panel

bl_info = {
    "name": "PointCloud Generator",
    "author": "CoDEmanX, pi",
    "version": (1, 0),
    "blender": (2, 67, 0),
    "location": "View3D > T-panel > Object Tools",
    "description": "Generate random points within the volume of a mesh",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Add Mesh"}


import bpy
from mathutils import Vector
from random import uniform

from math import pi

def pt_in_box(O,P):
    return Vector( (
                uniform( O.x, P.x ),
                uniform( O.y, P.y ),
                uniform( O.z, P.z )
            ) )

def main(context, num):
    max_attempts = 999

    context.scene.update()

    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]

    O = bbox[0]
    W = bbox[6]

    X = Vector( ( W.x - O.x, 0.0, 0.0 ) )
    #Y = Vector( ( 0.0, W.y - O.y, 0.0 ) )
    #Z = Vector( ( 0.0, 0.0, W.z - O.z ) )

    me = bpy.data.meshes.new("pointcloud")
    me.vertices.add(num)

    for v in me.vertices:
        got = False

        for i in range(1,max_attempts):
            A = pt_in_box( O, W )

            (location, normal, index) = ob.ray_cast(A, A+X)

            # don't need X.angle(hit.normal), woot!
            if index > -1  and  normal.x > 0.0 :
                got = True
                break

        if not got:
            print( "PtCloud Failed" )
            return

        v.co = A

    me.transform(mat)

    ob_new = bpy.data.objects.new("pointcloud", 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 PointCloudGenerator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.pointcloud"
    bl_label = "PointCloud"
    bl_options = {'REGISTER', 'UNDO'}

    amount = bpy.props.IntProperty(name="Points",
        description="Number of random points to generate",
        default=100,
        min=1, max=25000
        )

    @classmethod
    def poll(cls, context):
        return context.object is not None

    def execute(self, context):
        main(context, self.amount)
        return {'FINISHED'}


def draw_func(self, context):
    layout = self.layout
    layout.operator(PointCloudGenerator.bl_idname)

def register():
    bpy.utils.register_class(PointCloudGenerator)
    bpy.types.VIEW3D_PT_tools_objectmode.prepend(draw_func)


def unregister():
    bpy.utils.unregister_class(PointCloudGenerator)
    bpy.types.VIEW3D_PT_tools_objectmode.remove(draw_func)


if __name__ == "__main__":
    register()
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.