4
$\begingroup$

I have a script whose goal is to find all the visible vertices from the camera view. The simplest solution should work as follows:

  1. Go Into Camera View
  2. Update the view with bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)
  3. Get the coordinates of the borders of the camera and use bpy.ops.view3d.select_border()

However, the redraw_timer() function is giving me the following error

WM: failed to allocate texture for triple buffer drawing (glGenTextures).
wmSubWindowScissorSet 0: doesn't exist
Error: EXCEPTION_ACCESS_VIOLATION

Now, I'm not surprised. The Gotchas mentions that this could be expected.

How should I then replace redraw_timer()?

I have tried bpy.context.scene.update(), bpy.context.area.tag_redraw() and a modal operator with no success. How should I then proceed?

IMPORTANT UPDATE (02.11.2015)

Both answers (zeffii and batFINGER) select all vertices in the camera frustrum. This means that vertices behind others are selected. This is not what I'm looking for. I need the unobstructed and visible vertices. That's why I thought of using bpy.ops.view3d.select_border() in camera perspective. Here is an example.

If Suzanne is seen by the camera like this Suzanne watched through a camera.

I want the following vertices selected. Two images are shown of Suzanne in EDIT mode. The selected vertices are only the ones that are seen through the camera lens and unobstructed by others vertices.

I apologize for making my issue unclear.

$\endgroup$
  • $\begingroup$ related: blender.stackexchange.com/questions/882/… $\endgroup$ – zeffii Oct 30 '15 at 14:00
  • $\begingroup$ Thanks for adding a bit more specificity to your question, perhaps I'll remove my answer until I have time to modify it. $\endgroup$ – zeffii Nov 2 '15 at 12:42
  • $\begingroup$ you might need to be more specific when iterating over views... gist.github.com/zeffii/a01cbb28aed399146a86 this might be it.. if the error is that you are trying to set the border in a window area like the N panel of 3dview.. $\endgroup$ – zeffii Nov 2 '15 at 15:14
  • $\begingroup$ By the by, there is another way to do it. It's called back-face culling. Here is an answer that details how to do it. blender.stackexchange.com/questions/938/… $\endgroup$ – Axe Per Nov 5 '15 at 10:13
6
$\begingroup$

IMPORTANT EDIT

re question change, pretty much the same code added to a modal timer operator, and changed to use view3d.border_select. Run the operator in 3dview, then when in edit mode Right click in camera view to select vertices. Setspace.use_occlude_geometry to view unobstructed and visible vertices.

import bpy
from bpy_extras.view3d_utils import location_3d_to_region_2d

def view3d_find():
    # returns first 3d view, normally we get from context
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            v3d = area.spaces[0]
            rv3d = v3d.region_3d
            for region in area.regions:
                if region.type == 'WINDOW':
                    return region, rv3d
    return None, None

def view3d_camera_border(scene):
    obj = scene.camera
    cam = obj.data

    frame = cam.view_frame(scene)

    # move into object space
    frame = [obj.matrix_world * v for v in frame]

    # move into pixelspace

    region, rv3d = view3d_find()
    frame_px = [location_3d_to_region_2d(region, rv3d, v) for v in frame]
    return frame_px

class ModalTimerOperator(bpy.types.Operator):
    """Operator which runs its self from a timer"""
    bl_idname = "wm.modal_timer_operator"
    bl_label = "BXXX"

    _timer = None

    def modal(self, context, event):
        scene = context.scene
        obj = context.object
        region = context.area.regions[-1]

        if event.type in {'ESC'}:
            self.cancel(context)
            return {'CANCELLED'}

        if event.type in {'RIGHTMOUSE'}:
            print("modal")
            if obj.mode == 'EDIT' and obj.type == 'MESH':
                camframe = view3d_camera_border(scene)

                #print([xy for xy in camframe])

                bpy.ops.view3d.select_border(gesture_mode=3, xmin=camframe[2].x, xmax=camframe[0].x,
                                             ymin=camframe[1].y, ymax=camframe[0].y)

        return {'PASS_THROUGH'}

    def execute(self, context):
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.1, context.window)
        wm.modal_handler_add(self)
        return {'RUNNING_MODAL'}

    def cancel(self, context):
        wm = context.window_manager
        wm.event_timer_remove(self._timer)

def register():
    bpy.utils.register_class(ModalTimerOperator)

def unregister():
    bpy.utils.unregister_class(ModalTimerOperator)


if __name__ == "__main__":
    register()

Similar to the method used in https://blender.stackexchange.com/a/40264/15543 , the handler below selects only the verts in the camera view, and deselects the rest, live, in edit mode.

Uses view3d_camera_border(...) and view3d_find methods from https://blender.stackexchange.com/a/14746/15543

dic = {}
@persistent
def edit_object_change_handler(scene):
    # if not some.prop return
    obj = scene.objects.active
    if obj is None:
        return None

    if obj.mode == 'EDIT' and obj.type == 'MESH':
        camframe = view3d_camera_border(scene)
        region, rv3d = view3d_find()
        #print([xy for xy in camframe])
        bm = dic.setdefault(obj.name, bmesh.from_edit_mesh(obj.data))

        for v in bm.verts:
            xy = location_3d_to_region_2d(region, rv3d,
                                          obj.matrix_world * v.co)
            v.select = ((camframe[2].x <= xy.x <= camframe[0].x) 
                    and (camframe[1].y <= xy.y <= camframe[0].y))
            #print(v.select, v)            
        return None

    for bm in dic.values():
        bm.free()

    dic.clear()
    return None

Cam view select verts

Cannot see why same would not work in a modal(timer). Needs some property flag to turn it on or off.

I'd be tempted to extend the BMesh

$\endgroup$
  • $\begingroup$ Nice answer. But the context to bpy.ops.view3d.select_border(gesture_mode=3, xmin=camframe[2].x, xmax=camframe[0].x, ymin=camframe[1].y, ymax=camframe[0].y) is incorrect. More details here $\endgroup$ – Axe Per Nov 5 '15 at 10:06
  • $\begingroup$ Yep, quite aware of overriding context when running outside the view3d area. I used the name BXXX to make it quick to find in the spacebar menu for testing purposes. $\endgroup$ – batFINGER Nov 5 '15 at 10:17
  • $\begingroup$ @TedMilker Have a look at bpy.ops.view3d.select_box(...) $\endgroup$ – batFINGER Oct 24 at 14:20

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.