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)
class DurationFormat(QObject): class Format: Seconds = 0 Short = 1 Long = 2 ISO8601 = 3 Q_ENUMS(Format)
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 ""
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)
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()
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
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()
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)
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()
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()
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
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)
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)
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
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)
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()
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)
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
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()
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)
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)
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())
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)
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
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
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())
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()
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)