<commit_before># -*- coding: utf-8 -*-
###########################################################################
#   Copyright (C) 1998-2010 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 v1.8beta1 Blender 2.5 plug-in

bl_info = {
    "name": "SmallLuxGPU",
    "author": "see (SLG) AUTHORS.txt",
    "version": (2, 0, 4),
    "blender": (2, 6, 0),
    "location": "Render > Engine > SmallLuxGPU",
    "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 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',b'ERROR'],2)
            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

        # Get motion blur parameters
        if scene.slg.cameramotionblur:
            scene.frame_set(scene.frame_current - 1) # Blender now supports sub-frames!
            SLGBP.camdirBlur = scene.camera.matrix_world * Vector((0, 0, -10))
            SLGBP.camlocBlur = scene.camera.matrix_world.to_translation()
            SLGBP.camupBlur = scene.camera.matrix_world.to_3x3() * Vector((0,1,0))
            scene.frame_set(scene.frame_current + 1)

        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.latency.mode'] = format(scene.slg.low_latency, 'b')
        cfg['opencl.nativethread.count'] = format(scene.slg.native_threads)
        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.renderthread.count'] = format(scene.slg.devices_threads)
        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 == '4' and scene.slg.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['sppm.lookup.type'] = scene.slg.sppmlookuptype
        cfg['sppm.directlight.enable'] = format(scene.slg.sppmdirectlight, 'b')
        cfg['sppm.eyepath.maxdepth'] = format(scene.slg.sppm_eyepath_maxdepth)
        cfg['sppm.photon.alpha'] = ff(scene.slg.sppm_photon_alpha)
        cfg['sppm.photon.startradiusscale'] = ff(scene.slg.sppm_photon_radiusscale)
        cfg['sppm.photon.maxdepth'] = format(scene.slg.sppm_photon_maxdepth)
        cfg['sppm.stochastic.count'] = format(scene.slg.sppm_photon_per_pass)
        cfg['path.sampler.type'] = scene.slg.sampler_type
        cfg['path.pixelatomics.enable'] = format(scene.slg.pixelatomics_enable, 'b')
        cfg['path.sampler.xsamples'] = format(scene.slg.sampler_xsamples)
        cfg['path.sampler.ysamples'] = format(scene.slg.sampler_ysamples)
        cfg['path.sampler.largesteprate'] = ff(scene.slg.sampler_largesteprate)
        cfg['path.sampler.maxconsecutivereject'] = ff(scene.slg.sampler_maxconsecutivereject)
        cfg['path.filter.type'] = scene.slg.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.tracedepth)
        cfg['path.maxdiffusebounce'] = format(scene.slg.diffusebounce)
        cfg['path.russianroulette.depth'] = format(scene.slg.rrdepth)
        cfg['path.russianroulette.strategy'] = scene.slg.rrstrategy
        if scene.slg.rrstrategy == '0':
            cfg['path.russianroulette.prob'] = ff(scene.slg.rrprob)
        else:
            cfg['path.russianroulette.cap'] = ff(scene.slg.rrcap)
        cfg['path.lightstrategy'] = scene.slg.lightstrategy
        cfg['path.shadowrays'] = format(scene.slg.shadowrays)
        cfg['path.sampler.spp'] = scene.slg.sampleperpixel
        cfg['accelerator.type'] = scene.slg.accelerator_type

        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')

        scn['scene.camera.motionblur.enable'] = format(scene.slg.cameramotionblur, 'b')
        if scene.slg.cameramotionblur:
            if SLGBP.live:
                if 'scene.camera.lookat' in SLGBP.scn:
                    scn['scene.camera.motionblur.lookat'] = SLGBP.scn['scene.camera.lookat']
                    scn['scene.camera.motionblur.up'] = SLGBP.scn['scene.camera.up']
            else:
                scn['scene.camera.motionblur.lookat'] = '{} {} {} {} {} {}'.format(ff(SLGBP.camlocBlur.x),ff(SLGBP.camlocBlur.y),ff(SLGBP.camlocBlur.z),
                        ff(SLGBP.camdirBlur[0]),ff(SLGBP.camdirBlur[1]),ff(SLGBP.camdirBlur[2]))
                scn['scene.camera.motionblur.up'] = '{} {} {}'.format(ff(SLGBP.camupBlur.x),ff(SLGBP.camupBlur.y),ff(SLGBP.camupBlur.z))

        # 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))

        # Participating Media properties
        if scene.slg.enablepartmedia:
            scn['scene.partecipatingmedia.singlescatering.enable'] = '1'
            scn['scene.partecipatingmedia.singlescatering.bbox'] = '{} {} {} {} {} {}'.format(scene.slg.partmedia_bbox1.x, scene.slg.partmedia_bbox1.y, scene.slg.partmedia_bbox1.z, scene.slg.partmedia_bbox2.x, scene.slg.partmedia_bbox2.y, scene.slg.partmedia_bbox2.z)
            scn['scene.partecipatingmedia.singlescatering.stepsize'] = ff(scene.slg.partmedia_stepsize)
            scn['scene.partecipatingmedia.singlescatering.rrprob'] = ff(scene.slg.partmedia_rrprob)
            scn['scene.partecipatingmedia.singlescatering.emission'] = '{} {} {}'.format(scene.slg.partmedia_emission[0], scene.slg.partmedia_emission[1], scene.slg.partmedia_emission[2])
            scn['scene.partecipatingmedia.singlescatering.scattering'] = '{} {} {}'.format(scene.slg.partmedia_scattering[0], scene.slg.partmedia_scattering[1], scene.slg.partmedia_scattering[2])

        return scn

    # Get SLG .scn mat properties
    @staticmethod
    def getmatscn(scene, m):
        scn = {}
        matn = m.name.replace('.','_')
        if m.use_shadeless:
            matprop = None
        elif m.emit:
            matprop = 'scene.materials.light.{}'.format(matn)
            scn[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.use_transparency:
            if m.raytrace_transparency.ior == 1.0:
                matprop = 'scene.materials.archglass.{}'.format(matn)
                scn[matprop] = '{} {} {} {} {} {} {:b} {:b}'.format(ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),
                    ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),
                    ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                    m.raytrace_mirror.depth>0,m.raytrace_transparency.depth>0)
            else:
                matprop = 'scene.materials.glass.{}'.format(matn)
                scn[matprop] = '{} {} {} {} {} {} 1.0 {} {:b} {:b}'.format(ff(m.mirror_color[0]),ff(m.mirror_color[1]),ff(m.mirror_color[2]),
                    ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                    ff(m.raytrace_transparency.ior),m.raytrace_mirror.depth>0,m.raytrace_transparency.depth>0)
        else:
            if not m.raytrace_mirror.use or not m.raytrace_mirror.reflect_factor:
                matprop = 'scene.materials.matte.{}'.format(matn)
                scn[matprop] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))
            else:
                if m.raytrace_mirror.fresnel > 0:
                    matprop = 'scene.materials.alloy.{}'.format(matn)
                    scn[matprop] = '{} {} {} {} {} {} {} {} {:b}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                        ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),
                        ff(pow(10000.0,m.raytrace_mirror.gloss_factor)),m.raytrace_mirror.fresnel,m.raytrace_mirror.depth>0)
                else:
                    if m.raytrace_mirror.gloss_factor < 1:
                        refltype = 'metal'
                        gloss = ff(pow(10000.0,m.raytrace_mirror.gloss_factor))
                    else:
                        refltype = 'mirror'
                        gloss = ''
                    if m.raytrace_mirror.reflect_factor == 1:
                        matprop = 'scene.materials.{}.{}'.format(refltype,matn)
                        scn[matprop] = '{} {} {} {} {:b}'.format(ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),
                            ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),gloss,m.raytrace_mirror.depth>0)
                    else:
                        matprop = 'scene.materials.matte{}.{}'.format(refltype,matn)
                        scn[matprop] = '{} {} {} {} {} {} {} {:b}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                            ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),
                            gloss,m.raytrace_mirror.depth>0)
                        if m.diffuse_color.v+m.raytrace_mirror.reflect_factor*m.mirror_color.v > 1.0001:
                            SLGBP.warn = m.name + ': diffuse + reflected color greater than 1.0!'
                            print('WARNING: ' + SLGBP.warn)
        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[1] >= 62 : # matrix change between 2.61 and 2.62
                    scn['scene.objects.{}.{}.transformation'.format(matn,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(matn,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.{}.{}'.format(matn,objn)] = '{}/{}/{}.ply'.format(SLGBP.spath,SLGBP.sname,plyn)
                if scene.slg.vnormals:
                    scn['scene.objects.{}.{}.useplynormals'.format(matn,objn)] = '1'
                if scene.slg.vuvs and mat:
                    texmap = next((ts for ts in mat.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:
                        scn['scene.objects.{}.{}.texmap'.format(matn,objn)] = bpy.path.abspath(texmap.texture.image.filepath).replace('\\','/')
                        scn['scene.objects.{}.{}.texmap.gamma'.format(matn,objn)] = ff(texmap.texture.slg_gamma)
                    texbump = next((ts for ts in mat.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:
                        if texbump.texture.use_normal_map:
                            scn['scene.objects.{}.{}.normalmap'.format(matn,objn)] = bpy.path.abspath(texbump.texture.image.filepath).replace('\\','/')
                        else:
                            scn['scene.objects.{}.{}.bumpmap'.format(matn,objn)] = bpy.path.abspath(texbump.texture.image.filepath).replace('\\','/')
                            scn['scene.objects.{}.{}.bumpmap.scale'.format(matn,objn)] = ff(texbump.normal_factor)

        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' 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))]
        vert_vcs = [[] 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
                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)
                    # 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]
                    vcd = []
                    if bpy.app.version[1] >= 62 and bpy.app.version[2] > 0: # bmesh adaption
                        if scene.slg.vcolors and mesh.tessface_vertex_colors.active:
                            vcd = mesh.tessface_vertex_colors.active.data
                    else:
                        if scene.slg.vcolors and mesh.vertex_colors.active:
                            vcd = mesh.vertex_colors.active.data
                    uvd = []
                    if bpy.app.version[1] >= 62 and bpy.app.version[2] > 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]
                    for face, vc, uv in zip_longest(mesh.faces,vcd,uvd):
                        curmat = objmats[face.material_index] if objmats else onomat
                        # Get vertex colors, if avail
                        if vc:
                            colors = vc.color1, vc.color2, vc.color3, vc.color4
                            mvc[curmat] = True
                        # 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.vcolors:
                                if vc:
                                    color[0] = int(255.0*colors[j][0])
                                    color[1] = int(255.0*colors[j][1])
                                    color[2] = int(255.0*colors[j][2])
                                else:
                                    color[0] = color[1] = color[2] = 255<commit_msg>Blender api adaption conditional: mesh.faces or mesh.tessfaces according version-tuple<commit_after># -*- coding: utf-8 -*-
###########################################################################
#   Copyright (C) 1998-2010 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 v1.8beta1 Blender 2.5 plug-in

bl_info = {
    "name": "SmallLuxGPU",
    "author": "see (SLG) AUTHORS.txt",
    "version": (2, 0, 4),
    "blender": (2, 6, 0),
    "location": "Render > Engine > SmallLuxGPU",
    "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 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

# hack for bmesh
# TODO remove this when obsolete
def get_mesh_faces_old(mesh):
	return mesh.faces
def get_mesh_faces_new(mesh):
	return mesh.tessfaces

if bpy.app.version[1] >= 62 and bpy.app.version[2] > 1: # bmesh adaption
	get_mesh_faces = get_mesh_faces_new
else:
	get_mesh_faces = get_mesh_faces_old

# 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',b'ERROR'],2)
            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

        # Get motion blur parameters
        if scene.slg.cameramotionblur:
            scene.frame_set(scene.frame_current - 1) # Blender now supports sub-frames!
            SLGBP.camdirBlur = scene.camera.matrix_world * Vector((0, 0, -10))
            SLGBP.camlocBlur = scene.camera.matrix_world.to_translation()
            SLGBP.camupBlur = scene.camera.matrix_world.to_3x3() * Vector((0,1,0))
            scene.frame_set(scene.frame_current + 1)

        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.latency.mode'] = format(scene.slg.low_latency, 'b')
        cfg['opencl.nativethread.count'] = format(scene.slg.native_threads)
        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.renderthread.count'] = format(scene.slg.devices_threads)
        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 == '4' and scene.slg.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['sppm.lookup.type'] = scene.slg.sppmlookuptype
        cfg['sppm.directlight.enable'] = format(scene.slg.sppmdirectlight, 'b')
        cfg['sppm.eyepath.maxdepth'] = format(scene.slg.sppm_eyepath_maxdepth)
        cfg['sppm.photon.alpha'] = ff(scene.slg.sppm_photon_alpha)
        cfg['sppm.photon.startradiusscale'] = ff(scene.slg.sppm_photon_radiusscale)
        cfg['sppm.photon.maxdepth'] = format(scene.slg.sppm_photon_maxdepth)
        cfg['sppm.stochastic.count'] = format(scene.slg.sppm_photon_per_pass)
        cfg['path.sampler.type'] = scene.slg.sampler_type
        cfg['path.pixelatomics.enable'] = format(scene.slg.pixelatomics_enable, 'b')
        cfg['path.sampler.xsamples'] = format(scene.slg.sampler_xsamples)
        cfg['path.sampler.ysamples'] = format(scene.slg.sampler_ysamples)
        cfg['path.sampler.largesteprate'] = ff(scene.slg.sampler_largesteprate)
        cfg['path.sampler.maxconsecutivereject'] = ff(scene.slg.sampler_maxconsecutivereject)
        cfg['path.filter.type'] = scene.slg.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.tracedepth)
        cfg['path.maxdiffusebounce'] = format(scene.slg.diffusebounce)
        cfg['path.russianroulette.depth'] = format(scene.slg.rrdepth)
        cfg['path.russianroulette.strategy'] = scene.slg.rrstrategy
        if scene.slg.rrstrategy == '0':
            cfg['path.russianroulette.prob'] = ff(scene.slg.rrprob)
        else:
            cfg['path.russianroulette.cap'] = ff(scene.slg.rrcap)
        cfg['path.lightstrategy'] = scene.slg.lightstrategy
        cfg['path.shadowrays'] = format(scene.slg.shadowrays)
        cfg['path.sampler.spp'] = scene.slg.sampleperpixel
        cfg['accelerator.type'] = scene.slg.accelerator_type

        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')

        scn['scene.camera.motionblur.enable'] = format(scene.slg.cameramotionblur, 'b')
        if scene.slg.cameramotionblur:
            if SLGBP.live:
                if 'scene.camera.lookat' in SLGBP.scn:
                    scn['scene.camera.motionblur.lookat'] = SLGBP.scn['scene.camera.lookat']
                    scn['scene.camera.motionblur.up'] = SLGBP.scn['scene.camera.up']
            else:
                scn['scene.camera.motionblur.lookat'] = '{} {} {} {} {} {}'.format(ff(SLGBP.camlocBlur.x),ff(SLGBP.camlocBlur.y),ff(SLGBP.camlocBlur.z),
                        ff(SLGBP.camdirBlur[0]),ff(SLGBP.camdirBlur[1]),ff(SLGBP.camdirBlur[2]))
                scn['scene.camera.motionblur.up'] = '{} {} {}'.format(ff(SLGBP.camupBlur.x),ff(SLGBP.camupBlur.y),ff(SLGBP.camupBlur.z))

        # 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))

        # Participating Media properties
        if scene.slg.enablepartmedia:
            scn['scene.partecipatingmedia.singlescatering.enable'] = '1'
            scn['scene.partecipatingmedia.singlescatering.bbox'] = '{} {} {} {} {} {}'.format(scene.slg.partmedia_bbox1.x, scene.slg.partmedia_bbox1.y, scene.slg.partmedia_bbox1.z, scene.slg.partmedia_bbox2.x, scene.slg.partmedia_bbox2.y, scene.slg.partmedia_bbox2.z)
            scn['scene.partecipatingmedia.singlescatering.stepsize'] = ff(scene.slg.partmedia_stepsize)
            scn['scene.partecipatingmedia.singlescatering.rrprob'] = ff(scene.slg.partmedia_rrprob)
            scn['scene.partecipatingmedia.singlescatering.emission'] = '{} {} {}'.format(scene.slg.partmedia_emission[0], scene.slg.partmedia_emission[1], scene.slg.partmedia_emission[2])
            scn['scene.partecipatingmedia.singlescatering.scattering'] = '{} {} {}'.format(scene.slg.partmedia_scattering[0], scene.slg.partmedia_scattering[1], scene.slg.partmedia_scattering[2])

        return scn

    # Get SLG .scn mat properties
    @staticmethod
    def getmatscn(scene, m):
        scn = {}
        matn = m.name.replace('.','_')
        if m.use_shadeless:
            matprop = None
        elif m.emit:
            matprop = 'scene.materials.light.{}'.format(matn)
            scn[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.use_transparency:
            if m.raytrace_transparency.ior == 1.0:
                matprop = 'scene.materials.archglass.{}'.format(matn)
                scn[matprop] = '{} {} {} {} {} {} {:b} {:b}'.format(ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),
                    ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),
                    ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                    m.raytrace_mirror.depth>0,m.raytrace_transparency.depth>0)
            else:
                matprop = 'scene.materials.glass.{}'.format(matn)
                scn[matprop] = '{} {} {} {} {} {} 1.0 {} {:b} {:b}'.format(ff(m.mirror_color[0]),ff(m.mirror_color[1]),ff(m.mirror_color[2]),
                    ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                    ff(m.raytrace_transparency.ior),m.raytrace_mirror.depth>0,m.raytrace_transparency.depth>0)
        else:
            if not m.raytrace_mirror.use or not m.raytrace_mirror.reflect_factor:
                matprop = 'scene.materials.matte.{}'.format(matn)
                scn[matprop] = '{} {} {}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]))
            else:
                if m.raytrace_mirror.fresnel > 0:
                    matprop = 'scene.materials.alloy.{}'.format(matn)
                    scn[matprop] = '{} {} {} {} {} {} {} {} {:b}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                        ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),
                        ff(pow(10000.0,m.raytrace_mirror.gloss_factor)),m.raytrace_mirror.fresnel,m.raytrace_mirror.depth>0)
                else:
                    if m.raytrace_mirror.gloss_factor < 1:
                        refltype = 'metal'
                        gloss = ff(pow(10000.0,m.raytrace_mirror.gloss_factor))
                    else:
                        refltype = 'mirror'
                        gloss = ''
                    if m.raytrace_mirror.reflect_factor == 1:
                        matprop = 'scene.materials.{}.{}'.format(refltype,matn)
                        scn[matprop] = '{} {} {} {} {:b}'.format(ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),
                            ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),gloss,m.raytrace_mirror.depth>0)
                    else:
                        matprop = 'scene.materials.matte{}.{}'.format(refltype,matn)
                        scn[matprop] = '{} {} {} {} {} {} {} {:b}'.format(ff(m.diffuse_color[0]),ff(m.diffuse_color[1]),ff(m.diffuse_color[2]),
                            ff(m.raytrace_mirror.reflect_factor*m.mirror_color[0]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[1]),ff(m.raytrace_mirror.reflect_factor*m.mirror_color[2]),
                            gloss,m.raytrace_mirror.depth>0)
                        if m.diffuse_color.v+m.raytrace_mirror.reflect_factor*m.mirror_color.v > 1.0001:
                            SLGBP.warn = m.name + ': diffuse + reflected color greater than 1.0!'
                            print('WARNING: ' + SLGBP.warn)
        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[1] >= 62 : # matrix change between 2.61 and 2.62
                    scn['scene.objects.{}.{}.transformation'.format(matn,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(matn,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.{}.{}'.format(matn,objn)] = '{}/{}/{}.ply'.format(SLGBP.spath,SLGBP.sname,plyn)
                if scene.slg.vnormals:
                    scn['scene.objects.{}.{}.useplynormals'.format(matn,objn)] = '1'
                if scene.slg.vuvs and mat:
                    texmap = next((ts for ts in mat.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:
                        scn['scene.objects.{}.{}.texmap'.format(matn,objn)] = bpy.path.abspath(texmap.texture.image.filepath).replace('\\','/')
                        scn['scene.objects.{}.{}.texmap.gamma'.format(matn,objn)] = ff(texmap.texture.slg_gamma)
                    texbump = next((ts for ts in mat.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:
                        if texbump.texture.use_normal_map:
                            scn['scene.objects.{}.{}.normalmap'.format(matn,objn)] = bpy.path.abspath(texbump.texture.image.filepath).replace('\\','/')
                        else:
                            scn['scene.objects.{}.{}.bumpmap'.format(matn,objn)] = bpy.path.abspath(texbump.texture.image.filepath).replace('\\','/')
                            scn['scene.objects.{}.{}.bumpmap.scale'.format(matn,objn)] = ff(texbump.normal_factor)

        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' 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))]
        vert_vcs = [[] 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
                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)
                    # 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]
                    vcd = []
                    if bpy.app.version[1] >= 62 and bpy.app.version[2] > 0: # bmesh adaption
                        if scene.slg.vcolors and mesh.tessface_vertex_colors.active:
                            vcd = mesh.tessface_vertex_colors.active.data
                    else:
                        if scene.slg.vcolors and mesh.vertex_colors.active:
                            vcd = mesh.vertex_colors.active.data
                    uvd = []
                    if bpy.app.version[1] >= 62 and bpy.app.version[2] > 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]

                    mesh_faces = get_mesh_faces(mesh)
                    for face, vc, uv in zip_longest(mesh_faces,vcd,uvd):
                        curmat = objmats[face.material_index] if objmats else onomat


                        # Get vertex colors, if avail
                        if vc:
                            colors = vc.color1, vc.color2, vc.color3, vc.color4
                            mvc[curmat] = True
                        # 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.vcolors:
                                if vc:
                                    color[0] = int(255.0*colors[j][0])
                                    color[1] = int(255.0*colors[j][1])
                                    color[2] = int(255.0*colors[j][2])
                                else:
                                    color[0] = color[1] = color[2] = 255
{'fraction_non_alphanumeric': 0.1361958342982468, 'fraction_numerical': 0.009576482447656283, 'mean_word_length': 1.7778408445907594, 'pattern_counts': {'":': 20, '<': 7, '<?xml version=': 0, '>': 51, 'https://': 0, 'lorem ipsum': 0, 'www.': 8, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 98, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '929335', 'n_tokens_mistral': 23996, 'n_tokens_neox': 22874, 'n_words': 3890}