pax_global_header 0000666 0000000 0000000 00000000064 12601073162 0014510 g ustar 00root root 0000000 0000000 52 comment=a5b1f3a3a9422453a575e16cad1aa488d68cd2c3
virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/ 0000775 0000000 0000000 00000000000 12601073162 0023731 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/.gitignore 0000664 0000000 0000000 00000000006 12601073162 0025715 0 ustar 00root root 0000000 0000000 *.swp
virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/.gitmodules 0000664 0000000 0000000 00000000251 12601073162 0026104 0 ustar 00root root 0000000 0000000 [submodule "space_view3d_virtual_reality/lib/python-ovrsdk"]
path = space_view3d_virtual_reality/lib/python-ovrsdk
url = https://github.com/dfelinto/python-ovrsdk.git
virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/README.md 0000664 0000000 0000000 00000004624 12601073162 0025216 0 ustar 00root root 0000000 0000000 # Virtual Reality Viewport
Addon to bring virtual reality devices to the Blender viewport.
This is work in progress/pre-alpha state, use at your own risk.
Prerequisite
============
Currently working on Blender Test Build 2.75: http://wiki.blender.org/index.php/Dev:Ref/Release_Notes/2.75
How to Use
==========
In the viewport press ``Space`` + ``Virtual Reality Viewport``.
And then press:
* ``Alt + F11`` (Window Fullscreen)
* ``Alt + F10`` (Fullscreen Area and Hide Panels)
Current State
=============
Video of plugin working:
[![Video of plugin in action](http://img.youtube.com/vi/saSn2qvW0aE/0.jpg)](https://www.youtube.com/watch?v=saSn2qvW0aE)
Installation
============
In a terminal paste the following commands:
```
$ git clone https://github.com/dfelinto/virtual_reality_viewport.git
$ cd virtual_reality_viewport
$ git submodule update --init --recursive --remote
$ zip -x __pycache__ -x */.git* -r9 space_view3d_virtual_reality.zip space_view3d_virtual_reality
```
Now install the space_view3d_virtual_reality.zip in Blender as an addon.
Update
======
In a terminal paste the following commands:
```
$ git pull origin
$ git submodule update --recursive --remote
```
Followed by the rsync command for your OS:
Mac:
```
$ rsync -rv --exclude=.DS_Store --exclude=.git --exclude=*.blend1 --exclude=*.blend2 --exclude=*.swp --exclude=*.swo space_view3d_virtual_reality ~/Library/Application\ Support/Blender/2.75/scripts/addons/
```
Linux:
```
$ rsync -rv --exclude=.DS_Store --exclude=.git --exclude=*.blend1 --exclude=*.blend2 --exclude=*.swp --exclude=*.swo space_view3d_virtual_reality ~/.config/blender/2.75/scripts/addons/
```
Optionally, instead of rsync you can generate a new ``.zip``, remove the previous version of the addon and re-install it.
Roadmap
=======
Oculus DK2 is currently working. The next step is to support view navigation (`Shift + F`) within the addon.
Later we can make it flexible enough to support other HMD devices.
There are a few things I still need to implement:
* Automatically go to clean fullscreen (Alt+F10) and fullwindow (Alt+F11)
Feel free to send pull requests to any of the above.
Credits
=======
Oculus DK2 Shader by Martins Upitis (which I guess based his work from elsewhere)
OculusVR wrapper by https://github.com/jherico/python-ovrsdk
Blender Addon - Dalai Felinto - http://www.dalaifelinto.com
virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality/ 0000775 0000000 0000000 00000000000 12601073162 0031604 5 ustar 00root root 0000000 0000000 __init__.py 0000664 0000000 0000000 00000017654 12601073162 0033653 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality #====================== BEGIN GPL LICENSE BLOCK ======================
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
#======================= END GPL LICENSE BLOCK ========================
#
bl_info = {
"name": "Virtual Reality Viewport",
"author": "Dalai Felinto",
"version": (0, 9),
"blender": (2, 7, 7),
"location": "View 3D Tools",
"description": "",
"warning": "",
"wiki_url": "",
"tracker_url": "",
"category": "3D View"}
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 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))
return {'CANCELLED'}
def quit(self):
"""garbage colect"""
if self.color_id:
delete_image(self.color_id)
def is_stereo_left(self, context):
""""""
space = get_space_3dview(context)
return space.stereo_3d_eye == 'LEFT'
# ############################################################
# Un/Registration
# ############################################################
def register():
bpy.utils.register_class(VirtualRealityViewportOperator)
sandbox.register()
ui.register()
def unregister():
bpy.utils.unregister_class(VirtualRealityViewportOperator)
ui.unregister()
sandbox.unregister()
if __name__ == '__main__':
register()
virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality/lib/ 0000775 0000000 0000000 00000000000 12601073162 0032352 5 ustar 00root root 0000000 0000000 python-ovrsdk/ 0000775 0000000 0000000 00000000000 12601073162 0035122 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality/lib oculus.py 0000664 0000000 0000000 00000011154 12601073162 0033413 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality 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
opengl_helper.py 0000664 0000000 0000000 00000027121 12601073162 0034725 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality import bpy
from bgl import *
from mathutils import Matrix, Euler
SpaceView3D = bpy.types.SpaceView3D
callback_handle = []
fragment_shader ="""
#version 120
uniform sampler2D color_buffer;
void main(void)
{
vec2 coords = gl_TexCoord[0].st;
vec4 foreground = texture2D(color_buffer, coords);
gl_FragColor = mix(foreground, vec4(1.0, 0.0, 0.0, 1.0), 0.5);
}
"""
# ##################
# GLSL Debug
# ##################
def print_shader_errors(shader):
""""""
log = Buffer(GL_BYTE, len(fragment_shader))
length = Buffer(GL_INT, 1)
print('Shader Code:')
glGetShaderSource(shader, len(log), length, log)
line = 1
msg = " 1 "
for i in range(length[0]):
if chr(log[i-1]) == '\n':
line += 1
msg += "%3d %s" %(line, chr(log[i]))
else:
msg += chr(log[i])
print(msg)
glGetShaderInfoLog(shader, len(log), length, log)
print("Error in GLSL Shader:\n")
msg = ""
for i in range(length[0]):
msg += chr(log[i])
print (msg)
def print_program_errors(program):
""""""
log = Buffer(GL_BYTE, 1024)
length = Buffer(GL_INT, 1)
glGetProgramInfoLog(program, len(log), length, log)
print("Error in GLSL Program:\n")
msg = ""
for i in range(length[0]):
msg += chr(log[i])
print (msg)
# ######################
# OpenGL Image Routines
# ######################
def resize(self, context):
"""we can run every frame or only when width/height change"""
# remove old textures
self.quit()
self.width = context.region.width
self.height = context.region.height
self.buffer_width, self.buffer_height = calculate_image_size(self.width, self.height)
# image to dump screen
self.color_id = create_image(self.buffer_width, self.buffer_height, GL_RGBA)
def calculate_image_size(width, height):
"""get a power of 2 size"""
buffer_width, buffer_height = 0,0
i = 0
while (1 << i) <= width:i+= 1
buffer_width = 1 << i
i = 0
while (1 << i) <= height:i+= 1
buffer_height = 1 << i
return buffer_width, buffer_height
def update_image(tex_id, viewport, target=GL_RGBA, texture=GL_TEXTURE0):
"""copy the current buffer to the image"""
glActiveTexture(texture)
glBindTexture(GL_TEXTURE_2D, tex_id)
glCopyTexImage2D(GL_TEXTURE_2D, 0, target, viewport[0], viewport[1], viewport[2], viewport[3], 0)
def create_image(width, height, target=GL_RGBA):
"""create an empty image, dimensions pow2"""
if target == GL_RGBA:
target, internal_format, dimension = GL_RGBA, GL_RGB, 3
else:
target, internal_format, dimension = GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, 1
null_buffer = Buffer(GL_BYTE, [(width + 1) * (height + 1) * dimension])
id_buf = Buffer(GL_INT, 1)
glGenTextures(1, id_buf)
tex_id = id_buf.to_list()[0]
glBindTexture(GL_TEXTURE_2D, tex_id)
glTexImage2D(GL_TEXTURE_2D, 0, target, width, height, 0, internal_format, GL_UNSIGNED_BYTE, null_buffer)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
if target == GL_DEPTH_COMPONENT32:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE)
glCopyTexImage2D(GL_TEXTURE_2D, 0, target, 0, 0, width, height, 0)
glBindTexture(GL_TEXTURE_2D, 0)
del null_buffer
return tex_id
def delete_image(tex_id):
"""clear created image"""
id_buf = Buffer(GL_INT, 1)
id_buf.to_list()[0] = tex_id
if glIsTexture(tex_id):
glDeleteTextures(1, id_buf)
# ##################
# Framebuffer Routines
# ##################
def check_framebuffer_status(target):
status = glCheckFramebufferStatus(target)
if status == GL_FRAMEBUFFER_COMPLETE:
return True
elif status == GL_FRAMEBUFFER_UNDEFINED:
print("framebuffer not complete: GL_FRAMEBUFFER_UNDEFINED - returned if the specified framebuffer is the default read or draw framebuffer, but the default framebuffer does not exist.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT - returned if any of the framebuffer attachment points are framebuffer incomplete.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT - returned if the framebuffer does not have at least one image attached to it.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER - returned if the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for any color attachment point named by GL_DRAW_BUFFERi.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER - returned if GL_READ_BUFFER is not GL_NONE and the value of GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is GL_NONE for the color attachment point named by GL_READ_BUFFER.")
elif status == GL_FRAMEBUFFER_UNSUPPORTED:
print("framebuffer not complete: GL_FRAMEBUFFER_UNSUPPORTED - returned if the combination of internal formats of the attached images violates an implementation-dependent set of restrictions.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE - returned if the value of GL_RENDERBUFFER_SAMPLES is not the same for all attached renderbuffers; if the value of GL_TEXTURE_SAMPLES is the not same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_RENDERBUFFER_SAMPLES does not match the value of GL_TEXTURE_SAMPLES. also returned if the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS i s not the same for all attached textures; or, if the attached images are a mix of renderbuffers and textures, the value of GL_TEXTURE_FIXED_SAMPLE_LOCATIONS is not GL_TRUE for all attached textures.")
elif status == GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS:
print("framebuffer not complete: GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS - returned if any framebuffer attachment is layered, and any populated attachment is not layered, or if all populated color attachments are not from textures of the same target.")
else:
print("framebuffer not complete: status 0x%x (unknown)" % (status,))
return False
def create_framebuffer(width, height, target=GL_RGBA):
"""create an empty framebuffer"""
id_buf = Buffer(GL_INT, 1)
glGenFramebuffers(1, id_buf)
fbo_id = id_buf.to_list()[0]
if fbo_id == 0:
print("Framebuffer error on creation")
return -1
tex_id = create_image(width, height)
glBindFramebuffer(GL_FRAMEBUFFER, fbo_id)
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex_id, 0)
glGenRenderbuffers(1, id_buf)
depth_id = id_buf.to_list()[0]
glBindRenderbuffer(GL_RENDERBUFFER, depth_id)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, width, height)
# attach the depth buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_id)
#glDrawBuffers(fbo_id, GL_COLOR_ATTACHMENT0)
if not check_framebuffer_status(GL_DRAW_FRAMEBUFFER):
delete_framebuffer(fbo_id)
glBindFramebuffer(GL_FRAMEBUFFER, 0)
return -1
glBindFramebuffer(GL_FRAMEBUFFER, 0)
return fbo_id
def delete_framebuffer(fbo_id):
"""clear created framebuffer"""
id_buf = Buffer(GL_INT, 1)
id_buf.to_list()[0] = fbo_id
if glIsFramebuffer(fbo_id):
glDeleteFramebuffers(1, id_buf)
# ##################
# GLSL Screen Shader
# ##################
def create_shader(source, program=None, type=GL_FRAGMENT_SHADER):
""""""
if program == None:
program = glCreateProgram()
shader = glCreateShader(type)
glShaderSource(shader, source)
glCompileShader(shader)
success = Buffer(GL_INT, 1)
glGetShaderiv(shader, GL_COMPILE_STATUS, success)
if not success[0]:
print_shader_errors(shader)
glAttachShader(program, shader)
glLinkProgram(program)
return program
def setup_uniforms(program, color_id, width, height, is_left):
""""""
uniform = glGetUniformLocation(program, "bgl_RenderedTexture")
glActiveTexture(GL_TEXTURE0)
glBindTexture(GL_TEXTURE_2D, color_id)
if uniform != -1: glUniform1i(uniform, 0)
uniform = glGetUniformLocation(program, "bgl_RenderedTextureWidth")
if uniform != -1: glUniform1f(uniform, width)
uniform = glGetUniformLocation(program, "bgl_RenderedTextureHeight")
if uniform != -1: glUniform1f(uniform, height)
uniform = glGetUniformLocation(program, "bgl_RenderedStereoEye")
if uniform != -1: glUniform1i(uniform, 0 if is_left else 1)
def bindcode(image):
"""load the image in the graphic card if necessary"""
image.gl_touch(GL_NEAREST)
return image.bindcode
# ##################
# Drawing Routines
# ##################
def view_setup():
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glLoadIdentity()
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glOrtho(-1, 1, -1, 1, -20, 20)
gluLookAt(0.0, 0.0, 1.0, 0.0,0.0,0.0, 0.0,1.0,0.0)
def view_reset(viewport):
# Get texture info
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
def draw_rectangle_rainbow(zed=0.0):
texco = [(1, 1), (0, 1), (0, 0), (1,0)]
verco = [(1.0, 1.0), (-1.0, 1.0), (-1.0, -1.0), ( 1.0, -1.0)]
colors = [(1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0), (1.0, 1.0, 0.0, 0.0)]
glPolygonMode(GL_FRONT_AND_BACK , GL_FILL)
glBegin(GL_QUADS)
for i in range(4):
color = colors[i]
glColor4f(color[0], color[1], color[2], color[3])
glTexCoord3f(texco[i][0], texco[i][1], zed)
glVertex2f(verco[i][0], verco[i][1])
glEnd()
def draw_rectangle(zed=0.0):
texco = [(1, 1), (0, 1), (0, 0), (1,0)]
verco = [(1.0, 1.0), (-1.0, 1.0), (-1.0, -1.0), ( 1.0, -1.0)]
glPolygonMode(GL_FRONT_AND_BACK , GL_FILL)
glColor4f(1.0, 1.0, 1.0, 0.0)
glBegin(GL_QUADS)
for i in range(4):
glTexCoord3f(texco[i][0], texco[i][1], zed)
glVertex2f(verco[i][0], verco[i][1])
glEnd()
def draw_callback_px(self, context):
"""core function"""
if not self._enabled: return
is_left = self.is_stereo_left(context)
act_tex = Buffer(GL_INT, 1)
glGetIntegerv(GL_ACTIVE_TEXTURE, act_tex)
glGetIntegerv(GL_VIEWPORT, self.viewport)
# (1) dump buffer in texture
update_image(self.color_id, self.viewport, GL_RGBA, GL_TEXTURE0)
# (2) run screenshader
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
pjm = Buffer(GL_FLOAT, 16)
mvm = Buffer(GL_FLOAT, 16)
cam_pos = context.scene.camera.location.copy()
glMatrixMode(GL_MODELVIEW)
glTranslatef(cam_pos[0], cam_pos[1], cam_pos[2])
glGetFloatv(GL_PROJECTION_MATRIX, pjm)
glGetFloatv(GL_MODELVIEW_MATRIX, mvm)
# set identity matrices
view_setup()
# update shader
glUseProgram(self.program_shader)
setup_uniforms(self.program_shader, self.color_id, self.width, self.height, is_left)
draw_rectangle()
# (3) restore opengl defaults
glUseProgram(0)
glActiveTexture(act_tex[0])
glBindTexture(GL_TEXTURE_2D, 0)
view_reset(self.viewport)
glMatrixMode(GL_MODELVIEW)
glTranslatef(-cam_pos[0], -cam_pos[1], -cam_pos[2])
sandbox.py 0000664 0000000 0000000 00000030245 12601073162 0033541 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality import bpy
from bpy.app.handlers import persistent
TODO = True
from .opengl_helper import (
create_framebuffer,
delete_framebuffer,
draw_rectangle,
draw_rectangle_rainbow,
view_reset,
view_setup,
)
from bgl import *
class GLdata:
def __init__(self):
self.color_tex = -1
self.fb = -1
self.rb = -1
self.size = 0
global _time
_time = 0
class VirtualRealitySandboxOperator(bpy.types.Operator):
""""""
bl_idname = "view3d.virtual_reality_sandbox"
bl_label = "Toggle Virtual Reality Sandbox"
bl_description = ""
_gl_data = None
_timer = None
_handle = None
_width = 1920
_height = 1080
action = bpy.props.EnumProperty(
description="",
items=(("ENABLE", "Enable", "Enable"),
("DISABLE", "Disable", "Disable"),
),
default="DISABLE",
options={'SKIP_SAVE'},
)
@classmethod
def poll(cls, context):
return context.area.type == 'VIEW_3D'
def modal(self, context, event):
wm = context.window_manager
if not wm.virtual_reality.is_enabled:
self._quit_doit(context)
context.area.tag_redraw()
return {'FINISHED'}
if event.type == 'TIMER':
self._fbo_run()
# this redraw is only required for the visualization
context.area.tag_redraw()
return {'PASS_THROUGH'}
def invoke(self, context, event):
wm = context.window_manager
vr = wm.virtual_reality
is_enabled = vr.is_enabled
if self.action == 'DISABLE':
if vr.is_enabled:
self._quit(context)
return {'FINISHED'}
else:
self.report({'ERROR'}, "Virtual Reality is not enabled")
return {'CANCELLED'}
else: # ENABLE
if vr.is_enabled:
self.report({'ERROR'}, "Virtual Reality is already enabled")
return {'CANCELLED'}
self._init(context, self._width, self._height)
return {'RUNNING_MODAL'}
return {'CANCELLED'}
def _quit(self, context):
"""garbage collect"""
# change it so the original modal operator will clean things up
wm = context.window_manager
wm.virtual_reality.is_enabled = False
def _quit_doit(self, context):
"""actual quit"""
wm = context.window_manager
if self._timer:
wm.event_timer_remove(self._timer)
del self._timer
if self._handle:
bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
del self._handle
self._fbo_delete()
def _init(self, context, width, height):
wm = context.window_manager
wm.virtual_reality.is_enabled = True
# setup modal
self._timer = wm.event_timer_add(1.0 / 75.0, context.window) # 75 Hz
self._handle = bpy.types.SpaceView3D.draw_handler_add(self._draw_callback_px, (context,), 'WINDOW', 'POST_PIXEL')
wm.modal_handler_add(self)
# initialize opengl data
self._gl_data = GLdata()
self._fbo_setup()
def _fbo_setup(self):
gl_data = self._gl_data
size = 128
id_buf = Buffer(GL_INT, 1)
act_fbo = Buffer(GL_INT, 1)
glGetIntegerv(GL_FRAMEBUFFER, act_fbo)
act_tex = Buffer(GL_INT, 1)
glGetIntegerv(GL_ACTIVE_TEXTURE, act_tex)
#RGBA8 2D texture, 24 bit depth texture, sizexsize
glGenTextures(1, id_buf)
gl_data.color_tex = id_buf.to_list()[0]
glBindTexture(GL_TEXTURE_2D, gl_data.color_tex)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
# NULL means reserve texture memory, but texels are undefined
null_buffer = Buffer(GL_BYTE, [(size + 1) * (size + 1) * 4])
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, size, size, 0, GL_BGRA, GL_UNSIGNED_BYTE, null_buffer)
glBindTexture(GL_TEXTURE_2D, act_tex[0])
glGenFramebuffers(1, id_buf)
gl_data.fb = id_buf.to_list()[0]
glBindFramebuffer(GL_FRAMEBUFFER, gl_data.fb)
# Attach 2D texture to this FBO
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, gl_data.color_tex, 0)
glGenRenderbuffers(1, id_buf)
gl_data.depth_rb = id_buf.to_list()[0]
glBindRenderbuffer(GL_RENDERBUFFER, gl_data.depth_rb)
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, size, size)
# Attach depth buffer to FBO
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, gl_data.depth_rb)
# Does the GPU support current FBO configuration?
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
glBindFramebuffer(GL_FRAMEBUFFER, act_fbo[0])
if status == GL_FRAMEBUFFER_COMPLETE:
print("FBO: good: {0} : {1} : {2}".format(gl_data.color_tex, gl_data.depth_rb, gl_data.fb))
else:
print("FBO: error", status)
def _draw_a_quad(self):
"""
draw an animated quad on the screen
"""
import time
import math
global _time
speed = 0.01
one = 1.0
zer = 0.0
_time, _int = math.modf(_time + speed)
factor = _time * 2.0
if factor > 1.0:
factor = 2.0 - factor;
one = one - factor;
zer = factor - zer;
glClearColor(1.0, 1.0, 1.0, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glOrtho(-1.0, 1.0, -1.0, 1.0, 1.0, 20.0)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
gluLookAt(0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
current_color = Buffer(GL_FLOAT, 4)
glGetFloatv(GL_CURRENT_COLOR, current_color);
glEnable(GL_COLOR_MATERIAL)
glBegin(GL_QUADS)
glColor3f(one, zer, zer)
glVertex3f(-0.75, -0.75, 0.0)
glColor3f(zer, one, zer)
glVertex3f( 0.75, -0.75, 0.0)
glColor3f(zer, zer, one)
glVertex3f( 0.75, 0.75, 0.0)
glColor3f(one, one, zer)
glVertex3f(-0.75, 0.75, 0.0)
glEnd()
glColor4fv(current_color)
glDisable(GL_COLOR_MATERIAL)
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
def _fbo_run(self):
"""
draw in the FBO
"""
gl_data = self._gl_data
act_fbo = Buffer(GL_INT, 1)
glGetIntegerv(GL_FRAMEBUFFER, act_fbo)
# setup
glBindFramebuffer(GL_FRAMEBUFFER, gl_data.fb)
viewport = Buffer(GL_INT, 4)
glGetIntegerv(GL_VIEWPORT, viewport)
glViewport(0, 0, gl_data.size, gl_data.size)
# actual drawing
self._draw_a_quad()
# unbinding
glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
glBindFramebuffer(GL_FRAMEBUFFER, act_fbo[0])
def _fbo_visualize(self):
"""
draw the FBO in a quad
"""
gl_data = self._gl_data
current_color = Buffer(GL_FLOAT, 4)
glGetFloatv(GL_CURRENT_COLOR, current_color);
act_tex = Buffer(GL_INT, 1)
glGetIntegerv(GL_ACTIVE_TEXTURE, act_tex)
viewport = Buffer(GL_INT, 4)
glGetIntegerv(GL_VIEWPORT, viewport)
glViewport(300, 200, 256, 256)
glScissor(300, 200, 256, 256)
glClearColor(0.0, 0.0, 0.0, 0.0)
glClearDepth(1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glLoadIdentity()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glLoadIdentity()
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glLoadIdentity()
glOrtho(-1, 1, -1, 1, -20, 20)
gluLookAt(0.0, 0.0, 1.0,0.0,0.0, 0.0, 0.0,1.0,0.0)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, gl_data.color_tex)
glColor3f(1.0, 1.0, 1.0)
glBegin(GL_QUADS)
glTexCoord3f(1.0, 1.0, 0.0)
glVertex2f( 1.0, 1.0)
glTexCoord3f(0.0, 1.0, 0.0)
glVertex2f(-1.0, 1.0)
glTexCoord3f(0.0, 0.0, 0.0)
glVertex2f(-1.0,-1.0)
glTexCoord3f(1.0, 0.0, 0.0)
glVertex2f( 1.0,-1.0)
glEnd()
glColor4fv(current_color)
glDisable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, act_tex[0])
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
glScissor(viewport[0], viewport[1], viewport[2], viewport[3])
def _debug_quad(self):
viewport = Buffer(GL_INT, 4)
glGetIntegerv(GL_VIEWPORT, viewport)
glViewport(300, 200, 256, 256)
glScissor(300, 200, 256, 256)
# actual drawing
self._draw_a_quad()
glViewport(viewport[0], viewport[1], viewport[2], viewport[3])
glScissor(viewport[0], viewport[1], viewport[2], viewport[3])
def _fbo_delete(self):
"""
cleanup FBO data
"""
gl_data = self._gl_data
id_buf = Buffer(GL_INT, 1)
id_buf.to_list()[0] = gl_data.color_tex
glDeleteTextures(1, id_buf)
id_buf.to_list()[0] = gl_data.depth_rb
glDeleteRenderbuffers(1, id_buf)
id_buf.to_list()[0] = gl_data.fb
glDeleteFramebuffers(1, id_buf)
def _draw_callback_px(self, context):
"""core function"""
self._fbo_visualize()
self._debug_quad()
# ############################################################
# Roadmap
# ############################################################
"""
Testing FBO
===========
get FBO drawing working
* create FBO
* draw to FBO via python
* draw FBO on screen
Testing Oculus
==============
draw FBO to direct mode
* get as far as required for direct mode
Blender FBO Rendering
=====================
test bpy.ops.view3d.offset()
* make sure matrices are correctly taken into account
Final Oculus Implementation
===========================
(https://developer.oculus.com/documentation/pcsdk/latest/concepts/dg-render/)
* obtain eye position
* calculate modelview matrix
* obtain projection matrix
* bpy.ops.view3d.offset()
* submit frame to oculus
"""
# ############################################################
# Global Properties
# ############################################################
class VirtualRealityInfo(bpy.types.PropertyGroup):
is_enabled = bpy.props.BoolProperty(
name="Enabled",
default=False,
)
# ############################################################
# Un/Registration
# ############################################################
@persistent
def virtual_reality_load_pre(dummy):
wm = bpy.context.window_manager
wm.virtual_reality.is_enabled = False
@persistent
def virtual_reality_load_post(dummy):
wm = bpy.context.window_manager
wm.virtual_reality.is_enabled = False
# ############################################################
# Un/Registration
# ############################################################
def register():
bpy.app.handlers.load_pre.append(virtual_reality_load_pre)
bpy.app.handlers.load_pre.append(virtual_reality_load_post)
bpy.utils.register_class(VirtualRealitySandboxOperator)
bpy.utils.register_class(VirtualRealityInfo)
bpy.types.WindowManager.virtual_reality = bpy.props.PointerProperty(
name="virtual_reality",
type=VirtualRealityInfo,
options={'HIDDEN'},
)
def unregister():
bpy.app.handlers.load_pre.remove(virtual_reality_load_pre)
bpy.app.handlers.load_pre.remove(virtual_reality_load_post)
bpy.utils.unregister_class(VirtualRealitySandboxOperator)
del bpy.types.WindowManager.virtual_reality
bpy.utils.unregister_class(VirtualRealityInfo)
shaders/ 0000775 0000000 0000000 00000000000 12601073162 0033156 5 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality oculus_dk1.glsl 0000664 0000000 0000000 00000004457 12601073162 0036124 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality/shaders // Oculus DK1 lens distortion shader for multiple eyes
// shader is adapted from Oculus DK1 distortion shader by
// Lubosz Sarnecki(lubosz.wordpress.com/)
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
const vec4 kappa = vec4(1.0,1.7,0.7,15.0);
float screen_width = bgl_RenderedTextureWidth;
float screen_height = bgl_RenderedTextureHeight;
const float scaleFactor = 0.8;
const vec2 leftCenter = vec2(0.25, 0.5);
const vec2 rightCenter = vec2(0.75, 0.5);
const float separation = 0.01;
// Scales input texture coordinates for distortion.
vec2 hmdWarp(vec2 LensCenter, vec2 texCoord, vec2 Scale, vec2 ScaleIn) {
vec2 theta = (texCoord - LensCenter) * ScaleIn;
float rSq = theta.x * theta.x + theta.y * theta.y;
vec2 rvector = theta * (kappa.x +
kappa.y * rSq +
kappa.z * rSq * rSq +
kappa.w * rSq * rSq * rSq);
vec2 tc = LensCenter + Scale * rvector;
return tc;
}
bool validate(vec2 tc, int left_eye) {
//keep within bounds of texture
if ((left_eye == 1 && (tc.x < 0.0 || tc.x > 0.5)) ||
(left_eye == 0 && (tc.x < 0.5 || tc.x > 1.0)) ||
tc.y < 0.0 || tc.y > 1.0) {
return false;
}
return true;
}
void main() {
vec2 screen = vec2(screen_width, screen_height);
float as = float(screen.x / 2.0) / float(screen.y);
vec2 Scale = vec2(0.5, as);
vec2 ScaleIn = vec2(2.0 * scaleFactor, 1.0 / as * scaleFactor);
vec2 texCoord = gl_TexCoord[0].st;
vec2 texCoordSeparated = texCoord;
vec2 tc = vec2(0);
vec4 color = vec4(0);
// ad hoc enhanced separation to allow proper viewing factor
float ad_hoc_enhance_stereo = 4.0;
if (texCoord.x < 0.5) {
texCoordSeparated.x -= ad_hoc_enhance_stereo *separation;
tc = hmdWarp(leftCenter, texCoordSeparated, Scale, ScaleIn );
color = texture2D(bgl_RenderedTexture, tc);
if (!validate(tc, 1))
color = vec4(0);
} else {
texCoordSeparated.x += ad_hoc_enhance_stereo *separation;
tc = hmdWarp(rightCenter, texCoordSeparated, Scale, ScaleIn);
color = texture2D(bgl_RenderedTexture, tc);
if (!validate(tc, 0))
color = vec4(0);
}
gl_FragColor = color;
}
oculus_dk2.glsl 0000664 0000000 0000000 00000003606 12601073162 0036120 0 ustar 00root root 0000000 0000000 virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality/shaders // Oculus DK2 lens distortion shader for single eye
// shader is adapted from Oculus DK1 distortion shader by
// Lubosz Sarnecki(lubosz.wordpress.com/)
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;
const vec4 kappa = vec4(1.0,0.9,1.0,2.0);
float screen_width = bgl_RenderedTextureWidth;
float screen_height = bgl_RenderedTextureHeight;
const float scaleFactor = 0.83;
const vec2 lensCenter = vec2(0.5, 0.5);
// Scales input texture coordinates for distortion.
vec2 hmdWarp(vec2 texCoord, vec2 Scale, vec2 ScaleIn, float eta) {
vec2 theta = (texCoord - lensCenter) * ScaleIn;
float rSq = theta.x * theta.x + theta.y * theta.y;
vec2 rvector = theta * (kappa.x + kappa.y * rSq + kappa.z * rSq * rSq + kappa.w * rSq * rSq * rSq);
vec2 tc = lensCenter + Scale * eta * rvector;
return tc;
}
float edges(vec2 tc)
{
float vertL = smoothstep(0.0,0.05,tc.x);
float vertR = smoothstep(1.0,0.95,tc.x);
float horizL = smoothstep(0.0,0.05,tc.y);
float horizR = smoothstep(1.0,0.95,tc.y);
return vertL*vertR*horizL*horizR;
}
void main()
{
vec2 screen = vec2(screen_width, screen_height);
vec3 eta = vec3(1.00,1.018,1.042); //refraction indices
float as = float(screen.x) / float(screen.y);
vec2 Scale = vec2(1.0, 1.0);
vec2 ScaleIn = vec2(scaleFactor, scaleFactor);
vec2 texCoord = gl_TexCoord[0].st;
vec2 tcR = vec2(0.0);
vec2 tcG = vec2(0.0);
vec2 tcB = vec2(0.0);
vec4 color = vec4(0.0);
tcR = hmdWarp(texCoord, Scale, ScaleIn, eta.r );
tcG = hmdWarp(texCoord, Scale, ScaleIn, eta.g );
tcB = hmdWarp(texCoord, Scale, ScaleIn, eta.b );
color.r = texture2D(bgl_RenderedTexture, tcR*vec2(0.5,1.0)+vec2(0.25,0.0)).r;
color.g = texture2D(bgl_RenderedTexture, tcG*vec2(0.5,1.0)+vec2(0.25,0.0)).g;
color.b = texture2D(bgl_RenderedTexture, tcB*vec2(0.5,1.0)+vec2(0.25,0.0)).b;
color = color * edges(tcR);
gl_FragColor = color;
}
virtual_reality_viewport-a5b1f3a3a9422453a575e16cad1aa488d68cd2c3/space_view3d_virtual_reality/ui.py0000664 0000000 0000000 00000002216 12601073162 0032574 0 ustar 00root root 0000000 0000000 import bpy
# ############################################################
# User Interface
# ############################################################
class VirtualRealityPanel(bpy.types.Panel):
bl_label = "Head Mounted Display"
bl_space_type = 'VIEW_3D'
bl_region_type = 'TOOLS'
bl_category = 'Virtual Reality'
@staticmethod
def draw(self, context):
layout = self.layout
col = layout.column()
col.operator("view3d.virtual_reality_toggle", text="Virtual Reality Preview", icon="PLAY")
col.separator()
wm = context.window_manager
if wm.virtual_reality.is_enabled:
col.operator("view3d.virtual_reality_sandbox", text="Virtual Reality", icon="X").action='DISABLE'
else:
col.operator("view3d.virtual_reality_sandbox", text="Virtual Reality", icon="PLAY").action='ENABLE'
# ############################################################
# Un/Registration
# ############################################################
def register():
bpy.utils.register_class(VirtualRealityPanel)
def unregister():
bpy.utils.unregister_class(VirtualRealityPanel)