Featured post

new redirect for blender.org bpy docs.

http://www.blender.org/api/blender_python_api_current/ As of 10/11 november 2015 we can now link to the current api docs and not be worr...

July 04, 2011

bgl drawing with OpenGL onto blender 2.5 view

original thread over at blenderartists.com, i never finished it but the bgl is decent enough to get someone started.
'''
This script resects the camera position into virtual 3d space.
Dealga McArdle (c) 2011

The program may be distributed under the terms of the GNU General
Public License. The full terms of GNU GPL2 can be found at: 
http://www.gnu.org/licenses/gpl-2.0.html

Be sure that you understand the GLP2 terms prior to using this script.
Nothing between these tripple quote marks should be construed as
having deminished the full extent of the GPL2 license.
'''

import bpy
import bgl
import blf
from mathutils.geometry import intersect_line_line
from mathutils import Vector

'''
- TODO: complete vanishing point and horizon drawing
- TODO: correctly deal with impossible guides orientations
- TODO: implement rudimentary double buffer for 3d openGL drawing
'''


''' defining globals '''

# initial end point locations, lateron modified by the user.
guide_green_1 = [50, 100, 280, 120]
guide_green_2 = [50, 70, 280, 60]
guide_red_1 = [300, 120, 580, 100]
guide_red_2 = [300, 30, 580, 70]
guide_blue = [250, 50, 250, 250]
h_collection = [guide_green_1, guide_green_2, guide_red_1, guide_red_2, guide_blue]

# colours defined here for scope
line_green = 0.0, 1.0, 0.0, 0.4
line_red = 1.0, 0.0, 0.0, 0.4
line_blue = 0.0, 0.0, 1.0, 0.4
l_col_green = 0.5, 1.0, 0.5, 0.6
l_col_red = 1.0, 0.3, 0.3, 0.6
l_col_cyan = 0.6, 0.6, 1.0, 0.4

# handle size is double this value
hSize = 5

# colours/transparency of viewport text
dimension_colour = (1.0, 1.0, 1.0, 1.0)
explanation_colour = (1.0, 1.0, 1.0, 0.7)


''' G L  D R A W I N G '''


def drawOneLine(x1, y1, x2, y2, colour):
    '''accepts 2 coordinates and a colour then draws
    the line and the handle'''

    def DrawHandle(hX, hY):
        bgl.glBegin(bgl.GL_LINE_LOOP)
        bgl.glVertex2i(hX+hSize, hY-hSize)
        bgl.glVertex2i(hX-hSize, hY-hSize)
        bgl.glVertex2i(hX-hSize, hY+hSize)
        bgl.glVertex2i(hX+hSize, hY+hSize)
        bgl.glEnd()

    #set colour to use
    bgl.glColor4f(*colour)

    #draw main line and handles
    bgl.glBegin(bgl.GL_LINES)
    bgl.glVertex2i(x1,y1)
    bgl.glVertex2i(x2,y2)
    bgl.glEnd()
    DrawHandle(x1, y1)
    DrawHandle(x2, y2)


def DrawOrientationLines():
    '''configure and initialize the 5 orientation lines
    drawOneLine(x1, y1, x2, y2, colour)'''
    drawOneLine(*guide_green_1, colour=line_green)  #green
    drawOneLine(*guide_green_2, colour=line_green)
    drawOneLine(*guide_red_1, colour=line_red)  #red
    drawOneLine(*guide_red_2, colour=line_red)
    drawOneLine(*guide_blue, colour=line_blue)  #blue


def DrawPerspectiveLine(x1, y1, x2, y2, l_colour):
    '''reckon could be refactored with DrawOneLine
    but i'm considering giving these lines dashed style'''
    bgl.glColor4f(*l_colour)
    bgl.glBegin(bgl.GL_LINES)
    bgl.glVertex2i(x1,y1)
    bgl.glVertex2i(x2,y2)
    bgl.glEnd()


def IntersectionOf(line1, line2):
    '''mathutils.geometry expects lines to be expressed as two
    Vectors with three dimensions, at this point we pick an
    arbitrary value for the z component of this vector.
    I'm only interested in how the two guides extend towards
    a vanishing point, and the resulting x,y coordinate.'''
    arbitrary_z_value = 0.0
    A = Vector((line1[0], line1[1], arbitrary_z_value))
    B = Vector((line1[2], line1[3], arbitrary_z_value))
    C = Vector((line2[0], line2[1], arbitrary_z_value))
    D = Vector((line2[2], line2[3], arbitrary_z_value))
    my_xyz = intersect_line_line(A, B, C, D)
    if my_xyz == None: return 10,40
    return int(my_xyz[0][0]), int(my_xyz[0][1])


def DrawHorizonAndVanishingPoints():
    '''use the current state of the guide coordinates, to draw:
    - both vanishing points, O and R
    - all 4 guide ends (M,N,P,Q)'''
    # setting up extra drawing points/lines.
    p_point_o = IntersectionOf(guide_green_1, guide_green_2)
    p_point_r = IntersectionOf(guide_red_1, guide_red_2)

    p_line_mo = p_point_o[0], p_point_o[1], h_collection[0][0], h_collection[0][1]
    p_line_no = p_point_o[0], p_point_o[1], h_collection[1][0], h_collection[1][1]
    p_line_pr = p_point_r[0], p_point_r[1], h_collection[2][2], h_collection[2][3]
    p_line_qr = p_point_r[0], p_point_r[1], h_collection[3][2], h_collection[3][3]
    h_line_or = p_point_o[0], p_point_o[1], p_point_r[0], p_point_r[1]
    
    # draw the resulting perspective lines and horizon.
    DrawPerspectiveLine(*p_line_mo, l_colour = l_col_green) # green
    DrawPerspectiveLine(*p_line_no, l_colour = l_col_green) 
    DrawPerspectiveLine(*p_line_pr, l_colour = l_col_red) # red
    DrawPerspectiveLine(*p_line_qr, l_colour = l_col_red)
    DrawPerspectiveLine(*h_line_or, l_colour = l_col_cyan) # cyan - horizon
    return


def DrawGeneratedAxis():
    '''for drawing the dashed line to indicate major axis'''
    return


def DrawStringToViewport(my_string, pos_x, pos_y, size, colour_type):
    ''' my_string : the text we want to print
        pos_x, pos_y : coordinates in integer values
        size : font height.
        colour_type : used for definining the colour'''
    my_dpi, font_id = 72, 0 # dirty fast assignment
    bgl.glColor4f(*colour_type)
    blf.position(font_id, pos_x, pos_y, 0)
    blf.size(font_id, size, my_dpi)
    blf.draw(font_id, my_string)


def InitViewportText(self, context):
    '''used to deligate opengl text printing to the viewport'''
    this_h = context.region.height
    this_w = context.region.width
    dimension_string = "IMAGE_EDITOR: H " + str(this_h) + " / W " + str(this_w)
    explanation_string = "right-click to release the script, data will be stored"
    DrawStringToViewport(dimension_string, 10, 20, 20, dimension_colour)
    DrawStringToViewport(explanation_string, 10, 7, 10, explanation_colour)


def InitGLOverlay(self, context):
    InitViewportText(self, context)

    # 50% alpha, 2 pixel width line
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 0.5)
    bgl.glLineWidth(1.5)

    # start visible drawing
    DrawHorizonAndVanishingPoints()
    DrawOrientationLines()
    ## DrawGeneratedAxis()
    ## DrawGrid()

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


''' H A N D L E  C H E C K I N G '''


def CheckIsCursorOnPickPoints(event):
    '''CheckIsCursorOnPickPoints is not a prime example of HiQ code,
    but for now it does what i need, and identifies what handle will
    be modified by the drag, 0 = first handle, 2= second handle'''

    def FindGuideIndex(coordinates):
        for coord_iterator in range(len(h_collection)):
            if coordinates == h_collection[coord_iterator]:
                return coord_iterator

    def CheckHandle(guide, g_handle):
        if cmX >= (guide[g_handle]-hSize):
            if cmX <= (guide[g_handle]+hSize):
                if cmY >= (guide[g_handle+1]-hSize):
                    if cmY <= (guide[g_handle+1]+hSize):
                        return True
        else: return False

    def IsOnHandle(h_collection, cmX, cmY):
        for guide in h_collection:
            if CheckHandle(guide, 0): return (guide, 0)
            elif CheckHandle(guide, 2): return (guide, 2)
        return 'None'

    cmX, cmY = event.mouse_region_x, event.mouse_region_y
    is_on_handle_response = IsOnHandle(h_collection, cmX, cmY)
    if is_on_handle_response == 'None':
        return('None')
    else:
        handle_coordinates, handle_num = is_on_handle_response
        guide_index = FindGuideIndex(handle_coordinates)
        return(guide_index, handle_num)



class CameraMatchingPanel(bpy.types.Panel):
    bl_label = "Camera Matching"
    bl_space_type = "IMAGE_EDITOR"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        layout.label("Blue: nearest vertical")
        layout.label("Red/Green: perpendicular lines")
        layout.separator()

        layout = self.layout
        layout.label("Draw Perspective Lines")
        row = layout.row(align=True)
        row.operator("object.button", icon="MANIPUL")

        layout = self.layout
        layout.label("Solve Camera Location")
        row = layout.row(align=True)
        row.operator("object.button2", icon='SCENE')


class OBJECT_OT_Button(bpy.types.Operator):
    bl_idname = "object.button"
    bl_label = "Enable"

    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type == 'LEFTMOUSE':
            if event.value == 'PRESS':
                self.cursor_on_handle = CheckIsCursorOnPickPoints(event)
                if self.cursor_on_handle == 'None': print("no handle associated")
                else: print(self.cursor_on_handle)
            if event.value == 'RELEASE':
                self.cursor_on_handle = 'None'

        if event.type == 'MOUSEMOVE' and self.cursor_on_handle != 'None':
            print("mouse moving x", event.mouse_region_x,"y", event.mouse_region_y)
            global h_collection
            h_collection[self.cursor_on_handle[0]][self.cursor_on_handle[1]] = \   
                                                            event.mouse_region_x
            h_collection[self.cursor_on_handle[0]][self.cursor_on_handle[1]+1] = \                                                                 
                                                            event.mouse_region_y

        if event.type in ('RIGHTMOUSE', 'ESC'):
            context.region.callback_remove(self._handle)
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        if context.area.type == 'IMAGE_EDITOR':
            self.cursor_on_handle = 'None'
            context.window_manager.modal_handler_add(self)

            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            PP = 'POST_PIXEL'
            dest = (self, context)
            self._handle = context.region.callback_add(InitGLOverlay, dest, PP)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Image View not found, cannot run operator")
            return {'CANCELLED'}


class OBJECT_OT_Button2(bpy.types.Operator):
    bl_idname = "object.button2"
    bl_label = "Place Camera and Empty"

    def execute(self, context):
        print("Hello camera")
        return{'FINISHED'}

def register():
    bpy.utils.register_class(OBJECT_OT_Button)
    bpy.utils.register_class(OBJECT_OT_Button2)
    bpy.utils.register_class(CameraMatchingPanel)

def unregister():
    bpy.utils.unregister_class(OBJECT_OT_Button)
    bpy.utils.unregister_class(OBJECT_OT_Button2)
    bpy.utils.unregister_class(CameraMatchingPanel)

if __name__ == "__main__":
    register()