Commit 0d74a4ac authored by Dalai Felinto's avatar Dalai Felinto
Browse files

Massive refactoring

parent 3ea0d16f
Loading
Loading
Loading
Loading
+27 −199
Changes for space_view3d_virtual_reality/__init__.py: 27 added lines, 199 removed lines.
Original line number Diff line number Diff line
@@ -19,7 +19,7 @@
# <pep8 compliant>
bl_info = {
    "name": "Virtual Reality Viewport",
    "author": "Dalai Felinto",
    "author": "Dalai Felinto, Visgraf/IMPA",
    "version": (0, 9),
    "blender": (2, 7, 7),
    "location": "View 3D Tools",
@@ -32,206 +32,32 @@ bl_info = {

import bpy

from bgl import (
        Buffer,
        GL_INT,
        GL_RGBA,
        )

from .opengl_helper import (
        calculate_image_size,
        create_image,
        create_shader,
        delete_image,
        draw_callback_px,
        resize,
        )

from . import oculus

from . import ui
from . import operator

from . import sandbox

def get_context_3dview (context):
    """returns area and space"""
    screen = context.screen

    for area in screen.areas:
        if area.type == 'VIEW_3D':
            for region in area.regions:
                if region.type == 'WINDOW':
                    return area, region

    return None, None


def get_space_3dview(context):
    area = context.area
    for space in area.spaces:
        if space.type == 'VIEW_3D':
            return space
    return None


def get_glsl_shader(shader_file):
    import os
    folderpath = os.path.dirname(os.path.abspath(__file__))
    filepath = os.path.join(folderpath, 'shaders', shader_file)
    f = open(filepath, 'r')
    data = f.read()
    f.close()
    return data


class VirtualRealityViewportOperator(bpy.types.Operator):
    """"""
    bl_idname = "view3d.virtual_reality_toggle"
    bl_label = "Toggle Virtual Reality Mode"
    bl_description = ""

    _enabled = True
    _timer = None
    _display_mode = None
    _is_multiview = None
    _space = None

    @classmethod
    def poll(cls, context):
        camera = context.scene.camera
        return camera and camera.type == 'CAMERA'

    def modal(self, context, event):
        if event.type == 'ESC':
            return self.cancel(context)

        if event.type == 'TIMER':

            # bug, waiting for fix: "[#31026] context.region broken after QuadView on + off"
            # http://projects.blender.org/tracker/index.php?func=detail&aid=31026&group_id=9&atid=498
            if not context.region or \
                not context.space_data or \
                context.space_data.type != 'VIEW_3D':
                return {'PASS_THROUGH'}

            viewport_shade = context.space_data.viewport_shade
            self._enabled = (viewport_shade != 'RENDERED')

            if (self.width != context.region.width) or (self.height != context.region.height):
                resize(self, context)

            self.oculus.update()


        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        """
        * Create a fullscreen window with the editor in fullscreen with clean UI.
        * Views should be on, and this window should have stereo 3d mode set to side-by-side
        * Also you should lock a camera to the viewport to make sure you always look nicely
        * Sync the Oculus rotation + translation to the Blender camera
        * Now the opengl fun ... create a GLSL screen shader to run the warping distortions.
        """

        if context.area.type == 'VIEW_3D':
            scene = context.scene
            window = context.window

            self.oculus = oculus.Oculus(scene, self.report)

            if not self.oculus.isAvailable():
                return {'CANCELLED'}

            self._display_mode = window.stereo_3d_display.display_mode
            window.stereo_3d_display.display_mode = 'SIDEBYSIDE'

            #if bpy.ops.wm.window_fullscreen_toggle.poll():
            #    bpy.ops.wm.window_fullscreen_toggle()

            self._render = scene.render, scene.render.use_multiview, scene.render.resolution_x, scene.render.resolution_y
            scene.render.use_multiview = True
            scene.render.resolution_x = 1920
            scene.render.resolution_y = 1080

            #if bpy.ops.screen.screen_full_area.poll():
            #    bpy.ops.screen.screen_full_area(use_hide_panels=True)

            space = get_space_3dview(context)
            self._space = space, space.show_only_render, space.stereo_3d_camera, space.region_3d.view_perspective

            space.show_only_render = True
            space.stereo_3d_camera = 'S3D'
            space.region_3d.view_perspective = 'CAMERA'

            if bpy.ops.view3d.view_center_camera.poll():
                bpy.ops.view3d.view_center_camera()

            self._timer = context.window_manager.event_timer_add(1.0/75.0, context.window) # 75 Hz
            self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, (self, context), 'WINDOW', 'POST_VIEW')
            context.window_manager.modal_handler_add(self)

            self.viewport = Buffer(GL_INT, 4)
            self.width = context.region.width
            self.height = context.region.height

            # power of two dimensions
            self.buffer_width, self.buffer_height = calculate_image_size(self.width, self.height)

            # images to dump the screen buffers
            self.color_id = create_image(self.buffer_width, self.buffer_height, GL_RGBA)

            # glsl shaders
            fragment_shader = get_glsl_shader(self.oculus.shader_file)
            self.program_shader = create_shader(fragment_shader)

            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "View3D not found, cannot run operator")
            return {'CANCELLED'}

    def cancel(self, context):
        if self._handle:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            del self._handle

        if self._timer:
            context.window_manager.event_timer_remove(self._timer)
            del self._timer

        self.oculus.quit()
        self.quit()

        # set back the original values
        try:
            context.window.stereo_3d_display.display_mode = self._display_mode

            render, use_multiview, resolution_x, resolution_y = self._render
            render.use_multiview = use_multiview
            render.resolution_x = resolution_x
            render.resolution_y = resolution_y

            space, show_only_render, stereo_3d_camera, view_perspective  = self._space
            space.show_only_render = show_only_render
            space.stereo_3d_camera = stereo_3d_camera
            if space.region_3d:
                space.region_3d.view_perspective = view_perspective

        except Exception as err:
            self.report({'ERROR'}, str(err))

# ############################################################
# User Preferences
# ############################################################

        return {'CANCELLED'}
# Preferences
class VirtualRealityPreferences(bpy.types.AddonPreferences):
    bl_idname = __name__

    display_backend = bpy.props.EnumProperty(
        name="Display Backend",
        description="Library to use for the display",
        items=(("OCULUS", "Oculus", "Oculus - oculus.com"),
               ("DEBUG", "Debug", "Debug backend - no real HMD"),
               ),
        default="OCULUS",
        )

    def quit(self):
        """garbage colect"""
        if self.color_id:
            delete_image(self.color_id)
    def draw(self, context):
        layout = self.layout

    def is_stereo_left(self, context):
        """"""
        space = get_space_3dview(context)
        return space.stereo_3d_eye == 'LEFT'
        row = layout.row()
        row.prop(self, "display_backend")


# ############################################################
@@ -239,15 +65,17 @@ class VirtualRealityViewportOperator(bpy.types.Operator):
# ############################################################

def register():
    bpy.utils.register_class(VirtualRealityViewportOperator)
    sandbox.register()
    bpy.utils.register_class(VirtualRealityPreferences)

    operator.register()
    ui.register()


def unregister():
    bpy.utils.unregister_class(VirtualRealityViewportOperator)
    bpy.utils.unregister_class(VirtualRealityPreferences)

    operator.unregister()
    ui.unregister()
    sandbox.unregister()


if __name__ == '__main__':
+113 −0
Changes for space_view3d_virtual_reality/hmd/__init__.py: 113 added lines, 0 removed lines.
Original line number Diff line number Diff line

TODO = True

from mathutils import (
        Matrix,
        Vector,
        )


class HMD_Data:
    status = None
    projection_matrix = Matrix()
    modelview_matrix = Matrix()
    interpupillary_distance = Vector((0.0, 0.0))
    width = 0
    height = 0
    fbo = 0
    texture = 0


class HMD:
    __slots__ = {
        "_device",
        "_projection_matrix",
        "_modelview_matrix",
        "_interpupillary_distance",
        "_width",
        "_height",
        "_fbo",
        "_texture",
        }

    def __init__(self, display_backend):
        self._device = None
        self._projection_matrix = Matrix()
        self._modelview_matrix = Matrix()
        self._interpupillary_distance = Vector((0.0, 0.0))
        self._width = 0
        self._height = 0
        self._fbo = 0
        self._texture = 0

        from .oculus import Oculus
        from .debug import Debug

        displays = {
                'OCULUS':Oculus,
                'DEBUG':Debug,
                }

        assert(display_backend in displays)
        self._device = displays[display_backend]()

    @property
    def fbo(self):
        return self._fbo

    @property
    def texture(self):
        return self._texture

    @property
    def width(self):
        return self._width

    @property
    def height(self):
        return self._height

    @property
    def projection_matrix(self):
        return self._projection_matrix

    @property
    def modelview_matrix(self):
        return TODO

    def isConnected(self):
        """
        Check if device is connected

        :return: return True if the device is connected
        :rtype: bool
        """
        return self._device.isConnected()

    def init(self):
        """
        Initialize device

        :return: return True if the device was properly initialized
        :rtype: bool
        """
        return self._device.init()

    def loop(self):
        """
        Get fresh tracking data
        """
        return self._device.loop()

    def frameReady(self):
        """
        The frame is ready to be send to the device
        """
        return self._device.frameReady()

    def quit(self):
        """
        Garbage collection
        """
        return self._device.quit()
+51 −0
Changes for space_view3d_virtual_reality/hmd/debug.py: 51 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""
Debug
=====

Debug device for testing
"""

from . import HMD_Data

class Debug:
    def __init__(self):
        self._device = None

    def isConnected(self):
        """
        Check if device is connected

        :return: return True if the device is connected
        :rtype: bool
        """
        print('Debug:isConnected()')
        return True

    def init(self):
        """
        Initialize device

        :return: return True if the device was properly initialized
        :rtype: bool
        """
        print('Debug:init()')
        return True

    def loop(self):
        """
        Get fresh tracking data
        """
        print('Debug:loop()')

    def frameReady(self):
        """
        The frame is ready to be send to the device
        """
        print('Debug:frameReady()')

    def quit(self):
        """
        Garbage collection
        """
        print('Debug:quit()')
+78 −0
Changes for space_view3d_virtual_reality/hmd/oculus.py: 78 added lines, 0 removed lines.
Original line number Diff line number Diff line
"""
Oculus
======

Oculus (oculus.com) head mounted display
It uses a C app to connect with the SDK

The bridge code is hosted at Visgraf:
http://git.impa.br/dfelinto/oculus_sdk_bridge
"""

TODO = True

from . import HMD_Data

class Oculus:
    def __init__(self):
        self._device = None

    def isConnected(self):
        """
        Check if device is connected

        :return: return True if the device is connected
        :rtype: bool
        """
        TODO
        """
        Oculus SDK bridge

        return: true/false
        """

    def init(self):
        """
        Initialize device

        :return: return True if the device was properly initialized
        :rtype: bool
        """
        TODO
        """
        Oculus SDK bridge

        return: status, fbo, texture, projection matrix, eye separation, width, height
        """

    def loop(self):
        """
        Get fresh tracking data
        """
        TODO
        """
        Oculus SDK bridge

        return:head position, head orientation
        """

    def frameReady(self):
        """
        The frame is ready to be send to the device
        """
        TODO
        """
        Oculus SDK bridge
        """

    def quit(self):
        """
        Garbage collection
        """
        TODO
        """
        Oculus SDK bridge

        delete fbo, rbo, tex_id
        """
+0 −168
Changes for space_view3d_virtual_reality/oculus.py: 0 added lines, 168 removed lines.
Original line number Diff line number Diff line
class Oculus():
    def __init__(self, scene, report):
        self._report = report
        self._available = True
        self._hmd = None
        self._description = None
        self._camera = scene.camera
        self._version = 2 # DK2 by default

        self._matrix_world = self._camera.matrix_world.copy()
        self._lens = self._camera.data.lens

        self._scale = self._calculateScale(scene)

        self._checkModule()
        self._start()

    def isAvailable(self):
        return self._available

    def _checkModule(self):
        """if library exists append it to sys.path"""
        import sys
        import os

        addon_path = os.path.dirname(os.path.abspath(__file__))
        oculus_path = os.path.join(addon_path, "lib", "python-ovrsdk")

        if oculus_path not in sys.path:
            sys.path.append(oculus_path)

    def _start(self):
        try:
            from time import sleep
            from oculusvr import (
                    Hmd,
                    cast,
                    POINTER,
                    ovrHmdDesc,
                    ovrVector3f,
                    )

            Hmd.initialize()
            sleep(0.5)

        except SystemError as err:
            self._error("Oculus initialization failed, check the physical connections and run again")
            return

        if Hmd.detect() == 1:
            self._hmd = Hmd()
            self._description = cast(self._hmd.hmd, POINTER(ovrHmdDesc)).contents
            self._frame = 0
            self._eyes_offset = [ ovrVector3f(), ovrVector3f() ]
            self._eyes_offset[0] = 0.0, 0.0, 0.0
            self._eyes_offset[1] = 0.0, 0.0, 0.0

            self._hmd.configure_tracking()
            self._setVersion(self._description.ProductName)
            self._camera.data.lens = 16

        else:
            self._error("Oculus not connected")

    def _error(self, message):
        self._report({'ERROR'}, message)
        self._available = False

    def update(self):
        # update the camera
        matrix = self._getMatrix()

        if not matrix:
            return

        self._camera.matrix_world =  self._matrix_world * matrix

    def quit(self):
        from oculusvr import Hmd

        if self._camera:
            self._camera.matrix_world = self._matrix_world
            self._camera.data.lens = self._lens

        if Hmd.detect() == 1:
            self._hmd.destroy()
            self._hmd = None
            Hmd.shutdown()

    def _setVersion(self, product_name):
        try:
            if product_name.find(b'DK2') != -1:
                self._version = 2

            elif product_name.find(b'DK1') != -1:
                self._version = 1

            else:
                raise Exception

        except:
            print("Error guessing device version (\"{0}\")".format(product_name))

    def _calculateScale(self, scene):
        """
        if BU != 1 meter, scale the transformations
        """
        unit_settings = scene.unit_settings
        system = unit_settings.system

        if system == 'NONE':
            return None

        elif system == 'METRIC':
            return 1.0 / unit_settings.scale_length

        elif system == 'IMPERIAL':
            return 0.3048 / unit_settings.scale_length

        else:
            assert('Unit system not supported ({0})'.format(system))

    def _scaleMovement(self, position):
        """
        if BU != 1 meter, scale the transformations
        """
        if self._scale is None:
            return position

        return [position[0] * self._scale,
                position[1] * self._scale,
                position[2] * self._scale]

    @property
    def shader_file(self):
        if self._version == 1:
            return 'oculus_dk1.glsl'
        else:
            return 'oculus_dk2.glsl'

    def _getMatrix(self):
        from oculusvr import Hmd
        from mathutils import (
                Quaternion,
                Matrix,
                )

        if self._hmd and Hmd.detect() == 1:
            self._frame += 1

            poses = self._hmd.get_eye_poses(self._frame, self._eyes_offset)

            # oculus may be returning the matrix for both eyes
            # but we are using a single eye without offset

            rotation_raw = poses[0].Orientation.toList()
            position_raw = poses[0].Position.toList()

            # take scene units into consideration
            position_raw = self._scaleMovement(position_raw)

            rotation = Quaternion(rotation_raw).to_matrix().to_4x4()
            position = Matrix.Translation(position_raw)

            matrix = position * rotation
            return matrix

        return None
Loading