<commit_before>from PyQt5.QtGui import QColor, QOpenGLBuffer, QOpenGLContext, QOpenGLFramebufferObject, QOpenGLFramebufferObjectFormat, QSurfaceFormat, QOpenGLVersionProfile, QImage

from UM.Application import Application
from UM.View.Renderer import Renderer
from UM.Math.Vector import Vector
from UM.Math.Matrix import Matrix
from UM.Resources import Resources
from UM.Logger import Logger
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.Selection import Selection

from . import QtGL2Material

import numpy
from ctypes import c_void_p

vertexBufferProperty = '__qtgl2_vertex_buffer'
indexBufferProperty = '__qtgl2_index_buffer'

##  A Renderer implementation using OpenGL2 to render.
class QtGL2Renderer(Renderer):
    def __init__(self):
        super().__init__()

        self._scene = Application.getInstance().getController().getScene()

        self._vertexBufferCache = {}
        self._indexBufferCache = {}

        self._initialized = False

        self._lightPosition = Vector(0, 0, 0)
        self._backgroundColor = QColor(128, 128, 128)
        self._viewportWidth = 0
        self._viewportHeight = 0

        self._solidsQueue = []
        self._transparentQueue = []
        self._overlayQueue = []

        self._selection_buffer = None
        self._selection_map = {}
        self._selection_image = None

        self._camera = None

    def createMaterial(self, vert, frag):
        mat = QtGL2Material.QtGL2Material(self)
        mat.loadVertexShader(vert)
        mat.loadFragmentShader(frag)
        mat.build()
        return mat

    def createFrameBuffer(self, width, height):
        buffer_format = QOpenGLFramebufferObjectFormat()
        buffer_format.setAttachment(QOpenGLFramebufferObject.Depth)
        return QOpenGLFramebufferObject(width, height, buffer_format)

    def getSelectionImage(self):
        return self._selection_image

    def getSelectionMap(self):
        return self._selection_map

    def setLightPosition(self, position):
        self._lightPosition = position

    def setBackgroundColor(self, color):
        self._backgroundColor = color

    def setViewportSize(self, width, height):
        self._viewportWidth = width
        self._viewportHeight = height

    def beginRendering(self):
        if not self._initialized:
            self._initialize()

        self._gl.glViewport(0, 0, self._viewportWidth, self._viewportHeight)
        self._gl.glClearColor(self._backgroundColor.redF(), self._backgroundColor.greenF(), self._backgroundColor.blueF(), self._backgroundColor.alphaF())
        self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)

        if not QOpenGLContext.currentContext().format().renderableType() == QSurfaceFormat.OpenGLES:
            self._gl.glPointSize(2)

        self._solidsQueue.clear()
        self._transparentQueue.clear()
        self._overlayQueue.clear()

    def queueNode(self, node, **kwargs):
        queueItem = { 'node': node }

        if 'mesh' in kwargs:
            queueItem['mesh'] = kwargs['mesh']

        queueItem['material'] = kwargs.get('material', self._defaultMaterial)

        mode = kwargs.get('mode', Renderer.RenderTriangles)
        if mode is Renderer.RenderLines:
            queueItem['mode'] = self._gl.GL_LINES
        elif mode is Renderer.RenderLineLoop:
            queueItem['mode'] = self._gl.GL_LINE_LOOP
        elif mode is Renderer.RenderPoints:
            queueItem['mode'] = self._gl.GL_POINTS
        else:
            queueItem['mode'] = self._gl.GL_TRIANGLES

        queueItem['wireframe'] = (mode == Renderer.RenderWireframe)

        if kwargs.get('transparent', False):
            self._transparentQueue.append(queueItem)
        elif kwargs.get('overlay', False):
            self._overlayQueue.append(queueItem)
        else:
            self._solidsQueue.append(queueItem)

    def renderQueuedNodes(self):
        self._gl.glEnable(self._gl.GL_DEPTH_TEST)
        self._gl.glDepthFunc(self._gl.GL_LESS)
        self._gl.glDepthMask(self._gl.GL_TRUE)
        self._gl.glEnable(self._gl.GL_CULL_FACE)

        self._scene.acquireLock()

        self._camera = self._scene.getActiveCamera()
        if not self._camera:
            Logger.log("e", "No active camera set, can not render")
            self._scene.releaseLock()
            return

        # Render the selection image
        selectable_nodes = []
        for node in DepthFirstIterator(self._scene.getRoot()):
            if node.isSelectable() and node.getMeshData():
                selectable_nodes.append(node)

        if selectable_nodes:
            #TODO: Use a limited area around the mouse rather than a full viewport for rendering
            if self._selection_buffer.width() < self._viewportWidth or self._selection_buffer.height() < self._viewportHeight:
                self._selection_buffer = self.createFrameBuffer(self._viewportWidth, self._viewportHeight)

            self._selection_buffer.bind()
            self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
            self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
            self._gl.glDisable(self._gl.GL_BLEND)
            self._selection_map.clear()
            for node in selectable_nodes:
                color = self._getObjectColor(node)
                self._selection_map[color] = node
                self._selection_material.setUniformValue('u_color', [color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3] / 255.0])
                self._renderItem({
                    'node': node,
                    'material': self._selection_material,
                    'mode': self._gl.GL_TRIANGLES
                })
            self._selection_buffer.release()
            self._selection_image = self._selection_buffer.toImage()

        self._gl.glEnable(self._gl.GL_STENCIL_TEST)
        self._gl.glStencilMask(0xff)
        self._gl.glClearStencil(0)
        self._gl.glClear(self._gl.GL_STENCIL_BUFFER_BIT)
        self._gl.glStencilFunc(self._gl.GL_ALWAYS, 0xff, 0xff)
        self._gl.glStencilOp(self._gl.GL_REPLACE, self._gl.GL_REPLACE, self._gl.GL_REPLACE)
        self._gl.glStencilMask(0)

        for item in self._solidsQueue:
            if Selection.isSelected(item['node']):
                self._gl.glStencilMask(0xff)
                self._renderItem(item)
                self._gl.glStencilMask(0)
            else:
                self._renderItem(item)

        self._gl.glStencilMask(0)
        self._gl.glStencilFunc(self._gl.GL_EQUAL, 0, 0xff)
        self._gl.glLineWidth(5)
        for node in Selection.getAllSelectedObjects():
            if node.getMeshData():
                self._renderItem({
                    'node': node,
                    'material': self._outline_material,
                    'mode': self._gl.GL_TRIANGLES,
                    'wireframe': True
                })

        self._gl.glLineWidth(1)

        self._gl.glDisable(self._gl.GL_STENCIL_TEST)
        self._gl.glDepthMask(self._gl.GL_FALSE)
        self._gl.glEnable(self._gl.GL_BLEND)
        self._gl.glBlendFunc(self._gl.GL_SRC_ALPHA, self._gl.GL_ONE_MINUS_SRC_ALPHA)

        for item in self._transparentQueue:
            self._renderItem(item)

        self._gl.glDisable(self._gl.GL_DEPTH_TEST)

        for item in self._overlayQueue:
            self._renderItem(item)

        self._scene.releaseLock()

    def endRendering(self):
        pass

    def _initialize(self):
        profile = QOpenGLVersionProfile()
        profile.setVersion(2, 0)
        self._gl = QOpenGLContext.currentContext().versionFunctions(profile)
        self._gl.initializeOpenGLFunctions()

        self._defaultMaterial = self.createMaterial(
                                     Resources.getPath(Resources.ShadersLocation, 'default.vert'),
                                     Resources.getPath(Resources.ShadersLocation, 'default.frag')
                                )

        self._defaultMaterial.setUniformValue("u_ambientColor", [0.3, 0.3, 0.3, 1.0])
        self._defaultMaterial.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 1.0])
        self._defaultMaterial.setUniformValue("u_specularColor", [1.0, 1.0, 1.0, 1.0])
        self._defaultMaterial.setUniformValue("u_shininess", 50.0)

        self._selection_buffer = self.createFrameBuffer(128, 128)
        self._selection_material = self.createMaterial(
                                        Resources.getPath(Resources.ShadersLocation, 'basic.vert'),
                                        Resources.getPath(Resources.ShadersLocation, 'color.frag')
                                   )

        self._outline_material = self.createMaterial(
                                      Resources.getPath(Resources.ShadersLocation, 'outline.vert'),
                                       Resources.getPath(Resources.ShadersLocation, 'outline.frag')
                                 )

        self._initialized = True

    def _renderItem(self, item):
        node = item['node']
        mesh = item.get('mesh', node.getMeshData())
        transform = node.getGlobalTransformation()
        material = item['material']
        mode = item['mode']
        wireframe = item.get('wireframe', False)

        material.bind()
        material.setUniformValue("u_projectionMatrix", self._camera.getProjectionMatrix(), cache = False)
        material.setUniformValue("u_viewMatrix", self._camera.getGlobalTransformation().getInverse(), cache = False)
        material.setUniformValue("u_viewPosition", self._camera.getGlobalPosition(), cache = False)
        material.setUniformValue("u_modelMatrix", transform, cache = False)
        material.setUniformValue("u_lightPosition", self._camera.getGlobalPosition(), cache = False)

        if mesh.hasNormals():
            normalMatrix = transform
            normalMatrix.setRow(3, [0, 0, 0, 1])
            normalMatrix.setColumn(3, [0, 0, 0, 1])
            normalMatrix = normalMatrix.getInverse().getTransposed()
            material.setUniformValue("u_normalMatrix", normalMatrix, cache = False)

        try:
            vertexBuffer = getattr(mesh, vertexBufferProperty)
        except AttributeError:
            vertexBuffer =  self._createVertexBuffer(mesh)
        vertexBuffer.bind()

        if mesh.hasIndices():
            try:
                indexBuffer = getattr(mesh, indexBufferProperty)
            except AttributeError:
                indexBuffer = self._createIndexBuffer(mesh)
            indexBuffer.bind()

        material.enableAttribute("a_vertex", Vector, 0)

        if mesh.hasNormals():
            material.enableAttribute("a_normal", Vector, mesh.getVertexCount() * 3 * 4)

        if wireframe:
            self._gl.glPolygonMode(self._gl.GL_FRONT_AND_BACK, self._gl.GL_LINE)

        if mesh.hasIndices():
            self._gl.glDrawElements(mode, mesh.getFaceCount() * 3 , self._gl.GL_UNSIGNED_INT, None)
        else:
            self._gl.glDrawArrays(mode, 0, mesh.getVertexCount())

        if wireframe:
            self._gl.glPolygonMode(self._gl.GL_FRONT_AND_BACK, self._gl.GL_FILL)

        material.disableAttribute("a_vertex")
        material.disableAttribute("a_normal")
        vertexBuffer.release()

        if mesh.hasIndices():
            indexBuffer.release()

        material.release()

    def _createVertexBuffer(self, mesh):
        buffer = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer)
        buffer.create()
        buffer.bind()
        vertices = mesh.getVerticesAsByteArray()

        if mesh.hasNormals():
            normals = mesh.getNormalsAsByteArray()
            ##Number of vertices * number of components (3 for vertex, 3 for normal) * size of 32-bit float (4)
            buffer.allocate(mesh.getVertexCount() * 6 * 4)
            buffer.write(0, vertices, len(vertices))
            buffer.write(len(vertices), normals, len(normals))
        else:
            buffer.allocate(vertices, mesh.getVertexCount() * 3 * 4)
        buffer.release()

        setattr(mesh, vertexBufferProperty, buffer)
        return buffer

    def _createIndexBuffer(self, mesh):
        buffer = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer)
        buffer.create()
        buffer.bind()

        data = mesh.getIndicesAsByteArray()
        buffer.allocate(data, len(data))
        buffer.release()

        setattr(mesh, indexBufferProperty, buffer)
        return buffer

    def _getObjectColor(self, node):
        obj_id = id(node)
        r = (obj_id & 0xff000000) >> 24
        g = (obj_id & 0x00ff0000) >> 16
        b = (obj_id & 0x0000ff00) >> 8
        a = (obj_id & 0x000000ff) >> 0
        return (r, g, b, a)
<commit_msg>Make OpenGL ES2 no longer crash.<commit_after>from PyQt5.QtGui import QColor, QOpenGLBuffer, QOpenGLContext, QOpenGLFramebufferObject, QOpenGLFramebufferObjectFormat, QSurfaceFormat, QOpenGLVersionProfile, QImage

from UM.Application import Application
from UM.View.Renderer import Renderer
from UM.Math.Vector import Vector
from UM.Math.Matrix import Matrix
from UM.Resources import Resources
from UM.Logger import Logger
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Scene.Selection import Selection

from . import QtGL2Material

import numpy
from ctypes import c_void_p

vertexBufferProperty = '__qtgl2_vertex_buffer'
indexBufferProperty = '__qtgl2_index_buffer'

##  A Renderer implementation using OpenGL2 to render.
class QtGL2Renderer(Renderer):
    def __init__(self):
        super().__init__()

        self._scene = Application.getInstance().getController().getScene()

        self._vertexBufferCache = {}
        self._indexBufferCache = {}

        self._initialized = False

        self._lightPosition = Vector(0, 0, 0)
        self._backgroundColor = QColor(128, 128, 128)
        self._viewportWidth = 0
        self._viewportHeight = 0

        self._solidsQueue = []
        self._transparentQueue = []
        self._overlayQueue = []

        self._selection_buffer = None
        self._selection_map = {}
        self._selection_image = None

        self._camera = None

    def createMaterial(self, vert, frag):
        mat = QtGL2Material.QtGL2Material(self)
        mat.loadVertexShader(vert)
        mat.loadFragmentShader(frag)
        mat.build()
        return mat

    def createFrameBuffer(self, width, height):
        buffer_format = QOpenGLFramebufferObjectFormat()
        buffer_format.setAttachment(QOpenGLFramebufferObject.Depth)
        return QOpenGLFramebufferObject(width, height, buffer_format)

    def getSelectionImage(self):
        return self._selection_image

    def getSelectionMap(self):
        return self._selection_map

    def setLightPosition(self, position):
        self._lightPosition = position

    def setBackgroundColor(self, color):
        self._backgroundColor = color

    def setViewportSize(self, width, height):
        self._viewportWidth = width
        self._viewportHeight = height

    def beginRendering(self):
        if not self._initialized:
            self._initialize()

        self._gl.glViewport(0, 0, self._viewportWidth, self._viewportHeight)
        self._gl.glClearColor(self._backgroundColor.redF(), self._backgroundColor.greenF(), self._backgroundColor.blueF(), self._backgroundColor.alphaF())
        self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)

        if not QOpenGLContext.currentContext().format().renderableType() == QSurfaceFormat.OpenGLES:
            self._gl.glPointSize(2)

        self._solidsQueue.clear()
        self._transparentQueue.clear()
        self._overlayQueue.clear()

    def queueNode(self, node, **kwargs):
        queueItem = { 'node': node }

        if 'mesh' in kwargs:
            queueItem['mesh'] = kwargs['mesh']

        queueItem['material'] = kwargs.get('material', self._defaultMaterial)

        mode = kwargs.get('mode', Renderer.RenderTriangles)
        if mode is Renderer.RenderLines:
            queueItem['mode'] = self._gl.GL_LINES
        elif mode is Renderer.RenderLineLoop:
            queueItem['mode'] = self._gl.GL_LINE_LOOP
        elif mode is Renderer.RenderPoints:
            queueItem['mode'] = self._gl.GL_POINTS
        else:
            queueItem['mode'] = self._gl.GL_TRIANGLES

        queueItem['wireframe'] = (mode == Renderer.RenderWireframe)

        if kwargs.get('transparent', False):
            self._transparentQueue.append(queueItem)
        elif kwargs.get('overlay', False):
            self._overlayQueue.append(queueItem)
        else:
            self._solidsQueue.append(queueItem)

    def renderQueuedNodes(self):
        self._gl.glEnable(self._gl.GL_DEPTH_TEST)
        self._gl.glDepthFunc(self._gl.GL_LESS)
        self._gl.glDepthMask(self._gl.GL_TRUE)
        self._gl.glEnable(self._gl.GL_CULL_FACE)

        self._scene.acquireLock()

        self._camera = self._scene.getActiveCamera()
        if not self._camera:
            Logger.log("e", "No active camera set, can not render")
            self._scene.releaseLock()
            return

        # Render the selection image
        selectable_nodes = []
        for node in DepthFirstIterator(self._scene.getRoot()):
            if node.isSelectable() and node.getMeshData():
                selectable_nodes.append(node)

        if selectable_nodes:
            #TODO: Use a limited area around the mouse rather than a full viewport for rendering
            if self._selection_buffer.width() < self._viewportWidth or self._selection_buffer.height() < self._viewportHeight:
                self._selection_buffer = self.createFrameBuffer(self._viewportWidth, self._viewportHeight)

            self._selection_buffer.bind()
            self._gl.glClearColor(0.0, 0.0, 0.0, 0.0)
            self._gl.glClear(self._gl.GL_COLOR_BUFFER_BIT | self._gl.GL_DEPTH_BUFFER_BIT)
            self._gl.glDisable(self._gl.GL_BLEND)
            self._selection_map.clear()
            for node in selectable_nodes:
                color = self._getObjectColor(node)
                self._selection_map[color] = node
                self._selection_material.setUniformValue('u_color', [color[0] / 255.0, color[1] / 255.0, color[2] / 255.0, color[3] / 255.0])
                self._renderItem({
                    'node': node,
                    'material': self._selection_material,
                    'mode': self._gl.GL_TRIANGLES
                })
            self._selection_buffer.release()
            self._selection_image = self._selection_buffer.toImage()

        self._gl.glEnable(self._gl.GL_STENCIL_TEST)
        self._gl.glStencilMask(0xff)
        self._gl.glClearStencil(0)
        self._gl.glClear(self._gl.GL_STENCIL_BUFFER_BIT)
        self._gl.glStencilFunc(self._gl.GL_ALWAYS, 0xff, 0xff)
        self._gl.glStencilOp(self._gl.GL_REPLACE, self._gl.GL_REPLACE, self._gl.GL_REPLACE)
        self._gl.glStencilMask(0)

        for item in self._solidsQueue:
            if Selection.isSelected(item['node']):
                self._gl.glStencilMask(0xff)
                self._renderItem(item)
                self._gl.glStencilMask(0)
            else:
                self._renderItem(item)

        self._gl.glStencilMask(0)
        self._gl.glStencilFunc(self._gl.GL_EQUAL, 0, 0xff)
        self._gl.glLineWidth(5)
        for node in Selection.getAllSelectedObjects():
            if node.getMeshData():
                self._renderItem({
                    'node': node,
                    'material': self._outline_material,
                    'mode': self._gl.GL_TRIANGLES,
                    'wireframe': True
                })

        self._gl.glLineWidth(1)

        self._gl.glDisable(self._gl.GL_STENCIL_TEST)
        self._gl.glDepthMask(self._gl.GL_FALSE)
        self._gl.glEnable(self._gl.GL_BLEND)
        self._gl.glBlendFunc(self._gl.GL_SRC_ALPHA, self._gl.GL_ONE_MINUS_SRC_ALPHA)

        for item in self._transparentQueue:
            self._renderItem(item)

        self._gl.glDisable(self._gl.GL_DEPTH_TEST)

        for item in self._overlayQueue:
            self._renderItem(item)

        self._scene.releaseLock()

    def endRendering(self):
        pass

    def _initialize(self):
        profile = QOpenGLVersionProfile()
        profile.setVersion(2, 0)
        self._gl = QOpenGLContext.currentContext().versionFunctions(profile)
        self._gl.initializeOpenGLFunctions()

        self._defaultMaterial = self.createMaterial(
                                     Resources.getPath(Resources.ShadersLocation, 'default.vert'),
                                     Resources.getPath(Resources.ShadersLocation, 'default.frag')
                                )

        self._defaultMaterial.setUniformValue("u_ambientColor", [0.3, 0.3, 0.3, 1.0])
        self._defaultMaterial.setUniformValue("u_diffuseColor", [0.5, 0.5, 0.5, 1.0])
        self._defaultMaterial.setUniformValue("u_specularColor", [1.0, 1.0, 1.0, 1.0])
        self._defaultMaterial.setUniformValue("u_shininess", 50.0)

        self._selection_buffer = self.createFrameBuffer(128, 128)
        self._selection_material = self.createMaterial(
                                        Resources.getPath(Resources.ShadersLocation, 'basic.vert'),
                                        Resources.getPath(Resources.ShadersLocation, 'color.frag')
                                   )

        self._outline_material = self.createMaterial(
                                      Resources.getPath(Resources.ShadersLocation, 'outline.vert'),
                                       Resources.getPath(Resources.ShadersLocation, 'outline.frag')
                                 )

        self._initialized = True

    def _renderItem(self, item):
        node = item['node']
        mesh = item.get('mesh', node.getMeshData())
        transform = node.getGlobalTransformation()
        material = item['material']
        mode = item['mode']
        wireframe = item.get('wireframe', False)

        material.bind()
        material.setUniformValue("u_projectionMatrix", self._camera.getProjectionMatrix(), cache = False)
        material.setUniformValue("u_viewMatrix", self._camera.getGlobalTransformation().getInverse(), cache = False)
        material.setUniformValue("u_viewPosition", self._camera.getGlobalPosition(), cache = False)
        material.setUniformValue("u_modelMatrix", transform, cache = False)
        material.setUniformValue("u_lightPosition", self._camera.getGlobalPosition(), cache = False)

        if mesh.hasNormals():
            normalMatrix = transform
            normalMatrix.setRow(3, [0, 0, 0, 1])
            normalMatrix.setColumn(3, [0, 0, 0, 1])
            normalMatrix = normalMatrix.getInverse().getTransposed()
            material.setUniformValue("u_normalMatrix", normalMatrix, cache = False)

        try:
            vertexBuffer = getattr(mesh, vertexBufferProperty)
        except AttributeError:
            vertexBuffer =  self._createVertexBuffer(mesh)
        vertexBuffer.bind()

        if mesh.hasIndices():
            try:
                indexBuffer = getattr(mesh, indexBufferProperty)
            except AttributeError:
                indexBuffer = self._createIndexBuffer(mesh)
            indexBuffer.bind()

        material.enableAttribute("a_vertex", Vector, 0)

        if mesh.hasNormals():
            material.enableAttribute("a_normal", Vector, mesh.getVertexCount() * 3 * 4)

        if wireframe and hasattr(self._gl, 'glPolygonMode'):
            self._gl.glPolygonMode(self._gl.GL_FRONT_AND_BACK, self._gl.GL_LINE)

        if mesh.hasIndices():
            self._gl.glDrawElements(mode, mesh.getFaceCount() * 3 , self._gl.GL_UNSIGNED_INT, None)
        else:
            self._gl.glDrawArrays(mode, 0, mesh.getVertexCount())

        if wireframe and hasattr(self._gl, 'glPolygonMode'):
            self._gl.glPolygonMode(self._gl.GL_FRONT_AND_BACK, self._gl.GL_FILL)

        material.disableAttribute("a_vertex")
        material.disableAttribute("a_normal")
        vertexBuffer.release()

        if mesh.hasIndices():
            indexBuffer.release()

        material.release()

    def _createVertexBuffer(self, mesh):
        buffer = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer)
        buffer.create()
        buffer.bind()
        vertices = mesh.getVerticesAsByteArray()

        if mesh.hasNormals():
            normals = mesh.getNormalsAsByteArray()
            ##Number of vertices * number of components (3 for vertex, 3 for normal) * size of 32-bit float (4)
            buffer.allocate(mesh.getVertexCount() * 6 * 4)
            buffer.write(0, vertices, len(vertices))
            buffer.write(len(vertices), normals, len(normals))
        else:
            buffer.allocate(vertices, mesh.getVertexCount() * 3 * 4)
        buffer.release()

        setattr(mesh, vertexBufferProperty, buffer)
        return buffer

    def _createIndexBuffer(self, mesh):
        buffer = QOpenGLBuffer(QOpenGLBuffer.IndexBuffer)
        buffer.create()
        buffer.bind()

        data = mesh.getIndicesAsByteArray()
        buffer.allocate(data, len(data))
        buffer.release()

        setattr(mesh, indexBufferProperty, buffer)
        return buffer

    def _getObjectColor(self, node):
        obj_id = id(node)
        r = (obj_id & 0xff000000) >> 24
        g = (obj_id & 0x00ff0000) >> 16
        b = (obj_id & 0x0000ff00) >> 8
        a = (obj_id & 0x000000ff) >> 0
        return (r, g, b, a)
<|endoftext|>


<commit_before># Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.

from UM.Settings.Validators.IntValidator import IntValidator
from UM.Settings.Validators.FloatValidator import FloatValidator
from UM.Settings.Validators.ResultCodes import ResultCodes
from PyQt5.QtCore import QCoreApplication
from UM.Signal import Signal, SignalEmitter
from UM.Logger import Logger

import math
import ast
from copy import deepcopy

##  Raised when an inheritance function tries to use a blacklisted method.
class IllegalMethodError(Exception):
    pass

##    A setting object contains a (single) configuration setting.
#     Settings have validators that check if the value is valid, but do not prevent invalid values!
#     Settings have conditions that enable/disable this setting depending on other settings. (Ex: Dual-extrusion)
class Setting(SignalEmitter):
    def __init__(self, key, catalog, **kwargs):
        super().__init__()
        self._key = key
        self._i18n_catalog = catalog
        self._label = kwargs.get("label", key)
        self._description = kwargs.get("description", "")
        self._default_value = kwargs.get("default", None)
        self._value = kwargs.get("value", None)
        self._type = kwargs.get("type", "string")
        self._visible = kwargs.get("visible", True)
        self._validator = None
        self._parent = None
        self._hide_if_all_children_visible = True
        self._children = []
        self._category = kwargs.get("category", None)
        self._active = True
        self._machine_settings = self._category.getParent() if self._category else None
        self._active_if_setting = None
        self._active_if_value = None
        self._options = kwargs.get("options", [])
        self._unit = kwargs.get("unit", "")
        self._inherit = True
        self._inherit_function = None

    valueChanged = Signal()

    ##  Triggered when all settings are loaded and the setting has a conditional param
    def activeIfHandler(self):
        setting = self._machine_settings.getSettingByKey(self._active_if_setting)
        if setting is not None:
            setting.valueChanged.connect(self.conditionalActiveHandler)
            self.conditionalActiveHandler(setting)
        else:
            Logger.log("w", "Unknown active_if setting %s", self._active_if_setting)
            # Check current value and update active status. (It can happen that the default value is that it's not active)
            #self.setActive(str(self._machine_settings.getSettingValueByKey(self._active_if_setting)) == str(self._active_if_value))

    #   Triggered when the setting it's dependant on changes it's value
    def conditionalActiveHandler(self, setting):
        self.setActive(setting.getValue() == self._active_if_value)

    def isActive(self):
        return self._active
        #if self._parent != None:
        #   if self._parent.isActive() == True:
        #        return self._active
        #else:
        #    return self._active
        #return False

    def setActive(self, active):
        if self._active != active:
            self._active = active
            self.activeChanged.emit(self)

    activeChanged = Signal()

    ##  Bind new validator to object based on it's current type
    def bindValidator(self):
        if self._type == "float":
            FloatValidator(self) # Validator sets itself as validator to this setting
        elif self._type == "int":
            IntValidator(self)

    ##  Get the depth of this setting (how many steps is it 'away' from its category)
    def getDepth(self):
        return self._parent.getDepth() + 1

    ##  Set values of the setting by providing it with a dict object (as decoded by JSON parser)
    #   \param data Decoded JSON dict
    def fillByDict(self, data):
        if "default" in data:
            self._default_value = data["default"]

        if "type" in data:
            self._type = data["type"]

        self.bindValidator()

        if "label" in data:
            self._label = self._i18n_catalog.i18nc("{0} label".format(self._key), data["label"])

        if "description" in data:
            self._description = self._i18n_catalog.i18nc("{0} description".format(self._key), data["description"])

        if "warning_description" in data:
            self._warning_description = self._i18n_catalog.i18nc("{0} warning description".format(self._key), data["warning_description"])

        if "error_description" in data:
            self._error_description = self._i18n_catalog.i18nc("{0} error description".format(self._key), data["error_description"])
        if "visible" in data:
            self.setVisible(data["visible"])

        if "always_visible" in data:
            self._hide_if_all_children_visible = not data["always_visible"]

        if "unit" in data:
            self._unit = data["unit"]

        self._inherit = data.get("inherit", True)

        if "inherit_function" in data:
            try:
                tree = ast.parse(data["inherit_function"], "eval")
                names = _SettingExpressionVisitor().visit(tree)
                code = compile(data["inherit_function"], self._key, "eval")
            except (SyntaxError, TypeError) as e:
                Logger.log("e", "Parse error in inherit function for setting {0}: {1}".format(self._key, str(e)))
            except IllegalMethodError as e:
                Logger.log("e", "Use of illegal method {0} in inherit function for setting {1}".format(str(e), self._key))
            else:
                self._inherit_function = self._createInheritFunction(code, names)

        min_value = None
        max_value = None
        min_value_warning = None
        max_value_warning = None
        if "min_value" in data:
            min_value = data["min_value"]
        if "max_value" in data:
            max_value = data["max_value"]
        if "min_value_warning" in data:
            min_value_warning = data["min_value_warning"]
        if "max_value_warning" in data:
            max_value_warning = data["max_value_warning"]
        if  self.getValidator() is not None: #Strings don"t have validators as of yet
            self.getValidator().setRange(min_value,max_value,min_value_warning,max_value_warning)

        if "active_if" in data:
            if "setting" in data["active_if"] and "value" in data["active_if"]:
                self._active_if_setting = data["active_if"]["setting"]
                self._active_if_value = data["active_if"]["value"]
                self._machine_settings.settingsLoaded.connect(self.activeIfHandler)

        if "options" in data:
            self._options = data["options"]

        if "children" in data:
            for key, value in data["children"].items():
                setting = self.getSettingByKey(key)
                if not setting:
                    setting = Setting(key, self._i18n_catalog)
                    setting.setCategory(self._category)
                    setting.setParent(self)
                    setting.visibleChanged.connect(self._onChildVisibileChanged)
                    self._children.append(setting)

                setting.fillByDict(value)

    ##  Return the values this setting can have (needs to be set if this is setting is an enum!)
    def getOptions(self):
        return self._options

    ##  Set the validator of the Setting
    #   \param validator Validator
    def setValidator(self, validator):
        self._validator = validator

    ##  Get the validator
    #   \returns Validator
    def getValidator(self):
        return self._validator

    ##  Get the category of this setting.
    #   \returns SettingCategory
    def getCategory(self):
        return self._category

    ##  Set the category
    #   \params category SettingCategory
    def setCategory(self, category):
        self._category = category
        self._machine_settings = category.getParent() if category else None
        for child in self._children:
            child.setCategory(category)

    ##  Set the parent of this setting. Parents can override the value of the setting if the child setting is not visible.
    #   mostly used for giving a 'global' setting (such as speed), with children being travel speed, infill speed, etc.
    #   \param setting Setting
    def setParent(self, setting):
        self._parent = setting

    ##  Get the parent.
    #   \returns Setting
    def getParent(self):
        return self._parent

    ##  Add a child to this setting. See setParent for more info.
    #   \param setting Setting
    def addChild(self, setting):
        setting.setParent(self)
        self._children.append(setting)

    ##  Recursively check it's children to see if the key matches.
    #   \returns Setting if key match is found, None otherwise.
    def getSettingByKey(self, key):
        if self._key == key:
            return self
        for s in self._children:
            ret = s.getSettingByKey(key)
            if ret is not None:
                return ret
        return None

    ##  Set the default value of the setting.
    #   \param value
    def setDefaultValue(self, value):
        self._default_value = value
        return self

    ##  get the default value of the setting.
    #   \returns default_value
    def getDefaultValue(self):
        return self._default_value

    ##  Set the visibility of this setting. See setParent for more info.
    #   \param visible Bool
    def setVisible(self, visible):
        if visible != self._visible:
            self._visible = visible
            self.visibleChanged.emit(self)

    ##  Check if the setting is visible. It can be that the setting visible is true,
    #   but it still should be invisible as all it's children are visible (at this point this setting is overiden by its children
    #   changing it does nothing, so it needs to be hidden!)
    #   The value is also hidden if it's not active (due to condition (some properties are active based on values of other settings)
    #   \returns bool
    def isVisible(self):
        if not self._visible:
            return False
        if self._hide_if_all_children_visible and self.checkAllChildrenVisible():
            return False
        return True

    visibleChanged = Signal()

    ##  Check if all children are visible.
    #   \returns bool True if all children are visible. False otherwise
    def checkAllChildrenVisible(self):
        if len(self._children) < 1:
            return False
        for child in self._children:
            if not child.isVisible() and not child.checkAllChildrenVisible():
                return False
        return True

    ##  Set the range of the setting. The validator will give errors or warnings if these are met.
    #   See Validator for more info
    def setRange(self, min_value = None, max_value = None, min_value_warning = None, max_value_warning = None):
        if(self._validator is None):
            return
        self._validator.setRange(min_value, max_value, min_value_warning, max_value_warning)

    ##  Get the display name of the setting
    def getLabel(self):
        if self._label is None:
            return self._key # Return key so it will always have some sort of display name

        return self._label

    ##  Set the label (display name) of setting.
    #   \param label
    def setLabel(self, label):
        self._label = label

    ##  Get the description of this setting.
    #
    #   This will return a string with a description of this setting, if provided by the setting file.
    def getDescription(self):
        return self._i18n_catalog.i18n(self._description)

    ##  Get the identifier of the setting
    def getKey(self):
        return self._key

    ## Get the type of the setting
    # \returns type
    def getType(self):
        return self._type

    def getUnit(self):
        return self._unit

    ##  Get the effective value of the setting. This can be 'overriden' by a parent function if this function is invisible.
    #   \returns value
    def getValue(self):
        if not self._visible:
            if self._inherit and self._parent and type(self._parent) is Setting:
                if self._inherit_function:
                    try:
                        inherit_value = self._inherit_function(self._parent, self._machine_settings)
                    except Exception as e:
                        Logger.log("e", "An error occurred in inherit function for {0}: {1}".format(self._key, str(e)))
                    else:
                        self.setValue(inherit_value)
                else:
                    self.setValue(self._parent.getValue())

        retval = self._value
        if self._value is None:
            retval = self._default_value

        return retval

    ##  Set the value of this setting and emit valueChanged signal
    #   \param value Value to be set.
    def setValue(self, value):
        if self._value != value:
            # Strings and enums are stored as strings, do not try to parse them.
            # In addition, if we get a non-string type, also do not try to parse it.
            if type(value) is str and self._type != "string" and self._type != "enum":
                try:
                    self._value = ast.literal_eval(value)
                except SyntaxError:
                    self._value = value
            else:
                self._value = value
            self.valueChanged.emit(self)

    ##  Validate the value of this setting.
    #   \returns ResultCodes.succes if there is no validator or if validation is succesfull. Returns warning or error code otherwise.
    def validate(self):
        if self._validator is not None:
            return self._validator.validate()
        else:
            return ResultCodes.succes

    def getAllChildren(self):
        all_children = []
        for s in self._children:
            all_children.append(s)
            all_children.extend(s.getAllChildren())
        return all_children

    def getChildren(self):
        return self._children

    def __repr__(self):
        return "<Setting: %s>" % (self._key)

    def __deepcopy__(self, memo):
        copy = Setting(
            self._key,
            self._i18n_catalog,
            label = self._label,
            description = self._description,
            default = self._default_value,
            value = self._value,
            type = self._type,
            visible = self._visible,
            category = self._category,
            unit = self._unit
        )

        copy._validator = self._validator
        copy._parent = self._parent
        copy._hide_if_all_children_visible = self._hide_if_all_children_visible
        copy._children = deepcopy(self._children, memo)
        copy._active = self._active
        copy._machine_settings = self._machine_settings
        copy._active_if_setting = self._active_if_setting
        copy._active_if_value = self._active_if_value
        copy._options = deepcopy(self._options, memo)
        copy._inherit = self._inherit
        copy._inherit_function = deepcopy(self._inherit_function, memo)

        copy._fixChildren()

        return copy

    ## private:

    # Fixes parent assignment after a deepcopy operation
    def _fixChildren(self):
        for child in self._children:
            child.setParent(self)
            child._fixChildren()

    def _onChildVisibileChanged(self, setting):
        self.visibleChanged.emit(setting)
        self.visibleChanged.emit(self)

    # Create a function that will run \param code, making the names in \param names available as local variables
    def _createInheritFunction(self, code, names):
        def inherit(parent, settings, c = code, n = names):
            locals = {
                "parent_value": parent.getValue(),
                "settings": settings
            }

            for name in names:
                locals[name] = settings.getSettingValueByKey(name)

            return eval(c, globals(), locals)

        return inherit

# Private AST visitor class used to look up names in the inheritance functions.
class _SettingExpressionVisitor(ast.NodeVisitor):
    def __init__(self):
        super().__init__()
        self.names = []

    def visit(self, node):
        super().visit(node)
        return self.names

    def visit_Name(self, node): # [CodeStyle: ast.NodeVisitor requires this function name]
        if node.id in self._blacklist:
            raise IllegalMethodError(node.id)

        if node.id not in self._knownNames and node.id not in __builtins__:
            self.names.append(node.id)

    _knownNames = [
        "parent_value",
        "math"
    ]

    _blacklist = [
        "sys",
        "os",
        "import",
        "__import__"
    ]
<commit_msg>Make it possible to translate units in machine definitions<commit_after># Copyright (c) 2015 Ultimaker B.V.
# Uranium is released under the terms of the AGPLv3 or higher.

from UM.Settings.Validators.IntValidator import IntValidator
from UM.Settings.Validators.FloatValidator import FloatValidator
from UM.Settings.Validators.ResultCodes import ResultCodes
from PyQt5.QtCore import QCoreApplication
from UM.Signal import Signal, SignalEmitter
from UM.Logger import Logger

import math
import ast
from copy import deepcopy

##  Raised when an inheritance function tries to use a blacklisted method.
class IllegalMethodError(Exception):
    pass

##    A setting object contains a (single) configuration setting.
#     Settings have validators that check if the value is valid, but do not prevent invalid values!
#     Settings have conditions that enable/disable this setting depending on other settings. (Ex: Dual-extrusion)
class Setting(SignalEmitter):
    def __init__(self, key, catalog, **kwargs):
        super().__init__()
        self._key = key
        self._i18n_catalog = catalog
        self._label = kwargs.get("label", key)
        self._description = kwargs.get("description", "")
        self._default_value = kwargs.get("default", None)
        self._value = kwargs.get("value", None)
        self._type = kwargs.get("type", "string")
        self._visible = kwargs.get("visible", True)
        self._validator = None
        self._parent = None
        self._hide_if_all_children_visible = True
        self._children = []
        self._category = kwargs.get("category", None)
        self._active = True
        self._machine_settings = self._category.getParent() if self._category else None
        self._active_if_setting = None
        self._active_if_value = None
        self._options = kwargs.get("options", [])
        self._unit = kwargs.get("unit", "")
        self._inherit = True
        self._inherit_function = None

    valueChanged = Signal()

    ##  Triggered when all settings are loaded and the setting has a conditional param
    def activeIfHandler(self):
        setting = self._machine_settings.getSettingByKey(self._active_if_setting)
        if setting is not None:
            setting.valueChanged.connect(self.conditionalActiveHandler)
            self.conditionalActiveHandler(setting)
        else:
            Logger.log("w", "Unknown active_if setting %s", self._active_if_setting)
            # Check current value and update active status. (It can happen that the default value is that it's not active)
            #self.setActive(str(self._machine_settings.getSettingValueByKey(self._active_if_setting)) == str(self._active_if_value))

    #   Triggered when the setting it's dependant on changes it's value
    def conditionalActiveHandler(self, setting):
        self.setActive(setting.getValue() == self._active_if_value)

    def isActive(self):
        return self._active
        #if self._parent != None:
        #   if self._parent.isActive() == True:
        #        return self._active
        #else:
        #    return self._active
        #return False

    def setActive(self, active):
        if self._active != active:
            self._active = active
            self.activeChanged.emit(self)

    activeChanged = Signal()

    ##  Bind new validator to object based on it's current type
    def bindValidator(self):
        if self._type == "float":
            FloatValidator(self) # Validator sets itself as validator to this setting
        elif self._type == "int":
            IntValidator(self)

    ##  Get the depth of this setting (how many steps is it 'away' from its category)
    def getDepth(self):
        return self._parent.getDepth() + 1

    ##  Set values of the setting by providing it with a dict object (as decoded by JSON parser)
    #   \param data Decoded JSON dict
    def fillByDict(self, data):
        if "default" in data:
            self._default_value = data["default"]

        if "type" in data:
            self._type = data["type"]

        self.bindValidator()

        if "label" in data:
            self._label = self._i18n_catalog.i18nc("{0} label".format(self._key), data["label"])

        if "description" in data:
            self._description = self._i18n_catalog.i18nc("{0} description".format(self._key), data["description"])

        if "warning_description" in data:
            self._warning_description = self._i18n_catalog.i18nc("{0} warning description".format(self._key), data["warning_description"])

        if "error_description" in data:
            self._error_description = self._i18n_catalog.i18nc("{0} error description".format(self._key), data["error_description"])

        if "unit" in data:
            self._unit = self._i18n_catalog.i18nc("{0} unit".format(self._key), data["unit"])

        if "visible" in data:
            self.setVisible(data["visible"])

        if "always_visible" in data:
            self._hide_if_all_children_visible = not data["always_visible"]

        self._inherit = data.get("inherit", True)

        if "inherit_function" in data:
            try:
                tree = ast.parse(data["inherit_function"], "eval")
                names = _SettingExpressionVisitor().visit(tree)
                code = compile(data["inherit_function"], self._key, "eval")
            except (SyntaxError, TypeError) as e:
                Logger.log("e", "Parse error in inherit function for setting {0}: {1}".format(self._key, str(e)))
            except IllegalMethodError as e:
                Logger.log("e", "Use of illegal method {0} in inherit function for setting {1}".format(str(e), self._key))
            else:
                self._inherit_function = self._createInheritFunction(code, names)

        min_value = None
        max_value = None
        min_value_warning = None
        max_value_warning = None
        if "min_value" in data:
            min_value = data["min_value"]
        if "max_value" in data:
            max_value = data["max_value"]
        if "min_value_warning" in data:
            min_value_warning = data["min_value_warning"]
        if "max_value_warning" in data:
            max_value_warning = data["max_value_warning"]
        if  self.getValidator() is not None: #Strings don"t have validators as of yet
            self.getValidator().setRange(min_value,max_value,min_value_warning,max_value_warning)

        if "active_if" in data:
            if "setting" in data["active_if"] and "value" in data["active_if"]:
                self._active_if_setting = data["active_if"]["setting"]
                self._active_if_value = data["active_if"]["value"]
                self._machine_settings.settingsLoaded.connect(self.activeIfHandler)

        if "options" in data:
            self._options = data["options"]

        if "children" in data:
            for key, value in data["children"].items():
                setting = self.getSettingByKey(key)
                if not setting:
                    setting = Setting(key, self._i18n_catalog)
                    setting.setCategory(self._category)
                    setting.setParent(self)
                    setting.visibleChanged.connect(self._onChildVisibileChanged)
                    self._children.append(setting)

                setting.fillByDict(value)

    ##  Return the values this setting can have (needs to be set if this is setting is an enum!)
    def getOptions(self):
        return self._options

    ##  Set the validator of the Setting
    #   \param validator Validator
    def setValidator(self, validator):
        self._validator = validator

    ##  Get the validator
    #   \returns Validator
    def getValidator(self):
        return self._validator

    ##  Get the category of this setting.
    #   \returns SettingCategory
    def getCategory(self):
        return self._category

    ##  Set the category
    #   \params category SettingCategory
    def setCategory(self, category):
        self._category = category
        self._machine_settings = category.getParent() if category else None
        for child in self._children:
            child.setCategory(category)

    ##  Set the parent of this setting. Parents can override the value of the setting if the child setting is not visible.
    #   mostly used for giving a 'global' setting (such as speed), with children being travel speed, infill speed, etc.
    #   \param setting Setting
    def setParent(self, setting):
        self._parent = setting

    ##  Get the parent.
    #   \returns Setting
    def getParent(self):
        return self._parent

    ##  Add a child to this setting. See setParent for more info.
    #   \param setting Setting
    def addChild(self, setting):
        setting.setParent(self)
        self._children.append(setting)

    ##  Recursively check it's children to see if the key matches.
    #   \returns Setting if key match is found, None otherwise.
    def getSettingByKey(self, key):
        if self._key == key:
            return self
        for s in self._children:
            ret = s.getSettingByKey(key)
            if ret is not None:
                return ret
        return None

    ##  Set the default value of the setting.
    #   \param value
    def setDefaultValue(self, value):
        self._default_value = value
        return self

    ##  get the default value of the setting.
    #   \returns default_value
    def getDefaultValue(self):
        return self._default_value

    ##  Set the visibility of this setting. See setParent for more info.
    #   \param visible Bool
    def setVisible(self, visible):
        if visible != self._visible:
            self._visible = visible
            self.visibleChanged.emit(self)

    ##  Check if the setting is visible. It can be that the setting visible is true,
    #   but it still should be invisible as all it's children are visible (at this point this setting is overiden by its children
    #   changing it does nothing, so it needs to be hidden!)
    #   The value is also hidden if it's not active (due to condition (some properties are active based on values of other settings)
    #   \returns bool
    def isVisible(self):
        if not self._visible:
            return False
        if self._hide_if_all_children_visible and self.checkAllChildrenVisible():
            return False
        return True

    visibleChanged = Signal()

    ##  Check if all children are visible.
    #   \returns bool True if all children are visible. False otherwise
    def checkAllChildrenVisible(self):
        if len(self._children) < 1:
            return False
        for child in self._children:
            if not child.isVisible() and not child.checkAllChildrenVisible():
                return False
        return True

    ##  Set the range of the setting. The validator will give errors or warnings if these are met.
    #   See Validator for more info
    def setRange(self, min_value = None, max_value = None, min_value_warning = None, max_value_warning = None):
        if(self._validator is None):
            return
        self._validator.setRange(min_value, max_value, min_value_warning, max_value_warning)

    ##  Get the display name of the setting
    def getLabel(self):
        if self._label is None:
            return self._key # Return key so it will always have some sort of display name

        return self._label

    ##  Set the label (display name) of setting.
    #   \param label
    def setLabel(self, label):
        self._label = label

    ##  Get the description of this setting.
    #
    #   This will return a string with a description of this setting, if provided by the setting file.
    def getDescription(self):
        return self._i18n_catalog.i18n(self._description)

    ##  Get the identifier of the setting
    def getKey(self):
        return self._key

    ## Get the type of the setting
    # \returns type
    def getType(self):
        return self._type

    def getUnit(self):
        return self._unit

    ##  Get the effective value of the setting. This can be 'overriden' by a parent function if this function is invisible.
    #   \returns value
    def getValue(self):
        if not self._visible:
            if self._inherit and self._parent and type(self._parent) is Setting:
                if self._inherit_function:
                    try:
                        inherit_value = self._inherit_function(self._parent, self._machine_settings)
                    except Exception as e:
                        Logger.log("e", "An error occurred in inherit function for {0}: {1}".format(self._key, str(e)))
                    else:
                        self.setValue(inherit_value)
                else:
                    self.setValue(self._parent.getValue())

        retval = self._value
        if self._value is None:
            retval = self._default_value

        return retval

    ##  Set the value of this setting and emit valueChanged signal
    #   \param value Value to be set.
    def setValue(self, value):
        if self._value != value:
            # Strings and enums are stored as strings, do not try to parse them.
            # In addition, if we get a non-string type, also do not try to parse it.
            if type(value) is str and self._type != "string" and self._type != "enum":
                try:
                    self._value = ast.literal_eval(value)
                except SyntaxError:
                    self._value = value
            else:
                self._value = value
            self.valueChanged.emit(self)

    ##  Validate the value of this setting.
    #   \returns ResultCodes.succes if there is no validator or if validation is succesfull. Returns warning or error code otherwise.
    def validate(self):
        if self._validator is not None:
            return self._validator.validate()
        else:
            return ResultCodes.succes

    def getAllChildren(self):
        all_children = []
        for s in self._children:
            all_children.append(s)
            all_children.extend(s.getAllChildren())
        return all_children

    def getChildren(self):
        return self._children

    def __repr__(self):
        return "<Setting: %s>" % (self._key)

    def __deepcopy__(self, memo):
        copy = Setting(
            self._key,
            self._i18n_catalog,
            label = self._label,
            description = self._description,
            default = self._default_value,
            value = self._value,
            type = self._type,
            visible = self._visible,
            category = self._category,
            unit = self._unit
        )

        copy._validator = self._validator
        copy._parent = self._parent
        copy._hide_if_all_children_visible = self._hide_if_all_children_visible
        copy._children = deepcopy(self._children, memo)
        copy._active = self._active
        copy._machine_settings = self._machine_settings
        copy._active_if_setting = self._active_if_setting
        copy._active_if_value = self._active_if_value
        copy._options = deepcopy(self._options, memo)
        copy._inherit = self._inherit
        copy._inherit_function = deepcopy(self._inherit_function, memo)

        copy._fixChildren()

        return copy

    ## private:

    # Fixes parent assignment after a deepcopy operation
    def _fixChildren(self):
        for child in self._children:
            child.setParent(self)
            child._fixChildren()

    def _onChildVisibileChanged(self, setting):
        self.visibleChanged.emit(setting)
        self.visibleChanged.emit(self)

    # Create a function that will run \param code, making the names in \param names available as local variables
    def _createInheritFunction(self, code, names):
        def inherit(parent, settings, c = code, n = names):
            locals = {
                "parent_value": parent.getValue(),
                "settings": settings
            }

            for name in names:
                locals[name] = settings.getSettingValueByKey(name)

            return eval(c, globals(), locals)

        return inherit

# Private AST visitor class used to look up names in the inheritance functions.
class _SettingExpressionVisitor(ast.NodeVisitor):
    def __init__(self):
        super().__init__()
        self.names = []

    def visit(self, node):
        super().visit(node)
        return self.names

    def visit_Name(self, node): # [CodeStyle: ast.NodeVisitor requires this function name]
        if node.id in self._blacklist:
            raise IllegalMethodError(node.id)

        if node.id not in self._knownNames and node.id not in __builtins__:
            self.names.append(node.id)

    _knownNames = [
        "parent_value",
        "math"
    ]

    _blacklist = [
        "sys",
        "os",
        "import",
        "__import__"
    ]
<|endoftext|>


<commit_before>import json
import os
import subprocess
import tempfile
import os.path
import distutils.spawn, distutils.version
import numpy as np
from six import StringIO
import six
from gym import error, logger

def touch(path):
    open(path, 'a').close()

class VideoRecorder(object):
    """VideoRecorder renders a nice movie of a rollout, frame by frame. It
    comes with an `enabled` option so you can still use the same code
    on episodes where you don't want to record video.

    Note:
        You are responsible for calling `close` on a created
        VideoRecorder, or else you may leak an encoder process.

    Args:
        env (Env): Environment to take video of.
        path (Optional[str]): Path to the video file; will be randomly chosen if omitted.
        base_path (Optional[str]): Alternatively, path to the video file without extension, which will be added.
        metadata (Optional[dict]): Contents to save to the metadata file.
        enabled (bool): Whether to actually record video, or just no-op (for convenience)
    """

    def __init__(self, env, path=None, metadata=None, enabled=True, base_path=None):
        modes = env.metadata.get('render.modes', [])
        self._async = env.metadata.get('semantics.async')
        self.enabled = enabled

        # Don't bother setting anything else if not enabled
        if not self.enabled:
            return

        self.ansi_mode = False
        if 'rgb_array' not in modes:
            if 'ansi' in modes:
                self.ansi_mode = True
            else:
                logger.info('Disabling video recorder because {} neither supports video mode "rgb_array" nor "ansi".'.format(env))
                # Whoops, turns out we shouldn't be enabled after all
                self.enabled = False
                return

        if path is not None and base_path is not None:
            raise error.Error("You can pass at most one of `path` or `base_path`.")

        self.last_frame = None
        self.env = env

        required_ext = '.json' if self.ansi_mode else '.mp4'
        if path is None:
            if base_path is not None:
                # Base path given, append ext
                path = base_path + required_ext
            else:
                # Otherwise, just generate a unique filename
                with tempfile.NamedTemporaryFile(suffix=required_ext, delete=False) as f:
                    path = f.name
        self.path = path

        path_base, actual_ext = os.path.splitext(self.path)

        if actual_ext != required_ext:
            hint = " HINT: The environment is text-only, therefore we're recording its text output in a structured JSON format." if self.ansi_mode else ''
            raise error.Error("Invalid path given: {} -- must have file extension {}.{}".format(self.path, required_ext, hint))
        # Touch the file in any case, so we know it's present. (This
        # corrects for platform platform differences. Using ffmpeg on
        # OS X, the file is precreated, but not on Linux.
        touch(path)

        self.frames_per_sec = env.metadata.get('video.frames_per_second', 30)
        self.encoder = None # lazily start the process
        self.broken = False

        # Dump metadata
        self.metadata = metadata or {}
        self.metadata['content_type'] = 'video/vnd.openai.ansivid' if self.ansi_mode else 'video/mp4'
        self.metadata_path = '{}.meta.json'.format(path_base)
        self.write_metadata()

        logger.info('Starting new video recorder writing to %s', self.path)
        self.empty = True

    @property
    def functional(self):
        return self.enabled and not self.broken

    def capture_frame(self):
        """Render the given `env` and add the resulting frame to the video."""
        if not self.functional: return
        logger.debug('Capturing video frame: path=%s', self.path)

        render_mode = 'ansi' if self.ansi_mode else 'rgb_array'
        frame = self.env.render(mode=render_mode)

        if frame is None:
            if self._async:
                return
            else:
                # Indicates a bug in the environment: don't want to raise
                # an error here.
                logger.warn('Env returned None on render(). Disabling further rendering for video recorder by marking as disabled: path=%s metadata_path=%s', self.path, self.metadata_path)
                self.broken = True
        else:
            self.last_frame = frame
            if self.ansi_mode:
                self._encode_ansi_frame(frame)
            else:
                self._encode_image_frame(frame)

    def close(self):
        """Make sure to manually close, or else you'll leak the encoder process"""
        if not self.enabled:
            return

        if self.encoder:
            logger.debug('Closing video encoder: path=%s', self.path)
            self.encoder.close()
            self.encoder = None
        else:
            # No frames captured. Set metadata, and remove the empty output file.
            os.remove(self.path)

            if self.metadata is None:
                self.metadata = {}
            self.metadata['empty'] = True

        # If broken, get rid of the output file, otherwise we'd leak it.
        if self.broken:
            logger.info('Cleaning up paths for broken video recorder: path=%s metadata_path=%s', self.path, self.metadata_path)

            # Might have crashed before even starting the output file, don't try to remove in that case.
            if os.path.exists(self.path):
                os.remove(self.path)

            if self.metadata is None:
                self.metadata = {}
            self.metadata['broken'] = True

        self.write_metadata()

    def write_metadata(self):
        with open(self.metadata_path, 'w') as f:
            json.dump(self.metadata, f)

    def _encode_ansi_frame(self, frame):
        if not self.encoder:
            self.encoder = TextEncoder(self.path, self.frames_per_sec)
            self.metadata['encoder_version'] = self.encoder.version_info
        self.encoder.capture_frame(frame)
        self.empty = False

    def _encode_image_frame(self, frame):
        if not self.encoder:
            self.encoder = ImageEncoder(self.path, frame.shape, self.frames_per_sec)
            self.metadata['encoder_version'] = self.encoder.version_info

        try:
            self.encoder.capture_frame(frame)
        except error.InvalidFrame as e:
            logger.warn('Tried to pass invalid video frame, marking as broken: %s', e)
            self.broken = True
        else:
            self.empty = False

class TextEncoder(object):
    """Store a moving picture made out of ANSI frames. Format adapted from
    https://github.com/asciinema/asciinema/blob/master/doc/asciicast-v1.md"""

    def __init__(self, output_path, frames_per_sec):
        self.output_path = output_path
        self.frames_per_sec = frames_per_sec
        self.frames = []

    def capture_frame(self, frame):
        string = None
        if isinstance(frame, str):
            string = frame
        elif isinstance(frame, StringIO):
            string = frame.getvalue()
        else:
            raise error.InvalidFrame('Wrong type {} for {}: text frame must be a string or StringIO'.format(type(frame), frame))

        frame_bytes = string.encode('utf-8')

        if frame_bytes[-1:] != six.b('\n'):
            raise error.InvalidFrame('Frame must end with a newline: """{}"""'.format(string))

        if six.b('\r') in frame_bytes:
            raise error.InvalidFrame('Frame contains carriage returns (only newlines are allowed: """{}"""'.format(string))

        self.frames.append(frame_bytes)

    def close(self):
        #frame_duration = float(1) / self.frames_per_sec
        frame_duration = .5

        # Turn frames into events: clear screen beforehand
        # https://rosettacode.org/wiki/Terminal_control/Clear_the_screen#Python
        # https://rosettacode.org/wiki/Terminal_control/Cursor_positioning#Python
        clear_code = six.b("%c[2J\033[1;1H" % (27))
        # Decode the bytes as UTF-8 since JSON may only contain UTF-8
        events = [ (frame_duration, (clear_code+frame.replace(six.b('\n'),six.b('\r\n'))).decode('utf-8'))  for frame in self.frames ]

        # Calculate frame size from the largest frames.
        # Add some padding since we'll get cut off otherwise.
        height = max([frame.count(six.b('\n')) for frame in self.frames]) + 1
        width = max([max([len(line) for line in frame.split(six.b('\n'))]) for frame in self.frames]) + 2

        data = {
            "version": 1,
            "width": width,
            "height": height,
            "duration": len(self.frames)*frame_duration,
            "command": "-",
            "title": "gym VideoRecorder episode",
            "env": {}, # could add some env metadata here
            "stdout": events,
        }

        with open(self.output_path, 'w') as f:
            json.dump(data, f)

    @property
    def version_info(self):
        return {'backend':'TextEncoder','version':1}

class ImageEncoder(object):
    def __init__(self, output_path, frame_shape, frames_per_sec):
        self.proc = None
        self.output_path = output_path
        # Frame shape should be lines-first, so w and h are swapped
        h, w, pixfmt = frame_shape
        if pixfmt != 3 and pixfmt != 4:
            raise error.InvalidFrame("Your frame has shape {}, but we require (w,h,3) or (w,h,4), i.e. RGB values for a w-by-h image, with an optional alpha channl.".format(frame_shape))
        self.wh = (w,h)
        self.includes_alpha = (pixfmt == 4)
        self.frame_shape = frame_shape
        self.frames_per_sec = frames_per_sec

        if distutils.spawn.find_executable('avconv') is not None:
            self.backend = 'avconv'
        elif distutils.spawn.find_executable('ffmpeg') is not None:
            self.backend = 'ffmpeg'
        else:
            raise error.DependencyNotInstalled("""Found neither the ffmpeg nor avconv executables. On OS X, you can install ffmpeg via `brew install ffmpeg`. On most Ubuntu variants, `sudo apt-get install ffmpeg` should do it. On Ubuntu 14.04, however, you'll need to install avconv with `sudo apt-get install libav-tools`.""")

        self.start()

    @property
    def version_info(self):
        return {
            'backend':self.backend,
            'version':str(subprocess.check_output([self.backend, '-version'],
                                                  stderr=subprocess.STDOUT)),
            'cmdline':self.cmdline
        }

    def start(self):
        self.cmdline = (self.backend,
                     '-nostats',
                     '-loglevel', 'error', # suppress warnings
                     '-y',
                     '-r', '%d' % self.frames_per_sec,

                     # input
                     '-f', 'rawvideo',
                     '-s:v', '{}x{}'.format(*self.wh),
                     '-pix_fmt',('rgb32' if self.includes_alpha else 'rgb24'),
                     '-i', '-', # this used to be /dev/stdin, which is not Windows-friendly

                     # output
                     '-vcodec', 'libx264',
                     '-pix_fmt', 'yuv420p',
                     self.output_path
                     )

        logger.debug('Starting ffmpeg with "%s"', ' '.join(self.cmdline))
        if hasattr(os,'setsid'): #setsid not present on Windows
            self.proc = subprocess.Popen(self.cmdline, stdin=subprocess.PIPE, preexec_fn=os.setsid)
        else:
            self.proc = subprocess.Popen(self.cmdline, stdin=subprocess.PIPE)

    def capture_frame(self, frame):
        if not isinstance(frame, (np.ndarray, np.generic)):
            raise error.InvalidFrame('Wrong type {} for {} (must be np.ndarray or np.generic)'.format(type(frame), frame))
        if frame.shape != self.frame_shape:
            raise error.InvalidFrame("Your frame has shape {}, but the VideoRecorder is configured for shape {}.".format(frame.shape, self.frame_shape))
        if frame.dtype != np.uint8:
            raise error.InvalidFrame("Your frame has data type {}, but we require uint8 (i.e. RGB values from 0-255).".format(frame.dtype))

        if distutils.version.LooseVersion(np.__version__) >= distutils.version.LooseVersion('1.9.0'):
            self.proc.stdin.write(frame.tobytes())
        else:
            self.proc.stdin.write(frame.tostring())

    def close(self):
        self.proc.stdin.close()
        ret = self.proc.wait()
        if ret != 0:
            logger.error("VideoRecorder encoder exited with status {}".format(ret))
<commit_msg>Fix to enable use with odd image dimensions (#1101)<commit_after>import json
import os
import subprocess
import tempfile
import os.path
import distutils.spawn, distutils.version
import numpy as np
from six import StringIO
import six
from gym import error, logger

def touch(path):
    open(path, 'a').close()

class VideoRecorder(object):
    """VideoRecorder renders a nice movie of a rollout, frame by frame. It
    comes with an `enabled` option so you can still use the same code
    on episodes where you don't want to record video.

    Note:
        You are responsible for calling `close` on a created
        VideoRecorder, or else you may leak an encoder process.

    Args:
        env (Env): Environment to take video of.
        path (Optional[str]): Path to the video file; will be randomly chosen if omitted.
        base_path (Optional[str]): Alternatively, path to the video file without extension, which will be added.
        metadata (Optional[dict]): Contents to save to the metadata file.
        enabled (bool): Whether to actually record video, or just no-op (for convenience)
    """

    def __init__(self, env, path=None, metadata=None, enabled=True, base_path=None):
        modes = env.metadata.get('render.modes', [])
        self._async = env.metadata.get('semantics.async')
        self.enabled = enabled

        # Don't bother setting anything else if not enabled
        if not self.enabled:
            return

        self.ansi_mode = False
        if 'rgb_array' not in modes:
            if 'ansi' in modes:
                self.ansi_mode = True
            else:
                logger.info('Disabling video recorder because {} neither supports video mode "rgb_array" nor "ansi".'.format(env))
                # Whoops, turns out we shouldn't be enabled after all
                self.enabled = False
                return

        if path is not None and base_path is not None:
            raise error.Error("You can pass at most one of `path` or `base_path`.")

        self.last_frame = None
        self.env = env

        required_ext = '.json' if self.ansi_mode else '.mp4'
        if path is None:
            if base_path is not None:
                # Base path given, append ext
                path = base_path + required_ext
            else:
                # Otherwise, just generate a unique filename
                with tempfile.NamedTemporaryFile(suffix=required_ext, delete=False) as f:
                    path = f.name
        self.path = path

        path_base, actual_ext = os.path.splitext(self.path)

        if actual_ext != required_ext:
            hint = " HINT: The environment is text-only, therefore we're recording its text output in a structured JSON format." if self.ansi_mode else ''
            raise error.Error("Invalid path given: {} -- must have file extension {}.{}".format(self.path, required_ext, hint))
        # Touch the file in any case, so we know it's present. (This
        # corrects for platform platform differences. Using ffmpeg on
        # OS X, the file is precreated, but not on Linux.
        touch(path)

        self.frames_per_sec = env.metadata.get('video.frames_per_second', 30)
        self.encoder = None # lazily start the process
        self.broken = False

        # Dump metadata
        self.metadata = metadata or {}
        self.metadata['content_type'] = 'video/vnd.openai.ansivid' if self.ansi_mode else 'video/mp4'
        self.metadata_path = '{}.meta.json'.format(path_base)
        self.write_metadata()

        logger.info('Starting new video recorder writing to %s', self.path)
        self.empty = True

    @property
    def functional(self):
        return self.enabled and not self.broken

    def capture_frame(self):
        """Render the given `env` and add the resulting frame to the video."""
        if not self.functional: return
        logger.debug('Capturing video frame: path=%s', self.path)

        render_mode = 'ansi' if self.ansi_mode else 'rgb_array'
        frame = self.env.render(mode=render_mode)

        if frame is None:
            if self._async:
                return
            else:
                # Indicates a bug in the environment: don't want to raise
                # an error here.
                logger.warn('Env returned None on render(). Disabling further rendering for video recorder by marking as disabled: path=%s metadata_path=%s', self.path, self.metadata_path)
                self.broken = True
        else:
            self.last_frame = frame
            if self.ansi_mode:
                self._encode_ansi_frame(frame)
            else:
                self._encode_image_frame(frame)

    def close(self):
        """Make sure to manually close, or else you'll leak the encoder process"""
        if not self.enabled:
            return

        if self.encoder:
            logger.debug('Closing video encoder: path=%s', self.path)
            self.encoder.close()
            self.encoder = None
        else:
            # No frames captured. Set metadata, and remove the empty output file.
            os.remove(self.path)

            if self.metadata is None:
                self.metadata = {}
            self.metadata['empty'] = True

        # If broken, get rid of the output file, otherwise we'd leak it.
        if self.broken:
            logger.info('Cleaning up paths for broken video recorder: path=%s metadata_path=%s', self.path, self.metadata_path)

            # Might have crashed before even starting the output file, don't try to remove in that case.
            if os.path.exists(self.path):
                os.remove(self.path)

            if self.metadata is None:
                self.metadata = {}
            self.metadata['broken'] = True

        self.write_metadata()

    def write_metadata(self):
        with open(self.metadata_path, 'w') as f:
            json.dump(self.metadata, f)

    def _encode_ansi_frame(self, frame):
        if not self.encoder:
            self.encoder = TextEncoder(self.path, self.frames_per_sec)
            self.metadata['encoder_version'] = self.encoder.version_info
        self.encoder.capture_frame(frame)
        self.empty = False

    def _encode_image_frame(self, frame):
        if not self.encoder:
            self.encoder = ImageEncoder(self.path, frame.shape, self.frames_per_sec)
            self.metadata['encoder_version'] = self.encoder.version_info

        try:
            self.encoder.capture_frame(frame)
        except error.InvalidFrame as e:
            logger.warn('Tried to pass invalid video frame, marking as broken: %s', e)
            self.broken = True
        else:
            self.empty = False

class TextEncoder(object):
    """Store a moving picture made out of ANSI frames. Format adapted from
    https://github.com/asciinema/asciinema/blob/master/doc/asciicast-v1.md"""

    def __init__(self, output_path, frames_per_sec):
        self.output_path = output_path
        self.frames_per_sec = frames_per_sec
        self.frames = []

    def capture_frame(self, frame):
        string = None
        if isinstance(frame, str):
            string = frame
        elif isinstance(frame, StringIO):
            string = frame.getvalue()
        else:
            raise error.InvalidFrame('Wrong type {} for {}: text frame must be a string or StringIO'.format(type(frame), frame))

        frame_bytes = string.encode('utf-8')

        if frame_bytes[-1:] != six.b('\n'):
            raise error.InvalidFrame('Frame must end with a newline: """{}"""'.format(string))

        if six.b('\r') in frame_bytes:
            raise error.InvalidFrame('Frame contains carriage returns (only newlines are allowed: """{}"""'.format(string))

        self.frames.append(frame_bytes)

    def close(self):
        #frame_duration = float(1) / self.frames_per_sec
        frame_duration = .5

        # Turn frames into events: clear screen beforehand
        # https://rosettacode.org/wiki/Terminal_control/Clear_the_screen#Python
        # https://rosettacode.org/wiki/Terminal_control/Cursor_positioning#Python
        clear_code = six.b("%c[2J\033[1;1H" % (27))
        # Decode the bytes as UTF-8 since JSON may only contain UTF-8
        events = [ (frame_duration, (clear_code+frame.replace(six.b('\n'),six.b('\r\n'))).decode('utf-8'))  for frame in self.frames ]

        # Calculate frame size from the largest frames.
        # Add some padding since we'll get cut off otherwise.
        height = max([frame.count(six.b('\n')) for frame in self.frames]) + 1
        width = max([max([len(line) for line in frame.split(six.b('\n'))]) for frame in self.frames]) + 2

        data = {
            "version": 1,
            "width": width,
            "height": height,
            "duration": len(self.frames)*frame_duration,
            "command": "-",
            "title": "gym VideoRecorder episode",
            "env": {}, # could add some env metadata here
            "stdout": events,
        }

        with open(self.output_path, 'w') as f:
            json.dump(data, f)

    @property
    def version_info(self):
        return {'backend':'TextEncoder','version':1}

class ImageEncoder(object):
    def __init__(self, output_path, frame_shape, frames_per_sec):
        self.proc = None
        self.output_path = output_path
        # Frame shape should be lines-first, so w and h are swapped
        h, w, pixfmt = frame_shape
        if pixfmt != 3 and pixfmt != 4:
            raise error.InvalidFrame("Your frame has shape {}, but we require (w,h,3) or (w,h,4), i.e. RGB values for a w-by-h image, with an optional alpha channl.".format(frame_shape))
        self.wh = (w,h)
        self.includes_alpha = (pixfmt == 4)
        self.frame_shape = frame_shape
        self.frames_per_sec = frames_per_sec

        if distutils.spawn.find_executable('avconv') is not None:
            self.backend = 'avconv'
        elif distutils.spawn.find_executable('ffmpeg') is not None:
            self.backend = 'ffmpeg'
        else:
            raise error.DependencyNotInstalled("""Found neither the ffmpeg nor avconv executables. On OS X, you can install ffmpeg via `brew install ffmpeg`. On most Ubuntu variants, `sudo apt-get install ffmpeg` should do it. On Ubuntu 14.04, however, you'll need to install avconv with `sudo apt-get install libav-tools`.""")

        self.start()

    @property
    def version_info(self):
        return {
            'backend':self.backend,
            'version':str(subprocess.check_output([self.backend, '-version'],
                                                  stderr=subprocess.STDOUT)),
            'cmdline':self.cmdline
        }

    def start(self):
        self.cmdline = (self.backend,
                     '-nostats',
                     '-loglevel', 'error', # suppress warnings
                     '-y',
                     '-r', '%d' % self.frames_per_sec,

                     # input
                     '-f', 'rawvideo',
                     '-s:v', '{}x{}'.format(*self.wh),
                     '-pix_fmt',('rgb32' if self.includes_alpha else 'rgb24'),
                     '-i', '-', # this used to be /dev/stdin, which is not Windows-friendly

                     # output
                     '-vf', 'scale=trunc(iw/2)*2:trunc(ih/2)*2',
                     '-vcodec', 'libx264',
                     '-pix_fmt', 'yuv420p',
                     self.output_path
                     )

        logger.debug('Starting ffmpeg with "%s"', ' '.join(self.cmdline))
        if hasattr(os,'setsid'): #setsid not present on Windows
            self.proc = subprocess.Popen(self.cmdline, stdin=subprocess.PIPE, preexec_fn=os.setsid)
        else:
            self.proc = subprocess.Popen(self.cmdline, stdin=subprocess.PIPE)

    def capture_frame(self, frame):
        if not isinstance(frame, (np.ndarray, np.generic)):
            raise error.InvalidFrame('Wrong type {} for {} (must be np.ndarray or np.generic)'.format(type(frame), frame))
        if frame.shape != self.frame_shape:
            raise error.InvalidFrame("Your frame has shape {}, but the VideoRecorder is configured for shape {}.".format(frame.shape, self.frame_shape))
        if frame.dtype != np.uint8:
            raise error.InvalidFrame("Your frame has data type {}, but we require uint8 (i.e. RGB values from 0-255).".format(frame.dtype))

        if distutils.version.LooseVersion(np.__version__) >= distutils.version.LooseVersion('1.9.0'):
            self.proc.stdin.write(frame.tobytes())
        else:
            self.proc.stdin.write(frame.tostring())

    def close(self):
        self.proc.stdin.close()
        ret = self.proc.wait()
        if ret != 0:
            logger.error("VideoRecorder encoder exited with status {}".format(ret))
<|endoftext|>