Exemplo n.º 1
0
class Leaflet(QQuickItem):
    _min_ext = 0
    _max_ext = 800
    next_available = 0

    # QML accessible enum defs
    class Orientation:
        Horizontal, Vertical = range(2)

    Q_ENUMS(Orientation)

    class Direction:
        Positive, Negative = range(2)

    Q_ENUMS(Direction)

    onExtensionChanged = pyqtSignal([int], arguments=['extension'])
    onIndexChanged = pyqtSignal([int], arguments=['index'])

    def __init__(self, *args, **kwargs):
        QQuickItem.__init__(self, **kwargs)
        self._hwsoc = HWSOC()
        self._extension = 0
        self._index = Leaflet.next_available
        Leaflet.next_available += 1

    def componentComplete(self):
        QQuickItem.componentComplete(self)
        #  self.enableHWLink()

    @pyqtProperty(int, notify=onIndexChanged)
    def index(self):
        return self._index

    @index.setter
    def index(self, val):
        self._index = val
        self.onIndexChanged.emit(val)

    @pyqtProperty(int, constant=True)
    def min_extension(self):
        return Leaflet._min_ext

    @pyqtProperty(int, constant=True)
    def max_extension(self):
        return Leaflet._max_ext

    @pyqtProperty(int, notify=onExtensionChanged)
    def extension(self):
        return self._extension

    @extension.setter
    def extension(self, val):
        # bounds checking
        if (val < Leaflet._min_ext): val = Leaflet._min_ext
        elif (val > Leaflet._max_ext): val = Leaflet._max_ext
        self._extension = val
        self.onExtensionChanged.emit(val)
Exemplo n.º 2
0
class DurationFormat(QObject):
    class Format:
        Seconds = 0
        Short = 1
        Long = 2
        ISO8601 = 3
    Q_ENUMS(Format)
Exemplo n.º 3
0
class ResourcesProxy(QObject):
    class Type:
        Resources = UM.Resources.Resources.Resources
        Preferences = UM.Resources.Resources.Preferences
        Themes = UM.Resources.Resources.Themes
        Images = UM.Resources.Resources.Images
        Meshes = UM.Resources.Resources.Meshes
        MachineDefinitions = UM.Resources.Resources.MachineDefinitions
        MachineInstances = UM.Resources.Resources.MachineInstances
        Profiles = UM.Resources.Resources.Profiles
        i18n = UM.Resources.Resources.i18n
        Shaders = UM.Resources.Resources.Shaders
        UserType = UM.Resources.Resources.UserType

    Q_ENUMS(Type)

    def __init__(self, parent=None):
        super().__init__(parent)

    @pyqtSlot(int, str, result=str)
    def getPath(self, type, name):
        try:
            return UM.Resources.Resources.getPath(type, name)
        except:
            Logger.log("w",
                       "Could not find the requested resource: %s" % (name))
            return ""
Exemplo n.º 4
0
class DFRetrievalStatus(QObject):
    """
    Used as an intermediate QObject that registers the RetrievalStatus as a recognizable enum in QML, so that it can
    be used within QML objects as DigitalFactory.RetrievalStatus.<status>
    """

    Q_ENUMS(RetrievalStatus)
Exemplo n.º 5
0
class Button(_Button):
    """设计师里需要枚举,必须在QObject的对象里定义,所以这里重新继承下"""
    class EnumColors:
        White, BlueJeans, Aqua, Mint, Grass, Sunflower, Bittersweet, \
            Grapefruit, Lavender, PinkRose, LightGray, MediumGray, \
            DarkGray = range(13)

    Q_ENUMS(EnumColors)

    def __init__(self, *args, **kwargs):
        super(Button, self).__init__(*args, **kwargs)
        self._colorList = allColors()
        self._colorTheme = self.EnumColors.White

    def resetColorTheme(self):
        """* 重置主题列表"""
        self._colorTheme = self.EnumColors.White
        self.Color = self._colorList[0]()
        self.update()

    @pyqtProperty(EnumColors, freset=resetColorTheme)
    def colorTheme(self):
        return self._colorTheme

    @colorTheme.setter
    def colorTheme(self, theme):
        self._colorTheme = theme
        self.Color = self._colorList[theme]()
        self.update()
Exemplo n.º 6
0
class Geometry( Product.Product ):


    def __init__( self, parent=None, indices = None, attribs = None, primitive_type = PrimitiveType.TRIANGLES ):
        super(Geometry, self).__init__( parent )
        self._indices = None
        self._attribs = None
        self._primitiveType = PrimitiveType.TRIANGLES
        self.bvh = None

        self.indices = indices
        self.attribs = attribs
        self.primitiveType = primitive_type

    PrimitiveType = PrimitiveType

    Q_ENUMS(PrimitiveType)

    Product.InputProperty(vars(), int, 'primitiveType')

    Product.InputProperty(vars(), Array, 'indices')

    Product.InputProperty(vars(), Attribs, 'attribs')

    def goc_bvh(self, update = False):

        if self.bvh is None and self.primitiveType in [PrimitiveType.TRIANGLES, PrimitiveType.POINTS, PrimitiveType.LINES]:
            self.bvh = BVH(self, self.indices, self.attribs.vertices, self.primitiveType)
        if self.bvh is not None and update:
            self.bvh.update()
        return self.bvh
Exemplo n.º 7
0
class Expression(QObject):
    class ExpressionTypes(IntEnum):
        Instruction = 0
        Variable = 1
        Types = 2
        Header = 3

    Q_ENUMS(ExpressionTypes)

    @staticmethod
    def singletonProvider(engine: QQmlEngine,
                          script_engine: QJSEngine) -> QObject:
        return Expression()
Exemplo n.º 8
0
class BarUpdate(TextUpdateBase, QProgressBar, ScaleType):
    Q_ENUMS(ScaleType)
    ScaleType = ScaleType

    def __init__(self, parent):
        QProgressBar.__init__(self, parent)
        TextUpdateBase.__init__(self, parent)
        self._scientificNotation = False
        self._scale = ScaleType.Linear
        self.N = 100
        self.setMinimum(0)
        self.setMaximum(self.N)
        self._minimumValue = "0"
        self._maximumValue = "100"

    def setBarValue(self, val):
        low = eval(self._minimumValue)
        high = eval(self._maximumValue)
        barPercent = (val - low) / (high - low)
        self.setValue(barPercent * self.N)

    def setBarValueLog(self, val):
        low = math.log10(eval(self._minimumValue))
        high = math.log10(eval(self._maximumValue))
        val = math.log10(val)
        barPercent = (val - low) / (high - low)
        self.setValue(barPercent * self.N)

    def default_code(self):
        return """
			ui = self.ui
			from bsstudio.functions import widgetValue
			value = widgetValue(eval(self.source))
			if self.scale == self.ScaleType.Linear:
				self.setBarValue(value)
			elif self.scale == self.ScaleType.Logarithmic:
				self.setBarValueLog(value)
			if self.scientificNotation:
				self.setFormat("%.02E" % value) 
			else:
				self.setFormat("%.02f" % value) 
			"""[:-1]

    minimumValue = makeProperty("minimumValue")
    maximumValue = makeProperty("maximumValue")
    scientificNotation = makeProperty("scientificNotation", bool)
    scale = makeProperty("scale", ScaleType)
Exemplo n.º 9
0
class BVH( Product.Product ):

    def __init__( self, parent=None, indices=None, points=None, primitive_type = PrimitiveType.TRIANGLES):
        super(BVH, self).__init__( parent )
        self._indices = None
        self._points = None
        self._primitiveType = PrimitiveType.TRIANGLES

        self.indices = indices
        self.points = points
        self.primitiveType = primitive_type
        self.bvh = None
        self._shape_indices = None

    PrimitiveType = PrimitiveType

    Q_ENUMS(PrimitiveType)

    Product.InputProperty(vars(), int, 'primitiveType')

    Product.InputProperty(vars(), Array, 'indices')

    Product.InputProperty(vars(), Array, 'points')

    def _update(self):
        if self._indices is None or self._points is None:
            raise RuntimeError('indices or points is None')

        assert self._points.ndarray.shape[1] == 3, "points other than 3d not implemented"
        assert self._points.ndarray.dtype.type in [np.float32, np.float64], "not float32/64"
        assert self._indices.ndarray.dtype.type == np.uint32, 'BVH indices must be of type uint32'


        if self._primitiveType == PrimitiveType.TRIANGLES:
            self._shape_indices = self._indices.ndarray.reshape(self._indices.ndarray.shape[0]//3, 3, order = 'C')
            self.bvh = PybindBVH(self._shape_indices, self._points.ndarray.astype('f4'))
        elif self._primitiveType == PrimitiveType.POINTS:
            self._shape_indices = self._indices.ndarray.reshape(self._indices.ndarray.shape[0], 1, order = 'C')
            self.bvh = PybindPointsBVH(self._shape_indices, self._points.ndarray.astype('f4'))
        elif self._primitiveType == PrimitiveType.LINES:
            
            indices = Array(ndarray = np.array([0,1,2, 1,2,3, 0,4,2, 2,4,6, 1,5,3, 3,5,7, 4,5,6, 5,6,7, 2,3,6, 3,6,7], 'u4'))
            self._shape_indices = indices.ndarray.reshape(indices.ndarray.shape[0]//3, 3, order = 'C')
            self.bvh = PybindBVH(self._shape_indices, self._points.ndarray.astype('f4'))
        else:
            raise NotImplementedError()
Exemplo n.º 10
0
class Button(_Button):
    """设计师里需要枚举,必须在QObject的对象里定义,所以这里重新继承下"""

    Q_ENUMS(EnumThemes)

    def resetColorTheme(self):
        """重置主题"""
        self._colorTheme = self.EnumThemes.MediumGray
        self._resetColorTheme()

    @pyqtProperty(EnumThemes, freset=resetColorTheme)
    def colorTheme(self):
        return self._colorTheme

    @colorTheme.setter
    def colorTheme(self, colorTheme):
        self._colorTheme = colorTheme
        self._resetColorTheme()
Exemplo n.º 11
0
class FileCopier(QObject, CopyError):
    Q_ENUMS(CopyError)

    _add_to_queue = pyqtSignal(
        str, str, str, arguments=("uid", "source_path", "dest_path")
    )
    _remove_from_queue = pyqtSignal(str, arguments=("uid",))
    _clear_queue = pyqtSignal()

    copy_complete = pyqtSignal(str, arguments=("uid",))
    copy_error = pyqtSignal(str, int, arguments=("uid", "error"))
    copy_progress = pyqtSignal(str, int, int, arguments=("uid", "progress", "total"))
    copy_cancelled = pyqtSignal(str, arguments=("uid",))

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

        self._thread = QThread()
        self._worker = _FileCopierWorker(
            copy_complete=self.copy_complete,
            copy_error=self.copy_error,
            copy_progress=self.copy_progress,
            copy_cancelled=self.copy_cancelled,
        )
        self._add_to_queue.connect(self._worker.add_to_queue)
        self._remove_from_queue.connect(self._worker.remove_from_queue)
        self._clear_queue.connect(self._worker.clear_queue)
        self._worker.moveToThread(self._thread)
        self._thread.start()

    @staticmethod
    def new_uid():
        return str(uuid.uuid4())

    def copy_file(self, source_path: str, dest_path: str) -> str:
        uid = FileCopier.new_uid()
        self._add_to_queue.emit(uid, source_path, dest_path)
        return uid

    def copy_files(self, source_path_list: List[str], dest_path: str) -> List[str]:
        uid_list = []
        for source_path in source_path_list:
            uid_list.append(self.copy_file(source_path, dest_path))
        return uid_list
Exemplo n.º 12
0
class QLedIndicator(QFrame):
    Q_ENUMS(ComponentStatus)

    def __init__(self, title, parent=None):
        QFrame.__init__(self, parent)

        self.status = ComponentStatus.OFF

        self.titleLabel = QLabel(title)
        self.titleLabel.setAlignment(Qt.AlignCenter)

        self.statusLabel = QLabel("On" if self.status ==
                                  ComponentStatus.ON else "Off")
        self.statusLabel.setAlignment(Qt.AlignCenter)

        self.vLayout = QVBoxLayout(self)
        self.vLayout.addWidget(self.titleLabel)
        self.vLayout.addWidget(self.statusLabel)
        self.vLayout.setAlignment(self.vLayout,
                                  Qt.AlignHCenter | Qt.AlignVCenter)

        self.setLayout(self.vLayout)

        self.opacityEffect = QGraphicsOpacityEffect(self)

        self.setObjectName("LedIndicatorFrame")
        self.setFrameStyle(QFrame.Box | QFrame.Panel)
        self.setStyleSheet(
            '#LedIndicatorFrame { border: 3px solid #FFFFFF; background-color: #304E6E; }'
        )
        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        self.deactivate()

    def activate(self):
        self.status = ComponentStatus.ON
        self.opacityEffect.setOpacity(0.3)
        self.setGraphicsEffect(self.opacityEffect)

    def deactivate(self):
        self.status = ComponentStatus.OFF
        self.opacityEffect.setOpacity(0.3)
        self.setGraphicsEffect(self.opacityEffect)
Exemplo n.º 13
0
class BackendProxy(QObject):

    Q_ENUMS(BackendState)  # Expose the BackendState enum to QML

    def __init__(self, parent=None):
        super().__init__(parent)
        self._backend = Application.getInstance().getBackend()
        self._progress = -1
        self._state = BackendState.NotStarted
        if self._backend:
            self._backend.processingProgress.connect(
                self._onProcessingProgress)
            self._backend.backendStateChange.connect(
                self._onBackendStateChange)

    processingProgress = pyqtSignal(float, arguments=["amount"])

    @pyqtProperty(float, notify=processingProgress)
    def progress(self):
        return self._progress

    backendStateChange = pyqtSignal(int, arguments=["state"])

    @pyqtProperty(int, notify=backendStateChange)
    def state(self):
        """Returns the current state of processing of the backend.

        :return: :type{IntEnum} The current state of the backend.
        """

        return self._state

    def _onProcessingProgress(self, amount):
        if self._progress != amount:
            self._progress = amount
            self.processingProgress.emit(amount)

    def _onBackendStateChange(self, state):
        if self._state != state:
            self._state = state
            self.backendStateChange.emit(state)
Exemplo n.º 14
0
    def __new__(cls, name, bases, attributes):

        name = name[3:]
        # Find the enum type in thr rtneuron module with the same name
        enum = getattr(_rtneuron, name)
        # Create the nested class used by PyQt to enumerate the values
        values = type("Values", (), enum.names)
        attributes["Values"] = values
        Q_ENUMS(values)

        # Create the QML capable QObject
        enumType = type(QObject).__new__(cls, name, bases, attributes)

        # Finally we add the register function to the type. This can't be
        # done earlier because we need the type itself.
        @staticmethod
        def register():
            qmlRegisterType(enumType, name, 1, 0, name)

        enumType.register = register

        return enumType
Exemplo n.º 15
0
class ResourcesProxy(QObject):
    class Type:
        Resources = UM.Resources.Resources.Resources
        Preferences = UM.Resources.Resources.Preferences
        Themes = UM.Resources.Resources.Themes
        Images = UM.Resources.Resources.Images
        Meshes = UM.Resources.Resources.Meshes
        MachineDefinitions = UM.Resources.Resources.MachineDefinitions
        MachineInstances = UM.Resources.Resources.MachineInstances
        Profiles = UM.Resources.Resources.Profiles
        i18n = UM.Resources.Resources.i18n
        Shaders = UM.Resources.Resources.Shaders
        UserType = UM.Resources.Resources.UserType

    Q_ENUMS(Type)

    def __init__(self, parent=None):
        super().__init__(parent)

    @pyqtSlot(int, str, result=str)
    def getPath(self, type, name):
        return UM.Resources.Resources.getPath(type, name)
Exemplo n.º 16
0
class Button(_Button):
    """设计师里需要枚举,必须在QObject的对象里定义,所以这里重新继承下"""
    class EnumColors:
        Transparent, White, BlueJeans, Aqua, Mint, Grass, Sunflower, \
            Bittersweet, Grapefruit, Lavender, PinkRose, LightGray, MediumGray, \
            DarkGray = range(14)

    Q_ENUMS(EnumColors)

    def resetColorTheme(self):
        """重置主题"""
        self._colorTheme = self.EnumColors.MediumGray
        self._resetColorTheme()

    @pyqtProperty(EnumColors, freset=resetColorTheme)
    def colorTheme(self):
        return self._colorTheme

    @colorTheme.setter
    def colorTheme(self, colorTheme):
        self._colorTheme = colorTheme
        self._resetColorTheme()
Exemplo n.º 17
0
class ProxyModel(QSortFilterProxyModel):
    srcDataChanged = pyqtSignal(int, int)  # row1, row2
    taskClassFilterChanged = pyqtSignal()

    Q_ENUMS(dropPy34Enum(TaskClass))

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setDynamicSortFilter(True)
        self.sort(0, Qt.DescendingOrder)
        self.setFilterCaseSensitivity(False)
        self._taskClassFilter = TaskClass.RUNNING

    @pyqtProperty(int, notify=taskClassFilterChanged)
    def taskClassFilter(self):
        return self._taskClassFilter

    @taskClassFilter.setter
    def taskClassFilter(self, value):
        if value != self._taskClassFilter:
            self._taskClassFilter = value
            self.taskClassFilterChanged.emit()
            self.invalidateFilter()

    def filterAcceptsRow(self, srcRow: int, srcParent: QModelIndex):
        result = super().filterAcceptsRow(srcRow, srcParent)
        if result:
            srcModel = self.sourceModel()
            klass = srcModel.data(srcModel.index(srcRow, 0), TaskClassRole)
            if klass & self.taskClassFilter:
                return True
            else:
                return False

        return result

    @pyqtSlot(QModelIndex, QModelIndex, "QVector<int>")
    def _slotSrcDataChanged(self, topLeft, bottomRight, roles):
        self.srcDataChanged.emit(topLeft.row(), bottomRight.row())

    def setSourceModel(self, model):
        model.dataChanged.connect(self._slotSrcDataChanged)
        super().setSourceModel(model)
        self.setSortRole(CreationTimeRole)

    @pyqtSlot(int, result="QVariant")
    def get(self, i: int):
        index = self.mapToSource(self.index(i, 0))
        return self.sourceModel().get(index)

    def _getModelIndex(self, rowId):
        return self.index(rowId, 0)

    def _getSourceModelIndex(self, rowId):
        return self.mapToSource(self._getModelIndex(rowId))

    def _getModelIndice(self, rowIds):
        return map(lambda row: self.index(row, 0), rowIds)

    def _getSourceModelIndice(self, rowIds):
        return map(self.mapToSource, self._getModelIndice(rowIds))

    @pyqtSlot(str, result="void")
    def setNameFilter(self, name):
        if name:
            self.setFilterFixedString(name)
        else:
            self.setFilterFixedString(None)

    @pyqtSlot("QVariantMap", result="void")
    def pauseTasks(self, options):
        srcIndice = list(self._getSourceModelIndice(options["rows"]))
        self.sourceModel().pauseTasks(srcIndice, options)

    @pyqtSlot("QVariantMap", result="void")
    def startTasks(self, options):
        srcIndice = list(self._getSourceModelIndice(options["rows"]))
        self.sourceModel().startTasks(srcIndice, options)

    @pyqtSlot(int, result="void")
    def systemOpen(self, rowId):
        srcIndex = self._getSourceModelIndex(rowId)
        self.sourceModel().systemOpen(srcIndex)

    @pyqtSlot(int, bool, result="void")
    def openLixianChannel(self, rowId, enable: bool):
        srcIndex = self._getSourceModelIndex(rowId)
        self.sourceModel().openLixianChannel(srcIndex, enable)

    @pyqtSlot(int, result="void")
    def openVipChannel(self, rowId):
        srcIndex = self._getSourceModelIndex(rowId)
        self.sourceModel().openVipChannel(srcIndex)

    @pyqtSlot(int, result="void")
    def viewOneTask(self, rowId):
        srcIndex = self._getSourceModelIndex(rowId)
        self.sourceModel().viewOneTask(srcIndex)

    @pyqtSlot("QList<int>", result="void")
    def viewMultipleTasks(self, rowIds):
        srcIndice = list(self._getSourceModelIndice(rowIds))
        self.sourceModel().viewMultipleTasks(srcIndice)
Exemplo n.º 18
0
class FileSystemTable(QTableView, TableType):
    Q_ENUMS(TableType)

    transferFileRequest = pyqtSignal(str)
    rootChanged = pyqtSignal(str)

    def __init__(self, parent=None):
        super(FileSystemTable, self).__init__(parent)

        self._table_type = TableType.Local

        # This prevents doing unneeded initialization
        # when QtDesginer loads the plugin.
        if parent is None:
            return

        self.parent = parent
        self.path_data = dict()
        self.doubleClicked.connect(self.changeRoot)
        self.selected_row = None
        self.clipboard = QApplication.clipboard()

        self.model = QFileSystemModel()
        self.model.setReadOnly(True)
        self.model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot
                             | QDir.AllEntries)

        self.setModel(self.model)

        self.verticalHeader().hide()
        self.horizontalHeader().setStretchLastSection(True)
        self.setAlternatingRowColors(True)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.info = Info()
        self._nc_file_dir = self.info.getProgramPrefix()
        self.setRootPath(self._nc_file_dir)

    def showEvent(self, event=None):
        self.rootChanged.emit(self._nc_file_dir)

    def changeRoot(self, index):

        path = self.model.filePath(self.rootIndex())
        new_path = self.model.filePath(index)

        absolute_path = os.path.join(path, new_path)

        file_info = QFileInfo(absolute_path)
        if file_info.isDir():
            self.model.setRootPath(absolute_path)
            self.setRootIndex(self.model.index(absolute_path))

            self.rootChanged.emit(absolute_path)

    @pyqtSlot()
    def newFile(self):
        path = self.model.filePath(self.rootIndex())
        new_file = QFile(os.path.join(path, "New File"))
        new_file.open(QIODevice.ReadWrite)

    @pyqtSlot()
    def deleteFile(self):
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        if path:
            fileInfo = QFileInfo(path)
            if fileInfo.isFile():
                if not self.ask_dialog(
                        "Do you wan't to delete the selected file?"):
                    return
                file = QFile(path)
                file.remove()

            elif fileInfo.isDir():
                if not self.ask_dialog(
                        "Do you wan't to delete the selected directory?"):
                    return
                directory = QDir(path)
                directory.removeRecursively()

    @pyqtSlot()
    def createDirectory(self):
        path = self.model.filePath(self.rootIndex())
        directory = QDir()
        directory.setPath(path)
        directory.mkpath("New Folder")

    @pyqtSlot(str)
    def setRootPath(self, root_path):

        self.rootChanged.emit(root_path)
        self.model.setRootPath(root_path)
        self.setRootIndex(self.model.index(root_path))

        return True

    @pyqtSlot()
    def goUP(self):

        path = self.model.filePath(self.rootIndex())

        file_info = QFileInfo(path)
        directory = file_info.dir()
        new_path = directory.absolutePath()

        currentRoot = self.rootIndex()

        self.model.setRootPath(new_path)
        self.setRootIndex(currentRoot.parent())
        self.rootChanged.emit(new_path)

    @pyqtSlot()
    def doFileTransfer(self):
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        self.transferFileRequest.emit(path)

    @pyqtSlot(str)
    def transferFile(self, src_path):
        dest_path = self.model.filePath(self.rootIndex())

        src_file = QFile()
        src_file.setFileName(src_path)

        src_file_info = QFileInfo(src_path)

        dst_path = os.path.join(dest_path, src_file_info.fileName())

        src_file.copy(dst_path)

    @pyqtSlot()
    def getSelected(self):
        return self.selected_row

    @pyqtSlot()
    def getCurrentDirectory(self):
        return self.model.rootPath()

    @pyqtProperty(TableType)
    def tableType(self):
        return self._table_type

    @tableType.setter
    def tableType(self, table_type):
        self._table_type = table_type
        if table_type == TableType.Local:
            self.setRootPath(self._nc_file_dir)
        else:
            self.setRootPath('/media/')

    def ask_dialog(self, message):
        box = QMessageBox.question(self.parent, 'Are you sure?', message,
                                   QMessageBox.Yes, QMessageBox.No)
        if box == QMessageBox.Yes:
            return True
        else:
            return False
Exemplo n.º 19
0
class Account(QObject):
    """The account API provides a version-proof bridge to use Ultimaker Accounts

    Usage:

    .. code-block:: python

      from cura.API import CuraAPI
      api = CuraAPI()
      api.account.login()
      api.account.logout()
      api.account.userProfile    # Who is logged in
    """

    # The interval in which sync services are automatically triggered
    SYNC_INTERVAL = 30.0  # seconds
    Q_ENUMS(SyncState)

    loginStateChanged = pyqtSignal(bool)
    """Signal emitted when user logged in or out"""

    accessTokenChanged = pyqtSignal()
    syncRequested = pyqtSignal()
    """Sync services may connect to this signal to receive sync triggers.
    Services should be resilient to receiving a signal while they are still syncing,
    either by ignoring subsequent signals or restarting a sync.
    See setSyncState() for providing user feedback on the state of your service. 
    """
    lastSyncDateTimeChanged = pyqtSignal()
    syncStateChanged = pyqtSignal(int)  # because SyncState is an int Enum
    manualSyncEnabledChanged = pyqtSignal(bool)

    def __init__(self, application: "CuraApplication", parent=None) -> None:
        super().__init__(parent)
        self._application = application
        self._new_cloud_printers_detected = False

        self._error_message = None  # type: Optional[Message]
        self._logged_in = False
        self._sync_state = SyncState.IDLE
        self._manual_sync_enabled = False
        self._last_sync_str = "-"

        self._callback_port = 32118
        self._oauth_root = UltimakerCloudConstants.CuraCloudAccountAPIRoot

        self._oauth_settings = OAuth2Settings(
            OAUTH_SERVER_URL=self._oauth_root,
            CALLBACK_PORT=self._callback_port,
            CALLBACK_URL="http://localhost:{}/callback".format(
                self._callback_port),
            CLIENT_ID="um----------------------------ultimaker_cura",
            CLIENT_SCOPES=
            "account.user.read drive.backup.read drive.backup.write packages.download "
            "packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write "
            "cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write",
            AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
            AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(
                self._oauth_root),
            AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root))

        self._authorization_service = AuthorizationService(
            self._oauth_settings)

        # Create a timer for automatic account sync
        self._update_timer = QTimer()
        self._update_timer.setInterval(int(self.SYNC_INTERVAL * 1000))
        # The timer is restarted explicitly after an update was processed. This prevents 2 concurrent updates
        self._update_timer.setSingleShot(True)
        self._update_timer.timeout.connect(self.syncRequested)

        self._sync_services = {}  # type: Dict[str, int]
        """contains entries "service_name" : SyncState"""

    def initialize(self) -> None:
        self._authorization_service.initialize(
            self._application.getPreferences())
        self._authorization_service.onAuthStateChanged.connect(
            self._onLoginStateChanged)
        self._authorization_service.onAuthenticationError.connect(
            self._onLoginStateChanged)
        self._authorization_service.accessTokenChanged.connect(
            self._onAccessTokenChanged)
        self._authorization_service.loadAuthDataFromPreferences()

    def setSyncState(self, service_name: str, state: int) -> None:
        """ Can be used to register sync services and update account sync states

        Contract: A sync service is expected exit syncing state in all cases, within reasonable time

        Example: `setSyncState("PluginSyncService", SyncState.SYNCING)`
        :param service_name: A unique name for your service, such as `plugins` or `backups`
        :param state: One of SyncState
        """
        prev_state = self._sync_state

        self._sync_services[service_name] = state

        if any(val == SyncState.SYNCING
               for val in self._sync_services.values()):
            self._sync_state = SyncState.SYNCING
            self._setManualSyncEnabled(False)
        elif any(val == SyncState.ERROR
                 for val in self._sync_services.values()):
            self._sync_state = SyncState.ERROR
            self._setManualSyncEnabled(True)
        else:
            self._sync_state = SyncState.SUCCESS
            self._setManualSyncEnabled(False)

        if self._sync_state != prev_state:
            self.syncStateChanged.emit(self._sync_state)

            if self._sync_state == SyncState.SUCCESS:
                self._last_sync_str = datetime.now().strftime("%d/%m/%Y %H:%M")
                self.lastSyncDateTimeChanged.emit()

            if self._sync_state != SyncState.SYNCING:
                # schedule new auto update after syncing completed (for whatever reason)
                if not self._update_timer.isActive():
                    self._update_timer.start()

    def _onAccessTokenChanged(self):
        self.accessTokenChanged.emit()

    @property
    def is_staging(self) -> bool:
        """Indication whether the given authentication is applied against staging or not."""

        return "staging" in self._oauth_root

    @pyqtProperty(bool, notify=loginStateChanged)
    def isLoggedIn(self) -> bool:
        return self._logged_in

    def _onLoginStateChanged(self,
                             logged_in: bool = False,
                             error_message: Optional[str] = None) -> None:
        if error_message:
            if self._error_message:
                self._error_message.hide()
            self._error_message = Message(error_message,
                                          title=i18n_catalog.i18nc(
                                              "@info:title", "Login failed"))
            self._error_message.show()
            self._logged_in = False
            self.loginStateChanged.emit(False)
            if self._update_timer.isActive():
                self._update_timer.stop()
            return

        if self._logged_in != logged_in:
            self._logged_in = logged_in
            self.loginStateChanged.emit(logged_in)
            if logged_in:
                self._setManualSyncEnabled(False)
                self._sync()
            else:
                if self._update_timer.isActive():
                    self._update_timer.stop()

    def _sync(self) -> None:
        """Signals all sync services to start syncing

        This can be considered a forced sync: even when a
        sync is currently running, a sync will be requested.
        """

        if self._update_timer.isActive():
            self._update_timer.stop()
        elif self._sync_state == SyncState.SYNCING:
            Logger.warning(
                "Starting a new sync while previous sync was not completed\n{}",
                str(self._sync_services))

        self.syncRequested.emit()

    def _setManualSyncEnabled(self, enabled: bool) -> None:
        if self._manual_sync_enabled != enabled:
            self._manual_sync_enabled = enabled
            self.manualSyncEnabledChanged.emit(enabled)

    @pyqtSlot()
    @pyqtSlot(bool)
    def login(self, force_logout_before_login: bool = False) -> None:
        """
        Initializes the login process. If the user is logged in already and force_logout_before_login is true, Cura will
        logout from the account before initiating the authorization flow. If the user is logged in and
        force_logout_before_login is false, the function will return, as there is nothing to do.

        :param force_logout_before_login: Optional boolean parameter
        :return: None
        """
        if self._logged_in:
            if force_logout_before_login:
                self.logout()
            else:
                # Nothing to do, user already logged in.
                return
        self._authorization_service.startAuthorizationFlow(
            force_logout_before_login)

    @pyqtProperty(str, notify=loginStateChanged)
    def userName(self):
        user_profile = self._authorization_service.getUserProfile()
        if not user_profile:
            return None
        return user_profile.username

    @pyqtProperty(str, notify=loginStateChanged)
    def profileImageUrl(self):
        user_profile = self._authorization_service.getUserProfile()
        if not user_profile:
            return None
        return user_profile.profile_image_url

    @pyqtProperty(str, notify=accessTokenChanged)
    def accessToken(self) -> Optional[str]:
        return self._authorization_service.getAccessToken()

    @pyqtProperty("QVariantMap", notify=loginStateChanged)
    def userProfile(self) -> Optional[Dict[str, Optional[str]]]:
        """None if no user is logged in otherwise the logged in  user as a dict containing containing user_id, username and profile_image_url """

        user_profile = self._authorization_service.getUserProfile()
        if not user_profile:
            return None
        return user_profile.__dict__

    @pyqtProperty(str, notify=lastSyncDateTimeChanged)
    def lastSyncDateTime(self) -> str:
        return self._last_sync_str

    @pyqtProperty(bool, notify=manualSyncEnabledChanged)
    def manualSyncEnabled(self) -> bool:
        return self._manual_sync_enabled

    @pyqtSlot()
    @pyqtSlot(bool)
    def sync(self, user_initiated: bool = False) -> None:
        if user_initiated:
            self._setManualSyncEnabled(False)

        self._sync()

    @pyqtSlot()
    def popupOpened(self) -> None:
        self._setManualSyncEnabled(True)
        self._sync_state = SyncState.IDLE
        self.syncStateChanged.emit(self._sync_state)

    @pyqtSlot()
    def logout(self) -> None:
        if not self._logged_in:
            return  # Nothing to do, user isn't logged in.

        self._authorization_service.deleteAuthData()
Exemplo n.º 20
0
class ComboBox(QComboBox, TouchMarginPlugin, FocusablePlugin):
	Q_ENUMS(MarginWidth) #This is needed here. I don't know why the definition in the TouchMarginPlugin doesn't work.
	
	def __init__(self, parent=None, showHitRects=False):
		super().__init__(parent, showHitRects=showHitRects)
		
		self.theme = theme('dark')
		self.clickMarginColor = f"rgba({randint(128, 255)}, {randint(0, 32)}, {randint(0, 32)}, {randint(32,96)})"
		
		settings.observe('theme', 'dark', lambda name: (
			setattr(self, 'theme', theme(name)),
			self.refreshStyle(),
		))
		
		def onLowResRotate(delta, pressed):
			if pressed:
				self.injectKeystrokes(Qt.Key_Up if delta < 0 else Qt.Key_Down)
			else:
				self.selectWidget(delta)
		self.jogWheelLowResolutionRotation.connect(onLowResRotate)
		
		self.jogWheelClick.connect(lambda: self.injectKeystrokes(Qt.Key_Space))
		
		#Set up the custom list view (larger, accepts tap etc)
		self.dropdown = ScrollList()
		self.setView(self.dropdown)
		
		self.__nativeDropdownSize = None
	
	def sizeHint(self):
		return QSize(181, 81)
	
	
	def refreshStyle(self):
		if self.showHitRects:
			self.setStyleSheet(f"""
				ComboBox {{
					/* Editor style. Use border to show were click margin is, so we don't mess it up during layout. */
					color: {self.theme.text};
					font-size: 16px;
					background: {self.theme.baseInEditor}; /* The background is drawn under the button borders, so they are opaque if the background is opaque. */
					
					/* use borders instead of margins so we can see what we're doing */
					border-left:   {self.clickMarginLeft   * 10 + 1}px solid {self.clickMarginColor};
					border-right:  {self.clickMarginRight  * 10 + 1}px solid {self.clickMarginColor};
					border-top:    {self.clickMarginTop    * 10 + 1}px solid {self.clickMarginColor};
					border-bottom: {self.clickMarginBottom * 10 + 1}px solid {self.clickMarginColor};
					
					padding-left: 10px;
				}}
				ComboBox:on {{
					/*when dropdown exists*/
				}}

				ComboBox QAbstractItemView {{ /*This is the drop-down menu.*/
					border: 1px solid {self.theme.border};
					color: {self.theme.text};
					selection-background-color: {self.theme.highlight};
				}}
				ComboBox QAbstractItemView::item {{
					padding: 15px;
					background: {self.theme.base};
				}}
				ComboBox QAbstractItemView::item::selected {{
					background: {self.theme.highlight};
				}}

				ComboBox::drop-down {{
					subcontrol-origin: content;
					width: 40px;
					border: 0px solid {self.theme.border};
					border-left-width: 1px;
					color: {self.theme.text};
					max-height: 100px;
				}}
				ComboBox::drop-down:on {{
					/*Stupid hack because the dropdown scrollbar *can't* be increased in width. It's off the width of the drop-down button by -1px. We can't just decrease the width of the drop-down button, because every other button we own is 40px instead of 39px. So. What we do is adjust the button size down when the drop-down is open, because that's the only time the off-by-one with QScrollBar is noticable, and you're distracted by the scrollbar then.*/
					/*This issue is fixed in Qt 5.7, but may reappear in 5.11.*/
					/*padding-left: -1px;*/
				}}
				ComboBox::down-arrow {{
					image: url(:/assets/images/{self.theme.wedgeDownEnabled});
				}}
			""" + self.originalStyleSheet())
		else:
			self.setStyleSheet(f"""
				ComboBox {{
					subcontrol-origin: content;
					/*subcontrol-origin: padding; does nothing but mess up the drop-down button*/
					color: {self.theme.text};
					font-size: 16px;
					background: {self.theme.base};
					border: 1px solid {self.theme.border};
					margin-left: {self.clickMarginLeft*10}px;
					margin-right: {self.clickMarginRight*10}px;
					margin-top: {self.clickMarginTop*10}px;
					margin-bottom: {self.clickMarginBottom*10}px;
					padding-left: 10px;
				}}
				ComboBox:on {{
					/*when dropdown exists*/
				}}

				ComboBox QAbstractItemView {{ /*This is the drop-down menu.*/
					border: 1px solid {self.theme.border};
					color: {self.theme.text};
					selection-background-color: {self.theme.highlight};
				}}
				ComboBox QAbstractItemView::item {{
					padding: 15px;
					background: {self.theme.base}; /*Must explicitly set background colour to anything other than auto for padding to affect text position. 😠. Whyyyy.*/
					font-size: 6px; /*Doesn't work.*/
				}}
				ComboBox QAbstractItemView::item::selected {{
					background: {self.theme.highlight};
				}}
				ComboBox QScrollBar {{
					background: {self.theme.base};
					width: 8px;
					border: 1px solid {self.theme.border};
				}}
				ComboBox QScrollBar::handle {{
					background: {self.theme.highlight};
					border-radius: 3px; /*QScrollBar width - border-left - border-right / 2*/
				}}
				ComboBox QScrollBar::add-line,
				ComboBox QScrollBar::sub-line {{
					border: none;
					background: none;
				}}

				ComboBox::drop-down {{
					subcontrol-origin: content;
					width: 40px;
					border: 0px solid {self.theme.border};
					border-left-width: 1px;
					color: {self.theme.text};
				}}
				ComboBox::drop-down:on {{
					/*Stupid hack because the dropdown scrollbar *can't* be increased in width. It's off the width of the drop-down button by -1px. We can't just decrease the width of the drop-down button, because every other button we own is 40px instead of 39px. So. What we do is adjust the button size down when the drop-down is open, because that's the only time the off-by-one with QScrollBar is noticable, and you're distracted by the scrollbar then.*/
					/*This issue is fixed in Qt 5.7, but may reappear in 5.11.*/
					/*padding-left: -1px;*/
				}}
				ComboBox::down-arrow {{
					image: url(:/assets/images/{self.theme.wedgeDownEnabled});
				}}
			""" + self.originalStyleSheet())
	
	def showPopup(self):
		super().showPopup()
		
		#Only after the popup is shown do we know its native size.
		#We must cash this because size is only recomputed occasionally when shown.
		if not self.__nativeDropdownSize:
			self.__nativeDropdownSize = self.dropdown.geometry()
		
		#Adjust dropdown geometry for hit rects, because subcontrol-origin: content; doesn't work.
		margins = self.touchMargins()
		
		lineItemHeight = 50
		
		geom = self.dropdown.window().geometry()
		isBelow = geom.center().y() > self.geometry().center().y()
		geom.adjust( #Adjust position.
			margins['left'],
			-margins["bottom"] if isBelow else margins["top"],
			-margins['right'],
			-margins["bottom"] if isBelow else margins["top"], #TODO DDR 2019-01-14: make this a multiple of whatever the item size is?
		)
		
		heightAdjustment = (geom.height()-2) - (geom.height()-2)//lineItemHeight * lineItemHeight
		
		geom.adjust( #Adjust height so there's no spare whitespace.
			0, 0 if isBelow else +heightAdjustment,
			0, -heightAdjustment if isBelow else 0,
		)
		self.dropdown.window().setGeometry(geom)
Exemplo n.º 21
0
class DecimalSpinBox(QDoubleSpinBox, TouchMarginPlugin, DirectAPILinkPlugin, FocusablePlugin, SIUnitsPlugin):
	Q_ENUMS(MarginWidth) #This is needed here. I don't know why the definition in the TouchMarginPlugin doesn't work.
	
	def __init__(self, parent=None, showHitRects=False):
		super().__init__(parent, showHitRects=showHitRects)
		
		self.setCorrectionMode(self.CorrectToNearestValue)
		
		self.theme = theme('dark')
		self.clickMarginColor = f"rgba({randint(0, 32)}, {randint(128, 255)}, {randint(128, 255)}, {randint(32,96)})"
		
		settings.observe('theme', 'dark', lambda name: (
			setattr(self, 'theme', theme(name)),
			self.refreshStyle(),
		))
		
		self.isFocused = False
		self.inputMode = '' #Set to empty, 'jogWheel', or 'touch'. Used for defocus event handling behaviour.
		
		self.jogWheelClick.connect(self.jogWheelClicked)
		self.jogWheelLowResolutionRotation.connect(self.onLowResRotate)
		
		self.touchEnd.connect(self.editTapped)
		self.findChild(QLineEdit).installEventFilter(self) #Make touchEnd fire when our sub-edit is tapped. Otherwise, the keyboard only opens when the touch margins are tapped. The event filter itself is inherited from FocusablePlugin.
		self.doneEditing.connect(self.doneEditingCallback)
		
		#When we tap an input, we deselect selected text. But we want to
		#select all text. So, select it again after we've tapped it. Note:
		#This only applies if the keyboard hasn't bumped the text out of the
		#way first.
		self.selectAllTimer = QTimer()
		self.selectAllTimer.timeout.connect(self.selectAll)
		self.selectAllTimer.setSingleShot(True)
		
		valueChangedTap = signalTap(lambda val:
			(val * self.unitValue[self.siUnit],) )
		self.valueChanged.connect(valueChangedTap.emit)
		self.valueChanged = valueChangedTap
	
	def sizeHint(self):
		return QSize(201, 81)
	
	
	def refreshStyle(self):
		if self.showHitRects:
			self.setStyleSheet(f"""
				DecimalSpinBox {{
					/* Editor style. Use border to show were click margin is, so we don't mess it up during layout. */
					font-size: 16px;
					border: 1px solid black;
					padding-right: 0px;
					padding-left: 10px;
					background: {self.theme.baseInEditor}; /* The background is drawn under the button borders, so they are opaque if the background is opaque. */
					color: {self.theme.text};
					
					/* use borders instead of margins so we can see what we're doing */
					border-left:   {self.clickMarginLeft   * 10 + 1}px solid {self.clickMarginColor};
					border-right:  {self.clickMarginRight  * 10 + 1}px solid {self.clickMarginColor};
					border-top:    {self.clickMarginTop    * 10 + 1}px solid {self.clickMarginColor};
					border-bottom: {self.clickMarginBottom * 10 + 1}px solid {self.clickMarginColor};
				}}
				DecimalSpinBox:disabled {{ 
					color: {self.theme.dimText};
				}}
				DecimalSpinBox::up-button, DecimalSpinBox::down-button {{
					width: 0px; /*These buttons just take up room. We have a jog wheel for them.*/
				}}
			""" + self.originalStyleSheet())
		else:
			self.setStyleSheet(f"""
				DecimalSpinBox {{ 
					font-size: 16px;
					border: 1px solid {self.theme.border};
					padding-right: 0px;
					padding-left: 10px;
					background: {self.theme.base};
					color: {self.theme.text};
					
					/* Add some touch space so this widget is easier to press. */
					margin-left: {self.clickMarginLeft*10}px;
					margin-right: {self.clickMarginRight*10}px;
					margin-top: {self.clickMarginTop*10}px;
					margin-bottom: {self.clickMarginBottom*10}px;
				}}
				DecimalSpinBox:disabled {{ 
					color: {self.theme.dimText};
				}}
				DecimalSpinBox::up-button, DecimalSpinBox::down-button {{
					width: 0px; /*These buttons just take up room. We have a jog wheel for them.*/
				}}
			""" + self.originalStyleSheet())
	
	
	def onLowResRotate(self, delta, pressed):
		if self.isFocused or self.inputMode == 'touch':
			if self.inputMode == 'touch' and JOG_WHEEL_MOVES_CURSOR:
				if pressed:
					if delta > 0: #TODO: Make this, and spin_box, and line_edit, select instead of moving by word.
						self.findChild(QLineEdit).cursorWordForward(False)
					else:
						self.findChild(QLineEdit).cursorWordBackward(False)
				else:
					self.findChild(QLineEdit).cursorForward(False, delta)
				
				#An important detail - reset the cursor flash so it's always visible while moving, so we can see where we have moved it to.
				cursorFlashTime = self.window().app.cursorFlashTime()
				self.window().app.setCursorFlashTime(-1)
				self.window().app.setCursorFlashTime(cursorFlashTime)
			else:
				if pressed:
					self.injectKeystrokes(
						Qt.Key_PageUp if delta > 0 else Qt.Key_PageDown,
						count=abs(delta) )
				else:
					self.injectKeystrokes(
						Qt.Key_Up if delta > 0 else Qt.Key_Down,
						count=abs(delta) )
		else:
			if pressed:
				self.injectKeystrokes(
					Qt.Key_PageUp if delta > 0 else Qt.Key_PageDown,
					count=abs(delta) )
			else:
				self.selectWidget(delta)
	
	
	def jogWheelClicked(self):
		self.isFocused = not self.isFocused
		
		if self.isFocused:
			self.inputMode = 'jogWheel'
			self.window().focusRing.focusIn()
		else:
			self.inputMode = ''
			self.window().focusRing.focusOut()
	
	
	def editTapped(self):
		if self.inputMode == 'touch':
			return
		
		self.inputMode = 'touch'
		self.isFocused = True
		self.window().app.window.showInput(self,
			'numeric_with_units' if self.units[1:] else 'numeric_without_units', 
			focus=False,
		)
		self.window().focusRing.focusIn()
		
		self.selectAll()
		self.selectAllTimer.start(16)
	
	
	def doneEditingCallback(self):
		log.debug(f'DONE EDITING {self.objectName()}')
		self.inputMode = ''
		self.isFocused = False
		self.window().app.window.hideInput()
	
	
	def value(self):
		return self.realValue(super().value)
	def setValue(self, val):
		self.setRealValue(super().setValue, val)
	
	def minimum(self):
		return self.realMinimum(super().minimum)
	def setMinimum(self, val):
		self.setRealMinimum(super().setMinimum, val)
	
	def maximum(self):
		return self.realMaximum(super().maximum)
	def setMaximum(self, val):
		self.setRealMaximum(super().setMaximum, val)
Exemplo n.º 22
0
class DROWidget(QLabel, VCPWidget, Axis, RefType, Units):

    from PyQt5.QtCore import Q_ENUMS
    Q_ENUMS(Axis)
    Q_ENUMS(RefType)
    Q_ENUMS(Units)

    def __init__(self, parent=None):
        super(DROWidget, self).__init__(parent)

        self.log = logger.getLogger(__name__)

        self._axis_number = Axis.X
        self._ref_typ = RefType.Absolute

        self._metric_format = '%10.3f'
        self._imperial_format = '%9.4f'
        self._format = self._imperial_format

        self.update(POSITION.abs.getValue())
        STATUS.program_units.notify(self.onUnitsChanged, 'string')

    def update(self, pos):
        self.setText(self._format % pos[self._axis_number])

    def onUnitsChanged(self, units):
        if units == 'in':
            self._format = self._imperial_format
        else:
            self._format = self._metric_format
        self.update(
            getattr(POSITION, RefType.toString(self._ref_typ)).getValue())

    def initialize(self):
        getattr(POSITION, RefType.toString(self._ref_typ)).notify(self.update)

    # ==========================================================================
    # Designer property Getters/Setters
    # ==========================================================================

    @Property(RefType)
    def referenceType(self):
        return self._ref_typ

    @referenceType.setter
    def referenceType(self, ref_typ):
        new_ref_typ = RefType.toString(ref_typ)
        self._ref_typ = ref_typ
        self.update(getattr(POSITION, new_ref_typ).getValue())

    @Property(Axis)
    def axis(self):
        return self._axis_number

    @axis.setter
    def axis(self, axis):
        self._axis_number = axis
        self.update(
            getattr(POSITION, RefType.toString(self._ref_typ)).getValue())

    def getDiamterMode(self):
        return self._diameter_mode

    @Slot(bool)
    def setDiamterMode(self, diameter_mode):
        self._diameter_mode = diameter_mode
        self.updateUnits(STATUS.stat.program_units)

    @Property(str)
    def metricTemplate(self):
        return self._metric_format

    @metricTemplate.setter
    def metricTemplate(self, value):
        try:
            value % 12.456789
        except ValueError as va:
            self.log.debug(va)
            return
        self._metric_format = value
        self._format = self._metric_format
        self.update(
            getattr(POSITION, RefType.toString(self._ref_typ)).getValue())

    @Property(str)
    def imperialTemplate(self):
        return self._imperial_format

    @imperialTemplate.setter
    def imperialTemplate(self, value):
        try:
            value % 12.456789
        except ValueError as va:
            self.log.debug(va)
            return
        self._imperial_format = value
        self._format = self._imperial_format
        self.update(
            getattr(POSITION, RefType.toString(self._ref_typ)).getValue())
Exemplo n.º 23
0
class PrinterOutputDevice(QObject, OutputDevice):

    # Put ConnectionType here with Q_ENUMS() so it can be registered as a QML type and accessible via QML, and there is
    # no need to remember what those Enum integer values mean.
    Q_ENUMS(ConnectionType)

    printersChanged = pyqtSignal()
    connectionStateChanged = pyqtSignal(str)
    acceptsCommandsChanged = pyqtSignal()

    # Signal to indicate that the material of the active printer on the remote changed.
    materialIdChanged = pyqtSignal()

    # # Signal to indicate that the hotend of the active printer on the remote changed.
    hotendIdChanged = pyqtSignal()

    # Signal to indicate that the info text about the connection has changed.
    connectionTextChanged = pyqtSignal()

    # Signal to indicate that the configuration of one of the printers has changed.
    uniqueConfigurationsChanged = pyqtSignal()

    def __init__(self,
                 device_id: str,
                 connection_type: "ConnectionType" = ConnectionType.Unknown,
                 parent: QObject = None) -> None:
        super().__init__(
            device_id=device_id, parent=parent
        )  # type: ignore  # MyPy complains with the multiple inheritance

        self._printers = []  # type: List[PrinterOutputModel]
        self._unique_configurations = []  # type: List[ConfigurationModel]

        self._monitor_view_qml_path = ""  #type: str
        self._monitor_component = None  #type: Optional[QObject]
        self._monitor_item = None  #type: Optional[QObject]

        self._control_view_qml_path = ""  #type: str
        self._control_component = None  #type: Optional[QObject]
        self._control_item = None  #type: Optional[QObject]

        self._accepts_commands = False  #type: bool

        self._update_timer = QTimer()  #type: QTimer
        self._update_timer.setInterval(
            2000)  # TODO; Add preference for update interval
        self._update_timer.setSingleShot(False)
        self._update_timer.timeout.connect(self._update)

        self._connection_state = ConnectionState.Closed  #type: ConnectionState
        self._connection_type = connection_type

        self._firmware_updater = None  #type: Optional[FirmwareUpdater]
        self._firmware_name = None  #type: Optional[str]
        self._address = ""  #type: str
        self._connection_text = ""  #type: str
        self.printersChanged.connect(self._onPrintersChanged)
        QtApplication.getInstance().getOutputDeviceManager(
        ).outputDevicesChanged.connect(self._updateUniqueConfigurations)

    @pyqtProperty(str, notify=connectionTextChanged)
    def address(self) -> str:
        return self._address

    def setConnectionText(self, connection_text):
        if self._connection_text != connection_text:
            self._connection_text = connection_text
            self.connectionTextChanged.emit()

    @pyqtProperty(str, constant=True)
    def connectionText(self) -> str:
        return self._connection_text

    def materialHotendChangedMessage(self, callback: Callable[[int],
                                                              None]) -> None:
        Logger.log(
            "w",
            "materialHotendChangedMessage needs to be implemented, returning 'Yes'"
        )
        callback(QMessageBox.Yes)

    def isConnected(self) -> bool:
        return self._connection_state != ConnectionState.Closed and self._connection_state != ConnectionState.Error

    def setConnectionState(self, connection_state: "ConnectionState") -> None:
        if self._connection_state != connection_state:
            self._connection_state = connection_state
            self.connectionStateChanged.emit(self._id)

    def getConnectionType(self) -> "ConnectionType":
        return self._connection_type

    @pyqtProperty(str, notify=connectionStateChanged)
    def connectionState(self) -> "ConnectionState":
        return self._connection_state

    def _update(self) -> None:
        pass

    def _getPrinterByKey(self, key: str) -> Optional["PrinterOutputModel"]:
        for printer in self._printers:
            if printer.key == key:
                return printer

        return None

    def requestWrite(self,
                     nodes: List["SceneNode"],
                     file_name: Optional[str] = None,
                     limit_mimetypes: bool = False,
                     file_handler: Optional["FileHandler"] = None,
                     **kwargs: str) -> None:
        raise NotImplementedError("requestWrite needs to be implemented")

    @pyqtProperty(QObject, notify=printersChanged)
    def activePrinter(self) -> Optional["PrinterOutputModel"]:
        if len(self._printers):
            return self._printers[0]
        return None

    @pyqtProperty("QVariantList", notify=printersChanged)
    def printers(self) -> List["PrinterOutputModel"]:
        return self._printers

    @pyqtProperty(QObject, constant=True)
    def monitorItem(self) -> QObject:
        # Note that we specifically only check if the monitor component is created.
        # It could be that it failed to actually create the qml item! If we check if the item was created, it will try to
        # create the item (and fail) every time.
        if not self._monitor_component:
            self._createMonitorViewFromQML()
        return self._monitor_item

    @pyqtProperty(QObject, constant=True)
    def controlItem(self) -> QObject:
        if not self._control_component:
            self._createControlViewFromQML()
        return self._control_item

    def _createControlViewFromQML(self) -> None:
        if not self._control_view_qml_path:
            return
        if self._control_item is None:
            self._control_item = QtApplication.getInstance(
            ).createQmlComponent(self._control_view_qml_path,
                                 {"OutputDevice": self})

    def _createMonitorViewFromQML(self) -> None:
        if not self._monitor_view_qml_path:
            return

        if self._monitor_item is None:
            self._monitor_item = QtApplication.getInstance(
            ).createQmlComponent(self._monitor_view_qml_path,
                                 {"OutputDevice": self})

    ##  Attempt to establish connection
    def connect(self) -> None:
        self.setConnectionState(ConnectionState.Connecting)
        self._update_timer.start()

    ##  Attempt to close the connection
    def close(self) -> None:
        self._update_timer.stop()
        self.setConnectionState(ConnectionState.Closed)

    ##  Ensure that close gets called when object is destroyed
    def __del__(self) -> None:
        self.close()

    @pyqtProperty(bool, notify=acceptsCommandsChanged)
    def acceptsCommands(self) -> bool:
        return self._accepts_commands

    @deprecated("Please use the protected function instead", "3.2")
    def setAcceptsCommands(self, accepts_commands: bool) -> None:
        self._setAcceptsCommands(accepts_commands)

    ##  Set a flag to signal the UI that the printer is not (yet) ready to receive commands
    def _setAcceptsCommands(self, accepts_commands: bool) -> None:
        if self._accepts_commands != accepts_commands:
            self._accepts_commands = accepts_commands

            self.acceptsCommandsChanged.emit()

    # Returns the unique configurations of the printers within this output device
    @pyqtProperty("QVariantList", notify=uniqueConfigurationsChanged)
    def uniqueConfigurations(self) -> List["ConfigurationModel"]:
        return self._unique_configurations

    def _updateUniqueConfigurations(self) -> None:
        self._unique_configurations = list(
            set([
                printer.printerConfiguration for printer in self._printers
                if printer.printerConfiguration is not None
            ]))
        self._unique_configurations.sort(key=lambda k: k.printerType)
        self.uniqueConfigurationsChanged.emit()

    # Returns the unique configurations of the printers within this output device
    @pyqtProperty("QStringList", notify=uniqueConfigurationsChanged)
    def uniquePrinterTypes(self) -> List[str]:
        return list(
            set([
                configuration.printerType
                for configuration in self._unique_configurations
            ]))

    def _onPrintersChanged(self) -> None:
        for printer in self._printers:
            printer.configurationChanged.connect(
                self._updateUniqueConfigurations)

        # At this point there may be non-updated configurations
        self._updateUniqueConfigurations()

    ##  Set the device firmware name
    #
    #   \param name The name of the firmware.
    def _setFirmwareName(self, name: str) -> None:
        self._firmware_name = name

    ##  Get the name of device firmware
    #
    #   This name can be used to define device type
    def getFirmwareName(self) -> Optional[str]:
        return self._firmware_name

    def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
        return self._firmware_updater

    @pyqtSlot(str)
    def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
        if not self._firmware_updater:
            return

        self._firmware_updater.updateFirmware(firmware_file)
Exemplo n.º 24
0
class FileSystemTable(QTableView, TableType):
    if IN_DESIGNER:
        from PyQt5.QtCore import Q_ENUMS
        Q_ENUMS(TableType)

    gcodeFileSelected = Signal(bool)
    filePreviewText = Signal(str)
    fileNamePreviewText = Signal(str)
    transferFileRequest = Signal(str)
    rootChanged = Signal(str)

    def __init__(self, parent=None):
        super(FileSystemTable, self).__init__(parent)

        self._table_type = TableType.Local
        self._hidden_columns = ''

        # This prevents doing unneeded initialization
        # when QtDesginer loads the plugin.
        if parent is None:
            return

        self.parent = parent
        self.path_data = dict()
        self.doubleClicked.connect(self.openSelectedItem)
        self.selected_row = None
        self.clipboard = QApplication.clipboard()

        self.model = QFileSystemModel()
        self.model.setReadOnly(True)
        self.model.setFilter(QDir.AllDirs | QDir.NoDotAndDotDot
                             | QDir.AllEntries)

        self.setModel(self.model)

        self.verticalHeader().hide()
        self.horizontalHeader().setStretchLastSection(True)
        self.setAlternatingRowColors(True)
        self.setSelectionBehavior(QAbstractItemView.SelectRows)

        self.selection_model = self.selectionModel()
        self.selection_model.selectionChanged.connect(self.onSelectionChanged)

        self.info = Info()
        self.editor = self.info.getEditor()
        self._nc_file_dir = self.info.getProgramPrefix()
        self.nc_file_exts = self.info.getProgramExtentions()
        self.setRootPath(self._nc_file_dir)

    def showEvent(self, event=None):
        self.rootChanged.emit(self._nc_file_dir)

    def onSelectionChanged(self, selected, deselected):

        if len(selected) == 0:
            return

        index = selected.indexes()[0]
        path = self.model.filePath(index)

        if os.path.isfile(path):
            self.gcodeFileSelected.emit(True)
            with open(path, 'r') as fh:
                content = fh.read()
            self.filePreviewText.emit(content)
            self.fileNamePreviewText.emit(path)
        else:
            self.gcodeFileSelected.emit(False)
            self.filePreviewText.emit('')
            self.fileNamePreviewText.emit('')

    @Slot()
    def openSelectedItem(self, index=None):
        """If ngc file, opens in LinuxCNC, if dir displays dir."""
        if index is None:
            selection = self.getSelection()
            if selection is None:
                return
            index = selection[0]

        path = self.model.filePath(self.rootIndex())
        name = self.model.filePath(index)

        absolute_path = os.path.join(path, name)

        file_info = QFileInfo(absolute_path)
        if file_info.isDir():
            self.model.setRootPath(absolute_path)
            self.setRootIndex(self.model.index(absolute_path))
            self.rootChanged.emit(absolute_path)

        elif file_info.isFile():
            # if file_info.completeSuffix() not in self.nc_file_exts:
            #     LOG.warn("Unsuported NC program type with extention .%s",
            #              file_info.completeSuffix())
            loadProgram(absolute_path)

    @Slot()
    def editSelectedFile(self):
        """Open the selected file in editor."""
        selection = self.getSelection()
        if selection is not None:
            path = self.model.filePath(selection[0])
            subprocess.Popen([self.editor, path])
        return False

    @Slot()
    def loadSelectedFile(self):
        """Loads the selected file into LinuxCNC."""
        selection = self.getSelection()
        if selection is not None:
            path = self.model.filePath(selection[0])
            loadProgram(path)
            return True
        return False

    @Slot()
    def selectPrevious(self):
        """Select the previous item in the view."""
        selection = self.getSelection()
        if selection is None:
            # select last item in view
            self.selectRow(self.model.rowCount(self.rootIndex()) - 1)
        else:
            self.selectRow(selection[0].row() - 1)
        return True

    @Slot()
    def selectNext(self):
        """Select the next item in the view."""
        selection = self.getSelection()
        if selection is None:
            # select first item in view
            self.selectRow(0)
        else:
            self.selectRow(selection[-1].row() + 1)
        return True

    @Slot()
    def rename(self):
        """renames the selected file or folder"""
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        if path:
            file_info = QFileInfo(path)

            if file_info.isFile():
                filename = self.rename_dialog("file")

                if filename:
                    q_file = QFile(path)
                    file_info.absolutePath()
                    new_path = os.path.join(file_info.absolutePath(),
                                            str(filename))
                    q_file.rename(new_path)

            elif file_info.isDir():
                filename = self.rename_dialog("directory")

                if filename:
                    directory = QDir(path)
                    file_info.absolutePath()
                    new_path = os.path.join(file_info.absolutePath(),
                                            str(filename))
                    directory.rename(path, new_path)

    @Slot()
    def newFile(self):
        """Create a new empty file"""
        path = self.model.filePath(self.rootIndex())
        new_file_path = os.path.join(path, "New File.ngc")

        count = 1
        while os.path.exists(new_file_path):
            new_file_path = os.path.join(path, "New File {}.ngc".format(count))
            count += 1

        new_file = QFile(new_file_path)
        new_file.open(QIODevice.ReadWrite)

    @Slot()
    def newFolder(self):
        path = self.model.filePath(self.rootIndex())

        new_name = 'New Folder'

        count = 1
        while os.path.exists(os.path.join(path, new_name)):
            new_name = "New Folder {}".format(count)
            count += 1

        directory = QDir(path)
        directory.mkpath(new_name)
        directory.setPath(new_name)

    @Slot()
    @deprecated(replaced_by='newFolder',
                reason='for consistency with newFile method name')
    def createDirectory(self):
        self.newFolder()

    @Slot()
    def deleteItem(self):
        """Delete the selected item (either a file or folder)."""
        # ToDo: use Move2Trash, instead of deleting the file
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        if path:
            file_info = QFileInfo(path)
            if file_info.isFile():
                if not self.ask_dialog(
                        "Do you wan't to delete the selected file?"):
                    return
                q_file = QFile(path)
                q_file.remove()

            elif file_info.isDir():
                if not self.ask_dialog(
                        "Do you wan't to delete the selected directory?"):
                    return
                directory = QDir(path)
                directory.removeRecursively()

    @Slot()
    @deprecated(replaced_by='deleteItem',
                reason='because of unclear method name')
    def deleteFile(self):
        self.deleteItem()

    @Slot(str)
    def setRootPath(self, root_path):
        """Sets the currently displayed path."""

        self.rootChanged.emit(root_path)
        self.model.setRootPath(root_path)
        self.setRootIndex(self.model.index(root_path))

        return True

    @Slot()
    def viewParentDirectory(self):
        """View the parent directory of the current view."""

        path = self.model.filePath(self.rootIndex())

        file_info = QFileInfo(path)
        directory = file_info.dir()
        new_path = directory.absolutePath()

        currentRoot = self.rootIndex()

        self.model.setRootPath(new_path)
        self.setRootIndex(currentRoot.parent())
        self.rootChanged.emit(new_path)

    @Slot()
    @deprecated(replaced_by='viewParentDirectory')
    def goUP(self):
        self.viewParentDirecotry()

    @Slot()
    def viewHomeDirectory(self):
        self.setRootPath(os.path.expanduser('~/'))

    @Slot()
    def viewNCFilesDirectory(self):
        # ToDo: Make preset user definable
        path = os.path.expanduser('~/linuxcnc/nc_files')
        self.setRootPath(path)

    @Slot()
    def viewPresetDirectory(self):
        # ToDo: Make preset user definable
        preset = os.path.expanduser('~/linuxcnc/nc_files')
        self.setRootPath(preset)

    @Slot()
    def doFileTransfer(self):
        index = self.selectionModel().currentIndex()
        path = self.model.filePath(index)
        self.transferFileRequest.emit(path)

    @Slot(str)
    def transferFile(self, src_path):
        dest_path = self.model.filePath(self.rootIndex())

        src_file = QFile()
        src_file.setFileName(src_path)

        src_file_info = QFileInfo(src_path)

        dst_path = os.path.join(dest_path, src_file_info.fileName())

        src_file.copy(dst_path)

    @Slot()
    def getSelection(self):
        """Returns list of selected indexes, or None."""
        selection = self.selection_model.selectedIndexes()
        if len(selection) == 0:
            return None
        return selection

    @Slot()
    def getCurrentDirectory(self):
        return self.model.rootPath()

    @Property(TableType)
    def tableType(self):
        return self._table_type

    @tableType.setter
    def tableType(self, table_type):
        self._table_type = table_type
        if table_type == TableType.Local:
            self.setRootPath(self._nc_file_dir)
        else:
            self.setRootPath('/media/')

    @Property(str)
    def hiddenColumns(self):
        """String of comma separated column numbers to hide."""
        return self._hidden_columns

    @hiddenColumns.setter
    def hiddenColumns(self, columns):
        try:
            col_list = [int(c) for c in columns.split(',') if c != '']
        except:
            return False

        self._hidden_columns = columns

        header = self.horizontalHeader()
        for col in range(4):
            if col in col_list:
                header.hideSection(col)
            else:
                header.showSection(col)

    def ask_dialog(self, message):
        box = QMessageBox.question(self.parent, 'Are you sure?', message,
                                   QMessageBox.Yes, QMessageBox.No)
        if box == QMessageBox.Yes:
            return True
        else:
            return False

    def rename_dialog(self, data_type):
        text, ok_pressed = QInputDialog.getText(
            self.parent, "Rename", "New {} name:".format(data_type),
            QLineEdit.Normal, "")

        if ok_pressed and text != '':
            return text
        else:
            return False
Exemplo n.º 25
0
class LiveCapture( SensorCapture ):
    def __init__(self, parent = None):
        super(LiveCapture, self).__init__(parent)
        self._dev = None
        self._ip = ""
        self._oversampling = -1
        self._accumulation = -1
        self._basePoints = -1
        self._tracesROI = [0,1] #offset, count

        self._rejectedFalgs = [0, 3, 4, 9, 3072]  
        


        self._received_echoes = []
        self._n_received = 0
        self._received_traces_raw = []
        self._received_traces_proc = []
        self._received_states = []
        self._running = False
        self._locked = False
        self._tracesMode = TracesMode.NONE
        self._delay = 5000
        self._nDroppedPackages = 0
        self._ts_prev = None
        self._triggerMode = TriggerMode.LONG
        self._lcpgIndex = 0x08
        self._overriden_specs = False
        self._connected = False
        self._emitConnectedChanged = False
        self._properties = list(leddar.property_ids.keys())
        self._calibrating = False
        self._restoringCal = False
        self._calibration_samples = []
        self._calibration_states_samples = []
        self._n_calibration_samples = 100
        self._n_drop_calib_samples = 5
        self._calibrationProgress = 0.0
        self._calibration_wall_distance = 2.0
        self._interpCal = False
        self._dTCorrection = False
        self._dTGainFactor = 0.00489
        self._dTFix = 0.0
        self._aTCorrection = False
        self._aTGainFactor = 0.01521
        self._aTThreshold = 20.0
        self._editingResoSpecs = False
        self._resoSpecsMode = ""
        self._resoSpecsVal = 0





        if not os.path.exists('calib_results/'):
            os.makedirs('calib_results/')
        if not os.path.exists('rcalibration/'):
            os.makedirs('rcalibration/')
        if not os.path.exists('angles_table/'):
            os.makedirs('angles_table/')
        


        self._listFilesRCal = [os.path.basename(x) for x in glob.glob('rcalibration/' + '*.pkl')]
        self._listFilesCustomVAngles = [os.path.basename(x) for x in glob.glob('angles_table/' + '*.txt')]
        self._vtabanglesCorrection = False

        self._scalevangles = 1.0456

        self._savingcalibinto = "calibration.pkl"
        
 

        

    def _disconnect_device(self):
        if self._dev is not None and self._connected:
            self._dev.disconnect()
            self._dev = None
            if not self._overriden_specs:
                self._specs = None

    def _update_traces_roi(self):
        if self._dev is not None and self._connected:
            self._dev.set_traces_to_receive(int(self._tracesROI[0]), int(self._tracesROI[1]))

    TracesMode = TracesMode
    Q_ENUMS(TracesMode)

    def _update_traces_mode(self):
        if self._dev is None or not self._connected:
            return

        try:

            self._update_traces_roi()

            if self._tracesMode == TracesMode.PROCESSED:
                def proc_trace_callback(new_traces):
                    if not self._locked:
                        self._received_traces_proc.append(new_traces)
                        self.makeDirty()

                self._dev.set_data_mask(self._dev.get_data_mask() | leddar.data_masks["PDM_PROCESSED_TRACES"])
                self._dev.set_callback_processed_trace(proc_trace_callback)
            else: 
                self._dev.set_data_mask(self._dev.get_data_mask() & ~leddar.data_masks["PDM_PROCESSED_TRACES"])

            if self._tracesMode == TracesMode.RAW:
                def raw_trace_callback(new_traces):
                    if not self._locked:
                        self._received_traces_raw.append(new_traces)
                        self.makeDirty()

                self._dev.set_data_mask(self._dev.get_data_mask() | leddar.data_masks["PDM_RAW_TRACES"])
                self._dev.set_callback_raw_trace(raw_trace_callback)
            else:
                self._dev.set_data_mask(self._dev.get_data_mask() & ~leddar.data_masks["PDM_RAW_TRACES"])
        except:
            traceback.print_exc()
        

    Product.InputProperty(vars(), int, 'tracesMode', _update_traces_mode)

    Product.InputProperty(vars(), QVariant, 'tracesROI', _update_traces_roi)

    Product.InputProperty(vars(), QVariant, 'rejectedFlags')

    Product.InputProperty(vars(), str, 'ip', _disconnect_device)

    def _update_data_thread(self):
        if self._dev is None or not self._connected:
            return

        self._dev.set_data_thread_delay(self._delay)

        if self._running:
            self._dev.start_data_thread()
        else:
            self._dev.stop_data_thread()

    Product.InputProperty(vars(), bool, 'running', _update_data_thread)

    Product.InputProperty(vars(), int, 'delay', _update_data_thread)

    Product.ROProperty(vars(), int, 'nDroppedPackages')

    Product.ROProperty(vars(), bool, 'connected')

    Product.ROProperty(vars(), float, 'fps')

    Product.ConstProperty(vars(), list, 'properties')

    Product.ROProperty(vars(), QVariant, 'states')
    
    Product.ROProperty(vars(), bool, 'calibrating')

    Product.ROProperty(vars(), float, 'calibrationProgress')

    Product.InputProperty(vars(), bool, 'interpCal')

    Product.InputProperty(vars(), bool, 'dTCorrection')

    Product.InputProperty(vars(), float, 'dTFix')

    Product.InputProperty(vars(), float, 'dTGainFactor')

    Product.InputProperty(vars(), bool, 'aTCorrection')

    Product.InputProperty(vars(), float, 'aTGainFactor')

    Product.InputProperty(vars(), float, 'aTThreshold')

    Product.ROProperty(vars(), bool, 'editingResoSpecs')

    Product.ROProperty(vars(), float, 'resoSpecsVal')

    Product.ROProperty(vars(), bool, 'restoringCal')

    Product.ROProperty(vars(),list, 'listFilesRCal')

    Product.ROProperty(vars(),list, 'listFilesCustomVAngles')

    Product.InputProperty(vars(), bool, 'vtabanglesCorrection')

    Product.InputProperty(vars(), float, 'scalevangles')

    Product.InputProperty(vars(), str, 'savingcalibinto')






    @Slot(int, int, int, result = bool)
    def writeMemory(self, zone, address, value):
        self._dev.write_memory(zone, address, np.array([value], np.uint16))

    @Slot(int, int, int, result = str)
    def readMemory(self, zone, address, n_bytes):
        return str(np.array(self._dev.read_memory(zone, address, n_bytes), np.uint8).tolist())

    @Slot(float, int, result = bool)
    def calibrate(self, wall_distance, n_samples):
        if self._dev and self._connected:
            self._dev.set_calib_values(leddar.calib_types['ID_TIMEBASE_DELAY'], [0.0] * self._nChannels)
            self._calibration_wall_distance = wall_distance
            self._n_calibration_samples = n_samples
            self._calibration_samples = []
            self._calibration_states_samples = []
            self._received_echoes = []
            self.set_calibrating(self, True)
            self.set_calibrationProgress(self, 0.0)
            self.set_restoringCal(self, False)
            
            return True
        return False
    

    @Slot(str, result = bool)
    def restoreCalibration(self, spath_to = "rcalibration/calibration.pkl"):
        if self._dev and self._connected:
            self.set_restoringCal(self, True)
            self._dev.set_calib_values(leddar.calib_types['ID_TIMEBASE_DELAY'],[0.0] * self._nChannels)
            data = pickle.load(open(spath_to, 'rb'))

            self._specs['v'] =  data['v']
            self._specs['h'] = data['h']
            self._specs['v_fov'] = data['v_fov']
            self._specs['h_fov'] = data['h_fov']
            if 'angles' in data:
                self._specs['angles'] = data['data']
                self.scalevangles = data['scaling_angles']
            
            self.dTGainFactor = data['dT_slope']
            self.dTFix = data['dT_ref']
            self.aTGainFactor = data['aT_slope']
            self.aTThreshold = data['aT_threshold']
            self._dev.set_calib_values(leddar.calib_types['ID_TIMEBASE_DELAY'], data['offsets'])
            super(LiveCapture, self)._handle_specs_changed()

            
            return True
        return False

    
    @Slot(str, result = bool)
    def editResoSpecs(self, mode):
        if mode == "NONE":
            self.set_editingResoSpecs(self, False)
            self.set_resoSpecsVal(self,0)

        else:
            self.set_editingResoSpecs(self, True)
            self._resoSpecsMode = mode
            self.set_resoSpecsVal(self,self._specs[mode])
        return True
    
    @Slot(float, result = bool)
    def changeResoSpecs(self, new_value):
        if self._resoSpecsMode == "h_fov" or self._resoSpecsMode == "v_fov":
            self._specs[self._resoSpecsMode] = new_value
        elif self._resoSpecsMode == "h" or self._resoSpecsMode == "v":
            self._specs[self._resoSpecsMode] = int(new_value)
        super(LiveCapture, self)._handle_specs_changed()
        return True
    
    @Slot(str, result = bool)
    def changeAnglesSpecs(self, filename):
        c_filename = 'angles_table/' + filename
        self._specs['angles'] = clouds.custom_v_angles(self._specs, factor=self._scalevangles, filename=c_filename,  dtype = np.float32)

        self._specs['v_fov'] =  np.rad2deg(np.max(self._specs['angles'][:,0]) - np.min(self._specs['angles'][:,0]))
        
        super(LiveCapture, self)._handle_specs_changed()

        return True
        
    
    

    @Slot(str, result = bool)
    def changeIP(self, ip_string):
        if self._dev and self._connected:
            try:
                rv = self._dev.set_IP_config(False, ip_string)
                print(f"ip changed to {self._dev.get_IP_config()}, you need to reboot your device!")
                return rv
            except:
                traceback.print_exc()
                return False

    @Slot(str, QVariant, result = bool)
    def setLeddarProperty(self, name, value):
        if self._dev and self._connected:
            try:
                rv = self._dev.set_property_value(name, str(value))
                self.connectedChanged.emit()
                return rv
            except:
                traceback.print_exc()
                return False
    
    @Slot(str, result = QVariant)
    def getLeddarProperty(self, name):
        if self._dev and self._connected:
            try:
                return self._dev.get_property_value(name)
            except:
                traceback.print_exc()
                return False
        return None

    @Slot(str, result = QVariant)
    def getPropertyAvailableValues(self, name):
        if self._dev and self._connected:
            try:
                rv = self._dev.get_property_available_values(name)
                string_value = self._dev.get_property_value(name)
                if isinstance(rv, dict):
                    if rv['type'] in ['list', 'bitfield']:
                        rv['current'] = string_value
                    return rv
                else:
                    return {'type': 'list', 'data': [current], 'current': current}
            except:
                pass
        return None

    def _update(self):

        if not self._running:
            return

        if self._dev is None or not self._connected:

            self._dev = leddar.Device()

            if not self._dev.connect(self._ip):
                self._connected = False
                self.connectedChanged.emit()
                self._dev = None
                return

            self._connected = True
            self.connectedChanged.emit()
            self._emitConnectedChanged = True

            if self._specs is None:
                self._specs = utils.get_specs(self._dev)
            else:
                self._overriden_specs = True

            self._handle_specs_changed()

            def echo_callback(new_echoes):
                if not self._locked:
                    self._received_echoes.append(new_echoes)
                    self.makeDirty()

            self._dev.set_callback_echo(echo_callback)
            self._dev.set_data_mask(leddar.data_masks["PDM_ECHOES"])

            def state_callback(new_state):
                if not self._locked:
                    self._received_states.append(new_state)
                    self.makeDirty()

            self._dev.set_callback_state(state_callback)
            self._dev.set_data_mask(self._dev.get_data_mask() | leddar.data_masks["PDM_STATES"])

            self._update_traces_mode()

            self._update_data_thread()

            self._n_received = 0


        if self._received_echoes:
            self._n_received += 1
            self._locked = True
            self._nDroppedPackages = len(self._received_echoes) - 1
            last_received = self._received_echoes[-1]
            last_received['data'] = last_received['data'][np.isin(last_received['data']['flags'], self._rejectedFalgs, invert = True)]

            
            if self._calibrating:
                self._calibration_samples.append(last_received)
            else:
                if (self._dTCorrection) and (self._states is not None):
                    T = self._states['system_temp']
                    last_received = calibration.distance_correction_temperature(last_received, T, self._dTGainFactor, self._dTFix)
                
                if (self._aTCorrection) and (self._states is not None):
                    T = self._states['system_temp']
                    last_received = calibration.amplitude_correction_temperature(last_received, T, self._aTGainFactor, self._aTThreshold)
                

            
            self._received_echoes = []
            self._locked = False
            self.nDroppedPackagesChanged.emit()
            if self._ts_prev is None:
                self._ts_prev = time.time()

            ts = time.time()#last_received['timestamp']
            if self._n_received%10 == 0:
                self._fps = 10 /(ts - self._ts_prev)
                self.fpsChanged.emit()
                self._ts_prev = ts

            if self._emitConnectedChanged: #important for those watching properties. properties seem only valid after the first echo
                self._emitConnectedChanged = False
                self.connectedChanged.emit()

            

            
            self._update_with_last_received(last_received)

        if self._received_states:
            last_received = self._received_states[-1]
            self.set_states(self, last_received)
            self._received_states = []

            if self._calibrating:
                self._calibration_states_samples.append(last_received)
        
        if self._calibrating:
            self.set_calibrationProgress(self,  len(self._calibration_samples)/(self._n_calibration_samples+self._n_drop_calib_samples))

            if self._calibrationProgress >= 1.0:
                self._calibration_samples = self._calibration_samples[-self._n_calibration_samples::]
                self._calibration_states_samples = self._calibration_states_samples[-self._n_calibration_samples::]
                offsets = calibration.calibrate(self._calibration_samples, self._calibration_wall_distance, self._specs, self._interpCal)

                if self._dTCorrection:
                    offsets = calibration.set_offset_to_base_temperature(self._calibration_states_samples, offsets, self._dTGainFactor, self._dTFix)

                self._dev.set_calib_values(leddar.calib_types['ID_TIMEBASE_DELAY'], offsets.tolist())
                self.saveCalibration(offsets = offsets.tolist())
                self.set_calibrating(self, False)

        if self._received_traces_raw:
            self._update_traces_with_last_recieved(self._received_traces_raw[-1])
            self._received_traces_raw = []


    
    def saveCalibration(self, offsets, spath = 'calib_results/' ):

        if not os.path.exists(spath):
            os.makedirs(spath)
    

        dico = { 'v': self._specs['v'],
                 'h': self._specs['h'], 
                 'v_fov': self._specs['v_fov'], 
                 'h_fov': self._specs['h_fov'],
                 'dT_slope': self._dTGainFactor,
                 'dT_ref': self._dTFix,
                 'aT_slope': self._aTGainFactor,
                 'aT_threshold': self._aTThreshold,
                 'offsets': offsets,
                 'data': self._calibration_samples
                }
        
        if 'angles' in self._specs:
            dico['angles'] = self._specs['angles']
            dico['scaling_angles'] = self._scalevangles
        
        fname = spath + self._savingcalibinto
        with open(fname, 'wb') as f:
            pickle.dump(dico, f)
        
        return True
Exemplo n.º 26
0
class CheckBox(QCheckBox, TouchMarginPlugin, DirectAPILinkPlugin, FocusablePlugin):
	Q_ENUMS(MarginWidth) #This is needed here. I don't know why the definition in the TouchMarginPlugin doesn't work.
	jogWheelRotationCancelsClick = False #Widget doesn't use rotation. Ignore it.
	
	def __init__(self, parent=None, showHitRects=False):
		super().__init__(parent, showHitRects=showHitRects)
		
		self.theme = theme('dark')
		self._clickMarginRight = MarginWidth.none
		self.clickMarginColor = f"rgba({randint(128, 255)}, {randint(0, 32)}, {randint(128, 255)}, {randint(32,96)})"
		
		settings.observe('theme', 'dark', lambda name: (
			setattr(self, 'theme', theme(name)),
			self.refreshStyle(),
		))
		
		self.setMouseTracking(True)
		
		self.jogWheelLowResolutionRotation.connect(lambda delta, pressed: 
			not pressed and self.selectWidget(delta) )
		self.jogWheelClick.connect(lambda: self.injectKeystrokes(Qt.Key_Space))
		
		# Focus ring effect.
		self.jogWheelDown.connect(lambda: self.window().focusRing.focusIn(amount=.25)) #Small click effect.
		self.jogWheelUp.connect(lambda: self.window().focusRing.focusOut())
		self.jogWheelLongPress.connect(lambda: self.window().focusRing.focusOut(speed=.04))
	
	
	def sizeHint(self):
		return QSize(181, 81)
	
	
	def mousePressEvent(self, ev):
		"""Use the full area of the widget to toggle the checkbox, not just the visual checkbox and text."""
		if ev.button() == Qt.LeftButton:
			ev.accept()
			self.toggle()
		else:
			ev.ignore()
	
	
	def refreshStyle(self):
		if self.showHitRects:
			self.setStyleSheet(f"""
				CheckBox {{
					/* Editor style. Use border to show were click margin is, so we don't mess it up during layout. */
					font-size: 16px;
					color: {self.theme.text};
					background: {self.theme.backgroundInEditor}; /* The background is drawn under the button borders, so they are opaque if the background is opaque. */
					
					/* use borders instead of margins so we can see what we're doing */
					border-left:   {self.clickMarginLeft   * 10 + 1}px solid {self.clickMarginColor};
					border-right:  {self.clickMarginRight  * 10 + 1}px solid {self.clickMarginColor};
					border-top:    {self.clickMarginTop    * 10 + 1}px solid {self.clickMarginColor};
					border-bottom: {self.clickMarginBottom * 10 + 1}px solid {self.clickMarginColor};
				}}
				
				CheckBox::indicator:checked {{
					image: url(:/assets/images/{self.theme.checkbox.checked});
				}}
				CheckBox::indicator:unchecked {{
					image: url(:/assets/images/{self.theme.checkbox.unchecked});
				}}
				
			""" + self.originalStyleSheet())
		else:
			self.setStyleSheet(f"""
				CheckBox {{
					/* App style. Use margin to provide further click area outside the visual button. */
					font-size: 16px;
					color: {self.theme.text};
					background-color: {self.theme.background};
					
					/* Add some touch space so this widget is easier to press. */
					margin-left: {self.clickMarginLeft*10}px;
					margin-right: {self.clickMarginRight*10}px;
					margin-top: {self.clickMarginTop*10}px;
					margin-bottom: {self.clickMarginBottom*10}px;
				}}
				
				CheckBox::indicator:checked {{
					image: url(:/assets/images/{self.theme.checkbox.checked});
				}}
				CheckBox::indicator:unchecked {{
					image: url(:/assets/images/{self.theme.checkbox.unchecked});
				}}
			""" + self.originalStyleSheet())
Exemplo n.º 27
0
class CuraApplication(QtApplication):
    class ResourceTypes:
        QmlFiles = Resources.UserType + 1
        Firmware = Resources.UserType + 2

    Q_ENUMS(ResourceTypes)

    def __init__(self):
        Resources.addSearchPath(
            os.path.join(QtApplication.getInstallPrefix(), "share", "cura"))
        if not hasattr(sys, "frozen"):
            Resources.addSearchPath(
                os.path.join(os.path.abspath(os.path.dirname(__file__)), ".."))

        super().__init__(name="cura", version="master")

        self.setWindowIcon(
            QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))

        self.setRequiredPlugins([
            "CuraEngineBackend", "MeshView", "LayerView", "STLReader",
            "SelectionTool", "CameraTool", "GCodeWriter",
            "LocalFileOutputDevice"
        ])
        self._physics = None
        self._volume = None
        self._platform = None
        self._output_devices = {}
        self._print_information = None
        self._i18n_catalog = None
        self._previous_active_tool = None
        self._platform_activity = False
        self._job_name = None

        self.getMachineManager().activeMachineInstanceChanged.connect(
            self._onActiveMachineChanged)
        self.getMachineManager().addMachineRequested.connect(
            self._onAddMachineRequested)
        self.getController().getScene().sceneChanged.connect(
            self.updatePlatformActivity)

        Resources.addType(self.ResourceTypes.QmlFiles, "qml")
        Resources.addType(self.ResourceTypes.Firmware, "firmware")

        Preferences.getInstance().addPreference("cura/active_machine", "")
        Preferences.getInstance().addPreference("cura/active_mode", "simple")
        Preferences.getInstance().addPreference("cura/recent_files", "")
        Preferences.getInstance().addPreference("cura/categories_expanded", "")
        Preferences.getInstance().addPreference("view/center_on_select", True)
        Preferences.getInstance().addPreference("mesh/scale_to_fit", True)

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        self._recent_files = []
        files = Preferences.getInstance().getValue("cura/recent_files").split(
            ";")
        for f in files:
            if not os.path.isfile(f):
                continue

            self._recent_files.append(QUrl.fromLocalFile(f))

    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addPluginLocation(
            os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(
                os.path.join(os.path.abspath(os.path.dirname(__file__)), "..",
                             "plugins"))
            self._plugin_registry.loadPlugin("ConsoleLogger")

        self._plugin_registry.loadPlugins()

        if self.getBackend() == None:
            raise RuntimeError("Could not load the backend plugin!")

    def addCommandLineOptions(self, parser):
        super().addCommandLineOptions(parser)
        parser.add_argument(
            "file",
            nargs="*",
            help="Files to load after starting the application.")
        parser.add_argument("--debug",
                            dest="debug-mode",
                            action="store_true",
                            default=False,
                            help="Enable detailed crash reports.")

    def run(self):
        self._i18n_catalog = i18nCatalog("cura")

        i18nCatalog.setTagReplacements({
            "filename":
            "font color=\"black\"",
            "message":
            "font color=UM.Theme.colors.message_text;",
        })

        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("MeshView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis(
                [ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()
        self._platform = Platform(root)

        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setLightPosition(Vector(0, 150, 0))
        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(
            controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(0, 250, 900))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))
        controller.getScene().setActiveCamera("3d")

        self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(
            self.getController().getTool("CameraTool"))

        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))

        self.setMainQml(
            Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
        self.initializeEngine()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)

            self.exec_()

    #   Handle Qt events
    def event(self, event):
        if event.type() == QEvent.FileOpen:
            self._openFile(event.file())

        return super().event(event)

    def getPrintInformation(self):
        return self._print_information

    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation",
                                                self._print_information)
        self._cura_actions = CuraActions.CuraActions(self)
        engine.rootContext().setContextProperty("CuraActions",
                                                self._cura_actions)

        qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0,
                                   "ResourceTypes", "Just an Enum type")

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                if self._previous_active_tool:
                    self.getController().setActiveTool(
                        self._previous_active_tool)
                    self._previous_active_tool = None
                else:
                    self.getController().setActiveTool("TranslateTool")
            if Preferences.getInstance().getValue("view/center_on_select"):
                self._camera_animation.setStart(
                    self.getController().getTool("CameraTool").getOrigin())
                self._camera_animation.setTarget(
                    Selection.getSelectedObject(0).getWorldPosition())
                self._camera_animation.start()
        else:
            if self.getController().getActiveTool():
                self._previous_active_tool = self.getController(
                ).getActiveTool().getPluginId()
                self.getController().setActiveTool(None)
            else:
                self._previous_active_tool = None

    requestAddPrinter = pyqtSignal()
    activityChanged = pyqtSignal()

    @pyqtProperty(bool, notify=activityChanged)
    def getPlatformActivity(self):
        return self._platform_activity

    def updatePlatformActivity(self, node=None):
        count = 0
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            count += 1

        self._platform_activity = True if count > 0 else False
        self.activityChanged.emit()

    @pyqtSlot(str)
    def setJobName(self, name):
        if self._job_name != name:
            self._job_name = name
            self.jobNameChanged.emit()

    jobNameChanged = pyqtSignal()

    @pyqtProperty(str, notify=jobNameChanged)
    def jobName(self):
        return self._job_name

    ##  Remove an object from the scene
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            if node.getParent():
                group_node = node.getParent()
                if not group_node.callDecoration("isGroup"):
                    op = RemoveSceneNodeOperation(node)
                else:
                    while group_node.getParent().callDecoration("isGroup"):
                        group_node = group_node.getParent()
                    op = RemoveSceneNodeOperation(group_node)
            op.push()

    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            for i in range(count):
                if node.getParent() and node.getParent().callDecoration(
                        "isGroup"):
                    new_node = copy.deepcopy(
                        node.getParent())  #Copy the group node.
                    new_node.callDecoration("setConvexHull", None)

                    op.addOperation(
                        AddSceneNodeOperation(new_node,
                                              node.getParent().getParent()))
                else:
                    new_node = copy.deepcopy(node)
                    new_node.callDecoration("setConvexHull", None)
                    op.addOperation(
                        AddSceneNodeOperation(new_node, node.getParent()))

            op.push()

    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)
        if not node and object_id != 0:  #Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if not node:
            return

        if node.getParent() and node.getParent().callDecoration("isGroup"):
            node = node.getParent()

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()

    ##  Delete all mesh data on the scene.
    @pyqtSlot()
    def deleteAll(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  #Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  #Grouped nodes don't need resetting as their parent (the group) is resetted)
            nodes.append(node)
        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()

    ## Reset all translation on nodes with mesh data.
    @pyqtSlot()
    def resetAllTranslation(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  #Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  #Grouped nodes don't need resetting as their parent (the group) is resetted)

            nodes.append(node)
        if nodes:
            op = GroupedOperation()
            for node in nodes:
                # Ensure that the object is above the build platform
                move_distance = node.getBoundingBox().center.y
                if move_distance <= 0:
                    move_distance = -node.getBoundingBox().bottom
                op.addOperation(
                    SetTransformOperation(node, Vector(0, move_distance, 0)))

            op.push()

    ## Reset all transformations on nodes with mesh data.
    @pyqtSlot()
    def resetAll(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  #Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  #Grouped nodes don't need resetting as their parent (the group) is resetted)
            nodes.append(node)

        if nodes:
            op = GroupedOperation()

            for node in nodes:
                # Ensure that the object is above the build platform
                move_distance = node.getBoundingBox().center.y
                if move_distance <= 0:
                    move_distance = -node.getBoundingBox().bottom
                op.addOperation(
                    SetTransformOperation(node, Vector(0, move_distance, 0),
                                          Quaternion(), Vector(1, 1, 1)))

            op.push()

    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            if not node.getMeshData():
                continue

            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job._node = node
                job.finished.connect(self._reloadMeshFinished)
                job.start()

    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    recentFilesChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    @pyqtSlot("QStringList")
    def setExpandedCategories(self, categories):
        categories = list(set(categories))
        categories.sort()
        joined = ";".join(categories)
        if joined != Preferences.getInstance().getValue(
                "cura/categories_expanded"):
            Preferences.getInstance().setValue("cura/categories_expanded",
                                               joined)
            self.expandedCategoriesChanged.emit()

    expandedCategoriesChanged = pyqtSignal()

    @pyqtProperty("QStringList", notify=expandedCategoriesChanged)
    def expandedCategories(self):
        return Preferences.getInstance().getValue(
            "cura/categories_expanded").split(";")

    @pyqtSlot(str, result="QVariant")
    def getSettingValue(self, key):
        if not self.getMachineManager().getActiveProfile():
            return None
        return self.getMachineManager().getActiveProfile().getSettingValue(key)
        #return self.getActiveMachine().getSettingValueByKey(key)

    ##  Change setting by key value pair
    @pyqtSlot(str, "QVariant")
    def setSettingValue(self, key, value):
        if not self.getMachineManager().getActiveProfile():
            return

        self.getMachineManager().getActiveProfile().setSettingValue(key, value)

    @pyqtSlot()
    def mergeSelected(self):
        self.groupSelected()
        try:
            group_node = Selection.getAllSelectedObjects()[0]
        except Exception as e:
            return
        multi_material_decorator = MultiMaterialDecorator.MultiMaterialDecorator(
        )
        group_node.addDecorator(multi_material_decorator)
        # Reset the position of each node
        for node in group_node.getChildren():
            new_position = node.getMeshData().getCenterPosition()
            new_position = new_position.scale(node.getScale())
            node.setPosition(new_position)

        # Use the previously found center of the group bounding box as the new location of the group
        group_node.setPosition(group_node.getBoundingBox().center)

    @pyqtSlot()
    def groupSelected(self):
        group_node = SceneNode()
        group_decorator = GroupDecorator()
        group_node.addDecorator(group_decorator)
        group_node.setParent(self.getController().getScene().getRoot())

        for node in Selection.getAllSelectedObjects():
            node.setParent(group_node)
        group_node.setCenterPosition(group_node.getBoundingBox().center)
        #group_node.translate(Vector(0,group_node.getBoundingBox().center.y,0))
        group_node.translate(group_node.getBoundingBox().center)
        for node in group_node.getChildren():
            Selection.remove(node)

        Selection.add(group_node)

    @pyqtSlot()
    def ungroupSelected(self):
        ungrouped_nodes = []
        selected_objects = Selection.getAllSelectedObjects(
        )[:]  #clone the list
        for node in selected_objects:
            if node.callDecoration("isGroup"):
                children_to_move = []
                for child in node.getChildren():
                    if type(child) is SceneNode:
                        children_to_move.append(child)

                for child in children_to_move:
                    child.setParent(node.getParent())
                    print(node.getPosition())
                    child.translate(node.getPosition())
                    child.setPosition(child.getPosition().scale(
                        node.getScale()))
                    child.scale(node.getScale())
                    child.rotate(node.getOrientation())

                    Selection.add(child)
                    child.callDecoration("setConvexHull", None)
                node.setParent(None)
                ungrouped_nodes.append(node)
        for node in ungrouped_nodes:
            Selection.remove(node)

    def _onActiveMachineChanged(self):
        machine = self.getMachineManager().getActiveMachineInstance()
        if machine:
            pass
            #Preferences.getInstance().setValue("cura/active_machine", machine.getName())

            #self._volume.setWidth(machine.getSettingValueByKey("machine_width"))
            #self._volume.setHeight(machine.getSettingValueByKey("machine_height"))
            #self._volume.setDepth(machine.getSettingValueByKey("machine_depth"))

            #disallowed_areas = machine.getSettingValueByKey("machine_disallowed_areas")
            #areas = []
            #if disallowed_areas:
            #for area in disallowed_areas:
            #areas.append(Polygon(numpy.array(area, numpy.float32)))

            #self._volume.setDisallowedAreas(areas)

            #self._volume.rebuild()

            #offset = machine.getSettingValueByKey("machine_platform_offset")
            #if offset:
            #self._platform.setPosition(Vector(offset[0], offset[1], offset[2]))
            #else:
            #self._platform.setPosition(Vector(0.0, 0.0, 0.0))

    def _onFileLoaded(self, job):
        node = job.getResult()
        if node != None:
            node.setSelectable(True)
            node.setName(os.path.basename(job.getFileName()))

            op = AddSceneNodeOperation(
                node,
                self.getController().getScene().getRoot())
            op.push()

            self.getController().getScene().sceneChanged.emit(
                node)  #Force scene change.

    def _onJobFinished(self, job):
        if type(job) is not ReadMeshJob or not job.getResult():
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue("cura/recent_files", pref)
        self.recentFilesChanged.emit()

    def _reloadMeshFinished(self, job):
        # TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
        job._node.setMeshData(job.getResult().getMeshData())
        #job.getResult().setParent(self.getController().getScene().getRoot())
        #job._node.setParent(self.getController().getScene().getRoot())
        #job._node.meshDataChanged.emit(job._node)

    def _openFile(self, file):
        job = ReadMeshJob(os.path.abspath(file))
        job.finished.connect(self._onFileLoaded)
        job.start()

    def _onAddMachineRequested(self):
        self.requestAddPrinter.emit()
class ComboEditor(QDialog):

    closeSplit = pyqtSignal(QWidget)
    aboutToCloseComboEditor = pyqtSignal()
    allFilesClosed = pyqtSignal()
    splitEditor = pyqtSignal("QWidget*", "QWidget*", bool)
    recentTabsModified = pyqtSignal()

    class NAVIGATE:
        prev = 0
        next = 1

    Q_ENUMS(NAVIGATE)

    def __init__(self, original=False, Force_Free=False):
        super(ComboEditor, self).__init__(None)  #, Qt.WindowStaysOnTopHint)
        self.__original = original
        self.Force_Free = Force_Free
        self.__undocked = []
        self._single_undocked = []
        self._symbols_index = []
        self.__OFiles = []
        vbox = QVBoxLayout(self)
        vbox.setContentsMargins(0, 0, 0, 0)
        vbox.setSpacing(0)

        self.bar = ActionBar(self, main_combo=original)
        vbox.addWidget(self.bar)

        self.stackedEditor = QStackedLayout()
        vbox.addLayout(self.stackedEditor)

        self._main_container = IDE.get_service('main_container')

        if not self.__original and not self.Force_Free:
            self._main_container.fileOpened.connect(self._file_opened_by_main)

        # QApplication.instance().focusChanged["QWidget*", "QWidget*"].connect(\
        #     lambda w1, w2: QTimer.singleShot(10, lambda w1=w1, w2=w2: print("\n\nQApplication::focusChanged::", w1, w2)))

        self.bar.combo.showComboSelector.connect(
            self._main_container.change_tab)
        self.bar.changeCurrent.connect(self._set_current)
        self.bar.editorSplited.connect(self.split_editor)
        self.bar.runFile[str].connect(self._run_file)
        self.bar.closeFile.connect(lambda: self.closeSplit.emit(self))
        self.bar.addToProject[str].connect(self._add_to_project)
        self.bar.showFileInExplorer.connect(self._show_file_in_explorer)
        self.bar.goToSymbol.connect(self._go_to_symbol)
        self.bar.undockEditor.connect(self.undock_editor)
        self.bar.undockThisEditor.connect(self.single_undock_editor)
        self.bar.reopenTab[str].connect(self._main_container.open_file)
        self.bar.recentTabsModified.connect(
            self._main_container.recent_files_changed)
        self.bar.code_navigator.btnPrevious.clicked['bool'].connect(
            lambda: self._navigate_code(self.NAVIGATE.prev))
        self.bar.code_navigator.btnNext.clicked['bool'].connect(
            lambda: self._navigate_code(self.NAVIGATE.prev))

        # QTimer.singleShot(39999, lambda : print("\n\ncombo:-:", self))

    # def closeEvent(self, event):
    #     for comboeditor in self._single_undocked:
    #         print("has undocked", comboeditor)
    #         comboeditor.reject()
    #         comboeditor.deleteLater()

    #     self.bar._close_all_files()
    #     super(ComboEditor, self).closeEvent(event)

    def _navigate_code(self, val):
        op = self.bar.code_navigator.operation
        self._main_container.navigate_code_history(val, op)

    def setFocus(self):
        super(ComboEditor, self).setFocus()
        w = self.stackedEditor.currentWidget()
        if w:
            w.setFocus()
        self._editor_with_focus()

    def _file_opened_by_main(self, path):
        index = self.stackedEditor.currentIndex()
        ninjaide = IDE.getInstance()
        editable = ninjaide.get_or_create_editable(path)
        print("_file_opened_by_main", editable)
        self.add_editor(editable)
        self.bar.set_current_by_index(index)
        if index == -1:
            self.bar.set_current_by_index(0)

    def add_editor(self, neditable, keep_index=False):
        """Add Editor Widget to the UI area."""
        if neditable.editor:
            if self.__original or self.Force_Free:
                editor = neditable.editor
                print("\n\nadd_editor() ignora por ahora!", editor)

                # disconnect old Signals
                try:
                    editor.cursorPositionChanged[int, int].disconnect()
                except TypeError:
                    pass
                try:
                    editor.editorFocusObtained.disconnect()
                except TypeError:
                    pass
                try:
                    editor.currentLineChanged.disconnect()
                except TypeError:
                    pass
                try:
                    editor.modificationChanged['bool'].disconnect()
                except TypeError:
                    pass
                try:
                    neditable.checkersUpdated.disconnect()
                except TypeError:
                    pass
                try:
                    neditable.fileSaved.disconnect()
                except TypeError:
                    pass

                # Disonnect file system signals only in the original
                try:
                    neditable.fileClosing.disconnect()
                except TypeError:
                    pass
                if self.__original:
                    try:
                        neditable.askForSaveFileClosing.disconnect()
                    except TypeError:
                        pass
                    try:
                        neditable.fileChanged.disconnect()
                    except TypeError:
                        pass

            else:
                editor = self._main_container.create_text_editor_from_editable(
                    neditable)

            index = self.stackedEditor.currentIndex()
            self.stackedEditor.addWidget(editor)
            self.bar.add_item(neditable.display_name, neditable)
            if keep_index:
                self.bar.set_current_by_index(index)

            # Editor Signals
            editor.cursorPositionChanged[int, int].connect(
                self._update_cursor_position)
            editor.editorFocusObtained.connect(self._editor_with_focus)
            editor.currentLineChanged.connect(self._set_current_symbol)
            editor.modificationChanged['bool'].connect(self._editor_modified)
            neditable.checkersUpdated.connect(self._show_notification_icon)
            neditable.fileSaved.connect(self._update_symbols)
            neditable.fileSaved.connect(self._update_combo_info)

            # Connect file system signals only in the original
            neditable.fileClosing.connect(self._close_file)
            if self.__original:
                neditable.askForSaveFileClosing.connect(self._ask_for_save)
                neditable.fileChanged.connect(self._file_has_been_modified)

            # Load Symbols
            self._load_symbols(neditable)

    def show_combo_file(self):
        print("show_combo_file")
        self.bar.combo.showPopup()

    def show_combo_symbol(self):
        self.bar.symbols_combo.showPopup()

    def getOpenedFiles(self):
        return self.__OFiles.copy()

    def addOpenedFiles(self, fil):
        self.__OFiles.append(fil)

    def unlink_editors(self):
        for index in range(self.stackedEditor.count()):
            widget = self.stackedEditor.widget(index)
            widget.setDocument(QsciDocument())

    def split_editor(self, orientationVertical):
        new_widget = ComboEditor()
        for neditable in self.bar.get_editables():
            print("\nsplit_editor", neditable, new_widget)
            new_widget.add_editor(neditable)
        self.splitEditor.emit(self, new_widget, orientationVertical)

    def undock_editor(self):
        new_combo = ComboEditor()
        new_combo.setWindowTitle("NINJA-IDE")
        self.add_Undocked(new_combo)
        for neditable in self.bar.get_editables():
            print("undock_editor", neditable)
            new_combo.add_editor(neditable)
        new_combo.resize(500, 500)
        new_combo.aboutToCloseComboEditor.connect(self._remove_undock)
        new_combo.show()

    def _remove_undock(self):
        print("_remove_undock", self.sender())
        widget = self.sender()
        self.sub_Undocked(widget)

    def add_Undocked(self, combedit):
        self.__undocked.append(combedit)

    def sub_Undocked(self, combedit):
        self.__undocked.remove(combedit)

    def add_SingleUndocked(self, combedit):
        self._single_undocked.append(combedit)

    def sub_SingleUndocked(self, combedit):
        self._single_undocked.remove(combedit)

    # aún no se ha puesto en funcionamiento!.
    def single_split_editor(self, orientationVertical):
        new_widget = ComboEditor()
        for neditable in self.bar.get_editables():
            print("\nsingle_split_editor", neditable, new_widget)
            new_widget.add_editor(neditable)
        self.splitEditor.emit(self, new_widget, orientationVertical)

    def single_undock_editor(self):
        new_combo = ComboEditor(Force_Free=True)
        new_combo.setWindowTitle("NINJA-IDE")
        self.add_SingleUndocked(new_combo)

        nEdit = self.stackedEditor.takeAt(
            self.stackedEditor.currentIndex()).widget()
        ide = IDE.getInstance()
        ide.unload_NEditable(nEdit)
        neditable = self.bar.take_editable()
        print("\n\nsingle_undock_editor:::", neditable, neditable.editor,
              nEdit, neditable.nfile)

        if self.stackedEditor.isEmpty():
            self.allFilesClosed.emit()

        new_combo.add_editor(neditable)
        new_combo.stackedEditor.setCurrentIndex(
            0)  # new_combo.stackedEditor.setCurrentWidget(nEdit)
        new_combo.resize(500, 500)
        new_combo.aboutToCloseComboEditor.connect(self.single__remove_undock)
        new_combo.show()

    def single__remove_undock(self):
        print("single__remove_undock", self.sender())
        widget = self.sender()
        self.sub_SingleUndocked(widget)
        ##widget.deleteLater()

    def bind_Editable(self, editable):
        self.bar.add_item(neditable.display_name, editable)

    def close_current_file(self):
        self.bar.about_to_close_file()

    def _close_file(self, neditable):
        print("\n\n_close_file", self.__original, self.Force_Free)
        index = self.bar.close_file(neditable)
        layoutItem = self.stackedEditor.takeAt(index)
        #neditable.editor.completer.cc.unload_module()
        self._add_to_last_opened(neditable.file_path)
        layoutItem.widget().deleteLater(
        )  # @@4 -> Comentando ésta linea desaparece el mensaje

        if self.stackedEditor.isEmpty():
            self.allFilesClosed.emit()

    def _add_to_last_opened(self, path):
        if path not in settings.LAST_OPENED_FILES:
            settings.LAST_OPENED_FILES.append(path)
            if len(settings.LAST_OPENED_FILES) > settings.MAX_REMEMBER_TABS:
                self.__lastOpened = self.__lastOpened[1:]
            self.recentTabsModified.emit()

    def _editor_with_focus(self):
        if self._main_container.current_comboEditor is not self:
            self._main_container.current_comboEditor = self
            editor = self.stackedEditor.currentWidget()
            self._main_container.current_editor_changed(
                editor.neditable.file_path)
            self._load_symbols(editor.neditable)
            editor.neditable.update_checkers_display()

    def _ask_for_save(self, neditable):
        val = QMessageBox.No
        fileName = neditable.nfile.file_name
        val = QMessageBox.question(
            self, (self.tr('The file %s was not saved') % fileName),
            self.tr("Do you want to save before closing?"),
            QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        if val == QMessageBox.No:
            neditable.nfile.close(force_close=True)
        elif val == QMessageBox.Yes:
            neditable.ignore_checkers = True
            self._main_container.save_file(neditable.editor)
            neditable.nfile.close()

    def _file_has_been_modified(self, neditable):
        val = QMessageBox.No
        fileName = neditable.file_path
        val = QMessageBox.question(
            self, translations.TR_FILE_HAS_BEEN_MODIFIED,
            "%s%s" % (fileName, translations.TR_FILE_MODIFIED_OUTSIDE),
            QMessageBox.Yes | QMessageBox.No)
        if val == QMessageBox.Yes:
            neditable.reload_file()

    def _run_file(self, path):
        self._main_container.run_file(path)

    def _add_to_project(self, path):
        self._main_container._add_to_project(path)

    def _show_file_in_explorer(self, path):
        '''Connected to ActionBar's showFileInExplorer(QString)
        signal, forwards the file path on to the main container.'''
        self._main_container._show_file_in_explorer(path)

    def set_current(self, neditable):
        if neditable:
            self.bar.set_current_file(neditable)

    def _set_current(self, neditable, index):
        if neditable:
            self.stackedEditor.setCurrentIndex(index)
            editor = self.stackedEditor.currentWidget()
            self._update_cursor_position(ignore_sender=True)
            editor.setFocus()
            self._main_container.current_editor_changed(neditable.file_path)
            self._load_symbols(neditable)
            self._show_file_in_explorer(neditable.file_path)
            neditable.update_checkers_display()

    def widget(self, index):
        return self.stackedEditor.widget(index)

    def countEditors(self):
        """Return the number of editors opened."""
        return self.stackedEditor.count()

    def currentEditor(self):
        """ rtn -> Editor()# local QsciScintilla"""
        return self.stackedEditor.currentWidget()

    def _update_cursor_position(self, line=0, col=0, ignore_sender=False):
        obj = self.sender()
        editor = self.stackedEditor.currentWidget()
        # Check if it's current to avoid signals from other splits.
        if ignore_sender or editor == obj:
            line += 1
            self.bar.update_line_col(line, col)

    def _set_current_symbol(self, line, ignore_sender=False):
        obj = self.sender()
        editor = self.stackedEditor.currentWidget()
        # Check if it's current to avoid signals from other splits.
        if ignore_sender or editor == obj:
            index = bisect.bisect(self._symbols_index, line)
            if (index >= len(self._symbols_index)
                    or self._symbols_index[index] > (line + 1)):
                index -= 1
            self.bar.set_current_symbol(index)

    def _editor_modified(self, value):
        obj = self.sender()
        neditable = obj.neditable
        if value:
            text = "\u2022 %s" % neditable.display_name
            self.bar.update_item_text(neditable, text)
        else:
            self.bar.update_item_text(neditable, neditable.display_name)

    def _go_to_symbol(self, index):
        print("_go_to_symbol in index:", index)
        line = self._symbols_index[index]
        editor = self.stackedEditor.currentWidget()
        editor.go_to_line(line)

    def _update_symbols(self, neditable):
        editor = self.stackedEditor.currentWidget()
        # Check if it's current to avoid signals from other splits.
        if editor == neditable.editor:
            self._load_symbols(neditable)

    def _update_combo_info(self, neditable):
        self.bar.update_item_text(neditable, neditable.display_name)
        self._main_container.current_editor_changed(neditable.file_path)

    def _load_symbols(self, neditable):
        symbols_handler = handlers.get_symbols_handler('py')
        source = neditable.editor.text()
        source = source.encode(neditable.editor.encoding)
        symbols, symbols_simplified = symbols_handler.obtain_symbols(
            source, simple=True)
        self._symbols_index = sorted(symbols_simplified.keys())
        symbols_simplified = sorted(list(symbols_simplified.items()),
                                    key=lambda x: x[0])
        self.bar.add_symbols(symbols_simplified)
        line, _ = neditable.editor.getCursorPosition()
        self._set_current_symbol(line, True)
        tree_symbols = IDE.get_service('symbols_explorer')
        tree_symbols.update_symbols_tree(symbols, neditable.file_path)

    def _show_notification_icon(self, neditable):
        checkers = neditable.sorted_checkers
        icon = QIcon()
        for items in checkers:
            checker, color, _ = items
            if checker.checks:
                if isinstance(checker.checker_icon, int):
                    icon = self.style().standardIcon(checker.checker_icon)
                elif isinstance(checker.checker_icon, str):
                    icon = QIcon(checker.checker_icon)
                break
        self.bar.update_item_icon(neditable, icon)

    def show_menu_navigation(self):
        self.bar.code_navigator.show_menu_navigation()

    def closeEvent(self, event):
        for comboeditor in self._single_undocked:
            print("has undocked", comboeditor)
            comboeditor.reject()
            comboeditor.deleteLater()

        # print("\n\ncloseEvent", self)
        if self.__original:
            self.bar._close_all_files()
        self.aboutToCloseComboEditor.emit()
        super(ComboEditor, self).closeEvent(event)

    def reject(self):
        if not self.__original:
            super(ComboEditor, self).reject()
Exemplo n.º 29
0
class CuraApplication(QtApplication):
    class ResourceTypes:
        QmlFiles = Resources.UserType + 1
        Firmware = Resources.UserType + 2
        QualityInstanceContainer = Resources.UserType + 3
        MaterialInstanceContainer = Resources.UserType + 4
        VariantInstanceContainer = Resources.UserType + 5
        UserInstanceContainer = Resources.UserType + 6
        MachineStack = Resources.UserType + 7
        ExtruderStack = Resources.UserType + 8

    Q_ENUMS(ResourceTypes)

    def __init__(self):
        Resources.addSearchPath(
            os.path.join(QtApplication.getInstallPrefix(), "share", "cura",
                         "resources"))
        if not hasattr(sys, "frozen"):
            Resources.addSearchPath(
                os.path.join(os.path.abspath(os.path.dirname(__file__)), "..",
                             "resources"))

        self._open_file_queue = []  # Files to open when plug-ins are loaded.

        # Need to do this before ContainerRegistry tries to load the machines
        SettingDefinition.addSupportedProperty("settable_per_mesh",
                                               DefinitionPropertyType.Any,
                                               default=True,
                                               read_only=True)
        SettingDefinition.addSupportedProperty("settable_per_extruder",
                                               DefinitionPropertyType.Any,
                                               default=True,
                                               read_only=True)
        # this setting can be changed for each group in one-at-a-time mode
        SettingDefinition.addSupportedProperty("settable_per_meshgroup",
                                               DefinitionPropertyType.Any,
                                               default=True,
                                               read_only=True)
        SettingDefinition.addSupportedProperty("settable_globally",
                                               DefinitionPropertyType.Any,
                                               default=True,
                                               read_only=True)

        # From which stack the setting would inherit if not defined per object (handled in the engine)
        # AND for settings which are not settable_per_mesh:
        # which extruder is the only extruder this setting is obtained from
        SettingDefinition.addSupportedProperty("limit_to_extruder",
                                               DefinitionPropertyType.Function,
                                               default="-1")

        # For settings which are not settable_per_mesh and not settable_per_extruder:
        # A function which determines the glabel/meshgroup value by looking at the values of the setting in all (used) extruders
        SettingDefinition.addSupportedProperty("resolve",
                                               DefinitionPropertyType.Function,
                                               default=None)

        SettingDefinition.addSettingType("extruder", None, str, Validator)

        SettingFunction.registerOperator(
            "extruderValues", cura.Settings.ExtruderManager.getExtruderValues)
        SettingFunction.registerOperator(
            "extruderValue", cura.Settings.ExtruderManager.getExtruderValue)
        SettingFunction.registerOperator(
            "resolveOrValue", cura.Settings.ExtruderManager.getResolveOrValue)

        ## Add the 4 types of profiles to storage.
        Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer,
                                 "quality")
        Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer,
                                 "variants")
        Resources.addStorageType(self.ResourceTypes.MaterialInstanceContainer,
                                 "materials")
        Resources.addStorageType(self.ResourceTypes.UserInstanceContainer,
                                 "user")
        Resources.addStorageType(self.ResourceTypes.ExtruderStack, "extruders")
        Resources.addStorageType(self.ResourceTypes.MachineStack,
                                 "machine_instances")

        ContainerRegistry.getInstance().addResourceType(
            self.ResourceTypes.QualityInstanceContainer)
        ContainerRegistry.getInstance().addResourceType(
            self.ResourceTypes.VariantInstanceContainer)
        ContainerRegistry.getInstance().addResourceType(
            self.ResourceTypes.MaterialInstanceContainer)
        ContainerRegistry.getInstance().addResourceType(
            self.ResourceTypes.UserInstanceContainer)
        ContainerRegistry.getInstance().addResourceType(
            self.ResourceTypes.ExtruderStack)
        ContainerRegistry.getInstance().addResourceType(
            self.ResourceTypes.MachineStack)

        ##  Initialise the version upgrade manager with Cura's storage paths.
        import UM.VersionUpgradeManager  #Needs to be here to prevent circular dependencies.
        UM.VersionUpgradeManager.VersionUpgradeManager.getInstance(
        ).setCurrentVersions({
            ("quality", UM.Settings.InstanceContainer.Version):
            (self.ResourceTypes.QualityInstanceContainer,
             "application/x-uranium-instancecontainer"),
            ("machine_stack", UM.Settings.ContainerStack.Version):
            (self.ResourceTypes.MachineStack,
             "application/x-uranium-containerstack"),
            ("preferences", UM.Preferences.Version):
            (Resources.Preferences, "application/x-uranium-preferences"),
            ("user", UM.Settings.InstanceContainer.Version):
            (self.ResourceTypes.UserInstanceContainer,
             "application/x-uranium-instancecontainer")
        })

        self._machine_action_manager = MachineActionManager.MachineActionManager(
        )
        self._machine_manager = None  # This is initialized on demand.
        self._setting_inheritance_manager = None

        self._additional_components = {
        }  # Components to add to certain areas in the interface

        super().__init__(name="cura",
                         version=CuraVersion,
                         buildtype=CuraBuildType)

        self.setWindowIcon(
            QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))

        self.setRequiredPlugins([
            "CuraEngineBackend", "MeshView", "LayerView", "STLReader",
            "SelectionTool", "CameraTool", "GCodeWriter",
            "LocalFileOutputDevice"
        ])
        self._physics = None
        self._volume = None
        self._output_devices = {}
        self._print_information = None
        self._previous_active_tool = None
        self._platform_activity = False
        self._scene_bounding_box = AxisAlignedBox.Null

        self._job_name = None
        self._center_after_select = False
        self._camera_animation = None
        self._cura_actions = None
        self._started = False

        self._message_box_callback = None
        self._message_box_callback_arguments = []

        self._i18n_catalog = i18nCatalog("cura")

        self.getController().getScene().sceneChanged.connect(
            self.updatePlatformActivity)
        self.getController().toolOperationStopped.connect(
            self._onToolOperationStopped)

        Resources.addType(self.ResourceTypes.QmlFiles, "qml")
        Resources.addType(self.ResourceTypes.Firmware, "firmware")

        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Loading machines..."))

        # Add empty variant, material and quality containers.
        # Since they are empty, they should never be serialized and instead just programmatically created.
        # We need them to simplify the switching between materials.
        empty_container = ContainerRegistry.getInstance(
        ).getEmptyInstanceContainer()
        empty_variant_container = copy.deepcopy(empty_container)
        empty_variant_container._id = "empty_variant"
        empty_variant_container.addMetaDataEntry("type", "variant")
        ContainerRegistry.getInstance().addContainer(empty_variant_container)
        empty_material_container = copy.deepcopy(empty_container)
        empty_material_container._id = "empty_material"
        empty_material_container.addMetaDataEntry("type", "material")
        ContainerRegistry.getInstance().addContainer(empty_material_container)
        empty_quality_container = copy.deepcopy(empty_container)
        empty_quality_container._id = "empty_quality"
        empty_quality_container.setName("Not supported")
        empty_quality_container.addMetaDataEntry("quality_type", "normal")
        empty_quality_container.addMetaDataEntry("type", "quality")
        ContainerRegistry.getInstance().addContainer(empty_quality_container)
        empty_quality_changes_container = copy.deepcopy(empty_container)
        empty_quality_changes_container._id = "empty_quality_changes"
        empty_quality_changes_container.addMetaDataEntry(
            "type", "quality_changes")
        ContainerRegistry.getInstance().addContainer(
            empty_quality_changes_container)

        # Set the filename to create if cura is writing in the config dir.
        self._config_lock_filename = os.path.join(
            Resources.getConfigStoragePath(), CONFIG_LOCK_FILENAME)
        self.waitConfigLockFile()
        ContainerRegistry.getInstance().load()

        Preferences.getInstance().addPreference("cura/active_mode", "simple")
        Preferences.getInstance().addPreference("cura/recent_files", "")
        Preferences.getInstance().addPreference("cura/categories_expanded", "")
        Preferences.getInstance().addPreference("cura/jobname_prefix", True)
        Preferences.getInstance().addPreference("view/center_on_select", True)
        Preferences.getInstance().addPreference("mesh/scale_to_fit", True)
        Preferences.getInstance().addPreference("mesh/scale_tiny_meshes", True)

        for key in [
                "dialog_load_path",  # dialog_save_path is in LocalFileOutputDevicePlugin
                "dialog_profile_path",
                "dialog_material_path"
        ]:

            Preferences.getInstance().addPreference("local_file/%s" % key,
                                                    os.path.expanduser("~/"))

        Preferences.getInstance().setDefault("local_file/last_used_type",
                                             "text/x-gcode")

        Preferences.getInstance().setDefault(
            "general/visible_settings", """
            machine_settings
            resolution
                layer_height
            shell
                wall_thickness
                top_bottom_thickness
            infill
                infill_sparse_density
            material
                material_print_temperature
                material_bed_temperature
                material_diameter
                material_flow
                retraction_enable
            speed
                speed_print
                speed_travel
                acceleration_print
                acceleration_travel
                jerk_print
                jerk_travel
            travel
            cooling
                cool_fan_enabled
            support
                support_enable
                support_extruder_nr
                support_type
                support_interface_density
            platform_adhesion
                adhesion_type
                adhesion_extruder_nr
                brim_width
                raft_airgap
                layer_0_z_overlap
                raft_surface_layers
            dual
                prime_tower_enable
                prime_tower_size
                prime_tower_position_x
                prime_tower_position_y
            meshfix
            blackmagic
                print_sequence
                infill_mesh
            experimental
        """.replace("\n", ";").replace(" ", ""))

        JobQueue.getInstance().jobFinished.connect(self._onJobFinished)

        self.applicationShuttingDown.connect(self.saveSettings)
        self.engineCreatedSignal.connect(self._onEngineCreated)
        self._recent_files = []
        files = Preferences.getInstance().getValue("cura/recent_files").split(
            ";")
        for f in files:
            if not os.path.isfile(f):
                continue

            self._recent_files.append(QUrl.fromLocalFile(f))

    ## Lock file check: if (another) Cura is writing in the Config dir.
    #  one may not be able to read a valid set of files while writing. Not entirely fool-proof,
    #  but works when you start Cura shortly after shutting down.
    def waitConfigLockFile(self):
        waitFileDisappear(
            self._config_lock_filename,
            max_age_seconds=10,
            msg="Waiting for Cura to finish writing in the config dir...")

    def _onEngineCreated(self):
        self._engine.addImageProvider(
            "camera", CameraImageProvider.CameraImageProvider())

    ## A reusable dialogbox
    #
    showMessageBox = pyqtSignal(str,
                                str,
                                str,
                                str,
                                int,
                                int,
                                arguments=[
                                    "title", "text", "informativeText",
                                    "detailedText", "buttons", "icon"
                                ])

    def messageBox(self,
                   title,
                   text,
                   informativeText="",
                   detailedText="",
                   buttons=QMessageBox.Ok,
                   icon=QMessageBox.NoIcon,
                   callback=None,
                   callback_arguments=[]):
        self._message_box_callback = callback
        self._message_box_callback_arguments = callback_arguments
        self.showMessageBox.emit(title, text, informativeText, detailedText,
                                 buttons, icon)

    @pyqtSlot(int)
    def messageBoxClosed(self, button):
        if self._message_box_callback:
            self._message_box_callback(button,
                                       *self._message_box_callback_arguments)
            self._message_box_callback = None
            self._message_box_callback_arguments = []

    showPrintMonitor = pyqtSignal(bool, arguments=["show"])

    ##  Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
    #
    #   Note that the AutoSave plugin also calls this method.
    def saveSettings(self):
        if not self._started:  # Do not do saving during application start
            return

        self.waitConfigLockFile()

        # When starting Cura, we check for the lockFile which is created and deleted here
        with lockFile(self._config_lock_filename):

            for instance in ContainerRegistry.getInstance(
            ).findInstanceContainers():
                if not instance.isDirty():
                    continue

                try:
                    data = instance.serialize()
                except NotImplementedError:
                    continue
                except Exception:
                    Logger.logException(
                        "e",
                        "An exception occurred when serializing container %s",
                        instance.getId())
                    continue

                mime_type = ContainerRegistry.getMimeTypeForContainer(
                    type(instance))
                file_name = urllib.parse.quote_plus(
                    instance.getId()) + "." + mime_type.preferredSuffix
                instance_type = instance.getMetaDataEntry("type")
                path = None
                if instance_type == "material":
                    path = Resources.getStoragePath(
                        self.ResourceTypes.MaterialInstanceContainer,
                        file_name)
                elif instance_type == "quality" or instance_type == "quality_changes":
                    path = Resources.getStoragePath(
                        self.ResourceTypes.QualityInstanceContainer, file_name)
                elif instance_type == "user":
                    path = Resources.getStoragePath(
                        self.ResourceTypes.UserInstanceContainer, file_name)
                elif instance_type == "variant":
                    path = Resources.getStoragePath(
                        self.ResourceTypes.VariantInstanceContainer, file_name)

                if path:
                    instance.setPath(path)
                    with SaveFile(path, "wt") as f:
                        f.write(data)

            for stack in ContainerRegistry.getInstance().findContainerStacks():
                self.saveStack(stack)

    def saveStack(self, stack):
        if not stack.isDirty():
            return
        try:
            data = stack.serialize()
        except NotImplementedError:
            return
        except Exception:
            Logger.logException(
                "e", "An exception occurred when serializing container %s",
                stack.getId())
            return

        mime_type = ContainerRegistry.getMimeTypeForContainer(type(stack))
        file_name = urllib.parse.quote_plus(
            stack.getId()) + "." + mime_type.preferredSuffix
        stack_type = stack.getMetaDataEntry("type", None)
        path = None
        if not stack_type or stack_type == "machine":
            path = Resources.getStoragePath(self.ResourceTypes.MachineStack,
                                            file_name)
        elif stack_type == "extruder_train":
            path = Resources.getStoragePath(self.ResourceTypes.ExtruderStack,
                                            file_name)
        if path:
            stack.setPath(path)
            with SaveFile(path, "wt") as f:
                f.write(data)

    @pyqtSlot(str, result=QUrl)
    def getDefaultPath(self, key):
        default_path = Preferences.getInstance().getValue("local_file/%s" %
                                                          key)
        return QUrl.fromLocalFile(default_path)

    @pyqtSlot(str, str)
    def setDefaultPath(self, key, default_path):
        Preferences.getInstance().setValue("local_file/%s" % key, default_path)

    ##  Handle loading of all plugin types (and the backend explicitly)
    #   \sa PluginRegistery
    def _loadPlugins(self):
        self._plugin_registry.addType("profile_reader", self._addProfileReader)
        self._plugin_registry.addType("profile_writer", self._addProfileWriter)
        self._plugin_registry.addPluginLocation(
            os.path.join(QtApplication.getInstallPrefix(), "lib", "cura"))
        if not hasattr(sys, "frozen"):
            self._plugin_registry.addPluginLocation(
                os.path.join(os.path.abspath(os.path.dirname(__file__)), "..",
                             "plugins"))
            self._plugin_registry.loadPlugin("ConsoleLogger")
            self._plugin_registry.loadPlugin("CuraEngineBackend")

        self._plugin_registry.loadPlugins()

        if self.getBackend() == None:
            raise RuntimeError("Could not load the backend plugin!")

        self._plugins_loaded = True

    def addCommandLineOptions(self, parser):
        super().addCommandLineOptions(parser)
        parser.add_argument(
            "file",
            nargs="*",
            help="Files to load after starting the application.")
        parser.add_argument("--debug",
                            dest="debug-mode",
                            action="store_true",
                            default=False,
                            help="Enable detailed crash reports.")

    def run(self):
        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))

        controller = self.getController()

        controller.setActiveView("SolidView")
        controller.setCameraTool("CameraTool")
        controller.setSelectionTool("SelectionTool")

        t = controller.getTool("TranslateTool")
        if t:
            t.setEnabledAxis(
                [ToolHandle.XAxis, ToolHandle.YAxis, ToolHandle.ZAxis])

        Selection.selectionChanged.connect(self.onSelectionChanged)

        root = controller.getScene().getRoot()

        # The platform is a child of BuildVolume
        self._volume = BuildVolume.BuildVolume(root)

        self.getRenderer().setBackgroundColor(QColor(245, 245, 245))

        self._physics = PlatformPhysics.PlatformPhysics(
            controller, self._volume)

        camera = Camera("3d", root)
        camera.setPosition(Vector(-80, 250, 700))
        camera.setPerspective(True)
        camera.lookAt(Vector(0, 0, 0))
        controller.getScene().setActiveCamera("3d")

        self.getController().getTool("CameraTool").setOrigin(Vector(0, 100, 0))

        self._camera_animation = CameraAnimation.CameraAnimation()
        self._camera_animation.setCameraTool(
            self.getController().getTool("CameraTool"))

        self.showSplashMessage(
            self._i18n_catalog.i18nc("@info:progress", "Loading interface..."))

        # Initialise extruder so as to listen to global container stack changes before the first global container stack is set.
        cura.Settings.ExtruderManager.getInstance()
        qmlRegisterSingletonType(cura.Settings.MachineManager, "Cura", 1, 0,
                                 "MachineManager", self.getMachineManager)
        qmlRegisterSingletonType(cura.Settings.SettingInheritanceManager,
                                 "Cura", 1, 0, "SettingInheritanceManager",
                                 self.getSettingInheritanceManager)
        qmlRegisterSingletonType(MachineActionManager.MachineActionManager,
                                 "Cura", 1, 0, "MachineActionManager",
                                 self.getMachineActionManager)
        self.setMainQml(
            Resources.getPath(self.ResourceTypes.QmlFiles, "Cura.qml"))
        self._qml_import_paths.append(
            Resources.getPath(self.ResourceTypes.QmlFiles))
        self.initializeEngine()

        if self._engine.rootObjects:
            self.closeSplash()

            for file in self.getCommandLineOption("file", []):
                self._openFile(file)
            for file_name in self._open_file_queue:  #Open all the files that were queued up while plug-ins were loading.
                self._openFile(file_name)

            self._started = True

            self.exec_()

    def getMachineManager(self, *args):
        if self._machine_manager is None:
            self._machine_manager = cura.Settings.MachineManager.createMachineManager(
            )
        return self._machine_manager

    def getSettingInheritanceManager(self, *args):
        if self._setting_inheritance_manager is None:
            self._setting_inheritance_manager = cura.Settings.SettingInheritanceManager.createSettingInheritanceManager(
            )
        return self._setting_inheritance_manager

    ##  Get the machine action manager
    #   We ignore any *args given to this, as we also register the machine manager as qml singleton.
    #   It wants to give this function an engine and script engine, but we don't care about that.
    def getMachineActionManager(self, *args):
        return self._machine_action_manager

    ##   Handle Qt events
    def event(self, event):
        if event.type() == QEvent.FileOpen:
            if self._plugins_loaded:
                self._openFile(event.file())
            else:
                self._open_file_queue.append(event.file())

        return super().event(event)

    ##  Get print information (duration / material used)
    def getPrintInformation(self):
        return self._print_information

    ##  Registers objects for the QML engine to use.
    #
    #   \param engine The QML engine.
    def registerObjects(self, engine):
        engine.rootContext().setContextProperty("Printer", self)
        engine.rootContext().setContextProperty("CuraApplication", self)
        self._print_information = PrintInformation.PrintInformation()
        engine.rootContext().setContextProperty("PrintInformation",
                                                self._print_information)
        self._cura_actions = CuraActions.CuraActions(self)
        engine.rootContext().setContextProperty("CuraActions",
                                                self._cura_actions)

        qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0,
                                   "ResourceTypes", "Just an Enum type")

        qmlRegisterType(cura.Settings.ExtrudersModel, "Cura", 1, 0,
                        "ExtrudersModel")

        qmlRegisterType(cura.Settings.ContainerSettingsModel, "Cura", 1, 0,
                        "ContainerSettingsModel")
        qmlRegisterSingletonType(
            cura.Settings.ProfilesModel, "Cura", 1, 0, "ProfilesModel",
            cura.Settings.ProfilesModel.createProfilesModel)
        qmlRegisterType(cura.Settings.QualityAndUserProfilesModel, "Cura", 1,
                        0, "QualityAndUserProfilesModel")
        qmlRegisterType(cura.Settings.UserProfilesModel, "Cura", 1, 0,
                        "UserProfilesModel")
        qmlRegisterType(cura.Settings.MaterialSettingsVisibilityHandler,
                        "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
        qmlRegisterType(cura.Settings.QualitySettingsModel, "Cura", 1, 0,
                        "QualitySettingsModel")
        qmlRegisterType(cura.Settings.MachineNameValidator, "Cura", 1, 0,
                        "MachineNameValidator")

        qmlRegisterSingletonType(
            cura.Settings.ContainerManager, "Cura", 1, 0, "ContainerManager",
            cura.Settings.ContainerManager.createContainerManager)

        # As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
        actions_url = QUrl.fromLocalFile(
            os.path.abspath(
                Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
                                  "Actions.qml")))
        qmlRegisterSingletonType(actions_url, "Cura", 1, 0, "Actions")

        engine.rootContext().setContextProperty(
            "ExtruderManager", cura.Settings.ExtruderManager.getInstance())

        for path in Resources.getAllResourcesOfType(
                CuraApplication.ResourceTypes.QmlFiles):
            type_name = os.path.splitext(os.path.basename(path))[0]
            if type_name in ("Cura", "Actions"):
                continue

            qmlRegisterType(QUrl.fromLocalFile(path), "Cura", 1, 0, type_name)

    def onSelectionChanged(self):
        if Selection.hasSelection():
            if not self.getController().getActiveTool():
                if self._previous_active_tool:
                    self.getController().setActiveTool(
                        self._previous_active_tool)
                    self._previous_active_tool = None
                else:
                    self.getController().setActiveTool("TranslateTool")
            if Preferences.getInstance().getValue("view/center_on_select"):
                self._center_after_select = True
        else:
            if self.getController().getActiveTool():
                self._previous_active_tool = self.getController(
                ).getActiveTool().getPluginId()
                self.getController().setActiveTool(None)

    def _onToolOperationStopped(self, event):
        if self._center_after_select:
            self._center_after_select = False
            self._camera_animation.setStart(
                self.getController().getTool("CameraTool").getOrigin())
            self._camera_animation.setTarget(
                Selection.getSelectedObject(0).getWorldPosition())
            self._camera_animation.start()

    requestAddPrinter = pyqtSignal()
    activityChanged = pyqtSignal()
    sceneBoundingBoxChanged = pyqtSignal()

    @pyqtProperty(bool, notify=activityChanged)
    def getPlatformActivity(self):
        return self._platform_activity

    @pyqtProperty(str, notify=sceneBoundingBoxChanged)
    def getSceneBoundingBoxString(self):
        return self._i18n_catalog.i18nc(
            "@info", "%(width).1f x %(depth).1f x %(height).1f mm") % {
                'width': self._scene_bounding_box.width.item(),
                'depth': self._scene_bounding_box.depth.item(),
                'height': self._scene_bounding_box.height.item()
            }

    def updatePlatformActivity(self, node=None):
        count = 0
        scene_bounding_box = None
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            count += 1
            if not scene_bounding_box:
                scene_bounding_box = node.getBoundingBox()
            else:
                other_bb = node.getBoundingBox()
                if other_bb is not None:
                    scene_bounding_box = scene_bounding_box + node.getBoundingBox(
                    )

        if not scene_bounding_box:
            scene_bounding_box = AxisAlignedBox.Null

        if repr(self._scene_bounding_box) != repr(scene_bounding_box):
            self._scene_bounding_box = scene_bounding_box
            self.sceneBoundingBoxChanged.emit()

        self._platform_activity = True if count > 0 else False
        self.activityChanged.emit()

    # Remove all selected objects from the scene.
    @pyqtSlot()
    def deleteSelection(self):
        if not self.getController().getToolsEnabled():
            return
        removed_group_nodes = []
        op = GroupedOperation()
        nodes = Selection.getAllSelectedObjects()
        for node in nodes:
            op.addOperation(RemoveSceneNodeOperation(node))
            group_node = node.getParent()
            if group_node and group_node.callDecoration(
                    "isGroup") and group_node not in removed_group_nodes:
                remaining_nodes_in_group = list(
                    set(group_node.getChildren()) - set(nodes))
                if len(remaining_nodes_in_group) == 1:
                    removed_group_nodes.append(group_node)
                    op.addOperation(
                        SetParentOperation(remaining_nodes_in_group[0],
                                           group_node.getParent()))
                    op.addOperation(RemoveSceneNodeOperation(group_node))
        op.push()

    ##  Remove an object from the scene.
    #   Note that this only removes an object if it is selected.
    @pyqtSlot("quint64")
    def deleteObject(self, object_id):
        if not self.getController().getToolsEnabled():
            return

        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  # Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            op = GroupedOperation()
            op.addOperation(RemoveSceneNodeOperation(node))

            group_node = node.getParent()
            if group_node:
                # Note that at this point the node has not yet been deleted
                if len(group_node.getChildren()
                       ) <= 2 and group_node.callDecoration("isGroup"):
                    op.addOperation(
                        SetParentOperation(group_node.getChildren()[0],
                                           group_node.getParent()))
                    op.addOperation(RemoveSceneNodeOperation(group_node))

            op.push()

    ##  Create a number of copies of existing object.
    @pyqtSlot("quint64", int)
    def multiplyObject(self, object_id, count):
        node = self.getController().getScene().findObject(object_id)

        if not node and object_id != 0:  # Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if node:
            current_node = node
            # Find the topmost group
            while current_node.getParent() and current_node.getParent(
            ).callDecoration("isGroup"):
                current_node = current_node.getParent()

            op = GroupedOperation()
            for _ in range(count):
                new_node = copy.deepcopy(current_node)
                op.addOperation(
                    AddSceneNodeOperation(new_node, current_node.getParent()))
            op.push()

    ##  Center object on platform.
    @pyqtSlot("quint64")
    def centerObject(self, object_id):
        node = self.getController().getScene().findObject(object_id)
        if not node and object_id != 0:  # Workaround for tool handles overlapping the selected object
            node = Selection.getSelectedObject(0)

        if not node:
            return

        if node.getParent() and node.getParent().callDecoration("isGroup"):
            node = node.getParent()

        if node:
            op = SetTransformOperation(node, Vector())
            op.push()

    ##  Select all nodes containing mesh data in the scene.
    @pyqtSlot()
    def selectAll(self):
        if not self.getController().getToolsEnabled():
            return

        Selection.clear()
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  # Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
            Selection.add(node)

    ##  Delete all nodes containing mesh data in the scene.
    @pyqtSlot()
    def deleteAll(self):
        Logger.log("i", "Clearing scene")
        if not self.getController().getToolsEnabled():
            return

        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  # Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
            nodes.append(node)
        if nodes:
            op = GroupedOperation()

            for node in nodes:
                op.addOperation(RemoveSceneNodeOperation(node))

            op.push()
            Selection.clear()

    ## Reset all translation on nodes with mesh data.
    @pyqtSlot()
    def resetAllTranslation(self):
        Logger.log("i", "Resetting all scene translations")
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  # Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
            nodes.append(node)

        if nodes:
            op = GroupedOperation()
            for node in nodes:
                # Ensure that the object is above the build platform
                node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
                op.addOperation(
                    SetTransformOperation(
                        node,
                        Vector(
                            0,
                            node.getWorldPosition().y -
                            node.getBoundingBox().bottom, 0)))
            op.push()

    ## Reset all transformations on nodes with mesh data.
    @pyqtSlot()
    def resetAll(self):
        Logger.log("i", "Resetting all scene transformations")
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode:
                continue
            if not node.getMeshData() and not node.callDecoration("isGroup"):
                continue  # Node that doesnt have a mesh and is not a group.
            if node.getParent() and node.getParent().callDecoration("isGroup"):
                continue  # Grouped nodes don't need resetting as their parent (the group) is resetted)
            nodes.append(node)

        if nodes:
            op = GroupedOperation()
            for node in nodes:
                # Ensure that the object is above the build platform
                node.removeDecorator(ZOffsetDecorator.ZOffsetDecorator)
                center_y = 0
                if node.callDecoration("isGroup"):
                    center_y = node.getWorldPosition().y - node.getBoundingBox(
                    ).bottom
                else:
                    center_y = node.getMeshData().getCenterPosition().y
                op.addOperation(
                    SetTransformOperation(node, Vector(0, center_y, 0),
                                          Quaternion(), Vector(1, 1, 1)))
            op.push()

    ##  Reload all mesh data on the screen from file.
    @pyqtSlot()
    def reloadAll(self):
        Logger.log("i", "Reloading all loaded mesh data.")
        nodes = []
        for node in DepthFirstIterator(
                self.getController().getScene().getRoot()):
            if type(node) is not SceneNode or not node.getMeshData():
                continue

            nodes.append(node)

        if not nodes:
            return

        for node in nodes:
            file_name = node.getMeshData().getFileName()
            if file_name:
                job = ReadMeshJob(file_name)
                job._node = node
                job.finished.connect(self._reloadMeshFinished)
                job.start()
            else:
                Logger.log(
                    "w",
                    "Unable to reload data because we don't have a filename.")

    ##  Get logging data of the backend engine
    #   \returns \type{string} Logging data
    @pyqtSlot(result=str)
    def getEngineLog(self):
        log = ""

        for entry in self.getBackend().getLog():
            log += entry.decode()

        return log

    recentFilesChanged = pyqtSignal()

    @pyqtProperty("QVariantList", notify=recentFilesChanged)
    def recentFiles(self):
        return self._recent_files

    @pyqtSlot("QStringList")
    def setExpandedCategories(self, categories):
        categories = list(set(categories))
        categories.sort()
        joined = ";".join(categories)
        if joined != Preferences.getInstance().getValue(
                "cura/categories_expanded"):
            Preferences.getInstance().setValue("cura/categories_expanded",
                                               joined)
            self.expandedCategoriesChanged.emit()

    expandedCategoriesChanged = pyqtSignal()

    @pyqtProperty("QStringList", notify=expandedCategoriesChanged)
    def expandedCategories(self):
        return Preferences.getInstance().getValue(
            "cura/categories_expanded").split(";")

    @pyqtSlot()
    def mergeSelected(self):
        self.groupSelected()
        try:
            group_node = Selection.getAllSelectedObjects()[0]
        except Exception as e:
            Logger.log("d", "mergeSelected: Exception:", e)
            return

        # Compute the center of the objects when their origins are aligned.
        object_centers = [
            node.getBoundingBox().center for node in group_node.getChildren()
        ]
        if object_centers and len(object_centers) > 0:
            middle_x = sum([v.x for v in object_centers]) / len(object_centers)
            middle_y = sum([v.y for v in object_centers]) / len(object_centers)
            middle_z = sum([v.z for v in object_centers]) / len(object_centers)
            offset = Vector(middle_x, middle_y, middle_z)
        else:
            offset = Vector(0, 0, 0)
        # Move each node to the same position.
        for center, node in zip(object_centers, group_node.getChildren()):
            # Align the object and also apply the offset to center it inside the group.
            node.translate(-1 * (center - offset),
                           SceneNode.TransformSpace.World)

        # Use the previously found center of the group bounding box as the new location of the group
        group_node.setPosition(group_node.getBoundingBox().center)

    @pyqtSlot()
    def groupSelected(self):
        # Create a group-node
        group_node = SceneNode()
        group_decorator = GroupDecorator()
        group_node.addDecorator(group_decorator)
        group_node.setParent(self.getController().getScene().getRoot())
        group_node.setSelectable(True)
        center = Selection.getSelectionCenter()
        group_node.setPosition(center)
        group_node.setCenterPosition(center)

        # Move selected nodes into the group-node
        Selection.applyOperation(SetParentOperation, group_node)

        # Deselect individual nodes and select the group-node instead
        for node in group_node.getChildren():
            Selection.remove(node)
        Selection.add(group_node)

    @pyqtSlot()
    def ungroupSelected(self):
        selected_objects = Selection.getAllSelectedObjects().copy()
        for node in selected_objects:
            if node.callDecoration("isGroup"):
                op = GroupedOperation()

                group_parent = node.getParent()
                children = node.getChildren().copy()
                for child in children:
                    # Set the parent of the children to the parent of the group-node
                    op.addOperation(SetParentOperation(child, group_parent))

                    # Add all individual nodes to the selection
                    Selection.add(child)

                op.push()
                # Note: The group removes itself from the scene once all its children have left it,
                # see GroupDecorator._onChildrenChanged

    def _createSplashScreen(self):
        return CuraSplashScreen.CuraSplashScreen()

    def _onActiveMachineChanged(self):
        pass

    fileLoaded = pyqtSignal(str)

    def _onFileLoaded(self, job):
        nodes = job.getResult()
        for node in nodes:
            if node is not None:
                self.fileLoaded.emit(job.getFileName())
                node.setSelectable(True)
                node.setName(os.path.basename(job.getFileName()))
                op = AddSceneNodeOperation(
                    node,
                    self.getController().getScene().getRoot())
                op.push()

                self.getController().getScene().sceneChanged.emit(
                    node)  #Force scene change.

    def _onJobFinished(self, job):
        if type(job) is not ReadMeshJob or not job.getResult():
            return

        f = QUrl.fromLocalFile(job.getFileName())
        if f in self._recent_files:
            self._recent_files.remove(f)

        self._recent_files.insert(0, f)
        if len(self._recent_files) > 10:
            del self._recent_files[10]

        pref = ""
        for path in self._recent_files:
            pref += path.toLocalFile() + ";"

        Preferences.getInstance().setValue("cura/recent_files", pref)
        self.recentFilesChanged.emit()

    def _reloadMeshFinished(self, job):
        # TODO; This needs to be fixed properly. We now make the assumption that we only load a single mesh!
        mesh_data = job.getResult().getMeshData()
        if mesh_data:
            job._node.setMeshData(mesh_data)
        else:
            Logger.log("w", "Could not find a mesh in reloaded node.")

    def _openFile(self, file):
        job = ReadMeshJob(os.path.abspath(file))
        job.finished.connect(self._onFileLoaded)
        job.start()

    def _addProfileReader(self, profile_reader):
        # TODO: Add the profile reader to the list of plug-ins that can be used when importing profiles.
        pass

    def _addProfileWriter(self, profile_writer):
        pass

    @pyqtSlot("QSize")
    def setMinimumWindowSize(self, size):
        self.getMainWindow().setMinimumSize(size)

    def getBuildVolume(self):
        return self._volume

    additionalComponentsChanged = pyqtSignal(str, arguments=["areaId"])

    @pyqtProperty("QVariantMap", notify=additionalComponentsChanged)
    def additionalComponents(self):
        return self._additional_components

    ##  Add a component to a list of components to be reparented to another area in the GUI.
    #   The actual reparenting is done by the area itself.
    #   \param area_id \type{str} Identifying name of the area to which the component should be reparented
    #   \param component \type{QQuickComponent} The component that should be reparented
    @pyqtSlot(str, "QVariant")
    def addAdditionalComponent(self, area_id, component):
        if area_id not in self._additional_components:
            self._additional_components[area_id] = []
        self._additional_components[area_id].append(component)

        self.additionalComponentsChanged.emit(area_id)

    @pyqtSlot(str)
    def log(self, msg):
        Logger.log("d", msg)
class ActionBar(QFrame):
    """
    SIGNALS:
    @changeCurrent(PyQt_PyObject)
    @runFile(QString)
    @reopenTab(QString)
    @recentTabsModified()
    """
    changeCurrent = pyqtSignal('QObject*', int)
    runFile = pyqtSignal(str)
    reopenTab = pyqtSignal(str)
    recentTabsModified = pyqtSignal()
    closeFile = pyqtSignal()  # closeSplit
    editorSplited = pyqtSignal(bool)  # splitEditor, hasSplitEditor
    addToProject = pyqtSignal(str)
    showFileInExplorer = pyqtSignal(str)
    goToSymbol = pyqtSignal(int)
    dockedWidget = pyqtSignal("QObject*")
    # undockedWidget = pyqtSignal()
    undockEditor = pyqtSignal()
    undockThisEditor = pyqtSignal()

    class ORIENTATION:
        Horizontal = 0
        Vertical = 1

    Q_ENUMS(ORIENTATION)

    def __init__(self, combo_editor, main_combo=False):
        super(ActionBar, self).__init__()
        self.setObjectName("actionbar")
        self.__combo_editor = combo_editor

        hbox = QHBoxLayout(self)
        hbox.setContentsMargins(1, 1, 1, 1)
        hbox.setSpacing(1)
        self.setStyleSheet("background-color:rgb(255,0,0)")

        self.lbl_checks = QLabel('')
        self.lbl_checks.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.lbl_checks.setFixedWidth(48)
        self.lbl_checks.setVisible(False)
        hbox.addWidget(self.lbl_checks)

        self.combo = ComboFiles(undocked=not main_combo)
        self.combo.setParentalComboEditor(combo_editor)
        # por aca iria 'self.combo.setContainer()'
        self.combo.setIconSize(QSize(16, 16))
        #model = QStandardItemModel()
        #self.combo.setModel(model)
        #self.combo.view().setDragDropMode(QAbstractItemView.InternalMove)
        self.combo.setMaximumWidth(300)
        self.combo.setObjectName("combotab")
        self.combo.currentIndexChanged[int].connect(self.current_changed)
        self.combo.setToolTip(translations.TR_COMBO_FILE_TOOLTIP)
        self.combo.setContextMenuPolicy(Qt.CustomContextMenu)
        self.combo.customContextMenuRequested['const QPoint &'].connect(
            self._context_menu_requested)
        hbox.addWidget(self.combo)
        #QTimer.singleShot(50000, lambda: print("singleShot", self.combo.showPopup()))

        self.symbols_combo = QComboBox()
        self.symbols_combo.setIconSize(QSize(16, 16))
        self.symbols_combo.setObjectName("combo_symbols")
        self.symbols_combo.activated[int].connect(self.current_symbol_changed)
        hbox.addWidget(self.symbols_combo)

        self.code_navigator = CodeNavigator()
        hbox.addWidget(self.code_navigator)

        self.createContextualMenu()

        self._pos_text = "Line: %d, Col: %d"
        # self.lbl_position = QLabel(self._pos_text % (0, 0))
        # self.lbl_position.setObjectName("position")
        # self.lbl_position.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        # hbox.addWidget(self.lbl_position)
        # hbox.addSpacerItem(QSpacerItem(10,10, QSizePolicy.Expanding))
        hbox.addSpacing(100)

        btn_close = QPushButton(
            self.style().standardIcon(QStyle.SP_DialogCloseButton), '')
        btn_close.setIconSize(QSize(16, 16))
        if main_combo:
            btn_close.setObjectName('navigation_button')
            btn_close.setToolTip(translations.TR_CLOSE_FILE)
            btn_close.clicked['bool'].connect(
                lambda s: self.about_to_close_file())
        else:
            btn_close.setObjectName('close_split')
            btn_close.setToolTip(translations.TR_CLOSE_SPLIT)
            btn_close.clicked['bool'].connect(self.close_split)
        btn_close.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        hbox.addWidget(btn_close)

    @property
    def ParentalComboEditor(self):
        return self.__combo_editor

    def resizeEvent(self, event):
        super(ActionBar, self).resizeEvent(event)
        if event.size().width() < 350:
            self.symbols_combo.hide()
            self.code_navigator.hide()
            # self.lbl_position.hide()
        else:
            self.symbols_combo.show()
            self.code_navigator.show()
            # self.lbl_position.show()

    def add_item(self, text, neditable):
        """Add a new item to the combo and add the neditable data."""
        self.combo.addItem(text, neditable)
        self.combo.setCurrentIndex(self.combo.count() - 1)

    def take_editable(self, index=-1):
        return self.combo.take_editable(index)

    def get_editable(self, index=-1):
        return self.combo.get_editable(index)

    def get_editables(self):
        return self.combo.get_editables()

    # def clearItems(self):
    #     self.combo.clearItems()

    def add_symbols(self, symbols):
        """Add the symbols to the symbols's combo."""
        self.symbols_combo.clear()
        for symbol in symbols:
            data = symbol[1]
            if data[1] == 'f':
                icon = QIcon(":img/function")
            else:
                icon = QIcon(":img/class")
            self.symbols_combo.addItem(icon, data[0])

    def set_current_symbol(self, index):
        self.symbols_combo.setCurrentIndex(index)

    def update_item_icon(self, neditable, icon):
        index = self.combo.findData(neditable)
        self.combo.setItemIcon(index, icon)

    def update_item_text(self, neditable, text):
        index = self.combo.findData(neditable)
        self.combo.setItemText(index, text)

    def current_changed(self, index):
        """Change the current item in the combo."""
        neditable = self.combo.itemData(index)
        self.changeCurrent.emit(neditable, index)

    def current_symbol_changed(self, index):
        """Change the current symbol in the combo."""
        self.goToSymbol.emit(index)

    def update_line_col(self, line, col):
        """Update the line and column position."""
        #self.lbl_position.setText(self._pos_text % (line, col))
        IDE.getInstance().showMessageStatus(self._pos_text % (line, col))

    def createContextualMenu(self):
        menu = QMenu()
        self.menu = menu

        actionAdd = menu.addAction(translations.TR_ADD_TO_PROJECT)
        actionRun = menu.addAction(translations.TR_RUN_FILE)
        menuSyntax = menu.addMenu(translations.TR_CHANGE_SYNTAX)
        self._create_menu_syntax(menuSyntax)
        menu.addSeparator()
        actionClose = menu.addAction(translations.TR_CLOSE_FILE)
        actionCloseAll = menu.addAction(translations.TR_CLOSE_ALL_FILES)
        actionCloseAllNotThis = menu.addAction(
            translations.TR_CLOSE_OTHER_FILES)
        menu.addSeparator()
        actionSplitH = menu.addAction(translations.TR_SPLIT_VERTICALLY)
        actionSplitV = menu.addAction(translations.TR_SPLIT_HORIZONTALLY)
        menu.addSeparator()
        actionCopyPath = menu.addAction(
            translations.TR_COPY_FILE_PATH_TO_CLIPBOARD)
        actionShowFileInExplorer = menu.addAction(
            translations.TR_SHOW_FILE_IN_EXPLORER)
        actionReopen = menu.addAction(translations.TR_REOPEN_FILE)
        actionUndock = menu.addAction(translations.TR_UNDOCK_EDITOR)
        actionUndockThis = menu.addAction("UndockThis")

        if len(settings.LAST_OPENED_FILES) == 0:
            actionReopen.setEnabled(False)
        #Connect actions
        actionSplitH.triggered['bool'].connect(
            lambda s: self._split(self.ORIENTATION.Horizontal))
        actionSplitV.triggered['bool'].connect(
            lambda s: self._split(self.ORIENTATION.Vertical))
        actionRun.triggered['bool'].connect(lambda s: self._run_this_file())
        actionAdd.triggered['bool'].connect(lambda s: self._add_to_project())
        actionClose.triggered['bool'].connect(
            lambda s: self.about_to_close_file())
        actionCloseAllNotThis.triggered['bool'].connect(
            lambda s: self._close_all_files_except_this())
        actionCloseAll.triggered['bool'].connect(
            lambda s: self._close_all_files())
        actionCopyPath.triggered['bool'].connect(
            lambda s: self._copy_file_location())
        actionShowFileInExplorer.triggered['bool'].connect(
            lambda s: self._show_file_in_explorer())
        actionReopen.triggered['bool'].connect(
            lambda s: self._reopen_last_tab())
        actionUndock.triggered['bool'].connect(
            lambda s: self.undockEditor.emit())
        actionUndockThis.triggered['bool'].connect(
            lambda s: self.undockThisEditor.emit())

    def _context_menu_requested(self, point):
        """Display context menu for the combo file."""
        if self.combo.count() == 0:
            # If there is not an Editor opened, don't show the menu
            return

        self.menu.exec_(self.mapToGlobal(point))  #QCursor.pos())

    def _create_menu_syntax(self, menuSyntax):
        """Create Menu with the list of syntax supported."""
        syntax = list(settings.SYNTAX.keys())
        syntax.sort()
        for syn in syntax:
            act = menuSyntax.addAction(syn)
            act.triggered['bool'].connect(lambda s: QMessageBox.critical(
                self, "Beta Info", "Can not construct this segment!")
                                          )  #self._reapply_syntax

    def _reapply_syntax(self, syntaxAction):
        #TODO
        if [self.combo.currentIndex(), syntaxAction] != self._resyntax:
            self._resyntax = [self.combo.currentIndex(), syntaxAction]
            self.syntaxChangedemit(self.currentEditor(), syntaxAction.text())

    def set_current_file(self, neditable):
        index = self.combo.findData(neditable)
        self.combo.setCurrentIndex(index)

    def set_current_by_index(self, index):
        self.combo.setCurrentIndex(index)

    def about_to_close_file(self, index=None):
        """Close the NFile object."""
        print("\n\nabout_to_close_file", self, index)
        if index is None:
            index = self.combo.currentIndex()
        neditable = self.combo.itemData(index)
        if neditable:
            print("\n\nabout_to_close_file:", self.combo.count(), index,
                  self.combo.currentIndex())
            neditable.nfile.close()

    def close_split(self):
        self.closeFile.emit()

    def close_file(self, neditable):
        """Receive the confirmation to close the file."""
        index = self.combo.findData(neditable)
        self.combo.removeItem(index)
        return index

    def _run_this_file(self):
        """Execute the current file."""
        neditable = self.combo.itemData(self.combo.currentIndex())
        self.runFile.emit(neditable.file_path)

    def _add_to_project(self):
        """Emit a signal to let someone handle the inclusion of the file
        inside a project."""
        neditable = self.combo.itemData(self.combo.currentIndex())
        self.addToProject.emit(neditable.file_path)

    def _show_file_in_explorer(self):
        '''Triggered when the "Show File in Explorer" context
        menu action is selected. Emits the "showFileInExplorer(QString)"
        signal with the current file's full path as argument.'''
        neditable = self.combo.itemData(self.combo.currentIndex())
        self.showFileInExplorer.emit(neditable.file_path)

    def _reopen_last_tab(self):
        self.reopenTab.emit(settings.LAST_OPENED_FILES.pop())
        self.recentTabsModified.emit()

    # def _undock_editor(self):
    #     self.undockEditor.emit()

    def _split(self, orientation):
        self.editorSplited.emit(orientation)

    def _copy_file_location(self):
        """Copy the path of the current opened file to the clipboard."""
        neditable = self.combo.itemData(self.combo.currentIndex())
        QApplication.clipboard().setText(neditable.file_path,
                                         QClipboard.Clipboard)

    def _close_all_files(self):
        """Close all the files opened."""
        print("_close_all_files")
        for i in range(self.combo.count()):
            self.about_to_close_file(0)

    def _close_all_files_except_this(self):
        """Close all the files except the current one."""
        neditable = self.combo.itemData(self.combo.currentIndex())
        for i in reversed(list(range(self.combo.count()))):
            ne = self.combo.itemData(i)
            if ne is not neditable:
                self.about_to_close_file(i)