# -*- coding: utf-8 -*-
###########################################################################
#   Copyright (C) 1998-2013 by authors (see AUTHORS.txt)                  #
#                                                                         #
#   This file is part of LuxRays.                                         #
#                                                                         #
#   LuxRays 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 3 of the License, or     #
#   (at your option) any later version.                                   #
#                                                                         #
#   LuxRays 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, see <http://www.gnu.org/licenses/>. #
#                                                                         #
#   LuxRays website: http://www.luxrender.net                             #
###########################################################################
#
# SmallLuxGPU v4.0devel1 Blender 2.5 plug-in

bl_info = {
    "name": "SmallLuxGPU",
    "author": "see (SLG) AUTHORS.txt",
    "version": (4, 0, 1),
    "blender": (2, 6, 0),
    "location": "Info Header > Engine dropdown menu",
    "description": "SmallLuxGPU Exporter and Live! mode Plugin",
    "warning": "",
    "wiki_url": "http://www.luxrender.net/wiki/index.php?title=Blender_2.5_exporter",
    "tracker_url": "http://www.luxrender.net/forum/viewforum.php?f=34",
    "category": "Render"}

import multiprocessing
import bpy, bl_operators, bl_ui
import blf
from mathutils import Matrix, Vector
import os
from threading import Lock, Thread
from time import sleep, time
from telnetlib import Telnet
from struct import pack
from itertools import zip_longest
from subprocess import Popen
from math import isnan

# SLG Telnet interface
class SLGTelnet:
    def __init__(self):
        self.slgtn = None
        try:
            self.slgtn = Telnet('localhost',18081)
            self.slgtn.read_until(b'SmallLuxGPU Telnet Server Interface')
            self.connected = True
        except:
            self.connected = False
    def __del__(self):
        if self.slgtn:
            self.slgtn.close()
    def send(self, cmd):
        try:
            self.slgtn.write(str.encode(cmd+'\n'))
            r = self.slgtn.expect([b'OK\n',b'ERROR\n'],5)
            if r[0] == 0:
                return True
            else:
                return False
        except:
            return False
    def close(self):
        if self.slgtn:
            self.slgtn.close()

# Formatted float filter
ff = lambda f:format(f,'.6f').rstrip('0')

# SmallLuxGPU Blender Plugin: one instance
class SLGBP:
    slgpath = spath = sname = sfullpath = image_filename = ''
    nomatn = 'nomat'
    cfg = {}
    scn = {}
    sun = infinitelight = None
    live = liveanim = False
    LIVECFG, LIVESCN, LIVEMTL, LIVEOBJ, LIVEALL = 1, 2, 4, 8, 15
    livemat = None
    liveact = 0
    slgproc = thread = None
    lock = Lock()
    telnet = None
    abort = False

    # Initialize SLG scene for export
    @staticmethod
    def init(scene, errout):
        # Reset Abort flag
        SLGBP.abort = False
        SLGBP.warn = ''

        # SmallLuxGPU executable path
        SLGBP.slgpath = bpy.path.abspath(scene.slg.slgpath).replace('\\','/')
        if os.path.isdir(SLGBP.slgpath):
            SLGBP.slgpath = os.path.join(SLGBP.slgpath, 'slg')
        elif not os.path.isfile(SLGBP.slgpath):
            errout("Full path to SmallLuxGPU executable required")
            return False
        if not os.path.exists(SLGBP.slgpath):
            errout("Cannot find SLG executable at specified path")
            return False

        # Scenes files path
        SLGBP.spath = bpy.path.abspath(scene.slg.scene_path).replace('\\','/')
        if SLGBP.spath.endswith('/'):
            SLGBP.spath = os.path.dirname(SLGBP.spath)
        if not os.path.exists(SLGBP.spath):
            errout("Cannot find scenes directory")
            return False

        # Scene name
        SLGBP.sname = scene.slg.scenename
        if os.path.isfile(scene.slg.scenename):
            SLGBP.sname = os.path.basename(scene.slg.scenename)
        SLGBP.sname = SLGBP.sname.split('/')[-1].split('\\')[-1]
        if not SLGBP.sname:
            errout("Invalid scene name")
            return False
        SLGBP.sname = os.path.splitext(SLGBP.sname)[0]

        # Make sure we have at least one light in our scene
        SLGBP.sun = next((f for f in scene.objects if f.type=="LAMP" and f.data and f.data.type=="SUN" and scene.layers[next((i for i in range(len(f.layers)) if f.layers[i]))] and (f.data.sky.use_sky or f.data.sky.use_atmosphere)),None)
        if scene.world:
            SLGBP.infinitelight = next((ts for ts in scene.world.texture_slots if ts and hasattr(ts.texture,'image')), None)
        else:
            SLGBP.infinitelight = None
        emitters = any(m.users and m.emit for m in bpy.data.materials)
        if not SLGBP.sun and not SLGBP.infinitelight and not emitters:
            errout("No lights in scene!  SLG requires an emitter, world image or sun-lamp")
            return False

        # Determine output image filename (template)
        if os.path.exists(os.path.dirname(scene.render.filepath)):
            if not scene.render.filepath.split('/')[-1].split('\\')[-1]:
                SLGBP.image_filename = (scene.render.filepath + '{:05d}')
            else:
                i = scene.render.filepath.count('#')
                if i > 0:
                    SLGBP.image_filename = scene.render.filepath.replace('#'*i, '{:0'+str(i)+'d}')
                else:
                    SLGBP.image_filename = scene.render.filepath
            if not SLGBP.image_filename.endswith(scene.render.file_extension):
                # SLG requires extension, force one
                SLGBP.image_filename += scene.render.file_extension
        else:
            SLGBP.image_filename = '{}/{}/{}{}'.format(SLGBP.spath,SLGBP.sname,SLGBP.sname,scene.render.file_extension)

        # Check/create scene directory to hold scene files
        SLGBP.sfullpath = '{}/{}'.format(SLGBP.spath,SLGBP.sname)
        if not os.path.exists(SLGBP.sfullpath):
            print("SLGBP ===> Create scene directory")
            try:
                os.mkdir(SLGBP.sfullpath)
            except:
                errout("Cannot create SLG scenes directory")
                return False

        return True

    # Get SLG .cfg properties
    @staticmethod
    def getcfg(scene):
        cfg = {}
        cfg['film.tonemap.type'] = scene.slg.film_tonemap_type
        if scene.slg.film_tonemap_type == '0':
            cfg['film.tonemap.linear.scale'] = ff(scene.slg.linear_scale)
        else:
            cfg['film.tonemap.reinhard02.burn'] = ff(scene.slg.reinhard_burn)
            cfg['film.tonemap.reinhard02.prescale'] = ff(scene.slg.reinhard_prescale)
            cfg['film.tonemap.reinhard02.postscale'] = ff(scene.slg.reinhard_postscale)
        cfg['image.filename'] = SLGBP.image_filename.format(scene.frame_current)

        # ---- End of Live properties ----
        if SLGBP.live:
            return cfg

        cfg['image.width'] = format(int(scene.render.resolution_x*scene.render.resolution_percentage*0.01))
        cfg['image.height'] = format(int(scene.render.resolution_y*scene.render.resolution_percentage*0.01))
        cfg['batch.halttime'] = format(scene.slg.enablebatchmode*scene.slg.batchmodetime)
        cfg['batch.haltspp'] = format(scene.slg.enablebatchmode*scene.slg.batchmodespp)
        cfg['batch.periodicsave'] = format(scene.slg.batchmode_periodicsave)
        cfg['scene.file'] = '{}/{}/{}.scn'.format(SLGBP.spath,SLGBP.sname,SLGBP.sname)
        cfg['scene.epsilon'] = format(0.0001/scene.unit_settings.scale_length,'g')
        cfg['opencl.cpu.use'] = format(scene.slg.opencl_cpu, 'b')
        cfg['opencl.gpu.use'] = format(scene.slg.opencl_gpu, 'b')
        cfg['opencl.task.count'] = format(scene.slg.opencl_task_count)
        cfg['opencl.platform.index'] = format(scene.slg.platform)
        if scene.slg.devices.strip():
            cfg['opencl.devices.select'] = scene.slg.devices
        cfg['opencl.gpu.workgroup.size'] = format(scene.slg.gpu_workgroup_size)
        cfg['film.gamma'] = format(scene.slg.film_gamma, 'g')
        if (scene.slg.rendering_type == 'PATHOCL' or scene.slg.rendering_type == 'RTPATHOCL') and scene.slg.ocl_filter_type != 'NONE':
            cfg['film.filter.type'] = '0'
        else:
            cfg['film.filter.type'] = scene.slg.film_filter_type
        cfg['film.alphachannel.enable'] = format(scene.slg.alphachannel, 'b')
        cfg['screen.refresh.interval'] = format(scene.slg.refreshrate)
        cfg['renderengine.type'] = scene.slg.rendering_type
        cfg['path.sampler.type'] = scene.slg.sampler_type
        cfg['path.pixelatomics.enable'] = format(scene.slg.pixelatomics_enable, 'b')
        cfg['path.filter.type'] = scene.slg.ocl_filter_type
        cfg['path.filter.width.x'] = ff(scene.slg.filter_width_x)
        cfg['path.filter.width.y'] = ff(scene.slg.filter_width_y)
        cfg['path.filter.alpha'] = ff(scene.slg.filter_alpha)
        cfg['path.filter.B'] = ff(scene.slg.filter_B)
        cfg['path.filter.C'] = ff(scene.slg.filter_C)
        cfg['path.maxdepth'] = format(scene.slg.path_maxdepth)
        cfg['path.russianroulette.depth'] = format(scene.slg.rrdepth)
        cfg['path.russianroulette.cap'] = ff(scene.slg.rrcap)
        cfg['accelerator.type'] = scene.slg.accelerator_type
        cfg['bidirvm.lightpath.count'] = format(scene.slg.bidirvm_lightpath_count)
        cfg['bidirvm.startradius.scale'] = ff(scene.slg.bidirvm_startradius_scale)
        cfg['bidirvm.alpha'] = ff(scene.slg.bidirvm_alpha)
        cfg['cbidir.eyepath.count'] = format(scene.slg.cbidir_eyepath_count)
        cfg['cbidir.lightpath.count'] = format(scene.slg.cbidir_lightpath_count)
        cfg['native.threads.count'] = format(scene.slg.native_threads_count)
        cfg['light.maxdepth'] = format(scene.slg.light_maxdepth)
        cfg['sampler.largesteprate'] = ff(scene.slg.sampler_largesteprate)
        cfg['sampler.maxconsecutivereject'] = format(scene.slg.sampler_maxconsecutivereject)
        cfg['sampler.imagemutationrate'] = ff(scene.slg.sampler_imagemutationrate)

        return cfg

    # Get SLG .scn scene properties
    @staticmethod
    def getscn(scene):
        scn = {}

        # Get camera and lookat direction
        cam = scene.camera
        camdir = cam.matrix_world * Vector((0, 0, -1))

        # Camera.location not always updated, but matrix is
        camloc = cam.matrix_world.to_translation()
        scn['scene.camera.lookat'] = '{} {} {} {} {} {}'.format(ff(camloc.x),ff(camloc.y),ff(camloc.z),ff(camdir.x),ff(camdir.y),ff(camdir.z))
        camup = cam.matrix_world.to_3x3() * Vector((0,1,0))
        scn['scene.camera.up'] = '{} {} {}'.format(ff(camup.x),ff(camup.y),ff(camup.z))

        scn['scene.camera.fieldofview'] = format(cam.data.angle*180.0/3.1415926536,'g')

        # DOF
        fdist = (camloc-cam.data.dof_object.matrix_world.to_translation()).magnitude if cam.data.dof_object else cam.data.dof_distance
        if fdist:
            scn['scene.camera.focaldistance'] = ff(fdist)
            scn['scene.camera.lensradius'] = ff(cam.data.slg_lensradius)

        # Infinite light, if present
        if SLGBP.infinitelight:
            if not SLGBP.live:
                if hasattr(SLGBP.infinitelight.texture.image,'filepath'):
                    scn['scene.infinitelight.file'] = bpy.path.abspath(SLGBP.infinitelight.texture.image.filepath).replace('\\','/')
                    scn['scene.infinitelight.gamma'] = ff(SLGBP.infinitelight.texture.slg_gamma)
                    portal = [m.name for m in bpy.data.materials if m.use_shadeless]
                    if portal:
                        portalpath = '{}/{}/{}.ply'.format(SLGBP.spath,SLGBP.sname,portal[0].replace('.','_'))
                        if os.path.exists(portalpath):
                            scn['scene.infinitelight.file'] += '|'+portalpath
                        if len(portal) > 1:
                            SLGBP.warn = 'More than one portal material (Shadeless) defined!'
                            print('WARNING: ' + SLGBP.warn)
            if scene.world.light_settings.use_environment_light:
                wle = scene.world.light_settings.environment_energy
            else:
                wle = 1.0
            scn['scene.infinitelight.gain'] = '{} {} {}'.format(ff(SLGBP.infinitelight.texture.factor_red*wle),ff(SLGBP.infinitelight.texture.factor_green*wle),ff(SLGBP.infinitelight.texture.factor_blue*wle))
            scn['scene.infinitelight.shift'] = '{} {}'.format(ff(SLGBP.infinitelight.offset.x),ff(SLGBP.infinitelight.offset.y))
            if scene.slg.infinitelightbf:
                scn['scene.infinitelight.usebruteforce'] = '1'

        # Sun lamp
        if SLGBP.sun:
            # We only support one visible sun lamp
            sundir = SLGBP.sun.matrix_world.to_3x3() * Vector((0,0,1))
            sky = SLGBP.sun.data.sky
            # If envmap is also defined, only sun component is exported
            if not SLGBP.infinitelight and sky.use_atmosphere:
                scn['scene.skylight.dir'] = '{} {} {}'.format(ff(sundir.x),ff(sundir.y),ff(sundir.z))
                scn['scene.skylight.turbidity'] = ff(sky.atmosphere_turbidity)
                scn['scene.skylight.gain'] = '{} {} {}'.format(ff(sky.sun_intensity),ff(sky.sun_intensity),ff(sky.sun_intensity))
            if sky.use_sky:
                scn['scene.sunlight.dir'] = '{} {} {}'.format(ff(sundir.x),ff(sundir.y),ff(sundir.z))
                scn['scene.sunlight.turbidity'] = ff(sky.atmosphere_turbidity)
                scn['scene.sunlight.relsize'] = ff(sky.sun_size)
                scn['scene.sunlight.gain'] = '{} {} {}'.format(ff(sky.sun_brightness),ff(sky.sun_brightness),ff(sky.sun_brightness))

        return scn

    # Get SLG .scn mat properties
    @staticmethod
    def getmatscn(scene, m):
        scn = {}
        matn = m.name.replace('.','_')
        matprop = 'scene.materials.{}'.format(matn)       
        if m.use_shadeless:
            matprop = None
        elif m.emit:
            scn['{}.type'.format(matprop)] = 'matte'            
            scn['{}.emission'.format(matprop)] = '{} {} {}'.format(ff(m.emit*m.diffuse_color[0]),ff(m.emit*m.diffuse_color[1]),ff(m.emit*m.diffuse_color[2]))            
        elif m.translucency > 0:
            scn['{}.type'.format(matprop)] = 'mattetranslucent'            
            scn['{}.kr'.format(matprop)] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))
            scn['{}.kt'.format(matprop)] = '{} {} {}'.format(ff(m.mirror_color[0]),ff(m.mirror_color[1]),ff(m.mirror_color[2]))
            if m.diffuse_color.v+m.mirror_color.v > 1.0001:
                SLGBP.warn = m.name + ': transluted + reflected color greater than 1.0!'
                print('WARNING: ' + SLGBP.warn)
        elif m.use_transparency:
            if m.raytrace_transparency.ior == 1.0:
                scn['{}.type'.format(matprop)] = 'archglass'
                scn['{}.kr'.format(matprop)] = '{} {} {}'.format(ff(m.mirror_color[0]), ff(m.mirror_color[1]), ff(m.mirror_color[2]))
                scn['{}.kt'.format(matprop)] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))
            else:
                scn['{}.type'.format(matprop)] = 'glass'
                scn['{}.kr'.format(matprop)] = '{} {} {}'.format(ff(m.mirror_color[0]),ff(m.mirror_color[1]),ff(m.mirror_color[2]))
                scn['{}.kt'.format(matprop)] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))
                scn['{}.iorinside'.format(matprop)] = '{}'.format(ff(m.raytrace_transparency.ior))
                scn['{}.ioroutside'.format(matprop)] = '1.0'
        elif m.raytrace_mirror.use:
            if m.raytrace_mirror.reflect_factor < 1:
                scn['{}.type'.format(matprop)] = 'glossy2'
                scn['{}.kd'.format(matprop)] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))
                scn['{}.ks'.format(matprop)] = '{} {} {}'.format(ff(m.mirror_color[0]), ff(m.mirror_color[1]), ff(m.mirror_color[2]))
                scn['{}.uroughness'.format(matprop)] = '{}'.format(ff(m.raytrace_mirror.gloss_factor))
                scn['{}.vroughness'.format(matprop)] = '{}'.format(ff(m.raytrace_mirror.gloss_factor))
            else:
                if m.raytrace_mirror.gloss_factor > 0:
                    refltype = 'metal2'
                    scn['{}.n'.format(matprop)] = '{}_fresnelapproxN'.format(matn)
                    scn['{}.k'.format(matprop)] = '{}_fresnelapproxK'.format(matn)
                    scn['scene.textures.{}_fresnelapproxN.type'.format(matn)] = 'fresnelapproxn'
                    scn['scene.textures.{}_fresnelapproxN.texture'.format(matn)] = '{} {} {}'.format(ff(m.mirror_color[0]), ff(m.mirror_color[1]), ff(m.mirror_color[2]))
                    scn['scene.textures.{}_fresnelapproxK.type'.format(matn)] = 'fresnelapproxk'
                    scn['scene.textures.{}_fresnelapproxK.texture'.format(matn)] = '{} {} {}'.format(ff(m.mirror_color[0]), ff(m.mirror_color[1]), ff(m.mirror_color[2]))
                else:
                    refltype = 'mirror'
                    scn['{}.kr'.format(matprop)] = '{} {} {}'.format(ff(m.mirror_color[0]), ff(m.mirror_color[1]), ff(m.mirror_color[2]))                
                    scn['{}.kd'.format(matprop)] = '{} {} {}'.format(ff(m.diffuse_color[0]), ff(m.diffuse_color[1]), ff(m.diffuse_color[2]))
                            
                scn['{}.type'.format(matprop)] = refltype
                scn['{}.uroughness'.format(matprop)] = '{}'.format(ff(m.raytrace_mirror.gloss_factor))
                scn['{}.vroughness'.format(matprop)] = '{}'.format(ff(m.raytrace_mirror.gloss_factor))                        
        else:
            scn['{}.type'.format(matprop)] = 'matte'            
            scn['{}.kd'.format(matprop)] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))

        if scene.slg.vuvs:
            texmap = next((ts for ts in m.texture_slots if ts and ts.use_map_color_diffuse and hasattr(ts.texture,'image') and hasattr(ts.texture.image,'filepath') and ts.use), None)
            if texmap:                        
                texn = texmap.name.replace('.','_')
                texprop = 'scene.textures.{}'.format(texn)
                scn['{}.file'.format(texprop)] = bpy.path.abspath(texmap.texture.image.filepath).replace('\\','/')
                scn['{}.gamma'.format(texprop)] = ff(texmap.texture.slg_gamma)
                scn['{}.mapping.uvscale'.format(texprop)] = '{} {}'.format(ff(1.), ff(1.))
                scn['{}.kd'.format(matprop)] = texn
            texspect = next((ts for ts in m.texture_slots if ts and ts.use_map_color_spec and hasattr(ts.texture,'image') and hasattr(ts.texture.image,'filepath') and ts.use), None)
            if texspect:
                texspectn = texspect.name.replace('.','_')
                texspectprop = 'scene.textures.{}'.format(texspectn)
                scn['{}.file'.format(texspectprop)] = bpy.path.abspath(texspect.texture.image.filepath).replace('\\','/')
                scn['{}.gamma'.format(texspectprop)] = ff(texspect.texture.slg_gamma)
                scn['{}.mapping.uvscale'.format(texspectprop)] = '{} {}'.format(ff(1.), ff(1.))
                scn['{}.ks'.format(matprop)] = texspectn                
            texbump = next((ts for ts in m.texture_slots if ts and ts.use_map_normal and hasattr(ts.texture,'image') and hasattr(ts.texture.image,'filepath') and ts.use), None)
            if texbump:
                texbumpn = texbump.name.replace('.','_')                
                texbumpprop = 'scene.textures.{}'.format(texbumpn)                
                if texbump.texture.use_normal_map:                    
                    scn['{}.normaltex'.format(matprop)] = texbumpn
                else:                    
                    scaledn = 'scaled_{}'.format(texbumpn)
                    scaledprop = 'scene.textures.{}'.format(scaledn)
                    scn['{}.type'.format(scaledprop)] = 'scale'
                    scn['{}.texture1'.format(scaledprop)] = ff(texbump.normal_factor)
                    scn['{}.texture2'.format(scaledprop)] = texbumpn
                    scn['{}.bumptex'.format(matprop)] = scaledn                                                          
                scn['{}.file'.format(texbumpprop)] = bpy.path.abspath(texbump.texture.image.filepath).replace('\\','/')
                scn['{}.gamma'.format(texbumpprop)] = ff(texbump.texture.slg_gamma)
                scn['{}.mapping.uvscale'.format(texbumpprop)] = '{} {}'.format(ff(1.), ff(1.))
            
        matprop = ''
        for p in scn:            
            if matn in p:
                if 'type' in p:
                    matprop = p+' = '+scn[p]+','+matprop                    
                else:
                    matprop = matprop+p+' = '+scn[p]+','
        matprop = matprop[:-1]

        return matprop, scn

    # Get SLG .scn obj properties
    @staticmethod
    def getobjscn(scene):
        scn = {}
        def objscn(plyn, matn, objn, mat, tm):
            if tm:
                if bpy.app.version >= (2, 62, 0 ) : # matrix change between 2.61 and 2.62
                    scn['scene.objects.{}.transformation'.format(objn)] = '{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}'.format(
                        ff(tm[0][0]),ff(tm[1][0]),ff(tm[2][0]),ff(tm[3][0]),ff(tm[0][1]),ff(tm[1][1]),ff(tm[2][1]),ff(tm[3][1]),
                        ff(tm[0][2]),ff(tm[1][2]),ff(tm[2][2]),ff(tm[3][2]),ff(tm[0][3]),ff(tm[1][3]),ff(tm[2][3]),ff(tm[3][3]))
                else:
                    scn['scene.objects.{}.transformation'.format(objn)] = '{} {} {} {} {} {} {} {} {} {} {} {} {} {} {} {}'.format(
                        ff(tm[0][0]),ff(tm[0][1]),ff(tm[0][2]),ff(tm[0][3]),ff(tm[1][0]),ff(tm[1][1]),ff(tm[1][2]),ff(tm[1][3]),
                        ff(tm[2][0]),ff(tm[2][1]),ff(tm[2][2]),ff(tm[2][3]),ff(tm[3][0]),ff(tm[3][1]),ff(tm[3][2]),ff(tm[3][3]))

            if not SLGBP.live:
                scn['scene.objects.{}.material'.format(objn)] = '{}'.format(matn)
                scn['scene.objects.{}.ply'.format(objn)] = '{}/{}/{}.ply'.format(SLGBP.spath,SLGBP.sname,plyn)
                if scene.slg.vnormals:
                    scn['scene.objects.{}.useplynormals'.format(objn)] = '1'

        for ply in SLGBP.plys:
            plyn = ply.replace('.','_')
            slgos = SLGBP.plys[ply]
            if type(slgos) != tuple:
                # Non-instanced object meshes grouped by material
                if not SLGBP.live:
                    objscn(plyn, plyn, plyn, slgos, None)
            else:
                # Instance object
                so, dms = slgos
                for do, mat in dms:
                    matn = mat.name.replace('.','_') if mat else SLGBP.nomatn
                    if type(do) == bpy.types.ParticleSystem:
                        if do.settings.render_type == 'GROUP' and not do.settings.use_whole_group:
                            gn = next((i for i,o in enumerate(do.settings.dupli_group.objects) if o == so), 0)
                            ndgos = len(do.settings.dupli_group.objects)
                        for i,p in enumerate(do.particles):
                            if do.settings.render_type == 'GROUP' and not do.settings.use_whole_group:
                                if gn != i % ndgos:
                                    continue
                            if (p.alive_state == 'ALIVE' or do.settings.show_unborn or do.settings.use_dead) and p.is_exist and (do.point_cache.is_baked or p.is_visible):
                                objn = do.id_data.name.replace('.','_')+'{P}'+str(i)+plyn
                                if isnan(p.rotation[0]): # Deal with Blender bug...
                                    rm = Matrix()
                                else:
                                    rm = p.rotation.to_matrix().to_4x4()
                                if do.settings.use_whole_group:
                                    tm = Matrix.Translation(p.location) * rm * so.matrix_world * Matrix.Scale(p.size,4)
                                elif do.settings.use_global_dupli:
                                    tm = Matrix.Translation(so.matrix_world.to_translation()) * Matrix.Translation(p.location) * rm * so.matrix_world.to_3x3().to_4x4() * Matrix.Scale(p.size,4)
                                else:
                                    tm = Matrix.Translation(p.location) * rm * so.matrix_world.to_3x3().to_4x4() * Matrix.Scale(p.size,4)
                                objscn(plyn, matn, objn, mat, tm)
                    else:
                        objn = do.name.replace('.','_')+plyn
                        if type(so) == bpy.types.Mesh:
                            tm = do.matrix_world
                        else:
                            if do.data == so.data:
                                # Used by both instances and dupligroups at the same time
                                tm = do.matrix_world
                            else:
                                tm = do.matrix_world * so.matrix_world
                        objscn(plyn, matn, objn, mat, tm)
        return scn

    # Export obj/mat .ply files
    @staticmethod
    def expmatply(scene):
        print('SLGBP ===> Begin export')
        # Consider all used materials
        plys = dict([(m.name, m) for m in bpy.data.materials if m.users])
        plymats = [p for p in plys]
        nomat = len(plymats)
        plymats.append(SLGBP.nomatn)
        plys[SLGBP.nomatn] = None
        curmat = nomat
        instobjs = {}
        objs = []
        rendertypes = ['MESH', 'SURFACE', 'META', 'TEXT', 'CURVE']
        inscenelayer = lambda o:scene.layers[next((i for i in range(len(o.layers)) if o.layers[i]))]
        # Get materials with force ply flag
        mfp = [m.name for m in bpy.data.materials if m.slg_forceply]

        # Add Dupli Object to ply
        def dupliobj(so, do): # source object, duplicate object
            if so in instobjs:
                if so.data.materials:
                    for i in range(len(so.data.materials)):
                        plys[(so.name+'{D}S'+str(i))][1].append((do, so.material_slots[i].material))
                else:
                    plys[(so.name+'{D}'+SLGBP.nomatn)][1].append((do, None))
            elif not so.hide_render and so.type in rendertypes:
                addo = False
                if so not in objs:
                    if scene.slg.export or mfp and any(m for m in mfp if m in so.material_slots):
                        objs.append(so)
                if inscenelayer(so):
                    addo = True
                instobjs[so] = len(plymats)
                if so.data.materials:
                    for i in range(len(so.data.materials)):
                        plyn = (so.name+'{D}S'+str(i))
                        plymats.append(plyn)
                        plys[plyn] = (so,[(do, so.material_slots[i].material)])
                        if addo:
                            plys[plyn][1].append((so, so.material_slots[i].material))
                else:
                    plyn = (so.name+'{D}'+SLGBP.nomatn)
                    plymats.append(plyn)
                    plys[plyn] = (so,[(do, None)])
                    if addo:
                        plys[plyn][1].append((so, None))

        # Objects to render
        for obj in scene.objects:
            if not obj.hide_render:
                if obj.type in rendertypes and inscenelayer(obj) and not (obj.particle_systems and not any(ps for ps in obj.particle_systems if ps.settings.use_render_emitter)):
                    if scene.slg.forceobjplys and not any(m for m in obj.material_slots if m.material.use_shadeless):
                        # Force export of one PLY file for every object and material slot combination (Treat everything as SLG "Instances")
                        dupliobj(obj, obj)
                    # Mesh instances
                    elif obj.slg_forceinst or (obj.data.users > 1 and not obj.modifiers):
                        if obj.data in instobjs:
                            if obj.data.materials:
                                for i in range(len(obj.data.materials)):
                                    plys[(obj.data.name+'S'+str(i))][1].append((obj, obj.material_slots[i].material))
                            else:
                                plys[(obj.data.name+SLGBP.nomatn)][1].append((obj, None))
                        else:
                            objs.append(obj.data)
                            instobjs[obj.data] = len(plymats)
                            if obj.data.materials:
                                for i in range(len(obj.data.materials)):
                                    plyn = (obj.data.name+'S'+str(i))
                                    plymats.append(plyn)
                                    plys[plyn] = (obj.data,[(obj, obj.material_slots[i].material)])
                            else:
                                plyn = (obj.data.name+SLGBP.nomatn)
                                plymats.append(plyn)
                                plys[plyn] = (obj.data,[(obj, None)])
                    else:
                        # Collect all non-instance meshes by material
                        if scene.slg.export or mfp and any(m for m in mfp if m in obj.material_slots):
                            objs.append(obj)
                # Dupligroups
                if obj.dupli_type == 'GROUP' and inscenelayer(obj):
                    for so in obj.dupli_group.objects:
                        dupliobj(so, obj)
                # Particle Systems
                for ps in obj.particle_systems:
                    if ps.settings.type == 'EMITTER':
                        # Dupli Object
                        if ps.settings.render_type == 'OBJECT' and ps.settings.dupli_object:
                            # Blender 2.5 Python bug!: incorrect dupli_object returned when multiple particle systems
                            dupliobj(ps.settings.dupli_object, ps)
                        # Dupli Group
                        if ps.settings.render_type == 'GROUP' and ps.settings.dupli_group:
                            for o in ps.settings.dupli_group.objects:
                                dupliobj(o, ps)

        verts = [[] for i in range(len(plymats))]
        mvc = [False]*len(plymats)
        vert_uvs = [[] for i in range(len(plymats))]
        faces = [[] for i in range(len(plymats))]
        sharedverts = {}
        vertnum = [0]*len(plymats)
        color = [0,0,0]
        uvco = [0,0]
        addv = False
        # Only get mesh data if PLY export or a material has "force ply"
        if scene.slg.export or mfp:
            # Delete existing ply files
            if scene.slg.export and os.path.exists(SLGBP.sfullpath):
                any(os.remove('{}/{}'.format(SLGBP.sfullpath,file)) for file in os.listdir(SLGBP.sfullpath) if file.endswith('.ply'))
            for obj in objs:
                print('SLGBP ===> Object: {}'.format(obj.name))
                # Create render mesh
                try:
                    if type(obj) == bpy.types.Object:
                        print("SLGBP        Create render mesh: {}".format(obj.name))
                        mesh = obj.to_mesh(scene, True, 'RENDER')
                    else:
                        print("SLGBP        Using render mesh: {}".format(obj.name))
                        mesh = obj
                        if bpy.app.version > (2, 62, 0 ) and not mesh.tessfaces: # bmesh adaption
                            mesh.update(calc_tessface=True)
                except:
                    pass
                else:
                    if type(obj) == bpy.types.Object and obj not in instobjs:
                        print("SLGBP        Xform render mesh: {}".format(obj.name))
                        mesh.transform(obj.matrix_world)
                        mesh.update(calc_tessface=True)
                    # Make copy of verts for fast direct index access (mesh.vertices was very slow)
                    if scene.slg.vnormals:
                        v = [vert.co[:] + vert.normal[:] for vert in mesh.vertices]
                    else:
                        v = [vert.co[:] for vert in mesh.vertices]
                    uvd = []
                    if bpy.app.version > (2, 62, 0 ): # bmesh adaption
                        if scene.slg.vuvs and mesh.tessface_uv_textures.active:
                            uvd = mesh.tessface_uv_textures.active.data
                    else:
                        if scene.slg.vuvs and mesh.uv_textures.active:
                            uvd = mesh.uv_textures.active.data

                    if obj in instobjs:
                        # Correlate obj mat slots with plymat slots
                        onomat = instobjs[obj]
                        objmats = [i for i in range(onomat, onomat+len(mesh.materials))]
                    else:
                        # Correlate obj mat slots with global mat pool
                        onomat = nomat
                        objmats = [plymats.index(ms.material.name) if ms.material else onomat for ms in obj.material_slots]
                    tessfaces = mesh.tessfaces if bpy.app.version > (2, 62, 1 ) else mesh.faces # bmesh
                    for face, uv in zip_longest(tessfaces,uvd):
                        curmat = objmats[face.material_index] if objmats else onomat
                        # Get uvs if there is an image texture attached
                        if uv:
                            uvcos = uv.uv1, uv.uv2, uv.uv3, uv.uv4
                        if not face.use_smooth or uv:
                            faces[curmat].append((vertnum[curmat], vertnum[curmat]+1, vertnum[curmat]+2))
                            if len(face.vertices) == 4:
                                faces[curmat].append((vertnum[curmat], vertnum[curmat]+2, vertnum[curmat]+3))
                        for j, vert in enumerate(face.vertices):
                            if scene.slg.vuvs:
                                if uv:
                                    uvco[0] = uvcos[j][0]
                                    uvco[1] = 1.0 - uvcos[j][1]
                                else:
                                    uvco[0] = uvco[1] = 0
                            if face.use_smooth and not uv:
                                if (curmat,vert) in sharedverts:
                                    addv = False
                                else:
                                    sharedverts[curmat,vert]=vertnum[curmat]
                                    addv = True
                            else:
                                addv = True
                            if addv:
                                if scene.slg.vnormals and not face.use_smooth:
                                    verts[curmat].append(v[vert][:3]+face.normal[:])
                                else:
                                    verts[curmat].append(v[vert])
                                if scene.slg.vuvs:
                                    vert_uvs[curmat].append(tuple(uvco))
                                vertnum[curmat] += 1
                        if face.use_smooth and not uv:
                            faces[curmat].append((sharedverts[curmat,face.vertices[0]], sharedverts[curmat,face.vertices[1]], sharedverts[curmat,face.vertices[2]]))
                            if len(face.vertices) == 4:
                                faces[curmat].append((sharedverts[curmat,face.vertices[0]], sharedverts[curmat,face.vertices[2]], sharedverts[curmat,face.vertices[3]]))
                sharedverts = {}
                # Delete working mesh
                if not mesh.users:
                    print("SLGBP        delete render mesh: {}".format(obj.name))
                    bpy.data.meshes.remove(mesh)
                if SLGBP.abort:
                    return
        # Process each plymat
        for i, pm in enumerate(plymats):
            plyn = pm.replace('.','_')
            # If have mesh using this plymat...
            if verts[i] or (not scene.slg.export and os.path.exists('{}/{}.ply'.format(SLGBP.sfullpath,plyn))):
                if scene.slg.export or pm in mfp:
                    # Write out PLY
                    print("SLGBP        writing PLY: {}".format(plyn))
                    fply = open('{}/{}.ply'.format(SLGBP.sfullpath,plyn), 'wb')
                    fply.write(b'ply\n')
                    #fply.write(b'format ascii 1.0\n')
                    fply.write(b'format binary_little_endian 1.0\n')
                    fply.write(str.encode('comment Created by SmallLuxGPU exporter for Blender 2.5, source file: {}\n'.format((bpy.data.filepath.split('/')[-1].split('\\')[-1]))))
                    fply.write(str.encode('element vertex {}\n'.format(vertnum[i])))
                    fply.write(b'property float x\n')
                    fply.write(b'property float y\n')
                    fply.write(b'property float z\n')
                    if scene.slg.vnormals:
                        fply.write(b'property float nx\n')
                        fply.write(b'property float ny\n')
                        fply.write(b'property float nz\n')
                    if scene.slg.vuvs:
                        fply.write(b'property float s\n')
                        fply.write(b'property float t\n')
                    fply.write(str.encode('element face {}\n'.format(len(faces[i]))))
                    fply.write(b'property list uchar uint vertex_indices\n')
                    fply.write(b'end_header\n')
                    # Write out vertices
                    for j, v in enumerate(verts[i]):
                        if scene.slg.vnormals:
                            #fply.write(str.encode('{:.6f} {:.6f} {:.6f} {:.6g} {:.6g} {:.6g}'.format(*v)))
                            fply.write(pack('<6f', *v))
                        else:
                            #fply.write(str.encode('{:.6f} {:.6f} {:.6f}'.format(*v)))
                            fply.write(pack('<3f', *v))
                        if scene.slg.vuvs:
                            #fply.write(str.encode(' {:.6g} {:.6g}'.format(*vert_uvs[i][j])))
                            fply.write(pack('<2f', *vert_uvs[i][j]))
                        #fply.write(b'\n')
                    # Write out faces
                    for f in faces[i]:
                        #fply.write(str.encode('3 {} {} {}\n'.format(*f)))
                        fply.write(pack('<B3I', 3, *f))
                    fply.close()
            elif pm in plys:
                del plys[pm]
            if SLGBP.abort:
                return
        SLGBP.plys = plys
        SLGBP.instobjs = instobjs

    # Export SLG scene
    @staticmethod
    def export(scene):
        with SLGBP.lock:
            SLGBP.cfg = SLGBP.getcfg(scene)
            fcfg = open('{}/{}/render.cfg'.format(SLGBP.spath,SLGBP.sname), 'w')
            for k in sorted(SLGBP.cfg):
                fcfg.write(k + ' = ' + SLGBP.cfg[k] + '\n')
            fcfg.close()
            SLGBP.expmatply(scene)
            SLGBP.scn = SLGBP.getscn(scene)
            SLGBP.matprops = {}
            for m in bpy.data.materials:
                if m.users:
                    matprop, scn = SLGBP.getmatscn(scene, m)
                    SLGBP.matprops[m.name] = matprop
                    SLGBP.scn.update(scn)
            SLGBP.scn['scene.materials.'+SLGBP.nomatn+'.type'] = 'matte'
            SLGBP.scn['scene.materials.'+SLGBP.nomatn+'.kd'] = '0.75 0.75 0.75'
            SLGBP.scn.update(SLGBP.getobjscn(scene))
            fscn = open('{}/{}/{}.scn'.format(SLGBP.spath,SLGBP.sname,SLGBP.sname), 'w')
            for k in sorted(SLGBP.scn):
                fscn.write(k + ' = ' + SLGBP.scn[k] + '\n')
            fscn.close()

    # Run SLG executable with current scene
    @staticmethod
    def runslg(scene):
        if scene.slg.enabletelnet == True:
            print('SLGBP ===> launch SLG: {} -T {}/{}/render.cfg'.format(SLGBP.slgpath,SLGBP.spath,SLGBP.sname))
            SLGBP.slgproc = Popen([SLGBP.slgpath,'-T','{}/{}/render.cfg'.format(SLGBP.spath,SLGBP.sname)], cwd=SLGBP.spath, shell=False)
        else:
            print('SLGBP ===> launch SLG: {} {}/{}/render.cfg'.format(SLGBP.slgpath,SLGBP.spath,SLGBP.sname))
            SLGBP.slgproc = Popen([SLGBP.slgpath,'{}/{}/render.cfg'.format(SLGBP.spath,SLGBP.sname)], cwd=SLGBP.spath, shell=False)

    # Export SLG scene and render it
    @staticmethod
    def exportrun(scene, errout):
        sleep(0.25) # Allow time for screen to display last msg
        if not SLGBP.init(scene, errout):
            return
        SLGBP.msg = 'SLG exporting frame: ' + str(scene.frame_current) + " (ESC to abort)"
        SLGBP.msgrefresh()
        SLGBP.export(scene)
        if not SLGBP.abort:
            SLGBP.msg = 'SLG rendering frame: ' + str(scene.frame_current) + " (ESC to abort)"
            SLGBP.msgrefresh()
            SLGBP.runslg(scene)

    # Update SLG parameters via telnet interface
    @staticmethod
    def liveconnect(scene, errout):
        SLGBP.msg = 'Connecting via telnet to SLG... (SLG Live! Mode)'
        SLGBP.msgrefresh()
        SLGBP.telnet = SLGTelnet()
        if SLGBP.telnet.connected:
            SLGBP.live = True
            SLGBP.msg = 'SLG Live! (ESC to exit)'
            SLGBP.msgrefresh()
            SLGBP.telnetecho = scene.slg.telnetecho
            if not SLGBP.telnetecho:
                SLGBP.telnet.send('echocmd.off')
        else:
            SLGBP.msg = 'Unable to connect to SLG via telnet... (SLG Live! Mode)'
            SLGBP.msgrefresh()
            errout("Can't connect.  SLG must be running with telnet interface enabled")

    @staticmethod
    def livetrigger(scene, liveact):
        SLGBP.liveact = SLGBP.liveact|liveact
        if SLGBP.thread == None or not SLGBP.thread.is_alive():
            SLGBP.thread = Thread(target=SLGBP.liveupdate,args=[scene])
            SLGBP.thread.start()

    @staticmethod
    def liveupdate(scene):
        def cmpupd(prop, src, dst, reset, force):
            if not force and prop in dst and src[prop] == dst[prop]:
                return False
            if reset:
                SLGBP.telnet.send('render.stop')
                SLGBP.telnet.send('image.reset')
            SLGBP.telnet.send('set ' + prop + ' = '+ src[prop])
            dst[prop] = src[prop]
            return True
        while True:
            act = SLGBP.liveact
            SLGBP.liveact = 0
            wasreset = False
            if act & SLGBP.LIVECFG > 0:
                if SLGBP.telnetecho != scene.slg.telnetecho:
                    SLGBP.telnetecho = scene.slg.telnetecho
                    SLGBP.telnet.send('echocmd.' + 'on' if SLGBP.telnetecho else 'off')
                cfg = SLGBP.getcfg(scene)
                # Changing film.tonemap.type resets all tonemap params; must re-initialize
                forcetm = cmpupd('film.tonemap.type', cfg, SLGBP.cfg, False, False)
                del cfg['film.tonemap.type']
                for k in cfg:
                    # Tonemapping does not require a film reset
                    if k.startswith('film.tonemap'):
                        cmpupd(k, cfg, SLGBP.cfg, False, forcetm)
                    elif cmpupd(k, cfg, SLGBP.cfg, not wasreset, False):
                        wasreset = True
            if act & SLGBP.LIVESCN > 0:
                with SLGBP.lock:
                    scn = SLGBP.getscn(scene)
                for k in scn:
                    if cmpupd(k, scn, SLGBP.scn, not wasreset, False):
                        wasreset = True
            if act & SLGBP.LIVEMTL > 0:
                wasreset = False
                if SLGBP.livemat:
                    matprop, scn = SLGBP.getmatscn(scene, SLGBP.livemat)                    
                    for p in scn:
                        if 'type' in p:
                            print(p+'  '+scn[p]+' '+SLGBP.scn[p])
                            if scn[p] != SLGBP.scn[p]:
                                #material type changed, update properties in scene desciption
                                for mp in scn:
                                    if SLGBP.livemat.name in p:
                                        del SLGBP.scn[p]
                                SLGBP.scn.update(scn)
                                wasreset = True
                            else:
                                for p in scn:
                                    if scn[p] != SLGBP.scn[p]:
                                        SLGBP.scn.update(scn)
                                        wasreset = True
                                
                    SLGBP.matprops[SLGBP.livemat.name] = matprop
                    SLGBP.livemat = None
                else:
                    scn = {}
                    for m in bpy.data.materials:
                        if m.users:
                            matprop, matscn = SLGBP.getmatscn(scene, m)
                            scn.update(matscn)
                            if matprop != SLGBP.matprops[m.name]:
                                wasreset = True
                                SLGBP.matprops[m.name] = matprop

                if wasreset:
                    SLGBP.telnet.send('render.stop')
                    SLGBP.telnet.send('edit.start')
                    SLGBP.telnet.send('set ' +matprop)
                    SLGBP.telnet.send('edit.stop')                    
            if act & SLGBP.LIVEOBJ > 0:
                scn = SLGBP.getobjscn(scene)
                for k in scn:
                    if cmpupd(k, scn, SLGBP.scn, not wasreset, False):
                        wasreset = True
            if wasreset:
                SLGBP.telnet.send('render.start')

            sleep(0.5) # param?
            if SLGBP.liveact == 0: break

def pre_draw_callback():
    # Prevent simultaneous SLGBP export and View3D update (object matrices sometimes modified during draw, e.g. dupligroups)
    SLGBP.lock.acquire()

# Output informational message to main viewport
def info_callback(context):
    blf.position(0, 75, 30, 0)
    blf.size(0, 14, 72)
    blf.draw(0, SLGBP.msg)
    if SLGBP.lock.locked():
        SLGBP.lock.release()
    if SLGBP.live:
        if SLGBP.slgproc.poll() != None:
            SLGBP.live = False
            SLGBP.abort = True
            return
        if not SLGBP.liveanim:
            # If frame is unchanged check only base SCN and OBJ
            if SLGBP.curframe == context.scene.frame_current:
                SLGBP.livetrigger(context.scene, SLGBP.LIVESCN|SLGBP.LIVEOBJ)
            # Else could be frame skip / playback where anything can change
            else:
                SLGBP.curframe = context.scene.frame_current
                SLGBP.livetrigger(context.scene, SLGBP.LIVEALL)

# SLG Render (without Blender render engine) operator
class SLGRender(bpy.types.Operator):
    bl_idname = "render.slg_render"
    bl_label = "SLG Render"
    bl_description = "SLG export and render without using Blender's render engine"

    animation = bpy.props.BoolProperty(name="Animation", description="Render Animation", default= False)

    def _error(self, msg):
        self._iserror = True
        self.report({'ERROR'}, "SLGBP: " + msg)

    def _reset(self, context):
        if bpy.app.version >= (2, 65, 10):
            bpy.types.SpaceView3D.draw_handler_remove(self._pdcb, 'WINDOW')
            bpy.types.SpaceView3D.draw_handler_remove(self._icb, 'WINDOW')
        else:
            context.region.callback_remove(self._pdcb)
            context.region.callback_remove(self._icb)
        context.area.tag_redraw()
        if self.properties.animation:
            context.window_manager.event_timer_remove(SLGBP.animtimer)
            SLGBP.thread = None
            if SLGBP.slgproc:
                if SLGBP.slgproc.poll() == None:
                    SLGBP.slgproc.terminate()

    def modal(self, context, event):
        if SLGBP.thread:
            if event.type == 'ESC':
                SLGBP.abort = True
            if self._iserror:
                self._reset(context)
                return {'CANCELLED'}
            if SLGBP.abort:
                self._reset(context)
                self.report({'WARNING'}, "SLG export aborted.")
                return {'CANCELLED'}
            if self.properties.animation:
                if event.type == 'TIMER':
                    # Make sure it's our timer! (camera fly mode triggers same TIMER)
                    if time() - SLGBP.animlasttime >= context.scene.slg.batchmodetime:
                        SLGBP.slgproc.wait()
                        if context.scene.frame_current+context.scene.frame_step > context.scene.frame_end:
                            self._reset(context)
                            self.report({'INFO'}, "SLG animation render done.")
                            return {'FINISHED'}
                        else:
                            context.scene.frame_set(context.scene.frame_current+context.scene.frame_step)
                            SLGBP.thread = Thread(target=SLGBP.exportrun,args=[context.scene, self._error])
                            SLGBP.thread.start()
                            return {'PASS_THROUGH'}
                return {'PASS_THROUGH'}
            else:
                if SLGBP.thread.is_alive():
                    return {'PASS_THROUGH'}
                self._reset(context)
                self.report({'INFO'}, "SLG export done.")
                return {'FINISHED'}
        return {'PASS_THROUGH'}

    def invoke(self, context, event):
        if self.properties.animation and not context.scene.slg.enablebatchmode:
            self.report({'ERROR'}, "SLGBP: Enable batch mode for animations")
            return {'CANCELLED'}
        if SLGBP.live:
            self.report({'ERROR'}, "SLGBP: Can't export during SLG Live! mode")
            return {'CANCELLED'}
        if SLGBP.thread:
            if SLGBP.thread.is_alive():
                self.report({'ERROR'}, "SLGBP is busy")
                return {'CANCELLED'}
        if SLGBP.slgproc:
            if SLGBP.slgproc.poll() == None:
                self.report({'ERROR'}, "SLG is already running")
                return {'CANCELLED'}
        if self.properties.animation:
            context.scene.frame_set(context.scene.frame_start)
        elif not SLGBP.init(context.scene, self._error):
            return {'CANCELLED'}
        self._iserror = False
        SLGBP.abort = False
        SLGBP.msg = 'SLG exporting and rendering... (ESC to abort)'
        SLGBP.msgrefresh = context.area.tag_redraw
        context.window_manager.modal_handler_add(self)
        if bpy.app.version >= (2, 65, 10):
            self._icb = bpy.types.SpaceView3D.draw_handler_add(info_callback, (context,), 'WINDOW', 'POST_PIXEL')
            self._pdcb = bpy.types.SpaceView3D.draw_handler_add(pre_draw_callback, (), 'WINDOW', 'PRE_VIEW')
        else:
            self._icb = context.region.callback_add(info_callback, (context,), 'POST_PIXEL')
            self._pdcb = context.region.callback_add(pre_draw_callback, (), 'PRE_VIEW')
        SLGBP.msgrefresh()
        SLGBP.thread = Thread(target=SLGBP.exportrun,args=[context.scene, self._error])
        SLGBP.thread.start()
        if self.properties.animation:
            SLGBP.animlasttime = time()
            SLGBP.animtimer = context.window_manager.event_timer_add(context.scene.slg.batchmodetime+1, context.window)
        return {'RUNNING_MODAL'}

# SLG Live operator
class SLGLive(bpy.types.Operator):
    bl_idname = "render.slg_live"
    bl_label = "SLG Live"
    bl_description = "SLG Live! mode"

    def _error(self, msg):
        self._iserror = True
        self.report({'ERROR'}, "SLGBP: " + msg)

    def modal(self, context, event):
        if self._iserror or SLGBP.abort or event.type == 'ESC':
            if SLGBP.liveanim:
                context.window_manager.event_timer_remove(SLGBP.animtimer)
                SLGBP.liveanim = False
            SLGBP.live = False
            SLGBP.telnet.close()
            if bpy.app.version >= (2, 65, 10):
                bpy.types.SpaceView3D.draw_handler_remove(self._pdcb, 'WINDOW')
                bpy.types.SpaceView3D.draw_handler_remove(self._icb, 'WINDOW')
            else:
                context.region.callback_remove(self._pdcb)
                context.region.callback_remove(self._icb)
            context.area.tag_redraw()
            bpy.context.user_preferences.edit.use_global_undo = self._undo
            return {'FINISHED'}
        if SLGBP.liveanim and event.type == 'TIMER':
            # Make sure it's our timer! (camera fly mode triggers same TIMER)
            if time() - SLGBP.animlasttime >= context.scene.slg.batchmodetime:
                SLGBP.animlasttime = time()
                SLGBP.telnet.send('render.stop')
                SLGBP.telnet.send('image.save')
                if context.scene.frame_current+context.scene.frame_step > context.scene.frame_end:
                    context.window_manager.event_timer_remove(SLGBP.animtimer)
                    SLGBP.liveanim = False
                    SLGBP.msg = 'SLG Live! (ESC to exit)'
                else:
                    context.scene.frame_set(context.scene.frame_current+context.scene.frame_step)
                    SLGBP.livetrigger(context.scene, SLGBP.LIVEALL)
                    SLGBP.msg = 'SLG Live! rendering animation frame: ' + str(context.scene.frame_current) + " (ESC to abort)"
                SLGBP.msgrefresh()

        return {'PASS_THROUGH'}

    @classmethod
    def poll(cls, context):
        return not SLGBP.live

    #def execute(self, context):
    def invoke(self, context, event):
        if SLGBP.thread:
            if SLGBP.thread.is_alive():
                self.report({'ERROR'}, "SLGBP is busy")
                return {'CANCELLED'}
        if SLGBP.slgproc:
            if SLGBP.slgproc.poll() != None:
                self.report({'ERROR'}, "SLG must be running with telnet interface enabled")
                return {'CANCELLED'}
        self._iserror = False
        context.window_manager.modal_handler_add(self)
        # Disable global undo as it corrupts all pointers used by SLG Live! mode
        self._undo = bpy.context.user_preferences.edit.use_global_undo
        bpy.context.user_preferences.edit.use_global_undo = False
        SLGBP.abort = False
        SLGBP.msg = 'SLG Live!'
        SLGBP.msgrefresh = context.area.tag_redraw
        SLGBP.curframe = context.scene.frame_current
        if bpy.app.version >= (2, 65, 10):
            self._icb = bpy.types.SpaceView3D.draw_handler_add(info_callback, (context,), 'WINDOW', 'POST_PIXEL')
            self._pdcb = bpy.types.SpaceView3D.draw_handler_add(pre_draw_callback, (), 'WINDOW', 'PRE_VIEW')
        else:        
            self._icb = context.region.callback_add(info_callback, (context,), 'POST_PIXEL')
            self._pdcb = context.region.callback_add(pre_draw_callback, (), 'PRE_VIEW')
        SLGBP.thread = Thread(target=SLGBP.liveconnect,args=[context.scene, self._error])
        SLGBP.thread.start()
        return {'RUNNING_MODAL'}

# SLG Live update all operator
class SLGLiveUpdate(bpy.types.Operator):
    bl_idname = "render.slg_liveupd"
    bl_label = "SLG Live Update All"
    bl_description = "SLG Live! mode: update all"

    @classmethod
    def poll(cls, context):
        return SLGBP.live

    # def execute(self, context):
    def invoke(self, context, event):
        SLGBP.livetrigger(context.scene, SLGBP.LIVEALL)
        return {'FINISHED'}

# SLG Live animation render operator
class SLGLiveAnim(bpy.types.Operator):
    bl_idname = "render.slg_liveanim"
    bl_label = "SLG Live Animation Render"
    bl_description = "SLG Live! mode: render animation"

    @classmethod
    def poll(cls, context):
        return SLGBP.live

    # def execute(self, context):
    def invoke(self, context, event):
        SLGBP.liveanim = True
        SLGBP.msg = 'SLG Live! rendering animation frame: ' + str(context.scene.frame_start) + " (ESC to abort)"
        context.scene.frame_set(context.scene.frame_start)
        SLGBP.livetrigger(context.scene, SLGBP.LIVEALL)
        SLGBP.animlasttime = time()
        SLGBP.animtimer = context.window_manager.event_timer_add(context.scene.slg.batchmodetime+1, context.window)
        return {'FINISHED'}

class SmallLuxGPURender(bpy.types.RenderEngine):
    bl_idname = 'SLG_RENDER'
    bl_label = "SmallLuxGPU"

    def _error(self, error):
        self.update_stats("-=|ERROR|", error)
        # Hold the render results window while we display the error message
        while True:
            if self.test_break():
                break
            sleep(0.1)

    def render(self, scene):

        if SLGBP.live:
            self._error("SLGBP: Can't render during SLG Live! mode")
            return
        if SLGBP.thread:
            if SLGBP.thread.is_alive():
                self._error("SLGBP is Busy")
                return
        self.update_stats("", "SmallLuxGPU: Exporting scene, please wait...")
        if not SLGBP.init(scene, self._error):
            return
        # Force an update to object matrices when rendering animations
        scene.update()
        SLGBP.export(scene)
        SLGBP.runslg(scene)

        if scene.slg.waitrender:
            # Wait for SLG, load image
            if scene.slg.enablebatchmode:
                self.update_stats("", "SmallLuxGPU: Batch Rendering frame# {} (see console for progress), please wait...".format(scene.frame_current))
            else:
                self.update_stats("", "SmallLuxGPU: Waiting... (in SLG window: press 'p' to save image, 'Esc' to exit)")
            # Hold the render results window hostage until SLG returns...
            while True:
                if SLGBP.slgproc.poll() != None:
                    result = self.begin_result(0, 0, int(SLGBP.cfg['image.width']), int(SLGBP.cfg['image.height']))
                    try:
                        print('SLGBP ===> load image from file: ' + SLGBP.image_filename.format(scene.frame_current))
                        result.layers[0].load_from_file(SLGBP.image_filename.format(scene.frame_current))
                        # Delete SLG's file (Blender can save its own)
                        os.remove(SLGBP.image_filename.format(scene.frame_current))
                    except:
                        pass
                    self.end_result(result)
                    break
                if self.test_break():
                    try:
                        SLGBP.slgproc.terminate()
                    except:
                        pass
                    break
                sleep(0.1)

class SLGSettings(bpy.types.PropertyGroup):
    pass

def slg_add_properties():
    # Add SmallLuxGPU properties to scene (prop. name max length is 31)
    from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty, FloatVectorProperty, CollectionProperty
    bpy.types.Scene.slg = PointerProperty(type=SLGSettings, name="SLG", description="SLG Settings")

    SLGSettings.slgpath = StringProperty(name="SmallLuxGPU Path",
        description="Full path to SmallLuxGPU's executable",
        default="", maxlen=1024, subtype="FILE_PATH")

    SLGSettings.scene_path = StringProperty(name="Export scene path",
        description="Full path to directory where the exported scene is created",
        default="", maxlen=1024, subtype="DIR_PATH")

    SLGSettings.scenename = StringProperty(name="Scene Name",
        description="Name of SmallLuxGPU scene to create",
        default="testscene", maxlen=1024)

    SLGSettings.export = BoolProperty(name="PLY",
        description="Export PLY (mesh data) files (uncheck only if scene has already been exported)",
        default=True)

    SLGSettings.vuvs = BoolProperty(name="UVs",
        description="Export optional vertex uv information (only if assigned)",
        default=True)

    SLGSettings.vnormals = BoolProperty(name="VNs",
        description="Export optional vertex normal information",
        default=True)

    SLGSettings.forceobjplys = BoolProperty(name="Instance all Objects",
        description="Create PLYs/transforms for every object/material pair - SLG Live! mode (may cause issues with MQBVH)",
        default=False)

    SLGSettings.infinitelightbf = BoolProperty(name="InfiniteLight BF",
        description="Enable brute force rendering for InifinteLight light source",
        default=False)

    SLGSettings.refreshrate = IntProperty(name="Screen Refresh Interval",
        description="How often, in milliseconds, the screen refreshes",
        default=250, min=1, soft_min=1)

    SLGSettings.rendering_type = EnumProperty(name="Rendering Type",
        description="Select the desired rendering type",
        items=(("PATHOCL", "PathOCL", "Path tracing using OpenCL only"),
               ("LIGHTCPU", "LightCPU", "Light tracing using CPU only"),
               ("PATHCPU", "PathCPU", "Path tracing using CPU only"),
               ("BIDIRCPU", "BiDirCPU", "Bidirectional path tracing using CPU only"),
               ("BIDIRHYBRID", "BiDirHybrid", "Bidirectional path tracing using CPU and OpenCL"),
               ("CBIDIRHYBRID", "CBiDirHybrid", "Combinatorial bidirectional path tracing using CPU and OpenCL"),
               ("BIDIRVMCPU", "BiDirVMCPU", "Bidirectional path tracing with Vertex Merging using CPU only"),
               ("RTPATHOCL", "RTPathOCL", "Low Latency 'Realtime' Path tracing using OpenCL only")),
        default="PATHOCL")

    SLGSettings.sampler_type = EnumProperty(name="Sampler Type",
        description="Sampler Type",
        items=(("INLINED_RANDOM", "Random", "Random"),
               ("SOBOL", "Sobol", "Sobol"),
               ("METROPOLIS", "Metropolis", "Metropolis")),
        default="METROPOLIS")

    SLGSettings.ocl_filter_type = EnumProperty(name="OpenCL Filter Type",
        description="OpenCL Filter Type",
        items=(("NONE", "None", "None"),
               ("BOX", "Box", "Box"),
               ("GAUSSIAN", "Gaussian", "Gaussian"),
               ("MITCHELL", "Mitchell", "Mitchell")),
        default="NONE")

    SLGSettings.pixelatomics_enable = BoolProperty(name="Use Pixel Atomics",
        description="Use Pixel Atomics",
        default=False)

    SLGSettings.sampler_largesteprate = FloatProperty(name="Large Step Rate",
        description="Large Step Rate",
        default=0.4, min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, precision=3)

    SLGSettings.sampler_maxconsecutivereject = IntProperty(name="Max Consecutive Reject",
        description="Max Consecutive Reject",
        default=512, min=1)

    SLGSettings.sampler_imagemutationrate = FloatProperty(name="Image Mutation Rate",
        description="Image Mutation Rate",
        default=0.1, min=0.0001, max=1.0)

    SLGSettings.filter_width_x = FloatProperty(name="Filter Width X",
        description="Filter width x",
        default=1.5, min=0.0, max=1.5, soft_min=0.0, soft_max=1.5, precision=3)

    SLGSettings.filter_width_y = FloatProperty(name="Filter Width Y",
        description="Filter width y",
        default=1.5, min=0.0, max=1.5, soft_min=0.0, soft_max=1.5, precision=3)

    SLGSettings.filter_alpha = FloatProperty(name="Filter Alpha",
        description="Filter alpha",
        default=2.0, min=0.0, max=10, soft_min=0.0, soft_max=10, precision=3)

    SLGSettings.filter_B = FloatProperty(name="Filter B",
        description="Filter B",
        default=0.333333, min=0.0, max=10, soft_min=0.0, soft_max=10, precision=3)

    SLGSettings.filter_C = FloatProperty(name="Filter C",
        description="Filter C",
        default=0.333333, min=0.0, max=10, soft_min=0.0, soft_max=10, precision=3)

    SLGSettings.opencl_task_count = IntProperty(name="OpenCL Task Count",
        description="OpenCL Task Count: Higher values can lead to better performance but consumes more memory",
        default=131072, min=1, soft_min=1)

    SLGSettings.accelerator_type = EnumProperty(name="Accelerator Type",
        description="Select the desired ray tracing accelerator type",
        items=(("-1", "Default", "Default"),
               ("0", "BVH", "Bounding Volume Hierarchy"),
               ("1", "QBVH", "Quad-Bounding Volume Hierarchy"),
               ("2", "QBVH (image storage disabled)", "Quad-Bounding Volume Hierarchy with disabled image storage"),
               ("3", "MQBVH (instances support)", "Multilevel Quad-Bounding Volume Hierarchy")),
        default="-1")

    SLGSettings.film_filter_type = EnumProperty(name="Film Filter Type",
        description="Select the desired film filter type",
        items=(("NONE", "None", "No filter"),
               ("BOX", "Box", "Box filter"),
               ("GAUSSIAN", "Gaussian", "Gaussian filter"),
               ("MITCHELL", "Mitchell", "Mitchell"),
               ("MITCHELL_SS", "Mitchell SS", "Mitchell with supersampling")),
        default="GAUSSIAN")

    SLGSettings.film_tonemap_type = EnumProperty(name="Tonemap Type",
        description="Select the desired film tonemap type",
        items=(("0", "Linear tonemapping", "Linear tonemapping"),
               ("1", "Reinhard '02 tonemapping", "Reinhard '02 tonemapping")),
        default="0")

    SLGSettings.linear_scale = FloatProperty(name="Scale",
        description="Linear tonemapping scale",
        default=1.0, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    SLGSettings.reinhard_prescale = FloatProperty(name="Pre-scale",
        description="Reinhard '02 tonemapping pre-scale",
        default=1.0, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    SLGSettings.reinhard_postscale = FloatProperty(name="Post-scale",
        description="Reinhard '02 tonemapping post-scale",
        default=1.2, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    SLGSettings.reinhard_burn = FloatProperty(name="Burn",
        description="Reinhard '02 tonemapping burn",
        default=3.75, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    SLGSettings.film_gamma = FloatProperty(name="Gamma",
        description="Gamma correction on screen and for saving non-HDR file format",
        default=2.2, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    SLGSettings.alphachannel = BoolProperty(name="Alpha Background",
        description="Render background as transparent alpha channel in image file",
        default=False)

    SLGSettings.rrdepth = IntProperty(name="Russian Roulette Depth",
        description="Russian roulette depth",
        default=5, min=1, max=1024, soft_min=1, soft_max=1024)

    SLGSettings.rrcap = FloatProperty(name="Russian Roulette Importance Cap",
        description="Russian roulette importance cap",
        default=0.25, min=0.01, max=0.99, soft_min=0.1, soft_max=0.9, precision=3)

    SLGSettings.enablebatchmode = BoolProperty(name="Batch Mode",
        description="Render in background (required for animations)",
        default=False)

    SLGSettings.batchmodetime = IntProperty(name="Batch mode max run time",
        description="Max number of seconds to render; 0 = ignore",
        default=120, min=0, soft_min=0)

    SLGSettings.batchmodespp = IntProperty(name="Batch mode max samples per pixel",
        description="Max number of samples per pixels in batch mode; 0 = ignore",
        default=128, min=0, soft_min=0)

    SLGSettings.batchmode_periodicsave = IntProperty(name="Periodic save interval",
        description="Save image periodically (in seconds); 0 = ignore",
        default=0, min=0, soft_min=0)

    SLGSettings.waitrender = BoolProperty(name="Wait",
        description="Wait for render to finish; load image into render results (required for animations)",
        default=False)

    SLGSettings.enabletelnet = BoolProperty(name="Telnet",
        description="Enable SLG's telnet interface to allow use of SLG Live! mode",
        default=False)

    SLGSettings.telnetecho = BoolProperty(name="Echo",
        description="Enable SLG's telnet echo of commands and informational messages (helps problem solve)",
        default=False)

    SLGSettings.opencl_cpu = BoolProperty(name="CPU",
        description="Use OpenCL CPU devices if available",
        default=False)

    SLGSettings.opencl_gpu = BoolProperty(name="GPU",
        description="Use OpenCL GPU devices if available",
        default=True)

    SLGSettings.gpu_workgroup_size = IntProperty(name="GPU Workgroup Size",
        description="Use a value of 0 to use the default value for your GPU",
        default=64, min=0, max=4096, soft_min=0, soft_max=4096)

    SLGSettings.platform = IntProperty(name="OpenCL platform",
        description="OpenCL Platform to use; if you have multiple OpenCL ICDs installed",
        default=-1, min=-1, max=256, soft_min=-1, soft_max=256)

    SLGSettings.devices = StringProperty(name="OpenCL devices to use",
        description="blank = default (bitwise on/off value for each device, see SLG docs)",
        default="", maxlen=64)

    # Shared among all *CPU render engine
    SLGSettings.native_threads_count = IntProperty(name="Number of threads",
        description="Number of threads used for the rendering",
        default=multiprocessing.cpu_count(), min=1)

    # PathCPU/OCL engine options
    SLGSettings.path_maxdepth = IntProperty(name="Max Eye Path Depth",
        description="Maximum eye path tracing depth",
        default=5, min=1, max=1024, soft_min=1, soft_max=1024)

    # LightCPU engine options
    SLGSettings.light_maxdepth = IntProperty(name="Max Light Path Depth",
        description="Maximum light path tracing depth",
        default=5, min=1, max=1024, soft_min=1, soft_max=1024)

    # BiDirVM options
    SLGSettings.bidirvm_lightpath_count = IntProperty(name="Light Path Count",
        description="Vertex Merging light path count",
        default=16000, min=64, max=1000000)
    SLGSettings.bidirvm_startradius_scale = FloatProperty(name="Vertex Merging start radius",
        description="Vertex Merging start radius",
        default=0.003, min=0.000001, max=0.5)
    SLGSettings.bidirvm_alpha = FloatProperty(name="Vertex Merging alpha",
        description="Vertex Merging radius decrease factor",
        default=0.95, min=0.01, max=1.0)

    # CBiDir options
    SLGSettings.cbidir_eyepath_count = IntProperty(name="Combinatorial Eye Path Count",
        description="Combinatorial eye path count",
        default=5, min=1, max=1000)
    SLGSettings.cbidir_lightpath_count = IntProperty(name="Combinatorial Light Path Count",
        description="Combinatorial light path count",
        default=5, min=1, max=1000)

    # Add SLG Camera Lens Radius
    bpy.types.Camera.slg_lensradius = FloatProperty(name="SLG DOF Lens Radius",
        description="SmallLuxGPU camera lens radius for depth of field",
        default=0.015, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    # Add Material PLY export override
    bpy.types.Material.slg_forceply = BoolProperty(name="SLG Force PLY Export",
        description="SmallLuxGPU - Force export of PLY (mesh data) related to this material",
        default=False)

    # Add SLG Image Texture Gamma
    bpy.types.ImageTexture.slg_gamma = FloatProperty(name="SLG Gamma",
        description="SmallLuxGPU gamma input value",
        default=2.2, min=0, max=10, soft_min=0, soft_max=10, precision=3)

    # Add Object Force Instance
    bpy.types.Object.slg_forceinst = BoolProperty(name="SLG Force Instance",
        description="SmallLuxGPU - Force export of instance for this object",
        default=False)

    # Use some of the existing panels
    bl_ui.properties_render.RENDER_PT_render.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_render.RENDER_PT_layers.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_render.RENDER_PT_output.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_render.RENDER_PT_post_processing.COMPAT_ENGINES.add('SLG_RENDER')

    bl_ui.properties_material.MATERIAL_PT_context_material.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_material.MATERIAL_PT_diffuse.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_material.MATERIAL_PT_shading.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_material.MATERIAL_PT_pipeline.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_material.MATERIAL_PT_transp.COMPAT_ENGINES.add('SLG_RENDER')
    bl_ui.properties_material.MATERIAL_PT_mirror.COMPAT_ENGINES.add('SLG_RENDER')

    for member in dir(bl_ui.properties_texture):
        subclass = getattr(bl_ui.properties_texture, member)
        try:
            subclass.COMPAT_ENGINES.add('SLG_RENDER')
        except:
            pass

    for member in dir(bl_ui.properties_data_camera):
        subclass = getattr(bl_ui.properties_data_camera, member)
        try:
            subclass.COMPAT_ENGINES.add('SLG_RENDER')
        except:
            pass

    for member in dir(bl_ui.properties_scene):
        subclass = getattr(bl_ui.properties_scene, member)
        try:
            subclass.COMPAT_ENGINES.add('SLG_RENDER')
        except:
            pass

    bl_ui.properties_world.WORLD_PT_environment_lighting.COMPAT_ENGINES.add('SLG_RENDER')

    bl_ui.properties_data_lamp.DATA_PT_sunsky.COMPAT_ENGINES.add('SLG_RENDER')

    for member in dir(bl_ui.properties_particle):
        subclass = getattr(bl_ui.properties_particle, member)
        try:
            subclass.COMPAT_ENGINES.add('SLG_RENDER')
        except:
            pass

# Add SLG Camera Lens Radius on Camera panel
def slg_lensradius(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        self.layout.split().column().prop(context.camera, "slg_lensradius", text="SLG Lens Radius")
        if SLGBP.live:
            SLGBP.livetrigger(context.scene, SLGBP.LIVESCN)

# Add Material PLY export override on Material panel
def slg_forceply(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        self.layout.split().column().prop(context.material, "slg_forceply")
        if SLGBP.live:
            SLGBP.livemat = context.material
            SLGBP.livetrigger(context.scene, SLGBP.LIVEMTL)

# Add SLG Image Texture Gamma
def slg_gamma(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        self.layout.split().column().prop(context.texture, "slg_gamma")

# Add Object Force Instance on Object panel
def slg_forceinst(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        self.layout.split().column().prop(context.object, "slg_forceinst")

def slg_livescn(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        if SLGBP.live:
            SLGBP.livetrigger(context.scene, SLGBP.LIVESCN)

# Add SLG Operators to View3D toolbar
def slg_operators(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        row = self.layout.row(align=True)
        if SLGBP.live:
            row.operator("render.slg_liveupd", text="", icon='ANIM')
            row.operator("render.slg_liveanim", text="", icon='RENDER_ANIMATION')
        else:
            row.operator("render.slg_render", text="", icon='RENDER_STILL')
            row.operator("render.slg_render", text="", icon='RENDER_ANIMATION').animation = True
        row.operator("render.slg_live", text="", icon='RENDER_RESULT')

# Add (non-render engine) Render to Menu
def slg_rendermenu(self, context):
    if context.scene.render.engine == 'SLG_RENDER':
        self.layout.separator()
        if SLGBP.live:
            self.layout.operator("render.slg_liveupd", text="SLG Live! Update All", icon='ANIM')
            self.layout.operator("render.slg_liveanim", text="SLG Live! Animation Render", icon='RENDER_ANIMATION')
        else:
            self.layout.operator("render.slg_render", text="SmallLuxGPU Export and Render Scene", icon='RENDER_STILL')
            self.layout.operator("render.slg_render", text="SmallLuxGPU Export and Render Animation", icon='RENDER_ANIMATION').animation = True

class RENDER_MT_slg_presets(bpy.types.Menu):
    bl_label = "SLG Presets"
    preset_subdir = "slg"
    preset_operator = "script.execute_preset"
    draw = bpy.types.Menu.draw_preset

class AddPresetSLG(bl_operators.presets.AddPresetBase, bpy.types.Operator):
    '''Add an SLG Preset'''
    bl_idname = "render.slg_preset_add"
    bl_label = "Add SLG Preset"
    preset_menu = "RENDER_MT_slg_presets"

    preset_defines = ["scene = bpy.context.scene"]

    preset_values = [
        "scene.slg.slgpath",
        "scene.slg.scene_path",
        "scene.slg.export",
        "scene.slg.vuvs",
        "scene.slg.vnormals",
        "scene.slg.forceobjplys",
        "scene.slg.infinitelightbf",
        "scene.slg.rendering_type",
        "scene.slg.accelerator_type",
        "scene.slg.film_filter_type",
        "scene.slg.film_tonemap_type",
        "scene.slg.linear_scale",
        "scene.slg.reinhard_burn",
        "scene.slg.reinhard_prescale",
        "scene.slg.reinhard_postscale",
        "scene.slg.alphachannel",
        "scene.slg.film_gamma",
        "scene.slg.path_maxdepth",
        "scene.slg.light_maxdepth",
        "scene.slg.rrdepth",
        "scene.slg.rrcap",
        "scene.slg.refreshrate",
        "scene.slg.enablebatchmode",
        "scene.slg.waitrender",
        "scene.slg.enabletelnet",
        "scene.slg.telnetecho",
        "scene.slg.batchmodetime",
        "scene.slg.batchmodespp",
        "scene.slg.batchmode_periodicsave",
        "scene.slg.opencl_cpu",
        "scene.slg.opencl_gpu",
        "scene.slg.opencl_task_count",
        "scene.slg.gpu_workgroup_size",
        "scene.slg.platform",
        "scene.slg.devices",
        "scene.slg.sampler_type",
        "scene.slg.pixelatomics_enable",
        "scene.slg.sampler_largesteprate",
        "scene.slg.sampler_maxconsecutivereject",
        "scene.slg.sampler_imagemutationrate",
        "scene.slg.ocl_filter_type",
        "scene.slg.filter_width_x",
        "scene.slg.filter_width_y",
        "scene.slg.filter_alpha",
        "scene.slg.filter_B",
        "scene.slg.filter_C",
        "scene.slg.bidirvm_lightpath_count",
        "scene.slg.bidirvm_startradius_scale",
        "scene.slg.bidirvm_alpha",
        "scene.slg.cbidir_eyepath_count",
        "scene.slg.cbidir_lightpath_count",
        "scene.slg.native_threads_count"
    ]
    preset_subdir = "slg"

class RenderButtonsPanel():
    bl_space_type = 'PROPERTIES'
    bl_region_type = 'WINDOW'
    bl_context = "render"

    @classmethod
    def poll(cls, context):
        return context.scene.render.engine in cls.COMPAT_ENGINES

class RENDER_PT_slg_settings(bpy.types.Panel, RenderButtonsPanel):
    bl_label = "SmallLuxGPU Settings"
    COMPAT_ENGINES = {'SLG_RENDER'}

    def draw(self, context):
        layout = self.layout
        slg = context.scene.slg

        row = layout.row(align=True)
        row.menu("RENDER_MT_slg_presets", text=bpy.types.RENDER_MT_slg_presets.bl_label)
        row.operator("render.slg_preset_add", text="", icon="ZOOMIN")
        row.operator("render.slg_preset_add", text="", icon="ZOOMOUT").remove_active = True

        split = layout.split()
        col = split.column()
        col.label(text="Full path to SmallLuxGPU's executable:")
        col.prop(slg, "slgpath", text="")
        col.label(text="Full path where the scene is exported:")
        col.prop(slg, "scene_path", text="")
        col.prop(slg, "scenename", text="Scene Name")
        split = layout.split(percentage=0.33)
        col = split.column()
        col.prop(slg, "export")
        col = split.column()
        col.active = slg.export
        col.prop(slg, "vuvs")
        col = split.column()
        col.active = slg.export
        col.prop(slg, "vnormals")
        split = layout.split()
        col = split.column()
        col.prop(slg, "forceobjplys")
        col = split.column()
        col.prop(slg, "infinitelightbf")

        ########################################################################
        # Rendering engine type
        ########################################################################
        split = layout.split()
        col = split.column()
        col.prop(slg, "rendering_type")

        ########################################################################
        # Rendering engine specific options
        ########################################################################

		# CBIDIRHYBRID
        if slg.rendering_type == 'CBIDIRHYBRID':
            split = layout.split()
            col = split.column()
            col.prop(slg, "cbidir_eyepath_count")
            split = layout.split()
            col = split.column()
            col.prop(slg, "cbidir_lightpath_count")

		# BIDIRVMCPU
        if slg.rendering_type == 'BIDIRVMCPU':
            split = layout.split()
            col = split.column()
            col.prop(slg, "bidirvm_lightpath_count")
            split = layout.split()
            col = split.column()
            col.prop(slg, "bidirvm_startradius_scale")
            split = layout.split()
            col = split.column()
            col.prop(slg, "bidirvm_alpha")

        if slg.rendering_type in ('PATHOCL', 'RTPATHOCL', 'PATHCPU', 'BIDIRCPU', 'BIDIRVMCPU', 'BIDIRHYBRID', 'CBIDIRHYBRID'):
            split = layout.split()
            col = split.column()
            col.prop(slg, "path_maxdepth", text="Eye Path Depth")
        if slg.rendering_type in ('LIGHTCPU', 'BIDIRCPU', 'BIDIRVMCPU', 'BIDIRHYBRID', 'CBIDIRHYBRID'):
            split = layout.split()
            col = split.column()
            col.prop(slg, "light_maxdepth", text="Light Path Depth")

		# All *OCL or Hybrid
        if slg.rendering_type in ('PATHOCL', 'RTPATHOCL', 'BIDIRHYBRID', 'CBIDIRHYBRID'):
            split = layout.split(percentage=0.33)
            col = split.column()
            col.label(text="OpenCL devs:")
            col = split.column()
            col.prop(slg, "opencl_cpu")
            col = split.column()
            col.prop(slg, "opencl_gpu")
            split = layout.split()
            col = split.column()
            col.prop(slg, "gpu_workgroup_size")
            split = layout.split()
            col = split.column()
            col.prop(slg, "platform", text="Platform")
            col = split.column()
            col.prop(slg, "devices", text='Devs')

		# PATHOCL or RTPATHOCL
        if slg.rendering_type in ('PATHOCL', 'RTPATHOCL'):
            col = split.column()
            col.prop(slg, "opencl_task_count")
            split = layout.split()
            col = split.column()
            col.prop(slg, "pixelatomics_enable")

		# All *CPU
        if slg.rendering_type in ('PATHCPU', 'LIGHTCPU', 'BIDIRCPU', 'BIDIRVMCPU'):
            split = layout.split()
            col = split.column()
            col.prop(slg, "native_threads_count")

        ########################################################################
        # Sampler specific options
        ########################################################################

        split = layout.split()
        col = split.column()
        col.prop(slg, "sampler_type")
        if slg.sampler_type == 'METROPOLIS':
            split = layout.split()
            col = split.column()
            col.prop(slg, "sampler_largesteprate")
            col = split.column()
            col.prop(slg, "sampler_maxconsecutivereject")
            col = split.column()
            col.prop(slg, "sampler_imagemutationrate")

        ########################################################################

        split = layout.split()
        col = split.column()
        col.prop(slg, "accelerator_type")
        split = layout.split()
        col = split.column()
        col.prop(slg, "film_filter_type")
        if slg.rendering_type == 'PATHOCL':
            split = layout.split()
            col = split.column()
            col.prop(slg, "ocl_filter_type")
        split = layout.split()
        col = split.column()
        col.prop(slg, "filter_width_x")
        col = split.column()
        col.prop(slg, "filter_width_y")
        if slg.ocl_filter_type == 'GAUSSIAN' or slg.film_filter_type == 'GAUSSIAN':
            split = layout.split()
            col = split.column()
            col.prop(slg, "filter_alpha")
        elif slg.ocl_filter_type == 'MITCHELL' or slg.film_filter_type == 'MITCHELL' or slg.ocl_filter_type == 'MITCHELL_SS' or slg.film_filter_type == 'MITCHELL_SS':
            split = layout.split()
            col = split.column()
            col.prop(slg, "filter_B")
            col = split.column()
            col.prop(slg, "filter_C")
        split = layout.split()
        col = split.column()
        col.prop(slg, "film_tonemap_type")
        col = split.column()
        if slg.film_tonemap_type == '0':
            col.prop(slg, "linear_scale")
        else:
            col.prop(slg, "reinhard_burn")
            split = layout.split()
            col = split.column()
            col.prop(slg, "reinhard_prescale")
            col = split.column()
            col.prop(slg, "reinhard_postscale")
        split = layout.split()
        col = split.column()
        col.prop(slg, "alphachannel")
        col = split.column()
        col.prop(slg, "film_gamma")
        split = layout.split()
        col = split.column()
        col.prop(slg, "rrdepth", text="RR Depth")
        col = split.column()
        col.prop(slg, "rrcap", text="RR Cap")
        split = layout.split()
        col = split.column()
        split = layout.split()
        col = split.column()
        col.prop(slg, "refreshrate", text="Screen refresh rate")
        split = layout.split()
        col = split.column()
        col.prop(slg, "enablebatchmode")
        col = split.column()
        col.prop(slg, "waitrender")
        col = split.column()
        col.prop(slg, "enabletelnet")
        col = split.column()
        col.prop(slg, "telnetecho")
        if slg.enablebatchmode or SLGBP.live:
            split = layout.split()
            col = split.column()
            col.prop(slg, "batchmodetime", text="Seconds")
            col = split.column()
            col.prop(slg, "batchmodespp", text="Samples")
        split = layout.split()
        col = split.column()
        col.prop(slg, "batchmode_periodicsave")
        if SLGBP.live:
            SLGBP.livetrigger(context.scene, SLGBP.LIVECFG)

def register():
    bpy.utils.register_module(__name__)
    slg_add_properties()
    bpy.types.DATA_PT_camera_dof.append(slg_lensradius)
    bpy.types.MATERIAL_PT_diffuse.append(slg_forceply)
    bpy.types.OBJECT_PT_transform.append(slg_forceinst)
    bpy.types.WORLD_PT_environment_lighting.append(slg_livescn)
    bpy.types.TEXTURE_PT_mapping.append(slg_livescn)
    bpy.types.TEXTURE_PT_colors.append(slg_gamma)
    bpy.types.DATA_PT_sunsky.append(slg_livescn)
    bpy.types.VIEW3D_HT_header.append(slg_operators)
    bpy.types.INFO_MT_render.append(slg_rendermenu)

def unregister():
    del bpy.types.Scene.slg
    bpy.types.DATA_PT_camera_dof.remove(slg_lensradius)
    bpy.types.MATERIAL_PT_diffuse.remove(slg_forceply)
    bpy.types.OBJECT_PT_transform.remove(slg_forceinst)
    bpy.types.WORLD_PT_environment_lighting.remove(slg_livescn)
    bpy.types.TEXTURE_PT_mapping.remove(slg_livescn)
    bpy.types.TEXTURE_PT_colors.remove(slg_gamma)
    bpy.types.DATA_PT_sunsky.remove(slg_livescn)
    bpy.types.VIEW3D_HT_header.remove(slg_operators)
    bpy.types.INFO_MT_render.remove(slg_rendermenu)
    bpy.utils.unregister_module(__name__)

if __name__ == "__main__":
    register()
